diff options
520 files changed, 20478 insertions, 5757 deletions
diff --git a/.gitignore b/.gitignore index 8572c8c0b0..3b7680ea1e 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,9 @@ /git-commit-tree /git-config /git-count-objects +/git-credential-cache +/git-credential-cache--daemon +/git-credential-store /git-cvsexportcommit /git-cvsimport /git-cvsserver @@ -167,10 +170,12 @@ /gitweb/static/gitweb.js /gitweb/static/gitweb.min.* /test-chmtime +/test-credential /test-ctype /test-date /test-delta /test-dump-cache-tree +/test-scrap-cache-tree /test-genrandom /test-index-version /test-line-buffer diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index fe1c1e5bc2..483008699f 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -81,6 +81,10 @@ For shell scripts specifically (not exhaustive): are ERE elements not BRE (note that \? and \+ are not even part of BRE -- making them accessible from BRE is a GNU extension). + - Use Git's gettext wrappers in git-sh-i18n to make the user + interface translatable. See "Marking strings for translation" in + po/README. + For C programs: - We use tabs to indent, and interpret tabs as taking up to @@ -144,6 +148,9 @@ For C programs: - When we pass <string, length> pair to functions, we should try to pass them in that order. + - Use Git's gettext wrappers to make the user interface + translatable. See "Marking strings for translation" in po/README. + Writing Documentation: Every user-visible change should be reflected in the documentation. diff --git a/Documentation/Makefile b/Documentation/Makefile index 6346a75dda..116f17587e 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -1,12 +1,13 @@ MAN1_TXT= \ $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \ $(wildcard git-*.txt)) \ - gitk.txt git.txt + gitk.txt gitweb.txt git.txt MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \ - gitrepository-layout.txt + gitrepository-layout.txt gitweb.conf.txt MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \ gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \ gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt +MAN7_TXT += gitcredentials.txt MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT) MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT)) @@ -46,8 +47,8 @@ MANPAGE_XSL = manpage-normal.xsl XMLTO_EXTRA = INSTALL?=install RM ?= rm -f -DOC_REF = origin/man -HTML_REF = origin/html +MAN_REPO = ../../git-manpages +HTML_REPO = ../../git-htmldocs infodir?=$(prefix)/share/info MAKEINFO=makeinfo @@ -327,12 +328,23 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt install-webdoc : html '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST) +# You must have a clone of git-htmldocs and git-manpages repositories +# next to the git repository itself for the following to work. + quick-install: quick-install-man -quick-install-man: - '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir) +require-manrepo:: + @if test ! -d $(MAN_REPO); \ + then echo "git-manpages repository must exist at $(MAN_REPO)"; exit 1; fi + +quick-install-man: require-manrepo + '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir) + +require-htmlrepo:: + @if test ! -d $(HTML_REPO); \ + then echo "git-htmldocs repository must exist at $(HTML_REPO)"; exit 1; fi -quick-install-html: - '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir) +quick-install-html: require-htmlrepo + '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir) .PHONY: FORCE diff --git a/Documentation/RelNotes/1.7.6.5.txt b/Documentation/RelNotes/1.7.6.5.txt new file mode 100644 index 0000000000..6713132a9e --- /dev/null +++ b/Documentation/RelNotes/1.7.6.5.txt @@ -0,0 +1,26 @@ +Git v1.7.6.5 Release Notes +========================== + +Fixes since v1.7.6.4 +-------------------- + + * The date parser did not accept timezone designators that lack minutes + part and also has a colon between "hh:mm". + + * After fetching from a remote that has very long refname, the reporting + output could have corrupted by overrunning a static buffer. + + * "git mergetool" did not use its arguments as pathspec, but as a path to + the file that may not even have any conflict. + + * "git name-rev --all" tried to name all _objects_, naturally failing to + describe many blobs and trees, instead of showing only commits as + advertised in its documentation. + + * "git remote rename $a $b" were not careful to match the remote name + against $a (i.e. source side of the remote nickname). + + * "gitweb" used to produce a non-working link while showing the contents + of a blob, when JavaScript actions are enabled. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.7.1.txt b/Documentation/RelNotes/1.7.7.1.txt new file mode 100644 index 0000000000..ac9b838e25 --- /dev/null +++ b/Documentation/RelNotes/1.7.7.1.txt @@ -0,0 +1,60 @@ +Git v1.7.7.1 Release Notes +========================== + +Fixes since v1.7.7 +------------------ + + * On some BSD systems, adding +s bit on directories is detrimental + (it is not necessary on BSD to begin with). "git init --shared" + has been updated to take this into account without extra makefile + settings on platforms the Makefile knows about. + + * After incorrectly written third-party tools store a tag object in + HEAD, git diagnosed it as a repository corruption and refused to + proceed in order to avoid spreading the damage. We now gracefully + recover from such a situation by pretending as if the commit that + is pointed at by the tag were in HEAD. + + * "git apply --whitespace=error" did not bother to report the exact + line number in the patch that introduced new blank lines at the end + of the file. + + * "git apply --index" did not check corrupted patch. + + * "git checkout $tree $directory/" resurrected paths locally removed or + modified only in the working tree in $directory/ that did not appear + in $directory of the given $tree. They should have been kept intact. + + * "git diff $tree $path" used to apply the pathspec at the output stage, + reading the whole tree, wasting resources. + + * The code to check for updated submodules during a "git fetch" of the + superproject had an unnecessary quadratic loop. + + * "git fetch" from a large bundle did not enable the progress output. + + * When "git fsck --lost-and-found" found that an empty blob object in the + object store is unreachable, it incorrectly reported an error after + writing the lost blob out successfully. + + * "git filter-branch" did not refresh the index before checking that the + working tree was clean. + + * "git grep $tree" when run with multiple threads had an unsafe access to + the object database that should have been protected with mutex. + + * The "--ancestry-path" option to "git log" and friends misbehaved in a + history with complex criss-cross merges and showed an uninteresting + side history as well. + + * Test t1304 assumed LOGNAME is always set, which may not be true on + some systems. + + * Tests with --valgrind failed to find "mergetool" scriptlets. + + * "git patch-id" miscomputed the patch-id in a patch that has a line longer + than 1kB. + + * When an "exec" insn failed after modifying the index and/or the working + tree during "rebase -i", we now check and warn that the changes need to + be cleaned up. diff --git a/Documentation/RelNotes/1.7.7.2.txt b/Documentation/RelNotes/1.7.7.2.txt new file mode 100644 index 0000000000..e6bbef2f01 --- /dev/null +++ b/Documentation/RelNotes/1.7.7.2.txt @@ -0,0 +1,44 @@ +Git v1.7.7.2 Release Notes +========================== + +Fixes since v1.7.7.1 +-------------------- + + * We used to drop error messages from libcurl on certain kinds of + errors. + + * Error report from smart HTTP transport, when the connection was + broken in the middle of a transfer, showed a useless message on + a corrupt packet. + + * "git fetch --prune" was unsafe when used with refspecs from the + command line. + + * The attribute mechanism did not use case insensitive match when + core.ignorecase was set. + + * "git bisect" did not notice when it failed to update the working tree + to the next commit to be tested. + + * "git config --bool --get-regexp" failed to separate the variable name + and its value "true" when the variable is defined without "= true". + + * "git remote rename $a $b" were not careful to match the remote name + against $a (i.e. source side of the remote nickname). + + * "git mergetool" did not use its arguments as pathspec, but as a path to + the file that may not even have any conflict. + + * "git diff --[num]stat" used to use the number of lines of context + different from the default, potentially giving different results from + "git diff | diffstat" and confusing the users. + + * "git pull" and "git rebase" did not work well even when GIT_WORK_TREE is + set correctly with GIT_DIR if the current directory is outside the working + tree. + + * "git send-email" did not honor the configured hostname when restarting + the HELO/EHLO exchange after switching TLS on. + + * "gitweb" used to produce a non-working link while showing the contents + of a blob, when JavaScript actions are enabled. diff --git a/Documentation/RelNotes/1.7.7.3.txt b/Documentation/RelNotes/1.7.7.3.txt new file mode 100644 index 0000000000..09301f0957 --- /dev/null +++ b/Documentation/RelNotes/1.7.7.3.txt @@ -0,0 +1,19 @@ +Git v1.7.7.3 Release Notes +========================== + +Fixes since v1.7.7.2 +-------------------- + + * Adjust the "quick-install-doc" procedures as preformatted + html/manpage are no longer in the source repository. + + * The logic to optimize the locality of the data in a pack introduced in + 1.7.7 was grossly inefficient. + + * The logic to filter out forked projects in the project list in + "gitweb" was broken for some time. + + * "git branch -m/-M" advertised to update RENAME_REF ref in the + commit log message that introduced the feature but not anywhere in + the documentation, and never did update such a ref anyway. This + undocumented misfeature that did not exist has been excised. diff --git a/Documentation/RelNotes/1.7.7.4.txt b/Documentation/RelNotes/1.7.7.4.txt new file mode 100644 index 0000000000..e5234485e7 --- /dev/null +++ b/Documentation/RelNotes/1.7.7.4.txt @@ -0,0 +1,14 @@ +Git v1.7.7.4 Release Notes +========================== + +Fixes since v1.7.7.3 +-------------------- + + * A few header dependencies were missing from the Makefile. + + * Some newer parts of the code used C99 __VA_ARGS__ while we still + try to cater to older compilers. + + * "git name-rev --all" tried to name all _objects_, naturally failing to + describe many blobs and trees, instead of showing only commits as + advertised in its documentation. diff --git a/Documentation/RelNotes/1.7.7.5.txt b/Documentation/RelNotes/1.7.7.5.txt new file mode 100644 index 0000000000..7b0931987b --- /dev/null +++ b/Documentation/RelNotes/1.7.7.5.txt @@ -0,0 +1,14 @@ +Git v1.7.7.5 Release Notes +========================== + +Fixes since v1.7.7.4 +-------------------- + + * After fetching from a remote that has very long refname, the reporting + output could have corrupted by overrunning a static buffer. + + * "git checkout" and "git merge" treated in-tree .gitignore and exclude + file in $GIT_DIR/info/ directory inconsistently when deciding which + untracked files are ignored and expendable. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.8.1.txt b/Documentation/RelNotes/1.7.8.1.txt new file mode 100644 index 0000000000..33dc948b94 --- /dev/null +++ b/Documentation/RelNotes/1.7.8.1.txt @@ -0,0 +1,38 @@ +Git v1.7.8.1 Release Notes +========================== + +Fixes since v1.7.8 +------------------ + + * In some codepaths (notably, checkout and merge), the ignore patterns + recorded in $GIT_DIR/info/exclude were not honored. They now are. + + * "git apply --check" did not error out when given an empty input + without any patch. + + * "git archive" mistakenly allowed remote clients to ask for commits + that are not at the tip of any ref. + + * "git checkout" and "git merge" treated in-tree .gitignore and exclude + file in $GIT_DIR/info/ directory inconsistently when deciding which + untracked files are ignored and expendable. + + * LF-to-CRLF streaming filter used when checking out a large-ish blob + fell into an infinite loop with a rare input. + + * The function header pattern for files with "diff=cpp" attribute did + not consider "type *funcname(type param1,..." as the beginning of a + function. + + * The error message from "git diff" and "git status" when they fail + to inspect changes in submodules did not report which submodule they + had trouble with. + + * After fetching from a remote that has very long refname, the reporting + output could have corrupted by overrunning a static buffer. + + * "git pack-objects" avoids creating cyclic dependencies among deltas + when seeing a broken packfile that records the same object in both + the deflated form and as a delta. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.8.txt b/Documentation/RelNotes/1.7.8.txt new file mode 100644 index 0000000000..b4d90bba0f --- /dev/null +++ b/Documentation/RelNotes/1.7.8.txt @@ -0,0 +1,161 @@ +Git v1.7.8 Release Notes +======================== + +Updates since v1.7.7 +-------------------- + + * Some git-svn, git-gui, git-p4 (in contrib) and msysgit updates. + + * Updates to bash completion scripts. + + * The build procedure has been taught to take advantage of computed + dependency automatically when the complier supports it. + + * The date parser now accepts timezone designators that lack minutes + part and also has a colon between "hh:mm". + + * The contents of the /etc/mailname file, if exists, is used as the + default value of the hostname part of the committer/author e-mail. + + * "git am" learned how to read from patches generated by Hg. + + * "git archive" talking with a remote repository can report errors + from the remote side in a more informative way. + + * "git branch" learned an explicit --list option to ask for branches + listed, optionally with a glob matching pattern to limit its output. + + * "git check-attr" learned "--cached" option to look at .gitattributes + files from the index, not from the working tree. + + * Variants of "git cherry-pick" and "git revert" that take multiple + commits learned to "--continue" and "--abort". + + * "git daemon" gives more human readble error messages to clients + using ERR packets when appropriate. + + * Errors at the network layer is logged by "git daemon". + + * "git diff" learned "--minimal" option to spend extra cycles to come + up with a minimal patch output. + + * "git diff" learned "--function-context" option to show the whole + function as context that was affected by a change. + + * "git difftool" can be told to skip launching the tool for a path by + answering 'n' to its prompt. + + * "git fetch" learned to honor transfer.fsckobjects configuration to + validate the objects that were received from the other end, just like + "git receive-pack" (the receiving end of "git push") does. + + * "git fetch" makes sure that the set of objects it received from the + other end actually completes the history before updating the refs. + "git receive-pack" (the receiving end of "git push") learned to do the + same. + + * "git fetch" learned that fetching/cloning from a regular file on the + filesystem is not necessarily a request to unpack a bundle file; the + file could be ".git" with "gitdir: <path>" in it. + + * "git for-each-ref" learned "%(contents:subject)", "%(contents:body)" + and "%(contents:signature)". The last one is useful for signed tags. + + * "git grep" used to incorrectly pay attention to .gitignore files + scattered in the directory it was working in even when "--no-index" + option was used. It no longer does this. The "--exclude-standard" + option needs to be given to explicitly activate the ignore + mechanism. + + * "git grep" learned "--untracked" option, where given patterns are + searched in untracked (but not ignored) files as well as tracked + files in the working tree, so that matches in new but not yet + added files do not get missed. + + * The recursive merge backend no longer looks for meaningless + existing merges in submodules unless in the outermost merge. + + * "git log" and friends learned "--children" option. + + * "git ls-remote" learned to respond to "-h"(elp) requests. + + * "mediawiki" remote helper can interact with (surprise!) MediaWiki + with "git fetch" & "git push". + + * "git merge" learned the "--edit" option to allow users to edit the + merge commit log message. + + * "git rebase -i" can be told to use special purpose editor suitable + only for its insn sheet via sequence.editor configuration variable. + + * "git send-email" learned to respond to "-h"(elp) requests. + + * "git send-email" allows the value given to sendemail.aliasfile to begin + with "~/" to refer to the $HOME directory. + + * "git send-email" forces use of Authen::SASL::Perl to work around + issues between Authen::SASL::Cyrus and AUTH PLAIN/LOGIN. + + * "git stash" learned "--include-untracked" option to stash away + untracked/ignored cruft from the working tree. + + * "git submodule clone" does not leak an error message to the UI + level unnecessarily anymore. + + * "git submodule update" learned to honor "none" as the value for + submodule.<name>.update to specify that the named submodule should + not be checked out by default. + + * When populating a new submodule directory with "git submodule init", + the $GIT_DIR metainformation directory for submodules is created inside + $GIT_DIR/modules/<name>/ directory of the superproject and referenced + via the gitfile mechanism. This is to make it possible to switch + between commits in the superproject that has and does not have the + submodule in the tree without re-cloning. + + * "gitweb" leaked unescaped control characters from syntax hiliter + outputs. + + * "gitweb" can be told to give custom string at the end of the HTML + HEAD element. + + * "gitweb" now has its own manual pages. + + +Also contains other documentation updates and minor code cleanups. + + +Fixes since v1.7.7 +------------------ + +Unless otherwise noted, all fixes in the 1.7.7.X maintenance track are +included in this release. + + * HTTP transport did not use pushurl correctly, and also did not tell + what host it is trying to authenticate with when asking for + credentials. + (merge deba493 jk/http-auth later to maint). + + * "git blame" was aborted if started from an uncommitted content and + the path had the textconv filter in effect. + (merge 8518088 ss/blame-textconv-fake-working-tree later to maint). + + * Adding many refs to the local repository in one go (e.g. "git fetch" + that fetches many tags) and looking up a ref by name in a repository + with too many refs were unnecessarily slow. + (merge 17d68a54d jp/get-ref-dir-unsorted later to maint). + + * Report from "git commit" on untracked files was confused under + core.ignorecase option. + (merge 395c7356 jk/name-hash-dirent later to maint). + + * "git merge" did not understand ":/<pattern>" as a way to name a commit. + + " "git push" on the receiving end used to call post-receive and post-update + hooks for attempted removal of non-existing refs. + (merge 160b81ed ph/push-to-delete-nothing later to maint). + + * Help text for "git remote set-url" and "git remote set-branches" + were misspelled. + (merge c49904e fc/remote-seturl-usage-fix later to maint). + (merge 656cdf0 jc/remote-setbranches-usage-fix later to maint). diff --git a/Documentation/RelNotes/1.7.9.txt b/Documentation/RelNotes/1.7.9.txt new file mode 100644 index 0000000000..70ff060231 --- /dev/null +++ b/Documentation/RelNotes/1.7.9.txt @@ -0,0 +1,160 @@ +Git v1.7.9 Release Notes (draft) +======================== + +Updates since v1.7.8 +-------------------- + + * gitk updates accumulated since early 2011. + + * git-gui updated to 0.16.0. + + * git-p4 (in contrib/) updates. + + * i18n effort is going forward and Git uses localized messages if + available. + + * Porcelain commands like "git reset" did not distinguish deletions + and type-changes from ordinary modification, and reported them with + the same 'M' moniker. They now use 'D' (for deletion) and 'T' (for + type-change) to match "git status -s" and "git diff --name-status". + + * The code to handle username/password for HTTP transaction used in + "git push" & "git fetch" learned to talk "credential API" to + external programs to cache or store them, to allow integration with + platform native keychain mechanisms. + + * "git commit" and "git reset" re-learned the optimization to prime + the cache-tree information in the index, which makes it faster to + write a tree object out after the index entries are updated. + + * "git add" learned to stream large files directly into a packfile + instead of writing them into individual loose object files. + + * "git branch -m <current branch> HEAD" is an obvious no-op and is + now allowed. + + * "git checkout -B <current branch> <elsewhere>" is a more intuitive + way to spell "git reset --keep <elsewhere>". + + * "git checkout" and "git merge" learned "--no-overwrite-ignore" option + to tell Git that untracked and ignored files are not expendable. + + * "git commit --amend" learned "--no-edit" option to say that the + user is amending the tree being recorded, without updating the + commit log message. + + * fsck and prune are relatively lengthy operations that still go + silent while making the end-user wait. They learned to give progress + output like other slow operations. + + * The set of built-in function-header patterns for various languages + knows MATLAB. + + * "git pull" can be used to fetch and merge an annotated/signed tag, + instead of the tip of a topic branch. The GPG signature from the + signed tag is recorded in the resulting merge commit for later + auditing. + + * "git branch --edit-description" can be used to add descriptive text + to explain what a topic branch is about. + + * "git fmt-merge-msg" learned to take the branch description into + account when preparing a merge summary that "git merge" records + when merging a local branch. + + * "git request-pull" has been updated to convey more information + useful for integrators to decide if a topic is worth merging and + what is pulled is indeed what the requestor asked to pull, + including: + + - the tip of the branch being requested to be merged; + - the branch description describing what the topic is about; + - the contents of the annotated tag, when requesting to pull a tag. + + * "git pull" learned to notice 'pull.rebase' configuration variable, + which serves as a global fallback for setting 'branch.<name>.rebase' + configuration variable per branch. + + * "git tag" learned "--cleanup" option to control how the whitespaces + and empty lines in tag message are cleaned up. + + * "gitweb" learned to show side-by-side diff. + +Also contains minor documentation updates and code clean-ups. + + +Fixes since v1.7.8 +------------------ + + * The function header pattern for files with "diff=cpp" attribute did + not consider "type *funcname(type param1,..." as the beginning of a + function. + (merge 37e7793 tr/userdiff-c-returns-pointer later to maint). + + * The replacement implemention for snprintf used on platforms with + native snprintf that is broken did not use va_copy correctly. + (merge a9bfbc5 jk/maint-snprintf-va-copy later to maint). + + * LF-to-CRLF streaming filter used when checking out a large-ish blob + fell into an infinite loop with a rare input. + (merge 284e3d2 cn/maint-lf-to-crlf-filter later to maint). + + * git native connection going over TCP (not over SSH) did not set + SO_KEEPALIVE option which failed to receive link layer errors. + (merge e47a858 ew/keepalive later to maint). + + * "git archive" mistakenly allowed remote clients to ask for commits + that are not at the tip of any ref. + (merge 7b51c33 jk/maint-upload-archive later to maint). + + * "git apply --check" did not error out when given an empty input + without any patch. + (merge cc64b31 bc/maint-apply-check-no-patch later to maint). + + * "git checkout -m" did not recreate the conflicted state in a "both + sides added, without any common ancestor version" conflict + situation. + (merge 335c6e4 jc/checkout-m-twoway later to maint). + + * "git cherry-pick $commit" (not a range) created an unnecessary + sequencer state and interfered with valid workflow to use the + command during a session to cherry-pick multiple commits. + (merge d596118 jn/maint-sequencer-fixes later to maint). + + * The error message from "git diff" and "git status" when they fail + to inspect changes in submodules did not report which submodule they + had trouble with. + (merge 6a5ceda jl/submodule-status-failure-report later to maint). + + * "fast-import" did not correctly update an existing notes tree, + possibly corrupting the fan-out. + + * "git fetch-pack" accepted unqualified refs that do not begin with + refs/ by mistake and compensated it by matching the refspec with + tail-match, which was doubly wrong. This broke fetching from a + repository with a funny named ref "refs/foo/refs/heads/master" and a + 'master' branch with "git fetch-pack refs/heads/master", as the + command incorrectly considered the former a "match". + (merge bab8d28 jk/fetch-no-tail-match-refs later to maint). + + * "git mv" gave suboptimal error/warning messages when it overwrites + target files. It also did not pay attention to "-v" option. + (merge 534376c jk/maint-mv later to maint). + + * When a "reword" action in "git rebase -i" failed to run "commit --amend", + we did not give the control back to the user to resolve the situation, and + instead kept the original commit log message. + (merge 0becb3e aw/rebase-i-stop-on-failure-to-amend later to maint). + + * Authenticated "git push" over dumb HTTP were broken with a recent + change and failed without asking for password when username is + given. + (merge a4ddbc3 jk/maint-push-over-dav later to maint). + +-- +exec >/var/tmp/1 +O=v1.7.8-351-g2dccad3 +echo O=$(git describe master) +git log --first-parent --oneline --reverse ^$O master +echo +git shortlog --no-merges ^$O ^maint master diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index e76195ac97..d4a51da464 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -117,5 +117,4 @@ commit. And the default value is 40. If there are more than one take effect. -h:: ---help:: Show help message. diff --git a/Documentation/config.txt b/Documentation/config.txt index 0658ffb889..6e63b5938f 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -45,9 +45,10 @@ 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 a case insensitive alternative `[section.subsection]` syntax. -In this syntax, subsection names follow the same restrictions as for section -names. +There is also a deprecated `[section.subsection]` syntax. With this +syntax, the subsection name is converted to lower-case and is also +compared case sensitively. These subsection names follow the same +restrictions as section names. All the other lines (and the remainder of the line after the section header) are recognized as setting variables, in the form @@ -114,40 +115,37 @@ in the appropriate manual page. You will find a description of non-core porcelain configuration variables in the respective porcelain documentation. advice.*:: - When set to 'true', display the given optional help message. - When set to 'false', do not display. The configuration variables - are: + These variables control various optional help messages designed to + aid new users. All 'advice.*' variables default to 'true', and you + can tell Git that you do not need help by setting these to 'false': + -- pushNonFastForward:: Advice shown when linkgit:git-push[1] refuses - non-fast-forward refs. Default: true. + non-fast-forward refs. statusHints:: Directions on how to stage/unstage/add shown in the output of linkgit:git-status[1] and the template shown - when writing commit messages. Default: true. + when writing commit messages. commitBeforeMerge:: Advice shown when linkgit:git-merge[1] refuses to merge to avoid overwriting local changes. - Default: true. resolveConflict:: Advices shown by various commands when conflicts prevent the operation from being performed. - Default: true. implicitIdentity:: Advice on how to set your identity configuration when your information is guessed from the system username and - domain name. Default: true. - + domain name. detachedHead:: - Advice shown when you used linkgit::git-checkout[1] to + Advice shown when you used linkgit:git-checkout[1] to move to the detach HEAD state, to instruct how to create - a local branch after the fact. Default: true. + a local branch after the fact. -- core.fileMode:: If false, the executable bit differences between the index and - the working copy are ignored; useful on broken filesystems like FAT. + the working tree are ignored; useful on broken filesystems like FAT. See linkgit:git-update-index[1]. + The default is true, except linkgit:git-clone[1] or linkgit:git-init[1] @@ -179,7 +177,7 @@ is created. core.trustctime:: If false, the ctime differences between the index and the - working copy are ignored; useful when the inode change time + working tree are ignored; useful when the inode change time is regularly modified by something outside Git (file system crawlers and some backup systems). See linkgit:git-update-index[1]. True by default. @@ -292,7 +290,7 @@ core.ignoreStat:: If true, commands which modify both the working tree and the index will mark the updated paths with the "assume unchanged" bit in the index. These marked files are then assumed to stay unchanged in the - working copy, until you mark them otherwise manually - Git will not + working tree, until you mark them otherwise manually - Git will not detect the file changes by lstat() calls. This is useful on systems where those are very slow, such as Microsoft Windows. See linkgit:git-update-index[1]. @@ -473,6 +471,12 @@ core.editor:: variable when it is set, and the environment variable `GIT_EDITOR` is not set. See linkgit:git-var[1]. +sequence.editor:: + Text editor used by `git rebase -i` for editing the rebase insn file. + The value is meant to be interpreted by the shell when it is used. + It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable. + When not configured the default commit message editor is used instead. + core.pager:: The command that git will use to paginate output. Can be overridden with the `GIT_PAGER` environment @@ -670,10 +674,12 @@ branch.<name>.mergeoptions:: branch.<name>.rebase:: When true, rebase the branch <name> on top of the fetched branch, instead of merging the default branch from the default remote when - "git pull" is run. - *NOTE*: this is a possibly dangerous operation; do *not* use - it unless you understand the implications (see linkgit:git-rebase[1] - for details). + "git pull" is run. See "pull.rebase" for doing this in a non + branch-specific manner. ++ +*NOTE*: this is a possibly dangerous operation; do *not* use +it unless you understand the implications (see linkgit:git-rebase[1] +for details). browser.<tool>.cmd:: Specify the command to invoke the specified browser. The @@ -825,6 +831,29 @@ commit.template:: "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the specified user's home directory. +credential.helper:: + Specify an external helper to be called when a username or + password credential is needed; the helper may consult external + storage to avoid prompting the user for the credentials. See + linkgit:gitcredentials[7] for details. + +credential.useHttpPath:: + When acquiring credentials, consider the "path" component of an http + or https URL to be important. Defaults to false. See + linkgit:gitcredentials[7] for more information. + +credential.username:: + If no username is set for a network authentication, use this username + by default. See credential.<context>.* below, and + linkgit:gitcredentials[7]. + +credential.<url>.*:: + Any of the credential.* options above can be applied selectively to + some credentials. For example "credential.https://example.com.username" + would set the default username only for https connections to + example.com. See linkgit:gitcredentials[7] for details on how URLs are + matched. + include::diff-config.txt[] difftool.<tool>.path:: @@ -857,6 +886,13 @@ fetch.recurseSubmodules:: when its superproject retrieves a commit that updates the submodule's reference. +fetch.fsckObjects:: + If it is set to true, git-fetch-pack will check all fetched + objects. It will abort in the case of a malformed object or a + broken link. The result of an abort are only dangling objects. + Defaults to false. If not set, the value of `transfer.fsckObjects` + is used instead. + fetch.unpackLimit:: If the number of objects fetched over the git native transfer is below this @@ -1064,6 +1100,23 @@ All gitcvs variables except for 'gitcvs.usecrlfattr' and is one of "ext" and "pserver") to make them apply only for the given access method. +gitweb.category:: +gitweb.description:: +gitweb.owner:: +gitweb.url:: + See linkgit:gitweb[1] for description. + +gitweb.avatar:: +gitweb.blame:: +gitweb.grep:: +gitweb.highlight:: +gitweb.patches:: +gitweb.pickaxe:: +gitweb.remote_heads:: +gitweb.showsizes:: +gitweb.snapshot:: + See linkgit:gitweb.conf[5] for description. + grep.lineNumber:: If set to true, enable '-n' option by default. @@ -1453,7 +1506,8 @@ notes.rewriteRef:: You may also specify this configuration several times. + Does not have a default value; you must configure this variable to -enable note rewriting. +enable note rewriting. Set it to `refs/notes/commits` to enable +rewriting for the default commit notes. + This setting can be overridden with the `GIT_NOTES_REWRITE_REF` environment variable, which must be a colon separated list of refs or @@ -1558,6 +1612,16 @@ pretty.<name>:: Note that an alias with the same name as a built-in format will be silently ignored. +pull.rebase:: + When true, rebase branches on top of the fetched branch, instead + of merging the default branch from the default remote when "git + pull" is run. See "branch.<name>.rebase" for setting this on a + per-branch basis. ++ +*NOTE*: this is a possibly dangerous operation; do *not* use +it unless you understand the implications (see linkgit:git-rebase[1] +for details). + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. @@ -1595,7 +1659,8 @@ 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 broken link. The result of an abort are only dangling objects. - Defaults to false. + Defaults to false. If not set, the value of `transfer.fsckObjects` + is used instead. receive.unpackLimit:: If the number of objects received in a push is below this @@ -1830,6 +1895,11 @@ tar.umask:: archiving user's umask will be used instead. See umask(2) and linkgit:git-archive[1]. +transfer.fsckObjects:: + When `fetch.fsckObjects` or `receive.fsckObjects` are + not set, the value of this variable is used instead. + Defaults to false. + transfer.unpackLimit:: When `fetch.unpackLimit` or `receive.unpackLimit` are not set, the value of this variable is used instead. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index b620b3afec..9f7cba2be6 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -45,6 +45,10 @@ ifndef::git-format-patch[] Synonym for `-p --raw`. endif::git-format-patch[] +--minimal:: + Spend extra time to make sure the smallest possible + diff is produced. + --patience:: Generate a diff using the "patience diff" algorithm. @@ -404,7 +408,12 @@ endif::git-format-patch[] Show the context between diff hunks, up to the specified number of lines, thereby fusing hunks that are close to each other. +-W:: +--function-context:: + Show whole surrounding functions of changes. + ifndef::git-format-patch[] +ifndef::git-log[] --exit-code:: Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and @@ -412,6 +421,7 @@ ifndef::git-format-patch[] --quiet:: Disable all output of the program. Implies `--exit-code`. +endif::git-log[] endif::git-format-patch[] --ext-diff:: diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 507b8d0ab2..0427e80a35 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -9,18 +9,23 @@ SYNOPSIS -------- [verse] 'git branch' [--color[=<when>] | --no-color] [-r | -a] - [-v [--abbrev=<length> | --no-abbrev]] - [(--merged | --no-merged | --contains) [<commit>]] + [--list] [-v [--abbrev=<length> | --no-abbrev]] + [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>] 'git branch' (-m | -M) [<oldbranch>] <newbranch> 'git branch' (-d | -D) [-r] <branchname>... +'git branch' --edit-description [<branchname>] DESCRIPTION ----------- With no arguments, existing branches are listed and the current branch will be highlighted with an asterisk. Option `-r` causes the remote-tracking -branches to be listed, and option `-a` shows both. +branches to be listed, and option `-a` shows both. This list mode is also +activated by the `--list` option (see below). +<pattern> restricts the output to matching branches, the pattern is a shell +wildcard (i.e., matched using fnmatch(3)) +Multiple patterns may be given; if any of them matches, the tag is shown. With `--contains`, shows only the branches that contain the named commit (in other words, the branches whose tip commits are descendants of the @@ -64,6 +69,7 @@ way to clean up all obsolete remote-tracking branches. OPTIONS ------- -d:: +--delete:: Delete a branch. The branch must be fully merged in its upstream branch, or in `HEAD` if no upstream was set with `--track` or `--set-upstream`. @@ -72,6 +78,7 @@ OPTIONS Delete a branch irrespective of its merged status. -l:: +--create-reflog:: Create the 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}". @@ -84,6 +91,7 @@ OPTIONS already. Without `-f` 'git branch' refuses to change an existing branch. -m:: +--move:: Move/rename a branch and the corresponding reflog. -M:: @@ -100,14 +108,21 @@ OPTIONS Same as `--color=never`. -r:: +--remotes:: List or delete (if used with -d) the remote-tracking branches. -a:: +--all:: List both remote-tracking branches and local branches. +--list:: + Activate the list mode. `git branch <pattern>` would try to create a branch, + use `git branch --list <pattern>` to list matching branches. + -v:: --verbose:: - Show sha1 and commit subject line for each head, along with + When in list mode, + 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. @@ -144,6 +159,10 @@ start-point is either a local or remote-tracking branch. like '--track' would when creating the branch, except that where branch points to is not changed. +--edit-description:: + Open an editor and edit the text to explain what the branch is + for, to be used by various other commands (e.g. `request-pull`). + --contains <commit>:: Only list branches which contain the specified commit. diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt index 1f7312a189..5abdbaa51c 100644 --- a/Documentation/git-check-attr.txt +++ b/Documentation/git-check-attr.txt @@ -24,6 +24,9 @@ OPTIONS paths. If this option is used, then 'unspecified' attributes will not be included in the output. +--cached:: + Consider `.gitattributes` in the index only, ignoring the working tree. + --stdin:: Read file names from stdin instead of from the command-line. diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index c9fdf84a08..103e7b128d 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -8,8 +8,9 @@ git-check-ref-format - Ensures that a reference name is well formed SYNOPSIS -------- [verse] -'git check-ref-format' <refname> -'git check-ref-format' --print <refname> +'git check-ref-format' [--normalize] + [--[no-]allow-onelevel] [--refspec-pattern] + <refname> 'git check-ref-format' --branch <branchname-shorthand> DESCRIPTION @@ -28,22 +29,28 @@ git imposes the following rules on how references are named: . They can include slash `/` for hierarchical (directory) grouping, but no slash-separated component can begin with a - dot `.`. + dot `.` or end with the sequence `.lock`. . They must contain at least one `/`. This enforces the presence of a category like `heads/`, `tags/` etc. but the actual names are not - restricted. + restricted. If the `--allow-onelevel` option is used, this rule + is waived. . They cannot have two consecutive dots `..` anywhere. . They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 `DEL`), space, tilde `~`, - caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`, - or open bracket `[` anywhere. + caret `{caret}`, or colon `:` anywhere. -. They cannot end with a slash `/` nor a dot `.`. +. They cannot have question-mark `?`, asterisk `{asterisk}`, or open + bracket `[` anywhere. See the `--refspec-pattern` option below for + an exception to this rule. -. They cannot end with the sequence `.lock`. +. They cannot begin or end with a slash `/` or contain multiple + consecutive slashes (see the `--normalize` option below for an + exception to this rule) + +. They cannot end with a dot `.`. . They cannot contain a sequence `@{`. @@ -68,16 +75,36 @@ reference name expressions (see linkgit:gitrevisions[7]): . at-open-brace `@{` is used as a notation to access a reflog entry. -With the `--print` option, if 'refname' is acceptable, it prints the -canonicalized name of a hypothetical reference with that name. That is, -it prints 'refname' with any extra `/` characters removed. - With the `--branch` option, it expands the ``previous branch syntax'' `@{-n}`. For example, `@{-1}` is a way to refer the last branch you were on. This option should be used by porcelains to accept this syntax anywhere a branch name is expected, so they can act as if you typed the branch name. +OPTIONS +------- +--allow-onelevel:: +--no-allow-onelevel:: + Controls whether one-level refnames are accepted (i.e., + refnames that do not contain multiple `/`-separated + components). The default is `--no-allow-onelevel`. + +--refspec-pattern:: + Interpret <refname> as a reference name pattern for a refspec + (as used with remote repositories). If this option is + enabled, <refname> is allowed to contain a single `{asterisk}` + in place of a one full pathname component (e.g., + `foo/{asterisk}/bar` but not `foo/bar{asterisk}`). + +--normalize:: + Normalize 'refname' by removing any leading slash (`/`) + characters and collapsing runs of adjacent slashes between + name components into a single slash. Iff the normalized + refname is valid then print it to standard output and exit + with a status of 0. (`--print` is a deprecated way to spell + `--normalize`.) + + EXAMPLES -------- @@ -90,7 +117,7 @@ $ git check-ref-format --branch @{-1} * Determine the reference name to use for a new branch: + ------------ -$ ref=$(git check-ref-format --print "refs/heads/$newbranch") || +$ ref=$(git check-ref-format --normalize "refs/heads/$newbranch") || die "we do not like '$newbranch' as a branch name." ------------ diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 7cfa3d92ac..fed5097e00 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -9,6 +9,9 @@ SYNOPSIS -------- [verse] 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>... +'git cherry-pick' --continue +'git cherry-pick' --quit +'git cherry-pick' --abort DESCRIPTION ----------- @@ -110,6 +113,10 @@ effect to your index in a row. Pass the merge strategy-specific option through to the merge strategy. See linkgit:git-merge[1] for details. +SEQUENCER SUBCOMMANDS +--------------------- +include::sequencer.txt[] + EXAMPLES -------- `git cherry-pick master`:: diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 0fdb82ee86..cfb9906bb5 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -9,7 +9,8 @@ git-commit-tree - Create a new commit object SYNOPSIS -------- [verse] -'git commit-tree' <tree> [(-p <parent commit>)...] < changelog +'git commit-tree' <tree> [(-p <parent>)...] < changelog +'git commit-tree' [(-p <parent>)...] [(-m <message>)...] [(-F <file>)...] <tree> DESCRIPTION ----------- @@ -17,7 +18,8 @@ This is usually not what an end user wants to run directly. See linkgit:git-commit[1] instead. Creates a new commit object based on the provided tree object and -emits the new commit object id on stdout. +emits the new commit object id on stdout. The log message is read +from the standard input, unless `-m` or `-F` options are given. A commit object may have any number of parents. With exactly one parent, it is an ordinary commit. Having more than one parent makes @@ -39,9 +41,17 @@ OPTIONS <tree>:: An existing tree object --p <parent commit>:: +-p <parent>:: Each '-p' indicates the id of a parent commit object. +-m <message>:: + A paragraph in the commig log message. This can be given more than + once and each <message> becomes its own paragraph. + +-F <file>:: + Read the commit log message from the given file. Use `-` to read + from the standard input. + Commit Information ------------------ @@ -68,7 +78,9 @@ if set: In case (some of) these environment variables are not set, the information is taken from the configuration items user.name and user.email, or, if not -present, system user name and fully qualified hostname. +present, system user name and the hostname used for outgoing mail (taken +from `/etc/mailname` and falling back to the fully qualified hostname when +that file does not exist). A commit comment is read from stdin. If a changelog entry is not provided via "<" redirection, 'git commit-tree' will just wait @@ -90,6 +102,10 @@ Discussion include::i18n.txt[] +FILES +----- +/etc/mailname + SEE ALSO -------- linkgit:git-write-tree[1] diff --git a/Documentation/git-credential-cache--daemon.txt b/Documentation/git-credential-cache--daemon.txt new file mode 100644 index 0000000000..11edc5a173 --- /dev/null +++ b/Documentation/git-credential-cache--daemon.txt @@ -0,0 +1,26 @@ +git-credential-cache--daemon(1) +=============================== + +NAME +---- +git-credential-cache--daemon - temporarily store user credentials in memory + +SYNOPSIS +-------- +[verse] +git credential-cache--daemon <socket> + +DESCRIPTION +----------- + +NOTE: You probably don't want to invoke this command yourself; it is +started automatically when you use linkgit:git-credential-cache[1]. + +This command listens on the Unix domain socket specified by `<socket>` +for `git-credential-cache` clients. Clients may store and retrieve +credentials. Each credential is held for a timeout specified by the +client; once no credentials are held, the daemon exits. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-credential-cache.txt b/Documentation/git-credential-cache.txt new file mode 100644 index 0000000000..f3d09c5d51 --- /dev/null +++ b/Documentation/git-credential-cache.txt @@ -0,0 +1,77 @@ +git-credential-cache(1) +======================= + +NAME +---- +git-credential-cache - helper to temporarily store passwords in memory + +SYNOPSIS +-------- +----------------------------- +git config credential.helper 'cache [options]' +----------------------------- + +DESCRIPTION +----------- + +This command caches credentials in memory for use by future git +programs. The stored credentials never touch the disk, and are forgotten +after a configurable timeout. The cache is accessible over a Unix +domain socket, restricted to the current user by filesystem permissions. + +You probably don't want to invoke this command directly; it is meant to +be used as a credential helper by other parts of git. See +linkgit:gitcredentials[7] or `EXAMPLES` below. + +OPTIONS +------- + +--timeout <seconds>:: + + Number of seconds to cache credentials (default: 900). + +--socket <path>:: + + Use `<path>` to contact a running cache daemon (or start a new + cache daemon if one is not started). Defaults to + `~/.git-credential-cache/socket`. If your home directory is on a + network-mounted filesystem, you may need to change this to a + local filesystem. + +CONTROLLING THE DAEMON +---------------------- + +If you would like the daemon to exit early, forgetting all cached +credentials before their timeout, you can issue an `exit` action: + +-------------------------------------- +git credential-cache exit +-------------------------------------- + +EXAMPLES +-------- + +The point of this helper is to reduce the number of times you must type +your username or password. For example: + +------------------------------------ +$ git config credential.helper cache +$ git push http://example.com/repo.git +Username: <type your username> +Password: <type your password> + +[work for 5 more minutes] +$ git push http://example.com/repo.git +[your credentials are used automatically] +------------------------------------ + +You can provide options via the credential.helper configuration +variable (this example drops the cache time to 5 minutes): + +------------------------------------------------------- +$ git config credential.helper 'cache --timeout=300' +------------------------------------------------------- + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt new file mode 100644 index 0000000000..31093467d1 --- /dev/null +++ b/Documentation/git-credential-store.txt @@ -0,0 +1,75 @@ +git-credential-store(1) +======================= + +NAME +---- +git-credential-store - helper to store credentials on disk + +SYNOPSIS +-------- +------------------- +git config credential.helper 'store [options]' +------------------- + +DESCRIPTION +----------- + +NOTE: Using this helper will store your passwords unencrypted on disk, +protected only by filesystem permissions. If this is not an acceptable +security tradeoff, try linkgit:git-credential-cache[1], or find a helper +that integrates with secure storage provided by your operating system. + +This command stores credentials indefinitely on disk for use by future +git programs. + +You probably don't want to invoke this command directly; it is meant to +be used as a credential helper by other parts of git. See +linkgit:gitcredentials[7] or `EXAMPLES` below. + +OPTIONS +------- + +--store=<path>:: + + Use `<path>` to store credentials. The file will have its + filesystem permissions set to prevent other users on the system + from reading it, but will not be encrypted or otherwise + protected. Defaults to `~/.git-credentials`. + +EXAMPLES +-------- + +The point of this helper is to reduce the number of times you must type +your username or password. For example: + +------------------------------------------ +$ git config credential.helper store +$ git push http://example.com/repo.git +Username: <type your username> +Password: <type your password> + +[several days later] +$ git push http://example.com/repo.git +[your credentials are used automatically] +------------------------------------------ + +STORAGE FORMAT +-------------- + +The `.git-credentials` file is stored in plaintext. Each credential is +stored on its own line as a URL like: + +------------------------------ +https://user:pass@example.com +------------------------------ + +When git needs authentication for a particular URL context, +credential-store will consider that context a pattern to match against +each entry in the credentials file. If the protocol, hostname, and +username (if we already have one) match, then the password is returned +to git. See the discussion of configuration in linkgit:gitcredentials[7] +for more information. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 69a1e4af9e..31b28fc29f 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -161,6 +161,16 @@ the facility of inet daemon to achieve the same before spawning repository configuration. By default, all the services are overridable. +--informative-errors:: +--no-informative-errors:: + When informative errors are turned on, git-daemon will report + more verbose errors to the client, differentiating conditions + like "no such repository" from "repository not exported". This + is more convenient for clients, but may leak information about + the existence of unexported repositories. When informative + errors are not enabled, all errors report "access denied" to the + client. The default is --no-informative-errors. + <directory>:: A directory to add to the whitelist of allowed directories. Unless --strict-paths is specified this will also include subdirectories diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index a03515f1ec..19d473c070 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -31,7 +31,7 @@ OPTIONS -t <tool>:: --tool=<tool>:: Use the diff tool specified by <tool>. - Valid merge tools are: + Valid diff tools are: araxis, bc3, diffuse, emerge, ecmerge, gvimdiff, kdiff3, kompare, meld, opendiff, p4merge, tkdiff, vimdiff and xxdiff. + diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 152e695c81..c872b883ba 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -101,9 +101,10 @@ Fields that have name-email-date tuple as its value (`author`, `committer`, and `tagger`) can be suffixed with `name`, `email`, and `date` to extract the named component. -The first line of the message in a commit and tag object is -`subject`, the remaining lines are `body`. The whole message -is `contents`. +The complete message in a commit and tag object is `contents`. +Its first line is `contents:subject`, the remaining lines +are `contents:body` and the optional GPG signature +is `contents:signature`. For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `taggerdate`). diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index a2a508dc28..0a17b4258e 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] - [--[no-]full] [--strict] [--verbose] [--lost-found] [<object>*] + [--[no-]full] [--strict] [--verbose] [--lost-found] + [--[no-]progress] [<object>*] DESCRIPTION ----------- @@ -72,6 +73,14 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless a blob, the contents are written into the file, rather than its object name. +--progress:: +--no-progress:: + Progress status is reported on the standard error stream by + default when it is attached to a terminal, unless + --no-progress or --verbose is specified. --progress forces + progress status even if the standard error stream is not + directed to a terminal. + It tests SHA1 and general object sanity, and it does full tracking of the resulting reachability and everything else. It prints out any corruption it finds (missing or bad objects), and if you use the diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index e44a4988b7..15d6711d46 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -23,7 +23,7 @@ SYNOPSIS [-A <post-context>] [-B <pre-context>] [-C <context>] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] - [--cached | --no-index | <tree>...] + [ [--exclude-standard] [--cached | --no-index | --untracked] | <tree>...] [--] [<pathspec>...] DESCRIPTION @@ -49,7 +49,20 @@ OPTIONS blobs registered in the index file. --no-index:: - Search files in the current directory, not just those tracked by git. + Search files in the current directory that is not managed by git. + +--untracked:: + In addition to searching in the tracked files in the working + tree, search also in untracked files. + +--no-exclude-standard:: + Also search in ignored files by not honoring the `.gitignore` + mechanism. Only useful with `--untracked`. + +--exclude-standard:: + Do not pay attention to ignored files specified via the `.gitignore` + mechanism. Only useful when searching files in the current directory + with `--no-index`. -a:: --text:: diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index ea95c90460..f3eef510f2 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -84,6 +84,10 @@ If the configuration variable 'instaweb.browser' is not set, 'web.browser' will be used instead if it is defined. See linkgit:git-web{litdd}browse[1] for more information about this. +SEE ALSO +-------- +linkgit:gitweb[1] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 3470910109..2a49de7cfe 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -17,9 +17,10 @@ Use `git mergetool` to run one of several merge utilities to resolve merge conflicts. It is typically run after 'git merge'. If one or more <file> parameters are given, the merge tool program will -be run to resolve differences on each file. If no <file> names are -specified, 'git mergetool' will run the merge tool program on every file -with merge conflicts. +be run to resolve differences on each file (skipping those without +conflicts). Specifying a directory will include all unresolved files in +that path. If no <file> names are specified, 'git mergetool' will run +the merge tool program on every file with merge conflicts. OPTIONS ------- diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt index b8db373964..e3c8448614 100644 --- a/Documentation/git-mv.txt +++ b/Documentation/git-mv.txt @@ -15,8 +15,8 @@ DESCRIPTION ----------- This script is used to move or rename a file, directory or symlink. - git mv [-f] [-n] <source> <destination> - git mv [-f] [-n] [-k] <source> ... <destination directory> + git mv [-v] [-f] [-n] [-k] <source> <destination> + git mv [-v] [-f] [-n] [-k] <source> ... <destination directory> In the first form, it renames <source>, which must exist and be either a file, symlink or directory, to <destination>. @@ -40,6 +40,10 @@ OPTIONS --dry-run:: Do nothing; only show what would happen +-v:: +--verbose:: + Report the names of files as they are moved. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index e1da468766..0f18ec891a 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -108,7 +108,7 @@ include::merge-options.txt[] fetched, the rebase uses that information to avoid rebasing non-local changes. + -See `branch.<name>.rebase` and `branch.autosetuprebase` in +See `pull.rebase`, `branch.<name>.rebase` and `branch.autosetuprebase` in linkgit:git-config[1] if you want to make `git pull` always use `{litdd}rebase` instead of merging. + diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index c45d53c6e1..5375549820 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -47,7 +47,7 @@ OPTIONS -i:: Usually a merge requires the index file as well as the - files in the working tree are up to date with the + files in the working tree to be up to date with the current head commit, in order not to lose local changes. This flag disables the check with the working tree and is meant to be used when creating a merge of @@ -71,21 +71,21 @@ OPTIONS --aggressive:: Usually a three-way merge by 'git read-tree' resolves the merge for really trivial cases and leaves other - cases unresolved in the index, so that Porcelains can + cases unresolved in the index, so that porcelains can implement different merge policies. This flag makes the - command to resolve a few more cases internally: + command resolve a few more cases internally: + * when one side removes a path and the other side leaves the path unmodified. The resolution is to remove that path. * when both sides remove a path. The resolution is to remove that path. -* when both sides adds a path identically. The resolution +* when both sides add a path identically. The resolution is to add that path. --prefix=<prefix>/:: Keep the current index contents, and read the contents - of named tree-ish under directory at `<prefix>`. The + of the named tree-ish under the directory at `<prefix>`. The original index file cannot have anything at the path - `<prefix>` itself, and have nothing in `<prefix>/` + `<prefix>` itself, nor anything in the `<prefix>/` directory. Note that the `<prefix>/` value must end with a slash. @@ -379,45 +379,45 @@ have finished your work-in-progress), attempt the merge again. Sparse checkout --------------- -"Sparse checkout" allows to sparsely populate working directory. -It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell -Git whether a file on working directory is worth looking at. +"Sparse checkout" allows populating the working directory sparsely. +It uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell +Git whether a file in the working directory is worth looking at. -"git read-tree" and other merge-based commands ("git merge", "git -checkout"...) can help maintaining skip-worktree bitmap and working +'git read-tree' and other merge-based commands ('git merge', 'git +checkout'...) can help maintaining the skip-worktree bitmap and working directory update. `$GIT_DIR/info/sparse-checkout` is used to -define the skip-worktree reference bitmap. When "git read-tree" needs -to update working directory, it will reset skip-worktree bit in index +define the skip-worktree reference bitmap. When 'git read-tree' needs +to update the working directory, it resets the skip-worktree bit in the index based on this file, which uses the same syntax as .gitignore files. -If an entry matches a pattern in this file, skip-worktree will be -set on that entry. Otherwise, skip-worktree will be unset. +If an entry matches a pattern in this file, skip-worktree will not be +set on that entry. Otherwise, skip-worktree will be set. Then it compares the new skip-worktree value with the previous one. If -skip-worktree turns from unset to set, it will add the corresponding -file back. If it turns from set to unset, that file will be removed. +skip-worktree turns from set to unset, it will add the corresponding +file back. If it turns from unset to set, that file will be removed. While `$GIT_DIR/info/sparse-checkout` is usually used to specify what -files are in. You can also specify what files are _not_ in, using -negate patterns. For example, to remove file "unwanted": +files are in, you can also specify what files are _not_ in, using +negate patterns. For example, to remove the file `unwanted`: ---------------- -* +/* !unwanted ---------------- -Another tricky thing is fully repopulating working directory when you +Another tricky thing is fully repopulating the working directory when you no longer want sparse checkout. You cannot just disable "sparse -checkout" because skip-worktree are still in the index and you working -directory is still sparsely populated. You should re-populate working +checkout" because skip-worktree bits are still in the index and your working +directory is still sparsely populated. You should re-populate the working directory with the `$GIT_DIR/info/sparse-checkout` file content as follows: ---------------- -* +/* ---------------- -Then you can disable sparse checkout. Sparse checkout support in "git -read-tree" and similar commands is disabled by default. You need to +Then you can disable sparse checkout. Sparse checkout support in 'git +read-tree' and similar commands is disabled by default. You need to turn `core.sparseCheckout` on in order to have sparse checkout support. diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index b2832fc7eb..b674866e6d 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -9,8 +9,8 @@ SYNOPSIS -------- [verse] 'git reset' [-q] [<commit>] [--] <paths>... -'git reset' [--patch|-p] [<commit>] [--] [<paths>...] -'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>] +'git reset' (--patch | -p) [<commit>] [--] [<paths>...] +'git reset' (--soft | --mixed | --hard | --merge | --keep) [-q] [<commit>] DESCRIPTION ----------- @@ -34,7 +34,7 @@ Alternatively, using linkgit:git-checkout[1] and specifying a commit, you can copy the contents of a path out of a commit to the index and to the working tree in one go. -'git reset' --patch|-p [<commit>] [--] [<paths>...]:: +'git reset' (--patch | -p) [<commit>] [--] [<paths>...]:: Interactively select hunks in the difference between the index and <commit> (defaults to HEAD). The chosen hunks are applied in reverse to the index. @@ -43,7 +43,7 @@ This means that `git reset -p` is the opposite of `git add -p`, i.e. you can use it to selectively reset hunks. See the ``Interactive Mode'' section of linkgit:git-add[1] to learn how to operate the `\--patch` mode. -'git reset' [--<mode>] [<commit>]:: +'git reset' --<mode> [<commit>]:: This form resets the current branch head to <commit> and possibly updates the index (resetting it to the tree of <commit>) and the working tree depending on <mode>, which diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 42c9676eaa..8023dc086d 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -180,6 +180,10 @@ print a message to stderr and exit with nonzero status. <args>...:: Flags and parameters to be parsed. +--resolve-git-dir <path>:: + Check if <path> is a valid git-dir or a git-file pointing to a valid + git-dir. If <path> is a valid git-dir the resolved path to git-dir will + be printed. include::revisions.txt[] diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index b311d59c7c..b699a3458e 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -9,6 +9,9 @@ SYNOPSIS -------- [verse] 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>... +'git revert' --continue +'git revert' --quit +'git revert' --abort DESCRIPTION ----------- @@ -91,6 +94,10 @@ effect to your index in a row. Pass the merge strategy-specific option through to the merge strategy. See linkgit:git-merge[1] for details. +SEQUENCER SUBCOMMANDS +--------------------- +include::sequencer.txt[] + EXAMPLES -------- `git revert HEAD~3`:: diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt index b78f031cd4..a80d94650d 100644 --- a/Documentation/git-stripspace.txt +++ b/Documentation/git-stripspace.txt @@ -3,26 +3,83 @@ git-stripspace(1) NAME ---- -git-stripspace - Filter out empty lines +git-stripspace - Remove unnecessary whitespace SYNOPSIS -------- [verse] -'git stripspace' [-s | --strip-comments] < <stream> +'git stripspace' [-s | --strip-comments] < input DESCRIPTION ----------- -Remove multiple empty lines, and empty lines at beginning and end. + +Clean the input in the manner used by 'git' for text such as commit +messages, notes, tags and branch descriptions. + +With no arguments, this will: + +- remove trailing whitespace from all lines +- collapse multiple consecutive empty lines into one empty line +- remove empty lines from the beginning and end of the input +- add a missing '\n' to the last line if necessary. + +In the case where the input consists entirely of whitespace characters, no +output will be produced. + +*NOTE*: This is intended for cleaning metadata, prefer the `--whitespace=fix` +mode of linkgit:git-apply[1] for correcting whitespace of patches or files in +the repository. OPTIONS ------- -s:: --strip-comments:: - In addition to empty lines, also strip lines starting with '#'. + Skip and remove all lines starting with '#'. + +EXAMPLES +-------- + +Given the following noisy input with '$' indicating the end of a line: -<stream>:: - Byte stream to act on. +-------- +|A brief introduction $ +| $ +|$ +|A new paragraph$ +|# with a commented-out line $ +|explaining lots of stuff.$ +|$ +|# An old paragraph, also commented-out. $ +| $ +|The end.$ +| $ +--------- + +Use 'git stripspace' with no arguments to obtain: + +-------- +|A brief introduction$ +|$ +|A new paragraph$ +|# with a commented-out line$ +|explaining lots of stuff.$ +|$ +|# An old paragraph, also commented-out.$ +|$ +|The end.$ +--------- + +Use 'git stripspace --strip-comments' to obtain: + +-------- +|A brief introduction$ +|$ +|A new paragraph$ +|explaining lots of stuff.$ +|$ +|The end.$ +--------- GIT --- diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 67cf5f0f8b..6ec3fef079 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -120,6 +120,8 @@ too (and can also report changes to a submodule's work tree). init:: Initialize the submodules, i.e. register each submodule name and url found in .gitmodules into .git/config. + It will also copy the value of `submodule.$name.update` into + .git/config. The key used in .git/config is `submodule.$name.url`. This command does not alter existing information in .git/config. You can then customize the submodule clone URLs in .git/config @@ -133,7 +135,7 @@ update:: checkout the commit specified in the index of the containing repository. This will make the submodules HEAD be detached unless `--rebase` or `--merge` is specified or the key `submodule.$name.update` is set to - `rebase` or `merge`. + `rebase`, `merge` or `none`. + If the submodule is not yet initialized, and you just want to use the setting as stored in .gitmodules, you can automatically initialize the @@ -141,6 +143,10 @@ submodule with the `--init` option. + If `--recursive` is specified, this command will recurse into the registered submodules, and update any nested submodules within. ++ +If the configuration key `submodule.$name.update` is set to `none` the +submodule with name `$name` will not be updated by default. This can be +overriden by adding `--checkout` to the command. summary:: Show commit summary between the given commit (defaults to HEAD) and diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 08cad6d2b6..34ee785064 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -234,6 +234,14 @@ svn:mergeinfo property in the SVN repository when possible. Currently, this can only be done when dcommitting non-fast-forward merges where all parents but the first have already been pushed into SVN. +--interactive;; + Ask the user to confirm that a patch set should actually be sent to SVN. + For each patch, one may answer "yes" (accept this patch), "no" (discard this + patch), "all" (accept all patches), or "quit". + + + 'git svn dcommit' returns immediately if answer if "no" or "quit", without + commiting anything to SVN. + 'branch':: Create a branch in the SVN repository. @@ -318,7 +326,7 @@ Any other arguments are passed directly to 'git log' Show what revision and author last modified each line of a file. The output of this mode is format-compatible with the output of `svn blame' by default. Like the SVN blame command, - local uncommitted changes in the working copy are ignored; + local uncommitted changes in the working tree are ignored; the version of the file in the HEAD revision is annotated. Unknown arguments are passed directly to 'git blame'. + diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index 75b1ae5061..a45d4c4f29 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -43,12 +43,9 @@ In the past, `.git/HEAD` was a symbolic link pointing at `refs/heads/master`. When we wanted to switch to another branch, we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we wanted to find out which branch we are on, we did `readlink .git/HEAD`. -This was fine, and internally that is what still happens by -default, but on platforms that do not have working symlinks, -or that do not have the `readlink(1)` command, this was a bit -cumbersome. On some platforms, `ln -sf` does not even work as -advertised (horrors). Therefore symbolic links are now deprecated -and symbolic refs are used by default. +But symbolic links are not entirely portable, so they are now +deprecated and symbolic refs (as described above) are used by +default. 'git symbolic-ref' will exit with status 0 if the contents of the symbolic ref were printed correctly, with status 1 if the requested diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index fb1c0ac694..622a019eb0 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -43,12 +43,15 @@ GnuPG key for signing. OPTIONS ------- -a:: +--annotate:: Make an unsigned, annotated tag object -s:: +--sign:: Make a GPG-signed tag, using the default e-mail address's key -u <key-id>:: +--local-user=<key-id>:: Make a GPG-signed tag, using the given key -f:: @@ -56,9 +59,11 @@ OPTIONS Replace an existing tag with the given name (instead of failing) -d:: +--delete:: Delete existing tags with the given names. -v:: +--verify:: Verify the gpg signature of the given tag names. -n<num>:: @@ -69,6 +74,7 @@ OPTIONS If the tag is not annotated, the commit message is displayed instead. -l <pattern>:: +--list <pattern>:: List tags with names that match the given pattern (or all if no pattern is given). Running "git tag" without arguments also lists all tags. The pattern is a shell wildcard (i.e., matched @@ -79,6 +85,7 @@ OPTIONS Only list tags which contain the specified commit. -m <msg>:: +--message=<msg>:: Use the given tag message (instead of prompting). If multiple `-m` options are given, their values are concatenated as separate paragraphs. @@ -86,11 +93,19 @@ OPTIONS is given. -F <file>:: +--file=<file>:: Take the tag message from the given file. Use '-' to read the message from the standard input. Implies `-a` if none of `-a`, `-s`, or `-u <key-id>` is given. +--cleanup=<mode>:: + This option sets how the tag message is cleaned up. + The '<mode>' can be one of 'verbatim', 'whitespace' and 'strip'. The + 'strip' mode is default. The 'verbatim' mode does not change message at + all, 'whitespace' removes just leading/trailing whitespace lines and + 'strip' removes both whitespace and commentary. + <tagname>:: The name of the tag to create, delete, or describe. The new tag name must pass all checks defined by diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index d3931294d1..a3081f4e23 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -264,7 +264,9 @@ tree files, you have to explicitly tell git about it by dropping "assume unchanged" bit, either before or after you modify them. In order to set "assume unchanged" bit, use `--assume-unchanged` -option. To unset, use `--no-assume-unchanged`. +option. To unset, use `--no-assume-unchanged`. To see which files +have the "assume unchanged" bit set, use `git ls-files -v` +(see linkgit:git-ls-files[1]). The command looks at `core.ignorestat` configuration variable. When this is true, paths updated with `git update-index paths...` and @@ -363,7 +365,8 @@ ctime for marking files processed) (see linkgit:git-config[1]). SEE ALSO -------- linkgit:git-config[1], -linkgit:git-add[1] +linkgit:git-add[1], +linkgit:git-ls-files[1] GIT --- diff --git a/Documentation/git.txt b/Documentation/git.txt index cbc51d5a94..da7d48787e 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,14 +44,25 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.7/git.html[documentation for release 1.7.7] +* link:v1.7.8/git.html[documentation for release 1.7.8] * release notes for + link:RelNotes/1.7.8.txt[1.7.8]. + +* link:v1.7.7.5/git.html[documentation for release 1.7.7.5] + +* release notes for + link:RelNotes/1.7.7.5.txt[1.7.7.5], + link:RelNotes/1.7.7.4.txt[1.7.7.4], + link:RelNotes/1.7.7.3.txt[1.7.7.3], + link:RelNotes/1.7.7.2.txt[1.7.7.2], + link:RelNotes/1.7.7.1.txt[1.7.7.1], link:RelNotes/1.7.7.txt[1.7.7]. -* link:v1.7.6.4/git.html[documentation for release 1.7.6.4] +* link:v1.7.6.5/git.html[documentation for release 1.7.6.5] * release notes for + link:RelNotes/1.7.6.5.txt[1.7.6.5], link:RelNotes/1.7.6.4.txt[1.7.6.4], link:RelNotes/1.7.6.3.txt[1.7.6.3], link:RelNotes/1.7.6.2.txt[1.7.6.2], diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 25e46aeb7a..a85b187e04 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -500,6 +500,8 @@ patterns are available: - `java` suitable for source code in the Java language. +- `matlab` suitable for source code in the MATLAB language. + - `objc` suitable for source code in the Objective-C language. - `pascal` suitable for source code in the Pascal/Delphi language. diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt new file mode 100644 index 0000000000..066f825f2e --- /dev/null +++ b/Documentation/gitcredentials.txt @@ -0,0 +1,183 @@ +gitcredentials(7) +================= + +NAME +---- +gitcredentials - providing usernames and passwords to git + +SYNOPSIS +-------- +------------------ +git config credential.https://example.com.username myusername +git config credential.helper "$helper $options" +------------------ + +DESCRIPTION +----------- + +Git will sometimes need credentials from the user in order to perform +operations; for example, it may need to ask for a username and password +in order to access a remote repository over HTTP. This manual describes +the mechanisms git uses to request these credentials, as well as some +features to avoid inputting these credentials repeatedly. + +REQUESTING CREDENTIALS +---------------------- + +Without any credential helpers defined, git will try the following +strategies to ask the user for usernames and passwords: + +1. If the `GIT_ASKPASS` environment variable is set, the program + specified by the variable is invoked. A suitable prompt is provided + to the program on the command line, and the user's input is read + from its standard output. + +2. Otherwise, if the `core.askpass` configuration variable is set, its + value is used as above. + +3. Otherwise, if the `SSH_ASKPASS` environment variable is set, its + value is used as above. + +4. Otherwise, the user is prompted on the terminal. + +AVOIDING REPETITION +------------------- + +It can be cumbersome to input the same credentials over and over. Git +provides two methods to reduce this annoyance: + +1. Static configuration of usernames for a given authentication context. + +2. Credential helpers to cache or store passwords, or to interact with + a system password wallet or keychain. + +The first is simple and appropriate if you do not have secure storage available +for a password. It is generally configured by adding this to your config: + +--------------------------------------- +[credential "https://example.com"] + username = me +--------------------------------------- + +Credential helpers, on the other hand, are external programs from which git can +request both usernames and passwords; they typically interface with secure +storage provided by the OS or other programs. + +To use a helper, you must first select one to use. Git currently +includes the following helpers: + +cache:: + + Cache credentials in memory for a short period of time. See + linkgit:git-credential-cache[1] for details. + +store:: + + Store credentials indefinitely on disk. See + linkgit:git-credential-store[1] for details. + +You may also have third-party helpers installed; search for +`credential-*` in the output of `git help -a`, and consult the +documentation of individual helpers. Once you have selected a helper, +you can tell git to use it by putting its name into the +credential.helper variable. + +1. Find a helper. ++ +------------------------------------------- +$ git help -a | grep credential- +credential-foo +------------------------------------------- + +2. Read its description. ++ +------------------------------------------- +$ git help credential-foo +------------------------------------------- + +3. Tell git to use it. ++ +------------------------------------------- +$ git config --global credential.helper foo +------------------------------------------- + +If there are multiple instances of the `credential.helper` configuration +variable, each helper will be tried in turn, and may provide a username, +password, or nothing. Once git has acquired both a username and a +password, no more helpers will be tried. + + +CREDENTIAL CONTEXTS +------------------- + +Git considers each credential to have a context defined by a URL. This context +is used to look up context-specific configuration, and is passed to any +helpers, which may use it as an index into secure storage. + +For instance, imagine we are accessing `https://example.com/foo.git`. When git +looks into a config file to see if a section matches this context, it will +consider the two a match if the context is a more-specific subset of the +pattern in the config file. For example, if you have this in your config file: + +-------------------------------------- +[credential "https://example.com"] + username = foo +-------------------------------------- + +then we will match: both protocols are the same, both hosts are the same, and +the "pattern" URL does not care about the path component at all. However, this +context would not match: + +-------------------------------------- +[credential "https://kernel.org"] + username = foo +-------------------------------------- + +because the hostnames differ. Nor would it match `foo.example.com`; git +compares hostnames exactly, without considering whether two hosts are part of +the same domain. Likewise, a config entry for `http://example.com` would not +match: git compares the protocols exactly. + + +CONFIGURATION OPTIONS +--------------------- + +Options for a credential context can be configured either in +`credential.\*` (which applies to all credentials), or +`credential.<url>.\*`, where <url> matches the context as described +above. + +The following options are available in either location: + +helper:: + + The name of an external credential helper, and any associated options. + If the helper name is not an absolute path, then the string `git + credential-` is prepended. The resulting string is executed by the + shell (so, for example, setting this to `foo --option=bar` will execute + `git credential-foo --option=bar` via the shell. See the manual of + specific helpers for examples of their use. + +username:: + + A default username, if one is not provided in the URL. + +useHttpPath:: + + By default, git does not consider the "path" component of an http URL + to be worth matching via external helpers. This means that a credential + stored for `https://example.com/foo.git` will also be used for + `https://example.com/bar.git`. If you do want to distinguish these + cases, set this option to `true`. + + +CUSTOM HELPERS +-------------- + +You can write your own custom helpers to interface with any system in +which you keep credentials. See the documentation for git's +link:technical/api-credentials.html[credentials API] for details. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt new file mode 100644 index 0000000000..7aba497b74 --- /dev/null +++ b/Documentation/gitweb.conf.txt @@ -0,0 +1,889 @@ +gitweb.conf(5) +============== + +NAME +---- +gitweb.conf - Gitweb (git web interface) configuration file + +SYNOPSIS +-------- +/etc/gitweb.conf, /etc/gitweb-common.conf, $GITWEBDIR/gitweb_config.perl + +DESCRIPTION +----------- + +The gitweb CGI script for viewing Git repositories over the web uses a +perl script fragment as its configuration file. You can set variables +using "`our $variable = value`"; text from a "#" character until the +end of a line is ignored. See *perlsyn*(1) for details. + +An example: + + # gitweb configuration file for http://git.example.org + # + our $projectroot = "/srv/git"; # FHS recommendation + our $site_name = 'Example.org >> Repos'; + + +The configuration file is used to override the default settings that +were built into gitweb at the time the 'gitweb.cgi' script was generated. + +While one could just alter the configuration settings in the gitweb +CGI itself, those changes would be lost upon upgrade. Configuration +settings might also be placed into a file in the same directory as the +CGI script with the default name 'gitweb_config.perl' -- allowing +one to have multiple gitweb instances with different configurations by +the use of symlinks. + +Note that some configuration can be controlled on per-repository rather than +gitweb-wide basis: see "Per-repository gitweb configuration" subsection on +linkgit:gitweb[1] manpage. + + +DISCUSSION +---------- +Gitweb reads configuration data from the following sources in the +following order: + + * built-in values (some set during build stage), + + * common system-wide configuration file (defaults to + '/etc/gitweb-common.conf'), + + * either per-instance configuration file (defaults to 'gitweb_config.perl' + in the same directory as the installed gitweb), or if it does not exists + then fallback system-wide configuration file (defaults to '/etc/gitweb.conf'). + +Values obtained in later configuration files override values obtained earlier +in the above sequence. + +Locations of the common system-wide configuration file, the fallback +system-wide configuration file and the per-instance configuration file +are defined at compile time using build-time Makefile configuration +variables, respectively `GITWEB_CONFIG_COMMON`, `GITWEB_CONFIG_SYSTEM` +and `GITWEB_CONFIG`. + +You can also override locations of gitweb configuration files during +runtime by setting the following environment variables: +`GITWEB_CONFIG_COMMON`, `GITWEB_CONFIG_SYSTEM` and `GITWEB_CONFIG` +to a non-empty value. + + +The syntax of the configuration files is that of Perl, since these files are +handled by sourcing them as fragments of Perl code (the language that +gitweb itself is written in). Variables are typically set using the +`our` qualifier (as in "`our $variable = <value>;`") to avoid syntax +errors if a new version of gitweb no longer uses a variable and therefore +stops declaring it. + +You can include other configuration file using read_config_file() +subroutine. For example, one might want to put gitweb configuration +related to access control for viewing repositories via Gitolite (one +of git repository management tools) in a separate file, e.g. in +'/etc/gitweb-gitolite.conf'. To include it, put + +-------------------------------------------------- +read_config_file("/etc/gitweb-gitolite.conf"); +-------------------------------------------------- + +somewhere in gitweb configuration file used, e.g. in per-installation +gitweb configuration file. Note that read_config_file() checks itself +that the file it reads exists, and does nothing if it is not found. +It also handles errors in included file. + + +The default configuration with no configuration file at all may work +perfectly well for some installations. Still, a configuration file is +useful for customizing or tweaking the behavior of gitweb in many ways, and +some optional features will not be present unless explicitly enabled using +the configurable `%features` variable (see also "Configuring gitweb +features" section below). + + +CONFIGURATION VARIABLES +----------------------- +Some configuration variables have their default values (embedded in the CGI +script) set during building gitweb -- if that is the case, this fact is put +in their description. See gitweb's 'INSTALL' file for instructions on building +and installing gitweb. + + +Location of repositories +~~~~~~~~~~~~~~~~~~~~~~~~ +The configuration variables described below control how gitweb finds +git repositories, and how repositories are displayed and accessed. + +See also "Repositories" and later subsections in linkgit:gitweb[1] manpage. + +$projectroot:: + Absolute filesystem path which will be prepended to project path; + the path to repository is `$projectroot/$project`. Set to + `$GITWEB_PROJECTROOT` during installation. This variable has to be + set correctly for gitweb to find repositories. ++ +For example, if `$projectroot` is set to "/srv/git" by putting the following +in gitweb config file: ++ +---------------------------------------------------------------------------- +our $projectroot = "/srv/git"; +---------------------------------------------------------------------------- ++ +then ++ +------------------------------------------------ +http://git.example.com/gitweb.cgi?p=foo/bar.git +------------------------------------------------ ++ +and its path_info based equivalent ++ +------------------------------------------------ +http://git.example.com/gitweb.cgi/foo/bar.git +------------------------------------------------ ++ +will map to the path '/srv/git/foo/bar.git' on the filesystem. + +$projects_list:: + Name of a plain text file listing projects, or a name of directory + to be scanned for projects. ++ +Project list files should list one project per line, with each line +having the following format ++ +----------------------------------------------------------------------------- +<URI-encoded filesystem path to repository> SP <URI-encoded repository owner> +----------------------------------------------------------------------------- ++ +The default value of this variable is determined by the `GITWEB_LIST` +makefile variable at installation time. If this variable is empty, gitweb +will fall back to scanning the `$projectroot` directory for repositories. + +$project_maxdepth:: + If `$projects_list` variable is unset, gitweb will recursively + scan filesystem for git repositories. The `$project_maxdepth` + is used to limit traversing depth, relative to `$projectroot` + (starting point); it means that directories which are further + from `$projectroot` than `$project_maxdepth` will be skipped. ++ +It is purely performance optimization, originally intended for MacOS X, +where recursive directory traversal is slow. Gitweb follows symbolic +links, but it detects cycles, ignoring any duplicate files and directories. ++ +The default value of this variable is determined by the build-time +configuration variable `GITWEB_PROJECT_MAXDEPTH`, which defaults to +2007. + +$export_ok:: + Show repository only if this file exists (in repository). Only + effective if this variable evaluates to true. Can be set when + building gitweb by setting `GITWEB_EXPORT_OK`. This path is + relative to `GIT_DIR`. git-daemon[1] uses 'git-daemon-export-ok', + unless started with `--export-all`. By default this variable is + not set, which means that this feature is turned off. + +$export_auth_hook:: + Function used to determine which repositories should be shown. + This subroutine should take one parameter, the full path to + a project, and if it returns true, that project will be included + in the projects list and can be accessed through gitweb as long + as it fulfills the other requirements described by $export_ok, + $projects_list, and $projects_maxdepth. Example: ++ +---------------------------------------------------------------------------- +our $export_auth_hook = sub { return -e "$_[0]/git-daemon-export-ok"; }; +---------------------------------------------------------------------------- ++ +though the above might be done by using `$export_ok` instead ++ +---------------------------------------------------------------------------- +our $export_ok = "git-daemon-export-ok"; +---------------------------------------------------------------------------- ++ +If not set (default), it means that this feature is disabled. ++ +See also more involved example in "Controlling access to git repositories" +subsection on linkgit:gitweb[1] manpage. + +$strict_export:: + Only allow viewing of repositories also shown on the overview page. + This for example makes `$gitweb_export_ok` file decide if repository is + available and not only if it is shown. If `$gitweb_list` points to + file with list of project, only those repositories listed would be + available for gitweb. Can be set during building gitweb via + `GITWEB_STRICT_EXPORT`. By default this variable is not set, which + means that you can directly access those repositories that are hidden + from projects list page (e.g. the are not listed in the $projects_list + file). + + +Finding files +~~~~~~~~~~~~~ +The following configuration variables tell gitweb where to find files. +The values of these variables are paths on the filesystem. + +$GIT:: + Core git executable to use. By default set to `$GIT_BINDIR/git`, which + in turn is by default set to `$(bindir)/git`. If you use git installed + from a binary package, you should usually set this to "/usr/bin/git". + This can just be "git" if your web server has a sensible PATH; from + security point of view it is better to use absolute path to git binary. + If you have multiple git versions installed it can be used to choose + which one to use. Must be (correctly) set for gitweb to be able to + work. + +$mimetypes_file:: + File to use for (filename extension based) guessing of MIME types before + trying '/etc/mime.types'. *NOTE* that this path, if relative, is taken + as relative to the current git repository, not to CGI script. If unset, + only '/etc/mime.types' is used (if present on filesystem). If no mimetypes + file is found, mimetype guessing based on extension of file is disabled. + Unset by default. + +$highlight_bin:: + Path to the highlight executable to use (it must be the one from + http://www.andre-simon.de[] due to assumptions about parameters and output). + By default set to 'highlight'; set it to full path to highlight + executable if it is not installed on your web server's PATH. + Note that 'highlight' feature must be set for gitweb to actually + use syntax hightlighting. ++ +*NOTE*: if you want to add support for new file type (supported by +"highlight" but not used by gitweb), you need to modify `%highlight_ext` +or `%highlight_basename`, depending on whether you detect type of file +based on extension (for example "sh") or on its basename (for example +"Makefile"). The keys of these hashes are extension and basename, +respectively, and value for given key is name of syntax to be passed via +`--syntax <syntax>` to highlighter. ++ +For example if repositories you are hosting use "phtml" extension for +PHP files, and you want to have correct syntax-highlighting for those +files, you can add the following to gitweb configuration: ++ +--------------------------------------------------------- +our %highlight_ext; +$highlight_ext{'phtml'} = 'php'; +--------------------------------------------------------- + + +Links and their targets +~~~~~~~~~~~~~~~~~~~~~~~ +The configuration variables described below configure some of gitweb links: +their target and their look (text or image), and where to find page +prerequisites (stylesheet, favicon, images, scripts). Usually they are left +at their default values, with the possible exception of `@stylesheets` +variable. + +@stylesheets:: + List of URIs of stylesheets (relative to the base URI of a page). You + might specify more than one stylesheet, for example to use "gitweb.css" + as base with site specific modifications in a separate stylesheet + to make it easier to upgrade gitweb. For example, you can add + a `site` stylesheet by putting ++ +---------------------------------------------------------------------------- +push @stylesheets, "gitweb-site.css"; +---------------------------------------------------------------------------- ++ +in the gitweb config file. Those values that are relative paths are +relative to base URI of gitweb. ++ +This list should contain the URI of gitweb's standard stylesheet. The default +URI of gitweb stylesheet can be set at build time using the `GITWEB_CSS` +makefile variable. Its default value is 'static/gitweb.css' +(or 'static/gitweb.min.css' if the `CSSMIN` variable is defined, +i.e. if CSS minifier is used during build). ++ +*Note*: there is also a legacy `$stylesheet` configuration variable, which was +used by older gitweb. If `$stylesheet` variable is defined, only CSS stylesheet +given by this variable is used by gitweb. + +$logo:: + Points to the location where you put 'git-logo.png' on your web + server, or to be more the generic URI of logo, 72x27 size). This image + is displayed in the top right corner of each gitweb page and used as + a logo for the Atom feed. Relative to the base URI of gitweb (as a path). + Can be adjusted when building gitweb using `GITWEB_LOGO` variable + By default set to 'static/git-logo.png'. + +$favicon:: + Points to the location where you put 'git-favicon.png' on your web + server, or to be more the generic URI of favicon, which will be served + as "image/png" type. Web browsers that support favicons (website icons) + may display them in the browser's URL bar and next to the site name in + bookmarks. Relative to the base URI of gitweb. Can be adjusted at + build time using `GITWEB_FAVICON` variable. + By default set to 'static/git-favicon.png'. + +$javascript:: + Points to the location where you put 'gitweb.js' on your web server, + or to be more generic the URI of JavaScript code used by gitweb. + Relative to the base URI of gitweb. Can be set at build time using + the `GITWEB_JS` build-time configuration variable. ++ +The default value is either 'static/gitweb.js', or 'static/gitweb.min.js' if +the `JSMIN` build variable was defined, i.e. if JavaScript minifier was used +at build time. *Note* that this single file is generated from multiple +individual JavaScript "modules". + +$home_link:: + Target of the home link on the top of all pages (the first part of view + "breadcrumbs"). By default it is set to the absolute URI of a current page + (to the value of `$my_uri` variable, or to "/" if `$my_uri` is undefined + or is an empty string). + +$home_link_str:: + Label for the "home link" at the top of all pages, leading to `$home_link` + (usually the main gitweb page, which contains the projects list). It is + used as the first component of gitweb's "breadcrumb trail": + `<home link> / <project> / <action>`. Can be set at build time using + the `GITWEB_HOME_LINK_STR` variable. By default it is set to "projects", + as this link leads to the list of projects. Other popular choice it to + set it to the name of site. + +$logo_url:: +$logo_label:: + URI and label (title) for the Git logo link (or your site logo, + if you chose to use different logo image). By default, these both + refer to git homepage, http://git-scm.com[]; in the past, they pointed + to git documentation at http://www.kernel.org[]. + + +Changing gitweb's look +~~~~~~~~~~~~~~~~~~~~~~ +You can adjust how pages generated by gitweb look using the variables described +below. You can change the site name, add common headers and footers for all +pages, and add a description of this gitweb installation on its main page +(which is the projects list page), etc. + +$site_name:: + Name of your site or organization, to appear in page titles. Set it + to something descriptive for clearer bookmarks etc. If this variable + is not set or is, then gitweb uses the value of the `SERVER_NAME` + CGI environment variable, setting site name to "$SERVER_NAME Git", + or "Untitled Git" if this variable is not set (e.g. if running gitweb + as standalone script). ++ +Can be set using the `GITWEB_SITENAME` at build time. Unset by default. + +$site_html_head_string:: + HTML snippet to be included in the <head> section of each page. + Can be set using `GITWEB_SITE_HTML_HEAD_STRING` at build time. + No default value. + +$site_header:: + Name of a file with HTML to be included at the top of each page. + Relative to the directory containing the 'gitweb.cgi' script. + Can be set using `GITWEB_SITE_HEADER` at build time. No default + value. + +$site_footer:: + Name of a file with HTML to be included at the bottom of each page. + Relative to the directory containing the 'gitweb.cgi' script. + Can be set using `GITWEB_SITE_FOOTER` at build time. No default + value. + +$home_text:: + Name of a HTML file which, if it exists, is included on the + gitweb projects overview page ("projects_list" view). Relative to + the directory containing the gitweb.cgi script. Default value + can be adjusted during build time using `GITWEB_HOMETEXT` variable. + By default set to 'indextext.html'. + +$projects_list_description_width:: + The width (in characters) of the "Description" column of the projects list. + Longer descriptions will be truncated (trying to cut at word boundary); + the full description is available in the 'title' attribute (usually shown on + mouseover). The default is 25, which might be too small if you + use long project descriptions. + +$default_projects_order:: + Default value of ordering of projects on projects list page, which + means the ordering used if you don't explicitly sort projects list + (if there is no "o" CGI query parameter in the URL). Valid values + are "none" (unsorted), "project" (projects are by project name, + i.e. path to repository relative to `$projectroot`), "descr" + (project description), "owner", and "age" (by date of most current + commit). ++ +Default value is "project". Unknown value means unsorted. + + +Changing gitweb's behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~ +These configuration variables control _internal_ gitweb behavior. + +$default_blob_plain_mimetype:: + Default mimetype for the blob_plain (raw) view, if mimetype checking + doesn't result in some other type; by default "text/plain". + Gitweb guesses mimetype of a file to display based on extension + of its filename, using `$mimetypes_file` (if set and file exists) + and '/etc/mime.types' files (see *mime.types*(5) manpage; only + filename extension rules are supported by gitweb). + +$default_text_plain_charset:: + Default charset for text files. If this is not set, the web server + configuration will be used. Unset by default. + +$fallback_encoding:: + Gitweb assumes this charset when a line contains non-UTF-8 characters. + The fallback decoding is used without error checking, so it can be even + "utf-8". The value must be a valid encoding; see the *Encoding::Supported*(3pm) + man page for a list. The default is "latin1", aka. "iso-8859-1". + +@diff_opts:: + Rename detection options for git-diff and git-diff-tree. The default is + (\'-M'); set it to (\'-C') or (\'-C', \'-C') to also detect copies, + or set it to () i.e. empty list if you don't want to have renames + detection. ++ +*Note* that rename and especially copy detection can be quite +CPU-intensive. Note also that non git tools can have problems with +patches generated with options mentioned above, especially when they +involve file copies (\'-C') or criss-cross renames (\'-B'). + + +Some optional features and policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Most of features are configured via `%feature` hash; however some of extra +gitweb features can be turned on and configured using variables described +below. This list beside configuration variables that control how gitweb +looks does contain variables configuring administrative side of gitweb +(e.g. cross-site scripting prevention; admittedly this as side effect +affects how "summary" pages look like, or load limiting). + +@git_base_url_list:: + List of git base URLs. These URLs are used to generate URLs + describing from where to fetch a project, which are shown on + project summary page. The full fetch URL is "`$git_base_url/$project`", + for each element of this list. You can set up multiple base URLs + (for example one for `git://` protocol, and one for `http://` + protocol). ++ +Note that per repository configuration can be set in '$GIT_DIR/cloneurl' +file, or as values of multi-value `gitweb.url` configuration variable in +project config. Per-repository configuration takes precedence over value +composed from `@git_base_url_list` elements and project name. ++ +You can setup one single value (single entry/item in this list) at build +time by setting the `GITWEB_BASE_URL` built-time configuration variable. +By default it is set to (), i.e. an empty list. This means that gitweb +would not try to create project URL (to fetch) from project name. + +$projects_list_group_categories:: + Whether to enables the grouping of projects by category on the project + list page. The category of a project is determined by the + `$GIT_DIR/category` file or the `gitweb.category` variable in each + repository's configuration. Disabled by default (set to 0). + +$project_list_default_category:: + Default category for projects for which none is specified. If this is + set to the empty string, such projects will remain uncategorized and + listed at the top, above categorized projects. Used only if project + categories are enabled, which means if `$projects_list_group_categories` + is true. By default set to "" (empty string). + +$prevent_xss:: + If true, some gitweb features are disabled to prevent content in + repositories from launching cross-site scripting (XSS) attacks. Set this + to true if you don't trust the content of your repositories. + False by default (set to 0). + +$maxload:: + Used to set the maximum load that we will still respond to gitweb queries. + If the server load exceeds this value then gitweb will return + "503 Service Unavailable" error. The server load is taken to be 0 + if gitweb cannot determine its value. Currently it works only on Linux, + where it uses '/proc/loadavg'; the load there is the number of active + tasks on the system -- processes that are actually running -- averaged + over the last minute. ++ +Set `$maxload` to undefined value (`undef`) to turn this feature off. +The default value is 300. + +$per_request_config:: + If this is set to code reference, it will be run once for each request. + You can set parts of configuration that change per session this way. + For example, one might use the following code in a gitweb configuration + file ++ +-------------------------------------------------------------------------------- +our $per_request_config = sub { + $ENV{GL_USER} = $cgi->remote_user || "gitweb"; +}; +-------------------------------------------------------------------------------- ++ +If `$per_request_config` is not a code reference, it is interpreted as boolean +value. If it is true gitweb will process config files once per request, +and if it is false gitweb will process config files only once, each time it +is executed. True by default (set to 1). ++ +*NOTE*: `$my_url`, `$my_uri`, and `$base_url` are overwritten with their default +values before every request, so if you want to change them, be sure to set +this variable to true or a code reference effecting the desired changes. ++ +This variable matters only when using persistent web environments that +serve multiple requests using single gitweb instance, like mod_perl, +FastCGI or Plackup. + + +Other variables +~~~~~~~~~~~~~~~ +Usually you should not need to change (adjust) any of configuration +variables described below; they should be automatically set by gitweb to +correct value. + + +$version:: + Gitweb version, set automatically when creating gitweb.cgi from + gitweb.perl. You might want to modify it if you are running modified + gitweb, for example ++ +--------------------------------------------------- +our $version .= " with caching"; +--------------------------------------------------- ++ +if you run modified version of gitweb with caching support. This variable +is purely informational, used e.g. in the "generator" meta header in HTML +header. + +$my_url:: +$my_uri:: + Full URL and absolute URL of the gitweb script; + in earlier versions of gitweb you might have need to set those + variables, but now there should be no need to do it. See + `$per_request_config` if you need to set them still. + +$base_url:: + Base URL for relative URLs in pages generated by gitweb, + (e.g. `$logo`, `$favicon`, `@stylesheets` if they are relative URLs), + needed and used '<base href="$base_url">' only for URLs with nonempty + PATH_INFO. Usually gitweb sets its value correctly, + and there is no need to set this variable, e.g. to $my_uri or "/". + See `$per_request_config` if you need to override it anyway. + + +CONFIGURING GITWEB FEATURES +--------------------------- +Many gitweb features can be enabled (or disabled) and configured using the +`%feature` hash. Names of gitweb features are keys of this hash. + +Each `%feature` hash element is a hash reference and has the following +structure: +---------------------------------------------------------------------- +"<feature_name>" => { + "sub" => <feature-sub (subroutine)>, + "override" => <allow-override (boolean)>, + "default" => [ <options>... ] +}, +---------------------------------------------------------------------- +Some features cannot be overridden per project. For those +features the structure of appropriate `%feature` hash element has a simpler +form: +---------------------------------------------------------------------- +"<feature_name>" => { + "override" => 0, + "default" => [ <options>... ] +}, +---------------------------------------------------------------------- +As one can see it lacks the \'sub' element. + +The meaning of each part of feature configuration is described +below: + +default:: + List (array reference) of feature parameters (if there are any), + used also to toggle (enable or disable) given feature. ++ +Note that it is currently *always* an array reference, even if +feature doesn't accept any configuration parameters, and \'default' +is used only to turn it on or off. In such case you turn feature on +by setting this element to `[1]`, and torn it off by setting it to +`[0]`. See also the passage about the "blame" feature in the "Examples" +section. ++ +To disable features that accept parameters (are configurable), you +need to set this element to empty list i.e. `[]`. + +override:: + If this field has a true value then the given feature is + overriddable, which means that it can be configured + (or enabled/disabled) on a per-repository basis. ++ +Usually given "<feature>" is configurable via the `gitweb.<feature>` +config variable in the per-repository git configuration file. ++ +*Note* that no feature is overriddable by default. + +sub:: + Internal detail of implementation. What is important is that + if this field is not present then per-repository override for + given feature is not supported. ++ +You wouldn't need to ever change it in gitweb config file. + + +Features in `%feature` +~~~~~~~~~~~~~~~~~~~~~~ +The gitweb features that are configurable via `%feature` hash are listed +below. This should be a complete list, but ultimately the authoritative +and complete list is in gitweb.cgi source code, with features described +in the comments. + +blame:: + Enable the "blame" and "blame_incremental" blob views, showing for + each line the last commit that modified it; see linkgit:git-blame[1]. + This can be very CPU-intensive and is therefore disabled by default. ++ +This feature can be configured on a per-repository basis via +repository's `gitweb.blame` configuration variable (boolean). + +snapshot:: + Enable and configure the "snapshot" action, which allows user to + download a compressed archive of any tree or commit, as produced + by linkgit:git-archive[1] and possibly additionally compressed. + This can potentially generate high traffic if you have large project. ++ +The value of \'default' is a list of names of snapshot formats, +defined in `%known_snapshot_formats` hash, that you wish to offer. +Supported formats include "tgz", "tbz2", "txz" (gzip/bzip2/xz +compressed tar archive) and "zip"; please consult gitweb sources for +a definitive list. By default only "tgz" is offered. ++ +This feature can be configured on a per-repository basis via +repository's `gitweb.blame` configuration variable, which contains +a comma separated list of formats or "none" to disable snapshots. +Unknown values are ignored. + +grep:: + Enable grep search, which lists the files in currently selected + tree (directory) containing the given string; see linkgit:git-grep[1]. + This can be potentially CPU-intensive, of course. Enabled by default. ++ +This feature can be configured on a per-repository basis via +repository's `gitweb.grep` configuration variable (boolean). + +pickaxe:: + Enable the so called pickaxe search, which will list the commits + that introduced or removed a given string in a file. This can be + practical and quite faster alternative to "blame" action, but it is + still potentially CPU-intensive. Enabled by default. ++ +The pickaxe search is described in linkgit:git-log[1] (the +description of `-S<string>` option, which refers to pickaxe entry in +linkgit:gitdiffcore[7] for more details). ++ +This feature can be configured on a per-repository basis by setting +repository's `gitweb.pickaxe` configuration variable (boolean). + +show-sizes:: + Enable showing size of blobs (ordinary files) in a "tree" view, in a + separate column, similar to what `ls -l` does; see description of + `-l` option in linkgit:git-ls-tree[1] manpage. This costs a bit of + I/O. Enabled by default. ++ +This feature can be configured on a per-repository basis via +repository's `gitweb.showsizes` configuration variable (boolean). + +patches:: + Enable and configure "patches" view, which displays list of commits in email + (plain text) output format; see also linkgit:git-format-patch[1]. + The value is the maximum number of patches in a patchset generated + in "patches" view. Set the 'default' field to a list containing single + item of or to an empty list to disable patch view, or to a list + containing a single negative number to remove any limit. + Default value is 16. ++ +This feature can be configured on a per-repository basis via +repository's `gitweb.patches` configuration variable (integer). + +avatar:: + Avatar support. When this feature is enabled, views such as + "shortlog" or "commit" will display an avatar associated with + the email of each committer and author. ++ +Currently available providers are *"gravatar"* and *"picon"*. +Only one provider at a time can be selected ('default' is one element list). +If an unknown provider is specified, the feature is disabled. +*Note* that some providers might require extra Perl packages to be +installed; see 'gitweb/INSTALL' for more details. ++ +This feature can be configured on a per-repository basis via +repository's `gitweb.avatar` configuration variable. ++ +See also `%avatar_size` with pixel sizes for icons and avatars +("default" is used for one-line like "log" and "shortlog", "double" +is used for two-line like "commit", "commitdiff" or "tag"). If the +default font sizes or lineheights are changed (e.g. via adding extra +CSS stylesheet in `@stylesheets`), it may be appropriate to change +these values. + +highlight:: + Server-side syntax highlight support in "blob" view. It requires + `$highlight_bin` program to be available (see the description of + this variable in the "Configuration variables" section above), + and therefore is disabled by default. ++ +This feature can be configured on a per-repository basis via +repository's `gitweb.highlight` configuration variable (boolean). + +remote_heads:: + Enable displaying remote heads (remote-tracking branches) in the "heads" + list. In most cases the list of remote-tracking branches is an + unnecessary internal private detail, and this feature is therefore + disabled by default. linkgit:git-instaweb[1], which is usually used + to browse local repositories, enables and uses this feature. ++ +This feature can be configured on a per-repository basis via +repository's `gitweb.remote_heads` configuration variable (boolean). + + +The remaining features cannot be overridden on a per project basis. + +search:: + Enable text search, which will list the commits which match author, + committer or commit text to a given string; see the description of + `--author`, `--committer` and `--grep` options in linkgit:git-log[1] + manpage. Enabled by default. ++ +Project specific override is not supported. + +forks:: + If this feature is enabled, gitweb considers projects in + subdirectories of project root (basename) to be forks of existing + projects. For each project `$projname.git`, projects in the + `$projname/` directory and its subdirectories will not be + shown in the main projects list. Instead, a \'+' mark is shown + next to `$projname`, which links to a "forks" view that lists all + the forks (all projects in `$projname/` subdirectory). Additionally + a "forks" view for a project is linked from project summary page. ++ +If the project list is taken from a file (`$projects_list` points to a +file), forks are only recognized if they are listed after the main project +in that file. ++ +Project specific override is not supported. + +actions:: + Insert custom links to the action bar of all project pages. This + allows you to link to third-party scripts integrating into gitweb. ++ +The "default" value consists of a list of triplets in the form +`("<label>", "<link>", "<position>")` where "position" is the label +after which to insert the link, "link" is a format string where `%n` +expands to the project name, `%f` to the project path within the +filesystem (i.e. "$projectroot/$project"), `%h` to the current hash +(\'h' gitweb parameter) and `%b` to the current hash base +(\'hb' gitweb parameter); `%%` expands to \'%'. ++ +For example, at the time this page was written, the http://repo.or.cz[] +git hosting site set it to the following to enable graphical log +(using the third party tool *git-browser*): ++ +---------------------------------------------------------------------- +$feature{'actions'}{'default'} = + [ ('graphiclog', '/git-browser/by-commit.html?r=%n', 'summary')]; +---------------------------------------------------------------------- ++ +This adds a link titled "graphiclog" after the "summary" link, leading to +`git-browser` script, passing `r=<project>` as a query parameter. ++ +Project specific override is not supported. + +timed:: + Enable displaying how much time and how many git commands it took to + generate and display each page in the page footer (at the bottom of + page). For example the footer might contain: "This page took 6.53325 + seconds and 13 git commands to generate." Disabled by default. ++ +Project specific override is not supported. + +javascript-timezone:: + Enable and configure the ability to change a common timezone for dates + in gitweb output via JavaScript. Dates in gitweb output include + authordate and committerdate in "commit", "commitdiff" and "log" + views, and taggerdate in "tag" view. Enabled by default. ++ +The value is a list of three values: a default timezone (for if the client +hasn't selected some other timezone and saved it in a cookie), a name of cookie +where to store selected timezone, and a CSS class used to mark up +dates for manipulation. If you want to turn this feature off, set "default" +to empty list: `[]`. ++ +Typical gitweb config files will only change starting (default) timezone, +and leave other elements at their default values: ++ +--------------------------------------------------------------------------- +$feature{'javascript-timezone'}{'default'}[0] = "utc"; +--------------------------------------------------------------------------- ++ +The example configuration presented here is guaranteed to be backwards +and forward compatible. ++ +Timezone values can be "local" (for local timezone that browser uses), "utc" +(what gitweb uses when JavaScript or this feature is disabled), or numerical +timezones in the form of "+/-HHMM", such as "+0200". ++ +Project specific override is not supported. + + +EXAMPLES +-------- + +To enable blame, pickaxe search, and snapshot support (allowing "tar.gz" and +"zip" snapshots), while allowing individual projects to turn them off, put +the following in your GITWEB_CONFIG file: + + $feature{'blame'}{'default'} = [1]; + $feature{'blame'}{'override'} = 1; + + $feature{'pickaxe'}{'default'} = [1]; + $feature{'pickaxe'}{'override'} = 1; + + $feature{'snapshot'}{'default'} = ['zip', 'tgz']; + $feature{'snapshot'}{'override'} = 1; + +If you allow overriding for the snapshot feature, you can specify which +snapshot formats are globally disabled. You can also add any command line +options you want (such as setting the compression level). For instance, you +can disable Zip compressed snapshots and set *gzip*(1) to run at level 6 by +adding the following lines to your gitweb configuration file: + + $known_snapshot_formats{'zip'}{'disabled'} = 1; + $known_snapshot_formats{'tgz'}{'compressor'} = ['gzip','-6']; + +ENVIRONMENT +----------- +The location of per-instance and system-wide configuration files can be +overridden using the following environment variables: + +GITWEB_CONFIG:: + Sets location of per-instance configuration file. +GITWEB_CONFIG_SYSTEM:: + Sets location of fallback system-wide configuration file. + This file is read only if per-instance one does not exist. +GITWEB_CONFIG_COMMON:: + Sets location of common system-wide configuration file. + + +FILES +----- +gitweb_config.perl:: + This is default name of per-instance configuration file. The + format of this file is described above. +/etc/gitweb.conf:: + This is default name of fallback system-wide configuration + file. This file is used only if per-instance configuration + variable is not found. +/etc/gitweb-common.conf:: + This is default name of common system-wide configuration + file. + + +SEE ALSO +-------- +linkgit:gitweb[1], linkgit:git-instaweb[1] + +'gitweb/README', 'gitweb/INSTALL' + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/gitweb.txt b/Documentation/gitweb.txt new file mode 100644 index 0000000000..605a085326 --- /dev/null +++ b/Documentation/gitweb.txt @@ -0,0 +1,704 @@ +gitweb(1) +========= + +NAME +---- +gitweb - Git web interface (web frontend to Git repositories) + +SYNOPSIS +-------- +To get started with gitweb, run linkgit:git-instaweb[1] from a git repository. +This would configure and start your web server, and run web browser pointing to +gitweb. + + +DESCRIPTION +----------- +Gitweb provides a web interface to git repositories. It's features include: + +* Viewing multiple Git repositories with common root. +* Browsing every revision of the repository. +* Viewing the contents of files in the repository at any revision. +* Viewing the revision log of branches, history of files and directories, + see what was changed when, by who. +* Viewing the blame/annotation details of any file (if enabled). +* Generating RSS and Atom feeds of commits, for any branch. + The feeds are auto-discoverable in modern web browsers. +* Viewing everything that was changed in a revision, and step through + revisions one at a time, viewing the history of the repository. +* Finding commits which commit messages matches given search term. + +See http://git.kernel.org/?p=git/git.git;a=tree;f=gitweb[] or +http://repo.or.cz/w/git.git/tree/HEAD:/gitweb/[] for gitweb source code, +browsed using gitweb itself. + + +CONFIGURATION +------------- +Various aspects of gitweb's behavior can be controlled through the configuration +file 'gitweb_config.perl' or '/etc/gitweb.conf'. See the linkgit:gitweb.conf[5] +for details. + +Repositories +~~~~~~~~~~~~ +Gitweb can show information from one or more Git repositories. These +repositories have to be all on local filesystem, and have to share common +repository root, i.e. be all under a single parent repository (but see also +"Advanced web server setup" section, "Webserver configuration with multiple +projects' root" subsection). + +----------------------------------------------------------------------- +our $projectroot = '/path/to/parent/directory'; +----------------------------------------------------------------------- + +The default value for `$projectroot` is '/pub/git'. You can change it during +building gitweb via `GITWEB_PROJECTROOT` build configuration variable. + +By default all git repositories under `$projectroot` are visible and available +to gitweb. The list of projects is generated by default by scanning the +`$projectroot` directory for git repositories (for object databases to be +more exact; gitweb is not interested in a working area, and is best suited +to showing "bare" repositories). + +The name of repository in gitweb is path to it's `$GIT_DIR` (it's object +database) relative to `$projectroot`. Therefore the repository $repo can be +found at "$projectroot/$repo". + + +Projects list file format +~~~~~~~~~~~~~~~~~~~~~~~~~ +Instead of having gitweb find repositories by scanning filesystem +starting from $projectroot, you can provide a pre-generated list of +visible projects by setting `$projects_list` to point to a plain text +file with a list of projects (with some additional info). + +This file uses the following format: + +* One record (for project / repository) per line; does not support line +continuation (newline escaping). + +* Leading and trailing whitespace are ignored. + +* Whitespace separated fields; any run of whitespace can be used as field +separator (rules for Perl's "`split(" ", $line)`"). + +* Fields use modified URI encoding, defined in RFC 3986, section 2.1 +(Percent-Encoding), or rather "Query string encoding" (see +link:http://en.wikipedia.org/wiki/Query_string#URL_encoding[]), the difference +being that SP (" ") can be encoded as "{plus}" (and therefore "{plus}" has to be +also percent-encoded). ++ +Reserved characters are: "%" (used for encoding), "{plus}" (can be used to +encode SPACE), all whitespace characters as defined in Perl, including SP, +TAB and LF, (used to separate fields in a record). + +* Currently recognized fields are: +<repository path>:: + path to repository GIT_DIR, relative to `$projectroot` +<repository owner>:: + displayed as repository owner, preferably full name, or email, + or both + +You can generate the projects list index file using the project_index action +(the 'TXT' link on projects list page) directly from gitweb; see also +"Generating projects list using gitweb" section below. + +Example contents: +----------------------------------------------------------------------- +foo.git Joe+R+Hacker+<joe@example.com> +foo/bar.git O+W+Ner+<owner@example.org> +----------------------------------------------------------------------- + + +By default this file controls only which projects are *visible* on projects +list page (note that entries that do not point to correctly recognized git +repositories won't be displayed by gitweb). Even if a project is not +visible on projects list page, you can view it nevertheless by hand-crafting +a gitweb URL. By setting `$strict_export` configuration variable (see +linkgit:gitweb.conf[5]) to true value you can allow viewing only of +repositories also shown on the overview page (i.e. only projects explicitly +listed in projects list file will be accessible). + + +Generating projects list using gitweb +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We assume that GITWEB_CONFIG has its default Makefile value, namely +'gitweb_config.perl'. Put the following in 'gitweb_make_index.perl' file: +---------------------------------------------------------------------------- +read_config_file("gitweb_config.perl"); +$projects_list = $projectroot; +---------------------------------------------------------------------------- + +Then create the following script to get list of project in the format +suitable for GITWEB_LIST build configuration variable (or +`$projects_list` variable in gitweb config): + +---------------------------------------------------------------------------- +#!/bin/sh + +export GITWEB_CONFIG="gitweb_make_index.perl" +export GATEWAY_INTERFACE="CGI/1.1" +export HTTP_ACCEPT="*/*" +export REQUEST_METHOD="GET" +export QUERY_STRING="a=project_index" + +perl -- /var/www/cgi-bin/gitweb.cgi +---------------------------------------------------------------------------- + +Run this script and save its output to a file. This file could then be used +as projects list file, which means that you can set `$projects_list` to its +filename. + + +Controlling access to git repositories +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +By default all git repositories under `$projectroot` are visible and +available to gitweb. You can however configure how gitweb controls access +to repositories. + +* As described in "Projects list file format" section, you can control which +projects are *visible* by selectively including repositories in projects +list file, and setting `$projects_list` gitweb configuration variable to +point to it. With `$strict_export` set, projects list file can be used to +control which repositories are *available* as well. + +* You can configure gitweb to only list and allow viewing of the explicitly +exported repositories, via `$export_ok` variable in gitweb config file; see +linkgit:gitweb.conf[5] manpage. If it evaluates to true, gitweb shows +repositories only if this file named by `$export_ok` exists in its object +database (if directory has the magic file named `$export_ok`). ++ +For example linkgit:git-daemon[1] by default (unless `--export-all` option +is used) allows pulling only for those repositories that have +'git-daemon-export-ok' file. Adding ++ +-------------------------------------------------------------------------- +our $export_ok = "git-daemon-export-ok"; +-------------------------------------------------------------------------- ++ +makes gitweb show and allow access only to those repositories that can be +fetched from via `git://` protocol. + +* Finally, it is possible to specify an arbitrary perl subroutine that will +be called for each repository to determine if it can be exported. The +subroutine receives an absolute path to the project (repository) as its only +parameter (i.e. "$projectroot/$project"). ++ +For example, if you use mod_perl to run the script, and have dumb +HTTP protocol authentication configured for your repositories, you +can use the following hook to allow access only if the user is +authorized to read the files: ++ +-------------------------------------------------------------------------- +$export_auth_hook = sub { + use Apache2::SubRequest (); + use Apache2::Const -compile => qw(HTTP_OK); + my $path = "$_[0]/HEAD"; + my $r = Apache2::RequestUtil->request; + my $sub = $r->lookup_file($path); + return $sub->filename eq $path + && $sub->status == Apache2::Const::HTTP_OK; +}; +-------------------------------------------------------------------------- + + +Per-repository gitweb configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +You can configure individual repositories shown in gitweb by creating file +in the 'GIT_DIR' of git repository, or by setting some repo configuration +variable (in 'GIT_DIR/config', see linkgit:git-config[1]). + +You can use the following files in repository: + +README.html:: + A html file (HTML fragment) which is included on the gitweb project + "summary" page inside `<div>` block element. You can use it for longer + description of a project, to provide links (for example to project's + homepage), etc. This is recognized only if XSS prevention is off + (`$prevent_xss` is false, see linkgit:gitweb.conf[5]); a way to include + a README safely when XSS prevention is on may be worked out in the + future. + +description (or `gitweb.description`):: + Short (shortened to `$projects_list_description_width` in the projects + list page, which is 25 characters by default; see + linkgit:gitweb.conf[5]) single line description of a project (of a + repository). Plain text file; HTML will be escaped. By default set to ++ +------------------------------------------------------------------------------- +Unnamed repository; edit this file to name it for gitweb. +------------------------------------------------------------------------------- ++ +from the template during repository creation, usually installed in +'/usr/share/git-core/templates/'. You can use the `gitweb.description` repo +configuration variable, but the file takes precedence. + +category (or `gitweb.category`):: + Singe line category of a project, used to group projects if + `$projects_list_group_categories` is enabled. By default (file and + configuration variable absent), uncategorized projects are put in the + `$project_list_default_category` category. You can use the + `gitweb.category` repo configuration variable, but the file takes + precedence. ++ +The configuration variables `$projects_list_group_categories` and +`$project_list_default_category` are described in linkgit:gitweb.conf[5] + +cloneurl (or multiple-valued `gitweb.url`):: + File with repository URL (used for clone and fetch), one per line. + Displayed in the project summary page. You can use multiple-valued + `gitweb.url` repository configuration variable for that, but the file + takes precedence. ++ +This is per-repository enhancement / version of global prefix-based +`@git_base_url_list` gitweb configuration variable (see +linkgit:gitweb.conf[5]). + +gitweb.owner:: + You can use the `gitweb.owner` repository configuration variable to set + repository's owner. It is displayed in the project list and summary + page. ++ +If it's not set, filesystem directory's owner is used (via GECOS field, +i.e. real name field from *getpwuid*(3)) if `$projects_list` is unset +(gitweb scans `$projectroot` for repositories); if `$projects_list` +points to file with list of repositories, then project owner defaults to +value from this file for given repository. + +various `gitweb.*` config variables (in config):: + Read description of `%feature` hash for detailed list, and descriptions. + See also "Configuring gitweb features" section in linkgit:gitweb.conf[5] + + +ACTIONS, AND URLS +----------------- +Gitweb can use path_info (component) based URLs, or it can pass all necessary +information via query parameters. The typical gitweb URLs are broken down in to +five components: + +----------------------------------------------------------------------- +.../gitweb.cgi/<repo>/<action>/<revision>:/<path>?<arguments> +----------------------------------------------------------------------- + +repo:: + The repository the action will be performed on. ++ +All actions except for those that list all available projects, +in whatever form, require this parameter. + +action:: + The action that will be run. Defaults to 'projects_list' if repo + is not set, and to 'summary' otherwise. + +revision:: + Revision shown. Defaults to HEAD. + +path:: + The path within the <repository> that the action is performed on, + for those actions that require it. + +arguments:: + Any arguments that control the behaviour of the action. + +Some actions require or allow to specify two revisions, and sometimes even two +pathnames. In most general form such path_info (component) based gitweb URL +looks like this: + +----------------------------------------------------------------------- +.../gitweb.cgi/<repo>/<action>/<revision_from>:/<path_from>..<revision_to>:/<path_to>?<arguments> +----------------------------------------------------------------------- + + +Each action is implemented as a subroutine, and must be present in %actions +hash. Some actions are disabled by default, and must be turned on via feature +mechanism. For example to enable 'blame' view add the following to gitweb +configuration file: + +----------------------------------------------------------------------- +$feature{'blame'}{'default'} = [1]; +----------------------------------------------------------------------- + + +Actions: +~~~~~~~~ +The standard actions are: + +project_list:: + Lists the available Git repositories. This is the default command if no + repository is specified in the URL. + +summary:: + Displays summary about given repository. This is the default command if + no action is specified in URL, and only repository is specified. + +heads:: +remotes:: + Lists all local or all remote-tracking branches in given repository. ++ +The latter is not available by default, unless configured. + +tags:: + List all tags (lightweight and annotated) in given repository. + +blob:: +tree:: + Shows the files and directories in a given repository path, at given + revision. This is default command if no action is specified in the URL, + and path is given. + +blob_plain:: + Returns the raw data for the file in given repository, at given path and + revision. Links to this action are marked 'raw'. + +blobdiff:: + Shows the difference between two revisions of the same file. + +blame:: +blame_incremental:: + Shows the blame (also called annotation) information for a file. On a + per line basis it shows the revision in which that line was last changed + and the user that committed the change. The incremental version (which + if configured is used automatically when JavaScript is enabled) uses + Ajax to incrementally add blame info to the contents of given file. ++ +This action is disabled by default for performance reasons. + +commit:: +commitdiff:: + Shows information about a specific commit in a repository. The 'commit' + view shows information about commit in more detail, the 'commitdiff' + action shows changeset for given commit. + +patch:: + Returns the commit in plain text mail format, suitable for applying with + linkgit:git-am[1]. + +tag:: + Display specific annotated tag (tag object). + +log:: +shortlog:: + Shows log information (commit message or just commit subject) for a + given branch (starting from given revision). ++ +The 'shortlog' view is more compact; it shows one commit per line. + +history:: + Shows history of the file or directory in a given repository path, + starting from given revision (defaults to HEAD, i.e. default branch). ++ +This view is similar to 'shortlog' view. + +rss:: +atom:: + Generates an RSS (or Atom) feed of changes to repository. + + +WEBSERVER CONFIGURATION +----------------------- +This section explains how to configure some common webservers to run gitweb. In +all cases, `/path/to/gitweb` in the examples is the directory you ran installed +gitweb in, and contains `gitweb_config.perl`. + +If you've configured a web server that isn't listed here for gitweb, please send +in the instructions so they can be included in a future release. + +Apache as CGI +~~~~~~~~~~~~~ +Apache must be configured to support CGI scripts in the directory in +which gitweb is installed. Let's assume that it is '/var/www/cgi-bin' +directory. + +----------------------------------------------------------------------- +ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" + +<Directory "/var/www/cgi-bin"> + Options Indexes FollowSymlinks ExecCGI + AllowOverride None + Order allow,deny + Allow from all +</Directory> +----------------------------------------------------------------------- + +With that configuration the full path to browse repositories would be: + + http://server/cgi-bin/gitweb.cgi + +Apache with mod_perl, via ModPerl::Registry +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +You can use mod_perl with gitweb. You must install Apache::Registry +(for mod_perl 1.x) or ModPerl::Registry (for mod_perl 2.x) to enable +this support. + +Assuming that gitweb is installed to '/var/www/perl', the following +Apache configuration (for mod_perl 2.x) is suitable. + +----------------------------------------------------------------------- +Alias /perl "/var/www/perl" + +<Directory "/var/www/perl"> + SetHandler perl-script + PerlResponseHandler ModPerl::Registry + PerlOptions +ParseHeaders + Options Indexes FollowSymlinks +ExecCGI + AllowOverride None + Order allow,deny + Allow from all +</Directory> +----------------------------------------------------------------------- + +With that configuration the full path to browse repositories would be: + + http://server/perl/gitweb.cgi + +Apache with FastCGI +~~~~~~~~~~~~~~~~~~~ +Gitweb works with Apache and FastCGI. First you need to rename, copy +or symlink gitweb.cgi to gitweb.fcgi. Let's assume that gitweb is +installed in '/usr/share/gitweb' directory. The following Apache +configuration is suitable (UNTESTED!) + +----------------------------------------------------------------------- +FastCgiServer /usr/share/gitweb/gitweb.cgi +ScriptAlias /gitweb /usr/share/gitweb/gitweb.cgi + +Alias /gitweb/static /usr/share/gitweb/static +<Directory /usr/share/gitweb/static> + SetHandler default-handler +</Directory> +----------------------------------------------------------------------- + +With that configuration the full path to browse repositories would be: + + http://server/gitweb + + +ADVANCED WEB SERVER SETUP +------------------------- +All of those examples use request rewriting, and need `mod_rewrite` +(or equivalent; examples below are written for Apache). + +Single URL for gitweb and for fetching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you want to have one URL for both gitweb and your `http://` +repositories, you can configure Apache like this: + +----------------------------------------------------------------------- +<VirtualHost *:80> + ServerName git.example.org + DocumentRoot /pub/git + SetEnv GITWEB_CONFIG /etc/gitweb.conf + + # turning on mod rewrite + RewriteEngine on + + # make the front page an internal rewrite to the gitweb script + RewriteRule ^/$ /cgi-bin/gitweb.cgi + + # make access for "dumb clients" work + RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \ + /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] +</VirtualHost> +----------------------------------------------------------------------- + +The above configuration expects your public repositories to live under +'/pub/git' and will serve them as `http://git.domain.org/dir-under-pub-git`, +both as cloneable GIT URL and as browseable gitweb interface. If you then +start your linkgit:git-daemon[1] with `--base-path=/pub/git --export-all` +then you can even use the `git://` URL with exactly the same path. + +Setting the environment variable `GITWEB_CONFIG` will tell gitweb to use the +named file (i.e. in this example '/etc/gitweb.conf') as a configuration for +gitweb. You don't really need it in above example; it is required only if +your configuration file is in different place than built-in (during +compiling gitweb) 'gitweb_config.perl' or '/etc/gitweb.conf'. See +linkgit:gitweb.conf[5] for details, especially information about precedence +rules. + +If you use the rewrite rules from the example you *might* also need +something like the following in your gitweb configuration file +('/etc/gitweb.conf' following example): +---------------------------------------------------------------------------- +@stylesheets = ("/some/absolute/path/gitweb.css"); +$my_uri = "/"; +$home_link = "/"; +$per_request_config = 1; +---------------------------------------------------------------------------- +Nowadays though gitweb should create HTML base tag when needed (to set base +URI for relative links), so it should work automatically. + + +Webserver configuration with multiple projects' root +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you want to use gitweb with several project roots you can edit your +Apache virtual host and gitweb configuration files in the following way. + +The virtual host configuration (in Apache configuration file) should look +like this: +-------------------------------------------------------------------------- +<VirtualHost *:80> + ServerName git.example.org + DocumentRoot /pub/git + SetEnv GITWEB_CONFIG /etc/gitweb.conf + + # turning on mod rewrite + RewriteEngine on + + # make the front page an internal rewrite to the gitweb script + RewriteRule ^/$ /cgi-bin/gitweb.cgi [QSA,L,PT] + + # look for a public_git folder in unix users' home + # http://git.example.org/~<user>/ + RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \ + [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] + + # http://git.example.org/+<user>/ + #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \ + [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] + + # http://git.example.org/user/<user>/ + #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$ /cgi-bin/gitweb.cgi \ + [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] + + # defined list of project roots + RewriteRule ^/scm(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \ + [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT] + RewriteRule ^/var(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \ + [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT] + + # make access for "dumb clients" work + RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \ + /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] +</VirtualHost> +-------------------------------------------------------------------------- + +Here actual project root is passed to gitweb via `GITWEB_PROJECT_ROOT` +environment variable from a web server, so you need to put the following +line in gitweb configuration file ('/etc/gitweb.conf' in above example): +-------------------------------------------------------------------------- +$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git"; +-------------------------------------------------------------------------- +*Note* that this requires to be set for each request, so either +`$per_request_config` must be false, or the above must be put in code +referenced by `$per_request_config`; + +These configurations enable two things. First, each unix user (`<user>`) of +the server will be able to browse through gitweb git repositories found in +'~/public_git/' with the following url: + + http://git.example.org/~<user>/ + +If you do not want this feature on your server just remove the second +rewrite rule. + +If you already use `mod_userdir` in your virtual host or you don't want to +use the \'~' as first character, just comment or remove the second rewrite +rule, and uncomment one of the following according to what you want. + +Second, repositories found in '/pub/scm/' and '/var/git/' will be accessible +through `http://git.example.org/scm/` and `http://git.example.org/var/`. +You can add as many project roots as you want by adding rewrite rules like +the third and the fourth. + + +PATH_INFO usage +~~~~~~~~~~~~~~~ +If you enable PATH_INFO usage in gitweb by putting +---------------------------------------------------------------------------- +$feature{'pathinfo'}{'default'} = [1]; +---------------------------------------------------------------------------- +in your gitweb configuration file, it is possible to set up your server so +that it consumes and produces URLs in the form + + http://git.example.com/project.git/shortlog/sometag + +i.e. without 'gitweb.cgi' part, by using a configuration such as the +following. This configuration assumes that '/var/www/gitweb' is the +DocumentRoot of your webserver, contains the gitweb.cgi script and +complementary static files (stylesheet, favicon, JavaScript): + +---------------------------------------------------------------------------- +<VirtualHost *:80> + ServerAlias git.example.com + + DocumentRoot /var/www/gitweb + + <Directory /var/www/gitweb> + Options ExecCGI + AddHandler cgi-script cgi + + DirectoryIndex gitweb.cgi + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^.* /gitweb.cgi/$0 [L,PT] + </Directory> +</VirtualHost> +---------------------------------------------------------------------------- +The rewrite rule guarantees that existing static files will be properly +served, whereas any other URL will be passed to gitweb as PATH_INFO +parameter. + +*Notice* that in this case you don't need special settings for +`@stylesheets`, `$my_uri` and `$home_link`, but you lose "dumb client" +access to your project .git dirs (described in "Single URL for gitweb and +for fetching" section). A possible workaround for the latter is the +following: in your project root dir (e.g. '/pub/git') have the projects +named *without* a .git extension (e.g. '/pub/git/project' instead of +'/pub/git/project.git') and configure Apache as follows: +---------------------------------------------------------------------------- +<VirtualHost *:80> + ServerAlias git.example.com + + DocumentRoot /var/www/gitweb + + AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3 + <Directory /var/www/gitweb> + Options ExecCGI + AddHandler cgi-script cgi + + DirectoryIndex gitweb.cgi + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^.* /gitweb.cgi/$0 [L,PT] + </Directory> +</VirtualHost> +---------------------------------------------------------------------------- + +The additional AliasMatch makes it so that + + http://git.example.com/project.git + +will give raw access to the project's git dir (so that the project can be +cloned), while + + http://git.example.com/project + +will provide human-friendly gitweb access. + +This solution is not 100% bulletproof, in the sense that if some project has +a named ref (branch, tag) starting with 'git/', then paths such as + + http://git.example.com/project/command/abranch..git/abranch + +will fail with a 404 error. + + +BUGS +---- +Please report any bugs or feature requests to git@vger.kernel.org, +putting "gitweb" in the subject of email. + +SEE ALSO +-------- +linkgit:gitweb.conf[5], linkgit:git-instaweb[1] + +'gitweb/README', 'gitweb/INSTALL' + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/install-doc-quick.sh b/Documentation/install-doc-quick.sh index 35f440876e..327f69bcf5 100755 --- a/Documentation/install-doc-quick.sh +++ b/Documentation/install-doc-quick.sh @@ -1,31 +1,39 @@ #!/bin/sh -# This requires a branch named in $head -# (usually 'man' or 'html', provided by the git.git repository) -set -e -head="$1" -mandir="$2" -SUBDIRECTORY_OK=t -USAGE='<refname> <target directory>' -. "$(git --exec-path)"/git-sh-setup -cd_to_toplevel +# This requires git-manpages and/or git-htmldocs repositories -test -z "$mandir" && usage -if ! git rev-parse --verify "$head^0" >/dev/null; then - echo >&2 "head: $head does not exist in the current repository" - usage +repository=${1?repository} +destdir=${2?destination} + +head=master GIT_DIR= +for d in "$repository/.git" "$repository" +do + if GIT_DIR="$d" git rev-parse refs/heads/master >/dev/null 2>&1 + then + GIT_DIR="$d" + export GIT_DIR + break + fi +done + +if test -z "$GIT_DIR" +then + echo >&2 "Neither $repository nor $repository/.git is a repository" + exit 1 fi -GIT_INDEX_FILE=`pwd`/.quick-doc.index -export GIT_INDEX_FILE +GIT_WORK_TREE=$(pwd) +GIT_INDEX_FILE=$(pwd)/.quick-doc.$$ +export GIT_INDEX_FILE GIT_WORK_TREE rm -f "$GIT_INDEX_FILE" trap 'rm -f "$GIT_INDEX_FILE"' 0 git read-tree $head -git checkout-index -a -f --prefix="$mandir"/ +git checkout-index -a -f --prefix="$destdir"/ -if test -n "$GZ"; then +if test -n "$GZ" +then git ls-tree -r --name-only $head | - xargs printf "$mandir/%s\n" | + xargs printf "$destdir/%s\n" | xargs gzip -f fi rm -f "$GIT_INDEX_FILE" diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index b613d4ed08..1a5c12e317 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -7,6 +7,11 @@ With --no-commit perform the merge but pretend the merge failed and do not autocommit, to give the user a chance to inspect and further tweak the merge result before committing. +--edit:: +-e:: + Invoke editor before committing successful merge to further + edit the default merge message. + --ff:: --no-ff:: Do not generate a merge commit if the merge resolved as diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt new file mode 100644 index 0000000000..5747f442f2 --- /dev/null +++ b/Documentation/sequencer.txt @@ -0,0 +1,12 @@ +--continue:: + Continue the operation in progress using the information in + '.git/sequencer'. Can be used to continue after resolving + conflicts in a failed cherry-pick or revert. + +--quit:: + Forget about the current operation in progress. Can be used + to clear the sequencer state after a failed cherry-pick or + revert. + +--abort:: + Cancel the operation and return to the pre-sequence state. diff --git a/Documentation/technical/api-argv-array.txt b/Documentation/technical/api-argv-array.txt new file mode 100644 index 0000000000..49b3d52952 --- /dev/null +++ b/Documentation/technical/api-argv-array.txt @@ -0,0 +1,46 @@ +argv-array API +============== + +The argv-array API allows one to dynamically build and store +NULL-terminated lists. An argv-array maintains the invariant that the +`argv` member always points to a non-NULL array, and that the array is +always NULL-terminated at the element pointed to by `argv[argc]`. This +makes the result suitable for passing to functions expecting to receive +argv from main(), or the link:api-run-command.html[run-command API]. + +The link:api-string-list.html[string-list API] is similar, but cannot be +used for these purposes; instead of storing a straight string pointer, +it contains an item structure with a `util` field that is not compatible +with the traditional argv interface. + +Each `argv_array` manages its own memory. Any strings pushed into the +array are duplicated, and all memory is freed by argv_array_clear(). + +Data Structures +--------------- + +`struct argv_array`:: + + A single array. This should be initialized by assignment from + `ARGV_ARRAY_INIT`, or by calling `argv_array_init`. The `argv` + member contains the actual array; the `argc` member contains the + number of elements in the array, not including the terminating + NULL. + +Functions +--------- + +`argv_array_init`:: + Initialize an array. This is no different than assigning from + `ARGV_ARRAY_INIT`. + +`argv_array_push`:: + Push a copy of a string onto the end of the array. + +`argv_array_pushf`:: + Format a string and push it onto the end of the array. This is a + convenience wrapper combining `strbuf_addf` and `argv_array_push`. + +`argv_array_clear`:: + Free all memory associated with the array and return it to the + initial, empty state. diff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt new file mode 100644 index 0000000000..21ca6a2553 --- /dev/null +++ b/Documentation/technical/api-credentials.txt @@ -0,0 +1,245 @@ +credentials API +=============== + +The credentials API provides an abstracted way of gathering username and +password credentials from the user (even though credentials in the wider +world can take many forms, in this document the word "credential" always +refers to a username and password pair). + +Data Structures +--------------- + +`struct credential`:: + + This struct represents a single username/password combination + along with any associated context. All string fields should be + heap-allocated (or NULL if they are not known or not applicable). + The meaning of the individual context fields is the same as + their counterparts in the helper protocol; see the section below + for a description of each field. ++ +The `helpers` member of the struct is a `string_list` of helpers. Each +string specifies an external helper which will be run, in order, to +either acquire or store credentials. See the section on credential +helpers below. ++ +This struct should always be initialized with `CREDENTIAL_INIT` or +`credential_init`. + + +Functions +--------- + +`credential_init`:: + + Initialize a credential structure, setting all fields to empty. + +`credential_clear`:: + + Free any resources associated with the credential structure, + returning it to a pristine initialized state. + +`credential_fill`:: + + Instruct the credential subsystem to fill the username and + password fields of the passed credential struct by first + consulting helpers, then asking the user. After this function + returns, the username and password fields of the credential are + guaranteed to be non-NULL. If an error occurs, the function will + die(). + +`credential_reject`:: + + Inform the credential subsystem that the provided credentials + have been rejected. This will cause the credential subsystem to + notify any helpers of the rejection (which allows them, for + example, to purge the invalid credentials from storage). It + will also free() the username and password fields of the + credential and set them to NULL (readying the credential for + another call to `credential_fill`). Any errors from helpers are + ignored. + +`credential_approve`:: + + Inform the credential subsystem that the provided credentials + were successfully used for authentication. This will cause the + credential subsystem to notify any helpers of the approval, so + that they may store the result to be used again. Any errors + from helpers are ignored. + +`credential_from_url`:: + + Parse a URL into broken-down credential fields. + +Example +------- + +The example below shows how the functions of the credential API could be +used to login to a fictitious "foo" service on a remote host: + +----------------------------------------------------------------------- +int foo_login(struct foo_connection *f) +{ + int status; + /* + * Create a credential with some context; we don't yet know the + * username or password. + */ + + struct credential c = CREDENTIAL_INIT; + c.protocol = xstrdup("foo"); + c.host = xstrdup(f->hostname); + + /* + * Fill in the username and password fields by contacting + * helpers and/or asking the user. The function will die if it + * fails. + */ + credential_fill(&c); + + /* + * Otherwise, we have a username and password. Try to use it. + */ + status = send_foo_login(f, c.username, c.password); + switch (status) { + case FOO_OK: + /* It worked. Store the credential for later use. */ + credential_accept(&c); + break; + case FOO_BAD_LOGIN: + /* Erase the credential from storage so we don't try it + * again. */ + credential_reject(&c); + break; + default: + /* + * Some other error occured. We don't know if the + * credential is good or bad, so report nothing to the + * credential subsystem. + */ + } + + /* Free any associated resources. */ + credential_clear(&c); + + return status; +} +----------------------------------------------------------------------- + + +Credential Helpers +------------------ + +Credential helpers are programs executed by git to fetch or save +credentials from and to long-term storage (where "long-term" is simply +longer than a single git process; e.g., credentials may be stored +in-memory for a few minutes, or indefinitely on disk). + +Each helper is specified by a single string. The string is transformed +by git into a command to be executed using these rules: + + 1. If the helper string begins with "!", it is considered a shell + snippet, and everything after the "!" becomes the command. + + 2. Otherwise, if the helper string begins with an absolute path, the + verbatim helper string becomes the command. + + 3. Otherwise, the string "git credential-" is prepended to the helper + string, and the result becomes the command. + +The resulting command then has an "operation" argument appended to it +(see below for details), and the result is executed by the shell. + +Here are some example specifications: + +---------------------------------------------------- +# run "git credential-foo" +foo + +# same as above, but pass an argument to the helper +foo --bar=baz + +# the arguments are parsed by the shell, so use shell +# quoting if necessary +foo --bar="whitespace arg" + +# you can also use an absolute path, which will not use the git wrapper +/path/to/my/helper --with-arguments + +# or you can specify your own shell snippet +!f() { echo "password=`cat $HOME/.secret`"; }; f +---------------------------------------------------- + +Generally speaking, rule (3) above is the simplest for users to specify. +Authors of credential helpers should make an effort to assist their +users by naming their program "git-credential-$NAME", and putting it in +the $PATH or $GIT_EXEC_PATH during installation, which will allow a user +to enable it with `git config credential.helper $NAME`. + +When a helper is executed, it will have one "operation" argument +appended to its command line, which is one of: + +`get`:: + + Return a matching credential, if any exists. + +`store`:: + + Store the credential, if applicable to the helper. + +`erase`:: + + Remove a matching credential, if any, from the helper's storage. + +The details of the credential will be provided on the helper's stdin +stream. The credential is split into a set of named attributes. +Attributes are provided to the helper, one per line. Each attribute is +specified by a key-value pair, separated by an `=` (equals) sign, +followed by a newline. The key may contain any bytes except `=`, +newline, or NUL. The value may contain any bytes except newline or NUL. +In both cases, all bytes are treated as-is (i.e., there is no quoting, +and one cannot transmit a value with newline or NUL in it). The list of +attributes is terminated by a blank line or end-of-file. + +Git will send the following attributes (but may not send all of +them for a given credential; for example, a `host` attribute makes no +sense when dealing with a non-network protocol): + +`protocol`:: + + The protocol over which the credential will be used (e.g., + `https`). + +`host`:: + + The remote hostname for a network credential. + +`path`:: + + The path with which the credential will be used. E.g., for + accessing a remote https repository, this will be the + repository's path on the server. + +`username`:: + + The credential's username, if we already have one (e.g., from a + URL, from the user, or from a previously run helper). + +`password`:: + + The credential's password, if we are asking it to be stored. + +For a `get` operation, the helper should produce a list of attributes +on stdout in the same format. A helper is free to produce a subset, or +even no values at all if it has nothing useful to provide. Any provided +attributes will overwrite those already known about by git. + +For a `store` or `erase` operation, the helper's output is ignored. +If it fails to perform the requested operation, it may complain to +stderr to inform the user. If it does not support the requested +operation (e.g., a read-only store), it should silently ignore the +request. + +If a helper receives any other operation, it should silently ignore the +request. This leaves room for future operations to be added (older +helpers will just ignore the new requests). diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index f6a4a361bd..4b92514f60 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -135,9 +135,14 @@ There are some macros to easily define options: describes the group or an empty string. Start the description with an upper-case letter. -`OPT_BOOLEAN(short, long, &int_var, description)`:: - Introduce a boolean option. - `int_var` is incremented on each use. +`OPT_BOOL(short, long, &int_var, description)`:: + Introduce a boolean option. `int_var` is set to one with + `--option` and set to zero with `--no-option`. + +`OPT_COUNTUP(short, long, &int_var, description)`:: + Introduce a count-up option. + `int_var` is incremented on each use of `--option`, and + reset to zero with `--no-option`. `OPT_BIT(short, long, &int_var, description, mask)`:: Introduce a boolean option. @@ -148,8 +153,9 @@ There are some macros to easily define options: 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`. + Introduce an integer option. + `int_var` is set to `integer` with `--option`, and + reset to zero with `--no-option`. `OPT_SET_PTR(short, long, &ptr_var, description, ptr)`:: Introduce a boolean option. @@ -198,6 +204,11 @@ There are some macros to easily define options: "auto", set `int_var` to 1 if stdout is a tty or a pager, 0 otherwise. +`OPT_NOOP_NOARG(short, long)`:: + Introduce an option that has no effect and takes no arguments. + Use it to hide deprecated options that are still to be recognized + and ignored silently. + The last element of the array must be `OPT_END()`. diff --git a/Documentation/technical/api-sha1-array.txt b/Documentation/technical/api-sha1-array.txt new file mode 100644 index 0000000000..4a4bae8109 --- /dev/null +++ b/Documentation/technical/api-sha1-array.txt @@ -0,0 +1,79 @@ +sha1-array API +============== + +The sha1-array API provides storage and manipulation of sets of SHA1 +identifiers. The emphasis is on storage and processing efficiency, +making them suitable for large lists. Note that the ordering of items is +not preserved over some operations. + +Data Structures +--------------- + +`struct sha1_array`:: + + A single array of SHA1 hashes. This should be initialized by + assignment from `SHA1_ARRAY_INIT`. The `sha1` member contains + the actual data. The `nr` member contains the number of items in + the set. The `alloc` and `sorted` members are used internally, + and should not be needed by API callers. + +Functions +--------- + +`sha1_array_append`:: + Add an item to the set. The sha1 will be placed at the end of + the array (but note that some operations below may lose this + ordering). + +`sha1_array_sort`:: + Sort the elements in the array. + +`sha1_array_lookup`:: + Perform a binary search of the array for a specific sha1. + If found, returns the offset (in number of elements) of the + sha1. If not found, returns a negative integer. If the array is + not sorted, this function has the side effect of sorting it. + +`sha1_array_clear`:: + Free all memory associated with the array and return it to the + initial, empty state. + +`sha1_array_for_each_unique`:: + Efficiently iterate over each unique element of the list, + executing the callback function for each one. If the array is + not sorted, this function has the side effect of sorting it. + +Examples +-------- + +----------------------------------------- +void print_callback(const unsigned char sha1[20], + void *data) +{ + printf("%s\n", sha1_to_hex(sha1)); +} + +void some_func(void) +{ + struct sha1_array hashes = SHA1_ARRAY_INIT; + unsigned char sha1[20]; + + /* Read objects into our set */ + while (read_object_from_stdin(sha1)) + sha1_array_append(&hashes, sha1); + + /* Check if some objects are in our set */ + while (read_object_from_stdin(sha1)) { + if (sha1_array_lookup(&hashes, sha1) >= 0) + printf("it's in there!\n"); + + /* + * Print the unique set of objects. We could also have + * avoided adding duplicate objects in the first place, + * but we would end up re-sorting the array repeatedly. + * Instead, this will sort once and then skip duplicates + * in linear time. + */ + sha1_array_for_each_unique(&hashes, print_callback, NULL); +} +----------------------------------------- diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index a7004c63e7..546980c0a4 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -60,6 +60,13 @@ process on the server side over the Git protocol is this: "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" | nc -v example.com 9418 +If the server refuses the request for some reasons, it could abort +gracefully with an error message. + +---- + error-line = PKT-LINE("ERR" SP explanation-text) +---- + SSH Transport ------------- diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index f1dc5faead..e18a30a16a 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.7 +DEF_VER=v1.7.8.GIT LF=' ' @@ -106,6 +106,18 @@ Issues of note: history graphically, and in git-gui. If you don't want gitk or git-gui, you can use NO_TCLTK. + - A gettext library is used by default for localizing Git. The + primary target is GNU libintl, but the Solaris gettext + implementation also works. + + We need a gettext.h on the system for C code, gettext.sh (or + Solaris gettext(1)) for shell scripts, and libintl-perl for Perl + programs. + + Set NO_GETTEXT to disable localization support and make Git only + use English. Under autoconf the configure script will do this + automatically if it can't find libintl on the system. + - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not have all the libraries/tools needed, or you may have @@ -139,34 +151,11 @@ Issues of note: uses some compatibility wrappers to work on AsciiDoc 8. If you have AsciiDoc 7, try "make ASCIIDOC7=YesPlease". - Alternatively, pre-formatted documentation is available in - "html" and "man" branches of the git repository itself. For - example, you could: - - $ mkdir manual && cd manual - $ git init - $ git fetch-pack git://git.kernel.org/pub/scm/git/git.git man html | - while read a b - do - echo $a >.git/$b - done - $ cp .git/refs/heads/man .git/refs/heads/master - $ git checkout - - to checkout the pre-built man pages. Also in this repository: - - $ git checkout html - - would instead give you a copy of what you see at: - - http://www.kernel.org/pub/software/scm/git/docs/ - There are also "make quick-install-doc", "make quick-install-man" and "make quick-install-html" which install preformatted man pages - and html documentation. - This does not require asciidoc/xmlto, but it only works from within - a cloned checkout of git.git with these two extra branches, and will - not work for the maintainer for obvious chicken-and-egg reasons. + and html documentation. To use these build targets, you need to + clone two separate git-htmldocs and git-manpages repositories next + to the clone of git itself. It has been reported that docbook-xsl version 1.72 and 1.73 are buggy; 1.72 misformats manual pages for callouts, and 1.73 needs @@ -43,6 +43,22 @@ all:: # Define EXPATDIR=/foo/bar if your expat header and library files are in # /foo/bar/include and /foo/bar/lib directories. # +# Define NO_GETTEXT if you don't want Git output to be translated. +# A translated Git requires GNU libintl or another gettext implementation, +# plus libintl-perl at runtime. +# +# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't +# trust the langinfo.h's nl_langinfo(CODESET) function to return the +# current character set. GNU and Solaris have a nl_langinfo(CODESET), +# FreeBSD can use either, but MinGW and some others need to use +# libcharset.h's locale_charset() instead. +# +# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't +# need -lintl when linking. +# +# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt +# doesn't support GNU extensions like --check and --statistics +# # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH # it specifies. # @@ -57,8 +73,8 @@ all:: # # Define NO_STRLCPY if you don't have strlcpy. # -# Define NO_STRTOUMAX if you don't have strtoumax in the C library. -# If your compiler also does not support long long or does not have +# Define NO_STRTOUMAX if you don't have both strtoimax and strtoumax in the +# C library. If your compiler also does not support long long or does not have # strtoull, define NO_STRTOULL. # # Define NO_SETENV if you don't have setenv in the C library. @@ -143,6 +159,8 @@ all:: # # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). # +# Define NO_UNIX_SOCKETS if your system does not offer unix sockets. +# # Define NO_SOCKADDR_STORAGE if your platform does not have struct # sockaddr_storage. # @@ -227,6 +245,9 @@ all:: # # Define NO_REGEX if you have no or inferior regex support in your C library. # +# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the +# user. +# # Define GETTEXT_POISON if you are debugging the choice of strings marked # for translation. In a GETTEXT_POISON build, you can turn all strings marked # for translation into gibberish by setting the GIT_GETTEXT_POISON variable @@ -250,9 +271,11 @@ all:: # DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR', # DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork' # -# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option -# and you want to avoid rebuilding objects when an unrelated header file -# changes. +# Define COMPUTE_HEADER_DEPENDENCIES to "yes" if you want dependencies on +# header files to be automatically computed, to avoid rebuilding objects when +# an unrelated header file changes. Define it to "no" to use the hard-coded +# dependency rules. The default is "auto", which means to use computed header +# dependencies if your compiler is detected to support it. # # Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded # dependency rules. @@ -305,6 +328,7 @@ gitexecdir = libexec/git-core mergetoolsdir = $(gitexecdir)/mergetools sharedir = $(prefix)/share gitwebdir = $(sharedir)/gitweb +localedir = $(sharedir)/locale template_dir = share/git-core/templates htmldir = share/doc/git-doc ETC_GITCONFIG = $(sysconfdir)/gitconfig @@ -313,7 +337,7 @@ lib = lib # DESTDIR= pathsep = : -export prefix bindir sharedir sysconfdir gitwebdir +export prefix bindir sharedir sysconfdir gitwebdir localedir CC = gcc AR = ar @@ -326,6 +350,7 @@ RPMBUILD = rpmbuild TCL_PATH = tclsh TCLTK_PATH = wish XGETTEXT = xgettext +MSGFMT = msgfmt PTHREAD_LIBS = -lpthread PTHREAD_CFLAGS = GCOV = gcov @@ -425,14 +450,17 @@ PROGRAM_OBJS += show-index.o PROGRAM_OBJS += upload-pack.o PROGRAM_OBJS += http-backend.o PROGRAM_OBJS += sh-i18n--envsubst.o +PROGRAM_OBJS += credential-store.o PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS)) TEST_PROGRAMS_NEED_X += test-chmtime +TEST_PROGRAMS_NEED_X += test-credential TEST_PROGRAMS_NEED_X += test-ctype TEST_PROGRAMS_NEED_X += test-date TEST_PROGRAMS_NEED_X += test-delta TEST_PROGRAMS_NEED_X += test-dump-cache-tree +TEST_PROGRAMS_NEED_X += test-scrap-cache-tree TEST_PROGRAMS_NEED_X += test-genrandom TEST_PROGRAMS_NEED_X += test-index-version TEST_PROGRAMS_NEED_X += test-line-buffer @@ -505,9 +533,11 @@ VCSSVN_LIB=vcs-svn/lib.a LIB_H += advice.h LIB_H += archive.h +LIB_H += argv-array.h LIB_H += attr.h LIB_H += blob.h LIB_H += builtin.h +LIB_H += bulk-checkin.h LIB_H += cache.h LIB_H += cache-tree.h LIB_H += color.h @@ -516,10 +546,14 @@ LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h LIB_H += compat/obstack.h +LIB_H += compat/terminal.h LIB_H += compat/win32/pthread.h LIB_H += compat/win32/syslog.h -LIB_H += compat/win32/sys/poll.h +LIB_H += compat/win32/poll.h LIB_H += compat/win32/dirent.h +LIB_H += connected.h +LIB_H += convert.h +LIB_H += credential.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -527,9 +561,11 @@ LIB_H += diffcore.h LIB_H += diff.h LIB_H += dir.h LIB_H += exec_cmd.h +LIB_H += fmt-merge-msg.h LIB_H += fsck.h LIB_H += gettext.h LIB_H += git-compat-util.h +LIB_H += gpg-interface.h LIB_H += graph.h LIB_H += grep.h LIB_H += hash.h @@ -553,6 +589,7 @@ LIB_H += parse-options.h LIB_H += patch-ids.h LIB_H += pkt-line.h LIB_H += progress.h +LIB_H += prompt.h LIB_H += quote.h LIB_H += reflog-walk.h LIB_H += refs.h @@ -561,6 +598,7 @@ LIB_H += rerere.h LIB_H += resolve-undo.h LIB_H += revision.h LIB_H += run-command.h +LIB_H += sequencer.h LIB_H += sha1-array.h LIB_H += sha1-lookup.h LIB_H += sideband.h @@ -586,21 +624,26 @@ LIB_OBJS += alloc.o LIB_OBJS += archive.o LIB_OBJS += archive-tar.o LIB_OBJS += archive-zip.o +LIB_OBJS += argv-array.o LIB_OBJS += attr.o LIB_OBJS += base85.o LIB_OBJS += bisect.o LIB_OBJS += blob.o LIB_OBJS += branch.o +LIB_OBJS += bulk-checkin.o LIB_OBJS += bundle.o LIB_OBJS += cache-tree.o LIB_OBJS += color.o LIB_OBJS += combine-diff.o LIB_OBJS += commit.o LIB_OBJS += compat/obstack.o +LIB_OBJS += compat/terminal.o LIB_OBJS += config.o LIB_OBJS += connect.o +LIB_OBJS += connected.o LIB_OBJS += convert.o LIB_OBJS += copy.o +LIB_OBJS += credential.o LIB_OBJS += csum-file.o LIB_OBJS += ctype.o LIB_OBJS += date.o @@ -620,6 +663,8 @@ LIB_OBJS += entry.o LIB_OBJS += environment.o LIB_OBJS += exec_cmd.o LIB_OBJS += fsck.o +LIB_OBJS += gpg-interface.o +LIB_OBJS += gettext.o LIB_OBJS += graph.o LIB_OBJS += grep.o LIB_OBJS += hash.o @@ -655,6 +700,7 @@ LIB_OBJS += pkt-line.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += progress.o +LIB_OBJS += prompt.o LIB_OBJS += quote.o LIB_OBJS += reachable.o LIB_OBJS += read-cache.o @@ -668,6 +714,7 @@ LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o LIB_OBJS += setup.o +LIB_OBJS += sequencer.o LIB_OBJS += sha1-array.o LIB_OBJS += sha1-lookup.o LIB_OBJS += sha1_file.o @@ -815,11 +862,15 @@ ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease + HAVE_DEV_TTY = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease + DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease endif ifeq ($(uname_S),UnixWare) CC = cc @@ -874,6 +925,7 @@ ifeq ($(uname_S),Darwin) endif NO_MEMMEM = YesPlease USE_ST_TIMESPEC = YesPlease + HAVE_DEV_TTY = YesPlease endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -886,6 +938,7 @@ ifeq ($(uname_S),SunOS) NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease NO_FNMATCH_CASEFOLD = YesPlease + NO_MSGFMT_EXTENDED_OPTIONS = YesPlease ifeq ($(uname_R),5.6) SOCKLEN_T = int NO_HSTRERROR = YesPlease @@ -1009,6 +1062,7 @@ ifeq ($(uname_S),GNU) NO_STRLCPY=YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease endif ifeq ($(uname_S),IRIX) NO_SETENV = YesPlease @@ -1085,8 +1139,10 @@ ifeq ($(uname_S),Windows) NO_PREAD = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease + NO_SYS_POLL_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease + NO_UNIX_SOCKETS = YesPlease NO_SETENV = YesPlease NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease @@ -1123,7 +1179,7 @@ ifeq ($(uname_S),Windows) BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE COMPAT_OBJS = compat/msvc.o compat/winansi.o \ compat/win32/pthread.o compat/win32/syslog.o \ - compat/win32/sys/poll.o compat/win32/dirent.o + compat/win32/poll.o compat/win32/dirent.o COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib @@ -1178,7 +1234,9 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_PREAD = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease + NO_SYS_POLL_H = YesPlease NO_SYMLINK_HEAD = YesPlease + NO_UNIX_SOCKETS = YesPlease NO_SETENV = YesPlease NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease @@ -1211,7 +1269,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/winansi.o \ compat/win32/pthread.o compat/win32/syslog.o \ - compat/win32/sys/poll.o compat/win32/dirent.o + compat/win32/poll.o compat/win32/dirent.o EXTLIBS += -lws2_32 PTHREAD_LIBS = X = .exe @@ -1223,6 +1281,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) EXTLIBS += /mingw/lib/libz.a NO_R_TO_GCC_LINKER = YesPlease INTERNAL_QSORT = YesPlease + HAVE_LIBCHARSET_H = YesPlease else NO_CURL = YesPlease endif @@ -1240,12 +1299,32 @@ endif endif ifdef CHECK_HEADER_DEPENDENCIES -COMPUTE_HEADER_DEPENDENCIES = +COMPUTE_HEADER_DEPENDENCIES = no USE_COMPUTED_HEADER_DEPENDENCIES = endif -ifdef COMPUTE_HEADER_DEPENDENCIES +ifndef COMPUTE_HEADER_DEPENDENCIES +COMPUTE_HEADER_DEPENDENCIES = auto +endif + +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto) +dep_check = $(shell $(CC) $(ALL_CFLAGS) \ + -c -MF /dev/null -MMD -MP -x c /dev/null -o /dev/null 2>&1; \ + echo $$?) +ifeq ($(dep_check),0) +override COMPUTE_HEADER_DEPENDENCIES = yes +else +override COMPUTE_HEADER_DEPENDENCIES = no +endif +endif + +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes) USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease +else +ifneq ($(COMPUTE_HEADER_DEPENDENCIES),no) +$(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \ +(not "$(COMPUTE_HEADER_DEPENDENCIES)")) +endif endif ifdef SANE_TOOL_PATH @@ -1391,6 +1470,11 @@ endif ifdef NEEDS_LIBGEN EXTLIBS += -lgen endif +ifndef NO_GETTEXT +ifndef LIBC_CONTAINS_LIBINTL + EXTLIBS += -lintl +endif +endif ifdef NEEDS_SOCKET EXTLIBS += -lsocket endif @@ -1433,9 +1517,11 @@ ifdef NO_SYMLINK_HEAD BASIC_CFLAGS += -DNO_SYMLINK_HEAD endif ifdef GETTEXT_POISON - LIB_OBJS += gettext.o BASIC_CFLAGS += -DGETTEXT_POISON endif +ifdef NO_GETTEXT + BASIC_CFLAGS += -DNO_GETTEXT +endif ifdef NO_STRCASESTR COMPAT_CFLAGS += -DNO_STRCASESTR COMPAT_OBJS += compat/strcasestr.o @@ -1446,7 +1532,7 @@ ifdef NO_STRLCPY endif ifdef NO_STRTOUMAX COMPAT_CFLAGS += -DNO_STRTOUMAX - COMPAT_OBJS += compat/strtoumax.o + COMPAT_OBJS += compat/strtoumax.o compat/strtoimax.o endif ifdef NO_STRTOULL COMPAT_CFLAGS += -DNO_STRTOULL @@ -1536,6 +1622,12 @@ ifdef NO_INET_PTON LIB_OBJS += compat/inet_pton.o BASIC_CFLAGS += -DNO_INET_PTON endif +ifndef NO_UNIX_SOCKETS + LIB_OBJS += unix-socket.o + LIB_H += unix-socket.h + PROGRAM_OBJS += credential-cache.o + PROGRAM_OBJS += credential-cache--daemon.o +endif ifdef NO_ICONV BASIC_CFLAGS += -DNO_ICONV @@ -1598,6 +1690,14 @@ ifdef HAVE_PATHS_H BASIC_CFLAGS += -DHAVE_PATHS_H endif +ifdef HAVE_LIBCHARSET_H + BASIC_CFLAGS += -DHAVE_LIBCHARSET_H +endif + +ifdef HAVE_DEV_TTY + BASIC_CFLAGS += -DHAVE_DEV_TTY +endif + ifdef DIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS endif @@ -1618,6 +1718,10 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT export GIT_TEST_CMP_USE_COPIED_CONTEXT endif +ifndef NO_MSGFMT_EXTENDED_OPTIONS + MSGFMT += --check --statistics +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks endif @@ -1648,6 +1752,7 @@ ifndef V QUIET_GEN = @echo ' ' GEN $@; QUIET_LNCP = @echo ' ' LN/CP $@; QUIET_XGETTEXT = @echo ' ' XGETTEXT $@; + QUIET_MSGFMT = @echo ' ' MSGFMT $@; QUIET_GCOV = @echo ' ' GCOV $@; QUIET_SP = @echo ' ' SP $<; QUIET_SUBDIR0 = +@subdir= @@ -1674,6 +1779,7 @@ bindir_SQ = $(subst ','\'',$(bindir)) bindir_relative_SQ = $(subst ','\'',$(bindir_relative)) mandir_SQ = $(subst ','\'',$(mandir)) infodir_SQ = $(subst ','\'',$(infodir)) +localedir_SQ = $(subst ','\'',$(localedir)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) template_dir_SQ = $(subst ','\'',$(template_dir)) htmldir_SQ = $(subst ','\'',$(htmldir)) @@ -1729,7 +1835,7 @@ ifndef NO_TCLTK $(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 + $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all endif ifndef NO_PYTHON $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all @@ -1779,6 +1885,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ -e 's|@@DIFF@@|$(DIFF_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ -e $(BROKEN_PATH_FIX) \ $@.sh >$@+ @@ -1892,9 +1999,9 @@ OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS) dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) -ifdef COMPUTE_HEADER_DEPENDENCIES +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes) $(dep_dirs): - mkdir -p $@ + @mkdir -p $@ missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs)) dep_file = $(dir $@).depend/$(notdir $@).d @@ -1905,7 +2012,7 @@ Please unset CHECK_HEADER_DEPENDENCIES and try again) endif endif -ifndef COMPUTE_HEADER_DEPENDENCIES +ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes) ifndef CHECK_HEADER_DEPENDENCIES dep_dirs = missing_dep_dirs = @@ -1995,13 +2102,13 @@ builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o tra builtin/bundle.o bundle.o transport.o: bundle.h builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h -builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h +builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h builtin/send-pack.o transport.o: send-pack.h builtin/log.o builtin/shortlog.o: shortlog.h builtin/prune.o builtin/reflog.o reachable.o: reachable.h builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/tar-tree.o archive-tar.o: tar.h -connect.o transport.o http-backend.o: url.h +connect.o transport.o url.o http-backend.o: url.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h @@ -2031,6 +2138,9 @@ config.sp config.s config.o: EXTRA_CPPFLAGS = \ attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \ -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' +gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \ + -DGIT_LOCALE_PATH='"$(localedir_SQ)"' + http.sp http.s http.o: EXTRA_CPPFLAGS = \ -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' @@ -2104,32 +2214,57 @@ XGETTEXT_FLAGS = \ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \ --keyword=_ --keyword=N_ --keyword="Q_:1,2" XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell +XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl LOCALIZED_C := $(C_OBJ:o=c) LOCALIZED_SH := $(SCRIPT_SH) +LOCALIZED_PERL := $(SCRIPT_PERL) + +ifdef XGETTEXT_INCLUDE_TESTS +LOCALIZED_C += t/t0200/test.c +LOCALIZED_SH += t/t0200/test.sh +LOCALIZED_PERL += t/t0200/test.perl +endif po/git.pot: $(LOCALIZED_C) $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C) $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \ $(LOCALIZED_SH) + $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_PERL) \ + $(LOCALIZED_PERL) mv $@+ $@ pot: po/git.pot +POFILES := $(wildcard po/*.po) +MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES)) + +ifndef NO_GETTEXT +all:: $(MOFILES) +endif + +po/build/locale/%/LC_MESSAGES/git.mo: po/%.po + $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $< + +FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \ + $(FIND) . \( -name .git -type d -prune \) \ + -o \( -name '*.[hcS]' -type f -print \) ) + $(ETAGS_TARGET): FORCE $(RM) $(ETAGS_TARGET) - $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET) + $(FIND_SOURCE_FILES) | xargs etags -a -o $(ETAGS_TARGET) tags: FORCE $(RM) tags - $(FIND) . -name '*.[hcS]' -print | xargs ctags -a + $(FIND_SOURCE_FILES) | xargs ctags -a cscope: $(RM) cscope* - $(FIND) . -name '*.[hcS]' -print | xargs cscope -b + $(FIND_SOURCE_FILES) | xargs cscope -b ### Detect prefix changes TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\ - $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) + $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\ + $(localedir_SQ) GIT-CFLAGS: FORCE @FLAGS='$(TRACK_CFLAGS)'; \ @@ -2166,7 +2301,9 @@ endif ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@ endif + @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@ @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@ + @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@ ### Detect Tck/Tk interpreter path changes ifndef NO_TCLTK @@ -2280,8 +2417,12 @@ install: all $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' - (cd mergetools && $(TAR) cf - .) | \ - (cd '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' && umask 022 && $(TAR) xof -) + $(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' +ifndef NO_GETTEXT + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(localedir_SQ)' + (cd po/build/locale && $(TAR) cf - .) | \ + (cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -) +endif ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install $(MAKE) -C gitweb install @@ -2418,6 +2559,7 @@ clean: $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers $(RM) -r $(dep_dirs) + $(RM) -r po/build/ $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache @@ -1 +1 @@ -Documentation/RelNotes/1.7.7.txt
\ No newline at end of file +Documentation/RelNotes/1.7.9.txt
\ No newline at end of file @@ -19,6 +19,15 @@ static struct { { "detachedhead", &advice_detached_head }, }; +void advise(const char *advice, ...) +{ + va_list params; + + va_start(params, advice); + vreportf("hint: ", advice, params); + va_end(params); +} + int git_default_advice_config(const char *var, const char *value) { const char *k = skip_prefix(var, "advice."); @@ -34,16 +43,24 @@ int git_default_advice_config(const char *var, const char *value) return 0; } -void NORETURN die_resolve_conflict(const char *me) +int error_resolve_conflict(const char *me) { - if (advice_resolve_conflict) + error("'%s' is not possible because you have unmerged files.", me); + if (advice_resolve_conflict) { /* * Message used both when 'git commit' fails and when * other commands doing a merge do. */ - die("'%s' is not possible because you have unmerged files.\n" - "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n" - "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me); - else - die("'%s' is not possible because you have unmerged files.", me); + advise("Fix them up in the work tree,"); + advise("and then use 'git add/rm <file>' as"); + advise("appropriate to mark resolution and make a commit,"); + advise("or use 'git commit -a'."); + } + return -1; +} + +void NORETURN die_resolve_conflict(const char *me) +{ + error_resolve_conflict(me); + die("Exiting because of an unresolved conflict."); } @@ -11,7 +11,8 @@ extern int advice_implicit_identity; extern int advice_detached_head; int git_default_advice_config(const char *var, const char *value); - +void advise(const char *advice, ...); +int error_resolve_conflict(const char *me); extern void NORETURN die_resolve_conflict(const char *me); #endif /* ADVICE_H */ @@ -247,7 +247,8 @@ static void parse_pathspec_arg(const char **pathspec, } static void parse_treeish_arg(const char **argv, - struct archiver_args *ar_args, const char *prefix) + struct archiver_args *ar_args, const char *prefix, + int remote) { const char *name = argv[0]; const unsigned char *commit_sha1; @@ -256,8 +257,17 @@ static void parse_treeish_arg(const char **argv, const struct commit *commit; unsigned char sha1[20]; - if (get_sha1(name, sha1)) - die("Not a valid object name"); + /* Remotes are only allowed to fetch actual refs */ + if (remote) { + char *ref = NULL; + if (!dwim_ref(name, strlen(name), sha1, &ref)) + die("no such ref: %s", name); + free(ref); + } + else { + if (get_sha1(name, sha1)) + die("Not a valid object name"); + } commit = lookup_commit_reference_gently(sha1, 1); if (commit) { @@ -318,7 +328,7 @@ static int parse_archive_args(int argc, const char **argv, "prepend prefix to each pathname in the archive"), OPT_STRING('o', "output", &output, "file", "write the archive to this file"), - OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes, + OPT_BOOL(0, "worktree-attributes", &worktree_attributes, "read .gitattributes in working directory"), OPT__VERBOSE(&verbose, "report archived files on stderr"), OPT__COMPR('0', &compression_level, "store only", 0), @@ -332,7 +342,7 @@ static int parse_archive_args(int argc, const char **argv, OPT__COMPR_HIDDEN('8', &compression_level, 8), OPT__COMPR('9', &compression_level, "compress better", 9), OPT_GROUP(""), - OPT_BOOLEAN('l', "list", &list, + OPT_BOOL('l', "list", &list, "list supported archive formats"), OPT_GROUP(""), OPT_STRING(0, "remote", &remote, "repo", @@ -414,7 +424,7 @@ int write_archive(int argc, const char **argv, const char *prefix, setup_git_directory(); } - parse_treeish_arg(argv, &args, prefix); + parse_treeish_arg(argv, &args, prefix, remote); parse_pathspec_arg(argv + 1, &args); return ar->write_archive(ar, &args); diff --git a/argv-array.c b/argv-array.c new file mode 100644 index 0000000000..a4e04201e6 --- /dev/null +++ b/argv-array.c @@ -0,0 +1,51 @@ +#include "cache.h" +#include "argv-array.h" +#include "strbuf.h" + +static const char *empty_argv_storage = NULL; +const char **empty_argv = &empty_argv_storage; + +void argv_array_init(struct argv_array *array) +{ + array->argv = empty_argv; + array->argc = 0; + array->alloc = 0; +} + +static void argv_array_push_nodup(struct argv_array *array, const char *value) +{ + if (array->argv == empty_argv) + array->argv = NULL; + + ALLOC_GROW(array->argv, array->argc + 2, array->alloc); + array->argv[array->argc++] = value; + array->argv[array->argc] = NULL; +} + +void argv_array_push(struct argv_array *array, const char *value) +{ + argv_array_push_nodup(array, xstrdup(value)); +} + +void argv_array_pushf(struct argv_array *array, const char *fmt, ...) +{ + va_list ap; + struct strbuf v = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_vaddf(&v, fmt, ap); + va_end(ap); + + argv_array_push_nodup(array, strbuf_detach(&v, NULL)); +} + +void argv_array_clear(struct argv_array *array) +{ + if (array->argv != empty_argv) { + int i; + for (i = 0; i < array->argc; i++) + free((char **)array->argv[i]); + free(array->argv); + } + argv_array_init(array); +} diff --git a/argv-array.h b/argv-array.h new file mode 100644 index 0000000000..74dd2b1bc0 --- /dev/null +++ b/argv-array.h @@ -0,0 +1,20 @@ +#ifndef ARGV_ARRAY_H +#define ARGV_ARRAY_H + +extern const char **empty_argv; + +struct argv_array { + const char **argv; + int argc; + int alloc; +}; + +#define ARGV_ARRAY_INIT { empty_argv, 0, 0 } + +void argv_array_init(struct argv_array *); +void argv_array_push(struct argv_array *, const char *); +__attribute__((format (printf,2,3))) +void argv_array_pushf(struct argv_array *, const char *fmt, ...); +void argv_array_clear(struct argv_array *); + +#endif /* ARGV_ARRAY_H */ @@ -11,6 +11,7 @@ #include "cache.h" #include "exec_cmd.h" #include "attr.h" +#include "dir.h" const char git_attr__true[] = "(builtin)true"; const char git_attr__false[] = "\0(builtin)false"; @@ -20,8 +21,6 @@ static const char git_attr__unknown[] = "(builtin)unknown"; #define ATTR__UNSET NULL #define ATTR__UNKNOWN git_attr__unknown -static const char *attributes_file; - /* This is a randomly chosen prime. */ #define HASHSIZE 257 @@ -494,14 +493,6 @@ static int git_attr_system(void) return !git_env_bool("GIT_ATTR_NOSYSTEM", 0); } -static int git_attr_config(const char *var, const char *value, void *dummy) -{ - if (!strcmp(var, "core.attributesfile")) - return git_config_pathname(&attributes_file, var, value); - - return 0; -} - static void bootstrap_attr_stack(void) { if (!attr_stack) { @@ -521,9 +512,8 @@ static void bootstrap_attr_stack(void) } } - git_config(git_attr_config, NULL); - if (attributes_file) { - elem = read_attr_from_file(attributes_file, 1); + if (git_attributes_file) { + elem = read_attr_from_file(git_attributes_file, 1); if (elem) { elem->origin = NULL; elem->prev = attr_stack; @@ -533,7 +523,7 @@ static void bootstrap_attr_stack(void) if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { elem = read_attr(GITATTRIBUTES_FILE, 1); - elem->origin = strdup(""); + elem->origin = xstrdup(""); elem->prev = attr_stack; attr_stack = elem; debug_push(elem); @@ -552,7 +542,6 @@ static void prepare_attr_stack(const char *path) { struct attr_stack *elem, *info; int dirlen, len; - struct strbuf pathbuf; const char *cp; cp = strrchr(path, '/'); @@ -561,8 +550,6 @@ static void prepare_attr_stack(const char *path) else dirlen = cp - path; - strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE)); - /* * At the bottom of the attribute stack is the built-in * set of attribute definitions, followed by the contents @@ -607,27 +594,28 @@ static void prepare_attr_stack(const char *path) * Read from parent directories and push them down */ if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { - while (1) { - char *cp; + struct strbuf pathbuf = STRBUF_INIT; + while (1) { len = strlen(attr_stack->origin); if (dirlen <= len) break; - strbuf_reset(&pathbuf); - strbuf_add(&pathbuf, path, dirlen); + cp = memchr(path + len + 1, '/', dirlen - len - 1); + if (!cp) + cp = path + dirlen; + strbuf_add(&pathbuf, path, cp - path); strbuf_addch(&pathbuf, '/'); - cp = strchr(pathbuf.buf + len + 1, '/'); - strcpy(cp + 1, GITATTRIBUTES_FILE); + strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE); elem = read_attr(pathbuf.buf, 0); - *cp = '\0'; - elem->origin = strdup(pathbuf.buf); + strbuf_setlen(&pathbuf, cp - path); + elem->origin = strbuf_detach(&pathbuf, NULL); elem->prev = attr_stack; attr_stack = elem; debug_push(elem); } - } - strbuf_release(&pathbuf); + strbuf_release(&pathbuf); + } /* * Finally push the "info" one at the top of the stack. @@ -644,7 +632,7 @@ static int path_matches(const char *pathname, int pathlen, /* match basename */ const char *basename = strrchr(pathname, '/'); basename = basename ? basename + 1 : pathname; - return (fnmatch(pattern, basename, 0) == 0); + return (fnmatch_icase(pattern, basename, 0) == 0); } /* * match with FNM_PATHNAME; the pattern has base implicitly @@ -658,7 +646,7 @@ static int path_matches(const char *pathname, int pathlen, return 0; if (baselen != 0) baselen++; - return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0; + return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0; } static int macroexpand_one(int attr_nr, int rem); @@ -10,18 +10,13 @@ #include "log-tree.h" #include "bisect.h" #include "sha1-array.h" +#include "argv-array.h" 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}; static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL}; @@ -405,21 +400,6 @@ struct commit_list *find_bisection(struct commit_list *list, 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 int register_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { @@ -449,16 +429,10 @@ static void read_bisect_paths(struct argv_array *array) die_errno("Could not open file '%s'", filename); 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) + if (sq_dequote_to_argv_array(str.buf, array)) die("Badly quoted content in file '%s': %s", - filename, quoted); + filename, str.buf); } strbuf_release(&str); @@ -623,7 +597,7 @@ 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 }; + struct argv_array rev_argv = ARGV_ARRAY_INIT; int i; init_revisions(revs, prefix); @@ -631,17 +605,17 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix, 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); + argv_array_push(&rev_argv, "bisect_rev_setup"); + argv_array_pushf(&rev_argv, bad_format, sha1_to_hex(current_bad_sha1)); for (i = 0; i < good_revs.nr; i++) - argv_array_push_sha1(&rev_argv, good_revs.sha1[i], - good_format); - argv_array_push(&rev_argv, xstrdup("--")); + argv_array_pushf(&rev_argv, good_format, + sha1_to_hex(good_revs.sha1[i])); + argv_array_push(&rev_argv, "--"); 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); + setup_revisions(rev_argv.argc, rev_argv.argv, revs, NULL); + /* XXX leak rev_argv, as "revs" may still be pointing to it */ } static void bisect_common(struct rev_info *revs) @@ -826,25 +800,25 @@ static int check_ancestors(const char *prefix) { struct rev_info revs; struct object_array pending_copy; - int i, res; + int 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); + pending_copy = revs.pending; + revs.leak_pending = 1; + /* + * bisect_common calls prepare_revision_walk right away, which + * (together with .leak_pending = 1) makes us the sole owner of + * the list of pending objects. + */ 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); - } + clear_commit_marks_for_object_array(&pending_copy, ALL_REV_FLAGS); + free(pending_copy.objects); return res; } @@ -135,6 +135,37 @@ static int setup_tracking(const char *new_ref, const char *orig_ref, return 0; } +struct branch_desc_cb { + const char *config_name; + const char *value; +}; + +static int read_branch_desc_cb(const char *var, const char *value, void *cb) +{ + struct branch_desc_cb *desc = cb; + if (strcmp(desc->config_name, var)) + return 0; + free((char *)desc->value); + return git_config_string(&desc->value, var, value); +} + +int read_branch_desc(struct strbuf *buf, const char *branch_name) +{ + struct branch_desc_cb cb; + struct strbuf name = STRBUF_INIT; + strbuf_addf(&name, "branch.%s.description", branch_name); + cb.config_name = name.buf; + cb.value = NULL; + if (git_config(read_branch_desc_cb, &cb) < 0) { + strbuf_release(&name); + return -1; + } + if (cb.value) + strbuf_addstr(buf, cb.value); + strbuf_release(&name); + return 0; +} + int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only) { @@ -150,7 +181,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref, const char *head; unsigned char sha1[20]; - head = resolve_ref("HEAD", sha1, 0, NULL); + head = resolve_ref_unsafe("HEAD", sha1, 0, NULL); if (!is_bare_repository() && head && !strcmp(head, ref->buf)) die("Cannot force update the current branch."); } @@ -159,7 +190,8 @@ int validate_new_branchname(const char *name, struct strbuf *ref, void create_branch(const char *head, const char *name, const char *start_name, - int force, int reflog, enum branch_track track) + int force, int reflog, int clobber_head, + enum branch_track track) { struct ref_lock *lock = NULL; struct commit *commit; @@ -174,7 +206,8 @@ void create_branch(const char *head, explicit_tracking = 1; if (validate_new_branchname(name, &ref, force, - track == BRANCH_TRACK_OVERRIDE)) { + track == BRANCH_TRACK_OVERRIDE || + clobber_head)) { if (!force) dont_change_ref = 1; else @@ -240,6 +273,7 @@ void create_branch(const char *head, void remove_branch_state(void) { unlink(git_path("CHERRY_PICK_HEAD")); + unlink(git_path("REVERT_HEAD")); unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_RR")); unlink(git_path("MERGE_MSG")); @@ -13,7 +13,8 @@ * branch for (if any). */ void create_branch(const char *head, const char *name, const char *start_name, - int force, int reflog, enum branch_track track); + int force, int reflog, + int clobber_head, enum branch_track track); /* * Validates that the requested branch may be created, returning the @@ -46,4 +47,9 @@ void remove_branch_state(void); #define BRANCH_CONFIG_VERBOSE 01 extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote); +/* + * Read branch description + */ +extern int read_branch_desc(struct strbuf *, const char *branch_name); + #endif @@ -14,8 +14,14 @@ extern const char git_usage_string[]; extern const char git_more_info_string[]; extern void prune_packed_objects(int); + +struct fmt_merge_msg_opts { + unsigned add_title:1; + int shortlog_len; +}; + extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out, - int merge_title, int shortlog_len); + struct fmt_merge_msg_opts *); extern void commit_notes(struct notes_tree *t, const char *msg); struct notes_rewrite_cfg { @@ -133,6 +139,7 @@ extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); extern int cmd_update_server_info(int argc, const char **argv, const char *prefix); extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); +extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix); extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); extern int cmd_var(int argc, const char **argv, const char *prefix); extern int cmd_verify_tag(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index c59b0c98fe..1c42900ff8 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -13,6 +13,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "bulk-checkin.h" static const char * const builtin_add_usage[] = { "git add [options] [--] <filepattern>...", @@ -458,11 +459,15 @@ int cmd_add(int argc, const char **argv, const char *prefix) free(seen); } + plug_bulk_checkin(); + exit_status |= add_files_to_cache(prefix, pathspec, flags); if (add_new_files) exit_status |= add_files(&dir, flags); + unplug_bulk_checkin(); + finish: if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || diff --git a/builtin/apply.c b/builtin/apply.c index f2edc52818..c24dc546d0 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -250,9 +250,6 @@ static int fuzzy_matchlines(const char *s1, size_t n1, const char *last2 = s2 + n2 - 1; int result = 0; - if (n1 < 0 || n2 < 0) - return 0; - /* ignore line endings */ while ((*last1 == '\r') || (*last1 == '\n')) last1--; @@ -1407,6 +1404,9 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc "%d leading pathname components (line %d)" , p_value, linenr); patch->old_name = patch->new_name = patch->def_name; } + if (!patch->is_delete && !patch->new_name) + die("git diff header lacks filename information " + "(line %d)", linenr); patch->is_toplevel_relative = 1; *hdrsize = git_hdr_len; return offset; @@ -2447,6 +2447,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, char *old, *oldlines; struct strbuf newlines; int new_blank_lines_at_end = 0; + int found_new_blank_lines_at_end = 0; + int hunk_linenr = frag->linenr; unsigned long leading, trailing; int pos, applied_pos; struct image preimage; @@ -2540,14 +2542,18 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, error("invalid start of line: '%c'", first); return -1; } - if (added_blank_line) + if (added_blank_line) { + if (!new_blank_lines_at_end) + found_new_blank_lines_at_end = hunk_linenr; new_blank_lines_at_end++; + } else if (is_blank_context) ; else new_blank_lines_at_end = 0; patch += len; size -= len; + hunk_linenr++; } if (inaccurate_eof && old > oldlines && old[-1] == '\n' && @@ -2629,7 +2635,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, preimage.nr + applied_pos >= img->nr && (ws_rule & WS_BLANK_AT_EOF) && ws_error_action != nowarn_ws_error) { - record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr); + record_ws_error(WS_BLANK_AT_EOF, "+", 1, + found_new_blank_lines_at_end); if (ws_error_action == correct_ws_error) { while (new_blank_lines_at_end--) remove_last_line(&postimage); @@ -3580,15 +3587,12 @@ static int write_out_one_reject(struct patch *patch) return -1; } -static int write_out_results(struct patch *list, int skipped_patch) +static int write_out_results(struct patch *list) { int phase; int errs = 0; struct patch *l; - if (!list && !skipped_patch) - return error("No changes"); - for (phase = 0; phase < 2; phase++) { l = list; while (l) { @@ -3714,6 +3718,9 @@ static int apply_patch(int fd, const char *filename, int options) offset += nr; } + if (!list && !skipped_patch) + die("unrecognized input"); + if (whitespace_error && (ws_error_action == die_on_ws_error)) apply = 0; @@ -3731,7 +3738,7 @@ static int apply_patch(int fd, const char *filename, int options) !apply_with_reject) exit(1); - if (apply && write_out_results(list, skipped_patch)) + if (apply && write_out_results(list)) exit(1); if (fake_ancestor) @@ -3831,7 +3838,6 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) int i; int errs = 0; int is_not_gitdir = !startup_info->have_repository; - int binary; int force_apply = 0; const char *whitespace_option = NULL; @@ -3850,12 +3856,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) "ignore additions made by the patch"), OPT_BOOLEAN(0, "stat", &diffstat, "instead of applying the patch, output diffstat for the input"), - { 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_NOOP_NOARG(0, "allow-binary-replacement"), + OPT_NOOP_NOARG(0, "binary"), OPT_BOOLEAN(0, "numstat", &numstat, "shows number of added and deleted lines in decimal notation"), OPT_BOOLEAN(0, "summary", &summary, diff --git a/builtin/archive.c b/builtin/archive.c index 883c0092ad..931956def9 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -61,6 +61,8 @@ static int run_remote_archiver(int argc, const char **argv, if (strcmp(buf, "ACK")) { if (len > 5 && !prefixcmp(buf, "NACK ")) die(_("git archive: NACK %s"), buf + 5); + if (len > 4 && !prefixcmp(buf, "ERR ")) + die(_("remote error: %s"), buf + 4); die(_("git archive: protocol error")); } diff --git a/builtin/blame.c b/builtin/blame.c index 26a5d424b8..5a67c202f0 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1598,7 +1598,7 @@ static const char *format_time(unsigned long time, const char *tz_str, int tz; if (show_raw_time) { - sprintf(time_buf, "%lu %s", time, tz_str); + snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str); } else { tz = atoi(tz_str); @@ -2096,6 +2096,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, if (!contents_from || strcmp("-", contents_from)) { struct stat st; const char *read_from; + char *buf_ptr; unsigned long buf_len; if (contents_from) { @@ -2113,8 +2114,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, switch (st.st_mode & S_IFMT) { case S_IFREG: if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len)) - buf.len = buf_len; + textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len)) + strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1); else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) die_errno("cannot open or read '%s'", read_from); break; diff --git a/builtin/branch.c b/builtin/branch.c index f49596f826..7095718c13 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -104,6 +104,7 @@ static int branch_merged(int kind, const char *name, */ struct commit *reference_rev = NULL; const char *reference_name = NULL; + void *reference_name_to_free = NULL; int merged; if (kind == REF_LOCAL_BRANCH) { @@ -114,8 +115,8 @@ static int branch_merged(int kind, const char *name, branch->merge && branch->merge[0] && branch->merge[0]->dst && - (reference_name = - resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) + (reference_name = reference_name_to_free = + resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) reference_rev = lookup_commit_reference(sha1); } if (!reference_rev) @@ -141,6 +142,7 @@ static int branch_merged(int kind, const char *name, " '%s', even though it is merged to HEAD."), name, reference_name); } + free(reference_name_to_free); return merged; } @@ -186,7 +188,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) free(name); name = xstrdup(mkpath(fmt, bname.buf)); - if (!resolve_ref(name, sha1, 1, NULL)) { + if (read_ref(name, sha1)) { error(_("%sbranch '%s' not found."), remote, bname.buf); ret = 1; @@ -250,7 +252,7 @@ static char *resolve_symref(const char *src, const char *prefix) int flag; const char *dst, *cp; - dst = resolve_ref(src, sha1, 0, &flag); + dst = resolve_ref_unsafe(src, sha1, 0, &flag); if (!(dst && (flag & REF_ISSYMREF))) return NULL; if (prefix && (cp = skip_prefix(dst, prefix))) @@ -260,9 +262,22 @@ static char *resolve_symref(const char *src, const char *prefix) struct append_ref_cb { struct ref_list *ref_list; + const char **pattern; int ret; }; +static int match_patterns(const char **pattern, const char *refname) +{ + if (!*pattern) + return 1; /* no pattern always matches */ + while (*pattern) { + if (!fnmatch(*pattern, refname, 0)) + return 1; + pattern++; + } + return 0; +} + static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data); @@ -297,6 +312,9 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, if ((kind & ref_list->kinds) == 0) return 0; + if (!match_patterns(cb->pattern, refname)) + return 0; + commit = NULL; if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { commit = lookup_commit_reference_gently(sha1, 1); @@ -492,7 +510,7 @@ static void show_detached(struct ref_list *ref_list) } } -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit) +static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) { int i; struct append_ref_cb cb; @@ -506,6 +524,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru if (merge_filter != NO_FILTER) init_revisions(&ref_list.revs, NULL); cb.ref_list = &ref_list; + cb.pattern = pattern; cb.ret = 0; for_each_rawref(append_ref, &cb); if (merge_filter != NO_FILTER) { @@ -523,7 +542,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached) + if (detached && match_patterns(pattern, "HEAD")) show_detached(&ref_list); for (i = 0; i < ref_list.index; i++) { @@ -548,9 +567,9 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru 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; + int clobber_head_ok; if (!oldname) die(_("cannot rename the current branch while not on any.")); @@ -560,13 +579,19 @@ static void rename_branch(const char *oldname, const char *newname, int force) * 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)) + if (ref_exists(oldref.buf)) recovery = 1; else die(_("Invalid branch name: '%s'"), oldname); } - validate_new_branchname(newname, &newref, force, 0); + /* + * A command like "git branch -M currentbranch currentbranch" cannot + * cause the worktree to become inconsistent with HEAD, so allow it. + */ + clobber_head_ok = !strcmp(oldname, newname); + + validate_new_branchname(newname, &newref, force, clobber_head_ok); strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); @@ -606,11 +631,49 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int return 0; } +static const char edit_description[] = "BRANCH_DESCRIPTION"; + +static int edit_branch_description(const char *branch_name) +{ + FILE *fp; + int status; + struct strbuf buf = STRBUF_INIT; + struct strbuf name = STRBUF_INIT; + + read_branch_desc(&buf, branch_name); + if (!buf.len || buf.buf[buf.len-1] != '\n') + strbuf_addch(&buf, '\n'); + strbuf_addf(&buf, + "# Please edit the description for the branch\n" + "# %s\n" + "# Lines starting with '#' will be stripped.\n", + branch_name); + fp = fopen(git_path(edit_description), "w"); + if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) { + strbuf_release(&buf); + return error(_("could not write branch description template: %s\n"), + strerror(errno)); + } + strbuf_reset(&buf); + if (launch_editor(git_path(edit_description), &buf, NULL)) { + strbuf_release(&buf); + return -1; + } + stripspace(&buf, 1); + + strbuf_addf(&name, "branch.%s.description", branch_name); + status = git_config_set(name.buf, buf.buf); + strbuf_release(&name); + strbuf_release(&buf); + + return status; +} + int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, rename = 0, force_create = 0; + int delete = 0, rename = 0, force_create = 0, list = 0; int verbose = 0, abbrev = -1, detached = 0; - int reflog = 0; + int reflog = 0, edit_description = 0; enum branch_track track; int kinds = REF_LOCAL_BRANCH; struct commit_list *with_commit = NULL; @@ -624,7 +687,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", BRANCH_TRACK_OVERRIDE), OPT__COLOR(&branch_use_color, "use colored output"), - OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", + OPT_SET_INT('r', "remotes", &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), { OPTION_CALLBACK, 0, "contains", &with_commit, "commit", @@ -641,13 +704,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT__ABBREV(&abbrev), OPT_GROUP("Specific git-branch actions:"), - OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches", + OPT_SET_INT('a', "all", &kinds, "list both remote-tracking and local branches", REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), - OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1), + OPT_BIT('d', "delete", &delete, "delete fully merged branch", 1), OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2), - OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), + OPT_BIT('m', "move", &rename, "move/rename a branch and its reflog", 1), OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), - OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), + OPT_BOOLEAN(0, "list", &list, "list branch names"), + OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"), + OPT_BOOLEAN(0, "edit-description", &edit_description, + "edit the description for the branch"), OPT__FORCE(&force_create, "force creation (when already exists)"), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, @@ -671,10 +737,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head = resolve_ref("HEAD", head_sha1, 0, NULL); + head = resolve_refdup("HEAD", head_sha1, 0, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); - head = xstrdup(head); if (!strcmp(head, "HEAD")) { detached = 1; } else { @@ -686,7 +751,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); - if (!!delete + !!rename + !!force_create > 1) + + if (!delete && !rename && !edit_description && argc == 0) + list = 1; + + if (!!delete + !!rename + !!force_create + !!list > 1) usage_with_options(builtin_branch_usage, options); if (abbrev == -1) @@ -694,17 +763,33 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) return delete_branches(argc, argv, delete > 1, kinds); - else if (argc == 0) - return print_ref_list(kinds, detached, verbose, abbrev, with_commit); - else if (rename && (argc == 1)) - rename_branch(head, argv[0], rename > 1); - else if (rename && (argc == 2)) - rename_branch(argv[0], argv[1], rename > 1); - else if (argc <= 2) { + else if (list) + return print_ref_list(kinds, detached, verbose, abbrev, + with_commit, argv); + else if (edit_description) { + const char *branch_name; + if (detached) + die("Cannot give description to detached HEAD"); + if (!argc) + branch_name = head; + else if (argc == 1) + branch_name = argv[0]; + else + usage_with_options(builtin_branch_usage, options); + if (edit_branch_description(branch_name)) + return 1; + } else if (rename) { + if (argc == 1) + rename_branch(head, argv[0], rename > 1); + else if (argc == 2) + rename_branch(argv[0], argv[1], rename > 1); + else + usage_with_options(builtin_branch_usage, options); + } else if (argc > 0 && argc <= 2) { if (kinds != REF_LOCAL_BRANCH) die(_("-a and -r options to 'git branch' do not make sense with a branch name")); create_branch(head, argv[0], (argc == 2) ? argv[1] : head, - force_create, reflog, track); + force_create, reflog, 0, track); } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/bundle.c b/builtin/bundle.c index 81046a9cb8..92a8a6026a 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -58,7 +58,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } else if (!strcmp(cmd, "unbundle")) { if (!startup_info->have_repository) die(_("Need a repository to unbundle.")); - return !!unbundle(&header, bundle_fd) || + return !!unbundle(&header, bundle_fd, 0) || list_bundle_refs(&header, argc, argv); } else usage(builtin_bundle_usage); diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 708988a0e1..44c421eb0f 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -5,6 +5,7 @@ #include "parse-options.h" static int all_attrs; +static int cached_attrs; static int stdin_paths; static const char * const check_attr_usage[] = { "git check-attr [-a | --all | attr...] [--] pathname...", @@ -16,6 +17,7 @@ static int null_term_line; static const struct option check_attr_options[] = { OPT_BOOLEAN('a', "all", &all_attrs, "report all attributes set on file"), + OPT_BOOLEAN(0, "cached", &cached_attrs, "use .gitattributes only from the index"), OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"), OPT_BOOLEAN('z', NULL, &null_term_line, "input paths are terminated by a null character"), @@ -92,6 +94,8 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) struct git_attr_check *check; int cnt, i, doubledash, filei; + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, check_attr_options, check_attr_usage, PARSE_OPT_KEEP_DASHDASH); @@ -99,6 +103,9 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) die("invalid cache"); } + if (cached_attrs) + git_attr_set_direction(GIT_ATTR_INDEX, NULL); + doubledash = -1; for (i = 0; doubledash < 0 && i < argc; i++) { if (!strcmp(argv[i], "--")) diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index 0723cf245e..28a7320271 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -8,29 +8,32 @@ #include "strbuf.h" static const char builtin_check_ref_format_usage[] = -"git check-ref-format [--print] <refname>\n" +"git check-ref-format [--normalize] [options] <refname>\n" " or: git check-ref-format --branch <branchname-shorthand>"; /* - * Remove leading slashes and replace each run of adjacent slashes in - * src with a single slash, and write the result to dst. + * Return a copy of refname but with leading slashes removed and runs + * of adjacent slashes replaced with single slashes. * * This function is similar to normalize_path_copy(), but stripped down * to meet check_ref_format's simpler needs. */ -static void collapse_slashes(char *dst, const char *src) +static char *collapse_slashes(const char *refname) { + char *ret = xmalloc(strlen(refname) + 1); char ch; char prev = '/'; + char *cp = ret; - while ((ch = *src++) != '\0') { + while ((ch = *refname++) != '\0') { if (prev == '/' && ch == prev) continue; - *dst++ = ch; + *cp++ = ch; prev = ch; } - *dst = '\0'; + *cp = '\0'; + return ret; } static int check_ref_format_branch(const char *arg) @@ -45,27 +48,41 @@ static int check_ref_format_branch(const char *arg) return 0; } -static int check_ref_format_print(const char *arg) -{ - char *refname = xmalloc(strlen(arg) + 1); - - if (check_ref_format(arg)) - return 1; - collapse_slashes(refname, arg); - printf("%s\n", refname); - return 0; -} - int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { + int i; + int normalize = 0; + int flags = 0; + const char *refname; + if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_check_ref_format_usage); if (argc == 3 && !strcmp(argv[1], "--branch")) return check_ref_format_branch(argv[2]); - if (argc == 3 && !strcmp(argv[1], "--print")) - return check_ref_format_print(argv[2]); - if (argc != 2) + + for (i = 1; i < argc && argv[i][0] == '-'; i++) { + if (!strcmp(argv[i], "--normalize") || !strcmp(argv[i], "--print")) + normalize = 1; + else if (!strcmp(argv[i], "--allow-onelevel")) + flags |= REFNAME_ALLOW_ONELEVEL; + else if (!strcmp(argv[i], "--no-allow-onelevel")) + flags &= ~REFNAME_ALLOW_ONELEVEL; + else if (!strcmp(argv[i], "--refspec-pattern")) + flags |= REFNAME_REFSPEC_PATTERN; + else + usage(builtin_check_ref_format_usage); + } + if (! (i == argc - 1)) usage(builtin_check_ref_format_usage); - return !!check_ref_format(argv[1]); + + refname = argv[i]; + if (normalize) + refname = collapse_slashes(refname); + if (check_refname_format(refname, flags)) + return 1; + if (normalize) + printf("%s\n", refname); + + return 0; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 5e356a6c61..f1984d9933 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -19,6 +19,7 @@ #include "ll-merge.h" #include "resolve-undo.h" #include "submodule.h" +#include "argv-array.h" static const char * const checkout_usage[] = { "git checkout [options] <branch>", @@ -33,6 +34,7 @@ struct checkout_opts { int force_detach; int writeout_stage; int writeout_error; + int overwrite_ignore; /* not set by parse_options */ int branch_exists; @@ -71,7 +73,7 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen, hashcpy(ce->sha1, sha1); memcpy(ce->name, base, baselen); memcpy(ce->name + baselen, pathname, len - baselen); - ce->ce_flags = create_ce_flags(len, 0); + ce->ce_flags = create_ce_flags(len, 0) | CE_UPDATE; ce->ce_mode = create_ce_mode(mode); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); return 0; @@ -113,16 +115,21 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) return error(_("path '%s' does not have their version"), ce->name); } -static int check_all_stages(struct cache_entry *ce, int pos) +static int check_stages(unsigned stages, struct cache_entry *ce, int pos) { - if (ce_stage(ce) != 1 || - active_nr <= pos + 2 || - strcmp(active_cache[pos+1]->name, ce->name) || - ce_stage(active_cache[pos+1]) != 2 || - strcmp(active_cache[pos+2]->name, ce->name) || - ce_stage(active_cache[pos+2]) != 3) - return error(_("path '%s' does not have all three versions"), - ce->name); + unsigned seen = 0; + const char *name = ce->name; + + while (pos < active_nr) { + ce = active_cache[pos]; + if (strcmp(name, ce->name)) + break; + seen |= (1 << ce_stage(ce)); + pos++; + } + if ((stages & seen) != stages) + return error(_("path '%s' does not have all necessary versions"), + name); return 0; } @@ -149,18 +156,27 @@ static int checkout_merged(int pos, struct checkout *state) int status; unsigned char sha1[20]; mmbuffer_t result_buf; + unsigned char threeway[3][20]; + unsigned mode = 0; + + memset(threeway, 0, sizeof(threeway)); + while (pos < active_nr) { + int stage; + stage = ce_stage(ce); + if (!stage || strcmp(path, ce->name)) + break; + hashcpy(threeway[stage - 1], ce->sha1); + if (stage == 2) + mode = create_ce_mode(ce->ce_mode); + pos++; + ce = active_cache[pos]; + } + if (is_null_sha1(threeway[1]) || is_null_sha1(threeway[2])) + return error(_("path '%s' does not have necessary versions"), path); - if (ce_stage(ce) != 1 || - active_nr <= pos + 2 || - strcmp(active_cache[pos+1]->name, path) || - ce_stage(active_cache[pos+1]) != 2 || - strcmp(active_cache[pos+2]->name, path) || - ce_stage(active_cache[pos+2]) != 3) - return error(_("path '%s' does not have all 3 versions"), path); - - read_mmblob(&ancestor, active_cache[pos]->sha1); - read_mmblob(&ours, active_cache[pos+1]->sha1); - read_mmblob(&theirs, active_cache[pos+2]->sha1); + read_mmblob(&ancestor, threeway[0]); + read_mmblob(&ours, threeway[1]); + read_mmblob(&theirs, threeway[2]); /* * NEEDSWORK: re-create conflicts from merges with @@ -191,9 +207,7 @@ static int checkout_merged(int pos, struct checkout *state) if (write_sha1_file(result_buf.ptr, result_buf.size, blob_type, sha1)) die(_("Unable to add merge result for '%s'"), path); - ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode), - sha1, - path, 2, 0); + ce = make_cache_entry(mode, sha1, path, 2, 0); if (!ce) die(_("make_cache_entry failed for path '%s'"), path); status = checkout_entry(ce, state, NULL); @@ -228,6 +242,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; + if (source_tree && !(ce->ce_flags & CE_UPDATE)) + continue; match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched); } @@ -249,7 +265,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, } else if (stage) { errs |= check_stage(stage, ce, pos); } else if (opts->merge) { - errs |= check_all_stages(ce, pos); + errs |= check_stages((1<<2) | (1<<3), ce, pos); } else { errs = 1; error(_("path '%s' is unmerged"), ce->name); @@ -266,6 +282,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, state.refresh_cache = 1; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; + if (source_tree && !(ce->ce_flags & CE_UPDATE)) + continue; if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { if (!ce_stage(ce)) { errs |= checkout_entry(ce, &state, NULL); @@ -283,7 +301,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, commit_locked_index(lock_file)) die(_("unable to write new index file")); - resolve_ref("HEAD", rev, 0, &flag); + read_ref_full("HEAD", rev, 0, &flag); head = lookup_commit_reference_gently(rev, 1); errs |= post_checkout_hook(head, head, 0); @@ -404,9 +422,11 @@ static int merge_working_tree(struct checkout_opts *opts, topts.gently = opts->merge && old->commit; topts.verbose_update = !opts->quiet; topts.fn = twoway_merge; - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->flags |= DIR_SHOW_IGNORED; - topts.dir->exclude_per_dir = ".gitignore"; + if (opts->overwrite_ignore) { + topts.dir = xcalloc(1, sizeof(*topts.dir)); + topts.dir->flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(topts.dir); + } tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : EMPTY_TREE_SHA1_BIN); @@ -535,7 +555,9 @@ static void update_refs_for_switch(struct checkout_opts *opts, else create_branch(old->name, opts->new_branch, new->name, opts->new_branch_force ? 1 : 0, - opts->new_branch_log, opts->track); + opts->new_branch_log, + opts->new_branch_force ? 1 : 0, + opts->track); new->name = opts->new_branch; setup_branch_path(new); } @@ -560,8 +582,12 @@ static void update_refs_for_switch(struct checkout_opts *opts, create_symref("HEAD", new->path, msg.buf); if (!opts->quiet) { if (old->path && !strcmp(new->path, old->path)) { - fprintf(stderr, _("Already on '%s'\n"), - new->name); + if (opts->new_branch_force) + fprintf(stderr, _("Reset branch '%s'\n"), + new->name); + else + fprintf(stderr, _("Already on '%s'\n"), + new->name); } else if (opts->new_branch) { if (opts->branch_exists) fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name); @@ -588,35 +614,11 @@ static void update_refs_for_switch(struct checkout_opts *opts, report_tracking(new); } -struct rev_list_args { - int argc; - int alloc; - const char **argv; -}; - -static void add_one_rev_list_arg(struct rev_list_args *args, const char *s) +static int add_pending_uninteresting_ref(const char *refname, + const unsigned char *sha1, + int flags, void *cb_data) { - ALLOC_GROW(args->argv, args->argc + 1, args->alloc); - args->argv[args->argc++] = s; -} - -static int add_one_ref_to_rev_list_arg(const char *refname, - const unsigned char *sha1, - int flags, - void *cb_data) -{ - add_one_rev_list_arg(cb_data, refname); - return 0; -} - -static int clear_commit_marks_from_one_ref(const char *refname, - const unsigned char *sha1, - int flags, - void *cb_data) -{ - struct commit *commit = lookup_commit_reference_gently(sha1, 1); - if (commit) - clear_commit_marks(commit, -1); + add_pending_sha1(cb_data, refname, sha1, flags | UNINTERESTING); return 0; } @@ -685,19 +687,21 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) */ static void orphaned_commit_warning(struct commit *commit) { - struct rev_list_args args = { 0, 0, NULL }; struct rev_info revs; - - add_one_rev_list_arg(&args, "(internal)"); - add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1)); - add_one_rev_list_arg(&args, "--not"); - for_each_ref(add_one_ref_to_rev_list_arg, &args); - add_one_rev_list_arg(&args, "--"); - add_one_rev_list_arg(&args, NULL); + struct object *object = &commit->object; + struct object_array refs; init_revisions(&revs, NULL); - if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1) - die(_("internal error: only -- alone should have been left")); + setup_revisions(0, NULL, &revs, NULL); + + object->flags &= ~UNINTERESTING; + add_pending_object(&revs, object, sha1_to_hex(object->sha1)); + + for_each_ref(add_pending_uninteresting_ref, &revs); + + refs = revs.pending; + revs.leak_pending = 1; + if (prepare_revision_walk(&revs)) die(_("internal error in revision walk")); if (!(commit->object.flags & UNINTERESTING)) @@ -705,23 +709,22 @@ static void orphaned_commit_warning(struct commit *commit) else describe_detached_head(_("Previous HEAD position was"), commit); - clear_commit_marks(commit, -1); - for_each_ref(clear_commit_marks_from_one_ref, NULL); + clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS); + free(refs.objects); } static int switch_branches(struct checkout_opts *opts, struct branch_info *new) { int ret = 0; struct branch_info old; + void *path_to_free; unsigned char rev[20]; int flag; memset(&old, 0, sizeof(old)); - old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag)); + old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag); old.commit = lookup_commit_reference_gently(rev, 1); - if (!(flag & REF_ISSYMREF)) { - free((char *)old.path); + if (!(flag & REF_ISSYMREF)) old.path = NULL; - } if (old.path && !prefixcmp(old.path, "refs/heads/")) old.name = old.path + strlen("refs/heads/"); @@ -735,8 +738,10 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) } ret = merge_working_tree(opts, &old, new); - if (ret) + if (ret) { + free(path_to_free); return ret; + } if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) orphaned_commit_warning(old.commit); @@ -744,7 +749,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) update_refs_for_switch(opts, &old, new); ret = post_checkout_hook(old.commit, new->commit, 1); - free((char *)old.path); + free(path_to_free); return ret || opts->writeout_error; } @@ -882,8 +887,8 @@ static int parse_branchname_arg(int argc, const char **argv, new->name = arg; setup_branch_path(new); - if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK && - resolve_ref(new->path, branch_rev, 1, NULL)) + if (!check_refname_format(new->path, 0) && + !read_ref(new->path, branch_rev)) hashcpy(rev, branch_rev); else new->path = NULL; /* not an existing branch */ @@ -943,6 +948,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 3), OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"), OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"), + OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, "update ignored files (default)"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), @@ -954,6 +960,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); + opts.overwrite_ignore = 1; gitmodules_config(); git_config(git_checkout_config, &opts); @@ -1074,7 +1081,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; opts.branch_exists = validate_new_branchname(opts.new_branch, &buf, - !!opts.new_branch_force, 0); + !!opts.new_branch_force, + !!opts.new_branch_force); strbuf_release(&buf); } diff --git a/builtin/clone.c b/builtin/clone.c index 488f48e9a5..86db954730 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -84,8 +84,8 @@ static struct option builtin_clone_options[] = { "directory from which templates will be used"), OPT_CALLBACK(0 , "reference", &option_reference, "repo", "reference repository", &opt_parse_reference), - OPT_STRING('o', "origin", &option_origin, "branch", - "use <branch> instead of 'origin' to track upstream"), + OPT_STRING('o', "origin", &option_origin, "name", + "use <name> instead of 'origin' to track upstream"), OPT_STRING('b', "branch", &option_branch, "branch", "checkout <branch> instead of the remote's HEAD"), OPT_STRING('u', "upload-pack", &option_upload_pack, "path", @@ -577,9 +577,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (0 <= option_verbosity) { if (option_bare) - printf(_("Cloning into bare repository %s...\n"), dir); + printf(_("Cloning into bare repository '%s'...\n"), dir); else - printf(_("Cloning into %s...\n"), dir); + printf(_("Cloning into '%s'...\n"), dir); } init_db(option_template, INIT_DB_QUIET); write_config(&option_config); diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index d083795e26..b9c331225f 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -9,7 +9,7 @@ #include "builtin.h" #include "utf8.h" -static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog"; +static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-m <message>] [-F <file>] <sha1> <changelog"; static void new_parent(struct commit *parent, struct commit_list **parents_p) { @@ -27,7 +27,7 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p) int cmd_commit_tree(int argc, const char **argv, const char *prefix) { - int i; + int i, got_tree = 0; struct commit_list *parents = NULL; unsigned char tree_sha1[20]; unsigned char commit_sha1[20]; @@ -37,24 +37,66 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) if (argc < 2 || !strcmp(argv[1], "-h")) usage(commit_tree_usage); - if (get_sha1(argv[1], tree_sha1)) - die("Not a valid object name %s", argv[1]); - for (i = 2; i < argc; i += 2) { - unsigned char sha1[20]; - const char *a, *b; - a = argv[i]; b = argv[i+1]; - if (!b || strcmp(a, "-p")) - usage(commit_tree_usage); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "-p")) { + unsigned char sha1[20]; + if (argc <= ++i) + usage(commit_tree_usage); + if (get_sha1(argv[i], sha1)) + die("Not a valid object name %s", argv[i]); + assert_sha1_type(sha1, OBJ_COMMIT); + new_parent(lookup_commit(sha1), &parents); + continue; + } + + if (!strcmp(arg, "-m")) { + if (argc <= ++i) + usage(commit_tree_usage); + if (buffer.len) + strbuf_addch(&buffer, '\n'); + strbuf_addstr(&buffer, argv[i]); + strbuf_complete_line(&buffer); + continue; + } + + if (!strcmp(arg, "-F")) { + int fd; - if (get_sha1(b, sha1)) - die("Not a valid object name %s", b); - assert_sha1_type(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); + if (argc <= ++i) + usage(commit_tree_usage); + if (buffer.len) + strbuf_addch(&buffer, '\n'); + if (!strcmp(argv[i], "-")) + fd = 0; + else { + fd = open(argv[i], O_RDONLY); + if (fd < 0) + die_errno("git commit-tree: failed to open '%s'", + argv[i]); + } + if (strbuf_read(&buffer, fd, 0) < 0) + die_errno("git commit-tree: failed to read '%s'", + argv[i]); + if (fd && close(fd)) + die_errno("git commit-tree: failed to close '%s'", + argv[i]); + strbuf_complete_line(&buffer); + continue; + } + + if (get_sha1(arg, tree_sha1)) + die("Not a valid object name %s", arg); + if (got_tree) + die("Cannot give more than one trees"); + got_tree = 1; } - if (strbuf_read(&buffer, 0, 0) < 0) - die_errno("git commit-tree: failed to read"); + if (!buffer.len) { + if (strbuf_read(&buffer, 0, 0) < 0) + die_errno("git commit-tree: failed to read"); + } if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) { strbuf_release(&buffer); diff --git a/builtin/commit.c b/builtin/commit.c index cbc9613ec6..be1ab2e257 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -81,7 +81,8 @@ static const char *template_file; static const char *author_message, *author_message_buffer; static char *edit_message, *use_message; static char *fixup_message, *squash_message; -static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff; +static int all, also, interactive, patch_interactive, only, amend, signoff; +static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; @@ -138,10 +139,10 @@ static struct option builtin_commit_options[] = { OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"), OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"), OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"), - OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"), + OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_FILENAME('t', "template", &template_file, "use specified template file"), - OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), + OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"), OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"), /* end commit message options */ @@ -255,8 +256,9 @@ static int list_paths(struct string_list *list, const char *with_tree, m = xcalloc(1, i); if (with_tree) { - const char *max_prefix = pathspec_prefix(prefix, pattern); - overlay_tree_on_cache(with_tree, max_prefix); + char *max_prefix = common_prefix(pattern); + overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix); + free(max_prefix); } for (i = 0; i < active_nr; i++) { @@ -393,6 +395,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, fd = hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, pathspec, 0); refresh_cache_or_die(refresh_flags); + update_main_cache_tree(1); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) die(_("unable to write new_index file")); @@ -413,6 +416,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, fd = hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); if (active_cache_changed) { + update_main_cache_tree(1); if (write_cache(fd, active_cache, active_nr) || commit_locked_index(&index_lock)) die(_("unable to write new_index file")); @@ -861,10 +865,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ discard_cache(); read_cache_from(index_file); - if (!active_cache_tree) - active_cache_tree = cache_tree(); - if (cache_tree_update(active_cache_tree, - active_cache, active_nr, 0, 0) < 0) { + if (update_main_cache_tree(0)) { error(_("Error building trees")); return 0; } @@ -1019,8 +1020,8 @@ static int parse_and_validate_options(int argc, const char *argv[], if (logfile || message.len || use_message || fixup_message) use_editor = 0; - if (edit_flag) - use_editor = 1; + if (0 <= edit_flag) + use_editor = edit_flag; if (!use_editor) setenv("GIT_EDITOR", ":", 1); @@ -1258,7 +1259,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, struct commit *commit; struct strbuf format = STRBUF_INIT; unsigned char junk_sha1[20]; - const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL); + const char *head; struct pretty_print_context pctx = {0}; struct strbuf author_ident = STRBUF_INIT; struct strbuf committer_ident = STRBUF_INIT; @@ -1303,6 +1304,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); + head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL); printf("[%s%s ", !prefixcmp(head, "refs/heads/") ? head + 11 : @@ -1381,6 +1383,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) int allow_fast_forward = 1; struct wt_status s; struct commit *current_head = NULL; + struct commit_extra_header *extra = NULL; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); @@ -1392,7 +1395,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (get_sha1("HEAD", sha1)) current_head = NULL; else { - current_head = lookup_commit(sha1); + current_head = lookup_commit_or_die(sha1, "HEAD"); if (!current_head || parse_commit(current_head)) die(_("could not parse HEAD commit")); } @@ -1434,10 +1437,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die_errno(_("could not open '%s' for reading"), git_path("MERGE_HEAD")); while (strbuf_getline(&m, fp, '\n') != EOF) { - unsigned char sha1[20]; - if (get_sha1_hex(m.buf, sha1) < 0) + struct commit *parent; + + parent = get_merge_parent(m.buf); + if (!parent) die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); - pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next; + pptr = &commit_list_insert(parent, pptr)->next; } fclose(fp); strbuf_release(&m); @@ -1480,12 +1485,20 @@ int cmd_commit(int argc, const char **argv, const char *prefix) exit(1); } - if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1, - author_ident.buf)) { + if (amend) { + extra = read_commit_extra_headers(current_head); + } else { + struct commit_extra_header **tail = &extra; + append_merge_tag_headers(parents, &tail); + } + + if (commit_tree_extended(sb.buf, active_cache_tree->sha1, parents, sha1, + author_ident.buf, extra)) { rollback_index_files(); die(_("failed to write commit object")); } strbuf_release(&author_ident); + free_commit_extra_headers(extra); ref_lock = lock_any_ref_for_update("HEAD", !current_head @@ -1511,6 +1524,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } unlink(git_path("CHERRY_PICK_HEAD")); + unlink(git_path("REVERT_HEAD")); unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_MSG")); unlink(git_path("MERGE_MODE")); diff --git a/builtin/config.c b/builtin/config.c index 0b4ecac855..0315ad76f8 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -99,6 +99,7 @@ static int show_config(const char *key_, const char *value_, void *cb) const char *vptr = value; int must_free_vptr = 0; int dup_error = 0; + int must_print_delim = 0; if (!use_key_regexp && strcmp(key_, key)) return 0; @@ -109,10 +110,8 @@ static int show_config(const char *key_, const char *value_, void *cb) return 0; if (show_keys) { - if (value_) - printf("%s%c", key_, key_delim); - else - printf("%s", key_); + printf("%s", key_); + must_print_delim = 1; } if (seen && !do_all) dup_error = 1; @@ -130,16 +129,23 @@ static int show_config(const char *key_, const char *value_, void *cb) } else if (types == TYPE_PATH) { git_config_pathname(&vptr, key_, value_); must_free_vptr = 1; + } else if (value_) { + vptr = value_; + } else { + /* Just show the key name */ + vptr = ""; + must_print_delim = 0; } - else - vptr = value_?value_:""; seen++; if (dup_error) { error("More than one value for the key %s: %s", key_, vptr); } - else + else { + if (must_print_delim) + printf("%c", key_delim); printf("%s%c", vptr, term); + } if (must_free_vptr) /* If vptr must be freed, it's a pointer to a * dynamically allocated buffer, it's safe to cast to diff --git a/builtin/diff.c b/builtin/diff.c index 1118689fb2..0fe638fc45 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -182,7 +182,7 @@ static int builtin_diff_combined(struct rev_info *revs, hashcpy((unsigned char *)(parent + i), ent[i].item->sha1); diff_tree_combined(parent[0], parent + 1, ents - 1, revs->dense_combined_merges, revs); - free(parent); + free((void *)parent); return 0; } diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 412bd327b5..6207ecd298 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -15,7 +15,9 @@ static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; static int unpack_limit = 100; static int prefer_ofs_delta = 1; -static int no_done = 0; +static int no_done; +static int fetch_fsck_objects = -1; +static int transfer_fsck_objects = -1; static struct fetch_pack_args args = { /* .uploadpack = */ "git-upload-pack", }; @@ -544,7 +546,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) for (ref = *refs; ref; ref = next) { next = ref->next; if (!memcmp(ref->name, "refs/", 5) && - check_ref_format(ref->name + 5)) + check_refname_format(ref->name + 5, 0)) ; /* trash */ else if (args.fetch_all && (!args.depth || prefixcmp(ref->name, "refs/tags/") )) { @@ -554,11 +556,16 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) continue; } else { - int order = path_match(ref->name, nr_match, match); - if (order) { - return_refs[order-1] = ref; - continue; /* we will link it later */ + int i; + for (i = 0; i < nr_match; i++) { + if (!strcmp(ref->name, match[i])) { + match[i][0] = '\0'; + return_refs[i] = ref; + break; + } } + if (i < nr_match) + continue; /* we will link it later */ } free(ref); } @@ -734,6 +741,12 @@ static int get_pack(int xd[2], char **pack_lockfile) } if (*hdr_arg) *av++ = hdr_arg; + if (fetch_fsck_objects >= 0 + ? fetch_fsck_objects + : transfer_fsck_objects >= 0 + ? transfer_fsck_objects + : 0) + *av++ = "--strict"; *av++ = NULL; cmd.in = demux.out; @@ -853,6 +866,16 @@ static int fetch_pack_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "fetch.fsckobjects")) { + fetch_fsck_objects = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "transfer.fsckobjects")) { + transfer_fsck_objects = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -958,7 +981,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.verbose ? CONNECT_VERBOSE : 0); } - get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); + get_remote_heads(fd[0], &ref, 0, NULL); ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, pack_lockfile_ptr); diff --git a/builtin/fetch.c b/builtin/fetch.c index e422ced929..33ad3aad2c 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -13,6 +13,7 @@ #include "sigchain.h" #include "transport.h" #include "submodule.h" +#include "connected.h" static const char * const builtin_fetch_usage[] = { "git fetch [<options>] [<repository> [<refspec>...]]", @@ -239,23 +240,23 @@ static int s_update_ref(const char *action, static int update_local_ref(struct ref *ref, const char *remote, - char *display) + struct strbuf *display) { struct commit *current = NULL, *updated; enum object_type type; struct branch *current_branch = branch_get(NULL); const char *pretty_ref = prettify_refname(ref->name); - *display = 0; type = sha1_object_info(ref->new_sha1, NULL); if (type < 0) die(_("object %s not found"), sha1_to_hex(ref->new_sha1)); if (!hashcmp(ref->old_sha1, ref->new_sha1)) { if (verbosity > 0) - sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH, - _("[up to date]"), REFCOL_WIDTH, remote, - pretty_ref); + strbuf_addf(display, "= %-*s %-*s -> %s", + TRANSPORT_SUMMARY_WIDTH, + _("[up to date]"), REFCOL_WIDTH, + remote, pretty_ref); return 0; } @@ -267,9 +268,10 @@ static int update_local_ref(struct ref *ref, * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ - sprintf(display, _("! %-*s %-*s -> %s (can't fetch in current branch)"), - TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote, - pretty_ref); + strbuf_addf(display, + _("! %-*s %-*s -> %s (can't fetch in current branch)"), + TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), + REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -277,9 +279,11 @@ static int update_local_ref(struct ref *ref, !prefixcmp(ref->name, "refs/tags/")) { int r; r = s_update_ref("updating tag", ref, 0); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-', - TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), REFCOL_WIDTH, remote, - pretty_ref, r ? _(" (unable to update local ref)") : ""); + strbuf_addf(display, "%c %-*s %-*s -> %s%s", + r ? '!' : '-', + TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), + REFCOL_WIDTH, remote, pretty_ref, + r ? _(" (unable to update local ref)") : ""); return r; } @@ -302,9 +306,11 @@ static int update_local_ref(struct ref *ref, } r = s_update_ref(msg, ref, 0); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', - TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, - r ? _(" (unable to update local ref)") : ""); + strbuf_addf(display, "%c %-*s %-*s -> %s%s", + r ? '!' : '*', + TRANSPORT_SUMMARY_WIDTH, what, + REFCOL_WIDTH, remote, pretty_ref, + r ? _(" (unable to update local ref)") : ""); return r; } @@ -318,9 +324,11 @@ static int update_local_ref(struct ref *ref, (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("fast-forward", ref, 1); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', - TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, - pretty_ref, r ? _(" (unable to update local ref)") : ""); + strbuf_addf(display, "%c %-*s %-*s -> %s%s", + r ? '!' : ' ', + TRANSPORT_SUMMARY_WIDTH, quickref, + REFCOL_WIDTH, remote, pretty_ref, + r ? _(" (unable to update local ref)") : ""); return r; } else if (force || ref->force) { char quickref[84]; @@ -332,26 +340,40 @@ static int update_local_ref(struct ref *ref, (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("forced-update", ref, 1); - sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', - TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, - pretty_ref, - r ? _("unable to update local ref") : _("forced update")); + strbuf_addf(display, "%c %-*s %-*s -> %s (%s)", + r ? '!' : '+', + TRANSPORT_SUMMARY_WIDTH, quickref, + REFCOL_WIDTH, remote, pretty_ref, + r ? _("unable to update local ref") : _("forced update")); return r; } else { - sprintf(display, "! %-*s %-*s -> %s %s", - TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote, - pretty_ref, _("(non-fast-forward)")); + strbuf_addf(display, "! %-*s %-*s -> %s %s", + TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), + REFCOL_WIDTH, remote, pretty_ref, + _("(non-fast-forward)")); return 1; } } +static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) +{ + struct ref **rm = cb_data; + struct ref *ref = *rm; + + if (!ref) + return -1; /* end of the list */ + *rm = ref->next; + hashcpy(sha1, ref->old_sha1); + return 0; +} + static int store_updated_refs(const char *raw_url, const char *remote_name, struct ref *ref_map) { FILE *fp; struct commit *commit; - int url_len, i, note_len, shown_url = 0, rc = 0; - char note[1024]; + int url_len, i, shown_url = 0, rc = 0; + struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); @@ -364,6 +386,13 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, url = transport_anonymize_url(raw_url); else url = xstrdup("foreign"); + + rm = ref_map; + if (check_everything_connected(iterate_ref_map, 0, &rm)) { + rc = error(_("%s did not send all necessary objects\n"), url); + goto abort; + } + for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; @@ -407,19 +436,16 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, if (4 < i && !strncmp(".git", url + i - 3, 4)) url_len = i - 3; - note_len = 0; + strbuf_reset(¬e); if (*what) { if (*kind) - note_len += sprintf(note + note_len, "%s ", - kind); - note_len += sprintf(note + note_len, "'%s' of ", what); + strbuf_addf(¬e, "%s ", kind); + strbuf_addf(¬e, "'%s' of ", what); } - note[note_len] = '\0'; fprintf(fp, "%s\t%s\t%s", - sha1_to_hex(commit ? commit->object.sha1 : - rm->old_sha1), + sha1_to_hex(rm->old_sha1), rm->merge ? "" : "not-for-merge", - note); + note.buf); for (i = 0; i < url_len; ++i) if ('\n' == url[i]) fputs("\\n", fp); @@ -427,29 +453,36 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, fputc(url[i], fp); fputc('\n', fp); + strbuf_reset(¬e); if (ref) { - rc |= update_local_ref(ref, what, note); + rc |= update_local_ref(ref, what, ¬e); free(ref); } else - sprintf(note, "* %-*s %-*s -> FETCH_HEAD", - TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch", - REFCOL_WIDTH, *what ? what : "HEAD"); - if (*note) { + strbuf_addf(¬e, "* %-*s %-*s -> FETCH_HEAD", + TRANSPORT_SUMMARY_WIDTH, + *kind ? kind : "branch", + REFCOL_WIDTH, + *what ? what : "HEAD"); + if (note.len) { if (verbosity >= 0 && !shown_url) { fprintf(stderr, _("From %.*s\n"), url_len, url); shown_url = 1; } if (verbosity >= 0) - fprintf(stderr, " %s\n", note); + fprintf(stderr, " %s\n", note.buf); } } - free(url); - fclose(fp); + 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); + + abort: + strbuf_release(¬e); + free(url); + fclose(fp); return rc; } @@ -457,23 +490,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, * We would want to bypass the object transfer altogether if * everything we are going to fetch already exists and is connected * locally. - * - * The refs we are going to fetch are in ref_map. If running - * - * $ git rev-list --objects --stdin --not --all - * - * (feeding all the refs in ref_map on its standard input) - * does not error out, that means everything reachable from the - * refs we are going to fetch exists and is connected to some of - * our existing refs. */ static int quickfetch(struct ref *ref_map) { - struct child_process revlist; - struct ref *ref; - int err; - const char *argv[] = {"rev-list", - "--quiet", "--objects", "--stdin", "--not", "--all", NULL}; + struct ref *rm = ref_map; /* * If we are deepening a shallow clone we already have these @@ -484,47 +504,7 @@ static int quickfetch(struct ref *ref_map) */ if (depth) return -1; - - if (!ref_map) - return 0; - - memset(&revlist, 0, sizeof(revlist)); - revlist.argv = argv; - revlist.git_cmd = 1; - revlist.no_stdout = 1; - revlist.no_stderr = 1; - revlist.in = -1; - - err = start_command(&revlist); - if (err) { - error(_("could not run rev-list")); - return err; - } - - /* - * If rev-list --stdin encounters an unknown commit, it terminates, - * which will cause SIGPIPE in the write loop below. - */ - sigchain_push(SIGPIPE, SIG_IGN); - - for (ref = ref_map; ref; ref = ref->next) { - if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || - write_str_in_full(revlist.in, "\n") < 0) { - if (errno != EPIPE && errno != EINVAL) - error(_("failed write to rev-list: %s"), strerror(errno)); - err = -1; - break; - } - } - - if (close(revlist.in)) { - error(_("failed to close rev-list's stdin: %s"), strerror(errno)); - err = -1; - } - - sigchain_pop(SIGPIPE); - - return finish_command(&revlist) || err; + return check_everything_connected(iterate_ref_map, 1, &rm); } static int fetch_refs(struct transport *transport, struct ref *ref_map) @@ -540,10 +520,10 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map) return ret; } -static int prune_refs(struct transport *transport, struct ref *ref_map) +static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map) { int result = 0; - struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); + struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map); const char *dangling_msg = dry_run ? _(" (%s will become dangling)\n") : _(" (%s has become dangling)\n"); @@ -734,8 +714,31 @@ static int do_fetch(struct transport *transport, free_refs(ref_map); return 1; } - if (prune) - prune_refs(transport, ref_map); + if (prune) { + /* If --tags was specified, pretend the user gave us the canonical tags refspec */ + if (tags == TAGS_SET) { + const char *tags_str = "refs/tags/*:refs/tags/*"; + struct refspec *tags_refspec, *refspec; + + /* Copy the refspec and add the tags to it */ + refspec = xcalloc(ref_count + 1, sizeof(struct refspec)); + tags_refspec = parse_fetch_refspec(1, &tags_str); + memcpy(refspec, refs, ref_count * sizeof(struct refspec)); + memcpy(&refspec[ref_count], tags_refspec, sizeof(struct refspec)); + ref_count++; + + prune_refs(refspec, ref_count, ref_map); + + ref_count--; + /* The rest of the strings belong to fetch_one */ + free_refspec(1, tags_refspec); + free(refspec); + } else if (ref_count) { + prune_refs(refs, ref_count, ref_map); + } else { + prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map); + } + } free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag @@ -918,7 +921,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) atexit(unlock_pack); refspec = parse_fetch_refspec(ref_nr, refs); exit_code = do_fetch(transport, refspec, ref_nr); - free(refspec); + free_refspec(ref_nr, refspec); transport_disconnect(transport); transport = NULL; return exit_code; diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 75816329d6..c81a7fef26 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -5,32 +5,43 @@ #include "revision.h" #include "tag.h" #include "string-list.h" +#include "branch.h" +#include "fmt-merge-msg.h" +#include "gpg-interface.h" static const char * const fmt_merge_msg_usage[] = { "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]", NULL }; -static int shortlog_len; +static int use_branch_desc; -static int fmt_merge_msg_config(const char *key, const char *value, void *cb) +int fmt_merge_msg_config(const char *key, const char *value, void *cb) { if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { int is_bool; - shortlog_len = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool && shortlog_len < 0) + merge_log_config = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool && merge_log_config < 0) return error("%s: negative length %s", key, value); - if (is_bool && shortlog_len) - shortlog_len = DEFAULT_MERGE_LOG_LEN; + if (is_bool && merge_log_config) + merge_log_config = DEFAULT_MERGE_LOG_LEN; + } else if (!strcmp(key, "merge.branchdesc")) { + use_branch_desc = git_config_bool(key, value); } return 0; } +/* merge data per repository where the merged tips came from */ struct src_data { struct string_list branch, tag, r_branch, generic; int head_status; }; +struct origin_data { + unsigned char sha1[20]; + unsigned is_local_branch:1; +}; + static void init_src_data(struct src_data *data) { data->branch.strdup_strings = 1; @@ -45,7 +56,7 @@ static struct string_list origins = STRING_LIST_INIT_DUP; static int handle_line(char *line) { int i, len = strlen(line); - unsigned char *sha1; + struct origin_data *origin_data; char *src, *origin; struct src_data *src_data; struct string_list_item *item; @@ -61,16 +72,23 @@ static int handle_line(char *line) return 2; line[40] = 0; - sha1 = xmalloc(20); - i = get_sha1(line, sha1); + origin_data = xcalloc(1, sizeof(struct origin_data)); + i = get_sha1(line, origin_data->sha1); line[40] = '\t'; - if (i) + if (i) { + free(origin_data); return 3; + } if (line[len - 1] == '\n') line[len - 1] = 0; line += 42; + /* + * At this point, line points at the beginning of comment e.g. + * "branch 'frotz' of git://that/repository.git". + * Find the repository name and point it with src. + */ src = strstr(line, " of "); if (src) { *src = 0; @@ -93,6 +111,7 @@ static int handle_line(char *line) origin = src; src_data->head_status |= 1; } else if (!prefixcmp(line, "branch ")) { + origin_data->is_local_branch = 1; origin = line + 7; string_list_append(&src_data->branch, origin); src_data->head_status |= 2; @@ -119,7 +138,9 @@ static int handle_line(char *line) sprintf(new_origin, "%s of %s", origin, src); origin = new_origin; } - string_list_append(&origins, origin)->util = sha1; + if (strcmp(".", src)) + origin_data->is_local_branch = 0; + string_list_append(&origins, origin)->util = origin_data; return 0; } @@ -140,9 +161,30 @@ static void print_joined(const char *singular, const char *plural, } } -static void shortlog(const char *name, unsigned char *sha1, - struct commit *head, struct rev_info *rev, int limit, - struct strbuf *out) +static void add_branch_desc(struct strbuf *out, const char *name) +{ + struct strbuf desc = STRBUF_INIT; + + if (!read_branch_desc(&desc, name)) { + const char *bp = desc.buf; + while (*bp) { + const char *ep = strchrnul(bp, '\n'); + if (*ep) + ep++; + strbuf_addf(out, " : %.*s", (int)(ep - bp), bp); + bp = ep; + } + if (out->buf[out->len - 1] != '\n') + strbuf_addch(out, '\n'); + } + strbuf_release(&desc); +} + +static void shortlog(const char *name, + struct origin_data *origin_data, + struct commit *head, + struct rev_info *rev, int limit, + struct strbuf *out) { int i, count = 0; struct commit *commit; @@ -150,6 +192,7 @@ static void shortlog(const char *name, unsigned char *sha1, struct string_list subjects = STRING_LIST_INIT_DUP; int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; struct strbuf sb = STRBUF_INIT; + const unsigned char *sha1 = origin_data->sha1; branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); if (!branch || branch->type != OBJ_COMMIT) @@ -188,6 +231,9 @@ static void shortlog(const char *name, unsigned char *sha1, else strbuf_addf(out, "\n* %s:\n", name); + if (origin_data->is_local_branch && use_branch_desc) + add_branch_desc(out, name); + for (i = 0; i < subjects.nr; i++) if (i >= limit) strbuf_addf(out, " ...\n"); @@ -203,7 +249,7 @@ static void shortlog(const char *name, unsigned char *sha1, string_list_clear(&subjects, 0); } -static void do_fmt_merge_msg_title(struct strbuf *out, +static void fmt_merge_msg_title(struct strbuf *out, const char *current_branch) { int i = 0; char *sep = ""; @@ -256,14 +302,81 @@ static void do_fmt_merge_msg_title(struct strbuf *out, strbuf_addf(out, " into %s\n", current_branch); } -static int do_fmt_merge_msg(int merge_title, struct strbuf *in, - struct strbuf *out, int shortlog_len) { +static void fmt_tag_signature(struct strbuf *tagbuf, + struct strbuf *sig, + const char *buf, + unsigned long len) +{ + const char *tag_body = strstr(buf, "\n\n"); + if (tag_body) { + tag_body += 2; + strbuf_add(tagbuf, tag_body, buf + len - tag_body); + } + strbuf_complete_line(tagbuf); + strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len); +} + +static void fmt_merge_msg_sigs(struct strbuf *out) +{ + int i, tag_number = 0, first_tag = 0; + struct strbuf tagbuf = STRBUF_INIT; + + for (i = 0; i < origins.nr; i++) { + unsigned char *sha1 = origins.items[i].util; + enum object_type type; + unsigned long size, len; + char *buf = read_sha1_file(sha1, &type, &size); + struct strbuf sig = STRBUF_INIT; + + if (!buf || type != OBJ_TAG) + goto next; + len = parse_signature(buf, size); + + if (size == len) + ; /* merely annotated */ + else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) { + if (!sig.len) + strbuf_addstr(&sig, "gpg verification failed.\n"); + } + + if (!tag_number++) { + fmt_tag_signature(&tagbuf, &sig, buf, len); + first_tag = i; + } else { + if (tag_number == 2) { + struct strbuf tagline = STRBUF_INIT; + strbuf_addf(&tagline, "\n# %s\n", + origins.items[first_tag].string); + strbuf_insert(&tagbuf, 0, tagline.buf, + tagline.len); + strbuf_release(&tagline); + } + strbuf_addf(&tagbuf, "\n# %s\n", + origins.items[i].string); + fmt_tag_signature(&tagbuf, &sig, buf, len); + } + strbuf_release(&sig); + next: + free(buf); + } + if (tagbuf.len) { + strbuf_addch(out, '\n'); + strbuf_addbuf(out, &tagbuf); + } + strbuf_release(&tagbuf); +} + +int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + struct fmt_merge_msg_opts *opts) +{ int i = 0, pos = 0; unsigned char head_sha1[20]; const char *current_branch; + void *current_branch_to_free; /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); + current_branch = current_branch_to_free = + resolve_refdup("HEAD", head_sha1, 1, NULL); if (!current_branch) die("No current branch"); if (!prefixcmp(current_branch, "refs/heads/")) @@ -283,17 +396,17 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in, die ("Error in line %d: %.*s", i, len, p); } - if (!srcs.nr) - return 0; + if (opts->add_title && srcs.nr) + fmt_merge_msg_title(out, current_branch); - if (merge_title) - do_fmt_merge_msg_title(out, current_branch); + if (origins.nr) + fmt_merge_msg_sigs(out); - if (shortlog_len) { + if (opts->shortlog_len) { struct commit *head; struct rev_info rev; - head = lookup_commit(head_sha1); + head = lookup_commit_or_die(head_sha1, "HEAD"); init_revisions(&rev, NULL); rev.commit_format = CMIT_FMT_ONELINE; rev.ignore_merges = 1; @@ -303,21 +416,21 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in, strbuf_addch(out, '\n'); for (i = 0; i < origins.nr; i++) - shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, shortlog_len, out); + shortlog(origins.items[i].string, + origins.items[i].util, + head, &rev, opts->shortlog_len, out); } - return 0; -} -int fmt_merge_msg(struct strbuf *in, struct strbuf *out, - int merge_title, int shortlog_len) { - return do_fmt_merge_msg(merge_title, in, out, shortlog_len); + strbuf_complete_line(out); + free(current_branch_to_free); + return 0; } int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; const char *message = NULL; + int shortlog_len = -1; struct option options[] = { { OPTION_INTEGER, 0, "log", &shortlog_len, "n", "populate log with at most <n> entries from shortlog", @@ -335,20 +448,15 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) FILE *in = stdin; struct strbuf input = STRBUF_INIT, output = STRBUF_INIT; int ret; + struct fmt_merge_msg_opts opts; git_config(fmt_merge_msg_config, NULL); argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage, 0); if (argc > 0) usage_with_options(fmt_merge_msg_usage, options); - if (message && !shortlog_len) { - char nl = '\n'; - write_in_full(STDOUT_FILENO, message, strlen(message)); - write_in_full(STDOUT_FILENO, &nl, 1); - return 0; - } if (shortlog_len < 0) - die("Negative --log=%d", shortlog_len); + shortlog_len = (merge_log_config > 0) ? merge_log_config : 0; if (inpath && strcmp(inpath, "-")) { in = fopen(inpath, "r"); @@ -361,10 +469,12 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (message) strbuf_addstr(&output, message); - ret = fmt_merge_msg(&input, &output, - message ? 0 : 1, - shortlog_len); + memset(&opts, 0, sizeof(opts)); + opts.add_title = !message; + opts.shortlog_len = shortlog_len; + + ret = fmt_merge_msg(&input, &output, &opts); if (ret) return ret; write_in_full(STDOUT_FILENO, output.buf, output.len); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 89e75c6894..b01d76a243 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -69,6 +69,9 @@ static struct { { "subject" }, { "body" }, { "contents" }, + { "contents:subject" }, + { "contents:body" }, + { "contents:signature" }, { "upstream" }, { "symref" }, { "flag" }, @@ -361,6 +364,18 @@ static const char *copy_email(const char *buf) return xmemdupz(email, eoemail + 1 - email); } +static char *copy_subject(const char *buf, unsigned long len) +{ + char *r = xmemdupz(buf, len); + int i; + + for (i = 0; i < len; i++) + if (r[i] == '\n') + r[i] = ' '; + + return r; +} + static void grab_date(const char *buf, struct atom_value *v, const char *atomname) { const char *eoemail = strstr(buf, "> "); @@ -458,38 +473,56 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru } } -static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) +static void find_subpos(const char *buf, unsigned long sz, + const char **sub, unsigned long *sublen, + const char **body, unsigned long *bodylen, + unsigned long *nonsiglen, + const char **sig, unsigned long *siglen) { - while (*buf) { - const char *eol = strchr(buf, '\n'); - if (!eol) - return; - if (eol[1] == '\n') { - buf = eol + 1; - break; /* found end of header */ - } - buf = eol + 1; + const char *eol; + /* skip past header until we hit empty line */ + while (*buf && *buf != '\n') { + eol = strchrnul(buf, '\n'); + if (*eol) + eol++; + buf = eol; } + /* skip any empty lines */ while (*buf == '\n') buf++; - if (!*buf) - return; - *sub = buf; /* first non-empty line */ - buf = strchr(buf, '\n'); - if (!buf) { - *body = ""; - return; /* no body */ + + /* parse signature first; we might not even have a subject line */ + *sig = buf + parse_signature(buf, strlen(buf)); + *siglen = strlen(*sig); + + /* subject is first non-empty line */ + *sub = buf; + /* subject goes to first empty line */ + while (buf < *sig && *buf && *buf != '\n') { + eol = strchrnul(buf, '\n'); + if (*eol) + eol++; + buf = eol; } + *sublen = buf - *sub; + /* drop trailing newline, if present */ + if (*sublen && (*sub)[*sublen - 1] == '\n') + *sublen -= 1; + + /* skip any empty lines */ while (*buf == '\n') - buf++; /* skip blank between subject and body */ + buf++; *body = buf; + *bodylen = strlen(buf); + *nonsiglen = *sig - buf; } /* See grab_values */ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; - const char *subpos = NULL, *bodypos = NULL; + const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; + unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; @@ -500,17 +533,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj name++; if (strcmp(name, "subject") && strcmp(name, "body") && - strcmp(name, "contents")) + strcmp(name, "contents") && + strcmp(name, "contents:subject") && + strcmp(name, "contents:body") && + strcmp(name, "contents:signature")) continue; if (!subpos) - find_subpos(buf, sz, &subpos, &bodypos); - if (!subpos) - return; + find_subpos(buf, sz, + &subpos, &sublen, + &bodypos, &bodylen, &nonsiglen, + &sigpos, &siglen); if (!strcmp(name, "subject")) - v->s = copy_line(subpos); + v->s = copy_subject(subpos, sublen); + else if (!strcmp(name, "contents:subject")) + v->s = copy_subject(subpos, sublen); else if (!strcmp(name, "body")) - v->s = xstrdup(bodypos); + v->s = xmemdupz(bodypos, bodylen); + else if (!strcmp(name, "contents:body")) + v->s = xmemdupz(bodypos, nonsiglen); + else if (!strcmp(name, "contents:signature")) + v->s = xmemdupz(sigpos, siglen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); } @@ -585,11 +628,8 @@ static void populate_value(struct refinfo *ref) if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { unsigned char unused1[20]; - const char *symref; - symref = resolve_ref(ref->refname, unused1, 1, NULL); - if (symref) - ref->symref = xstrdup(symref); - else + ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL); + if (!ref->symref) ref->symref = ""; } diff --git a/builtin/fsck.c b/builtin/fsck.c index 5ae0366bc8..8c479a791b 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -11,6 +11,7 @@ #include "fsck.h" #include "parse-options.h" #include "dir.h" +#include "progress.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -27,8 +28,10 @@ static const char *head_points_at; static int errors_found; static int write_lost_and_found; static int verbose; +static int show_progress = -1; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 +#define ERROR_PACK 04 #ifdef NO_D_INO_IN_DIRENT #define SORT_DIRENT 0 @@ -137,7 +140,11 @@ static int traverse_one_object(struct object *obj) static int traverse_reachable(void) { + struct progress *progress = NULL; + unsigned int nr = 0; int result = 0; + if (show_progress) + progress = start_progress_delay("Checking connectivity", 0, 0, 2); while (pending.nr) { struct object_array_entry *entry; struct object *obj; @@ -145,7 +152,9 @@ static int traverse_reachable(void) entry = pending.objects + --pending.nr; obj = entry->item; result |= traverse_one_object(obj); + display_progress(progress, ++nr); } + stop_progress(&progress); return !!result; } @@ -231,12 +240,9 @@ static void check_unreachable_object(struct object *obj) unsigned long size; char *buf = read_sha1_file(obj->sha1, &type, &size); - if (buf) { - if (fwrite(buf, size, 1, f) != 1) - die_errno("Could not write '%s'", - filename); - free(buf); - } + if (buf && fwrite(buf, 1, size, f) != size) + die_errno("Could not write '%s'", filename); + free(buf); } else fprintf(f, "%s\n", sha1_to_hex(obj->sha1)); if (fclose(f)) @@ -284,14 +290,8 @@ static void check_connectivity(void) } } -static int fsck_sha1(const unsigned char *sha1) +static int fsck_obj(struct object *obj) { - struct object *obj = parse_object(sha1); - if (!obj) { - errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", - sha1_to_hex(sha1)); - } if (obj->flags & SEEN) return 0; obj->flags |= SEEN; @@ -334,6 +334,29 @@ static int fsck_sha1(const unsigned char *sha1) return 0; } +static int fsck_sha1(const unsigned char *sha1) +{ + struct object *obj = parse_object(sha1); + if (!obj) { + errors_found |= ERROR_OBJECT; + return error("%s: object corrupt or missing", + sha1_to_hex(sha1)); + } + return fsck_obj(obj); +} + +static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, + unsigned long size, void *buffer, int *eaten) +{ + struct object *obj; + obj = parse_object_buffer(sha1, type, size, buffer, eaten); + if (!obj) { + errors_found |= ERROR_OBJECT; + return error("%s: object corrupt or missing", sha1_to_hex(sha1)); + } + return fsck_obj(obj); +} + /* * This is the sorting chunk size: make it reasonably * big so that we can sort well.. @@ -515,15 +538,20 @@ static void get_default_heads(void) static void fsck_object_dir(const char *path) { int i; + struct progress *progress = NULL; if (verbose) fprintf(stderr, "Checking object directory\n"); + if (show_progress) + progress = start_progress("Checking object directories", 256); for (i = 0; i < 256; i++) { static char dir[4096]; sprintf(dir, "%s/%02x", path, i); fsck_dir(i, dir); + display_progress(progress, i+1); } + stop_progress(&progress); fsck_sha1_list(); } @@ -535,7 +563,7 @@ static int fsck_head_link(void) if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag); + head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag); if (!head_points_at) return error("Invalid HEAD"); if (!strcmp(head_points_at, "HEAD")) @@ -594,6 +622,7 @@ static struct option fsck_opts[] = { OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"), OPT_BOOLEAN(0, "lost-found", &write_lost_and_found, "write dangling objects in .git/lost-found"), + OPT_BOOL(0, "progress", &show_progress, "show progress"), OPT_END(), }; @@ -606,6 +635,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) read_replace_refs = 0; argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); + + if (show_progress == -1) + show_progress = isatty(2); + if (verbose) + show_progress = 0; + if (write_lost_and_found) { check_full = 1; include_reflogs = 0; @@ -625,20 +660,28 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (check_full) { struct packed_git *p; + uint32_t total = 0, count = 0; + struct progress *progress = NULL; prepare_packed_git(); - for (p = packed_git; p; p = p->next) - /* verify gives error messages itself */ - verify_pack(p); + if (show_progress) { + for (p = packed_git; p; p = p->next) { + if (open_pack_index(p)) + continue; + total += p->num_objects; + } + + progress = start_progress("Checking objects", total); + } for (p = packed_git; p; p = p->next) { - uint32_t j, num; - if (open_pack_index(p)) - continue; - num = p->num_objects; - for (j = 0; j < num; j++) - fsck_sha1(nth_packed_object_sha1(p, j)); + /* verify gives error messages itself */ + if (verify_pack(p, fsck_obj_buffer, + progress, count)) + errors_found |= ERROR_PACK; + count += p->num_objects; } + stop_progress(&progress); } heads = 0; diff --git a/builtin/gc.c b/builtin/gc.c index 0498094711..271376d82b 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -32,7 +32,7 @@ static const char *prune_expire = "2.weeks.ago"; static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL}; static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL}; static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL}; -static const char *argv_prune[] = {"prune", "--expire", NULL, NULL}; +static const char *argv_prune[] = {"prune", "--expire", NULL, NULL, NULL}; static const char *argv_rerere[] = {"rerere", "gc", NULL}; static int gc_config(const char *var, const char *value, void *cb) @@ -243,6 +243,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (prune_expire) { argv_prune[2] = prune_expire; + if (quiet) + argv_prune[3] = "--no-progress"; if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) return error(FAILED_RUN, argv_prune[0]); } diff --git a/builtin/grep.c b/builtin/grep.c index 1c359c2671..988ea1d332 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -74,13 +74,32 @@ static int all_work_added; /* This lock protects all the variables above. */ static pthread_mutex_t grep_mutex; +static inline void grep_lock(void) +{ + if (use_threads) + pthread_mutex_lock(&grep_mutex); +} + +static inline void grep_unlock(void) +{ + if (use_threads) + pthread_mutex_unlock(&grep_mutex); +} + /* Used to serialize calls to read_sha1_file. */ static pthread_mutex_t read_sha1_mutex; -#define grep_lock() pthread_mutex_lock(&grep_mutex) -#define grep_unlock() pthread_mutex_unlock(&grep_mutex) -#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex) -#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex) +static inline void read_sha1_lock(void) +{ + if (use_threads) + pthread_mutex_lock(&read_sha1_mutex); +} + +static inline void read_sha1_unlock(void) +{ + if (use_threads) + pthread_mutex_unlock(&read_sha1_mutex); +} /* Signalled when a new work_item is added to todo. */ static pthread_cond_t cond_add; @@ -354,13 +373,9 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type { void *data; - if (use_threads) { - read_sha1_lock(); - data = read_sha1_file(sha1, type, size); - read_sha1_unlock(); - } else { - data = read_sha1_file(sha1, type, size); - } + read_sha1_lock(); + data = read_sha1_file(sha1, type, size); + read_sha1_unlock(); return data; } @@ -542,18 +557,19 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, struct tree_desc *tree, struct strbuf *base, int tn_len) { - int hit = 0, match = 0; + int hit = 0; + enum interesting match = entry_not_interesting; struct name_entry entry; int old_baselen = base->len; while (tree_entry(tree, &entry)) { - int te_len = tree_entry_len(entry.path, entry.sha1); + int te_len = tree_entry_len(&entry); - if (match != 2) { + if (match != all_entries_interesting) { match = tree_entry_interesting(&entry, base, tn_len, pathspec); - if (match < 0) + if (match == all_entries_not_interesting) break; - if (match == 0) + if (match == entry_not_interesting) continue; } @@ -598,8 +614,11 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct strbuf base; int hit, len; + read_sha1_lock(); data = read_object_with_reference(obj->sha1, tree_type, &size, NULL); + read_sha1_unlock(); + if (!data) die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1)); @@ -637,13 +656,15 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, return hit; } -static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec) +static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, + int exc_std) { struct dir_struct dir; int i, hit = 0; memset(&dir, 0, sizeof(dir)); - setup_standard_excludes(&dir); + if (exc_std) + setup_standard_excludes(&dir); fill_directory(&dir, pathspec->raw); for (i = 0; i < dir.nr; i++) { @@ -750,7 +771,7 @@ static int help_callback(const struct option *opt, const char *arg, int unset) int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; - int cached = 0; + int cached = 0, untracked = 0, opt_exclude = -1; int seen_dashdash = 0; int external_grep_allowed__ignored; const char *show_in_pager = NULL, *default_pager = "dummy"; @@ -774,8 +795,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_BOOLEAN(0, "cached", &cached, "search in index instead of in the work tree"), - OPT_BOOLEAN(0, "index", &use_index, - "--no-index finds in contents not managed by git"), + { OPTION_BOOLEAN, 0, "index", &use_index, NULL, + "finds in contents not managed by git", + PARSE_OPT_NOARG | PARSE_OPT_NEGHELP }, + OPT_BOOLEAN(0, "untracked", &untracked, + "search in both tracked and untracked files"), + OPT_SET_INT(0, "exclude-standard", &opt_exclude, + "search also in ignored files", 1), OPT_GROUP(""), OPT_BOOLEAN('v', "invert-match", &opt.invert, "show non-matching lines"), @@ -1045,13 +1071,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!show_in_pager) setup_pager(); + if (!use_index && (untracked || cached)) + die(_("--cached or --untracked cannot be used with --no-index.")); - if (!use_index) { - if (cached) - die(_("--cached cannot be used with --no-index.")); + if (!use_index || untracked) { + int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; if (list.nr) - die(_("--no-index cannot be used with revs.")); - hit = grep_directory(&opt, &pathspec); + die(_("--no-index or --untracked cannot be used with revs.")); + hit = grep_directory(&opt, &pathspec, use_exclude); + } else if (0 <= opt_exclude) { + die(_("--[no-]exclude-standard cannot be used for tracked contents.")); } else if (!list.nr) { if (!cached) setup_work_tree(); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 0945adbb3b..98025da767 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1122,8 +1122,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (!index_name) die("--verify with no packfile name given"); read_idx_option(&opts, index_name); - opts.flags |= WRITE_IDX_VERIFY; + opts.flags |= WRITE_IDX_VERIFY | WRITE_IDX_STRICT; } + if (strict) + opts.flags |= WRITE_IDX_STRICT; curr_pack = open_pack_file(pack_name); parse_pack_header(); diff --git a/builtin/init-db.c b/builtin/init-db.c index d07554c884..0dacb8b79c 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -351,7 +351,7 @@ static void separate_git_dir(const char *git_dir) else if (S_ISDIR(st.st_mode)) src = git_link; else - die(_("unable to handle file type %d"), st.st_mode); + die(_("unable to handle file type %d"), (int)st.st_mode); if (rename(src, git_dir)) die_errno(_("unable to move %s to %s"), src, git_dir); diff --git a/builtin/log.c b/builtin/log.c index f5d4930590..7d1f6f88a0 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -19,6 +19,7 @@ #include "remote.h" #include "string-list.h" #include "parse-options.h" +#include "branch.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -72,8 +73,6 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse static void cmd_log_init_defaults(struct rev_info *rev) { - rev->abbrev = DEFAULT_ABBREV; - rev->commit_format = CMIT_FMT_DEFAULT; if (fmt_pretty) get_commit_format(fmt_pretty, rev); rev->verbose_header = 1; @@ -746,10 +745,24 @@ static void print_signature(void) printf("-- \n%s\n\n", signature); } +static void add_branch_description(struct strbuf *buf, const char *branch_name) +{ + struct strbuf desc = STRBUF_INIT; + if (!branch_name || !*branch_name) + return; + read_branch_desc(&desc, branch_name); + if (desc.len) { + strbuf_addch(buf, '\n'); + strbuf_add(buf, desc.buf, desc.len); + strbuf_addch(buf, '\n'); + } +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, int numbered, int numbered_files, struct commit *origin, int nr, struct commit **list, struct commit *head, + const char *branch_name, int quiet) { const char *committer; @@ -807,6 +820,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, pp_user_info(&pp, NULL, &sb, committer, encoding); pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); pp_remainder(&pp, &msg, &sb, 0); + add_branch_description(&sb, branch_name); printf("%s\n", sb.buf); strbuf_release(&sb); @@ -1006,6 +1020,35 @@ static int cc_callback(const struct option *opt, const char *arg, int unset) return 0; } +static char *find_branch_name(struct rev_info *rev) +{ + int i, positive = -1; + unsigned char branch_sha1[20]; + struct strbuf buf = STRBUF_INIT; + const char *branch; + + for (i = 0; i < rev->cmdline.nr; i++) { + if (rev->cmdline.rev[i].flags & UNINTERESTING) + continue; + if (positive < 0) + positive = i; + else + return NULL; + } + if (positive < 0) + return NULL; + strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name); + branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL); + if (!branch || + prefixcmp(branch, "refs/heads/") || + hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1)) + branch = NULL; + strbuf_release(&buf); + if (branch) + return xstrdup(rev->cmdline.rev[positive].name); + return NULL; +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1027,6 +1070,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; + char *branch_name = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, "use [PATCH n/m] even with a single patch", @@ -1217,8 +1261,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * origin" that prepares what the origin side still * does not have. */ + unsigned char sha1[20]; + const char *ref; + rev.pending.objects[0].item->flags |= UNINTERESTING; add_head_to_pending(&rev); + ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + if (ref && !prefixcmp(ref, "refs/heads/")) + branch_name = xstrdup(ref + strlen("refs/heads/")); + else + branch_name = xstrdup(""); /* no branch */ } /* * Otherwise, it is "format-patch -22 HEAD", and/or @@ -1234,16 +1286,26 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.show_root_diff = 1; if (cover_letter) { - /* remember the range */ + /* + * NEEDSWORK:randomly pick one positive commit to show + * diffstat; this is often the tip and the command + * happens to do the right thing in most cases, but a + * complex command like "--cover-letter a b c ^bottom" + * picks "c" and shows diffstat between bottom..c + * which may not match what the series represents at + * all and totally broken. + */ int i; for (i = 0; i < rev.pending.nr; i++) { struct object *o = rev.pending.objects[i].item; if (!(o->flags & UNINTERESTING)) head = (struct commit *)o; } - /* We can't generate a cover letter without any patches */ + /* There is nothing to show; it is not an error, though. */ if (!head) return 0; + if (!branch_name) + branch_name = find_branch_name(&rev); } if (ignore_if_in_upstream) { @@ -1294,7 +1356,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, numbered, numbered_files, - origin, nr, list, head, quiet); + origin, nr, list, head, branch_name, quiet); total++; start_number--; } @@ -1366,6 +1428,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) fclose(stdout); } free(list); + free(branch_name); string_list_clear(&extra_to, 0); string_list_clear(&extra_cc, 0); string_list_clear(&extra_hdr, 0); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index e8a800d3ac..7cff175745 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -545,7 +545,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) strip_trailing_slash_from_submodules(); /* Find common prefix for all pathspec's */ - max_prefix = pathspec_prefix(prefix, pathspec); + max_prefix = common_prefix(pathspec); max_prefix_len = max_prefix ? strlen(max_prefix) : 0; /* Treat unmatching pathspec elements as errors */ diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 10223092a9..41c88a98a2 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -43,6 +43,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) struct transport *transport; const struct ref *ref; + if (argc == 2 && !strcmp("-h", argv[1])) + usage(ls_remote_usage); + for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin/merge.c b/builtin/merge.c index ab4077f272..24579409c0 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -26,6 +26,7 @@ #include "merge-recursive.h" #include "resolve-undo.h" #include "remote.h" +#include "fmt-merge-msg.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -44,13 +45,13 @@ static const char * const builtin_merge_usage[] = { NULL }; -static int show_diffstat = 1, shortlog_len, squash; +static int show_diffstat = 1, shortlog_len = -1, squash; static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only; +static int fast_forward_only, option_edit; static int allow_trivial = 1, have_message; +static int overwrite_ignore = 1; static struct strbuf merge_msg; static struct commit_list *remoteheads; -static unsigned char head[20], stash[20]; static struct strategy **use_strategies; static size_t use_strategies_nr, use_strategies_alloc; static const char **xopts; @@ -190,6 +191,8 @@ static struct option builtin_merge_options[] = { "create a single commit instead of doing a merge"), OPT_BOOLEAN(0, "commit", &option_commit, "perform a commit if the merge succeeds (default)"), + OPT_BOOLEAN('e', "edit", &option_edit, + "edit message before committing"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, "allow fast-forward (default)"), OPT_BOOLEAN(0, "ff-only", &fast_forward_only, @@ -206,6 +209,7 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "abort", &abort_current_merge, "abort the current in-progress merge"), OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1), + OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"), OPT_END() }; @@ -217,7 +221,7 @@ static void drop_save(void) unlink(git_path("MERGE_MODE")); } -static void save_state(void) +static int save_state(unsigned char *stash) { int len; struct child_process cp; @@ -236,11 +240,12 @@ static void save_state(void) if (finish_command(&cp) || len < 0) die(_("stash failed")); - else if (!len) - return; + else if (!len) /* no changes */ + return -1; strbuf_setlen(&buffer, buffer.len-1); if (get_sha1(buffer.buf, stash)) die(_("not a valid object: %s"), buffer.buf); + return 0; } static void read_empty(unsigned const char *sha1, int verbose) @@ -278,7 +283,8 @@ static void reset_hard(unsigned const char *sha1, int verbose) die(_("read-tree failed")); } -static void restore_state(void) +static void restore_state(const unsigned char *head, + const unsigned char *stash) { struct strbuf sb = STRBUF_INIT; const char *args[] = { "stash", "apply", NULL, NULL }; @@ -308,25 +314,25 @@ static void finish_up_to_date(const char *msg) drop_save(); } -static void squash_message(void) +static void squash_message(struct commit *commit) { struct rev_info rev; - struct commit *commit; struct strbuf out = STRBUF_INIT; struct commit_list *j; + const char *filename; int fd; struct pretty_print_context ctx = {0}; printf(_("Squash commit -- not updating HEAD\n")); - fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); + filename = git_path("SQUASH_MSG"); + fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) - die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG")); + die_errno(_("Could not write to '%s'"), filename); init_revisions(&rev, NULL); rev.ignore_merges = 1; rev.commit_format = CMIT_FMT_MEDIUM; - commit = lookup_commit(head); commit->object.flags |= UNINTERESTING; add_pending_object(&rev, &commit->object, NULL); @@ -355,9 +361,11 @@ static void squash_message(void) strbuf_release(&out); } -static void finish(const unsigned char *new_head, const char *msg) +static void finish(struct commit *head_commit, + const unsigned char *new_head, const char *msg) { struct strbuf reflog_message = STRBUF_INIT; + const unsigned char *head = head_commit->object.sha1; if (!msg) strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); @@ -368,7 +376,7 @@ static void finish(const unsigned char *new_head, const char *msg) getenv("GIT_REFLOG_ACTION"), msg); } if (squash) { - squash_message(); + squash_message(head_commit); } else { if (verbosity >= 0 && !merge_msg.len) printf(_("No merge message -- not updating HEAD\n")); @@ -406,8 +414,8 @@ static void finish(const unsigned char *new_head, const char *msg) /* Get the name for the merge commit's message. */ static void merge_name(const char *remote, struct strbuf *msg) { - struct object *remote_head; - unsigned char branch_head[20], buf_sha[20]; + struct commit *remote_head; + unsigned char branch_head[20]; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; const char *ptr; @@ -418,7 +426,7 @@ static void merge_name(const char *remote, struct strbuf *msg) remote = bname.buf; memset(branch_head, 0, sizeof(branch_head)); - remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); + remote_head = get_merge_parent(remote); if (!remote_head) die(_("'%s' does not point to a commit"), remote); @@ -428,6 +436,11 @@ static void merge_name(const char *remote, struct strbuf *msg) sha1_to_hex(branch_head), remote); goto cleanup; } + if (!prefixcmp(found_ref, "refs/tags/")) { + strbuf_addf(msg, "%s\t\ttag '%s' of .\n", + sha1_to_hex(branch_head), remote); + goto cleanup; + } if (!prefixcmp(found_ref, "refs/remotes/")) { strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", sha1_to_hex(branch_head), remote); @@ -466,10 +479,10 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_addstr(&truname, "refs/heads/"); strbuf_addstr(&truname, remote); strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 1, NULL)) { + if (ref_exists(truname.buf)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", - sha1_to_hex(remote_head->sha1), + sha1_to_hex(remote_head->object.sha1), truname.buf + 11, (early ? " (early part)" : "")); strbuf_release(&truname); @@ -479,14 +492,16 @@ static void merge_name(const char *remote, struct strbuf *msg) if (!strcmp(remote, "FETCH_HEAD") && !access(git_path("FETCH_HEAD"), R_OK)) { + const char *filename; FILE *fp; struct strbuf line = STRBUF_INIT; char *ptr; - fp = fopen(git_path("FETCH_HEAD"), "r"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "r"); if (!fp) die_errno(_("could not open '%s' for reading"), - git_path("FETCH_HEAD")); + filename); strbuf_getline(&line, fp, '\n'); fclose(fp); ptr = strstr(line.buf, "\tnot-for-merge\t"); @@ -497,7 +512,7 @@ static void merge_name(const char *remote, struct strbuf *msg) goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", - sha1_to_hex(remote_head->sha1), remote); + sha1_to_hex(remote_head->object.sha1), remote); cleanup: strbuf_release(&buf); strbuf_release(&bname); @@ -525,6 +540,8 @@ static void parse_branch_merge_options(char *bmo) static int git_merge_config(const char *k, const char *v, void *cb) { + int status; + if (branch && !prefixcmp(k, "branch.") && !prefixcmp(k + 7, branch) && !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { @@ -541,15 +558,7 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_octopus, k, v); else if (!strcmp(k, "merge.renormalize")) option_renormalize = git_config_bool(k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) { - int is_bool; - shortlog_len = git_config_bool_or_int(k, v, &is_bool); - if (!is_bool && shortlog_len < 0) - return error(_("%s: negative length %s"), k, v); - if (is_bool && shortlog_len) - shortlog_len = DEFAULT_MERGE_LOG_LEN; - return 0; - } else if (!strcmp(k, "merge.ff")) { + else if (!strcmp(k, "merge.ff")) { int boolval = git_config_maybe_bool(k, v); if (0 <= boolval) { allow_fast_forward = boolval; @@ -562,6 +571,9 @@ static int git_merge_config(const char *k, const char *v, void *cb) default_to_upstream = git_config_bool(k, v); return 0; } + status = fmt_merge_msg_config(k, v, cb); + if (status) + return status; return git_diff_ui_config(k, v, cb); } @@ -661,7 +673,7 @@ int try_merge_command(const char *strategy, size_t xopts_nr, } static int try_merge_strategy(const char *strategy, struct commit_list *common, - const char *head_arg) + struct commit *head, const char *head_arg) { int index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); @@ -701,13 +713,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); o.branch1 = head_arg; - o.branch2 = remoteheads->item->util; + o.branch2 = merge_remote_util(remoteheads->item)->name; for (j = common; j; j = j->next) commit_list_insert(j->item, &reversed); index_fd = hold_locked_index(lock, 1); - clean = merge_recursive(&o, lookup_commit(head), + clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || @@ -756,10 +768,12 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote memset(&trees, 0, sizeof(trees)); memset(&opts, 0, sizeof(opts)); memset(&t, 0, sizeof(t)); - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - dir.exclude_per_dir = ".gitignore"; - opts.dir = &dir; + if (overwrite_ignore) { + memset(&dir, 0, sizeof(dir)); + dir.flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(&dir); + opts.dir = &dir; + } opts.head_idx = 1; opts.src_index = &the_index; @@ -832,51 +846,78 @@ static void add_strategies(const char *string, unsigned attr) } -static void write_merge_msg(void) +static void write_merge_msg(struct strbuf *msg) { - int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); + const char *filename = git_path("MERGE_MSG"); + int fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) die_errno(_("Could not open '%s' for writing"), - git_path("MERGE_MSG")); - if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len) - die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG")); + filename); + if (write_in_full(fd, msg->buf, msg->len) != msg->len) + die_errno(_("Could not write to '%s'"), filename); close(fd); } -static void read_merge_msg(void) +static void read_merge_msg(struct strbuf *msg) { - strbuf_reset(&merge_msg); - if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0) - die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG")); + const char *filename = git_path("MERGE_MSG"); + strbuf_reset(msg); + if (strbuf_read_file(msg, filename, 0) < 0) + die_errno(_("Could not read from '%s'"), filename); } -static void run_prepare_commit_msg(void) +static void write_merge_state(void); +static void abort_commit(const char *err_msg) { - write_merge_msg(); + if (err_msg) + error("%s", err_msg); + fprintf(stderr, + _("Not committing merge; use 'git commit' to complete the merge.\n")); + write_merge_state(); + exit(1); +} + +static void prepare_to_commit(void) +{ + struct strbuf msg = STRBUF_INIT; + strbuf_addbuf(&msg, &merge_msg); + strbuf_addch(&msg, '\n'); + write_merge_msg(&msg); run_hook(get_index_file(), "prepare-commit-msg", git_path("MERGE_MSG"), "merge", NULL, NULL); - read_merge_msg(); + if (option_edit) { + if (launch_editor(git_path("MERGE_MSG"), NULL, NULL)) + abort_commit(NULL); + } + read_merge_msg(&msg); + stripspace(&msg, option_edit); + if (!msg.len) + abort_commit(_("Empty commit message.")); + strbuf_release(&merge_msg); + strbuf_addbuf(&merge_msg, &msg); + strbuf_release(&msg); } -static int merge_trivial(void) +static int merge_trivial(struct commit *head) { unsigned char result_tree[20], result_commit[20]; struct commit_list *parent = xmalloc(sizeof(*parent)); write_tree_trivial(result_tree); printf(_("Wonderful.\n")); - parent->item = lookup_commit(head); + parent->item = head; parent->next = xmalloc(sizeof(*parent->next)); parent->next->item = remoteheads->item; parent->next->next = NULL; - run_prepare_commit_msg(); + prepare_to_commit(); commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL); - finish(result_commit, "In-index merge"); + finish(head, result_commit, "In-index merge"); drop_save(); return 0; } -static int finish_automerge(struct commit_list *common, +static int finish_automerge(struct commit *head, + struct commit_list *common, unsigned char *result_tree, const char *wt_strategy) { @@ -887,22 +928,22 @@ static int finish_automerge(struct commit_list *common, free_commit_list(common); if (allow_fast_forward) { parents = remoteheads; - commit_list_insert(lookup_commit(head), &parents); + commit_list_insert(head, &parents); parents = reduce_heads(parents); } else { struct commit_list **pptr = &parents; - pptr = &commit_list_insert(lookup_commit(head), + pptr = &commit_list_insert(head, pptr)->next; for (j = remoteheads; j; j = j->next) pptr = &commit_list_insert(j->item, pptr)->next; } - free_commit_list(remoteheads); strbuf_addch(&merge_msg, '\n'); - run_prepare_commit_msg(); + prepare_to_commit(); + free_commit_list(remoteheads); commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); - finish(result_commit, buf.buf); + finish(head, result_commit, buf.buf); strbuf_release(&buf); drop_save(); return 0; @@ -910,13 +951,14 @@ static int finish_automerge(struct commit_list *common, static int suggest_conflicts(int renormalizing) { + const char *filename; FILE *fp; int pos; - fp = fopen(git_path("MERGE_MSG"), "a"); + filename = git_path("MERGE_MSG"); + fp = fopen(filename, "a"); if (!fp) - die_errno(_("Could not open '%s' for writing"), - git_path("MERGE_MSG")); + die_errno(_("Could not open '%s' for writing"), filename); fprintf(fp, "\nConflicts:\n"); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; @@ -936,7 +978,8 @@ static int suggest_conflicts(int renormalizing) return 1; } -static struct commit *is_old_style_invocation(int argc, const char **argv) +static struct commit *is_old_style_invocation(int argc, const char **argv, + const unsigned char *head) { struct commit *second_token = NULL; if (argc > 2) { @@ -1005,16 +1048,59 @@ static int setup_with_upstream(const char ***argv) return i; } +static void write_merge_state(void) +{ + const char *filename; + int fd; + struct commit_list *j; + struct strbuf buf = STRBUF_INIT; + + for (j = remoteheads; j; j = j->next) { + unsigned const char *sha1; + struct commit *c = j->item; + if (c->util && merge_remote_util(c)->obj) { + sha1 = merge_remote_util(c)->obj->sha1; + } else { + sha1 = c->object.sha1; + } + strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); + } + filename = git_path("MERGE_HEAD"); + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (write_in_full(fd, buf.buf, buf.len) != buf.len) + die_errno(_("Could not write to '%s'"), filename); + close(fd); + strbuf_addch(&merge_msg, '\n'); + write_merge_msg(&merge_msg); + + filename = git_path("MERGE_MODE"); + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + strbuf_reset(&buf); + if (!allow_fast_forward) + strbuf_addf(&buf, "no-ff"); + if (write_in_full(fd, buf.buf, buf.len) != buf.len) + die_errno(_("Could not write to '%s'"), filename); + close(fd); +} + int cmd_merge(int argc, const char **argv, const char *prefix) { unsigned char result_tree[20]; + unsigned char stash[20]; + unsigned char head_sha1[20]; + struct commit *head_commit; struct strbuf buf = STRBUF_INIT; const char *head_arg; - int flag, head_invalid = 0, i; + int flag, i, ret = 0; int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; + void *branch_to_free; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1023,11 +1109,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = resolve_ref("HEAD", head, 0, &flag); + branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag); if (branch && !prefixcmp(branch, "refs/heads/")) branch += 11; - if (is_null_sha1(head)) - head_invalid = 1; + if (!branch || is_null_sha1(head_sha1)) + head_commit = NULL; + else + head_commit = lookup_commit_or_die(head_sha1, "HEAD"); git_config(git_merge_config, NULL); @@ -1035,6 +1123,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); + if (shortlog_len < 0) + shortlog_len = (merge_log_config > 0) ? merge_log_config : 0; if (verbosity < 0 && show_progress == -1) show_progress = 0; @@ -1047,7 +1137,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("There is no merge to abort (MERGE_HEAD missing).")); /* Invoke 'git reset --merge' */ - return cmd_reset(nargc, nargv, prefix); + ret = cmd_reset(nargc, nargv, prefix); + goto done; } if (read_cache_unmerged()) @@ -1086,9 +1177,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("You cannot combine --no-ff with --ff-only.")); if (!abort_current_merge) { - if (!argc && default_to_upstream) - argc = setup_with_upstream(&argv); - else if (argc == 1 && !strcmp(argv[0], "-")) + if (!argc) { + if (default_to_upstream) + argc = setup_with_upstream(&argv); + else + die(_("No commit specified and merge.defaultToUpstream not set.")); + } else if (argc == 1 && !strcmp(argv[0], "-")) argv[0] = "@{-1}"; } if (!argc) @@ -1104,13 +1198,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * additional safety measure to check for it. */ - if (!have_message && is_old_style_invocation(argc, argv)) { + if (!have_message && head_commit && + is_old_style_invocation(argc, argv, head_commit->object.sha1)) { strbuf_addstr(&merge_msg, argv[0]); head_arg = argv[1]; argv += 2; argc -= 2; - } else if (head_invalid) { - struct object *remote_head; + } else if (!head_commit) { + struct commit *remote_head; /* * If the merged head is a valid one there is no reason * to forbid "git merge" into a branch yet to be born. @@ -1124,13 +1219,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!allow_fast_forward) die(_("Non-fast-forward commit does not make sense into " "an empty head")); - remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); + remote_head = get_merge_parent(argv[0]); if (!remote_head) die(_("%s - not something we can merge"), argv[0]); - read_empty(remote_head->sha1, 0); - update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, - DIE_ON_ERR); - return 0; + read_empty(remote_head->object.sha1, 0); + update_ref("initial pull", "HEAD", remote_head->object.sha1, + NULL, 0, DIE_ON_ERR); + goto done; } else { struct strbuf merge_names = STRBUF_INIT; @@ -1138,25 +1233,26 @@ int cmd_merge(int argc, const char **argv, const char *prefix) head_arg = "HEAD"; /* - * All the rest are the commits being merged; - * prepare the standard merge summary message to - * be appended to the given message. If remote - * is invalid we will die later in the common - * codepath so we discard the error in this - * loop. + * All the rest are the commits being merged; prepare + * the standard merge summary message to be appended + * to the given message. */ for (i = 0; i < argc; i++) merge_name(argv[i], &merge_names); if (!have_message || shortlog_len) { - fmt_merge_msg(&merge_names, &merge_msg, !have_message, - shortlog_len); + struct fmt_merge_msg_opts opts; + memset(&opts, 0, sizeof(opts)); + opts.add_title = !have_message; + opts.shortlog_len = shortlog_len; + + fmt_merge_msg(&merge_names, &merge_msg, &opts); if (merge_msg.len) strbuf_setlen(&merge_msg, merge_msg.len - 1); } } - if (head_invalid || !argc) + if (!head_commit || !argc) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1167,19 +1263,20 @@ int cmd_merge(int argc, const char **argv, const char *prefix) strbuf_reset(&buf); for (i = 0; i < argc; i++) { - struct object *o; - struct commit *commit; - - o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); - if (!o) + struct commit *commit = get_merge_parent(argv[i]); + if (!commit) die(_("%s - not something we can merge"), argv[i]); - commit = lookup_commit(o->sha1); - commit->util = (void *)argv[i]; remotes = &commit_list_insert(commit, remotes)->next; - - strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); + strbuf_addf(&buf, "GITHEAD_%s", + sha1_to_hex(commit->object.sha1)); setenv(buf.buf, argv[i], 1); strbuf_reset(&buf); + if (merge_remote_util(commit) && + merge_remote_util(commit)->obj && + merge_remote_util(commit)->obj->type == OBJ_TAG) { + option_edit = 1; + allow_fast_forward = 0; + } } if (!use_strategies) { @@ -1197,17 +1294,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (!remoteheads->next) - common = get_merge_bases(lookup_commit(head), - remoteheads->item, 1); + common = get_merge_bases(head_commit, remoteheads->item, 1); else { struct commit_list *list = remoteheads; - commit_list_insert(lookup_commit(head), &list); + commit_list_insert(head_commit, &list); common = get_octopus_merge_bases(list); free(list); } - update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0, - DIE_ON_ERR); + update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, + NULL, 0, DIE_ON_ERR); if (!common) ; /* No common ancestors found. We need a real merge. */ @@ -1218,16 +1314,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * but first the most common case of merging one remote. */ finish_up_to_date("Already up-to-date."); - return 0; + goto done; } else if (allow_fast_forward && !remoteheads->next && !common->next && - !hashcmp(common->item->object.sha1, head)) { + !hashcmp(common->item->object.sha1, head_commit->object.sha1)) { /* Again the most common case of merging one remote. */ struct strbuf msg = STRBUF_INIT; - struct object *o; + struct commit *commit; char hex[41]; - strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV)); + strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV)); if (verbosity >= 0) printf(_("Updating %s..%s\n"), @@ -1238,17 +1334,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (have_message) strbuf_addstr(&msg, " (no commit created; -m option ignored)"); - o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1), - 0, NULL, OBJ_COMMIT); - if (!o) - return 1; + commit = remoteheads->item; + if (!commit) { + ret = 1; + goto done; + } - if (checkout_fast_forward(head, remoteheads->item->object.sha1)) - return 1; + if (checkout_fast_forward(head_commit->object.sha1, + commit->object.sha1)) { + ret = 1; + goto done; + } - finish(o->sha1, msg.buf); + finish(head_commit, commit->object.sha1, msg.buf); drop_save(); - return 0; + goto done; } else if (!remoteheads->next && common->next) ; /* @@ -1266,8 +1366,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) git_committer_info(IDENT_ERROR_ON_NO_NAME); printf(_("Trying really trivial in-index merge...\n")); if (!read_tree_trivial(common->item->object.sha1, - head, remoteheads->item->object.sha1)) - return merge_trivial(); + head_commit->object.sha1, + remoteheads->item->object.sha1)) { + ret = merge_trivial(head_commit); + goto done; + } printf(_("Nope.\n")); } } else { @@ -1286,8 +1389,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * merge_bases again, otherwise "git merge HEAD^ * HEAD^^" would be missed. */ - common_one = get_merge_bases(lookup_commit(head), - j->item, 1); + common_one = get_merge_bases(head_commit, j->item, 1); if (hashcmp(common_one->item->object.sha1, j->item->object.sha1)) { up_to_date = 0; @@ -1296,7 +1398,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (up_to_date) { finish_up_to_date("Already up-to-date. Yeeah!"); - return 0; + goto done; } } @@ -1314,21 +1416,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * sync with the head commit. The strategies are responsible * to ensure this. */ - if (use_strategies_nr != 1) { - /* - * Stash away the local changes so that we can try more - * than one. - */ - save_state(); - } else { - memcpy(stash, null_sha1, 20); - } + if (use_strategies_nr == 1 || + /* + * Stash away the local changes so that we can try more than one. + */ + save_state(stash)) + hashcpy(stash, null_sha1); for (i = 0; i < use_strategies_nr; i++) { int ret; if (i) { printf(_("Rewinding the tree to pristine...\n")); - restore_state(); + restore_state(head_commit->object.sha1, stash); } if (use_strategies_nr != 1) printf(_("Trying merge strategy %s...\n"), @@ -1340,7 +1439,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) wt_strategy = use_strategies[i]->name; ret = try_merge_strategy(use_strategies[i]->name, - common, head_arg); + common, head_commit, head_arg); if (!option_commit && !ret) { merge_was_ok = 1; /* @@ -1381,66 +1480,48 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * If we have a resulting tree, that means the strategy module * auto resolved the merge cleanly. */ - if (automerge_was_ok) - return finish_automerge(common, result_tree, wt_strategy); + if (automerge_was_ok) { + ret = finish_automerge(head_commit, common, result_tree, + wt_strategy); + goto done; + } /* * Pick the result from the best strategy and have the user fix * it up. */ if (!best_strategy) { - restore_state(); + restore_state(head_commit->object.sha1, stash); if (use_strategies_nr > 1) fprintf(stderr, _("No merge strategy handled the merge.\n")); else fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); - return 2; + ret = 2; + goto done; } else if (best_strategy == wt_strategy) ; /* We already have its result in the working tree. */ else { printf(_("Rewinding the tree to pristine...\n")); - restore_state(); + restore_state(head_commit->object.sha1, stash); printf(_("Using the %s to prepare resolving by hand.\n"), best_strategy); - try_merge_strategy(best_strategy, common, head_arg); + try_merge_strategy(best_strategy, common, head_commit, head_arg); } if (squash) - finish(NULL, NULL); - else { - int fd; - struct commit_list *j; - - for (j = remoteheads; j; j = j->next) - strbuf_addf(&buf, "%s\n", - sha1_to_hex(j->item->object.sha1)); - fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), - git_path("MERGE_HEAD")); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD")); - close(fd); - strbuf_addch(&merge_msg, '\n'); - write_merge_msg(); - fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), - git_path("MERGE_MODE")); - strbuf_reset(&buf); - if (!allow_fast_forward) - strbuf_addf(&buf, "no-ff"); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE")); - close(fd); - } + finish(head_commit, NULL, NULL); + else + write_merge_state(); - if (merge_was_ok) { + if (merge_was_ok) fprintf(stderr, _("Automatic merge went well; " "stopped before committing as requested\n")); - return 0; - } else - return suggest_conflicts(option_renormalize); + else + ret = suggest_conflicts(option_renormalize); + +done: + free(branch_to_free); + return ret; } diff --git a/builtin/mktree.c b/builtin/mktree.c index 098395fda1..4ae1c412d4 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -60,6 +60,7 @@ static void write_tree(unsigned char *sha1) } write_sha1_file(buf.buf, buf.len, tree_type, sha1); + strbuf_release(&buf); } static const char *mktree_usage[] = { diff --git a/builtin/mv.c b/builtin/mv.c index 40f33ca4d0..2a144b011c 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -29,7 +29,11 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec, to_copy--; if (to_copy != length || base_name) { char *it = xmemdupz(result[i], to_copy); - result[i] = base_name ? strdup(basename(it)) : it; + if (base_name) { + result[i] = xstrdup(basename(it)); + free(it); + } else + result[i] = it; } } return get_pathspec(prefix, result); @@ -55,6 +59,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int i, newfd; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { + OPT__VERBOSE(&verbose, "be verbose"), OPT__DRY_RUN(&show_only, "dry run"), OPT__FORCE(&force, "force move/rename even if target exists"), OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), @@ -89,7 +94,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) destination = copy_pathspec(dest_path[0], argv, argc, 1); } else { if (argc != 1) - usage_with_options(builtin_mv_usage, builtin_mv_options); + die("destination '%s' is not a directory", dest_path[0]); destination = dest_path; } @@ -172,7 +177,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) * check both source and destination */ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - warning(_("%s; will overwrite!"), bad); + if (verbose) + warning(_("overwriting '%s'"), dst); bad = NULL; } else bad = _("Cannot overwrite"); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 31f5c1c971..1b374583c2 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -172,7 +172,9 @@ static void show_name(const struct object *obj, } static char const * const name_rev_usage[] = { - "git name-rev [options] ( --all | --stdin | <commit>... )", + "git name-rev [options] <commit>...", + "git name-rev [options] --all", + "git name-rev [options] --stdin", NULL }; @@ -289,7 +291,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) max = get_max_object_index(); for (i = 0; i < max; i++) { struct object *obj = get_indexed_object(i); - if (!obj) + if (!obj || obj->type != OBJ_COMMIT) continue; show_name(obj, NULL, always, allow_undefined, data.name_only); diff --git a/builtin/notes.c b/builtin/notes.c index f8e437db01..667e20a1e1 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -804,6 +804,8 @@ static int merge_commit(struct notes_merge_options *o) struct notes_tree *t; struct commit *partial; struct pretty_print_context pretty_ctx; + void *local_ref_to_free; + int ret; /* * Read partial merge result from .git/NOTES_MERGE_PARTIAL, @@ -825,7 +827,8 @@ static int merge_commit(struct notes_merge_options *o) t = xcalloc(1, sizeof(struct notes_tree)); init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); - o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL); + o->local_ref = local_ref_to_free = + resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL); if (!o->local_ref) die("Failed to resolve NOTES_MERGE_REF"); @@ -843,7 +846,9 @@ static int merge_commit(struct notes_merge_options *o) free_notes(t); strbuf_release(&msg); - return merge_abort(o); + ret = merge_abort(o); + free(local_ref_to_free); + return ret; } static int merge(int argc, const char **argv, const char *prefix) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index a9c67c18ba..96c1680976 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -76,7 +76,7 @@ static struct pack_idx_option pack_idx_opts; static const char *base_name; static int progress = 1; static int window = 10; -static unsigned long pack_size_limit, pack_size_limit_cfg; +static unsigned long pack_size_limit; static int depth = 50; static int delta_search_threads; static int pack_to_stdout; @@ -409,25 +409,56 @@ static unsigned long write_object(struct sha1file *f, return hdrlen + datalen; } -static int write_one(struct sha1file *f, - struct object_entry *e, - off_t *offset) +enum write_one_status { + WRITE_ONE_SKIP = -1, /* already written */ + WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */ + WRITE_ONE_WRITTEN = 1, /* normal */ + WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */ +}; + +static enum write_one_status write_one(struct sha1file *f, + struct object_entry *e, + off_t *offset) { unsigned long size; + int recursing; - /* offset is non zero if object is written already. */ - if (e->idx.offset || e->preferred_base) - return -1; + /* + * we set offset to 1 (which is an impossible value) to mark + * the fact that this object is involved in "write its base + * first before writing a deltified object" recursion. + */ + recursing = (e->idx.offset == 1); + if (recursing) { + warning("recursive delta detected for object %s", + sha1_to_hex(e->idx.sha1)); + return WRITE_ONE_RECURSIVE; + } else if (e->idx.offset || e->preferred_base) { + /* offset is non zero if object is written already. */ + return WRITE_ONE_SKIP; + } /* if we are deltified, write out base object first. */ - if (e->delta && !write_one(f, e->delta, offset)) - return 0; + if (e->delta) { + e->idx.offset = 1; /* now recurse */ + switch (write_one(f, e->delta, offset)) { + case WRITE_ONE_RECURSIVE: + /* we cannot depend on this one */ + e->delta = NULL; + break; + default: + break; + case WRITE_ONE_BREAK: + e->idx.offset = recursing; + return WRITE_ONE_BREAK; + } + } e->idx.offset = *offset; size = write_object(f, e, *offset); if (!size) { - e->idx.offset = 0; - return 0; + e->idx.offset = recursing; + return WRITE_ONE_BREAK; } written_list[nr_written++] = &e->idx; @@ -435,7 +466,7 @@ static int write_one(struct sha1file *f, if (signed_add_overflows(*offset, size)) die("pack too large for current definition of off_t"); *offset += size; - return 1; + return WRITE_ONE_WRITTEN; } static int mark_tagged(const char *path, const unsigned char *sha1, int flag, @@ -454,8 +485,8 @@ static int mark_tagged(const char *path, const unsigned char *sha1, int flag, return 0; } -static void add_to_write_order(struct object_entry **wo, - int *endp, +static inline void add_to_write_order(struct object_entry **wo, + unsigned int *endp, struct object_entry *e) { if (e->filled) @@ -465,32 +496,62 @@ static void add_to_write_order(struct object_entry **wo, } static void add_descendants_to_write_order(struct object_entry **wo, - int *endp, + unsigned int *endp, struct object_entry *e) { - struct object_entry *child; - - for (child = e->delta_child; child; child = child->delta_sibling) - add_to_write_order(wo, endp, child); - for (child = e->delta_child; child; child = child->delta_sibling) - add_descendants_to_write_order(wo, endp, child); + int add_to_order = 1; + while (e) { + if (add_to_order) { + struct object_entry *s; + /* add this node... */ + add_to_write_order(wo, endp, e); + /* all its siblings... */ + for (s = e->delta_sibling; s; s = s->delta_sibling) { + add_to_write_order(wo, endp, s); + } + } + /* drop down a level to add left subtree nodes if possible */ + if (e->delta_child) { + add_to_order = 1; + e = e->delta_child; + } else { + add_to_order = 0; + /* our sibling might have some children, it is next */ + if (e->delta_sibling) { + e = e->delta_sibling; + continue; + } + /* go back to our parent node */ + e = e->delta; + while (e && !e->delta_sibling) { + /* we're on the right side of a subtree, keep + * going up until we can go right again */ + e = e->delta; + } + if (!e) { + /* done- we hit our original root node */ + return; + } + /* pass it off to sibling at this level */ + e = e->delta_sibling; + } + }; } static void add_family_to_write_order(struct object_entry **wo, - int *endp, + unsigned int *endp, struct object_entry *e) { struct object_entry *root; for (root = e; root->delta; root = root->delta) ; /* nothing */ - add_to_write_order(wo, endp, root); add_descendants_to_write_order(wo, endp, root); } static struct object_entry **compute_write_order(void) { - int i, wo_end; + unsigned int i, wo_end, last_untagged; struct object_entry **wo = xmalloc(nr_objects * sizeof(*wo)); @@ -506,8 +567,8 @@ static struct object_entry **compute_write_order(void) * Make sure delta_sibling is sorted in the original * recency order. */ - for (i = nr_objects - 1; 0 <= i; i--) { - struct object_entry *e = &objects[i]; + for (i = nr_objects; i > 0;) { + struct object_entry *e = &objects[--i]; if (!e->delta) continue; /* Mark me as the first child */ @@ -521,7 +582,7 @@ static struct object_entry **compute_write_order(void) for_each_tag_ref(mark_tagged, NULL); /* - * Give the commits in the original recency order until + * Give the objects in the original recency order until * we see a tagged tip. */ for (i = wo_end = 0; i < nr_objects; i++) { @@ -529,6 +590,7 @@ static struct object_entry **compute_write_order(void) break; add_to_write_order(wo, &wo_end, &objects[i]); } + last_untagged = i; /* * Then fill all the tagged tips. @@ -541,7 +603,7 @@ static struct object_entry **compute_write_order(void) /* * And then all remaining commits and tags. */ - for (i = 0; i < nr_objects; i++) { + for (i = last_untagged; i < nr_objects; i++) { if (objects[i].type != OBJ_COMMIT && objects[i].type != OBJ_TAG) continue; @@ -551,7 +613,7 @@ static struct object_entry **compute_write_order(void) /* * And then all the trees. */ - for (i = 0; i < nr_objects; i++) { + for (i = last_untagged; i < nr_objects; i++) { if (objects[i].type != OBJ_TREE) continue; add_to_write_order(wo, &wo_end, &objects[i]); @@ -560,8 +622,13 @@ static struct object_entry **compute_write_order(void) /* * Finally all the rest in really tight order */ - for (i = 0; i < nr_objects; i++) - add_family_to_write_order(wo, &wo_end, &objects[i]); + for (i = last_untagged; i < nr_objects; i++) { + if (!objects[i].filled) + add_family_to_write_order(wo, &wo_end, &objects[i]); + } + + if (wo_end != nr_objects) + die("ordered %u objects, expected %"PRIu32, wo_end, nr_objects); return wo; } @@ -571,7 +638,6 @@ static void write_pack_file(void) uint32_t i = 0, j; struct sha1file *f; off_t offset; - struct pack_header hdr; uint32_t nr_remaining = nr_result; time_t last_mtime = 0; struct object_entry **write_order; @@ -585,26 +651,18 @@ static void write_pack_file(void) unsigned char sha1[20]; char *pack_tmp_name = NULL; - if (pack_to_stdout) { + if (pack_to_stdout) f = sha1fd_throughput(1, "<stdout>", progress_state); - } else { - char tmpname[PATH_MAX]; - int fd; - fd = odb_mkstemp(tmpname, sizeof(tmpname), - "pack/tmp_pack_XXXXXX"); - pack_tmp_name = xstrdup(tmpname); - f = sha1fd(fd, pack_tmp_name); - } - - hdr.hdr_signature = htonl(PACK_SIGNATURE); - hdr.hdr_version = htonl(PACK_VERSION); - hdr.hdr_entries = htonl(nr_remaining); - sha1write(f, &hdr, sizeof(hdr)); - offset = sizeof(hdr); + else + f = create_tmp_packfile(&pack_tmp_name); + + offset = write_pack_header(f, nr_remaining); + if (!offset) + die_errno("unable to write pack header"); nr_written = 0; for (; i < nr_objects; i++) { struct object_entry *e = write_order[i]; - if (!write_one(f, e, &offset)) + if (write_one(f, e, &offset) == WRITE_ONE_BREAK) break; display_progress(progress_state, written); } @@ -626,20 +684,8 @@ static void write_pack_file(void) if (!pack_to_stdout) { struct stat st; - const char *idx_tmp_name; char tmpname[PATH_MAX]; - idx_tmp_name = write_idx_file(NULL, written_list, nr_written, - &pack_idx_opts, sha1); - - snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", - base_name, sha1_to_hex(sha1)); - free_pack_by_name(tmpname); - if (adjust_shared_perm(pack_tmp_name)) - die_errno("unable to make temporary pack file readable"); - if (rename(pack_tmp_name, tmpname)) - die_errno("unable to rename temporary pack file"); - /* * Packs are runtime accessed in their mtime * order since newer packs are more likely to contain @@ -647,28 +693,27 @@ static void write_pack_file(void) * packs then we should modify the mtime of later ones * to preserve this property. */ - if (stat(tmpname, &st) < 0) { + if (stat(pack_tmp_name, &st) < 0) { warning("failed to stat %s: %s", - tmpname, strerror(errno)); + pack_tmp_name, strerror(errno)); } else if (!last_mtime) { last_mtime = st.st_mtime; } else { struct utimbuf utb; utb.actime = st.st_atime; utb.modtime = --last_mtime; - if (utime(tmpname, &utb) < 0) + if (utime(pack_tmp_name, &utb) < 0) warning("failed utime() on %s: %s", tmpname, strerror(errno)); } - snprintf(tmpname, sizeof(tmpname), "%s-%s.idx", - base_name, sha1_to_hex(sha1)); - if (adjust_shared_perm(idx_tmp_name)) - die_errno("unable to make temporary index file readable"); - if (rename(idx_tmp_name, tmpname)) - die_errno("unable to rename temporary index file"); - - free((void *) idx_tmp_name); + /* Enough space for "-<sha-1>.pack"? */ + if (sizeof(tmpname) <= strlen(base_name) + 50) + die("pack base name '%s' too long", base_name); + snprintf(tmpname, sizeof(tmpname), "%s-", base_name); + finish_tmp_packfile(tmpname, pack_tmp_name, + written_list, nr_written, + &pack_idx_opts, sha1); free(pack_tmp_name); puts(sha1_to_hex(sha1)); } @@ -804,6 +849,10 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type, off_t offset = find_pack_entry_one(sha1, p); if (offset) { if (!found_pack) { + if (!is_pack_valid(p)) { + warning("packfile %s cannot be accessed", p->pack_name); + continue; + } found_offset = offset; found_pack = p; } @@ -975,7 +1024,7 @@ static void add_pbase_object(struct tree_desc *tree, while (tree_entry(tree,&entry)) { if (S_ISGITLINK(entry.mode)) continue; - cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 : + cmp = tree_entry_len(&entry) != cmplen ? 1 : memcmp(name, entry.path, cmplen); if (cmp > 0) continue; @@ -2027,10 +2076,6 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_idx_opts.version); return 0; } - if (!strcmp(k, "pack.packsizelimit")) { - pack_size_limit_cfg = git_config_ulong(k, v); - return 0; - } return git_default_config(k, v, cb); } @@ -2073,7 +2118,9 @@ static void show_commit(struct commit *commit, void *data) commit->object.flags |= OBJECT_ADDED; } -static void show_object(struct object *obj, const struct name_path *path, const char *last) +static void show_object(struct object *obj, + const struct name_path *path, const char *last, + void *data) { char *name = path_name(path, last); diff --git a/builtin/patch-id.c b/builtin/patch-id.c index f821eb3f0b..3cfe02d5a5 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -56,13 +56,13 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) +static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf) { - static char line[1000]; int patchlen = 0, found_next = 0; int before = -1, after = -1; - while (fgets(line, sizeof(line), stdin) != NULL) { + while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { + char *line = line_buf->buf; char *p = line; int len; @@ -133,14 +133,16 @@ static void generate_id_list(void) unsigned char sha1[20], n[20]; git_SHA_CTX ctx; int patchlen; + struct strbuf line_buf = STRBUF_INIT; git_SHA1_Init(&ctx); hashclr(sha1); while (!feof(stdin)) { - patchlen = get_one_patchid(n, &ctx); + patchlen = get_one_patchid(n, &ctx, &line_buf); flush_current_id(patchlen, sha1, &ctx); hashcpy(sha1, n); } + strbuf_release(&line_buf); } static const char patch_id_usage[] = "git patch-id < patch"; diff --git a/builtin/prune.c b/builtin/prune.c index e65690ba37..58d7cb8324 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -5,6 +5,7 @@ #include "builtin.h" #include "reachable.h" #include "parse-options.h" +#include "progress.h" #include "dir.h" static const char * const prune_usage[] = { @@ -14,6 +15,7 @@ static const char * const prune_usage[] = { static int show_only; static int verbose; static unsigned long expire; +static int show_progress = -1; static int prune_tmp_object(const char *path, const char *filename) { @@ -124,9 +126,11 @@ static void remove_temporary_files(const char *path) int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; + struct progress *progress = NULL; const struct option options[] = { OPT__DRY_RUN(&show_only, "do not remove, show only"), OPT__VERBOSE(&verbose, "report pruned objects"), + OPT_BOOL(0, "progress", &show_progress, "show progress"), OPT_DATE(0, "expire", &expire, "expire objects older than <time>"), OPT_END() @@ -152,7 +156,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix) else die("unrecognized argument: %s", name); } - mark_reachable_objects(&revs, 1); + + if (show_progress == -1) + show_progress = isatty(2); + if (show_progress) + progress = start_progress_delay("Checking connectivity", 0, 0, 2); + + mark_reachable_objects(&revs, 1, progress); + stop_progress(&progress); prune_object_dir(get_object_directory()); prune_packed_objects(show_only); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index ae164da4d5..d2dcb7e4af 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -11,6 +11,7 @@ #include "transport.h" #include "string-list.h" #include "sha1-array.h" +#include "connected.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -25,7 +26,8 @@ static int deny_deletes; static int deny_non_fast_forwards; static enum deny_action deny_current_branch = DENY_UNCONFIGURED; static enum deny_action deny_delete_current = DENY_UNCONFIGURED; -static int receive_fsck_objects; +static int receive_fsck_objects = -1; +static int transfer_fsck_objects = -1; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; @@ -35,6 +37,7 @@ static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; static const char *head_name; +static void *head_name_to_free; static int sent_capabilities; static enum deny_action parse_deny_action(const char *var, const char *value) @@ -79,6 +82,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "transfer.fsckobjects") == 0) { + transfer_fsck_objects = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "receive.denycurrentbranch")) { deny_current_branch = parse_deny_action(var, value); return 0; @@ -147,7 +155,8 @@ static void write_head_info(void) struct command { struct command *next; const char *error_string; - unsigned int skip_update; + unsigned int skip_update:1, + did_not_exist:1; unsigned char old_sha1[20]; unsigned char new_sha1[20]; char ref_name[FLEX_ARRAY]; /* more */ @@ -205,21 +214,15 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } -static int run_receive_hook(struct command *commands, const char *hook_name) +typedef int (*feed_fn)(void *, const char **, size_t *); +static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state) { - static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; - struct command *cmd; struct child_process proc; struct async muxer; const char *argv[2]; - int have_input = 0, code; - - for (cmd = commands; !have_input && cmd; cmd = cmd->next) { - if (!cmd->error_string) - have_input = 1; - } + int code; - if (!have_input || access(hook_name, X_OK) < 0) + if (access(hook_name, X_OK) < 0) return 0; argv[0] = hook_name; @@ -247,15 +250,13 @@ static int run_receive_hook(struct command *commands, const char *hook_name) return code; } - for (cmd = commands; cmd; cmd = cmd->next) { - if (!cmd->error_string) { - size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n", - sha1_to_hex(cmd->old_sha1), - sha1_to_hex(cmd->new_sha1), - cmd->ref_name); - if (write_in_full(proc.in, buf, n) != n) - break; - } + while (1) { + const char *buf; + size_t n; + if (feed(feed_state, &buf, &n)) + break; + if (write_in_full(proc.in, buf, n) != n) + break; } close(proc.in); if (use_sideband) @@ -263,6 +264,51 @@ static int run_receive_hook(struct command *commands, const char *hook_name) return finish_command(&proc); } +struct receive_hook_feed_state { + struct command *cmd; + int skip_broken; + struct strbuf buf; +}; + +static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) +{ + struct receive_hook_feed_state *state = state_; + struct command *cmd = state->cmd; + + while (cmd && + state->skip_broken && (cmd->error_string || cmd->did_not_exist)) + cmd = cmd->next; + if (!cmd) + return -1; /* EOF */ + strbuf_reset(&state->buf); + strbuf_addf(&state->buf, "%s %s %s\n", + sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1), + cmd->ref_name); + state->cmd = cmd->next; + if (bufp) { + *bufp = state->buf.buf; + *sizep = state->buf.len; + } + return 0; +} + +static int run_receive_hook(struct command *commands, const char *hook_name, + int skip_broken) +{ + struct receive_hook_feed_state state; + int status; + + strbuf_init(&state.buf, 0); + state.cmd = commands; + state.skip_broken = skip_broken; + if (feed_receive_hook(&state, NULL, NULL)) + return 0; + state.cmd = commands; + status = run_and_feed_hook(hook_name, feed_receive_hook, &state); + strbuf_release(&state.buf); + return status; +} + static int run_update_hook(struct command *cmd) { static const char update_hook[] = "hooks/update"; @@ -356,7 +402,7 @@ static const char *update(struct command *cmd) struct ref_lock *lock; /* only refs/... are allowed */ - if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) { + if (prefixcmp(name, "refs/") || check_refname_format(name + 5, 0)) { rp_error("refusing to create funny ref '%s' remotely", name); return "funny refname"; } @@ -445,8 +491,13 @@ static const char *update(struct command *cmd) if (is_null_sha1(new_sha1)) { if (!parse_object(old_sha1)) { - rp_warning("Allowing deletion of corrupt ref."); old_sha1 = NULL; + if (ref_exists(name)) { + rp_warning("Allowing deletion of corrupt ref."); + } else { + rp_warning("Deleting a non-existent ref."); + cmd->did_not_exist = 1; + } } if (delete_ref(namespaced_name, old_sha1, 0)) { rp_error("failed to delete %s", name); @@ -477,7 +528,7 @@ static void run_update_post_hook(struct command *commands) struct child_process proc; for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string) + if (cmd->error_string || cmd->did_not_exist) continue; argc++; } @@ -488,7 +539,7 @@ static void run_update_post_hook(struct command *commands) for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { char *p; - if (cmd->error_string) + if (cmd->error_string || cmd->did_not_exist) continue; p = xmalloc(strlen(cmd->ref_name) + 1); strcpy(p, cmd->ref_name); @@ -521,7 +572,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); - dst_name = resolve_ref(buf.buf, sha1, 0, &flag); + dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag); strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) @@ -579,6 +630,48 @@ static void check_aliased_updates(struct command *commands) string_list_clear(&ref_list, 0); } +static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]) +{ + struct command **cmd_list = cb_data; + struct command *cmd = *cmd_list; + + if (!cmd || is_null_sha1(cmd->new_sha1)) + return -1; /* end of list */ + *cmd_list = NULL; /* this returns only one */ + hashcpy(sha1, cmd->new_sha1); + return 0; +} + +static void set_connectivity_errors(struct command *commands) +{ + struct command *cmd; + + for (cmd = commands; cmd; cmd = cmd->next) { + struct command *singleton = cmd; + if (!check_everything_connected(command_singleton_iterator, + 0, &singleton)) + continue; + cmd->error_string = "missing necessary objects"; + } +} + +static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) +{ + struct command **cmd_list = cb_data; + struct command *cmd = *cmd_list; + + while (cmd) { + if (!is_null_sha1(cmd->new_sha1)) { + hashcpy(sha1, cmd->new_sha1); + *cmd_list = cmd->next; + return 0; + } + cmd = cmd->next; + } + *cmd_list = NULL; + return -1; /* end of list */ +} + static void execute_commands(struct command *commands, const char *unpacker_error) { struct command *cmd; @@ -590,7 +683,12 @@ static void execute_commands(struct command *commands, const char *unpacker_erro return; } - if (run_receive_hook(commands, pre_receive_hook)) { + cmd = commands; + if (check_everything_connected(iterate_receive_command_list, + 0, &cmd)) + set_connectivity_errors(commands); + + if (run_receive_hook(commands, pre_receive_hook, 0)) { for (cmd = commands; cmd; cmd = cmd->next) cmd->error_string = "pre-receive hook declined"; return; @@ -598,7 +696,8 @@ static void execute_commands(struct command *commands, const char *unpacker_erro check_aliased_updates(commands); - head_name = resolve_ref("HEAD", sha1, 0, NULL); + free(head_name_to_free); + head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL); for (cmd = commands; cmd; cmd = cmd->next) if (!cmd->skip_update) @@ -674,6 +773,11 @@ static const char *unpack(void) struct pack_header hdr; const char *hdr_err; char hdr_arg[38]; + int fsck_objects = (receive_fsck_objects >= 0 + ? receive_fsck_objects + : transfer_fsck_objects >= 0 + ? transfer_fsck_objects + : 0); hdr_err = parse_pack_header(&hdr); if (hdr_err) @@ -686,7 +790,7 @@ static const char *unpack(void) int code, i = 0; const char *unpacker[4]; unpacker[i++] = "unpack-objects"; - if (receive_fsck_objects) + if (fsck_objects) unpacker[i++] = "--strict"; unpacker[i++] = hdr_arg; unpacker[i++] = NULL; @@ -706,7 +810,7 @@ static const char *unpack(void) keeper[i++] = "index-pack"; keeper[i++] = "--stdin"; - if (receive_fsck_objects) + if (fsck_objects) keeper[i++] = "--strict"; keeper[i++] = "--fix-thin"; keeper[i++] = hdr_arg; @@ -853,7 +957,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) unlink_or_warn(pack_lockfile); if (report_status) report(commands, unpack_status); - run_receive_hook(commands, post_receive_hook); + run_receive_hook(commands, post_receive_hook, 1); run_update_post_hook(commands); if (auto_gc) { const char *argv_gc_auto[] = { diff --git a/builtin/reflog.c b/builtin/reflog.c index 3a9c80f3db..062d7dad1b 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -647,7 +647,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) init_revisions(&cb.revs, prefix); if (cb.verbose) printf("Marking reachable objects..."); - mark_reachable_objects(&cb.revs, 0); + mark_reachable_objects(&cb.revs, 0, NULL); if (cb.verbose) putchar('\n'); } diff --git a/builtin/remote.c b/builtin/remote.c index f2a9c26dc3..583eec90e0 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -343,13 +343,13 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat states->tracked.strdup_strings = 1; states->stale.strdup_strings = 1; for (ref = fetch_map; ref; ref = ref->next) { - unsigned char sha1[20]; - if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) + if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) string_list_append(&states->new, abbrev_branch(ref->name)); else string_list_append(&states->tracked, abbrev_branch(ref->name)); } - stale_refs = get_stale_heads(states->remote, fetch_map); + stale_refs = get_stale_heads(states->remote->fetch, + states->remote->fetch_refspec_nr, fetch_map); for (ref = stale_refs; ref; ref = ref->next) { struct string_list_item *item = string_list_append(&states->stale, abbrev_branch(ref->name)); @@ -389,8 +389,8 @@ static int get_push_ref_states(const struct ref *remote_refs, 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); + match_push_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) { @@ -570,10 +570,10 @@ static int read_remote_branches(const char *refname, unsigned char orig_sha1[20]; const char *symref; - strbuf_addf(&buf, "refs/remotes/%s", rename->old); + strbuf_addf(&buf, "refs/remotes/%s/", rename->old); if (!prefixcmp(refname, buf.buf)) { item = string_list_append(rename->remote_branches, xstrdup(refname)); - symref = resolve_ref(refname, orig_sha1, 1, &flag); + symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag); if (flag & REF_ISSYMREF) item->util = xstrdup(symref); else @@ -621,10 +621,11 @@ static int mv(int argc, const char **argv) OPT_END() }; struct remote *oldremote, *newremote; - struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT, + old_remote_context = STRBUF_INIT; struct string_list remote_branches = STRING_LIST_INIT_NODUP; struct rename_info rename; - int i; + int i, refspec_updated = 0; if (argc != 3) usage_with_options(builtin_remote_rename_usage, options); @@ -659,15 +660,25 @@ static int mv(int argc, const char **argv) strbuf_addf(&buf, "remote.%s.fetch", rename.new); if (git_config_set_multivar(buf.buf, NULL, NULL, 1)) return error("Could not remove config section '%s'", buf.buf); + strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old); for (i = 0; i < oldremote->fetch_refspec_nr; i++) { char *ptr; strbuf_reset(&buf2); strbuf_addstr(&buf2, oldremote->fetch_refspec[i]); - ptr = strstr(buf2.buf, rename.old); - if (ptr) - strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old), - rename.new, strlen(rename.new)); + ptr = strstr(buf2.buf, old_remote_context.buf); + if (ptr) { + refspec_updated = 1; + strbuf_splice(&buf2, + ptr-buf2.buf + strlen(":refs/remotes/"), + strlen(rename.old), rename.new, + strlen(rename.new)); + } else + warning("Not updating non-default fetch respec\n" + "\t%s\n" + "\tPlease update the configuration manually if necessary.", + buf2.buf); + if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0)) return error("Could not append '%s'", buf.buf); } @@ -685,6 +696,9 @@ static int mv(int argc, const char **argv) } } + if (!refspec_updated) + return 0; + /* * First remove symrefs, then rename the rest, finally create * the new symrefs. @@ -695,7 +709,7 @@ static int mv(int argc, const char **argv) int flag = 0; unsigned char sha1[20]; - resolve_ref(item->string, sha1, 1, &flag); + read_ref_full(item->string, sha1, 1, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(item->string, NULL, REF_NODEREF)) @@ -1205,10 +1219,9 @@ static int set_head(int argc, const char **argv) usage_with_options(builtin_remote_sethead_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)) + if (!ref_exists(buf2.buf)) 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); @@ -1384,7 +1397,7 @@ static int set_branches(int argc, const char **argv) builtin_remote_setbranches_usage, 0); if (argc == 0) { error("no remote specified"); - usage_with_options(builtin_remote_seturl_usage, options); + usage_with_options(builtin_remote_setbranches_usage, options); } argv[argc] = NULL; @@ -1412,7 +1425,7 @@ static int set_url(int argc, const char **argv) "delete URLs"), OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage, + argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage, PARSE_OPT_KEEP_ARGV0); if (add_mode && delete_mode) diff --git a/builtin/replace.c b/builtin/replace.c index fe3a647a36..4a8970e9c9 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -58,7 +58,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) had_error = 1; continue; } - if (!resolve_ref(ref, sha1, 1, NULL)) { + if (read_ref(ref, sha1)) { error("replace ref '%s' not found.", *p); had_error = 1; continue; @@ -94,10 +94,10 @@ static int replace_object(const char *object_ref, const char *replace_ref, "refs/replace/%s", sha1_to_hex(object)) > sizeof(ref) - 1) die("replace ref name too long: %.*s...", 50, ref); - if (check_ref_format(ref)) + if (check_refname_format(ref, 0)) die("'%s' is not a valid ref name.", ref); - if (!resolve_ref(ref, prev, 1, NULL)) + if (read_ref(ref, prev)) hashclr(prev); else if (!force) die("replace ref '%s' already exists", ref); diff --git a/builtin/reset.c b/builtin/reset.c index 811e8e252c..8c2c1d52a2 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -43,6 +43,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet int nr = 1; int newfd; struct tree_desc desc[2]; + struct tree *tree; struct unpack_trees_options opts; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); @@ -84,6 +85,12 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet return error(_("Failed to find tree of %s."), sha1_to_hex(sha1)); if (unpack_trees(nr, desc, &opts)) return -1; + + if (reset_type == MIXED || reset_type == HARD) { + tree = parse_tree_indirect(sha1); + prime_cache_tree(&active_cache_tree, tree); + } + if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock)) return error(_("Could not write new index file.")); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 56727e8c1d..ab3be7ca82 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -168,29 +168,24 @@ static void finish_commit(struct commit *commit, void *data) commit->buffer = NULL; } -static void finish_object(struct object *obj, const struct name_path *path, const char *name) +static void finish_object(struct object *obj, + const struct name_path *path, const char *name, + void *cb_data) { if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1)) die("missing blob object '%s'", sha1_to_hex(obj->sha1)); } -static void show_object(struct object *obj, const struct name_path *path, const char *component) +static void show_object(struct object *obj, + const struct name_path *path, const char *component, + void *cb_data) { - char *name = path_name(path, component); - /* An object with name "foo\n0000000..." can be used to - * confuse downstream "git pack-objects" very badly. - */ - const char *ep = strchr(name, '\n'); + struct rev_info *info = cb_data; - finish_object(obj, path, name); - if (ep) { - printf("%s %.*s\n", sha1_to_hex(obj->sha1), - (int) (ep - name), - name); - } - else - printf("%s %s\n", sha1_to_hex(obj->sha1), name); - free(name); + finish_object(obj, path, component, cb_data); + if (info->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT) + parse_object(obj->sha1); + show_object_with_name(stdout, obj, path, component); } static void show_edge(struct commit *commit) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 4c19f844a9..98d1cbecca 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -468,6 +468,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) return 0; } + if (argc > 2 && !strcmp(argv[1], "--resolve-git-dir")) { + const char *gitdir = resolve_gitdir(argv[2]); + if (!gitdir) + die("not a gitdir '%s'", argv[2]); + puts(gitdir); + return 0; + } + if (argc > 1 && !strcmp("-h", argv[1])) usage(builtin_rev_parse_usage); diff --git a/builtin/revert.c b/builtin/revert.c index 3117776c2c..fce3f92981 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -13,6 +13,8 @@ #include "rerere.h" #include "merge-recursive.h" #include "refs.h" +#include "dir.h" +#include "sequencer.h" /* * This implements the builtins revert and cherry-pick. @@ -27,85 +29,212 @@ static const char * const revert_usage[] = { "git revert [options] <commit-ish>", + "git revert <subcommand>", NULL }; static const char * const cherry_pick_usage[] = { "git cherry-pick [options] <commit-ish>", + "git cherry-pick <subcommand>", NULL }; -static int edit, no_replay, no_commit, mainline, signoff, allow_ff; -static enum { REVERT, CHERRY_PICK } action; -static struct commit *commit; -static int commit_argc; -static const char **commit_argv; -static int allow_rerere_auto; +enum replay_action { REVERT, CHERRY_PICK }; +enum replay_subcommand { + REPLAY_NONE, + REPLAY_REMOVE_STATE, + REPLAY_CONTINUE, + REPLAY_ROLLBACK +}; + +struct replay_opts { + enum replay_action action; + enum replay_subcommand subcommand; -static const char *me; + /* Boolean options */ + int edit; + int record_origin; + int no_commit; + int signoff; + int allow_ff; + int allow_rerere_auto; -/* Merge strategy. */ -static const char *strategy; -static const char **xopts; -static size_t xopts_nr, xopts_alloc; + int mainline; + + /* Merge strategy */ + const char *strategy; + const char **xopts; + size_t xopts_nr, xopts_alloc; + + /* Only used by REPLAY_NONE */ + struct rev_info *revs; +}; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" +static const char *action_name(const struct replay_opts *opts) +{ + return opts->action == REVERT ? "revert" : "cherry-pick"; +} + static char *get_encoding(const char *message); -static const char * const *revert_or_cherry_pick_usage(void) +static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - return action == REVERT ? revert_usage : cherry_pick_usage; + return opts->action == REVERT ? revert_usage : cherry_pick_usage; } static int option_parse_x(const struct option *opt, const char *arg, int unset) { + struct replay_opts **opts_ptr = opt->value; + struct replay_opts *opts = *opts_ptr; + if (unset) return 0; - ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); - xopts[xopts_nr++] = xstrdup(arg); + ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); + opts->xopts[opts->xopts_nr++] = xstrdup(arg); return 0; } -static void parse_args(int argc, const char **argv) +static void verify_opt_compatible(const char *me, const char *base_opt, ...) +{ + const char *this_opt; + va_list ap; + + va_start(ap, base_opt); + while ((this_opt = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; + } + va_end(ap); + + if (this_opt) + die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); +} + +static void verify_opt_mutually_compatible(const char *me, ...) { - const char * const * usage_str = revert_or_cherry_pick_usage(); - int noop; + const char *opt1, *opt2 = NULL; + va_list ap; + + va_start(ap, me); + while ((opt1 = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; + } + if (opt1) { + while ((opt2 = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; + } + } + + if (opt1 && opt2) + die(_("%s: %s cannot be used with %s"), me, opt1, opt2); +} + +static void parse_args(int argc, const char **argv, struct replay_opts *opts) +{ + const char * const * usage_str = revert_or_cherry_pick_usage(opts); + const char *me = action_name(opts); + int remove_state = 0; + int contin = 0; + int rollback = 0; struct option options[] = { - OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"), - OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"), - { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 }, - OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), - OPT_INTEGER('m', "mainline", &mainline, "parent number"), - OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), - OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"), - OPT_CALLBACK('X', "strategy-option", &xopts, "option", + OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"), + OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"), + OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"), + OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"), + OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"), + OPT_NOOP_NOARG('r', NULL), + OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"), + OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"), + OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto), + OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"), + OPT_CALLBACK('X', "strategy-option", &opts, "option", "option for merge strategy", option_parse_x), OPT_END(), OPT_END(), OPT_END(), }; - if (action == CHERRY_PICK) { + if (opts->action == CHERRY_PICK) { struct option cp_extra[] = { - OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"), - OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"), + OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), + OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), OPT_END(), }; if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) die(_("program error")); } - commit_argc = parse_options(argc, argv, NULL, options, usage_str, - PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN); - if (commit_argc < 2) - usage_with_options(usage_str, options); + argc = parse_options(argc, argv, NULL, options, usage_str, + PARSE_OPT_KEEP_ARGV0 | + PARSE_OPT_KEEP_UNKNOWN); + + /* Check for incompatible subcommands */ + verify_opt_mutually_compatible(me, + "--quit", remove_state, + "--continue", contin, + "--abort", rollback, + NULL); + + /* Set the subcommand */ + if (remove_state) + opts->subcommand = REPLAY_REMOVE_STATE; + else if (contin) + opts->subcommand = REPLAY_CONTINUE; + else if (rollback) + opts->subcommand = REPLAY_ROLLBACK; + else + opts->subcommand = REPLAY_NONE; + + /* Check for incompatible command line arguments */ + if (opts->subcommand != REPLAY_NONE) { + char *this_operation; + if (opts->subcommand == REPLAY_REMOVE_STATE) + this_operation = "--quit"; + else if (opts->subcommand == REPLAY_CONTINUE) + this_operation = "--continue"; + else { + assert(opts->subcommand == REPLAY_ROLLBACK); + this_operation = "--abort"; + } - commit_argv = argv; + verify_opt_compatible(me, this_operation, + "--no-commit", opts->no_commit, + "--signoff", opts->signoff, + "--mainline", opts->mainline, + "--strategy", opts->strategy ? 1 : 0, + "--strategy-option", opts->xopts ? 1 : 0, + "-x", opts->record_origin, + "--ff", opts->allow_ff, + NULL); + } + + if (opts->allow_ff) + verify_opt_compatible(me, "--ff", + "--signoff", opts->signoff, + "--no-commit", opts->no_commit, + "-x", opts->record_origin, + "--edit", opts->edit, + NULL); + + if (opts->subcommand != REPLAY_NONE) { + opts->revs = NULL; + } else { + opts->revs = xmalloc(sizeof(*opts->revs)); + init_revisions(opts->revs, NULL); + opts->revs->no_walk = 1; + if (argc < 2) + usage_with_options(usage_str, options); + argc = setup_revisions(argc, argv, opts->revs, NULL); + } + + if (argc > 1) + usage_with_options(usage_str, options); } struct commit_message { @@ -116,25 +245,25 @@ struct commit_message { const char *message; }; -static int get_message(const char *raw_message, struct commit_message *out) +static int get_message(struct commit *commit, struct commit_message *out) { const char *encoding; const char *abbrev, *subject; int abbrev_len, subject_len; char *q; - if (!raw_message) + if (!commit->buffer) return -1; - encoding = get_encoding(raw_message); + encoding = get_encoding(commit->buffer); if (!encoding) encoding = "UTF-8"; if (!git_commit_encoding) git_commit_encoding = "UTF-8"; out->reencoded_message = NULL; - out->message = raw_message; + out->message = commit->buffer; if (strcmp(encoding, git_commit_encoding)) - out->reencoded_message = reencode_string(raw_message, + out->reencoded_message = reencode_string(commit->buffer, git_commit_encoding, encoding); if (out->reencoded_message) out->message = out->reencoded_message; @@ -167,9 +296,6 @@ static char *get_encoding(const char *message) { const char *p = message, *eol; - if (!p) - die (_("Could not read commit message of %s"), - sha1_to_hex(commit->object.sha1)); while (*p && *p != '\n') { for (eol = p + 1; *eol && *eol != '\n'; eol++) ; /* do nothing */ @@ -185,45 +311,24 @@ static char *get_encoding(const char *message) return NULL; } -static void add_message_to_msg(struct strbuf *msgbuf, const char *message) -{ - const char *p = message; - while (*p && (*p != '\n' || p[1] != '\n')) - p++; - - if (!*p) - strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1)); - - p += 2; - strbuf_addstr(msgbuf, p); -} - -static void write_cherry_pick_head(void) +static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) { + const char *filename; int fd; struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); - fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666); + filename = git_path("%s", pseudoref); + fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) - die_errno(_("Could not open '%s' for writing"), - git_path("CHERRY_PICK_HEAD")); + die_errno(_("Could not open '%s' for writing"), filename); if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) - die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD")); + die_errno(_("Could not write to '%s'"), filename); strbuf_release(&buf); } -static void advise(const char *advice, ...) -{ - va_list params; - - va_start(params, advice); - vreportf("hint: ", advice, params); - va_end(params); -} - -static void print_advice(void) +static void print_advice(int show_hint) { char *msg = getenv("GIT_CHERRY_PICK_HELP"); @@ -238,9 +343,11 @@ static void print_advice(void) return; } - advise("after resolving the conflicts, mark the corrected paths"); - advise("with 'git add <paths>' or 'git rm <paths>'"); - advise("and commit the result with 'git commit'"); + if (show_hint) { + advise("after resolving the conflicts, mark the corrected paths"); + advise("with 'git add <paths>' or 'git rm <paths>'"); + advise("and commit the result with 'git commit'"); + } } static void write_message(struct strbuf *msgbuf, const char *filename) @@ -250,7 +357,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename) int msg_fd = hold_lock_file_for_update(&msg_file, filename, LOCK_DIE_ON_ERROR); if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - die_errno(_("Could not write to %s."), filename); + die_errno(_("Could not write to %s"), filename); strbuf_release(msgbuf); if (commit_lock_file(&msg_file) < 0) die(_("Error wrapping up %s"), filename); @@ -261,25 +368,20 @@ static struct tree *empty_tree(void) return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); } -static NORETURN void die_dirty_index(const char *me) +static int error_dirty_index(struct replay_opts *opts) { - if (read_cache_unmerged()) { - die_resolve_conflict(me); - } else { - if (advice_commit_before_merge) { - if (action == REVERT) - die(_("Your local changes would be overwritten by revert.\n" - "Please, commit your changes or stash them to proceed.")); - else - die(_("Your local changes would be overwritten by cherry-pick.\n" - "Please, commit your changes or stash them to proceed.")); - } else { - if (action == REVERT) - die(_("Your local changes would be overwritten by revert.\n")); - else - die(_("Your local changes would be overwritten by cherry-pick.\n")); - } - } + if (read_cache_unmerged()) + return error_resolve_conflict(action_name(opts)); + + /* Different translation strings for cherry-pick and revert */ + if (opts->action == CHERRY_PICK) + error(_("Your local changes would be overwritten by cherry-pick.")); + else + error(_("Your local changes would be overwritten by revert.")); + + if (advice_commit_before_merge) + advise(_("Commit your changes or stash them to proceed.")); + return -1; } static int fast_forward_to(const unsigned char *to, const unsigned char *from) @@ -295,7 +397,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from) static int do_recursive_merge(struct commit *base, struct commit *next, const char *base_label, const char *next_label, - unsigned char *head, struct strbuf *msgbuf) + unsigned char *head, struct strbuf *msgbuf, + struct replay_opts *opts) { struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; @@ -316,7 +419,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, next_tree = next ? next->tree : empty_tree(); base_tree = base ? base->tree : empty_tree(); - for (xopt = xopts; xopt != xopts + xopts_nr; xopt++) + for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) parse_merge_opt(&o, *xopt); clean = merge_trees(&o, @@ -327,7 +430,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(&index_lock))) /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ - die(_("%s: Unable to write new index file"), me); + die(_("%s: Unable to write new index file"), action_name(opts)); rollback_lock_file(&index_lock); if (!clean) { @@ -356,7 +459,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, * If we are revert, or if our cherry-pick results in a hand merge, * we had better say that the current user is responsible for that. */ -static int run_git_commit(const char *defmsg) +static int run_git_commit(const char *defmsg, struct replay_opts *opts) { /* 6 is max possible length of our args array including NULL */ const char *args[6]; @@ -364,9 +467,9 @@ static int run_git_commit(const char *defmsg) args[i++] = "commit"; args[i++] = "-n"; - if (signoff) + if (opts->signoff) args[i++] = "-s"; - if (!edit) { + if (!opts->edit) { args[i++] = "-F"; args[i++] = defmsg; } @@ -375,7 +478,7 @@ static int run_git_commit(const char *defmsg) return run_command_v_opt(args, RUN_GIT_CMD); } -static int do_pick_commit(void) +static int do_pick_commit(struct commit *commit, struct replay_opts *opts) { unsigned char head[20]; struct commit *base, *next, *parent; @@ -385,7 +488,7 @@ static int do_pick_commit(void) struct strbuf msgbuf = STRBUF_INIT; int res; - if (no_commit) { + if (opts->no_commit) { /* * We do not intend to commit immediately. We just want to * merge the differences in, so let's compute the tree @@ -396,9 +499,9 @@ static int do_pick_commit(void) die (_("Your index file is unmerged.")); } else { if (get_sha1("HEAD", head)) - die (_("You do not have a valid HEAD")); + return error(_("You do not have a valid HEAD")); if (index_differs_from("HEAD", 0)) - die_dirty_index(me); + return error_dirty_index(opts); } discard_cache(); @@ -410,36 +513,36 @@ static int do_pick_commit(void) int cnt; struct commit_list *p; - if (!mainline) - die(_("Commit %s is a merge but no -m option was given."), - sha1_to_hex(commit->object.sha1)); + if (!opts->mainline) + return error(_("Commit %s is a merge but no -m option was given."), + sha1_to_hex(commit->object.sha1)); for (cnt = 1, p = commit->parents; - cnt != mainline && p; + cnt != opts->mainline && p; cnt++) p = p->next; - if (cnt != mainline || !p) - die(_("Commit %s does not have parent %d"), - sha1_to_hex(commit->object.sha1), mainline); + if (cnt != opts->mainline || !p) + return error(_("Commit %s does not have parent %d"), + sha1_to_hex(commit->object.sha1), opts->mainline); parent = p->item; - } else if (0 < mainline) - die(_("Mainline was specified but commit %s is not a merge."), - sha1_to_hex(commit->object.sha1)); + } else if (0 < opts->mainline) + return error(_("Mainline was specified but commit %s is not a merge."), + sha1_to_hex(commit->object.sha1)); else parent = commit->parents->item; - if (allow_ff && parent && !hashcmp(parent->object.sha1, head)) + if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head)) return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be "revert" or "cherry-pick", the second %s a SHA1 */ - die(_("%s: cannot parse parent commit %s"), - me, sha1_to_hex(parent->object.sha1)); + return error(_("%s: cannot parse parent commit %s"), + action_name(opts), sha1_to_hex(parent->object.sha1)); - if (get_message(commit->buffer, &msg) != 0) - die(_("Cannot get commit message for %s"), - sha1_to_hex(commit->object.sha1)); + if (get_message(commit, &msg) != 0) + return error(_("Cannot get commit message for %s"), + sha1_to_hex(commit->object.sha1)); /* * "commit" is an existing commit. We would want to apply @@ -450,7 +553,7 @@ static int do_pick_commit(void) defmsg = git_pathdup("MERGE_MSG"); - if (action == REVERT) { + if (opts->action == REVERT) { base = commit; base_label = msg.label; next = parent; @@ -466,23 +569,34 @@ static int do_pick_commit(void) } strbuf_addstr(&msgbuf, ".\n"); } else { + const char *p; + base = parent; base_label = msg.parent_label; next = commit; next_label = msg.label; - add_message_to_msg(&msgbuf, msg.message); - if (no_replay) { + + /* + * Append the commit log message to msgbuf; it starts + * after the tree, parent, author, committer + * information followed by "\n\n". + */ + p = strstr(msg.message, "\n\n"); + if (p) { + p += 2; + strbuf_addstr(&msgbuf, p); + } + + if (opts->record_origin) { strbuf_addstr(&msgbuf, "(cherry picked from commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } - if (!no_commit) - write_cherry_pick_head(); } - if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) { res = do_recursive_merge(base, next, base_label, next_label, - head, &msgbuf); + head, &msgbuf, opts); write_message(&msgbuf, defmsg); } else { struct commit_list *common = NULL; @@ -492,23 +606,34 @@ static int do_pick_commit(void) commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res = try_merge_command(strategy, xopts_nr, xopts, common, - sha1_to_hex(head), remotes); + res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, + common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); } + /* + * If the merge was clean or if it failed due to conflict, we write + * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. + * However, if the merge did not even start, then we don't want to + * write it at all. + */ + if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) + write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); + if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1)) + write_cherry_pick_head(commit, "REVERT_HEAD"); + if (res) { - error(action == REVERT + error(opts->action == REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), msg.subject); - print_advice(); - rerere(allow_rerere_auto); + print_advice(res == 1); + rerere(opts->allow_rerere_auto); } else { - if (!no_commit) - res = run_git_commit(defmsg); + if (!opts->no_commit) + res = run_git_commit(defmsg, opts); } free_message(&msg); @@ -517,84 +642,523 @@ static int do_pick_commit(void) return res; } -static void prepare_revs(struct rev_info *revs) +static void prepare_revs(struct replay_opts *opts) { - int argc; - - init_revisions(revs, NULL); - revs->no_walk = 1; - if (action != REVERT) - revs->reverse = 1; - - argc = setup_revisions(commit_argc, commit_argv, revs, NULL); - if (argc > 1) - usage(*revert_or_cherry_pick_usage()); + if (opts->action != REVERT) + opts->revs->reverse ^= 1; - if (prepare_revision_walk(revs)) + if (prepare_revision_walk(opts->revs)) die(_("revision walk setup failed")); - if (!revs->commits) + if (!opts->revs->commits) die(_("empty commit set passed")); } -static void read_and_refresh_cache(const char *me) +static void read_and_refresh_cache(struct replay_opts *opts) { static struct lock_file index_lock; int index_fd = hold_locked_index(&index_lock, 0); if (read_index_preload(&the_index, NULL) < 0) - die(_("git %s: failed to read the index"), me); + die(_("git %s: failed to read the index"), action_name(opts)); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed) { if (write_index(&the_index, index_fd) || commit_locked_index(&index_lock)) - die(_("git %s: failed to refresh the index"), me); + die(_("git %s: failed to refresh the index"), action_name(opts)); } rollback_lock_file(&index_lock); } -static int revert_or_cherry_pick(int argc, const char **argv) +/* + * Append a commit to the end of the commit_list. + * + * next starts by pointing to the variable that holds the head of an + * empty commit_list, and is updated to point to the "next" field of + * the last item on the list as new commits are appended. + * + * Usage example: + * + * struct commit_list *list; + * struct commit_list **next = &list; + * + * next = commit_list_append(c1, next); + * next = commit_list_append(c2, next); + * assert(commit_list_count(list) == 2); + * return list; + */ +static struct commit_list **commit_list_append(struct commit *commit, + struct commit_list **next) { - struct rev_info revs; + struct commit_list *new = xmalloc(sizeof(struct commit_list)); + new->item = commit; + *next = new; + new->next = NULL; + return &new->next; +} - git_config(git_default_config, NULL); - me = action == REVERT ? "revert" : "cherry-pick"; - setenv(GIT_REFLOG_ACTION, me, 0); - parse_args(argc, argv); +static int format_todo(struct strbuf *buf, struct commit_list *todo_list, + struct replay_opts *opts) +{ + struct commit_list *cur = NULL; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; + const char *sha1_abbrev = NULL; + const char *action_str = opts->action == REVERT ? "revert" : "pick"; + + for (cur = todo_list; cur; cur = cur->next) { + sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV); + if (get_message(cur->item, &msg)) + return error(_("Cannot get commit message for %s"), sha1_abbrev); + strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject); + } + return 0; +} - if (allow_ff) { - if (signoff) - die(_("cherry-pick --ff cannot be used with --signoff")); - if (no_commit) - die(_("cherry-pick --ff cannot be used with --no-commit")); - if (no_replay) - die(_("cherry-pick --ff cannot be used with -x")); - if (edit) - die(_("cherry-pick --ff cannot be used with --edit")); +static struct commit *parse_insn_line(char *start, struct replay_opts *opts) +{ + unsigned char commit_sha1[20]; + char sha1_abbrev[40]; + enum replay_action action; + int insn_len = 0; + char *p, *q; + + if (!prefixcmp(start, "pick ")) { + action = CHERRY_PICK; + insn_len = strlen("pick"); + p = start + insn_len + 1; + } else if (!prefixcmp(start, "revert ")) { + action = REVERT; + insn_len = strlen("revert"); + p = start + insn_len + 1; + } else + return NULL; + + q = strchr(p, ' '); + if (!q) + return NULL; + q++; + + strlcpy(sha1_abbrev, p, q - p); + + /* + * Verify that the action matches up with the one in + * opts; we don't support arbitrary instructions + */ + if (action != opts->action) { + const char *action_str; + action_str = action == REVERT ? "revert" : "cherry-pick"; + error(_("Cannot %s during a %s"), action_str, action_name(opts)); + return NULL; } - read_and_refresh_cache(me); + if (get_sha1(sha1_abbrev, commit_sha1) < 0) + return NULL; - prepare_revs(&revs); + return lookup_commit_reference(commit_sha1); +} + +static int parse_insn_buffer(char *buf, struct commit_list **todo_list, + struct replay_opts *opts) +{ + struct commit_list **next = todo_list; + struct commit *commit; + char *p = buf; + int i; + + for (i = 1; *p; i++) { + commit = parse_insn_line(p, opts); + if (!commit) + return error(_("Could not parse line %d."), i); + next = commit_list_append(commit, next); + p = strchrnul(p, '\n'); + if (*p) + p++; + } + if (!*todo_list) + return error(_("No commits parsed.")); + return 0; +} + +static void read_populate_todo(struct commit_list **todo_list, + struct replay_opts *opts) +{ + const char *todo_file = git_path(SEQ_TODO_FILE); + struct strbuf buf = STRBUF_INIT; + int fd, res; + + fd = open(todo_file, O_RDONLY); + if (fd < 0) + die_errno(_("Could not open %s"), todo_file); + if (strbuf_read(&buf, fd, 0) < 0) { + close(fd); + strbuf_release(&buf); + die(_("Could not read %s."), todo_file); + } + close(fd); + + res = parse_insn_buffer(buf.buf, todo_list, opts); + strbuf_release(&buf); + if (res) + die(_("Unusable instruction sheet: %s"), todo_file); +} + +static int populate_opts_cb(const char *key, const char *value, void *data) +{ + struct replay_opts *opts = data; + int error_flag = 1; + + if (!value) + error_flag = 0; + else if (!strcmp(key, "options.no-commit")) + opts->no_commit = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.edit")) + opts->edit = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.signoff")) + opts->signoff = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.record-origin")) + opts->record_origin = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.allow-ff")) + opts->allow_ff = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.mainline")) + opts->mainline = git_config_int(key, value); + else if (!strcmp(key, "options.strategy")) + git_config_string(&opts->strategy, key, value); + else if (!strcmp(key, "options.strategy-option")) { + ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); + opts->xopts[opts->xopts_nr++] = xstrdup(value); + } else + return error(_("Invalid key: %s"), key); + + if (!error_flag) + return error(_("Invalid value for %s: %s"), key, value); + + return 0; +} - while ((commit = get_revision(&revs))) { - int res = do_pick_commit(); +static void read_populate_opts(struct replay_opts **opts_ptr) +{ + const char *opts_file = git_path(SEQ_OPTS_FILE); + + if (!file_exists(opts_file)) + return; + if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0) + die(_("Malformed options sheet: %s"), opts_file); +} + +static void walk_revs_populate_todo(struct commit_list **todo_list, + struct replay_opts *opts) +{ + struct commit *commit; + struct commit_list **next; + + prepare_revs(opts); + + next = todo_list; + while ((commit = get_revision(opts->revs))) + next = commit_list_append(commit, next); +} + +static int create_seq_dir(void) +{ + const char *seq_dir = git_path(SEQ_DIR); + + if (file_exists(seq_dir)) { + error(_("a cherry-pick or revert is already in progress")); + advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); + return -1; + } + else if (mkdir(seq_dir, 0777) < 0) + die_errno(_("Could not create sequencer directory %s"), seq_dir); + return 0; +} + +static void save_head(const char *head) +{ + const char *head_file = git_path(SEQ_HEAD_FILE); + static struct lock_file head_lock; + struct strbuf buf = STRBUF_INIT; + int fd; + + fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR); + strbuf_addf(&buf, "%s\n", head); + if (write_in_full(fd, buf.buf, buf.len) < 0) + die_errno(_("Could not write to %s"), head_file); + if (commit_lock_file(&head_lock) < 0) + die(_("Error wrapping up %s."), head_file); +} + +static int reset_for_rollback(const unsigned char *sha1) +{ + const char *argv[4]; /* reset --merge <arg> + NULL */ + argv[0] = "reset"; + argv[1] = "--merge"; + argv[2] = sha1_to_hex(sha1); + argv[3] = NULL; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int rollback_single_pick(void) +{ + unsigned char head_sha1[20]; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + if (read_ref_full("HEAD", head_sha1, 0, NULL)) + return error(_("cannot resolve HEAD")); + if (is_null_sha1(head_sha1)) + return error(_("cannot abort from a branch yet to be born")); + return reset_for_rollback(head_sha1); +} + +static int sequencer_rollback(struct replay_opts *opts) +{ + const char *filename; + FILE *f; + unsigned char sha1[20]; + struct strbuf buf = STRBUF_INIT; + + filename = git_path(SEQ_HEAD_FILE); + f = fopen(filename, "r"); + if (!f && errno == ENOENT) { + /* + * There is no multiple-cherry-pick in progress. + * If CHERRY_PICK_HEAD or REVERT_HEAD indicates + * a single-cherry-pick in progress, abort that. + */ + return rollback_single_pick(); + } + if (!f) + return error(_("cannot open %s: %s"), filename, + strerror(errno)); + if (strbuf_getline(&buf, f, '\n')) { + error(_("cannot read %s: %s"), filename, ferror(f) ? + strerror(errno) : _("unexpected end of file")); + fclose(f); + goto fail; + } + fclose(f); + if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { + error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), + filename); + goto fail; + } + if (reset_for_rollback(sha1)) + goto fail; + remove_sequencer_state(); + strbuf_release(&buf); + return 0; +fail: + strbuf_release(&buf); + return -1; +} + +static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) +{ + const char *todo_file = git_path(SEQ_TODO_FILE); + static struct lock_file todo_lock; + struct strbuf buf = STRBUF_INIT; + int fd; + + fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR); + if (format_todo(&buf, todo_list, opts) < 0) + die(_("Could not format %s."), todo_file); + if (write_in_full(fd, buf.buf, buf.len) < 0) { + strbuf_release(&buf); + die_errno(_("Could not write to %s"), todo_file); + } + if (commit_lock_file(&todo_lock) < 0) { + strbuf_release(&buf); + die(_("Error wrapping up %s."), todo_file); + } + strbuf_release(&buf); +} + +static void save_opts(struct replay_opts *opts) +{ + const char *opts_file = git_path(SEQ_OPTS_FILE); + + if (opts->no_commit) + git_config_set_in_file(opts_file, "options.no-commit", "true"); + if (opts->edit) + git_config_set_in_file(opts_file, "options.edit", "true"); + if (opts->signoff) + git_config_set_in_file(opts_file, "options.signoff", "true"); + if (opts->record_origin) + git_config_set_in_file(opts_file, "options.record-origin", "true"); + if (opts->allow_ff) + git_config_set_in_file(opts_file, "options.allow-ff", "true"); + if (opts->mainline) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "%d", opts->mainline); + git_config_set_in_file(opts_file, "options.mainline", buf.buf); + strbuf_release(&buf); + } + if (opts->strategy) + git_config_set_in_file(opts_file, "options.strategy", opts->strategy); + if (opts->xopts) { + int i; + for (i = 0; i < opts->xopts_nr; i++) + git_config_set_multivar_in_file(opts_file, + "options.strategy-option", + opts->xopts[i], "^$", 0); + } +} + +static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) +{ + struct commit_list *cur; + int res; + + setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + if (opts->allow_ff) + assert(!(opts->signoff || opts->no_commit || + opts->record_origin || opts->edit)); + read_and_refresh_cache(opts); + + for (cur = todo_list; cur; cur = cur->next) { + save_todo(cur, opts); + res = do_pick_commit(cur->item, opts); if (res) return res; } + /* + * Sequence of picks finished successfully; cleanup by + * removing the .git/sequencer directory + */ + remove_sequencer_state(); return 0; } +static int continue_single_pick(void) +{ + const char *argv[] = { "commit", NULL }; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int sequencer_continue(struct replay_opts *opts) +{ + struct commit_list *todo_list = NULL; + + if (!file_exists(git_path(SEQ_TODO_FILE))) + return continue_single_pick(); + read_populate_opts(&opts); + read_populate_todo(&todo_list, opts); + + /* Verify that the conflict has been resolved */ + if (file_exists(git_path("CHERRY_PICK_HEAD")) || + file_exists(git_path("REVERT_HEAD"))) { + int ret = continue_single_pick(); + if (ret) + return ret; + } + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + todo_list = todo_list->next; + return pick_commits(todo_list, opts); +} + +static int single_pick(struct commit *cmit, struct replay_opts *opts) +{ + setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + return do_pick_commit(cmit, opts); +} + +static int pick_revisions(struct replay_opts *opts) +{ + struct commit_list *todo_list = NULL; + unsigned char sha1[20]; + + if (opts->subcommand == REPLAY_NONE) + assert(opts->revs); + + read_and_refresh_cache(opts); + + /* + * Decide what to do depending on the arguments; a fresh + * cherry-pick should be handled differently from an existing + * one that is being continued + */ + if (opts->subcommand == REPLAY_REMOVE_STATE) { + remove_sequencer_state(); + return 0; + } + if (opts->subcommand == REPLAY_ROLLBACK) + return sequencer_rollback(opts); + if (opts->subcommand == REPLAY_CONTINUE) + return sequencer_continue(opts); + + /* + * If we were called as "git cherry-pick <commit>", just + * cherry-pick/revert it, set CHERRY_PICK_HEAD / + * REVERT_HEAD, and don't touch the sequencer state. + * This means it is possible to cherry-pick in the middle + * of a cherry-pick sequence. + */ + if (opts->revs->cmdline.nr == 1 && + opts->revs->cmdline.rev->whence == REV_CMD_REV && + opts->revs->no_walk && + !opts->revs->cmdline.rev->flags) { + struct commit *cmit; + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + cmit = get_revision(opts->revs); + if (!cmit || get_revision(opts->revs)) + die("BUG: expected exactly one commit from walk"); + return single_pick(cmit, opts); + } + + /* + * Start a new cherry-pick/ revert sequence; but + * first, make sure that an existing one isn't in + * progress + */ + + walk_revs_populate_todo(&todo_list, opts); + if (create_seq_dir() < 0) + return -1; + if (get_sha1("HEAD", sha1)) { + if (opts->action == REVERT) + return error(_("Can't revert as initial commit")); + return error(_("Can't cherry-pick into empty head")); + } + save_head(sha1_to_hex(sha1)); + save_opts(opts); + return pick_commits(todo_list, opts); +} + int cmd_revert(int argc, const char **argv, const char *prefix) { + struct replay_opts opts; + int res; + + memset(&opts, 0, sizeof(opts)); if (isatty(0)) - edit = 1; - action = REVERT; - return revert_or_cherry_pick(argc, argv); + opts.edit = 1; + opts.action = REVERT; + git_config(git_default_config, NULL); + parse_args(argc, argv, &opts); + res = pick_revisions(&opts); + if (res < 0) + die(_("revert failed")); + return res; } int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - action = CHERRY_PICK; - return revert_or_cherry_pick(argc, argv); + struct replay_opts opts; + int res; + + memset(&opts, 0, sizeof(opts)); + opts.action = CHERRY_PICK; + git_config(git_default_config, NULL); + parse_args(argc, argv, &opts); + res = pick_revisions(&opts); + if (res < 0) + die(_("cherry-pick failed")); + return res; } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index c1f6ddd927..cd1115ffc6 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -334,7 +334,7 @@ int send_pack(struct send_pack_args *args, demux.data = fd; demux.out = -1; if (start_async(&demux)) - die("receive-pack: unable to fork off sideband demultiplexer"); + die("send-pack: unable to fork off sideband demultiplexer"); in = demux.out; } @@ -494,8 +494,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) memset(&extra_have, 0, sizeof(extra_have)); - get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL, - &extra_have); + get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have); transport_verify_remote_names(nr_refspecs, refspecs); @@ -509,7 +508,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) flags |= MATCH_REFS_MIRROR; /* match them up */ - if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) + if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) return -1; set_ref_status_for_push(remote_refs, args.send_mirror, diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 4b480d7c7c..a59e088cf5 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -726,10 +726,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (ac == 0) { static const char *fake_av[2]; - const char *refname; - refname = resolve_ref("HEAD", sha1, 1, NULL); - fake_av[0] = xstrdup(refname); + fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL); fake_av[1] = NULL; av = fake_av; ac = 1; @@ -791,7 +789,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } } - head_p = resolve_ref("HEAD", head_sha1, 1, NULL); + head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL); if (head_p) { head_len = strlen(head_p); memcpy(head, head_p, head_len + 1); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 45f0340c3e..3911661900 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -145,7 +145,7 @@ static int exclude_existing(const char *match) if (strncmp(ref, match, matchlen)) continue; } - if (check_ref_format(ref)) { + if (check_refname_format(ref, 0)) { warning("ref '%s' ignored", ref); continue; } @@ -225,7 +225,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) unsigned char sha1[20]; if (!prefixcmp(*pattern, "refs/") && - resolve_ref(*pattern, sha1, 1, NULL)) { + !read_ref(*pattern, sha1)) { if (!quiet) show_one(*pattern, sha1); } diff --git a/builtin/stripspace.c b/builtin/stripspace.c index 4d3b93fedb..f16986c0ae 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -22,8 +22,6 @@ static size_t cleanup(char *line, size_t len) * Remove empty lines from the beginning and end * and also trailing spaces from every line. * - * Note that the buffer will not be NUL-terminated. - * * Turn multiple consecutive empty lines between paragraphs * into just one empty line. * @@ -77,7 +75,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) !strcmp(argv[1], "--strip-comments"))) strip_comments = 1; else if (argc > 1) - usage("git stripspace [-s | --strip-comments] < <stream>"); + usage("git stripspace [-s | --strip-comments] < input"); if (strbuf_read(&buf, 0, 1024) < 0) die_errno("could not read the input"); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index dea849c3c5..2ef5962386 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -12,7 +12,7 @@ static void check_symref(const char *HEAD, int quiet) { unsigned char sha1[20]; int flag; - const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag); + const char *refs_heads_master = resolve_ref_unsafe(HEAD, sha1, 0, &flag); if (!refs_heads_master) die("No such ref: %s", HEAD); diff --git a/builtin/tag.c b/builtin/tag.c index 667515e527..31f02e80f6 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -14,6 +14,7 @@ #include "parse-options.h" #include "diff.h" #include "revision.h" +#include "gpg-interface.h" static const char * const git_tag_usage[] = { "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]", @@ -23,8 +24,6 @@ static const char * const git_tag_usage[] = { NULL }; -static char signingkey[1000]; - struct tag_filter { const char **patterns; int lines; @@ -174,7 +173,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) had_error = 1; continue; } - if (!resolve_ref(ref, sha1, 1, NULL)) { + if (read_ref(ref, sha1)) { error(_("tag '%s' not found."), *p); had_error = 1; continue; @@ -208,83 +207,29 @@ static int verify_tag(const char *name, const char *ref, static int do_sign(struct strbuf *buffer) { - struct child_process gpg; - const char *args[4]; - char *bracket; - int len; - int i, j; - - if (!*signingkey) { - if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME), - sizeof(signingkey)) > sizeof(signingkey) - 1) - return error(_("committer info too long.")); - bracket = strchr(signingkey, '>'); - if (bracket) - bracket[1] = '\0'; - } - - /* When the username signingkey is bad, program could be terminated - * because gpg exits without reading and then write gets SIGPIPE. */ - signal(SIGPIPE, SIG_IGN); - - memset(&gpg, 0, sizeof(gpg)); - gpg.argv = args; - gpg.in = -1; - gpg.out = -1; - args[0] = "gpg"; - args[1] = "-bsau"; - args[2] = signingkey; - args[3] = NULL; - - if (start_command(&gpg)) - return error(_("could not run gpg.")); - - if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) { - close(gpg.in); - close(gpg.out); - finish_command(&gpg); - return error(_("gpg did not accept the tag data")); - } - close(gpg.in); - len = strbuf_read(buffer, gpg.out, 1024); - close(gpg.out); - - if (finish_command(&gpg) || !len || len < 0) - return error(_("gpg failed to sign the tag")); - - /* Strip CR from the line endings, in case we are on Windows. */ - for (i = j = 0; i < buffer->len; i++) - if (buffer->buf[i] != '\r') { - if (i != j) - buffer->buf[j] = buffer->buf[i]; - j++; - } - strbuf_setlen(buffer, j); - - return 0; + return sign_buffer(buffer, buffer, get_signing_key()); } static const char tag_template[] = N_("\n" "#\n" "# Write a tag message\n" + "# Lines starting with '#' will be ignored.\n" "#\n"); -static void set_signingkey(const char *value) -{ - if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey)) - die(_("signing key value too long (%.10s...)"), value); -} +static const char tag_template_nocleanup[] = + N_("\n" + "#\n" + "# Write a tag message\n" + "# Lines starting with '#' will be kept; you may remove them" + " yourself if you want to.\n" + "#\n"); static int git_tag_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "user.signingkey")) { - if (!value) - return config_error_nonbool(var); - set_signingkey(value); - return 0; - } - + int status = git_gpg_config(var, value, cb); + if (status) + return status; return git_default_config(var, value, cb); } @@ -319,8 +264,18 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result) return 0; } +struct create_tag_options { + unsigned int message_given:1; + unsigned int sign; + enum { + CLEANUP_NONE, + CLEANUP_SPACE, + CLEANUP_ALL + } cleanup_mode; +}; + static void create_tag(const unsigned char *object, const char *tag, - struct strbuf *buf, int message, int sign, + struct strbuf *buf, struct create_tag_options *opt, unsigned char *prev, unsigned char *result) { enum object_type type; @@ -345,7 +300,7 @@ static void create_tag(const unsigned char *object, const char *tag, if (header_len > sizeof(header_buf) - 1) die(_("tag header too big.")); - if (!message) { + if (!opt->message_given) { int fd; /* write the template message before editing: */ @@ -356,8 +311,12 @@ static void create_tag(const unsigned char *object, const char *tag, if (!is_null_sha1(prev)) write_tag_body(fd, prev); + else if (opt->cleanup_mode == CLEANUP_ALL) + write_or_die(fd, _(tag_template), + strlen(_(tag_template))); else - write_or_die(fd, _(tag_template), strlen(_(tag_template))); + write_or_die(fd, _(tag_template_nocleanup), + strlen(_(tag_template_nocleanup))); close(fd); if (launch_editor(path, buf, NULL)) { @@ -367,14 +326,15 @@ static void create_tag(const unsigned char *object, const char *tag, } } - stripspace(buf, 1); + if (opt->cleanup_mode != CLEANUP_NONE) + stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); - if (!message && !buf->len) + if (!opt->message_given && !buf->len) die(_("no tag message?")); strbuf_insert(buf, 0, header_buf, header_len); - if (build_tag_object(buf, sign, result) < 0) { + if (build_tag_object(buf, opt->sign, result) < 0) { if (path) fprintf(stderr, _("The tag message has been left in %s\n"), path); @@ -407,12 +367,12 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) { if (name[0] == '-') - return CHECK_REF_FORMAT_ERROR; + return -1; strbuf_reset(sb); strbuf_addf(sb, "refs/tags/%s", name); - return check_ref_format(sb->buf); + return check_refname_format(sb->buf, 0); } int cmd_tag(int argc, const char **argv, const char *prefix) @@ -422,28 +382,31 @@ int cmd_tag(int argc, const char **argv, const char *prefix) unsigned char object[20], prev[20]; const char *object_ref, *tag; struct ref_lock *lock; - - int annotate = 0, sign = 0, force = 0, lines = -1, - list = 0, delete = 0, verify = 0; + struct create_tag_options opt; + char *cleanup_arg = NULL; + int annotate = 0, force = 0, lines = -1, list = 0, + delete = 0, verify = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct commit_list *with_commit = NULL; struct option options[] = { - OPT_BOOLEAN('l', NULL, &list, "list tag names"), + OPT_BOOLEAN('l', "list", &list, "list tag names"), { 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"), + OPT_BOOLEAN('d', "delete", &delete, "delete tags"), + OPT_BOOLEAN('v', "verify", &verify, "verify tags"), OPT_GROUP("Tag creation options"), - OPT_BOOLEAN('a', NULL, &annotate, + OPT_BOOLEAN('a', "annotate", &annotate, "annotated tag, needs a message"), - OPT_CALLBACK('m', NULL, &msg, "message", + OPT_CALLBACK('m', "message", &msg, "message", "tag message", parse_msg_arg), - OPT_FILENAME('F', NULL, &msgfile, "read message from file"), - OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), - OPT_STRING('u', NULL, &keyid, "key-id", + OPT_FILENAME('F', "file", &msgfile, "read message from file"), + OPT_BOOLEAN('s', "sign", &opt.sign, "annotated and GPG-signed tag"), + OPT_STRING(0, "cleanup", &cleanup_arg, "mode", + "how to strip spaces and #comments from message"), + OPT_STRING('u', "local-user", &keyid, "key-id", "use another key to sign the tag"), OPT__FORCE(&force, "replace the tag if exists"), @@ -459,13 +422,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix) git_config(git_tag_config, NULL); + memset(&opt, 0, sizeof(opt)); + argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0); if (keyid) { - sign = 1; - set_signingkey(keyid); + opt.sign = 1; + set_signing_key(keyid); } - if (sign) + if (opt.sign) annotate = 1; if (argc == 0 && !(delete || verify)) list = 1; @@ -518,14 +483,24 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (strbuf_check_tag_ref(&ref, tag)) die(_("'%s' is not a valid tag name."), tag); - if (!resolve_ref(ref.buf, prev, 1, NULL)) + if (read_ref(ref.buf, prev)) hashclr(prev); else if (!force) die(_("tag '%s' already exists"), tag); + opt.message_given = msg.given || msgfile; + + if (!cleanup_arg || !strcmp(cleanup_arg, "strip")) + opt.cleanup_mode = CLEANUP_ALL; + else if (!strcmp(cleanup_arg, "verbatim")) + opt.cleanup_mode = CLEANUP_NONE; + else if (!strcmp(cleanup_arg, "whitespace")) + opt.cleanup_mode = CLEANUP_SPACE; + else + die(_("Invalid cleanup mode %s"), cleanup_arg); + if (annotate) - create_tag(object, tag, &buf, msg.given || msgfile, - sign, prev, object); + create_tag(object, tag, &buf, &opt, prev, object); lock = lock_any_ref_for_update(ref.buf, prev, 0); if (!lock) diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 2d0b38333e..b928beb8ed 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -6,6 +6,7 @@ #include "archive.h" #include "pkt-line.h" #include "sideband.h" +#include "run-command.h" static const char upload_archive_usage[] = "git upload-archive <repo>"; @@ -13,12 +14,9 @@ static const char upload_archive_usage[] = static const char deadchild[] = "git upload-archive: archiver died with error"; -static const char lostchild[] = -"git upload-archive: archiver process was lost"; - #define MAX_ARGS (64) -static int run_upload_archive(int argc, const char **argv, const char *prefix) +int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) { const char *sent_argv[MAX_ARGS]; const char *arg_cmd = "argument "; @@ -96,8 +94,8 @@ static ssize_t process_input(int child_fd, int band) int cmd_upload_archive(int argc, const char **argv, const char *prefix) { - pid_t writer; - int fd1[2], fd2[2]; + struct child_process writer = { argv }; + /* * Set up sideband subprocess. * @@ -105,39 +103,24 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) * multiplexed out to our fd#1. If the child dies, we tell the other * end over channel #3. */ - if (pipe(fd1) < 0 || pipe(fd2) < 0) { - int err = errno; - packet_write(1, "NACK pipe failed on the remote side\n"); - die("upload-archive: %s", strerror(err)); - } - writer = fork(); - if (writer < 0) { + argv[0] = "upload-archive--writer"; + writer.out = writer.err = -1; + writer.git_cmd = 1; + if (start_command(&writer)) { int err = errno; - packet_write(1, "NACK fork failed on the remote side\n"); + packet_write(1, "NACK unable to spawn subprocess\n"); die("upload-archive: %s", strerror(err)); } - if (!writer) { - /* child - connect fd#1 and fd#2 to the pipe */ - dup2(fd1[1], 1); - dup2(fd2[1], 2); - close(fd1[1]); close(fd2[1]); - close(fd1[0]); close(fd2[0]); /* we do not read from pipe */ - - exit(run_upload_archive(argc, argv, prefix)); - } - /* parent - read from child, multiplex and send out to fd#1 */ - close(fd1[1]); close(fd2[1]); /* we do not write to pipe */ packet_write(1, "ACK\n"); packet_flush(1); while (1) { struct pollfd pfd[2]; - int status; - pfd[0].fd = fd1[0]; + pfd[0].fd = writer.out; pfd[0].events = POLLIN; - pfd[1].fd = fd2[0]; + pfd[1].fd = writer.err; pfd[1].events = POLLIN; if (poll(pfd, 2, -1) < 0) { if (errno != EINTR) { @@ -156,9 +139,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) if (process_input(pfd[0].fd, 1)) continue; - if (waitpid(writer, &status, 0) < 0) - error_clnt("%s", lostchild); - else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + if (finish_command(&writer)) error_clnt("%s", deadchild); packet_flush(1); break; diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 3134766049..28c2174338 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -11,6 +11,7 @@ #include "run-command.h" #include <signal.h> #include "parse-options.h" +#include "gpg-interface.h" static const char * const verify_tag_usage[] = { "git verify-tag [-v|--verbose] <tag>...", @@ -19,42 +20,16 @@ static const char * const verify_tag_usage[] = { static int run_gpg_verify(const char *buf, unsigned long size, int verbose) { - struct child_process gpg; - const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; - char path[PATH_MAX]; - size_t len; - int fd, ret; + int len; - fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX"); - if (fd < 0) - return error("could not create temporary file '%s': %s", - path, strerror(errno)); - if (write_in_full(fd, buf, size) < 0) - return error("failed writing temporary file '%s': %s", - path, strerror(errno)); - close(fd); - - /* find the length without signature */ len = parse_signature(buf, size); if (verbose) write_in_full(1, buf, len); - memset(&gpg, 0, sizeof(gpg)); - gpg.argv = args_gpg; - gpg.in = -1; - args_gpg[2] = path; - if (start_command(&gpg)) { - unlink(path); - return error("could not run gpg."); - } - - write_in_full(gpg.in, buf, len); - close(gpg.in); - ret = finish_command(&gpg); + if (size == len) + return error("no signature found"); - unlink_or_warn(path); - - return ret; + return verify_signed_buffer(buf, len, buf + len, size - len, NULL); } static int verify_tag(const char *name, int verbose) diff --git a/bulk-checkin.c b/bulk-checkin.c new file mode 100644 index 0000000000..6b0b6d4904 --- /dev/null +++ b/bulk-checkin.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2011, Google Inc. + */ +#include "bulk-checkin.h" +#include "csum-file.h" +#include "pack.h" + +static int pack_compression_level = Z_DEFAULT_COMPRESSION; + +static struct bulk_checkin_state { + unsigned plugged:1; + + char *pack_tmp_name; + struct sha1file *f; + off_t offset; + struct pack_idx_option pack_idx_opts; + + struct pack_idx_entry **written; + uint32_t alloc_written; + uint32_t nr_written; +} state; + +static void finish_bulk_checkin(struct bulk_checkin_state *state) +{ + unsigned char sha1[20]; + char packname[PATH_MAX]; + int i; + + if (!state->f) + return; + + if (state->nr_written == 0) { + close(state->f->fd); + unlink(state->pack_tmp_name); + goto clear_exit; + } else if (state->nr_written == 1) { + sha1close(state->f, sha1, CSUM_FSYNC); + } else { + int fd = sha1close(state->f, sha1, 0); + fixup_pack_header_footer(fd, sha1, state->pack_tmp_name, + state->nr_written, sha1, + state->offset); + close(fd); + } + + sprintf(packname, "%s/pack/pack-", get_object_directory()); + finish_tmp_packfile(packname, state->pack_tmp_name, + state->written, state->nr_written, + &state->pack_idx_opts, sha1); + for (i = 0; i < state->nr_written; i++) + free(state->written[i]); + +clear_exit: + free(state->written); + memset(state, 0, sizeof(*state)); + + /* Make objects we just wrote available to ourselves */ + reprepare_packed_git(); +} + +static int already_written(struct bulk_checkin_state *state, unsigned char sha1[]) +{ + int i; + + /* The object may already exist in the repository */ + if (has_sha1_file(sha1)) + return 1; + + /* Might want to keep the list sorted */ + for (i = 0; i < state->nr_written; i++) + if (!hashcmp(state->written[i]->sha1, sha1)) + return 1; + + /* This is a new object we need to keep */ + return 0; +} + +/* + * Read the contents from fd for size bytes, streaming it to the + * packfile in state while updating the hash in ctx. Signal a failure + * by returning a negative value when the resulting pack would exceed + * the pack size limit and this is not the first object in the pack, + * so that the caller can discard what we wrote from the current pack + * by truncating it and opening a new one. The caller will then call + * us again after rewinding the input fd. + * + * The already_hashed_to pointer is kept untouched by the caller to + * make sure we do not hash the same byte when we are called + * again. This way, the caller does not have to checkpoint its hash + * status before calling us just in case we ask it to call us again + * with a new pack. + */ +static int stream_to_pack(struct bulk_checkin_state *state, + git_SHA_CTX *ctx, off_t *already_hashed_to, + int fd, size_t size, enum object_type type, + const char *path, unsigned flags) +{ + git_zstream s; + unsigned char obuf[16384]; + unsigned hdrlen; + int status = Z_OK; + int write_object = (flags & HASH_WRITE_OBJECT); + off_t offset = 0; + + memset(&s, 0, sizeof(s)); + git_deflate_init(&s, pack_compression_level); + + hdrlen = encode_in_pack_object_header(type, size, obuf); + s.next_out = obuf + hdrlen; + s.avail_out = sizeof(obuf) - hdrlen; + + while (status != Z_STREAM_END) { + unsigned char ibuf[16384]; + + if (size && !s.avail_in) { + ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf); + if (xread(fd, ibuf, rsize) != rsize) + die("failed to read %d bytes from '%s'", + (int)rsize, path); + offset += rsize; + if (*already_hashed_to < offset) { + size_t hsize = offset - *already_hashed_to; + if (rsize < hsize) + hsize = rsize; + if (hsize) + git_SHA1_Update(ctx, ibuf, hsize); + *already_hashed_to = offset; + } + s.next_in = ibuf; + s.avail_in = rsize; + size -= rsize; + } + + status = git_deflate(&s, size ? 0 : Z_FINISH); + + if (!s.avail_out || status == Z_STREAM_END) { + if (write_object) { + size_t written = s.next_out - obuf; + + /* would we bust the size limit? */ + if (state->nr_written && + pack_size_limit_cfg && + pack_size_limit_cfg < state->offset + written) { + git_deflate_abort(&s); + return -1; + } + + sha1write(state->f, obuf, written); + state->offset += written; + } + s.next_out = obuf; + s.avail_out = sizeof(obuf); + } + + switch (status) { + case Z_OK: + case Z_BUF_ERROR: + case Z_STREAM_END: + continue; + default: + die("unexpected deflate failure: %d", status); + } + } + git_deflate_end(&s); + return 0; +} + +/* Lazily create backing packfile for the state */ +static void prepare_to_stream(struct bulk_checkin_state *state, + unsigned flags) +{ + if (!(flags & HASH_WRITE_OBJECT) || state->f) + return; + + state->f = create_tmp_packfile(&state->pack_tmp_name); + reset_pack_idx_option(&state->pack_idx_opts); + + /* Pretend we are going to write only one object */ + state->offset = write_pack_header(state->f, 1); + if (!state->offset) + die_errno("unable to write pack header"); +} + +static int deflate_to_pack(struct bulk_checkin_state *state, + unsigned char result_sha1[], + int fd, size_t size, + enum object_type type, const char *path, + unsigned flags) +{ + off_t seekback, already_hashed_to; + git_SHA_CTX ctx; + unsigned char obuf[16384]; + unsigned header_len; + struct sha1file_checkpoint checkpoint; + struct pack_idx_entry *idx = NULL; + + seekback = lseek(fd, 0, SEEK_CUR); + if (seekback == (off_t) -1) + return error("cannot find the current offset"); + + header_len = sprintf((char *)obuf, "%s %" PRIuMAX, + typename(type), (uintmax_t)size) + 1; + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, obuf, header_len); + + /* Note: idx is non-NULL when we are writing */ + if ((flags & HASH_WRITE_OBJECT) != 0) + idx = xcalloc(1, sizeof(*idx)); + + already_hashed_to = 0; + + while (1) { + prepare_to_stream(state, flags); + if (idx) { + sha1file_checkpoint(state->f, &checkpoint); + idx->offset = state->offset; + crc32_begin(state->f); + } + if (!stream_to_pack(state, &ctx, &already_hashed_to, + fd, size, type, path, flags)) + break; + /* + * Writing this object to the current pack will make + * it too big; we need to truncate it, start a new + * pack, and write into it. + */ + if (!idx) + die("BUG: should not happen"); + sha1file_truncate(state->f, &checkpoint); + state->offset = checkpoint.offset; + finish_bulk_checkin(state); + if (lseek(fd, seekback, SEEK_SET) == (off_t) -1) + return error("cannot seek back"); + } + git_SHA1_Final(result_sha1, &ctx); + if (!idx) + return 0; + + idx->crc32 = crc32_end(state->f); + if (already_written(state, result_sha1)) { + sha1file_truncate(state->f, &checkpoint); + state->offset = checkpoint.offset; + free(idx); + } else { + hashcpy(idx->sha1, result_sha1); + ALLOC_GROW(state->written, + state->nr_written + 1, + state->alloc_written); + state->written[state->nr_written++] = idx; + } + return 0; +} + +int index_bulk_checkin(unsigned char *sha1, + int fd, size_t size, enum object_type type, + const char *path, unsigned flags) +{ + int status = deflate_to_pack(&state, sha1, fd, size, type, + path, flags); + if (!state.plugged) + finish_bulk_checkin(&state); + return status; +} + +void plug_bulk_checkin(void) +{ + state.plugged = 1; +} + +void unplug_bulk_checkin(void) +{ + state.plugged = 0; + if (state.f) + finish_bulk_checkin(&state); +} diff --git a/bulk-checkin.h b/bulk-checkin.h new file mode 100644 index 0000000000..4f599f8841 --- /dev/null +++ b/bulk-checkin.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2011, Google Inc. + */ +#ifndef BULK_CHECKIN_H +#define BULK_CHECKIN_H + +#include "cache.h" + +extern int index_bulk_checkin(unsigned char sha1[], + int fd, size_t size, enum object_type type, + const char *path, unsigned flags); + +extern void plug_bulk_checkin(void); +extern void unplug_bulk_checkin(void); + +#endif @@ -23,50 +23,102 @@ static void add_to_ref_list(const unsigned char *sha1, const char *name, list->nr++; } -/* returns an fd */ -int read_bundle_header(const char *path, struct bundle_header *header) +/* Eventually this should go to strbuf.[ch] */ +static int strbuf_readline_fd(struct strbuf *sb, int fd) { - char buffer[1024]; - int fd; - long fpos; - FILE *ffd = fopen(path, "rb"); + strbuf_reset(sb); + + while (1) { + char ch; + ssize_t len = xread(fd, &ch, 1); + if (len < 0) + return -1; + strbuf_addch(sb, ch); + if (ch == '\n') + break; + } + return 0; +} - if (!ffd) - return error("could not open '%s'", path); - if (!fgets(buffer, sizeof(buffer), ffd) || - strcmp(buffer, bundle_signature)) { - fclose(ffd); - return error("'%s' does not look like a v2 bundle file", path); +static int parse_bundle_header(int fd, struct bundle_header *header, + const char *report_path) +{ + struct strbuf buf = STRBUF_INIT; + int status = 0; + + /* The bundle header begins with the signature */ + if (strbuf_readline_fd(&buf, fd) || + strcmp(buf.buf, bundle_signature)) { + if (report_path) + error("'%s' does not look like a v2 bundle file", + report_path); + status = -1; + goto abort; } - while (fgets(buffer, sizeof(buffer), ffd) - && buffer[0] != '\n') { - int is_prereq = buffer[0] == '-'; - int offset = is_prereq ? 1 : 0; - int len = strlen(buffer); + + /* The bundle header ends with an empty line */ + while (!strbuf_readline_fd(&buf, fd) && + buf.len && buf.buf[0] != '\n') { unsigned char sha1[20]; - struct ref_list *list = is_prereq ? &header->prerequisites - : &header->references; - char delim; - - if (len && buffer[len - 1] == '\n') - buffer[len - 1] = '\0'; - if (get_sha1_hex(buffer + offset, sha1)) { - warning("unrecognized header: %s", buffer); - continue; + int is_prereq = 0; + + if (*buf.buf == '-') { + is_prereq = 1; + strbuf_remove(&buf, 0, 1); + } + strbuf_rtrim(&buf); + + /* + * Tip lines have object name, SP, and refname. + * Prerequisites have object name that is optionally + * followed by SP and subject line. + */ + if (get_sha1_hex(buf.buf, sha1) || + (40 <= buf.len && !isspace(buf.buf[40])) || + (!is_prereq && buf.len <= 40)) { + if (report_path) + error("unrecognized header: %s%s (%d)", + (is_prereq ? "-" : ""), buf.buf, (int)buf.len); + status = -1; + break; + } else { + if (is_prereq) + add_to_ref_list(sha1, "", &header->prerequisites); + else + add_to_ref_list(sha1, buf.buf + 41, &header->references); } - delim = buffer[40 + offset]; - if (!isspace(delim) && (delim != '\0' || !is_prereq)) - die ("invalid header: %s", buffer); - add_to_ref_list(sha1, isspace(delim) ? - buffer + 41 + offset : "", list); } - fpos = ftell(ffd); - fclose(ffd); - fd = open(path, O_RDONLY); + + abort: + if (status) { + close(fd); + fd = -1; + } + strbuf_release(&buf); + return fd; +} + +int read_bundle_header(const char *path, struct bundle_header *header) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) return error("could not open '%s'", path); - lseek(fd, fpos, SEEK_SET); - return fd; + return parse_bundle_header(fd, header, path); +} + +int is_bundle(const char *path, int quiet) +{ + struct bundle_header header; + int fd = open(path, O_RDONLY); + + if (fd < 0) + return 0; + memset(&header, 0, sizeof(header)); + fd = parse_bundle_header(fd, &header, quiet ? NULL : path); + if (fd >= 0) + close(fd); + return (fd >= 0); } static int list_refs(struct ref_list *r, int argc, const char **argv) @@ -122,11 +174,8 @@ int verify_bundle(struct bundle_header *header, int verbose) req_nr = revs.pending.nr; setup_revisions(2, argv, &revs, NULL); - memset(&refs, 0, sizeof(struct object_array)); - for (i = 0; i < revs.pending.nr; i++) { - struct object_array_entry *e = revs.pending.objects + i; - add_object_array(e->item, e->name, &refs); - } + refs = revs.pending; + revs.leak_pending = 1; if (prepare_revision_walk(&revs)) die("revision walk setup failed"); @@ -144,8 +193,8 @@ int verify_bundle(struct bundle_header *header, int verbose) refs.objects[i].name); } - for (i = 0; i < refs.nr; i++) - clear_commit_marks((struct commit *)refs.objects[i].item, -1); + clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS); + free(refs.objects); if (verbose) { struct ref_list *r; @@ -271,7 +320,7 @@ int create_bundle(struct bundle_header *header, const char *path, continue; if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) continue; - if (!resolve_ref(e->name, sha1, 1, &flag)) + if (read_ref_full(e->name, sha1, 1, &flag)) flag = 0; display_ref = (flag & REF_ISSYMREF) ? e->name : ref; @@ -380,12 +429,15 @@ int create_bundle(struct bundle_header *header, const char *path, return 0; } -int unbundle(struct bundle_header *header, int bundle_fd) +int unbundle(struct bundle_header *header, int bundle_fd, int flags) { const char *argv_index_pack[] = {"index-pack", - "--fix-thin", "--stdin", NULL}; + "--fix-thin", "--stdin", NULL, NULL}; struct child_process ip; + if (flags & BUNDLE_VERBOSE) + argv_index_pack[3] = "-v"; + if (verify_bundle(header, 0)) return -1; memset(&ip, 0, sizeof(ip)); @@ -14,11 +14,13 @@ struct bundle_header { struct ref_list references; }; +int is_bundle(const char *path, int quiet); int read_bundle_header(const char *path, struct bundle_header *header); int create_bundle(struct bundle_header *header, const char *path, int argc, const char **argv); int verify_bundle(struct bundle_header *header, int verbose); -int unbundle(struct bundle_header *header, int bundle_fd); +#define BUNDLE_VERBOSE 1 +int unbundle(struct bundle_header *header, int bundle_fd, int flags); int list_bundle_refs(struct bundle_header *header, int argc, const char **argv); diff --git a/cache-tree.c b/cache-tree.c index f755590da8..8de39590d5 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -150,7 +150,7 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path) } static int verify_cache(struct cache_entry **cache, - int entries) + int entries, int silent) { int i, funny; @@ -159,6 +159,8 @@ static int verify_cache(struct cache_entry **cache, for (i = 0; i < entries; i++) { struct cache_entry *ce = cache[i]; if (ce_stage(ce) || (ce->ce_flags & CE_INTENT_TO_ADD)) { + if (silent) + return -1; if (10 < ++funny) { fprintf(stderr, "...\n"); break; @@ -370,10 +372,11 @@ int cache_tree_update(struct cache_tree *it, struct cache_entry **cache, int entries, int missing_ok, - int dryrun) + int dryrun, + int silent) { int i; - i = verify_cache(cache, entries); + i = verify_cache(cache, entries, silent); if (i) return i; i = update_one(it, cache, entries, "", 0, missing_ok, dryrun); @@ -573,7 +576,7 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) if (cache_tree_update(active_cache_tree, active_cache, active_nr, - missing_ok, 0) < 0) + missing_ok, 0, 0) < 0) return WRITE_TREE_UNMERGED_INDEX; if (0 <= newfd) { if (!write_cache(newfd, active_cache, active_nr) && @@ -668,3 +671,11 @@ int cache_tree_matches_traversal(struct cache_tree *root, return it->entry_count; return 0; } + +int update_main_cache_tree (int silent) +{ + if (!the_index.cache_tree) + the_index.cache_tree = cache_tree(); + return cache_tree_update(the_index.cache_tree, + the_index.cache, the_index.cache_nr, 0, 0, silent); +} diff --git a/cache-tree.h b/cache-tree.h index 3df641f593..0ec0b2a159 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -29,7 +29,9 @@ void cache_tree_write(struct strbuf *, struct cache_tree *root); struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); int cache_tree_fully_valid(struct cache_tree *); -int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int); +int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int, int); + +int update_main_cache_tree(int); /* bitmasks to write_cache_as_tree flags */ #define WRITE_TREE_MISSING_OK 1 @@ -35,6 +35,7 @@ int git_inflate(git_zstream *, int flush); void git_deflate_init(git_zstream *, int level); void git_deflate_init_gzip(git_zstream *, int level); void git_deflate_end(git_zstream *); +int git_deflate_abort(git_zstream *); int git_deflate_end_gently(git_zstream *); int git_deflate(git_zstream *, int flush); unsigned long git_deflate_bound(git_zstream *, unsigned long); @@ -168,6 +169,7 @@ struct cache_entry { unsigned int ce_flags; unsigned char sha1[20]; struct cache_entry *next; + struct cache_entry *dir_next; char name[FLEX_ARRAY]; /* more */ }; @@ -305,7 +307,7 @@ static inline unsigned int canon_mode(unsigned int mode) } #define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7) -#define cache_entry_size(len) flexible_size(cache_entry,len) +#define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1) #define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len) #define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len) @@ -315,7 +317,6 @@ struct index_state { struct string_list *resolve_undo; struct cache_tree *cache_tree; struct cache_time timestamp; - void *alloc; unsigned name_hash_initialized : 1, initialized : 1; struct hash_table name_hash; @@ -439,12 +440,12 @@ extern const char *get_git_namespace(void); extern const char *strip_namespace(const char *namespaced_ref); extern const char *get_git_work_tree(void); extern const char *read_gitfile(const char *path); +extern const char *resolve_gitdir(const char *suspect); extern void set_git_work_tree(const char *tree); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" extern const char **get_pathspec(const char *prefix, const char **pathspec); -extern const char *pathspec_prefix(const char *prefix, const char **pathspec); extern void setup_work_tree(void); extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); @@ -589,6 +590,7 @@ extern int warn_ambiguous_refs; extern int shared_repository; extern const char *apply_default_whitespace; extern const char *apply_default_ignorewhitespace; +extern const char *git_attributes_file; extern int zlib_compression_level; extern int core_compression_level; extern int core_compression_seen; @@ -596,6 +598,7 @@ extern size_t packed_git_window_size; extern size_t packed_git_limit; extern size_t delta_base_cache_limit; extern unsigned long big_file_threshold; +extern unsigned long pack_size_limit_cfg; extern int read_replace_refs; extern int fsync_object_files; extern int core_preload_index; @@ -734,7 +737,7 @@ int safe_create_leading_directories(char *path); int safe_create_leading_directories_const(const char *path); int mkdir_in_gitdir(const char *path); extern char *expand_user_path(const char *path); -char *enter_repo(char *path, int strict); +const char *enter_repo(const char *path, int strict); static inline int is_absolute_path(const char *path) { return is_dir_sep(path[0]) || has_dos_drive_prefix(path); @@ -819,10 +822,54 @@ static inline int get_sha1_with_context(const char *str, unsigned char *sha1, st { return get_sha1_with_context_1(str, sha1, orc, 0, NULL); } + +/* + * Try to read a SHA1 in hexadecimal format from the 40 characters + * starting at hex. Write the 20-byte result to sha1 in binary form. + * Return 0 on success. Reading stops if a NUL is encountered in the + * input, so it is safe to pass this function an arbitrary + * null-terminated string. + */ extern int get_sha1_hex(const char *hex, unsigned char *sha1); + extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ -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 read_ref_full(const char *refname, unsigned char *sha1, + int reading, int *flags); +extern int read_ref(const char *refname, unsigned char *sha1); + +/* + * Resolve a reference, recursively following symbolic refererences. + * + * Store the referred-to object's name in sha1 and return the name of + * the non-symbolic reference that ultimately pointed at it. The + * return value, if not NULL, is a pointer into either a static buffer + * or the input ref. + * + * If the reference cannot be resolved to an object, the behavior + * depends on the "reading" argument: + * + * - If reading is set, return NULL. + * + * - If reading is not set, clear sha1 and return the name of the last + * reference name in the chain, which will either be a non-symbolic + * reference or an undefined reference. If this is a prelude to + * "writing" to the ref, the return value is the name of the ref + * that will actually be created or changed. + * + * If flag is non-NULL, set the value that it points to the + * combination of REF_ISPACKED (if the reference was found among the + * packed references) and REF_ISSYMREF (if the initial reference was a + * symbolic reference). + * + * If ref is not a properly-formatted, normalized reference, return + * NULL. If more than MAXDEPTH recursive symbolic lookups are needed, + * give up and return NULL. + * + * errno is sometimes set on errors, but not always. + */ +extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag); +extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag); + 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_branch_name(const char *str, struct strbuf *); @@ -830,7 +877,7 @@ extern int get_sha1_mb(const char *str, unsigned char *sha1); extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); extern const char *ref_rev_parse_rules[]; -extern const char *ref_fetch_rules[]; +#define ref_fetch_rules ref_rev_parse_rules extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg); extern int validate_headref(const char *ref); @@ -981,16 +1028,14 @@ struct ref { extern struct ref *find_ref_by_name(const struct ref *list, const char *name); #define CONNECT_VERBOSE (1u << 0) -extern char *git_getpass(const char *prompt); extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); extern int finish_connect(struct child_process *conn); extern int git_connection_is_socket(struct child_process *conn); -extern int path_match(const char *path, int nr, char **match); struct extra_have_objects { int nr, alloc; unsigned char (*array)[20]; }; -extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *); +extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *); extern int server_supports(const char *feature); extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); @@ -1014,6 +1059,7 @@ extern struct packed_git *add_packed_git(const char *, int, int); extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t); extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t); extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); +extern int is_pack_valid(struct packed_git *); extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *); extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); @@ -1076,9 +1122,11 @@ extern int git_config_bool(const char *, const char *); extern int git_config_maybe_bool(const char *, const char *); extern int git_config_string(const char **, const char *, const char *); extern int git_config_pathname(const char **, const char *, const char *); +extern int git_config_set_in_file(const char *, const char *, const char *); extern int git_config_set(const char *, const char *); extern int git_config_parse_key(const char *, char **, int *); extern int git_config_set_multivar(const char *, const char *, const char *, int); +extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); extern int git_config_rename_section(const char *, const char *); extern const char *git_etc_gitconfig(void); extern int check_repository_format_version(const char *var, const char *value, void *cb); diff --git a/command-list.txt b/command-list.txt index 95bf18cf06..a36ee9b015 100644 --- a/command-list.txt +++ b/command-list.txt @@ -130,5 +130,6 @@ git-upload-pack synchelpers git-var plumbinginterrogators git-verify-pack plumbinginterrogators git-verify-tag ancillaryinterrogators +gitweb ancillaryinterrogators git-whatchanged ancillaryinterrogators git-write-tree plumbingmanipulators @@ -39,6 +39,18 @@ struct commit *lookup_commit_reference(const unsigned char *sha1) return lookup_commit_reference_gently(sha1, 0); } +struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name) +{ + struct commit *c = lookup_commit_reference(sha1); + if (!c) + die(_("could not parse %s"), ref_name); + if (hashcmp(sha1, c->object.sha1)) { + warning(_("%s %s is not a commit!"), + ref_name, sha1_to_hex(sha1)); + } + return c; +} + struct commit *lookup_commit(const unsigned char *sha1) { struct object *obj = lookup_object(sha1); @@ -430,6 +442,20 @@ void clear_commit_marks(struct commit *commit, unsigned int mark) } } +void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark) +{ + struct object *object; + struct commit *commit; + unsigned int i; + + for (i = 0; i < a->nr; i++) { + object = a->objects[i].item; + commit = lookup_commit_reference_gently(object->sha1, 1); + if (commit) + clear_commit_marks(commit, mark); + } +} + struct commit *pop_commit(struct commit_list **stack) { struct commit_list *top = *stack; @@ -814,14 +840,160 @@ struct commit_list *reduce_heads(struct commit_list *heads) return result; } +static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail) +{ + struct merge_remote_desc *desc; + struct commit_extra_header *mergetag; + char *buf; + unsigned long size, len; + enum object_type type; + + desc = merge_remote_util(parent); + if (!desc || !desc->obj) + return; + buf = read_sha1_file(desc->obj->sha1, &type, &size); + if (!buf || type != OBJ_TAG) + goto free_return; + len = parse_signature(buf, size); + if (size == len) + goto free_return; + /* + * We could verify this signature and either omit the tag when + * it does not validate, but the integrator may not have the + * public key of the signer of the tag he is merging, while a + * later auditor may have it while auditing, so let's not run + * verify-signed-buffer here for now... + * + * if (verify_signed_buffer(buf, len, buf + len, size - len, ...)) + * warn("warning: signed tag unverified."); + */ + mergetag = xcalloc(1, sizeof(*mergetag)); + mergetag->key = xstrdup("mergetag"); + mergetag->value = buf; + mergetag->len = size; + + **tail = mergetag; + *tail = &mergetag->next; + return; + +free_return: + free(buf); +} + +void append_merge_tag_headers(struct commit_list *parents, + struct commit_extra_header ***tail) +{ + while (parents) { + struct commit *parent = parents->item; + handle_signed_tag(parent, tail); + parents = parents->next; + } +} + +static void add_extra_header(struct strbuf *buffer, + struct commit_extra_header *extra) +{ + strbuf_addstr(buffer, extra->key); + if (extra->len) + strbuf_add_lines(buffer, " ", extra->value, extra->len); + else + strbuf_addch(buffer, '\n'); +} + +struct commit_extra_header *read_commit_extra_headers(struct commit *commit) +{ + struct commit_extra_header *extra = NULL; + unsigned long size; + enum object_type type; + char *buffer = read_sha1_file(commit->object.sha1, &type, &size); + if (buffer && type == OBJ_COMMIT) + extra = read_commit_extra_header_lines(buffer, size); + free(buffer); + return extra; +} + +static inline int standard_header_field(const char *field, size_t len) +{ + return ((len == 4 && !memcmp(field, "tree ", 5)) || + (len == 6 && !memcmp(field, "parent ", 7)) || + (len == 6 && !memcmp(field, "author ", 7)) || + (len == 9 && !memcmp(field, "committer ", 10)) || + (len == 8 && !memcmp(field, "encoding ", 9))); +} + +struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size) +{ + struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL; + const char *line, *next, *eof, *eob; + struct strbuf buf = STRBUF_INIT; + + for (line = buffer, eob = line + size; + line < eob && *line != '\n'; + line = next) { + next = memchr(line, '\n', eob - line); + next = next ? next + 1 : eob; + if (*line == ' ') { + /* continuation */ + if (it) + strbuf_add(&buf, line + 1, next - (line + 1)); + continue; + } + if (it) + it->value = strbuf_detach(&buf, &it->len); + strbuf_reset(&buf); + it = NULL; + + eof = strchr(line, ' '); + if (next <= eof) + eof = next; + + if (standard_header_field(line, eof - line)) + continue; + + it = xcalloc(1, sizeof(*it)); + it->key = xmemdupz(line, eof-line); + *tail = it; + tail = &it->next; + if (eof + 1 < next) + strbuf_add(&buf, eof + 1, next - (eof + 1)); + } + if (it) + it->value = strbuf_detach(&buf, &it->len); + return extra; +} + +void free_commit_extra_headers(struct commit_extra_header *extra) +{ + while (extra) { + struct commit_extra_header *next = extra->next; + free(extra->key); + free(extra->value); + free(extra); + extra = next; + } +} + +int commit_tree(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author) +{ + struct commit_extra_header *extra = NULL, **tail = &extra; + int result; + + append_merge_tag_headers(parents, &tail); + result = commit_tree_extended(msg, tree, parents, ret, author, extra); + free_commit_extra_headers(extra); + return result; +} + static const char commit_utf8_warn[] = "Warning: commit message does not conform to UTF-8.\n" "You may want to amend it after fixing the message, or set the config\n" "variable i18n.commitencoding to the encoding your project uses.\n"; -int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author) +int commit_tree_extended(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author, struct commit_extra_header *extra) { int result; int encoding_is_utf8; @@ -842,8 +1014,10 @@ int commit_tree(const char *msg, unsigned char *tree, */ while (parents) { struct commit_list *next = parents->next; + struct commit *parent = parents->item; + strbuf_addf(&buffer, "parent %s\n", - sha1_to_hex(parents->item->object.sha1)); + sha1_to_hex(parent->object.sha1)); free(parents); parents = next; } @@ -855,6 +1029,11 @@ int commit_tree(const char *msg, unsigned char *tree, strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); if (!encoding_is_utf8) strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding); + + while (extra) { + add_extra_header(&buffer, extra); + extra = extra->next; + } strbuf_addch(&buffer, '\n'); /* And add the comment */ @@ -868,3 +1047,22 @@ int commit_tree(const char *msg, unsigned char *tree, strbuf_release(&buffer); return result; } + +struct commit *get_merge_parent(const char *name) +{ + struct object *obj; + struct commit *commit; + unsigned char sha1[20]; + if (get_sha1(name, sha1)) + return NULL; + obj = parse_object(sha1); + commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); + if (commit && !commit->util) { + struct merge_remote_desc *desc; + desc = xmalloc(sizeof(*desc)); + desc->obj = obj; + desc->name = strdup(name); + commit->util = desc; + } + return commit; +} @@ -38,6 +38,13 @@ struct commit *lookup_commit_reference_gently(const unsigned char *sha1, int quiet); struct commit *lookup_commit_reference_by_name(const char *name); +/* + * Look up object named by "sha1", dereference tag as necessary, + * get a commit and return it. If "sha1" does not dereference to + * a commit, use ref_name to report an error and die. + */ +struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name); + int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size); int parse_commit(struct commit *item); @@ -126,6 +133,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list, struct commit *pop_commit(struct commit_list **stack); void clear_commit_marks(struct commit *commit, unsigned int mark); +void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark); /* * Performs an in-place topological sort of list supplied. @@ -173,8 +181,41 @@ static inline int single_parent(struct commit *commit) struct commit_list *reduce_heads(struct commit_list *heads); +struct commit_extra_header { + struct commit_extra_header *next; + char *key; + char *value; + size_t len; +}; + +extern void append_merge_tag_headers(struct commit_list *parents, + struct commit_extra_header ***tail); + extern int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author); + struct commit_list *parents, unsigned char *ret, + const char *author); + +extern int commit_tree_extended(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author, + struct commit_extra_header *); + +extern struct commit_extra_header *read_commit_extra_headers(struct commit *); +extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len); + +extern void free_commit_extra_headers(struct commit_extra_header *extra); + +struct merge_remote_desc { + struct object *obj; /* the named object, could be a tag */ + const char *name; +}; +#define merge_remote_util(commit) ((struct merge_remote_desc *)((commit)->util)) + +/* + * Given "name" from the command line to merge, find the commit object + * and return it, while storing merge_remote_desc in its ->util field, + * to allow callers to tell if we are told to merge a tag. + */ +struct commit *get_merge_parent(const char *name); #endif /* COMMIT_H */ diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c index ea249c6ac6..60b5a1d0f8 100644 --- a/compat/inet_ntop.c +++ b/compat/inet_ntop.c @@ -98,7 +98,9 @@ inet_ntop6(const u_char *src, char *dst, size_t size) for (i = 0; i < NS_IN6ADDRSZ; i++) words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); best.base = -1; + best.len = 0; cur.base = -1; + cur.len = 0; for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { if (words[i] == 0) { if (cur.base == -1) diff --git a/compat/mingw.c b/compat/mingw.c index 6ef0cc4f99..a0ac487c0c 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1183,7 +1183,7 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service, } ai->ai_addrlen = sizeof(struct sockaddr_in); if (hints && (hints->ai_flags & AI_CANONNAME)) - ai->ai_canonname = h ? strdup(h->h_name) : NULL; + ai->ai_canonname = h ? xstrdup(h->h_name) : NULL; else ai->ai_canonname = NULL; @@ -1321,6 +1321,13 @@ static void ensure_socket_initialization(void) initialized = 1; } +#undef gethostname +int mingw_gethostname(char *name, int namelen) +{ + ensure_socket_initialization(); + return gethostname(name, namelen); +} + #undef gethostbyname struct hostent *mingw_gethostbyname(const char *host) { @@ -1705,7 +1712,7 @@ char *getpass(const char *prompt) return strbuf_detach(&buf, NULL); } -pid_t waitpid(pid_t pid, int *status, unsigned options) +pid_t waitpid(pid_t pid, int *status, int options) { HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, pid); diff --git a/compat/mingw.h b/compat/mingw.h index ce9dd980eb..0ff1e04812 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -120,7 +120,7 @@ static inline int mingw_mkdir(const char *path, int mode) #define mkdir mingw_mkdir #define WNOHANG 1 -pid_t waitpid(pid_t pid, int *status, unsigned options); +pid_t waitpid(pid_t pid, int *status, int options); #define kill mingw_kill int mingw_kill(pid_t pid, int sig); @@ -190,6 +190,9 @@ char *mingw_getcwd(char *pointer, int len); char *mingw_getenv(const char *name); #define getenv mingw_getenv +int mingw_gethostname(char *host, int namelen); +#define gethostname mingw_gethostname + struct hostent *mingw_gethostbyname(const char *host); #define gethostbyname mingw_gethostbyname diff --git a/compat/msvc.h b/compat/msvc.h index a33b01c032..aa4b56315a 100644 --- a/compat/msvc.h +++ b/compat/msvc.h @@ -4,6 +4,7 @@ #include <direct.h> #include <process.h> #include <malloc.h> +#include <io.h> /* porting function */ #define inline __inline diff --git a/compat/obstack.c b/compat/obstack.c index a89ab5b8e8..e276ccd7b3 100644 --- a/compat/obstack.c +++ b/compat/obstack.c @@ -173,7 +173,7 @@ _obstack_begin (struct obstack *h, alignment - 1); h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size; - chunk->prev = 0; + chunk->prev = NULL; /* The initial chunk now contains no empty object. */ h->maybe_empty_object = 0; h->alloc_failed = 0; @@ -221,7 +221,7 @@ _obstack_begin_1 (struct obstack *h, int size, int alignment, alignment - 1); h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size; - chunk->prev = 0; + chunk->prev = NULL; /* The initial chunk now contains no empty object. */ h->maybe_empty_object = 0; h->alloc_failed = 0; @@ -321,12 +321,12 @@ _obstack_allocated_p (struct obstack *h, void *obj) /* We use >= rather than > since the object cannot be exactly at the beginning of the chunk but might be an empty object exactly at the end of an adjacent chunk. */ - while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj)) + while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj)) { plp = lp->prev; lp = plp; } - return lp != 0; + return lp != NULL; } /* Free objects in obstack H, including OBJ and everything allocate @@ -344,7 +344,7 @@ obstack_free (struct obstack *h, void *obj) /* We use >= because there cannot be an object at the beginning of a chunk. But there can be an empty object at that address at the end of another chunk. */ - while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj)) + while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj)) { plp = lp->prev; CALL_FREEFUN (h, lp); @@ -359,7 +359,7 @@ obstack_free (struct obstack *h, void *obj) h->chunk_limit = lp->limit; h->chunk = lp; } - else if (obj != 0) + else if (obj != NULL) /* obj is not in any of the chunks! */ abort (); } @@ -376,7 +376,7 @@ _obstack_memory_used (struct obstack *h) register struct _obstack_chunk* lp; register int nbytes = 0; - for (lp = h->chunk; lp != 0; lp = lp->prev) + for (lp = h->chunk; lp != NULL; lp = lp->prev) { nbytes += lp->limit - (char *) lp; } @@ -395,7 +395,6 @@ _obstack_memory_used (struct obstack *h) # endif static void -__attribute__ ((noreturn)) print_and_abort (void) { /* Don't change any of these strings. Yes, it would be possible to add diff --git a/compat/qsort.c b/compat/qsort.c index d93dce2cf8..9574d537bd 100644 --- a/compat/qsort.c +++ b/compat/qsort.c @@ -55,7 +55,7 @@ void git_qsort(void *b, size_t n, size_t s, msort_with_tmp(b, n, s, cmp, buf); } else { /* It's somewhat large, so malloc it. */ - char *tmp = malloc(size); + char *tmp = xmalloc(size); msort_with_tmp(b, n, s, cmp, tmp); free(tmp); } diff --git a/compat/setenv.c b/compat/setenv.c index 3a22ea7b75..fc1439a643 100644 --- a/compat/setenv.c +++ b/compat/setenv.c @@ -6,7 +6,10 @@ int gitsetenv(const char *name, const char *value, int replace) size_t namelen, valuelen; char *envstr; - if (!name || !value) return -1; + if (!name || strchr(name, '=') || !value) { + errno = EINVAL; + return -1; + } if (!replace) { char *oldval = NULL; oldval = getenv(name); @@ -16,7 +19,10 @@ int gitsetenv(const char *name, const char *value, int replace) namelen = strlen(name); valuelen = strlen(value); envstr = malloc((namelen + valuelen + 2)); - if (!envstr) return -1; + if (!envstr) { + errno = ENOMEM; + return -1; + } memcpy(envstr, name, namelen); envstr[namelen] = '='; diff --git a/compat/snprintf.c b/compat/snprintf.c index e1e0e7543d..42ea1ac110 100644 --- a/compat/snprintf.c +++ b/compat/snprintf.c @@ -19,11 +19,14 @@ #undef vsnprintf int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap) { + va_list cp; char *s; int ret = -1; if (maxsize > 0) { - ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap); + va_copy(cp, ap); + ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, cp); + va_end(cp); if (ret == maxsize-1) ret = -1; /* Windows does not NUL-terminate if result fills buffer */ @@ -42,7 +45,9 @@ int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap) if (! str) break; s = str; - ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap); + va_copy(cp, ap); + ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, cp); + va_end(cp); if (ret == maxsize-1) ret = -1; } diff --git a/compat/strtoimax.c b/compat/strtoimax.c new file mode 100644 index 0000000000..ac09ed89e7 --- /dev/null +++ b/compat/strtoimax.c @@ -0,0 +1,10 @@ +#include "../git-compat-util.h" + +intmax_t gitstrtoimax (const char *nptr, char **endptr, int base) +{ +#if defined(NO_STRTOULL) + return strtol(nptr, endptr, base); +#else + return strtoll(nptr, endptr, base); +#endif +} diff --git a/compat/terminal.c b/compat/terminal.c new file mode 100644 index 0000000000..6d16c8fba0 --- /dev/null +++ b/compat/terminal.c @@ -0,0 +1,81 @@ +#include "git-compat-util.h" +#include "compat/terminal.h" +#include "sigchain.h" +#include "strbuf.h" + +#ifdef HAVE_DEV_TTY + +static int term_fd = -1; +static struct termios old_term; + +static void restore_term(void) +{ + if (term_fd < 0) + return; + + tcsetattr(term_fd, TCSAFLUSH, &old_term); + term_fd = -1; +} + +static void restore_term_on_signal(int sig) +{ + restore_term(); + sigchain_pop(sig); + raise(sig); +} + +char *git_terminal_prompt(const char *prompt, int echo) +{ + static struct strbuf buf = STRBUF_INIT; + int r; + FILE *fh; + + fh = fopen("/dev/tty", "w+"); + if (!fh) + return NULL; + + if (!echo) { + struct termios t; + + if (tcgetattr(fileno(fh), &t) < 0) { + fclose(fh); + return NULL; + } + + old_term = t; + term_fd = fileno(fh); + sigchain_push_common(restore_term_on_signal); + + t.c_lflag &= ~ECHO; + if (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) { + term_fd = -1; + fclose(fh); + return NULL; + } + } + + fputs(prompt, fh); + fflush(fh); + + r = strbuf_getline(&buf, fh, '\n'); + if (!echo) { + putc('\n', fh); + fflush(fh); + } + + restore_term(); + fclose(fh); + + if (r == EOF) + return NULL; + return buf.buf; +} + +#else + +char *git_terminal_prompt(const char *prompt, int echo) +{ + return getpass(prompt); +} + +#endif diff --git a/compat/terminal.h b/compat/terminal.h new file mode 100644 index 0000000000..97db7cd69d --- /dev/null +++ b/compat/terminal.h @@ -0,0 +1,6 @@ +#ifndef COMPAT_TERMINAL_H +#define COMPAT_TERMINAL_H + +char *git_terminal_prompt(const char *prompt, int echo); + +#endif /* COMPAT_TERMINAL_H */ diff --git a/compat/vcbuild/include/arpa/inet.h b/compat/vcbuild/include/arpa/inet.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/arpa/inet.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/grp.h b/compat/vcbuild/include/grp.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/grp.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/inttypes.h b/compat/vcbuild/include/inttypes.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/inttypes.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/netdb.h b/compat/vcbuild/include/netdb.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/netdb.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/netinet/in.h b/compat/vcbuild/include/netinet/in.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/netinet/in.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/netinet/tcp.h b/compat/vcbuild/include/netinet/tcp.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/netinet/tcp.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/pwd.h b/compat/vcbuild/include/pwd.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/pwd.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/ioctl.h b/compat/vcbuild/include/sys/ioctl.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/sys/ioctl.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/select.h b/compat/vcbuild/include/sys/select.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/sys/select.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/socket.h b/compat/vcbuild/include/sys/socket.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/sys/socket.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/wait.h b/compat/vcbuild/include/sys/wait.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/sys/wait.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/termios.h b/compat/vcbuild/include/termios.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/termios.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/win32/sys/poll.c b/compat/win32/poll.c index 708a6c9bec..403eaa7a3c 100644 --- a/compat/win32/sys/poll.c +++ b/compat/win32/poll.c @@ -1,7 +1,7 @@ /* Emulation for poll(2) Contributed by Paolo Bonzini. - Copyright 2001-2003, 2006-2010 Free Software Foundation, Inc. + Copyright 2001-2003, 2006-2011 Free Software Foundation, Inc. This file is part of gnulib. @@ -27,7 +27,10 @@ #include <malloc.h> #include <sys/types.h> -#include "poll.h" + +/* Specification. */ +#include <poll.h> + #include <errno.h> #include <limits.h> #include <assert.h> @@ -314,10 +317,7 @@ compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds) #endif /* !MinGW */ int -poll (pfd, nfd, timeout) - struct pollfd *pfd; - nfds_t nfd; - int timeout; +poll (struct pollfd *pfd, nfds_t nfd, int timeout) { #ifndef WIN32_NATIVE fd_set rfds, wfds, efds; @@ -454,6 +454,7 @@ poll (pfd, nfd, timeout) if (!hEvent) hEvent = CreateEvent (NULL, FALSE, FALSE, NULL); +restart: handle_array[0] = hEvent; nhandles = 1; FD_ZERO (&rfds); @@ -594,6 +595,12 @@ poll (pfd, nfd, timeout) rc++; } + if (!rc && timeout == INFTIM) + { + SwitchToThread(); + goto restart; + } + return rc; #endif } diff --git a/compat/win32/sys/poll.h b/compat/win32/poll.h index b7aa59d973..b7aa59d973 100644 --- a/compat/win32/sys/poll.h +++ b/compat/win32/poll.h diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c index 42b95a9b51..d015e436d5 100644 --- a/compat/win32/syslog.c +++ b/compat/win32/syslog.c @@ -1,5 +1,4 @@ #include "../../git-compat-util.h" -#include "../../strbuf.h" static HANDLE ms_eventlog; @@ -16,13 +15,8 @@ void openlog(const char *ident, int logopt, int facility) void syslog(int priority, const char *fmt, ...) { - struct strbuf sb = STRBUF_INIT; - struct strbuf_expand_dict_entry dict[] = { - {"1", "% 1"}, - {NULL, NULL} - }; WORD logtype; - char *str; + char *str, *pos; int str_len; va_list ap; @@ -39,11 +33,24 @@ void syslog(int priority, const char *fmt, ...) } str = malloc(str_len + 1); + if (!str) { + warning("malloc failed: '%s'", strerror(errno)); + return; + } + va_start(ap, fmt); vsnprintf(str, str_len + 1, fmt, ap); va_end(ap); - strbuf_expand(&sb, str, strbuf_expand_dict_cb, &dict); - free(str); + + while ((pos = strstr(str, "%1")) != NULL) { + str = realloc(str, ++str_len + 1); + if (!str) { + warning("realloc failed: '%s'", strerror(errno)); + return; + } + memmove(pos + 2, pos + 1, strlen(pos)); + pos[1] = ' '; + } switch (priority) { case LOG_EMERG: @@ -66,7 +73,6 @@ void syslog(int priority, const char *fmt, ...) } ReportEventA(ms_eventlog, logtype, 0, 0, NULL, 1, 0, - (const char **)&sb.buf, NULL); - - strbuf_release(&sb); + (const char **)&str, NULL); + free(str); } @@ -333,7 +333,7 @@ static int git_parse_file(config_fn_t fn, void *data) die("bad config file line %d in %s", cf->linenr, cf->name); } -static int parse_unit_factor(const char *end, unsigned long *val) +static int parse_unit_factor(const char *end, uintmax_t *val) { if (!*end) return 1; @@ -356,11 +356,23 @@ static int git_parse_long(const char *value, long *ret) { if (value && *value) { char *end; - long val = strtol(value, &end, 0); - unsigned long factor = 1; + intmax_t val; + uintmax_t uval; + uintmax_t factor = 1; + + errno = 0; + val = strtoimax(value, &end, 0); + if (errno == ERANGE) + return 0; if (!parse_unit_factor(end, &factor)) return 0; - *ret = val * factor; + uval = abs(val); + uval *= factor; + if ((uval > maximum_signed_value_of_type(long)) || + (abs(val) > uval)) + return 0; + val *= factor; + *ret = val; return 1; } return 0; @@ -370,9 +382,19 @@ int git_parse_ulong(const char *value, unsigned long *ret) { if (value && *value) { char *end; - unsigned long val = strtoul(value, &end, 0); + uintmax_t val; + uintmax_t oldval; + + errno = 0; + val = strtoumax(value, &end, 0); + if (errno == ERANGE) + return 0; + oldval = val; if (!parse_unit_factor(end, &val)) return 0; + if ((val > maximum_unsigned_value_of_type(long)) || + (oldval > val)) + return 0; *ret = val; return 1; } @@ -491,6 +513,9 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.attributesfile")) + return git_config_pathname(&git_attributes_file, var, value); + if (!strcmp(var, "core.bare")) { is_bare_repository_cfg = git_config_bool(var, value); return 0; @@ -550,7 +575,7 @@ static int git_default_core_config(const char *var, const char *value) if (!strcmp(var, "core.packedgitwindowsize")) { int pgsz_x2 = getpagesize() * 2; - packed_git_window_size = git_config_int(var, value); + packed_git_window_size = git_config_ulong(var, value); /* This value must be multiple of (pagesize * 2) */ packed_git_window_size /= pgsz_x2; @@ -561,18 +586,17 @@ static int git_default_core_config(const char *var, const char *value) } if (!strcmp(var, "core.bigfilethreshold")) { - long n = git_config_int(var, value); - big_file_threshold = 0 < n ? n : 0; + big_file_threshold = git_config_ulong(var, value); return 0; } if (!strcmp(var, "core.packedgitlimit")) { - packed_git_limit = git_config_int(var, value); + packed_git_limit = git_config_ulong(var, value); return 0; } if (!strcmp(var, "core.deltabasecachelimit")) { - delta_base_cache_limit = git_config_int(var, value); + delta_base_cache_limit = git_config_ulong(var, value); return 0; } @@ -794,6 +818,10 @@ int git_default_config(const char *var, const char *value, void *dummy) return 0; } + if (!strcmp(var, "pack.packsizelimit")) { + pack_size_limit_cfg = git_config_ulong(var, value); + return 0; + } /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@ -862,12 +890,12 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) home = getenv("HOME"); if (home) { - char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); + char buf[PATH_MAX]; + char *user_config = mksnpath(buf, sizeof(buf), "%s/.gitconfig", home); if (!access(user_config, R_OK)) { ret += git_config_from_file(fn, user_config, data); found += 1; } - free(user_config); } if (repo_config && !access(repo_config, R_OK)) { @@ -1095,6 +1123,12 @@ contline: return offset; } +int git_config_set_in_file(const char *config_filename, + const char *key, const char *value) +{ + return git_config_set_multivar_in_file(config_filename, key, value, NULL, 0); +} + int git_config_set(const char *key, const char *value) { return git_config_set_multivar(key, value, NULL, 0); @@ -1192,19 +1226,14 @@ out_free_ret_1: * - 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_in_file(const char *config_filename, + const char *key, const char *value, + const char *value_regex, int multi_replace) { int fd = -1, in_fd; int ret; - char *config_filename; struct lock_file *lock = NULL; - if (config_exclusive_filename) - config_filename = xstrdup(config_exclusive_filename); - else - config_filename = git_pathdup("config"); - /* parse-key returns negative; flip the sign to feed exit(3) */ ret = 0 - git_config_parse_key(key, &store.key, &store.baselen); if (ret) @@ -1381,7 +1410,6 @@ int git_config_set_multivar(const char *key, const char *value, out_free: if (lock) rollback_lock_file(lock); - free(config_filename); return ret; write_err_out: @@ -1390,6 +1418,24 @@ write_err_out: } +int git_config_set_multivar(const char *key, const char *value, + const char *value_regex, int multi_replace) +{ + const char *config_filename; + char *buf = NULL; + int ret; + + if (config_exclusive_filename) + config_filename = config_exclusive_filename; + else + config_filename = buf = git_pathdup("config"); + + ret = git_config_set_multivar_in_file(config_filename, key, value, + value_regex, multi_replace); + free(buf); + return ret; +} + static int section_name_match (const char *buf, const char *name) { int i = 0, j = 0, dot = 0; diff --git a/config.mak.in b/config.mak.in index ab371012a2..10698c8292 100644 --- a/config.mak.in +++ b/config.mak.in @@ -35,6 +35,9 @@ NO_CURL=@NO_CURL@ NO_EXPAT=@NO_EXPAT@ NO_LIBGEN_H=@NO_LIBGEN_H@ HAVE_PATHS_H=@HAVE_PATHS_H@ +HAVE_LIBCHARSET_H=@HAVE_LIBCHARSET_H@ +NO_GETTEXT=@NO_GETTEXT@ +LIBC_CONTAINS_LIBINTL=@LIBC_CONTAINS_LIBINTL@ NEEDS_LIBICONV=@NEEDS_LIBICONV@ NEEDS_SOCKET=@NEEDS_SOCKET@ NEEDS_RESOLV=@NEEDS_RESOLV@ diff --git a/configure.ac b/configure.ac index 048a1d4972..630dbdd19d 100644 --- a/configure.ac +++ b/configure.ac @@ -636,6 +636,12 @@ AC_CHECK_LIB([c], [basename], AC_SUBST(NEEDS_LIBGEN) test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen" +AC_CHECK_LIB([c], [gettext], +[LIBC_CONTAINS_LIBINTL=YesPlease], +[LIBC_CONTAINS_LIBINTL=]) +AC_SUBST(LIBC_CONTAINS_LIBINTL) +test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl" + ## Checks for header files. AC_MSG_NOTICE([CHECKS for header files]) # @@ -818,6 +824,19 @@ AC_CHECK_HEADER([paths.h], [HAVE_PATHS_H=]) AC_SUBST(HAVE_PATHS_H) # +# Define NO_GETTEXT if you don't want Git output to be translated. +# A translated Git requires GNU libintl or another gettext implementation +AC_CHECK_HEADER([libintl.h], +[NO_GETTEXT=], +[NO_GETTEXT=YesPlease]) +AC_SUBST(NO_GETTEXT) +# +# Define HAVE_LIBCHARSET_H if have libcharset.h +AC_CHECK_HEADER([libcharset.h], +[HAVE_LIBCHARSET_H=YesPlease], +[HAVE_LIBCHARSET_H=]) +AC_SUBST(HAVE_LIBCHARSET_H) +# # Define NO_STRCASESTR if you don't have strcasestr. GIT_CHECK_FUNC(strcasestr, [NO_STRCASESTR=], @@ -22,7 +22,7 @@ static int check_ref(const char *name, int len, unsigned int flags) len -= 5; /* REF_NORMAL means that we don't want the magic fake tag refs */ - if ((flags & REF_NORMAL) && check_ref_format(name) < 0) + if ((flags & REF_NORMAL) && check_refname_format(name, 0)) return 0; /* REF_HEADS means that we want regular branch heads */ @@ -53,7 +53,6 @@ static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1 * Read all the refs from the other end */ struct ref **get_remote_heads(int in, struct ref **list, - int nr_match, char **match, unsigned int flags, struct extra_have_objects *extra_have) { @@ -92,8 +91,6 @@ struct ref **get_remote_heads(int in, struct ref **list, if (!check_ref(name, name_len, flags)) continue; - if (nr_match && !path_match(name, nr_match, match)) - continue; ref = alloc_ref(buffer + 41); hashcpy(ref->old_sha1, old_sha1); *list = ref; @@ -108,27 +105,6 @@ int server_supports(const char *feature) strstr(server_capabilities, feature) != NULL; } -int path_match(const char *path, int nr, char **match) -{ - int i; - int pathlen = strlen(path); - - for (i = 0; i < nr; i++) { - char *s = match[i]; - int len = strlen(s); - - if (!len || len > pathlen) - continue; - if (memcmp(path + pathlen - len, s, len)) - continue; - if (pathlen > len && path[pathlen - len - 1] != '/') - continue; - *s = 0; - return (i + 1); - } - return 0; -} - enum protocol { PROTO_LOCAL = 1, PROTO_SSH, @@ -175,6 +151,15 @@ static void get_host_and_port(char **host, const char **port) } } +static void enable_keepalive(int sockfd) +{ + int ka = 1; + + if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0) + fprintf(stderr, "unable to set SO_KEEPALIVE on socket: %s\n", + strerror(errno)); +} + #ifndef NO_IPV6 static const char *ai_name(const struct addrinfo *ai) @@ -239,6 +224,8 @@ static int git_tcp_connect_sock(char *host, int flags) if (sockfd < 0) die("unable to connect to %s:\n%s", host, error_message.buf); + enable_keepalive(sockfd); + if (flags & CONNECT_VERBOSE) fprintf(stderr, "done.\n"); @@ -312,6 +299,8 @@ static int git_tcp_connect_sock(char *host, int flags) if (sockfd < 0) die("unable to connect to %s:\n%s", host, error_message.buf); + enable_keepalive(sockfd); + if (flags & CONNECT_VERBOSE) fprintf(stderr, "done.\n"); @@ -619,47 +608,3 @@ int finish_connect(struct child_process *conn) free(conn); return code; } - -char *git_getpass(const char *prompt) -{ - const char *askpass; - struct child_process pass; - const char *args[3]; - static struct strbuf buffer = STRBUF_INIT; - - askpass = getenv("GIT_ASKPASS"); - if (!askpass) - askpass = askpass_program; - if (!askpass) - askpass = getenv("SSH_ASKPASS"); - if (!askpass || !(*askpass)) { - char *result = getpass(prompt); - if (!result) - die_errno("Could not read password"); - return result; - } - - args[0] = askpass; - args[1] = prompt; - args[2] = NULL; - - memset(&pass, 0, sizeof(pass)); - pass.argv = args; - pass.out = -1; - - if (start_command(&pass)) - exit(1); - - strbuf_reset(&buffer); - if (strbuf_read(&buffer, pass.out, 20) < 0) - die("failed to read password from %s\n", askpass); - - close(pass.out); - - if (finish_command(&pass)) - exit(1); - - strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n")); - - return buffer.buf; -} diff --git a/connected.c b/connected.c new file mode 100644 index 0000000000..d7624230d4 --- /dev/null +++ b/connected.c @@ -0,0 +1,62 @@ +#include "cache.h" +#include "run-command.h" +#include "sigchain.h" +#include "connected.h" + +/* + * If we feed all the commits we want to verify to this command + * + * $ git rev-list --verify-objects --stdin --not --all + * + * and if it does not error out, that means everything reachable from + * these commits locally exists and is connected to some of our + * existing refs. + * + * Returns 0 if everything is connected, non-zero otherwise. + */ +int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data) +{ + struct child_process rev_list; + const char *argv[] = {"rev-list", "--verify-objects", + "--stdin", "--not", "--all", NULL, NULL}; + char commit[41]; + unsigned char sha1[20]; + int err = 0; + + if (fn(cb_data, sha1)) + return err; + + if (quiet) + argv[5] = "--quiet"; + + memset(&rev_list, 0, sizeof(rev_list)); + rev_list.argv = argv; + rev_list.git_cmd = 1; + rev_list.in = -1; + rev_list.no_stdout = 1; + rev_list.no_stderr = quiet; + if (start_command(&rev_list)) + return error(_("Could not run 'git rev-list'")); + + sigchain_push(SIGPIPE, SIG_IGN); + + commit[40] = '\n'; + do { + memcpy(commit, sha1_to_hex(sha1), 40); + if (write_in_full(rev_list.in, commit, 41) < 0) { + if (errno != EPIPE && errno != EINVAL) + error(_("failed write to rev-list: %s"), + strerror(errno)); + err = -1; + break; + } + } while (!fn(cb_data, sha1)); + + if (close(rev_list.in)) { + error(_("failed to close rev-list's stdin: %s"), strerror(errno)); + err = -1; + } + + sigchain_pop(SIGPIPE); + return finish_command(&rev_list) || err; +} diff --git a/connected.h b/connected.h new file mode 100644 index 0000000000..7e4585a6cb --- /dev/null +++ b/connected.h @@ -0,0 +1,20 @@ +#ifndef CONNECTED_H +#define CONNECTED_H + +/* + * Take callback data, and return next object name in the buffer. + * When called after returning the name for the last object, return -1 + * to signal EOF, otherwise return 0. + */ +typedef int (*sha1_iterate_fn)(void *, unsigned char [20]); + +/* + * Make sure that our object store has all the commits necessary to + * connect the ancestry chain to some of our existing refs, and all + * the trees and blobs that these commits use. + * + * Return 0 if Ok, non zero otherwise (i.e. some missing objects) + */ +extern int check_everything_connected(sha1_iterate_fn, int quiet, void *cb_data); + +#endif /* CONNECTED_H */ diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 8648a36e7b..cc1bdf9609 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -110,6 +110,7 @@ __git_ps1_show_upstream () local upstream=git legacy="" verbose="" # get some config options from git-config + local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')" while read key value; do case "$key" in bash.showupstream) @@ -125,7 +126,7 @@ __git_ps1_show_upstream () upstream=svn+git # default upstream is SVN if available, else git ;; esac - done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ') + done <<< "$output" # parse configuration values for option in ${GIT_PS1_SHOWUPSTREAM}; do @@ -485,8 +486,13 @@ _get_comp_words_by_ref () fi fi -# __gitcomp accepts 1, 2, 3, or 4 arguments -# generates completion reply with compgen +# Generates completion reply with compgen, appending a space to possible +# completion words, if necessary. +# It accepts 1 to 4 arguments: +# 1: List of possible completion words. +# 2: A prefix to be added to each possible completion word (optional). +# 3: Generate possible completion matches for this word (optional). +# 4: A suffix to be appended to each possible completion word (optional). __gitcomp () { local cur_="$cur" @@ -507,42 +513,49 @@ __gitcomp () esac } -# __git_heads accepts 0 or 1 arguments (to pass to __gitdir) +# Generates completion reply with compgen from newline-separated possible +# completion words by appending a space to all of them. +# It accepts 1 to 4 arguments: +# 1: List of possible completion words, separated by a single newline. +# 2: A prefix to be added to each possible completion word (optional). +# 3: Generate possible completion matches for this word (optional). +# 4: A suffix to be appended to each possible completion word instead of +# the default space (optional). If specified but empty, nothing is +# appended. +__gitcomp_nl () +{ + local s=$'\n' IFS=' '$'\t'$'\n' + local cur_="$cur" suffix=" " + + if [ $# -gt 2 ]; then + cur_="$3" + if [ $# -gt 3 ]; then + suffix="$4" + fi + fi + + IFS=$s + COMPREPLY=($(compgen -P "${2-}" -S "$suffix" -W "$1" -- "$cur_")) +} + __git_heads () { - local cmd i is_hash=y dir="$(__gitdir "${1-}")" + local dir="$(__gitdir)" if [ -d "$dir" ]; then git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ refs/heads return fi - for i in $(git ls-remote "${1-}" 2>/dev/null); do - case "$is_hash,$i" in - y,*) is_hash=n ;; - n,*^{}) is_hash=y ;; - n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; - n,*) is_hash=y; echo "$i" ;; - esac - done } -# __git_tags accepts 0 or 1 arguments (to pass to __gitdir) __git_tags () { - local cmd i is_hash=y dir="$(__gitdir "${1-}")" + local dir="$(__gitdir)" if [ -d "$dir" ]; then git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ refs/tags return fi - for i in $(git ls-remote "${1-}" 2>/dev/null); do - case "$is_hash,$i" in - y,*) is_hash=n ;; - n,*^{}) is_hash=y ;; - n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; - n,*) is_hash=y; echo "$i" ;; - esac - done } # __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments @@ -550,7 +563,7 @@ __git_tags () # by checkout for tracking branches __git_refs () { - local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}" + local i hash dir="$(__gitdir "${1-}")" track="${2-}" local format refs if [ -d "$dir" ]; then case "$cur" in @@ -586,16 +599,27 @@ __git_refs () fi return fi - for i in $(git ls-remote "$dir" 2>/dev/null); do - case "$is_hash,$i" in - y,*) is_hash=n ;; - n,*^{}) is_hash=y ;; - n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; - n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; - n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;; - n,*) is_hash=y; echo "$i" ;; - esac - done + case "$cur" in + refs|refs/*) + git ls-remote "$dir" "$cur*" 2>/dev/null | \ + while read hash i; do + case "$i" in + *^{}) ;; + *) echo "$i" ;; + esac + done + ;; + *) + git ls-remote "$dir" HEAD ORIG_HEAD 'refs/tags/*' 'refs/heads/*' 'refs/remotes/*' 2>/dev/null | \ + while read hash i; do + case "$i" in + *^{}) ;; + refs/*) echo "${i#refs/*/}" ;; + *) echo "$i" ;; + esac + done + ;; + esac } # __git_refs2 requires 1 argument (to pass to __git_refs) @@ -610,18 +634,10 @@ __git_refs2 () # __git_refs_remotes requires 1 argument (to pass to ls-remote) __git_refs_remotes () { - local cmd i is_hash=y - for i in $(git ls-remote "$1" 2>/dev/null); do - case "$is_hash,$i" in - n,refs/heads/*) - is_hash=y - echo "$i:refs/remotes/$1/${i#refs/heads/}" - ;; - y,*) is_hash=n ;; - n,*^{}) is_hash=y ;; - n,refs/tags/*) is_hash=y;; - n,*) is_hash=y; ;; - esac + local i hash + git ls-remote "$1" 'refs/heads/*' 2>/dev/null | \ + while read hash i; do + echo "$i:refs/remotes/$1/${i#refs/heads/}" done } @@ -711,15 +727,15 @@ __git_complete_revlist_file () *...*) pfx="${cur_%...*}..." cur_="${cur_#*...}" - __gitcomp "$(__git_refs)" "$pfx" "$cur_" + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_" ;; *..*) pfx="${cur_%..*}.." cur_="${cur_#*..}" - __gitcomp "$(__git_refs)" "$pfx" "$cur_" + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_" ;; *) - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" ;; esac } @@ -759,7 +775,7 @@ __git_complete_remote_or_refspec () c=$((++c)) done if [ -z "$remote" ]; then - __gitcomp "$(__git_remotes)" + __gitcomp_nl "$(__git_remotes)" return fi if [ $no_complete_refspec = 1 ]; then @@ -784,23 +800,23 @@ __git_complete_remote_or_refspec () case "$cmd" in fetch) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_" + __gitcomp_nl "$(__git_refs2 "$remote")" "$pfx" "$cur_" else - __gitcomp "$(__git_refs)" "$pfx" "$cur_" + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_" fi ;; pull) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_" + __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_" else - __gitcomp "$(__git_refs)" "$pfx" "$cur_" + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_" fi ;; push) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs)" "$pfx" "$cur_" + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_" else - __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_" + __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_" fi ;; esac @@ -1079,7 +1095,7 @@ _git_archive () return ;; --remote=*) - __gitcomp "$(__git_remotes)" "" "${cur##--remote=}" + __gitcomp_nl "$(__git_remotes)" "" "${cur##--remote=}" return ;; --*) @@ -1110,7 +1126,7 @@ _git_bisect () case "$subcommand" in bad|good|reset|skip|start) - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" ;; *) COMPREPLY=() @@ -1141,9 +1157,9 @@ _git_branch () ;; *) if [ $only_local_ref = "y" -a $has_r = "n" ]; then - __gitcomp "$(__git_heads)" + __gitcomp_nl "$(__git_heads)" else - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" fi ;; esac @@ -1190,7 +1206,7 @@ _git_checkout () if [ -n "$(__git_find_on_cmdline "$flags")" ]; then track='' fi - __gitcomp "$(__git_refs '' $track)" + __gitcomp_nl "$(__git_refs '' $track)" ;; esac } @@ -1207,7 +1223,7 @@ _git_cherry_pick () __gitcomp "--edit --no-commit" ;; *) - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" ;; esac } @@ -1259,12 +1275,9 @@ _git_commit () " "" "${cur##--cleanup=}" return ;; - --reuse-message=*) - __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}" - return - ;; - --reedit-message=*) - __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}" + --reuse-message=*|--reedit-message=*|\ + --fixup=*|--squash=*) + __gitcomp_nl "$(__git_refs)" "" "${cur#*=}" return ;; --untracked-files=*) @@ -1278,7 +1291,7 @@ _git_commit () --dry-run --reuse-message= --reedit-message= --reset-author --file= --message= --template= --cleanup= --untracked-files --untracked-files= - --verbose --quiet + --verbose --quiet --fixup= --squash= " return esac @@ -1295,7 +1308,7 @@ _git_describe () " return esac - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" } __git_diff_common_options="--stat --numstat --shortstat --summary @@ -1432,6 +1445,10 @@ _git_gitk () _gitk } +__git_match_ctag() { + awk "/^${1////\\/}/ { print \$1 }" "$2" +} + _git_grep () { __git_has_doubledash && return @@ -1454,7 +1471,16 @@ _git_grep () ;; esac - __gitcomp "$(__git_refs)" + case "$cword,$prev" in + 2,*|*,-*) + if test -r tags; then + __gitcomp_nl "$(__git_match_ctag "$cur" tags)" + return + fi + ;; + esac + + __gitcomp_nl "$(__git_refs)" } _git_help () @@ -1512,7 +1538,7 @@ _git_ls_files () _git_ls_remote () { - __gitcomp "$(__git_remotes)" + __gitcomp_nl "$(__git_remotes)" } _git_ls_tree () @@ -1556,14 +1582,9 @@ _git_log () merge="--merge" fi case "$cur" in - --pretty=*) - __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) - " "" "${cur##--pretty=}" - return - ;; - --format=*) + --pretty=*|--format=*) __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) - " "" "${cur##--format=}" + " "" "${cur#*=}" return ;; --date=*) @@ -1613,7 +1634,7 @@ _git_merge () __gitcomp "$__git_merge_options" return esac - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" } _git_mergetool () @@ -1633,7 +1654,7 @@ _git_mergetool () _git_merge_base () { - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" } _git_mv () @@ -1664,18 +1685,16 @@ _git_notes () ,*) case "${words[cword-1]}" in --ref) - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" ;; *) __gitcomp "$subcommands --ref" ;; esac ;; - add,--reuse-message=*|append,--reuse-message=*) - __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}" - ;; + add,--reuse-message=*|append,--reuse-message=*|\ add,--reedit-message=*|append,--reedit-message=*) - __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}" + __gitcomp_nl "$(__git_refs)" "" "${cur#*=}" ;; add,--*|append,--*) __gitcomp '--file= --message= --reedit-message= @@ -1694,7 +1713,7 @@ _git_notes () -m|-F) ;; *) - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" ;; esac ;; @@ -1722,18 +1741,18 @@ _git_push () { case "$prev" in --repo) - __gitcomp "$(__git_remotes)" + __gitcomp_nl "$(__git_remotes)" return esac case "$cur" in --repo=*) - __gitcomp "$(__git_remotes)" "" "${cur##--repo=}" + __gitcomp_nl "$(__git_remotes)" "" "${cur##--repo=}" return ;; --*) __gitcomp " --all --mirror --tags --dry-run --force --verbose - --receive-pack= --repo= + --receive-pack= --repo= --set-upstream " return ;; @@ -1765,7 +1784,7 @@ _git_rebase () return esac - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" } _git_reflog () @@ -1776,7 +1795,7 @@ _git_reflog () if [ -z "$subcommand" ]; then __gitcomp "$subcommands" else - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" fi } @@ -1858,23 +1877,27 @@ _git_config () { case "$prev" in branch.*.remote) - __gitcomp "$(__git_remotes)" + __gitcomp_nl "$(__git_remotes)" return ;; branch.*.merge) - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" return ;; remote.*.fetch) local remote="${prev#remote.}" remote="${remote%.fetch}" - __gitcomp "$(__git_refs_remotes "$remote")" + if [ -z "$cur" ]; then + COMPREPLY=("refs/heads/") + return + fi + __gitcomp_nl "$(__git_refs_remotes "$remote")" return ;; remote.*.push) local remote="${prev#remote.}" remote="${remote%.push}" - __gitcomp "$(git --git-dir="$(__gitdir)" \ + __gitcomp_nl "$(git --git-dir="$(__gitdir)" \ for-each-ref --format='%(refname):%(refname)' \ refs/heads)" return @@ -1921,7 +1944,7 @@ _git_config () return ;; --get|--get-all|--unset|--unset-all) - __gitcomp "$(__git_config_get_set_variables)" + __gitcomp_nl "$(__git_config_get_set_variables)" return ;; *.*) @@ -1947,7 +1970,7 @@ _git_config () ;; branch.*) local pfx="${cur%.*}." cur_="${cur#*.}" - __gitcomp "$(__git_heads)" "$pfx" "$cur_" "." + __gitcomp_nl "$(__git_heads)" "$pfx" "$cur_" "." return ;; guitool.*.*) @@ -1976,7 +1999,7 @@ _git_config () pager.*) local pfx="${cur%.*}." cur_="${cur#*.}" __git_compute_all_commands - __gitcomp "$__git_all_commands" "$pfx" "$cur_" + __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" return ;; remote.*.*) @@ -1989,7 +2012,7 @@ _git_config () ;; remote.*) local pfx="${cur%.*}." cur_="${cur#*.}" - __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "." + __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "." return ;; url.*.*) @@ -2290,7 +2313,7 @@ _git_remote () case "$subcommand" in rename|rm|show|prune) - __gitcomp "$(__git_remotes)" + __gitcomp_nl "$(__git_remotes)" ;; update) local i c='' IFS=$'\n' @@ -2308,7 +2331,7 @@ _git_remote () _git_replace () { - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" } _git_reset () @@ -2321,7 +2344,7 @@ _git_reset () return ;; esac - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" } _git_revert () @@ -2332,7 +2355,7 @@ _git_revert () return ;; esac - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" } _git_rm () @@ -2370,14 +2393,9 @@ _git_show () __git_has_doubledash && return case "$cur" in - --pretty=*) - __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) - " "" "${cur##--pretty=}" - return - ;; - --format=*) + --pretty=*|--format=*) __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) - " "" "${cur##--format=}" + " "" "${cur#*=}" return ;; --*) @@ -2436,7 +2454,7 @@ _git_stash () COMPREPLY=() ;; show,*|apply,*|drop,*|pop,*|branch,*) - __gitcomp "$(git --git-dir="$(__gitdir)" stash list \ + __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \ | sed -n -e 's/:.*//p')" ;; *) @@ -2570,7 +2588,7 @@ _git_tag () i="${words[c]}" case "$i" in -d|-v) - __gitcomp "$(__git_tags)" + __gitcomp_nl "$(__git_tags)" return ;; -f) @@ -2586,13 +2604,13 @@ _git_tag () ;; -*|tag) if [ $f = 1 ]; then - __gitcomp "$(__git_tags)" + __gitcomp_nl "$(__git_tags)" else COMPREPLY=() fi ;; *) - __gitcomp "$(__git_refs)" + __gitcomp_nl "$(__git_refs)" ;; esac } diff --git a/contrib/credential/osxkeychain/.gitignore b/contrib/credential/osxkeychain/.gitignore new file mode 100644 index 0000000000..6c5b7026c5 --- /dev/null +++ b/contrib/credential/osxkeychain/.gitignore @@ -0,0 +1 @@ +git-credential-osxkeychain diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile new file mode 100644 index 0000000000..75c07f8be4 --- /dev/null +++ b/contrib/credential/osxkeychain/Makefile @@ -0,0 +1,14 @@ +all:: git-credential-osxkeychain + +CC = gcc +RM = rm -f +CFLAGS = -g -Wall + +git-credential-osxkeychain: git-credential-osxkeychain.o + $(CC) -o $@ $< -Wl,-framework -Wl,Security + +git-credential-osxkeychain.o: git-credential-osxkeychain.c + $(CC) -c $(CFLAGS) $< + +clean: + $(RM) git-credential-osxkeychain git-credential-osxkeychain.o diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c new file mode 100644 index 0000000000..6beed123ab --- /dev/null +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c @@ -0,0 +1,173 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <Security/Security.h> + +static SecProtocolType protocol; +static char *host; +static char *path; +static char *username; +static char *password; +static UInt16 port; + +static void die(const char *err, ...) +{ + char msg[4096]; + va_list params; + va_start(params, err); + vsnprintf(msg, sizeof(msg), err, params); + fprintf(stderr, "%s\n", msg); + va_end(params); + exit(1); +} + +static void *xstrdup(const char *s1) +{ + void *ret = strdup(s1); + if (!ret) + die("Out of memory"); + return ret; +} + +#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x +#define KEYCHAIN_ARGS \ + NULL, /* default keychain */ \ + KEYCHAIN_ITEM(host), \ + 0, NULL, /* account domain */ \ + KEYCHAIN_ITEM(username), \ + KEYCHAIN_ITEM(path), \ + port, \ + protocol, \ + kSecAuthenticationTypeDefault + +static void write_item(const char *what, const char *buf, int len) +{ + printf("%s=", what); + fwrite(buf, 1, len, stdout); + putchar('\n'); +} + +static void find_username_in_item(SecKeychainItemRef item) +{ + SecKeychainAttributeList list; + SecKeychainAttribute attr; + + list.count = 1; + list.attr = &attr; + attr.tag = kSecAccountItemAttr; + + if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL)) + return; + + write_item("username", attr.data, attr.length); + SecKeychainItemFreeContent(&list, NULL); +} + +static void find_internet_password(void) +{ + void *buf; + UInt32 len; + SecKeychainItemRef item; + + if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item)) + return; + + write_item("password", buf, len); + if (!username) + find_username_in_item(item); + + SecKeychainItemFreeContent(NULL, buf); +} + +static void delete_internet_password(void) +{ + SecKeychainItemRef item; + + /* + * Require at least a protocol and host for removal, which is what git + * will give us; if you want to do something more fancy, use the + * Keychain manager. + */ + if (!protocol || !host) + return; + + if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item)) + return; + + SecKeychainItemDelete(item); +} + +static void add_internet_password(void) +{ + /* Only store complete credentials */ + if (!protocol || !host || !username || !password) + return; + + if (SecKeychainAddInternetPassword( + KEYCHAIN_ARGS, + KEYCHAIN_ITEM(password), + NULL)) + return; +} + +static void read_credential(void) +{ + char buf[1024]; + + while (fgets(buf, sizeof(buf), stdin)) { + char *v; + + if (!strcmp(buf, "\n")) + break; + buf[strlen(buf)-1] = '\0'; + + v = strchr(buf, '='); + if (!v) + die("bad input: %s", buf); + *v++ = '\0'; + + if (!strcmp(buf, "protocol")) { + if (!strcmp(v, "https")) + protocol = kSecProtocolTypeHTTPS; + else if (!strcmp(v, "http")) + protocol = kSecProtocolTypeHTTP; + else /* we don't yet handle other protocols */ + exit(0); + } + else if (!strcmp(buf, "host")) { + char *colon = strchr(v, ':'); + if (colon) { + *colon++ = '\0'; + port = atoi(colon); + } + host = xstrdup(v); + } + else if (!strcmp(buf, "path")) + path = xstrdup(v); + else if (!strcmp(buf, "username")) + username = xstrdup(v); + else if (!strcmp(buf, "password")) + password = xstrdup(v); + } +} + +int main(int argc, const char **argv) +{ + const char *usage = + "Usage: git credential-osxkeychain <get|store|erase>"; + + if (!argv[1]) + die(usage); + + read_credential(); + + if (!strcmp(argv[1], "get")) + find_internet_password(); + else if (!strcmp(argv[1], "store")) + add_internet_password(); + else if (!strcmp(argv[1], "erase")) + delete_internet_password(); + /* otherwise, ignore unknown action */ + + return 0; +} diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README new file mode 100644 index 0000000000..1b7b6df8eb --- /dev/null +++ b/contrib/diff-highlight/README @@ -0,0 +1,57 @@ +diff-highlight +============== + +Line oriented diffs are great for reviewing code, because for most +hunks, you want to see the old and the new segments of code next to each +other. Sometimes, though, when an old line and a new line are very +similar, it's hard to immediately see the difference. + +You can use "--color-words" to highlight only the changed portions of +lines. However, this can often be hard to read for code, as it loses +the line structure, and you end up with oddly formatted bits. + +Instead, this script post-processes the line-oriented diff, finds pairs +of lines, and highlights the differing segments. It's currently very +simple and stupid about doing these tasks. In particular: + + 1. It will only highlight a pair of lines if they are the only two + lines in a hunk. It could instead try to match up "before" and + "after" lines for a given hunk into pairs of similar lines. + However, this may end up visually distracting, as the paired + lines would have other highlighted lines in between them. And in + practice, the lines which most need attention called to their + small, hard-to-see changes are touching only a single line. + + 2. It will find the common prefix and suffix of two lines, and + consider everything in the middle to be "different". It could + instead do a real diff of the characters between the two lines and + find common subsequences. However, the point of the highlight is to + call attention to a certain area. Even if some small subset of the + highlighted area actually didn't change, that's OK. In practice it + ends up being more readable to just have a single blob on the line + showing the interesting bit. + +The goal of the script is therefore not to be exact about highlighting +changes, but to call attention to areas of interest without being +visually distracting. Non-diff lines and existing diff coloration is +preserved; the intent is that the output should look exactly the same as +the input, except for the occasional highlight. + +Use +--- + +You can try out the diff-highlight program with: + +--------------------------------------------- +git log -p --color | /path/to/diff-highlight +--------------------------------------------- + +If you want to use it all the time, drop it in your $PATH and put the +following in your git configuration: + +--------------------------------------------- +[pager] + log = diff-highlight | less + show = diff-highlight | less + diff = diff-highlight | less +--------------------------------------------- diff --git a/contrib/diff-highlight/diff-highlight b/contrib/diff-highlight/diff-highlight new file mode 100755 index 0000000000..d8938982e4 --- /dev/null +++ b/contrib/diff-highlight/diff-highlight @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +# Highlight by reversing foreground and background. You could do +# other things like bold or underline if you prefer. +my $HIGHLIGHT = "\x1b[7m"; +my $UNHIGHLIGHT = "\x1b[27m"; +my $COLOR = qr/\x1b\[[0-9;]*m/; + +my @window; + +while (<>) { + # We highlight only single-line changes, so we need + # a 4-line window to make a decision on whether + # to highlight. + push @window, $_; + next if @window < 4; + if ($window[0] =~ /^$COLOR*(\@| )/ && + $window[1] =~ /^$COLOR*-/ && + $window[2] =~ /^$COLOR*\+/ && + $window[3] !~ /^$COLOR*\+/) { + print shift @window; + show_pair(shift @window, shift @window); + } + else { + print shift @window; + } + + # Most of the time there is enough output to keep things streaming, + # but for something like "git log -Sfoo", you can get one early + # commit and then many seconds of nothing. We want to show + # that one commit as soon as possible. + # + # Since we can receive arbitrary input, there's no optimal + # place to flush. Flushing on a blank line is a heuristic that + # happens to match git-log output. + if (!length) { + local $| = 1; + } +} + +# Special case a single-line hunk at the end of file. +if (@window == 3 && + $window[0] =~ /^$COLOR*(\@| )/ && + $window[1] =~ /^$COLOR*-/ && + $window[2] =~ /^$COLOR*\+/) { + print shift @window; + show_pair(shift @window, shift @window); +} + +# And then flush any remaining lines. +while (@window) { + print shift @window; +} + +exit 0; + +sub show_pair { + my @a = split_line(shift); + my @b = split_line(shift); + + # Find common prefix, taking care to skip any ansi + # color codes. + my $seen_plusminus; + my ($pa, $pb) = (0, 0); + while ($pa < @a && $pb < @b) { + if ($a[$pa] =~ /$COLOR/) { + $pa++; + } + elsif ($b[$pb] =~ /$COLOR/) { + $pb++; + } + elsif ($a[$pa] eq $b[$pb]) { + $pa++; + $pb++; + } + elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { + $seen_plusminus = 1; + $pa++; + $pb++; + } + else { + last; + } + } + + # Find common suffix, ignoring colors. + my ($sa, $sb) = ($#a, $#b); + while ($sa >= $pa && $sb >= $pb) { + if ($a[$sa] =~ /$COLOR/) { + $sa--; + } + elsif ($b[$sb] =~ /$COLOR/) { + $sb--; + } + elsif ($a[$sa] eq $b[$sb]) { + $sa--; + $sb--; + } + else { + last; + } + } + + print highlight(\@a, $pa, $sa); + print highlight(\@b, $pb, $sb); +} + +sub split_line { + local $_ = shift; + return map { /$COLOR/ ? $_ : (split //) } + split /($COLOR*)/; +} + +sub highlight { + my ($line, $prefix, $suffix) = @_; + + return join('', + @{$line}[0..($prefix-1)], + $HIGHLIGHT, + @{$line}[$prefix..$suffix], + $UNHIGHLIGHT, + @{$line}[($suffix+1)..$#$line] + ); +} diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 2f7b270566..594980302b 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -22,37 +22,41 @@ def p4_build_cmd(cmd): location. It means that hooking into the environment, or other configuration can be done more easily. """ - real_cmd = "%s " % "p4" + real_cmd = ["p4"] user = gitConfig("git-p4.user") if len(user) > 0: - real_cmd += "-u %s " % user + real_cmd += ["-u",user] password = gitConfig("git-p4.password") if len(password) > 0: - real_cmd += "-P %s " % password + real_cmd += ["-P", password] port = gitConfig("git-p4.port") if len(port) > 0: - real_cmd += "-p %s " % port + real_cmd += ["-p", port] host = gitConfig("git-p4.host") if len(host) > 0: - real_cmd += "-h %s " % host + real_cmd += ["-h", host] client = gitConfig("git-p4.client") if len(client) > 0: - real_cmd += "-c %s " % client + real_cmd += ["-c", client] - real_cmd += "%s" % (cmd) - if verbose: - print real_cmd + + if isinstance(cmd,basestring): + real_cmd = ' '.join(real_cmd) + ' ' + cmd + else: + real_cmd += cmd return real_cmd def chdir(dir): - if os.name == 'nt': - os.environ['PWD']=dir + # P4 uses the PWD environment variable rather than getcwd(). Since we're + # not using the shell, we have to set it ourselves. This path could + # be relative, so go there first, then figure out where we ended up. os.chdir(dir) + os.environ['PWD'] = os.getcwd() def die(msg): if verbose: @@ -61,29 +65,34 @@ def die(msg): sys.stderr.write(msg + "\n") sys.exit(1) -def write_pipe(c, str): +def write_pipe(c, stdin): if verbose: - sys.stderr.write('Writing pipe: %s\n' % c) + sys.stderr.write('Writing pipe: %s\n' % str(c)) - pipe = os.popen(c, 'w') - val = pipe.write(str) - if pipe.close(): - die('Command failed: %s' % c) + expand = isinstance(c,basestring) + p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand) + pipe = p.stdin + val = pipe.write(stdin) + pipe.close() + if p.wait(): + die('Command failed: %s' % str(c)) return val -def p4_write_pipe(c, str): +def p4_write_pipe(c, stdin): real_cmd = p4_build_cmd(c) - return write_pipe(real_cmd, str) + return write_pipe(real_cmd, stdin) def read_pipe(c, ignore_error=False): if verbose: - sys.stderr.write('Reading pipe: %s\n' % c) + sys.stderr.write('Reading pipe: %s\n' % str(c)) - pipe = os.popen(c, 'rb') + expand = isinstance(c,basestring) + p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand) + pipe = p.stdout val = pipe.read() - if pipe.close() and not ignore_error: - die('Command failed: %s' % c) + if p.wait() and not ignore_error: + die('Command failed: %s' % str(c)) return val @@ -93,12 +102,14 @@ def p4_read_pipe(c, ignore_error=False): def read_pipe_lines(c): if verbose: - sys.stderr.write('Reading pipe: %s\n' % c) - ## todo: check return status - pipe = os.popen(c, 'rb') + sys.stderr.write('Reading pipe: %s\n' % str(c)) + + expand = isinstance(c, basestring) + p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand) + pipe = p.stdout val = pipe.readlines() - if pipe.close(): - die('Command failed: %s' % c) + if pipe.close() or p.wait(): + die('Command failed: %s' % str(c)) return val @@ -108,23 +119,73 @@ def p4_read_pipe_lines(c): return read_pipe_lines(real_cmd) def system(cmd): + expand = isinstance(cmd,basestring) if verbose: - sys.stderr.write("executing %s\n" % cmd) - if os.system(cmd) != 0: - die("command failed: %s" % cmd) + sys.stderr.write("executing %s\n" % str(cmd)) + subprocess.check_call(cmd, shell=expand) def p4_system(cmd): """Specifically invoke p4 as the system command. """ real_cmd = p4_build_cmd(cmd) - return system(real_cmd) + expand = isinstance(real_cmd, basestring) + subprocess.check_call(real_cmd, shell=expand) + +def p4_integrate(src, dest): + p4_system(["integrate", "-Dt", src, dest]) + +def p4_sync(path): + p4_system(["sync", path]) -def isP4Exec(kind): - """Determine if a Perforce 'kind' should have execute permission +def p4_add(f): + p4_system(["add", f]) + +def p4_delete(f): + p4_system(["delete", f]) + +def p4_edit(f): + p4_system(["edit", f]) + +def p4_revert(f): + p4_system(["revert", f]) + +def p4_reopen(type, file): + p4_system(["reopen", "-t", type, file]) + +# +# Canonicalize the p4 type and return a tuple of the +# base type, plus any modifiers. See "p4 help filetypes" +# for a list and explanation. +# +def split_p4_type(p4type): + + p4_filetypes_historical = { + "ctempobj": "binary+Sw", + "ctext": "text+C", + "cxtext": "text+Cx", + "ktext": "text+k", + "kxtext": "text+kx", + "ltext": "text+F", + "tempobj": "binary+FSw", + "ubinary": "binary+F", + "uresource": "resource+F", + "uxbinary": "binary+Fx", + "xbinary": "binary+x", + "xltext": "text+Fx", + "xtempobj": "binary+Swx", + "xtext": "text+x", + "xunicode": "unicode+x", + "xutf16": "utf16+x", + } + if p4type in p4_filetypes_historical: + p4type = p4_filetypes_historical[p4type] + mods = "" + s = p4type.split("+") + base = s[0] + mods = "" + if len(s) > 1: + mods = s[1] + return (base, mods) - 'p4 help filetypes' gives a list of the types. If it starts with 'x', - or x follows one of a few letters. Otherwise, if there is an 'x' after - a plus sign, it is also executable""" - return (re.search(r"(^[cku]?x)|\+.*x", kind) != None) def setP4ExecBit(file, mode): # Reopens an already open file and changes the execute bit to match @@ -139,12 +200,12 @@ def setP4ExecBit(file, mode): if p4Type[-1] == "+": p4Type = p4Type[0:-1] - p4_system("reopen -t %s %s" % (p4Type, file)) + p4_reopen(p4Type, file) def getP4OpenedType(file): # Returns the perforce file type for the given file. - result = p4_read_pipe("opened %s" % file) + result = p4_read_pipe(["opened", file]) match = re.match(".*\((.+)\)\r?$", result) if match: return match.group(1) @@ -200,9 +261,17 @@ def isModeExecChanged(src_mode, dst_mode): return isModeExec(src_mode) != isModeExec(dst_mode) def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None): - cmd = p4_build_cmd("-G %s" % (cmd)) + + if isinstance(cmd,basestring): + cmd = "-G " + cmd + expand = True + else: + cmd = ["-G"] + cmd + expand = False + + cmd = p4_build_cmd(cmd) if verbose: - sys.stderr.write("Opening pipe: %s\n" % cmd) + sys.stderr.write("Opening pipe: %s\n" % str(cmd)) # Use a temporary file to avoid deadlocks without # subprocess.communicate(), which would put another copy @@ -210,11 +279,16 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None): stdin_file = None if stdin is not None: stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode) - stdin_file.write(stdin) + if isinstance(stdin,basestring): + stdin_file.write(stdin) + else: + for i in stdin: + stdin_file.write(i + '\n') stdin_file.flush() stdin_file.seek(0) - p4 = subprocess.Popen(cmd, shell=True, + p4 = subprocess.Popen(cmd, + shell=expand, stdin=stdin_file, stdout=subprocess.PIPE) @@ -247,7 +321,7 @@ def p4Where(depotPath): if not depotPath.endswith("/"): depotPath += "/" depotPath = depotPath + "..." - outputList = p4CmdList("where %s" % depotPath) + outputList = p4CmdList(["where", depotPath]) output = None for entry in outputList: if "depotFile" in entry: @@ -449,8 +523,10 @@ def originP4BranchesExist(): def p4ChangesForPaths(depotPaths, changeRange): assert depotPaths - output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange) - for p in depotPaths])) + cmd = ['changes'] + for p in depotPaths: + cmd += ["%s...%s" % (p, changeRange)] + output = p4_read_pipe_lines(cmd) changes = {} for line in output: @@ -533,7 +609,7 @@ class P4Debug(Command): def run(self, args): j = 0 - for output in p4CmdList(" ".join(args)): + for output in p4CmdList(args): print 'Element: %d' % j j += 1 print output @@ -687,7 +763,7 @@ class P4Submit(Command, P4UserMap): break if not client: die("could not get client spec") - results = p4CmdList("changes -c %s -m 1" % client) + results = p4CmdList(["changes", "-c", client, "-m", "1"]) for r in results: if r.has_key('change'): return r['change'] @@ -750,7 +826,7 @@ class P4Submit(Command, P4UserMap): # remove lines in the Files section that show changes to files outside the depot path we're committing into template = "" inFilesSection = False - for line in p4_read_pipe_lines("change -o"): + for line in p4_read_pipe_lines(['change', '-o']): if line.endswith("\r\n"): line = line[:-2] + "\n" if inFilesSection: @@ -772,6 +848,41 @@ class P4Submit(Command, P4UserMap): return template + def edit_template(self, template_file): + """Invoke the editor to let the user change the submission + message. Return true if okay to continue with the submit.""" + + # if configured to skip the editing part, just submit + if gitConfig("git-p4.skipSubmitEdit") == "true": + return True + + # look at the modification time, to check later if the user saved + # the file + mtime = os.stat(template_file).st_mtime + + # invoke the editor + if os.environ.has_key("P4EDITOR"): + editor = os.environ.get("P4EDITOR") + else: + editor = read_pipe("git var GIT_EDITOR").strip() + system(editor + " " + template_file) + + # If the file was not saved, prompt to see if this patch should + # be skipped. But skip this verification step if configured so. + if gitConfig("git-p4.skipSubmitEditCheck") == "true": + return True + + # modification time updated means user saved the file + if os.stat(template_file).st_mtime > mtime: + return True + + while True: + response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ") + if response == 'y': + return True + if response == 'n': + return False + def applyCommit(self, id): print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id)) @@ -807,7 +918,7 @@ class P4Submit(Command, P4UserMap): modifier = diff['status'] path = diff['src'] if modifier == "M": - p4_system("edit \"%s\"" % path) + p4_edit(path) if isModeExecChanged(diff['src_mode'], diff['dst_mode']): filesToChangeExecBit[path] = diff['dst_mode'] editedFiles.add(path) @@ -822,21 +933,21 @@ class P4Submit(Command, P4UserMap): filesToAdd.remove(path) elif modifier == "C": src, dest = diff['src'], diff['dst'] - p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest)) + p4_integrate(src, dest) if diff['src_sha1'] != diff['dst_sha1']: - p4_system("edit \"%s\"" % (dest)) + p4_edit(dest) if isModeExecChanged(diff['src_mode'], diff['dst_mode']): - p4_system("edit \"%s\"" % (dest)) + p4_edit(dest) filesToChangeExecBit[dest] = diff['dst_mode'] os.unlink(dest) editedFiles.add(dest) elif modifier == "R": src, dest = diff['src'], diff['dst'] - p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest)) + p4_integrate(src, dest) if diff['src_sha1'] != diff['dst_sha1']: - p4_system("edit \"%s\"" % (dest)) + p4_edit(dest) if isModeExecChanged(diff['src_mode'], diff['dst_mode']): - p4_system("edit \"%s\"" % (dest)) + p4_edit(dest) filesToChangeExecBit[dest] = diff['dst_mode'] os.unlink(dest) editedFiles.add(dest) @@ -859,9 +970,9 @@ class P4Submit(Command, P4UserMap): if response == "s": print "Skipping! Good luck with the next patches..." for f in editedFiles: - p4_system("revert \"%s\"" % f); + p4_revert(f) for f in filesToAdd: - system("rm %s" %f) + os.remove(f) return elif response == "a": os.system(applyPatchCmd) @@ -882,10 +993,10 @@ class P4Submit(Command, P4UserMap): system(applyPatchCmd) for f in filesToAdd: - p4_system("add \"%s\"" % f) + p4_add(f) for f in filesToDelete: - p4_system("revert \"%s\"" % f) - p4_system("delete \"%s\"" % f) + p4_revert(f) + p4_delete(f) # Set/clear executable bits for f in filesToChangeExecBit.keys(): @@ -907,7 +1018,7 @@ class P4Submit(Command, P4UserMap): del(os.environ["P4DIFF"]) diff = "" for editedFile in editedFiles: - diff += p4_read_pipe("diff -du %r" % editedFile) + diff += p4_read_pipe(['diff', '-du', editedFile]) newdiff = "" for newFile in filesToAdd: @@ -926,7 +1037,7 @@ class P4Submit(Command, P4UserMap): separatorLine = "######## everything below this line is just the diff #######\n" - [handle, fileName] = tempfile.mkstemp() + (handle, fileName) = tempfile.mkstemp() tmpFile = os.fdopen(handle, "w+") if self.isWindows: submitTemplate = submitTemplate.replace("\n", "\r\n") @@ -934,46 +1045,32 @@ class P4Submit(Command, P4UserMap): newdiff = newdiff.replace("\n", "\r\n") tmpFile.write(submitTemplate + separatorLine + diff + newdiff) tmpFile.close() - mtime = os.stat(fileName).st_mtime - if os.environ.has_key("P4EDITOR"): - editor = os.environ.get("P4EDITOR") - else: - editor = read_pipe("git var GIT_EDITOR").strip() - system(editor + " " + fileName) - - if gitConfig("git-p4.skipSubmitEditCheck") == "true": - checkModTime = False - else: - checkModTime = True - response = "y" - if checkModTime and (os.stat(fileName).st_mtime <= mtime): - response = "x" - while response != "y" and response != "n": - response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ") - - if response == "y": + if self.edit_template(fileName): + # read the edited message and submit tmpFile = open(fileName, "rb") message = tmpFile.read() tmpFile.close() submitTemplate = message[:message.index(separatorLine)] if self.isWindows: submitTemplate = submitTemplate.replace("\r\n", "\n") - p4_write_pipe("submit -i", submitTemplate) + p4_write_pipe(['submit', '-i'], submitTemplate) if self.preserveUser: if p4User: # Get last changelist number. Cannot easily get it from - # the submit command output as the output is unmarshalled. + # the submit command output as the output is + # unmarshalled. changelist = self.lastP4Changelist() self.modifyChangelistUser(changelist, p4User) - else: + # skip this patch + print "Submission cancelled, undoing p4 changes." for f in editedFiles: - p4_system("revert \"%s\"" % f); + p4_revert(f) for f in filesToAdd: - p4_system("revert \"%s\"" % f); - system("rm %s" %f) + p4_revert(f) + os.remove(f) os.remove(fileName) else: @@ -1024,10 +1121,13 @@ class P4Submit(Command, P4UserMap): print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath) self.oldWorkingDirectory = os.getcwd() + # ensure the clientPath exists + if not os.path.exists(self.clientPath): + os.makedirs(self.clientPath) + chdir(self.clientPath) print "Synchronizing p4 checkout..." - p4_system("sync ...") - + p4_sync("...") self.check() commits = [] @@ -1219,38 +1319,66 @@ class P4Sync(Command, P4UserMap): # - helper for streamP4Files def streamOneP4File(self, file, contents): - if file["type"] == "apple": - print "\nfile %s is a strange apple file that forks. Ignoring" % \ - file['depotFile'] - return - relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes) relPath = self.wildcard_decode(relPath) if verbose: sys.stderr.write("%s\n" % relPath) - mode = "644" - if isP4Exec(file["type"]): - mode = "755" - elif file["type"] == "symlink": - mode = "120000" - # p4 print on a symlink contains "target\n", so strip it off + (type_base, type_mods) = split_p4_type(file["type"]) + + git_mode = "100644" + if "x" in type_mods: + git_mode = "100755" + if type_base == "symlink": + git_mode = "120000" + # p4 print on a symlink contains "target\n"; remove the newline data = ''.join(contents) contents = [data[:-1]] - if self.isWindows and file["type"].endswith("text"): + if type_base == "utf16": + # p4 delivers different text in the python output to -G + # than it does when using "print -o", or normal p4 client + # operations. utf16 is converted to ascii or utf8, perhaps. + # But ascii text saved as -t utf16 is completely mangled. + # Invoke print -o to get the real contents. + text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']]) + contents = [ text ] + + if type_base == "apple": + # Apple filetype files will be streamed as a concatenation of + # its appledouble header and the contents. This is useless + # on both macs and non-macs. If using "print -q -o xx", it + # will create "xx" with the data, and "%xx" with the header. + # This is also not very useful. + # + # Ideally, someday, this script can learn how to generate + # appledouble files directly and import those to git, but + # non-mac machines can never find a use for apple filetype. + print "\nIgnoring apple filetype file %s" % file['depotFile'] + return + + # Perhaps windows wants unicode, utf16 newlines translated too; + # but this is not doing it. + if self.isWindows and type_base == "text": mangled = [] for data in contents: data = data.replace("\r\n", "\n") mangled.append(data) contents = mangled - if file['type'] in ('text+ko', 'unicode+ko', 'binary+ko'): - contents = map(lambda text: re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text), contents) - elif file['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'): - contents = map(lambda text: re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text), contents) + # Note that we do not try to de-mangle keywords on utf16 files, + # even though in theory somebody may want that. + if type_base in ("text", "unicode", "binary"): + if "ko" in type_mods: + text = ''.join(contents) + text = re.sub(r'\$(Id|Header):[^$]*\$', r'$\1$', text) + contents = [ text ] + elif "k" in type_mods: + text = ''.join(contents) + text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', text) + contents = [ text ] - self.gitStream.write("M %s inline %s\n" % (mode, relPath)) + self.gitStream.write("M %s inline %s\n" % (git_mode, relPath)) # total length... length = 0 @@ -1322,10 +1450,11 @@ class P4Sync(Command, P4UserMap): def streamP4FilesCbSelf(entry): self.streamP4FilesCb(entry) - p4CmdList("-x - print", - '\n'.join(['%s#%s' % (f['path'], f['rev']) - for f in filesToRead]), - cb=streamP4FilesCbSelf) + fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead] + + p4CmdList(["-x", "-", "print"], + stdin=fileArgs, + cb=streamP4FilesCbSelf) # do the last chunk if self.stream_file.has_key('depotFile'): @@ -1386,8 +1515,8 @@ class P4Sync(Command, P4UserMap): if self.verbose: print "Change %s is labelled %s" % (change, labelDetails) - files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change) - for p in branchPrefixes])) + files = p4CmdList(["files"] + ["%s...@%s" % (p, change) + for p in branchPrefixes]) if len(files) == len(labelRevisions): @@ -1435,9 +1564,9 @@ class P4Sync(Command, P4UserMap): newestChange = 0 if self.verbose: print "Querying files for label %s" % label - for file in p4CmdList("files " - + ' '.join (["%s...@%s" % (p, label) - for p in self.depotPaths])): + for file in p4CmdList(["files"] + + ["%s...@%s" % (p, label) + for p in self.depotPaths]): revisions[file["depotFile"]] = file["rev"] change = int(file["change"]) if change > newestChange: @@ -1692,10 +1821,9 @@ class P4Sync(Command, P4UserMap): newestRevision = 0 fileCnt = 0 - for info in p4CmdList("files " - + ' '.join(["%s...%s" - % (p, revision) - for p in self.depotPaths])): + fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths] + + for info in p4CmdList(["files"] + fileArgs): if 'code' in info and info['code'] == 'error': sys.stderr.write("p4 returned an error: %s\n" diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt index 52003ae904..5044a121e0 100644 --- a/contrib/fast-import/git-p4.txt +++ b/contrib/fast-import/git-p4.txt @@ -202,11 +202,24 @@ able to find the relevant client. This client spec will be used to both filter the files cloned by git and set the directory layout as specified in the client (this implies --keep-path style semantics). -git-p4.skipSubmitModTimeCheck +git-p4.skipSubmitEdit - git config [--global] git-p4.skipSubmitModTimeCheck false + git config [--global] git-p4.skipSubmitEdit false -If true, submit will not check if the p4 change template has been modified. +Normally, git-p4 invokes an editor after each commit is applied so +that you can make changes to the submit message. Setting this +variable to true will skip the editing step, submitting the change as is. + +git-p4.skipSubmitEditCheck + + git config [--global] git-p4.skipSubmitEditCheck false + +After the editor is invoked, git-p4 normally makes sure you saved the +change description, as an indication that you did indeed read it over +and edit it. You can quit without saving to abort the submit (or skip +this change and continue). Setting this variable to true will cause +git-p4 not to check if you saved the change description. This variable +only matters if git-p4.skipSubmitEdit has not been set to true. git-p4.preserveUser diff --git a/contrib/git-jump/README b/contrib/git-jump/README new file mode 100644 index 0000000000..1cebc328cb --- /dev/null +++ b/contrib/git-jump/README @@ -0,0 +1,92 @@ +git-jump +======== + +Git-jump is a script for helping you jump to "interesting" parts of your +project in your editor. It works by outputting a set of interesting +spots in the "quickfix" format, which editors like vim can use as a +queue of places to visit (this feature is usually used to jump to errors +produced by a compiler). For example, given a diff like this: + +------------------------------------ +diff --git a/foo.c b/foo.c +index a655540..5a59044 100644 +--- a/foo.c ++++ b/foo.c +@@ -1,3 +1,3 @@ + int main(void) { +- printf("hello word!\n"); ++ printf("hello world!\n"); + } +----------------------------------- + +git-jump will feed this to the editor: + +----------------------------------- +foo.c:2: printf("hello word!\n"); +----------------------------------- + +Obviously this trivial case isn't that interesting; you could just open +`foo.c` yourself. But when you have many changes scattered across a +project, you can use the editor's support to "jump" from point to point. + +Git-jump can generate three types of interesting lists: + + 1. The beginning of any diff hunks. + + 2. The beginning of any merge conflict markers. + + 3. Any grep matches. + + +Using git-jump +-------------- + +To use it, just drop git-jump in your PATH, and then invoke it like +this: + +-------------------------------------------------- +# jump to changes not yet staged for commit +git jump diff + +# jump to changes that are staged for commit; you can give +# arbitrary diff options +git jump diff --cached + +# jump to merge conflicts +git jump merge + +# jump to all instances of foo_bar +git jump grep foo_bar + +# same as above, but case-insensitive; you can give +# arbitrary grep options +git jump grep -i foo_bar +-------------------------------------------------- + + +Related Programs +---------------- + +You can accomplish some of the same things with individual tools. For +example, you can use `git mergetool` to start vimdiff on each unmerged +file. `git jump merge` is for the vim-wielding luddite who just wants to +jump straight to the conflict text with no fanfare. + +As of git v1.7.2, `git grep` knows the `--open-files-in-pager` option, +which does something similar to `git jump grep`. However, it is limited +to positioning the cursor to the correct line in only the first file, +leaving you to locate subsequent hits in that file or other files using +the editor or pager. By contrast, git-jump provides the editor with a +complete list of files and line numbers for each match. + + +Limitations +----------- + +This scripts was written and tested with vim. Given that the quickfix +format is the same as what gcc produces, I expect emacs users have a +similar feature for iterating through the list, but I know nothing about +how to activate it. + +The shell snippets to generate the quickfix lines will almost certainly +choke on filenames with exotic characters (like newlines). diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump new file mode 100755 index 0000000000..a33674e47a --- /dev/null +++ b/contrib/git-jump/git-jump @@ -0,0 +1,69 @@ +#!/bin/sh + +usage() { + cat <<\EOF +usage: git jump <mode> [<args>] + +Jump to interesting elements in an editor. +The <mode> parameter is one of: + +diff: elements are diff hunks. Arguments are given to diff. + +merge: elements are merge conflicts. Arguments are ignored. + +grep: elements are grep hits. Arguments are given to grep. +EOF +} + +open_editor() { + editor=`git var GIT_EDITOR` + eval "$editor -q \$1" +} + +mode_diff() { + git diff --relative "$@" | + perl -ne ' + if (m{^\+\+\+ b/(.*)}) { $file = $1; next } + defined($file) or next; + if (m/^@@ .*\+(\d+)/) { $line = $1; next } + defined($line) or next; + if (/^ /) { $line++; next } + if (/^[-+]\s*(.*)/) { + print "$file:$line: $1\n"; + $line = undef; + } + ' +} + +mode_merge() { + git ls-files -u | + perl -pe 's/^.*?\t//' | + sort -u | + while IFS= read fn; do + grep -Hn '^<<<<<<<' "$fn" + done +} + +# Grep -n generates nice quickfix-looking lines by itself, +# but let's clean up extra whitespace, so they look better if the +# editor shows them to us in the status bar. +mode_grep() { + git grep -n "$@" | + perl -pe ' + s/[ \t]+/ /g; + s/^ *//; + ' +} + +if test $# -lt 1; then + usage >&2 + exit 1 +fi +mode=$1; shift + +trap 'rm -f "$tmp"' 0 1 2 3 15 +tmp=`mktemp -t git-jump.XXXXXX` || exit 1 +type "mode_$mode" >/dev/null 2>&1 || { usage >&2; exit 1; } +"mode_$mode" "$@" >"$tmp" +test -s "$tmp" || exit 0 +open_editor "$tmp" diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki new file mode 100755 index 0000000000..c18bfa1f15 --- /dev/null +++ b/contrib/mw-to-git/git-remote-mediawiki @@ -0,0 +1,827 @@ +#! /usr/bin/perl + +# Copyright (C) 2011 +# Jérémie Nikaes <jeremie.nikaes@ensimag.imag.fr> +# Arnaud Lacurie <arnaud.lacurie@ensimag.imag.fr> +# Claire Fousse <claire.fousse@ensimag.imag.fr> +# David Amouyal <david.amouyal@ensimag.imag.fr> +# Matthieu Moy <matthieu.moy@grenoble-inp.fr> +# License: GPL v2 or later + +# Gateway between Git and MediaWiki. +# https://github.com/Bibzball/Git-Mediawiki/wiki +# +# Known limitations: +# +# - Only wiki pages are managed, no support for [[File:...]] +# attachments. +# +# - Poor performance in the best case: it takes forever to check +# whether we're up-to-date (on fetch or push) or to fetch a few +# revisions from a large wiki, because we use exclusively a +# page-based synchronization. We could switch to a wiki-wide +# synchronization when the synchronization involves few revisions +# but the wiki is large. +# +# - Git renames could be turned into MediaWiki renames (see TODO +# below) +# +# - login/password support requires the user to write the password +# cleartext in a file (see TODO below). +# +# - No way to import "one page, and all pages included in it" +# +# - Multiple remote MediaWikis have not been very well tested. + +use strict; +use MediaWiki::API; +use DateTime::Format::ISO8601; +use encoding 'utf8'; + +# use encoding 'utf8' doesn't change STDERROR +# but we're going to output UTF-8 filenames to STDERR +binmode STDERR, ":utf8"; + +use URI::Escape; +use warnings; + +# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced +use constant SLASH_REPLACEMENT => "%2F"; + +# It's not always possible to delete pages (may require some +# priviledges). Deleted pages are replaced with this content. +use constant DELETED_CONTENT => "[[Category:Deleted]]\n"; + +# It's not possible to create empty pages. New empty files in Git are +# sent with this content instead. +use constant EMPTY_CONTENT => "<!-- empty page -->\n"; + +# used to reflect file creation or deletion in diff. +use constant NULL_SHA1 => "0000000000000000000000000000000000000000"; + +my $remotename = $ARGV[0]; +my $url = $ARGV[1]; + +# Accept both space-separated and multiple keys in config file. +# Spaces should be written as _ anyway because we'll use chomp. +my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".pages")); +chomp(@tracked_pages); + +# Just like @tracked_pages, but for MediaWiki categories. +my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories")); +chomp(@tracked_categories); + +my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin"); +# TODO: ideally, this should be able to read from keyboard, but we're +# inside a remote helper, so our stdin is connect to git, not to a +# terminal. +my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword"); +my $wiki_domain = run_git("config --get remote.". $remotename .".mwDomain"); +chomp($wiki_login); +chomp($wiki_passwd); +chomp($wiki_domain); + +# Import only last revisions (both for clone and fetch) +my $shallow_import = run_git("config --get --bool remote.". $remotename .".shallow"); +chomp($shallow_import); +$shallow_import = ($shallow_import eq "true"); + +# Dumb push: don't update notes and mediawiki ref to reflect the last push. +# +# Configurable with mediawiki.dumbPush, or per-remote with +# remote.<remotename>.dumbPush. +# +# This means the user will have to re-import the just-pushed +# revisions. On the other hand, this means that the Git revisions +# corresponding to MediaWiki revisions are all imported from the wiki, +# regardless of whether they were initially created in Git or from the +# web interface, hence all users will get the same history (i.e. if +# the push from Git to MediaWiki loses some information, everybody +# will get the history with information lost). If the import is +# deterministic, this means everybody gets the same sha1 for each +# MediaWiki revision. +my $dumb_push = run_git("config --get --bool remote.$remotename.dumbPush"); +unless ($dumb_push) { + $dumb_push = run_git("config --get --bool mediawiki.dumbPush"); +} +chomp($dumb_push); +$dumb_push = ($dumb_push eq "true"); + +my $wiki_name = $url; +$wiki_name =~ s/[^\/]*:\/\///; +# If URL is like http://user:password@example.com/, we clearly don't +# want the password in $wiki_name. While we're there, also remove user +# and '@' sign, to avoid author like MWUser@HTTPUser@host.com +$wiki_name =~ s/^.*@//; + +# Commands parser +my $entry; +my @cmd; +while (<STDIN>) { + chomp; + @cmd = split(/ /); + if (defined($cmd[0])) { + # Line not blank + if ($cmd[0] eq "capabilities") { + die("Too many arguments for capabilities") unless (!defined($cmd[1])); + mw_capabilities(); + } elsif ($cmd[0] eq "list") { + die("Too many arguments for list") unless (!defined($cmd[2])); + mw_list($cmd[1]); + } elsif ($cmd[0] eq "import") { + die("Invalid arguments for import") unless ($cmd[1] ne "" && !defined($cmd[2])); + mw_import($cmd[1]); + } elsif ($cmd[0] eq "option") { + die("Too many arguments for option") unless ($cmd[1] ne "" && $cmd[2] ne "" && !defined($cmd[3])); + mw_option($cmd[1],$cmd[2]); + } elsif ($cmd[0] eq "push") { + mw_push($cmd[1]); + } else { + print STDERR "Unknown command. Aborting...\n"; + last; + } + } else { + # blank line: we should terminate + last; + } + + BEGIN { $| = 1 } # flush STDOUT, to make sure the previous + # command is fully processed. +} + +########################## Functions ############################## + +# MediaWiki API instance, created lazily. +my $mediawiki; + +sub mw_connect_maybe { + if ($mediawiki) { + return; + } + $mediawiki = MediaWiki::API->new; + $mediawiki->{config}->{api_url} = "$url/api.php"; + if ($wiki_login) { + if (!$mediawiki->login({ + lgname => $wiki_login, + lgpassword => $wiki_passwd, + lgdomain => $wiki_domain, + })) { + print STDERR "Failed to log in mediawiki user \"$wiki_login\" on $url\n"; + print STDERR "(error " . + $mediawiki->{error}->{code} . ': ' . + $mediawiki->{error}->{details} . ")\n"; + exit 1; + } else { + print STDERR "Logged in with user \"$wiki_login\".\n"; + } + } +} + +sub get_mw_first_pages { + my $some_pages = shift; + my @some_pages = @{$some_pages}; + + my $pages = shift; + + # pattern 'page1|page2|...' required by the API + my $titles = join('|', @some_pages); + + my $mw_pages = $mediawiki->api({ + action => 'query', + titles => $titles, + }); + if (!defined($mw_pages)) { + print STDERR "fatal: could not query the list of wiki pages.\n"; + print STDERR "fatal: '$url' does not appear to be a mediawiki\n"; + print STDERR "fatal: make sure '$url/api.php' is a valid page.\n"; + exit 1; + } + while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) { + if ($id < 0) { + print STDERR "Warning: page $page->{title} not found on wiki\n"; + } else { + $pages->{$page->{title}} = $page; + } + } +} + +sub get_mw_pages { + mw_connect_maybe(); + + my %pages; # hash on page titles to avoid duplicates + my $user_defined; + if (@tracked_pages) { + $user_defined = 1; + # The user provided a list of pages titles, but we + # still need to query the API to get the page IDs. + + my @some_pages = @tracked_pages; + while (@some_pages) { + my $last = 50; + if ($#some_pages < $last) { + $last = $#some_pages; + } + my @slice = @some_pages[0..$last]; + get_mw_first_pages(\@slice, \%pages); + @some_pages = @some_pages[51..$#some_pages]; + } + } + if (@tracked_categories) { + $user_defined = 1; + foreach my $category (@tracked_categories) { + if (index($category, ':') < 0) { + # Mediawiki requires the Category + # prefix, but let's not force the user + # to specify it. + $category = "Category:" . $category; + } + my $mw_pages = $mediawiki->list( { + action => 'query', + list => 'categorymembers', + cmtitle => $category, + cmlimit => 'max' } ) + || die $mediawiki->{error}->{code} . ': ' . $mediawiki->{error}->{details}; + foreach my $page (@{$mw_pages}) { + $pages{$page->{title}} = $page; + } + } + } + if (!$user_defined) { + # No user-provided list, get the list of pages from + # the API. + my $mw_pages = $mediawiki->list({ + action => 'query', + list => 'allpages', + aplimit => 500, + }); + if (!defined($mw_pages)) { + print STDERR "fatal: could not get the list of wiki pages.\n"; + print STDERR "fatal: '$url' does not appear to be a mediawiki\n"; + print STDERR "fatal: make sure '$url/api.php' is a valid page.\n"; + exit 1; + } + foreach my $page (@{$mw_pages}) { + $pages{$page->{title}} = $page; + } + } + return values(%pages); +} + +sub run_git { + open(my $git, "-|:encoding(UTF-8)", "git " . $_[0]); + my $res = do { local $/; <$git> }; + close($git); + + return $res; +} + + +sub get_last_local_revision { + # Get note regarding last mediawiki revision + my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null"); + my @note_info = split(/ /, $note); + + my $lastrevision_number; + if (!(defined($note_info[0]) && $note_info[0] eq "mediawiki_revision:")) { + print STDERR "No previous mediawiki revision found"; + $lastrevision_number = 0; + } else { + # Notes are formatted : mediawiki_revision: #number + $lastrevision_number = $note_info[1]; + chomp($lastrevision_number); + print STDERR "Last local mediawiki revision found is $lastrevision_number"; + } + return $lastrevision_number; +} + +# Remember the timestamp corresponding to a revision id. +my %basetimestamps; + +sub get_last_remote_revision { + mw_connect_maybe(); + + my @pages = get_mw_pages(); + + my $max_rev_num = 0; + + foreach my $page (@pages) { + my $id = $page->{pageid}; + + my $query = { + action => 'query', + prop => 'revisions', + rvprop => 'ids|timestamp', + pageids => $id, + }; + + my $result = $mediawiki->api($query); + + my $lastrev = pop(@{$result->{query}->{pages}->{$id}->{revisions}}); + + $basetimestamps{$lastrev->{revid}} = $lastrev->{timestamp}; + + $max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num); + } + + print STDERR "Last remote revision found is $max_rev_num.\n"; + return $max_rev_num; +} + +# Clean content before sending it to MediaWiki +sub mediawiki_clean { + my $string = shift; + my $page_created = shift; + # Mediawiki does not allow blank space at the end of a page and ends with a single \n. + # This function right trims a string and adds a \n at the end to follow this rule + $string =~ s/\s+$//; + if ($string eq "" && $page_created) { + # Creating empty pages is forbidden. + $string = EMPTY_CONTENT; + } + return $string."\n"; +} + +# Filter applied on MediaWiki data before adding them to Git +sub mediawiki_smudge { + my $string = shift; + if ($string eq EMPTY_CONTENT) { + $string = ""; + } + # This \n is important. This is due to mediawiki's way to handle end of files. + return $string."\n"; +} + +sub mediawiki_clean_filename { + my $filename = shift; + $filename =~ s/@{[SLASH_REPLACEMENT]}/\//g; + # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded. + # Do a variant of URL-encoding, i.e. looks like URL-encoding, + # but with _ added to prevent MediaWiki from thinking this is + # an actual special character. + $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge; + # If we use the uri escape before + # we should unescape here, before anything + + return $filename; +} + +sub mediawiki_smudge_filename { + my $filename = shift; + $filename =~ s/\//@{[SLASH_REPLACEMENT]}/g; + $filename =~ s/ /_/g; + # Decode forbidden characters encoded in mediawiki_clean_filename + $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf("%c", hex($1))/ge; + return $filename; +} + +sub literal_data { + my ($content) = @_; + print STDOUT "data ", bytes::length($content), "\n", $content; +} + +sub mw_capabilities { + # Revisions are imported to the private namespace + # refs/mediawiki/$remotename/ by the helper and fetched into + # refs/remotes/$remotename later by fetch. + print STDOUT "refspec refs/heads/*:refs/mediawiki/$remotename/*\n"; + print STDOUT "import\n"; + print STDOUT "list\n"; + print STDOUT "push\n"; + print STDOUT "\n"; +} + +sub mw_list { + # MediaWiki do not have branches, we consider one branch arbitrarily + # called master, and HEAD pointing to it. + print STDOUT "? refs/heads/master\n"; + print STDOUT "\@refs/heads/master HEAD\n"; + print STDOUT "\n"; +} + +sub mw_option { + print STDERR "remote-helper command 'option $_[0]' not yet implemented\n"; + print STDOUT "unsupported\n"; +} + +sub fetch_mw_revisions_for_page { + my $page = shift; + my $id = shift; + my $fetch_from = shift; + my @page_revs = (); + my $query = { + action => 'query', + prop => 'revisions', + rvprop => 'ids', + rvdir => 'newer', + rvstartid => $fetch_from, + rvlimit => 500, + pageids => $id, + }; + + my $revnum = 0; + # Get 500 revisions at a time due to the mediawiki api limit + while (1) { + my $result = $mediawiki->api($query); + + # Parse each of those 500 revisions + foreach my $revision (@{$result->{query}->{pages}->{$id}->{revisions}}) { + my $page_rev_ids; + $page_rev_ids->{pageid} = $page->{pageid}; + $page_rev_ids->{revid} = $revision->{revid}; + push(@page_revs, $page_rev_ids); + $revnum++; + } + last unless $result->{'query-continue'}; + $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid}; + } + if ($shallow_import && @page_revs) { + print STDERR " Found 1 revision (shallow import).\n"; + @page_revs = sort {$b->{revid} <=> $a->{revid}} (@page_revs); + return $page_revs[0]; + } + print STDERR " Found ", $revnum, " revision(s).\n"; + return @page_revs; +} + +sub fetch_mw_revisions { + my $pages = shift; my @pages = @{$pages}; + my $fetch_from = shift; + + my @revisions = (); + my $n = 1; + foreach my $page (@pages) { + my $id = $page->{pageid}; + + print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n"; + $n++; + my @page_revs = fetch_mw_revisions_for_page($page, $id, $fetch_from); + @revisions = (@page_revs, @revisions); + } + + return ($n, @revisions); +} + +sub import_file_revision { + my $commit = shift; + my %commit = %{$commit}; + my $full_import = shift; + my $n = shift; + + my $title = $commit{title}; + my $comment = $commit{comment}; + my $content = $commit{content}; + my $author = $commit{author}; + my $date = $commit{date}; + + print STDOUT "commit refs/mediawiki/$remotename/master\n"; + print STDOUT "mark :$n\n"; + print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n"; + literal_data($comment); + + # If it's not a clone, we need to know where to start from + if (!$full_import && $n == 1) { + print STDOUT "from refs/mediawiki/$remotename/master^0\n"; + } + if ($content ne DELETED_CONTENT) { + print STDOUT "M 644 inline $title.mw\n"; + literal_data($content); + print STDOUT "\n\n"; + } else { + print STDOUT "D $title.mw\n"; + } + + # mediawiki revision number in the git note + if ($full_import && $n == 1) { + print STDOUT "reset refs/notes/$remotename/mediawiki\n"; + } + print STDOUT "commit refs/notes/$remotename/mediawiki\n"; + print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n"; + literal_data("Note added by git-mediawiki during import"); + if (!$full_import && $n == 1) { + print STDOUT "from refs/notes/$remotename/mediawiki^0\n"; + } + print STDOUT "N inline :$n\n"; + literal_data("mediawiki_revision: " . $commit{mw_revision}); + print STDOUT "\n\n"; +} + +# parse a sequence of +# <cmd> <arg1> +# <cmd> <arg2> +# \n +# (like batch sequence of import and sequence of push statements) +sub get_more_refs { + my $cmd = shift; + my @refs; + while (1) { + my $line = <STDIN>; + if ($line =~ m/^$cmd (.*)$/) { + push(@refs, $1); + } elsif ($line eq "\n") { + return @refs; + } else { + die("Invalid command in a '$cmd' batch: ". $_); + } + } +} + +sub mw_import { + # multiple import commands can follow each other. + my @refs = (shift, get_more_refs("import")); + foreach my $ref (@refs) { + mw_import_ref($ref); + } + print STDOUT "done\n"; +} + +sub mw_import_ref { + my $ref = shift; + # The remote helper will call "import HEAD" and + # "import refs/heads/master". + # Since HEAD is a symbolic ref to master (by convention, + # followed by the output of the command "list" that we gave), + # we don't need to do anything in this case. + if ($ref eq "HEAD") { + return; + } + + mw_connect_maybe(); + + my @pages = get_mw_pages(); + + print STDERR "Searching revisions...\n"; + my $last_local = get_last_local_revision(); + my $fetch_from = $last_local + 1; + if ($fetch_from == 1) { + print STDERR ", fetching from beginning.\n"; + } else { + print STDERR ", fetching from here.\n"; + } + my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from); + + # Creation of the fast-import stream + print STDERR "Fetching & writing export data...\n"; + + $n = 0; + my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined + + foreach my $pagerevid (sort {$a->{revid} <=> $b->{revid}} @revisions) { + # fetch the content of the pages + my $query = { + action => 'query', + prop => 'revisions', + rvprop => 'content|timestamp|comment|user|ids', + revids => $pagerevid->{revid}, + }; + + my $result = $mediawiki->api($query); + + my $rev = pop(@{$result->{query}->{pages}->{$pagerevid->{pageid}}->{revisions}}); + + $n++; + + my %commit; + $commit{author} = $rev->{user} || 'Anonymous'; + $commit{comment} = $rev->{comment} || '*Empty MediaWiki Message*'; + $commit{title} = mediawiki_smudge_filename( + $result->{query}->{pages}->{$pagerevid->{pageid}}->{title} + ); + $commit{mw_revision} = $pagerevid->{revid}; + $commit{content} = mediawiki_smudge($rev->{'*'}); + + if (!defined($rev->{timestamp})) { + $last_timestamp++; + } else { + $last_timestamp = $rev->{timestamp}; + } + $commit{date} = DateTime::Format::ISO8601->parse_datetime($last_timestamp); + + print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n"; + + import_file_revision(\%commit, ($fetch_from == 1), $n); + } + + if ($fetch_from == 1 && $n == 0) { + print STDERR "You appear to have cloned an empty MediaWiki.\n"; + # Something has to be done remote-helper side. If nothing is done, an error is + # thrown saying that HEAD is refering to unknown object 0000000000000000000 + # and the clone fails. + } +} + +sub error_non_fast_forward { + my $advice = run_git("config --bool advice.pushNonFastForward"); + chomp($advice); + if ($advice ne "false") { + # Native git-push would show this after the summary. + # We can't ask it to display it cleanly, so print it + # ourselves before. + print STDERR "To prevent you from losing history, non-fast-forward updates were rejected\n"; + print STDERR "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"; + print STDERR "'Note about fast-forwards' section of 'git push --help' for details.\n"; + } + print STDOUT "error $_[0] \"non-fast-forward\"\n"; + return 0; +} + +sub mw_push_file { + my $diff_info = shift; + # $diff_info contains a string in this format: + # 100644 100644 <sha1_of_blob_before_commit> <sha1_of_blob_now> <status> + my @diff_info_split = split(/[ \t]/, $diff_info); + + # Filename, including .mw extension + my $complete_file_name = shift; + # Commit message + my $summary = shift; + # MediaWiki revision number. Keep the previous one by default, + # in case there's no edit to perform. + my $newrevid = shift; + + my $new_sha1 = $diff_info_split[3]; + my $old_sha1 = $diff_info_split[2]; + my $page_created = ($old_sha1 eq NULL_SHA1); + my $page_deleted = ($new_sha1 eq NULL_SHA1); + $complete_file_name = mediawiki_clean_filename($complete_file_name); + + if (substr($complete_file_name,-3) eq ".mw") { + my $title = substr($complete_file_name,0,-3); + + my $file_content; + if ($page_deleted) { + # Deleting a page usually requires + # special priviledges. A common + # convention is to replace the page + # with this content instead: + $file_content = DELETED_CONTENT; + } else { + $file_content = run_git("cat-file blob $new_sha1"); + } + + mw_connect_maybe(); + + my $result = $mediawiki->edit( { + action => 'edit', + summary => $summary, + title => $title, + basetimestamp => $basetimestamps{$newrevid}, + text => mediawiki_clean($file_content, $page_created), + }, { + skip_encoding => 1 # Helps with names with accentuated characters + }); + if (!$result) { + if ($mediawiki->{error}->{code} == 3) { + # edit conflicts, considered as non-fast-forward + print STDERR 'Warning: Error ' . + $mediawiki->{error}->{code} . + ' from mediwiki: ' . $mediawiki->{error}->{details} . + ".\n"; + return ($newrevid, "non-fast-forward"); + } else { + # Other errors. Shouldn't happen => just die() + die 'Fatal: Error ' . + $mediawiki->{error}->{code} . + ' from mediwiki: ' . $mediawiki->{error}->{details}; + } + } + $newrevid = $result->{edit}->{newrevid}; + print STDERR "Pushed file: $new_sha1 - $title\n"; + } else { + print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n" + } + return ($newrevid, "ok"); +} + +sub mw_push { + # multiple push statements can follow each other + my @refsspecs = (shift, get_more_refs("push")); + my $pushed; + for my $refspec (@refsspecs) { + my ($force, $local, $remote) = $refspec =~ /^(\+)?([^:]*):([^:]*)$/ + or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>"); + if ($force) { + print STDERR "Warning: forced push not allowed on a MediaWiki.\n"; + } + if ($local eq "") { + print STDERR "Cannot delete remote branch on a MediaWiki\n"; + print STDOUT "error $remote cannot delete\n"; + next; + } + if ($remote ne "refs/heads/master") { + print STDERR "Only push to the branch 'master' is supported on a MediaWiki\n"; + print STDOUT "error $remote only master allowed\n"; + next; + } + if (mw_push_revision($local, $remote)) { + $pushed = 1; + } + } + + # Notify Git that the push is done + print STDOUT "\n"; + + if ($pushed && $dumb_push) { + print STDERR "Just pushed some revisions to MediaWiki.\n"; + print STDERR "The pushed revisions now have to be re-imported, and your current branch\n"; + print STDERR "needs to be updated with these re-imported commits. You can do this with\n"; + print STDERR "\n"; + print STDERR " git pull --rebase\n"; + print STDERR "\n"; + } +} + +sub mw_push_revision { + my $local = shift; + my $remote = shift; # actually, this has to be "refs/heads/master" at this point. + my $last_local_revid = get_last_local_revision(); + print STDERR ".\n"; # Finish sentence started by get_last_local_revision() + my $last_remote_revid = get_last_remote_revision(); + my $mw_revision = $last_remote_revid; + + # Get sha1 of commit pointed by local HEAD + my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1); + # Get sha1 of commit pointed by remotes/$remotename/master + my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/$remotename/master 2>/dev/null"); + chomp($remoteorigin_sha1); + + if ($last_local_revid > 0 && + $last_local_revid < $last_remote_revid) { + return error_non_fast_forward($remote); + } + + if ($HEAD_sha1 eq $remoteorigin_sha1) { + # nothing to push + return 0; + } + + # Get every commit in between HEAD and refs/remotes/origin/master, + # including HEAD and refs/remotes/origin/master + my @commit_pairs = (); + if ($last_local_revid > 0) { + my $parsed_sha1 = $remoteorigin_sha1; + # Find a path from last MediaWiki commit to pushed commit + while ($parsed_sha1 ne $HEAD_sha1) { + my @commit_info = grep(/^$parsed_sha1/, split(/\n/, run_git("rev-list --children $local"))); + if (!@commit_info) { + return error_non_fast_forward($remote); + } + my @commit_info_split = split(/ |\n/, $commit_info[0]); + # $commit_info_split[1] is the sha1 of the commit to export + # $commit_info_split[0] is the sha1 of its direct child + push(@commit_pairs, \@commit_info_split); + $parsed_sha1 = $commit_info_split[1]; + } + } else { + # No remote mediawiki revision. Export the whole + # history (linearized with --first-parent) + print STDERR "Warning: no common ancestor, pushing complete history\n"; + my $history = run_git("rev-list --first-parent --children $local"); + my @history = split('\n', $history); + @history = @history[1..$#history]; + foreach my $line (reverse @history) { + my @commit_info_split = split(/ |\n/, $line); + push(@commit_pairs, \@commit_info_split); + } + } + + foreach my $commit_info_split (@commit_pairs) { + my $sha1_child = @{$commit_info_split}[0]; + my $sha1_commit = @{$commit_info_split}[1]; + my $diff_infos = run_git("diff-tree -r --raw -z $sha1_child $sha1_commit"); + # TODO: we could detect rename, and encode them with a #redirect on the wiki. + # TODO: for now, it's just a delete+add + my @diff_info_list = split(/\0/, $diff_infos); + # Keep the first line of the commit message as mediawiki comment for the revision + my $commit_msg = (split(/\n/, run_git("show --pretty=format:\"%s\" $sha1_commit")))[0]; + chomp($commit_msg); + # Push every blob + while (@diff_info_list) { + my $status; + # git diff-tree -z gives an output like + # <metadata>\0<filename1>\0 + # <metadata>\0<filename2>\0 + # and we've split on \0. + my $info = shift(@diff_info_list); + my $file = shift(@diff_info_list); + ($mw_revision, $status) = mw_push_file($info, $file, $commit_msg, $mw_revision); + if ($status eq "non-fast-forward") { + # we may already have sent part of the + # commit to MediaWiki, but it's too + # late to cancel it. Stop the push in + # the middle, but still give an + # accurate error message. + return error_non_fast_forward($remote); + } + if ($status ne "ok") { + die("Unknown error from mw_push_file()"); + } + } + unless ($dumb_push) { + run_git("notes --ref=$remotename/mediawiki add -m \"mediawiki_revision: $mw_revision\" $sha1_commit"); + run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child"); + } + } + + print STDOUT "ok $remote\n"; + return 1; +} diff --git a/contrib/mw-to-git/git-remote-mediawiki.txt b/contrib/mw-to-git/git-remote-mediawiki.txt new file mode 100644 index 0000000000..4d211f5b81 --- /dev/null +++ b/contrib/mw-to-git/git-remote-mediawiki.txt @@ -0,0 +1,7 @@ +Git-Mediawiki is a project which aims the creation of a gate +between git and mediawiki, allowing git users to push and pull +objects from mediawiki just as one would do with a classic git +repository thanks to remote-helpers. + +For more information, visit the wiki at +https://github.com/Bibzball/Git-Mediawiki/wiki @@ -641,7 +641,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len, return 1; } -static int git_path_check_crlf(const char *path, struct git_attr_check *check) +static enum crlf_action git_path_check_crlf(const char *path, struct git_attr_check *check) { const char *value = check->value; @@ -658,7 +658,7 @@ static int git_path_check_crlf(const char *path, struct git_attr_check *check) return CRLF_GUESS; } -static int git_path_check_eol(const char *path, struct git_attr_check *check) +static enum eol git_path_check_eol(const char *path, struct git_attr_check *check) { const char *value = check->value; @@ -811,7 +811,7 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str src = dst->buf; len = dst->len; } - return ret | convert_to_git(path, src, len, dst, 0); + return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_FALSE); } /***************************************************************** @@ -876,24 +876,42 @@ int is_null_stream_filter(struct stream_filter *filter) /* * LF-to-CRLF filter */ + +struct lf_to_crlf_filter { + struct stream_filter filter; + unsigned want_lf:1; +}; + static int lf_to_crlf_filter_fn(struct stream_filter *filter, const char *input, size_t *isize_p, char *output, size_t *osize_p) { - size_t count; + size_t count, o = 0; + struct lf_to_crlf_filter *lf_to_crlf = (struct lf_to_crlf_filter *)filter; + + /* Output a pending LF if we need to */ + if (lf_to_crlf->want_lf) { + output[o++] = '\n'; + lf_to_crlf->want_lf = 0; + } + + /* We are told to drain */ + if (!input) { + *osize_p -= o; + return 0; + } - if (!input) - return 0; /* we do not keep any states */ count = *isize_p; if (count) { - size_t i, o; - for (i = o = 0; o < *osize_p && i < count; i++) { + size_t i; + for (i = 0; o < *osize_p && i < count; i++) { char ch = input[i]; if (ch == '\n') { - if (o + 1 < *osize_p) - output[o++] = '\r'; - else - break; + output[o++] = '\r'; + if (o >= *osize_p) { + lf_to_crlf->want_lf = 1; + continue; /* We need to increase i */ + } } output[o++] = ch; } @@ -904,15 +922,23 @@ static int lf_to_crlf_filter_fn(struct stream_filter *filter, return 0; } +static void lf_to_crlf_free_fn(struct stream_filter *filter) +{ + free(filter); +} + static struct stream_filter_vtbl lf_to_crlf_vtbl = { lf_to_crlf_filter_fn, - null_free_fn, + lf_to_crlf_free_fn, }; -static struct stream_filter lf_to_crlf_filter_singleton = { - &lf_to_crlf_vtbl, -}; +static struct stream_filter *lf_to_crlf_filter(void) +{ + struct lf_to_crlf_filter *lf_to_crlf = xcalloc(1, sizeof(*lf_to_crlf)); + lf_to_crlf->filter.vtbl = &lf_to_crlf_vtbl; + return (struct stream_filter *)lf_to_crlf; +} /* * Cascade filter @@ -1194,7 +1220,7 @@ struct stream_filter *get_stream_filter(const char *path, const unsigned char *s else if (output_eol(crlf_action) == EOL_CRLF && !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS)) - filter = cascade_filter(filter, &lf_to_crlf_filter_singleton); + filter = cascade_filter(filter, lf_to_crlf_filter()); return filter; } diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c new file mode 100644 index 0000000000..390f194252 --- /dev/null +++ b/credential-cache--daemon.c @@ -0,0 +1,269 @@ +#include "cache.h" +#include "credential.h" +#include "unix-socket.h" +#include "sigchain.h" + +static const char *socket_path; + +static void cleanup_socket(void) +{ + if (socket_path) + unlink(socket_path); +} + +static void cleanup_socket_on_signal(int sig) +{ + cleanup_socket(); + sigchain_pop(sig); + raise(sig); +} + +struct credential_cache_entry { + struct credential item; + unsigned long expiration; +}; +static struct credential_cache_entry *entries; +static int entries_nr; +static int entries_alloc; + +static void cache_credential(struct credential *c, int timeout) +{ + struct credential_cache_entry *e; + + ALLOC_GROW(entries, entries_nr + 1, entries_alloc); + e = &entries[entries_nr++]; + + /* take ownership of pointers */ + memcpy(&e->item, c, sizeof(*c)); + memset(c, 0, sizeof(*c)); + e->expiration = time(NULL) + timeout; +} + +static struct credential_cache_entry *lookup_credential(const struct credential *c) +{ + int i; + for (i = 0; i < entries_nr; i++) { + struct credential *e = &entries[i].item; + if (credential_match(c, e)) + return &entries[i]; + } + return NULL; +} + +static void remove_credential(const struct credential *c) +{ + struct credential_cache_entry *e; + + e = lookup_credential(c); + if (e) + e->expiration = 0; +} + +static int check_expirations(void) +{ + static unsigned long wait_for_entry_until; + int i = 0; + unsigned long now = time(NULL); + unsigned long next = (unsigned long)-1; + + /* + * Initially give the client 30 seconds to actually contact us + * and store a credential before we decide there's no point in + * keeping the daemon around. + */ + if (!wait_for_entry_until) + wait_for_entry_until = now + 30; + + while (i < entries_nr) { + if (entries[i].expiration <= now) { + entries_nr--; + credential_clear(&entries[i].item); + if (i != entries_nr) + memcpy(&entries[i], &entries[entries_nr], sizeof(*entries)); + /* + * Stick around 30 seconds in case a new credential + * shows up (e.g., because we just removed a failed + * one, and we will soon get the correct one). + */ + wait_for_entry_until = now + 30; + } + else { + if (entries[i].expiration < next) + next = entries[i].expiration; + i++; + } + } + + if (!entries_nr) { + if (wait_for_entry_until <= now) + return 0; + next = wait_for_entry_until; + } + + return next - now; +} + +static int read_request(FILE *fh, struct credential *c, + struct strbuf *action, int *timeout) { + static struct strbuf item = STRBUF_INIT; + const char *p; + + strbuf_getline(&item, fh, '\n'); + p = skip_prefix(item.buf, "action="); + if (!p) + return error("client sent bogus action line: %s", item.buf); + strbuf_addstr(action, p); + + strbuf_getline(&item, fh, '\n'); + p = skip_prefix(item.buf, "timeout="); + if (!p) + return error("client sent bogus timeout line: %s", item.buf); + *timeout = atoi(p); + + if (credential_read(c, fh) < 0) + return -1; + return 0; +} + +static void serve_one_client(FILE *in, FILE *out) +{ + struct credential c = CREDENTIAL_INIT; + struct strbuf action = STRBUF_INIT; + int timeout = -1; + + if (read_request(in, &c, &action, &timeout) < 0) + /* ignore error */ ; + else if (!strcmp(action.buf, "get")) { + struct credential_cache_entry *e = lookup_credential(&c); + if (e) { + fprintf(out, "username=%s\n", e->item.username); + fprintf(out, "password=%s\n", e->item.password); + } + } + else if (!strcmp(action.buf, "exit")) + exit(0); + else if (!strcmp(action.buf, "erase")) + remove_credential(&c); + else if (!strcmp(action.buf, "store")) { + if (timeout < 0) + warning("cache client didn't specify a timeout"); + else if (!c.username || !c.password) + warning("cache client gave us a partial credential"); + else { + remove_credential(&c); + cache_credential(&c, timeout); + } + } + else + warning("cache client sent unknown action: %s", action.buf); + + credential_clear(&c); + strbuf_release(&action); +} + +static int serve_cache_loop(int fd) +{ + struct pollfd pfd; + unsigned long wakeup; + + wakeup = check_expirations(); + if (!wakeup) + return 0; + + pfd.fd = fd; + pfd.events = POLLIN; + if (poll(&pfd, 1, 1000 * wakeup) < 0) { + if (errno != EINTR) + die_errno("poll failed"); + return 1; + } + + if (pfd.revents & POLLIN) { + int client, client2; + FILE *in, *out; + + client = accept(fd, NULL, NULL); + if (client < 0) { + warning("accept failed: %s", strerror(errno)); + return 1; + } + client2 = dup(client); + if (client2 < 0) { + warning("dup failed: %s", strerror(errno)); + close(client); + return 1; + } + + in = xfdopen(client, "r"); + out = xfdopen(client2, "w"); + serve_one_client(in, out); + fclose(in); + fclose(out); + } + return 1; +} + +static void serve_cache(const char *socket_path) +{ + int fd; + + fd = unix_stream_listen(socket_path); + if (fd < 0) + die_errno("unable to bind to '%s'", socket_path); + + printf("ok\n"); + fclose(stdout); + + while (serve_cache_loop(fd)) + ; /* nothing */ + + close(fd); + unlink(socket_path); +} + +static const char permissions_advice[] = +"The permissions on your socket directory are too loose; other\n" +"users may be able to read your cached credentials. Consider running:\n" +"\n" +" chmod 0700 %s"; +static void check_socket_directory(const char *path) +{ + struct stat st; + char *path_copy = xstrdup(path); + char *dir = dirname(path_copy); + + if (!stat(dir, &st)) { + if (st.st_mode & 077) + die(permissions_advice, dir); + free(path_copy); + return; + } + + /* + * We must be sure to create the directory with the correct mode, + * not just chmod it after the fact; otherwise, there is a race + * condition in which somebody can chdir to it, sleep, then try to open + * our protected socket. + */ + if (safe_create_leading_directories_const(dir) < 0) + die_errno("unable to create directories for '%s'", dir); + if (mkdir(dir, 0700) < 0) + die_errno("unable to mkdir '%s'", dir); + free(path_copy); +} + +int main(int argc, const char **argv) +{ + socket_path = argv[1]; + + if (!socket_path) + die("usage: git-credential-cache--daemon <socket_path>"); + check_socket_directory(socket_path); + + atexit(cleanup_socket); + sigchain_push_common(cleanup_socket_on_signal); + + serve_cache(socket_path); + + return 0; +} diff --git a/credential-cache.c b/credential-cache.c new file mode 100644 index 0000000000..dc98372e59 --- /dev/null +++ b/credential-cache.c @@ -0,0 +1,120 @@ +#include "cache.h" +#include "credential.h" +#include "string-list.h" +#include "parse-options.h" +#include "unix-socket.h" +#include "run-command.h" + +#define FLAG_SPAWN 0x1 +#define FLAG_RELAY 0x2 + +static int send_request(const char *socket, const struct strbuf *out) +{ + int got_data = 0; + int fd = unix_stream_connect(socket); + + if (fd < 0) + return -1; + + if (write_in_full(fd, out->buf, out->len) < 0) + die_errno("unable to write to cache daemon"); + shutdown(fd, SHUT_WR); + + while (1) { + char in[1024]; + int r; + + r = read_in_full(fd, in, sizeof(in)); + if (r == 0) + break; + if (r < 0) + die_errno("read error from cache daemon"); + write_or_die(1, in, r); + got_data = 1; + } + return got_data; +} + +static void spawn_daemon(const char *socket) +{ + struct child_process daemon; + const char *argv[] = { NULL, NULL, NULL }; + char buf[128]; + int r; + + memset(&daemon, 0, sizeof(daemon)); + argv[0] = "git-credential-cache--daemon"; + argv[1] = socket; + daemon.argv = argv; + daemon.no_stdin = 1; + daemon.out = -1; + + if (start_command(&daemon)) + die_errno("unable to start cache daemon"); + r = read_in_full(daemon.out, buf, sizeof(buf)); + if (r < 0) + die_errno("unable to read result code from cache daemon"); + if (r != 3 || memcmp(buf, "ok\n", 3)) + die("cache daemon did not start: %.*s", r, buf); + close(daemon.out); +} + +static void do_cache(const char *socket, const char *action, int timeout, + int flags) +{ + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "action=%s\n", action); + strbuf_addf(&buf, "timeout=%d\n", timeout); + if (flags & FLAG_RELAY) { + if (strbuf_read(&buf, 0, 0) < 0) + die_errno("unable to relay credential"); + } + + if (!send_request(socket, &buf)) + return; + if (flags & FLAG_SPAWN) { + spawn_daemon(socket); + send_request(socket, &buf); + } + strbuf_release(&buf); +} + +int main(int argc, const char **argv) +{ + char *socket_path = NULL; + int timeout = 900; + const char *op; + const char * const usage[] = { + "git credential-cache [options] <action>", + NULL + }; + struct option options[] = { + OPT_INTEGER(0, "timeout", &timeout, + "number of seconds to cache credentials"), + OPT_STRING(0, "socket", &socket_path, "path", + "path of cache-daemon socket"), + OPT_END() + }; + + argc = parse_options(argc, argv, NULL, options, usage, 0); + if (!argc) + usage_with_options(usage, options); + op = argv[0]; + + if (!socket_path) + socket_path = expand_user_path("~/.git-credential-cache/socket"); + if (!socket_path) + die("unable to find a suitable socket path; use --socket"); + + if (!strcmp(op, "exit")) + do_cache(socket_path, op, timeout, 0); + else if (!strcmp(op, "get") || !strcmp(op, "erase")) + do_cache(socket_path, op, timeout, FLAG_RELAY); + else if (!strcmp(op, "store")) + do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN); + else + ; /* ignore unknown operation */ + + return 0; +} diff --git a/credential-store.c b/credential-store.c new file mode 100644 index 0000000000..26f7589a60 --- /dev/null +++ b/credential-store.c @@ -0,0 +1,157 @@ +#include "cache.h" +#include "credential.h" +#include "string-list.h" +#include "parse-options.h" + +static struct lock_file credential_lock; + +static void parse_credential_file(const char *fn, + struct credential *c, + void (*match_cb)(struct credential *), + void (*other_cb)(struct strbuf *)) +{ + FILE *fh; + struct strbuf line = STRBUF_INIT; + struct credential entry = CREDENTIAL_INIT; + + fh = fopen(fn, "r"); + if (!fh) { + if (errno != ENOENT) + die_errno("unable to open %s", fn); + return; + } + + while (strbuf_getline(&line, fh, '\n') != EOF) { + credential_from_url(&entry, line.buf); + if (entry.username && entry.password && + credential_match(c, &entry)) { + if (match_cb) { + match_cb(&entry); + break; + } + } + else if (other_cb) + other_cb(&line); + } + + credential_clear(&entry); + strbuf_release(&line); + fclose(fh); +} + +static void print_entry(struct credential *c) +{ + printf("username=%s\n", c->username); + printf("password=%s\n", c->password); +} + +static void print_line(struct strbuf *buf) +{ + strbuf_addch(buf, '\n'); + write_or_die(credential_lock.fd, buf->buf, buf->len); +} + +static void rewrite_credential_file(const char *fn, struct credential *c, + struct strbuf *extra) +{ + if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0) + die_errno("unable to get credential storage lock"); + if (extra) + print_line(extra); + parse_credential_file(fn, c, NULL, print_line); + if (commit_lock_file(&credential_lock) < 0) + die_errno("unable to commit credential store"); +} + +static void store_credential(const char *fn, struct credential *c) +{ + struct strbuf buf = STRBUF_INIT; + + /* + * Sanity check that what we are storing is actually sensible. + * In particular, we can't make a URL without a protocol field. + * Without either a host or pathname (depending on the scheme), + * we have no primary key. And without a username and password, + * we are not actually storing a credential. + */ + if (!c->protocol || !(c->host || c->path) || + !c->username || !c->password) + return; + + strbuf_addf(&buf, "%s://", c->protocol); + strbuf_addstr_urlencode(&buf, c->username, 1); + strbuf_addch(&buf, ':'); + strbuf_addstr_urlencode(&buf, c->password, 1); + strbuf_addch(&buf, '@'); + if (c->host) + strbuf_addstr_urlencode(&buf, c->host, 1); + if (c->path) { + strbuf_addch(&buf, '/'); + strbuf_addstr_urlencode(&buf, c->path, 0); + } + + rewrite_credential_file(fn, c, &buf); + strbuf_release(&buf); +} + +static void remove_credential(const char *fn, struct credential *c) +{ + /* + * Sanity check that we actually have something to match + * against. The input we get is a restrictive pattern, + * so technically a blank credential means "erase everything". + * But it is too easy to accidentally send this, since it is equivalent + * to empty input. So explicitly disallow it, and require that the + * pattern have some actual content to match. + */ + if (c->protocol || c->host || c->path || c->username) + rewrite_credential_file(fn, c, NULL); +} + +static int lookup_credential(const char *fn, struct credential *c) +{ + parse_credential_file(fn, c, print_entry, NULL); + return c->username && c->password; +} + +int main(int argc, const char **argv) +{ + const char * const usage[] = { + "git credential-store [options] <action>", + NULL + }; + const char *op; + struct credential c = CREDENTIAL_INIT; + char *file = NULL; + struct option options[] = { + OPT_STRING(0, "file", &file, "path", + "fetch and store credentials in <path>"), + OPT_END() + }; + + umask(077); + + argc = parse_options(argc, argv, NULL, options, usage, 0); + if (argc != 1) + usage_with_options(usage, options); + op = argv[0]; + + if (!file) + file = expand_user_path("~/.git-credentials"); + if (!file) + die("unable to set up default path; use --file"); + + if (credential_read(&c, stdin) < 0) + die("unable to read credential"); + + if (!strcmp(op, "get")) + lookup_credential(file, &c); + else if (!strcmp(op, "erase")) + remove_credential(file, &c); + else if (!strcmp(op, "store")) + store_credential(file, &c); + else + ; /* Ignore unknown operation. */ + + return 0; +} diff --git a/credential.c b/credential.c new file mode 100644 index 0000000000..62d1c56819 --- /dev/null +++ b/credential.c @@ -0,0 +1,365 @@ +#include "cache.h" +#include "credential.h" +#include "string-list.h" +#include "run-command.h" +#include "url.h" +#include "prompt.h" + +void credential_init(struct credential *c) +{ + memset(c, 0, sizeof(*c)); + c->helpers.strdup_strings = 1; +} + +void credential_clear(struct credential *c) +{ + free(c->protocol); + free(c->host); + free(c->path); + free(c->username); + free(c->password); + string_list_clear(&c->helpers, 0); + + credential_init(c); +} + +int credential_match(const struct credential *want, + const struct credential *have) +{ +#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x))) + return CHECK(protocol) && + CHECK(host) && + CHECK(path) && + CHECK(username); +#undef CHECK +} + +static int credential_config_callback(const char *var, const char *value, + void *data) +{ + struct credential *c = data; + const char *key, *dot; + + key = skip_prefix(var, "credential."); + if (!key) + return 0; + + if (!value) + return config_error_nonbool(var); + + dot = strrchr(key, '.'); + if (dot) { + struct credential want = CREDENTIAL_INIT; + char *url = xmemdupz(key, dot - key); + int matched; + + credential_from_url(&want, url); + matched = credential_match(&want, c); + + credential_clear(&want); + free(url); + + if (!matched) + return 0; + key = dot + 1; + } + + if (!strcmp(key, "helper")) + string_list_append(&c->helpers, value); + else if (!strcmp(key, "username")) { + if (!c->username) + c->username = xstrdup(value); + } + else if (!strcmp(key, "usehttppath")) + c->use_http_path = git_config_bool(var, value); + + return 0; +} + +static int proto_is_http(const char *s) +{ + if (!s) + return 0; + return !strcmp(s, "https") || !strcmp(s, "http"); +} + +static void credential_apply_config(struct credential *c) +{ + if (c->configured) + return; + git_config(credential_config_callback, c); + c->configured = 1; + + if (!c->use_http_path && proto_is_http(c->protocol)) { + free(c->path); + c->path = NULL; + } +} + +static void credential_describe(struct credential *c, struct strbuf *out) +{ + if (!c->protocol) + return; + strbuf_addf(out, "%s://", c->protocol); + if (c->username && *c->username) + strbuf_addf(out, "%s@", c->username); + if (c->host) + strbuf_addstr(out, c->host); + if (c->path) + strbuf_addf(out, "/%s", c->path); +} + +static char *credential_ask_one(const char *what, struct credential *c, + int flags) +{ + struct strbuf desc = STRBUF_INIT; + struct strbuf prompt = STRBUF_INIT; + char *r; + + credential_describe(c, &desc); + if (desc.len) + strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf); + else + strbuf_addf(&prompt, "%s: ", what); + + r = git_prompt(prompt.buf, flags); + + strbuf_release(&desc); + strbuf_release(&prompt); + return xstrdup(r); +} + +static void credential_getpass(struct credential *c) +{ + if (!c->username) + c->username = credential_ask_one("Username", c, + PROMPT_ASKPASS|PROMPT_ECHO); + if (!c->password) + c->password = credential_ask_one("Password", c, + PROMPT_ASKPASS); +} + +int credential_read(struct credential *c, FILE *fp) +{ + struct strbuf line = STRBUF_INIT; + + while (strbuf_getline(&line, fp, '\n') != EOF) { + char *key = line.buf; + char *value = strchr(key, '='); + + if (!line.len) + break; + + if (!value) { + warning("invalid credential line: %s", key); + strbuf_release(&line); + return -1; + } + *value++ = '\0'; + + if (!strcmp(key, "username")) { + free(c->username); + c->username = xstrdup(value); + } else if (!strcmp(key, "password")) { + free(c->password); + c->password = xstrdup(value); + } else if (!strcmp(key, "protocol")) { + free(c->protocol); + c->protocol = xstrdup(value); + } else if (!strcmp(key, "host")) { + free(c->host); + c->host = xstrdup(value); + } else if (!strcmp(key, "path")) { + free(c->path); + c->path = xstrdup(value); + } + /* + * Ignore other lines; we don't know what they mean, but + * this future-proofs us when later versions of git do + * learn new lines, and the helpers are updated to match. + */ + } + + strbuf_release(&line); + return 0; +} + +static void credential_write_item(FILE *fp, const char *key, const char *value) +{ + if (!value) + return; + fprintf(fp, "%s=%s\n", key, value); +} + +static void credential_write(const struct credential *c, FILE *fp) +{ + credential_write_item(fp, "protocol", c->protocol); + credential_write_item(fp, "host", c->host); + credential_write_item(fp, "path", c->path); + credential_write_item(fp, "username", c->username); + credential_write_item(fp, "password", c->password); +} + +static int run_credential_helper(struct credential *c, + const char *cmd, + int want_output) +{ + struct child_process helper; + const char *argv[] = { NULL, NULL }; + FILE *fp; + + memset(&helper, 0, sizeof(helper)); + argv[0] = cmd; + helper.argv = argv; + helper.use_shell = 1; + helper.in = -1; + if (want_output) + helper.out = -1; + else + helper.no_stdout = 1; + + if (start_command(&helper) < 0) + return -1; + + fp = xfdopen(helper.in, "w"); + credential_write(c, fp); + fclose(fp); + + if (want_output) { + int r; + fp = xfdopen(helper.out, "r"); + r = credential_read(c, fp); + fclose(fp); + if (r < 0) { + finish_command(&helper); + return -1; + } + } + + if (finish_command(&helper)) + return -1; + return 0; +} + +static int credential_do(struct credential *c, const char *helper, + const char *operation) +{ + struct strbuf cmd = STRBUF_INIT; + int r; + + if (helper[0] == '!') + strbuf_addstr(&cmd, helper + 1); + else if (is_absolute_path(helper)) + strbuf_addstr(&cmd, helper); + else + strbuf_addf(&cmd, "git credential-%s", helper); + + strbuf_addf(&cmd, " %s", operation); + r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get")); + + strbuf_release(&cmd); + return r; +} + +void credential_fill(struct credential *c) +{ + int i; + + if (c->username && c->password) + return; + + credential_apply_config(c); + + for (i = 0; i < c->helpers.nr; i++) { + credential_do(c, c->helpers.items[i].string, "get"); + if (c->username && c->password) + return; + } + + credential_getpass(c); + if (!c->username && !c->password) + die("unable to get password from user"); +} + +void credential_approve(struct credential *c) +{ + int i; + + if (c->approved) + return; + if (!c->username || !c->password) + return; + + credential_apply_config(c); + + for (i = 0; i < c->helpers.nr; i++) + credential_do(c, c->helpers.items[i].string, "store"); + c->approved = 1; +} + +void credential_reject(struct credential *c) +{ + int i; + + credential_apply_config(c); + + for (i = 0; i < c->helpers.nr; i++) + credential_do(c, c->helpers.items[i].string, "erase"); + + free(c->username); + c->username = NULL; + free(c->password); + c->password = NULL; + c->approved = 0; +} + +void credential_from_url(struct credential *c, const char *url) +{ + const char *at, *colon, *cp, *slash, *host, *proto_end; + + credential_clear(c); + + /* + * Match one of: + * (1) proto://<host>/... + * (2) proto://<user>@<host>/... + * (3) proto://<user>:<pass>@<host>/... + */ + proto_end = strstr(url, "://"); + if (!proto_end) + return; + cp = proto_end + 3; + at = strchr(cp, '@'); + colon = strchr(cp, ':'); + slash = strchrnul(cp, '/'); + + if (!at || slash <= at) { + /* Case (1) */ + host = cp; + } + else if (!colon || at <= colon) { + /* Case (2) */ + c->username = url_decode_mem(cp, at - cp); + host = at + 1; + } else { + /* Case (3) */ + c->username = url_decode_mem(cp, colon - cp); + c->password = url_decode_mem(colon + 1, at - (colon + 1)); + host = at + 1; + } + + if (proto_end - url > 0) + c->protocol = xmemdupz(url, proto_end - url); + if (slash - host > 0) + c->host = url_decode_mem(host, slash - host); + /* Trim leading and trailing slashes from path */ + while (*slash == '/') + slash++; + if (*slash) { + char *p; + c->path = url_decode(slash); + p = c->path + strlen(c->path) - 1; + while (p > c->path && *p == '/') + *p-- = '\0'; + } +} diff --git a/credential.h b/credential.h new file mode 100644 index 0000000000..96ea41bd1c --- /dev/null +++ b/credential.h @@ -0,0 +1,33 @@ +#ifndef CREDENTIAL_H +#define CREDENTIAL_H + +#include "string-list.h" + +struct credential { + struct string_list helpers; + unsigned approved:1, + configured:1, + use_http_path:1; + + char *username; + char *password; + char *protocol; + char *host; + char *path; +}; + +#define CREDENTIAL_INIT { STRING_LIST_INIT_DUP } + +void credential_init(struct credential *); +void credential_clear(struct credential *); + +void credential_fill(struct credential *); +void credential_approve(struct credential *); +void credential_reject(struct credential *); + +int credential_read(struct credential *, FILE *); +void credential_from_url(struct credential *, const char *url); +int credential_match(const struct credential *have, + const struct credential *want); + +#endif /* CREDENTIAL_H */ diff --git a/csum-file.c b/csum-file.c index fc97d6e045..53f5375b6c 100644 --- a/csum-file.c +++ b/csum-file.c @@ -158,6 +158,26 @@ struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp return f; } +void sha1file_checkpoint(struct sha1file *f, struct sha1file_checkpoint *checkpoint) +{ + sha1flush(f); + checkpoint->offset = f->total; + checkpoint->ctx = f->ctx; +} + +int sha1file_truncate(struct sha1file *f, struct sha1file_checkpoint *checkpoint) +{ + off_t offset = checkpoint->offset; + + if (ftruncate(f->fd, offset) || + lseek(f->fd, offset, SEEK_SET) != offset) + return -1; + f->total = offset; + f->ctx = checkpoint->ctx; + f->offset = 0; /* sha1flush() was called in checkpoint */ + return 0; +} + void crc32_begin(struct sha1file *f) { f->crc32 = crc32(0, NULL, 0); diff --git a/csum-file.h b/csum-file.h index 6a7967c6bf..3b540bdc21 100644 --- a/csum-file.h +++ b/csum-file.h @@ -17,6 +17,15 @@ struct sha1file { unsigned char buffer[8192]; }; +/* Checkpoint */ +struct sha1file_checkpoint { + off_t offset; + git_SHA_CTX ctx; +}; + +extern void sha1file_checkpoint(struct sha1file *, struct sha1file_checkpoint *); +extern int sha1file_truncate(struct sha1file *, struct sha1file_checkpoint *); + /* sha1close flags */ #define CSUM_CLOSE 1 #define CSUM_FSYNC 2 @@ -20,6 +20,7 @@ static int log_syslog; static int verbose; static int reuseaddr; +static int informative_errors; static const char daemon_usage[] = "git daemon [--verbose] [--syslog] [--export-all]\n" @@ -108,11 +109,11 @@ static void NORETURN daemon_die(const char *err, va_list params) exit(1); } -static char *path_ok(char *directory) +static const char *path_ok(char *directory) { static char rpath[PATH_MAX]; static char interp_path[PATH_MAX]; - char *path; + const char *path; char *dir; dir = directory; @@ -247,6 +248,14 @@ static int git_daemon_config(const char *var, const char *value, void *cb) return 0; } +static int daemon_error(const char *dir, const char *msg) +{ + if (!informative_errors) + msg = "access denied or repository not exported"; + packet_write(1, "ERR %s: %s", msg, dir); + return -1; +} + static int run_service(char *dir, struct daemon_service *service) { const char *path; @@ -257,11 +266,11 @@ static int run_service(char *dir, struct daemon_service *service) if (!enabled && !service->overridable) { logerror("'%s': service not enabled.", service->name); errno = EACCES; - return -1; + return daemon_error(dir, "service not enabled"); } if (!(path = path_ok(dir))) - return -1; + return daemon_error(dir, "no such repository"); /* * Security on the cheap. @@ -277,7 +286,7 @@ static int run_service(char *dir, struct daemon_service *service) if (!export_all_trees && access("git-daemon-export-ok", F_OK)) { logerror("'%s': repository not exported.", path); errno = EACCES; - return -1; + return daemon_error(dir, "repository not exported"); } if (service->overridable) { @@ -291,7 +300,7 @@ static int run_service(char *dir, struct daemon_service *service) logerror("'%s': service not enabled for '%s'", service->name, path); errno = EACCES; - return -1; + return daemon_error(dir, "service not enabled"); } /* @@ -734,6 +743,29 @@ struct socketlist { size_t alloc; }; +static const char *ip2str(int family, struct sockaddr *sin, socklen_t len) +{ +#ifdef NO_IPV6 + static char ip[INET_ADDRSTRLEN]; +#else + static char ip[INET6_ADDRSTRLEN]; +#endif + + switch (family) { +#ifndef NO_IPV6 + case AF_INET6: + inet_ntop(family, &((struct sockaddr_in6*)sin)->sin6_addr, ip, len); + break; +#endif + case AF_INET: + inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len); + break; + default: + strcpy(ip, "<unknown>"); + } + return ip; +} + #ifndef NO_IPV6 static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist) @@ -780,15 +812,22 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis #endif if (set_reuse_addr(sockfd)) { + logerror("Could not set SO_REUSEADDR: %s", strerror(errno)); close(sockfd); continue; } if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) { + logerror("Could not bind to %s: %s", + ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen), + strerror(errno)); close(sockfd); continue; /* not fatal */ } if (listen(sockfd, 5) < 0) { + logerror("Could not listen to %s: %s", + ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen), + strerror(errno)); close(sockfd); continue; /* not fatal */ } @@ -835,16 +874,23 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis return 0; if (set_reuse_addr(sockfd)) { + logerror("Could not set SO_REUSEADDR: %s", strerror(errno)); close(sockfd); return 0; } if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) { + logerror("Could not listen to %s: %s", + ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)), + strerror(errno)); close(sockfd); return 0; } if (listen(sockfd, 5) < 0) { + logerror("Could not listen to %s: %s", + ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)), + strerror(errno)); close(sockfd); return 0; } @@ -1053,6 +1099,8 @@ int main(int argc, char **argv) struct credentials *cred = NULL; int i; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); for (i = 1; i < argc; i++) { @@ -1167,6 +1215,14 @@ int main(int argc, char **argv) make_service_overridable(arg + 18, 0); continue; } + if (!prefixcmp(arg, "--informative-errors")) { + informative_errors = 1; + continue; + } + if (!prefixcmp(arg, "--no-informative-errors")) { + informative_errors = 0; + continue; + } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; break; @@ -552,23 +552,35 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt static int match_tz(const char *date, int *offp) { char *end; - int offset = strtoul(date+1, &end, 10); - int min, hour; - int n = end - date - 1; + int hour = strtoul(date + 1, &end, 10); + int n = end - (date + 1); + int min = 0; - min = offset % 100; - hour = offset / 100; + if (n == 4) { + /* hhmm */ + min = hour % 100; + hour = hour / 100; + } else if (n != 2) { + min = 99; /* random crap */ + } else if (*end == ':') { + /* hh:mm? */ + min = strtoul(end + 1, &end, 10); + if (end - (date + 1) != 5) + min = 99; /* random crap */ + } /* otherwise we parsed "hh" */ /* - * Don't accept any random crap.. At least 3 digits, and - * a valid minute. We might want to check that the minutes - * are divisible by 30 or something too. + * Don't accept any random crap. Even though some places have + * offset larger than 12 hours (e.g. Pacific/Kiritimati is at + * UTC+14), there is something wrong if hour part is much + * larger than that. We might also want to check that the + * minutes are divisible by 15 or something too. (Offset of + * Kathmandu, Nepal is UTC+5:45) */ - if (min < 60 && n > 2) { - offset = hour*60+min; + if (min < 60 && hour < 24) { + int offset = hour * 60 + min; if (*date == '-') offset = -offset; - *offp = offset; } return end - date; diff --git a/diff-lib.c b/diff-lib.c index f8454dd291..62f4cd94cf 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -289,7 +289,7 @@ static void show_new_file(struct rev_info *revs, /* * New file in the index: it might actually be different in - * the working copy. + * the working tree. */ if (get_stat_data(new, &sha1, &mode, cached, match_missing, &dirty_submodule, &revs->diffopt) < 0) @@ -468,6 +468,7 @@ static int diff_cache(struct rev_info *revs, opts.unpack_data = revs; opts.src_index = &the_index; opts.dst_index = NULL; + opts.pathspec = &revs->diffopt.pathspec; init_tree_desc(&t, tree->buffer, tree->size); return unpack_trees(1, &t, &opts); @@ -2169,6 +2169,8 @@ static void builtin_diff(const char *name_a, xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; + if (DIFF_OPT_TST(o, FUNCCONTEXT)) + xecfg.flags |= XDL_EMIT_FUNCCONTEXT; if (pe) xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags); if (!diffopts) @@ -2274,6 +2276,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = o->xdl_opts; + xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, &xpp, &xecfg); } @@ -3385,6 +3389,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } /* xdiff options */ + else if (!strcmp(arg, "--minimal")) + DIFF_XDL_SET(options, NEED_MINIMAL); + else if (!strcmp(arg, "--no-minimal")) + DIFF_XDL_CLR(options, NEED_MINIMAL); else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) DIFF_XDL_SET(options, IGNORE_WHITESPACE); else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) @@ -3530,6 +3538,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (opt_arg(arg, '\0', "inter-hunk-context", &options->interhunkcontext)) ; + else if (!strcmp(arg, "-W")) + DIFF_OPT_SET(options, FUNCCONTEXT); + else if (!strcmp(arg, "--function-context")) + DIFF_OPT_SET(options, FUNCCONTEXT); + else if (!strcmp(arg, "--no-function-context")) + DIFF_OPT_CLR(options, FUNCCONTEXT); else if ((argcount = parse_long_opt("output", av, &optarg))) { options->file = fopen(optarg, "w"); if (!options->file) @@ -79,6 +79,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) #define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27) #define DIFF_OPT_DIRSTAT_BY_LINE (1 << 28) +#define DIFF_OPT_FUNCCONTEXT (1 << 29) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index c3760cfefd..380a837b5b 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -8,6 +8,46 @@ #include "xdiff-interface.h" #include "kwset.h" +typedef int (*pickaxe_fn)(struct diff_filepair *p, struct diff_options *o, regex_t *regexp, kwset_t kws); + +static void pickaxe(struct diff_queue_struct *q, struct diff_options *o, + regex_t *regexp, kwset_t kws, pickaxe_fn fn) +{ + int i; + struct diff_queue_struct outq; + + DIFF_QUEUE_CLEAR(&outq); + + if (o->pickaxe_opts & DIFF_PICKAXE_ALL) { + /* Showing the whole changeset if needle exists */ + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (fn(p, o, regexp, kws)) + return; /* do not munge the queue */ + } + + /* + * Otherwise we will clear the whole queue by copying + * the empty outq at the end of this function, but + * first clear the current entries in the queue. + */ + for (i = 0; i < q->nr; i++) + diff_free_filepair(q->queue[i]); + } else { + /* Showing only the filepairs that has the needle */ + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (fn(p, o, regexp, kws)) + diff_q(&outq, p); + else + diff_free_filepair(p); + } + } + + free(q->queue); + *q = outq; +} + struct diffgrep_cb { regex_t *regexp; int hit; @@ -45,7 +85,8 @@ static void fill_one(struct diff_filespec *one, } } -static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o) +static int diff_grep(struct diff_filepair *p, struct diff_options *o, + regex_t *regexp, kwset_t kws) { regmatch_t regmatch; struct userdiff_driver *textconv_one = NULL; @@ -95,12 +136,8 @@ static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_optio static void diffcore_pickaxe_grep(struct diff_options *o) { - struct diff_queue_struct *q = &diff_queued_diff; - int i, has_changes, err; + int err; regex_t regex; - struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; err = regcomp(®ex, o->pickaxe, REG_EXTENDED | REG_NEWLINE); if (err) { @@ -110,51 +147,21 @@ static void diffcore_pickaxe_grep(struct diff_options *o) die("invalid log-grep regex: %s", errbuf); } - if (o->pickaxe_opts & DIFF_PICKAXE_ALL) { - /* Showing the whole changeset if needle exists */ - for (i = has_changes = 0; !has_changes && i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (diff_grep(p, ®ex, o)) - has_changes++; - } - if (has_changes) - return; /* do not munge the queue */ - - /* - * Otherwise we will clear the whole queue by copying - * the empty outq at the end of this function, but - * first clear the current entries in the queue. - */ - for (i = 0; i < q->nr; i++) - diff_free_filepair(q->queue[i]); - } else { - /* Showing only the filepairs that has the needle */ - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (diff_grep(p, ®ex, o)) - diff_q(&outq, p); - else - diff_free_filepair(p); - } - } + pickaxe(&diff_queued_diff, o, ®ex, NULL, diff_grep); regfree(®ex); - - free(q->queue); - *q = outq; return; } -static unsigned int contains(struct diff_filespec *one, - const char *needle, unsigned long len, +static unsigned int contains(struct diff_filespec *one, struct diff_options *o, regex_t *regexp, kwset_t kws) { unsigned int cnt; unsigned long sz; const char *data; - if (diff_populate_filespec(one, 0)) + if (!o->pickaxe[0]) return 0; - if (!len) + if (diff_populate_filespec(one, 0)) return 0; sz = one->size; @@ -176,14 +183,15 @@ static unsigned int contains(struct diff_filespec *one, } else { /* Classic exact string match */ while (sz) { - size_t offset = kwsexec(kws, data, sz, NULL); + struct kwsmatch kwsm; + size_t offset = kwsexec(kws, data, sz, &kwsm); const char *found; if (offset == -1) break; else found = data + offset; - sz -= found - data + len; - data = found + len; + sz -= found - data + kwsm.size[0]; + data = found + kwsm.size[0]; cnt++; } } @@ -191,17 +199,31 @@ static unsigned int contains(struct diff_filespec *one, return cnt; } +static int has_changes(struct diff_filepair *p, struct diff_options *o, + regex_t *regexp, kwset_t kws) +{ + if (!DIFF_FILE_VALID(p->one)) { + if (!DIFF_FILE_VALID(p->two)) + return 0; /* ignore unmerged */ + /* created */ + return contains(p->two, o, regexp, kws) != 0; + } + if (!DIFF_FILE_VALID(p->two)) + return contains(p->one, o, regexp, kws) != 0; + if (!diff_unmodified_pair(p)) { + return contains(p->one, o, regexp, kws) != + contains(p->two, o, regexp, kws); + } + return 0; +} + static void diffcore_pickaxe_count(struct diff_options *o) { const char *needle = o->pickaxe; int opts = o->pickaxe_opts; - struct diff_queue_struct *q = &diff_queued_diff; unsigned long len = strlen(needle); - int i, has_changes; regex_t regex, *regexp = NULL; kwset_t kws = NULL; - struct diff_queue_struct outq; - DIFF_QUEUE_CLEAR(&outq); if (opts & DIFF_PICKAXE_REGEX) { int err; @@ -220,72 +242,12 @@ static void diffcore_pickaxe_count(struct diff_options *o) kwsprep(kws); } - if (opts & DIFF_PICKAXE_ALL) { - /* Showing the whole changeset if needle exists */ - for (i = has_changes = 0; !has_changes && i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (!DIFF_FILE_VALID(p->one)) { - if (!DIFF_FILE_VALID(p->two)) - continue; /* ignore unmerged */ - /* created */ - if (contains(p->two, needle, len, regexp, kws)) - has_changes++; - } - else if (!DIFF_FILE_VALID(p->two)) { - if (contains(p->one, needle, len, regexp, kws)) - has_changes++; - } - else if (!diff_unmodified_pair(p) && - contains(p->one, needle, len, regexp, kws) != - contains(p->two, needle, len, regexp, kws)) - has_changes++; - } - if (has_changes) - return; /* not munge the queue */ - - /* otherwise we will clear the whole queue - * by copying the empty outq at the end of this - * function, but first clear the current entries - * in the queue. - */ - for (i = 0; i < q->nr; i++) - diff_free_filepair(q->queue[i]); - } - else - /* Showing only the filepairs that has the needle */ - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - has_changes = 0; - if (!DIFF_FILE_VALID(p->one)) { - if (!DIFF_FILE_VALID(p->two)) - ; /* ignore unmerged */ - /* created */ - else if (contains(p->two, needle, len, regexp, - kws)) - has_changes = 1; - } - else if (!DIFF_FILE_VALID(p->two)) { - if (contains(p->one, needle, len, regexp, kws)) - has_changes = 1; - } - else if (!diff_unmodified_pair(p) && - contains(p->one, needle, len, regexp, kws) != - contains(p->two, needle, len, regexp, kws)) - has_changes = 1; - - if (has_changes) - diff_q(&outq, p); - else - diff_free_filepair(p); - } + pickaxe(&diff_queued_diff, o, regexp, kws, has_changes); if (opts & DIFF_PICKAXE_REGEX) regfree(®ex); else kwsfree(kws); - - free(q->queue); - *q = outq; return; } @@ -34,49 +34,54 @@ int fnmatch_icase(const char *pattern, const char *string, int flags) return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0)); } -static int common_prefix(const char **pathspec) +static size_t common_prefix_len(const char **pathspec) { - const char *path, *slash, *next; - int prefix; + const char *n, *first; + size_t max = 0; if (!pathspec) - return 0; + return max; + + first = *pathspec; + while ((n = *pathspec++)) { + size_t i, len = 0; + for (i = 0; first == n || i < max; i++) { + char c = n[i]; + if (!c || c != first[i] || is_glob_special(c)) + break; + if (c == '/') + len = i + 1; + } + if (first == n || len < max) { + max = len; + if (!max) + break; + } + } + return max; +} - path = *pathspec; - slash = strrchr(path, '/'); - if (!slash) - return 0; +/* + * Returns a copy of the longest leading path common among all + * pathspecs. + */ +char *common_prefix(const char **pathspec) +{ + unsigned long len = common_prefix_len(pathspec); - /* - * The first 'prefix' characters of 'path' are common leading - * path components among the pathspecs we have seen so far, - * including the trailing slash. - */ - prefix = slash - path + 1; - while ((next = *++pathspec) != NULL) { - int len, last_matching_slash = -1; - for (len = 0; len < prefix && next[len] == path[len]; len++) - if (next[len] == '/') - last_matching_slash = len; - if (len == prefix) - continue; - if (last_matching_slash < 0) - return 0; - prefix = last_matching_slash + 1; - } - return prefix; + return len ? xmemdupz(*pathspec, len) : NULL; } int fill_directory(struct dir_struct *dir, const char **pathspec) { const char *path; - int len; + size_t len; /* * Calculate common prefix for the pathspec, and * use that to optimize the directory walk */ - len = common_prefix(pathspec); + len = common_prefix_len(pathspec); path = ""; if (len) @@ -84,6 +89,8 @@ int fill_directory(struct dir_struct *dir, const char **pathspec) /* Read the directory and prune it */ read_directory(dir, path, len, pathspec); + if (*path) + free((char *)path); return len; } @@ -961,34 +968,34 @@ static int read_directory_recursive(struct dir_struct *dir, { DIR *fdir = opendir(*base ? base : "."); int contents = 0; + struct dirent *de; + char path[PATH_MAX + 1]; - if (fdir) { - struct dirent *de; - char path[PATH_MAX + 1]; - memcpy(path, base, baselen); - - while ((de = readdir(fdir)) != NULL) { - int len; - switch (treat_path(dir, de, path, sizeof(path), - baselen, simplify, &len)) { - case path_recurse: - contents += read_directory_recursive - (dir, path, len, 0, simplify); - continue; - case path_ignored: - continue; - case path_handled: - break; - } - contents++; - if (check_only) - goto exit_early; - else - dir_add_name(dir, path, len); + if (!fdir) + return 0; + + memcpy(path, base, baselen); + + while ((de = readdir(fdir)) != NULL) { + int len; + switch (treat_path(dir, de, path, sizeof(path), + baselen, simplify, &len)) { + case path_recurse: + contents += read_directory_recursive(dir, path, len, 0, simplify); + continue; + case path_ignored: + continue; + case path_handled: + break; } -exit_early: - closedir(fdir); + contents++; + if (check_only) + goto exit_early; + else + dir_add_name(dir, path, len); } +exit_early: + closedir(fdir); return contents; } @@ -64,6 +64,7 @@ struct dir_struct { #define MATCHED_RECURSIVELY 1 #define MATCHED_FNMATCH 2 #define MATCHED_EXACTLY 3 +extern char *common_prefix(const char **pathspec); extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); extern int match_pathspec_depth(const struct pathspec *pathspec, const char *name, int namelen, diff --git a/environment.c b/environment.c index e96edcfebc..c93b8f44df 100644 --- a/environment.c +++ b/environment.c @@ -9,6 +9,7 @@ */ #include "cache.h" #include "refs.h" +#include "fmt-merge-msg.h" char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; @@ -29,6 +30,7 @@ const char *git_log_output_encoding; int shared_repository = PERM_UMASK; const char *apply_default_whitespace; const char *apply_default_ignorewhitespace; +const char *git_attributes_file; int zlib_compression_level = Z_BEST_SPEED; int core_compression_level; int core_compression_seen; @@ -58,7 +60,9 @@ enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; char *notes_ref_name; int grafts_replace_parents = 1; int core_apply_sparse_checkout; +int merge_log_config = -1; struct startup_info *startup_info; +unsigned long pack_size_limit_cfg; /* Parallel index stat data preload? */ int core_preload_index = 0; @@ -106,7 +110,7 @@ static char *expand_namespace(const char *raw_namespace) if (strcmp((*c)->buf, "/") != 0) strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf); strbuf_list_free(components); - if (check_ref_format(buf.buf) != CHECK_REF_FORMAT_OK) + if (check_refname_format(buf.buf, 0)) die("bad git namespace path \"%s\"", raw_namespace); strbuf_addch(&buf, '/'); return strbuf_detach(&buf, NULL); diff --git a/fast-import.c b/fast-import.c index 742e7da6b8..4b9c4b73a0 100644 --- a/fast-import.c +++ b/fast-import.c @@ -722,13 +722,8 @@ static struct branch *new_branch(const char *name) if (b) die("Invalid attempt to create duplicate branch: %s", name); - switch (check_ref_format(name)) { - case 0: break; /* its valid */ - case CHECK_REF_FORMAT_ONELEVEL: - break; /* valid, but too few '/', allow anyway */ - default: + if (check_refname_format(name, REFNAME_ALLOW_ONELEVEL)) die("Branch name doesn't conform to GIT standards: %s", name); - } b = pool_calloc(1, sizeof(struct branch)); b->name = pool_strdup(name); @@ -1148,17 +1143,11 @@ static int store_object( return 0; } -static void truncate_pack(off_t to, git_SHA_CTX *ctx) +static void truncate_pack(struct sha1file_checkpoint *checkpoint) { - if (ftruncate(pack_data->pack_fd, to) - || lseek(pack_data->pack_fd, to, SEEK_SET) != to) + if (sha1file_truncate(pack_file, checkpoint)) die_errno("cannot truncate pack to skip duplicate"); - pack_size = to; - - /* yes this is a layering violation */ - pack_file->total = to; - pack_file->offset = 0; - pack_file->ctx = *ctx; + pack_size = checkpoint->offset; } static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) @@ -1171,8 +1160,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) unsigned long hdrlen; off_t offset; git_SHA_CTX c; - git_SHA_CTX pack_file_ctx; git_zstream s; + struct sha1file_checkpoint checkpoint; int status = Z_OK; /* Determine if we should auto-checkpoint. */ @@ -1180,11 +1169,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) || (pack_size + 60 + len) < pack_size) cycle_packfile(); - offset = pack_size; - - /* preserve the pack_file SHA1 ctx in case we have to truncate later */ - sha1flush(pack_file); - pack_file_ctx = pack_file->ctx; + sha1file_checkpoint(pack_file, &checkpoint); + offset = checkpoint.offset; hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1; if (out_sz <= hdrlen) @@ -1250,14 +1236,14 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) if (e->idx.offset) { duplicate_count_by_type[OBJ_BLOB]++; - truncate_pack(offset, &pack_file_ctx); + truncate_pack(&checkpoint); } else if (find_sha1_pack(sha1, packed_git)) { e->type = OBJ_BLOB; e->pack_id = MAX_PACK_ID; e->idx.offset = 1; /* just not zero! */ duplicate_count_by_type[OBJ_BLOB]++; - truncate_pack(offset, &pack_file_ctx); + truncate_pack(&checkpoint); } else { e->depth = 0; @@ -2178,6 +2164,11 @@ static uintmax_t do_change_note_fanout( if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) { /* This is a note entry */ + if (fanout == 0xff) { + /* Counting mode, no rename */ + num_notes++; + continue; + } construct_path_with_fanout(hex_sha1, fanout, realpath); if (!strcmp(fullpath, realpath)) { /* Note entry is in correct location */ @@ -2384,7 +2375,7 @@ static void file_change_cr(struct branch *b, int rename) leaf.tree); } -static void note_change_n(struct branch *b, unsigned char old_fanout) +static void note_change_n(struct branch *b, unsigned char *old_fanout) { const char *p = command_buf.buf + 2; static struct strbuf uq = STRBUF_INIT; @@ -2395,6 +2386,23 @@ static void note_change_n(struct branch *b, unsigned char old_fanout) uint16_t inline_data = 0; unsigned char new_fanout; + /* + * When loading a branch, we don't traverse its tree to count the real + * number of notes (too expensive to do this for all non-note refs). + * This means that recently loaded notes refs might incorrectly have + * b->num_notes == 0, and consequently, old_fanout might be wrong. + * + * Fix this by traversing the tree and counting the number of notes + * when b->num_notes == 0. If the notes tree is truly empty, the + * calculation should not take long. + */ + if (b->num_notes == 0 && *old_fanout == 0) { + /* Invoke change_note_fanout() in "counting mode". */ + b->num_notes = change_note_fanout(&b->branch_tree, 0xff); + *old_fanout = convert_num_notes_to_fanout(b->num_notes); + } + + /* Now parse the notemodify command. */ /* <dataref> or 'inline' */ if (*p == ':') { char *x; @@ -2416,6 +2424,8 @@ static void note_change_n(struct branch *b, unsigned char old_fanout) /* <committish> */ s = lookup_branch(p); if (s) { + if (is_null_sha1(s->sha1)) + die("Can't add a note on empty branch."); hashcpy(commit_sha1, s->sha1); } else if (*p == ':') { uintmax_t commit_mark = strtoumax(p + 1, NULL, 10); @@ -2453,7 +2463,7 @@ static void note_change_n(struct branch *b, unsigned char old_fanout) typename(type), command_buf.buf); } - construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path); + construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path); if (tree_content_remove(&b->branch_tree, path, NULL)) b->num_notes--; @@ -2640,7 +2650,7 @@ static void parse_new_commit(void) else if (!prefixcmp(command_buf.buf, "C ")) file_change_cr(b, 0); else if (!prefixcmp(command_buf.buf, "N ")) - note_change_n(b, prev_fanout); + note_change_n(b, &prev_fanout); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); else if (!prefixcmp(command_buf.buf, "ls ")) @@ -2717,6 +2727,8 @@ static void parse_new_tag(void) from = strchr(command_buf.buf, ' ') + 1; s = lookup_branch(from); if (s) { + if (is_null_sha1(s->sha1)) + die("Can't tag an empty branch."); hashcpy(sha1, s->sha1); type = OBJ_COMMIT; } else if (*from == ':') { @@ -3293,6 +3305,8 @@ int main(int argc, const char **argv) git_extract_argv0_path(argv[0]); + git_setup_gettext(); + if (argc == 2 && !strcmp(argv[1], "-h")) usage(fast_import_usage); diff --git a/fmt-merge-msg.h b/fmt-merge-msg.h new file mode 100644 index 0000000000..b28d3a6113 --- /dev/null +++ b/fmt-merge-msg.h @@ -0,0 +1,7 @@ +#ifndef FMT_MERGE_MSG_H +#define FMT_MERGE_MSG_H + +extern int merge_log_config; +extern int fmt_merge_msg_config(const char *key, const char *value, void *cb); + +#endif /* FMT_MERGE_MSG_H */ @@ -5,6 +5,18 @@ #include "git-compat-util.h" #include "gettext.h" +#ifndef NO_GETTEXT +# include <locale.h> +# include <libintl.h> +# ifdef HAVE_LIBCHARSET_H +# include <libcharset.h> +# else +# include <langinfo.h> +# define locale_charset() nl_langinfo(CODESET) +# endif +#endif + +#ifdef GETTEXT_POISON int use_gettext_poison(void) { static int poison_requested = -1; @@ -12,3 +24,108 @@ int use_gettext_poison(void) poison_requested = getenv("GIT_GETTEXT_POISON") ? 1 : 0; return poison_requested; } +#endif + +#ifndef NO_GETTEXT +static void init_gettext_charset(const char *domain) +{ + const char *charset; + + /* + This trick arranges for messages to be emitted in the user's + requested encoding, but avoids setting LC_CTYPE from the + environment for the whole program. + + This primarily done to avoid a bug in vsnprintf in the GNU C + Library [1]. which triggered a "your vsnprintf is broken" error + on Git's own repository when inspecting v0.99.6~1 under a UTF-8 + locale. + + That commit contains a ISO-8859-1 encoded author name, which + the locale aware vsnprintf(3) won't interpolate in the format + argument, due to mismatch between the data encoding and the + locale. + + Even if it wasn't for that bug we wouldn't want to use LC_CTYPE at + this point, because it'd require auditing all the code that uses C + functions whose semantics are modified by LC_CTYPE. + + But only setting LC_MESSAGES as we do creates a problem, since + we declare the encoding of our PO files[2] the gettext + implementation will try to recode it to the user's locale, but + without LC_CTYPE it'll emit something like this on 'git init' + under the Icelandic locale: + + Bj? til t?ma Git lind ? /hlagh/.git/ + + Gettext knows about the encoding of our PO file, but we haven't + told it about the user's encoding, so all the non-US-ASCII + characters get encoded to question marks. + + But we're in luck! We can set LC_CTYPE from the environment + only while we call nl_langinfo and + bind_textdomain_codeset. That suffices to tell gettext what + encoding it should emit in, so it'll now say: + + Bjó til tóma Git lind í /hlagh/.git/ + + And the equivalent ISO-8859-1 string will be emitted under a + ISO-8859-1 locale. + + With this change way we get the advantages of setting LC_CTYPE + (talk to the user in his language/encoding), without the major + drawbacks (changed semantics for C functions we rely on). + + However foreign functions using other message catalogs that + aren't using our neat trick will still have a problem, e.g. if + we have to call perror(3): + + #include <stdio.h> + #include <locale.h> + #include <errno.h> + + int main(void) + { + setlocale(LC_MESSAGES, ""); + setlocale(LC_CTYPE, "C"); + errno = ENODEV; + perror("test"); + return 0; + } + + Running that will give you a message with question marks: + + $ LANGUAGE= LANG=de_DE.utf8 ./test + test: Kein passendes Ger?t gefunden + + In the long term we should probably see about getting that + vsnprintf bug in glibc fixed, and audit our code so it won't + fall apart under a non-C locale. + + Then we could simply set LC_CTYPE from the environment, which would + make things like the external perror(3) messages work. + + See t/t0203-gettext-setlocale-sanity.sh's "gettext.c" tests for + regression tests. + + 1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530 + 2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po + */ + setlocale(LC_CTYPE, ""); + charset = locale_charset(); + bind_textdomain_codeset(domain, charset); + setlocale(LC_CTYPE, "C"); +} + +void git_setup_gettext(void) +{ + const char *podir = getenv("GIT_TEXTDOMAINDIR"); + + if (!podir) + podir = GIT_LOCALE_PATH; + bindtextdomain("git", podir); + setlocale(LC_MESSAGES, ""); + init_gettext_charset("git"); + textdomain("git"); +} +#endif @@ -13,8 +13,29 @@ #error "namespace conflict: '_' or 'Q_' is pre-defined?" #endif +#ifndef NO_GETTEXT +# include <libintl.h> +#else +# ifdef gettext +# undef gettext +# endif +# define gettext(s) (s) +# ifdef ngettext +# undef ngettext +# endif +# define ngettext(s, p, n) ((n == 1) ? (s) : (p)) +#endif + #define FORMAT_PRESERVING(n) __attribute__((format_arg(n))) +#ifndef NO_GETTEXT +extern void git_setup_gettext(void); +#else +static inline void git_setup_gettext(void) +{ +} +#endif + #ifdef GETTEXT_POISON extern int use_gettext_poison(void); #else @@ -23,7 +44,7 @@ extern int use_gettext_poison(void); static inline FORMAT_PRESERVING(1) const char *_(const char *msgid) { - return use_gettext_poison() ? "# GETTEXT POISON #" : msgid; + return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid); } static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2) @@ -31,7 +52,7 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n) { if (use_gettext_poison()) return "# GETTEXT POISON #"; - return n == 1 ? msgid : plu; + return ngettext(msgid, plu, n); } /* Mark msgid for translation but do not translate it. */ @@ -311,6 +311,40 @@ split_patches () { this= msgnum= ;; + hg) + this=0 + for hg in "$@" + do + this=$(( $this + 1 )) + msgnum=$(printf "%0${prec}d" $this) + # hg stores changeset metadata in #-commented lines preceding + # the commit message and diff(s). The only metadata we care about + # are the User and Date (Node ID and Parent are hashes which are + # only relevant to the hg repository and thus not useful to us) + # Since we cannot guarantee that the commit message is in + # git-friendly format, we put no Subject: line and just consume + # all of the message as the body + perl -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 } + if ($subject) { print ; } + elsif (/^\# User /) { s/\# User/From:/ ; print ; } + elsif (/^\# Date /) { + my ($hashsign, $str, $time, $tz) = split ; + $tz = sprintf "%+05d", (0-$tz)/36; + print "Date: " . + strftime("%a, %d %b %Y %H:%M:%S ", + localtime($time)) + . "$tz\n"; + } elsif (/^\# /) { next ; } + else { + print "\n", $_ ; + $subject = 1; + } + ' <"$hg" >"$dotest/$msgnum" || clean_abort + done + echo "$this" >"$dotest/last" + this= + msgnum= + ;; *) if test -n "$patch_format" then @@ -496,7 +530,6 @@ else echo "$sign" >"$dotest/sign" echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" - echo "$keepcr" >"$dotest/keepcr" echo "$scissors" >"$dotest/scissors" echo "$no_inbody_headers" >"$dotest/no_inbody_headers" echo "$GIT_QUIET" >"$dotest/quiet" @@ -542,12 +575,6 @@ if test "$(cat "$dotest/keep")" = t then keep=-k fi -case "$(cat "$dotest/keepcr")" in -t) - keepcr=--keep-cr ;; -f) - keepcr=--no-keep-cr ;; -esac case "$(cat "$dotest/scissors")" in t) scissors=--scissors ;; diff --git a/git-bisect.sh b/git-bisect.sh index 2524060475..99efbe8845 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -126,7 +126,8 @@ bisect_start() { start_head=$(cat "$GIT_DIR/BISECT_START") if test "z$mode" != "z--no-checkout" then - git checkout "$start_head" -- + git checkout "$start_head" -- || + die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <validbranch>'.")" fi else # Get rev from where we start. diff --git a/git-compat-util.h b/git-compat-util.h index 5ef8ff76f6..8f3972cd32 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -116,7 +116,12 @@ #else #include <poll.h> #endif -#ifndef __MINGW32__ +#if defined(__MINGW32__) +/* pull in Windows compatibility stuff */ +#include "compat/mingw.h" +#elif defined(_MSC_VER) +#include "compat/msvc.h" +#else #include <sys/wait.h> #include <sys/resource.h> #include <sys/socket.h> @@ -130,6 +135,7 @@ #include <arpa/inet.h> #include <netdb.h> #include <pwd.h> +#include <sys/un.h> #ifndef NO_INTTYPES_H #include <inttypes.h> #else @@ -145,12 +151,6 @@ #include <grp.h> #define _ALL_SOURCE 1 #endif -#else /* __MINGW32__ */ -/* pull in Windows compatibility stuff */ -#include "compat/mingw.h" -#endif /* __MINGW32__ */ -#ifdef _MSC_VER -#include "compat/msvc.h" #endif #ifndef NO_LIBGEN_H @@ -219,7 +219,7 @@ extern char *gitbasename(char *); #define find_last_dir_sep(path) strrchr(path, '/') #endif -#if __HP_cc >= 61000 +#if defined(__HP_cc) && (__HP_cc >= 61000) #define NORETURN __attribute__((noreturn)) #define NORETURN_PTR #elif defined(__GNUC__) && !defined(NO_NORETURN) @@ -351,6 +351,8 @@ extern size_t gitstrlcpy(char *, const char *, size_t); #ifdef NO_STRTOUMAX #define strtoumax gitstrtoumax extern uintmax_t gitstrtoumax(const char *, char **, int); +#define strtoimax gitstrtoimax +extern intmax_t gitstrtoimax(const char *, char **, int); #endif #ifdef NO_STRTOK_R diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 1b8bff2cac..b8eddabc94 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -109,14 +109,14 @@ my $usage = " --strict-paths : Don't allow recursing into subdirectories\n". " --export-all : Don't check for gitcvs.enabled in config\n". " --version, -V : Print version information and exit\n". - " --help, -h, -H : Print usage information and exit\n". + " -h, -H : Print usage information and exit\n". "\n". "<directory> ... is a list of allowed directories. If no directories\n". "are given, all are allowed. This is an additional restriction, gitcvs\n". "access still needs to be enabled by the gitcvs.enabled config option.\n". "Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n"; -my @opts = ( 'help|h|H', 'version|V', +my @opts = ( 'h|H', 'version|V', 'base-path=s', 'strict-paths', 'export-all' ); GetOptions( $state, @opts ) or die $usage; diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 8452890be9..e6558d1010 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -43,12 +43,15 @@ launch_merge_tool () { printf "\nViewing: '$MERGED'\n" if use_ext_cmd then - printf "Hit return to launch '%s': " \ + printf "Launch '%s' [Y/n]: " \ "$GIT_DIFFTOOL_EXTCMD" else - printf "Hit return to launch '%s': " "$merge_tool" + printf "Launch '%s' [Y/n]: " "$merge_tool" + fi + if read ans && test "$ans" = n + then + return fi - read ans fi if use_ext_cmd diff --git a/git-difftool.perl b/git-difftool.perl index ced1615e21..09b65f1770 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -97,7 +97,7 @@ sub generate_command $prompt = 'yes'; next; } - if ($arg eq '-h' || $arg eq '--help') { + if ($arg eq '-h') { usage(); } push @command, $arg; diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 804a7f4bc9..add2c0247f 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -108,9 +108,7 @@ OPTIONS_SPEC= . git-sh-setup if [ "$(is_bare_repository)" = false ]; then - git diff-files --ignore-submodules --quiet && - git diff-index --cached --quiet HEAD -- || - die "Cannot rewrite branch(es) with a dirty working directory." + require_clean_work_tree 'rewrite branches' fi tempdir=.git-rewrite diff --git a/git-gui/.gitattributes b/git-gui/.gitattributes index f96112d47f..33d07c06bd 100644 --- a/git-gui/.gitattributes +++ b/git-gui/.gitattributes @@ -1,3 +1,4 @@ +* whitespace=indent-with-non-tab,trailing-space,space-before-tab,tabwidth=4 * encoding=US-ASCII git-gui.sh encoding=UTF-8 /po/*.po encoding=UTF-8 diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN index 1fb4d9b4b7..65709437ff 100755 --- a/git-gui/GIT-VERSION-GEN +++ b/git-gui/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=0.13.GITGUI +DEF_VER=0.16.GITGUI LF=' ' diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index fd6a43d0a2..ba4e5c1330 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -299,7 +299,9 @@ proc is_config_true {name} { global repo_config if {[catch {set v $repo_config($name)}]} { return 0 - } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} { + } + set v [string tolower $v] + if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} { return 1 } else { return 0 @@ -310,7 +312,9 @@ proc is_config_false {name} { global repo_config if {[catch {set v $repo_config($name)}]} { return 0 - } elseif {$v eq {false} || $v eq {0} || $v eq {no}} { + } + set v [string tolower $v] + if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} { return 1 } else { return 0 @@ -460,6 +464,35 @@ proc _which {what args} { return {} } +# Test a file for a hashbang to identify executable scripts on Windows. +proc is_shellscript {filename} { + if {![file exists $filename]} {return 0} + set f [open $filename r] + fconfigure $f -encoding binary + set magic [read $f 2] + close $f + return [expr {$magic eq "#!"}] +} + +# Run a command connected via pipes on stdout. +# This is for use with textconv filters and uses sh -c "..." to allow it to +# contain a command with arguments. On windows we must check for shell +# scripts specifically otherwise just call the filter command. +proc open_cmd_pipe {cmd path} { + global env + if {![file executable [shellpath]]} { + set exe [auto_execok [lindex $cmd 0]] + if {[is_shellscript [lindex $exe 0]]} { + set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path] + } else { + set run [concat $exe [lrange $cmd 1 end] $path] + } + } else { + set run [list [shellpath] -c "$cmd \"\$0\"" $path] + } + return [open |$run r] +} + proc _lappend_nice {cmd_var} { global _nice upvar $cmd_var cmd @@ -725,7 +758,10 @@ if {[is_Windows]} { gitlogo put gray26 -to 5 15 11 16 gitlogo redither - wm iconphoto . -default gitlogo + image create photo gitlogo32 -width 32 -height 32 + gitlogo32 copy gitlogo -zoom 2 2 + + wm iconphoto . -default gitlogo gitlogo32 } } @@ -846,6 +882,7 @@ set default_config(gui.fastcopyblame) false set default_config(gui.copyblamethreshold) 40 set default_config(gui.blamehistoryctx) 7 set default_config(gui.diffcontext) 5 +set default_config(gui.diffopts) {} set default_config(gui.commitmsgwidth) 75 set default_config(gui.newbranchtemplate) {} set default_config(gui.spellingdictionary) {} @@ -854,10 +891,12 @@ set default_config(gui.fontdiff) [font configure font_diff] # TODO: this option should be added to the git-config documentation set default_config(gui.maxfilesdisplayed) 5000 set default_config(gui.usettk) 1 +set default_config(gui.warndetachedcommit) 1 set font_descs { {fontui font_ui {mc "Main Font"}} {fontdiff font_diff {mc "Diff/Console Font"}} } +set default_config(gui.stageuntracked) ask ###################################################################### ## @@ -1059,6 +1098,10 @@ git-version proc _parse_config {arr_name args} { } else { set arr($name) $value } + } elseif {[regexp {^([^\n]+)$} $line line name]} { + # no value given, but interpreting them as + # boolean will be handled as true + set arr($name) {} } } } @@ -1074,6 +1117,10 @@ git-version proc _parse_config {arr_name args} { } else { set arr($name) $value } + } elseif {[regexp {^([^=]+)$} $line line name]} { + # no value given, but interpreting them as + # boolean will be handled as true + set arr($name) {} } } close $fd_rc @@ -1526,7 +1573,7 @@ proc run_prepare_commit_msg_hook {} { # prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an - # empty file but existant file. + # empty file but existent file. set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a] @@ -2473,6 +2520,7 @@ proc toggle_or_diff {w x y} { [concat $after [list ui_ready]] } } else { + set selected_paths($path) 1 show_diff $path $w $lno } } @@ -3361,6 +3409,7 @@ foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 gr $ui_diff tag configure clri3$n -background $c } $ui_diff tag configure clr1 -font font_diffbold +$ui_diff tag configure clr4 -underline 1 $ui_diff tag conf d_info -foreground blue -font font_diffbold @@ -3877,7 +3926,7 @@ after 1 { $ui_comm configure -state disabled -background gray } } -if {[is_enabled multicommit]} { +if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} { after 1000 hint_gc } if {[is_enabled retcode]} { diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 61e358f960..324f7744c4 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -22,6 +22,7 @@ field w_asim ; # text column: annotations (simple computation) field w_file ; # text column: actual file data field w_cviewer ; # pane showing commit message field finder ; # find mini-dialog frame +field gotoline ; # line goto mini-dialog frame field status ; # status mega-widget instance field old_height ; # last known height of $w.file_pane @@ -218,7 +219,8 @@ constructor new {i_commit i_path i_jump} { eval grid $w_columns $w.file_pane.out.sby -sticky nsew grid conf \ $w.file_pane.out.sbx \ - -column [expr {[llength $w_columns] - 1}] \ + -column 0 \ + -columnspan [expr {[llength $w_columns] + 1}] \ -sticky we grid columnconfigure \ $w.file_pane.out \ @@ -228,7 +230,14 @@ constructor new {i_commit i_path i_jump} { set finder [::searchbar::new \ $w.file_pane.out.ff $w_file \ - -column [expr {[llength $w_columns] - 1}] \ + -column 0 \ + -columnspan [expr {[llength $w_columns] + 1}] \ + ] + + set gotoline [::linebar::new \ + $w.file_pane.out.lf $w_file \ + -column 0 \ + -columnspan [expr {[llength $w_columns] + 1}] \ ] set w_cviewer $w.file_pane.cm.t @@ -274,7 +283,11 @@ constructor new {i_commit i_path i_jump} { $w.ctxm add command \ -label [mc "Find Text..."] \ -accelerator F7 \ - -command [list searchbar::show $finder] + -command [cb _show_finder] + $w.ctxm add command \ + -label [mc "Goto Line..."] \ + -accelerator "Ctrl-G" \ + -command [cb _show_linebar] menu $w.ctxm.enc build_encoding_menu $w.ctxm.enc [cb _setencoding] $w.ctxm add cascade \ @@ -341,10 +354,13 @@ constructor new {i_commit i_path i_jump} { bind $w_cviewer <Tab> "[list focus $w_file];break" bind $w_cviewer <Button-1> [list focus $w_cviewer] bind $w_file <Visibility> [cb _focus_search $w_file] - bind $top <F7> [list searchbar::show $finder] + bind $top <F7> [cb _show_finder] + bind $top <Key-slash> [cb _show_finder] + bind $top <Control-Key-s> [cb _show_finder] bind $top <Escape> [list searchbar::hide $finder] bind $top <F3> [list searchbar::find_next $finder] bind $top <Shift-F3> [list searchbar::find_prev $finder] + bind $top <Control-Key-g> [cb _show_linebar] catch { bind $top <Shift-Key-XF86_Switch_VT_3> [list searchbar::find_prev $finder] } grid configure $w.header -sticky ew @@ -460,14 +476,7 @@ method _load {jump} { } if {$commit eq {}} { if {$do_textconv ne 0} { - # Run textconv with sh -c "..." to allow it to - # contain command + arguments. On windows, just - # call the filter command. - if {![file executable [shellpath]]} { - set fd [open |[linsert $textconv end $path] r] - } else { - set fd [open |[list [shellpath] -c "$textconv \"\$0\"" $path] r] - } + set fd [open_cmd_pipe $textconv $path] } else { set fd [open $path r] } @@ -559,7 +568,11 @@ method _read_file {fd jump} { foreach i $w_columns {$i conf -state disabled} if {[eof $fd]} { - close $fd + fconfigure $fd -blocking 1; # enable error reporting on close + if {[catch {close $fd} err]} { + tk_messageBox -icon error -title [mc Error] \ + -message $err + } # If we don't force Tk to update the widgets *right now* # none of our jump commands will cause a change in the UI. @@ -1049,7 +1062,7 @@ method _gitkcommit {} { set radius [get_config gui.blamehistoryctx] set cmdline [list --select-commit=$cmit] - if {$radius > 0} { + if {$radius > 0} { set author_time {} set committer_time {} @@ -1157,7 +1170,7 @@ method _read_diff_load_commit {fd cparent new_path tline} { } if {[eof $fd]} { - close $fd; + close $fd set current_fd {} _load_new_commit $this \ @@ -1188,6 +1201,7 @@ method _open_tooltip {cur_w} { _hide_tooltip $this set tooltip_wm [toplevel $cur_w.tooltip -borderwidth 1] + catch {wm attributes $tooltip_wm -type tooltip} wm overrideredirect $tooltip_wm 1 wm transient $tooltip_wm [winfo toplevel $cur_w] set tooltip_t $tooltip_wm.label @@ -1298,9 +1312,9 @@ method _position_tooltip {} { set pos_y [expr {[winfo pointery .] + 10}] set g "${req_w}x${req_h}" - if {$pos_x >= 0} {append g +} + if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +} append g $pos_x - if {$pos_y >= 0} {append g +} + if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +} append g $pos_y wm geometry $tooltip_wm $g @@ -1336,4 +1350,14 @@ method _resize {new_height} { set old_height $new_height } +method _show_finder {} { + linebar::hide $gotoline + searchbar::show $finder +} + +method _show_linebar {} { + searchbar::hide $finder + linebar::show $gotoline +} + } diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index a8c6223511..0328338fda 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@ -26,8 +26,14 @@ constructor new {commit {path {}}} { wm withdraw $top wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]] + if {$path ne {}} { + if {[string index $path end] ne {/}} { + append path / + } + } + set browser_commit $commit - set browser_path $browser_commit:$path + set browser_path "$browser_commit:[escape_path $path]" ${NS}::label $w.path \ -textvariable @browser_path \ diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl index c12d5e1698..6dae7937d5 100644 --- a/git-gui/lib/choose_rev.tcl +++ b/git-gui/lib/choose_rev.tcl @@ -497,6 +497,7 @@ method _open_tooltip {} { if {$tooltip_wm eq {}} { set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1] + catch {wm attributes $tooltip_wm -type tooltip} wm overrideredirect $tooltip_wm 1 wm transient $tooltip_wm [winfo toplevel $w_list] set tooltip_t $tooltip_wm.label @@ -610,9 +611,9 @@ method _position_tooltip {} { set pos_y [expr {[winfo pointery .] + 10}] set g "${req_w}x${req_h}" - if {$pos_x >= 0} {append g +} + if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +} append g $pos_x - if {$pos_y >= 0} {append g +} + if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +} append g $pos_y wm geometry $tooltip_wm $g diff --git a/git-gui/lib/class.tcl b/git-gui/lib/class.tcl index c27b71476a..f08506f383 100644 --- a/git-gui/lib/class.tcl +++ b/git-gui/lib/class.tcl @@ -138,6 +138,7 @@ proc make_dialog {t w args} { upvar $t top $w pfx this this global use_ttk uplevel [linsert $args 0 make_toplevel $t $w] + catch {wm attributes $top -type dialog} pave_toplevel $pfx } diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 5ce46877bf..0d81432af5 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -260,8 +260,25 @@ proc commit_prehook_wait {fd_ph curHEAD msg_p} { } proc commit_commitmsg {curHEAD msg_p} { + global is_detached repo_config global pch_error + if {$is_detached + && ![file exists [gitdir rebase-merge head-name]] + && [is_config_true gui.warndetachedcommit]} { + set msg [mc "You are about to commit on a detached head.\ +This is a potentially dangerous thing to do because if you switch\ +to another branch you will loose your changes and it can be difficult\ +to retrieve them later from the reflog. You should probably cancel this\ +commit and create a new branch to continue.\n\ +\n\ +Do you really want to proceed with your Commit?"] + if {[ask_popup $msg] ne yes} { + unlock_index + return + } + } + # -- Run the commit-msg hook. # set fd_ph [githook_read commit-msg $msg_p] diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index cf8a95ec34..ec4405567a 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -309,6 +309,7 @@ proc start_show_diff {cont_info {add_opts {}}} { lappend cmd -p lappend cmd --color + set cmd [concat $cmd $repo_config(gui.diffopts)] if {$repo_config(gui.diffcontext) >= 1} { lappend cmd "-U$repo_config(gui.diffcontext)" } @@ -502,9 +503,9 @@ proc read_diff {fd conflict_size cont_info} { foreach {posbegin colbegin posend colend} $markup { set prefix clr - foreach style [split $colbegin ";"] { + foreach style [lsort -integer [split $colbegin ";"]] { if {$style eq "7"} {append prefix i; continue} - if {$style < 30 || $style > 47} {continue} + if {$style != 4 && ($style < 30 || $style > 47)} {continue} set a "$mark linestart + $posbegin chars" set b "$mark linestart + $posend chars" catch {$ui_diff tag add $prefix$style $a $b} diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index 5d7bbf23ed..8efbbdde21 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -356,12 +356,33 @@ proc do_add_all {} { global file_states set paths [list] + set untracked_paths [list] foreach path [array names file_states] { switch -glob -- [lindex $file_states($path) 0] { U? {continue} ?M - ?T - ?D {lappend paths $path} + ?O {lappend untracked_paths $path} + } + } + if {[llength $untracked_paths]} { + set reply 0 + switch -- [get_config gui.stageuntracked] { + no { + set reply 0 + } + yes { + set reply 1 + } + ask - + default { + set reply [ask_popup [mc "Stage %d untracked files?" \ + [llength $untracked_paths]]] + } + } + if {$reply} { + set paths [concat $paths $untracked_paths] } } add_helper {Adding all changed files} $paths diff --git a/git-gui/lib/line.tcl b/git-gui/lib/line.tcl new file mode 100644 index 0000000000..a026de954c --- /dev/null +++ b/git-gui/lib/line.tcl @@ -0,0 +1,81 @@ +# goto line number +# based on code from gitk, Copyright (C) Paul Mackerras + +class linebar { + +field w +field ctext + +field linenum {} + +constructor new {i_w i_text args} { + global use_ttk NS + set w $i_w + set ctext $i_text + + ${NS}::frame $w + ${NS}::label $w.l -text [mc "Goto Line:"] + tentry $w.ent \ + -textvariable ${__this}::linenum \ + -background lightgreen \ + -validate key \ + -validatecommand [cb _validate %P] + ${NS}::button $w.bn -text [mc Go] -command [cb _goto] + + pack $w.l -side left + pack $w.bn -side right + pack $w.ent -side left -expand 1 -fill x + + eval grid conf $w -sticky we $args + grid remove $w + + trace add variable linenum write [cb _goto_cb] + bind $w.ent <Return> [cb _goto] + bind $w.ent <Escape> [cb hide] + + bind $w <Destroy> [list delete_this $this] + return $this +} + +method show {} { + if {![visible $this]} { + grid $w + } + focus -force $w.ent +} + +method hide {} { + if {[visible $this]} { + $w.ent delete 0 end + focus $ctext + grid remove $w + } +} + +method visible {} { + return [winfo ismapped $w] +} + +method editor {} { + return $w.ent +} + +method _validate {P} { + # only accept numbers as input + string is integer $P +} + +method _goto_cb {name ix op} { + after idle [cb _goto 1] +} + +method _goto {{nohide {0}}} { + if {$linenum ne {}} { + $ctext see $linenum.0 + if {!$nohide} { + hide $this + } + } +} + +} diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index 3807c8d283..0cf1da1d7e 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -153,9 +153,12 @@ proc do_options {} { {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}} {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}} {i-1..99 gui.diffcontext {mc "Number of Diff Context Lines"}} + {t gui.diffopts {mc "Additional Diff Parameters"}} {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}} {t gui.newbranchtemplate {mc "New Branch Name Template"}} {c gui.encoding {mc "Default File Contents Encoding"}} + {b gui.warndetachedcommit {mc "Warn before committing to a detached head"}} + {s gui.stageuntracked {mc "Staging of untracked files"} {list "yes" "no" "ask"}} } { set type [lindex $option 0] set name [lindex $option 1] @@ -208,6 +211,23 @@ proc do_options {} { } pack $w.$f.$optid -side top -anchor w -fill x } + s { + set opts [eval [lindex $option 3]] + ${NS}::frame $w.$f.$optid + ${NS}::label $w.$f.$optid.l -text "$text:" + if {$use_ttk} { + ttk::combobox $w.$f.$optid.v \ + -textvariable ${f}_config_new($name) \ + -values $opts -state readonly + } else { + eval tk_optionMenu $w.$f.$optid.v \ + ${f}_config_new($name) \ + $opts + } + pack $w.$f.$optid.l -side left -anchor w -fill x + pack $w.$f.$optid.v -side right -anchor e -padx 5 + pack $w.$f.$optid -side top -anchor w -fill x + } } } } diff --git a/git-gui/lib/search.tcl b/git-gui/lib/search.tcl index 7fdbf87bcd..ef1e55521d 100644 --- a/git-gui/lib/search.tcl +++ b/git-gui/lib/search.tcl @@ -7,9 +7,16 @@ field w field ctext field searchstring {} -field casesensitive 1 +field regexpsearch +field default_regexpsearch +field casesensitive +field default_casesensitive +field smartcase field searchdirn -forwards +field history +field history_index + field smarktop field smarkbot @@ -18,15 +25,37 @@ constructor new {i_w i_text args} { set w $i_w set ctext $i_text + set default_regexpsearch [is_config_true gui.search.regexp] + switch -- [get_config gui.search.case] { + no { + set default_casesensitive 0 + set smartcase 0 + } + smart { + set default_casesensitive 0 + set smartcase 1 + } + yes - + default { + set default_casesensitive 1 + set smartcase 0 + } + } + + set history [list] + ${NS}::frame $w ${NS}::label $w.l -text [mc Find:] - entry $w.ent -textvariable ${__this}::searchstring -background lightgreen + tentry $w.ent -textvariable ${__this}::searchstring -background lightgreen ${NS}::button $w.bn -text [mc Next] -command [cb find_next] ${NS}::button $w.bp -text [mc Prev] -command [cb find_prev] - ${NS}::checkbutton $w.cs -text [mc Case-Sensitive] \ + ${NS}::checkbutton $w.re -text [mc RegExp] \ + -variable ${__this}::regexpsearch -command [cb _incrsearch] + ${NS}::checkbutton $w.cs -text [mc Case] \ -variable ${__this}::casesensitive -command [cb _incrsearch] pack $w.l -side left pack $w.cs -side right + pack $w.re -side right pack $w.bp -side right pack $w.bn -side right pack $w.ent -side left -expand 1 -fill x @@ -35,6 +64,10 @@ constructor new {i_w i_text args} { grid remove $w trace add variable searchstring write [cb _incrsearch_cb] + bind $w.ent <Return> [cb find_next] + bind $w.ent <Shift-Return> [cb find_prev] + bind $w.ent <Key-Up> [cb _prev_search] + bind $w.ent <Key-Down> [cb _next_search] bind $w <Destroy> [list delete_this $this] return $this @@ -43,6 +76,10 @@ constructor new {i_w i_text args} { method show {} { if {![visible $this]} { grid $w + $w.ent delete 0 end + set regexpsearch $default_regexpsearch + set casesensitive $default_casesensitive + set history_index [llength $history] } focus -force $w.ent } @@ -51,6 +88,7 @@ method hide {} { if {[visible $this]} { focus $ctext grid remove $w + _save_search $this } } @@ -96,6 +134,9 @@ method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} { upvar $mlenvar mlen lappend cmd -count mlen } + if {$regexpsearch} { + lappend cmd -regexp + } if {!$casesensitive} { lappend cmd -nocase } @@ -103,14 +144,16 @@ method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} { set dir $searchdirn } lappend cmd $dir -- $searchstring - if {$endbound ne {}} { - set here [eval $cmd [list $start] [list $endbound]] - } else { - set here [eval $cmd [list $start]] - if {$here eq {}} { - set here [eval $cmd [_get_wrap_anchor $this $dir]] + if {[catch { + if {$endbound ne {}} { + set here [eval $cmd [list $start] [list $endbound]] + } else { + set here [eval $cmd [list $start]] + if {$here eq {}} { + set here [eval $cmd [_get_wrap_anchor $this $dir]] + } } - } + } err]} { set here {} } return $here } @@ -124,19 +167,76 @@ method _incrsearch {} { $ctext mark set anchor [_get_new_anchor $this] } if {$searchstring ne {}} { + if {$smartcase && [regexp {[[:upper:]]} $searchstring]} { + set casesensitive 1 + } set here [_do_search $this anchor mlen] if {$here ne {}} { $ctext see $here $ctext tag remove sel 1.0 end $ctext tag add sel $here "$here + $mlen c" - $w.ent configure -background lightgreen + #$w.ent configure -background lightgreen + $w.ent state !pressed _set_marks $this 1 } else { - $w.ent configure -background lightpink + #$w.ent configure -background lightpink + $w.ent state pressed } + } elseif {$smartcase} { + # clearing the field resets the smart case detection + set casesensitive 0 + } +} + +method _save_search {} { + if {$searchstring eq {}} { + return + } + if {[llength $history] > 0} { + foreach {s_regexp s_case s_expr} [lindex $history end] break + } else { + set s_regexp $regexpsearch + set s_case $casesensitive + set s_expr "" + } + if {$searchstring eq $s_expr} { + # update modes + set history [lreplace $history end end \ + [list $regexpsearch $casesensitive $searchstring]] + } else { + lappend history [list $regexpsearch $casesensitive $searchstring] + } + set history_index [llength $history] +} + +method _prev_search {} { + if {$history_index > 0} { + incr history_index -1 + foreach {s_regexp s_case s_expr} [lindex $history $history_index] break + $w.ent delete 0 end + $w.ent insert 0 $s_expr + set regexpsearch $s_regexp + set casesensitive $s_case } } +method _next_search {} { + if {$history_index < [llength $history]} { + incr history_index + } + if {$history_index < [llength $history]} { + foreach {s_regexp s_case s_expr} [lindex $history $history_index] break + } else { + set s_regexp $default_regexpsearch + set s_case $default_casesensitive + set s_expr "" + } + $w.ent delete 0 end + $w.ent insert 0 $s_expr + set regexpsearch $s_regexp + set casesensitive $s_case +} + method find_prev {} { find_next $this -backwards } @@ -147,6 +247,7 @@ method find_next {{dir -forwards}} { set searchdirn $dir $ctext mark unset anchor if {$searchstring ne {}} { + _save_search $this set start [_get_new_anchor $this] if {$dir eq "-forwards"} { set start "$start + 1c" @@ -196,4 +297,4 @@ method scrolled {} { } } -}
\ No newline at end of file +} diff --git a/git-gui/lib/sshkey.tcl b/git-gui/lib/sshkey.tcl index 5f75bc96b3..aa6457bbb5 100644 --- a/git-gui/lib/sshkey.tcl +++ b/git-gui/lib/sshkey.tcl @@ -117,7 +117,7 @@ proc read_sshkey_output {fd w} { } else { set finfo [find_ssh_key] if {$finfo eq {}} { - set sshkey_title [mc "Generation succeded, but no keys found."] + set sshkey_title [mc "Generation succeeded, but no keys found."] $w.contents insert end $sshkey_output } else { set sshkey_title [mc "Your key is in: %s" [lindex $finfo 0]] diff --git a/git-gui/lib/themed.tcl b/git-gui/lib/themed.tcl index 1da458673b..8b88d3678b 100644 --- a/git-gui/lib/themed.tcl +++ b/git-gui/lib/themed.tcl @@ -23,10 +23,59 @@ proc InitTheme {} { ttk::style configure Gold.TFrame -background gold -relief flat # listboxes should have a theme border so embed in ttk::frame ttk::style layout SListbox.TFrame { - SListbox.Frame.Entry.field -sticky news -border true -children { - SListbox.Frame.padding -sticky news - } - } + SListbox.Frame.Entry.field -sticky news -border true -children { + SListbox.Frame.padding -sticky news + } + } + + # Handle either current Tk or older versions of 8.5 + if {[catch {set theme [ttk::style theme use]}]} { + set theme $::ttk::currentTheme + } + + if {[lsearch -exact {default alt classic clam} $theme] != -1} { + # Simple override of standard ttk::entry to change the field + # packground according to a state flag. We should use 'user1' + # but not all versions of 8.5 support that so make use of 'pressed' + # which is not normally in use for entry widgets. + ttk::style layout Edged.Entry [ttk::style layout TEntry] + ttk::style map Edged.Entry {*}[ttk::style map TEntry] + ttk::style configure Edged.Entry {*}[ttk::style configure TEntry] \ + -fieldbackground lightgreen + ttk::style map Edged.Entry -fieldbackground { + {pressed !disabled} lightpink + } + } else { + # For fancier themes, in particular the Windows ones, the field + # element may not support changing the background color. So instead + # override the fill using the default fill element. If we overrode + # the vista theme field element we would loose the themed border + # of the widget. + catch { + ttk::style element create color.fill from default + } + + ttk::style layout Edged.Entry { + Edged.Entry.field -sticky nswe -border 0 -children { + Edged.Entry.border -sticky nswe -border 1 -children { + Edged.Entry.padding -sticky nswe -children { + Edged.Entry.color.fill -sticky nswe -children { + Edged.Entry.textarea -sticky nswe + } + } + } + } + } + + ttk::style configure Edged.Entry {*}[ttk::style configure TEntry] \ + -background lightgreen -padding 0 -borderwidth 0 + ttk::style map Edged.Entry {*}[ttk::style map TEntry] \ + -background {{pressed !disabled} lightpink} + } + + if {[lsearch [bind . <<ThemeChanged>>] InitTheme] == -1} { + bind . <<ThemeChanged>> +[namespace code [list InitTheme]] + } } proc gold_frame {w args} { @@ -74,6 +123,7 @@ proc paddedlabel {w args} { # place a themed frame over the surface. proc Dialog {w args} { eval [linsert $args 0 toplevel $w -class Dialog] + catch {wm attributes $w -type dialog} pave_toplevel $w return $w } @@ -143,6 +193,47 @@ proc tspinbox {w args} { } } +proc tentry {w args} { + global use_ttk + if {$use_ttk} { + InitTheme + ttk::entry $w -style Edged.Entry + } else { + entry $w + } + + rename $w _$w + interp alias {} $w {} tentry_widgetproc $w + eval [linsert $args 0 tentry_widgetproc $w configure] + return $w +} +proc tentry_widgetproc {w cmd args} { + global use_ttk + switch -- $cmd { + state { + if {$use_ttk} { + return [uplevel 1 [list _$w $cmd] $args] + } else { + if {[lsearch -exact $args pressed] != -1} { + _$w configure -background lightpink + } else { + _$w configure -background lightgreen + } + } + } + configure { + if {$use_ttk} { + if {[set n [lsearch -exact $args -background]] != -1} { + set args [lreplace $args $n [incr n]] + if {[llength $args] == 0} {return} + } + } + return [uplevel 1 [list _$w $cmd] $args] + } + default { return [uplevel 1 [list _$w $cmd] $args] } + } +} + # Tk 8.6 provides a standard font selection dialog. This uses the native # dialogs on Windows and MacOSX or a standard Tk dialog on X11. proc tchoosefont {w title familyvar sizevar} { diff --git a/git-gui/lib/tools.tcl b/git-gui/lib/tools.tcl index 95e6e5553e..6ec94113db 100644 --- a/git-gui/lib/tools.tcl +++ b/git-gui/lib/tools.tcl @@ -87,8 +87,14 @@ proc tools_exec {fullname} { return } } elseif {[is_config_true "guitool.$fullname.confirm"]} { - if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} { - return + if {[is_config_true "guitool.$fullname.needsfile"]} { + if {[ask_popup [mc "Are you sure you want to run %1\$s on file \"%2\$s\"?" $fullname $current_diff_path]] ne {yes}} { + return + } + } else { + if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} { + return + } } } diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl index 7fad9b7d91..e5d211edea 100644 --- a/git-gui/lib/transport.tcl +++ b/git-gui/lib/transport.tcl @@ -124,6 +124,7 @@ proc do_push_anywhere {} { set w .push_setup toplevel $w + catch {wm attributes $w -type dialog} wm withdraw $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" pave_toplevel $w diff --git a/git-gui/po/README b/git-gui/po/README index 595bbf5dee..0f5837d48e 100644 --- a/git-gui/po/README +++ b/git-gui/po/README @@ -18,28 +18,23 @@ specialized so-called "po file editors" (e.g. emacs po-mode, KBabel, poedit, GTranslator --- any of them would work well). Please install them. -You would then need to clone the git-gui internationalization project -repository, so that you can work on it: +You would then need to clone the git-gui project repository and create +a feature branch to begin working: - $ git clone mob@repo.or.cz:/srv/git/git-gui/git-gui-i18n.git/ - $ cd git-gui-i18n - $ git checkout --track -b mob origin/mob - $ git config remote.origin.push mob + $ git clone git://repo.or.cz/git-gui.git + $ cd git-gui.git + $ git checkout -b my-translation -The "git checkout" command creates a 'mob' branch from upstream's -corresponding branch and makes it your current branch. You will be -working on this branch. - -The "git config" command records in your repository configuration file -that you would push "mob" branch to the upstream when you say "git -push". +The "git checkout" command creates a new branch to keep your work +isolated and to make it simple to post your patch series when +completed. You will be working on this branch. 2. Starting a new language. -In the git-gui-i18n directory is a po/ subdirectory. It has a -handful files whose names end with ".po". Is there a file that has -messages in your language? +In the git-gui directory is a po/ subdirectory. It has a handful of +files whose names end with ".po". Is there a file that has messages +in your language? If you do not know what your language should be named, you need to find it. This currently follows ISO 639-1 two letter codes: @@ -149,15 +144,18 @@ There is a trick to test your translation without first installing: $ make $ LANG=af ./git-gui.sh -When you are satisfied with your translation, commit your changes, and -push it back to the 'mob' branch: +When you are satisfied with your translation, commit your changes then submit +your patch series to the maintainer and the Git mailing list: $ edit po/af.po ... be sure to update Last-Translator: and ... PO-Revision-Date: lines. $ git add po/af.po - $ git commit -m 'Started Afrikaans translation.' - $ git push + $ git commit -s -m 'git-gui: added Afrikaans translation.' + $ git send-email --to 'git@vger.kernel.org' \ + --cc 'Pat Thoyts <patthoyts@users.sourceforge.net>' \ + --subject 'git-gui: Afrikaans translation' \ + master.. 3. Updating your translation. @@ -169,6 +167,7 @@ itself was updated and there are new messages that need translation. In any case, make sure you are up-to-date before starting your work: + $ git checkout master $ git pull In the former case, you will edit po/af.po (again, replace "af" with diff --git a/git-gui/po/sv.po b/git-gui/po/sv.po index 8bd3c5d75f..24cc4e3675 100644 --- a/git-gui/po/sv.po +++ b/git-gui/po/sv.po @@ -1714,7 +1714,7 @@ msgstr "" #: lib/index.tcl:30 msgid "Continue" -msgstr "Forstätt" +msgstr "Fortsätt" #: lib/index.tcl:33 msgid "Unlock Index" diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 7aeb96952f..f612cb847a 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -117,7 +117,7 @@ case "${1:-.}${2:-.}${3:-.}" in # If we do not have enough common material, it is not # worth trying two-file merge using common subsections. - expr "$sz0" \< "$sz1" \* 2 >/dev/null || : >$orig + expr $sz0 \< $sz1 \* 2 >/dev/null || : >$orig ;; *) echo "Auto-merging $4" diff --git a/git-mergetool.sh b/git-mergetool.sh index b6d463f0d0..085e213a12 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -342,64 +342,42 @@ merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo fa last_status=0 rollup_status=0 -rerere=false - -files_to_merge() { - if test "$rerere" = true - then - git rerere remaining - else - git ls-files -u | sed -e 's/^[^ ]* //' | sort -u - fi -} - +files= if test $# -eq 0 ; then cd_to_toplevel if test -e "$GIT_DIR/MERGE_RR" then - rerere=true - fi - - files=$(files_to_merge) - if test -z "$files" ; then - echo "No files need merging" - exit 0 + files=$(git rerere remaining) + else + files=$(git ls-files -u | sed -e 's/^[^ ]* //' | sort -u) fi +else + files=$(git ls-files -u -- "$@" | sed -e 's/^[^ ]* //' | sort -u) +fi - # Save original stdin - exec 3<&0 +if test -z "$files" ; then + echo "No files need merging" + exit 0 +fi - printf "Merging:\n" - printf "$files\n" +printf "Merging:\n" +printf "$files\n" - files_to_merge | - while IFS= read i - do - if test $last_status -ne 0; then - prompt_after_failed_merge <&3 || exit 1 - fi - printf "\n" - merge_file "$i" <&3 - last_status=$? - if test $last_status -ne 0; then - rollup_status=1 - fi - done -else - while test $# -gt 0; do - if test $last_status -ne 0; then - prompt_after_failed_merge || exit 1 - fi - printf "\n" - merge_file "$1" - last_status=$? - if test $last_status -ne 0; then - rollup_status=1 - fi - shift - done -fi +IFS=' +' +for i in $files +do + if test $last_status -ne 0; then + prompt_after_failed_merge || exit 1 + fi + printf "\n" + merge_file "$i" + last_status=$? + if test $last_status -ne 0; then + rollup_status=1 + fi +done exit $rollup_status diff --git a/git-pull.sh b/git-pull.sh index 63da37bcc2..d8b64d7a67 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -11,7 +11,7 @@ OPTIONS_SPEC= . git-sh-setup . git-sh-i18n set_reflog_action "pull${1+ $*}" -require_work_tree +require_work_tree_exists cd_to_toplevel @@ -44,6 +44,10 @@ merge_args= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short="${curr_branch#refs/heads/}" rebase=$(git config --bool branch.$curr_branch_short.rebase) +if test -z "$rebase" +then + rebase=$(git config --bool pull.rebase) +fi dry_run= while : do @@ -120,7 +124,7 @@ do --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run) dry_run=--dry-run ;; - -h|--h|--he|--hel|--help|--help-|--help-a|--help-al|--help-all) + -h|--help-all) usage ;; *) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index c6ba7c1551..5812222eb9 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -143,6 +143,21 @@ die_with_patch () { die "$2" } +exit_with_patch () { + echo "$1" > "$state_dir"/stopped-sha + make_patch $1 + git rev-parse --verify HEAD > "$amend" + warn "You can amend the commit now, with" + warn + warn " git commit --amend" + warn + warn "Once you are satisfied with your changes, run" + warn + warn " git rebase --continue" + warn + exit $2 +} + die_abort () { rm -rf "$state_dir" die "$1" @@ -161,6 +176,19 @@ do_with_author () { ) } +git_sequence_editor () { + if test -z "$GIT_SEQUENCE_EDITOR" + then + GIT_SEQUENCE_EDITOR="$(git config sequence.editor)" + if [ -z "$GIT_SEQUENCE_EDITOR" ] + then + GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $? + fi + fi + + eval "$GIT_SEQUENCE_EDITOR" '"$@"' +} + pick_one () { ff=--ff case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac @@ -395,7 +423,13 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" - git commit --amend --no-post-rewrite + git commit --amend --no-post-rewrite || { + warn "Could not amend commit after successfully picking $sha1... $rest" + warn "This is most likely due to an empty commit message, or the pre-commit hook" + warn "failed. If the pre-commit hook failed, you may need to resolve the issue before" + warn "you are able to reword the commit." + exit_with_patch $sha1 1 + } record_in_rewritten $sha1 ;; edit|e) @@ -404,19 +438,8 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" - echo "$sha1" > "$state_dir"/stopped-sha - make_patch $sha1 - git rev-parse --verify HEAD > "$amend" warn "Stopped at $sha1... $rest" - warn "You can amend the commit now, with" - warn - warn " git commit --amend" - warn - warn "Once you are satisfied with your changes, run" - warn - warn " git rebase --continue" - warn - exit 0 + exit_with_patch $sha1 0 ;; squash|s|fixup|f) case "$command" in @@ -472,18 +495,24 @@ do_next () { git rev-parse --verify HEAD > "$state_dir"/stopped-sha ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution status=$? + # Run in subshell because require_clean_work_tree can die. + dirty=f + (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t if test "$status" -ne 0 then warn "Execution failed: $rest" + test "$dirty" = f || + warn "and made changes to the index and/or the working tree" + warn "You can fix the problem, and then run" warn warn " git rebase --continue" warn exit "$status" - fi - # Run in subshell because require_clean_work_tree can die. - if ! (require_clean_work_tree "rebase") + elif test "$dirty" = t then + warn "Execution succeeded: $rest" + warn "but left changes to the index and/or the working tree" warn "Commit or stash your changes, and then run" warn warn " git rebase --continue" @@ -647,8 +676,24 @@ continue) then : Nothing to commit -- skip this else + if ! test -f "$author_script" + then + die "You have staged changes in your working tree. If these changes are meant to be +squashed into the previous commit, run: + + git commit --amend + +If they are meant to go into a new commit, run: + + git commit + +In both case, once you're done, continue with: + + git rebase --continue +" + fi . "$author_script" || - die "Cannot find the author identity" + die "Error trying to find the author identity to amend commit" current_head= if test -f "$amend" then @@ -810,7 +855,7 @@ has_action "$todo" || die_abort "Nothing to do" cp "$todo" "$todo".backup -git_editor "$todo" || +git_sequence_editor "$todo" || die_abort "Could not execute editor" has_action "$todo" || diff --git a/git-rebase.sh b/git-rebase.sh index 6759702c57..00ca7b99fe 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -63,7 +63,7 @@ skip! skip current patch and continue " . git-sh-setup set_reflog_action rebase -require_work_tree +require_work_tree_exists cd_to_toplevel LF=' diff --git a/git-request-pull.sh b/git-request-pull.sh index fc080cc5e4..d7ba1178ae 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -35,44 +35,103 @@ do shift done -base=$1 -url=$2 -head=${3-HEAD} +base=$1 url=$2 head=${3-HEAD} status=0 branch_name= -[ "$base" ] || usage -[ "$url" ] || usage +headref=$(git symbolic-ref -q "$head") +if git show-ref -q --verify "$headref" +then + branch_name=${headref#refs/heads/} + if test "z$branch_name" = "z$headref" || + ! git config "branch.$branch_name.description" >/dev/null + then + branch_name= + fi +fi + +tag_name=$(git describe --exact "$head^0" 2>/dev/null) -baserev=`git rev-parse --verify "$base"^0` && -headrev=`git rev-parse --verify "$head"^0` || exit +test -n "$base" && test -n "$url" || usage +baserev=$(git rev-parse --verify "$base"^0) && +headrev=$(git rev-parse --verify "$head"^0) || exit -merge_base=`git merge-base $baserev $headrev` || +merge_base=$(git merge-base $baserev $headrev) || die "fatal: No commits in common between $base and $head" -branch=$(git ls-remote "$url" \ - | sed -n -e "/^$headrev refs.heads./{ - s/^.* refs.heads.// - p - q - }") +# $head is the token given from the command line. If a ref with that +# name exists at the remote and their values match, we should use it. +# Otherwise find a ref that matches $headrev. +find_matching_ref=' + sub abbr { + my $ref = shift; + if ($ref =~ s|refs/heads/|| || $ref =~ s|refs/tags/||) { + return $ref; + } else { + return $ref; + } + } + + my ($exact, $found); + while (<STDIN>) { + my ($sha1, $ref, $deref) = /^(\S+)\s+(\S+?)(\^\{\})?$/; + next unless ($sha1 eq $ARGV[1]); + $found = abbr($ref); + if ($ref =~ m|/\Q$ARGV[0]\E$|) { + $exact = $found; + last; + } + } + if ($exact) { + print "$exact\n"; + } elsif ($found) { + print "$found\n"; + } +' + +ref=$(git ls-remote "$url" | perl -e "$find_matching_ref" "$head" "$headrev") + url=$(git ls-remote --get-url "$url") -if [ -z "$branch" ]; then - echo "warn: No branch of $url is at:" >&2 - git log --max-count=1 --pretty='tformat:warn: %h: %s' $headrev >&2 - echo "warn: Are you sure you pushed $head there?" >&2 - echo >&2 - echo >&2 - branch=..BRANCH.NOT.VERIFIED.. - status=1 -fi git show -s --format='The following changes since commit %H: %s (%ci) -are available in the git repository at:' $baserev && -echo " $url $branch" && -echo && +are available in the git repository at: +' $baserev && +echo " $url${ref+ $ref}" && +git show -s --format=' +for you to fetch changes up to %H: + + %s (%ci) + +----------------------------------------------------------------' $headrev && + +if test -n "$branch_name" +then + echo "(from the branch description for $branch_name local branch)" + echo + git config "branch.$branch_name.description" +fi && + +if test -n "$tag_name" +then + git cat-file tag "$tag_name" | + sed -n -e '1,/^$/d' -e '/^-----BEGIN PGP /q' -e p + echo +fi && + +if test -n "$branch_name" || test -n "$tag_name" +then + echo "----------------------------------------------------------------" +fi && git shortlog ^$baserev $headrev && -git diff -M --stat --summary $patch $merge_base..$headrev || exit +git diff -M --stat --summary $patch $merge_base..$headrev || status=1 + +if test -z "$ref" +then + echo "warn: No branch of $url is at:" >&2 + git show -s --format='warn: %h: %s' $headrev >&2 + echo "warn: Are you sure you pushed '$head' there?" >&2 + status=1 +fi exit $status diff --git a/git-send-email.perl b/git-send-email.perl index 98ab33aae7..d491db92c9 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -225,7 +225,6 @@ my %config_settings = ( "cccmd" => \$cc_cmd, "aliasfiletype" => \$aliasfiletype, "bcc" => \@bcclist, - "aliasesfile" => \@alias_files, "suppresscc" => \@suppress_cc, "envelopesender" => \$envelope_sender, "multiedit" => \$multiedit, @@ -234,6 +233,10 @@ my %config_settings = ( "assume8bitencoding" => \$auto_8bit_encoding, ); +my %config_path_settings = ( + "aliasesfile" => \@alias_files, +); + # Help users prepare for 1.7.0 sub chain_reply_to { if (defined $chain_reply_to && @@ -275,7 +278,9 @@ $SIG{INT} = \&signal_handler; # Begin by accumulating all the variables (defined above), that we will end up # needing, first, from the command line: -my $rc = GetOptions("sender|from=s" => \$sender, +my $help; +my $rc = GetOptions("h" => \$help, + "sender|from=s" => \$sender, "in-reply-to=s" => \$initial_reply_to, "subject=s" => \$initial_subject, "to=s" => \@initial_to, @@ -313,6 +318,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "force" => \$force, ); +usage() if $help; unless ($rc) { usage(); } @@ -330,6 +336,19 @@ sub read_config { $$target = Git::config_bool(@repo, "$prefix.$setting") unless (defined $$target); } + foreach my $setting (keys %config_path_settings) { + my $target = $config_path_settings{$setting}; + if (ref($target) eq "ARRAY") { + unless (@$target) { + my @values = Git::config_path(@repo, "$prefix.$setting"); + @$target = @values if (@values && defined $values[0]); + } + } + else { + $$target = Git::config_path(@repo, "$prefix.$setting") unless (defined $$target); + } + } + foreach my $setting (keys %config_settings) { my $target = $config_settings{$setting}; next if $setting eq "to" and defined $no_to; @@ -1079,7 +1098,7 @@ X-Mailer: git-send-email $gitversion $smtp_encryption = ''; # Send EHLO again to receive fresh # supported commands - $smtp->hello(); + $smtp->hello($smtp_domain); } else { die "Server does not support STARTTLS! ".$smtp->message; } @@ -1095,6 +1114,12 @@ X-Mailer: git-send-email $gitversion } if (defined $smtp_authuser) { + # Workaround AUTH PLAIN/LOGIN interaction defect + # with Authen::SASL::Cyrus + eval { + require Authen::SASL; + Authen::SASL->import(qw(Perl)); + }; if (!defined $smtp_authpass) { diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh index e672366f0c..b4575fb3a1 100644 --- a/git-sh-i18n.sh +++ b/git-sh-i18n.sh @@ -2,47 +2,91 @@ # # Copyright (c) 2010 Ævar Arnfjörð Bjarmason # -# This is a skeleton no-op implementation of gettext for Git. It'll be -# replaced by something that uses gettext.sh in a future patch series. +# This is Git's interface to gettext.sh. See po/README for usage +# instructions. + +# Export the TEXTDOMAIN* data that we need for Git +TEXTDOMAIN=git +export TEXTDOMAIN +if test -z "$GIT_TEXTDOMAINDIR" +then + TEXTDOMAINDIR="@@LOCALEDIR@@" +else + TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR" +fi +export TEXTDOMAINDIR if test -z "$GIT_GETTEXT_POISON" then - gettext () { - printf "%s" "$1" - } + if test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && type gettext.sh >/dev/null 2>&1 + then + # This is GNU libintl's gettext.sh, we don't need to do anything + # else than setting up the environment and loading gettext.sh + GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu + export GIT_INTERNAL_GETTEXT_SH_SCHEME - gettextln() { - printf "%s\n" "$1" - } + # Try to use libintl's gettext.sh, or fall back to English if we + # can't. + . gettext.sh - eval_gettext () { - printf "%s" "$1" | ( - export PATH $(git sh-i18n--envsubst --variables "$1"); - git sh-i18n--envsubst "$1" - ) - } + elif test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && test "$(gettext -h 2>&1)" = "-h" + then + # We don't have gettext.sh, but there's a gettext binary in our + # path. This is probably Solaris or something like it which has a + # gettext implementation that isn't GNU libintl. + GIT_INTERNAL_GETTEXT_SH_SCHEME=solaris + export GIT_INTERNAL_GETTEXT_SH_SCHEME - eval_gettextln () { - printf "%s\n" "$1" | ( - export PATH $(git sh-i18n--envsubst --variables "$1"); - git sh-i18n--envsubst "$1" - ) - } + # Solaris has a gettext(1) but no eval_gettext(1) + eval_gettext () { + gettext "$1" | ( + export PATH $(git sh-i18n--envsubst --variables "$1"); + git sh-i18n--envsubst "$1" + ) + } + + else + # Since gettext.sh isn't available we'll have to define our own + # dummy pass-through functions. + + # Tell our tests that we don't have the real gettext.sh + GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough + export GIT_INTERNAL_GETTEXT_SH_SCHEME + + gettext () { + printf "%s" "$1" + } + + eval_gettext () { + printf "%s" "$1" | ( + export PATH $(git sh-i18n--envsubst --variables "$1"); + git sh-i18n--envsubst "$1" + ) + } + fi else + # Emit garbage under GETTEXT_POISON=YesPlease. Unlike the C tests + # this relies on an environment variable + + GIT_INTERNAL_GETTEXT_SH_SCHEME=poison + export GIT_INTERNAL_GETTEXT_SH_SCHEME + gettext () { printf "%s" "# GETTEXT POISON #" } - gettextln () { - printf "%s\n" "# GETTEXT POISON #" - } - eval_gettext () { printf "%s" "# GETTEXT POISON #" } - - eval_gettextln () { - printf "%s\n" "# GETTEXT POISON #" - } fi +# Git-specific wrapper functions +gettextln () { + gettext "$1" + echo +} + +eval_gettextln () { + eval_gettext "$1" + echo +} diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 8e427dab31..1fba6c2de0 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -90,7 +90,7 @@ $LONG_USAGE" fi case "$1" in - -h|--h|--he|--hel|--help) + -h) echo "$LONG_USAGE" exit esac diff --git a/git-stash.sh b/git-stash.sh index 31dec0abf1..c76669284c 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -211,7 +211,7 @@ save_stash () { if test -n "$patch_mode" && test -n "$untracked" then - die "Can't use --patch and ---include-untracked or --all at the same time" + die "Can't use --patch and --include-untracked or --all at the same time" fi stash_msg="$*" @@ -240,7 +240,7 @@ save_stash () { test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION= if test -n "$untracked" then - git clean --force --quiet $CLEAN_X_OPTION + git clean --force --quiet -d $CLEAN_X_OPTION fi if test "$keep_index" = "t" && test -n $i_tree diff --git a/git-submodule.sh b/git-submodule.sh index 814d0d914e..3adab93635 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -104,9 +104,9 @@ module_name() re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g') name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) - test -z "$name" && - die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")" - echo "$name" + test -z "$name" && + die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")" + echo "$name" } # @@ -128,13 +128,49 @@ module_clone() quiet=-q fi - if test -n "$reference" + gitdir= + gitdir_base= + name=$(module_name "$path" 2>/dev/null) + base_path=$(dirname "$path") + + gitdir=$(git rev-parse --git-dir) + gitdir_base="$gitdir/modules/$base_path" + gitdir="$gitdir/modules/$path" + + case $gitdir in + /*) + a="$(cd_to_toplevel && pwd)/" + b=$gitdir + while [ "$b" ] && [ "${a%%/*}" = "${b%%/*}" ] + do + a=${a#*/} b=${b#*/}; + done + + rel="$a$name" + rel=`echo $rel | sed -e 's|[^/]*|..|g'` + rel_gitdir="$rel/$b" + ;; + *) + rel=`echo $name | sed -e 's|[^/]*|..|g'` + rel_gitdir="$rel/$gitdir" + ;; + esac + + if test -d "$gitdir" then - git-clone $quiet "$reference" -n "$url" "$path" + mkdir -p "$path" + echo "gitdir: $rel_gitdir" >"$path/.git" + rm -f "$gitdir/index" else - git-clone $quiet -n "$url" "$path" - fi || - die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")" + mkdir -p "$gitdir_base" + if test -n "$reference" + then + git-clone $quiet "$reference" -n "$url" "$path" --separate-git-dir "$gitdir" + else + git-clone $quiet -n "$url" "$path" --separate-git-dir "$gitdir" + fi || + die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")" + fi } # @@ -426,6 +462,9 @@ cmd_update() --recursive) recursive=1 ;; + --checkout) + update="checkout" + ;; --) shift break @@ -458,7 +497,19 @@ cmd_update() fi name=$(module_name "$path") || exit url=$(git config submodule."$name".url) - update_module=$(git config submodule."$name".update) + if ! test -z "$update" + then + update_module=$update + else + update_module=$(git config submodule."$name".update) + fi + + if test "$update_module" = "none" + then + echo "Skipping submodule '$path'" + continue + fi + if test -z "$url" then # Only mention uninitialized submodules when its @@ -480,11 +531,6 @@ Maybe you want to use 'update --init'?")" die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")" fi - if ! test -z "$update" - then - update_module=$update - fi - if test "$subsha1" != "$sha1" then subforce=$force diff --git a/git-svn.perl b/git-svn.perl index 351e743a90..eeb83d3759 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -22,14 +22,13 @@ $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; $Git::SVN::Ra::_log_window_size = 100; $Git::SVN::_minimize_url = 'unset'; -if (! exists $ENV{SVN_SSH}) { - if (exists $ENV{GIT_SSH}) { - $ENV{SVN_SSH} = $ENV{GIT_SSH}; - if ($^O eq 'msys') { - $ENV{SVN_SSH} =~ s/\\/\\\\/g; - $ENV{SVN_SSH} =~ s/(.*)/"$1"/; - } - } +if (! exists $ENV{SVN_SSH} && exists $ENV{GIT_SSH}) { + $ENV{SVN_SSH} = $ENV{GIT_SSH}; +} + +if (exists $ENV{SVN_SSH} && $^O eq 'msys') { + $ENV{SVN_SSH} =~ s/\\/\\\\/g; + $ENV{SVN_SSH} =~ s/(.*)/"$1"/; } $Git::SVN::Log::TZ = $ENV{TZ}; @@ -87,14 +86,15 @@ my ($_stdin, $_help, $_edit, $_version, $_fetch_all, $_no_rebase, $_fetch_parent, $_merge, $_strategy, $_dry_run, $_local, $_prefix, $_no_checkout, $_url, $_verbose, - $_git_format, $_commit_url, $_tag, $_merge_info); + $_git_format, $_commit_url, $_tag, $_merge_info, $_interactive); $Git::SVN::_follow_parent = 1; $SVN::Git::Fetcher::_placeholder_filename = ".gitignore"; $_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 ); + 'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex, + 'ignore-refs=s' => \$Git::SVN::Ra::_ignore_refs_regex ); my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'authors-prog=s' => \$_authors_prog, @@ -163,6 +163,7 @@ my %cmd = ( 'revision|r=i' => \$_revision, 'no-rebase' => \$_no_rebase, 'mergeinfo=s' => \$_merge_info, + 'interactive|i' => \$_interactive, %cmt_opts, %fc_opts } ], branch => [ \&cmd_branch, 'Create a branch in the SVN repository', @@ -256,6 +257,27 @@ my %cmd = ( {} ], ); +use Term::ReadLine; +package FakeTerm; +sub new { + my ($class, $reason) = @_; + return bless \$reason, shift; +} +sub readline { + my $self = shift; + die "Cannot use readline on FakeTerm: $$self"; +} +package main; + +my $term = eval { + $ENV{"GIT_SVN_NOTTY"} + ? new Term::ReadLine 'git-svn', \*STDIN, \*STDOUT + : new Term::ReadLine 'git-svn'; +}; +if ($@) { + $term = new FakeTerm "$@: going non-interactive"; +} + my $cmd; for (my $i = 0; $i < @ARGV; $i++) { if (defined $cmd{$ARGV[$i]}) { @@ -299,7 +321,7 @@ read_git_config(\%opts); if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) { Getopt::Long::Configure('pass_through'); } -my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, +my $rv = GetOptions(%opts, 'h|H' => \$_help, 'version|V' => \$_version, 'minimize-connections' => \$Git::SVN::Migration::_minimize, 'id|i=s' => \$Git::SVN::default_ref_id, 'svn-remote|remote|R=s' => sub { @@ -366,6 +388,36 @@ sub version { exit 0; } +sub ask { + my ($prompt, %arg) = @_; + my $valid_re = $arg{valid_re}; + my $default = $arg{default}; + my $resp; + my $i = 0; + + if ( !( defined($term->IN) + && defined( fileno($term->IN) ) + && defined( $term->OUT ) + && defined( fileno($term->OUT) ) ) ){ + return defined($default) ? $default : undef; + } + + 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; +} + sub do_git_init_db { unless (-d $ENV{GIT_DIR}) { my @init_db = ('init'); @@ -388,9 +440,12 @@ 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; + my $ignore_paths_regex = \$SVN::Git::Fetcher::_ignore_regex; + command_noisy('config', "$pfx.ignore-paths", $$ignore_paths_regex) + if defined $$ignore_paths_regex; + my $ignore_refs_regex = \$Git::SVN::Ra::_ignore_refs_regex; + command_noisy('config', "$pfx.ignore-refs", $$ignore_refs_regex) + if defined $$ignore_refs_regex; if (defined $SVN::Git::Fetcher::_preserve_empty_dirs) { my $fname = \$SVN::Git::Fetcher::_placeholder_filename; @@ -629,7 +684,7 @@ sub populate_merge_info { fatal "merge commit $d has ancestor $parent, but that change " ."does not have git-svn metadata!"; } - unless ($branchurl =~ /^$rooturl(.*)/) { + unless ($branchurl =~ /^\Q$rooturl\E(.*)/) { fatal "commit $parent git-svn metadata changed mid-run!"; } my $branchpath = $1; @@ -746,6 +801,27 @@ sub cmd_dcommit { "If these changes depend on each other, re-running ", "without --no-rebase may be required." } + + if (defined $_interactive){ + my $ask_default = "y"; + foreach my $d (@$linear_refs){ + my ($fh, $ctx) = command_output_pipe(qw(show --summary), "$d"); + while (<$fh>){ + print $_; + } + command_close_pipe($fh, $ctx); + $_ = ask("Commit this patch to SVN? ([y]es (default)|[n]o|[q]uit|[a]ll): ", + valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i, + default => $ask_default); + die "Commit this patch reply required" unless defined $_; + if (/^[nq]/i) { + exit(0); + } elsif (/^a/i) { + last; + } + } + } + my $expect_url = $url; my $push_merge_info = eval { @@ -791,7 +867,7 @@ sub cmd_dcommit { ."has uuid $uuid!"; } - unless ($branchurl =~ /^$rooturl(.*)/) { + unless ($branchurl =~ /^\Q$rooturl\E(.*)/) { # This branch is very strange indeed. fatal "merge parent $parent for $d is on branch " ."$branchurl, which is not under the " @@ -2119,6 +2195,8 @@ sub read_all_remotes { $r->{$1}->{url} = $2; } elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) { $r->{$1}->{pushurl} = $2; + } elsif (m!^(.+)\.ignore-refs=\s*(.*)\s*$!) { + $r->{$1}->{ignore_refs_regex} = $2; } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) { my ($remote, $t, $local_ref, $remote_ref) = ($1, $2, $3, $4); @@ -2155,6 +2233,16 @@ sub read_all_remotes { } } keys %$r; + foreach my $remote (keys %$r) { + foreach ( grep { defined $_ } + map { $r->{$remote}->{$_} } qw(branches tags) ) { + foreach my $rs ( @$_ ) { + $rs->{ignore_refs_regex} = + $r->{$remote}->{ignore_refs_regex}; + } + } + } + $r; } @@ -5301,7 +5389,7 @@ sub apply_diff { $self->{mergeinfo}); } $self->rmdirs if $_rmdir; - if (@$mods == 0) { + if (@$mods == 0 && !defined($self->{mergeinfo})) { $self->abort_edit; } else { $self->close_edit; @@ -5310,7 +5398,7 @@ sub apply_diff { } package Git::SVN::Ra; -use vars qw/@ISA $config_dir $_log_window_size/; +use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/; use strict; use warnings; my ($ra_invalid, $can_do_switch, %ignored_err, $RA); @@ -5768,6 +5856,17 @@ sub get_dir_globbed { @finalents; } +# return value: 0 -- don't ignore, 1 -- ignore +sub is_ref_ignored { + my ($g, $p) = @_; + my $refname = $g->{ref}->full_path($p); + return 1 if defined($g->{ignore_refs_regex}) && + $refname =~ m!$g->{ignore_refs_regex}!; + return 0 unless defined($_ignore_refs_regex); + return 1 if $refname =~ m!$_ignore_refs_regex!o; + return 0; +} + sub match_globs { my ($self, $exists, $paths, $globs, $r) = @_; @@ -5804,6 +5903,7 @@ sub match_globs { next unless /$g->{path}->{regex}/; my $p = $1; my $pathname = $g->{path}->full_path($p); + next if is_ref_ignored($g, $p); next if $exists->{$pathname}; next if ($self->check_path($pathname, $r) != $SVN::Node::dir); diff --git a/git-web--browse.sh b/git-web--browse.sh index e9de241dd0..1e827264b4 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -156,7 +156,7 @@ firefox|iceweasel|seamonkey|iceape) ;; google-chrome|chrome|chromium|chromium-browser) # No need to specify newTab. It's default in chromium - eval "$browser_path" "$@" & + "$browser_path" "$@" & ;; konqueror) case "$(basename "$browser_path")" in @@ -164,10 +164,10 @@ konqueror) # It's simpler to use kfmclient to open a new tab in konqueror. browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')" type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found." - eval "$browser_path" newTab "$@" + "$browser_path" newTab "$@" & ;; kfmclient) - eval "$browser_path" newTab "$@" + "$browser_path" newTab "$@" & ;; *) "$browser_path" "$@" & @@ -175,7 +175,7 @@ konqueror) esac ;; w3m|elinks|links|lynx|open) - eval "$browser_path" "$@" + "$browser_path" "$@" ;; start) exec "$browser_path" '"web-browse"' "$@" @@ -185,7 +185,7 @@ opera|dillo) ;; *) if test -n "$browser_cmd"; then - ( eval $browser_cmd "$@" ) + ( eval "$browser_cmd \"\$@\"" ) fi ;; esac @@ -434,6 +434,7 @@ static void handle_internal_command(int argc, const char **argv) { "update-ref", cmd_update_ref, RUN_SETUP }, { "update-server-info", cmd_update_server_info, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, + { "upload-archive--writer", cmd_upload_archive_writer }, { "var", cmd_var, RUN_SETUP_GENTLY }, { "verify-pack", cmd_verify_pack }, { "verify-tag", cmd_verify_tag, RUN_SETUP }, @@ -537,6 +538,8 @@ int main(int argc, const char **argv) if (!cmd) cmd = "git-help"; + git_setup_gettext(); + /* * "git-xxxx" is the same as "git xxxx", but we obviously: * diff --git a/git.spec.in b/git.spec.in index 91c8462b7d..c562c62284 100644 --- a/git.spec.in +++ b/git.spec.in @@ -199,7 +199,11 @@ rm -rf $RPM_BUILD_ROOT %files -n gitweb %defattr(-,root,root) +%doc gitweb/README gitweb/INSTALL Documentation/*gitweb*.txt %{_datadir}/gitweb +%{!?_without_docs: %{_mandir}/man1/*gitweb*.1*} +%{!?_without_docs: %{_mandir}/man5/*gitweb*.5*} +%{!?_without_docs: %doc Documentation/*gitweb*.html } %files -n perl-Git -f perl-files %defattr(-,root,root) @@ -208,6 +212,9 @@ rm -rf $RPM_BUILD_ROOT # No files for you! %changelog +* Sun Sep 18 2011 Jakub Narebski <jnareb@gmail.com> +- Add gitweb manpages to 'gitweb' subpackage + * Wed Jun 30 2010 Junio C Hamano <gitster@pobox.com> - Add 'gitweb' subpackage. diff --git a/git_remote_helpers/git/git.py b/git_remote_helpers/git/git.py index a383e6c08d..007a1bfdf3 100644 --- a/git_remote_helpers/git/git.py +++ b/git_remote_helpers/git/git.py @@ -54,7 +54,7 @@ def valid_git_ref (ref_name): # The following is a reimplementation of the git check-ref-format # command. The rules were derived from the git check-ref-format(1) # manual page. This code should be replaced by a call to - # check_ref_format() in the git library, when such is available. + # check_refname_format() in the git library, when such is available. if ref_name.endswith('/') or \ ref_name.startswith('.') or \ ref_name.count('/.') or \ diff --git a/gitk-git/gitk b/gitk-git/gitk index 4cde0c493b..64ef3c4013 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -2,20 +2,16 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright © 2005-2009 Paul Mackerras. All rights reserved. +# Copyright © 2005-2011 Paul Mackerras. All rights reserved. # This program is free software; it may be used, copied, modified # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. package require Tk -proc gitdir {} { - global env - if {[info exists env(GIT_DIR)]} { - return $env(GIT_DIR) - } else { - return [exec git rev-parse --git-dir] - } +proc hasworktree {} { + return [expr {[exec git rev-parse --is-bare-repository] == "false" && + [exec git rev-parse --is-inside-git-dir] == "false"}] } # A simple scheduler for compute-intensive stuff. @@ -468,11 +464,11 @@ proc updatecommits {} { global viewactive viewcomplete tclencoding global startmsecs showneartags showlocalchanges global mainheadid viewmainheadid viewmainheadid_orig pending_select - global isworktree + global hasworktree global varcid vposids vnegids vflags vrevs global show_notes - set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}] + set hasworktree [hasworktree] rereadrefs set view $curview if {$mainheadid ne $viewmainheadid_orig($view)} { @@ -659,7 +655,7 @@ proc newvarc {view id} { if {![info exists commitinfo($id)]} { parsecommit $id $commitdata($id) 1 } - set cdate [lindex $commitinfo($id) 4] + set cdate [lindex [lindex $commitinfo($id) 4] 0] if {![string is integer -strict $cdate]} { set cdate 0 } @@ -1621,7 +1617,7 @@ proc readcommit {id} { } proc parsecommit {id contents listed} { - global commitinfo cdate + global commitinfo set inhdr 1 set comment {} @@ -1641,10 +1637,10 @@ proc parsecommit {id contents listed} { set line [split $line " "] set tag [lindex $line 0] if {$tag == "author"} { - set audate [lindex $line end-1] + set audate [lrange $line end-1 end] set auname [join [lrange $line 1 end-2] " "] } elseif {$tag == "committer"} { - set comdate [lindex $line end-1] + set comdate [lrange $line end-1 end] set comname [join [lrange $line 1 end-2] " "] } } @@ -1671,11 +1667,9 @@ proc parsecommit {id contents listed} { } set comment $newcomment } - if {$comdate != {}} { - set cdate($id) $comdate - } + set hasnote [string first "\nNotes:\n" $contents] set commitinfo($id) [list $headline $auname $audate \ - $comname $comdate $comment] + $comname $comdate $comment $hasnote] } proc getcommit {id} { @@ -2437,9 +2431,9 @@ proc makewindow {} { bindkey n "selnextline 1" bindkey z "goback" bindkey x "goforw" - bindkey i "selnextline -1" - bindkey k "selnextline 1" - bindkey j "goback" + bindkey k "selnextline -1" + bindkey j "selnextline 1" + bindkey h "goback" bindkey l "goforw" bindkey b prevfile bindkey d "$ctext yview scroll 18 units" @@ -2815,7 +2809,7 @@ proc about {} { message $w.m -text [mc " Gitk - a commit viewer for git -Copyright \u00a9 2005-2010 Paul Mackerras +Copyright \u00a9 2005-2011 Paul Mackerras Use and redistribute under the terms of the GNU General Public License"] \ -justify center -aspect 400 -border 2 -bg white -relief groove @@ -2850,9 +2844,9 @@ proc keys {} { [mc "<%s-W> Close window" $M1T] [mc "<Home> Move to first commit"] [mc "<End> Move to last commit"] -[mc "<Up>, p, i Move up one commit"] -[mc "<Down>, n, k Move down one commit"] -[mc "<Left>, z, j Go back in history list"] +[mc "<Up>, p, k Move up one commit"] +[mc "<Down>, n, j Move down one commit"] +[mc "<Left>, z, h Go back in history list"] [mc "<Right>, x, l Go forward in history list"] [mc "<PageUp> Move up one page in commit list"] [mc "<PageDown> Move down one page in commit list"] @@ -3333,8 +3327,7 @@ proc gitknewtmpdir {} { global diffnum gitktmpdir gitdir if {![info exists gitktmpdir]} { - set gitktmpdir [file join [file dirname $gitdir] \ - [format ".gitk-tmp.%s" [pid]]] + set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]] if {[catch {file mkdir $gitktmpdir} err]} { error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err" unset gitktmpdir @@ -3366,10 +3359,10 @@ proc save_file_from_commit {filename output what} { proc external_diff_get_one_file {diffid filename diffdir} { global nullid nullid2 nullfile - global gitdir + global worktree if {$diffid == $nullid} { - set difffile [file join [file dirname $gitdir] $filename] + set difffile [file join $worktree $filename] if {[file exists $difffile]} { return $difffile } @@ -3559,7 +3552,7 @@ proc make_relative {f} { } proc external_blame {parent_idx {line {}}} { - global flist_menu_file gitdir + global flist_menu_file cdup global nullid nullid2 global parentlist selectedline currentid @@ -3578,7 +3571,7 @@ proc external_blame {parent_idx {line {}}} { if {$line ne {} && $line > 1} { lappend cmdline "--line=$line" } - set f [file join [file dirname $gitdir] $flist_menu_file] + set f [file join $cdup $flist_menu_file] # Unfortunately it seems git gui blame doesn't like # being given an absolute path... set f [make_relative $f] @@ -3591,7 +3584,7 @@ proc external_blame {parent_idx {line {}}} { proc show_line_source {} { global cmitmode currentid parents curview blamestuff blameinst global diff_menu_line diff_menu_filebase flist_menu_file - global nullid nullid2 gitdir + global nullid nullid2 gitdir cdup set from_index {} if {$cmitmode eq "tree"} { @@ -3644,7 +3637,7 @@ proc show_line_source {} { } else { lappend blameargs $id } - lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file] + lappend blameargs -- [file join $cdup $flist_menu_file] if {[catch { set f [open $blameargs r] } err]} { @@ -4529,12 +4522,22 @@ proc makepatterns {l} { proc do_file_hl {serial} { global highlight_files filehighlight highlight_paths gdttype fhl_list + global cdup findtype if {$gdttype eq [mc "touching paths:"]} { + # If "exact" match then convert backslashes to forward slashes. + # Most useful to support Windows-flavoured file paths. + if {$findtype eq [mc "Exact"]} { + set highlight_files [string map {"\\" "/"} $highlight_files] + } if {[catch {set paths [shellsplit $highlight_files]}]} return set highlight_paths [makepatterns $paths] highlight_filelist - set gdtargs [concat -- $paths] + set relative_paths {} + foreach path $paths { + lappend relative_paths [file join $cdup $path] + } + set gdtargs [concat -- $relative_paths] } elseif {$gdttype eq [mc "adding/removing string:"]} { set gdtargs [list "-S$highlight_files"] } else { @@ -5031,9 +5034,9 @@ proc dohidelocalchanges {} { # spawn off a process to do git diff-index --cached HEAD proc dodiffindex {} { global lserial showlocalchanges vfilelimit curview - global isworktree + global hasworktree - if {!$showlocalchanges || !$isworktree} return + if {!$showlocalchanges || !$hasworktree} return incr lserial set cmd "|git diff-index --cached HEAD" if {$vfilelimit($curview) ne {}} { @@ -5899,6 +5902,9 @@ proc drawcmittext {id row col} { || [info exists idotherrefs($id)]} { set xt [drawtags $id $x $xt $y] } + if {[lindex $commitinfo($id) 6] > 0} { + set xt [drawnotesign $xt $y] + } set headline [lindex $commitinfo($id) 0] set name [lindex $commitinfo($id) 1] set date [lindex $commitinfo($id) 2] @@ -6345,6 +6351,17 @@ proc drawtags {id x xt y1} { return $xt } +proc drawnotesign {xt y} { + global linespc canv fgcolor + + set orad [expr {$linespc / 3}] + set t [$canv create rectangle [expr {$xt - $orad}] [expr {$y - $orad}] \ + [expr {$xt + $orad - 1}] [expr {$y + $orad - 1}] \ + -fill yellow -outline $fgcolor -width 1 -tags circle] + set xt [expr {$xt + $orad * 3}] + return $xt +} + proc xcoord {i level ln} { global canvx0 xspc1 xspc2 @@ -9043,6 +9060,7 @@ proc exec_citool {tool_args {baseid {}}} { proc cherrypick {} { global rowmenuid curview global mainhead mainheadid + global gitdir set oldhead [exec git rev-parse HEAD] set dheads [descheads $rowmenuid] @@ -9071,7 +9089,7 @@ proc cherrypick {} { conflict.\nDo you wish to run git citool to\ resolve it?"]]} { # Force citool to read MERGE_MSG - file delete [file join [gitdir] "GITGUI_MSG"] + file delete [file join $gitdir "GITGUI_MSG"] exec_citool {} $rowmenuid } } else { @@ -9437,6 +9455,7 @@ proc refill_reflist {} { proc getallcommits {} { global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate global idheads idtags idotherrefs allparents tagobjid + global gitdir if {![info exists allcommits]} { set nextarc 0 @@ -9444,7 +9463,7 @@ proc getallcommits {} { set seeds {} set allcwait 0 set cachedarcs 0 - set allccache [file join [gitdir] "gitk.cache"] + set allccache [file join $gitdir "gitk.cache"] if {![catch { set f [open $allccache r] set allcwait 1 @@ -11024,7 +11043,7 @@ proc prefsok {} { proc formatdate {d} { global datetimeformat if {$d ne {}} { - set d [clock format $d -format $datetimeformat] + set d [clock format [lindex $d 0] -format $datetimeformat] } return $d } @@ -11505,14 +11524,10 @@ setui $uicolor setoptions # check that we can find a .git directory somewhere... -if {[catch {set gitdir [gitdir]}]} { +if {[catch {set gitdir [exec git rev-parse --git-dir]}]} { show_error {} . [mc "Cannot find a git repository here."] exit 1 } -if {![file isdirectory $gitdir]} { - show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir] - exit 1 -} set selecthead {} set selectheadid {} @@ -11628,7 +11643,12 @@ set stopped 0 set stuffsaved 0 set patchnum 0 set lserial 0 -set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}] +set hasworktree [hasworktree] +set cdup {} +if {[expr {[exec git rev-parse --is-inside-work-tree] == "true"}]} { + set cdup [exec git rev-parse --show-cdup] +} +set worktree [exec git rev-parse --show-toplevel] setcoords makewindow catch { diff --git a/gitweb/INSTALL b/gitweb/INSTALL index f5efe7454c..6d45406797 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -130,6 +130,8 @@ You can specify the following configuration variables when building GIT: Points to an .html file which is included on the gitweb project overview page ('projects_list' view), if it exists. Relative to gitweb.cgi script. [Default: indextext.html] + * GITWEB_SITE_HTML_HEAD_STRING + html snippet to include in the <head> section of each page. [No default] * GITWEB_SITE_HEADER Filename of html text to include at top of each page. Relative to gitweb.cgi script. [No default] @@ -229,7 +231,7 @@ Gitweb config file ------------------ See also "Runtime gitweb configuration" section in README file -for gitweb (in gitweb/README). +for gitweb (in gitweb/README), and gitweb.conf(5) manpage. - You can configure gitweb further using the per-instance gitweb configuration file; by default this is a file named gitweb_config.perl in the same place as @@ -287,97 +289,19 @@ adding the following lines to your $GITWEB_CONFIG: Gitweb repositories ------------------- -- By default all git repositories under projectroot are visible and - available to gitweb. The list of projects is generated by default by - scanning the projectroot directory for git repositories (for object - databases to be more exact). - - You can provide a pre-generated list of [visible] repositories, - together with information about their owners (the project ownership - defaults to the owner of the repository directory otherwise), by setting - the GITWEB_LIST build configuration variable (or the $projects_list - variable in the gitweb config file) to point to a plain file. - - Each line of the projects list file should consist of the url-encoded path - to the project repository database (relative to projectroot), followed - by the url-encoded project owner on the same line (separated by a space). - Spaces in both project path and project owner have to be encoded as either - '%20' or '+'. - - Other characters that have to be url-encoded, i.e. replaced by '%' - followed by two-digit character number in octal, are: other whitespace - characters (because they are field separator in a record), plus sign '+' - (because it can be used as replacement for spaces), and percent sign '%' - (which is used for encoding / escaping). - - You can generate the projects list index file using the project_index - action (the 'TXT' link on projects list page) directly from gitweb. - -- By default, even if a project is not visible on projects list page, you - can view it nevertheless by hand-crafting a gitweb URL. You can set the - GITWEB_STRICT_EXPORT build configuration variable (or the $strict_export - variable in the gitweb config file) to only allow viewing of - repositories also shown on the overview page. - -- Alternatively, you can configure gitweb to only list and allow - viewing of the explicitly exported repositories, via the - GITWEB_EXPORT_OK build configuration variable (or the $export_ok - variable in gitweb config file). If it evaluates to true, gitweb - shows repositories only if this file exists in its object database - (if directory has the magic file named $export_ok). - -- Finally, it is possible to specify an arbitrary perl subroutine that - will be called for each project to determine if it can be exported. - The subroutine receives an absolute path to the project as its only - parameter. - - For example, if you use mod_perl to run the script, and have dumb - http protocol authentication configured for your repositories, you - can use the following hook to allow access only if the user is - authorized to read the files: - - $export_auth_hook = sub { - use Apache2::SubRequest (); - use Apache2::Const -compile => qw(HTTP_OK); - my $path = "$_[0]/HEAD"; - my $r = Apache2::RequestUtil->request; - my $sub = $r->lookup_file($path); - return $sub->filename eq $path - && $sub->status == Apache2::Const::HTTP_OK; - }; - - -Generating projects list using gitweb -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We assume that GITWEB_CONFIG has its default Makefile value, namely -gitweb_config.perl. Put the following in gitweb_make_index.perl file: - - $GITWEB_CONFIG = "gitweb_config.perl"; - do $GITWEB_CONFIG if -e $GITWEB_CONFIG; - - $projects_list = $projectroot; - -Then create the following script to get list of project in the format -suitable for GITWEB_LIST build configuration variable (or -$projects_list variable in gitweb config): - - #!/bin/sh - - export GITWEB_CONFIG="gitweb_make_index.perl" - export GATEWAY_INTERFACE="CGI/1.1" - export HTTP_ACCEPT="*/*" - export REQUEST_METHOD="GET" - export QUERY_STRING="a=project_index" - - perl -- /var/www/cgi-bin/gitweb.cgi +By default gitweb shows all git repositories under single common repository +root on a local filesystem; see description of GITWEB_PROJECTROOT build-time +configuration variable above (and also of GITWEB_LIST). + +More advanced usage, like limiting access or visibility of repositories and +managing multiple roots are described on gitweb manpage. Example web server configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -See also "Webserver configuration" section in README file for gitweb -(in gitweb/README). +See also "Webserver configuration" and "Advanced web server setup" sections +in gitweb(1) manpage. - Apache2, gitweb installed as CGI script, diff --git a/gitweb/Makefile b/gitweb/Makefile index 1c85b5fda8..cd194d057f 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -34,6 +34,7 @@ GITWEB_CSS = static/gitweb.css GITWEB_LOGO = static/git-logo.png GITWEB_FAVICON = static/git-favicon.png GITWEB_JS = static/gitweb.js +GITWEB_SITE_HTML_HEAD_STRING = GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = HIGHLIGHT_BIN = highlight @@ -144,6 +145,7 @@ GITWEB_REPLACE = \ -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \ + -e 's|++GITWEB_SITE_HTML_HEAD_STRING++|$(GITWEB_SITE_HTML_HEAD_STRING)|g' \ -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g' @@ -185,7 +187,9 @@ install: all ### Cleaning rules clean: - $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS + $(RM) gitweb.cgi static/gitweb.js \ + static/gitweb.min.js static/gitweb.min.css \ + GITWEB-BUILD-OPTIONS .PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE diff --git a/gitweb/README b/gitweb/README index a9988200d6..6da4778b73 100644 --- a/gitweb/README +++ b/gitweb/README @@ -7,9 +7,18 @@ The one working on: From the git version 1.4.0 gitweb is bundled with git. +Build time gitweb configuration +------------------------------- +There are many configuration variables which affect building gitweb (among +others creating gitweb.cgi out of gitweb.perl by replacing placeholders such +as `++GIT_BINDIR++` by their build-time values). + +Building and installing gitweb is described in gitweb's INSTALL file +(in 'gitweb/INSTALL'). + + Runtime gitweb configuration ---------------------------- - Gitweb obtains configuration data from the following sources in the following order: @@ -41,400 +50,22 @@ Ultimate description on how to reconfigure the default features setting in your `GITWEB_CONFIG` or per-project in `project.git/config` can be found as comments inside 'gitweb.cgi'. -See also the "Gitweb config file" (with an example of config file), and -the "Gitweb repositories" sections in INSTALL file for gitweb. - - -The gitweb config file is a fragment of perl code. You can set variables -using "our $variable = value"; text from "#" character until the end -of a line is ignored. See perlsyn(1) man page for details. - -Below is the list of variables which you might want to set in gitweb config. -See the top of 'gitweb.cgi' for the full list of variables and their -descriptions. - -Gitweb config file variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can set, among others, the following variables in gitweb config files -(with the exception of $projectroot and $projects_list this list does -not include variables usually directly set during build): - * $GIT - Core git executable to use. By default set to "$GIT_BINDIR/git", which - in turn is by default set to "$(bindir)/git". If you use git from binary - package, set this to "/usr/bin/git". This can just be "git" if your - webserver has a sensible PATH. If you have multiple git versions - installed it can be used to choose which one to use. - * $version - Gitweb version, set automatically when creating gitweb.cgi from - gitweb.perl. You might want to modify it if you are running modified - gitweb. - * $projectroot - Absolute filesystem path which will be prepended to project path; - the path to repository is $projectroot/$project. Set to - $GITWEB_PROJECTROOT during installation. This variable have to be - set correctly for gitweb to find repositories. - * $projects_list - Source of projects list, either directory to scan, or text file - with list of repositories (in the "<URI-encoded repository path> SP - <URI-encoded repository owner>" line format; actually there can be - any sequence of whitespace in place of space (SP)). Set to - $GITWEB_LIST during installation. If empty, $projectroot is used - to scan for repositories. - * $my_url, $my_uri - Full URL and absolute URL of gitweb script; - in earlier versions of gitweb you might have need to set those - variables, now there should be no need to do it. See - $per_request_config if you need to set them still. - * $base_url - Base URL for relative URLs in pages generated by gitweb, - (e.g. $logo, $favicon, @stylesheets if they are relative URLs), - needed and used only for URLs with nonempty PATH_INFO via - <base href="$base_url">. Usually gitweb sets its value correctly, - and there is no need to set this variable, e.g. to $my_uri or "/". - See $per_request_config if you need to set it anyway. - * $home_link - Target of the home link on top of all pages (the first part of view - "breadcrumbs"). By default set to absolute URI of a page ($my_uri). - * @stylesheets - List of URIs of stylesheets (relative to base URI of a page). You - might specify more than one stylesheet, for example use gitweb.css - as base, with site specific modifications in separate stylesheet - to make it easier to upgrade gitweb. You can add 'site' stylesheet - for example by using - push @stylesheets, "gitweb-site.css"; - in the gitweb config file. - * $logo_url, $logo_label - URI and label (title) of GIT logo link (or your site logo, if you choose - to use different logo image). By default they point to git homepage; - in the past they pointed to git documentation at www.kernel.org. - * $projects_list_description_width - The width (in characters) of the projects list "Description" column. - Longer descriptions will be cut (trying to cut at word boundary); - full description is available as 'title' attribute (usually shown on - mouseover). By default set to 25, which might be too small if you - use long project descriptions. - * $projects_list_group_categories - Enables the grouping of projects by category on the project list page. - The category of a project is determined by the $GIT_DIR/category - file or the 'gitweb.category' variable in its repository configuration. - Disabled by default. - * $project_list_default_category - Default category for projects for which none is specified. If set - to the empty string, such projects will remain uncategorized and - listed at the top, above categorized projects. - * @git_base_url_list - List of git base URLs used for URL to where fetch project from, shown - in project summary page. Full URL is "$git_base_url/$project". - You can setup multiple base URLs (for example one for git:// protocol - access, and one for http:// "dumb" protocol access). Note that per - repository configuration in 'cloneurl' file, or as values of gitweb.url - project config. - * $default_blob_plain_mimetype - Default mimetype for blob_plain (raw) view, if mimetype checking - doesn't result in some other type; by default 'text/plain'. - * $default_text_plain_charset - Default charset for text files. If not set, web server configuration - would be used. - * $mimetypes_file - File to use for (filename extension based) guessing of MIME types before - trying /etc/mime.types. Path, if relative, is taken currently as - relative to the current git repository. - * $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 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 - ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or - set it to () if you don't want to have renames detection. - * $prevent_xss - If true, some gitweb features are disabled to prevent content in - repositories from launching cross-site scripting (XSS) attacks. Set this - to true if you don't trust the content of your repositories. The default - is false. - * $maxload - Used to set the maximum load that we will still respond to gitweb queries. - If server load exceed this value then return "503 Service Unavailable" error. - Server load is taken to be 0 if gitweb cannot determine its value. Set it to - undefined value to turn it off. The default is 300. - * $highlight_bin - Path to the highlight executable to use (must be the one from - http://www.andre-simon.de due to assumptions about parameters and output). - Useful if highlight is not installed on your webserver's PATH. - [Default: highlight] - * $per_request_config - If set to code reference, it would be run once per each request. You can - set parts of configuration that change per session, e.g. by setting it to - sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; } - Otherwise it is treated as boolean value: if true gitweb would process - config file once per request, if false it would process config file only - once. Note: $my_url, $my_uri, and $base_url are overwritten with - their default values before every request, so if you want to change - them, be sure to set this variable to true or a code reference effecting - the desired changes. The default is true. - -Projects list file format -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Instead of having gitweb find repositories by scanning filesystem starting -from $projectroot (or $projects_list, if it points to directory), you can -provide list of projects by setting $projects_list to a text file with list -of projects (and some additional info). This file uses the following -format: - -One record (for project / repository) per line, whitespace separated fields; -does not support (at least for now) lines continuation (newline escaping). -Leading and trailing whitespace are ignored, any run of whitespace can be -used as field separator (rules for Perl's "split(' ', $line)"). Keyed by -the first field, which is project name, i.e. path to repository GIT_DIR -relative to $projectroot. Fields use modified URI encoding, defined in -RFC 3986, section 2.1 (Percent-Encoding), or rather "Query string encoding" -(see http://en.wikipedia.org/wiki/Query_string#URL_encoding), the difference -being that SP (' ') can be encoded as '+' (and therefore '+' has to be also -percent-encoded). Reserved characters are: '%' (used for encoding), '+' -(can be used to encode SPACE), all whitespace characters as defined in Perl, -including SP, TAB and LF, (used to separate fields in a record). - -Currently list of fields is - * <repository path> - path to repository GIT_DIR, relative to $projectroot - * <repository owner> - displayed as repository owner, preferably full name, - or email, or both - -You can additionally use $projects_list file to limit which repositories -are visible, and together with $strict_export to limit access to -repositories (see "Gitweb repositories" section in gitweb/INSTALL). - - -Per-repository gitweb configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also configure individual repositories shown in gitweb by creating -file in the GIT_DIR of git repository, or by setting some repo configuration -variable (in GIT_DIR/config). - -You can use the following files in repository: - * README.html - A .html file (HTML fragment) which is included on the gitweb project - summary page inside <div> block element. You can use it for longer - description of a project, to provide links (for example to project's - homepage), etc. This is recognized only if XSS prevention is off - ($prevent_xss is false); a way to include a readme safely when XSS - prevention is on may be worked out in the future. - * description (or gitweb.description) - Short (shortened by default to 25 characters in the projects list page) - single line description of a project (of a repository). Plain text file; - HTML will be escaped. By default set to - Unnamed repository; edit this file to name it for gitweb. - from the template during repository creation. You can use the - gitweb.description repo configuration variable, but the file takes - precedence. - * category (or gitweb.category) - Singe line category of a project, used to group projects if - $projects_list_group_categories is enabled. By default (file and - configuration variable absent), uncategorized projects are put in - the $project_list_default_category category. You can use the - gitweb.category repo configuration variable, but the file takes - precedence. - * cloneurl (or multiple-valued gitweb.url) - File with repository URL (used for clone and fetch), one per line. - Displayed in the project summary page. You can use multiple-valued - gitweb.url repository configuration variable for that, but the file - takes precedence. - * gitweb.owner - You can use the gitweb.owner repository configuration variable to set - repository's owner. It is displayed in the project list and summary - page. If it's not set, filesystem directory's owner is used - (via GECOS field / real name field from getpwiud(3)). - * various gitweb.* config variables (in config) - Read description of %feature hash for detailed list, and some - descriptions. - - -Webserver configuration ------------------------ - -If you want to have one URL for both gitweb and your http:// -repositories, you can configure apache like this: - -<VirtualHost *:80> - ServerName git.example.org - DocumentRoot /pub/git - SetEnv GITWEB_CONFIG /etc/gitweb.conf - - # turning on mod rewrite - RewriteEngine on - - # make the front page an internal rewrite to the gitweb script - RewriteRule ^/$ /cgi-bin/gitweb.cgi - - # make access for "dumb clients" work - RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] -</VirtualHost> - -The above configuration expects your public repositories to live under -/pub/git and will serve them as http://git.domain.org/dir-under-pub-git, -both as cloneable GIT URL and as browseable gitweb interface. -If you then start your git-daemon with --base-path=/pub/git --export-all -then you can even use the git:// URL with exactly the same path. - -Setting the environment variable GITWEB_CONFIG will tell gitweb to use -the named file (i.e. in this example /etc/gitweb.conf) as a -configuration for gitweb. Perl variables defined in here will -override the defaults given at the head of the gitweb.perl (or -gitweb.cgi). Look at the comments in that file for information on -which variables and what they mean. - -If you use the rewrite rules from the example you'll likely also need -something like the following in your gitweb.conf (or gitweb_config.perl) file: - - @stylesheets = ("/some/absolute/path/gitweb.css"); - $my_uri = "/"; - $home_link = "/"; - - -Webserver configuration with multiple projects' root ----------------------------------------------------- - -If you want to use gitweb with several project roots you can edit your apache -virtual host and gitweb.conf configuration files like this : - -virtual host configuration : - -<VirtualHost *:80> - ServerName git.example.org - DocumentRoot /pub/git - SetEnv GITWEB_CONFIG /etc/gitweb.conf - - # turning on mod rewrite - RewriteEngine on - - # make the front page an internal rewrite to the gitweb script - RewriteRule ^/$ /cgi-bin/gitweb.cgi [QSA,L,PT] - - # look for a public_git folder in unix users' home - # http://git.example.org/~<user>/ - RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] - - # http://git.example.org/+<user>/ - #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] - - # http://git.example.org/user/<user>/ - #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] - - # defined list of project roots - RewriteRule ^/scm(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT] - RewriteRule ^/var(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT] - - # make access for "dumb clients" work - RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] -</VirtualHost> - -gitweb.conf configuration : - -$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git"; - -These configurations enable two things. First, each unix user (<user>) of the -server will be able to browse through gitweb git repositories found in -~/public_git/ with the following url : http://git.example.org/~<user>/ - -If you do not want this feature on your server just remove the second rewrite rule. - -If you already use mod_userdir in your virtual host or you don't want to use -the '~' as first character just comment or remove the second rewrite rule and -uncomment one of the following according to what you want. - -Second, repositories found in /pub/scm/ and /var/git/ will be accesible -through http://git.example.org/scm/ and http://git.example.org/var/. -You can add as many project roots as you want by adding rewrite rules like the -third and the fourth. - - -PATH_INFO usage ------------------------ -If you enable PATH_INFO usage in gitweb by putting - - $feature{'pathinfo'}{'default'} = [1]; - -in your gitweb.conf, it is possible to set up your server so that it -consumes and produces URLs in the form - -http://git.example.com/project.git/shortlog/sometag - -by using a configuration such as the following, that assumes that -/var/www/gitweb is the DocumentRoot of your webserver, and that it -contains the gitweb.cgi script and complementary static files -(stylesheet, favicon): - -<VirtualHost *:80> - ServerAlias git.example.com - - DocumentRoot /var/www/gitweb - - <Directory /var/www/gitweb> - Options ExecCGI - AddHandler cgi-script cgi - - DirectoryIndex gitweb.cgi - - RewriteEngine On - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^.* /gitweb.cgi/$0 [L,PT] - </Directory> -</VirtualHost> - -The rewrite rule guarantees that existing static files will be properly -served, whereas any other URL will be passed to gitweb as PATH_INFO -parameter. - -Notice that in this case you don't need special settings for -@stylesheets, $my_uri and $home_link, but you lose "dumb client" access -to your project .git dirs. A possible workaround for the latter is the -following: in your project root dir (e.g. /pub/git) have the projects -named without a .git extension (e.g. /pub/git/project instead of -/pub/git/project.git) and configure Apache as follows: - -<VirtualHost *:80> - ServerAlias git.example.com - - DocumentRoot /var/www/gitweb - - AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3 - <Directory /var/www/gitweb> - Options ExecCGI - AddHandler cgi-script cgi - - DirectoryIndex gitweb.cgi - - RewriteEngine On - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^.* /gitweb.cgi/$0 [L,PT] - </Directory> -</VirtualHost> - -The additional AliasMatch makes it so that - -http://git.example.com/project.git - -will give raw access to the project's git dir (so that the project can -be cloned), while - -http://git.example.com/project - -will provide human-friendly gitweb access. - -This solution is not 100% bulletproof, in the sense that if some project -has a named ref (branch, tag) starting with 'git/', then paths such as +See also gitweb.conf(5) manpage. -http://git.example.com/project/command/abranch..git/abranch -will fail with a 404 error. +Web server configuration +------------------------ +Gitweb can be run as CGI script, as legacy mod_perl application (using +ModPerl::Registry), and as FastCGI script. You can find some simple examples +in "Example web server configuration" section in INSTALL file for gitweb (in +gitweb/INSTALL). +See "Webserver configuration" and "Advanced web server setup" sections in +gitweb(1) manpage. +AUTHORS +------- Originally written by: Kay Sievers <kay.sievers@vrfy.org> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 70a576a626..f80f2594cb 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -85,6 +85,8 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++"; our $site_name = "++GITWEB_SITENAME++" || ($ENV{'SERVER_NAME'} || "Untitled") . " Git"; +# html snippet to include in the <head> section of each page +our $site_html_head_string = "++GITWEB_SITE_HTML_HEAD_STRING++"; # filename of html text to include at top of each page our $site_header = "++GITWEB_SITE_HEADER++"; # html text to include at home page @@ -757,6 +759,7 @@ our @cgi_param_mapping = ( extra_options => "opt", search_use_regexp => "sr", ctag => "by_tag", + diff_style => "ds", # this must be last entry (for manipulation from JavaScript) javascript => "js" ); @@ -1517,6 +1520,17 @@ sub esc_path { return $str; } +# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0) +sub sanitize { + my $str = shift; + + return undef unless defined $str; + + $str = to_utf8($str); + $str =~ s|([[:cntrl:]])|($1 =~ /[\t\n\r]/ ? $1 : quot_cec($1))|eg; + return $str; +} + # Make control characters "printable", using character escape codes (CEC) sub quot_cec { my $cntrl = shift; @@ -2212,93 +2226,119 @@ sub format_diff_cc_simplified { return $result; } -# format patch (diff) line (not to be used for diff headers) -sub format_diff_line { - my $line = shift; - my ($from, $to) = @_; - my $diff_class = ""; - - chomp $line; +sub diff_line_class { + my ($line, $from, $to) = @_; + # ordinary diff + my $num_sign = 1; + # combined diff if ($from && $to && ref($from->{'href'}) eq "ARRAY") { - # combined diff - my $prefix = substr($line, 0, scalar @{$from->{'href'}}); - if ($line =~ m/^\@{3}/) { - $diff_class = " chunk_header"; - } elsif ($line =~ m/^\\/) { - $diff_class = " incomplete"; - } elsif ($prefix =~ tr/+/+/) { - $diff_class = " add"; - } elsif ($prefix =~ tr/-/-/) { - $diff_class = " rem"; - } - } else { - # assume ordinary diff - my $char = substr($line, 0, 1); - if ($char eq '+') { - $diff_class = " add"; - } elsif ($char eq '-') { - $diff_class = " rem"; - } elsif ($char eq '@') { - $diff_class = " chunk_header"; - } elsif ($char eq "\\") { - $diff_class = " incomplete"; - } + $num_sign = scalar @{$from->{'href'}}; + } + + my @diff_line_classifier = ( + { regexp => qr/^\@\@{$num_sign} /, class => "chunk_header"}, + { regexp => qr/^\\/, class => "incomplete" }, + { regexp => qr/^ {$num_sign}/, class => "ctx" }, + # classifier for context must come before classifier add/rem, + # or we would have to use more complicated regexp, for example + # qr/(?= {0,$m}\+)[+ ]{$num_sign}/, where $m = $num_sign - 1; + { regexp => qr/^[+ ]{$num_sign}/, class => "add" }, + { regexp => qr/^[- ]{$num_sign}/, class => "rem" }, + ); + for my $clsfy (@diff_line_classifier) { + return $clsfy->{'class'} + if ($line =~ $clsfy->{'regexp'}); } - $line = untabify($line); - if ($from && $to && $line =~ m/^\@{2} /) { - my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) = - $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/; - $from_lines = 0 unless defined $from_lines; - $to_lines = 0 unless defined $to_lines; + # fallback + return ""; +} - if ($from->{'href'}) { - $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start", - -class=>"list"}, $from_text); - } - if ($to->{'href'}) { - $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start", - -class=>"list"}, $to_text); - } - $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" . - "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>"; - return "<div class=\"diff$diff_class\">$line</div>\n"; - } elsif ($from && $to && $line =~ m/^\@{3}/) { - my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/; - my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines); +# assumes that $from and $to are defined and correctly filled, +# and that $line holds a line of chunk header for unified diff +sub format_unidiff_chunk_header { + my ($line, $from, $to) = @_; - @from_text = split(' ', $ranges); - for (my $i = 0; $i < @from_text; ++$i) { - ($from_start[$i], $from_nlines[$i]) = - (split(',', substr($from_text[$i], 1)), 0); - } + my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) = + $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/; - $to_text = pop @from_text; - $to_start = pop @from_start; - $to_nlines = pop @from_nlines; + $from_lines = 0 unless defined $from_lines; + $to_lines = 0 unless defined $to_lines; - $line = "<span class=\"chunk_info\">$prefix "; - for (my $i = 0; $i < @from_text; ++$i) { - if ($from->{'href'}[$i]) { - $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]", - -class=>"list"}, $from_text[$i]); - } else { - $line .= $from_text[$i]; - } - $line .= " "; - } - if ($to->{'href'}) { - $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start", - -class=>"list"}, $to_text); + if ($from->{'href'}) { + $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start", + -class=>"list"}, $from_text); + } + if ($to->{'href'}) { + $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start", + -class=>"list"}, $to_text); + } + $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" . + "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>"; + return $line; +} + +# assumes that $from and $to are defined and correctly filled, +# and that $line holds a line of chunk header for combined diff +sub format_cc_diff_chunk_header { + my ($line, $from, $to) = @_; + + my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/; + my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines); + + @from_text = split(' ', $ranges); + for (my $i = 0; $i < @from_text; ++$i) { + ($from_start[$i], $from_nlines[$i]) = + (split(',', substr($from_text[$i], 1)), 0); + } + + $to_text = pop @from_text; + $to_start = pop @from_start; + $to_nlines = pop @from_nlines; + + $line = "<span class=\"chunk_info\">$prefix "; + for (my $i = 0; $i < @from_text; ++$i) { + if ($from->{'href'}[$i]) { + $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]", + -class=>"list"}, $from_text[$i]); } else { - $line .= $to_text; + $line .= $from_text[$i]; } - $line .= " $prefix</span>" . - "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>"; - return "<div class=\"diff$diff_class\">$line</div>\n"; + $line .= " "; + } + if ($to->{'href'}) { + $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start", + -class=>"list"}, $to_text); + } else { + $line .= $to_text; + } + $line .= " $prefix</span>" . + "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>"; + return $line; +} + +# process patch (diff) line (not to be used for diff headers), +# returning class and HTML-formatted (but not wrapped) line +sub process_diff_line { + my $line = shift; + my ($from, $to) = @_; + + my $diff_class = diff_line_class($line, $from, $to); + + chomp $line; + $line = untabify($line); + + if ($from && $to && $line =~ m/^\@{2} /) { + $line = format_unidiff_chunk_header($line, $from, $to); + return $diff_class, $line; + + } elsif ($from && $to && $line =~ m/^\@{3}/) { + $line = format_cc_diff_chunk_header($line, $from, $to); + return $diff_class, $line; + } - return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n"; + return $diff_class, esc_html($line, -nbsp=>1); } # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)", @@ -2875,7 +2915,7 @@ sub filter_forks_from_projects_list { $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git' next unless ($path); # skip '.git' repository: tests, git-instaweb - next unless (-d $path); # containing directory exists + next unless (-d "$projectroot/$path"); # containing directory exists $pr->{'forks'} = []; # there can be 0 or more forks of project # add to trie @@ -3868,6 +3908,11 @@ EOF print "<base href=\"".esc_url($base_url)."\" />\n"; } print_header_links($status); + + if (defined $site_html_head_string) { + print to_utf8($site_html_head_string); + } + print "</head>\n" . "<body>\n"; @@ -4815,8 +4860,97 @@ sub git_difftree_body { print "</table>\n"; } +sub print_sidebyside_diff_chunk { + my @chunk = @_; + my (@ctx, @rem, @add); + + return unless @chunk; + + # incomplete last line might be among removed or added lines, + # or both, or among context lines: find which + for (my $i = 1; $i < @chunk; $i++) { + if ($chunk[$i][0] eq 'incomplete') { + $chunk[$i][0] = $chunk[$i-1][0]; + } + } + + # guardian + push @chunk, ["", ""]; + + foreach my $line_info (@chunk) { + my ($class, $line) = @$line_info; + + # print chunk headers + if ($class && $class eq 'chunk_header') { + print $line; + next; + } + + ## print from accumulator when type of class of lines change + # empty contents block on start rem/add block, or end of chunk + if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) { + print join '', + '<div class="chunk_block ctx">', + '<div class="old">', + @ctx, + '</div>', + '<div class="new">', + @ctx, + '</div>', + '</div>'; + @ctx = (); + } + # empty add/rem block on start context block, or end of chunk + if ((@rem || @add) && (!$class || $class eq 'ctx')) { + if (!@add) { + # pure removal + print join '', + '<div class="chunk_block rem">', + '<div class="old">', + @rem, + '</div>', + '</div>'; + } elsif (!@rem) { + # pure addition + print join '', + '<div class="chunk_block add">', + '<div class="new">', + @add, + '</div>', + '</div>'; + } else { + # assume that it is change + print join '', + '<div class="chunk_block chg">', + '<div class="old">', + @rem, + '</div>', + '<div class="new">', + @add, + '</div>', + '</div>'; + } + @rem = @add = (); + } + + ## adding lines to accumulator + # guardian value + last unless $line; + # rem, add or change + if ($class eq 'rem') { + push @rem, $line; + } elsif ($class eq 'add') { + push @add, $line; + } + # context line + if ($class eq 'ctx') { + push @ctx, $line; + } + } +} + sub git_patchset_body { - my ($fd, $difftree, $hash, @hash_parents) = @_; + my ($fd, $diff_style, $difftree, $hash, @hash_parents) = @_; my ($hash_parent) = $hash_parents[0]; my $is_combined = (@hash_parents > 1); @@ -4826,6 +4960,7 @@ sub git_patchset_body { my $diffinfo; my $to_name; my (%from, %to); + my @chunk; # for side-by-side diff print "<div class=\"patchset\">\n"; @@ -4932,10 +5067,29 @@ sub git_patchset_body { next PATCH if ($patch_line =~ m/^diff /); - print format_diff_line($patch_line, \%from, \%to); + my ($class, $line) = process_diff_line($patch_line, \%from, \%to); + my $diff_classes = "diff"; + $diff_classes .= " $class" if ($class); + $line = "<div class=\"$diff_classes\">$line</div>\n"; + + if ($diff_style eq 'sidebyside' && !$is_combined) { + if ($class eq 'chunk_header') { + print_sidebyside_diff_chunk(@chunk); + @chunk = ( [ $class, $line ] ); + } else { + push @chunk, [ $class, $line ]; + } + } else { + # default 'inline' style and unknown styles + print $line; + } } } continue { + if (@chunk) { + print_sidebyside_diff_chunk(@chunk); + @chunk = (); + } print "</div>\n"; # class="patch" } @@ -6484,7 +6638,8 @@ sub git_blob { $nr++; $line = untabify($line); printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!, - $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? to_utf8($line) : esc_html($line, -nbsp=>1); + $nr, esc_attr(href(-replay => 1)), $nr, $nr, + $syntax ? sanitize($line) : esc_html($line, -nbsp=>1); } } close $fd @@ -6930,6 +7085,7 @@ sub git_object { sub git_blobdiff { my $format = shift || 'html'; + my $diff_style = $input_params{'diff_style'} || 'inline'; my $fd; my @difftree; @@ -7008,6 +7164,7 @@ sub git_blobdiff { my $formats_nav = $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)}, "raw"); + $formats_nav .= diff_style_nav($diff_style); git_header_html(undef, $expires); if (defined $hash_base && (my %co = parse_commit($hash_base))) { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); @@ -7039,7 +7196,8 @@ sub git_blobdiff { if ($format eq 'html') { print "<div class=\"page_body\">\n"; - git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base); + git_patchset_body($fd, $diff_style, + [ \%diffinfo ], $hash_base, $hash_parent_base); close $fd; print "</div>\n"; # class="page_body" @@ -7064,9 +7222,31 @@ sub git_blobdiff_plain { git_blobdiff('plain'); } +# assumes that it is added as later part of already existing navigation, +# so it returns "| foo | bar" rather than just "foo | bar" +sub diff_style_nav { + my ($diff_style, $is_combined) = @_; + $diff_style ||= 'inline'; + + return "" if ($is_combined); + + my @styles = (inline => 'inline', 'sidebyside' => 'side by side'); + my %styles = @styles; + @styles = + @styles[ map { $_ * 2 } 0..$#styles/2 ]; + + return join '', + map { " | ".$_ } + map { + $_ eq $diff_style ? $styles{$_} : + $cgi->a({-href => href(-replay=>1, diff_style => $_)}, $styles{$_}) + } @styles; +} + sub git_commitdiff { my %params = @_; my $format = $params{-format} || 'html'; + my $diff_style = $input_params{'diff_style'} || 'inline'; my ($patch_max) = gitweb_get_feature('patches'); if ($format eq 'patch') { @@ -7092,6 +7272,7 @@ sub git_commitdiff { $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); } + $formats_nav .= diff_style_nav($diff_style, @{$co{'parents'}} > 1); if (defined $hash_parent && $hash_parent ne '-c' && $hash_parent ne '--cc') { @@ -7109,8 +7290,8 @@ sub git_commitdiff { } } $formats_nav .= ': ' . - $cgi->a({-href => href(action=>"commitdiff", - hash=>$hash_parent)}, + $cgi->a({-href => href(-replay=>1, + hash=>$hash_parent, hash_base=>undef)}, esc_html($hash_parent_short)) . ')'; } elsif (!$co{'parent'}) { @@ -7120,28 +7301,28 @@ sub git_commitdiff { # single parent commit $formats_nav .= ' (parent: ' . - $cgi->a({-href => href(action=>"commitdiff", - hash=>$co{'parent'})}, + $cgi->a({-href => href(-replay=>1, + hash=>$co{'parent'}, hash_base=>undef)}, esc_html(substr($co{'parent'}, 0, 7))) . ')'; } else { # merge commit if ($hash_parent eq '--cc') { $formats_nav .= ' | ' . - $cgi->a({-href => href(action=>"commitdiff", + $cgi->a({-href => href(-replay=>1, hash=>$hash, hash_parent=>'-c')}, 'combined'); } else { # $hash_parent eq '-c' $formats_nav .= ' | ' . - $cgi->a({-href => href(action=>"commitdiff", + $cgi->a({-href => href(-replay=>1, hash=>$hash, hash_parent=>'--cc')}, 'compact'); } $formats_nav .= ' (merge: ' . join(' ', map { - $cgi->a({-href => href(action=>"commitdiff", - hash=>$_)}, + $cgi->a({-href => href(-replay=>1, + hash=>$_, hash_base=>undef)}, esc_html(substr($_, 0, 7))); } @{$co{'parents'}} ) . ')'; @@ -7270,7 +7451,8 @@ sub git_commitdiff { $use_parents ? @{$co{'parents'}} : $hash_parent); print "<br/>\n"; - git_patchset_body($fd, \@difftree, $hash, + git_patchset_body($fd, $diff_style, + \@difftree, $hash, $use_parents ? @{$co{'parents'}} : $hash_parent); close $fd; print "</div>\n"; # class="page_body" diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 7d88509208..c7827e8f1d 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -475,6 +475,36 @@ div.diff.nodifferences { color: #600000; } +/* side-by-side diff */ +div.chunk_block { + overflow: hidden; +} + +div.chunk_block div.old { + float: left; + width: 50%; + overflow: hidden; +} + +div.chunk_block div.new { + margin-left: 50%; + width: 50%; +} + +div.chunk_block.rem div.old div.diff.rem { + background-color: #fff5f5; +} +div.chunk_block.add div.new div.diff.add { + background-color: #f8fff8; +} +div.chunk_block.chg div div.diff { + background-color: #fffff0; +} +div.chunk_block.ctx div div.diff.ctx { + color: #404040; +} + + div.index_include { border: solid #d9d8d1; border-width: 0px 0px 1px; diff --git a/gitweb/static/js/javascript-detection.js b/gitweb/static/js/javascript-detection.js index 93dd2bdd91..fa2596f77c 100644 --- a/gitweb/static/js/javascript-detection.js +++ b/gitweb/static/js/javascript-detection.js @@ -16,7 +16,7 @@ * and other reasons to not add 'js=1' param at the end of link * @constant */ -var jsExceptionsRe = /[;?]js=[01]$/; +var jsExceptionsRe = /[;?]js=[01](#.*)?$/; /** * Add '?js=1' or ';js=1' to the end of every link in the document @@ -33,9 +33,9 @@ function fixLinks() { var allLinks = document.getElementsByTagName("a") || document.links; for (var i = 0, len = allLinks.length; i < len; i++) { var link = allLinks[i]; - if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/; - link.href += - (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1'; + if (!jsExceptionsRe.test(link)) { + link.href = link.href.replace(/(#|$)/, + (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1$1'); } } } diff --git a/gpg-interface.c b/gpg-interface.c new file mode 100644 index 0000000000..ff232c8c5d --- /dev/null +++ b/gpg-interface.c @@ -0,0 +1,138 @@ +#include "cache.h" +#include "run-command.h" +#include "strbuf.h" +#include "gpg-interface.h" +#include "sigchain.h" + +static char *configured_signing_key; + +void set_signing_key(const char *key) +{ + free(configured_signing_key); + configured_signing_key = xstrdup(key); +} + +int git_gpg_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "user.signingkey")) { + if (!value) + return config_error_nonbool(var); + set_signing_key(value); + } + return 0; +} + +const char *get_signing_key(void) +{ + if (configured_signing_key) + return configured_signing_key; + return git_committer_info(IDENT_ERROR_ON_NO_NAME|IDENT_NO_DATE); +} + +/* + * Create a detached signature for the contents of "buffer" and append + * it after "signature"; "buffer" and "signature" can be the same + * strbuf instance, which would cause the detached signature appended + * at the end. + */ +int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) +{ + struct child_process gpg; + const char *args[4]; + ssize_t len; + size_t i, j, bottom; + + memset(&gpg, 0, sizeof(gpg)); + gpg.argv = args; + gpg.in = -1; + gpg.out = -1; + args[0] = "gpg"; + args[1] = "-bsau"; + args[2] = signing_key; + args[3] = NULL; + + if (start_command(&gpg)) + return error(_("could not run gpg.")); + + /* + * When the username signingkey is bad, program could be terminated + * because gpg exits without reading and then write gets SIGPIPE. + */ + sigchain_push(SIGPIPE, SIG_IGN); + + if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) { + close(gpg.in); + close(gpg.out); + finish_command(&gpg); + return error(_("gpg did not accept the data")); + } + close(gpg.in); + + bottom = signature->len; + len = strbuf_read(signature, gpg.out, 1024); + close(gpg.out); + + sigchain_pop(SIGPIPE); + + if (finish_command(&gpg) || !len || len < 0) + return error(_("gpg failed to sign the data")); + + /* Strip CR from the line endings, in case we are on Windows. */ + for (i = j = bottom; i < signature->len; i++) + if (signature->buf[i] != '\r') { + if (i != j) + signature->buf[j] = signature->buf[i]; + j++; + } + strbuf_setlen(signature, j); + + return 0; +} + +/* + * Run "gpg" to see if the payload matches the detached signature. + * gpg_output_to tells where the output from "gpg" should go: + * < 0: /dev/null + * = 0: standard error of the calling process + * > 0: the specified file descriptor + */ +int verify_signed_buffer(const char *payload, size_t payload_size, + const char *signature, size_t signature_size, + struct strbuf *gpg_output) +{ + struct child_process gpg; + const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; + char path[PATH_MAX]; + int fd, ret; + + fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX"); + if (fd < 0) + return error("could not create temporary file '%s': %s", + path, strerror(errno)); + if (write_in_full(fd, signature, signature_size) < 0) + return error("failed writing detached signature to '%s': %s", + path, strerror(errno)); + close(fd); + + memset(&gpg, 0, sizeof(gpg)); + gpg.argv = args_gpg; + gpg.in = -1; + if (gpg_output) + gpg.err = -1; + args_gpg[2] = path; + if (start_command(&gpg)) { + unlink(path); + return error("could not run gpg."); + } + + write_in_full(gpg.in, payload, payload_size); + close(gpg.in); + + if (gpg_output) + strbuf_read(gpg_output, gpg.err, 0); + ret = finish_command(&gpg); + + unlink_or_warn(path); + + return ret; +} diff --git a/gpg-interface.h b/gpg-interface.h new file mode 100644 index 0000000000..b9c36088ce --- /dev/null +++ b/gpg-interface.h @@ -0,0 +1,10 @@ +#ifndef GPG_INTERFACE_H +#define GPG_INTERFACE_H + +extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); +extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output); +extern int git_gpg_config(const char *, const char *, void *); +extern void set_signing_key(const char *); +extern const char *get_signing_key(void); + +#endif @@ -39,7 +39,15 @@ int get_sha1_hex(const char *hex, unsigned char *sha1) { int i; for (i = 0; i < 20; i++) { - unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); + unsigned int val; + /* + * hex[1]=='\0' is caught when val is checked below, + * but if hex[0] is NUL we have to avoid reading + * past the end of the string: + */ + if (!hex[0]) + return -1; + val = (hexval(hex[0]) << 4) | hexval(hex[1]); if (val & ~0xff) return -1; *sha1++ = val; diff --git a/http-backend.c b/http-backend.c index 59ad7da605..869d515383 100644 --- a/http-backend.c +++ b/http-backend.c @@ -545,6 +545,8 @@ int main(int argc, char **argv) char *cmd_arg = NULL; int i; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); set_die_routine(die_webcgi); diff --git a/http-fetch.c b/http-fetch.c index 8c4c5d2224..ba3ea10670 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -22,6 +22,8 @@ int main(int argc, const char **argv) int get_verbosely = 0; int get_recover = 0; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); while (arg < argc && argv[arg][0] == '-') { @@ -67,7 +69,7 @@ int main(int argc, const char **argv) git_config(git_default_config, NULL); - http_init(NULL); + http_init(NULL, url, 0); walker = get_http_walker(url); walker->get_tree = get_tree; walker->get_history = get_history; diff --git a/http-push.c b/http-push.c index 376331a76f..f22f7e43ca 100644 --- a/http-push.c +++ b/http-push.c @@ -1606,10 +1606,10 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) strbuf_release(&buffer); } -static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1) +static int verify_merge_base(unsigned char *head_sha1, struct ref *remote) { - struct commit *head = lookup_commit(head_sha1); - struct commit *branch = lookup_commit(branch_sha1); + struct commit *head = lookup_commit_or_die(head_sha1, "HEAD"); + struct commit *branch = lookup_commit_or_die(remote->old_sha1, remote->name); struct commit_list *merge_bases = get_merge_bases(head, branch, 1); return (merge_bases && !merge_bases->next && merge_bases->item == branch); @@ -1680,7 +1680,7 @@ static int delete_remote_branch(const char *pattern, int force) return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1)); /* Remote branch must be an ancestor of remote HEAD */ - if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) { + if (!verify_merge_base(head_sha1, remote_ref)) { return error("The branch '%s' is not an ancestor " "of your current HEAD.\n" "If you are sure you want to delete it," @@ -1747,7 +1747,8 @@ int main(int argc, char **argv) int i; int new_refs; struct ref *ref, *local_refs; - struct remote *remote; + + git_setup_gettext(); git_extract_argv0_path(argv[0]); @@ -1821,14 +1822,7 @@ int main(int argc, char **argv) memset(remote_dir_exists, -1, 256); - /* - * 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); + http_init(NULL, repo->url, 1); #ifdef USE_CURL_MULTI is_running_queue = 0; @@ -1877,8 +1871,8 @@ int main(int argc, char **argv) } /* match them up */ - if (match_refs(local_refs, &remote_refs, - nr_refspec, (const char **) refspec, push_all)) { + if (match_push_refs(local_refs, &remote_refs, + nr_refspec, (const char **) refspec, push_all)) { rc = -1; goto cleanup; } @@ -3,8 +3,8 @@ #include "sideband.h" #include "run-command.h" #include "url.h" +#include "credential.h" -int data_received; int active_requests; int http_is_verbose; size_t http_post_buffer = 16 * LARGE_PACKET_MAX; @@ -42,7 +42,8 @@ static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; static const char *curl_http_proxy; static const char *curl_cookie_file; -static char *user_name, *user_pass; +static struct credential http_auth = CREDENTIAL_INIT; +static int http_proactive_auth; static const char *user_agent; #if LIBCURL_VERSION_NUM >= 0x071700 @@ -53,7 +54,7 @@ static const char *user_agent; #define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD #endif -static char *ssl_cert_password; +static struct credential cert_auth = CREDENTIAL_INIT; static int ssl_cert_password_required; static struct curl_slist *pragma_header; @@ -99,13 +100,11 @@ size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) struct strbuf *buffer = buffer_; strbuf_add(buffer, ptr, size); - data_received++; return size; } size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf) { - data_received++; return eltsize * nmemb; } @@ -211,11 +210,11 @@ static int http_options(const char *var, const char *value, void *cb) static void init_curl_http_auth(CURL *result) { - if (user_name) { + if (http_auth.username) { struct strbuf up = STRBUF_INIT; - if (!user_pass) - user_pass = xstrdup(git_getpass("Password: ")); - strbuf_addf(&up, "%s:%s", user_name, user_pass); + credential_fill(&http_auth); + strbuf_addf(&up, "%s:%s", + http_auth.username, http_auth.password); curl_easy_setopt(result, CURLOPT_USERPWD, strbuf_detach(&up, NULL)); } @@ -223,18 +222,14 @@ static void init_curl_http_auth(CURL *result) static int has_cert_password(void) { - if (ssl_cert_password != NULL) - return 1; if (ssl_cert == NULL || ssl_cert_password_required != 1) return 0; - /* Only prompt the user once. */ - ssl_cert_password_required = -1; - ssl_cert_password = git_getpass("Certificate Password: "); - if (ssl_cert_password != NULL) { - ssl_cert_password = xstrdup(ssl_cert_password); - return 1; - } else - return 0; + if (!cert_auth.password) { + cert_auth.protocol = xstrdup("cert"); + cert_auth.path = xstrdup(ssl_cert); + credential_fill(&cert_auth); + } + return 1; } static CURL *get_curl_handle(void) @@ -258,12 +253,13 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY); #endif - init_curl_http_auth(result); + if (http_proactive_auth) + init_curl_http_auth(result); if (ssl_cert != NULL) curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); if (has_cert_password()) - curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password); + curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password); #if LIBCURL_VERSION_NUM >= 0x070903 if (ssl_key != NULL) curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); @@ -305,55 +301,6 @@ static CURL *get_curl_handle(void) return result; } -static void http_auth_init(const char *url) -{ - char *at, *colon, *cp, *slash, *decoded; - 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'; - decoded = url_decode(user_name); - free(user_name); - user_name = decoded; - user_pass = NULL; - } else { - len = colon - cp; - user_name = xmalloc(len + 1); - memcpy(user_name, cp, len); - user_name[len] = '\0'; - decoded = url_decode(user_name); - free(user_name); - user_name = decoded; - len = at - (colon + 1); - user_pass = xmalloc(len + 1); - memcpy(user_pass, colon + 1, len); - user_pass[len] = '\0'; - decoded = url_decode(user_pass); - free(user_pass); - user_pass = decoded; - } -} - static void set_from_env(const char **var, const char *envname) { const char *val = getenv(envname); @@ -361,7 +308,7 @@ static void set_from_env(const char **var, const char *envname) *var = val; } -void http_init(struct remote *remote) +void http_init(struct remote *remote, const char *url, int proactive_auth) { char *low_speed_limit; char *low_speed_time; @@ -372,6 +319,8 @@ void http_init(struct remote *remote) curl_global_init(CURL_GLOBAL_ALL); + http_proactive_auth = proactive_auth; + if (remote && remote->http_proxy) curl_http_proxy = xstrdup(remote->http_proxy); @@ -425,11 +374,11 @@ 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]); + if (url) { + credential_from_url(&http_auth, url); if (!ssl_cert_password_required && getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") && - !prefixcmp(remote->url[0], "https://")) + !prefixcmp(url, "https://")) ssl_cert_password_required = 1; } @@ -475,10 +424,10 @@ void http_cleanup(void) curl_http_proxy = NULL; } - if (ssl_cert_password != NULL) { - memset(ssl_cert_password, 0, strlen(ssl_cert_password)); - free(ssl_cert_password); - ssl_cert_password = NULL; + if (cert_auth.password != NULL) { + memset(cert_auth.password, 0, strlen(cert_auth.password)); + free(cert_auth.password); + cert_auth.password = NULL; } ssl_cert_password_required = 0; } @@ -530,7 +479,6 @@ struct active_request_slot *get_active_slot(void) active_requests++; slot->in_use = 1; - slot->local = NULL; slot->results = NULL; slot->finished = NULL; slot->callback_data = NULL; @@ -634,8 +582,6 @@ void step_active_slots(void) void run_active_slot(struct active_request_slot *slot) { #ifdef USE_CURL_MULTI - long last_pos = 0; - long current_pos; fd_set readfds; fd_set writefds; fd_set excfds; @@ -645,25 +591,33 @@ void run_active_slot(struct active_request_slot *slot) slot->finished = &finished; while (!finished) { - data_received = 0; step_active_slots(); - if (!data_received && slot->local != NULL) { - current_pos = ftell(slot->local); - if (current_pos > last_pos) - data_received++; - last_pos = current_pos; - } + if (slot->in_use) { +#if LIBCURL_VERSION_NUM >= 0x070f04 + long curl_timeout; + curl_multi_timeout(curlm, &curl_timeout); + if (curl_timeout == 0) { + continue; + } else if (curl_timeout == -1) { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 50000; + } else { + select_timeout.tv_sec = curl_timeout / 1000; + select_timeout.tv_usec = (curl_timeout % 1000) * 1000; + } +#else + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 50000; +#endif - if (slot->in_use && !data_received) { - max_fd = 0; + max_fd = -1; FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&excfds); - select_timeout.tv_sec = 0; - select_timeout.tv_usec = 50000; - select(max_fd, &readfds, &writefds, - &excfds, &select_timeout); + curl_multi_fdset(curlm, &readfds, &writefds, &excfds, &max_fd); + + select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout); } } #else @@ -741,14 +695,6 @@ static inline int needs_quote(int ch) return 1; } -static inline int hex(int v) -{ - if (v < 10) - return '0' + v; - else - return 'A' + v - 10; -} - static char *quote_ref_url(const char *base, const char *ref) { struct strbuf buf = STRBUF_INIT; @@ -816,7 +762,6 @@ static int http_request(const char *url, void *result, int target, int options) headers = curl_slist_append(headers, buf.buf); strbuf_reset(&buf); } - slot->local = result; } else curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); @@ -838,40 +783,47 @@ static int http_request(const char *url, void *result, int target, int options) else if (missing_target(&results)) ret = HTTP_MISSING_TARGET; else if (results.http_code == 401) { - if (user_name) { + if (http_auth.username && http_auth.password) { + credential_reject(&http_auth); ret = HTTP_NOAUTH; } else { - /* - * git_getpass is needed here because its very likely stdin/stdout are - * pipes to our parent process. So we instead need to use /dev/tty, - * but that is non-portable. Using git_getpass() can at least be stubbed - * on other platforms with a different implementation if/when necessary. - */ - user_name = xstrdup(git_getpass("Username: ")); + credential_fill(&http_auth); init_curl_http_auth(slot->curl); ret = HTTP_REAUTH; } - } else + } else { + if (!curl_errorstr[0]) + strlcpy(curl_errorstr, + curl_easy_strerror(results.curl_result), + sizeof(curl_errorstr)); ret = HTTP_ERROR; + } } else { error("Unable to start HTTP request for %s", url); ret = HTTP_START_FAILED; } - slot->local = NULL; curl_slist_free_all(headers); strbuf_release(&buf); + if (ret == HTTP_OK) + credential_approve(&http_auth); + return ret; } +static int http_request_reauth(const char *url, void *result, int target, + int options) +{ + int ret = http_request(url, result, target, options); + if (ret != HTTP_REAUTH) + return ret; + return http_request(url, result, target, options); +} + int http_get_strbuf(const char *url, struct strbuf *result, int options) { - int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options); - if (http_ret == HTTP_REAUTH) { - http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options); - } - return http_ret; + return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options); } /* @@ -894,7 +846,7 @@ static int http_get_file(const char *url, const char *filename, int options) goto cleanup; } - ret = http_request(url, result, HTTP_REQUEST_FILE, options); + ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options); fclose(result); if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename)) @@ -908,7 +860,7 @@ 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); + error("%s while accessing %s", curl_errorstr, url); return ret; } @@ -1048,7 +1000,6 @@ 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); @@ -1070,7 +1021,6 @@ int finish_http_pack_request(struct http_pack_request *preq) fclose(preq->packfile); preq->packfile = NULL; - preq->slot->local = NULL; lst = preq->lst; while (*lst != p) @@ -1139,7 +1089,6 @@ struct http_pack_request *new_http_pack_request( } 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, preq->url); @@ -1196,7 +1145,6 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb, 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; } @@ -49,7 +49,6 @@ struct slot_results { struct active_request_slot { CURL *curl; - FILE *local; int in_use; CURLcode curl_result; long http_code; @@ -86,10 +85,10 @@ extern void add_fill_function(void *data, int (*fill)(void *)); extern void step_active_slots(void); #endif -extern void http_init(struct remote *remote); +extern void http_init(struct remote *remote, const char *url, + int proactive_auth); extern void http_cleanup(void); -extern int data_received; extern int active_requests; extern int http_is_verbose; extern size_t http_post_buffer; @@ -50,6 +50,54 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz) } +static int add_mailname_host(char *buf, size_t len) +{ + FILE *mailname; + + mailname = fopen("/etc/mailname", "r"); + if (!mailname) { + if (errno != ENOENT) + warning("cannot open /etc/mailname: %s", + strerror(errno)); + return -1; + } + if (!fgets(buf, len, mailname)) { + if (ferror(mailname)) + warning("cannot read /etc/mailname: %s", + strerror(errno)); + fclose(mailname); + return -1; + } + /* success! */ + fclose(mailname); + return 0; +} + +static void add_domainname(char *buf, size_t len) +{ + struct hostent *he; + size_t namelen; + const char *domainname; + + if (gethostname(buf, len)) { + warning("cannot get host name: %s", strerror(errno)); + strlcpy(buf, "(none)", len); + return; + } + namelen = strlen(buf); + if (memchr(buf, '.', namelen)) + return; + + he = gethostbyname(buf); + buf[namelen++] = '.'; + buf += namelen; + len -= namelen; + if (he && (domainname = strchr(he->h_name, '.'))) + strlcpy(buf, domainname + 1, len); + else + strlcpy(buf, "(none)", len); +} + static void copy_email(const struct passwd *pw) { /* @@ -61,35 +109,29 @@ static void copy_email(const struct passwd *pw) die("Your sysadmin must hate you!"); memcpy(git_default_email, pw->pw_name, len); git_default_email[len++] = '@'; - gethostname(git_default_email + len, sizeof(git_default_email) - len); - if (!strchr(git_default_email+len, '.')) { - struct hostent *he = gethostbyname(git_default_email + len); - char *domainname; - - len = strlen(git_default_email); - git_default_email[len++] = '.'; - if (he && (domainname = strchr(he->h_name, '.'))) - strlcpy(git_default_email + len, domainname + 1, - sizeof(git_default_email) - len); - else - strlcpy(git_default_email + len, "(none)", - sizeof(git_default_email) - len); - } + + if (!add_mailname_host(git_default_email + len, + sizeof(git_default_email) - len)) + return; /* read from "/etc/mailname" (Debian) */ + add_domainname(git_default_email + len, + sizeof(git_default_email) - len); } -static void setup_ident(void) +static void setup_ident(const char **name, const char **emailp) { struct passwd *pw = NULL; /* Get the name ("gecos") */ - if (!git_default_name[0]) { + if (!*name && !git_default_name[0]) { pw = getpwuid(getuid()); if (!pw) die("You don't exist. Go away!"); copy_gecos(pw, git_default_name, sizeof(git_default_name)); } + if (!*name) + *name = git_default_name; - if (!git_default_email[0]) { + if (!*emailp && !git_default_email[0]) { const char *email = getenv("EMAIL"); if (email && email[0]) { @@ -104,6 +146,8 @@ static void setup_ident(void) copy_email(pw); } } + if (!*emailp) + *emailp = git_default_email; /* And set the default date */ if (!git_default_date[0]) @@ -199,11 +243,7 @@ const char *fmt_ident(const char *name, const char *email, int warn_on_no_name = (flag & IDENT_WARN_ON_NO_NAME); int name_addr_only = (flag & IDENT_NO_DATE); - setup_ident(); - if (!name) - name = git_default_name; - if (!email) - email = git_default_email; + setup_ident(&name, &email); if (!*name) { struct passwd *pw; diff --git a/imap-send.c b/imap-send.c index e1ad1a48ce..e40125a22b 100644 --- a/imap-send.c +++ b/imap-send.c @@ -25,6 +25,7 @@ #include "cache.h" #include "exec_cmd.h" #include "run-command.h" +#include "prompt.h" #ifdef NO_OPENSSL typedef void *SSL; #else @@ -161,7 +162,6 @@ static struct imap_server_conf server = { struct imap_store_conf { struct store_conf gen; struct imap_server_conf *server; - unsigned use_namespace:1; }; #define NIL (void *)0x1 @@ -1209,13 +1209,10 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) goto bail; } if (!srvc->pass) { - char prompt[80]; - sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host); - arg = git_getpass(prompt); - if (!arg) { - perror("getpass"); - exit(1); - } + struct strbuf prompt = STRBUF_INIT; + strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host); + arg = git_getpass(prompt.buf); + strbuf_release(&prompt); if (!*arg) { fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host); goto bail; @@ -1539,6 +1536,8 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); + git_setup_gettext(); + if (argc != 1) usage(imap_send_usage); @@ -674,7 +674,7 @@ cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch) copy of the preceding main search loops. */ if (lim - mch > kwset->maxd) lim = mch + kwset->maxd; - lmch = 0; + lmch = NULL; d = 1; while (lim - end >= d) { diff --git a/list-objects.c b/list-objects.c index 0fb44e7ed7..3dd4a96019 100644 --- a/list-objects.c +++ b/list-objects.c @@ -12,7 +12,8 @@ static void process_blob(struct rev_info *revs, struct blob *blob, show_object_fn show, struct name_path *path, - const char *name) + const char *name, + void *cb_data) { struct object *obj = &blob->object; @@ -23,7 +24,7 @@ static void process_blob(struct rev_info *revs, if (obj->flags & (UNINTERESTING | SEEN)) return; obj->flags |= SEEN; - show(obj, path, name); + show(obj, path, name, cb_data); } /* @@ -52,7 +53,8 @@ static void process_gitlink(struct rev_info *revs, const unsigned char *sha1, show_object_fn show, struct name_path *path, - const char *name) + const char *name, + void *cb_data) { /* Nothing to do */ } @@ -62,13 +64,15 @@ static void process_tree(struct rev_info *revs, show_object_fn show, struct name_path *path, struct strbuf *base, - const char *name) + const char *name, + void *cb_data) { struct object *obj = &tree->object; struct tree_desc desc; struct name_entry entry; struct name_path me; - int match = revs->diffopt.pathspec.nr == 0 ? 2 : 0; + enum interesting match = revs->diffopt.pathspec.nr == 0 ? + all_entries_interesting: entry_not_interesting; int baselen = base->len; if (!revs->tree_objects) @@ -80,7 +84,7 @@ static void process_tree(struct rev_info *revs, if (parse_tree(tree) < 0) die("bad tree object %s", sha1_to_hex(obj->sha1)); obj->flags |= SEEN; - show(obj, path, name); + show(obj, path, name, cb_data); me.up = path; me.elem = name; me.elem_len = strlen(name); @@ -94,26 +98,29 @@ static void process_tree(struct rev_info *revs, init_tree_desc(&desc, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { - if (match != 2) { + if (match != all_entries_interesting) { match = tree_entry_interesting(&entry, base, 0, &revs->diffopt.pathspec); - if (match < 0) + if (match == all_entries_not_interesting) break; - if (match == 0) + if (match == entry_not_interesting) continue; } if (S_ISDIR(entry.mode)) process_tree(revs, lookup_tree(entry.sha1), - show, &me, base, entry.path); + show, &me, base, entry.path, + cb_data); else if (S_ISGITLINK(entry.mode)) process_gitlink(revs, entry.sha1, - show, &me, entry.path); + show, &me, entry.path, + cb_data); else process_blob(revs, lookup_blob(entry.sha1), - show, &me, entry.path); + show, &me, entry.path, + cb_data); } strbuf_setlen(base, baselen); free(tree->buffer); @@ -185,17 +192,17 @@ void traverse_commit_list(struct rev_info *revs, continue; if (obj->type == OBJ_TAG) { obj->flags |= SEEN; - show_object(obj, NULL, name); + show_object(obj, NULL, name, data); continue; } if (obj->type == OBJ_TREE) { process_tree(revs, (struct tree *)obj, show_object, - NULL, &base, name); + NULL, &base, name, data); continue; } if (obj->type == OBJ_BLOB) { process_blob(revs, (struct blob *)obj, show_object, - NULL, name); + NULL, name, data); continue; } die("unknown pending object %s (%s)", diff --git a/list-objects.h b/list-objects.h index d65dbf03e6..3db7bb6fa3 100644 --- a/list-objects.h +++ b/list-objects.h @@ -2,11 +2,10 @@ #define LIST_OBJECTS_H 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 *); - +typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *, void *); void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *); +typedef void (*show_edge_fn)(struct commit *); void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn); #endif diff --git a/log-tree.c b/log-tree.c index 24c295ea1d..e7694a3a4c 100644 --- a/log-tree.c +++ b/log-tree.c @@ -165,6 +165,14 @@ static void show_parents(struct commit *commit, int abbrev) } } +static void show_children(struct rev_info *opt, struct commit *commit, int abbrev) +{ + struct commit_list *p = lookup_decoration(&opt->children, &commit->object); + for ( ; p; p = p->next) { + printf(" %s", find_unique_abbrev(p->item->object.sha1, abbrev)); + } +} + void show_decorations(struct rev_info *opt, struct commit *commit) { const char *prefix; @@ -414,6 +422,8 @@ void show_log(struct rev_info *opt) fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->print_parents) show_parents(commit, abbrev_commit); + if (opt->children.name) + show_children(opt, commit, abbrev_commit); show_decorations(opt, commit); if (opt->graph && !graph_is_commit_finished(opt->graph)) { putchar('\n'); @@ -473,6 +483,8 @@ void show_log(struct rev_info *opt) stdout); if (opt->print_parents) show_parents(commit, abbrev_commit); + if (opt->children.name) + show_children(opt, commit, abbrev_commit); if (parent) printf(" (from %s)", find_unique_abbrev(parent->object.sha1, @@ -70,8 +70,7 @@ static void add_mapping(struct string_list *map, } else { /* create mailmap entry */ struct string_list_item *item = string_list_insert_at_index(map, index, old_email); - item->util = xmalloc(sizeof(struct mailmap_entry)); - memset(item->util, 0, sizeof(struct mailmap_entry)); + item->util = xcalloc(1, sizeof(struct mailmap_entry)); ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1; } me = (struct mailmap_entry *)map->items[index].util; @@ -88,7 +87,7 @@ static void add_mapping(struct string_list *map, me->email = xstrdup(new_email); } } else { - struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info)); + struct mailmap_info *mi = xcalloc(1, sizeof(struct mailmap_info)); debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index); if (new_name) mi->name = xstrdup(new_name); diff --git a/merge-recursive.c b/merge-recursive.c index 3efc04e04f..d83cd6c662 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -38,16 +38,15 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two, return lookup_tree(shifted); } -/* - * A virtual commit has (const char *)commit->util set to the name. - */ - static struct commit *make_virtual_commit(struct tree *tree, const char *comment) { struct commit *commit = xcalloc(1, sizeof(struct commit)); + struct merge_remote_desc *desc = xmalloc(sizeof(*desc)); + + desc->name = comment; + desc->obj = (struct object *)commit; commit->tree = tree; - commit->util = (void*)comment; - /* avoid warnings */ + commit->util = desc; commit->object.parsed = 1; return commit; } @@ -184,7 +183,7 @@ static void output_commit_title(struct merge_options *o, struct commit *commit) for (i = o->call_depth; i--;) fputs(" ", stdout); if (commit->util) - printf("virtual %s\n", (char *)commit->util); + printf("virtual %s\n", merge_remote_util(commit)->name); else { printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); if (parse_commit(commit) != 0) @@ -265,7 +264,7 @@ struct tree *write_tree_from_memory(struct merge_options *o) if (!cache_tree_fully_valid(active_cache_tree) && cache_tree_update(active_cache_tree, - active_cache, active_nr, 0, 0) < 0) + active_cache, active_nr, 0, 0, 0) < 0) die("error building trees"); result = lookup_tree(active_cache_tree->sha1); @@ -390,7 +389,7 @@ static void record_df_conflict_files(struct merge_options *o, struct string_list *entries) { /* If there is a D/F conflict and the file for such a conflict - * currently exist in the working copy, we want to allow it to be + * currently exist in the working tree, we want to allow it to be * removed to make room for the corresponding directory if needed. * The files underneath the directories of such D/F conflicts will * be processed before the corresponding file involved in the D/F @@ -946,8 +945,10 @@ static struct merge_file_info merge_file_1(struct merge_options *o, free(result_buf.ptr); result.clean = (merge_status == 0); } else if (S_ISGITLINK(a->mode)) { - result.clean = merge_submodule(result.sha, one->path, one->sha1, - a->sha1, b->sha1); + result.clean = merge_submodule(result.sha, + one->path, one->sha1, + a->sha1, b->sha1, + !o->call_depth); } else if (S_ISLNK(a->mode)) { hashcpy(result.sha, a->sha1); diff --git a/mergetools/bc3 b/mergetools/bc3 index 27b3dd48b8..b6319d206e 100644 --- a/mergetools/bc3 +++ b/mergetools/bc3 @@ -16,5 +16,10 @@ merge_cmd () { } translate_merge_tool_path() { - echo bcompare + if type bcomp >/dev/null 2>/dev/null + then + echo bcomp + else + echo bcompare + fi } diff --git a/name-hash.c b/name-hash.c index c6b6a3fe4c..d8d25c23e9 100644 --- a/name-hash.c +++ b/name-hash.c @@ -57,12 +57,10 @@ static void hash_index_entry_directories(struct index_state *istate, struct cach if (*ptr == '/') { ++ptr; hash = hash_name(ce->name, ptr - ce->name); - if (!lookup_hash(hash, &istate->name_hash)) { - pos = insert_hash(hash, ce, &istate->name_hash); - if (pos) { - ce->next = *pos; - *pos = ce; - } + pos = insert_hash(hash, ce, &istate->name_hash); + if (pos) { + ce->dir_next = *pos; + *pos = ce; } } } @@ -76,7 +74,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) if (ce->ce_flags & CE_HASHED) return; ce->ce_flags |= CE_HASHED; - ce->next = NULL; + ce->next = ce->dir_next = NULL; hash = hash_name(ce->name, ce_namelen(ce)); pos = insert_hash(hash, ce, &istate->name_hash); if (pos) { @@ -166,7 +164,10 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na if (same_name(ce, name, namelen, icase)) return ce; } - ce = ce->next; + if (icase && name[namelen - 1] == '/') + ce = ce->dir_next; + else + ce = ce->next; } /* diff --git a/notes-merge.c b/notes-merge.c index e1aaf43b43..2de27af751 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -21,14 +21,6 @@ void init_notes_merge_options(struct notes_merge_options *o) o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT; } -#define OUTPUT(o, v, ...) \ - do { \ - if ((o)->verbosity >= (v)) { \ - printf(__VA_ARGS__); \ - puts(""); \ - } \ - } while (0) - static int path_to_sha1(const char *path, unsigned char *sha1) { char hex_sha1[40]; @@ -392,21 +384,26 @@ static int merge_one_change_manual(struct notes_merge_options *o, strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj)); - OUTPUT(o, 2, "Auto-merging notes for %s", sha1_to_hex(p->obj)); + if (o->verbosity >= 2) + printf("Auto-merging notes for %s\n", sha1_to_hex(p->obj)); check_notes_merge_worktree(o); if (is_null_sha1(p->local)) { /* D/F conflict, checkout p->remote */ assert(!is_null_sha1(p->remote)); - OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s " - "deleted in %s and modified in %s. Version from %s " - "left in tree.", sha1_to_hex(p->obj), lref, rref, rref); + if (o->verbosity >= 1) + printf("CONFLICT (delete/modify): Notes for object %s " + "deleted in %s and modified in %s. Version from %s " + "left in tree.\n", + sha1_to_hex(p->obj), lref, rref, rref); write_note_to_worktree(p->obj, p->remote); } else if (is_null_sha1(p->remote)) { /* D/F conflict, checkout p->local */ assert(!is_null_sha1(p->local)); - OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s " - "deleted in %s and modified in %s. Version from %s " - "left in tree.", sha1_to_hex(p->obj), rref, lref, lref); + if (o->verbosity >= 1) + printf("CONFLICT (delete/modify): Notes for object %s " + "deleted in %s and modified in %s. Version from %s " + "left in tree.\n", + sha1_to_hex(p->obj), rref, lref, lref); write_note_to_worktree(p->obj, p->local); } else { /* "regular" conflict, checkout result of ll_merge() */ @@ -415,8 +412,9 @@ static int merge_one_change_manual(struct notes_merge_options *o, reason = "add/add"; assert(!is_null_sha1(p->local)); assert(!is_null_sha1(p->remote)); - OUTPUT(o, 1, "CONFLICT (%s): Merge conflict in notes for " - "object %s", reason, sha1_to_hex(p->obj)); + if (o->verbosity >= 1) + printf("CONFLICT (%s): Merge conflict in notes for " + "object %s\n", reason, sha1_to_hex(p->obj)); ll_merge_in_worktree(o, p); } @@ -438,24 +436,30 @@ static int merge_one_change(struct notes_merge_options *o, case NOTES_MERGE_RESOLVE_MANUAL: return merge_one_change_manual(o, p, t); case NOTES_MERGE_RESOLVE_OURS: - OUTPUT(o, 2, "Using local notes for %s", sha1_to_hex(p->obj)); + if (o->verbosity >= 2) + printf("Using local notes for %s\n", + sha1_to_hex(p->obj)); /* nothing to do */ return 0; case NOTES_MERGE_RESOLVE_THEIRS: - OUTPUT(o, 2, "Using remote notes for %s", sha1_to_hex(p->obj)); + if (o->verbosity >= 2) + printf("Using remote notes for %s\n", + sha1_to_hex(p->obj)); if (add_note(t, p->obj, p->remote, combine_notes_overwrite)) die("BUG: combine_notes_overwrite failed"); return 0; case NOTES_MERGE_RESOLVE_UNION: - OUTPUT(o, 2, "Concatenating local and remote notes for %s", - sha1_to_hex(p->obj)); + if (o->verbosity >= 2) + printf("Concatenating local and remote notes for %s\n", + sha1_to_hex(p->obj)); if (add_note(t, p->obj, p->remote, combine_notes_concatenate)) die("failed to concatenate notes " "(combine_notes_concatenate)"); return 0; case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ: - OUTPUT(o, 2, "Concatenating unique lines in local and remote " - "notes for %s", sha1_to_hex(p->obj)); + if (o->verbosity >= 2) + printf("Concatenating unique lines in local and remote " + "notes for %s\n", sha1_to_hex(p->obj)); if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq)) die("failed to concatenate notes " "(combine_notes_cat_sort_uniq)"); @@ -518,8 +522,9 @@ static int merge_from_diffs(struct notes_merge_options *o, conflicts = merge_changes(o, changes, &num_changes, t); free(changes); - OUTPUT(o, 4, "Merge result: %i unmerged notes and a %s notes tree", - conflicts, t->dirty ? "dirty" : "clean"); + if (o->verbosity >= 4) + printf("Merge result: %i unmerged notes and a %s notes tree\n", + conflicts, t->dirty ? "dirty" : "clean"); return conflicts ? -1 : 1; } @@ -568,9 +573,10 @@ int notes_merge(struct notes_merge_options *o, o->local_ref, o->remote_ref); /* Dereference o->local_ref into local_sha1 */ - if (!resolve_ref(o->local_ref, local_sha1, 0, NULL)) + if (read_ref_full(o->local_ref, local_sha1, 0, NULL)) die("Failed to resolve local notes ref '%s'", o->local_ref); - else if (!check_ref_format(o->local_ref) && is_null_sha1(local_sha1)) + else if (!check_refname_format(o->local_ref, 0) && + is_null_sha1(local_sha1)) local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */ else if (!(local = lookup_commit_reference(local_sha1))) die("Could not parse local commit %s (%s)", @@ -583,7 +589,7 @@ int notes_merge(struct notes_merge_options *o, * Failed to get remote_sha1. If o->remote_ref looks like an * unborn ref, perform the merge using an empty notes tree. */ - if (!check_ref_format(o->remote_ref)) { + if (!check_refname_format(o->remote_ref, 0)) { hashclr(remote_sha1); remote = NULL; } else { @@ -616,33 +622,40 @@ int notes_merge(struct notes_merge_options *o, if (!bases) { base_sha1 = null_sha1; base_tree_sha1 = EMPTY_TREE_SHA1_BIN; - OUTPUT(o, 4, "No merge base found; doing history-less merge"); + if (o->verbosity >= 4) + printf("No merge base found; doing history-less merge\n"); } else if (!bases->next) { base_sha1 = bases->item->object.sha1; base_tree_sha1 = bases->item->tree->object.sha1; - OUTPUT(o, 4, "One merge base found (%.7s)", - sha1_to_hex(base_sha1)); + if (o->verbosity >= 4) + printf("One merge base found (%.7s)\n", + sha1_to_hex(base_sha1)); } else { /* TODO: How to handle multiple merge-bases? */ base_sha1 = bases->item->object.sha1; base_tree_sha1 = bases->item->tree->object.sha1; - OUTPUT(o, 3, "Multiple merge bases found. Using the first " - "(%.7s)", sha1_to_hex(base_sha1)); + if (o->verbosity >= 3) + printf("Multiple merge bases found. Using the first " + "(%.7s)\n", sha1_to_hex(base_sha1)); } - OUTPUT(o, 4, "Merging remote commit %.7s into local commit %.7s with " - "merge-base %.7s", sha1_to_hex(remote->object.sha1), - sha1_to_hex(local->object.sha1), sha1_to_hex(base_sha1)); + if (o->verbosity >= 4) + printf("Merging remote commit %.7s into local commit %.7s with " + "merge-base %.7s\n", sha1_to_hex(remote->object.sha1), + sha1_to_hex(local->object.sha1), + sha1_to_hex(base_sha1)); if (!hashcmp(remote->object.sha1, base_sha1)) { /* Already merged; result == local commit */ - OUTPUT(o, 2, "Already up-to-date!"); + if (o->verbosity >= 2) + printf("Already up-to-date!\n"); hashcpy(result_sha1, local->object.sha1); goto found_result; } if (!hashcmp(local->object.sha1, base_sha1)) { /* Fast-forward; result == remote commit */ - OUTPUT(o, 2, "Fast-forward"); + if (o->verbosity >= 2) + printf("Fast-forward\n"); hashcpy(result_sha1, remote->object.sha1); goto found_result; } @@ -680,12 +693,13 @@ int notes_merge_commit(struct notes_merge_options *o, * Finally store the new commit object SHA1 into 'result_sha1'. */ struct dir_struct dir; - const char *path = git_path(NOTES_MERGE_WORKTREE "/"); + char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/")); int path_len = strlen(path), i; const char *msg = strstr(partial_commit->buffer, "\n\n"); - OUTPUT(o, 3, "Committing notes in notes merge worktree at %.*s", - path_len - 1, path); + if (o->verbosity >= 3) + printf("Committing notes in notes merge worktree at %.*s\n", + path_len - 1, path); if (!msg || msg[2] == '\0') die("partial notes commit has empty message"); @@ -700,7 +714,9 @@ int notes_merge_commit(struct notes_merge_options *o, unsigned char obj_sha1[20], blob_sha1[20]; if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) { - OUTPUT(o, 3, "Skipping non-SHA1 entry '%s'", ent->name); + if (o->verbosity >= 3) + printf("Skipping non-SHA1 entry '%s'\n", + ent->name); continue; } @@ -712,14 +728,17 @@ int notes_merge_commit(struct notes_merge_options *o, if (add_note(partial_tree, obj_sha1, blob_sha1, NULL)) die("Failed to add resolved note '%s' to notes tree", ent->name); - OUTPUT(o, 4, "Added resolved note for object %s: %s", - sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1)); + if (o->verbosity >= 4) + printf("Added resolved note for object %s: %s\n", + sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1)); } create_notes_commit(partial_tree, partial_commit->parents, msg, result_sha1); - OUTPUT(o, 4, "Finalized notes merge commit: %s", - sha1_to_hex(result_sha1)); + if (o->verbosity >= 4) + printf("Finalized notes merge commit: %s\n", + sha1_to_hex(result_sha1)); + free(path); return 0; } @@ -730,7 +749,8 @@ int notes_merge_abort(struct notes_merge_options *o) int ret; strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE)); - OUTPUT(o, 3, "Removing notes merge worktree at %s", buf.buf); + if (o->verbosity >= 3) + printf("Removing notes merge worktree at %s\n", buf.buf); ret = remove_dir_recursively(&buf, 0); strbuf_release(&buf); return ret; @@ -149,6 +149,8 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t struct tree *tree = lookup_tree(sha1); if (tree) { obj = &tree->object; + if (!tree->buffer) + tree->object.parsed = 0; if (!tree->object.parsed) { if (parse_tree_buffer(tree, buffer, size)) return NULL; diff --git a/pack-check.c b/pack-check.c index 0c19b6e5a5..63a595c45c 100644 --- a/pack-check.c +++ b/pack-check.c @@ -1,6 +1,7 @@ #include "cache.h" #include "pack.h" #include "pack-revindex.h" +#include "progress.h" struct idx_entry { off_t offset; @@ -42,7 +43,10 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, } static int verify_packfile(struct packed_git *p, - struct pack_window **w_curs) + struct pack_window **w_curs, + verify_fn fn, + struct progress *progress, uint32_t base_count) + { off_t index_size = p->index_size; const unsigned char *index_base = p->index_data; @@ -113,20 +117,25 @@ static int verify_packfile(struct packed_git *p, p->pack_name, (uintmax_t)offset); } data = unpack_entry(p, entries[i].offset, &type, &size); - if (!data) { + if (!data) err = error("cannot unpack %s from %s at offset %"PRIuMAX"", sha1_to_hex(entries[i].sha1), p->pack_name, (uintmax_t)entries[i].offset); - break; - } - if (check_sha1_signature(entries[i].sha1, data, size, typename(type))) { + else if (check_sha1_signature(entries[i].sha1, data, size, typename(type))) err = error("packed %s from %s is corrupt", sha1_to_hex(entries[i].sha1), p->pack_name); - free(data); - break; + else if (fn) { + int eaten = 0; + fn(entries[i].sha1, type, size, data, &eaten); + if (eaten) + data = NULL; } + if (((base_count + i) & 1023) == 0) + display_progress(progress, base_count + i); free(data); + } + display_progress(progress, base_count + i); free(entries); return err; @@ -155,7 +164,8 @@ int verify_pack_index(struct packed_git *p) return err; } -int verify_pack(struct packed_git *p) +int verify_pack(struct packed_git *p, verify_fn fn, + struct progress *progress, uint32_t base_count) { int err = 0; struct pack_window *w_curs = NULL; @@ -164,7 +174,7 @@ int verify_pack(struct packed_git *p) if (!p->index_data) return -1; - err |= verify_packfile(p, &w_curs); + err |= verify_packfile(p, &w_curs, fn, progress, base_count); unuse_pack(&w_curs); return err; diff --git a/pack-refs.c b/pack-refs.c index 1290570260..23bbd00e3e 100644 --- a/pack-refs.c +++ b/pack-refs.c @@ -72,7 +72,7 @@ static void try_remove_empty_parents(char *name) for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ while (*p && *p != '/') p++; - /* tolerate duplicate slashes; see check_ref_format() */ + /* tolerate duplicate slashes; see check_refname_format() */ while (*p == '/') p++; } diff --git a/pack-write.c b/pack-write.c index 9cd3bfbb4b..de2bd01414 100644 --- a/pack-write.c +++ b/pack-write.c @@ -129,6 +129,10 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec } sha1write(f, obj->sha1, 20); git_SHA1_Update(&ctx, obj->sha1, 20); + if ((opts->flags & WRITE_IDX_STRICT) && + (i && !hashcmp(list[-2]->sha1, obj->sha1))) + die("The same object %s appears twice in the pack", + sha1_to_hex(obj->sha1)); } if (index_version >= 2) { @@ -178,6 +182,18 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec return index_name; } +off_t write_pack_header(struct sha1file *f, uint32_t nr_entries) +{ + struct pack_header hdr; + + hdr.hdr_signature = htonl(PACK_SIGNATURE); + hdr.hdr_version = htonl(PACK_VERSION); + hdr.hdr_entries = htonl(nr_entries); + if (sha1write(f, &hdr, sizeof(hdr))) + return 0; + return sizeof(hdr); +} + /* * Update pack header with object_count and compute new SHA1 for pack data * associated to pack_fd, and write that SHA1 at the end. That new SHA1 @@ -316,3 +332,44 @@ int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned *hdr = c; return n; } + +struct sha1file *create_tmp_packfile(char **pack_tmp_name) +{ + char tmpname[PATH_MAX]; + int fd; + + fd = odb_mkstemp(tmpname, sizeof(tmpname), "pack/tmp_pack_XXXXXX"); + *pack_tmp_name = xstrdup(tmpname); + return sha1fd(fd, *pack_tmp_name); +} + +void finish_tmp_packfile(char *name_buffer, + const char *pack_tmp_name, + struct pack_idx_entry **written_list, + uint32_t nr_written, + struct pack_idx_option *pack_idx_opts, + unsigned char sha1[]) +{ + const char *idx_tmp_name; + char *end_of_name_prefix = strrchr(name_buffer, 0); + + if (adjust_shared_perm(pack_tmp_name)) + die_errno("unable to make temporary pack file readable"); + + idx_tmp_name = write_idx_file(NULL, written_list, nr_written, + pack_idx_opts, sha1); + if (adjust_shared_perm(idx_tmp_name)) + die_errno("unable to make temporary index file readable"); + + sprintf(end_of_name_prefix, "%s.pack", sha1_to_hex(sha1)); + free_pack_by_name(name_buffer); + + if (rename(pack_tmp_name, name_buffer)) + die_errno("unable to rename temporary pack file"); + + sprintf(end_of_name_prefix, "%s.idx", sha1_to_hex(sha1)); + if (rename(idx_tmp_name, name_buffer)) + die_errno("unable to rename temporary index file"); + + free((void *)idx_tmp_name); +} @@ -2,6 +2,7 @@ #define PACK_H #include "object.h" +#include "csum-file.h" /* * Packed object header @@ -37,7 +38,8 @@ struct pack_header { struct pack_idx_option { unsigned flags; /* flag bits */ -#define WRITE_IDX_VERIFY 01 +#define WRITE_IDX_VERIFY 01 /* verify only, do not write the idx file */ +#define WRITE_IDX_STRICT 02 uint32_t version; uint32_t off32_limit; @@ -70,10 +72,15 @@ struct pack_idx_entry { off_t offset; }; + +struct progress; +typedef int (*verify_fn)(const unsigned char*, enum object_type, unsigned long, void*, int*); + extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, unsigned char *sha1); extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr); extern int verify_pack_index(struct packed_git *); -extern int verify_pack(struct packed_git *); +extern int verify_pack(struct packed_git *, verify_fn fn, struct progress *, uint32_t); +extern off_t write_pack_header(struct sha1file *f, uint32_t); extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t); extern char *index_pack_lockfile(int fd); extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned char *); @@ -82,4 +89,8 @@ extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned ch #define PH_ERROR_PACK_SIGNATURE (-2) #define PH_ERROR_PROTOCOL (-3) extern int read_pack_header(int fd, struct pack_header *); + +extern struct sha1file *create_tmp_packfile(char **pack_tmp_name); +extern void finish_tmp_packfile(char *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, uint32_t nr_written, struct pack_idx_option *pack_idx_opts, unsigned char sha1[]); + #endif diff --git a/parse-options-cb.c b/parse-options-cb.c index 6db0921fc1..0de5fb168a 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -123,3 +123,8 @@ int parse_opt_string_list(const struct option *opt, const char *arg, int unset) string_list_append(v, xstrdup(arg)); return 0; } + +int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset) +{ + return 0; +} diff --git a/parse-options.c b/parse-options.c index 503ab5d500..f0098eb8ea 100644 --- a/parse-options.c +++ b/parse-options.c @@ -83,7 +83,7 @@ static int get_value(struct parse_opt_ctx_t *p, *(int *)opt->value &= ~opt->defval; return 0; - case OPTION_BOOLEAN: + case OPTION_COUNTUP: *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1; return 0; @@ -319,7 +319,7 @@ static void parse_options_check(const struct option *opts) err |= optbug(opts, "uses feature " "not supported for dashless options"); switch (opts->type) { - case OPTION_BOOLEAN: + case OPTION_COUNTUP: case OPTION_BIT: case OPTION_NEGBIT: case OPTION_SET_INT: diff --git a/parse-options.h b/parse-options.h index 59e0b524bd..2e811dc7da 100644 --- a/parse-options.h +++ b/parse-options.h @@ -10,7 +10,7 @@ enum parse_opt_type { /* options with no arguments */ OPTION_BIT, OPTION_NEGBIT, - OPTION_BOOLEAN, /* _INCR would have been a better name */ + OPTION_COUNTUP, OPTION_SET_INT, OPTION_SET_PTR, /* options with arguments (usually) */ @@ -21,6 +21,9 @@ enum parse_opt_type { OPTION_FILENAME }; +/* Deprecated synonym */ +#define OPTION_BOOLEAN OPTION_COUNTUP + enum parse_opt_flags { PARSE_OPT_KEEP_DASHDASH = 1, PARSE_OPT_STOP_AT_NON_OPTION = 2, @@ -122,10 +125,11 @@ struct option { 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, \ +#define OPT_COUNTUP(s, l, v, h) { OPTION_COUNTUP, (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_BOOL(s, l, v, h) OPT_SET_INT(s, l, v, h, 1) #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) } @@ -149,6 +153,13 @@ struct option { { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \ parse_opt_color_flag_cb, (intptr_t)"always" } +#define OPT_NOOP_NOARG(s, l) \ + { OPTION_CALLBACK, (s), (l), NULL, NULL, \ + "no-op (backward compatibility)", \ + PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb } + +/* Deprecated synonym */ +#define OPT_BOOLEAN OPT_COUNTUP /* parse_options() will filter out the processed options and leave the * non-option arguments in argv[]. @@ -210,6 +221,7 @@ extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); extern int parse_opt_string_list(const struct option *, const char *, int); +extern int parse_opt_noop_cb(const struct option *, const char *, int); #define OPT__VERBOSE(var, h) OPT_BOOLEAN('v', "verbose", (var), (h)) #define OPT__QUIET(var, h) OPT_BOOLEAN('q', "quiet", (var), (h)) @@ -283,7 +283,7 @@ return_null: * links. User relative paths are also returned as they are given, * except DWIM suffixing. */ -char *enter_repo(char *path, int strict) +const char *enter_repo(const char *path, int strict) { static char used_path[PATH_MAX]; static char validated_path[PATH_MAX]; @@ -295,16 +295,19 @@ char *enter_repo(char *path, int strict) static const char *suffix[] = { ".git/.git", "/.git", ".git", "", NULL, }; + const char *gitfile; int len = strlen(path); int i; - while ((1 < len) && (path[len-1] == '/')) { - path[len-1] = 0; + while ((1 < len) && (path[len-1] == '/')) len--; - } + if (PATH_MAX <= len) return NULL; - if (path[0] == '~') { - char *newpath = expand_user_path(path); + strncpy(used_path, path, len); used_path[len] = 0 ; + strcpy(validated_path, used_path); + + if (used_path[0] == '~') { + char *newpath = expand_user_path(used_path); if (!newpath || (PATH_MAX - 10 < strlen(newpath))) { free(newpath); return NULL; @@ -316,24 +319,23 @@ char *enter_repo(char *path, int strict) * anyway. */ strcpy(used_path, newpath); free(newpath); - strcpy(validated_path, path); - path = used_path; } else if (PATH_MAX - 10 < len) return NULL; - else { - path = strcpy(used_path, path); - strcpy(validated_path, path); - } - len = strlen(path); + len = strlen(used_path); for (i = 0; suffix[i]; i++) { - strcpy(path + len, suffix[i]); - if (!access(path, F_OK)) { + strcpy(used_path + len, suffix[i]); + if (!access(used_path, F_OK)) { strcat(validated_path, suffix[i]); break; } } - if (!suffix[i] || chdir(path)) + if (!suffix[i]) + return NULL; + gitfile = read_gitfile(used_path) ; + if (gitfile) + strcpy(used_path, gitfile); + if (chdir(used_path)) return NULL; path = validated_path; } diff --git a/perl/.gitignore b/perl/.gitignore index 98b24772c7..9235e73163 100644 --- a/perl/.gitignore +++ b/perl/.gitignore @@ -1,5 +1,6 @@ perl.mak perl.mak.old +MYMETA.yml blib blibdirs pm_to_blib diff --git a/perl/Git.pm b/perl/Git.pm index a86ab709c2..f7ce511bbb 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -570,30 +570,10 @@ does. In scalar context requires the variable to be set only one time (exception is thrown otherwise), in array context returns allows the variable to be set multiple times and returns all the values. -This currently wraps command('config') so it is not so fast. - =cut sub config { - my ($self, $var) = _maybe_self(@_); - - try { - my @cmd = ('config'); - unshift @cmd, $self if $self; - if (wantarray) { - return command(@cmd, '--get-all', $var); - } else { - return command_oneline(@cmd, '--get', $var); - } - } catch Git::Error::Command with { - my $E = shift; - if ($E->value() == 1) { - # Key not found. - return; - } else { - throw $E; - } - }; + return _config_common({}, @_); } @@ -603,30 +583,33 @@ Retrieve the bool configuration C<VARIABLE>. The return value is usable as a boolean in perl (and C<undef> if it's not defined, of course). -This currently wraps command('config') so it is not so fast. - =cut sub config_bool { - my ($self, $var) = _maybe_self(@_); + my $val = scalar _config_common({'kind' => '--bool'}, @_); - try { - my @cmd = ('config', '--bool', '--get', $var); - unshift @cmd, $self if $self; - my $val = command_oneline(@cmd); - return undef unless defined $val; + # Do not rewrite this as return (defined $val && $val eq 'true') + # as some callers do care what kind of falsehood they receive. + if (!defined $val) { + return undef; + } else { return $val eq 'true'; - } catch Git::Error::Command with { - my $E = shift; - if ($E->value() == 1) { - # Key not found. - return undef; - } else { - throw $E; - } - }; + } } + +=item config_path ( VARIABLE ) + +Retrieve the path configuration C<VARIABLE>. The return value +is an expanded path or C<undef> if it's not defined. + +=cut + +sub config_path { + return _config_common({'kind' => '--path'}, @_); +} + + =item config_int ( VARIABLE ) Retrieve the integer configuration C<VARIABLE>. The return value @@ -635,22 +618,31 @@ or 'g' in the config file will cause the value to be multiplied by 1024, 1048576 (1024^2), or 1073741824 (1024^3) prior to output. It would return C<undef> if configuration variable is not defined, -This currently wraps command('config') so it is not so fast. - =cut sub config_int { + return scalar _config_common({'kind' => '--int'}, @_); +} + +# Common subroutine to implement bulk of what the config* family of methods +# do. This curently wraps command('config') so it is not so fast. +sub _config_common { + my ($opts) = shift @_; my ($self, $var) = _maybe_self(@_); try { - my @cmd = ('config', '--int', '--get', $var); + my @cmd = ('config', $opts->{'kind'} ? $opts->{'kind'} : ()); unshift @cmd, $self if $self; - return command_oneline(@cmd); + if (wantarray) { + return command(@cmd, '--get-all', $var); + } else { + return command_oneline(@cmd, '--get', $var); + } } catch Git::Error::Command with { my $E = shift; if ($E->value() == 1) { # Key not found. - return undef; + return; } else { throw $E; } diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm new file mode 100644 index 0000000000..07597dcb93 --- /dev/null +++ b/perl/Git/I18N.pm @@ -0,0 +1,89 @@ +package Git::I18N; +use 5.008; +use strict; +use warnings; +use Exporter 'import'; + +our @EXPORT = qw(__); +our @EXPORT_OK = @EXPORT; + +sub __bootstrap_locale_messages { + our $TEXTDOMAIN = 'git'; + our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++'; + + require POSIX; + POSIX->import(qw(setlocale)); + # Non-core prerequisite module + require Locale::Messages; + Locale::Messages->import(qw(:locale_h :libintl_h)); + + setlocale(LC_MESSAGES(), ''); + setlocale(LC_CTYPE(), ''); + textdomain($TEXTDOMAIN); + bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR); + + return; +} + +BEGIN +{ + # Used by our test script to see if it should test fallbacks or + # not. + our $__HAS_LIBRARY = 1; + + local $@; + eval { + __bootstrap_locale_messages(); + *__ = \&Locale::Messages::gettext; + 1; + } or do { + # Tell test.pl that we couldn't load the gettext library. + $Git::I18N::__HAS_LIBRARY = 0; + + # Just a fall-through no-op + *__ = sub ($) { $_[0] }; + }; +} + +1; + +__END__ + +=head1 NAME + +Git::I18N - Perl interface to Git's Gettext localizations + +=head1 SYNOPSIS + + use Git::I18N; + + print __("Welcome to Git!\n"); + + printf __("The following error occured: %s\n"), $error; + +=head1 DESCRIPTION + +Git's internal Perl interface to gettext via L<Locale::Messages>. If +L<Locale::Messages> can't be loaded (it's not a core module) we +provide stub passthrough fallbacks. + +This is a distilled interface to gettext, see C<info '(gettext)Perl'> +for the full interface. This module implements only a small part of +it. + +=head1 FUNCTIONS + +=head2 __($) + +L<Locale::Messages>'s gettext function if all goes well, otherwise our +passthrough fallback function. + +=head1 AUTHOR + +E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com> + +=head1 COPYRIGHT + +Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com> + +=cut diff --git a/perl/Makefile b/perl/Makefile index a2ffb6402d..b2977cd0bc 100644 --- a/perl/Makefile +++ b/perl/Makefile @@ -5,6 +5,7 @@ makfile:=perl.mak PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) prefix_SQ = $(subst ','\'',$(prefix)) +localedir_SQ = $(subst ','\'',$(localedir)) ifndef V QUIET = @ @@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile echo ' echo $(instdir_SQ)' >> $@ else $(makfile): Makefile.PL ../GIT-CFLAGS - $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE='' + $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE='' --localedir='$(localedir_SQ)' endif # this is just added comfort for calling make directly in perl dir diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 0b9deca2cc..456d45bf40 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -1,4 +1,12 @@ +use strict; +use warnings; use ExtUtils::MakeMaker; +use Getopt::Long; + +# Sanity: die at first unknown option +Getopt::Long::Configure qw/ pass_through /; + +GetOptions("localedir=s" => \my $localedir); sub MY::postamble { return <<'MAKE_FRAG'; @@ -16,7 +24,10 @@ endif MAKE_FRAG } -my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm'); +my %pm = ( + 'Git.pm' => '$(INST_LIBDIR)/Git.pm', + 'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm', +); # We come with our own bundled Error.pm. It's not in the set of default # Perl modules so install it if it's not available on the system yet. @@ -33,6 +44,7 @@ WriteMakefile( NAME => 'Git', VERSION_FROM => 'Git.pm', PM => \%pm, + PM_FILTER => qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"], MAKEFILE => 'perl.mak', INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3' ); diff --git a/po/.gitignore b/po/.gitignore index a242a86e93..4caa631ff0 100644 --- a/po/.gitignore +++ b/po/.gitignore @@ -1 +1,2 @@ /git.pot +/build diff --git a/po/README b/po/README new file mode 100644 index 0000000000..10b0ad2ce8 --- /dev/null +++ b/po/README @@ -0,0 +1,229 @@ +Core GIT Translations +===================== + +This directory holds the translations for the core of Git. This +document describes how to add to and maintain these translations, and +how to mark source strings for translation. + + +Generating a .pot file +---------------------- + +The po/git.pot file contains a message catalog extracted from Git's +sources. You need to generate it to add new translations with +msginit(1), or update existing ones with msgmerge(1). + +Since the file can be automatically generated it's not checked into +git.git. To generate it do, at the top-level: + + make pot + + +Initializing a .po file +----------------------- + +To add a new translation first generate git.pot (see above) and then +in the po/ directory do: + + msginit --locale=XX + +Where XX is your locale, e.g. "is", "de" or "pt_BR". + +Then edit the automatically generated copyright info in your new XX.po +to be correct, e.g. for Icelandic: + + @@ -1,6 +1,6 @@ + -# Icelandic translations for PACKAGE package. + -# Copyright (C) 2010 THE PACKAGE'S COPYRIGHT HOLDER + -# This file is distributed under the same license as the PACKAGE package. + +# Icelandic translations for Git. + +# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com> + +# This file is distributed under the same license as the Git package. + # Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010. + +And change references to PACKAGE VERSION in the PO Header Entry to +just "Git": + + perl -pi -e 's/(?<="Project-Id-Version: )PACKAGE VERSION/Git/' XX.po + + +Updating a .po file +------------------- + +If there's an existing *.po file for your language but you need to +update the translation you first need to generate git.pot (see above) +and then in the po/ directory do: + + msgmerge --add-location --backup=off -U XX.po git.pot + +Where XX.po is the file you want to update. + +Testing your changes +-------------------- + +Before you submit your changes go back to the top-level and do: + + make + +On systems with GNU gettext (i.e. not Solaris) this will compile your +changed PO file with `msgfmt --check`, the --check option flags many +common errors, e.g. missing printf format strings, or translated +messages that deviate from the originals in whether they begin/end +with a newline or not. + + +Marking strings for translation +------------------------------- + +Before strings can be translated they first have to be marked for +translation. + +Git uses an internationalization interface that wraps the system's +gettext library, so most of the advice in your gettext documentation +(on GNU systems `info gettext` in a terminal) applies. + +General advice: + + - Don't mark everything for translation, only strings which will be + read by humans (the porcelain interface) should be translated. + + The output from Git's plumbing utilities will primarily be read by + programs and would break scripts under non-C locales if it was + translated. Plumbing strings should not be translated, since + they're part of Git's API. + + - Adjust the strings so that they're easy to translate. Most of the + advice in `info '(gettext)Preparing Strings'` applies here. + + - If something is unclear or ambiguous you can use a "TRANSLATORS" + comment to tell the translators what to make of it. These will be + extracted by xgettext(1) and put in the po/*.po files, e.g. from + git-am.sh: + + # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a] + # in your translation. The program will only accept English + # input at this point. + gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all " + + Or in C, from builtin/revert.c: + + /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + die(_("%s: Unable to write new index file"), action_name(opts)); + +We provide wrappers for C, Shell and Perl programs. Here's how they're +used: + +C: + + - Include builtin.h at the top, it'll pull in in gettext.h, which + defines the gettext interface. Consult with the list if you need to + use gettext.h directly. + + - The C interface is a subset of the normal GNU gettext + interface. We currently export these functions: + + - _() + + Mark and translate a string. E.g.: + + printf(_("HEAD is now at %s"), hex); + + - Q_() + + Mark and translate a plural string. E.g.: + + printf(Q_("%d commit", "%d commits", number_of_commits)); + + This is just a wrapper for the ngettext() function. + + - N_() + + A no-op pass-through macro for marking strings inside static + initializations, e.g.: + + static const char *reset_type_names[] = { + N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL + }; + + And then, later: + + die(_("%s reset is not allowed in a bare repository"), + _(reset_type_names[reset_type])); + + Here _() couldn't have statically determined what the translation + string will be, but since it was already marked for translation + with N_() the look-up in the message catalog will succeed. + +Shell: + + - The Git gettext shell interface is just a wrapper for + gettext.sh. Import it right after git-sh-setup like this: + + . git-sh-setup + . git-sh-i18n + + And then use the gettext or eval_gettext functions: + + # For constant interface messages: + gettext "A message for the user"; echo + + # To interpolate variables: + details="oh noes" + eval_gettext "An error occured: \$details"; echo + + In addition we have wrappers for messages that end with a trailing + newline. I.e. you could write the above as: + + # For constant interface messages: + gettextln "A message for the user" + + # To interpolate variables: + details="oh noes" + eval_gettextln "An error occured: \$details" + + More documentation about the interface is available in the GNU info + page: `info '(gettext)sh'`. Looking at git-am.sh (the first shell + command to be translated) for examples is also useful: + + git log --reverse -p --grep=i18n git-am.sh + +Perl: + + - The Git::I18N module provides a limited subset of the + Locale::Messages functionality, e.g.: + + use Git::I18N; + print __("Welcome to Git!\n"); + printf __("The following error occured: %s\n"), $error; + + Run `perldoc perl/Git/I18N.pm` for more info. + + +Testing marked strings +---------------------- + +Even if you've correctly marked porcelain strings for translation +something in the test suite might still depend on the US English +version of the strings, e.g. to grep some error message or other +output. + +To smoke out issues like these Git can be compiled with gettext poison +support, at the top-level: + + make GETTEXT_POISON=YesPlease + +That'll give you a git which emits gibberish on every call to +gettext. It's obviously not meant to be installed, but you should run +the test suite with it: + + cd t && prove -j 9 ./t[0-9]*.sh + +If tests break with it you should inspect them manually and see if +what you're translating is sane, i.e. that you're not translating +plumbing output. + +If not you should replace calls to grep with test_i18ngrep, or +test_cmp calls with test_i18ncmp. If that's not enough you can skip +the whole test by making it depend on the C_LOCALE_OUTPUT +prerequisite. See existing test files with this prerequisite for +examples. diff --git a/po/is.po b/po/is.po new file mode 100644 index 0000000000..8692a8b5b2 --- /dev/null +++ b/po/is.po @@ -0,0 +1,93 @@ +# Icelandic translations for Git. +# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com> +# This file is distributed under the same license as the Git package. +# Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: Git\n" +"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" +"POT-Creation-Date: 2010-09-20 14:44+0000\n" +"PO-Revision-Date: 2010-06-05 19:06 +0000\n" +"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n" +"Language-Team: Git Mailing List <git@vger.kernel.org>\n" +"Language: is\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.c:5 +msgid "See 'git help COMMAND' for more information on a specific command." +msgstr "Sjá 'git help SKIPUN' til að sjá hjálp fyrir tiltekna skipun." + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.c:10 +msgid "TEST: A C test string" +msgstr "TILRAUN: C tilraunastrengur" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.c:13 +#, c-format +msgid "TEST: A C test string %s" +msgstr "TILRAUN: C tilraunastrengur %s" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.c:16 +#, c-format +msgid "TEST: Hello World!" +msgstr "TILRAUN: Halló Heimur!" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.c:19 +#, c-format +msgid "TEST: Old English Runes" +msgstr "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.c:22 +#, c-format +msgid "TEST: ‘single’ and “double” quotes" +msgstr "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.sh:8 +msgid "TEST: A Shell test string" +msgstr "TILRAUN: Skeljartilraunastrengur" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.sh:11 +#, sh-format +msgid "TEST: A Shell test $variable" +msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.perl:8 +msgid "TEST: A Perl test string" +msgstr "TILRAUN: Perl tilraunastrengur" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.perl:11 +#, perl-format +msgid "TEST: A Perl test variable %s" +msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s" + +#. TRANSLATORS: The first '%s' is either "Reinitialized +#. existing" or "Initialized empty", the second " shared" or +#. "", and the last '%s%s' is the verbatim directory name. +#: builtin/init-db.c:355 +#, c-format +msgid "%s%s Git repository in %s%s\n" +msgstr "%s%s Git lind í %s%s\n" + +#: builtin/init-db.c:356 +msgid "Reinitialized existing" +msgstr "Endurgerði" + +#: builtin/init-db.c:356 +msgid "Initialized empty" +msgstr "Bjó til tóma" + +#: builtin/init-db.c:357 +msgid " shared" +msgstr " sameiginlega" @@ -1094,7 +1094,6 @@ void format_commit_message(const struct commit *commit, { struct format_commit_context context; static const char utf8[] = "UTF-8"; - const char *enc; const char *output_enc = pretty_ctx->output_encoding; memset(&context, 0, sizeof(context)); @@ -1103,10 +1102,13 @@ void format_commit_message(const struct commit *commit, context.wrap_start = sb->len; context.message = commit->buffer; if (output_enc) { - enc = get_header(commit, "encoding"); - enc = enc ? enc : utf8; - if (strcmp(enc, output_enc)) + char *enc = get_header(commit, "encoding"); + if (strcmp(enc ? enc : utf8, output_enc)) { context.message = logmsg_reencode(commit, output_enc); + if (!context.message) + context.message = commit->buffer; + } + free(enc); } strbuf_expand(sb, format, format_commit_item, &context); diff --git a/prompt.c b/prompt.c new file mode 100644 index 0000000000..72ab9de2f9 --- /dev/null +++ b/prompt.c @@ -0,0 +1,63 @@ +#include "cache.h" +#include "run-command.h" +#include "strbuf.h" +#include "prompt.h" +#include "compat/terminal.h" + +static char *do_askpass(const char *cmd, const char *prompt) +{ + struct child_process pass; + const char *args[3]; + static struct strbuf buffer = STRBUF_INIT; + + args[0] = cmd; + args[1] = prompt; + args[2] = NULL; + + memset(&pass, 0, sizeof(pass)); + pass.argv = args; + pass.out = -1; + + if (start_command(&pass)) + exit(1); + + strbuf_reset(&buffer); + if (strbuf_read(&buffer, pass.out, 20) < 0) + die("failed to get '%s' from %s\n", prompt, cmd); + + close(pass.out); + + if (finish_command(&pass)) + exit(1); + + strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n")); + + return buffer.buf; +} + +char *git_prompt(const char *prompt, int flags) +{ + char *r; + + if (flags & PROMPT_ASKPASS) { + const char *askpass; + + askpass = getenv("GIT_ASKPASS"); + if (!askpass) + askpass = askpass_program; + if (!askpass) + askpass = getenv("SSH_ASKPASS"); + if (askpass && *askpass) + return do_askpass(askpass, prompt); + } + + r = git_terminal_prompt(prompt, flags & PROMPT_ECHO); + if (!r) + die_errno("could not read '%s'", prompt); + return r; +} + +char *git_getpass(const char *prompt) +{ + return git_prompt(prompt, PROMPT_ASKPASS); +} diff --git a/prompt.h b/prompt.h new file mode 100644 index 0000000000..04f321a781 --- /dev/null +++ b/prompt.h @@ -0,0 +1,10 @@ +#ifndef PROMPT_H +#define PROMPT_H + +#define PROMPT_ASKPASS (1<<0) +#define PROMPT_ECHO (1<<1) + +char *git_prompt(const char *prompt, int flags); +char *git_getpass(const char *prompt); + +#endif /* PROMPT_H */ @@ -1,5 +1,6 @@ #include "cache.h" #include "quote.h" +#include "argv-array.h" int quote_path_fully = 1; @@ -120,7 +121,9 @@ 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) +static int sq_dequote_to_argv_internal(char *arg, + const char ***argv, int *nr, int *alloc, + struct argv_array *array) { char *next = arg; @@ -130,13 +133,27 @@ int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc) char *dequoted = sq_dequote_step(next, &next); if (!dequoted) return -1; - ALLOC_GROW(*argv, *nr + 1, *alloc); - (*argv)[(*nr)++] = dequoted; + if (argv) { + ALLOC_GROW(*argv, *nr + 1, *alloc); + (*argv)[(*nr)++] = dequoted; + } + if (array) + argv_array_push(array, dequoted); } while (next); return 0; } +int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc) +{ + return sq_dequote_to_argv_internal(arg, argv, nr, alloc, NULL); +} + +int sq_dequote_to_argv_array(char *arg, struct argv_array *array) +{ + return sq_dequote_to_argv_internal(arg, NULL, NULL, NULL, array); +} + /* 1 means: quote as octal * 0 means: quote as octal if (quote_path_fully) * -1 means: never quote @@ -40,12 +40,19 @@ 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. + * same string separated by space. Like sq_quote, it works in place, + * modifying arg and appending pointers into it to argv. */ extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc); +/* + * Same as above, but store the unquoted strings in an argv_array. We will + * still modify arg in place, but unlike sq_dequote_to_argv, the argv_array + * will duplicate and take ownership of the strings. + */ +struct argv_array; +extern int sq_dequote_to_argv_array(char *arg, struct argv_array *); + 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/reachable.c b/reachable.c index 3fc6b1d320..bf7970661f 100644 --- a/reachable.c +++ b/reachable.c @@ -7,11 +7,25 @@ #include "revision.h" #include "reachable.h" #include "cache-tree.h" +#include "progress.h" + +struct connectivity_progress { + struct progress *progress; + unsigned long count; +}; + +static void update_progress(struct connectivity_progress *cp) +{ + cp->count++; + if ((cp->count & 1023) == 0) + display_progress(cp->progress, cp->count); +} static void process_blob(struct blob *blob, struct object_array *p, struct name_path *path, - const char *name) + const char *name, + struct connectivity_progress *cp) { struct object *obj = &blob->object; @@ -20,6 +34,7 @@ static void process_blob(struct blob *blob, if (obj->flags & SEEN) return; obj->flags |= SEEN; + update_progress(cp); /* Nothing to do, really .. The blob lookup was the important part */ } @@ -34,7 +49,8 @@ static void process_gitlink(const unsigned char *sha1, static void process_tree(struct tree *tree, struct object_array *p, struct name_path *path, - const char *name) + const char *name, + struct connectivity_progress *cp) { struct object *obj = &tree->object; struct tree_desc desc; @@ -46,6 +62,7 @@ static void process_tree(struct tree *tree, if (obj->flags & SEEN) return; obj->flags |= SEEN; + update_progress(cp); if (parse_tree(tree) < 0) die("bad tree object %s", sha1_to_hex(obj->sha1)); add_object(obj, p, path, name); @@ -57,23 +74,25 @@ static void process_tree(struct tree *tree, while (tree_entry(&desc, &entry)) { if (S_ISDIR(entry.mode)) - process_tree(lookup_tree(entry.sha1), p, &me, entry.path); + process_tree(lookup_tree(entry.sha1), p, &me, entry.path, cp); else if (S_ISGITLINK(entry.mode)) process_gitlink(entry.sha1, p, &me, entry.path); else - process_blob(lookup_blob(entry.sha1), p, &me, entry.path); + process_blob(lookup_blob(entry.sha1), p, &me, entry.path, cp); } free(tree->buffer); tree->buffer = NULL; } -static void process_tag(struct tag *tag, struct object_array *p, const char *name) +static void process_tag(struct tag *tag, struct object_array *p, + const char *name, struct connectivity_progress *cp) { struct object *obj = &tag->object; if (obj->flags & SEEN) return; obj->flags |= SEEN; + update_progress(cp); if (parse_tag(tag) < 0) die("bad tag object %s", sha1_to_hex(obj->sha1)); @@ -81,15 +100,18 @@ static void process_tag(struct tag *tag, struct object_array *p, const char *nam add_object(tag->tagged, p, NULL, name); } -static void walk_commit_list(struct rev_info *revs) +static void walk_commit_list(struct rev_info *revs, + struct connectivity_progress *cp) { int i; struct commit *commit; struct object_array objects = OBJECT_ARRAY_INIT; /* Walk all commits, process their trees */ - while ((commit = get_revision(revs)) != NULL) - process_tree(commit->tree, &objects, NULL, ""); + while ((commit = get_revision(revs)) != NULL) { + process_tree(commit->tree, &objects, NULL, "", cp); + update_progress(cp); + } /* Then walk all the pending objects, recursively processing them too */ for (i = 0; i < revs->pending.nr; i++) { @@ -97,15 +119,15 @@ static void walk_commit_list(struct rev_info *revs) struct object *obj = pending->item; const char *name = pending->name; if (obj->type == OBJ_TAG) { - process_tag((struct tag *) obj, &objects, name); + process_tag((struct tag *) obj, &objects, name, cp); continue; } if (obj->type == OBJ_TREE) { - process_tree((struct tree *)obj, &objects, NULL, name); + process_tree((struct tree *)obj, &objects, NULL, name, cp); continue; } if (obj->type == OBJ_BLOB) { - process_blob((struct blob *)obj, &objects, NULL, name); + process_blob((struct blob *)obj, &objects, NULL, name, cp); continue; } die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); @@ -191,8 +213,11 @@ static void add_cache_refs(struct rev_info *revs) add_cache_tree(active_cache_tree, revs); } -void mark_reachable_objects(struct rev_info *revs, int mark_reflog) +void mark_reachable_objects(struct rev_info *revs, int mark_reflog, + struct progress *progress) { + struct connectivity_progress cp; + /* * Set up revision parsing, and mark us as being interested * in all object types, not just commits. @@ -211,11 +236,15 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog) if (mark_reflog) for_each_reflog(add_one_reflog, revs); + cp.progress = progress; + cp.count = 0; + /* * Set up the revision walk - this will move all commits * from the pending list to the commit walking list. */ if (prepare_revision_walk(revs)) die("revision walk setup failed"); - walk_commit_list(revs); + walk_commit_list(revs, &cp); + display_progress(cp.progress, cp.count); } diff --git a/reachable.h b/reachable.h index 40751810b6..5d082adfec 100644 --- a/reachable.h +++ b/reachable.h @@ -1,6 +1,7 @@ #ifndef REACHEABLE_H #define REACHEABLE_H -extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog); +struct progress; +extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog, struct progress *); #endif diff --git a/read-cache.c b/read-cache.c index 01a0e25051..a51bba1b95 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1001,7 +1001,8 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti */ static struct cache_entry *refresh_cache_ent(struct index_state *istate, struct cache_entry *ce, - unsigned int options, int *err) + unsigned int options, int *err, + int *changed_ret) { struct stat st; struct cache_entry *updated; @@ -1033,6 +1034,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, } changed = ie_match_stat(istate, ce, &st, options); + if (changed_ret) + *changed_ret = changed; if (!changed) { /* * The path is unchanged. If we were told to ignore @@ -1102,14 +1105,21 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p int first = 1; int in_porcelain = (flags & REFRESH_IN_PORCELAIN); unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0; - const char *needs_update_fmt; - const char *needs_merge_fmt; - - needs_update_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n"); - needs_merge_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n"); + const char *modified_fmt; + const char *deleted_fmt; + const char *typechange_fmt; + const char *added_fmt; + const char *unmerged_fmt; + + modified_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n"); + deleted_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n"); + typechange_fmt = (in_porcelain ? "T\t%s\n" : "%s needs update\n"); + added_fmt = (in_porcelain ? "A\t%s\n" : "%s needs update\n"); + unmerged_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n"); for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce, *new; int cache_errno = 0; + int changed = 0; ce = istate->cache[i]; if (ignore_submodules && S_ISGITLINK(ce->ce_mode)) @@ -1122,7 +1132,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p i--; if (allow_unmerged) continue; - show_file(needs_merge_fmt, ce->name, in_porcelain, &first, header_msg); + show_file(unmerged_fmt, ce->name, in_porcelain, &first, header_msg); has_errors = 1; continue; } @@ -1130,10 +1140,12 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen)) continue; - new = refresh_cache_ent(istate, ce, options, &cache_errno); + new = refresh_cache_ent(istate, ce, options, &cache_errno, &changed); if (new == ce) continue; if (!new) { + const char *fmt; + if (not_new && cache_errno == ENOENT) continue; if (really && cache_errno == EINVAL) { @@ -1145,7 +1157,17 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p } if (quiet) continue; - show_file(needs_update_fmt, ce->name, in_porcelain, &first, header_msg); + + if (cache_errno == ENOENT) + fmt = deleted_fmt; + else if (ce->ce_flags & CE_INTENT_TO_ADD) + fmt = added_fmt; /* must be before other checks */ + else if (changed & TYPE_CHANGED) + fmt = typechange_fmt; + else + fmt = modified_fmt; + show_file(fmt, + ce->name, in_porcelain, &first, header_msg); has_errors = 1; continue; } @@ -1157,7 +1179,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) { - return refresh_cache_ent(&the_index, ce, really, NULL); + return refresh_cache_ent(&the_index, ce, really, NULL, NULL); } static int verify_hdr(struct cache_header *hdr, unsigned long size) @@ -1202,29 +1224,18 @@ int read_index(struct index_state *istate) return read_index_from(istate, get_index_file()); } -static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce) +static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk) { + struct cache_entry *ce; size_t len; const char *name; + unsigned int flags; - 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); - ce->ce_uid = ntohl(ondisk->uid); - ce->ce_gid = ntohl(ondisk->gid); - ce->ce_size = ntohl(ondisk->size); /* On-disk flags are just 16 bits */ - ce->ce_flags = ntohs(ondisk->flags); - - hashcpy(ce->sha1, ondisk->sha1); + flags = ntohs(ondisk->flags); + len = flags & CE_NAMEMASK; - len = ce->ce_flags & CE_NAMEMASK; - - if (ce->ce_flags & CE_EXTENDED) { + if (flags & CE_EXTENDED) { struct ondisk_cache_entry_extended *ondisk2; int extended_flags; ondisk2 = (struct ondisk_cache_entry_extended *)ondisk; @@ -1232,7 +1243,7 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en /* We do not yet understand any bit out of CE_EXTENDED_FLAGS */ if (extended_flags & ~CE_EXTENDED_FLAGS) die("Unknown index entry format %08x", extended_flags); - ce->ce_flags |= extended_flags; + flags |= extended_flags; name = ondisk2->name; } else @@ -1240,25 +1251,26 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en if (len == CE_NAMEMASK) len = strlen(name); - /* - * NEEDSWORK: If the original index is crafted, this copy could - * go unchecked. - */ - memcpy(ce->name, name, len + 1); -} -static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries) -{ - long per_entry; + ce = xmalloc(cache_entry_size(len)); - per_entry = sizeof(struct cache_entry) - sizeof(struct ondisk_cache_entry); + 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); + ce->ce_uid = ntohl(ondisk->uid); + ce->ce_gid = ntohl(ondisk->gid); + ce->ce_size = ntohl(ondisk->size); + ce->ce_flags = flags; - /* - * Alignment can cause differences. This should be "alignof", but - * since that's a gcc'ism, just use the size of a pointer. - */ - per_entry += sizeof(void *); - return ondisk_size + entries*per_entry; + hashcpy(ce->sha1, ondisk->sha1); + + memcpy(ce->name, name, len); + ce->name[len] = '\0'; + return ce; } /* remember to discard_cache() before reading a different cache! */ @@ -1266,7 +1278,7 @@ int read_index_from(struct index_state *istate, const char *path) { int fd, i; struct stat st; - unsigned long src_offset, dst_offset; + unsigned long src_offset; struct cache_header *hdr; void *mmap; size_t mmap_size; @@ -1305,29 +1317,18 @@ int read_index_from(struct index_state *istate, const char *path) istate->cache_nr = ntohl(hdr->hdr_entries); istate->cache_alloc = alloc_nr(istate->cache_nr); istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *)); - - /* - * The disk format is actually larger than the in-memory format, - * due to space for nsec etc, so even though the in-memory one - * has room for a few more flags, we can allocate using the same - * index size - */ - istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr)); istate->initialized = 1; src_offset = sizeof(*hdr); - dst_offset = 0; for (i = 0; i < istate->cache_nr; i++) { struct ondisk_cache_entry *disk_ce; struct cache_entry *ce; disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset); - ce = (struct cache_entry *)((char *)istate->alloc + dst_offset); - convert_from_disk(disk_ce, ce); + ce = create_from_disk(disk_ce); set_index_entry(istate, i, ce); src_offset += ondisk_ce_size(ce); - dst_offset += ce_size(ce); } istate->timestamp.sec = st.st_mtime; istate->timestamp.nsec = ST_MTIME_NSEC(st); @@ -1361,11 +1362,15 @@ unmap: int is_index_unborn(struct index_state *istate) { - return (!istate->cache_nr && !istate->alloc && !istate->timestamp.sec); + return (!istate->cache_nr && !istate->timestamp.sec); } int discard_index(struct index_state *istate) { + int i; + + for (i = 0; i < istate->cache_nr; i++) + free(istate->cache[i]); resolve_undo_clear_index(istate); istate->cache_nr = 0; istate->cache_changed = 0; @@ -1374,8 +1379,6 @@ int discard_index(struct index_state *istate) istate->name_hash_initialized = 0; free_hash(&istate->name_hash); cache_tree_free(&(istate->cache_tree)); - free(istate->alloc); - istate->alloc = NULL; istate->initialized = 0; /* no need to throw away allocated active_cache */ diff --git a/reflog-walk.c b/reflog-walk.c index 5d81d39a52..64c677fc49 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -50,9 +50,13 @@ static struct complete_reflogs *read_complete_reflog(const char *ref) for_each_reflog_ent(ref, read_one_reflog, reflogs); if (reflogs->nr == 0) { unsigned char sha1[20]; - const char *name = resolve_ref(ref, sha1, 1, NULL); - if (name) + const char *name; + void *name_to_free; + name = name_to_free = resolve_refdup(ref, sha1, 1, NULL); + if (name) { for_each_reflog_ent(name, read_one_reflog, reflogs); + free(name_to_free); + } } if (reflogs->nr == 0) { int len = strlen(ref); @@ -168,11 +172,11 @@ int add_reflog_for_walk(struct reflog_walk_info *info, else { if (*branch == '\0') { unsigned char sha1[20]; - const char *head = resolve_ref("HEAD", sha1, 0, NULL); - if (!head) - die ("No current branch"); free(branch); - branch = xstrdup(head); + branch = resolve_refdup("HEAD", sha1, 0, NULL); + if (!branch) + die ("No current branch"); + } reflogs = read_complete_reflog(branch); if (!reflogs || reflogs->nr == 0) { @@ -4,18 +4,27 @@ #include "tag.h" #include "dir.h" -/* ISSYMREF=01 and ISPACKED=02 are public interfaces */ -#define REF_KNOWS_PEELED 04 -#define REF_BROKEN 010 +/* ISSYMREF=0x01, ISPACKED=0x02 and ISBROKEN=0x04 are public interfaces */ +#define REF_KNOWS_PEELED 0x10 -struct ref_list { - struct ref_list *next; +struct ref_entry { unsigned char flag; /* ISSYMREF? ISPACKED? */ unsigned char sha1[20]; unsigned char peeled[20]; + /* The full name of the reference (e.g., "refs/heads/master"): */ char name[FLEX_ARRAY]; }; +struct ref_array { + int nr, alloc; + struct ref_entry **refs; +}; + +/* + * Parse one line from a packed-refs file. Write the SHA1 to sha1. + * Return a pointer to the refname within the line (null-terminated), + * or NULL if there was a problem. + */ static const char *parse_ref_line(char *line, unsigned char *sha1) { /* @@ -44,155 +53,198 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) return line; } -static struct ref_list *add_ref(const char *name, const unsigned char *sha1, - int flag, struct ref_list *list, - struct ref_list **new_entry) +static struct ref_entry *create_ref_entry(const char *refname, + const unsigned char *sha1, int flag, + int check_name) { int len; - struct ref_list *entry; - - /* Allocate it and add it in.. */ - len = strlen(name) + 1; - entry = xmalloc(sizeof(struct ref_list) + len); - hashcpy(entry->sha1, sha1); - hashclr(entry->peeled); - memcpy(entry->name, name, len); - entry->flag = flag; - entry->next = list; - if (new_entry) - *new_entry = entry; - return entry; -} - -/* merge sort the ref list */ -static struct ref_list *sort_ref_list(struct ref_list *list) -{ - int psize, qsize, last_merge_count, cmp; - struct ref_list *p, *q, *l, *e; - struct ref_list *new_list = list; - int k = 1; - int merge_count = 0; - - if (!list) - return list; - - do { - last_merge_count = merge_count; - merge_count = 0; - - psize = 0; - - p = new_list; - q = new_list; - new_list = NULL; - l = NULL; - - while (p) { - merge_count++; - - while (psize < k && q->next) { - q = q->next; - psize++; - } - qsize = k; - - while ((psize > 0) || (qsize > 0 && q)) { - if (qsize == 0 || !q) { - e = p; - p = p->next; - psize--; - } else if (psize == 0) { - e = q; - q = q->next; - qsize--; - } else { - cmp = strcmp(q->name, p->name); - if (cmp < 0) { - e = q; - q = q->next; - qsize--; - } else if (cmp > 0) { - e = p; - p = p->next; - psize--; - } else { - if (hashcmp(q->sha1, p->sha1)) - die("Duplicated ref, and SHA1s don't match: %s", - q->name); - warning("Duplicated ref: %s", q->name); - e = q; - q = q->next; - qsize--; - free(e); - e = p; - p = p->next; - psize--; - } - } + struct ref_entry *ref; + + if (check_name && + check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT)) + die("Reference has invalid format: '%s'", refname); + len = strlen(refname) + 1; + ref = xmalloc(sizeof(struct ref_entry) + len); + hashcpy(ref->sha1, sha1); + hashclr(ref->peeled); + memcpy(ref->name, refname, len); + ref->flag = flag; + return ref; +} - e->next = NULL; +/* Add a ref_entry to the end of the ref_array (unsorted). */ +static void add_ref(struct ref_array *refs, struct ref_entry *ref) +{ + ALLOC_GROW(refs->refs, refs->nr + 1, refs->alloc); + refs->refs[refs->nr++] = ref; +} - if (l) - l->next = e; - if (!new_list) - new_list = e; - l = e; - } +static int ref_entry_cmp(const void *a, const void *b) +{ + struct ref_entry *one = *(struct ref_entry **)a; + struct ref_entry *two = *(struct ref_entry **)b; + return strcmp(one->name, two->name); +} - p = q; - }; +/* + * Emit a warning and return true iff ref1 and ref2 have the same name + * and the same sha1. Die if they have the same name but different + * sha1s. + */ +static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2) +{ + if (!strcmp(ref1->name, ref2->name)) { + /* Duplicate name; make sure that the SHA1s match: */ + if (hashcmp(ref1->sha1, ref2->sha1)) + die("Duplicated ref, and SHA1s don't match: %s", + ref1->name); + warning("Duplicated ref: %s", ref1->name); + return 1; + } else { + return 0; + } +} + +static void sort_ref_array(struct ref_array *array) +{ + int i, j; - k = k * 2; - } while ((last_merge_count != merge_count) || (last_merge_count != 1)); + /* Nothing to sort unless there are at least two entries */ + if (array->nr < 2) + return; - return new_list; + qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp); + + /* Remove any duplicates from the ref_array */ + i = 0; + for (j = 1; j < array->nr; j++) { + if (is_dup_ref(array->refs[i], array->refs[j])) { + free(array->refs[j]); + continue; + } + array->refs[++i] = array->refs[j]; + } + array->nr = i + 1; +} + +static struct ref_entry *search_ref_array(struct ref_array *array, const char *refname) +{ + struct ref_entry *e, **r; + int len; + + if (refname == NULL) + return NULL; + + if (!array->nr) + return NULL; + + len = strlen(refname) + 1; + e = xmalloc(sizeof(struct ref_entry) + len); + memcpy(e->name, refname, len); + + r = bsearch(&e, array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp); + + free(e); + + if (r == NULL) + return NULL; + + return *r; } /* * Future: need to be in "struct repository" * when doing a full libification. */ -static struct cached_refs { +static struct ref_cache { + struct ref_cache *next; char did_loose; char did_packed; - struct ref_list *loose; - struct ref_list *packed; -} cached_refs, submodule_refs; -static struct ref_list *current_ref; + struct ref_array loose; + struct ref_array packed; + /* The submodule name, or "" for the main repo. */ + char name[FLEX_ARRAY]; +} *ref_cache; + +static struct ref_entry *current_ref; -static struct ref_list *extra_refs; +static struct ref_array extra_refs; -static void free_ref_list(struct ref_list *list) +static void clear_ref_array(struct ref_array *array) { - struct ref_list *next; - for ( ; list; list = next) { - next = list->next; - free(list); - } + int i; + for (i = 0; i < array->nr; i++) + free(array->refs[i]); + free(array->refs); + array->nr = array->alloc = 0; + array->refs = NULL; } -static void invalidate_cached_refs(void) +static void clear_packed_ref_cache(struct ref_cache *refs) { - struct cached_refs *ca = &cached_refs; + if (refs->did_packed) + clear_ref_array(&refs->packed); + refs->did_packed = 0; +} - if (ca->did_loose && ca->loose) - free_ref_list(ca->loose); - if (ca->did_packed && ca->packed) - free_ref_list(ca->packed); - ca->loose = ca->packed = NULL; - ca->did_loose = ca->did_packed = 0; +static void clear_loose_ref_cache(struct ref_cache *refs) +{ + if (refs->did_loose) + clear_ref_array(&refs->loose); + refs->did_loose = 0; } -static void read_packed_refs(FILE *f, struct cached_refs *cached_refs) +static struct ref_cache *create_ref_cache(const char *submodule) { - struct ref_list *list = NULL; - struct ref_list *last = NULL; + int len; + struct ref_cache *refs; + if (!submodule) + submodule = ""; + len = strlen(submodule) + 1; + refs = xcalloc(1, sizeof(struct ref_cache) + len); + memcpy(refs->name, submodule, len); + return refs; +} + +/* + * Return a pointer to a ref_cache for the specified submodule. For + * the main repository, use submodule==NULL. The returned structure + * will be allocated and initialized but not necessarily populated; it + * should not be freed. + */ +static struct ref_cache *get_ref_cache(const char *submodule) +{ + struct ref_cache *refs = ref_cache; + if (!submodule) + submodule = ""; + while (refs) { + if (!strcmp(submodule, refs->name)) + return refs; + refs = refs->next; + } + + refs = create_ref_cache(submodule); + refs->next = ref_cache; + ref_cache = refs; + return refs; +} + +void invalidate_ref_cache(const char *submodule) +{ + struct ref_cache *refs = get_ref_cache(submodule); + clear_packed_ref_cache(refs); + clear_loose_ref_cache(refs); +} + +static void read_packed_refs(FILE *f, struct ref_array *array) +{ + struct ref_entry *last = NULL; char refline[PATH_MAX]; int flag = REF_ISPACKED; while (fgets(refline, sizeof(refline), f)) { unsigned char sha1[20]; - const char *name; + const char *refname; static const char header[] = "# pack-refs with:"; if (!strncmp(refline, header, sizeof(header)-1)) { @@ -203,9 +255,10 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs) continue; } - name = parse_ref_line(refline, sha1); - if (name) { - list = add_ref(name, sha1, flag, list, &last); + refname = parse_ref_line(refline, sha1); + if (refname) { + last = create_ref_entry(refname, sha1, flag, 1); + add_ref(array, last); continue; } if (last && @@ -215,54 +268,47 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs) !get_sha1_hex(refline + 1, sha1)) hashcpy(last->peeled, sha1); } - cached_refs->packed = sort_ref_list(list); + sort_ref_array(array); } -void add_extra_ref(const char *name, const unsigned char *sha1, int flag) +void add_extra_ref(const char *refname, const unsigned char *sha1, int flag) { - extra_refs = add_ref(name, sha1, flag, extra_refs, NULL); + add_ref(&extra_refs, create_ref_entry(refname, sha1, flag, 0)); } void clear_extra_refs(void) { - free_ref_list(extra_refs); - extra_refs = NULL; + clear_ref_array(&extra_refs); } -static struct ref_list *get_packed_refs(const char *submodule) +static struct ref_array *get_packed_refs(struct ref_cache *refs) { - const char *packed_refs_file; - struct cached_refs *refs; - - if (submodule) { - packed_refs_file = git_path_submodule(submodule, "packed-refs"); - refs = &submodule_refs; - free_ref_list(refs->packed); - } else { - packed_refs_file = git_path("packed-refs"); - refs = &cached_refs; - } + if (!refs->did_packed) { + const char *packed_refs_file; + FILE *f; - if (!refs->did_packed || submodule) { - FILE *f = fopen(packed_refs_file, "r"); - refs->packed = NULL; + if (*refs->name) + packed_refs_file = git_path_submodule(refs->name, "packed-refs"); + else + packed_refs_file = git_path("packed-refs"); + f = fopen(packed_refs_file, "r"); if (f) { - read_packed_refs(f, refs); + read_packed_refs(f, &refs->packed); fclose(f); } refs->did_packed = 1; } - return refs->packed; + return &refs->packed; } -static struct ref_list *get_ref_dir(const char *submodule, const char *base, - struct ref_list *list) +static void get_ref_dir(struct ref_cache *refs, const char *base, + struct ref_array *array) { DIR *dir; const char *path; - if (submodule) - path = git_path_submodule(submodule, "%s", base); + if (*refs->name) + path = git_path_submodule(refs->name, "%s", base); else path = git_path("%s", base); @@ -272,11 +318,11 @@ static struct ref_list *get_ref_dir(const char *submodule, const char *base, if (dir) { struct dirent *de; int baselen = strlen(base); - char *ref = xmalloc(baselen + 257); + char *refname = xmalloc(baselen + 257); - memcpy(ref, base, baselen); + memcpy(refname, base, baselen); if (baselen && base[baselen-1] != '/') - ref[baselen++] = '/'; + refname[baselen++] = '/'; while ((de = readdir(dir)) != NULL) { unsigned char sha1[20]; @@ -292,34 +338,32 @@ static struct ref_list *get_ref_dir(const char *submodule, const char *base, continue; if (has_extension(de->d_name, ".lock")) continue; - memcpy(ref + baselen, de->d_name, namelen+1); - refdir = submodule - ? git_path_submodule(submodule, "%s", ref) - : git_path("%s", ref); + memcpy(refname + baselen, de->d_name, namelen+1); + refdir = *refs->name + ? git_path_submodule(refs->name, "%s", refname) + : git_path("%s", refname); if (stat(refdir, &st) < 0) continue; if (S_ISDIR(st.st_mode)) { - list = get_ref_dir(submodule, ref, list); + get_ref_dir(refs, refname, array); continue; } - if (submodule) { + if (*refs->name) { hashclr(sha1); flag = 0; - if (resolve_gitlink_ref(submodule, ref, sha1) < 0) { - hashclr(sha1); - flag |= REF_BROKEN; - } - } else - if (!resolve_ref(ref, sha1, 1, &flag)) { + if (resolve_gitlink_ref(refs->name, refname, sha1) < 0) { hashclr(sha1); - flag |= REF_BROKEN; + flag |= REF_ISBROKEN; } - list = add_ref(ref, sha1, flag, list, NULL); + } else if (read_ref_full(refname, sha1, 1, &flag)) { + hashclr(sha1); + flag |= REF_ISBROKEN; + } + add_ref(array, create_ref_entry(refname, sha1, flag, 1)); } - free(ref); + free(refname); closedir(dir); } - return sort_ref_list(list); } struct warn_if_dangling_data { @@ -338,7 +382,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha if (!(flags & REF_ISSYMREF)) return 0; - resolves_to = resolve_ref(refname, junk, 0, NULL); + resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL); if (!resolves_to || strcmp(resolves_to, d->refname)) return 0; @@ -356,63 +400,55 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname) for_each_rawref(warn_if_dangling_symref, &data); } -static struct ref_list *get_loose_refs(const char *submodule) +static struct ref_array *get_loose_refs(struct ref_cache *refs) { - if (submodule) { - free_ref_list(submodule_refs.loose); - submodule_refs.loose = get_ref_dir(submodule, "refs", NULL); - return submodule_refs.loose; + if (!refs->did_loose) { + get_ref_dir(refs, "refs", &refs->loose); + sort_ref_array(&refs->loose); + refs->did_loose = 1; } - - if (!cached_refs.did_loose) { - cached_refs.loose = get_ref_dir(NULL, "refs", NULL); - cached_refs.did_loose = 1; - } - return cached_refs.loose; + return &refs->loose; } /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 #define MAXREFLEN (1024) -static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result) +/* + * Called by resolve_gitlink_ref_recursive() after it failed to read + * from the loose refs in ref_cache refs. Find <refname> in the + * packed-refs file for the submodule. + */ +static int resolve_gitlink_packed_ref(struct ref_cache *refs, + const char *refname, unsigned char *sha1) { - FILE *f; - struct cached_refs refs; - struct ref_list *ref; - int retval; + struct ref_entry *ref; + struct ref_array *array = get_packed_refs(refs); - strcpy(name + pathlen, "packed-refs"); - f = fopen(name, "r"); - if (!f) + ref = search_ref_array(array, refname); + if (ref == NULL) return -1; - read_packed_refs(f, &refs); - fclose(f); - ref = refs.packed; - retval = -1; - while (ref) { - if (!strcmp(ref->name, refname)) { - retval = 0; - memcpy(result, ref->sha1, 20); - break; - } - ref = ref->next; - } - free_ref_list(refs.packed); - return retval; + + memcpy(sha1, ref->sha1, 20); + return 0; } -static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *refname, unsigned char *result, int recursion) +static int resolve_gitlink_ref_recursive(struct ref_cache *refs, + const char *refname, unsigned char *sha1, + int recursion) { - int fd, len = strlen(refname); + int fd, len; char buffer[128], *p; + char *path; - if (recursion > MAXDEPTH || len > MAXREFLEN) + if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN) return -1; - memcpy(name + pathlen, refname, len+1); - fd = open(name, O_RDONLY); + path = *refs->name + ? git_path_submodule(refs->name, "%s", refname) + : git_path("%s", refname); + fd = open(path, O_RDONLY); if (fd < 0) - return resolve_gitlink_packed_ref(name, pathlen, refname, result); + return resolve_gitlink_packed_ref(refs, refname, sha1); len = read(fd, buffer, sizeof(buffer)-1); close(fd); @@ -423,7 +459,7 @@ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *re buffer[len] = 0; /* Was it a detached head or an old-fashioned symlink? */ - if (!get_sha1_hex(buffer, result)) + if (!get_sha1_hex(buffer, sha1)) return 0; /* Symref? */ @@ -433,60 +469,55 @@ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *re while (isspace(*p)) p++; - return resolve_gitlink_ref_recursive(name, pathlen, p, result, recursion+1); + return resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1); } -int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *result) +int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1) { int len = strlen(path), retval; - char *gitdir; - const char *tmp; + char *submodule; + struct ref_cache *refs; while (len && path[len-1] == '/') len--; if (!len) return -1; - gitdir = xmalloc(len + MAXREFLEN + 8); - memcpy(gitdir, path, len); - memcpy(gitdir + len, "/.git", 6); - len += 5; - - tmp = read_gitfile(gitdir); - if (tmp) { - free(gitdir); - len = strlen(tmp); - gitdir = xmalloc(len + MAXREFLEN + 3); - memcpy(gitdir, tmp, len); - } - gitdir[len] = '/'; - gitdir[++len] = '\0'; - retval = resolve_gitlink_ref_recursive(gitdir, len, refname, result, 0); - free(gitdir); + submodule = xstrndup(path, len); + refs = get_ref_cache(submodule); + free(submodule); + + retval = resolve_gitlink_ref_recursive(refs, refname, sha1, 0); return retval; } /* - * If the "reading" argument is set, this function finds out what _object_ - * the ref points at by "reading" the ref. The ref, if it is not symbolic, - * has to exist, and if it is symbolic, it has to point at an existing ref, - * because the "read" goes through the symref to the ref it points at. - * - * The access that is not "reading" may often be "writing", but does not - * have to; it can be merely checking _where it leads to_. If it is a - * prelude to "writing" to the ref, a write to a symref that points at - * yet-to-be-born ref will create the real ref pointed by the symref. - * reading=0 allows the caller to check where such a symref leads to. + * Try to read ref from the packed references. On success, set sha1 + * and return 0; otherwise, return -1. */ -const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag) +static int get_packed_ref(const char *refname, unsigned char *sha1) +{ + struct ref_array *packed = get_packed_refs(get_ref_cache(NULL)); + struct ref_entry *entry = search_ref_array(packed, refname); + if (entry) { + hashcpy(sha1, entry->sha1); + return 0; + } + return -1; +} + +const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag) { int depth = MAXDEPTH; ssize_t len; char buffer[256]; - static char ref_buffer[256]; + static char refname_buffer[256]; if (flag) *flag = 0; + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) + return NULL; + for (;;) { char path[PATH_MAX]; struct stat st; @@ -496,32 +527,39 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * if (--depth < 0) return NULL; - git_snpath(path, sizeof(path), "%s", ref); - /* Special case: non-existing file. */ + git_snpath(path, sizeof(path), "%s", refname); + if (lstat(path, &st) < 0) { - struct ref_list *list = get_packed_refs(NULL); - while (list) { - if (!strcmp(ref, list->name)) { - hashcpy(sha1, list->sha1); - if (flag) - *flag |= REF_ISPACKED; - return ref; - } - list = list->next; + if (errno != ENOENT) + return NULL; + /* + * The loose reference file does not exist; + * check for a packed reference. + */ + if (!get_packed_ref(refname, sha1)) { + if (flag) + *flag |= REF_ISPACKED; + return refname; } - if (reading || errno != ENOENT) + /* The reference is not a packed reference, either. */ + if (reading) { return NULL; - hashclr(sha1); - return ref; + } else { + hashclr(sha1); + return refname; + } } /* Follow "normalized" - ie "refs/.." symlinks by hand */ if (S_ISLNK(st.st_mode)) { len = readlink(path, buffer, sizeof(buffer)-1); - if (len >= 5 && !memcmp("refs/", buffer, 5)) { - buffer[len] = 0; - strcpy(ref_buffer, buffer); - ref = ref_buffer; + if (len < 0) + return NULL; + buffer[len] = 0; + if (!prefixcmp(buffer, "refs/") && + !check_refname_format(buffer, 0)) { + strcpy(refname_buffer, buffer); + refname = refname_buffer; if (flag) *flag |= REF_ISSYMREF; continue; @@ -543,27 +581,42 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * return NULL; len = read_in_full(fd, buffer, sizeof(buffer)-1); close(fd); + if (len < 0) + return NULL; + while (len && isspace(buffer[len-1])) + len--; + buffer[len] = '\0'; /* * Is it a symbolic ref? */ - if (len < 4 || memcmp("ref:", buffer, 4)) + if (prefixcmp(buffer, "ref:")) break; - buf = buffer + 4; - len -= 4; - while (len && isspace(*buf)) - buf++, len--; - while (len && isspace(buf[len-1])) - len--; - buf[len] = 0; - memcpy(ref_buffer, buf, len + 1); - ref = ref_buffer; if (flag) *flag |= REF_ISSYMREF; + buf = buffer + 4; + while (isspace(*buf)) + buf++; + if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { + if (flag) + *flag |= REF_ISBROKEN; + return NULL; + } + refname = strcpy(refname_buffer, buf); } - if (len < 40 || get_sha1_hex(buffer, sha1)) + /* Please note that FETCH_HEAD has a second line containing other data. */ + if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) { + if (flag) + *flag |= REF_ISBROKEN; return NULL; - return ref; + } + return refname; +} + +char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag) +{ + const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag); + return ret ? xstrdup(ret) : NULL; } /* The argument to filter_refs */ @@ -573,23 +626,28 @@ struct ref_filter { void *cb_data; }; -int read_ref(const char *ref, unsigned char *sha1) +int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags) { - if (resolve_ref(ref, sha1, 1, NULL)) + if (resolve_ref_unsafe(refname, sha1, reading, flags)) return 0; return -1; } +int read_ref(const char *refname, unsigned char *sha1) +{ + return read_ref_full(refname, sha1, 1, NULL); +} + #define DO_FOR_EACH_INCLUDE_BROKEN 01 static int do_one_ref(const char *base, each_ref_fn fn, int trim, - int flags, void *cb_data, struct ref_list *entry) + int flags, void *cb_data, struct ref_entry *entry) { if (prefixcmp(entry->name, base)) return 0; if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) { - if (entry->flag & REF_BROKEN) - return 0; /* ignore dangling symref */ + if (entry->flag & REF_ISBROKEN) + return 0; /* ignore broken refs e.g. dangling symref */ if (!has_sha1_file(entry->sha1)) { error("%s does not point to a valid object!", entry->name); return 0; @@ -599,23 +657,23 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); } -static int filter_refs(const char *ref, const unsigned char *sha, int flags, - void *data) +static int filter_refs(const char *refname, const unsigned char *sha1, int flags, + void *data) { struct ref_filter *filter = (struct ref_filter *)data; - if (fnmatch(filter->pattern, ref, 0)) + if (fnmatch(filter->pattern, refname, 0)) return 0; - return filter->fn(ref, sha, flags, filter->cb_data); + return filter->fn(refname, sha1, flags, filter->cb_data); } -int peel_ref(const char *ref, unsigned char *sha1) +int peel_ref(const char *refname, unsigned char *sha1) { int flag; unsigned char base[20]; struct object *o; - if (current_ref && (current_ref->name == ref - || !strcmp(current_ref->name, ref))) { + if (current_ref && (current_ref->name == refname + || !strcmp(current_ref->name, refname))) { if (current_ref->flag & REF_KNOWS_PEELED) { hashcpy(sha1, current_ref->peeled); return 0; @@ -624,29 +682,23 @@ int peel_ref(const char *ref, unsigned char *sha1) goto fallback; } - if (!resolve_ref(ref, base, 1, &flag)) + if (read_ref_full(refname, base, 1, &flag)) return -1; if ((flag & REF_ISPACKED)) { - struct ref_list *list = get_packed_refs(NULL); + struct ref_array *array = get_packed_refs(get_ref_cache(NULL)); + struct ref_entry *r = search_ref_array(array, refname); - while (list) { - if (!strcmp(list->name, ref)) { - if (list->flag & REF_KNOWS_PEELED) { - hashcpy(sha1, list->peeled); - return 0; - } - /* older pack-refs did not leave peeled ones */ - break; - } - list = list->next; + if (r != NULL && r->flag & REF_KNOWS_PEELED) { + hashcpy(sha1, r->peeled); + return 0; } } fallback: o = parse_object(base); if (o && o->type == OBJ_TAG) { - o = deref_tag(o, ref, 0); + o = deref_tag(o, refname, 0); if (o) { hashcpy(sha1, o->sha1); return 0; @@ -658,36 +710,40 @@ fallback: static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn, int trim, int flags, void *cb_data) { - int retval = 0; - struct ref_list *packed = get_packed_refs(submodule); - struct ref_list *loose = get_loose_refs(submodule); + int retval = 0, i, p = 0, l = 0; + struct ref_cache *refs = get_ref_cache(submodule); + struct ref_array *packed = get_packed_refs(refs); + struct ref_array *loose = get_loose_refs(refs); - struct ref_list *extra; + struct ref_array *extra = &extra_refs; - for (extra = extra_refs; extra; extra = extra->next) - retval = do_one_ref(base, fn, trim, flags, cb_data, extra); + for (i = 0; i < extra->nr; i++) + retval = do_one_ref(base, fn, trim, flags, cb_data, extra->refs[i]); - while (packed && loose) { - struct ref_list *entry; - int cmp = strcmp(packed->name, loose->name); + while (p < packed->nr && l < loose->nr) { + struct ref_entry *entry; + int cmp = strcmp(packed->refs[p]->name, loose->refs[l]->name); if (!cmp) { - packed = packed->next; + p++; continue; } if (cmp > 0) { - entry = loose; - loose = loose->next; + entry = loose->refs[l++]; } else { - entry = packed; - packed = packed->next; + entry = packed->refs[p++]; } retval = do_one_ref(base, fn, trim, flags, cb_data, entry); if (retval) goto end_each; } - for (packed = packed ? packed : loose; packed; packed = packed->next) { - retval = do_one_ref(base, fn, trim, flags, cb_data, packed); + if (l < loose->nr) { + p = l; + packed = loose; + } + + for (; p < packed->nr; p++) { + retval = do_one_ref(base, fn, trim, flags, cb_data, packed->refs[p]); if (retval) goto end_each; } @@ -710,7 +766,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) return 0; } - if (resolve_ref("HEAD", sha1, 1, &flag)) + if (!read_ref_full("HEAD", sha1, 1, &flag)) return fn("HEAD", sha1, flag, cb_data); return 0; @@ -790,7 +846,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data) int flag; strbuf_addf(&buf, "%sHEAD", get_git_namespace()); - if (resolve_ref(buf.buf, sha1, 1, &flag)) + if (!read_ref_full(buf.buf, sha1, 1, &flag)) ret = fn(buf.buf, sha1, flag, cb_data); strbuf_release(&buf); @@ -860,70 +916,87 @@ int for_each_rawref(each_ref_fn fn, void *cb_data) * - it contains a "\" (backslash) */ +/* Return true iff ch is not allowed in reference names. */ static inline int bad_ref_char(int ch) { if (((unsigned) ch) <= ' ' || ch == 0x7f || ch == '~' || ch == '^' || ch == ':' || ch == '\\') return 1; /* 2.13 Pattern Matching Notation */ - if (ch == '?' || ch == '[') /* Unsupported */ + if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */ return 1; - if (ch == '*') /* Supported at the end */ - return 2; return 0; } -int check_ref_format(const char *ref) +/* + * Try to read one refname component from the front of refname. Return + * the length of the component found, or -1 if the component is not + * legal. + */ +static int check_refname_component(const char *refname, int flags) { - int ch, level, bad_type, last; - int ret = CHECK_REF_FORMAT_OK; - const char *cp = ref; - - level = 0; - while (1) { - while ((ch = *cp++) == '/') - ; /* tolerate duplicated slashes */ - if (!ch) - /* should not end with slashes */ - return CHECK_REF_FORMAT_ERROR; - - /* we are at the beginning of the path component */ - if (ch == '.') - return CHECK_REF_FORMAT_ERROR; - bad_type = bad_ref_char(ch); - if (bad_type) { - if (bad_type == 2 && (!*cp || *cp == '/') && - ret == CHECK_REF_FORMAT_OK) - ret = CHECK_REF_FORMAT_WILDCARD; - else - return CHECK_REF_FORMAT_ERROR; - } + const char *cp; + char last = '\0'; + for (cp = refname; ; cp++) { + char ch = *cp; + if (ch == '\0' || ch == '/') + break; + if (bad_ref_char(ch)) + return -1; /* Illegal character in refname. */ + if (last == '.' && ch == '.') + return -1; /* Refname contains "..". */ + if (last == '@' && ch == '{') + return -1; /* Refname contains "@{". */ last = ch; - /* scan the rest of the path component */ - while ((ch = *cp++) != 0) { - bad_type = bad_ref_char(ch); - if (bad_type) - return CHECK_REF_FORMAT_ERROR; - if (ch == '/') - break; - 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; - if (has_extension(ref, ".lock")) - return CHECK_REF_FORMAT_ERROR; - return ret; + } + if (cp == refname) + return -1; /* Component has zero length. */ + if (refname[0] == '.') { + if (!(flags & REFNAME_DOT_COMPONENT)) + return -1; /* Component starts with '.'. */ + /* + * Even if leading dots are allowed, don't allow "." + * as a component (".." is prevented by a rule above). + */ + if (refname[1] == '\0') + return -1; /* Component equals ".". */ + } + if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5)) + return -1; /* Refname ends with ".lock". */ + return cp - refname; +} + +int check_refname_format(const char *refname, int flags) +{ + int component_len, component_count = 0; + + while (1) { + /* We are at the start of a path component. */ + component_len = check_refname_component(refname, flags); + if (component_len < 0) { + if ((flags & REFNAME_REFSPEC_PATTERN) && + refname[0] == '*' && + (refname[1] == '\0' || refname[1] == '/')) { + /* Accept one wildcard as a full refname component. */ + flags &= ~REFNAME_REFSPEC_PATTERN; + component_len = 1; + } else { + return -1; + } } + component_count++; + if (refname[component_len] == '\0') + break; + /* Skip to next component. */ + refname += component_len + 1; } + + if (refname[component_len - 1] == '.') + return -1; /* Refname ends with '.'. */ + if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2) + return -1; /* Refname has only one component. */ + return 0; } const char *prettify_refname(const char *name) @@ -945,13 +1018,6 @@ const char *ref_rev_parse_rules[] = { NULL }; -const char *ref_fetch_rules[] = { - "%.*s", - "refs/%.*s", - "refs/heads/%.*s", - NULL -}; - int refname_match(const char *abbrev_name, const char *full_name, const char **rules) { const char **p; @@ -969,7 +1035,7 @@ int refname_match(const char *abbrev_name, const char *full_name, const char **r static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { - if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) { + if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) { error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); return NULL; @@ -1002,33 +1068,129 @@ static int remove_empty_directories(const char *file) return result; } -static int is_refname_available(const char *ref, const char *oldref, - struct ref_list *list, int quiet) -{ - int namlen = strlen(ref); /* e.g. 'foo/bar' */ - while (list) { - /* list->name could be 'foo' or 'foo/bar/baz' */ - if (!oldref || strcmp(oldref, list->name)) { - int len = strlen(list->name); +/* + * Return true iff a reference named refname could be created without + * conflicting with the name of an existing reference. If oldrefname + * is non-NULL, ignore potential conflicts with oldrefname (e.g., + * because oldrefname is scheduled for deletion in the same + * operation). + */ +static int is_refname_available(const char *refname, const char *oldrefname, + struct ref_array *array) +{ + int i, namlen = strlen(refname); /* e.g. 'foo/bar' */ + for (i = 0; i < array->nr; i++ ) { + struct ref_entry *entry = array->refs[i]; + /* entry->name could be 'foo' or 'foo/bar/baz' */ + if (!oldrefname || strcmp(oldrefname, entry->name)) { + int len = strlen(entry->name); int cmplen = (namlen < len) ? namlen : len; - const char *lead = (namlen < len) ? list->name : ref; - if (!strncmp(ref, list->name, cmplen) && + const char *lead = (namlen < len) ? entry->name : refname; + if (!strncmp(refname, entry->name, cmplen) && lead[cmplen] == '/') { - if (!quiet) - error("'%s' exists; cannot create '%s'", - list->name, ref); + error("'%s' exists; cannot create '%s'", + entry->name, refname); return 0; } } - list = list->next; } return 1; } -static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p) +/* + * *string and *len will only be substituted, and *string returned (for + * later free()ing) if the string passed in is a magic short-hand form + * to name a branch. + */ +static char *substitute_branch_name(const char **string, int *len) +{ + struct strbuf buf = STRBUF_INIT; + int ret = interpret_branch_name(*string, &buf); + + if (ret == *len) { + size_t size; + *string = strbuf_detach(&buf, &size); + *len = size; + return (char *)*string; + } + + return NULL; +} + +int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) +{ + char *last_branch = substitute_branch_name(&str, &len); + const char **p, *r; + int refs_found = 0; + + *ref = NULL; + for (p = ref_rev_parse_rules; *p; p++) { + char fullref[PATH_MAX]; + unsigned char sha1_from_ref[20]; + unsigned char *this_result; + int flag; + + this_result = refs_found ? sha1_from_ref : sha1; + mksnpath(fullref, sizeof(fullref), *p, len, str); + r = resolve_ref_unsafe(fullref, this_result, 1, &flag); + if (r) { + if (!refs_found++) + *ref = xstrdup(r); + if (!warn_ambiguous_refs) + break; + } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) { + warning("ignoring dangling symref %s.", fullref); + } else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) { + warning("ignoring broken ref %s.", fullref); + } + } + free(last_branch); + return refs_found; +} + +int dwim_log(const char *str, int len, unsigned char *sha1, char **log) +{ + char *last_branch = substitute_branch_name(&str, &len); + const char **p; + int logs_found = 0; + + *log = NULL; + for (p = ref_rev_parse_rules; *p; p++) { + struct stat st; + unsigned char hash[20]; + char path[PATH_MAX]; + const char *ref, *it; + + mksnpath(path, sizeof(path), *p, len, str); + ref = resolve_ref_unsafe(path, hash, 1, NULL); + if (!ref) + continue; + if (!stat(git_path("logs/%s", path), &st) && + S_ISREG(st.st_mode)) + it = path; + else if (strcmp(ref, path) && + !stat(git_path("logs/%s", ref), &st) && + S_ISREG(st.st_mode)) + it = ref; + else + continue; + if (!logs_found++) { + *log = xstrdup(it); + hashcpy(sha1, hash); + } + if (!warn_ambiguous_refs) + break; + } + free(last_branch); + return logs_found; +} + +static struct ref_lock *lock_ref_sha1_basic(const char *refname, + const unsigned char *old_sha1, + int flags, int *type_p) { char *ref_file; - const char *orig_ref = ref; + const char *orig_refname = refname; struct ref_lock *lock; int last_errno = 0; int type, lflags; @@ -1038,27 +1200,27 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - ref = resolve_ref(ref, lock->old_sha1, mustexist, &type); - if (!ref && errno == EISDIR) { + refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type); + if (!refname && errno == EISDIR) { /* we are trying to lock foo but we used to * have foo/bar which now does not exist; * it is normal for the empty directory 'foo' * to remain. */ - ref_file = git_path("%s", orig_ref); + ref_file = git_path("%s", orig_refname); if (remove_empty_directories(ref_file)) { last_errno = errno; - error("there are still refs under '%s'", orig_ref); + error("there are still refs under '%s'", orig_refname); goto error_return; } - ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, &type); + refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type); } if (type_p) *type_p = type; - if (!ref) { + if (!refname) { last_errno = errno; error("unable to resolve reference %s: %s", - orig_ref, strerror(errno)); + orig_refname, strerror(errno)); goto error_return; } missing = is_null_sha1(lock->old_sha1); @@ -1068,7 +1230,7 @@ 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(NULL), 0)) { + !is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) { last_errno = ENOTDIR; goto error_return; } @@ -1077,12 +1239,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lflags = LOCK_DIE_ON_ERROR; if (flags & REF_NODEREF) { - ref = orig_ref; + refname = orig_refname; lflags |= LOCK_NODEREF; } - lock->ref_name = xstrdup(ref); - lock->orig_ref_name = xstrdup(orig_ref); - ref_file = git_path("%s", ref); + lock->ref_name = xstrdup(refname); + lock->orig_ref_name = xstrdup(orig_refname); + ref_file = git_path("%s", refname); if (missing) lock->force_write = 1; if ((flags & REF_NODEREF) && (type & REF_ISSYMREF)) @@ -1103,42 +1265,32 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char return NULL; } -struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) +struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1) { char refpath[PATH_MAX]; - if (check_ref_format(ref)) + if (check_refname_format(refname, 0)) return NULL; - strcpy(refpath, mkpath("refs/%s", ref)); + strcpy(refpath, mkpath("refs/%s", refname)); return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL); } -struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags) +struct ref_lock *lock_any_ref_for_update(const char *refname, + const unsigned char *old_sha1, int flags) { - switch (check_ref_format(ref)) { - default: + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) return NULL; - case 0: - case CHECK_REF_FORMAT_ONELEVEL: - return lock_ref_sha1_basic(ref, old_sha1, flags, NULL); - } + return lock_ref_sha1_basic(refname, old_sha1, flags, NULL); } static struct lock_file packlock; static int repack_without_ref(const char *refname) { - struct ref_list *list, *packed_ref_list; - int fd; - int found = 0; + struct ref_array *packed; + int fd, i; - packed_ref_list = get_packed_refs(NULL); - for (list = packed_ref_list; list; list = list->next) { - if (!strcmp(refname, list->name)) { - found = 1; - break; - } - } - if (!found) + packed = get_packed_refs(get_ref_cache(NULL)); + if (search_ref_array(packed, refname) == NULL) return 0; fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0); if (fd < 0) { @@ -1146,17 +1298,18 @@ static int repack_without_ref(const char *refname) return error("cannot delete '%s' from packed refs", refname); } - for (list = packed_ref_list; list; list = list->next) { + for (i = 0; i < packed->nr; i++) { char line[PATH_MAX + 100]; int len; + struct ref_entry *ref = packed->refs[i]; - if (!strcmp(refname, list->name)) + if (!strcmp(refname, ref->name)) continue; len = snprintf(line, sizeof(line), "%s %s\n", - sha1_to_hex(list->sha1), list->name); + sha1_to_hex(ref->sha1), ref->name); /* this should not happen but just being defensive */ if (len > sizeof(line)) - die("too long a refname '%s'", list->name); + die("too long a refname '%s'", ref->name); write_or_die(fd, line, len); } return commit_lock_file(&packlock); @@ -1195,7 +1348,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) ret |= repack_without_ref(refname); unlink_or_warn(git_path("logs/%s", lock->ref_name)); - invalidate_cached_refs(); + invalidate_ref_cache(NULL); unlock_ref(lock); return ret; } @@ -1209,104 +1362,98 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) */ #define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log" -int rename_ref(const char *oldref, const char *newref, const char *logmsg) +int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) { - static const char renamed_ref[] = "RENAMED-REF"; unsigned char sha1[20], orig_sha1[20]; int flag = 0, logmoved = 0; struct ref_lock *lock; struct stat loginfo; - int log = !lstat(git_path("logs/%s", oldref), &loginfo); + int log = !lstat(git_path("logs/%s", oldrefname), &loginfo); const char *symref = NULL; + struct ref_cache *refs = get_ref_cache(NULL); if (log && S_ISLNK(loginfo.st_mode)) - return error("reflog for %s is a symlink", oldref); + return error("reflog for %s is a symlink", oldrefname); - symref = resolve_ref(oldref, orig_sha1, 1, &flag); + symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag); if (flag & REF_ISSYMREF) return error("refname %s is a symbolic ref, renaming it is not supported", - oldref); + oldrefname); if (!symref) - return error("refname %s not found", oldref); + return error("refname %s not found", oldrefname); - if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0)) + if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs))) return 1; - if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0)) + if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs))) return 1; - lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL); - if (!lock) - return error("unable to lock %s", renamed_ref); - lock->force_write = 1; - if (write_ref_sha1(lock, orig_sha1, logmsg)) - return error("unable to save current sha1 in %s", renamed_ref); - - if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG))) + if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG))) return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s", - oldref, strerror(errno)); + oldrefname, strerror(errno)); - if (delete_ref(oldref, orig_sha1, REF_NODEREF)) { - error("unable to delete old %s", oldref); + if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) { + error("unable to delete old %s", oldrefname); goto rollback; } - if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1, REF_NODEREF)) { + if (!read_ref_full(newrefname, sha1, 1, &flag) && + delete_ref(newrefname, sha1, REF_NODEREF)) { if (errno==EISDIR) { - if (remove_empty_directories(git_path("%s", newref))) { - error("Directory not empty: %s", newref); + if (remove_empty_directories(git_path("%s", newrefname))) { + error("Directory not empty: %s", newrefname); goto rollback; } } else { - error("unable to delete existing %s", newref); + error("unable to delete existing %s", newrefname); goto rollback; } } - if (log && safe_create_leading_directories(git_path("logs/%s", newref))) { - error("unable to create directory for %s", newref); + if (log && safe_create_leading_directories(git_path("logs/%s", newrefname))) { + error("unable to create directory for %s", newrefname); goto rollback; } retry: - if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newref))) { + if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) { if (errno==EISDIR || errno==ENOTDIR) { /* * rename(a, b) when b is an existing * directory ought to result in ISDIR, but * Solaris 5.8 gives ENOTDIR. Sheesh. */ - if (remove_empty_directories(git_path("logs/%s", newref))) { - error("Directory not empty: logs/%s", newref); + if (remove_empty_directories(git_path("logs/%s", newrefname))) { + error("Directory not empty: logs/%s", newrefname); goto rollback; } goto retry; } else { error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", - newref, strerror(errno)); + newrefname, strerror(errno)); goto rollback; } } logmoved = log; - lock = lock_ref_sha1_basic(newref, NULL, 0, NULL); + lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL); if (!lock) { - error("unable to lock %s for update", newref); + error("unable to lock %s for update", newrefname); goto rollback; } lock->force_write = 1; hashcpy(lock->old_sha1, orig_sha1); if (write_ref_sha1(lock, orig_sha1, logmsg)) { - error("unable to write current sha1 into %s", newref); + error("unable to write current sha1 into %s", newrefname); goto rollback; } return 0; rollback: - lock = lock_ref_sha1_basic(oldref, NULL, 0, NULL); + lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL); if (!lock) { - error("unable to lock %s for rollback", oldref); + error("unable to lock %s for rollback", oldrefname); goto rollbacklog; } @@ -1314,17 +1461,17 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) flag = log_all_ref_updates; log_all_ref_updates = 0; if (write_ref_sha1(lock, orig_sha1, NULL)) - error("unable to write current sha1 into %s", oldref); + error("unable to write current sha1 into %s", oldrefname); log_all_ref_updates = flag; rollbacklog: - if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref))) + if (logmoved && rename(git_path("logs/%s", newrefname), git_path("logs/%s", oldrefname))) error("unable to restore logfile %s from %s: %s", - oldref, newref, strerror(errno)); + oldrefname, newrefname, strerror(errno)); if (!logmoved && log && - rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref))) + rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldrefname))) error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s", - oldref, strerror(errno)); + oldrefname, strerror(errno)); return 1; } @@ -1381,16 +1528,16 @@ static int copy_msg(char *buf, const char *msg) return cp - buf; } -int log_ref_setup(const char *ref_name, char *logfile, int bufsize) +int log_ref_setup(const char *refname, char *logfile, int bufsize) { int logfd, oflags = O_APPEND | O_WRONLY; - git_snpath(logfile, bufsize, "logs/%s", ref_name); + git_snpath(logfile, bufsize, "logs/%s", refname); if (log_all_ref_updates && - (!prefixcmp(ref_name, "refs/heads/") || - !prefixcmp(ref_name, "refs/remotes/") || - !prefixcmp(ref_name, "refs/notes/") || - !strcmp(ref_name, "HEAD"))) { + (!prefixcmp(refname, "refs/heads/") || + !prefixcmp(refname, "refs/remotes/") || + !prefixcmp(refname, "refs/notes/") || + !strcmp(refname, "HEAD"))) { if (safe_create_leading_directories(logfile) < 0) return error("unable to create directory for %s", logfile); @@ -1420,7 +1567,7 @@ int log_ref_setup(const char *ref_name, char *logfile, int bufsize) return 0; } -static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, +static int log_ref_write(const char *refname, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *msg) { int logfd, result, written, oflags = O_APPEND | O_WRONLY; @@ -1433,7 +1580,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, if (log_all_ref_updates < 0) log_all_ref_updates = !is_bare_repository(); - result = log_ref_setup(ref_name, log_file, sizeof(log_file)); + result = log_ref_setup(refname, log_file, sizeof(log_file)); if (result) return result; @@ -1494,7 +1641,7 @@ int write_ref_sha1(struct ref_lock *lock, unlock_ref(lock); return -1; } - invalidate_cached_refs(); + clear_loose_ref_cache(get_ref_cache(NULL)); if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 || (strcmp(lock->ref_name, lock->orig_ref_name) && log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) { @@ -1517,7 +1664,7 @@ int write_ref_sha1(struct ref_lock *lock, unsigned char head_sha1[20]; int head_flag; const char *head_ref; - head_ref = resolve_ref("HEAD", head_sha1, 1, &head_flag); + head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag); if (head_ref && (head_flag & REF_ISSYMREF) && !strcmp(head_ref, lock->ref_name)) log_ref_write("HEAD", lock->old_sha1, sha1, logmsg); @@ -1604,7 +1751,9 @@ static char *ref_msg(const char *line, const char *endp) return xmemdupz(line, ep - line); } -int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt) +int read_ref_at(const char *refname, unsigned long at_time, int cnt, + unsigned char *sha1, char **msg, + unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt) { const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; char *tz_c; @@ -1615,7 +1764,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * void *log_mapped; size_t mapsz; - logfile = git_path("logs/%s", ref); + logfile = git_path("logs/%s", refname); logfd = open(logfile, O_RDONLY, 0); if (logfd < 0) die_errno("Unable to read log '%s'", logfile); @@ -1708,14 +1857,14 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * return 1; } -int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data) +int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data) { const char *logfile; FILE *logfp; struct strbuf sb = STRBUF_INIT; int ret = 0; - logfile = git_path("logs/%s", ref); + logfile = git_path("logs/%s", refname); logfp = fopen(logfile, "r"); if (!logfp) return -1; @@ -1766,9 +1915,9 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, return ret; } -int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) +int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data) { - return for_each_recent_reflog_ent(ref, fn, 0, cb_data); + return for_each_recent_reflog_ent(refname, fn, 0, cb_data); } static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data) @@ -1803,7 +1952,7 @@ static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data) retval = do_for_each_reflog(log, fn, cb_data); } else { unsigned char sha1[20]; - if (!resolve_ref(log, sha1, 0, NULL)) + if (read_ref_full(log, sha1, 0, NULL)) retval = error("bad ref for %s", log); else retval = fn(log, sha1, 0, cb_data); @@ -1851,10 +2000,10 @@ int update_ref(const char *action, const char *refname, return 0; } -int ref_exists(char *refname) +int ref_exists(const char *refname) { unsigned char sha1[20]; - return !!resolve_ref(refname, sha1, 1, NULL); + return !!resolve_ref_unsafe(refname, sha1, 1, NULL); } struct ref *find_ref_by_name(const struct ref *list, const char *name) @@ -1888,7 +2037,7 @@ static void gen_scanf_fmt(char *scanf_fmt, const char *rule) return; } -char *shorten_unambiguous_ref(const char *ref, int strict) +char *shorten_unambiguous_ref(const char *refname, int strict) { int i; static char **scanf_fmts; @@ -1917,10 +2066,10 @@ char *shorten_unambiguous_ref(const char *ref, int strict) /* bail out if there are no rules */ if (!nr_rules) - return xstrdup(ref); + return xstrdup(refname); - /* buffer for scanf result, at most ref must fit */ - short_name = xstrdup(ref); + /* buffer for scanf result, at most refname must fit */ + short_name = xstrdup(refname); /* skip first rule, it will always match */ for (i = nr_rules - 1; i > 0 ; --i) { @@ -1928,7 +2077,7 @@ char *shorten_unambiguous_ref(const char *ref, int strict) int rules_to_fail = i; int short_name_len; - if (1 != sscanf(ref, scanf_fmts[i], short_name)) + if (1 != sscanf(refname, scanf_fmts[i], short_name)) continue; short_name_len = strlen(short_name); @@ -1946,7 +2095,6 @@ char *shorten_unambiguous_ref(const char *ref, int strict) */ 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 */ @@ -1960,7 +2108,7 @@ char *shorten_unambiguous_ref(const char *ref, int strict) */ mksnpath(refname, sizeof(refname), rule, short_name_len, short_name); - if (!read_ref(refname, short_objectname)) + if (ref_exists(refname)) break; } @@ -1973,5 +2121,5 @@ char *shorten_unambiguous_ref(const char *ref, int strict) } free(short_name); - return xstrdup(ref); + return xstrdup(refname); } @@ -10,8 +10,9 @@ struct ref_lock { int force_write; }; -#define REF_ISSYMREF 01 -#define REF_ISPACKED 02 +#define REF_ISSYMREF 0x01 +#define REF_ISPACKED 0x02 +#define REF_ISBROKEN 0x04 /* * Calls the specified function for each ref file until it returns nonzero, @@ -57,16 +58,18 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn */ extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags); extern void clear_extra_refs(void); -extern int ref_exists(char *); +extern int ref_exists(const char *); -extern int peel_ref(const char *, unsigned char *); +extern int peel_ref(const char *refname, unsigned char *sha1); /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ -extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1); +extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1); /** Locks any ref (for 'HEAD' type refs). */ #define REF_NODEREF 0x01 -extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags); +extern struct ref_lock *lock_any_ref_for_update(const char *refname, + const unsigned char *old_sha1, + int flags); /** Close the file descriptor owned by a lock and return the status */ extern int close_ref(struct ref_lock *lock); @@ -80,16 +83,26 @@ extern void unlock_ref(struct ref_lock *lock); /** Writes sha1 into the ref specified by the lock. **/ extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); +/* + * Invalidate the reference cache for the specified submodule. Use + * submodule=NULL to invalidate the cache for the main module. This + * function must be called if references are changed via a mechanism + * other than the refs API. + */ +extern void invalidate_ref_cache(const char *submodule); + /** Setup reflog before using. **/ int log_ref_setup(const char *ref_name, char *logfile, int bufsize); /** Reads log for the value of ref during at_time. **/ -extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt); +extern int read_ref_at(const char *refname, unsigned long at_time, int cnt, + unsigned char *sha1, char **msg, + unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt); /* iterate over reflog entries */ typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); -int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data); -int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data); +int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data); +int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data); /* * Calls the specified function for each reflog file until it returns nonzero, @@ -97,20 +110,35 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, voi */ extern int for_each_reflog(each_ref_fn, void *); -#define CHECK_REF_FORMAT_OK 0 -#define CHECK_REF_FORMAT_ERROR (-1) -#define CHECK_REF_FORMAT_ONELEVEL (-2) -#define CHECK_REF_FORMAT_WILDCARD (-3) -extern int check_ref_format(const char *target); +#define REFNAME_ALLOW_ONELEVEL 1 +#define REFNAME_REFSPEC_PATTERN 2 +#define REFNAME_DOT_COMPONENT 4 + +/* + * Return 0 iff refname has the correct format for a refname according + * to the rules described in Documentation/git-check-ref-format.txt. + * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level + * reference names. If REFNAME_REFSPEC_PATTERN is set in flags, then + * allow a "*" wildcard character in place of one of the name + * components. No leading or repeated slashes are accepted. If + * REFNAME_DOT_COMPONENT is set in flags, then allow refname + * components to start with "." (but not a whole component equal to + * "." or ".."). + */ +extern int check_refname_format(const char *refname, int flags); extern const char *prettify_refname(const char *refname); -extern char *shorten_unambiguous_ref(const char *ref, int strict); +extern char *shorten_unambiguous_ref(const char *refname, int strict); /** rename ref, return 0 on success **/ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); -/** resolve ref in nested "gitlink" repository */ -extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result); +/** + * Resolve refname in the nested "gitlink" repository that is located + * at path. If the resolution is successful, return 0 and set sha1 to + * the name of the object; otherwise, return a non-zero value. + */ +extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1); /** lock a ref and then write its file */ enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR }; diff --git a/remote-curl.c b/remote-curl.c index b8cf45a7dd..48c20b86f3 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -115,7 +115,7 @@ static struct discovery* discover_refs(const char *service) http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); /* try again with "plain" url (no ? or & appended) */ - if (http_ret != HTTP_OK) { + if (http_ret != HTTP_OK && http_ret != HTTP_NOAUTH) { free(refs_url); strbuf_reset(&buffer); @@ -188,7 +188,7 @@ static int write_discovery(int in, int out, void *data) return err; } -static struct ref *parse_git_refs(struct discovery *heads) +static struct ref *parse_git_refs(struct discovery *heads, int for_push) { struct ref *list = NULL; struct async async; @@ -200,7 +200,8 @@ static struct ref *parse_git_refs(struct discovery *heads) if (start_async(&async)) die("cannot start thread to parse advertised refs"); - get_remote_heads(async.out, &list, 0, NULL, 0, NULL); + get_remote_heads(async.out, &list, + for_push ? REF_NORMAL : 0, NULL); close(async.out); if (finish_async(&async)) die("ref parsing thread failed"); @@ -268,7 +269,7 @@ static struct ref *get_refs(int for_push) heads = discover_refs("git-upload-pack"); if (heads->proto_git) - return parse_git_refs(heads); + return parse_git_refs(heads, for_push); return parse_info_refs(heads); } @@ -573,7 +574,14 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) close(client.in); client.in = -1; - strbuf_read(&rpc->result, client.out, 0); + if (!err) { + strbuf_read(&rpc->result, client.out, 0); + } else { + char buf[4096]; + for (;;) + if (xread(client.out, buf, sizeof(buf)) <= 0) + break; + } close(client.out); client.out = -1; @@ -852,7 +860,7 @@ int main(int argc, const char **argv) url = strbuf_detach(&buf, NULL); - http_init(remote); + http_init(remote, url, 0); do { if (strbuf_getline(&buf, stdin, '\n') == EOF) { @@ -482,7 +482,7 @@ static void read_config(void) return; default_remote_name = xstrdup("origin"); current_branch = NULL; - head_ref = resolve_ref("HEAD", sha1, 0, &flag); + head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag); if (head_ref && (flag & REF_ISSYMREF) && !prefixcmp(head_ref, "refs/heads/")) { current_branch = @@ -493,23 +493,6 @@ static void read_config(void) } /* - * We need to make sure the remote-tracking branches are well formed, but a - * wildcard refspec in "struct refspec" must have a trailing slash. We - * temporarily drop the trailing '/' while calling check_ref_format(), - * and put it back. The caller knows that a CHECK_REF_FORMAT_ONELEVEL - * error return is Ok for a wildcard refspec. - */ -static int verify_refname(char *name, int is_glob) -{ - int result; - - result = check_ref_format(name); - if (is_glob && result == CHECK_REF_FORMAT_WILDCARD) - result = CHECK_REF_FORMAT_OK; - return result; -} - -/* * This function frees a refspec array. * Warning: code paths should be checked to ensure that the src * and dst pointers are always freeable pointers as well @@ -532,13 +515,13 @@ static void free_refspecs(struct refspec *refspec, int nr_refspec) static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify) { int i; - int st; struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec); for (i = 0; i < nr_refspec; i++) { size_t llen; int is_glob; const char *lhs, *rhs; + int flags; is_glob = 0; @@ -576,6 +559,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp rs[i].pattern = is_glob; rs[i].src = xstrndup(lhs, llen); + flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0); if (fetch) { /* @@ -585,26 +569,20 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp */ if (!*rs[i].src) ; /* empty is ok */ - else { - st = verify_refname(rs[i].src, is_glob); - if (st && st != CHECK_REF_FORMAT_ONELEVEL) - goto invalid; - } + else if (check_refname_format(rs[i].src, flags)) + goto invalid; /* * RHS * - missing is ok, and is same as empty. * - empty is ok; it means not to store. * - otherwise it must be a valid looking ref. */ - if (!rs[i].dst) { + if (!rs[i].dst) ; /* ok */ - } else if (!*rs[i].dst) { + else if (!*rs[i].dst) ; /* ok */ - } else { - st = verify_refname(rs[i].dst, is_glob); - if (st && st != CHECK_REF_FORMAT_ONELEVEL) - goto invalid; - } + else if (check_refname_format(rs[i].dst, flags)) + goto invalid; } else { /* * LHS @@ -616,8 +594,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp if (!*rs[i].src) ; /* empty is ok */ else if (is_glob) { - st = verify_refname(rs[i].src, is_glob); - if (st && st != CHECK_REF_FORMAT_ONELEVEL) + if (check_refname_format(rs[i].src, flags)) goto invalid; } else @@ -630,14 +607,12 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp * - otherwise it must be a valid looking ref. */ if (!rs[i].dst) { - st = verify_refname(rs[i].src, is_glob); - if (st && st != CHECK_REF_FORMAT_ONELEVEL) + if (check_refname_format(rs[i].src, flags)) goto invalid; } else if (!*rs[i].dst) { goto invalid; } else { - st = verify_refname(rs[i].dst, is_glob); - if (st && st != CHECK_REF_FORMAT_ONELEVEL) + if (check_refname_format(rs[i].dst, flags)) goto invalid; } } @@ -828,59 +803,56 @@ static int match_name_with_pattern(const char *key, const char *name, return ret; } -char *apply_refspecs(struct refspec *refspecs, int nr_refspec, - const char *name) +static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query) { int i; - char *ret = NULL; - for (i = 0; i < nr_refspec; i++) { - struct refspec *refspec = refspecs + i; - if (refspec->pattern) { - if (match_name_with_pattern(refspec->src, name, - refspec->dst, &ret)) - return ret; - } else if (!strcmp(refspec->src, name)) - return strdup(refspec->dst); - } - return NULL; -} + int find_src = !query->src; -int remote_find_tracking(struct remote *remote, struct refspec *refspec) -{ - int find_src = refspec->src == NULL; - char *needle, **result; - int i; + if (find_src && !query->dst) + return error("query_refspecs: need either src or dst"); - if (find_src) { - if (!refspec->dst) - return error("find_tracking: need either src or dst"); - needle = refspec->dst; - result = &refspec->src; - } else { - needle = refspec->src; - result = &refspec->dst; - } + for (i = 0; i < ref_count; i++) { + struct refspec *refspec = &refs[i]; + const char *key = find_src ? refspec->dst : refspec->src; + const char *value = find_src ? refspec->src : refspec->dst; + const char *needle = find_src ? query->dst : query->src; + char **result = find_src ? &query->src : &query->dst; - for (i = 0; i < remote->fetch_refspec_nr; i++) { - struct refspec *fetch = &remote->fetch[i]; - const char *key = find_src ? fetch->dst : fetch->src; - const char *value = find_src ? fetch->src : fetch->dst; - if (!fetch->dst) + if (!refspec->dst) continue; - if (fetch->pattern) { + if (refspec->pattern) { if (match_name_with_pattern(key, needle, value, result)) { - refspec->force = fetch->force; + query->force = refspec->force; return 0; } } else if (!strcmp(needle, key)) { *result = xstrdup(value); - refspec->force = fetch->force; + query->force = refspec->force; return 0; } } return -1; } +char *apply_refspecs(struct refspec *refspecs, int nr_refspec, + const char *name) +{ + struct refspec query; + + memset(&query, 0, sizeof(struct refspec)); + query.src = (char *)name; + + if (query_refspecs(refspecs, nr_refspec, &query)) + return NULL; + + return query.dst; +} + +int remote_find_tracking(struct remote *remote, struct refspec *refspec) +{ + return query_refspecs(remote->fetch, remote->fetch_refspec_nr, refspec); +} + static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen, const char *name) { @@ -1035,7 +1007,7 @@ static char *guess_ref(const char *name, struct ref *peer) struct strbuf buf = STRBUF_INIT; unsigned char sha1[20]; - const char *r = resolve_ref(peer->name, sha1, 1, NULL); + const char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL); if (!r) return NULL; @@ -1086,7 +1058,7 @@ static int match_explicit(struct ref *src, struct ref *dst, unsigned char sha1[20]; int flag; - dst_value = resolve_ref(matched_src->name, sha1, 1, &flag); + dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag); if (!dst_value || ((flag & REF_ISSYMREF) && prefixcmp(dst_value, "refs/heads/"))) @@ -1170,12 +1142,15 @@ static struct ref **tail_ref(struct ref **head) } /* - * 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. + * Given the set of refs the local repository has, the set of refs the + * remote repository has, and the refspec used for push, determine + * what remote refs we will update and with what value by setting + * peer_ref (which object is being pushed) and force (if the push is + * forced) in elements of "dst". The function may add new elements to + * dst (e.g. pushing to a new branch, done in match_explicit_refs). */ -int match_refs(struct ref *src, struct ref **dst, - int nr_refspec, const char **refspec, int flags) +int match_push_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; @@ -1427,8 +1402,8 @@ int get_fetch_map(const struct ref *remote_refs, for (rmp = &ref_map; *rmp; ) { if ((*rmp)->peer_ref) { - int st = check_ref_format((*rmp)->peer_ref->name + 5); - if (st && st != CHECK_REF_FORMAT_ONELEVEL) { + if (check_refname_format((*rmp)->peer_ref->name + 5, + REFNAME_ALLOW_ONELEVEL)) { struct ref *ignore = *rmp; error("* Ignoring funny ref '%s' locally", (*rmp)->peer_ref->name); @@ -1532,13 +1507,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) * nothing to report. */ base = branch->merge[0]->dst; - if (!resolve_ref(base, sha1, 1, NULL)) + if (read_ref(base, sha1)) return 0; theirs = lookup_commit_reference(sha1); if (!theirs) return 0; - if (!resolve_ref(branch->refname, sha1, 1, NULL)) + if (read_ref(branch->refname, sha1)) return 0; ours = lookup_commit_reference(sha1); if (!ours) @@ -1620,7 +1595,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla int len; /* we already know it starts with refs/ to get here */ - if (check_ref_format(refname + 5)) + if (check_refname_format(refname + 5, 0)) return 0; len = strlen(refname) + 1; @@ -1681,36 +1656,47 @@ struct ref *guess_remote_head(const struct ref *head, } struct stale_heads_info { - struct remote *remote; struct string_list *ref_names; struct ref **stale_refs_tail; + struct refspec *refs; + int ref_count; }; static int get_stale_heads_cb(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct stale_heads_info *info = cb_data; - struct refspec refspec; - memset(&refspec, 0, sizeof(refspec)); - refspec.dst = (char *)refname; - if (!remote_find_tracking(info->remote, &refspec)) { - if (!((flags & REF_ISSYMREF) || - string_list_has_string(info->ref_names, refspec.src))) { - struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); - hashcpy(ref->new_sha1, sha1); - } + struct refspec query; + memset(&query, 0, sizeof(struct refspec)); + query.dst = (char *)refname; + + if (query_refspecs(info->refs, info->ref_count, &query)) + return 0; /* No matches */ + + /* + * If we did find a suitable refspec and it's not a symref and + * it's not in the list of refs that currently exist in that + * remote we consider it to be stale. + */ + if (!((flags & REF_ISSYMREF) || + string_list_has_string(info->ref_names, query.src))) { + struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); + hashcpy(ref->new_sha1, sha1); } + + free(query.src); return 0; } -struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map) +struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map) { struct ref *ref, *stale_refs = NULL; struct string_list ref_names = STRING_LIST_INIT_NODUP; struct stale_heads_info info; - info.remote = remote; info.ref_names = &ref_names; info.stale_refs_tail = &stale_refs; + info.refs = refs; + info.ref_count = ref_count; for (ref = fetch_map; ref; ref = ref->next) string_list_append(&ref_names, ref->name); sort_string_list(&ref_names); @@ -96,8 +96,8 @@ void free_refspec(int nr_refspec, struct refspec *refspec); char *apply_refspecs(struct refspec *refspecs, int nr_refspec, const char *name); -int match_refs(struct ref *src, struct ref **dst, - int nr_refspec, const char **refspec, int all); +int match_push_refs(struct ref *src, struct ref **dst, + int nr_refspec, const char **refspec, int all); void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, int force_update); @@ -164,6 +164,6 @@ struct ref *guess_remote_head(const struct ref *head, int all); /* Return refs which no longer exist on remote */ -struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map); +struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map); #endif diff --git a/revision.c b/revision.c index c46cfaa3e4..8764dde381 100644 --- a/revision.c +++ b/revision.c @@ -40,6 +40,47 @@ char *path_name(const struct name_path *path, const char *name) return n; } +static int show_path_component_truncated(FILE *out, const char *name, int len) +{ + int cnt; + for (cnt = 0; cnt < len; cnt++) { + int ch = name[cnt]; + if (!ch || ch == '\n') + return -1; + fputc(ch, out); + } + return len; +} + +static int show_path_truncated(FILE *out, const struct name_path *path) +{ + int emitted, ours; + + if (!path) + return 0; + emitted = show_path_truncated(out, path->up); + if (emitted < 0) + return emitted; + if (emitted) + fputc('/', out); + ours = show_path_component_truncated(out, path->elem, path->elem_len); + if (ours < 0) + return ours; + return ours || emitted; +} + +void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component) +{ + struct name_path leaf; + leaf.up = (struct name_path *)path; + leaf.elem = component; + leaf.elem_len = strlen(component); + + fprintf(out, "%s ", sha1_to_hex(obj->sha1)); + show_path_truncated(out, &leaf); + fputc('\n', out); +} + void add_object(struct object *obj, struct object_array *p, struct name_path *path, @@ -185,6 +226,13 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con return object; } +void add_pending_sha1(struct rev_info *revs, const char *name, + const unsigned char *sha1, unsigned int flags) +{ + struct object *object = get_reference(revs, name, sha1, flags); + add_pending_object(revs, object, name); +} + static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name) { unsigned long flags = object->flags; @@ -729,12 +777,16 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li * to filter the result of "A..B" further to the ones that can actually * reach A. */ -static struct commit_list *collect_bottom_commits(struct commit_list *list) +static struct commit_list *collect_bottom_commits(struct rev_info *revs) { - struct commit_list *elem, *bottom = NULL; - for (elem = list; elem; elem = elem->next) - if (elem->item->object.flags & UNINTERESTING) - commit_list_insert(elem->item, &bottom); + struct commit_list *bottom = NULL; + int i; + for (i = 0; i < revs->cmdline.nr; i++) { + struct rev_cmdline_entry *elem = &revs->cmdline.rev[i]; + if ((elem->flags & UNINTERESTING) && + elem->item->type == OBJ_COMMIT) + commit_list_insert((struct commit *)elem->item, &bottom); + } return bottom; } @@ -765,7 +817,7 @@ static int limit_list(struct rev_info *revs) struct commit_list *bottom = NULL; if (revs->ancestry_path) { - bottom = collect_bottom_commits(list); + bottom = collect_bottom_commits(revs); if (!bottom) die("--ancestry-path given but there are no bottom commits"); } @@ -822,6 +874,23 @@ static int limit_list(struct rev_info *revs) return 0; } +static void add_rev_cmdline(struct rev_info *revs, + struct object *item, + const char *name, + int whence, + unsigned flags) +{ + struct rev_cmdline_info *info = &revs->cmdline; + int nr = info->nr; + + ALLOC_GROW(info->rev, nr + 1, info->alloc); + info->rev[nr].item = item; + info->rev[nr].name = name; + info->rev[nr].whence = whence; + info->rev[nr].flags = flags; + info->nr++; +} + struct all_refs_cb { int all_flags; int warned_bad_reflog; @@ -834,7 +903,8 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, struct all_refs_cb *cb = cb_data; struct object *object = get_reference(cb->all_revs, path, sha1, cb->all_flags); - add_pending_object(cb->all_revs, object, path); + add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags); + add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags); return 0; } @@ -860,6 +930,7 @@ static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data) struct object *o = parse_object(sha1); if (o) { o->flags |= cb->all_flags; + /* ??? CMDLINEFLAGS ??? */ add_pending_object(cb->all_revs, o, ""); } else if (!cb->warned_bad_reflog) { @@ -896,12 +967,13 @@ static void handle_reflog(struct rev_info *revs, unsigned flags) for_each_reflog(handle_one_reflog, &cb); } -static int add_parents_only(struct rev_info *revs, const char *arg, int flags) +static int add_parents_only(struct rev_info *revs, const char *arg_, int flags) { unsigned char sha1[20]; struct object *it; struct commit *commit; struct commit_list *parents; + const char *arg = arg_; if (*arg == '^') { flags ^= UNINTERESTING; @@ -925,6 +997,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) for (parents = commit->parents; parents; parents = parents->next) { it = &parents->item->object; it->flags |= flags; + add_rev_cmdline(revs, it, arg_, REV_CMD_PARENTS_ONLY, flags); add_pending_object(revs, it, arg); } return 1; @@ -986,10 +1059,12 @@ static void prepare_show_merge(struct rev_info *revs) const char **prune = NULL; int i, prune_num = 1; /* counting terminating NULL */ - if (get_sha1("HEAD", sha1) || !(head = lookup_commit(sha1))) + if (get_sha1("HEAD", sha1)) die("--merge without HEAD?"); - if (get_sha1("MERGE_HEAD", sha1) || !(other = lookup_commit(sha1))) + head = lookup_commit_or_die(sha1, "HEAD"); + if (get_sha1("MERGE_HEAD", sha1)) die("--merge without MERGE_HEAD?"); + other = lookup_commit_or_die(sha1, "MERGE_HEAD"); add_pending_object(revs, &head->object, "HEAD"); add_pending_object(revs, &other->object, "MERGE_HEAD"); bases = get_merge_bases(head, other, 1); @@ -1018,7 +1093,7 @@ static void prepare_show_merge(struct rev_info *revs) revs->limited = 1; } -int handle_revision_arg(const char *arg, struct rev_info *revs, +int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, int cant_be_filename) { @@ -1027,6 +1102,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, struct object *object; unsigned char sha1[20]; int local_flags; + const char *arg = arg_; dotdot = strstr(arg, ".."); if (dotdot) { @@ -1035,6 +1111,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, const char *this = arg; int symmetric = *next == '.'; unsigned int flags_exclude = flags ^ UNINTERESTING; + unsigned int a_flags; *dotdot = 0; next += symmetric; @@ -1069,10 +1146,15 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, add_pending_commit_list(revs, exclude, flags_exclude); free_commit_list(exclude); - a->object.flags |= flags | SYMMETRIC_LEFT; + a_flags = flags | SYMMETRIC_LEFT; } else - a->object.flags |= flags_exclude; + a_flags = flags_exclude; + a->object.flags |= a_flags; b->object.flags |= flags; + add_rev_cmdline(revs, &a->object, this, + REV_CMD_LEFT, a_flags); + add_rev_cmdline(revs, &b->object, next, + REV_CMD_RIGHT, flags); add_pending_object(revs, &a->object, this); add_pending_object(revs, &b->object, next); return 0; @@ -1103,6 +1185,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, if (!cant_be_filename) verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, sha1, flags ^ local_flags); + add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags); add_pending_object_with_mode(revs, object, arg, mode); return 0; } @@ -1342,6 +1425,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->tree_objects = 1; revs->blob_objects = 1; revs->edge_hint = 1; + } else if (!strcmp(arg, "--verify-objects")) { + revs->tag_objects = 1; + revs->tree_objects = 1; + revs->blob_objects = 1; + revs->verify_objects = 1; } else if (!strcmp(arg, "--unpacked")) { revs->unpacked = 1; } else if (!prefixcmp(arg, "--unpacked=")) { @@ -1969,7 +2057,8 @@ int prepare_revision_walk(struct rev_info *revs) } e++; } - free(list); + if (!revs->leak_pending) + free(list); if (revs->no_walk) return 0; diff --git a/revision.h b/revision.h index 3d64adad18..6aa53d1aa7 100644 --- a/revision.h +++ b/revision.h @@ -24,6 +24,23 @@ struct rev_info; struct log_info; struct string_list; +struct rev_cmdline_info { + unsigned int nr; + unsigned int alloc; + struct rev_cmdline_entry { + struct object *item; + const char *name; + enum { + REV_CMD_REF, + REV_CMD_PARENTS_ONLY, + REV_CMD_LEFT, + REV_CMD_RIGHT, + REV_CMD_REV + } whence; + unsigned flags; + } *rev; +}; + struct rev_info { /* Starting list */ struct commit_list *commits; @@ -32,6 +49,9 @@ struct rev_info { /* Parents of shown commits */ struct object_array boundary_commits; + /* The end-points specified by the end user */ + struct rev_cmdline_info cmdline; + /* Basic information */ const char *prefix; const char *def; @@ -53,6 +73,7 @@ struct rev_info { tag_objects:1, tree_objects:1, blob_objects:1, + verify_objects:1, edge_hint:1, limited:1, unpacked:1, @@ -97,6 +118,7 @@ struct rev_info { date_mode_explicit:1, preserve_subject:1; unsigned int disable_stdin:1; + unsigned int leak_pending:1; enum date_mode date_mode; @@ -185,12 +207,15 @@ struct name_path { char *path_name(const struct name_path *path, const char *name); +extern void show_object_with_name(FILE *, struct object *, const struct name_path *, const char *); + extern void add_object(struct object *obj, struct object_array *p, struct name_path *path, const char *name); extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name); +extern void add_pending_sha1(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags); extern void add_head_to_pending(struct rev_info *); diff --git a/run-command.c b/run-command.c index a2796c4cae..1c51043884 100644 --- a/run-command.c +++ b/run-command.c @@ -1,6 +1,7 @@ #include "cache.h" #include "run-command.h" #include "exec_cmd.h" +#include "argv-array.h" static inline void close_pair(int fd[2]) { @@ -605,26 +606,23 @@ int finish_async(struct async *async) int run_hook(const char *index_file, const char *name, ...) { struct child_process hook; - const char **argv = NULL, *env[2]; + struct argv_array argv = ARGV_ARRAY_INIT; + const char *p, *env[2]; char index[PATH_MAX]; va_list args; int ret; - size_t i = 0, alloc = 0; if (access(git_path("hooks/%s", name), X_OK) < 0) return 0; va_start(args, name); - ALLOC_GROW(argv, i + 1, alloc); - argv[i++] = git_path("hooks/%s", name); - while (argv[i-1]) { - ALLOC_GROW(argv, i + 1, alloc); - argv[i++] = va_arg(args, const char *); - } + argv_array_push(&argv, git_path("hooks/%s", name)); + while ((p = va_arg(args, const char *))) + argv_array_push(&argv, p); va_end(args); memset(&hook, 0, sizeof(hook)); - hook.argv = argv; + hook.argv = argv.argv; hook.no_stdin = 1; hook.stdout_to_stderr = 1; if (index_file) { @@ -635,6 +633,6 @@ int run_hook(const char *index_file, const char *name, ...) } ret = run_command(&hook); - free(argv); + argv_array_clear(&argv); return ret; } diff --git a/sequencer.c b/sequencer.c new file mode 100644 index 0000000000..d1f28a6945 --- /dev/null +++ b/sequencer.c @@ -0,0 +1,13 @@ +#include "cache.h" +#include "sequencer.h" +#include "strbuf.h" +#include "dir.h" + +void remove_sequencer_state(void) +{ + struct strbuf seq_dir = STRBUF_INIT; + + strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR)); + remove_dir_recursively(&seq_dir, 0); + strbuf_release(&seq_dir); +} diff --git a/sequencer.h b/sequencer.h new file mode 100644 index 0000000000..2d4528f292 --- /dev/null +++ b/sequencer.h @@ -0,0 +1,12 @@ +#ifndef SEQUENCER_H +#define SEQUENCER_H + +#define SEQ_DIR "sequencer" +#define SEQ_HEAD_FILE "sequencer/head" +#define SEQ_TODO_FILE "sequencer/todo" +#define SEQ_OPTS_FILE "sequencer/opts" + +/* Removes SEQ_DIR. */ +extern void remove_sequencer_state(void); + +#endif @@ -236,38 +236,6 @@ const char **get_pathspec(const char *prefix, const char **pathspec) return pathspec; } -const char *pathspec_prefix(const char *prefix, const char **pathspec) -{ - const char **p, *n, *prev; - unsigned long max; - - if (!pathspec) - return prefix ? xmemdupz(prefix, strlen(prefix)) : NULL; - - prev = NULL; - max = PATH_MAX; - for (p = pathspec; (n = *p) != NULL; p++) { - int i, len = 0; - for (i = 0; i < max; i++) { - char c = n[i]; - if (prev && prev[i] != c) - break; - if (!c || c == '*' || c == '?') - break; - if (c == '/') - len = i+1; - } - prev = n; - if (len < max) { - max = len; - if (!max) - break; - } - } - - return max ? xmemdupz(prev, max) : NULL; -} - /* * Test if it looks like we're at a git directory. * We want to see: @@ -812,3 +780,10 @@ const char *setup_git_directory(void) { return setup_git_directory_gently(NULL); } + +const char *resolve_gitdir(const char *suspect) +{ + if (is_git_directory(suspect)) + return suspect; + return read_gitfile(suspect); +} diff --git a/sha1_file.c b/sha1_file.c index e002056b85..f291f3f0f7 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -18,6 +18,7 @@ #include "refs.h" #include "pack-revindex.h" #include "sha1-lookup.h" +#include "bulk-checkin.h" #ifndef O_NOATIME #if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) @@ -248,27 +249,30 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative const char *objdir = get_object_directory(); struct alternate_object_database *ent; struct alternate_object_database *alt; - /* 43 = 40-byte + 2 '/' + terminating NUL */ - int pfxlen = len; - int entlen = pfxlen + 43; - int base_len = -1; + int pfxlen, entlen; + struct strbuf pathbuf = STRBUF_INIT; if (!is_absolute_path(entry) && relative_base) { - /* Relative alt-odb */ - if (base_len < 0) - base_len = strlen(relative_base) + 1; - entlen += base_len; - pfxlen += base_len; + strbuf_addstr(&pathbuf, real_path(relative_base)); + strbuf_addch(&pathbuf, '/'); } - ent = xmalloc(sizeof(*ent) + entlen); + strbuf_add(&pathbuf, entry, len); - if (!is_absolute_path(entry) && relative_base) { - memcpy(ent->base, relative_base, base_len - 1); - ent->base[base_len - 1] = '/'; - memcpy(ent->base + base_len, entry, len); - } - else - memcpy(ent->base, entry, pfxlen); + normalize_path_copy(pathbuf.buf, pathbuf.buf); + + pfxlen = strlen(pathbuf.buf); + + /* + * The trailing slash after the directory name is given by + * this function at the end. Remove duplicates. + */ + while (pfxlen && pathbuf.buf[pfxlen-1] == '/') + pfxlen -= 1; + + entlen = pfxlen + 43; /* '/' + 2 hex + '/' + 38 hex + NUL */ + ent = xmalloc(sizeof(*ent) + entlen); + memcpy(ent->base, pathbuf.buf, pfxlen); + strbuf_release(&pathbuf); ent->name = ent->base + pfxlen + 1; ent->base[pfxlen + 3] = '/'; @@ -1264,7 +1268,8 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf, while (c & 0x80) { if (len <= used || bitsizeof(long) <= shift) { error("bad object header"); - return 0; + size = used = 0; + break; } c = buf[used++]; size += (c & 0x7f) << shift; @@ -1984,7 +1989,7 @@ off_t find_pack_entry_one(const unsigned char *sha1, return 0; } -static int is_pack_valid(struct packed_git *p) +int is_pack_valid(struct packed_git *p) { /* An already open pack is known to be valid. */ if (p->pack_fd != -1) @@ -2035,7 +2040,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) * was loaded! */ if (!is_pack_valid(p)) { - error("packfile %s cannot be accessed", p->pack_name); + warning("packfile %s cannot be accessed", p->pack_name); goto next; } e->offset = offset; @@ -2613,7 +2618,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size, if ((type == OBJ_BLOB) && path) { struct strbuf nbuf = STRBUF_INIT; if (convert_to_git(path, buf, size, &nbuf, - write_object ? safe_crlf : 0)) { + write_object ? safe_crlf : SAFE_CRLF_FALSE)) { buf = strbuf_detach(&nbuf, &size); re_allocated = 1; } @@ -2676,10 +2681,8 @@ static int index_core(unsigned char *sha1, int fd, size_t size, } /* - * This creates one packfile per large blob, because the caller - * immediately wants the result sha1, and fast-import can report the - * object name via marks mechanism only by closing the created - * packfile. + * This creates one packfile per large blob unless bulk-checkin + * machinery is "plugged". * * This also bypasses the usual "convert-to-git" dance, and that is on * purpose. We could write a streaming version of the converting @@ -2693,65 +2696,7 @@ static int index_stream(unsigned char *sha1, int fd, size_t size, enum object_type type, const char *path, unsigned flags) { - struct child_process fast_import; - char export_marks[512]; - const char *argv[] = { "fast-import", "--quiet", export_marks, NULL }; - char tmpfile[512]; - char fast_import_cmd[512]; - char buf[512]; - int len, tmpfd; - - strcpy(tmpfile, git_path("hashstream_XXXXXX")); - tmpfd = git_mkstemp_mode(tmpfile, 0600); - if (tmpfd < 0) - die_errno("cannot create tempfile: %s", tmpfile); - if (close(tmpfd)) - die_errno("cannot close tempfile: %s", tmpfile); - sprintf(export_marks, "--export-marks=%s", tmpfile); - - memset(&fast_import, 0, sizeof(fast_import)); - fast_import.in = -1; - fast_import.argv = argv; - fast_import.git_cmd = 1; - if (start_command(&fast_import)) - die_errno("index-stream: git fast-import failed"); - - len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n", - (unsigned long) size); - write_or_whine(fast_import.in, fast_import_cmd, len, - "index-stream: feeding fast-import"); - while (size) { - char buf[10240]; - size_t sz = size < sizeof(buf) ? size : sizeof(buf); - ssize_t actual; - - actual = read_in_full(fd, buf, sz); - if (actual < 0) - die_errno("index-stream: reading input"); - if (write_in_full(fast_import.in, buf, actual) != actual) - die_errno("index-stream: feeding fast-import"); - size -= actual; - } - if (close(fast_import.in)) - die_errno("index-stream: closing fast-import"); - if (finish_command(&fast_import)) - die_errno("index-stream: finishing fast-import"); - - tmpfd = open(tmpfile, O_RDONLY); - if (tmpfd < 0) - die_errno("index-stream: cannot open fast-import mark"); - len = read(tmpfd, buf, sizeof(buf)); - if (len < 0) - die_errno("index-stream: reading fast-import mark"); - if (close(tmpfd) < 0) - die_errno("index-stream: closing fast-import mark"); - if (unlink(tmpfile)) - die_errno("index-stream: unlinking fast-import mark"); - if (len != 44 || - memcmp(":1 ", buf, 3) || - get_sha1_hex(buf + 3, sha1)) - die_errno("index-stream: unexpected fast-import mark: <%s>", buf); - return 0; + return index_bulk_checkin(sha1, fd, size, type, path, flags); } int index_fd(unsigned char *sha1, int fd, struct stat *st, diff --git a/sha1_name.c b/sha1_name.c index ff5992acc9..03ffc2caaa 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -241,91 +241,6 @@ static int ambiguous_path(const char *path, int len) return slash; } -/* - * *string and *len will only be substituted, and *string returned (for - * later free()ing) if the string passed in is a magic short-hand form - * to name a branch. - */ -static char *substitute_branch_name(const char **string, int *len) -{ - struct strbuf buf = STRBUF_INIT; - int ret = interpret_branch_name(*string, &buf); - - if (ret == *len) { - size_t size; - *string = strbuf_detach(&buf, &size); - *len = size; - return (char *)*string; - } - - return NULL; -} - -int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) -{ - char *last_branch = substitute_branch_name(&str, &len); - const char **p, *r; - int refs_found = 0; - - *ref = NULL; - for (p = ref_rev_parse_rules; *p; p++) { - char fullref[PATH_MAX]; - unsigned char sha1_from_ref[20]; - unsigned char *this_result; - int flag; - - this_result = refs_found ? sha1_from_ref : sha1; - mksnpath(fullref, sizeof(fullref), *p, len, str); - r = resolve_ref(fullref, this_result, 1, &flag); - if (r) { - if (!refs_found++) - *ref = xstrdup(r); - if (!warn_ambiguous_refs) - break; - } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) - warning("ignoring dangling symref %s.", fullref); - } - free(last_branch); - return refs_found; -} - -int dwim_log(const char *str, int len, unsigned char *sha1, char **log) -{ - char *last_branch = substitute_branch_name(&str, &len); - const char **p; - int logs_found = 0; - - *log = NULL; - for (p = ref_rev_parse_rules; *p; p++) { - struct stat st; - unsigned char hash[20]; - char path[PATH_MAX]; - const char *ref, *it; - - mksnpath(path, sizeof(path), *p, len, str); - ref = resolve_ref(path, hash, 1, NULL); - if (!ref) - continue; - if (!stat(git_path("logs/%s", path), &st) && - S_ISREG(st.st_mode)) - it = path; - else if (strcmp(ref, path) && - !stat(git_path("logs/%s", ref), &st) && - S_ISREG(st.st_mode)) - it = ref; - else - continue; - if (!logs_found++) { - *log = xstrdup(it); - hashcpy(sha1, hash); - } - if (!warn_ambiguous_refs) - break; - } - free(last_branch); - return logs_found; -} - static inline int upstream_mark(const char *string, int len) { const char *suffix[] = { "@{upstream}", "@{u}" }; @@ -501,12 +416,6 @@ struct object *peel_to_type(const char *name, int namelen, { if (name && !namelen) namelen = strlen(name); - if (!o) { - unsigned char sha1[20]; - if (get_sha1_1(name, namelen, sha1)) - return NULL; - o = parse_object(sha1); - } while (1) { if (!o || (!o->parsed && !parse_object(o->sha1))) return NULL; @@ -972,9 +881,9 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name) { strbuf_branchname(sb, name); if (name[0] == '-') - return CHECK_REF_FORMAT_ERROR; + return -1; strbuf_splice(sb, 0, 0, "refs/heads/", 11); - return check_ref_format(sb->buf); + return check_refname_format(sb->buf, 0); } /* @@ -137,6 +137,8 @@ int main(int argc, char **argv) int devnull_fd; int count; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); /* diff --git a/show-index.c b/show-index.c index 4c0ac138af..5a9eed7fd8 100644 --- a/show-index.c +++ b/show-index.c @@ -11,6 +11,8 @@ int main(int argc, char **argv) unsigned int version; static unsigned int top_index[256]; + git_setup_gettext(); + if (argc != 1) usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) @@ -48,7 +50,7 @@ int main(int argc, char **argv) unsigned char sha1[20]; uint32_t crc; uint32_t off; - } *entries = malloc(nr * sizeof(entries[0])); + } *entries = xmalloc(nr * sizeof(entries[0])); for (i = 0; i < nr; i++) if (fread(entries[i].sha1, 20, 1, stdin) != 1) die("unable to read sha1 %u/%u", i, nr); @@ -357,7 +357,6 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term) { int ch; - strbuf_grow(sb, 0); if (feof(fp)) return EOF; @@ -398,3 +397,54 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint) return len; } + +void strbuf_add_lines(struct strbuf *out, const char *prefix, + const char *buf, size_t size) +{ + while (size) { + const char *next = memchr(buf, '\n', size); + next = next ? (next + 1) : (buf + size); + strbuf_addstr(out, prefix); + strbuf_add(out, buf, next - buf); + size -= next - buf; + buf = next; + } + strbuf_complete_line(out); +} + +static int is_rfc3986_reserved(char ch) +{ + switch (ch) { + case '!': case '*': case '\'': case '(': case ')': case ';': + case ':': case '@': case '&': case '=': case '+': case '$': + case ',': case '/': case '?': case '#': case '[': case ']': + return 1; + } + return 0; +} + +static int is_rfc3986_unreserved(char ch) +{ + return isalnum(ch) || + ch == '-' || ch == '_' || ch == '.' || ch == '~'; +} + +void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len, + int reserved) +{ + strbuf_grow(sb, len); + while (len--) { + char ch = *s++; + if (is_rfc3986_unreserved(ch) || + (!reserved && is_rfc3986_reserved(ch))) + strbuf_addch(sb, ch); + else + strbuf_addf(sb, "%%%02x", ch); + } +} + +void strbuf_addstr_urlencode(struct strbuf *sb, const char *s, + int reserved) +{ + strbuf_add_urlencode(sb, s, strlen(s), reserved); +} @@ -100,6 +100,14 @@ extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); __attribute__((format (printf,2,0))) extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap); +extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size); + +static inline void strbuf_complete_line(struct strbuf *sb) +{ + if (sb->len && sb->buf[sb->len - 1] != '\n') + strbuf_addch(sb, '\n'); +} + extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); /* XXX: if read fails, any partial read is undone */ extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint); @@ -115,4 +123,9 @@ extern int launch_editor(const char *path, struct strbuf *buffer, const char *co extern int strbuf_branchname(struct strbuf *sb, const char *name); extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name); +extern void strbuf_add_urlencode(struct strbuf *, const char *, size_t, + int reserved); +extern void strbuf_addstr_urlencode(struct strbuf *, const char *, + int reserved); + #endif /* STRBUF_H */ diff --git a/submodule.c b/submodule.c index ad86534ba1..68c1ba90b9 100644 --- a/submodule.c +++ b/submodule.c @@ -8,12 +8,18 @@ #include "diffcore.h" #include "refs.h" #include "string-list.h" +#include "sha1-array.h" +#include "argv-array.h" static struct string_list config_name_for_path; static struct string_list config_fetch_recurse_submodules_for_name; static struct string_list config_ignore_for_name; static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static struct string_list changed_submodule_paths; +static int initialized_fetch_ref_tips; +static struct sha1_array ref_tips_before_fetch; +static struct sha1_array ref_tips_after_fetch; + /* * The following flag is set if the .gitmodules file is unmerged. We then * disable recursion for all submodules where .git/config doesn't have a @@ -385,7 +391,7 @@ static void commit_need_pushing(struct commit *commit, struct commit_list *paren rev.diffopt.format_callback_data = needs_pushing; diff_tree_combined(commit->object.sha1, parents, n, 1, &rev); - free(parents); + free((void *)parents); } int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name) @@ -474,20 +480,46 @@ static void submodule_collect_changed_cb(struct diff_queue_struct *q, } } +static int add_sha1_to_array(const char *ref, const unsigned char *sha1, + int flags, void *data) +{ + sha1_array_append(data, sha1); + return 0; +} + void check_for_new_submodule_commits(unsigned char new_sha1[20]) { + if (!initialized_fetch_ref_tips) { + for_each_ref(add_sha1_to_array, &ref_tips_before_fetch); + initialized_fetch_ref_tips = 1; + } + + sha1_array_append(&ref_tips_after_fetch, new_sha1); +} + +static void add_sha1_to_argv(const unsigned char sha1[20], void *data) +{ + argv_array_push(data, sha1_to_hex(sha1)); +} + +static void calculate_changed_submodule_paths(void) +{ struct rev_info rev; struct commit *commit; - const char *argv[] = {NULL, NULL, "--not", "--all", NULL}; - int argc = ARRAY_SIZE(argv) - 1; + struct argv_array argv = ARGV_ARRAY_INIT; /* No need to check if there are no submodules configured */ if (!config_name_for_path.nr) return; init_revisions(&rev, NULL); - argv[1] = xstrdup(sha1_to_hex(new_sha1)); - setup_revisions(argc, argv, &rev, NULL); + argv_array_push(&argv, "--"); /* argv[0] program name */ + sha1_array_for_each_unique(&ref_tips_after_fetch, + add_sha1_to_argv, &argv); + argv_array_push(&argv, "--not"); + sha1_array_for_each_unique(&ref_tips_before_fetch, + add_sha1_to_argv, &argv); + setup_revisions(argv.argc, argv.argv, &rev, NULL); if (prepare_revision_walk(&rev)) die("revision walk setup failed"); @@ -511,7 +543,11 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20]) parent = parent->next; } } - free((char *)argv[1]); + + argv_array_clear(&argv); + sha1_array_clear(&ref_tips_before_fetch); + sha1_array_clear(&ref_tips_after_fetch); + initialized_fetch_ref_tips = 0; } int fetch_populated_submodules(int num_options, const char **options, @@ -545,6 +581,8 @@ int fetch_populated_submodules(int num_options, const char **options, cp.git_cmd = 1; cp.no_stdin = 1; + calculate_changed_submodule_paths(); + for (i = 0; i < active_nr; i++) { struct strbuf submodule_path = STRBUF_INIT; struct strbuf submodule_git_dir = STRBUF_INIT; @@ -651,7 +689,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked) cp.out = -1; cp.dir = path; if (start_command(&cp)) - die("Could not run git status --porcelain"); + die("Could not run 'git status --porcelain' in submodule %s", path); len = strbuf_read(&buf, cp.out, 1024); line = buf.buf; @@ -676,7 +714,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked) close(cp.out); if (finish_command(&cp)) - die("git status --porcelain failed"); + die("'git status --porcelain' failed in submodule %s", path); strbuf_release(&buf); return dirty_submodule; @@ -756,7 +794,7 @@ static void print_commit(struct commit *commit) int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], const unsigned char a[20], - const unsigned char b[20]) + const unsigned char b[20], int search) { struct commit *commit_base, *commit_a, *commit_b; int parent_count; @@ -811,6 +849,10 @@ int merge_submodule(unsigned char result[20], const char *path, * user needs to confirm the resolution. */ + /* Skip the search if makes no sense to the calling context. */ + if (!search) + return 0; + /* find commit which merges them */ parent_count = find_first_merges(&merges, path, commit_a, commit_b); switch (parent_count) { diff --git a/submodule.h b/submodule.h index 799c22d6c6..80e04f3c8c 100644 --- a/submodule.h +++ b/submodule.h @@ -28,7 +28,7 @@ int fetch_populated_submodules(int num_options, const char **options, int quiet); unsigned is_submodule_modified(const char *path, int ignore_untracked); int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], - const unsigned char a[20], const unsigned char b[20]); + const unsigned char a[20], const unsigned char b[20], int search); int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name); #endif diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 292753f77c..21d11d6c2d 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -16,6 +16,7 @@ our \$projectroot = "$safe_pwd"; our \$project_maxdepth = 8; our \$home_link_str = 'projects'; our \$site_name = '[localhost]'; +our \$site_html_head_string = ''; our \$site_header = ''; our \$site_footer = ''; our \$home_text = 'indextext.html'; diff --git a/t/lib-credential.sh b/t/lib-credential.sh new file mode 100755 index 0000000000..4a37cd79e5 --- /dev/null +++ b/t/lib-credential.sh @@ -0,0 +1,254 @@ +#!/bin/sh + +# Try a set of credential helpers; the expected stdin, +# stdout and stderr should be provided on stdin, +# separated by "--". +check() { + read_chunk >stdin && + read_chunk >expect-stdout && + read_chunk >expect-stderr && + test-credential "$@" <stdin >stdout 2>stderr && + test_cmp expect-stdout stdout && + test_cmp expect-stderr stderr +} + +read_chunk() { + while read line; do + case "$line" in + --) break ;; + *) echo "$line" ;; + esac + done +} + +# Clear any residual data from previous tests. We only +# need this when testing third-party helpers which read and +# write outside of our trash-directory sandbox. +# +# Don't bother checking for success here, as it is +# outside the scope of tests and represents a best effort to +# clean up after ourselves. +helper_test_clean() { + reject $1 https example.com store-user + reject $1 https example.com user1 + reject $1 https example.com user2 + reject $1 http path.tld user + reject $1 https timeout.tld user +} + +reject() { + ( + echo protocol=$2 + echo host=$3 + echo username=$4 + ) | test-credential reject $1 +} + +helper_test() { + HELPER=$1 + + test_expect_success "helper ($HELPER) has no existing data" ' + check fill $HELPER <<-\EOF + protocol=https + host=example.com + -- + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://example.com'\'': + askpass: Password for '\''https://askpass-username@example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) stores password" ' + check approve $HELPER <<-\EOF + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + ' + + test_expect_success "helper ($HELPER) can retrieve password" ' + check fill $HELPER <<-\EOF + protocol=https + host=example.com + -- + username=store-user + password=store-pass + -- + EOF + ' + + test_expect_success "helper ($HELPER) requires matching protocol" ' + check fill $HELPER <<-\EOF + protocol=http + host=example.com + -- + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''http://example.com'\'': + askpass: Password for '\''http://askpass-username@example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) requires matching host" ' + check fill $HELPER <<-\EOF + protocol=https + host=other.tld + -- + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://other.tld'\'': + askpass: Password for '\''https://askpass-username@other.tld'\'': + EOF + ' + + test_expect_success "helper ($HELPER) requires matching username" ' + check fill $HELPER <<-\EOF + protocol=https + host=example.com + username=other + -- + username=other + password=askpass-password + -- + askpass: Password for '\''https://other@example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) requires matching path" ' + test_config credential.usehttppath true && + check approve $HELPER <<-\EOF && + protocol=http + host=path.tld + path=foo.git + username=user + password=pass + EOF + check fill $HELPER <<-\EOF + protocol=http + host=path.tld + path=bar.git + -- + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''http://path.tld/bar.git'\'': + askpass: Password for '\''http://askpass-username@path.tld/bar.git'\'': + EOF + ' + + test_expect_success "helper ($HELPER) can forget host" ' + check reject $HELPER <<-\EOF && + protocol=https + host=example.com + EOF + check fill $HELPER <<-\EOF + protocol=https + host=example.com + -- + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://example.com'\'': + askpass: Password for '\''https://askpass-username@example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) can store multiple users" ' + check approve $HELPER <<-\EOF && + protocol=https + host=example.com + username=user1 + password=pass1 + EOF + check approve $HELPER <<-\EOF && + protocol=https + host=example.com + username=user2 + password=pass2 + EOF + check fill $HELPER <<-\EOF && + protocol=https + host=example.com + username=user1 + -- + username=user1 + password=pass1 + EOF + check fill $HELPER <<-\EOF + protocol=https + host=example.com + username=user2 + -- + username=user2 + password=pass2 + EOF + ' + + test_expect_success "helper ($HELPER) can forget user" ' + check reject $HELPER <<-\EOF && + protocol=https + host=example.com + username=user1 + EOF + check fill $HELPER <<-\EOF + protocol=https + host=example.com + username=user1 + -- + username=user1 + password=askpass-password + -- + askpass: Password for '\''https://user1@example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) remembers other user" ' + check fill $HELPER <<-\EOF + protocol=https + host=example.com + username=user2 + -- + username=user2 + password=pass2 + EOF + ' +} + +helper_test_timeout() { + HELPER="$*" + + test_expect_success "helper ($HELPER) times out" ' + check approve "$HELPER" <<-\EOF && + protocol=https + host=timeout.tld + username=user + password=pass + EOF + sleep 2 && + check fill "$HELPER" <<-\EOF + protocol=https + host=timeout.tld + -- + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://timeout.tld'\'': + askpass: Password for '\''https://askpass-username@timeout.tld'\'': + EOF + ' +} + +cat >askpass <<\EOF +#!/bin/sh +echo >&2 askpass: $* +what=`echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z` +echo "askpass-$what" +EOF +chmod +x askpass +GIT_ASKPASS="$PWD/askpass" +export GIT_ASKPASS diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh new file mode 100644 index 0000000000..0f76f6cdc0 --- /dev/null +++ b/t/lib-gettext.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +. ./test-lib.sh + +GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/po/build/locale" +GIT_PO_PATH="$GIT_BUILD_DIR/po" +export GIT_TEXTDOMAINDIR GIT_PO_PATH + +. "$GIT_BUILD_DIR"/git-sh-i18n + +if test_have_prereq GETTEXT && ! test_have_prereq GETTEXT_POISON +then + # is_IS.UTF-8 on Solaris and FreeBSD, is_IS.utf8 on Debian + is_IS_locale=$(locale -a | sed -n '/^is_IS\.[uU][tT][fF]-*8$/{ + p + q + }') + # is_IS.ISO8859-1 on Solaris and FreeBSD, is_IS.iso88591 on Debian + is_IS_iso_locale=$(locale -a | sed -n '/^is_IS\.[iI][sS][oO]8859-*1$/{ + p + q + }') + + # Export them as an environment variable so the t0202/test.pl Perl + # test can use it too + export is_IS_locale is_IS_iso_locale + + if test -n "$is_IS_locale" && + test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough" + then + # Some of the tests need the reference Icelandic locale + test_set_prereq GETTEXT_LOCALE + + # Exporting for t0202/test.pl + GETTEXT_LOCALE=1 + export GETTEXT_LOCALE + say "# lib-gettext: Found '$is_IS_locale' as an is_IS UTF-8 locale" + else + say "# lib-gettext: No is_IS UTF-8 locale available" + fi + + if test -n "$is_IS_iso_locale" && + test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough" + then + # Some of the tests need the reference Icelandic locale + test_set_prereq GETTEXT_ISO_LOCALE + + say "# lib-gettext: Found '$is_IS_iso_locale' as an is_IS ISO-8859-1 locale" + else + say "# lib-gettext: No is_IS ISO-8859-1 locale available" + fi +fi diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh new file mode 100644 index 0000000000..a870f9a5d2 --- /dev/null +++ b/t/lib-git-p4.sh @@ -0,0 +1,74 @@ +# +# Library code for git-p4 tests +# + +. ./test-lib.sh + +if ! test_have_prereq PYTHON; then + skip_all='skipping git-p4 tests; python not available' + test_done +fi +( p4 -h && p4d -h ) >/dev/null 2>&1 || { + skip_all='skipping git-p4 tests; no p4 or p4d' + test_done +} + +GITP4="$GIT_BUILD_DIR/contrib/fast-import/git-p4" + +# Try to pick a unique port: guess a large number, then hope +# no more than one of each test is running. +# +# This does not handle the case where somebody else is running the +# same tests and has chosen the same ports. +testid=${this_test#t} +git_p4_test_start=9800 +P4DPORT=$((10669 + ($testid - $git_p4_test_start))) + +export P4PORT=localhost:$P4DPORT +export P4CLIENT=client + +db="$TRASH_DIRECTORY/db" +cli="$TRASH_DIRECTORY/cli" +git="$TRASH_DIRECTORY/git" +pidfile="$TRASH_DIRECTORY/p4d.pid" + +start_p4d() { + mkdir -p "$db" "$cli" "$git" && + ( + p4d -q -r "$db" -p $P4DPORT & + echo $! >"$pidfile" + ) && + for i in 1 2 3 4 5 ; do + p4 info >/dev/null 2>&1 && break || true && + echo waiting for p4d to start && + sleep 1 + done && + # complain if it never started + p4 info >/dev/null && + ( + cd "$cli" && + p4 client -i <<-EOF + Client: client + Description: client + Root: $cli + View: //depot/... //client/... + EOF + ) +} + +kill_p4d() { + pid=$(cat "$pidfile") + # it had better exist for the first kill + kill $pid && + for i in 1 2 3 4 5 ; do + kill $pid >/dev/null 2>&1 || break + sleep 1 + done && + # complain if it would not die + test_must_fail kill $pid >/dev/null 2>&1 && + rm -rf "$db" "$cli" "$pidfile" +} + +cleanup_git() { + rm -rf "$git" +} diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh new file mode 100755 index 0000000000..05824fa8e4 --- /dev/null +++ b/t/lib-gpg.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +gpg_version=`gpg --version 2>&1` +if test $? = 127; then + say "You do not seem to have gpg installed" +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 "Your version of gpg (1.0.6) is too buggy for testing" + ;; + *) + # key generation info: gpg --homedir t/lib-gpg --gen-key + # Type DSA and Elgamal, size 2048 bits, no expiration date. + # Name and email: C O Mitter <committer@example.com> + # No password given, to enable non-interactive operation. + cp -R "$TEST_DIRECTORY"/lib-gpg ./gpghome + chmod 0700 gpghome + GNUPGHOME="$(pwd)/gpghome" + export GNUPGHOME + test_set_prereq GPG + ;; + esac +fi + +sanitize_pgp() { + perl -ne ' + /^-----END PGP/ and $in_pgp = 0; + print unless $in_pgp; + /^-----BEGIN PGP/ and $in_pgp = 1; + ' +} diff --git a/t/t7004/pubring.gpg b/t/lib-gpg/pubring.gpg Binary files differindex 83855fa4e1..83855fa4e1 100644 --- a/t/t7004/pubring.gpg +++ b/t/lib-gpg/pubring.gpg diff --git a/t/t7004/random_seed b/t/lib-gpg/random_seed Binary files differindex 8fed1339ed..8fed1339ed 100644 --- a/t/t7004/random_seed +++ b/t/lib-gpg/random_seed diff --git a/t/t7004/secring.gpg b/t/lib-gpg/secring.gpg Binary files differindex d831cd9eb3..d831cd9eb3 100644 --- a/t/t7004/secring.gpg +++ b/t/lib-gpg/secring.gpg diff --git a/t/t7004/trustdb.gpg b/t/lib-gpg/trustdb.gpg Binary files differindex abace962b8..abace962b8 100644 --- a/t/t7004/trustdb.gpg +++ b/t/lib-gpg/trustdb.gpg diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index b8996a373a..f7dc0781d5 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -81,8 +81,7 @@ prepare_httpd() { if test -n "$LIB_HTTPD_SSL" then - HTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT - AUTH_HTTPD_URL=https://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT + HTTPD_PROTO=https RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \ -config "$TEST_PATH/ssl.cnf" \ @@ -93,9 +92,12 @@ prepare_httpd() { export GIT_SSL_NO_VERIFY HTTPD_PARA="$HTTPD_PARA -DSSL" else - HTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT - AUTH_HTTPD_URL=http://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT + HTTPD_PROTO=http fi + HTTPD_DEST=127.0.0.1:$LIB_HTTPD_PORT + HTTPD_URL=$HTTPD_PROTO://$HTTPD_DEST + HTTPD_URL_USER=$HTTPD_PROTO://user%40host@$HTTPD_DEST + HTTPD_URL_USER_PASS=$HTTPD_PROTO://user%40host:user%40host@$HTTPD_DEST if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN" then diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 0a4cdfa93e..3c12b05d60 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -92,6 +92,9 @@ SSLEngine On <Location /dumb/> Dav on </Location> + <Location /auth/dumb> + Dav on + </Location> </IfDefine> <IfDefine SVN> diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index ae2f1da28f..dbb2623d93 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -5,20 +5,16 @@ test_description=gitattributes . ./test-lib.sh attr_check () { + path="$1" expect="$2" - path="$1" - expect="$2" - - git check-attr test -- "$path" >actual 2>err && + git $3 check-attr test -- "$path" >actual 2>err && echo "$path: test: $2" >expect && test_cmp expect actual && test_line_count = 0 err - } test_expect_success 'setup' ' - mkdir -p a/b/d a/c b && ( echo "[attr]notest !test" @@ -27,6 +23,7 @@ test_expect_success 'setup' ' echo "onoff test -test" echo "offon -test test" echo "no notest" + echo "A/e/F test=A/e/F" ) >.gitattributes && ( echo "g test=a/g" && @@ -40,29 +37,27 @@ test_expect_success 'setup' ' ( echo "global test=global" ) >"$HOME"/global-gitattributes && - cat <<EOF >expect-all -f: test: f -a/f: test: f -a/c/f: test: f -a/g: test: a/g -a/b/g: test: a/b/g -b/g: test: unspecified -a/b/h: test: a/b/h -a/b/d/g: test: a/b/d/* -onoff: test: unset -offon: test: set -no: notest: set -no: test: unspecified -a/b/d/no: notest: set -a/b/d/no: test: a/b/d/* -a/b/d/yes: notest: set -a/b/d/yes: test: unspecified -EOF - + cat <<-EOF >expect-all + f: test: f + a/f: test: f + a/c/f: test: f + a/g: test: a/g + a/b/g: test: a/b/g + b/g: test: unspecified + a/b/h: test: a/b/h + a/b/d/g: test: a/b/d/* + onoff: test: unset + offon: test: set + no: notest: set + no: test: unspecified + a/b/d/no: notest: set + a/b/d/no: test: a/b/d/* + a/b/d/yes: notest: set + a/b/d/yes: test: unspecified + EOF ' test_expect_success 'command line checks' ' - test_must_fail git check-attr && test_must_fail git check-attr -- && test_must_fail git check-attr test && @@ -72,11 +67,9 @@ test_expect_success 'command line checks' ' echo "f" | test_must_fail git check-attr --stdin -- f && echo "f" | test_must_fail git check-attr --stdin test -- f && test_must_fail git check-attr "" -- f - ' test_expect_success 'attribute test' ' - attr_check f f && attr_check a/f f && attr_check a/c/f f && @@ -90,20 +83,72 @@ test_expect_success 'attribute test' ' attr_check no unspecified && attr_check a/b/d/no "a/b/d/*" && attr_check a/b/d/yes unspecified +' + +test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' ' + + test_must_fail attr_check F f "-c core.ignorecase=0" && + test_must_fail attr_check a/F f "-c core.ignorecase=0" && + test_must_fail attr_check a/c/F f "-c core.ignorecase=0" && + test_must_fail attr_check a/G a/g "-c core.ignorecase=0" && + test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" && + test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" && + test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" && + test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" && + test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" && + test_must_fail attr_check oFfOn set "-c core.ignorecase=0" && + attr_check NO unspecified "-c core.ignorecase=0" && + test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" && + attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" && + test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0" ' -test_expect_success 'unnormalized paths' ' +test_expect_success 'attribute matching is case insensitive when core.ignorecase=1' ' + + attr_check F f "-c core.ignorecase=1" && + attr_check a/F f "-c core.ignorecase=1" && + attr_check a/c/F f "-c core.ignorecase=1" && + attr_check a/G a/g "-c core.ignorecase=1" && + attr_check a/B/g a/b/g "-c core.ignorecase=1" && + attr_check a/b/G a/b/g "-c core.ignorecase=1" && + attr_check a/b/H a/b/h "-c core.ignorecase=1" && + attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=1" && + attr_check oNoFf unset "-c core.ignorecase=1" && + attr_check oFfOn set "-c core.ignorecase=1" && + attr_check NO unspecified "-c core.ignorecase=1" && + attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=1" && + attr_check a/b/d/YES unspecified "-c core.ignorecase=1" && + attr_check a/E/f "A/e/F" "-c core.ignorecase=1" + +' + +test_expect_success 'check whether FS is case-insensitive' ' + mkdir junk && + echo good >junk/CamelCase && + echo bad >junk/camelcase && + if test "$(cat junk/CamelCase)" != good + then + test_set_prereq CASE_INSENSITIVE_FS + fi +' + +test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' ' + test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" && + test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" && + attr_check A/b/h a/b/h "-c core.ignorecase=1" && + attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" && + attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1" +' +test_expect_success 'unnormalized paths' ' attr_check ./f f && attr_check ./a/g a/g && attr_check a/./g a/g && attr_check a/c/../b/g a/b/g - ' test_expect_success 'relative paths' ' - (cd a && attr_check ../f f) && (cd a && attr_check f f) && (cd a && attr_check i a/i) && @@ -112,7 +157,6 @@ test_expect_success 'relative paths' ' (cd b && attr_check ../a/f f) && (cd b && attr_check ../a/g a/g) && (cd b && attr_check ../a/b/g a/b/g) - ' test_expect_success 'core.attributesfile' ' @@ -121,41 +165,43 @@ test_expect_success 'core.attributesfile' ' attr_check global global && git config core.attributesfile "~/global-gitattributes" && attr_check global global && - echo "global test=precedence" >> .gitattributes && + echo "global test=precedence" >>.gitattributes && attr_check global precedence ' test_expect_success 'attribute test: read paths from stdin' ' - - grep -v notest < expect-all > expect && - sed -e "s/:.*//" < expect | git check-attr --stdin test > actual && + grep -v notest <expect-all >expect && + sed -e "s/:.*//" <expect | git check-attr --stdin test >actual && test_cmp expect actual ' test_expect_success 'attribute test: --all option' ' + grep -v unspecified <expect-all | sort >specified-all && + sed -e "s/:.*//" <expect-all | uniq >stdin-all && + git check-attr --stdin --all <stdin-all | sort >actual && + test_cmp specified-all actual +' - grep -v unspecified < expect-all | sort > expect && - sed -e "s/:.*//" < expect-all | uniq | - git check-attr --stdin --all | sort > actual && - test_cmp expect actual +test_expect_success 'attribute test: --cached option' ' + : >empty && + git check-attr --cached --stdin --all <stdin-all | sort >actual && + test_cmp empty actual && + git add .gitattributes a/.gitattributes a/b/.gitattributes && + git check-attr --cached --stdin --all <stdin-all | sort >actual && + test_cmp specified-all actual ' test_expect_success 'root subdir attribute test' ' - attr_check a/i a/i && attr_check subdir/a/i unspecified - ' test_expect_success 'setup bare' ' - git clone --bare . bare.git && cd bare.git - ' test_expect_success 'bare repository: check that .gitattribute is ignored' ' - ( echo "f test=f" echo "a/i test=a/i" @@ -165,11 +211,16 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' ' attr_check a/c/f unspecified && attr_check a/i unspecified && attr_check subdir/a/i unspecified +' +test_expect_success 'bare repository: check that --cached honors index' ' + GIT_INDEX_FILE=../.git/index \ + git check-attr --cached --stdin --all <../stdin-all | + sort >actual && + test_cmp ../specified-all actual ' test_expect_success 'bare repository: test info/attributes' ' - ( echo "f test=f" echo "a/i test=a/i" @@ -179,7 +230,6 @@ test_expect_success 'bare repository: test info/attributes' ' attr_check a/c/f f && attr_check a/i a/i && attr_check subdir/a/i unspecified - ' test_done diff --git a/t/t0006-date.sh b/t/t0006-date.sh index f87abb5a06..1d29810a7a 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -40,6 +40,12 @@ check_parse 2008-02 bad check_parse 2008-02-14 bad check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000' check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500' +check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015' +check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000' +check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000' +check_parse '2008-02-14 20:30:45 -05' '2008-02-14 20:30:45 -0500' +check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000' +check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500' check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5 check_approxidate() { diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 007f39d5e1..a1e4616feb 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -87,7 +87,7 @@ EOF test_expect_success 'long options' ' test-parse-options --boolean --integer 1729 --boolean --string2=321 \ --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\ - > output 2> output.err && + --obsolete > output 2> output.err && test ! -s output.err && test_cmp expect output ' diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh new file mode 100755 index 0000000000..6c33e28ee8 --- /dev/null +++ b/t/t0090-cache-tree.sh @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description="Test whether cache-tree is properly updated + +Tests whether various commands properly update and/or rewrite the +cache-tree extension. +" + . ./test-lib.sh + +cmp_cache_tree () { + test-dump-cache-tree >actual && + sed "s/$_x40/SHA/" <actual >filtered && + test_cmp "$1" filtered +} + +# We don't bother with actually checking the SHA1: +# test-dump-cache-tree already verifies that all existing data is +# correct. +test_shallow_cache_tree () { + printf "SHA (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >expect && + cmp_cache_tree expect +} + +test_invalid_cache_tree () { + echo "invalid (0 subtrees)" >expect && + printf "SHA #(ref) (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >>expect && + cmp_cache_tree expect +} + +test_no_cache_tree () { + : >expect && + cmp_cache_tree expect +} + +test_expect_failure 'initial commit has cache-tree' ' + test_commit foo && + test_shallow_cache_tree +' + +test_expect_success 'read-tree HEAD establishes cache-tree' ' + git read-tree HEAD && + test_shallow_cache_tree +' + +test_expect_success 'git-add invalidates cache-tree' ' + test_when_finished "git reset --hard; git read-tree HEAD" && + echo "I changed this file" > foo && + git add foo && + test_invalid_cache_tree +' + +test_expect_success 'update-index invalidates cache-tree' ' + test_when_finished "git reset --hard; git read-tree HEAD" && + echo "I changed this file" > foo && + git update-index --add foo && + test_invalid_cache_tree +' + +test_expect_success 'write-tree establishes cache-tree' ' + test-scrap-cache-tree && + git write-tree && + test_shallow_cache_tree +' + +test_expect_success 'test-scrap-cache-tree works' ' + git read-tree HEAD && + test-scrap-cache-tree && + test_no_cache_tree +' + +test_expect_success 'second commit has cache-tree' ' + test_commit bar && + test_shallow_cache_tree +' + +test_expect_success 'reset --hard gives cache-tree' ' + test-scrap-cache-tree && + git reset --hard && + test_shallow_cache_tree +' + +test_expect_success 'reset --hard without index gives cache-tree' ' + rm -f .git/index && + git reset --hard && + test_shallow_cache_tree +' + +test_expect_failure 'checkout gives cache-tree' ' + git checkout HEAD^ && + test_shallow_cache_tree +' + +test_done diff --git a/t/t0200-gettext-basic.sh b/t/t0200-gettext-basic.sh new file mode 100755 index 0000000000..8853d8afb9 --- /dev/null +++ b/t/t0200-gettext-basic.sh @@ -0,0 +1,108 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description='Gettext support for Git' + +. ./lib-gettext.sh + +test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" ' + test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME" +' + +test_expect_success 'sanity: $TEXTDOMAIN is git' ' + test $TEXTDOMAIN = "git" +' + +test_expect_success 'xgettext sanity: Perl _() strings are not extracted' ' + ! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po +' + +test_expect_success 'xgettext sanity: Comment extraction with --add-comments' ' + grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l >expect && + grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po | wc -l >actual && + test_cmp expect actual +' + +test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' ' + ! grep "This is a phony" "$GIT_PO_PATH"/is.po && + ! grep "the above comment" "$GIT_PO_PATH"/is.po +' + +test_expect_success GETTEXT 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' ' + test -d "$TEXTDOMAINDIR" && + test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR" +' + +test_expect_success GETTEXT 'sanity: Icelandic locale was compiled' ' + test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo" +' + +# TODO: When we have more locales, generalize this to test them +# all. Maybe we'll need a dir->locale map for that. +test_expect_success GETTEXT_LOCALE 'sanity: gettext("") metadata is OK' ' + # Return value may be non-zero + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >zero-expect && + grep "Project-Id-Version: Git" zero-expect && + grep "Git Mailing List <git@vger.kernel.org>" zero-expect && + grep "Content-Type: text/plain; charset=UTF-8" zero-expect && + grep "Content-Transfer-Encoding: 8bit" zero-expect +' + +test_expect_success GETTEXT_LOCALE 'sanity: gettext(unknown) is passed through' ' + printf "This is not a translation string" >expect && + gettext "This is not a translation string" >actual && + eval_gettext "This is not a translation string" >actual && + test_cmp expect actual +' + +# xgettext from C +test_expect_success GETTEXT_LOCALE 'xgettext: C extraction of _() and N_() strings' ' + printf "TILRAUN: C tilraunastrengur" >expect && + printf "\n" >>expect && + printf "Sjá '\''git help SKIPUN'\'' til að sjá hjálp fyrir tiltekna skipun." >>expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string" >actual && + printf "\n" >>actual && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "See '\''git help COMMAND'\'' for more information on a specific command." >>actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'xgettext: C extraction with %s' ' + printf "TILRAUN: C tilraunastrengur %%s" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string %s" >actual && + test_cmp expect actual +' + +# xgettext from Shell +test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction' ' + printf "TILRAUN: Skeljartilraunastrengur" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Shell test string" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction with $variable' ' + printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" >x-expect && + LANGUAGE=is LC_ALL="$is_IS_locale" variable="a var i able" eval_gettext "TEST: A Shell test \$variable" >x-actual && + test_cmp x-expect x-actual +' + +# xgettext from Perl +test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction' ' + printf "TILRAUN: Perl tilraunastrengur" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test string" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction with %s' ' + printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test variable %s" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'sanity: Some gettext("") data for real locale' ' + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >real-locale && + test -s real-locale +' + +test_done diff --git a/t/t0200/test.c b/t/t0200/test.c new file mode 100644 index 0000000000..584d45cf36 --- /dev/null +++ b/t/t0200/test.c @@ -0,0 +1,23 @@ +/* This is a phony C program that's only here to test xgettext message extraction */ + +const char help[] = + /* TRANSLATORS: This is a test. You don't need to translate it. */ + N_("See 'git help COMMAND' for more information on a specific command."); + +int main(void) +{ + /* TRANSLATORS: This is a test. You don't need to translate it. */ + puts(_("TEST: A C test string")); + + /* TRANSLATORS: This is a test. You don't need to translate it. */ + printf(_("TEST: A C test string %s"), "variable"); + + /* TRANSLATORS: This is a test. You don't need to translate it. */ + printf(_("TEST: Hello World!")); + + /* TRANSLATORS: This is a test. You don't need to translate it. */ + printf(_("TEST: Old English Runes")); + + /* TRANSLATORS: This is a test. You don't need to translate it. */ + printf(_("TEST: ‘single’ and “double” quotes")); +} diff --git a/t/t0200/test.perl b/t/t0200/test.perl new file mode 100644 index 0000000000..36fba341ba --- /dev/null +++ b/t/t0200/test.perl @@ -0,0 +1,14 @@ +# This is a phony Perl program that's only here to test xgettext +# message extraction + +# so the above comment won't be folded into the next one by xgettext +1; + +# TRANSLATORS: This is a test. You don't need to translate it. +print __("TEST: A Perl test string"); + +# TRANSLATORS: This is a test. You don't need to translate it. +printf __("TEST: A Perl test variable %s"), "moo"; + +# TRANSLATORS: If you see this, Git has a bug +print _"TEST: A Perl string xgettext will not get"; diff --git a/t/t0200/test.sh b/t/t0200/test.sh new file mode 100644 index 0000000000..022d607f4c --- /dev/null +++ b/t/t0200/test.sh @@ -0,0 +1,14 @@ +# This is a phony Shell program that's only here to test xgettext +# message extraction + +# so the above comment won't be folded into the next one by xgettext +echo + +# TRANSLATORS: This is a test. You don't need to translate it. +gettext "TEST: A Shell test string" + +# TRANSLATORS: This is a test. You don't need to translate it. +eval_gettext "TEST: A Shell test \$variable" + +# TRANSLATORS: If you see this, Git has a bug +_("TEST: A Shell string xgettext won't get") diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh index 54d98b9b10..52b1c27c2c 100755 --- a/t/t0201-gettext-fallbacks.sh +++ b/t/t0201-gettext-fallbacks.sh @@ -5,8 +5,24 @@ test_description='Gettext Shell fallbacks' -. ./test-lib.sh -. "$GIT_BUILD_DIR"/git-sh-i18n +GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease +export GIT_INTERNAL_GETTEXT_TEST_FALLBACKS + +. ./lib-gettext.sh + +test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" ' + test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME" +' + +test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' ' + test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" +' + +test_expect_success C_LOCALE_OUTPUT 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' ' + echo fallthrough >expect && + echo $GIT_INTERNAL_GETTEXT_SH_SCHEME >actual && + test_cmp expect actual +' test_expect_success 'gettext: our gettext() fallback has pass-through semantics' ' printf "test" >expect && diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh new file mode 100755 index 0000000000..428ebb0080 --- /dev/null +++ b/t/t0202-gettext-perl.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description='Perl gettext interface (Git::I18N)' + +. ./lib-gettext.sh + +if ! test_have_prereq PERL; then + skip_all='skipping perl interface tests, perl not available' + test_done +fi + +"$PERL_PATH" -MTest::More -e 0 2>/dev/null || { + skip_all="Perl Test::More unavailable, skipping test" + test_done +} + +# The external test will outputs its own plan +test_external_has_tap=1 + +test_external_without_stderr \ + 'Perl Git::I18N API' \ + "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl + +test_done diff --git a/t/t0202/test.pl b/t/t0202/test.pl new file mode 100644 index 0000000000..2c10cb4693 --- /dev/null +++ b/t/t0202/test.pl @@ -0,0 +1,110 @@ +#!/usr/bin/perl +use 5.008; +use lib (split(/:/, $ENV{GITPERLLIB})); +use strict; +use warnings; +use POSIX qw(:locale_h); +use Test::More tests => 8; +use Git::I18N; + +my $has_gettext_library = $Git::I18N::__HAS_LIBRARY; + +ok(1, "Testing Git::I18N with " . + ($has_gettext_library + ? (defined $Locale::Messages::VERSION + ? "Locale::Messages version $Locale::Messages::VERSION" + # Versions of Locale::Messages before 1.17 didn't have a + # $VERSION variable. + : "Locale::Messages version <1.17") + : "NO Perl gettext library")); +ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}"); + +{ + my $exports = @Git::I18N::EXPORT; + ok($exports, "sanity: Git::I18N has $exports export(s)"); +} +is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default"); + +# prototypes +{ + # Add prototypes here when modifying the public interface to add + # more gettext wrapper functions. + my %prototypes = (qw( + __ $ + )); + while (my ($sub, $proto) = each %prototypes) { + is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype"); + } +} + +# Test basic passthrough in the C locale +{ + local $ENV{LANGUAGE} = 'C'; + local $ENV{LC_ALL} = 'C'; + local $ENV{LANG} = 'C'; + + my ($got, $expect) = (('TEST: A Perl test string') x 2); + + is(__($got), $expect, "Passing a string through __() in the C locale works"); +} + +# Test a basic message on different locales +SKIP: { + unless ($ENV{GETTEXT_LOCALE}) { + # Can't reliably test __() with a non-C locales because the + # required locales may not be installed on the system. + # + # We test for these anyway as part of the shell + # tests. Skipping these here will eliminate failures on odd + # platforms with incomplete locale data. + + skip "GETTEXT_LOCALE must be set by lib-gettext.sh for exhaustive Git::I18N tests", 2; + } + + # The is_IS UTF-8 locale passed from lib-gettext.sh + my $is_IS_locale = $ENV{is_IS_locale}; + + my $test = sub { + my ($got, $expect, $msg, $locale) = @_; + # Maybe this system doesn't have the locale we're trying to + # test. + my $locale_ok = setlocale(LC_ALL, $locale); + is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>"); + }; + + my $env_C = sub { + $ENV{LANGUAGE} = 'C'; + $ENV{LC_ALL} = 'C'; + }; + + my $env_is = sub { + $ENV{LANGUAGE} = 'is'; + $ENV{LC_ALL} = $is_IS_locale; + }; + + # Translation's the same as the original + my ($got, $expect) = (('TEST: A Perl test string') x 2); + + if ($has_gettext_library) { + { + local %ENV; $env_C->(); + $test->($got, $expect, "With", 'C'); + } + + { + my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur'); + local %ENV; $env_is->(); + $test->($got, $expect, "With", $is_IS_locale); + } + } else { + { + local %ENV; $env_C->(); + $test->($got, $expect, "Without", 'C'); + } + + { + local %ENV; $env_is->(); + $test->($got, $expect, "Without", 'is'); + } + } +} diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh new file mode 100755 index 0000000000..a212460081 --- /dev/null +++ b/t/t0203-gettext-setlocale-sanity.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description="The Git C functions aren't broken by setlocale(3)" + +. ./lib-gettext.sh + +test_expect_success 'git show a ISO-8859-1 commit under C locale' ' + . "$TEST_DIRECTORY"/t3901-8859-1.txt && + test_commit "iso-c-commit" iso-under-c && + git show >out 2>err && + ! test -s err && + grep -q "iso-c-commit" out +' + +test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' ' + . "$TEST_DIRECTORY"/t3901-8859-1.txt && + test_commit "iso-utf8-commit" iso-under-utf8 && + LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err && + ! test -s err && + grep -q "iso-utf8-commit" out +' + +test_done diff --git a/t/t0204-gettext-reencode-sanity.sh b/t/t0204-gettext-reencode-sanity.sh new file mode 100755 index 0000000000..189af90c02 --- /dev/null +++ b/t/t0204-gettext-reencode-sanity.sh @@ -0,0 +1,78 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description="Gettext reencoding of our *.po/*.mo files works" + +. ./lib-gettext.sh + + +test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Icelandic' ' + printf "TILRAUN: Halló Heimur!" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Hello World!" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Runes' ' + printf "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Old English Runes" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Icelandic' ' + printf "TILRAUN: Halló Heimur!" | iconv -f UTF-8 -t ISO8859-1 >expect && + LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Hello World!" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Runes' ' + LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Old English Runes" >runes && + + if grep "^TEST: Old English Runes$" runes + then + say "Your system can not handle this complexity and returns the string as-is" + else + # Both Solaris and GNU libintl will return this stream of + # question marks, so it is s probably portable enough + printf "TILRAUN: ?? ???? ??? ?? ???? ?? ??? ????? ??????????? ??? ?? ????" >runes-expect && + test_cmp runes-expect runes + fi +' + +test_expect_success GETTEXT_LOCALE 'gettext: Fetching a UTF-8 msgid -> UTF-8' ' + printf "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: ‘single’ and “double” quotes" >actual && + test_cmp expect actual +' + +# How these quotes get transliterated depends on the gettext implementation: +# +# Debian: ,einfaldar' og ,,tvöfaldar" [GNU libintl] +# FreeBSD: `einfaldar` og "tvöfaldar" [GNU libintl] +# Solaris: ?einfaldar? og ?tvöfaldar? [Solaris libintl] +# +# Just make sure the contents are transliterated, and don't use grep -q +# so that these differences are emitted under --verbose for curious +# eyes. +test_expect_success GETTEXT_ISO_LOCALE 'gettext: Fetching a UTF-8 msgid -> ISO-8859-1' ' + LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: ‘single’ and “double” quotes" >actual && + grep "einfaldar" actual && + grep "$(echo tvöfaldar | iconv -f UTF-8 -t ISO8859-1)" actual +' + +test_expect_success GETTEXT_LOCALE 'gettext.c: git init UTF-8 -> UTF-8' ' + printf "Bjó til tóma Git lind" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" git init repo >actual && + test_when_finished "rm -rf repo" && + grep "^$(cat expect) " actual +' + +test_expect_success GETTEXT_ISO_LOCALE 'gettext.c: git init UTF-8 -> ISO-8859-1' ' + printf "Bjó til tóma Git lind" >expect && + LANGUAGE=is LC_ALL="$is_IS_iso_locale" git init repo >actual && + test_when_finished "rm -rf repo" && + grep "^$(cat expect | iconv -f UTF-8 -t ISO8859-1) " actual +' + +test_done diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh new file mode 100755 index 0000000000..2361590d54 --- /dev/null +++ b/t/t0205-gettext-poison.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description='Gettext Shell poison' + +. ./lib-gettext.sh + +test_expect_success GETTEXT_POISON "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" ' + test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME" +' + +test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' ' + test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison" +' + +test_expect_success GETTEXT_POISON 'gettext: our gettext() fallback has poison semantics' ' + printf "# GETTEXT POISON #" >expect && + gettext "test" >actual && + test_cmp expect actual && + printf "# GETTEXT POISON #" >expect && + gettext "test more words" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_POISON 'eval_gettext: our eval_gettext() fallback has poison semantics' ' + printf "# GETTEXT POISON #" >expect && + eval_gettext "test" >actual && + test_cmp expect actual && + printf "# GETTEXT POISON #" >expect && + eval_gettext "test more words" >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh new file mode 100755 index 0000000000..885af8fb62 --- /dev/null +++ b/t/t0300-credentials.sh @@ -0,0 +1,279 @@ +#!/bin/sh + +test_description='basic credential helper tests' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-credential.sh + +test_expect_success 'setup helper scripts' ' + cat >dump <<-\EOF && + whoami=`echo $0 | sed s/.*git-credential-//` + echo >&2 "$whoami: $*" + while IFS== read key value; do + echo >&2 "$whoami: $key=$value" + eval "$key=$value" + done + EOF + + cat >git-credential-useless <<-\EOF && + #!/bin/sh + . ./dump + exit 0 + EOF + chmod +x git-credential-useless && + + cat >git-credential-verbatim <<-\EOF && + #!/bin/sh + user=$1; shift + pass=$1; shift + . ./dump + test -z "$user" || echo username=$user + test -z "$pass" || echo password=$pass + EOF + chmod +x git-credential-verbatim && + + PATH="$PWD:$PATH" +' + +test_expect_success 'credential_fill invokes helper' ' + check fill "verbatim foo bar" <<-\EOF + -- + username=foo + password=bar + -- + verbatim: get + EOF +' + +test_expect_success 'credential_fill invokes multiple helpers' ' + check fill useless "verbatim foo bar" <<-\EOF + -- + username=foo + password=bar + -- + useless: get + verbatim: get + EOF +' + +test_expect_success 'credential_fill stops when we get a full response' ' + check fill "verbatim one two" "verbatim three four" <<-\EOF + -- + username=one + password=two + -- + verbatim: get + EOF +' + +test_expect_success 'credential_fill continues through partial response' ' + check fill "verbatim one \"\"" "verbatim two three" <<-\EOF + -- + username=two + password=three + -- + verbatim: get + verbatim: get + verbatim: username=one + EOF +' + +test_expect_success 'credential_fill passes along metadata' ' + check fill "verbatim one two" <<-\EOF + protocol=ftp + host=example.com + path=foo.git + -- + username=one + password=two + -- + verbatim: get + verbatim: protocol=ftp + verbatim: host=example.com + verbatim: path=foo.git + EOF +' + +test_expect_success 'credential_approve calls all helpers' ' + check approve useless "verbatim one two" <<-\EOF + username=foo + password=bar + -- + -- + useless: store + useless: username=foo + useless: password=bar + verbatim: store + verbatim: username=foo + verbatim: password=bar + EOF +' + +test_expect_success 'do not bother storing password-less credential' ' + check approve useless <<-\EOF + username=foo + -- + -- + EOF +' + + +test_expect_success 'credential_reject calls all helpers' ' + check reject useless "verbatim one two" <<-\EOF + username=foo + password=bar + -- + -- + useless: erase + useless: username=foo + useless: password=bar + verbatim: erase + verbatim: username=foo + verbatim: password=bar + EOF +' + +test_expect_success 'usernames can be preserved' ' + check fill "verbatim \"\" three" <<-\EOF + username=one + -- + username=one + password=three + -- + verbatim: get + verbatim: username=one + EOF +' + +test_expect_success 'usernames can be overridden' ' + check fill "verbatim two three" <<-\EOF + username=one + -- + username=two + password=three + -- + verbatim: get + verbatim: username=one + EOF +' + +test_expect_success 'do not bother completing already-full credential' ' + check fill "verbatim three four" <<-\EOF + username=one + password=two + -- + username=one + password=two + -- + EOF +' + +# We can't test the basic terminal password prompt here because +# getpass() tries too hard to find the real terminal. But if our +# askpass helper is run, we know the internal getpass is working. +test_expect_success 'empty helper list falls back to internal getpass' ' + check fill <<-\EOF + -- + username=askpass-username + password=askpass-password + -- + askpass: Username: + askpass: Password: + EOF +' + +test_expect_success 'internal getpass does not ask for known username' ' + check fill <<-\EOF + username=foo + -- + username=foo + password=askpass-password + -- + askpass: Password: + EOF +' + +HELPER="!f() { + cat >/dev/null + echo username=foo + echo password=bar + }; f" +test_expect_success 'respect configured credentials' ' + test_config credential.helper "$HELPER" && + check fill <<-\EOF + -- + username=foo + password=bar + -- + EOF +' + +test_expect_success 'match configured credential' ' + test_config credential.https://example.com.helper "$HELPER" && + check fill <<-\EOF + protocol=https + host=example.com + path=repo.git + -- + username=foo + password=bar + -- + EOF +' + +test_expect_success 'do not match configured credential' ' + test_config credential.https://foo.helper "$HELPER" && + check fill <<-\EOF + protocol=https + host=bar + -- + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://bar'\'': + askpass: Password for '\''https://askpass-username@bar'\'': + EOF +' + +test_expect_success 'pull username from config' ' + test_config credential.https://example.com.username foo && + check fill <<-\EOF + protocol=https + host=example.com + -- + username=foo + password=askpass-password + -- + askpass: Password for '\''https://foo@example.com'\'': + EOF +' + +test_expect_success 'http paths can be part of context' ' + check fill "verbatim foo bar" <<-\EOF && + protocol=https + host=example.com + path=foo.git + -- + username=foo + password=bar + -- + verbatim: get + verbatim: protocol=https + verbatim: host=example.com + EOF + test_config credential.https://example.com.useHttpPath true && + check fill "verbatim foo bar" <<-\EOF + protocol=https + host=example.com + path=foo.git + -- + username=foo + password=bar + -- + verbatim: get + verbatim: protocol=https + verbatim: host=example.com + verbatim: path=foo.git + EOF +' + +test_done diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh new file mode 100755 index 0000000000..82c8411210 --- /dev/null +++ b/t/t0301-credential-cache.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description='credential-cache tests' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-credential.sh + +test -z "$NO_UNIX_SOCKETS" || { + skip_all='skipping credential-cache tests, unix sockets not available' + test_done +} + +# don't leave a stale daemon running +trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT + +helper_test cache +helper_test_timeout cache --timeout=1 + +# we can't rely on our "trap" above working after test_done, +# as test_done will delete the trash directory containing +# our socket, leaving us with no way to access the daemon. +git credential-cache exit + +test_done diff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh new file mode 100755 index 0000000000..f61b40c69b --- /dev/null +++ b/t/t0302-credential-store.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +test_description='credential-store tests' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-credential.sh + +helper_test store + +test_done diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh new file mode 100755 index 0000000000..267f4c8ba3 --- /dev/null +++ b/t/t0303-credential-external.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +test_description='external credential helper tests' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-credential.sh + +pre_test() { + test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" || + eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP" + + # clean before the test in case there is cruft left + # over from a previous run that would impact results + helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER" +} + +post_test() { + # clean afterwards so that we are good citizens + # and don't leave cruft in the helper's storage, which + # might be long-term system storage + helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER" +} + +if test -z "$GIT_TEST_CREDENTIAL_HELPER"; then + say "# skipping external helper tests (set GIT_TEST_CREDENTIAL_HELPER)" +else + pre_test + helper_test "$GIT_TEST_CREDENTIAL_HELPER" + post_test +fi + +if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then + say "# skipping external helper timeout tests" +else + pre_test + helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT" + post_test +fi + +test_done diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 6d52b824b1..f83df8eb8b 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -189,7 +189,7 @@ for args in "-w --stdin-paths" "--stdin-paths -w"; do done test_expect_success 'corrupt tree' ' - echo abc >malformed-tree + echo abc >malformed-tree && test_must_fail git hash-object -t tree malformed-tree ' diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index 018c3546b6..5c0053a20b 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -234,4 +234,20 @@ test_expect_success 'read-tree --reset removes outside worktree' ' test_cmp empty result ' +test_expect_success 'print errors when failed to update worktree' ' + echo sub >.git/info/sparse-checkout && + git checkout -f init && + mkdir sub && + touch sub/added sub/addedtoo && + test_must_fail git checkout top 2>actual && + cat >expected <<\EOF && +error: The following untracked working tree files would be overwritten by checkout: + sub/added + sub/addedtoo +Please move or remove them before you can switch branches. +Aborting +EOF + test_cmp expected actual +' + test_done diff --git a/t/t1013-loose-object-format.sh b/t/t1013-loose-object-format.sh index 0a9cedd374..fbf5f2fc00 100755 --- a/t/t1013-loose-object-format.sh +++ b/t/t1013-loose-object-format.sh @@ -34,7 +34,7 @@ assert_blob_equals() { } test_expect_success setup ' - cp -R "$TEST_DIRECTORY/t1013/objects" .git/ + cp -R "$TEST_DIRECTORY/t1013/objects" .git/ && git --version ' diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index 3b1b985996..e23ac0e69d 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -118,7 +118,7 @@ test_expect_success 'alias expansion' ' ) ' -test_expect_success '!alias expansion' ' +test_expect_success NOT_MINGW '!alias expansion' ' pwd >expect && ( git config alias.test !pwd && diff --git a/t/t1050-large.sh b/t/t1050-large.sh index deba111bd7..29d6024b7f 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -7,21 +7,97 @@ test_description='adding and checking out large blobs' test_expect_success setup ' git config core.bigfilethreshold 200k && - echo X | dd of=large bs=1k seek=2000 + echo X | dd of=large1 bs=1k seek=2000 && + echo X | dd of=large2 bs=1k seek=2000 && + echo X | dd of=large3 bs=1k seek=2000 && + echo Y | dd of=huge bs=1k seek=2500 ' -test_expect_success 'add a large file' ' - git add large && - # make sure we got a packfile and no loose objects - test -f .git/objects/pack/pack-*.pack && - test ! -f .git/objects/??/?????????????????????????????????????? +test_expect_success 'add a large file or two' ' + git add large1 huge large2 && + # make sure we got a single packfile and no loose objects + bad= count=0 idx= && + for p in .git/objects/pack/pack-*.pack + do + count=$(( $count + 1 )) + if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx" + then + continue + fi + bad=t + done && + test -z "$bad" && + test $count = 1 && + cnt=$(git show-index <"$idx" | wc -l) && + test $cnt = 2 && + for l in .git/objects/??/?????????????????????????????????????? + do + test -f "$l" || continue + bad=t + done && + test -z "$bad" && + + # attempt to add another copy of the same + git add large3 && + bad= count=0 && + for p in .git/objects/pack/pack-*.pack + do + count=$(( $count + 1 )) + if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx" + then + continue + fi + bad=t + done && + test -z "$bad" && + test $count = 1 ' test_expect_success 'checkout a large file' ' - large=$(git rev-parse :large) && - git update-index --add --cacheinfo 100644 $large another && + large1=$(git rev-parse :large1) && + git update-index --add --cacheinfo 100644 $large1 another && git checkout another && - cmp large another ;# this must not be test_cmp + cmp large1 another ;# this must not be test_cmp +' + +test_expect_success 'packsize limit' ' + test_create_repo mid && + ( + cd mid && + git config core.bigfilethreshold 64k && + git config pack.packsizelimit 256k && + + # mid1 and mid2 will fit within 256k limit but + # appending mid3 will bust the limit and will + # result in a separate packfile. + test-genrandom "a" $(( 66 * 1024 )) >mid1 && + test-genrandom "b" $(( 80 * 1024 )) >mid2 && + test-genrandom "c" $(( 128 * 1024 )) >mid3 && + git add mid1 mid2 mid3 && + + count=0 + for pi in .git/objects/pack/pack-*.idx + do + test -f "$pi" && count=$(( $count + 1 )) + done && + test $count = 2 && + + ( + git hash-object --stdin <mid1 + git hash-object --stdin <mid2 + git hash-object --stdin <mid3 + ) | + sort >expect && + + for pi in .git/objects/pack/pack-*.idx + do + git show-index <"$pi" + done | + sed -e "s/^[0-9]* \([0-9a-f]*\) .*/\1/" | + sort >actual && + + test_cmp expect actual + ) ' test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 3e140c18f4..0690e0edf4 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -7,28 +7,28 @@ test_description='Test git config in different settings' . ./test-lib.sh -test -f .git/config && rm .git/config - -git config core.penguin "little blue" +test_expect_success 'clear default config' ' + rm -f .git/config +' cat > expect << EOF [core] penguin = little blue EOF - -test_expect_success 'initial' 'cmp .git/config expect' - -git config Core.Movie BadPhysics +test_expect_success 'initial' ' + git config core.penguin "little blue" && + test_cmp expect .git/config +' cat > expect << EOF [core] penguin = little blue Movie = BadPhysics EOF - -test_expect_success 'mixed case' 'cmp .git/config expect' - -git config Cores.WhatEver Second +test_expect_success 'mixed case' ' + git config Core.Movie BadPhysics && + test_cmp expect .git/config +' cat > expect << EOF [core] @@ -37,10 +37,10 @@ cat > expect << EOF [Cores] WhatEver = Second EOF - -test_expect_success 'similar section' 'cmp .git/config expect' - -git config CORE.UPPERCASE true +test_expect_success 'similar section' ' + git config Cores.WhatEver Second && + test_cmp expect .git/config +' cat > expect << EOF [core] @@ -50,8 +50,10 @@ cat > expect << EOF [Cores] WhatEver = Second EOF - -test_expect_success 'similar section' 'cmp .git/config expect' +test_expect_success 'uppercase section' ' + git config CORE.UPPERCASE true && + test_cmp expect .git/config +' test_expect_success 'replace with non-match' \ 'git config core.penguin kingpin !blue' @@ -69,7 +71,34 @@ cat > expect << EOF WhatEver = Second EOF -test_expect_success 'non-match result' 'cmp .git/config expect' +test_expect_success 'non-match result' 'test_cmp expect .git/config' + +test_expect_success 'find mixed-case key by canonical name' ' + echo Second >expect && + git config cores.whatever >actual && + test_cmp expect actual +' + +test_expect_success 'find mixed-case key by non-canonical name' ' + echo Second >expect && + git config CoReS.WhAtEvEr >actual && + test_cmp expect actual +' + +test_expect_success 'subsections are not canonicalized by git-config' ' + cat >>.git/config <<-\EOF && + [section.SubSection] + key = one + [section "SubSection"] + key = two + EOF + echo one >expect && + git config section.subsection.key >actual && + test_cmp expect actual && + echo two >expect && + git config section.SubSection.key >actual && + test_cmp expect actual +' cat > .git/config <<\EOF [alpha] @@ -88,7 +117,7 @@ bar = foo [beta] EOF -test_expect_success 'unset with cont. lines is correct' 'cmp .git/config expect' +test_expect_success 'unset with cont. lines is correct' 'test_cmp expect .git/config' cat > .git/config << EOF [beta] ; silly comment # another comment @@ -116,7 +145,7 @@ noIndent= sillyValue ; 'nother silly comment [nextSection] noNewline = ouch EOF -test_expect_success 'multiple unset is correct' 'cmp .git/config expect' +test_expect_success 'multiple unset is correct' 'test_cmp expect .git/config' cp .git/config2 .git/config @@ -140,9 +169,7 @@ noIndent= sillyValue ; 'nother silly comment [nextSection] noNewline = ouch EOF -test_expect_success 'all replaced' 'cmp .git/config expect' - -git config beta.haha alpha +test_expect_success 'all replaced' 'test_cmp expect .git/config' cat > expect << EOF [beta] ; silly comment # another comment @@ -153,10 +180,10 @@ noIndent= sillyValue ; 'nother silly comment haha = alpha [nextSection] noNewline = ouch EOF - -test_expect_success 'really mean test' 'cmp .git/config expect' - -git config nextsection.nonewline wow +test_expect_success 'really mean test' ' + git config beta.haha alpha && + test_cmp expect .git/config +' cat > expect << EOF [beta] ; silly comment # another comment @@ -168,11 +195,12 @@ noIndent= sillyValue ; 'nother silly comment [nextSection] nonewline = wow EOF - -test_expect_success 'really really mean test' 'cmp .git/config expect' +test_expect_success 'really really mean test' ' + git config nextsection.nonewline wow && + test_cmp expect .git/config +' test_expect_success 'get value' 'test alpha = $(git config beta.haha)' -git config --unset beta.haha cat > expect << EOF [beta] ; silly comment # another comment @@ -183,10 +211,10 @@ noIndent= sillyValue ; 'nother silly comment [nextSection] nonewline = wow EOF - -test_expect_success 'unset' 'cmp .git/config expect' - -git config nextsection.NoNewLine "wow2 for me" "for me$" +test_expect_success 'unset' ' + git config --unset beta.haha && + test_cmp expect .git/config +' cat > expect << EOF [beta] ; silly comment # another comment @@ -198,8 +226,10 @@ noIndent= sillyValue ; 'nother silly comment nonewline = wow NoNewLine = wow2 for me EOF - -test_expect_success 'multivar' 'cmp .git/config expect' +test_expect_success 'multivar' ' + git config nextsection.NoNewLine "wow2 for me" "for me$" && + test_cmp expect .git/config +' test_expect_success 'non-match' \ 'git config --get nextsection.nonewline !for' @@ -214,8 +244,6 @@ test_expect_success 'ambiguous get' ' test_expect_success 'get multivar' \ 'git config --get-all nextsection.nonewline' -git config nextsection.nonewline "wow3" "wow$" - cat > expect << EOF [beta] ; silly comment # another comment noIndent= sillyValue ; 'nother silly comment @@ -226,8 +254,10 @@ noIndent= sillyValue ; 'nother silly comment nonewline = wow3 NoNewLine = wow2 for me EOF - -test_expect_success 'multivar replace' 'cmp .git/config expect' +test_expect_success 'multivar replace' ' + git config nextsection.nonewline "wow3" "wow$" && + test_cmp expect .git/config +' test_expect_success 'ambiguous value' ' test_must_fail git config nextsection.nonewline @@ -241,8 +271,6 @@ test_expect_success 'invalid unset' ' test_must_fail git config --unset somesection.nonewline ' -git config --unset nextsection.nonewline "wow3$" - cat > expect << EOF [beta] ; silly comment # another comment noIndent= sillyValue ; 'nother silly comment @@ -253,7 +281,10 @@ noIndent= sillyValue ; 'nother silly comment NoNewLine = wow2 for me EOF -test_expect_success 'multivar unset' 'cmp .git/config expect' +test_expect_success 'multivar unset' ' + git config --unset nextsection.nonewline "wow3$" && + test_cmp expect .git/config +' test_expect_success 'invalid key' 'test_must_fail git config inval.2key blabla' @@ -276,7 +307,7 @@ noIndent= sillyValue ; 'nother silly comment Alpha = beta EOF -test_expect_success 'hierarchical section value' 'cmp .git/config expect' +test_expect_success 'hierarchical section value' 'test_cmp expect .git/config' cat > expect << EOF beta.noindent=sillyValue @@ -304,15 +335,16 @@ EOF test_expect_success '--get-regexp' \ 'git config --get-regexp in > output && cmp output expect' -git config --add nextsection.nonewline "wow4 for you" - cat > expect << EOF wow2 for me wow4 for you EOF -test_expect_success '--add' \ - 'git config --get-all nextsection.nonewline > output && cmp output expect' +test_expect_success '--add' ' + git config --add nextsection.nonewline "wow4 for you" && + git config --get-all nextsection.nonewline > output && + test_cmp expect output +' cat > .git/config << EOF [novalue] @@ -333,6 +365,12 @@ test_expect_success 'get-regexp variable with no value' \ 'git config --get-regexp novalue > output && cmp output expect' +echo 'novalue.variable true' > expect + +test_expect_success 'get-regexp --bool variable with no value' \ + 'git config --bool --get-regexp novalue > output && + cmp output expect' + echo 'emptyvalue.variable ' > expect test_expect_success 'get-regexp variable with empty value' \ @@ -361,8 +399,6 @@ cat > .git/config << EOF c = d EOF -git config a.x y - cat > expect << EOF [a.b] c = d @@ -370,10 +406,10 @@ cat > expect << EOF x = y EOF -test_expect_success 'new section is partial match of another' 'cmp .git/config expect' - -git config b.x y -git config a.b c +test_expect_success 'new section is partial match of another' ' + git config a.x y && + test_cmp expect .git/config +' cat > expect << EOF [a.b] @@ -385,7 +421,11 @@ cat > expect << EOF x = y EOF -test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect' +test_expect_success 'new variable inserts into proper section' ' + git config b.x y && + git config a.b c && + test_cmp expect .git/config +' test_expect_success 'alternative GIT_CONFIG (non-existing file should fail)' \ 'test_must_fail git config --file non-existing-config -l' @@ -399,9 +439,10 @@ cat > expect << EOF ein.bahn=strasse EOF -GIT_CONFIG=other-config git config -l > output - -test_expect_success 'alternative GIT_CONFIG' 'cmp output expect' +test_expect_success 'alternative GIT_CONFIG' ' + GIT_CONFIG=other-config git config -l >output && + test_cmp expect output +' test_expect_success 'alternative GIT_CONFIG (--file)' \ 'git config --file other-config -l > output && cmp output expect' @@ -417,8 +458,6 @@ test_expect_success 'refer config from subdirectory' ' ' -GIT_CONFIG=other-config git config anwohner.park ausweis - cat > expect << EOF [ein] bahn = strasse @@ -426,7 +465,10 @@ cat > expect << EOF park = ausweis EOF -test_expect_success '--set in alternative GIT_CONFIG' 'cmp other-config expect' +test_expect_success '--set in alternative GIT_CONFIG' ' + GIT_CONFIG=other-config git config anwohner.park ausweis && + test_cmp expect other-config +' cat > .git/config << EOF # Hallo @@ -516,8 +558,6 @@ EOF test_expect_success "section was removed properly" \ "test_cmp expect .git/config" -rm .git/config - cat > expect << EOF [gitcvs] enabled = true @@ -528,10 +568,11 @@ EOF test_expect_success 'section ending' ' + rm -f .git/config && git config gitcvs.enabled true && git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite && git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite && - cmp .git/config expect + test_cmp expect .git/config ' @@ -600,8 +641,6 @@ test_expect_success 'invalid bool (set)' ' test_must_fail git config --bool bool.nobool foobar' -rm .git/config - cat > expect <<\EOF [bool] true1 = true @@ -616,6 +655,7 @@ EOF test_expect_success 'set --bool' ' + rm -f .git/config && git config --bool bool.true1 01 && git config --bool bool.true2 -1 && git config --bool bool.true3 YeS && @@ -626,8 +666,6 @@ test_expect_success 'set --bool' ' git config --bool bool.false4 FALSE && cmp expect .git/config' -rm .git/config - cat > expect <<\EOF [int] val1 = 1 @@ -637,13 +675,12 @@ EOF test_expect_success 'set --int' ' + rm -f .git/config && git config --int int.val1 01 && git config --int int.val2 -1 && git config --int int.val3 5m && cmp expect .git/config' -rm .git/config - cat >expect <<\EOF [bool] true1 = true @@ -657,6 +694,7 @@ cat >expect <<\EOF EOF test_expect_success 'get --bool-or-int' ' + rm -f .git/config && ( echo "[bool]" echo true1 @@ -676,7 +714,6 @@ test_expect_success 'get --bool-or-int' ' ' -rm .git/config cat >expect <<\EOF [bool] true1 = true @@ -690,6 +727,7 @@ cat >expect <<\EOF EOF test_expect_success 'set --bool-or-int' ' + rm -f .git/config && git config --bool-or-int bool.true1 true && git config --bool-or-int bool.false1 false && git config --bool-or-int bool.true2 yes && @@ -700,8 +738,6 @@ test_expect_success 'set --bool-or-int' ' test_cmp expect .git/config ' -rm .git/config - cat >expect <<\EOF [path] home = ~/ @@ -710,6 +746,7 @@ cat >expect <<\EOF EOF test_expect_success NOT_MINGW 'set --path' ' + rm -f .git/config && git config --path path.home "~/" && git config --path path.normal "/dev/null" && git config --path path.trailingtilde "foo~" && @@ -750,13 +787,6 @@ test_expect_success NOT_MINGW 'get --path copes with unset $HOME' ' test_cmp expect result ' -rm .git/config - -git config quote.leading " test" -git config quote.ending "test " -git config quote.semicolon "test;test" -git config quote.hash "test#test" - cat > expect << EOF [quote] leading = " test" @@ -764,8 +794,14 @@ cat > expect << EOF semicolon = "test;test" hash = "test#test" EOF - -test_expect_success 'quoting' 'cmp .git/config expect' +test_expect_success 'quoting' ' + rm -f .git/config && + git config quote.leading " test" && + git config quote.ending "test " && + git config quote.semicolon "test;test" && + git config quote.hash "test#test" && + test_cmp expect .git/config +' test_expect_success 'key with newline' ' test_must_fail git config "key.with @@ -790,9 +826,10 @@ section.noncont=not continued section.quotecont=cont;inued EOF -git config --list > result - -test_expect_success 'value continued on next line' 'cmp result expect' +test_expect_success 'value continued on next line' ' + git config --list > result && + cmp result expect +' cat > .git/config <<\EOF [section "sub=section"] @@ -813,16 +850,17 @@ barQsection.sub=section.val3 Qsection.sub=section.val4 Qsection.sub=section.val5Q EOF +test_expect_success '--null --list' ' + git config --null --list | nul_to_q >result && + echo >>result && + test_cmp expect result +' -git config --null --list | perl -pe 'y/\000/Q/' > result -echo >>result - -test_expect_success '--null --list' 'cmp result expect' - -git config --null --get-regexp 'val[0-9]' | perl -pe 'y/\000/Q/' > result -echo >>result - -test_expect_success '--null --get-regexp' 'cmp result expect' +test_expect_success '--null --get-regexp' ' + git config --null --get-regexp "val[0-9]" | nul_to_q >result && + echo >>result && + test_cmp expect result +' test_expect_success 'inner whitespace kept verbatim' ' git config section.val "foo bar" && diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index b5d89a2250..2b962cfda7 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -25,6 +25,11 @@ else test_set_prereq SETFACL fi +if test -z "$LOGNAME" +then + LOGNAME=$USER +fi + check_perms_and_acl () { test -r "$1" && getfacl "$1" > actual && diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index ed4275afe3..1ae4d87c92 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -5,34 +5,126 @@ test_description='Test git check-ref-format' . ./test-lib.sh valid_ref() { - test_expect_success "ref name '$1' is valid" \ - "git check-ref-format '$1'" + prereq= + case $1 in + [A-Z]*) + prereq=$1 + shift + esac + test_expect_success $prereq "ref name '$1' is valid${2:+ with options $2}" " + git check-ref-format $2 '$1' + " } invalid_ref() { - test_expect_success "ref name '$1' is not valid" \ - "test_must_fail git check-ref-format '$1'" + prereq= + case $1 in + [A-Z]*) + prereq=$1 + shift + esac + test_expect_success $prereq "ref name '$1' is invalid${2:+ with options $2}" " + test_must_fail git check-ref-format $2 '$1' + " } -valid_ref 'heads/foo' -invalid_ref 'foo' +invalid_ref '' +invalid_ref NOT_MINGW '/' +invalid_ref NOT_MINGW '/' --allow-onelevel +invalid_ref NOT_MINGW '/' --normalize +invalid_ref NOT_MINGW '/' '--allow-onelevel --normalize' valid_ref 'foo/bar/baz' -valid_ref 'refs///heads/foo' +valid_ref 'foo/bar/baz' --normalize +invalid_ref 'refs///heads/foo' +valid_ref 'refs///heads/foo' --normalize invalid_ref 'heads/foo/' -valid_ref '/heads/foo' -valid_ref '///heads/foo' -invalid_ref '/foo' +invalid_ref NOT_MINGW '/heads/foo' +valid_ref NOT_MINGW '/heads/foo' --normalize +invalid_ref '///heads/foo' +valid_ref '///heads/foo' --normalize invalid_ref './foo' +invalid_ref './foo/bar' +invalid_ref 'foo/./bar' +invalid_ref 'foo/bar/.' invalid_ref '.refs/foo' invalid_ref 'heads/foo..bar' invalid_ref 'heads/foo?bar' valid_ref 'foo./bar' invalid_ref 'heads/foo.lock' +invalid_ref 'heads///foo.lock' +invalid_ref 'foo.lock/bar' +invalid_ref 'foo.lock///bar' valid_ref 'heads/foo@bar' invalid_ref 'heads/v@{ation' invalid_ref 'heads/foo\bar' invalid_ref "$(printf 'heads/foo\t')" invalid_ref "$(printf 'heads/foo\177')" valid_ref "$(printf 'heads/fu\303\237')" +invalid_ref 'heads/*foo/bar' --refspec-pattern +invalid_ref 'heads/foo*/bar' --refspec-pattern +invalid_ref 'heads/f*o/bar' --refspec-pattern + +ref='foo' +invalid_ref "$ref" +valid_ref "$ref" --allow-onelevel +invalid_ref "$ref" --refspec-pattern +valid_ref "$ref" '--refspec-pattern --allow-onelevel' +invalid_ref "$ref" --normalize +valid_ref "$ref" '--allow-onelevel --normalize' + +ref='foo/bar' +valid_ref "$ref" +valid_ref "$ref" --allow-onelevel +valid_ref "$ref" --refspec-pattern +valid_ref "$ref" '--refspec-pattern --allow-onelevel' +valid_ref "$ref" --normalize + +ref='foo/*' +invalid_ref "$ref" +invalid_ref "$ref" --allow-onelevel +valid_ref "$ref" --refspec-pattern +valid_ref "$ref" '--refspec-pattern --allow-onelevel' + +ref='*/foo' +invalid_ref "$ref" +invalid_ref "$ref" --allow-onelevel +valid_ref "$ref" --refspec-pattern +valid_ref "$ref" '--refspec-pattern --allow-onelevel' +invalid_ref "$ref" --normalize +valid_ref "$ref" '--refspec-pattern --normalize' + +ref='foo/*/bar' +invalid_ref "$ref" +invalid_ref "$ref" --allow-onelevel +valid_ref "$ref" --refspec-pattern +valid_ref "$ref" '--refspec-pattern --allow-onelevel' + +ref='*' +invalid_ref "$ref" +invalid_ref "$ref" --allow-onelevel +invalid_ref "$ref" --refspec-pattern +valid_ref "$ref" '--refspec-pattern --allow-onelevel' + +ref='foo/*/*' +invalid_ref "$ref" --refspec-pattern +invalid_ref "$ref" '--refspec-pattern --allow-onelevel' + +ref='*/foo/*' +invalid_ref "$ref" --refspec-pattern +invalid_ref "$ref" '--refspec-pattern --allow-onelevel' + +ref='*/*/foo' +invalid_ref "$ref" --refspec-pattern +invalid_ref "$ref" '--refspec-pattern --allow-onelevel' + +ref='/foo' +invalid_ref NOT_MINGW "$ref" +invalid_ref NOT_MINGW "$ref" --allow-onelevel +invalid_ref NOT_MINGW "$ref" --refspec-pattern +invalid_ref NOT_MINGW "$ref" '--refspec-pattern --allow-onelevel' +invalid_ref NOT_MINGW "$ref" --normalize +valid_ref NOT_MINGW "$ref" '--allow-onelevel --normalize' +invalid_ref NOT_MINGW "$ref" '--refspec-pattern --normalize' +valid_ref NOT_MINGW "$ref" '--refspec-pattern --allow-onelevel --normalize' test_expect_success "check-ref-format --branch @{-1}" ' T=$(git write-tree) && @@ -65,23 +157,41 @@ test_expect_success 'check-ref-format --branch from subdir' ' ' valid_ref_normalized() { - test_expect_success "ref name '$1' simplifies to '$2'" " - refname=\$(git check-ref-format --print '$1') && - test \"\$refname\" = '$2'" + prereq= + case $1 in + [A-Z]*) + prereq=$1 + shift + esac + test_expect_success $prereq "ref name '$1' simplifies to '$2'" " + refname=\$(git check-ref-format --normalize '$1') && + test \"\$refname\" = '$2' + " } invalid_ref_normalized() { - test_expect_success "check-ref-format --print rejects '$1'" " - test_must_fail git check-ref-format --print '$1'" + prereq= + case $1 in + [A-Z]*) + prereq=$1 + shift + esac + test_expect_success $prereq "check-ref-format --normalize rejects '$1'" " + test_must_fail git check-ref-format --normalize '$1' + " } valid_ref_normalized 'heads/foo' 'heads/foo' valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo' -valid_ref_normalized '/heads/foo' 'heads/foo' +valid_ref_normalized NOT_MINGW '/heads/foo' 'heads/foo' valid_ref_normalized '///heads/foo' 'heads/foo' invalid_ref_normalized 'foo' -invalid_ref_normalized '/foo' +invalid_ref_normalized NOT_MINGW '/foo' invalid_ref_normalized 'heads/foo/../bar' invalid_ref_normalized 'heads/./foo' invalid_ref_normalized 'heads\foo' +invalid_ref_normalized 'heads/foo.lock' +invalid_ref_normalized 'heads///foo.lock' +invalid_ref_normalized 'foo.lock/bar' +invalid_ref_normalized 'foo.lock///bar' test_done diff --git a/t/t1412-reflog-loop.sh b/t/t1412-reflog-loop.sh index 647d888507..3acd895afb 100755 --- a/t/t1412-reflog-loop.sh +++ b/t/t1412-reflog-loop.sh @@ -20,7 +20,7 @@ test_expect_success 'setup reflog with alternating commits' ' ' test_expect_success 'reflog shows all entries' ' - cat >expect <<-\EOF + cat >expect <<-\EOF && topic@{0} reset: moving to two topic@{1} reset: moving to one topic@{2} reset: moving to two diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 63849836c8..e661147c57 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -48,7 +48,7 @@ test_expect_success 'setup: helper for testing rev-parse' ' ' test_expect_success 'setup: core.worktree = relative path' ' - unset GIT_WORK_TREE; + sane_unset GIT_WORK_TREE && GIT_DIR=repo.git && GIT_CONFIG="$(pwd)"/$GIT_DIR/config && export GIT_DIR GIT_CONFIG && @@ -89,7 +89,7 @@ test_expect_success 'subdir of work tree' ' ' test_expect_success 'setup: core.worktree = absolute path' ' - unset GIT_WORK_TREE; + sane_unset GIT_WORK_TREE && GIT_DIR=$(pwd)/repo.git && GIT_CONFIG=$GIT_DIR/config && export GIT_DIR GIT_CONFIG && @@ -334,7 +334,7 @@ test_expect_success 'absolute pathspec should fail gracefully' ' ' test_expect_success 'make_relative_path handles double slashes in GIT_DIR' ' - >dummy_file + >dummy_file && echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file && git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file ' diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh index ec50a9ad70..80aedfca8c 100755 --- a/t/t1510-repo-setup.sh +++ b/t/t1510-repo-setup.sh @@ -603,7 +603,7 @@ test_expect_success '#22a: core.worktree = GIT_DIR = .git dir' ' # like case #6. setup_repo 22a "$here/22a/.git" "" unset && - setup_repo 22ab . "" unset + setup_repo 22ab . "" unset && mkdir -p 22a/.git/sub 22a/sub && mkdir -p 22ab/.git/sub 22ab/sub && try_case 22a/.git unset . \ @@ -742,7 +742,7 @@ test_expect_success '#28: core.worktree and core.bare conflict (gitfile case)' ' # Case #29: GIT_WORK_TREE(+core.worktree) overrides core.bare (gitfile case). test_expect_success '#29: setup' ' setup_repo 29 non-existent gitfile true && - mkdir -p 29/sub/sub 29/wt/sub + mkdir -p 29/sub/sub 29/wt/sub && ( cd 29 && GIT_WORK_TREE="$here/29" && diff --git a/t/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh index e043cb7c64..eaefc777bd 100755 --- a/t/t1511-rev-parse-caret.sh +++ b/t/t1511-rev-parse-caret.sh @@ -6,7 +6,7 @@ test_description='tests for ref^{stuff}' test_expect_success 'setup' ' echo blob >a-blob && - git tag -a -m blob blob-tag `git hash-object -w a-blob` + git tag -a -m blob blob-tag `git hash-object -w a-blob` && mkdir a-tree && echo moreblobs >a-tree/another-blob && git add . && diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh index 75874e85df..2741262369 100755 --- a/t/t2018-checkout-branch.sh +++ b/t/t2018-checkout-branch.sh @@ -189,12 +189,13 @@ test_expect_success 'checkout -b <describe>' ' test_cmp expect actual ' -test_expect_success 'checkout -B to the current branch fails before merging' ' +test_expect_success 'checkout -B to the current branch works' ' git checkout branch1 && + git checkout -B branch1-scratch && + setup_dirty_mergeable && - git commit -mfooble && - test_must_fail git checkout -B branch1 initial && - test_must_fail test_dirty_mergeable + git checkout -B branch1-scratch initial && + test_dirty_mergeable ' test_done diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index 2366f0f414..068fba4c8e 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -12,11 +12,14 @@ check_not_detached () { } ORPHAN_WARNING='you are leaving .* commit.*behind' +PREV_HEAD_DESC='Previous HEAD position was' check_orphan_warning() { - test_i18ngrep "$ORPHAN_WARNING" "$1" + test_i18ngrep "$ORPHAN_WARNING" "$1" && + test_i18ngrep ! "$PREV_HEAD_DESC" "$1" } check_no_orphan_warning() { - test_i18ngrep ! "$ORPHAN_WARNING" "$1" + test_i18ngrep ! "$ORPHAN_WARNING" "$1" && + test_i18ngrep "$PREV_HEAD_DESC" "$1" } reset () { diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh new file mode 100755 index 0000000000..56090d2eba --- /dev/null +++ b/t/t2022-checkout-paths.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='checkout $tree -- $paths' +. ./test-lib.sh + +test_expect_success setup ' + mkdir dir && + >dir/master && + echo common >dir/common && + git add dir/master dir/common && + test_tick && git commit -m "master has dir/master" && + git checkout -b next && + git mv dir/master dir/next0 && + echo next >dir/next1 && + git add dir && + test_tick && git commit -m "next has dir/next but not dir/master" +' + +test_expect_success 'checking out paths out of a tree does not clobber unrelated paths' ' + git checkout next && + git reset --hard && + rm dir/next0 && + cat dir/common >expect.common && + echo modified >expect.next1 && + cat expect.next1 >dir/next1 && + echo untracked >expect.next2 && + cat expect.next2 >dir/next2 && + + git checkout master dir && + + test_cmp expect.common dir/common && + test_path_is_file dir/master && + git diff --exit-code master dir/master && + + test_path_is_missing dir/next0 && + test_cmp expect.next1 dir/next1 && + test_path_is_file dir/next2 && + test_must_fail git ls-files --error-unmatch dir/next2 && + test_cmp expect.next2 dir/next2 +' + +test_done diff --git a/t/t2023-checkout-m.sh b/t/t2023-checkout-m.sh new file mode 100755 index 0000000000..7e18985134 --- /dev/null +++ b/t/t2023-checkout-m.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +test_description='checkout -m -- <conflicted path> + +Ensures that checkout -m on a resolved file restores the conflicted file' + +. ./test-lib.sh + +test_expect_success setup ' + test_tick && + test_commit both.txt both.txt initial && + git branch topic && + test_commit modified_in_master both.txt in_master && + test_commit added_in_master each.txt in_master && + git checkout topic && + test_commit modified_in_topic both.txt in_topic && + test_commit added_in_topic each.txt in_topic +' + +test_expect_success 'git merge master' ' + test_must_fail git merge master +' + +clean_branchnames () { + # Remove branch names after conflict lines + sed 's/^\([<>]\{5,\}\) .*$/\1/' +} + +test_expect_success '-m restores 2-way conflicted+resolved file' ' + cp each.txt each.txt.conflicted && + echo resolved >each.txt && + git add each.txt && + git checkout -m -- each.txt && + clean_branchnames <each.txt >each.txt.cleaned && + clean_branchnames <each.txt.conflicted >each.txt.conflicted.cleaned && + test_cmp each.txt.conflicted.cleaned each.txt.cleaned +' + +test_expect_success '-m restores 3-way conflicted+resolved file' ' + cp both.txt both.txt.conflicted && + echo resolved >both.txt && + git add both.txt && + git checkout -m -- both.txt && + clean_branchnames <both.txt >both.txt.cleaned && + clean_branchnames <both.txt.conflicted >both.txt.conflicted.cleaned && + test_cmp both.txt.conflicted.cleaned both.txt.cleaned +' + +test_done diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh index 2eec0118c4..88be904c09 100755 --- a/t/t3000-ls-files-others.sh +++ b/t/t3000-ls-files-others.sh @@ -65,4 +65,23 @@ test_expect_success '--no-empty-directory hides empty directory' ' test_cmp expected3 output ' +test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' ' + git init super && + git init sub && + ( + cd sub && + >a && + git add a && + git commit -m sub && + git pack-refs --all + ) && + ( + cd super && + "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" ../sub sub + git ls-files --others --exclude-standard >../actual + ) && + echo sub/ >expect && + test_cmp expect actual +' + test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 55ef1895d7..a5e3da7e41 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -285,17 +285,7 @@ test_expect_success 'merge-recursive simple' ' rm -fr [abcd] && git checkout -f "$c2" && - git merge-recursive "$c0" -- "$c2" "$c1" - status=$? - case "$status" in - 1) - : happy - ;; - *) - echo >&2 "why status $status!!!" - false - ;; - esac + test_expect_code 1 git merge-recursive "$c0" -- "$c2" "$c1" ' test_expect_success 'merge-recursive result' ' @@ -334,17 +324,7 @@ test_expect_success 'merge-recursive remove conflict' ' rm -fr [abcd] && git checkout -f "$c1" && - git merge-recursive "$c0" -- "$c1" "$c5" - status=$? - case "$status" in - 1) - : happy - ;; - *) - echo >&2 "why status $status!!!" - false - ;; - esac + test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c5" ' test_expect_success 'merge-recursive remove conflict' ' @@ -388,17 +368,7 @@ test_expect_success 'merge-recursive d/f conflict' ' git reset --hard && git checkout -f "$c1" && - git merge-recursive "$c0" -- "$c1" "$c4" - status=$? - case "$status" in - 1) - : happy - ;; - *) - echo >&2 "why status $status!!!" - false - ;; - esac + test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c4" ' test_expect_success 'merge-recursive d/f conflict result' ' @@ -422,17 +392,7 @@ test_expect_success 'merge-recursive d/f conflict the other way' ' git reset --hard && git checkout -f "$c4" && - git merge-recursive "$c0" -- "$c4" "$c1" - status=$? - case "$status" in - 1) - : happy - ;; - *) - echo >&2 "why status $status!!!" - false - ;; - esac + test_expect_code 1 git merge-recursive "$c0" -- "$c4" "$c1" ' test_expect_success 'merge-recursive d/f conflict result the other way' ' @@ -456,17 +416,7 @@ test_expect_success 'merge-recursive d/f conflict' ' git reset --hard && git checkout -f "$c1" && - git merge-recursive "$c0" -- "$c1" "$c6" - status=$? - case "$status" in - 1) - : happy - ;; - *) - echo >&2 "why status $status!!!" - false - ;; - esac + test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c6" ' test_expect_success 'merge-recursive d/f conflict result' ' @@ -490,17 +440,7 @@ test_expect_success 'merge-recursive d/f conflict' ' git reset --hard && git checkout -f "$c6" && - git merge-recursive "$c0" -- "$c6" "$c1" - status=$? - case "$status" in - 1) - : happy - ;; - *) - echo >&2 "why status $status!!!" - false - ;; - esac + test_expect_code 1 git merge-recursive "$c0" -- "$c6" "$c1" ' test_expect_success 'merge-recursive d/f conflict result' ' diff --git a/t/t3040-subprojects-basic.sh b/t/t3040-subprojects-basic.sh index f6973e96a5..0a4ff6d824 100755 --- a/t/t3040-subprojects-basic.sh +++ b/t/t3040-subprojects-basic.sh @@ -3,81 +3,81 @@ test_description='Basic subproject functionality' . ./test-lib.sh -test_expect_success 'Super project creation' \ - ': >Makefile && - git add Makefile && - git commit -m "Superproject created"' - - -cat >expected <<EOF -:000000 160000 00000... A sub1 -:000000 160000 00000... A sub2 -EOF -test_expect_success 'create subprojects' \ - 'mkdir sub1 && - ( cd sub1 && git init && : >Makefile && git add * && - git commit -q -m "subproject 1" ) && - mkdir sub2 && - ( cd sub2 && git init && : >Makefile && git add * && - git commit -q -m "subproject 2" ) && - git update-index --add sub1 && - git add sub2 && - git commit -q -m "subprojects added" && - git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current && - test_cmp expected current' - -git branch save HEAD - -test_expect_success 'check if fsck ignores the subprojects' \ - 'git fsck --full' - -test_expect_success 'check if commit in a subproject detected' \ - '( cd sub1 && - echo "all:" >>Makefile && - echo " true" >>Makefile && - git commit -q -a -m "make all" ) && { - git diff-files --exit-code - test $? = 1 - }' - -test_expect_success 'check if a changed subproject HEAD can be committed' \ - 'git commit -q -a -m "sub1 changed" && { - git diff-tree --exit-code HEAD^ HEAD - test $? = 1 - }' - -test_expect_success 'check if diff-index works for subproject elements' \ - 'git diff-index --exit-code --cached save -- sub1 - test $? = 1' - -test_expect_success 'check if diff-tree works for subproject elements' \ - 'git diff-tree --exit-code HEAD^ HEAD -- sub1 - test $? = 1' - -test_expect_success 'check if git diff works for subproject elements' \ - 'git diff --exit-code HEAD^ HEAD - test $? = 1' - -test_expect_success 'check if clone works' \ - 'git ls-files -s >expected && - git clone -l -s . cloned && - ( cd cloned && git ls-files -s ) >current && - test_cmp expected current' - -test_expect_success 'removing and adding subproject' \ - 'git update-index --force-remove -- sub2 && - mv sub2 sub3 && - git add sub3 && - git commit -q -m "renaming a subproject" && { - git diff -M --name-status --exit-code HEAD^ HEAD - test $? = 1 - }' +test_expect_success 'setup: create superproject' ' + : >Makefile && + git add Makefile && + git commit -m "Superproject created" +' + +test_expect_success 'setup: create subprojects' ' + mkdir sub1 && + ( cd sub1 && git init && : >Makefile && git add * && + git commit -q -m "subproject 1" ) && + mkdir sub2 && + ( cd sub2 && git init && : >Makefile && git add * && + git commit -q -m "subproject 2" ) && + git update-index --add sub1 && + git add sub2 && + git commit -q -m "subprojects added" && + git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current && + git branch save HEAD && + cat >expected <<-\EOF && + :000000 160000 00000... A sub1 + :000000 160000 00000... A sub2 + EOF + test_cmp expected current +' + +test_expect_success 'check if fsck ignores the subprojects' ' + git fsck --full +' + +test_expect_success 'check if commit in a subproject detected' ' + ( cd sub1 && + echo "all:" >>Makefile && + echo " true" >>Makefile && + git commit -q -a -m "make all" ) && + test_expect_code 1 git diff-files --exit-code +' + +test_expect_success 'check if a changed subproject HEAD can be committed' ' + git commit -q -a -m "sub1 changed" && + test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD +' + +test_expect_success 'check if diff-index works for subproject elements' ' + test_expect_code 1 git diff-index --exit-code --cached save -- sub1 +' + +test_expect_success 'check if diff-tree works for subproject elements' ' + test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD -- sub1 +' + +test_expect_success 'check if git diff works for subproject elements' ' + test_expect_code 1 git diff --exit-code HEAD^ HEAD +' + +test_expect_success 'check if clone works' ' + git ls-files -s >expected && + git clone -l -s . cloned && + ( cd cloned && git ls-files -s ) >current && + test_cmp expected current +' + +test_expect_success 'removing and adding subproject' ' + git update-index --force-remove -- sub2 && + mv sub2 sub3 && + git add sub3 && + git commit -q -m "renaming a subproject" && + test_expect_code 1 git diff -M --name-status --exit-code HEAD^ HEAD +' # the index must contain the object name the HEAD of the # subproject sub1 was at the point "save" -test_expect_success 'checkout in superproject' \ - 'git checkout save && - git diff-index --exit-code --raw --cached save -- sub1' +test_expect_success 'checkout in superproject' ' + git checkout save && + git diff-index --exit-code --raw --cached save -- sub1 +' # just interesting what happened... # git diff --name-status -M save master diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 7633930bb4..ea82424e47 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -22,8 +22,8 @@ test_expect_success \ test_expect_success \ 'git branch --help should not have created a bogus branch' ' - git branch --help </dev/null >/dev/null 2>/dev/null; - ! test -f .git/refs/heads/--help + test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null && + test_path_is_missing .git/refs/heads/--help ' test_expect_success 'branch -h in broken repository' ' @@ -39,11 +39,11 @@ test_expect_success 'branch -h in broken repository' ' test_expect_success \ 'git branch abc should create a branch' \ - 'git branch abc && test -f .git/refs/heads/abc' + 'git branch abc && test_path_is_file .git/refs/heads/abc' test_expect_success \ 'git branch a/b/c should create a branch' \ - 'git branch a/b/c && test -f .git/refs/heads/a/b/c' + 'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c' cat >expect <<EOF $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master @@ -52,15 +52,15 @@ test_expect_success \ 'git branch -l d/e/f should create a branch and a log' \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ git branch -l d/e/f && - test -f .git/refs/heads/d/e/f && - test -f .git/logs/refs/heads/d/e/f && + test_path_is_file .git/refs/heads/d/e/f && + test_path_is_file .git/logs/refs/heads/d/e/f && test_cmp expect .git/logs/refs/heads/d/e/f' test_expect_success \ 'git branch -d d/e/f should delete a branch and a log' \ 'git branch -d d/e/f && - test ! -f .git/refs/heads/d/e/f && - test ! -f .git/logs/refs/heads/d/e/f' + test_path_is_missing .git/refs/heads/d/e/f && + test_path_is_missing .git/logs/refs/heads/d/e/f' test_expect_success \ 'git branch j/k should work after branch j has been deleted' \ @@ -75,16 +75,21 @@ test_expect_success \ git branch l' test_expect_success \ + 'git branch -m dumps usage' \ + 'test_expect_code 129 git branch -m 2>err && + grep "[Uu]sage: git branch" err' + +test_expect_success \ 'git branch -m m m/m should work' \ 'git branch -l m && git branch -m m m/m && - test -f .git/logs/refs/heads/m/m' + test_path_is_file .git/logs/refs/heads/m/m' test_expect_success \ 'git branch -m n/n n should work' \ 'git branch -l n/n && - git branch -m n/n n - test -f .git/logs/refs/heads/n' + git branch -m n/n n && + test_path_is_file .git/logs/refs/heads/n' test_expect_success 'git branch -m o/o o should fail when o/p exists' ' git branch o/o && @@ -110,6 +115,54 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou git branch -M baz bam ' +test_expect_success 'git branch -M master should work when master is checked out' ' + git checkout master && + git branch -M master +' + +test_expect_success 'git branch -M master master should work when master is checked out' ' + git checkout master && + git branch -M master master +' + +test_expect_success 'git branch -M master2 master2 should work when master is checked out' ' + git checkout master && + git branch master2 && + git branch -M master2 master2 +' + +test_expect_success 'git branch -v -d t should work' ' + git branch t && + test_path_is_file .git/refs/heads/t && + git branch -v -d t && + test_path_is_missing .git/refs/heads/t +' + +test_expect_success 'git branch -v -m t s should work' ' + git branch t && + test_path_is_file .git/refs/heads/t && + git branch -v -m t s && + test_path_is_missing .git/refs/heads/t && + test_path_is_file .git/refs/heads/s && + git branch -d s +' + +test_expect_success 'git branch -m -d t s should fail' ' + git branch t && + test_path_is_file .git/refs/heads/t && + test_must_fail git branch -m -d t s && + git branch -d t && + test_path_is_missing .git/refs/heads/t +' + +test_expect_success 'git branch --list -d t should fail' ' + git branch t && + test_path_is_file .git/refs/heads/t && + test_must_fail git branch --list -d t && + git branch -d t && + test_path_is_missing .git/refs/heads/t +' + mv .git/config .git/config-saved test_expect_success 'git branch -m q q2 without config should succeed' ' @@ -124,12 +177,12 @@ git config branch.s/s.dummy Hello test_expect_success \ 'git branch -m s/s s should work when s/t is deleted' \ 'git branch -l s/s && - test -f .git/logs/refs/heads/s/s && + test_path_is_file .git/logs/refs/heads/s/s && git branch -l s/t && - test -f .git/logs/refs/heads/s/t && + test_path_is_file .git/logs/refs/heads/s/t && git branch -d s/t && git branch -m s/s s && - test -f .git/logs/refs/heads/s' + test_path_is_file .git/logs/refs/heads/s' test_expect_success 'config information was renamed, too' \ "test $(git config branch.s.dummy) = Hello && @@ -140,8 +193,8 @@ test_expect_success 'renaming a symref is not allowed' \ git symbolic-ref refs/heads/master2 refs/heads/master && test_must_fail git branch -m master2 master3 && git symbolic-ref refs/heads/master2 && - test -f .git/refs/heads/master && - ! test -f .git/refs/heads/master3 + test_path_is_file .git/refs/heads/master && + test_path_is_missing .git/refs/heads/master3 ' test_expect_success SYMLINKS \ @@ -250,8 +303,8 @@ test_expect_success \ 'git checkout -b g/h/i -l should create a branch and a log' \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ git checkout -b g/h/i -l master && - test -f .git/refs/heads/g/h/i && - test -f .git/logs/refs/heads/g/h/i && + test_path_is_file .git/refs/heads/g/h/i && + test_path_is_file .git/logs/refs/heads/g/h/i && test_cmp expect .git/logs/refs/heads/g/h/i' test_expect_success 'checkout -b makes reflog by default' ' diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 6b7c118e4f..76fe7e0060 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -32,6 +32,20 @@ test_expect_success 'git branch shows local branches' ' test_cmp expect actual ' +test_expect_success 'git branch --list shows local branches' ' + git branch --list >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' + branch-one + branch-two +EOF +test_expect_success 'git branch --list pattern shows matching local branches' ' + git branch --list branch* >actual && + test_cmp expect actual +' + cat >expect <<'EOF' origin/HEAD -> origin/branch-one origin/branch-one @@ -67,6 +81,20 @@ test_expect_success 'git branch -v shows branch summaries' ' ' cat >expect <<'EOF' +two +one +EOF +test_expect_success 'git branch --list -v pattern shows branch summaries' ' + git branch --list -v branch* >tmp && + awk "{print \$NF}" <tmp >actual && + test_cmp expect actual +' + +test_expect_success 'git branch -v pattern does not show branch summaries' ' + test_must_fail git branch -v branch* +' + +cat >expect <<'EOF' * (no branch) branch-one branch-two diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index 4ec4d11450..4367197953 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -389,7 +389,7 @@ test_expect_success 'abort notes merge' ' test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && test_cmp /dev/null output && # m has not moved (still == y) - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && @@ -525,9 +525,9 @@ EOF test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 && test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 && # Refs are unchanged - test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" - test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" - test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" + test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" && + test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" && + test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" && # Mention refs/notes/m, and its current and expected value in output grep -q "refs/notes/m" output && grep -q "$(git rev-parse refs/notes/m)" output && @@ -545,7 +545,7 @@ test_expect_success 'resolve situation by aborting the notes merge' ' test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && test_cmp /dev/null output && # m has not moved (still == w) - test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" + test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 6eaecec906..c355533236 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -172,8 +172,8 @@ test_expect_success 'fail when upstream arg is missing and not configured' ' test_expect_success 'default to @{upstream} when upstream arg is missing' ' git checkout -b default topic && - git config branch.default.remote . - git config branch.default.merge refs/heads/master + git config branch.default.remote . && + git config branch.default.merge refs/heads/master && git rebase && test "$(git rev-parse default~1)" = "$(git rev-parse master)" ' diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh index aea6685984..7ba17974c5 100755 --- a/t/t3401-rebase-partial.sh +++ b/t/t3401-rebase-partial.sh @@ -11,51 +11,35 @@ local branch. ' . ./test-lib.sh -test_expect_success \ - 'prepare repository with topic branch' \ - 'echo First > A && - git update-index --add A && - git commit -m "Add A." && - - git checkout -b my-topic-branch && - - echo Second > B && - git update-index --add B && - git commit -m "Add B." && - - echo AnotherSecond > C && - git update-index --add C && - git commit -m "Add C." && - - git checkout -f master && - - echo Third >> A && - git update-index A && - git commit -m "Modify A." +test_expect_success 'prepare repository with topic branch' ' + test_commit A && + git checkout -b my-topic-branch && + test_commit B && + test_commit C && + git checkout -f master && + test_commit A2 A.t ' -test_expect_success \ - 'pick top patch from topic branch into master' \ - 'git cherry-pick my-topic-branch^0 && - git checkout -f my-topic-branch && - git branch master-merge master && - git branch my-topic-branch-merge my-topic-branch +test_expect_success 'pick top patch from topic branch into master' ' + git cherry-pick C && + git checkout -f my-topic-branch ' -test_debug \ - 'git cherry master && - git format-patch -k --stdout --full-index master >/dev/null && - gitk --all & sleep 1 +test_debug ' + git cherry master && + git format-patch -k --stdout --full-index master >/dev/null && + gitk --all & sleep 1 ' -test_expect_success \ - 'rebase topic branch against new master and check git am did not get halted' \ - 'git rebase master && test ! -d .git/rebase-apply' +test_expect_success 'rebase topic branch against new master and check git am did not get halted' ' + git rebase master && + test_path_is_missing .git/rebase-apply +' -test_expect_success \ - 'rebase --merge topic branch that was partially merged upstream' \ - 'git checkout -f my-topic-branch-merge && - git rebase --merge master-merge && - test ! -d .git/rebase-merge' +test_expect_success 'rebase --merge topic branch that was partially merged upstream' ' + git reset --hard C && + git rebase --merge master && + test_path_is_missing .git/rebase-merge +' test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 8538813d1d..b981572d73 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -527,6 +527,20 @@ test_expect_success 'auto-amend only edited commits after "edit"' ' git rebase --abort ' +test_expect_success 'clean error after failed "exec"' ' + test_tick && + test_when_finished "git rebase --abort || :" && + ( + FAKE_LINES="1 exec_false" && + export FAKE_LINES && + test_must_fail git rebase -i HEAD^ + ) && + echo "edited again" > file7 && + git add file7 && + test_must_fail git rebase --continue 2>error && + grep "You have staged changes in your working tree." error +' + test_expect_success 'rebase a detached HEAD' ' grandparent=$(git rev-parse HEAD~2) && git checkout $(git rev-parse HEAD) && diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 1e855cdae5..2680375628 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -51,7 +51,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' test_commit "commit-new-file-F3-on-topic-branch" F3 32 && test_when_finished "rm -fr test-bin funny.was.run" && mkdir test-bin && - cat >test-bin/git-merge-funny <<-EOF + cat >test-bin/git-merge-funny <<-EOF && #!$SHELL_PATH case "\$1" in --opt) ;; *) exit 2 ;; esac shift && @@ -77,7 +77,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' test_expect_success 'rebase --continue remembers --rerere-autoupdate' ' rm -fr .git/rebase-* && git reset --hard commit-new-file-F3-on-topic-branch && - git checkout master + git checkout master && test_commit "commit-new-file-F3" F3 3 && git config rerere.enabled true && test_must_fail git rebase -m master topic && diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh index bd8efaf005..e70ac10a0c 100755 --- a/t/t3419-rebase-patch-id.sh +++ b/t/t3419-rebase-patch-id.sh @@ -39,7 +39,7 @@ run() } test_expect_success 'setup' ' - git commit --allow-empty -m initial + git commit --allow-empty -m initial && git tag root ' diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index 212ec54aaf..ee1659c178 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -77,6 +77,21 @@ test_expect_success 'cherry-pick --no-commit does not set CHERRY_PICK_HEAD' ' test_must_fail git rev-parse --verify CHERRY_PICK_HEAD ' +test_expect_success 'cherry-pick w/dirty tree does not set CHERRY_PICK_HEAD' ' + pristine_detach initial && + echo foo > foo && + test_must_fail git cherry-pick base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD +' + +test_expect_success \ + 'cherry-pick --strategy=resolve w/dirty tree does not set CHERRY_PICK_HEAD' ' + pristine_detach initial && + echo foo > foo && + test_must_fail git cherry-pick --strategy=resolve base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD +' + test_expect_success 'GIT_CHERRY_PICK_HELP suppresses CHERRY_PICK_HEAD' ' pristine_detach initial && ( @@ -238,6 +253,60 @@ test_expect_success 'revert also handles conflicts sanely' ' test_cmp expected actual ' +test_expect_success 'failed revert sets REVERT_HEAD' ' + pristine_detach initial && + test_must_fail git revert picked && + test_cmp_rev picked REVERT_HEAD +' + +test_expect_success 'successful revert does not set REVERT_HEAD' ' + pristine_detach base && + git revert base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_must_fail git rev-parse --verify REVERT_HEAD +' + +test_expect_success 'revert --no-commit sets REVERT_HEAD' ' + pristine_detach base && + git revert --no-commit base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_cmp_rev base REVERT_HEAD +' + +test_expect_success 'revert w/dirty tree does not set REVERT_HEAD' ' + pristine_detach base && + echo foo > foo && + test_must_fail git revert base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_must_fail git rev-parse --verify REVERT_HEAD +' + +test_expect_success 'GIT_CHERRY_PICK_HELP does not suppress REVERT_HEAD' ' + pristine_detach initial && + ( + GIT_CHERRY_PICK_HELP="and then do something else" && + GIT_REVERT_HELP="and then do something else, again" && + export GIT_CHERRY_PICK_HELP GIT_REVERT_HELP && + test_must_fail git revert picked + ) && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_cmp_rev picked REVERT_HEAD +' + +test_expect_success 'git reset clears REVERT_HEAD' ' + pristine_detach initial && + test_must_fail git revert picked && + git reset && + test_must_fail git rev-parse --verify REVERT_HEAD +' + +test_expect_success 'failed commit does not clear REVERT_HEAD' ' + pristine_detach initial && + test_must_fail git revert picked && + test_must_fail git commit && + test_cmp_rev picked REVERT_HEAD +' + test_expect_success 'revert conflict, diff3 -m style' ' pristine_detach initial && git config merge.conflictstyle diff3 && diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh new file mode 100755 index 0000000000..e80050e1fe --- /dev/null +++ b/t/t3510-cherry-pick-sequence.sh @@ -0,0 +1,481 @@ +#!/bin/sh + +test_description='Test cherry-pick continuation features + + + conflicting: rewrites unrelated to conflicting + + yetanotherpick: rewrites foo to e + + anotherpick: rewrites foo to d + + picked: rewrites foo to c + + unrelatedpick: rewrites unrelated to reallyunrelated + + base: rewrites foo to b + + initial: writes foo as a, unrelated as unrelated + +' + +. ./test-lib.sh + +pristine_detach () { + git cherry-pick --quit && + git checkout -f "$1^0" && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x +} + +test_cmp_rev () { + git rev-parse --verify "$1" >expect.rev && + git rev-parse --verify "$2" >actual.rev && + test_cmp expect.rev actual.rev +} + +test_expect_success setup ' + git config advice.detachedhead false + echo unrelated >unrelated && + git add unrelated && + test_commit initial foo a && + test_commit base foo b && + test_commit unrelatedpick unrelated reallyunrelated && + test_commit picked foo c && + test_commit anotherpick foo d && + test_commit yetanotherpick foo e && + pristine_detach initial && + test_commit conflicting unrelated +' + +test_expect_success 'cherry-pick persists data on failure' ' + pristine_detach initial && + test_must_fail git cherry-pick -s base..anotherpick && + test_path_is_dir .git/sequencer && + test_path_is_file .git/sequencer/head && + test_path_is_file .git/sequencer/todo && + test_path_is_file .git/sequencer/opts +' + +test_expect_success 'cherry-pick mid-cherry-pick-sequence' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + test_cmp_rev picked CHERRY_PICK_HEAD && + # "oops, I forgot that these patches rely on the change from base" + git checkout HEAD foo && + git cherry-pick base && + git cherry-pick picked && + git cherry-pick --continue && + git diff --exit-code anotherpick +' + +test_expect_success 'cherry-pick persists opts correctly' ' + pristine_detach initial && + test_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick && + test_path_is_dir .git/sequencer && + test_path_is_file .git/sequencer/head && + test_path_is_file .git/sequencer/todo && + test_path_is_file .git/sequencer/opts && + echo "true" >expect && + git config --file=.git/sequencer/opts --get-all options.signoff >actual && + test_cmp expect actual && + echo "1" >expect && + git config --file=.git/sequencer/opts --get-all options.mainline >actual && + test_cmp expect actual && + echo "recursive" >expect && + git config --file=.git/sequencer/opts --get-all options.strategy >actual && + test_cmp expect actual && + cat >expect <<-\EOF && + patience + ours + EOF + git config --file=.git/sequencer/opts --get-all options.strategy-option >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick cleans up sequencer state upon success' ' + pristine_detach initial && + git cherry-pick initial..picked && + test_path_is_missing .git/sequencer +' + +test_expect_success '--quit does not complain when no cherry-pick is in progress' ' + pristine_detach initial && + git cherry-pick --quit +' + +test_expect_success '--abort requires cherry-pick in progress' ' + pristine_detach initial && + test_must_fail git cherry-pick --abort +' + +test_expect_success '--quit cleans up sequencer state' ' + pristine_detach initial && + test_must_fail git cherry-pick base..picked && + git cherry-pick --quit && + test_path_is_missing .git/sequencer +' + +test_expect_success '--quit keeps HEAD and conflicted index intact' ' + pristine_detach initial && + cat >expect <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M unrelated + OBJID + :000000 100644 OBJID OBJID A foo + :000000 100644 OBJID OBJID A unrelated + EOF + test_must_fail git cherry-pick base..picked && + git cherry-pick --quit && + test_path_is_missing .git/sequencer && + test_must_fail git update-index --refresh && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + test_cmp expect actual +' + +test_expect_success '--abort to cancel multiple cherry-pick' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + git cherry-pick --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev initial HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success '--abort to cancel single cherry-pick' ' + pristine_detach initial && + test_must_fail git cherry-pick picked && + git cherry-pick --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev initial HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success 'cherry-pick --abort to cancel multiple revert' ' + pristine_detach anotherpick && + test_must_fail git revert base..picked && + git cherry-pick --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev anotherpick HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success 'revert --abort works, too' ' + pristine_detach anotherpick && + test_must_fail git revert base..picked && + git revert --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev anotherpick HEAD +' + +test_expect_success '--abort to cancel single revert' ' + pristine_detach anotherpick && + test_must_fail git revert picked && + git revert --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev anotherpick HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success '--abort keeps unrelated change, easy case' ' + pristine_detach unrelatedpick && + echo changed >expect && + test_must_fail git cherry-pick picked..yetanotherpick && + echo changed >unrelated && + git cherry-pick --abort && + test_cmp expect unrelated +' + +test_expect_success '--abort refuses to clobber unrelated change, harder case' ' + pristine_detach initial && + echo changed >expect && + test_must_fail git cherry-pick base..anotherpick && + echo changed >unrelated && + test_must_fail git cherry-pick --abort && + test_cmp expect unrelated && + git rev-list HEAD >log && + test_line_count = 2 log && + test_must_fail git update-index --refresh && + + git checkout unrelated && + git cherry-pick --abort && + test_cmp_rev initial HEAD +' + +test_expect_success 'cherry-pick still writes sequencer state when one commit is left' ' + pristine_detach initial && + test_must_fail git cherry-pick base..picked && + test_path_is_dir .git/sequencer && + echo "resolved" >foo && + git add foo && + git commit && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + cat >expect <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M unrelated + OBJID + :000000 100644 OBJID OBJID A foo + :000000 100644 OBJID OBJID A unrelated + EOF + test_cmp expect actual +' + +test_expect_success '--abort after last commit in sequence' ' + pristine_detach initial && + test_must_fail git cherry-pick base..picked && + git cherry-pick --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev initial HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success 'cherry-pick does not implicitly stomp an existing operation' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + test-chmtime -v +0 .git/sequencer >expect && + test_must_fail git cherry-pick unrelatedpick && + test-chmtime -v +0 .git/sequencer >actual && + test_cmp expect actual +' + +test_expect_success '--continue complains when no cherry-pick is in progress' ' + pristine_detach initial && + test_must_fail git cherry-pick --continue +' + +test_expect_success '--continue complains when there are unresolved conflicts' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + test_must_fail git cherry-pick --continue +' + +test_expect_success '--continue of single cherry-pick' ' + pristine_detach initial && + echo c >expect && + test_must_fail git cherry-pick picked && + echo c >foo && + git add foo && + git cherry-pick --continue && + + test_cmp expect foo && + test_cmp_rev initial HEAD^ && + git diff --exit-code HEAD && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD +' + +test_expect_success '--continue of single revert' ' + pristine_detach initial && + echo resolved >expect && + echo "Revert \"picked\"" >expect.msg && + test_must_fail git revert picked && + echo resolved >foo && + git add foo && + git cherry-pick --continue && + + git diff --exit-code HEAD && + test_cmp expect foo && + test_cmp_rev initial HEAD^ && + git diff-tree -s --pretty=tformat:%s HEAD >msg && + test_cmp expect.msg msg && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_must_fail git rev-parse --verify REVERT_HEAD +' + +test_expect_success '--continue after resolving conflicts' ' + pristine_detach initial && + echo d >expect && + cat >expect.log <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M unrelated + OBJID + :000000 100644 OBJID OBJID A foo + :000000 100644 OBJID OBJID A unrelated + EOF + test_must_fail git cherry-pick base..anotherpick && + echo c >foo && + git add foo && + git cherry-pick --continue && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual.log && + test_cmp expect foo && + test_cmp expect.log actual.log +' + +test_expect_success '--continue after resolving conflicts and committing' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + git cherry-pick --continue && + test_path_is_missing .git/sequencer && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + cat >expect <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M unrelated + OBJID + :000000 100644 OBJID OBJID A foo + :000000 100644 OBJID OBJID A unrelated + EOF + test_cmp expect actual +' + +test_expect_success '--continue asks for help after resolving patch to nil' ' + pristine_detach conflicting && + test_must_fail git cherry-pick initial..picked && + + test_cmp_rev unrelatedpick CHERRY_PICK_HEAD && + git checkout HEAD -- unrelated && + test_must_fail git cherry-pick --continue 2>msg && + test_i18ngrep "The previous cherry-pick is now empty" msg +' + +test_expect_success 'follow advice and skip nil patch' ' + pristine_detach conflicting && + test_must_fail git cherry-pick initial..picked && + + git checkout HEAD -- unrelated && + test_must_fail git cherry-pick --continue && + git reset && + git cherry-pick --continue && + + git rev-list initial..HEAD >commits && + test_line_count = 3 commits +' + +test_expect_success '--continue respects opts' ' + pristine_detach initial && + test_must_fail git cherry-pick -x base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + git cherry-pick --continue && + test_path_is_missing .git/sequencer && + git cat-file commit HEAD >anotherpick_msg && + git cat-file commit HEAD~1 >picked_msg && + git cat-file commit HEAD~2 >unrelatedpick_msg && + git cat-file commit HEAD~3 >initial_msg && + test_must_fail grep "cherry picked from" initial_msg && + grep "cherry picked from" unrelatedpick_msg && + grep "cherry picked from" picked_msg && + grep "cherry picked from" anotherpick_msg +' + +test_expect_success '--continue of single-pick respects -x' ' + pristine_detach initial && + test_must_fail git cherry-pick -x picked && + echo c >foo && + git add foo && + git cherry-pick --continue && + test_path_is_missing .git/sequencer && + git cat-file commit HEAD >msg && + grep "cherry picked from" msg +' + +test_expect_success '--continue respects -x in first commit in multi-pick' ' + pristine_detach initial && + test_must_fail git cherry-pick -x picked anotherpick && + echo c >foo && + git add foo && + git cherry-pick --continue && + test_path_is_missing .git/sequencer && + git cat-file commit HEAD^ >msg && + picked=$(git rev-parse --verify picked) && + grep "cherry picked from.*$picked" msg +' + +test_expect_success '--signoff is not automatically propagated to resolved conflict' ' + pristine_detach initial && + test_must_fail git cherry-pick --signoff base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + git cherry-pick --continue && + test_path_is_missing .git/sequencer && + git cat-file commit HEAD >anotherpick_msg && + git cat-file commit HEAD~1 >picked_msg && + git cat-file commit HEAD~2 >unrelatedpick_msg && + git cat-file commit HEAD~3 >initial_msg && + test_must_fail grep "Signed-off-by:" initial_msg && + grep "Signed-off-by:" unrelatedpick_msg && + test_must_fail grep "Signed-off-by:" picked_msg && + grep "Signed-off-by:" anotherpick_msg +' + +test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' ' + pristine_detach initial && + test_must_fail git cherry-pick -s picked anotherpick && + echo c >foo && + git add foo && + git cherry-pick --continue && + + git diff --exit-code HEAD && + test_cmp_rev initial HEAD^^ && + git cat-file commit HEAD^ >msg && + ! grep Signed-off-by: msg +' + +test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' ' + pristine_detach initial && + test_must_fail git cherry-pick -s picked && + echo c >foo && + git add foo && + git cherry-pick --continue && + + git diff --exit-code HEAD && + test_cmp_rev initial HEAD^ && + git cat-file commit HEAD >msg && + ! grep Signed-off-by: msg +' + +test_expect_success 'malformed instruction sheet 1' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + echo "resolved" >foo && + git add foo && + git commit && + sed "s/pick /pick/" .git/sequencer/todo >new_sheet && + cp new_sheet .git/sequencer/todo && + test_must_fail git cherry-pick --continue +' + +test_expect_success 'malformed instruction sheet 2' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + echo "resolved" >foo && + git add foo && + git commit && + sed "s/pick/revert/" .git/sequencer/todo >new_sheet && + cp new_sheet .git/sequencer/todo && + test_must_fail git cherry-pick --continue +' + +test_expect_success 'empty commit set' ' + pristine_detach initial && + test_expect_code 128 git cherry-pick base..base +' + +test_done diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index 4f2eedfd4f..ef44fb2260 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -17,19 +17,21 @@ test_expect_success 'stash save --include-untracked some dirty working directory echo 3 > file && test_tick && echo 1 > file2 && + mkdir untracked && + echo untracked >untracked/untracked && git stash --include-untracked && git diff-files --quiet && git diff-index --cached --quiet HEAD ' cat > expect <<EOF +?? actual ?? expect -?? output EOF test_expect_success 'stash save --include-untracked cleaned the untracked files' ' - git status --porcelain > output - test_cmp output expect + git status --porcelain >actual && + test_cmp expect actual ' cat > expect.diff <<EOF @@ -40,17 +42,26 @@ index 0000000..d00491f +++ b/file2 @@ -0,0 +1 @@ +1 +diff --git a/untracked/untracked b/untracked/untracked +new file mode 100644 +index 0000000..5a72eb2 +--- /dev/null ++++ b/untracked/untracked +@@ -0,0 +1 @@ ++untracked EOF cat > expect.lstree <<EOF file2 +untracked EOF test_expect_success 'stash save --include-untracked stashed the untracked files' ' test "!" -f file2 && - git diff HEAD..stash^3 -- file2 > output && - test_cmp output expect.diff && - git ls-tree --name-only stash^3: > output && - test_cmp output expect.lstree + test ! -e untracked && + git diff HEAD stash^3 -- file2 untracked >actual && + test_cmp expect.diff actual && + git ls-tree --name-only stash^3: >actual && + test_cmp expect.lstree actual ' test_expect_success 'stash save --patch --include-untracked fails' ' test_must_fail git stash --patch --include-untracked @@ -64,18 +75,21 @@ git clean --force --quiet cat > expect <<EOF M file +?? actual ?? expect ?? file2 -?? output +?? untracked/ EOF test_expect_success 'stash pop after save --include-untracked leaves files untracked again' ' git stash pop && - git status --porcelain > output - test_cmp output expect + git status --porcelain >actual && + test_cmp expect actual && + test "1" = "`cat file2`" && + test untracked = "`cat untracked/untracked`" ' -git clean --force --quiet +git clean --force --quiet -d test_expect_success 'stash save -u dirty index' ' echo 4 > file3 && @@ -96,8 +110,8 @@ EOF test_expect_success 'stash save --include-untracked dirty index got stashed' ' git stash pop --index && - git diff --cached > output && - test_cmp output expect + git diff --cached >actual && + test_cmp expect actual ' git reset > /dev/null @@ -125,30 +139,36 @@ test_expect_success 'stash save --include-untracked removed files got stashed' ' cat > .gitignore <<EOF .gitignore ignored +ignored.d/ EOF test_expect_success 'stash save --include-untracked respects .gitignore' ' echo ignored > ignored && + mkdir ignored.d && + echo ignored >ignored.d/untracked && git stash -u && test -s ignored && + test -s ignored.d/untracked && test -s .gitignore ' test_expect_success 'stash save -u can stash with only untracked files different' ' echo 4 > file4 && - git stash -u + git stash -u && test "!" -f file4 ' test_expect_success 'stash save --all does not respect .gitignore' ' git stash -a && test "!" -f ignored && + test "!" -e ignored.d && test "!" -f .gitignore ' test_expect_success 'stash save --all is stash poppable' ' git stash pop && test -s ignored && + test -s ignored.d/untracked && test -s .gitignore ' diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index b68c56b68c..4bd2a1c838 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -105,7 +105,7 @@ test_expect_funcname () { grep "^@@.*@@ $1" diff } -for p in bibtex cpp csharp fortran html java objc pascal perl php python ruby tex +for p in bibtex cpp csharp fortran html java matlab objc pascal perl php python ruby tex do test_expect_success "builtin $p pattern compiles" ' echo "*.java diff=$p" >.gitattributes && diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index c374aa4c1c..6f1e5a2a15 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -299,6 +299,7 @@ test_language_driver csharp test_language_driver fortran test_language_driver html test_language_driver java +test_language_driver matlab test_language_driver objc test_language_driver pascal test_language_driver perl diff --git a/t/t4034/matlab/expect b/t/t4034/matlab/expect new file mode 100644 index 0000000000..72cf3e93a2 --- /dev/null +++ b/t/t4034/matlab/expect @@ -0,0 +1,14 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index dc204db..70e05f0 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,9 +1,9 @@<RESET> +(<RED>1<RESET><GREEN>0<RESET>) (<RED>-1e10<RESET><GREEN>-0e10<RESET>) '<RED>b<RESET><GREEN>y<RESET>'; +[<RED>a<RESET><GREEN>x<RESET>] {<RED>a<RESET><GREEN>x<RESET>} <RED>a<RESET><GREEN>x<RESET>.<RED>b<RESET><GREEN>y<RESET>; +~<RED>a<RESET><GREEN>x<RESET>; +<RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>.*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>./<RED>b a<RESET><GREEN>y x<RESET>^<RED>b a<RESET><GREEN>y x<RESET>.^<RED>b a<RESET><GREEN>y x<RESET>.\<RED>b a<RESET><GREEN>y x<RESET>.'; +<RED>a<RESET><GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET><GREEN>y<RESET>; +<RED>a<RESET><GREEN>x<RESET>&<RED>b a<RESET><GREEN>y x<RESET>&&<RED>b a<RESET><GREEN>y x<RESET>|<RED>b a<RESET><GREEN>y x<RESET>||<RED>b<RESET><GREEN>y<RESET>; +<RED>a<RESET><GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET><GREEN>y<RESET>; +<RED>a<RESET><GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>~=<RED>b<RESET><GREEN>y<RESET>; +<RED>a<RESET><GREEN>x<RESET>,<RED>b<RESET><GREEN>y<RESET>; diff --git a/t/t4034/matlab/post b/t/t4034/matlab/post new file mode 100644 index 0000000000..70e05f0753 --- /dev/null +++ b/t/t4034/matlab/post @@ -0,0 +1,9 @@ +(0) (-0e10) 'y'; +[x] {x} x.y; +~x; +x*y x.*y x/y x./y x^y x.^y x.\y x.'; +x+y x-y; +x&y x&&y x|y x||y; +x<y x<=y x>y x>=y; +x==y x~=y; +x,y; diff --git a/t/t4034/matlab/pre b/t/t4034/matlab/pre new file mode 100644 index 0000000000..dc204db486 --- /dev/null +++ b/t/t4034/matlab/pre @@ -0,0 +1,9 @@ +(1) (-1e10) 'b'; +[a] {a} a.b; +~a; +a*b a.*b a/b a./b a^b a.^b a.\b a.'; +a+b a-b; +a&b a&&b a|b a||b; +a<b a<=b a>b a>=b; +a==b a~=b; +a,b; diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh new file mode 100755 index 0000000000..001d678e09 --- /dev/null +++ b/t/t4051-diff-function-context.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +test_description='diff function context' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh + + +cat <<\EOF >hello.c +#include <stdio.h> + +static int a(void) +{ + /* + * Dummy. + */ +} + +static int hello_world(void) +{ + /* Classic. */ + printf("Hello world.\n"); + + /* Success! */ + return 0; +} +static int b(void) +{ + /* + * Dummy, too. + */ +} + +int main(int argc, char **argv) +{ + a(); + b(); + return hello_world(); +} +EOF + +test_expect_success 'setup' ' + git add hello.c && + test_tick && + git commit -m initial && + + grep -v Classic <hello.c >hello.c.new && + mv hello.c.new hello.c +' + +cat <<\EOF >expected +diff --git a/hello.c b/hello.c +--- a/hello.c ++++ b/hello.c +@@ -10,8 +10,7 @@ static int a(void) + static int hello_world(void) + { +- /* Classic. */ + printf("Hello world.\n"); + + /* Success! */ + return 0; + } +EOF + +test_expect_success 'diff -U0 -W' ' + git diff -U0 -W >actual && + compare_diff_patch actual expected +' + +cat <<\EOF >expected +diff --git a/hello.c b/hello.c +--- a/hello.c ++++ b/hello.c +@@ -9,9 +9,8 @@ static int a(void) + + static int hello_world(void) + { +- /* Classic. */ + printf("Hello world.\n"); + + /* Success! */ + return 0; + } +EOF + +test_expect_success 'diff -W' ' + git diff -W >actual && + compare_diff_patch actual expected +' + +test_done diff --git a/t/t4131-apply-fake-ancestor.sh b/t/t4131-apply-fake-ancestor.sh index 94373ca9a0..b1361ce546 100755 --- a/t/t4131-apply-fake-ancestor.sh +++ b/t/t4131-apply-fake-ancestor.sh @@ -11,7 +11,7 @@ test_expect_success 'setup' ' test_commit 1 && test_commit 2 && mkdir sub && - test_commit 3 sub/3 && + test_commit 3 sub/3.t && test_commit 4 ' diff --git a/t/t4136-apply-check.sh b/t/t4136-apply-check.sh new file mode 100755 index 0000000000..a321f7c245 --- /dev/null +++ b/t/t4136-apply-check.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='git apply should exit non-zero with unrecognized input.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit 1 +' + +test_expect_success 'apply --check exits non-zero with unrecognized input' ' + test_must_fail git apply --check - <<-\EOF + I am not a patch + I look nothing like a patch + git apply must fail + EOF +' + +test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 983e34bec6..222f7559e9 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -346,11 +346,11 @@ test_expect_success 'set up more tangled history' ' ' cat > expect <<\EOF -* Merge commit 'reach' +* Merge tag 'reach' |\ | \ | \ -*-. \ Merge commit 'octopus-a'; commit 'octopus-b' +*-. \ Merge tags 'octopus-a' and 'octopus-b' |\ \ \ * | | | seventh | | * | octopus-b diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh new file mode 100755 index 0000000000..b7da95fac5 --- /dev/null +++ b/t/t4254-am-corrupt.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='git am with corrupt input' +. ./test-lib.sh + +# Note the missing "+++" line: +cat > bad-patch.diff <<'EOF' +From: A U Thor <au.thor@example.com> +diff --git a/f b/f +index 7898192..6178079 100644 +--- a/f +@@ -1 +1 @@ +-a ++b +EOF + +test_expect_success setup ' + test $? = 0 && + echo a > f && + git add f && + test_tick && + git commit -m initial +' + +# This used to fail before, too, but with a different diagnostic. +# fatal: unable to write file '(null)' mode 100644: Bad address +# Also, it had the unwanted side-effect of deleting f. +test_expect_success 'try to apply corrupted patch' ' + git am bad-patch.diff 2> actual + test $? = 1 +' + +cat > expected <<EOF +fatal: git diff header lacks filename information (line 4) +EOF + +test_expect_success 'compare diagnostic; ensure file is still here' ' + test $? = 0 && + test -f f && + test_cmp expected actual +' + +test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index d9068981f8..527c9e7548 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -96,7 +96,7 @@ test_expect_success 'git archive with --output' \ 'git archive --output=b4.tar HEAD && test_cmp b.tar b4.tar' -test_expect_success NOT_MINGW 'git archive --remote' \ +test_expect_success 'git archive --remote' \ 'git archive --remote=. HEAD >b5.tar && test_cmp b.tar b5.tar' @@ -242,6 +242,14 @@ test_expect_success \ 'git archive --list outside of a git repo' \ 'GIT_DIR=some/non-existing/directory git archive --list' +test_expect_success 'clients cannot access unreachable commits' ' + test_commit unreachable && + sha1=`git rev-parse HEAD` && + git reset --hard HEAD^ && + git archive $sha1 >remote.tar && + test_must_fail git archive --remote=. $sha1 >remote.tar +' + test_expect_success 'git-archive --prefix=olde-' ' git archive --prefix=olde- >h.tar HEAD && ( @@ -266,7 +274,7 @@ test_expect_success 'archive --list mentions user filter' ' grep "^bar\$" output ' -test_expect_success NOT_MINGW 'archive --list shows only enabled remote filters' ' +test_expect_success 'archive --list shows only enabled remote filters' ' git archive --list --remote=. >output && ! grep "^tar\.foo\$" output && grep "^bar\$" output @@ -298,7 +306,7 @@ test_expect_success 'extension matching requires dot' ' test_cmp b.tar config-implicittar.foo ' -test_expect_success NOT_MINGW 'only enabled filters are available remotely' ' +test_expect_success 'only enabled filters are available remotely' ' test_must_fail git archive --remote=. --format=tar.foo HEAD \ >remote.tar.foo && git archive --remote=. --format=bar >remote.bar HEAD && @@ -341,12 +349,12 @@ test_expect_success GZIP,GUNZIP 'extract tgz file' ' test_cmp b.tar j.tar ' -test_expect_success GZIP,NOT_MINGW 'remote tar.gz is allowed by default' ' +test_expect_success GZIP 'remote tar.gz is allowed by default' ' git archive --remote=. --format=tar.gz HEAD >remote.tar.gz && test_cmp j.tgz remote.tar.gz ' -test_expect_success GZIP,NOT_MINGW 'remote tar.gz can be disabled' ' +test_expect_success GZIP 'remote tar.gz can be disabled' ' git config tar.tar.gz.remote false && test_must_fail git archive --remote=. --format=tar.gz HEAD \ >remote.tar.gz diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh index 02d4d2284d..f47d8717fd 100755 --- a/t/t5001-archive-attr.sh +++ b/t/t5001-archive-attr.sh @@ -57,6 +57,15 @@ test_expect_missing worktree/ignored test_expect_exists worktree/ignored-by-tree test_expect_missing worktree/ignored-by-worktree +test_expect_success 'git archive --worktree-attributes option' ' + git archive --worktree-attributes --worktree-attributes HEAD >worktree.tar && + (mkdir worktree2 && cd worktree2 && "$TAR" xf -) <worktree.tar +' + +test_expect_missing worktree2/ignored +test_expect_exists worktree2/ignored-by-tree +test_expect_missing worktree2/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 diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh index 9cc0a42ea9..da25bc2d1f 100755 --- a/t/t5150-request-pull.sh +++ b/t/t5150-request-pull.sh @@ -67,9 +67,11 @@ test_expect_success 'setup: two scripts for reading pull requests' ' cat <<-\EOT >read-request.sed && #!/bin/sed -nf + # Note that a request could ask for "tag $tagname" / in the git repository at:$/!d n /^$/ n + s/ tag \([^ ]*\)$/ tag--\1/ s/^[ ]*\(.*\) \([^ ]*\)/please pull\ \1\ \2/p @@ -86,6 +88,7 @@ test_expect_success 'setup: two scripts for reading pull requests' ' s/$downstream_url_for_sed/URL/g s/for-upstream/BRANCH/g s/mnemonic.txt/FILENAME/g + s/^version [0-9]/VERSION/ /^ FILENAME | *[0-9]* [-+]*\$/ b diffstat /^AUTHOR ([0-9]*):\$/ b shortlog p @@ -177,6 +180,7 @@ test_expect_success 'request names an appropriate branch' ' read branch } <digest && { + test "$branch" = full || test "$branch" = master || test "$branch" = for-upstream } @@ -193,8 +197,17 @@ test_expect_success 'pull request format' ' SUBJECT (DATE) are available in the git repository at: + URL BRANCH + for you to fetch changes up to OBJECT_NAME: + + SUBJECT (DATE) + + ---------------------------------------------------------------- + VERSION + + ---------------------------------------------------------------- SHORTLOG DIFFSTAT diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index d05a9138b4..1753ef2b91 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -31,44 +31,44 @@ EOF done test_expect_success 'post-checkout runs as expected ' ' - GIT_DIR=clone1/.git git checkout master && - test -e clone1/.git/post-checkout.args + GIT_DIR=clone1/.git git checkout master && + test -e clone1/.git/post-checkout.args ' test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' ' - old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && - new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && - flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && - test $old = $new -a $flag = 1 + old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && + new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && + flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && + test $old = $new -a $flag = 1 ' test_expect_success 'post-checkout runs as expected ' ' - GIT_DIR=clone1/.git git checkout master && - test -e clone1/.git/post-checkout.args + GIT_DIR=clone1/.git git checkout master && + test -e clone1/.git/post-checkout.args ' test_expect_success 'post-checkout args are correct with git checkout -b ' ' - GIT_DIR=clone1/.git git checkout -b new1 && - old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && - new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && - flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && - test $old = $new -a $flag = 1 + GIT_DIR=clone1/.git git checkout -b new1 && + old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && + new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && + flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && + test $old = $new -a $flag = 1 ' test_expect_success 'post-checkout receives the right args with HEAD changed ' ' - GIT_DIR=clone2/.git git checkout new2 && - old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && - new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && - flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && - test $old != $new -a $flag = 1 + GIT_DIR=clone2/.git git checkout new2 && + old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && + new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && + flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && + test $old != $new -a $flag = 1 ' test_expect_success 'post-checkout receives the right args when not switching branches ' ' - GIT_DIR=clone2/.git git checkout master b && - old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && - new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && - flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && - test $old = $new -a $flag = 0 + GIT_DIR=clone2/.git git checkout master b && + old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && + new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && + flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && + test $old = $new -a $flag = 0 ' if test "$(git config --bool core.filemode)" = true; then diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index bafcca765e..9bf69e9a0f 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -97,7 +97,7 @@ test_expect_success 'setup' ' git symbolic-ref HEAD refs/heads/B ' -pull_to_client 1st "B A" $((11*3)) +pull_to_client 1st "refs/heads/B refs/heads/A" $((11*3)) test_expect_success 'post 1st pull setup' ' add A11 $A10 && @@ -110,9 +110,9 @@ test_expect_success 'post 1st pull setup' ' done ' -pull_to_client 2nd "B" $((64*3)) +pull_to_client 2nd "refs/heads/B" $((64*3)) -pull_to_client 3rd "A" $((1*3)) +pull_to_client 3rd "refs/heads/A" $((1*3)) test_expect_success 'clone shallow' ' git clone --depth 2 "file://$(pwd)/." shallow diff --git a/t/t5501-fetch-push-alternates.sh b/t/t5501-fetch-push-alternates.sh index b5ced8483a..1bc57ac03f 100755 --- a/t/t5501-fetch-push-alternates.sh +++ b/t/t5501-fetch-push-alternates.sh @@ -28,7 +28,7 @@ test_expect_success setup ' done ) && ( - git clone --reference=original "file:///$(pwd)/original" one && + git clone --reference=original "file://$(pwd)/original" one && cd one && echo Z >count && git add count && diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh new file mode 100755 index 0000000000..8341fc4d15 --- /dev/null +++ b/t/t5504-fetch-receive-strict.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +test_description='fetch/receive strict mode' +. ./test-lib.sh + +test_expect_success setup ' + echo hello >greetings && + git add greetings && + git commit -m greetings && + + S=$(git rev-parse :greetings | sed -e "s|^..|&/|") && + X=$(echo bye | git hash-object -w --stdin | sed -e "s|^..|&/|") && + mv -f .git/objects/$X .git/objects/$S && + + test_must_fail git fsck +' + +test_expect_success 'fetch without strict' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config fetch.fsckobjects false && + git config transfer.fsckobjects false && + test_must_fail git fetch ../.git master + ) +' + +test_expect_success 'fetch with !fetch.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config fetch.fsckobjects false && + git config transfer.fsckobjects true && + test_must_fail git fetch ../.git master + ) +' + +test_expect_success 'fetch with fetch.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config fetch.fsckobjects true && + git config transfer.fsckobjects false && + test_must_fail git fetch ../.git master + ) +' + +test_expect_success 'fetch with transfer.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config transfer.fsckobjects true && + test_must_fail git fetch ../.git master + ) +' + +test_expect_success 'push without strict' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config fetch.fsckobjects false && + git config transfer.fsckobjects false + ) && + git push dst master:refs/heads/test +' + +test_expect_success 'push with !receive.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config receive.fsckobjects false && + git config transfer.fsckobjects true + ) && + git push dst master:refs/heads/test +' + +test_expect_success 'push with receive.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config receive.fsckobjects true && + git config transfer.fsckobjects false + ) && + test_must_fail git push dst master:refs/heads/test +' + +test_expect_success 'push with transfer.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config transfer.fsckobjects true + ) && + test_must_fail git push dst master:refs/heads/test +' + +test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 0d0222ea2a..e8af615e6d 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -631,6 +631,37 @@ test_expect_success 'rename a remote' ' ' +test_expect_success 'rename does not update a non-default fetch refspec' ' + + git clone one four.one && + (cd four.one && + git config remote.origin.fetch +refs/heads/*:refs/heads/origin/* && + git remote rename origin upstream && + test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/heads/origin/*" && + git rev-parse -q origin/master) + +' + +test_expect_success 'rename a remote with name part of fetch spec' ' + + git clone one four.two && + (cd four.two && + git remote rename origin remote && + git remote rename remote upstream && + test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*") + +' + +test_expect_success 'rename a remote with name prefix of other remote' ' + + git clone one four.three && + (cd four.three && + git remote add o git://example.com/repo.git && + git remote rename o upstream && + test "$(git rev-parse origin/master)" = "$(git rev-parse master)") + +' + cat > remotes_origin << EOF URL: $(pwd)/one Push: refs/heads/master:refs/heads/upstream diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 7e433b179f..e88dbd50f3 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -76,6 +76,56 @@ test_expect_success "fetch test for-merge" ' cut -f -2 .git/FETCH_HEAD >actual && test_cmp expected actual' +test_expect_success 'fetch --prune on its own works as expected' ' + cd "$D" && + git clone . prune && + cd prune && + git fetch origin refs/heads/master:refs/remotes/origin/extrabranch && + + git fetch --prune origin && + test_must_fail git rev-parse origin/extrabranch +' + +test_expect_success 'fetch --prune with a branch name keeps branches' ' + cd "$D" && + git clone . prune-branch && + cd prune-branch && + git fetch origin refs/heads/master:refs/remotes/origin/extrabranch && + + git fetch --prune origin master && + git rev-parse origin/extrabranch +' + +test_expect_success 'fetch --prune with a namespace keeps other namespaces' ' + cd "$D" && + git clone . prune-namespace && + cd prune-namespace && + + git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* && + git rev-parse origin/master +' + +test_expect_success 'fetch --prune --tags does not delete the remote-tracking branches' ' + cd "$D" && + git clone . prune-tags && + cd prune-tags && + git fetch origin refs/heads/master:refs/tags/sometag && + + git fetch --prune --tags origin && + git rev-parse origin/master && + test_must_fail git rev-parse somebranch +' + +test_expect_success 'fetch --prune --tags with branch does not delete other remote-tracking branches' ' + cd "$D" && + git clone . prune-tags-branch && + cd prune-tags-branch && + git fetch origin refs/heads/master:refs/remotes/origin/extrabranch && + + git fetch --prune --tags origin master && + git rev-parse origin/extrabranch +' + test_expect_success 'fetch tags when there is no tags' ' cd "$D" && @@ -116,7 +166,7 @@ test_expect_success 'fetch must not resolve short tag name' ' ' -test_expect_success 'fetch must not resolve short remote name' ' +test_expect_success 'fetch can now resolve short remote name' ' cd "$D" && git update-ref refs/remotes/six/HEAD HEAD && @@ -125,8 +175,7 @@ test_expect_success 'fetch must not resolve short remote name' ' cd six && git init && - test_must_fail git fetch .. six:six - + git fetch .. six:six ' test_expect_success 'create bundle 1' ' diff --git a/t/t5515/fetch.br-branches-default b/t/t5515/fetch.br-branches-default index 2e0414f6c3..a1bc3d53a6 100644 --- a/t/t5515/fetch.br-branches-default +++ b/t/t5515/fetch.br-branches-default @@ -1,8 +1,8 @@ # br-branches-default 754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-default-merge b/t/t5515/fetch.br-branches-default-merge index ca2cc1d1b4..e3a41ae811 100644 --- a/t/t5515/fetch.br-branches-default-merge +++ b/t/t5515/fetch.br-branches-default-merge @@ -1,9 +1,9 @@ # br-branches-default-merge 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-default-merge_branches-default b/t/t5515/fetch.br-branches-default-merge_branches-default index 7d947cd80f..1f60561cb1 100644 --- a/t/t5515/fetch.br-branches-default-merge_branches-default +++ b/t/t5515/fetch.br-branches-default-merge_branches-default @@ -1,9 +1,9 @@ # br-branches-default-merge branches-default 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-default-octopus b/t/t5515/fetch.br-branches-default-octopus index ec39c54b7e..f31e1b3080 100644 --- a/t/t5515/fetch.br-branches-default-octopus +++ b/t/t5515/fetch.br-branches-default-octopus @@ -2,9 +2,9 @@ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-default-octopus_branches-default b/t/t5515/fetch.br-branches-default-octopus_branches-default index 6bf42e24b6..7060bd9ae5 100644 --- a/t/t5515/fetch.br-branches-default-octopus_branches-default +++ b/t/t5515/fetch.br-branches-default-octopus_branches-default @@ -2,9 +2,9 @@ 754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-default_branches-default b/t/t5515/fetch.br-branches-default_branches-default index 4a2bf3c95c..8cbd718936 100644 --- a/t/t5515/fetch.br-branches-default_branches-default +++ b/t/t5515/fetch.br-branches-default_branches-default @@ -1,8 +1,8 @@ # br-branches-default branches-default 754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-one b/t/t5515/fetch.br-branches-one index 12ac8d20fb..c98f670526 100644 --- a/t/t5515/fetch.br-branches-one +++ b/t/t5515/fetch.br-branches-one @@ -1,8 +1,8 @@ # br-branches-one 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-one-merge b/t/t5515/fetch.br-branches-one-merge index b4b3b35ce0..aa1c8a937e 100644 --- a/t/t5515/fetch.br-branches-one-merge +++ b/t/t5515/fetch.br-branches-one-merge @@ -1,9 +1,9 @@ # br-branches-one-merge 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-one-merge_branches-one b/t/t5515/fetch.br-branches-one-merge_branches-one index 2ecef384eb..c93310a730 100644 --- a/t/t5515/fetch.br-branches-one-merge_branches-one +++ b/t/t5515/fetch.br-branches-one-merge_branches-one @@ -1,9 +1,9 @@ # br-branches-one-merge branches-one 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-one-octopus b/t/t5515/fetch.br-branches-one-octopus index 96e3029416..97c4b544b8 100644 --- a/t/t5515/fetch.br-branches-one-octopus +++ b/t/t5515/fetch.br-branches-one-octopus @@ -1,9 +1,9 @@ # br-branches-one-octopus 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-one-octopus_branches-one b/t/t5515/fetch.br-branches-one-octopus_branches-one index 55e0bad621..df705f74c7 100644 --- a/t/t5515/fetch.br-branches-one-octopus_branches-one +++ b/t/t5515/fetch.br-branches-one-octopus_branches-one @@ -1,9 +1,9 @@ # br-branches-one-octopus branches-one 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-branches-one_branches-one b/t/t5515/fetch.br-branches-one_branches-one index 281fa09d48..96890e5bd9 100644 --- a/t/t5515/fetch.br-branches-one_branches-one +++ b/t/t5515/fetch.br-branches-one_branches-one @@ -1,8 +1,8 @@ # br-branches-one branches-one 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-explicit b/t/t5515/fetch.br-config-explicit index e2fa9c8654..68fc927263 100644 --- a/t/t5515/fetch.br-config-explicit +++ b/t/t5515/fetch.br-config-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-explicit-merge b/t/t5515/fetch.br-config-explicit-merge index ec1a7231aa..f6475b717a 100644 --- a/t/t5515/fetch.br-config-explicit-merge +++ b/t/t5515/fetch.br-config-explicit-merge @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-explicit-merge_config-explicit b/t/t5515/fetch.br-config-explicit-merge_config-explicit index 54f689151f..018bdd752a 100644 --- a/t/t5515/fetch.br-config-explicit-merge_config-explicit +++ b/t/t5515/fetch.br-config-explicit-merge_config-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-explicit-octopus b/t/t5515/fetch.br-config-explicit-octopus index 7011dfc181..36d0270502 100644 --- a/t/t5515/fetch.br-config-explicit-octopus +++ b/t/t5515/fetch.br-config-explicit-octopus @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-explicit-octopus_config-explicit b/t/t5515/fetch.br-config-explicit-octopus_config-explicit index bdad51f871..6654ad0898 100644 --- a/t/t5515/fetch.br-config-explicit-octopus_config-explicit +++ b/t/t5515/fetch.br-config-explicit-octopus_config-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-explicit_config-explicit b/t/t5515/fetch.br-config-explicit_config-explicit index 1b237dde6e..b19b0162e1 100644 --- a/t/t5515/fetch.br-config-explicit_config-explicit +++ b/t/t5515/fetch.br-config-explicit_config-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-glob b/t/t5515/fetch.br-config-glob index e75ec2f72b..946d70ca07 100644 --- a/t/t5515/fetch.br-config-glob +++ b/t/t5515/fetch.br-config-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-glob-merge b/t/t5515/fetch.br-config-glob-merge index ce8f739a0d..8bb5e8bc4e 100644 --- a/t/t5515/fetch.br-config-glob-merge +++ b/t/t5515/fetch.br-config-glob-merge @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-glob-merge_config-glob b/t/t5515/fetch.br-config-glob-merge_config-glob index 5817bed8f8..113c08dabe 100644 --- a/t/t5515/fetch.br-config-glob-merge_config-glob +++ b/t/t5515/fetch.br-config-glob-merge_config-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-glob-octopus b/t/t5515/fetch.br-config-glob-octopus index 938e532db2..9bbd537579 100644 --- a/t/t5515/fetch.br-config-glob-octopus +++ b/t/t5515/fetch.br-config-glob-octopus @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-glob-octopus_config-glob b/t/t5515/fetch.br-config-glob-octopus_config-glob index c9225bf6ff..4e510437d9 100644 --- a/t/t5515/fetch.br-config-glob-octopus_config-glob +++ b/t/t5515/fetch.br-config-glob-octopus_config-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-config-glob_config-glob b/t/t5515/fetch.br-config-glob_config-glob index a6c20f92ce..19daf0cb77 100644 --- a/t/t5515/fetch.br-config-glob_config-glob +++ b/t/t5515/fetch.br-config-glob_config-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-explicit b/t/t5515/fetch.br-remote-explicit index 83534d2ec8..ab44bc5519 100644 --- a/t/t5515/fetch.br-remote-explicit +++ b/t/t5515/fetch.br-remote-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-explicit-merge b/t/t5515/fetch.br-remote-explicit-merge index a9064dd65a..7421b2cb86 100644 --- a/t/t5515/fetch.br-remote-explicit-merge +++ b/t/t5515/fetch.br-remote-explicit-merge @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-explicit-merge_remote-explicit b/t/t5515/fetch.br-remote-explicit-merge_remote-explicit index 732a37e4d3..b6975d3890 100644 --- a/t/t5515/fetch.br-remote-explicit-merge_remote-explicit +++ b/t/t5515/fetch.br-remote-explicit-merge_remote-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-explicit-octopus b/t/t5515/fetch.br-remote-explicit-octopus index ecf020d929..76812812ee 100644 --- a/t/t5515/fetch.br-remote-explicit-octopus +++ b/t/t5515/fetch.br-remote-explicit-octopus @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit b/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit index af77531011..4c896cfc1a 100644 --- a/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit +++ b/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-explicit_remote-explicit b/t/t5515/fetch.br-remote-explicit_remote-explicit index 51fae567c8..01e014e6a0 100644 --- a/t/t5515/fetch.br-remote-explicit_remote-explicit +++ b/t/t5515/fetch.br-remote-explicit_remote-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-glob b/t/t5515/fetch.br-remote-glob index 94e6ad31e3..09bfcee00f 100644 --- a/t/t5515/fetch.br-remote-glob +++ b/t/t5515/fetch.br-remote-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-glob-merge b/t/t5515/fetch.br-remote-glob-merge index 09362e25af..4b62b01de4 100644 --- a/t/t5515/fetch.br-remote-glob-merge +++ b/t/t5515/fetch.br-remote-glob-merge @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-glob-merge_remote-glob b/t/t5515/fetch.br-remote-glob-merge_remote-glob index e2eabec62e..7478f1f158 100644 --- a/t/t5515/fetch.br-remote-glob-merge_remote-glob +++ b/t/t5515/fetch.br-remote-glob-merge_remote-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-glob-octopus b/t/t5515/fetch.br-remote-glob-octopus index b08e046195..2543420581 100644 --- a/t/t5515/fetch.br-remote-glob-octopus +++ b/t/t5515/fetch.br-remote-glob-octopus @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-glob-octopus_remote-glob b/t/t5515/fetch.br-remote-glob-octopus_remote-glob index d4d547c847..5ffde9c03c 100644 --- a/t/t5515/fetch.br-remote-glob-octopus_remote-glob +++ b/t/t5515/fetch.br-remote-glob-octopus_remote-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-remote-glob_remote-glob b/t/t5515/fetch.br-remote-glob_remote-glob index 646dbc8770..20ba5cb172 100644 --- a/t/t5515/fetch.br-remote-glob_remote-glob +++ b/t/t5515/fetch.br-remote-glob_remote-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig b/t/t5515/fetch.br-unconfig index 65ce6d99e2..887ccfc41f 100644 --- a/t/t5515/fetch.br-unconfig +++ b/t/t5515/fetch.br-unconfig @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_--tags_.._.git b/t/t5515/fetch.br-unconfig_--tags_.._.git index 8258c80868..1669cc4af0 100644 --- a/t/t5515/fetch.br-unconfig_--tags_.._.git +++ b/t/t5515/fetch.br-unconfig_--tags_.._.git @@ -1,7 +1,7 @@ # br-unconfig --tags ../.git -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file b/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file index f02bab2fb4..74115361ba 100644 --- a/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file +++ b/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file @@ -2,7 +2,7 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 tag 'tag-one' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file index 85de41109e..7726983818 100644 --- a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file +++ b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file @@ -1,7 +1,7 @@ # br-unconfig ../.git tag tag-one-tree tag tag-three-file 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three index 0da2337f1b..7b3750ce5c 100644 --- a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three +++ b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three @@ -1,7 +1,7 @@ # br-unconfig ../.git tag tag-one tag tag-three 8e32a6d901327a23ef831511badce7bf3bf46689 tag 'tag-one' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b tag 'tag-three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 tag 'tag-three' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_branches-default b/t/t5515/fetch.br-unconfig_branches-default index fc7041eefc..da30e3c62c 100644 --- a/t/t5515/fetch.br-unconfig_branches-default +++ b/t/t5515/fetch.br-unconfig_branches-default @@ -1,8 +1,8 @@ # br-unconfig branches-default 754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_branches-one b/t/t5515/fetch.br-unconfig_branches-one index e94cde745b..e4614314c5 100644 --- a/t/t5515/fetch.br-unconfig_branches-one +++ b/t/t5515/fetch.br-unconfig_branches-one @@ -1,8 +1,8 @@ # br-unconfig branches-one 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_config-explicit b/t/t5515/fetch.br-unconfig_config-explicit index 01a283e70d..ed323c9871 100644 --- a/t/t5515/fetch.br-unconfig_config-explicit +++ b/t/t5515/fetch.br-unconfig_config-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_config-glob b/t/t5515/fetch.br-unconfig_config-glob index 3a556c5e96..2372ed03c5 100644 --- a/t/t5515/fetch.br-unconfig_config-glob +++ b/t/t5515/fetch.br-unconfig_config-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_remote-explicit b/t/t5515/fetch.br-unconfig_remote-explicit index db216dfa56..6318dd11b4 100644 --- a/t/t5515/fetch.br-unconfig_remote-explicit +++ b/t/t5515/fetch.br-unconfig_remote-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.br-unconfig_remote-glob b/t/t5515/fetch.br-unconfig_remote-glob index aee65c204d..1d9afad7d8 100644 --- a/t/t5515/fetch.br-unconfig_remote-glob +++ b/t/t5515/fetch.br-unconfig_remote-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master b/t/t5515/fetch.master index 950fd078db..9b29d67200 100644 --- a/t/t5515/fetch.master +++ b/t/t5515/fetch.master @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_--tags_.._.git b/t/t5515/fetch.master_--tags_.._.git index 0e59950c7b..8a7493537b 100644 --- a/t/t5515/fetch.master_--tags_.._.git +++ b/t/t5515/fetch.master_--tags_.._.git @@ -1,7 +1,7 @@ # master --tags ../.git -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file b/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file index 82868524ca..0672d1292f 100644 --- a/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file +++ b/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file @@ -2,7 +2,7 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 tag 'tag-one' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file b/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file index 2e133eff29..0fd737cf81 100644 --- a/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file +++ b/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file @@ -1,7 +1,7 @@ # master ../.git tag tag-one-tree tag tag-three-file 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three b/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three index 92b18b40cc..e488986653 100644 --- a/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three +++ b/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three @@ -1,7 +1,7 @@ # master ../.git tag tag-one tag tag-three 8e32a6d901327a23ef831511badce7bf3bf46689 tag 'tag-one' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b tag 'tag-three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 tag 'tag-three' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_branches-default b/t/t5515/fetch.master_branches-default index 603d6d2331..2eedd3bfa4 100644 --- a/t/t5515/fetch.master_branches-default +++ b/t/t5515/fetch.master_branches-default @@ -1,8 +1,8 @@ # master branches-default 754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_branches-one b/t/t5515/fetch.master_branches-one index fe9bb0b798..901ce21d33 100644 --- a/t/t5515/fetch.master_branches-one +++ b/t/t5515/fetch.master_branches-one @@ -1,8 +1,8 @@ # master branches-one 8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_config-explicit b/t/t5515/fetch.master_config-explicit index 4be97c7575..251c826aa9 100644 --- a/t/t5515/fetch.master_config-explicit +++ b/t/t5515/fetch.master_config-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_config-glob b/t/t5515/fetch.master_config-glob index cb0726ff8d..27c158e332 100644 --- a/t/t5515/fetch.master_config-glob +++ b/t/t5515/fetch.master_config-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_remote-explicit b/t/t5515/fetch.master_remote-explicit index 44a1ca8429..b3cfe6b98b 100644 --- a/t/t5515/fetch.master_remote-explicit +++ b/t/t5515/fetch.master_remote-explicit @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5515/fetch.master_remote-glob b/t/t5515/fetch.master_remote-glob index 724e8db0a5..118befd1e4 100644 --- a/t/t5515/fetch.master_remote-glob +++ b/t/t5515/fetch.master_remote-glob @@ -3,9 +3,9 @@ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../ 0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../ 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../ -754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../ +6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ -0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../ +c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../ 0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../ -6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../ +525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../ diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 3abb2907ea..b69cf574d7 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -40,6 +40,40 @@ mk_test () { ) } +mk_test_with_hooks() { + mk_test "$@" && + ( + cd testrepo && + mkdir .git/hooks && + cd .git/hooks && + + cat >pre-receive <<-'EOF' && + #!/bin/sh + cat - >>pre-receive.actual + EOF + + cat >update <<-'EOF' && + #!/bin/sh + printf "%s %s %s\n" "$@" >>update.actual + EOF + + cat >post-receive <<-'EOF' && + #!/bin/sh + cat - >>post-receive.actual + EOF + + cat >post-update <<-'EOF' && + #!/bin/sh + for ref in "$@" + do + printf "%s\n" "$ref" >>post-update.actual + done + EOF + + chmod +x pre-receive update post-receive post-update + ) +} + mk_child() { rm -rf "$1" && git clone testrepo "$1" @@ -559,6 +593,169 @@ test_expect_success 'allow deleting an invalid remote ref' ' ' +test_expect_success 'pushing valid refs triggers post-receive and post-update hooks' ' + mk_test_with_hooks heads/master heads/next && + orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && + newmaster=$(git show-ref -s --verify refs/heads/master) && + orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) && + newnext=$_z40 && + git push testrepo refs/heads/master:refs/heads/master :refs/heads/next && + ( + cd testrepo/.git && + cat >pre-receive.expect <<-EOF && + $orgmaster $newmaster refs/heads/master + $orgnext $newnext refs/heads/next + EOF + + cat >update.expect <<-EOF && + refs/heads/master $orgmaster $newmaster + refs/heads/next $orgnext $newnext + EOF + + cat >post-receive.expect <<-EOF && + $orgmaster $newmaster refs/heads/master + $orgnext $newnext refs/heads/next + EOF + + cat >post-update.expect <<-EOF && + refs/heads/master + refs/heads/next + EOF + + test_cmp pre-receive.expect pre-receive.actual && + test_cmp update.expect update.actual && + test_cmp post-receive.expect post-receive.actual && + test_cmp post-update.expect post-update.actual + ) +' + +test_expect_success 'deleting dangling ref triggers hooks with correct args' ' + mk_test_with_hooks heads/master && + rm -f testrepo/.git/objects/??/* && + git push testrepo :refs/heads/master && + ( + cd testrepo/.git && + cat >pre-receive.expect <<-EOF && + $_z40 $_z40 refs/heads/master + EOF + + cat >update.expect <<-EOF && + refs/heads/master $_z40 $_z40 + EOF + + cat >post-receive.expect <<-EOF && + $_z40 $_z40 refs/heads/master + EOF + + cat >post-update.expect <<-EOF && + refs/heads/master + EOF + + test_cmp pre-receive.expect pre-receive.actual && + test_cmp update.expect update.actual && + test_cmp post-receive.expect post-receive.actual && + test_cmp post-update.expect post-update.actual + ) +' + +test_expect_success 'deletion of a non-existent ref is not fed to post-receive and post-update hooks' ' + mk_test_with_hooks heads/master && + orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && + newmaster=$(git show-ref -s --verify refs/heads/master) && + git push testrepo master :refs/heads/nonexistent && + ( + cd testrepo/.git && + cat >pre-receive.expect <<-EOF && + $orgmaster $newmaster refs/heads/master + $_z40 $_z40 refs/heads/nonexistent + EOF + + cat >update.expect <<-EOF && + refs/heads/master $orgmaster $newmaster + refs/heads/nonexistent $_z40 $_z40 + EOF + + cat >post-receive.expect <<-EOF && + $orgmaster $newmaster refs/heads/master + EOF + + cat >post-update.expect <<-EOF && + refs/heads/master + EOF + + test_cmp pre-receive.expect pre-receive.actual && + test_cmp update.expect update.actual && + test_cmp post-receive.expect post-receive.actual && + test_cmp post-update.expect post-update.actual + ) +' + +test_expect_success 'deletion of a non-existent ref alone does trigger post-receive and post-update hooks' ' + mk_test_with_hooks heads/master && + git push testrepo :refs/heads/nonexistent && + ( + cd testrepo/.git && + cat >pre-receive.expect <<-EOF && + $_z40 $_z40 refs/heads/nonexistent + EOF + + cat >update.expect <<-EOF && + refs/heads/nonexistent $_z40 $_z40 + EOF + + test_cmp pre-receive.expect pre-receive.actual && + test_cmp update.expect update.actual && + test_path_is_missing post-receive.actual && + test_path_is_missing post-update.actual + ) +' + +test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks with correct input' ' + mk_test_with_hooks heads/master heads/next heads/pu && + orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && + newmaster=$(git show-ref -s --verify refs/heads/master) && + orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) && + newnext=$_z40 && + orgpu=$(cd testrepo && git show-ref -s --verify refs/heads/pu) && + newpu=$(git show-ref -s --verify refs/heads/master) && + git push testrepo refs/heads/master:refs/heads/master \ + refs/heads/master:refs/heads/pu :refs/heads/next \ + :refs/heads/nonexistent && + ( + cd testrepo/.git && + cat >pre-receive.expect <<-EOF && + $orgmaster $newmaster refs/heads/master + $orgnext $newnext refs/heads/next + $orgpu $newpu refs/heads/pu + $_z40 $_z40 refs/heads/nonexistent + EOF + + cat >update.expect <<-EOF && + refs/heads/master $orgmaster $newmaster + refs/heads/next $orgnext $newnext + refs/heads/pu $orgpu $newpu + refs/heads/nonexistent $_z40 $_z40 + EOF + + cat >post-receive.expect <<-EOF && + $orgmaster $newmaster refs/heads/master + $orgnext $newnext refs/heads/next + $orgpu $newpu refs/heads/pu + EOF + + cat >post-update.expect <<-EOF && + refs/heads/master + refs/heads/next + refs/heads/pu + EOF + + test_cmp pre-receive.expect pre-receive.actual && + test_cmp update.expect update.actual && + test_cmp post-receive.expect post-receive.actual && + test_cmp post-update.expect post-update.actual + ) +' + test_expect_success 'allow deleting a ref using --delete' ' mk_test heads/master && (cd testrepo && git config receive.denyDeleteCurrent warn) && diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 0e5eb678ce..35304b41e9 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -94,16 +94,35 @@ test_expect_success '--rebase' ' test $(git rev-parse HEAD^) = $(git rev-parse copy) && test new = $(git show HEAD:file2) ' +test_expect_success 'pull.rebase' ' + git reset --hard before-rebase && + git config --bool pull.rebase true && + test_when_finished "git config --unset pull.rebase" && + git pull . copy && + test $(git rev-parse HEAD^) = $(git rev-parse copy) && + test new = $(git show HEAD:file2) +' test_expect_success 'branch.to-rebase.rebase' ' git reset --hard before-rebase && - git config branch.to-rebase.rebase 1 && + git config --bool branch.to-rebase.rebase true && + test_when_finished "git config --unset branch.to-rebase.rebase" && git pull . copy && - git config branch.to-rebase.rebase 0 && test $(git rev-parse HEAD^) = $(git rev-parse copy) && test new = $(git show HEAD:file2) ' +test_expect_success 'branch.to-rebase.rebase should override pull.rebase' ' + git reset --hard before-rebase && + git config --bool pull.rebase true && + test_when_finished "git config --unset pull.rebase" && + git config --bool branch.to-rebase.rebase false && + test_when_finished "git config --unset branch.to-rebase.rebase" && + git pull . copy && + test $(git rev-parse HEAD^) != $(git rev-parse copy) && + test new = $(git show HEAD:file2) +' + test_expect_success '--rebase with rebased upstream' ' git remote add -f me . && diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh new file mode 100755 index 0000000000..edea9f957e --- /dev/null +++ b/t/t5527-fetch-odd-refs.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +test_description='test fetching of oddly-named refs' +. ./test-lib.sh + +# afterwards we will have: +# HEAD - two +# refs/for/refs/heads/master - one +# refs/heads/master - three +test_expect_success 'setup repo with odd suffix ref' ' + echo content >file && + git add . && + git commit -m one && + git update-ref refs/for/refs/heads/master HEAD && + echo content >>file && + git commit -a -m two && + echo content >>file && + git commit -a -m three && + git checkout HEAD^ +' + +test_expect_success 'suffix ref is ignored during fetch' ' + git clone --bare file://"$PWD" suffix && + echo three >expect && + git --git-dir=suffix log -1 --format=%s refs/heads/master >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 64767d8705..1eea647656 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -40,6 +40,22 @@ test_expect_success 'setup remote repository' ' mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" ' +test_expect_success 'create password-protected repository' ' + mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb" && + cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \ + "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" +' + +test_expect_success 'setup askpass helper' ' + cat >askpass <<-\EOF && + #!/bin/sh + echo user@host + EOF + chmod +x askpass && + GIT_ASKPASS="$PWD/askpass" && + export GIT_ASKPASS +' + test_expect_success 'clone remote repository' ' cd "$ROOT_PATH" && git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone @@ -144,6 +160,24 @@ test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' ' test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ "$ROOT_PATH"/test_repo_clone master +test_expect_success 'push to password-protected repository (user in URL)' ' + test_commit pw-user && + git push "$HTTPD_URL_USER/auth/dumb/test_repo.git" HEAD && + git rev-parse --verify HEAD >expect && + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \ + rev-parse --verify HEAD >actual && + test_cmp expect actual +' + +test_expect_failure 'push to password-protected repository (no user in URL)' ' + test_commit pw-nouser && + git push "$HTTPD_URL/auth/dumb/test_repo.git" HEAD && + git rev-parse --verify HEAD >expect && + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \ + rev-parse --verify HEAD >actual && + test_cmp expect actual +' + stop_httpd test_done diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index a73c82635f..9b85d420c3 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -154,5 +154,37 @@ test_expect_success 'push (chunked)' ' test $HEAD = $(git rev-parse --verify HEAD)) ' +test_expect_success 'push --all can push to empty repo' ' + d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git && + git init --bare "$d" && + git --git-dir="$d" config http.receivepack true && + git push --all "$HTTPD_URL"/smart/empty-all.git +' + +test_expect_success 'push --mirror can push to empty repo' ' + d=$HTTPD_DOCUMENT_ROOT_PATH/empty-mirror.git && + git init --bare "$d" && + git --git-dir="$d" config http.receivepack true && + git push --mirror "$HTTPD_URL"/smart/empty-mirror.git +' + +test_expect_success 'push --all to repo with alternates' ' + s=$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git && + d=$HTTPD_DOCUMENT_ROOT_PATH/alternates-all.git && + git clone --bare --shared "$s" "$d" && + git --git-dir="$d" config http.receivepack true && + git --git-dir="$d" repack -adl && + git push --all "$HTTPD_URL"/smart/alternates-all.git +' + +test_expect_success 'push --mirror to repo with alternates' ' + s=$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git && + d=$HTTPD_DOCUMENT_ROOT_PATH/alternates-mirror.git && + git clone --bare --shared "$s" "$d" && + git --git-dir="$d" config http.receivepack true && + git --git-dir="$d" repack -adl && + git push --mirror "$HTTPD_URL"/smart/alternates-mirror.git +' + stop_httpd test_done diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index a1883ca6b6..95a133d697 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -8,8 +8,8 @@ if test -n "$NO_CURL"; then test_done fi -. "$TEST_DIRECTORY"/lib-httpd.sh LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'} +. "$TEST_DIRECTORY"/lib-httpd.sh start_httpd test_expect_success 'setup repository' ' @@ -35,11 +35,98 @@ test_expect_success 'clone http repository' ' test_cmp file clone/file ' -test_expect_success 'clone http repository with authentication' ' +test_expect_success 'create password-protected repository' ' mkdir "$HTTPD_DOCUMENT_ROOT_PATH/auth/" && - cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git" && - git clone $AUTH_HTTPD_URL/auth/repo.git clone-auth && - test_cmp file clone-auth/file + cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ + "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git" +' + +test_expect_success 'setup askpass helpers' ' + cat >askpass <<-EOF && + #!/bin/sh + echo >>"$PWD/askpass-query" "askpass: \$*" && + cat "$PWD/askpass-response" + EOF + chmod +x askpass && + GIT_ASKPASS="$PWD/askpass" && + export GIT_ASKPASS +' + +expect_askpass() { + dest=$HTTPD_DEST + { + case "$1" in + none) + ;; + pass) + echo "askpass: Password for 'http://$2@$dest': " + ;; + both) + echo "askpass: Username for 'http://$dest': " + echo "askpass: Password for 'http://$2@$dest': " + ;; + *) + false + ;; + esac + } >askpass-expect && + test_cmp askpass-expect askpass-query +} + +test_expect_success 'cloning password-protected repository can fail' ' + >askpass-query && + echo wrong >askpass-response && + test_must_fail git clone "$HTTPD_URL/auth/repo.git" clone-auth-fail && + expect_askpass both wrong +' + +test_expect_success 'http auth can use user/pass in URL' ' + >askpass-query && + echo wrong >askpass-response && + git clone "$HTTPD_URL_USER_PASS/auth/repo.git" clone-auth-none && + expect_askpass none +' + +test_expect_success 'http auth can use just user in URL' ' + >askpass-query && + echo user@host >askpass-response && + git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-pass && + expect_askpass pass user@host +' + +test_expect_success 'http auth can request both user and pass' ' + >askpass-query && + echo user@host >askpass-response && + git clone "$HTTPD_URL/auth/repo.git" clone-auth-both && + expect_askpass both user@host +' + +test_expect_success 'http auth respects credential helper config' ' + test_config_global credential.helper "!f() { + cat >/dev/null + echo username=user@host + echo password=user@host + }; f" && + >askpass-query && + echo wrong >askpass-response && + git clone "$HTTPD_URL/auth/repo.git" clone-auth-helper && + expect_askpass none +' + +test_expect_success 'http auth can get username from config' ' + test_config_global "credential.$HTTPD_URL.username" user@host && + >askpass-query && + echo user@host >askpass-response && + git clone "$HTTPD_URL/auth/repo.git" clone-auth-user && + expect_askpass pass user@host +' + +test_expect_success 'configured username does not override URL' ' + test_config_global "credential.$HTTPD_URL.username" wrong && + >askpass-query && + echo user@host >askpass-response && + git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-user2 && + expect_askpass pass user@host ' test_expect_success 'fetch changes via http' ' diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index e8103144bb..87ee01662c 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -206,6 +206,20 @@ test_expect_success 'clone from .git file' ' git clone dst/.git dst2 ' +test_expect_success 'fetch from .git gitfile' ' + ( + cd dst2 && + git fetch ../dst/.git + ) +' + +test_expect_success 'fetch from gitfile parent' ' + ( + cd dst2 && + git fetch ../dst + ) +' + test_expect_success 'clone separate gitdir where target already exists' ' rm -rf dst && test_must_fail git clone --separate-git-dir realgitdir src dst diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index 895f5595ae..c4c375ac04 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -146,4 +146,11 @@ test_expect_success 'cloning with reference being subset of source (-l -s)' \ cd "$base_dir" +test_expect_success 'clone with reference from a tagged repository' ' + ( + cd A && git tag -a -m 'tagged' HEAD + ) && + git clone --reference=A A I +' + test_done diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh index 76410293b3..39b4cb0ecd 100755 --- a/t/t6019-rev-list-ancestry-path.sh +++ b/t/t6019-rev-list-ancestry-path.sh @@ -70,4 +70,42 @@ test_expect_success 'rev-list --ancestry-patch D..M -- M.t' ' test_cmp expect actual ' +# b---bc +# / \ / +# a X +# \ / \ +# c---cb +# +# All refnames prefixed with 'x' to avoid confusion with the tags +# generated by test_commit on case-insensitive systems. +test_expect_success 'setup criss-cross' ' + mkdir criss-cross && + (cd criss-cross && + git init && + test_commit A && + git checkout -b xb master && + test_commit B && + git checkout -b xc master && + test_commit C && + git checkout -b xbc xb -- && + git merge xc && + git checkout -b xcb xc -- && + git merge xb && + git checkout master) +' + +# no commits in bc descend from cb +test_expect_success 'criss-cross: rev-list --ancestry-path cb..bc' ' + (cd criss-cross && + git rev-list --ancestry-path xcb..xbc > actual && + test -z "$(cat actual)") +' + +# no commits in repository descend from cb +test_expect_success 'criss-cross: rev-list --ancestry-path --all ^cb' ' + (cd criss-cross && + git rev-list --ancestry-path --all ^xcb > actual && + test -z "$(cat actual)") +' + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index c6f1f9f8ab..691e4a4481 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -164,7 +164,7 @@ test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if cp .git/BISECT_START saved && test_must_fail git bisect start $HASH4 foo -- && git branch > branch.output && - grep "* (no branch)" branch.output > /dev/null && + test_i18ngrep "* (no branch)" branch.output > /dev/null && test_cmp saved .git/BISECT_START ' test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' ' diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 19de5b16eb..19272bc551 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -51,6 +51,22 @@ test_expect_success 'branch -v' ' test_i18ncmp expect actual ' +cat >expect <<\EOF +b1 origin/master: ahead 1, behind 1 +b2 origin/master: ahead 1, behind 1 +b3 origin/master: behind 1 +b4 origin/master: ahead 2 +EOF + +test_expect_success 'branch -vv' ' + ( + cd test && + git branch -vv + ) | + sed -n -e "$script" >actual && + test_i18ncmp expect actual +' + test_expect_success 'checkout' ' ( cd test && git checkout b1 diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 7dc8a510c7..172178490a 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -6,6 +6,7 @@ test_description='for-each-ref test' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh # Mon Jul 3 15:18:43 2006 +0000 datestamp=1151939923 @@ -37,11 +38,13 @@ test_atom() { case "$1" in head) ref=refs/heads/master ;; tag) ref=refs/tags/testtag ;; + *) ref=$1 ;; esac printf '%s\n' "$3" >expected - test_expect_${4:-success} "basic atom: $1 $2" " + test_expect_${4:-success} $PREREQ "basic atom: $1 $2" " git for-each-ref --format='%($2)' $ref >actual && - test_cmp expected actual + sanitize_pgp <actual >actual.clean && + test_cmp expected actual.clean " } @@ -71,7 +74,10 @@ test_atom head taggerdate '' test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200' test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200' test_atom head subject 'Initial' +test_atom head contents:subject 'Initial' test_atom head body '' +test_atom head contents:body '' +test_atom head contents:signature '' test_atom head contents 'Initial ' @@ -101,7 +107,10 @@ test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200' test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200' test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200' test_atom tag subject 'Tagging at 1151939927' +test_atom tag contents:subject 'Tagging at 1151939927' test_atom tag body '' +test_atom tag contents:body '' +test_atom tag contents:signature '' test_atom tag contents 'Tagging at 1151939927 ' @@ -359,4 +368,92 @@ test_expect_success 'an unusual tag with an incomplete line' ' ' +test_expect_success 'create tag with subject and body content' ' + cat >>msg <<-\EOF && + the subject line + + first body line + second body line + EOF + git tag -F msg subject-body +' +test_atom refs/tags/subject-body subject 'the subject line' +test_atom refs/tags/subject-body body 'first body line +second body line +' +test_atom refs/tags/subject-body contents 'the subject line + +first body line +second body line +' + +test_expect_success 'create tag with multiline subject' ' + cat >msg <<-\EOF && + first subject line + second subject line + + first body line + second body line + EOF + git tag -F msg multiline +' +test_atom refs/tags/multiline subject 'first subject line second subject line' +test_atom refs/tags/multiline contents:subject 'first subject line second subject line' +test_atom refs/tags/multiline body 'first body line +second body line +' +test_atom refs/tags/multiline contents:body 'first body line +second body line +' +test_atom refs/tags/multiline contents:signature '' +test_atom refs/tags/multiline contents 'first subject line +second subject line + +first body line +second body line +' + +test_expect_success GPG 'create signed tags' ' + git tag -s -m "" signed-empty && + git tag -s -m "subject line" signed-short && + cat >msg <<-\EOF && + subject line + + body contents + EOF + git tag -s -F msg signed-long +' + +sig='-----BEGIN PGP SIGNATURE----- +-----END PGP SIGNATURE----- +' + +PREREQ=GPG +test_atom refs/tags/signed-empty subject '' +test_atom refs/tags/signed-empty contents:subject '' +test_atom refs/tags/signed-empty body "$sig" +test_atom refs/tags/signed-empty contents:body '' +test_atom refs/tags/signed-empty contents:signature "$sig" +test_atom refs/tags/signed-empty contents "$sig" + +test_atom refs/tags/signed-short subject 'subject line' +test_atom refs/tags/signed-short contents:subject 'subject line' +test_atom refs/tags/signed-short body "$sig" +test_atom refs/tags/signed-short contents:body '' +test_atom refs/tags/signed-short contents:signature "$sig" +test_atom refs/tags/signed-short contents "subject line +$sig" + +test_atom refs/tags/signed-long subject 'subject line' +test_atom refs/tags/signed-long contents:subject 'subject line' +test_atom refs/tags/signed-long body "body contents +$sig" +test_atom refs/tags/signed-long contents:body 'body contents +' +test_atom refs/tags/signed-long contents:signature "$sig" +test_atom refs/tags/signed-long contents "subject line + +body contents +$sig" + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 097ce2bc83..e93ac73829 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -8,6 +8,7 @@ test_description='git tag Tests for operations with tags.' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh # creating and listing lightweight tags: @@ -585,24 +586,6 @@ test_expect_success \ test_cmp expect actual ' -# subsequent tests require gpg; check if it is available -gpg --version >/dev/null 2>/dev/null -if [ $? -eq 127 ]; then - 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 GPG \ @@ -625,16 +608,6 @@ test_expect_success GPG \ # creating and verifying signed tags: -# 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> -# No password given, to enable non-interactive operation. - -cp -R "$TEST_DIRECTORY"/t7004 ./gpghome -chmod 0700 gpghome -GNUPGHOME="$(pwd)/gpghome" -export GNUPGHOME - get_tag_header signed-tag $commit commit $time >expect echo 'A signed tag message' >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 320e1d1dbe..ff2590849d 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -6,11 +6,6 @@ test_description='Test automatic use of a pager.' . "$TEST_DIRECTORY"/lib-pager.sh . "$TEST_DIRECTORY"/lib-terminal.sh -cleanup_fail() { - echo >&2 cleanup failed - (exit 1) -} - test_expect_success 'setup' ' sane_unset GIT_PAGER GIT_PAGER_IN_USE && test_unconfig core.pager && @@ -22,9 +17,7 @@ test_expect_success 'setup' ' ' test_expect_success TTY 'some commands use a pager' ' - rm -f paginated.out || - cleanup_fail && - + rm -f paginated.out && test_terminal git log && test -e paginated.out ' @@ -45,49 +38,37 @@ test_expect_failure TTY 'pager runs from subdir' ' ' test_expect_success TTY 'some commands do not use a pager' ' - rm -f paginated.out || - cleanup_fail && - + rm -f paginated.out && test_terminal git rev-list HEAD && ! test -e paginated.out ' test_expect_success 'no pager when stdout is a pipe' ' - rm -f paginated.out || - cleanup_fail && - + rm -f paginated.out && git log | cat && ! test -e paginated.out ' test_expect_success 'no pager when stdout is a regular file' ' - rm -f paginated.out || - cleanup_fail && - + rm -f paginated.out && git log >file && ! test -e paginated.out ' test_expect_success TTY 'git --paginate rev-list uses a pager' ' - rm -f paginated.out || - cleanup_fail && - + rm -f paginated.out && test_terminal git --paginate rev-list HEAD && test -e paginated.out ' test_expect_success 'no pager even with --paginate when stdout is a pipe' ' - rm -f file paginated.out || - cleanup_fail && - + rm -f file paginated.out && git --paginate log | cat && ! test -e paginated.out ' test_expect_success TTY 'no pager with --no-pager' ' - rm -f paginated.out || - cleanup_fail && - + rm -f paginated.out && test_terminal git --no-pager log && ! test -e paginated.out ' @@ -136,9 +117,7 @@ colorful() { } test_expect_success 'tests can detect color' ' - rm -f colorful.log colorless.log || - cleanup_fail && - + rm -f colorful.log colorless.log && git log --no-color >colorless.log && git log --color >colorful.log && ! colorful colorless.log && @@ -147,18 +126,14 @@ test_expect_success 'tests can detect color' ' test_expect_success 'no color when stdout is a regular file' ' rm -f colorless.log && - test_config color.ui auto || - cleanup_fail && - + test_config color.ui auto && git log >colorless.log && ! colorful colorless.log ' test_expect_success TTY 'color when writing to a pager' ' rm -f paginated.out && - test_config color.ui auto || - cleanup_fail && - + test_config color.ui auto && ( TERM=vt100 && export TERM && @@ -181,9 +156,7 @@ test_expect_success TTY 'colors are suppressed by color.pager' ' test_expect_success 'color when writing to a file intended for a pager' ' rm -f colorful.log && - test_config color.ui auto || - cleanup_fail && - + test_config color.ui auto && ( TERM=vt100 && GIT_PAGER_IN_USE=true && @@ -242,9 +215,7 @@ test_default_pager() { $test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" " sane_unset PAGER GIT_PAGER && test_unconfig core.pager && - rm -f default_pager_used || - cleanup_fail && - + rm -f default_pager_used && cat >\$less <<-\EOF && #!/bin/sh wc >default_pager_used @@ -265,9 +236,7 @@ test_PAGER_overrides() { $test_expectation TTY "$cmd - PAGER overrides default pager" " sane_unset GIT_PAGER && test_unconfig core.pager && - rm -f PAGER_used || - cleanup_fail && - + rm -f PAGER_used && PAGER='wc >PAGER_used' && export PAGER && $full_command && @@ -292,9 +261,7 @@ test_core_pager() { $test_expectation TTY "$cmd - repository-local core.pager setting $used_if_wanted" " sane_unset GIT_PAGER && - rm -f core.pager_used || - cleanup_fail && - + rm -f core.pager_used && PAGER=wc && export PAGER && test_config core.pager 'wc >core.pager_used' && @@ -321,9 +288,7 @@ test_pager_subdir_helper() { $test_expectation TTY "$cmd - core.pager $used_if_wanted from subdirectory" " sane_unset GIT_PAGER && rm -f core.pager_used && - rm -fr sub || - cleanup_fail && - + rm -fr sub && PAGER=wc && stampname=\$(pwd)/core.pager_used && export PAGER stampname && @@ -341,9 +306,7 @@ test_GIT_PAGER_overrides() { parse_args "$@" $test_expectation TTY "$cmd - GIT_PAGER overrides core.pager" " - rm -f GIT_PAGER_used || - cleanup_fail && - + rm -f GIT_PAGER_used && test_config core.pager wc && GIT_PAGER='wc >GIT_PAGER_used' && export GIT_PAGER && @@ -356,9 +319,7 @@ test_doesnt_paginate() { parse_args "$@" $test_expectation TTY "no pager for '$cmd'" " - rm -f GIT_PAGER_used || - cleanup_fail && - + rm -f GIT_PAGER_used && GIT_PAGER='wc >GIT_PAGER_used' && export GIT_PAGER && $full_command && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 69115269c2..695f7afdf3 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -362,10 +362,10 @@ test_expect_success 'update --init' ' git submodule update init > update.out && cat update.out && test_i18ngrep "not initialized" update.out && - ! test -d init/.git && + test_must_fail git rev-parse --resolve-git-dir init/.git && git submodule update --init init && - test -d init/.git + git rev-parse --resolve-git-dir init/.git ' test_expect_success 'do not add files from a submodule' ' diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index 95ffe349a7..3620215c1f 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -56,8 +56,9 @@ test_expect_success '"git submodule sync" should update submodule URLs' ' git pull --no-recurse-submodules && git submodule sync ) && - test -d "$(git config -f super-clone/submodule/.git/config \ - remote.origin.url)" && + test -d "$(cd super-clone/submodule && + git config remote.origin.url + )" && (cd super-clone/submodule && git checkout master && git pull diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh index a8fb30b792..0d5b42a25b 100755 --- a/t/t7405-submodule-merge.sh +++ b/t/t7405-submodule-merge.sh @@ -228,4 +228,55 @@ test_expect_success 'merging with a modify/modify conflict between merge bases' git merge d ' +# canonical criss-cross history in top and submodule +test_expect_success 'setup for recursive merge with submodule' ' + mkdir merge-recursive && + (cd merge-recursive && + git init && + mkdir sub && + (cd sub && + git init && + test_commit a && + git checkout -b sub-b master && + test_commit b && + git checkout -b sub-c master && + test_commit c && + git checkout -b sub-bc sub-b && + git merge sub-c && + git checkout -b sub-cb sub-c && + git merge sub-b && + git checkout master) && + git add sub && + git commit -m a && + git checkout -b top-b master && + (cd sub && git checkout sub-b) && + git add sub && + git commit -m b && + git checkout -b top-c master && + (cd sub && git checkout sub-c) && + git add sub && + git commit -m c && + git checkout -b top-bc top-b && + git merge -s ours --no-commit top-c && + (cd sub && git checkout sub-bc) && + git add sub && + git commit -m bc && + git checkout -b top-cb top-c && + git merge -s ours --no-commit top-b && + (cd sub && git checkout sub-cb) && + git add sub && + git commit -m cb) +' + +# merge should leave submodule unmerged in index +test_expect_success 'recursive merge with submodule' ' + (cd merge-recursive && + test_must_fail git merge top-bc && + echo "160000 $(git rev-parse top-cb:sub) 2 sub" > expect2 && + echo "160000 $(git rev-parse top-bc:sub) 3 sub" > expect3 && + git ls-files -u > actual && + grep "$(cat expect2)" actual > /dev/null && + grep "$(cat expect3)" actual > /dev/null) +' + test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index c679f36a4d..33b292b8a8 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -30,6 +30,7 @@ test_expect_success 'setup a submodule tree' ' git clone super submodule && git clone super rebasing && git clone super merging && + git clone super none && (cd super && git submodule add ../submodule submodule && test_tick && @@ -58,6 +59,11 @@ test_expect_success 'setup a submodule tree' ' test_tick && git commit -m "rebasing" ) + (cd super && + git submodule add ../none none && + test_tick && + git commit -m "none" + ) ' test_expect_success 'submodule update detaching the HEAD ' ' @@ -298,6 +304,62 @@ test_expect_success 'submodule update ignores update=rebase config for new submo ) ' +test_expect_success 'submodule init picks up update=none' ' + (cd super && + git config -f .gitmodules submodule.none.update none && + git submodule init none && + test "none" = "$(git config submodule.none.update)" + ) +' + +test_expect_success 'submodule update - update=none in .git/config' ' + (cd super && + git config submodule.submodule.update none && + (cd submodule && + git checkout master && + compare_head + ) && + git diff --raw | grep " submodule" && + git submodule update && + git diff --raw | grep " submodule" && + (cd submodule && + compare_head + ) && + git config --unset submodule.submodule.update && + git submodule update submodule + ) +' + +test_expect_success 'submodule update - update=none in .git/config but --checkout given' ' + (cd super && + git config submodule.submodule.update none && + (cd submodule && + git checkout master && + compare_head + ) && + git diff --raw | grep " submodule" && + git submodule update --checkout && + test_must_fail git diff --raw \| grep " submodule" && + (cd submodule && + test_must_fail compare_head + ) && + git config --unset submodule.submodule.update + ) +' + +test_expect_success 'submodule update --init skips submodule with update=none' ' + (cd super && + git add .gitmodules && + git commit -m ".gitmodules" + ) && + git clone super cloned && + (cd cloned && + git submodule update --init && + test -e submodule/.git && + test_must_fail test -e none/.git + ) +' + test_expect_success 'submodule update continues after checkout error' ' (cd super && git reset --hard HEAD && @@ -408,6 +470,7 @@ test_expect_success 'submodule update exit immediately in case of merge conflict test_cmp expect actual ) ' + test_expect_success 'submodule update exit immediately after recursive rebase error' ' (cd super && git checkout master && @@ -442,4 +505,110 @@ test_expect_success 'submodule update exit immediately after recursive rebase er test_cmp expect actual ) ' + +test_expect_success 'add different submodules to the same path' ' + (cd super && + git submodule add ../submodule s1 && + test_must_fail git submodule add ../merging s1 + ) +' + +test_expect_success 'submodule add places git-dir in superprojects git-dir' ' + (cd super && + mkdir deeper && + git submodule add ../submodule deeper/submodule && + (cd deeper/submodule && + git log > ../../expected + ) && + (cd .git/modules/deeper/submodule && + git log > ../../../../actual + ) && + test_cmp actual expected + ) +' + +test_expect_success 'submodule update places git-dir in superprojects git-dir' ' + (cd super && + git commit -m "added submodule" + ) && + git clone super super2 && + (cd super2 && + git submodule init deeper/submodule && + git submodule update && + (cd deeper/submodule && + git log > ../../expected + ) && + (cd .git/modules/deeper/submodule && + git log > ../../../../actual + ) && + test_cmp actual expected + ) +' + +test_expect_success 'submodule add places git-dir in superprojects git-dir recursive' ' + (cd super2 && + (cd deeper/submodule && + git submodule add ../submodule subsubmodule && + (cd subsubmodule && + git log > ../../../expected + ) && + git commit -m "added subsubmodule" && + git push + ) && + (cd .git/modules/deeper/submodule/modules/subsubmodule && + git log > ../../../../../actual + ) && + git add deeper/submodule && + git commit -m "update submodule" && + git push && + test_cmp actual expected + ) +' + +test_expect_success 'submodule update places git-dir in superprojects git-dir recursive' ' + mkdir super_update_r && + (cd super_update_r && + git init --bare + ) && + mkdir subsuper_update_r && + (cd subsuper_update_r && + git init --bare + ) && + mkdir subsubsuper_update_r && + (cd subsubsuper_update_r && + git init --bare + ) && + git clone subsubsuper_update_r subsubsuper_update_r2 && + (cd subsubsuper_update_r2 && + test_commit "update_subsubsuper" file && + git push origin master + ) && + git clone subsuper_update_r subsuper_update_r2 && + (cd subsuper_update_r2 && + test_commit "update_subsuper" file && + git submodule add ../subsubsuper_update_r subsubmodule && + git commit -am "subsubmodule" && + git push origin master + ) && + git clone super_update_r super_update_r2 && + (cd super_update_r2 && + test_commit "update_super" file && + git submodule add ../subsuper_update_r submodule && + git commit -am "submodule" && + git push origin master + ) && + rm -rf super_update_r2 && + git clone super_update_r super_update_r2 && + (cd super_update_r2 && + git submodule update --init --recursive && + (cd submodule/subsubmodule && + git log > ../../expected + ) && + (cd .git/modules/submodule/modules/subsubmodule + git log > ../../../../../actual + ) + test_cmp actual expected + ) +' + test_done diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index be745fb23f..9b69fe2e14 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -118,19 +118,19 @@ test_expect_success 'use "submodule foreach" to checkout 2nd level submodule' ' git clone super clone2 && ( cd clone2 && - test ! -d sub1/.git && - test ! -d sub2/.git && - test ! -d sub3/.git && - test ! -d nested1/.git && + test_must_fail git rev-parse --resolve-git-dir sub1/.git && + test_must_fail git rev-parse --resolve-git-dir sub2/.git && + test_must_fail git rev-parse --resolve-git-dir sub3/.git && + test_must_fail git rev-parse --resolve-git-dir nested1/.git && git submodule update --init && - test -d sub1/.git && - test -d sub2/.git && - test -d sub3/.git && - test -d nested1/.git && - test ! -d nested1/nested2/.git && + git rev-parse --resolve-git-dir sub1/.git && + git rev-parse --resolve-git-dir sub2/.git && + git rev-parse --resolve-git-dir sub3/.git && + git rev-parse --resolve-git-dir nested1/.git && + test_must_fail git rev-parse --resolve-git-dir nested1/nested2/.git && git submodule foreach "git submodule update --init" && - test -d nested1/nested2/.git && - test ! -d nested1/nested2/nested3/.git + git rev-parse --resolve-git-dir nested1/nested1/nested2/.git + test_must_fail git rev-parse --resolve-git-dir nested1/nested2/nested3/.git ) ' @@ -138,8 +138,8 @@ test_expect_success 'use "foreach --recursive" to checkout all submodules' ' ( cd clone2 && git submodule foreach --recursive "git submodule update --init" && - test -d nested1/nested2/nested3/.git && - test -d nested1/nested2/nested3/submodule/.git + git rev-parse --resolve-git-dir nested1/nested2/nested3/.git && + git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git ) ' @@ -183,18 +183,18 @@ test_expect_success 'use "update --recursive" to checkout all submodules' ' git clone super clone3 && ( cd clone3 && - test ! -d sub1/.git && - test ! -d sub2/.git && - test ! -d sub3/.git && - test ! -d nested1/.git && + test_must_fail git rev-parse --resolve-git-dir sub1/.git && + test_must_fail git rev-parse --resolve-git-dir sub2/.git && + test_must_fail git rev-parse --resolve-git-dir sub3/.git && + test_must_fail git rev-parse --resolve-git-dir nested1/.git && git submodule update --init --recursive && - test -d sub1/.git && - test -d sub2/.git && - test -d sub3/.git && - test -d nested1/.git && - test -d nested1/nested2/.git && - test -d nested1/nested2/nested3/.git && - test -d nested1/nested2/nested3/submodule/.git + git rev-parse --resolve-git-dir sub1/.git && + git rev-parse --resolve-git-dir sub2/.git && + git rev-parse --resolve-git-dir sub3/.git && + git rev-parse --resolve-git-dir nested1/.git && + git rev-parse --resolve-git-dir nested1/nested2/.git && + git rev-parse --resolve-git-dir nested1/nested2/nested3/.git && + git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git ) ' @@ -247,14 +247,17 @@ test_expect_success 'ensure "status --cached --recursive" preserves the --cached test_expect_success 'use "git clone --recursive" to checkout all submodules' ' git clone --recursive super clone4 && - test -d clone4/.git && - test -d clone4/sub1/.git && - test -d clone4/sub2/.git && - test -d clone4/sub3/.git && - test -d clone4/nested1/.git && - test -d clone4/nested1/nested2/.git && - test -d clone4/nested1/nested2/nested3/.git && - test -d clone4/nested1/nested2/nested3/submodule/.git + ( + cd clone4 && + git rev-parse --resolve-git-dir .git && + git rev-parse --resolve-git-dir sub1/.git && + git rev-parse --resolve-git-dir sub2/.git && + git rev-parse --resolve-git-dir sub3/.git && + git rev-parse --resolve-git-dir nested1/.git && + git rev-parse --resolve-git-dir nested1/nested2/.git && + git rev-parse --resolve-git-dir nested1/nested2/nested3/.git && + git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git + ) ' test_expect_success 'test "update --recursive" with a flag with spaces' ' @@ -262,14 +265,14 @@ test_expect_success 'test "update --recursive" with a flag with spaces' ' git clone super clone5 && ( cd clone5 && - test ! -d nested1/.git && + test_must_fail git rev-parse --resolve-git-dir d nested1/.git && git submodule update --init --recursive --reference="$(dirname "$PWD")/common objects" && - test -d nested1/.git && - test -d nested1/nested2/.git && - test -d nested1/nested2/nested3/.git && - test -f nested1/.git/objects/info/alternates && - test -f nested1/nested2/.git/objects/info/alternates && - test -f nested1/nested2/nested3/.git/objects/info/alternates + git rev-parse --resolve-git-dir nested1/.git && + git rev-parse --resolve-git-dir nested1/nested2/.git && + git rev-parse --resolve-git-dir nested1/nested2/nested3/.git && + test -f .git/modules/nested1/objects/info/alternates && + test -f .git/modules/nested1/modules/nested2/objects/info/alternates && + test -f .git/modules/nested1/modules/nested2/modules/nested3/objects/info/alternates ) ' @@ -277,18 +280,18 @@ test_expect_success 'use "update --recursive nested1" to checkout all submodules git clone super clone6 && ( cd clone6 && - test ! -d sub1/.git && - test ! -d sub2/.git && - test ! -d sub3/.git && - test ! -d nested1/.git && + test_must_fail git rev-parse --resolve-git-dir sub1/.git && + test_must_fail git rev-parse --resolve-git-dir sub2/.git && + test_must_fail git rev-parse --resolve-git-dir sub3/.git && + test_must_fail git rev-parse --resolve-git-dir nested1/.git && git submodule update --init --recursive -- nested1 && - test ! -d sub1/.git && - test ! -d sub2/.git && - test ! -d sub3/.git && - test -d nested1/.git && - test -d nested1/nested2/.git && - test -d nested1/nested2/nested3/.git && - test -d nested1/nested2/nested3/submodule/.git + test_must_fail git rev-parse --resolve-git-dir sub1/.git && + test_must_fail git rev-parse --resolve-git-dir sub2/.git && + test_must_fail git rev-parse --resolve-git-dir sub3/.git && + git rev-parse --resolve-git-dir nested1/.git && + git rev-parse --resolve-git-dir nested1/nested2/.git && + git rev-parse --resolve-git-dir nested1/nested2/nested3/.git && + git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git ) ' diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index cc16d3f05d..ab37c368d0 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -43,7 +43,7 @@ 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' +'test `wc -l <super/.git/modules/sub/objects/info/alternates` = 1' cd "$base_dir" @@ -66,7 +66,7 @@ test_expect_success 'update with reference' \ cd "$base_dir" test_expect_success 'after update: existence of info/alternates' \ -'test `wc -l <super-clone/sub/.git/objects/info/alternates` = 1' +'test `wc -l <super-clone/.git/modules/sub/objects/info/alternates` = 1' cd "$base_dir" diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 3ad04363b5..8bb38337a9 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -8,39 +8,39 @@ test_description='git commit' . ./test-lib.sh +. "$TEST_DIRECTORY/diff-lib.sh" -test_tick +author='The Real Author <someguy@his.email.org>' -test_expect_success \ - "initial status" \ - "echo 'bongo bongo' >file && - git add file" +test_tick -test_expect_success "Constructing initial commit" ' +test_expect_success 'initial status' ' + echo bongo bongo >file && + git add file && git status >actual && test_i18ngrep "Initial commit" actual ' -test_expect_success \ - "fail initial amend" \ - "test_must_fail git commit --amend" +test_expect_success 'fail initial amend' ' + test_must_fail git commit --amend +' -test_expect_success \ - "initial commit" \ - "git commit -m initial" +test_expect_success 'setup: initial commit' ' + git commit -m initial +' -test_expect_success \ - "invalid options 1" \ - "test_must_fail git commit -m foo -m bar -F file" +test_expect_success '-m and -F do not mix' ' + test_must_fail git commit -m foo -m bar -F file +' -test_expect_success \ - "invalid options 2" \ - "test_must_fail git commit -C HEAD -m illegal" +test_expect_success '-m and -C do not mix' ' + test_must_fail git commit -C HEAD -m illegal +' -test_expect_success \ - "using paths with -a" \ - "echo King of the bongo >file && - test_must_fail git commit -m foo -a file" +test_expect_success 'paths and -a do not mix' ' + echo King of the bongo >file && + test_must_fail git commit -m foo -a file +' test_expect_success PERL 'can use paths with --interactive' ' echo bong-o-bong >file && @@ -50,139 +50,163 @@ test_expect_success PERL 'can use paths with --interactive' ' git reset --hard HEAD^ ' -test_expect_success \ - "using invalid commit with -C" \ - "test_must_fail git commit -C bogus" +test_expect_success 'using invalid commit with -C' ' + test_must_fail git commit -C bogus +' -test_expect_success \ - "testing nothing to commit" \ - "test_must_fail git commit -m initial" +test_expect_success 'nothing to commit' ' + test_must_fail git commit -m initial +' -test_expect_success \ - "next commit" \ - "echo 'bongo bongo bongo' >file \ - git commit -m next -a" +test_expect_success 'setup: non-initial commit' ' + echo bongo bongo bongo >file && + git commit -m next -a +' -test_expect_success \ - "commit message from non-existing file" \ - "echo 'more bongo: bongo bongo bongo bongo' >file && \ - test_must_fail git commit -F gah -a" +test_expect_success 'commit message from non-existing file' ' + echo more bongo: bongo bongo bongo bongo >file && + test_must_fail git commit -F gah -a +' -# Empty except stray tabs and spaces on a few lines. -sed -e 's/@$//' >msg <<EOF - @ +test_expect_success 'empty commit message' ' + # Empty except stray tabs and spaces on a few lines. + sed -e "s/@//g" >msg <<-\EOF && + @ @ + @@ + @ @ + @Signed-off-by: hula@ + EOF + test_must_fail git commit -F msg -a +' - @ -Signed-off-by: hula -EOF -test_expect_success \ - "empty commit message" \ - "test_must_fail git commit -F msg -a" +test_expect_success 'setup: commit message from file' ' + echo this is the commit message, coming from a file >msg && + git commit -F msg -a +' -test_expect_success \ - "commit message from file" \ - "echo 'this is the commit message, coming from a file' >msg && \ - git commit -F msg -a" +test_expect_success 'amend commit' ' + cat >editor <<-\EOF && + #!/bin/sh + sed -e "s/a file/an amend commit/g" < "$1" > "$1-" + mv "$1-" "$1" + EOF + chmod 755 editor && + EDITOR=./editor git commit --amend +' -cat >editor <<\EOF -#!/bin/sh -sed -e "s/a file/an amend commit/g" < "$1" > "$1-" -mv "$1-" "$1" -EOF -chmod 755 editor +test_expect_success 'set up editor' ' + cat >editor <<-\EOF && + #!/bin/sh + sed -e "s/unamended/amended/g" <"$1" >"$1-" + mv "$1-" "$1" + EOF + chmod 755 editor +' -test_expect_success \ - "amend commit" \ - "EDITOR=./editor git commit --amend" +test_expect_success 'amend without launching editor' ' + echo unamended >expect && + git commit --allow-empty -m "unamended" && + echo needs more bongo >file && + git add file && + EDITOR=./editor git commit --no-edit --amend && + git diff --exit-code HEAD -- file && + git diff-tree -s --format=%s HEAD >msg && + test_cmp expect msg +' -test_expect_success \ - "passing -m and -F" \ - "echo 'enough with the bongos' >file && \ - test_must_fail git commit -F msg -m amending ." +test_expect_success '--amend --edit' ' + echo amended >expect && + git commit --allow-empty -m "unamended" && + echo bongo again >file && + git add file && + EDITOR=./editor git commit --edit --amend && + git diff-tree -s --format=%s HEAD >msg && + test_cmp expect msg +' -test_expect_success \ - "using message from other commit" \ - "git commit -C HEAD^ ." +test_expect_success '-m --edit' ' + echo amended >expect && + git commit --allow-empty -m buffer && + echo bongo bongo >file && + git add file && + EDITOR=./editor git commit -m unamended --edit && + git diff-tree -s --format=%s HEAD >msg && + test_cmp expect msg +' -cat >editor <<\EOF -#!/bin/sh -sed -e "s/amend/older/g" < "$1" > "$1-" -mv "$1-" "$1" -EOF -chmod 755 editor - -test_expect_success \ - "editing message from other commit" \ - "echo 'hula hula' >file && \ - EDITOR=./editor git commit -c HEAD^ -a" - -test_expect_success \ - "message from stdin" \ - "echo 'silly new contents' >file && \ - echo commit message from stdin | git commit -F - -a" - -test_expect_success \ - "overriding author from command line" \ - "echo 'gak' >file && \ - git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a >output 2>&1" - -test_expect_success \ - "commit --author output mentions author" \ - "grep Rubber.Duck output" - -test_expect_success PERL \ - "interactive add" \ - "echo 7 | git commit --interactive | grep 'What now'" - -test_expect_success PERL \ - "commit --interactive doesn't change index if editor aborts" \ - "echo zoo >file && +test_expect_success '-m and -F do not mix' ' + echo enough with the bongos >file && + test_must_fail git commit -F msg -m amending . +' + +test_expect_success 'using message from other commit' ' + git commit -C HEAD^ . +' + +test_expect_success 'editing message from other commit' ' + cat >editor <<-\EOF && + #!/bin/sh + sed -e "s/amend/older/g" < "$1" > "$1-" + mv "$1-" "$1" + EOF + chmod 755 editor && + echo hula hula >file && + EDITOR=./editor git commit -c HEAD^ -a +' + +test_expect_success 'message from stdin' ' + echo silly new contents >file && + echo commit message from stdin | + git commit -F - -a +' + +test_expect_success 'overriding author from command line' ' + echo gak >file && + git commit -m author \ + --author "Rubber Duck <rduck@convoy.org>" -a >output 2>&1 && + grep Rubber.Duck output +' + +test_expect_success PERL 'interactive add' ' + echo 7 | + git commit --interactive | + grep "What now" +' + +test_expect_success PERL "commit --interactive doesn't change index if editor aborts" ' + echo zoo >file && test_must_fail git diff --exit-code >diff1 && - (echo u ; echo '*' ; echo q) | - (EDITOR=: && export EDITOR && - test_must_fail git commit --interactive) && + (echo u ; echo "*" ; echo q) | + ( + EDITOR=: && + export EDITOR && + test_must_fail git commit --interactive + ) && git diff >diff2 && - test_cmp diff1 diff2" - -test_expect_success \ - "showing committed revisions" \ - "git rev-list HEAD >current" + compare_diff_patch diff1 diff2 +' -cat >editor <<\EOF -#!/bin/sh -sed -e "s/good/bad/g" < "$1" > "$1-" -mv "$1-" "$1" -EOF -chmod 755 editor - -cat >msg <<EOF -A good commit message. -EOF - -test_expect_success \ - 'editor not invoked if -F is given' ' - echo "moo" >file && - EDITOR=./editor git commit -a -F msg && - git show -s --pretty=format:"%s" | grep -q good && - echo "quack" >file && - echo "Another good message." | EDITOR=./editor git commit -a -F - && - git show -s --pretty=format:"%s" | grep -q good - ' -# We could just check the head sha1, but checking each commit makes it -# easier to isolate bugs. - -cat >expected <<\EOF -72c0dc9855b0c9dadcbfd5a31cab072e0cb774ca -9b88fc14ce6b32e3d9ee021531a54f18a5cf38a2 -3536bbb352c3a1ef9a420f5b4242d48578b92aa7 -d381ac431806e53f3dd7ac2f1ae0534f36d738b9 -4fd44095ad6334f3ef72e4c5ec8ddf108174b54a -402702b49136e7587daa9280e91e4bb7cb2179f7 -EOF - -test_expect_success \ - 'validate git rev-list output.' \ - 'test_cmp expected current' +test_expect_success 'editor not invoked if -F is given' ' + cat >editor <<-\EOF && + #!/bin/sh + sed -e s/good/bad/g <"$1" >"$1-" + mv "$1-" "$1" + EOF + chmod 755 editor && + + echo A good commit message. >msg && + echo moo >file && + + EDITOR=./editor git commit -a -F msg && + git show -s --pretty=format:%s >subject && + grep -q good subject && + + echo quack >file && + echo Another good message. | + EDITOR=./editor git commit -a -F - && + git show -s --pretty=format:%s >subject && + grep -q good subject +' test_expect_success 'partial commit that involves removal (1)' ' @@ -216,7 +240,6 @@ test_expect_success 'partial commit that involves removal (3)' ' ' -author="The Real Author <someguy@his.email.org>" test_expect_success 'amend commit to fix author' ' oldtick=$GIT_AUTHOR_DATE && @@ -345,7 +368,6 @@ test_expect_success 'multiple -m' ' ' -author="The Real Author <someguy@his.email.org>" test_expect_success 'amend commit to fix author' ' oldtick=$GIT_AUTHOR_DATE && @@ -372,15 +394,8 @@ test_expect_success 'git commit <file> with dirty index' ' test_expect_success 'same tree (single parent)' ' - git reset --hard - - if git commit -m empty - then - echo oops -- should have complained - false - else - : happy - fi + git reset --hard && + test_must_fail git commit -m empty ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 905255adf0..fc57b135c5 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -189,7 +189,7 @@ test_expect_success 'status with gitignore' ' # untracked EOF git status --ignored >output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success 'status with gitignore (nothing untracked)' ' @@ -247,7 +247,7 @@ test_expect_success 'status with gitignore (nothing untracked)' ' # untracked EOF git status --ignored >output && - test_cmp expect output + test_i18ncmp expect output ' rm -f .gitignore diff --git a/t/t7511-status-index.sh b/t/t7511-status-index.sh new file mode 100755 index 0000000000..b5fdc048a5 --- /dev/null +++ b/t/t7511-status-index.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='git status with certain file name lengths' + +. ./test-lib.sh + +files="0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z" + +check() { + len=$1 + prefix=$2 + + for i in $files + do + : >$prefix$i + done + + test_expect_success "status, filename length $len" " + git add $prefix* && + git status + " + rm $prefix* .git/index +} + +check 1 +check 2 p +check 3 px +check 4 pre +check 5 pref +check 6 prefi +check 7 prefix +check 8 prefix- +check 9 prefix-p +check 10 prefix-pr +check 11 prefix-pre +check 12 prefix-pref +check 13 prefix-prefi +check 14 prefix-prefix +check 15 prefix-prefix- +check 16 prefix-prefix-p +check 17 prefix-prefix-pr +check 18 prefix-prefix-pre +check 19 prefix-prefix-pref +check 20 prefix-prefix-prefi +check 21 prefix-prefix-prefix +check 22 prefix-prefix-prefix- +check 23 prefix-prefix-prefix-p +check 24 prefix-prefix-prefix-pr + +test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 87aac835a1..5d8c428543 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -38,8 +38,8 @@ printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9 >empty create_merge_msgs () { - echo "Merge commit 'c2'" >msg.1-5 && - echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 && + echo "Merge tag 'c2'" >msg.1-5 && + echo "Merge tags 'c2' and 'c3'" >msg.1-5-9 && { echo "Squashed commit of the following:" && echo && @@ -57,7 +57,7 @@ create_merge_msgs () { } >squash.1-5-9 && echo >msg.nolog && { - echo "* commit 'c3':" && + echo "* tag 'c3':" && echo " commit 3" && echo } >msg.log @@ -96,7 +96,11 @@ verify_parents () { verify_mergeheads () { printf '%s\n' "$@" >mergehead.expected && - test_cmp mergehead.expected .git/MERGE_HEAD + while read sha1 rest + do + git rev-parse $sha1 + done <.git/MERGE_HEAD >mergehead.actual && + test_cmp mergehead.expected mergehead.actual } verify_no_mergehead () { @@ -643,4 +647,27 @@ test_expect_success 'amending no-ff merge commit' ' test_debug 'git log --graph --decorate --oneline --all' +cat >editor <<\EOF +#!/bin/sh +# Add a new message string that was not in the template +( + echo "Merge work done on the side branch c1" + echo + cat <"$1" +) >"$1.tmp" && mv "$1.tmp" "$1" +# strip comments and blank lines from end of message +sed -e '/^#/d' < "$1" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > expected +EOF +chmod 755 editor + +test_expect_success 'merge --no-ff --edit' ' + git reset --hard c0 && + EDITOR=./editor git merge --no-ff --edit c1 && + verify_parents $c0 $c1 && + git cat-file commit HEAD >raw && + grep "work done on the side branch" raw && + sed "1,/^$/d" >actual raw && + test_cmp actual expected +' + test_done diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh index 9114785ef7..89619cf446 100755 --- a/t/t7604-merge-custom-message.sh +++ b/t/t7604-merge-custom-message.sh @@ -11,7 +11,7 @@ create_merge_msgs() { cp exp.subject exp.log && echo >>exp.log "" && - echo >>exp.log "* commit 'c2':" && + echo >>exp.log "* tag 'c2':" && echo >>exp.log " c2" } diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 72a8731d5e..aa74184c31 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -107,6 +107,7 @@ error: The following untracked working tree files would be overwritten by merge: sub sub2 Please move or remove them before you can merge. +Aborting EOF test_expect_success 'will not overwrite untracked file in leading path' ' diff --git a/t/t7608-merge-messages.sh b/t/t7608-merge-messages.sh index 9225fa6f02..8e7e0a5865 100755 --- a/t/t7608-merge-messages.sh +++ b/t/t7608-merge-messages.sh @@ -35,7 +35,7 @@ test_expect_success 'merge tag' ' git checkout master && test_commit master-3 && git merge tag-1 && - check_oneline "Merge commit Qtag-1Q" + check_oneline "Merge tag Qtag-1Q" ' test_expect_success 'ambiguous tag' ' @@ -44,7 +44,7 @@ test_expect_success 'ambiguous tag' ' git checkout master && test_commit master-4 && git merge ambiguous && - check_oneline "Merge commit QambiguousQ" + check_oneline "Merge tag QambiguousQ" ' test_expect_success 'remote-tracking branch' ' diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh index c994836c53..0e4a682c64 100755 --- a/t/t7609-merge-co-error-msgs.sh +++ b/t/t7609-merge-co-error-msgs.sh @@ -32,6 +32,7 @@ error: The following untracked working tree files would be overwritten by merge: three two Please move or remove them before you can merge. +Aborting EOF test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' ' @@ -56,6 +57,7 @@ Please, commit your changes or stash them before you can merge. error: The following untracked working tree files would be overwritten by merge: five Please move or remove them before you can merge. +Aborting EOF test_expect_success 'untracked files or local changes ovewritten by merge' ' @@ -71,6 +73,7 @@ error: Your local changes to the following files would be overwritten by checkou rep/one rep/two Please, commit your changes or stash them before you can switch branches. +Aborting EOF test_expect_success 'cannot switch branches because of local changes' ' @@ -92,6 +95,7 @@ error: Your local changes to the following files would be overwritten by checkou rep/one rep/two Please, commit your changes or stash them before you can switch branches. +Aborting EOF test_expect_success 'not uptodate file porcelain checkout error' ' @@ -105,6 +109,7 @@ error: Updating the following directories would lose untracked files in it: rep rep2 +Aborting EOF test_expect_success 'not_uptodate_dir porcelain checkout error' ' diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index cbc08e3276..4aab2a75b8 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -16,6 +16,7 @@ Testing basic merge tool invocation' test_expect_success 'setup' ' git config rerere.enabled true && echo master >file1 && + echo master spaced >"spaced name" && echo master file11 >file11 && echo master file12 >file12 && echo master file13 >file13 && @@ -30,13 +31,14 @@ test_expect_success 'setup' ' git commit -m "Add foo" ) && git submodule add git://example.com/submod submod && - git add file1 file1[1-4] subdir/file3 .gitmodules submod && + git add file1 "spaced name" file1[1-4] subdir/file3 .gitmodules submod && git commit -m "add initial versions" && git checkout -b branch1 master && git submodule update -N && echo branch1 change >file1 && echo branch1 newfile >file2 && + echo branch1 spaced >"spaced name" && echo branch1 change file11 >file11 && echo branch1 change file13 >file13 && echo branch1 sub >subdir/file3 && @@ -47,7 +49,7 @@ test_expect_success 'setup' ' git commit -m "Add bar on branch1" && git checkout -b submod-branch1 ) && - git add file1 file11 file13 file2 subdir/file3 submod && + git add file1 "spaced name" file11 file13 file2 subdir/file3 submod && git rm file12 && git commit -m "branch1 changes" && @@ -55,6 +57,7 @@ test_expect_success 'setup' ' git submodule update -N && echo master updated >file1 && echo master new >file2 && + echo master updated spaced >"spaced name" && echo master updated file12 >file12 && echo master updated file14 >file14 && echo master new sub >subdir/file3 && @@ -65,7 +68,7 @@ test_expect_success 'setup' ' git commit -m "Add bar on master" && git checkout -b submod-master ) && - git add file1 file12 file14 file2 subdir/file3 submod && + git add file1 "spaced name" file12 file14 file2 subdir/file3 submod && git rm file11 && git commit -m "master updates" && @@ -78,8 +81,8 @@ test_expect_success 'custom mergetool' ' git checkout -b test1 branch1 && git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && - ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && - ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file1 ) && + ( yes "" | git mergetool file2 "spaced name" >/dev/null 2>&1 ) && ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && @@ -97,6 +100,7 @@ test_expect_success 'mergetool crlf' ' test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && + ( yes "" | git mergetool "spaced name" >/dev/null 2>&1 ) && ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && @@ -126,7 +130,7 @@ test_expect_success 'mergetool on file in parent dir' ' ( cd subdir && ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && - ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) && + ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) && ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) && ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) && ( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) && @@ -180,6 +184,24 @@ test_expect_success 'mergetool skips resolved paths when rerere is active' ' git reset --hard ' +test_expect_success 'mergetool takes partial path' ' + git config rerere.enabled false && + git checkout -b test12 branch1 && + git submodule update -N && + test_must_fail git merge master && + + #shouldnt need these lines + #( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && + #( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && + #( yes "l" | git mergetool submod >/dev/null 2>&1 ) && + #( yes "" | git mergetool file1 file2 >/dev/null 2>&1 ) && + + ( yes "" | git mergetool subdir ) && + + test "$(cat subdir/file3)" = "master new sub" && + git reset --hard +' + test_expect_success 'deleted vs modified submodule' ' git checkout -b test6 branch1 && git submodule update -N && @@ -189,7 +211,7 @@ test_expect_success 'deleted vs modified submodule' ' git checkout -b test6.a test6 && test_must_fail git merge master && test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && ( yes "r" | git mergetool submod ) && rmdir submod && mv submod-movedaside submod && @@ -205,7 +227,7 @@ test_expect_success 'deleted vs modified submodule' ' git submodule update -N && test_must_fail git merge master && test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && ( yes "l" | git mergetool submod ) && test ! -e submod && @@ -218,7 +240,7 @@ test_expect_success 'deleted vs modified submodule' ' git submodule update -N && test_must_fail git merge test6 && test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && ( yes "r" | git mergetool submod ) && test ! -e submod && @@ -233,7 +255,7 @@ test_expect_success 'deleted vs modified submodule' ' git submodule update -N && test_must_fail git merge test6 && test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && ( yes "l" | git mergetool submod ) && test "$(cat submod/bar)" = "master submodule" && @@ -256,7 +278,7 @@ test_expect_success 'file vs modified submodule' ' git checkout -b test7.a branch1 && test_must_fail git merge master && test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && ( yes "r" | git mergetool submod ) && rmdir submod && mv submod-movedaside submod && @@ -271,7 +293,7 @@ test_expect_success 'file vs modified submodule' ' git checkout -b test7.b test7 && test_must_fail git merge master && test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && ( yes "l" | git mergetool submod ) && git submodule update -N && @@ -286,7 +308,7 @@ test_expect_success 'file vs modified submodule' ' git submodule update -N && test_must_fail git merge test7 && test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && ( yes "r" | git mergetool submod ) && test -d submod.orig && @@ -301,7 +323,7 @@ test_expect_success 'file vs modified submodule' ' git submodule update -N && test_must_fail git merge test7 && test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && ( yes "l" | git mergetool submod ) && test "$(cat submod/bar)" = "master submodule" && @@ -388,7 +410,7 @@ test_expect_success 'directory vs modified submodule' ' test "$(cat submod/file16)" = "not a submodule" && rm -rf submod.orig && - git reset --hard && + git reset --hard >/dev/null 2>&1 && test_must_fail git merge master && test -n "$(git ls-files -u)" && test ! -e submod.orig && @@ -400,7 +422,7 @@ test_expect_success 'directory vs modified submodule' ' ( cd submod && git clean -f && git reset --hard ) && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git reset --hard && rm -rf submod-movedaside && + git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside && git checkout -b test11.c master && git submodule update -N && @@ -410,7 +432,7 @@ test_expect_success 'directory vs modified submodule' ' git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git reset --hard && + git reset --hard >/dev/null 2>&1 && git submodule update -N && test_must_fail git merge test11 && test -n "$(git ls-files -u)" && @@ -418,7 +440,7 @@ test_expect_success 'directory vs modified submodule' ' ( yes "r" | git mergetool submod ) && test "$(cat submod/file16)" = "not a submodule" && - git reset --hard master && + git reset --hard master >/dev/null 2>&1 && ( cd submod && git clean -f && git reset --hard ) && git submodule update -N ' diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 395adfc8a9..4fb4c9384a 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -38,7 +38,17 @@ restore_test_defaults() prompt_given() { prompt="$1" - test "$prompt" = "Hit return to launch 'test-tool': branch" + test "$prompt" = "Launch 'test-tool' [Y/n]: branch" +} + +stdin_contains() +{ + grep >/dev/null "$1" +} + +stdin_doesnot_contain() +{ + ! stdin_contains "$1" } # Create a file on master and change it on branch @@ -265,4 +275,35 @@ test_expect_success PERL 'difftool --extcmd cat arg2' ' test "$diff" = branch ' +# Create a second file on master and a different version on branch +test_expect_success PERL 'setup with 2 files different' ' + echo m2 >file2 && + git add file2 && + git commit -m "added file2" && + + git checkout branch && + echo br2 >file2 && + git add file2 && + git commit -a -m "branch changed file2" && + git checkout master +' + +test_expect_success PERL 'say no to the first file' ' + diff=$( (echo n; echo) | git difftool -x cat branch ) && + + echo "$diff" | stdin_contains m2 && + echo "$diff" | stdin_contains br2 && + echo "$diff" | stdin_doesnot_contain master && + echo "$diff" | stdin_doesnot_contain branch +' + +test_expect_success PERL 'say no to the second file' ' + diff=$( (echo; echo n) | git difftool -x cat branch ) && + + echo "$diff" | stdin_contains master && + echo "$diff" | stdin_contains branch && + echo "$diff" | stdin_doesnot_contain m2 && + echo "$diff" | stdin_doesnot_contain br2 +' + test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 0d600163c8..81263b7851 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -554,7 +554,6 @@ test_expect_success 'outside of git repository' ' mkdir -p non/git/sub && echo hello >non/git/file1 && echo world >non/git/sub/file2 && - echo ".*o*" >non/git/.gitignore && { echo file1:hello && echo sub/file2:world @@ -571,6 +570,23 @@ test_expect_success 'outside of git repository' ' test_must_fail git grep o && git grep --no-index o >../../actual.sub && test_cmp ../../expect.sub ../../actual.sub + ) && + + echo ".*o*" >non/git/.gitignore && + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non/git" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git grep o && + git grep --no-index --exclude-standard o >../actual.full && + test_cmp ../expect.full ../actual.full && + + { + echo ".gitignore:.*o*" + cat ../expect.full + } >../expect.with.ignored && + git grep --no-index --no-exclude o >../actual.full && + test_cmp ../expect.with.ignored ../actual.full ) ' @@ -583,6 +599,10 @@ test_expect_success 'inside git repository but with --no-index' ' { echo file1:hello && echo sub/file2:world + } >is/expect.unignored && + { + echo ".gitignore:.*o*" && + cat is/expect.unignored } >is/expect.full && : >is/expect.empty && echo file2:world >is/expect.sub && @@ -591,12 +611,24 @@ test_expect_success 'inside git repository but with --no-index' ' git init && test_must_fail git grep o >../actual.full && test_cmp ../expect.empty ../actual.full && + + git grep --untracked o >../actual.unignored && + test_cmp ../expect.unignored ../actual.unignored && + git grep --no-index o >../actual.full && test_cmp ../expect.full ../actual.full && + + git grep --no-index --exclude-standard o >../actual.unignored && + test_cmp ../expect.unignored ../actual.unignored && + cd sub && test_must_fail git grep o >../../actual.sub && test_cmp ../../expect.empty ../../actual.sub && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub && + + git grep --untracked o >../../actual.sub && test_cmp ../../expect.sub ../../actual.sub ) ' diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh index 32ec82ad67..4ee42f12f0 100755 --- a/t/t8006-blame-textconv.sh +++ b/t/t8006-blame-textconv.sh @@ -15,6 +15,7 @@ EOF chmod +x helper test_expect_success 'setup ' ' + echo "bin: test number 0" >zero.bin && echo "bin: test 1" >one.bin && echo "bin: test number 2" >two.bin && if test_have_prereq SYMLINKS; then @@ -43,6 +44,7 @@ test_expect_success 'no filter specified' ' test_expect_success 'setup textconv filters' ' echo "*.bin diff=test" >.gitattributes && + echo "zero.bin eol=crlf" >>.gitattributes && git config diff.test.textconv ./helper && git config diff.test.cachetextconv false ' @@ -74,6 +76,15 @@ test_expect_success 'blame --textconv going through revisions' ' test_cmp expected result ' +test_expect_success 'blame --textconv with local changes' ' + test_when_finished "git checkout zero.bin" && + printf "bin: updated number 0\015" >zero.bin && + git blame --textconv zero.bin >blame && + expect="(Not Committed Yet ....-..-.. ..:..:.. +0000 1)" && + expect="$expect converted: updated number 0" && + expr "$(find_blame <blame)" : "^$expect" +' + test_expect_success 'setup +cachetextconv' ' git config diff.test.cachetextconv true ' diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 579ddb7572..8c12c65c72 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -23,6 +23,7 @@ test_expect_success $PREREQ \ echo do echo " echo \"!\$a!\"" echo "done >commandline\$output" + test_have_prereq MINGW && echo "dos2unix commandline\$output" echo "cat > msgtxt\$output" ) >fake.sendmail && chmod +x ./fake.sendmail && @@ -1168,4 +1169,32 @@ test_expect_success $PREREQ '--force sends cover letter template anyway' ' test -n "$(ls msgtxt*)" ' +test_expect_success $PREREQ 'sendemail.aliasfiletype=mailrc' ' + clean_fake_sendmail && + echo "alias sbd somebody@example.org" >.mailrc && + git config --replace-all sendemail.aliasesfile "$(pwd)/.mailrc" && + git config sendemail.aliasfiletype mailrc && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=sbd \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0001-*.patch \ + 2>errors >out && + grep "^!somebody@example\.org!$" commandline1 +' + +test_expect_success $PREREQ 'sendemail.aliasfile=~/.mailrc' ' + clean_fake_sendmail && + echo "alias sbd someone@example.org" >~/.mailrc && + git config --replace-all sendemail.aliasesfile "~/.mailrc" && + git config sendemail.aliasfiletype mailrc && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=sbd \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0001-*.patch \ + 2>errors >out && + grep "^!someone@example\.org!$" commandline1 +' + test_done diff --git a/t/t9159-git-svn-no-parent-mergeinfo.sh b/t/t9159-git-svn-no-parent-mergeinfo.sh index 85120b70db..69e4815781 100755 --- a/t/t9159-git-svn-no-parent-mergeinfo.sh +++ b/t/t9159-git-svn-no-parent-mergeinfo.sh @@ -2,6 +2,14 @@ test_description='git svn handling of root commits in merge ranges' . ./lib-git-svn.sh +svn_ver="$(svn --version --quiet)" +case $svn_ver in +0.* | 1.[0-4].*) + skip_all="skipping git-svn test - SVN too old ($svn_ver)" + test_done + ;; +esac + test_expect_success 'test handling of root commits in merge ranges' ' mkdir -p init/trunk init/branches init/tags && echo "r1" > init/trunk/file.txt && diff --git a/t/t9162-git-svn-dcommit-interactive.sh b/t/t9162-git-svn-dcommit-interactive.sh new file mode 100755 index 0000000000..e38d9fa37b --- /dev/null +++ b/t/t9162-git-svn-dcommit-interactive.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# +# Copyright (c) 2011 Frédéric Heitzmann + +test_description='git svn dcommit --interactive series' +. ./lib-git-svn.sh + +test_expect_success 'initialize repo' ' + svn_cmd mkdir -m"mkdir test-interactive" "$svnrepo/test-interactive" && + git svn clone "$svnrepo/test-interactive" test-interactive && + cd test-interactive && + touch foo && git add foo && git commit -m"foo: first commit" && + git svn dcommit + ' + +test_expect_success 'answers: y [\n] yes' ' + ( + echo "change #1" >> foo && git commit -a -m"change #1" && + echo "change #2" >> foo && git commit -a -m"change #2" && + echo "change #3" >> foo && git commit -a -m"change #3" && + ( echo "y + +y" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) && + test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn) + ) + ' + +test_expect_success 'answers: yes yes no' ' + ( + echo "change #1" >> foo && git commit -a -m"change #1" && + echo "change #2" >> foo && git commit -a -m"change #2" && + echo "change #3" >> foo && git commit -a -m"change #3" && + ( echo "yes +yes +no" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) && + test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) && + git reset --hard remotes/git-svn + ) + ' + +test_expect_success 'answers: yes quit' ' + ( + echo "change #1" >> foo && git commit -a -m"change #1" && + echo "change #2" >> foo && git commit -a -m"change #2" && + echo "change #3" >> foo && git commit -a -m"change #3" && + ( echo "yes +quit" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) && + test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) && + git reset --hard remotes/git-svn + ) + ' + +test_expect_success 'answers: all' ' + ( + echo "change #1" >> foo && git commit -a -m"change #1" && + echo "change #2" >> foo && git commit -a -m"change #2" && + echo "change #3" >> foo && git commit -a -m"change #3" && + ( echo "all" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) && + test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn) && + git reset --hard remotes/git-svn + ) + ' + +test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 1a6c06631c..438aaf6b14 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -820,6 +820,18 @@ test_expect_success \ 'test 1 = `git rev-list J | wc -l` && test 0 = `git ls-tree J | wc -l`' +cat >input <<INPUT_END +reset refs/heads/J2 + +tag wrong_tag +from refs/heads/J2 +data <<EOF +Tag branch that was reset. +EOF +INPUT_END +test_expect_success \ + 'J: tag must fail on empty branch' \ + 'test_must_fail git fast-import <input' ### ### series K ### @@ -1975,6 +1987,23 @@ test_expect_success \ 'Q: verify second note for second commit' \ 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual' +cat >input <<EOF +reset refs/heads/Q0 + +commit refs/heads/note-Q0 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +Note for an empty branch. +COMMIT + +N inline refs/heads/Q0 +data <<NOTE +some note +NOTE +EOF +test_expect_success \ + 'Q: deny note on empty branch' \ + 'test_must_fail git fast-import <input' ### ### series R (feature and option) ### @@ -2208,7 +2237,7 @@ test_expect_success 'R: cat-blob-fd must be a nonnegative integer' ' test_must_fail git fast-import --cat-blob-fd=-1 </dev/null ' -test_expect_success 'R: print old blob' ' +test_expect_success NOT_MINGW 'R: print old blob' ' blob=$(echo "yes it can" | git hash-object -w --stdin) && cat >expect <<-EOF && ${blob} blob 11 @@ -2220,7 +2249,7 @@ test_expect_success 'R: print old blob' ' test_cmp expect actual ' -test_expect_success 'R: in-stream cat-blob-fd not respected' ' +test_expect_success NOT_MINGW 'R: in-stream cat-blob-fd not respected' ' echo hello >greeting && blob=$(git hash-object -w greeting) && cat >expect <<-EOF && @@ -2241,7 +2270,7 @@ test_expect_success 'R: in-stream cat-blob-fd not respected' ' test_cmp expect actual.1 ' -test_expect_success 'R: print new blob' ' +test_expect_success NOT_MINGW 'R: print new blob' ' blob=$(echo "yep yep yep" | git hash-object --stdin) && cat >expect <<-EOF && ${blob} blob 12 @@ -2259,7 +2288,7 @@ test_expect_success 'R: print new blob' ' test_cmp expect actual ' -test_expect_success 'R: print new blob by sha1' ' +test_expect_success NOT_MINGW 'R: print new blob by sha1' ' blob=$(echo "a new blob named by sha1" | git hash-object --stdin) && cat >expect <<-EOF && ${blob} blob 25 diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh index 463254c727..83acf68bc3 100755 --- a/t/t9301-fast-import-notes.sh +++ b/t/t9301-fast-import-notes.sh @@ -505,9 +505,63 @@ test_expect_success 'verify that non-notes are untouched by a fanout change' ' test_cmp expect_non-note3 actual ' + +# Change the notes for the three top commits +test_tick +cat >input <<INPUT_END +commit refs/notes/many_notes +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +changing notes for the top three commits +COMMIT +from refs/notes/many_notes^0 +INPUT_END + +rm expect +i=$num_commits +j=0 +while test $j -lt 3 +do + cat >>input <<INPUT_END +N inline refs/heads/many_commits~$j +data <<EOF +changed note for commit #$i +EOF +INPUT_END + cat >>expect <<EXPECT_END + commit #$i + changed note for commit #$i +EXPECT_END + i=$(($i - 1)) + j=$(($j + 1)) +done + +test_expect_success 'change a few existing notes' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/many_notes git log -n3 refs/heads/many_commits | + grep "^ " > actual && + test_cmp expect actual + +' + +test_expect_success 'verify that changing notes respect existing fanout' ' + + # None of the entries in the top-level notes tree should be a full SHA1 + git ls-tree --name-only refs/notes/many_notes | + while read path + do + if test $(expr length "$path") -ge 40 + then + return 1 + fi + done + +' + remaining_notes=10 test_tick -cat >>input <<INPUT_END +cat >input <<INPUT_END commit refs/notes/many_notes committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -516,12 +570,11 @@ COMMIT from refs/notes/many_notes^0 INPUT_END -i=$remaining_notes -while test $i -lt $num_commits +i=$(($num_commits - $remaining_notes)) +for sha1 in $(git rev-list -n $i refs/heads/many_commits) do - i=$(($i + 1)) cat >>input <<INPUT_END -N 0000000000000000000000000000000000000000 :$i +N 0000000000000000000000000000000000000000 $sha1 INPUT_END done diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 53297156a3..ab24917812 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -274,6 +274,53 @@ test_expect_success \ 'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"' # ---------------------------------------------------------------------- +# commitdiff testing (incomplete lines) + +test_expect_success 'setup incomplete lines' ' + cat >file<<-\EOF && + Dominus regit me, + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + CHANGE_ME + EOF + git commit -a -m "Preparing for incomplete lines" && + echo "incomplete" | tr -d "\\012" >>file && + git commit -a -m "Add incomplete line" && + git tag incomplete_lines_add && + sed -e s/CHANGE_ME/change_me/ <file >file+ && + mv -f file+ file && + git commit -a -m "Incomplete context line" && + git tag incomplete_lines_ctx && + echo "Dominus regit me," >file && + echo "incomplete line" | tr -d "\\012" >>file && + git commit -a -m "Change incomplete line" && + git tag incomplete_lines_chg + echo "Dominus regit me," >file && + git commit -a -m "Remove incomplete line" && + git tag incomplete_lines_rem +' + +test_expect_success 'commitdiff(1): addition of incomplete line' ' + gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add" +' + +test_expect_success 'commitdiff(1): incomplete line as context line' ' + gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx" +' + +test_expect_success 'commitdiff(1): change incomplete line' ' + gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg" +' + +test_expect_success 'commitdiff(1): removal of incomplete line' ' + gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem" +' + +# ---------------------------------------------------------------------- # commit, commitdiff: merge, large test_expect_success \ 'Create a merge' \ @@ -282,7 +329,8 @@ test_expect_success \ git add b && git commit -a -m "On branch" && git checkout master && - git pull . b' + git pull . b && + git tag merge_commit' test_expect_success \ 'commit(0): merge commit' \ @@ -332,6 +380,29 @@ test_expect_success \ 'gitweb_run "p=.git;a=commitdiff;h=b"' # ---------------------------------------------------------------------- +# side-by-side diff + +test_expect_success 'side-by-side: addition of incomplete line' ' + gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add;ds=sidebyside" +' + +test_expect_success 'side-by-side: incomplete line as context line' ' + gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx;ds=sidebyside" +' + +test_expect_success 'side-by-side: changed incomplete line' ' + gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg;ds=sidebyside" +' + +test_expect_success 'side-by-side: removal of incomplete line' ' + gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem;ds=sidebyside" +' + +test_expect_success 'side-by-side: merge commit' ' + gitweb_run "p=.git;a=commitdiff;h=merge_commit;ds=sidebyside" +' + +# ---------------------------------------------------------------------- # tags testing test_expect_success \ diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh index 3787186703..435d896476 100755 --- a/t/t9700-perl-git.sh +++ b/t/t9700-perl-git.sh @@ -43,7 +43,11 @@ test_expect_success \ git config --add test.booltrue true && git config --add test.boolfalse no && git config --add test.boolother other && - git config --add test.int 2k + git config --add test.int 2k && + git config --add test.path "~/foo" && + git config --add test.pathexpanded "$HOME/foo" && + git config --add test.pathmulti foo && + git config --add test.pathmulti bar ' # The external test will outputs its own plan diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 13ba96e21a..3b9b48408a 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -33,6 +33,10 @@ is($r->config_int("test.int"), 2048, "config_int: integer"); is($r->config_int("test.nonexistent"), undef, "config_int: nonexistent"); ok($r->config_bool("test.booltrue"), "config_bool: true"); ok(!$r->config_bool("test.boolfalse"), "config_bool: false"); +is($r->config_path("test.path"), $r->config("test.pathexpanded"), + "config_path: ~/foo expansion"); +is_deeply([$r->config_path("test.pathmulti")], ["foo", "bar"], + "config_path: multiple values"); our $ansi_green = "\x1b[32m"; is($r->get_color("color.test.slot1", "red"), $ansi_green, "get_color"); # Cannot test $r->get_colorbool("color.foo")) because we do not diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh index 01ba041fde..272de3fea3 100755 --- a/t/t9800-git-p4.sh +++ b/t/t9800-git-p4.sh @@ -2,76 +2,51 @@ test_description='git-p4 tests' -. ./test-lib.sh +. ./lib-git-p4.sh -( p4 -h && p4d -h ) >/dev/null 2>&1 || { - skip_all='skipping git-p4 tests; no p4 or p4d' - test_done -} - -GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4 -P4DPORT=10669 - -export P4PORT=localhost:$P4DPORT - -db="$TRASH_DIRECTORY/db" -cli="$TRASH_DIRECTORY/cli" -git="$TRASH_DIRECTORY/git" - -test_debug 'echo p4d -q -d -r "$db" -p $P4DPORT' -test_expect_success setup ' - mkdir -p "$db" && - p4d -q -d -r "$db" -p $P4DPORT && - mkdir -p "$cli" && - mkdir -p "$git" && - export P4PORT=localhost:$P4DPORT +test_expect_success 'start p4d' ' + start_p4d ' test_expect_success 'add p4 files' ' - cd "$cli" && - p4 client -i <<-EOF && - Client: client - Description: client - Root: $cli - View: //depot/... //client/... - EOF - export P4CLIENT=client && - echo file1 >file1 && - p4 add file1 && - p4 submit -d "file1" && - echo file2 >file2 && - p4 add file2 && - p4 submit -d "file2" && - cd "$TRASH_DIRECTORY" + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "file1" && + echo file2 >file2 && + p4 add file2 && + p4 submit -d "file2" + ) ' -cleanup_git() { - cd "$TRASH_DIRECTORY" && - rm -rf "$git" && - mkdir "$git" -} - test_expect_success 'basic git-p4 clone' ' "$GITP4" clone --dest="$git" //depot && test_when_finished cleanup_git && - cd "$git" && - git log --oneline >lines && - test_line_count = 1 lines + ( + cd "$git" && + git log --oneline >lines && + test_line_count = 1 lines + ) ' test_expect_success 'git-p4 clone @all' ' "$GITP4" clone --dest="$git" //depot@all && test_when_finished cleanup_git && - cd "$git" && - git log --oneline >lines && - test_line_count = 2 lines + ( + cd "$git" && + git log --oneline >lines && + test_line_count = 2 lines + ) ' test_expect_success 'git-p4 sync uninitialized repo' ' test_create_repo "$git" && test_when_finished cleanup_git && - cd "$git" && - test_must_fail "$GITP4" sync + ( + cd "$git" && + test_must_fail "$GITP4" sync + ) ' # @@ -81,17 +56,19 @@ test_expect_success 'git-p4 sync uninitialized repo' ' test_expect_success 'git-p4 sync new branch' ' test_create_repo "$git" && test_when_finished cleanup_git && - cd "$git" && - test_commit head && - "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all && - git log --oneline p4/depot >lines && - test_line_count = 2 lines + ( + cd "$git" && + test_commit head && + "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all && + git log --oneline p4/depot >lines && + test_line_count = 2 lines + ) ' test_expect_success 'exit when p4 fails to produce marshaled output' ' badp4dir="$TRASH_DIRECTORY/badp4dir" && - mkdir -p "$badp4dir" && - test_when_finished "rm -rf $badp4dir" && + mkdir "$badp4dir" && + test_when_finished "rm \"$badp4dir/p4\" && rmdir \"$badp4dir\"" && cat >"$badp4dir"/p4 <<-EOF && #!$SHELL_PATH exit 1 @@ -103,61 +80,61 @@ test_expect_success 'exit when p4 fails to produce marshaled output' ' ' test_expect_success 'add p4 files with wildcards in the names' ' - cd "$cli" && - echo file-wild-hash >file-wild#hash && - echo file-wild-star >file-wild\*star && - echo file-wild-at >file-wild@at && - echo file-wild-percent >file-wild%percent && - p4 add -f file-wild* && - p4 submit -d "file wildcards" + ( + cd "$cli" && + echo file-wild-hash >file-wild#hash && + echo file-wild-star >file-wild\*star && + echo file-wild-at >file-wild@at && + echo file-wild-percent >file-wild%percent && + p4 add -f file-wild* && + p4 submit -d "file wildcards" + ) ' test_expect_success 'wildcard files git-p4 clone' ' "$GITP4" clone --dest="$git" //depot && test_when_finished cleanup_git && - cd "$git" && - test -f file-wild#hash && - test -f file-wild\*star && - test -f file-wild@at && - test -f file-wild%percent + ( + cd "$git" && + test -f file-wild#hash && + test -f file-wild\*star && + test -f file-wild@at && + test -f file-wild%percent + ) ' test_expect_success 'clone bare' ' "$GITP4" clone --dest="$git" --bare //depot && test_when_finished cleanup_git && - cd "$git" && - test ! -d .git && - bare=`git config --get core.bare` && - test "$bare" = true + ( + cd "$git" && + test ! -d .git && + bare=`git config --get core.bare` && + test "$bare" = true + ) ' p4_add_user() { - name=$1 - fullname=$2 - p4 user -f -i <<EOF && -User: $name -Email: $name@localhost -FullName: $fullname -EOF - p4 passwd -P secret $name + name=$1 fullname=$2 && + p4 user -f -i <<-EOF && + User: $name + Email: $name@localhost + FullName: $fullname + EOF + p4 passwd -P secret $name } p4_grant_admin() { - name=$1 - p4 protect -o |\ - awk "{print}END{print \" admin user $name * //depot/...\"}" |\ - p4 protect -i + name=$1 && + { + p4 protect -o && + echo " admin user $name * //depot/..." + } | p4 protect -i } p4_check_commit_author() { - file=$1 - user=$2 - if p4 changes -m 1 //depot/$file | grep $user > /dev/null ; then - return 0 - else - echo "file $file not modified by user $user" 1>&2 - return 1 - fi + file=$1 user=$2 && + p4 changes -m 1 //depot/$file | grep -q $user } make_change_by_user() { @@ -174,15 +151,17 @@ test_expect_success 'preserve users' ' p4_grant_admin alice && "$GITP4" clone --dest="$git" //depot && test_when_finished cleanup_git && - cd "$git" && - echo "username: a change by alice" >> file1 && - echo "username: a change by bob" >> file2 && - git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 && - git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 && - git config git-p4.skipSubmitEditCheck true && - P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user && - p4_check_commit_author file1 alice && - p4_check_commit_author file2 bob + ( + cd "$git" && + echo "username: a change by alice" >>file1 && + echo "username: a change by bob" >>file2 && + git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 && + git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 && + git config git-p4.skipSubmitEditCheck true && + P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user && + p4_check_commit_author file1 alice && + p4_check_commit_author file2 bob + ) ' # Test username support, submitting as bob, who lacks admin rights. Should @@ -190,32 +169,37 @@ test_expect_success 'preserve users' ' test_expect_success 'refuse to preserve users without perms' ' "$GITP4" clone --dest="$git" //depot && test_when_finished cleanup_git && - cd "$git" && - git config git-p4.skipSubmitEditCheck true && - echo "username-noperms: a change by alice" >> file1 && - git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 && - ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user && - ! git diff --exit-code HEAD..p4/master > /dev/null + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + echo "username-noperms: a change by alice" >>file1 && + git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 && + P4EDITOR=touch P4USER=bob P4PASSWD=secret test_must_fail "$GITP4" commit --preserve-user && + test_must_fail git diff --exit-code HEAD..p4/master + ) ' # What happens with unknown author? Without allowMissingP4Users it should fail. test_expect_success 'preserve user where author is unknown to p4' ' "$GITP4" clone --dest="$git" //depot && test_when_finished cleanup_git && - cd "$git" && - git config git-p4.skipSubmitEditCheck true && - echo "username-bob: a change by bob" >> file1 && - git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 && - echo "username-unknown: a change by charlie" >> file1 && - git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 && - ! P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user && - ! git diff --exit-code HEAD..p4/master > /dev/null && - echo "$0: repeat with allowMissingP4Users enabled" && - git config git-p4.allowMissingP4Users true && - git config git-p4.preserveUser true && - P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit && - git diff --exit-code HEAD..p4/master > /dev/null && - p4_check_commit_author file1 alice + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + echo "username-bob: a change by bob" >>file1 && + git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 && + echo "username-unknown: a change by charlie" >>file1 && + git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 && + P4EDITOR=touch P4USER=alice P4PASSWD=secret test_must_fail "$GITP4" commit --preserve-user && + test_must_fail git diff --exit-code HEAD..p4/master && + + echo "$0: repeat with allowMissingP4Users enabled" && + git config git-p4.allowMissingP4Users true && + git config git-p4.preserveUser true && + P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit && + git diff --exit-code HEAD..p4/master && + p4_check_commit_author file1 alice + ) ' # If we're *not* using --preserve-user, git-p4 should warn if we're submitting @@ -225,33 +209,35 @@ test_expect_success 'preserve user where author is unknown to p4' ' test_expect_success 'not preserving user with mixed authorship' ' "$GITP4" clone --dest="$git" //depot && test_when_finished cleanup_git && - cd "$git" && - git config git-p4.skipSubmitEditCheck true && - p4_add_user derek Derek && - - make_change_by_user usernamefile3 Derek derek@localhost && - P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && - grep "git author derek@localhost does not match" actual && - - make_change_by_user usernamefile3 Charlie charlie@localhost && - P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && - grep "git author charlie@localhost does not match" actual && - - make_change_by_user usernamefile3 alice alice@localhost && - P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && - ! grep "git author.*does not match" actual && - - git config git-p4.skipUserNameCheck true && - make_change_by_user usernamefile3 Charlie charlie@localhost && - P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && - ! grep "git author.*does not match" actual && - - p4_check_commit_author usernamefile3 alice + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + p4_add_user derek Derek && + + make_change_by_user usernamefile3 Derek derek@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\ + grep "git author derek@localhost does not match" && + + make_change_by_user usernamefile3 Charlie charlie@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\ + grep "git author charlie@localhost does not match" && + + make_change_by_user usernamefile3 alice alice@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" |\ + test_must_fail grep "git author.*does not match" && + + git config git-p4.skipUserNameCheck true && + make_change_by_user usernamefile3 Charlie charlie@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\ + test_must_fail grep "git author.*does not match" && + + p4_check_commit_author usernamefile3 alice + ) ' marshal_dump() { what=$1 - python -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]' + "$PYTHON_PATH" -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]' } # Sleep a bit so that the top-most p4 change did not happen "now". Then @@ -263,10 +249,12 @@ test_expect_success 'initial import time from top change time' ' sleep 3 && "$GITP4" clone --dest="$git" //depot && test_when_finished cleanup_git && - cd "$git" && - gittime=$(git show -s --raw --pretty=format:%at HEAD) && - echo $p4time $gittime && - test $p4time = $gittime + ( + cd "$git" && + gittime=$(git show -s --raw --pretty=format:%at HEAD) && + echo $p4time $gittime && + test $p4time = $gittime + ) ' # Rename a file and confirm that rename is not detected in P4. @@ -279,47 +267,49 @@ test_expect_success 'initial import time from top change time' ' test_expect_success 'detect renames' ' "$GITP4" clone --dest="$git" //depot@all && test_when_finished cleanup_git && - cd "$git" && - git config git-p4.skipSubmitEditCheck true && - - git mv file1 file4 && - git commit -a -m "Rename file1 to file4" && - git diff-tree -r -M HEAD && - "$GITP4" submit && - p4 filelog //depot/file4 && - ! p4 filelog //depot/file4 | grep -q "branch from" && - - git mv file4 file5 && - git commit -a -m "Rename file4 to file5" && - git diff-tree -r -M HEAD && - git config git-p4.detectRenames true && - "$GITP4" submit && - p4 filelog //depot/file5 && - p4 filelog //depot/file5 | grep -q "branch from //depot/file4" && - - git mv file5 file6 && - echo update >>file6 && - git add file6 && - git commit -a -m "Rename file5 to file6 with changes" && - git diff-tree -r -M HEAD && - level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") && - test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 && - git config git-p4.detectRenames $((level + 2)) && - "$GITP4" submit && - p4 filelog //depot/file6 && - ! p4 filelog //depot/file6 | grep -q "branch from" && - - git mv file6 file7 && - echo update >>file7 && - git add file7 && - git commit -a -m "Rename file6 to file7 with changes" && - git diff-tree -r -M HEAD && - level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") && - test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 && - git config git-p4.detectRenames $((level - 2)) && - "$GITP4" submit && - p4 filelog //depot/file7 && - p4 filelog //depot/file7 | grep -q "branch from //depot/file6" + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + + git mv file1 file4 && + git commit -a -m "Rename file1 to file4" && + git diff-tree -r -M HEAD && + "$GITP4" submit && + p4 filelog //depot/file4 && + p4 filelog //depot/file4 | test_must_fail grep -q "branch from" && + + git mv file4 file5 && + git commit -a -m "Rename file4 to file5" && + git diff-tree -r -M HEAD && + git config git-p4.detectRenames true && + "$GITP4" submit && + p4 filelog //depot/file5 && + p4 filelog //depot/file5 | grep -q "branch from //depot/file4" && + + git mv file5 file6 && + echo update >>file6 && + git add file6 && + git commit -a -m "Rename file5 to file6 with changes" && + git diff-tree -r -M HEAD && + level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") && + test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 && + git config git-p4.detectRenames $(($level + 2)) && + "$GITP4" submit && + p4 filelog //depot/file6 && + p4 filelog //depot/file6 | test_must_fail grep -q "branch from" && + + git mv file6 file7 && + echo update >>file7 && + git add file7 && + git commit -a -m "Rename file6 to file7 with changes" && + git diff-tree -r -M HEAD && + level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") && + test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 && + git config git-p4.detectRenames $(($level - 2)) && + "$GITP4" submit && + p4 filelog //depot/file7 && + p4 filelog //depot/file7 | grep -q "branch from //depot/file6" + ) ' # Copy a file and confirm that copy is not detected in P4. @@ -336,141 +326,79 @@ test_expect_success 'detect renames' ' test_expect_success 'detect copies' ' "$GITP4" clone --dest="$git" //depot@all && test_when_finished cleanup_git && - cd "$git" && - git config git-p4.skipSubmitEditCheck true && - - cp file2 file8 && - git add file8 && - git commit -a -m "Copy file2 to file8" && - git diff-tree -r -C HEAD && - "$GITP4" submit && - p4 filelog //depot/file8 && - ! p4 filelog //depot/file8 | grep -q "branch from" && - - cp file2 file9 && - git add file9 && - git commit -a -m "Copy file2 to file9" && - git diff-tree -r -C HEAD && - git config git-p4.detectCopies true && - "$GITP4" submit && - p4 filelog //depot/file9 && - ! p4 filelog //depot/file9 | grep -q "branch from" && - - echo "file2" >>file2 && - cp file2 file10 && - git add file2 file10 && - git commit -a -m "Modify and copy file2 to file10" && - git diff-tree -r -C HEAD && - "$GITP4" submit && - p4 filelog //depot/file10 && - p4 filelog //depot/file10 | grep -q "branch from //depot/file" && - - cp file2 file11 && - git add file11 && - git commit -a -m "Copy file2 to file11" && - git diff-tree -r -C --find-copies-harder HEAD && - src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && - test "$src" = file10 && - git config git-p4.detectCopiesHarder true && - "$GITP4" submit && - p4 filelog //depot/file11 && - p4 filelog //depot/file11 | grep -q "branch from //depot/file" && - - cp file2 file12 && - echo "some text" >>file12 && - git add file12 && - git commit -a -m "Copy file2 to file12 with changes" && - git diff-tree -r -C --find-copies-harder HEAD && - level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") && - test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 && - src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && - test "$src" = file10 && - git config git-p4.detectCopies $((level + 2)) && - "$GITP4" submit && - p4 filelog //depot/file12 && - ! p4 filelog //depot/file12 | grep -q "branch from" && - - cp file2 file13 && - echo "different text" >>file13 && - git add file13 && - git commit -a -m "Copy file2 to file13 with changes" && - git diff-tree -r -C --find-copies-harder HEAD && - level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") && - test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 && - src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && - test "$src" = file10 && - git config git-p4.detectCopies $((level - 2)) && - "$GITP4" submit && - p4 filelog //depot/file13 && - p4 filelog //depot/file13 | grep -q "branch from //depot/file" -' - -# Create a simple branch structure in P4 depot to check if it is correctly -# cloned. -test_expect_success 'add simple p4 branches' ' - cd "$cli" && - mkdir branch1 && - cd branch1 && - echo file1 >file1 && - echo file2 >file2 && - p4 add file1 file2 && - p4 submit -d "branch1" && - p4 integrate //depot/branch1/... //depot/branch2/... && - p4 submit -d "branch2" && - echo file3 >file3 && - p4 add file3 && - p4 submit -d "add file3 in branch1" && - p4 open file2 && - echo update >>file2 && - p4 submit -d "update file2 in branch1" && - p4 integrate //depot/branch1/... //depot/branch3/... && - p4 submit -d "branch3" && - cd "$TRASH_DIRECTORY" -' - -# Configure branches through git-config and clone them. -# All files are tested to make sure branches were cloned correctly. -# Finally, make an update to branch1 on P4 side to check if it is imported -# correctly by git-p4. -test_expect_success 'git-p4 clone simple branches' ' - test_when_finished cleanup_git && - test_create_repo "$git" && - cd "$git" && - git config git-p4.branchList branch1:branch2 && - git config --add git-p4.branchList branch1:branch3 && - "$GITP4" clone --dest=. --detect-branches //depot@all && - git log --all --graph --decorate --stat && - git reset --hard p4/depot/branch1 && - test -f file1 && - test -f file2 && - test -f file3 && - grep -q update file2 && - git reset --hard p4/depot/branch2 && - test -f file1 && - test -f file2 && - test ! -f file3 && - ! grep -q update file2 && - git reset --hard p4/depot/branch3 && - test -f file1 && - test -f file2 && - test -f file3 && - grep -q update file2 && - cd "$cli" && - cd branch1 && - p4 edit file2 && - echo file2_ >>file2 && - p4 submit -d "update file2 in branch1" && - cd "$git" && - git reset --hard p4/depot/branch1 && - "$GITP4" rebase && - grep -q file2_ file2 + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + + cp file2 file8 && + git add file8 && + git commit -a -m "Copy file2 to file8" && + git diff-tree -r -C HEAD && + "$GITP4" submit && + p4 filelog //depot/file8 && + p4 filelog //depot/file8 | test_must_fail grep -q "branch from" && + + cp file2 file9 && + git add file9 && + git commit -a -m "Copy file2 to file9" && + git diff-tree -r -C HEAD && + git config git-p4.detectCopies true && + "$GITP4" submit && + p4 filelog //depot/file9 && + p4 filelog //depot/file9 | test_must_fail grep -q "branch from" && + + echo "file2" >>file2 && + cp file2 file10 && + git add file2 file10 && + git commit -a -m "Modify and copy file2 to file10" && + git diff-tree -r -C HEAD && + "$GITP4" submit && + p4 filelog //depot/file10 && + p4 filelog //depot/file10 | grep -q "branch from //depot/file" && + + cp file2 file11 && + git add file11 && + git commit -a -m "Copy file2 to file11" && + git diff-tree -r -C --find-copies-harder HEAD && + src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && + test "$src" = file10 && + git config git-p4.detectCopiesHarder true && + "$GITP4" submit && + p4 filelog //depot/file11 && + p4 filelog //depot/file11 | grep -q "branch from //depot/file" && + + cp file2 file12 && + echo "some text" >>file12 && + git add file12 && + git commit -a -m "Copy file2 to file12 with changes" && + git diff-tree -r -C --find-copies-harder HEAD && + level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") && + test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 && + src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && + test "$src" = file10 && + git config git-p4.detectCopies $(($level + 2)) && + "$GITP4" submit && + p4 filelog //depot/file12 && + p4 filelog //depot/file12 | test_must_fail grep -q "branch from" && + + cp file2 file13 && + echo "different text" >>file13 && + git add file13 && + git commit -a -m "Copy file2 to file13 with changes" && + git diff-tree -r -C --find-copies-harder HEAD && + level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") && + test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 && + src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && + test "$src" = file10 && + git config git-p4.detectCopies $(($level - 2)) && + "$GITP4" submit && + p4 filelog //depot/file13 && + p4 filelog //depot/file13 | grep -q "branch from //depot/file" + ) ' -test_expect_success 'shutdown' ' - pid=`pgrep -f p4d` && - test -n "$pid" && - test_debug "ps wl `echo $pid`" && - kill $pid +test_expect_success 'kill p4d' ' + kill_p4d ' test_done diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh new file mode 100755 index 0000000000..a25f18d36a --- /dev/null +++ b/t/t9801-git-p4-branch.sh @@ -0,0 +1,233 @@ +#!/bin/sh + +test_description='git-p4 p4 branching tests' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +# +# 1: //depot/main/f1 +# 2: //depot/main/f2 +# 3: integrate //depot/main/... -> //depot/branch1/... +# 4: //depot/main/f4 +# 5: //depot/branch1/f5 +# .: named branch branch2 +# 6: integrate -b branch2 +# 7: //depot/branch2/f7 +# 8: //depot/main/f8 +# +test_expect_success 'basic p4 branches' ' + ( + cd "$cli" && + mkdir -p main && + + echo f1 >main/f1 && + p4 add main/f1 && + p4 submit -d "main/f1" && + + echo f2 >main/f2 && + p4 add main/f2 && + p4 submit -d "main/f2" && + + p4 integrate //depot/main/... //depot/branch1/... && + p4 submit -d "integrate main to branch1" && + + echo f4 >main/f4 && + p4 add main/f4 && + p4 submit -d "main/f4" && + + echo f5 >branch1/f5 && + p4 add branch1/f5 && + p4 submit -d "branch1/f5" && + + p4 branch -i <<-EOF && + Branch: branch2 + View: //depot/main/... //depot/branch2/... + EOF + + p4 integrate -b branch2 && + p4 submit -d "integrate main to branch2" && + + echo f7 >branch2/f7 && + p4 add branch2/f7 && + p4 submit -d "branch2/f7" && + + echo f8 >main/f8 && + p4 add main/f8 && + p4 submit -d "main/f8" + ) +' + +test_expect_success 'import main, no branch detection' ' + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" //depot/main@all && + ( + cd "$git" && + git log --oneline --graph --decorate --all && + git rev-list master >wc && + test_line_count = 4 wc + ) +' + +test_expect_success 'import branch1, no branch detection' ' + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" //depot/branch1@all && + ( + cd "$git" && + git log --oneline --graph --decorate --all && + git rev-list master >wc && + test_line_count = 2 wc + ) +' + +test_expect_success 'import branch2, no branch detection' ' + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" //depot/branch2@all && + ( + cd "$git" && + git log --oneline --graph --decorate --all && + git rev-list master >wc && + test_line_count = 2 wc + ) +' + +test_expect_success 'import depot, no branch detection' ' + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" //depot@all && + ( + cd "$git" && + git log --oneline --graph --decorate --all && + git rev-list master >wc && + test_line_count = 8 wc + ) +' + +test_expect_success 'import depot, branch detection' ' + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" --detect-branches //depot@all && + ( + cd "$git" && + + git log --oneline --graph --decorate --all && + + # 4 main commits + git rev-list master >wc && + test_line_count = 4 wc && + + # 3 main, 1 integrate, 1 on branch2 + git rev-list p4/depot/branch2 >wc && + test_line_count = 5 wc && + + # no branch1, since no p4 branch created for it + test_must_fail git show-ref p4/depot/branch1 + ) +' + +test_expect_success 'import depot, branch detection, branchList branch definition' ' + test_when_finished cleanup_git && + test_create_repo "$git" && + ( + cd "$git" && + git config git-p4.branchList main:branch1 && + "$GITP4" clone --dest=. --detect-branches //depot@all && + + git log --oneline --graph --decorate --all && + + # 4 main commits + git rev-list master >wc && + test_line_count = 4 wc && + + # 3 main, 1 integrate, 1 on branch2 + git rev-list p4/depot/branch2 >wc && + test_line_count = 5 wc && + + # 2 main, 1 integrate, 1 on branch1 + git rev-list p4/depot/branch1 >wc && + test_line_count = 4 wc + ) +' + +test_expect_success 'restart p4d' ' + kill_p4d && + start_p4d +' + +# +# 1: //depot/branch1/file1 +# //depot/branch1/file2 +# 2: integrate //depot/branch1/... -> //depot/branch2/... +# 3: //depot/branch1/file3 +# 4: //depot/branch1/file2 (edit) +# 5: integrate //depot/branch1/... -> //depot/branch3/... +# +## Create a simple branch structure in P4 depot. +test_expect_success 'add simple p4 branches' ' + ( + cd "$cli" && + mkdir branch1 && + cd branch1 && + echo file1 >file1 && + echo file2 >file2 && + p4 add file1 file2 && + p4 submit -d "branch1" && + p4 integrate //depot/branch1/... //depot/branch2/... && + p4 submit -d "branch2" && + echo file3 >file3 && + p4 add file3 && + p4 submit -d "add file3 in branch1" && + p4 open file2 && + echo update >>file2 && + p4 submit -d "update file2 in branch1" && + p4 integrate //depot/branch1/... //depot/branch3/... && + p4 submit -d "branch3" + ) +' + +# Configure branches through git-config and clone them. +# All files are tested to make sure branches were cloned correctly. +# Finally, make an update to branch1 on P4 side to check if it is imported +# correctly by git-p4. +test_expect_success 'git-p4 clone simple branches' ' + test_when_finished cleanup_git && + test_create_repo "$git" && + ( + cd "$git" && + git config git-p4.branchList branch1:branch2 && + git config --add git-p4.branchList branch1:branch3 && + "$GITP4" clone --dest=. --detect-branches //depot@all && + git log --all --graph --decorate --stat && + git reset --hard p4/depot/branch1 && + test -f file1 && + test -f file2 && + test -f file3 && + grep -q update file2 && + git reset --hard p4/depot/branch2 && + test -f file1 && + test -f file2 && + test ! -f file3 && + test_must_fail grep -q update file2 && + git reset --hard p4/depot/branch3 && + test -f file1 && + test -f file2 && + test -f file3 && + grep -q update file2 && + cd "$cli" && + cd branch1 && + p4 edit file2 && + echo file2_ >>file2 && + p4 submit -d "update file2 in branch3" && + cd "$git" && + git reset --hard p4/depot/branch1 && + "$GITP4" rebase && + grep -q file2_ file2 + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh new file mode 100755 index 0000000000..992bb8cf0b --- /dev/null +++ b/t/t9802-git-p4-filetype.sh @@ -0,0 +1,139 @@ +#!/bin/sh + +test_description='git-p4 p4 filetype tests' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'utf-16 file create' ' + ( + cd "$cli" && + + # p4 saves this verbatim + printf "three\nline\ntext\n" >f-ascii && + p4 add -t text f-ascii && + + # p4 adds \377\376 header + cp f-ascii f-ascii-as-utf16 && + p4 add -t utf16 f-ascii-as-utf16 && + + # p4 saves this exactly as iconv produced it + printf "three\nline\ntext\n" | iconv -f ascii -t utf-16 >f-utf16 && + p4 add -t utf16 f-utf16 && + + # this also is unchanged + cp f-utf16 f-utf16-as-text && + p4 add -t text f-utf16-as-text && + + p4 submit -d "f files" && + + # force update of client files + p4 sync -f + ) +' + +test_expect_success 'utf-16 file test' ' + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" //depot@all && + ( + cd "$git" && + + test_cmp "$cli/f-ascii" f-ascii && + test_cmp "$cli/f-ascii-as-utf16" f-ascii-as-utf16 && + test_cmp "$cli/f-utf16" f-utf16 && + test_cmp "$cli/f-utf16-as-text" f-utf16-as-text + ) +' + +test_expect_success 'keyword file create' ' + ( + cd "$cli" && + + printf "id\n\$Id\$\n\$Author\$\ntext\n" >k-text-k && + p4 add -t text+k k-text-k && + + cp k-text-k k-text-ko && + p4 add -t text+ko k-text-ko && + + cat k-text-k | iconv -f ascii -t utf-16 >k-utf16-k && + p4 add -t utf16+k k-utf16-k && + + cp k-utf16-k k-utf16-ko && + p4 add -t utf16+ko k-utf16-ko && + + p4 submit -d "k files" && + p4 sync -f + ) +' + +build_smush() { + cat >k_smush.py <<-\EOF && + import re, sys + sys.stdout.write(re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', sys.stdin.read())) + EOF + cat >ko_smush.py <<-\EOF + import re, sys + sys.stdout.write(re.sub(r'(?i)\$(Id|Header):[^$]*\$', r'$\1$', sys.stdin.read())) + EOF +} + +test_expect_success 'keyword file test' ' + build_smush && + test_when_finished rm -f k_smush.py ko_smush.py && + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" //depot@all && + ( + cd "$git" && + + # text, ensure unexpanded + "$PYTHON_PATH" "$TRASH_DIRECTORY/k_smush.py" <"$cli/k-text-k" >cli-k-text-k-smush && + test_cmp cli-k-text-k-smush k-text-k && + "$PYTHON_PATH" "$TRASH_DIRECTORY/ko_smush.py" <"$cli/k-text-ko" >cli-k-text-ko-smush && + test_cmp cli-k-text-ko-smush k-text-ko && + + # utf16, even though p4 expands keywords, git-p4 does not + # try to undo that + test_cmp "$cli/k-utf16-k" k-utf16-k && + test_cmp "$cli/k-utf16-ko" k-utf16-ko + ) +' + +build_gendouble() { + cat >gendouble.py <<-\EOF + import sys + import struct + import array + + s = array.array("c", '\0' * 26) + struct.pack_into(">L", s, 0, 0x00051607) # AppleDouble + struct.pack_into(">L", s, 4, 0x00020000) # version 2 + s.tofile(sys.stdout) + EOF +} + +test_expect_success 'ignore apple' ' + test_when_finished rm -f gendouble.py && + build_gendouble && + ( + cd "$cli" && + test-genrandom apple 1024 >double.png && + "$PYTHON_PATH" "$TRASH_DIRECTORY/gendouble.py" >%double.png && + p4 add -t apple double.png && + p4 submit -d appledouble + ) && + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" //depot@all && + ( + cd "$git" && + test ! -f double.png + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9803-git-shell-metachars.sh b/t/t9803-git-shell-metachars.sh new file mode 100755 index 0000000000..db04375a13 --- /dev/null +++ b/t/t9803-git-shell-metachars.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +test_description='git-p4 transparency to shell metachars in filenames' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot' ' + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "file1" + ) +' + +test_expect_success 'shell metachars in filenames' ' + "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + echo f1 >foo\$bar && + git add foo\$bar && + echo f2 >"file with spaces" && + git add "file with spaces" && + git commit -m "add files" && + P4EDITOR=touch "$GITP4" submit + ) && + ( + cd "$cli" && + p4 sync ... && + test -e "file with spaces" && + test -e "foo\$bar" + ) +' + +test_expect_success 'deleting with shell metachars' ' + "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + git rm foo\$bar && + git rm file\ with\ spaces && + git commit -m "remove files" && + P4EDITOR=touch "$GITP4" submit + ) && + ( + cd "$cli" && + p4 sync ... && + test ! -e "file with spaces" && + test ! -e foo\$bar + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9805-skip-submit-edit.sh b/t/t9805-skip-submit-edit.sh new file mode 100755 index 0000000000..df929e0555 --- /dev/null +++ b/t/t9805-skip-submit-edit.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +test_description='git-p4 skipSubmitEdit config variables' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot' ' + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "change 1" + ) +' + +# this works because EDITOR is set to : +test_expect_success 'no config, unedited, say yes' ' + "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + echo line >>file1 && + git commit -a -m "change 2" && + echo y | "$GITP4" submit && + p4 changes //depot/... >wc && + test_line_count = 2 wc + ) +' + +test_expect_success 'no config, unedited, say no' ' + "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + echo line >>file1 && + git commit -a -m "change 3 (not really)" && + printf "bad response\nn\n" | "$GITP4" submit && + p4 changes //depot/... >wc && + test_line_count = 2 wc + ) +' + +test_expect_success 'skipSubmitEdit' ' + "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + # will fail if editor is even invoked + git config core.editor /bin/false && + echo line >>file1 && + git commit -a -m "change 3" && + "$GITP4" submit && + p4 changes //depot/... >wc && + test_line_count = 3 wc + ) +' + +test_expect_success 'skipSubmitEditCheck' ' + "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + echo line >>file1 && + git commit -a -m "change 4" && + "$GITP4" submit && + p4 changes //depot/... >wc && + test_line_count = 4 wc + ) +' + +# check the normal case, where the template really is edited +test_expect_success 'no config, edited' ' + "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && + ed="$TRASH_DIRECTORY/ed.sh" && + test_when_finished "rm \"$ed\"" && + cat >"$ed" <<-EOF && + #!$SHELL_PATH + sleep 1 + touch "\$1" + exit 0 + EOF + chmod 755 "$ed" && + ( + cd "$git" && + echo line >>file1 && + git commit -a -m "change 5" && + EDITOR="\"$ed\"" "$GITP4" submit && + p4 changes //depot/... >wc && + test_line_count = 5 wc + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9807-submit.sh b/t/t9807-submit.sh new file mode 100755 index 0000000000..2cb724e14c --- /dev/null +++ b/t/t9807-submit.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +test_description='git-p4 submit' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot' ' + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "change 1" + ) +' + +test_expect_success 'submit with no client dir' ' + test_when_finished cleanup_git && + "$GITP4" clone --dest="$git" //depot && + ( + cd "$git" && + echo file2 >file2 && + git add file2 && + git commit -m "git commit 2" && + rm -rf "$cli" && + git config git-p4.skipSubmitEdit true && + "$GITP4" submit + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9808-chdir.sh b/t/t9808-chdir.sh new file mode 100755 index 0000000000..eb8cc9523e --- /dev/null +++ b/t/t9808-chdir.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +test_description='git-p4 relative chdir' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot' ' + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "change 1" + ) +' + +# P4 reads from P4CONFIG file to find its server params, if the +# environment variable is set +test_expect_success 'P4CONFIG and absolute dir clone' ' + printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config && + test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" && + test_when_finished cleanup_git && + ( + P4CONFIG=p4config && export P4CONFIG && + unset P4PORT P4CLIENT && + "$GITP4" clone --verbose --dest="$git" //depot + ) +' + +# same thing, but with relative directory name, note missing $ on --dest +test_expect_success 'P4CONFIG and relative dir clone' ' + printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config && + test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" && + test_when_finished cleanup_git && + ( + P4CONFIG=p4config && export P4CONFIG && + unset P4PORT P4CLIENT && + "$GITP4" clone --verbose --dest="git" //depot + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9901-git-web--browse.sh b/t/t9901-git-web--browse.sh new file mode 100755 index 0000000000..b0a6bad8dd --- /dev/null +++ b/t/t9901-git-web--browse.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# + +test_description='git web--browse basic tests + +This test checks that git web--browse can handle various valid URLs.' + +. ./test-lib.sh + +test_web_browse () { + # browser=$1 url=$2 + git web--browse --browser="$1" "$2" >actual && + tr -d '\015' <actual >text && + test_cmp expect text +} + +test_expect_success \ + 'URL with an ampersand in it' ' + echo http://example.com/foo\&bar >expect && + git config browser.custom.cmd echo && + test_web_browse custom http://example.com/foo\&bar +' + +test_expect_success \ + 'URL with a semi-colon in it' ' + echo http://example.com/foo\;bar >expect && + git config browser.custom.cmd echo && + test_web_browse custom http://example.com/foo\;bar +' + +test_expect_success \ + 'URL with a hash in it' ' + echo http://example.com/foo#bar >expect && + git config browser.custom.cmd echo && + test_web_browse custom http://example.com/foo#bar +' + +test_expect_success \ + 'browser paths are properly quoted' ' + echo fake: http://example.com/foo >expect && + cat >"fake browser" <<-\EOF && + #!/bin/sh + echo fake: "$@" + EOF + chmod +x "fake browser" && + git config browser.w3m.path "`pwd`/fake browser" && + test_web_browse w3m http://example.com/foo +' + +test_expect_success \ + 'browser command allows arbitrary shell code' ' + echo "arg: http://example.com/foo" >expect && + git config browser.custom.cmd " + f() { + for i in \"\$@\"; do + echo arg: \$i + done + } + f" && + test_web_browse custom http://example.com/foo +' + +test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index a73a77b3b6..a65dfc7ea9 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -44,6 +44,7 @@ export LANG LC_ALL PAGER TERM TZ EDITOR=: unset VISUAL unset EMAIL +unset LANGUAGE unset $(perl -e ' my @env = keys %ENV; my $ok = join("|", qw( @@ -380,6 +381,11 @@ test_config () { git config "$@" } +test_config_global () { + test_when_finished "test_unconfig --global '$1'" && + git config --global "$@" +} + # Use test_set_prereq to tell that a particular prerequisite is available. # The prerequisite can later be checked for in two ways: # @@ -951,6 +957,8 @@ then do make_valgrind_symlink $file done + # special-case the mergetools loadables + make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools" OLDIFS=$IFS IFS=: for path in $PATH @@ -1112,12 +1120,14 @@ esac test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PYTHON" && test_set_prereq PYTHON test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE +test -z "$NO_GETTEXT" && test_set_prereq GETTEXT # Can we rely on git's output in the C locale? if test -n "$GETTEXT_POISON" then GIT_GETTEXT_POISON=YesPlease export GIT_GETTEXT_POISON + test_set_prereq GETTEXT_POISON else test_set_prereq C_LOCALE_OUTPUT fi @@ -139,6 +139,11 @@ int parse_tag(struct tag *item) return ret; } +/* + * Look at a signed tag object, and return the offset where + * the embedded detached signature begins, or the end of the + * data when there is no such signature. + */ size_t parse_signature(const char *buf, unsigned long size) { char *eol; diff --git a/templates/hooks--pre-commit.sample b/templates/hooks--pre-commit.sample index b187c4bb1f..18c4829765 100755 --- a/templates/hooks--pre-commit.sample +++ b/templates/hooks--pre-commit.sample @@ -18,6 +18,9 @@ fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) +# Redirect output to stderr. +exec 1>&2 + # 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. @@ -25,8 +28,8 @@ if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. - test "$(git diff --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0')" + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then echo "Error: Attempt to add a non-ascii file name." echo @@ -43,4 +46,5 @@ then exit 1 fi +# If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- diff --git a/test-credential.c b/test-credential.c new file mode 100644 index 0000000000..dee200e7f2 --- /dev/null +++ b/test-credential.c @@ -0,0 +1,38 @@ +#include "cache.h" +#include "credential.h" +#include "string-list.h" + +static const char usage_msg[] = +"test-credential <fill|approve|reject> [helper...]"; + +int main(int argc, const char **argv) +{ + const char *op; + struct credential c = CREDENTIAL_INIT; + int i; + + op = argv[1]; + if (!op) + usage(usage_msg); + for (i = 2; i < argc; i++) + string_list_append(&c.helpers, argv[i]); + + if (credential_read(&c, stdin) < 0) + die("unable to read credential from stdin"); + + if (!strcmp(op, "fill")) { + credential_fill(&c); + if (c.username) + printf("username=%s\n", c.username); + if (c.password) + printf("password=%s\n", c.password); + } + else if (!strcmp(op, "approve")) + credential_approve(&c); + else if (!strcmp(op, "reject")) + credential_reject(&c); + else + usage(usage_msg); + + return 0; +} diff --git a/test-ctype.c b/test-ctype.c index 033c74911e..707a821f03 100644 --- a/test-ctype.c +++ b/test-ctype.c @@ -1,78 +1,42 @@ #include "cache.h" +static int rc; -static int test_isdigit(int c) +static void report_error(const char *class, int ch) { - return isdigit(c); + printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch); + rc = 1; } -static int test_isspace(int c) +static int is_in(const char *s, int ch) { - return isspace(c); + /* We can't find NUL using strchr. It's classless anyway. */ + if (ch == '\0') + return 0; + return !!strchr(s, ch); } -static int test_isalpha(int c) -{ - return isalpha(c); -} - -static int test_isalnum(int c) -{ - return isalnum(c); -} - -static int test_is_glob_special(int c) -{ - return is_glob_special(c); -} - -static int test_is_regex_special(int c) -{ - return is_regex_special(c); +#define TEST_CLASS(t,s) { \ + int i; \ + for (i = 0; i < 256; i++) { \ + if (is_in(s, i) != t(i)) \ + report_error(#t, i); \ + } \ } #define DIGIT "0123456789" #define LOWER "abcdefghijklmnopqrstuvwxyz" #define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -static const struct ctype_class { - const char *name; - int (*test_fn)(int); - const char *members; -} classes[] = { - { "isdigit", test_isdigit, DIGIT }, - { "isspace", test_isspace, " \n\r\t" }, - { "isalpha", test_isalpha, LOWER UPPER }, - { "isalnum", test_isalnum, LOWER UPPER DIGIT }, - { "is_glob_special", test_is_glob_special, "*?[\\" }, - { "is_regex_special", test_is_regex_special, "$()*+.?[\\^{|" }, - { NULL } -}; - -static int test_class(const struct ctype_class *test) -{ - int i, rc = 0; - - for (i = 0; i < 256; i++) { - int expected = i ? !!strchr(test->members, i) : 0; - int actual = test->test_fn(i); - - if (actual != expected) { - rc = 1; - printf("%s classifies char %d (0x%02x) wrongly\n", - test->name, i, i); - } - } - return rc; -} - int main(int argc, char **argv) { - const struct ctype_class *test; - int rc = 0; - - for (test = classes; test->name; test++) - rc |= test_class(test); + TEST_CLASS(isdigit, DIGIT); + TEST_CLASS(isspace, " \n\r\t"); + TEST_CLASS(isalpha, LOWER UPPER); + TEST_CLASS(isalnum, LOWER UPPER DIGIT); + TEST_CLASS(is_glob_special, "*?[\\"); + TEST_CLASS(is_regex_special, "$()*+.?[\\^{|"); + TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~"); return rc; } diff --git a/test-dump-cache-tree.c b/test-dump-cache-tree.c index 1f73f1ea7d..e6c292385f 100644 --- a/test-dump-cache-tree.c +++ b/test-dump-cache-tree.c @@ -59,6 +59,6 @@ int main(int ac, char **av) struct cache_tree *another = cache_tree(); if (read_cache() < 0) die("unable to read index file"); - cache_tree_update(another, active_cache, active_nr, 0, 1); + cache_tree_update(another, active_cache, active_nr, 0, 1, 0); return dump_cache_tree(active_cache_tree, another, ""); } diff --git a/test-parse-options.c b/test-parse-options.c index 91a5701657..36487c402b 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -54,6 +54,7 @@ int main(int argc, const char **argv) OPT_STRING(0, "string2", &string, "str", "get another string"), OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"), OPT_STRING('o', NULL, &string, "str", "get another string"), + OPT_NOOP_NOARG(0, "obsolete"), OPT_SET_PTR(0, "default-string", &string, "set string to default", (unsigned long)"default"), OPT_STRING_LIST(0, "list", &list, "str", "add str to list"), diff --git a/test-scrap-cache-tree.c b/test-scrap-cache-tree.c new file mode 100644 index 0000000000..4728013910 --- /dev/null +++ b/test-scrap-cache-tree.c @@ -0,0 +1,17 @@ +#include "cache.h" +#include "tree.h" +#include "cache-tree.h" + +static struct lock_file index_lock; + +int main(int ac, char **av) +{ + int fd = hold_locked_index(&index_lock, 1); + if (read_cache() < 0) + die("unable to read index file"); + active_cache_tree = NULL; + if (write_cache(fd, active_cache, active_nr) + || commit_lock_file(&index_lock)) + die("unable to write index file"); + return 0; +} diff --git a/transport-helper.c b/transport-helper.c index 4eab844d4a..6f227e253b 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -183,7 +183,7 @@ static struct child_process *get_helper(struct transport *transport) ALLOC_GROW(refspecs, refspec_nr + 1, refspec_alloc); - refspecs[refspec_nr++] = strdup(capname + strlen("refspec ")); + refspecs[refspec_nr++] = xstrdup(capname + strlen("refspec ")); } else if (!strcmp(capname, "connect")) { data->connect = 1; } else if (!prefixcmp(capname, "export-marks ")) { @@ -445,9 +445,11 @@ static int fetch_with_import(struct transport *transport, if (data->refspecs) private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name); else - private = strdup(posn->name); - read_ref(private, posn->old_sha1); - free(private); + private = xstrdup(posn->name); + if (private) { + read_ref(private, posn->old_sha1); + free(private); + } } strbuf_release(&buf); return 0; diff --git a/transport.c b/transport.c index fa279d531f..63f38e3940 100644 --- a/transport.c +++ b/transport.c @@ -163,7 +163,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs, /* Follow symbolic refs (mainly for HEAD). */ localname = ref->peer_ref->name; remotename = ref->name; - tmp = resolve_ref(localname, sha, 1, &flag); + tmp = resolve_ref_unsafe(localname, sha, 1, &flag); if (tmp && flag & REF_ISSYMREF && !prefixcmp(tmp, "refs/heads/")) localname = tmp; @@ -432,7 +432,8 @@ static int fetch_refs_from_bundle(struct transport *transport, int nr_heads, struct ref **to_fetch) { struct bundle_transport_data *data = transport->data; - return unbundle(&data->header, data->fd); + return unbundle(&data->header, data->fd, + transport->progress ? BUNDLE_VERBOSE : 0); } static int close_bundle(struct transport *transport) @@ -501,7 +502,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus struct ref *refs; connect_setup(transport, for_push, 0); - get_remote_heads(data->fd[0], &refs, 0, NULL, + get_remote_heads(data->fd[0], &refs, for_push ? REF_NORMAL : 0, &data->extra_have); data->got_remote_heads = 1; @@ -536,7 +537,7 @@ static int fetch_refs_via_pack(struct transport *transport, if (!data->got_remote_heads) { connect_setup(transport, 0, 0); - get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL); + get_remote_heads(data->fd[0], &refs_tmp, 0, NULL); data->got_remote_heads = 1; } @@ -754,18 +755,10 @@ void transport_verify_remote_names(int nr_heads, const char **heads) 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]); + if (check_refname_format(remote, + REFNAME_ALLOW_ONELEVEL|REFNAME_REFSPEC_PATTERN)) + die("remote part of refspec is not a valid name in %s", + heads[i]); } } @@ -779,8 +772,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re struct ref *tmp_refs; connect_setup(transport, 1, 0); - get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL, - NULL); + get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL); data->got_remote_heads = 1; } @@ -914,7 +906,7 @@ struct transport *transport_get(struct remote *remote, const char *url) ret->fetch = fetch_objs_via_rsync; ret->push = rsync_transport_push; ret->smart_options = NULL; - } else if (is_local(url) && is_file(url)) { + } else if (is_local(url) && is_file(url) && is_bundle(url, 1)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); ret->data = data; ret->get_refs_list = get_refs_from_bundle; @@ -1033,8 +1025,8 @@ int transport_push(struct transport *transport, if (flags & TRANSPORT_PUSH_MIRROR) match_flags |= MATCH_REFS_MIRROR; - if (match_refs(local_refs, &remote_refs, - refspec_nr, refspec, match_flags)) { + if (match_push_refs(local_refs, &remote_refs, + refspec_nr, refspec, match_flags)) { return -1; } diff --git a/tree-diff.c b/tree-diff.c index b3cc2e4753..7a51d091b5 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -21,8 +21,8 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, sha1 = tree_entry_extract(t1, &path1, &mode1); sha2 = tree_entry_extract(t2, &path2, &mode2); - pathlen1 = tree_entry_len(path1, sha1); - pathlen2 = tree_entry_len(path2, sha2); + pathlen1 = tree_entry_len(&t1->entry); + pathlen2 = tree_entry_len(&t2->entry); cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2); if (cmp < 0) { show_entry(opt, "-", t1, base); @@ -64,14 +64,14 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, struct strbuf *base) { - int match = 0; + enum interesting match = entry_not_interesting; for (; desc->size; update_tree_entry(desc)) { - if (match != 2) { + if (match != all_entries_interesting) { match = tree_entry_interesting(&desc->entry, base, 0, &opt->pathspec); - if (match < 0) + if (match == all_entries_not_interesting) break; - if (match == 0) + if (match == entry_not_interesting) continue; } show_entry(opt, prefix, desc, base); @@ -85,7 +85,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, unsigned mode; const char *path; const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode); - int pathlen = tree_entry_len(path, sha1); + int pathlen = tree_entry_len(&desc->entry); int old_baselen = base->len; strbuf_add(base, path, pathlen); @@ -114,12 +114,13 @@ static void show_entry(struct diff_options *opt, const char *prefix, } static void skip_uninteresting(struct tree_desc *t, struct strbuf *base, - struct diff_options *opt, int *match) + struct diff_options *opt, + enum interesting *match) { while (t->size) { *match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec); if (*match) { - if (*match < 0) + if (*match == all_entries_not_interesting) t->size = 0; break; } @@ -132,7 +133,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, { struct strbuf base; int baselen = strlen(base_str); - int t1_match = 0, t2_match = 0; + enum interesting t1_match = entry_not_interesting; + enum interesting t2_match = entry_not_interesting; /* Enable recursion indefinitely */ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE); diff --git a/tree-walk.c b/tree-walk.c index 33f749e1e7..f82dba6a1f 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -116,7 +116,7 @@ void setup_traverse_info(struct traverse_info *info, const char *base) char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n) { - int len = tree_entry_len(n->path, n->sha1); + int len = tree_entry_len(n); int pathlen = info->pathlen; path[pathlen + len] = 0; @@ -126,7 +126,7 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str break; path[--pathlen] = '/'; n = &info->name; - len = tree_entry_len(n->path, n->sha1); + len = tree_entry_len(n); info = info->prev; pathlen -= len; } @@ -253,7 +253,7 @@ static void extended_entry_extract(struct tree_desc_x *t, * The caller wants "first" from this tree, or nothing. */ path = a->path; - len = tree_entry_len(a->path, a->sha1); + len = tree_entry_len(a); switch (check_entry_match(first, first_len, path, len)) { case -1: entry_clear(a); @@ -271,7 +271,7 @@ static void extended_entry_extract(struct tree_desc_x *t, while (probe.size) { entry_extract(&probe, a); path = a->path; - len = tree_entry_len(a->path, a->sha1); + len = tree_entry_len(a); switch (check_entry_match(first, first_len, path, len)) { case -1: entry_clear(a); @@ -309,6 +309,18 @@ static void free_extended_entry(struct tree_desc_x *t) } } +static inline int prune_traversal(struct name_entry *e, + struct traverse_info *info, + struct strbuf *base, + int still_interesting) +{ + if (!info->pathspec || still_interesting == 2) + return 2; + if (still_interesting < 0) + return still_interesting; + return tree_entry_interesting(e, base, 0, info->pathspec); +} + int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) { int ret = 0; @@ -316,15 +328,23 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) struct name_entry *entry = xmalloc(n*sizeof(*entry)); int i; struct tree_desc_x *tx = xcalloc(n, sizeof(*tx)); + struct strbuf base = STRBUF_INIT; + int interesting = 1; for (i = 0; i < n; i++) tx[i].d = t[i]; + if (info->prev) { + strbuf_grow(&base, info->pathlen); + make_traverse_path(base.buf, info->prev, &info->name); + base.buf[info->pathlen-1] = '/'; + strbuf_setlen(&base, info->pathlen); + } for (;;) { unsigned long mask, dirmask; const char *first = NULL; int first_len = 0; - struct name_entry *e; + struct name_entry *e = NULL; int len; for (i = 0; i < n; i++) { @@ -342,7 +362,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) e = entry + i; if (!e->path) continue; - len = tree_entry_len(e->path, e->sha1); + len = tree_entry_len(e); if (!first) { first = e->path; first_len = len; @@ -361,7 +381,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) /* Cull the ones that are not the earliest */ if (!e->path) continue; - len = tree_entry_len(e->path, e->sha1); + len = tree_entry_len(e); if (name_compare(e->path, len, first, first_len)) entry_clear(e); } @@ -376,16 +396,22 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) mask |= 1ul << i; if (S_ISDIR(entry[i].mode)) dirmask |= 1ul << i; + e = &entry[i]; } if (!mask) break; - ret = info->fn(n, mask, dirmask, entry, info); - if (ret < 0) { - error = ret; - if (!info->show_all_errors) - break; + interesting = prune_traversal(e, info, &base, interesting); + if (interesting < 0) + break; + if (interesting) { + ret = info->fn(n, mask, dirmask, entry, info); + if (ret < 0) { + error = ret; + if (!info->show_all_errors) + break; + } + mask &= ret; } - mask &= ret; ret = 0; for (i = 0; i < n; i++) if (mask & (1ul << i)) @@ -395,6 +421,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) for (i = 0; i < n; i++) free_extended_entry(tx + i); free(tx); + strbuf_release(&base); return error; } @@ -407,8 +434,8 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char int entrylen, cmp; sha1 = tree_entry_extract(t, &entry, mode); + entrylen = tree_entry_len(&t->entry); update_tree_entry(t); - entrylen = tree_entry_len(entry, sha1); if (entrylen > namelen) continue; cmp = memcmp(name, entry, entrylen); @@ -438,7 +465,6 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch int retval; void *tree; unsigned long size; - struct tree_desc t; unsigned char root[20]; tree = read_object_with_reference(tree_sha1, tree_type, &size, root); @@ -451,8 +477,13 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch return 0; } - init_tree_desc(&t, tree, size); - retval = find_tree_entry(&t, name, sha1, mode); + if (!size) { + retval = -1; + } else { + struct tree_desc t; + init_tree_desc(&t, tree, size); + retval = find_tree_entry(&t, name, sha1, mode); + } free(tree); return retval; } @@ -522,7 +553,7 @@ static int match_entry(const struct name_entry *entry, int pathlen, return 0; } -static int match_dir_prefix(const char *base, int baselen, +static int match_dir_prefix(const char *base, const char *match, int matchlen) { if (strncmp(base, match, matchlen)) @@ -546,30 +577,26 @@ static int match_dir_prefix(const char *base, int baselen, * * Pre-condition: either baselen == base_offset (i.e. empty path) * or base[baselen-1] == '/' (i.e. with trailing slash). - * - * Return: - * - 2 for "yes, and all subsequent entries will be" - * - 1 for yes - * - zero for no - * - negative for "no, and no subsequent entries will be either" */ -int tree_entry_interesting(const struct name_entry *entry, - struct strbuf *base, int base_offset, - const struct pathspec *ps) +enum interesting tree_entry_interesting(const struct name_entry *entry, + struct strbuf *base, int base_offset, + const struct pathspec *ps) { int i; int pathlen, baselen = base->len - base_offset; - int never_interesting = ps->has_wildcard ? 0 : -1; + int never_interesting = ps->has_wildcard ? + entry_not_interesting : all_entries_not_interesting; if (!ps->nr) { if (!ps->recursive || ps->max_depth == -1) - return 2; - return !!within_depth(base->buf + base_offset, baselen, - !!S_ISDIR(entry->mode), - ps->max_depth); + return all_entries_interesting; + return within_depth(base->buf + base_offset, baselen, + !!S_ISDIR(entry->mode), + ps->max_depth) ? + entry_interesting : entry_not_interesting; } - pathlen = tree_entry_len(entry->path, entry->sha1); + pathlen = tree_entry_len(entry); for (i = ps->nr - 1; i >= 0; i--) { const struct pathspec_item *item = ps->items+i; @@ -579,42 +606,43 @@ int tree_entry_interesting(const struct name_entry *entry, if (baselen >= matchlen) { /* If it doesn't match, move along... */ - if (!match_dir_prefix(base_str, baselen, match, matchlen)) + if (!match_dir_prefix(base_str, match, matchlen)) goto match_wildcards; if (!ps->recursive || ps->max_depth == -1) - return 2; + return all_entries_interesting; - return !!within_depth(base_str + matchlen + 1, - baselen - matchlen - 1, - !!S_ISDIR(entry->mode), - ps->max_depth); + return within_depth(base_str + matchlen + 1, + baselen - matchlen - 1, + !!S_ISDIR(entry->mode), + ps->max_depth) ? + entry_interesting : entry_not_interesting; } - /* Does the base match? */ - if (!strncmp(base_str, match, baselen)) { + /* Either there must be no base, or the base must match. */ + if (baselen == 0 || !strncmp(base_str, match, baselen)) { if (match_entry(entry, pathlen, match + baselen, matchlen - baselen, &never_interesting)) - return 1; + return entry_interesting; - if (ps->items[i].use_wildcard) { + if (item->use_wildcard) { if (!fnmatch(match + baselen, entry->path, 0)) - return 1; + return entry_interesting; /* * Match all directories. We'll try to * match files later on. */ if (ps->recursive && S_ISDIR(entry->mode)) - return 1; + return entry_interesting; } continue; } match_wildcards: - if (!ps->items[i].use_wildcard) + if (!item->use_wildcard) continue; /* @@ -626,7 +654,7 @@ match_wildcards: if (!fnmatch(match, base->buf + base_offset, 0)) { strbuf_setlen(base, base_offset + baselen); - return 1; + return entry_interesting; } strbuf_setlen(base, base_offset + baselen); @@ -635,7 +663,7 @@ match_wildcards: * later on. */ if (ps->recursive && S_ISDIR(entry->mode)) - return 1; + return entry_interesting; } return never_interesting; /* No matches */ } diff --git a/tree-walk.h b/tree-walk.h index 39524b7dba..2bf0db9814 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -20,9 +20,9 @@ static inline const unsigned char *tree_entry_extract(struct tree_desc *desc, co return desc->entry.sha1; } -static inline int tree_entry_len(const char *name, const unsigned char *sha1) +static inline int tree_entry_len(const struct name_entry *ne) { - return (const char *)sha1 - name - 1; + return (const char *)ne->sha1 - ne->path - 1; } void update_tree_entry(struct tree_desc *); @@ -44,6 +44,7 @@ struct traverse_info { struct traverse_info *prev; struct name_entry name; int pathlen; + struct pathspec *pathspec; unsigned long conflicts; traverse_callback_t fn; @@ -57,9 +58,19 @@ extern void setup_traverse_info(struct traverse_info *info, const char *base); static inline int traverse_path_len(const struct traverse_info *info, const struct name_entry *n) { - return info->pathlen + tree_entry_len(n->path, n->sha1); + return info->pathlen + tree_entry_len(n); } -extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps); +/* in general, positive means "kind of interesting" */ +enum interesting { + all_entries_not_interesting = -1, /* no, and no subsequent entries will be either */ + entry_not_interesting = 0, + entry_interesting = 1, + all_entries_interesting = 2 /* yes, and all subsequent entries will be */ +}; + +extern enum interesting tree_entry_interesting(const struct name_entry *, + struct strbuf *, int, + const struct pathspec *ps); #endif @@ -52,7 +52,8 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, struct tree_desc desc; struct name_entry entry; unsigned char sha1[20]; - int len, retval = 0, oldlen = base->len; + int len, oldlen = base->len; + enum interesting retval = entry_not_interesting; if (parse_tree(tree)) return -1; @@ -60,11 +61,11 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, init_tree_desc(&desc, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { - if (retval != 2) { + if (retval != all_entries_interesting) { retval = tree_entry_interesting(&entry, base, 0, pathspec); - if (retval < 0) + if (retval == all_entries_not_interesting) break; - if (retval == 0) + if (retval == entry_not_interesting) continue; } @@ -99,7 +100,7 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, else continue; - len = tree_entry_len(entry.path, entry.sha1); + len = tree_entry_len(&entry); strbuf_add(base, entry.path, len); strbuf_addch(base, '/'); retval = read_tree_1(lookup_tree(sha1), diff --git a/unix-socket.c b/unix-socket.c new file mode 100644 index 0000000000..84b15099f2 --- /dev/null +++ b/unix-socket.c @@ -0,0 +1,56 @@ +#include "cache.h" +#include "unix-socket.h" + +static int unix_stream_socket(void) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + die_errno("unable to create socket"); + return fd; +} + +static void unix_sockaddr_init(struct sockaddr_un *sa, const char *path) +{ + int size = strlen(path) + 1; + if (size > sizeof(sa->sun_path)) + die("socket path is too long to fit in sockaddr"); + memset(sa, 0, sizeof(*sa)); + sa->sun_family = AF_UNIX; + memcpy(sa->sun_path, path, size); +} + +int unix_stream_connect(const char *path) +{ + int fd; + struct sockaddr_un sa; + + unix_sockaddr_init(&sa, path); + fd = unix_stream_socket(); + if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + close(fd); + return -1; + } + return fd; +} + +int unix_stream_listen(const char *path) +{ + int fd; + struct sockaddr_un sa; + + unix_sockaddr_init(&sa, path); + fd = unix_stream_socket(); + + unlink(path); + if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + close(fd); + return -1; + } + + if (listen(fd, 5) < 0) { + close(fd); + return -1; + } + + return fd; +} diff --git a/unix-socket.h b/unix-socket.h new file mode 100644 index 0000000000..e271aeec5a --- /dev/null +++ b/unix-socket.h @@ -0,0 +1,7 @@ +#ifndef UNIX_SOCKET_H +#define UNIX_SOCKET_H + +int unix_stream_connect(const char *path); +int unix_stream_listen(const char *path); + +#endif /* UNIX_SOCKET_H */ diff --git a/unpack-trees.c b/unpack-trees.c index cc616c3f99..7c9ecf665d 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -159,7 +159,7 @@ static void display_error_msgs(struct unpack_trees_options *o) string_list_clear(rejects, 0); } if (something_displayed) - printf("Aborting\n"); + fprintf(stderr, "Aborting\n"); } /* @@ -444,8 +444,9 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, newinfo = *info; newinfo.prev = info; + newinfo.pathspec = info->pathspec; newinfo.name = *p; - newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1; + newinfo.pathlen += tree_entry_len(p) + 1; newinfo.conflicts |= df_conflicts; for (i = 0; i < n; i++, dirmask >>= 1) { @@ -494,7 +495,7 @@ static int do_compare_entry(const struct cache_entry *ce, const struct traverse_ ce_len -= pathlen; ce_name = ce->name + pathlen; - len = tree_entry_len(n->path, n->sha1); + len = tree_entry_len(n); return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode); } @@ -625,7 +626,7 @@ static int find_cache_pos(struct traverse_info *info, struct unpack_trees_options *o = info->data; struct index_state *index = o->src_index; int pfxlen = info->pathlen; - int p_len = tree_entry_len(p->path, p->sha1); + int p_len = tree_entry_len(p); for (pos = o->cache_bottom; pos < index->cache_nr; pos++) { struct cache_entry *ce = index->cache[pos]; @@ -1040,6 +1041,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options info.fn = unpack_callback; info.data = o; info.show_all_errors = o->show_all_errors; + info.pathspec = o->pathspec; if (o->prefix) { /* @@ -1089,6 +1091,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options */ mark_new_skip_worktree(o->el, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE); + ret = 0; for (i = 0; i < o->result.cache_nr; i++) { struct cache_entry *ce = o->result.cache[i]; @@ -1101,19 +1104,30 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options * correct CE_NEW_SKIP_WORKTREE */ if (ce->ce_flags & CE_ADDED && - verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) - return -1; + verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) { + if (!o->show_all_errors) + goto return_failed; + ret = -1; + } if (apply_sparse_checkout(ce, o)) { + if (!o->show_all_errors) + goto return_failed; ret = -1; - goto done; } if (!ce_skip_worktree(ce)) empty_worktree = 0; } + if (ret < 0) + goto return_failed; + /* + * Sparse checkout is meant to narrow down checkout area + * but it does not make sense to narrow down to empty working + * tree. This is usually a mistake in sparse checkout rules. + * Do not allow users to do that. + */ if (o->result.cache_nr && empty_worktree) { - /* dubious---why should this fail??? */ ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory"); goto done; } diff --git a/unpack-trees.h b/unpack-trees.h index 7998948307..5e432f576e 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -52,6 +52,7 @@ struct unpack_trees_options { const char *prefix; int cache_bottom; struct dir_struct *dir; + struct pathspec *pathspec; merge_fn_t fn; const char *msgs[NB_UNPACK_TREES_ERROR_TYPES]; /* diff --git a/upload-pack.c b/upload-pack.c index 8739bfacdf..6f36f6255c 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -84,22 +84,11 @@ static void show_commit(struct commit *commit, void *data) commit->buffer = NULL; } -static void show_object(struct object *obj, const struct name_path *path, const char *component) +static void show_object(struct object *obj, + const struct name_path *path, const char *component, + void *cb_data) { - /* An object with name "foo\n0000000..." can be used to - * confuse downstream git-pack-objects very badly. - */ - const char *name = path_name(path, component); - const char *ep = strchr(name, '\n'); - if (ep) { - fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(obj->sha1), - (int) (ep - name), - name); - } - else - fprintf(pack_pipe, "%s %s\n", - sha1_to_hex(obj->sha1), name); - free((char *)name); + show_object_with_name(pack_pipe, obj, path, component); } static void show_edge(struct commit *commit) @@ -795,6 +784,8 @@ int main(int argc, char **argv) int i; int strict = 0; + git_setup_gettext(); + packet_trace_identity("upload-pack"); git_extract_argv0_path(argv[0]); read_replace_refs = 0; @@ -18,35 +18,15 @@ int is_urlschemechar(int first_flag, int ch) int is_url(const char *url) { - const char *url2, *first_slash; - - if (!url) - return 0; - url2 = url; - first_slash = strchr(url, '/'); - - /* Input with no slash at all or slash first can't be URL. */ - if (!first_slash || first_slash == url) + /* Is "scheme" part reasonable? */ + if (!url || !is_urlschemechar(1, *url++)) return 0; - /* Character before must be : and next must be /. */ - if (first_slash[-1] != ':' || first_slash[1] != '/') - return 0; - /* There must be something before the :// */ - if (first_slash == url + 1) - return 0; - /* - * Check all characters up to first slash - 1. Only alphanum - * is allowed. - */ - url2 = url; - while (url2 < first_slash - 1) { - if (!is_urlschemechar(url2 == url, (unsigned char)*url2)) + while (*url && *url != ':') { + if (!is_urlschemechar(0, *url++)) return 0; - url2++; } - - /* Valid enough. */ - return 1; + /* We've seen "scheme"; we want colon-slash-slash */ + return (url[0] == ':' && url[1] == '/' && url[2] == '/'); } static int url_decode_char(const char *q) @@ -68,18 +48,20 @@ static int url_decode_char(const char *q) return val; } -static char *url_decode_internal(const char **query, const char *stop_at, - struct strbuf *out, int decode_plus) +static char *url_decode_internal(const char **query, int len, + const char *stop_at, struct strbuf *out, + int decode_plus) { const char *q = *query; - do { + while (len) { unsigned char c = *q; if (!c) break; if (stop_at && strchr(stop_at, c)) { q++; + len--; break; } @@ -88,6 +70,7 @@ static char *url_decode_internal(const char **query, const char *stop_at, if (0 <= val) { strbuf_addch(out, val); q += 3; + len -= 3; continue; } } @@ -97,34 +80,41 @@ static char *url_decode_internal(const char **query, const char *stop_at, else strbuf_addch(out, c); q++; - } while (1); + len--; + } *query = q; return strbuf_detach(out, NULL); } char *url_decode(const char *url) { + return url_decode_mem(url, strlen(url)); +} + +char *url_decode_mem(const char *url, int len) +{ struct strbuf out = STRBUF_INIT; - const char *colon = strchr(url, ':'); + const char *colon = memchr(url, ':', len); /* Skip protocol part if present */ if (colon && url < colon) { strbuf_add(&out, url, colon - url); + len -= colon - url; url = colon; } - return url_decode_internal(&url, NULL, &out, 0); + return url_decode_internal(&url, len, NULL, &out, 0); } char *url_decode_parameter_name(const char **query) { struct strbuf out = STRBUF_INIT; - return url_decode_internal(query, "&=", &out, 1); + return url_decode_internal(query, -1, "&=", &out, 1); } char *url_decode_parameter_value(const char **query) { struct strbuf out = STRBUF_INIT; - return url_decode_internal(query, "&", &out, 1); + return url_decode_internal(query, -1, "&", &out, 1); } void end_url_with_slash(struct strbuf *buf, const char *url) @@ -4,6 +4,7 @@ extern int is_url(const char *url); extern int is_urlschemechar(int first_flag, int ch); extern char *url_decode(const char *url); +extern char *url_decode_mem(const char *url, int len); extern char *url_decode_parameter_name(const char **query); extern char *url_decode_parameter_value(const char **query); diff --git a/userdiff.c b/userdiff.c index bf553ad91b..76109da4bc 100644 --- a/userdiff.c +++ b/userdiff.c @@ -37,6 +37,9 @@ PATTERNS("java", "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" "|[-+*/<>%&^|=!]=" "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"), +PATTERNS("matlab", + "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$", + "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"), PATTERNS("objc", /* Negate C statements that can look like functions */ "!^[ \t]*(do|for|if|else|return|switch|while)\n" @@ -115,7 +118,7 @@ PATTERNS("cpp", /* Jump targets or access declarations */ "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n" /* C/++ functions/methods at top level */ - "^([A-Za-z_][A-Za-z_0-9]*([ \t]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n" + "^([A-Za-z_][A-Za-z_0-9]*([ \t*]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n" /* compound type at top level */ "^((struct|class|enum)[^;]*)$", /* -- */ @@ -190,7 +190,7 @@ static int interpret_target(struct walker *walker, char *target, unsigned char * { if (!get_sha1_hex(target, sha1)) return 0; - if (!check_ref_format(target)) { + if (!check_refname_format(target, 0)) { struct ref *ref = alloc_ref(target); if (!walker->fetch_ref(walker, ref)) { hashcpy(sha1, ref->old_sha1); diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh index 09feb1f737..53a8dd0a3f 100644 --- a/wrap-for-bin.sh +++ b/wrap-for-bin.sh @@ -15,7 +15,8 @@ else export GIT_TEMPLATE_DIR fi GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib' +GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale' PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH" -export GIT_EXEC_PATH GITPERLLIB PATH +export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR exec "${GIT_EXEC_PATH}/@@PROG@@" "$@" diff --git a/wt-status.c b/wt-status.c index 8836a527d0..9ffc535f1a 100644 --- a/wt-status.c +++ b/wt-status.c @@ -111,7 +111,6 @@ void status_printf_more(struct wt_status *s, const char *color, void wt_status_prepare(struct wt_status *s) { unsigned char sha1[20]; - const char *head; memset(s, 0, sizeof(*s)); memcpy(s->color_palette, default_wt_status_colors, @@ -119,8 +118,7 @@ void wt_status_prepare(struct wt_status *s) s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; s->use_color = -1; s->relative_paths = 1; - head = resolve_ref("HEAD", sha1, 0, NULL); - s->branch = head ? xstrdup(head) : NULL; + s->branch = resolve_refdup("HEAD", sha1, 0, NULL); s->reference = "HEAD"; s->fp = stdout; s->index_file = get_index_file(); @@ -396,7 +394,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) if (s->ignore_submodule_arg) { DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); - } + } rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; init_pathspec(&rev.prune_data, s->pathspec); diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 4beb10c678..00d36c3ac7 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -43,6 +43,7 @@ extern "C" { #define XDL_EMIT_FUNCNAMES (1 << 0) #define XDL_EMIT_COMMON (1 << 1) +#define XDL_EMIT_FUNCCONTEXT (1 << 2) #define XDL_MMB_READONLY (1 << 0) diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 277e2eec5b..2e669c3e25 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -100,14 +100,40 @@ static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, return 0; } +struct func_line { + long len; + char buf[80]; +}; + +static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, + struct func_line *func_line, long start, long limit) +{ + find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff; + long l, size, step = (start > limit) ? -1 : 1; + char *buf, dummy[1]; + + buf = func_line ? func_line->buf : dummy; + size = func_line ? sizeof(func_line->buf) : sizeof(dummy); + + for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { + const char *rec; + long reclen = xdl_get_rec(&xe->xdf1, l, &rec); + long len = ff(rec, reclen, buf, size, xecfg->find_func_priv); + if (len >= 0) { + if (func_line) + func_line->len = len; + return l; + } + } + return -1; +} + int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdemitconf_t const *xecfg) { long s1, s2, e1, e2, lctx; xdchange_t *xch, *xche; - char funcbuf[80]; - long funclen = 0; long funclineprev = -1; - find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff; + struct func_line func_line = { 0 }; if (xecfg->flags & XDL_EMIT_COMMON) return xdl_emit_common(xe, xscr, ecb, xecfg); @@ -118,6 +144,17 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1); + if (fs1 < 0) + fs1 = 0; + if (fs1 < s1) { + s2 -= s1 - fs1; + s1 = fs1; + } + } + + again: lctx = xecfg->ctxlen; lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); @@ -125,27 +162,43 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, e1 = xche->i1 + xche->chg1 + lctx; e2 = xche->i2 + xche->chg2 + lctx; + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fe1 = get_func_line(xe, xecfg, NULL, + xche->i1 + xche->chg1, + xe->xdf1.nrec); + if (fe1 < 0) + fe1 = xe->xdf1.nrec; + if (fe1 > e1) { + e2 += fe1 - e1; + e1 = fe1; + } + + /* + * Overlap with next change? Then include it + * in the current hunk and start over to find + * its new end. + */ + if (xche->next) { + long l = xche->next->i1; + if (l <= e1 || + get_func_line(xe, xecfg, NULL, l, e1) < 0) { + xche = xche->next; + goto again; + } + } + } + /* * Emit current hunk header. */ if (xecfg->flags & XDL_EMIT_FUNCNAMES) { - long l; - for (l = s1 - 1; l >= 0 && l > funclineprev; l--) { - const char *rec; - long reclen = xdl_get_rec(&xe->xdf1, l, &rec); - long newfunclen = ff(rec, reclen, funcbuf, - sizeof(funcbuf), - xecfg->find_func_priv); - if (newfunclen >= 0) { - funclen = newfunclen; - break; - } - } + get_func_line(xe, xecfg, &func_line, + s1 - 1, funclineprev); funclineprev = s1 - 1; } if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, - funcbuf, funclen, ecb) < 0) + func_line.buf, func_line.len, ecb) < 0) return -1; /* diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c index 5a33d1a869..e419f4f726 100644 --- a/xdiff/xprepare.c +++ b/xdiff/xprepare.c @@ -383,7 +383,7 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { * might be potentially discarded if they happear in a run of discardable. */ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { - long i, nm, nreff; + long i, nm, nreff, mlim; xrecord_t **recs; xdlclass_t *rcrec; char *dis, *dis1, *dis2; @@ -396,16 +396,20 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd dis1 = dis; dis2 = dis1 + xdf1->nrec + 1; + if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { rcrec = cf->rcrecs[(*recs)->ha]; nm = rcrec ? rcrec->len2 : 0; - dis1[i] = (nm == 0) ? 0: 1; + dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; } + if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { rcrec = cf->rcrecs[(*recs)->ha]; nm = rcrec ? rcrec->len1 : 0; - dis2[i] = (nm == 0) ? 0: 1; + dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; } for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; @@ -188,13 +188,20 @@ void git_deflate_init_gzip(git_zstream *strm, int level) strm->z.msg ? strm->z.msg : "no message"); } -void git_deflate_end(git_zstream *strm) +int git_deflate_abort(git_zstream *strm) { int status; zlib_pre_call(strm); status = deflateEnd(&strm->z); zlib_post_call(strm); + return status; +} + +void git_deflate_end(git_zstream *strm) +{ + int status = git_deflate_abort(strm); + if (status == Z_OK) return; error("deflateEnd: %s (%s)", zerr_to_string(status), |