diff options
568 files changed, 24493 insertions, 5598 deletions
diff --git a/.gitignore b/.gitignore index 8df8f88bea..14e2b6bde9 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,7 @@ /git-remote-https /git-remote-ftp /git-remote-ftps +/git-remote-testgit /git-repack /git-replace /git-repo-config @@ -155,7 +156,9 @@ /git-write-tree /git-core-*/?* /gitk-git/gitk-wish +/gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi +/gitweb/gitweb.min.* /test-chmtime /test-ctype /test-date @@ -177,6 +180,7 @@ *.exe *.[aos] *.py[co] +.depend/ *+ /config.mak /autom4te.cache diff --git a/Documentation/Makefile b/Documentation/Makefile index 04f69cf64e..a4c4063e50 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -6,7 +6,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \ gitrepository-layout.txt MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \ gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \ - gitdiffcore.txt gitworkflows.txt + gitdiffcore.txt gitrevisions.txt gitworkflows.txt MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT) MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT)) diff --git a/Documentation/RelNotes-1.7.1.1.txt b/Documentation/RelNotes-1.7.1.1.txt new file mode 100644 index 0000000000..3f6b3148a3 --- /dev/null +++ b/Documentation/RelNotes-1.7.1.1.txt @@ -0,0 +1,96 @@ +Git v1.7.1.1 Release Notes +========================== + +Fixes since v1.7.1 +------------------ + + * Authentication over http transport can now be made lazily, in that the + request can first go to a URL without username, get a 401 response and + then the client will ask for the username to use. + + * We used to mistakenly think "../work" is a subdirectory of the current + directory when we are in "../work-xyz". + + * The attribute mechanism now allows an entry that uses an attribute + macro that set/unset one attribute, immediately followed by an + overriding setting; this makes attribute macros much easier to use. + + * We didn't recognize timezone "Z" as a synonym for "UTC" (75b37e70). + + * In 1.7.0, read-tree and user commands that use the mechanism such as + checkout and merge were fixed to handle switching between branches one + of which has a file while the other has a directory at the same path + correctly even when there are some "confusing" pathnames in them. But + the algorithm used for this fix was suboptimal and had a terrible + performance degradation especially in larger trees. + + * "git am -3" did not show diagnosis when the patch in the message was corrupt. + + * After "git apply --whitespace=fix" removed trailing blank lines in an + patch in a patch series, it failed to apply later patches that depend + on the presence of such blank lines. + + * "git bundle --stdin" segfaulted. + + * "git checkout" and "git rebase" overwrote paths that are marked "assume + unchanged". + + * "git commit --amend" on a commit with an invalid author-name line that + lacks the display name didn't work. + + * "git describe" did not tie-break tags that point at the same commit + correctly; newer ones are preferred by paying attention to the + tagger date now. + + * "git diff" used to tell underlying xdiff machinery to work very hard to + minimize the output, but this often was spending too many extra cycles + for very little gain. + + * "git diff --color" did not paint extended diff headers per line + (i.e. the coloring escape sequence didn't end at the end of line), + which confused "less -R". + + * "git fetch" over HTTP verifies the downloaded packfiles more robustly. + + * The memory usage by "git index-pack" (run during "git fetch" and "git + push") got leaner. + + * "GIT_DIR=foo.git git init --bare bar.git" created foo.git instead of bar.git. + + * "git log --abbrev=$num --format='%h' ignored --abbrev=$num. + + * "git ls-files ../out/side/cwd" refused to work. + + * "git merge --log" used to replace the custom message given by "-m" with + the shortlog, instead of appending to it. + + * "git notes copy" without any other argument segfaulted. + + * "git pull" accepted "--dry-run", gave it to underlying "git fetch" but + ignored the option itself, resulting in a bogus attempt to merge + unrelated commit. + + * "git rebase" did not faithfully reproduce a malformed author ident, that + is often seen in a repository converted from foreign SCMs. + + * "git reset --hard" started from a wrong directory and a working tree in + a nonstandard location is in use got confused. + + * "git send-email" lacked a way to specify the domainname used in the + EHLO/HELO exchange, causing rejected connection from picky servers. + It learned --smtp-domain option to solve this issue. + + * "git send-email" did not declare a content-transfer-encoding and + content-type even when its payload needs to be sent in 8-bit. + + * "git show -C -C" and other corner cases lost diff metainfo output + in 1.7.0. + + * "git stash" incorrectly lost paths in the working tree that were + previously removed from the index. + + * "git status" stopped refreshing the index by mistake in 1.7.1. + + * "git status" showed excess "hints" even when advice.statusHints is set to false. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes-1.7.1.2.txt b/Documentation/RelNotes-1.7.1.2.txt new file mode 100644 index 0000000000..61ba14e262 --- /dev/null +++ b/Documentation/RelNotes-1.7.1.2.txt @@ -0,0 +1,28 @@ +Git v1.7.1.2 Release Notes +========================== + +Fixes since v1.7.1.1 +-------------------- + + * "git commit" did not honor GIT_REFLOG_ACTION environment variable, resulting + reflog messages for cherry-pick and revert actions to be recorded as "commit". + + * "git clone/fetch/pull" issued an incorrect error message when a ref and + a symref that points to the ref were updated at the same time. This + obviously would update them to the same value, and should not result in + an error condition. + + * "git diff" inside a tree with many pathnames that have certain + characters has become very slow in 1.7.0 by mistake. + + * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option + when --keep-dashdash was in effect. + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git config --path conf.var" to attempt to expand a variable conf.var + that uses "~/" short-hand segfaulted when $HOME environment variable + was not set. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes-1.7.1.txt b/Documentation/RelNotes-1.7.1.txt new file mode 100644 index 0000000000..9d89fedb36 --- /dev/null +++ b/Documentation/RelNotes-1.7.1.txt @@ -0,0 +1,89 @@ +Git v1.7.1 Release Notes +======================== + +Updates since v1.7.0 +-------------------- + + * Eric Raymond is the maintainer of updated CIAbot scripts, in contrib/. + + * gitk updates. + + * Some commands (e.g. svn and http interfaces) that interactively ask + for a password can be told to use an external program given via + GIT_ASKPASS. + + * Conflict markers that lead the common ancestor in diff3-style output + now have a label, which hopefully would help third-party tools that + expect one. + + * Comes with an updated bash-completion script. + + * "git am" learned "--keep-cr" option to handle inputs that are + a mixture of changes to files with and without CRLF line endings. + + * "git cvsimport" learned -R option to leave revision mapping between + CVS revisions and resulting git commits. + + * "git diff --submodule" notices and describes dirty submodules. + + * "git for-each-ref" learned %(symref), %(symref:short) and %(flag) + tokens. + + * "git hash-object --stdin-paths" can take "--no-filters" option now. + + * "git init" can be told to look at init.templatedir configuration + variable (obviously that has to come from either /etc/gitconfig or + $HOME/.gitconfig). + + * "git grep" learned "--no-index" option, to search inside contents that + are not managed by git. + + * "git grep" learned --color=auto/always/never. + + * "git grep" learned to paint filename and line-number in colors. + + * "git log -p --first-parent -m" shows one-parent diff for merge + commits, instead of showing combined diff. + + * "git merge-file" learned to use custom conflict marker size and also + to use the "union merge" behaviour. + + * "git notes" command has been rewritten in C and learned many commands + and features to help you carry notes forward across rebases and amends. + + * "git request-pull" identifies the commit the request is relative to in + a more readable way. + + * "git reset" learned "--keep" option that lets you discard commits + near the tip while preserving your local changes in a way similar + to how "git checkout branch" does. + + * "git status" notices and describes dirty submodules. + + * "git svn" should work better when interacting with repositories + with CRLF line endings. + + * "git imap-send" learned to support CRAM-MD5 authentication. + + * "gitweb" installation procedure can use "minified" js/css files + better. + + * Various documentation updates. + +Fixes since v1.7.0 +------------------ + +All of the fixes in v1.7.0.X maintenance series are included in this +release, unless otherwise noted. + + * "git add frotz/nitfol" did not complain when the entire frotz/ directory + was ignored. + + * "git diff --stat" used "int" to count the size of differences, + which could result in overflowing. + + * "git rev-list --pretty=oneline" didn't terminate a record with LF for + commits without any message. + + * "git rev-list --abbrev-commit" defaulted to 40-byte abbreviations, unlike + newer tools in the git toolset. diff --git a/Documentation/RelNotes-1.7.2.1.txt b/Documentation/RelNotes-1.7.2.1.txt new file mode 100644 index 0000000000..1103c47a4f --- /dev/null +++ b/Documentation/RelNotes-1.7.2.1.txt @@ -0,0 +1,25 @@ +Git v1.7.2.1 Release Notes +========================== + +Fixes since v1.7.2 +------------------ + + * "git instaweb" wasn't useful when your Apache was installed under a + name other than apache2 (e.g. "httpd"). + + * Similarly, "git web--browse" (invoked by "git help -w") learned that + chrome browser is sometimes called google-chrome. + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git config --path conf.var" to attempt to expand a variable conf.var + that uses "~/" short-hand segfaulted when $HOME environment variable + was not set. + + * Documentation on Cygwin failed to build. + + * The error message from "git pull blarg" when 'blarg' is an unknown + remote name has been improved. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes-1.7.2.txt b/Documentation/RelNotes-1.7.2.txt new file mode 100644 index 0000000000..15cf01178c --- /dev/null +++ b/Documentation/RelNotes-1.7.2.txt @@ -0,0 +1,151 @@ +Git v1.7.2 Release Notes +======================== + +Updates since v1.7.1 +-------------------- + + * core.eol configuration and text/eol attributes are the new way to control + the end of line conventions for files in the working tree. + + * core.autocrlf has been made safer - it will now only handle line + endings for new files and files that are LF-only in the + repository. To normalize content that has been checked in with + CRLF, use the new eol/text attributes. + + * The whitespace rules used in "git apply --whitespace" and "git diff" + gained a new member in the family (tab-in-indent) to help projects with + policy to indent only with spaces. + + * When working from a subdirectory, by default, git does not look for its + metadirectory ".git" across filesystems, primarily to help people who + have invocations of git in their custom PS1 prompts, as being outside + of a git repository would look for ".git" all the way up to the root + directory, and NFS mounts are often slow. DISCOVERY_ACROSS_FILESYSTEM + environment variable can be used to tell git not to stop at a + filesystem boundary. + + * Usage help messages generated by parse-options library (i.e. most + of the Porcelain commands) are sent to the standard output now. + + * ':/<string>' notation to look for a commit now takes regular expression + and it is not anchored at the beginning of the commit log message + anymore (this is a backward incompatible change). + + * "git" wrapper learned "-c name=value" option to override configuration + variable from the command line. + + * Improved portability for various platforms including older SunOS, + HP-UX 10/11, AIX, Tru64, etc. and platforms with Python 2.4. + + * The message from "git am -3" has been improved when conflict + resolution ended up making the patch a no-op. + + * "git blame" applies the textconv filter to the contents it works + on, when available. + + * "git checkout --orphan newbranch" is similar to "-b newbranch" but + prepares to create a root commit that is not connected to any existing + commit. + + * "git cherry-pick" learned to pick a range of commits + (e.g. "cherry-pick A..B" and "cherry-pick --stdin"), so did "git + revert"; these do not support the nicer sequencing control "rebase + [-i]" has, though. + + * "git cherry-pick" and "git revert" learned --strategy option to specify + the merge strategy to be used when performing three-way merges. + + * "git cvsserver" can be told to use pserver; its password file can be + stored outside the repository. + + * The output from the textconv filter used by "git diff" can be cached to + speed up their reuse. + + * "git diff --word-diff=<mode>" extends the existing "--color-words" + option, making it more useful in color-challenged environments. + + * The regexp to detect function headers used by "git diff" for PHP has + been enhanced for visibility modifiers (public, protected, etc.) to + better support PHP5. + + * "diff.noprefix" configuration variable can be used to implicitly + ask for "diff --no-prefix" behaviour. + + * "git for-each-ref" learned "%(objectname:short)" that gives the object + name abbreviated. + + * "git format-patch" learned --signature option and format.signature + configuration variable to customize the e-mail signature used in the + output. + + * Various options to "git grep" (e.g. --count, --name-only) work better + with binary files. + + * "git grep" learned "-Ovi" to open the files with hits in your editor. + + * "git help -w" learned "chrome" and "chromium" browsers. + + * "git log --decorate" shows commit decorations in various colours. + + * "git log --follow <path>" follows across copies (it used to only follow + renames). This may make the processing more expensive. + + * "git log --pretty=format:<template>" specifier learned "% <something>" + magic that inserts a space only when %<something> expands to a + non-empty string; this is similar to "%+<something>" magic, but is + useful in a context to generate a single line output. + + * "git notes prune" learned "-n" (dry-run) and "-v" options, similar to + what "git prune" has. + + * "git patch-id" can be fed a mbox without getting confused by the + signature line in the format-patch output. + + * "git remote" learned "set-branches" subcommand. + + * "git rev-list A..B" learned --ancestry-path option to further limit + the result to the commits that are on the ancestry chain between A and + B (i.e. commits that are not descendants of A are excluded). + + * "git show -5" is equivalent to "git show --do-walk 5"; this is similar + to the update to make "git show master..next" walk the history, + introduced in 1.6.4. + + * "git status [-s] --ignored" can be used to list ignored paths. + + * "git status -s -b" shows the current branch in the output. + + * "git status" learned "--ignore-submodules" option. + + * Various "gitweb" enhancements and clean-ups, including syntax + highlighting, "plackup" support for instaweb, .fcgi suffix to run + it as FastCGI script, etc. + + * The test harness has been updated to produce TAP-friendly output. + + * Many documentation improvement patches are also included. + + +Fixes since v1.7.1 +------------------ + +All of the fixes in v1.7.1.X maintenance series are included in this +release, unless otherwise noted. + + * We didn't URL decode "file:///path/to/repo" correctly when path/to/repo + had percent-encoded characters (638794c, 9d2e942, ce83eda, 3c73a1d). + + * "git clone" did not configure remote.origin.url correctly for bare + clones (df61c889). + + * "git diff --graph" works better with "--color-words" and other options + (81fa024..4297c0a). + + * "git diff" could show ambiguous abbreviation of blob object names on + its "index" line (3e5a188). + + * "git reset --hard" started from a wrong directory and a working tree in + a nonstandard location is in use got confused (560fb6a1). + + * "git read-tree -m A B" used to switch to branch B while retaining + local changes added an incorrect cache-tree information (b1f47514). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index c686f8646b..ece3c77482 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -7,17 +7,16 @@ Checklist (and a short version for the impatient): before committing - do not check in commented out code or unneeded files - the first line of the commit message should be a short - description and should skip the full stop + description (50 characters is the soft limit, see DISCUSSION + in git-commit(1)), and should skip the full stop - the body should provide a meaningful commit message, which: - uses the imperative, present tense: "change", not "changed" or "changes". - includes motivation for the change, and contrasts its implementation with previous behaviour - - if you want your work included in git.git, add a - "Signed-off-by: Your Name <you@example.com>" line to the - commit message (or just use the option "-s" when - committing) to confirm that you agree to the Developer's - Certificate of Origin + - add a "Signed-off-by: Your Name <you@example.com>" line to the + commit message (or just use the option "-s" when committing) + to confirm that you agree to the Developer's Certificate of Origin - make sure that you have tests for the bug you are fixing - make sure that the test suite passes after your commit @@ -41,6 +40,7 @@ Checklist (and a short version for the impatient): maintainer (gitster@pobox.com) if (and only if) the patch is ready for inclusion. If you use git-send-email(1), please test it first by sending email to yourself. + - see below for instructions specific to your mailer Long version: @@ -53,6 +53,34 @@ But the patch submission requirements are a lot more relaxed here on the technical/contents front, because the core GIT is thousand times smaller ;-). So here is only the relevant bits. +(0) Decide what to base your work on. + +In general, always base your work on the oldest branch that your +change is relevant to. + + - A bugfix should be based on 'maint' in general. If the bug is not + present in 'maint', base it on 'master'. For a bug that's not yet + in 'master', find the topic that introduces the regression, and + base your work on the tip of the topic. + + - A new feature should be based on 'master' in general. If the new + feature depends on a topic that is in 'pu', but not in 'master', + base your work on the tip of that topic. + + - Corrections and enhancements to a topic not yet in 'master' should + be based on the tip of that topic. If the topic has not been merged + to 'next', it's alright to add a note to squash minor corrections + into the series. + + - In the exceptional case that a new feature depends on several topics + not in 'master', start working on 'next' or 'pu' privately and send + out patches for discussion. Before the final merge, you may have to + wait until some of the dependent topics graduate to 'master', and + rebase your work. + +To find the tip of a topic branch, run "git log --first-parent +master..pu" and look for the merge commit. The second parent of this +commit is the tip of the topic branch. (1) Make separate commits for logically separate changes. @@ -170,17 +198,16 @@ patch, format it as "multipart/signed", not a text/plain message that starts with '-----BEGIN PGP SIGNED MESSAGE-----'. That is not a text/plain, it's something else. -Note that your maintainer does not necessarily read everything -on the git mailing list. If your patch is for discussion first, -send it "To:" the mailing list, and optionally "cc:" him. If it -is trivially correct or after the list reached a consensus, send -it "To:" the maintainer and optionally "cc:" the list for -inclusion. - -Also note that your maintainer does not actively involve himself in -maintaining what are in contrib/ hierarchy. When you send fixes and -enhancements to them, do not forget to "cc: " the person who primarily -worked on that hierarchy in contrib/. +Unless your patch is a very trivial and an obviously correct one, +first send it with "To:" set to the mailing list, with "cc:" listing +people who are involved in the area you are touching (the output from +"git blame $path" and "git shortlog --no-merges $path" would help to +identify them), to solicit comments and reviews. After the list +reached a consensus that it is a good idea to apply the patch, re-send +it with "To:" set to the maintainer and optionally "cc:" the list for +inclusion. Do not forget to add trailers such as "Acked-by:", +"Reviewed-by:" and "Tested-by:" after your "Signed-off-by:" line as +necessary. (4) Sign your work @@ -519,12 +546,28 @@ Gmail GMail does not appear to have any way to turn off line wrapping in the web interface, so this will mangle any emails that you send. You can however -use any IMAP email client to connect to the google imap server, and forward -the emails through that. Just make sure to disable line wrapping in that -email client. Alternatively, use "git send-email" instead. +use "git send-email" and send your patches through the GMail SMTP server, or +use any IMAP email client to connect to the google IMAP server and forward +the emails through that. + +To use "git send-email" and send your patches through the GMail SMTP server, +edit ~/.gitconfig to specify your account settings: + +[sendemail] + smtpencryption = tls + smtpserver = smtp.gmail.com + smtpuser = user@gmail.com + smtppass = p4ssw0rd + smtpserverport = 587 -Submitting properly formatted patches via Gmail is simple now that -IMAP support is available. First, edit your ~/.gitconfig to specify your +Once your commits are ready to be sent to the mailing list, run the +following commands: + + $ git format-patch --cover-letter -M origin/master -o outgoing/ + $ edit outgoing/0000-* + $ git send-email outgoing/* + +To submit using the IMAP interface, first, edit your ~/.gitconfig to specify your account settings: [imap] @@ -538,14 +581,12 @@ account settings: You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error that the "Folder doesn't exist". -Next, ensure that your Gmail settings are correct. In "Settings" the -"Use Unicode (UTF-8) encoding for outgoing messages" should be checked. - -Once your commits are ready to send to the mailing list, run the following -command to send the patch emails to your Gmail Drafts folder. +Once your commits are ready to be sent to the mailing list, run the +following commands: - $ git format-patch -M --stdout origin/master | git imap-send + $ git format-patch --cover-letter -M --stdout origin/master | git imap-send -Go to your Gmail account, open the Drafts folder, find the patch email, fill -in the To: and CC: fields and send away! +Just make sure to disable line wrapping in the email client (GMail web +interface will line wrap no matter what, so you need to use a real +IMAP client). diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index d8205691c6..16e3c68576 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -90,9 +90,9 @@ of lines before or after the line given by <start>. running extra passes of inspection. + <num> is optional but it is the lower bound on the number of -alphanumeric characters that git must detect as moving +alphanumeric characters that git must detect as moving/copying within a file for it to associate those lines with the parent -commit. +commit. The default value is 20. -C|<num>|:: In addition to `-M`, detect lines moved or copied from other @@ -105,9 +105,11 @@ commit. looks for copies from other files in any commit. + <num> is optional but it is the lower bound on the number of -alphanumeric characters that git must detect as moving +alphanumeric characters that git must detect as moving/copying between files for it to associate those lines with the parent -commit. +commit. And the default value is 40. If there are more than one +`-C` options given, the <num> argument of the last `-C` will +take effect. -h:: --help:: diff --git a/Documentation/config.txt b/Documentation/config.txt index 8adb55c801..f81fb918da 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -196,20 +196,17 @@ core.quotepath:: quoted without `-z` regardless of the setting of this variable. -core.autocrlf:: - If true, makes git convert `CRLF` at the end of lines in text files to - `LF` when reading from the work tree, and convert in reverse when - writing to the work tree. The variable can be set to - 'input', in which case the conversion happens only while - reading from the work tree but files are written out to the work - tree with `LF` at the end of lines. A file is considered - "text" (i.e. be subjected to the autocrlf mechanism) based on - the file's `crlf` attribute, or if `crlf` is unspecified, - based on the file's contents. See linkgit:gitattributes[5]. +core.eol:: + Sets the line ending type to use in the working directory for + files that have the `text` property set. Alternatives are + 'lf', 'crlf' and 'native', which uses the platform's native + line ending. The default value is `native`. See + linkgit:gitattributes[5] for more information on end-of-line + conversion. core.safecrlf:: - If true, makes git check if converting `CRLF` as controlled by - `core.autocrlf` is reversible. Git will verify if a command + If true, makes git check if converting `CRLF` is reversible when + end-of-line conversion is active. Git will verify if a command modifies a file in the work tree either directly or indirectly. For example, committing a file followed by checking out the same file should yield the original file in the work tree. If @@ -219,7 +216,7 @@ core.safecrlf:: irreversible conversion but continue the operation. + CRLF conversion bears a slight chance of corrupting data. -autocrlf=true will convert CRLF to LF during commit and LF to +When it is enabled, git will convert CRLF to LF during commit and LF to CRLF during checkout. A file that contains a mixture of LF and CRLF before the commit cannot be recreated by git. For text files this is the right thing to do: it corrects line endings @@ -243,15 +240,25 @@ converting CRLFs corrupts data. + Note, this safety check does not mean that a checkout will generate a file identical to the original file for a different setting of -`core.autocrlf`, but only for the current one. For example, a text -file with `LF` would be accepted with `core.autocrlf=input` and could -later be checked out with `core.autocrlf=true`, in which case the +`core.eol` and `core.autocrlf`, but only for the current one. For +example, a text file with `LF` would be accepted with `core.eol=lf` +and could later be checked out with `core.eol=crlf`, in which case the resulting file would contain `CRLF`, although the original file contained `LF`. However, in both work trees the line endings would be consistent, that is either all `LF` or all `CRLF`, but never mixed. A file with mixed line endings would be reported by the `core.safecrlf` mechanism. +core.autocrlf:: + Setting this variable to "true" is almost the same as setting + the `text` attribute to "auto" on all files except that text + files are not guaranteed to be normalized: files that contain + `CRLF` in the repository will not be touched. Use this + setting if you want to have `CRLF` line endings in your + working directory even though the repository does not have + normalized line endings. This variable can be set to 'input', + in which case no output conversion is performed. + core.symlinks:: If false, symbolic links are checked out as small plain files that contain the link text. linkgit:git-update-index[1] and @@ -481,6 +488,8 @@ core.whitespace:: error (enabled by default). * `indent-with-non-tab` treats a line that is indented with 8 or more space characters as an error (not enabled by default). +* `tab-in-indent` treats a tab character in the initial indent part of + the line as an error (not enabled by default). * `blank-at-eof` treats blank lines added at the end of file as an error (enabled by default). * `trailing-space` is a short-hand to cover both `blank-at-eol` and @@ -518,16 +527,12 @@ check that makes sure that existing object files will not get overwritten. core.notesRef:: When showing commit messages, also show notes which are stored in - the given ref. This ref is expected to contain files named - after the full SHA-1 of the commit they annotate. + the given ref. The ref must be fully qualified. If the given + ref does not exist, it is not an error but means that no + notes should be printed. + -If such a file exists in the given ref, the referenced blob is read, and -appended to the commit message, separated by a "Notes:" line. If the -given ref itself does not exist, it is not an error, but means that no -notes should be printed. -+ -This setting defaults to "refs/notes/commits", and can be overridden by -the `GIT_NOTES_REF` environment variable. +This setting defaults to "refs/notes/commits", and it can be overridden by +the 'GIT_NOTES_REF' environment variable. See linkgit:git-notes[1]. core.sparseCheckout:: Enable "sparse checkout" feature. See section "Sparse checkout" in @@ -555,6 +560,13 @@ it will be treated as a shell command. For example, defining executed from the top-level directory of a repository, which may not necessarily be the current directory. +am.keepcr:: + If true, git-am will call git-mailsplit for patches in mbox format + with parameter '--keep-cr'. In this case git-mailsplit will + not remove `\r` from lines ending with `\r\n`. Can be overrriden + by giving '--no-keep-cr' from the command line. + See linkgit:git-am[1], linkgit:git-mailsplit[1]. + apply.ignorewhitespace:: When set to 'change', tells 'git apply' to ignore changes in whitespace, in the same way as the '--ignore-space-change' @@ -678,14 +690,39 @@ color.diff.<slot>:: (highlighting whitespace errors). The values of these variables may be specified as in color.branch.<slot>. +color.decorate.<slot>:: + Use customized color for 'git log --decorate' output. `<slot>` is one + of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local + branches, remote tracking branches, tags, stash and HEAD, respectively. + color.grep:: When set to `always`, always highlight matches. When `false` (or `never`), never. When set to `true` or `auto`, use color only when the output is written to the terminal. Defaults to `false`. -color.grep.match:: - Use customized color for matches. The value of this variable - may be specified as in color.branch.<slot>. +color.grep.<slot>:: + Use customized color for grep colorization. `<slot>` specifies which + part of the line to use the specified color, and is one of ++ +-- +`context`;; + non-matching text in context lines (when using `-A`, `-B`, or `-C`) +`filename`;; + filename prefix (when not using `-h`) +`function`;; + function name lines (when using `-p`) +`linenumber`;; + line number prefix (when using `-n`) +`match`;; + matching text +`selected`;; + non-matching text in selected lines +`separator`;; + separators between fields on a line (`:`, `-`, and `=`) + and between hunks (`--`) +-- ++ +The values of these variables may be specified as in color.branch.<slot>. color.interactive:: When set to `always`, always use colors for interactive prompts @@ -767,6 +804,8 @@ diff.mnemonicprefix:: standard "a/" and "b/" depending on what is being compared. When this configuration is in effect, reverse diff output also swaps the order of the prefixes: +diff.noprefix:: + If set, 'git diff' does not show any source or destination prefix. `git diff`;; compares the (i)ndex and the (w)ork tree; `git diff HEAD`;; @@ -847,14 +886,22 @@ format.headers:: Additional email headers to include in a patch to be submitted by mail. See linkgit:git-format-patch[1]. +format.to:: format.cc:: - Additional "Cc:" headers to include in a patch to be submitted - by mail. See the --cc option in linkgit:git-format-patch[1]. + Additional recipients to include in a patch to be submitted + by mail. See the --to and --cc options in + linkgit:git-format-patch[1]. format.subjectprefix:: The default for format-patch is to output files with the '[PATCH]' subject prefix. Use this variable to change that prefix. +format.signature:: + The default for format-patch is to output a signature containing + the git version number. Use this variable to change that default. + Set this variable to the empty string ("") to suppress + signature generation. + format.suffix:: The default for format-patch is to output files with the suffix `.patch`. Use this variable to change that suffix (make sure to @@ -915,13 +962,19 @@ gc.pruneexpire:: unreachable objects immediately. gc.reflogexpire:: +gc.<pattern>.reflogexpire:: 'git reflog expire' removes reflog entries older than - this time; defaults to 90 days. + this time; defaults to 90 days. With "<pattern>" (e.g. + "refs/stash") in the middle the setting applies only to + the refs that match the <pattern>. gc.reflogexpireunreachable:: +gc.<ref>.reflogexpireunreachable:: 'git reflog expire' removes reflog entries older than this time and are not reachable from the current tip; - defaults to 30 days. + defaults to 30 days. With "<pattern>" (e.g. "refs/stash") + in the middle, the setting applies only to the refs that + match the <pattern>. gc.rerereresolved:: Records of conflicted merge you resolved earlier are @@ -946,13 +999,15 @@ gitcvs.logfile:: various stuff. See linkgit:git-cvsserver[1]. gitcvs.usecrlfattr:: - If true, the server will look up the `crlf` attribute for - files to determine the '-k' modes to use. If `crlf` is set, - the '-k' mode will be left blank, so cvs clients will - treat it as text. If `crlf` is explicitly unset, the file + If true, the server will look up the end-of-line conversion + attributes for files to determine the '-k' modes to use. If + the attributes force git to treat a file as text, + the '-k' mode will be left blank so cvs clients will + treat it as text. If they suppress text conversion, the file will be set with '-kb' mode, which suppresses any newline munging - the client might otherwise do. If `crlf` is not specified, - then 'gitcvs.allbinary' is used. See linkgit:gitattributes[5]. + the client might otherwise do. If the attributes do not allow + the file type to be determined, then 'gitcvs.allbinary' is + used. See linkgit:gitattributes[5]. gitcvs.allbinary:: This is used if 'gitcvs.usecrlfattr' does not resolve @@ -1203,6 +1258,10 @@ imap:: The configuration variables in the 'imap' section are described in linkgit:git-imap-send[1]. +init.templatedir:: + Specify the directory from which templates will be copied. + (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].) + instaweb.browser:: Specify the program that will be used to browse your working repository in gitweb. See linkgit:git-instaweb[1]. @@ -1235,6 +1294,13 @@ log.date:: following alternatives: {relative,local,default,iso,rfc,short}. See linkgit:git-log[1]. +log.decorate:: + Print out the ref names of any commits that are shown by the log + command. If 'short' is specified, the ref name prefixes 'refs/heads/', + 'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is + specified, the full ref name (including prefix) will be printed. + This is the same as the log commands '--decorate' option. + log.showroot:: If true, the initial commit will be shown as a big creation event. This is equivalent to a diff against an empty tree. @@ -1303,6 +1369,53 @@ mergetool.keepTemporaries:: mergetool.prompt:: Prompt before each invocation of the merge resolution program. +notes.displayRef:: + The (fully qualified) refname from which to show notes when + showing commit messages. The value of this variable can be set + to a glob, in which case notes from all matching refs will be + shown. You may also specify this configuration variable + several times. A warning will be issued for refs that do not + exist, but a glob that does not match any refs is silently + ignored. ++ +This setting can be overridden with the `GIT_NOTES_DISPLAY_REF` +environment variable, which must be a colon separated list of refs or +globs. ++ +The effective value of "core.notesRef" (possibly overridden by +GIT_NOTES_REF) is also implicitly added to the list of refs to be +displayed. + +notes.rewrite.<command>:: + When rewriting commits with <command> (currently `amend` or + `rebase`) and this variable is set to `true`, git + automatically copies your notes from the original to the + rewritten commit. Defaults to `true`, but see + "notes.rewriteRef" below. + +notes.rewriteMode:: + When copying notes during a rewrite (see the + "notes.rewrite.<command>" option), determines what to do if + the target commit already has a note. Must be one of + `overwrite`, `concatenate`, or `ignore`. Defaults to + `concatenate`. ++ +This setting can be overridden with the `GIT_NOTES_REWRITE_MODE` +environment variable. + +notes.rewriteRef:: + When copying notes during a rewrite, specifies the (fully + qualified) ref whose notes should be copied. The ref may be a + glob, in which case notes in all matching refs will be copied. + You may also specify this configuration several times. ++ +Does not have a default value; you must configure this variable to +enable note rewriting. ++ +This setting can be overridden with the `GIT_NOTES_REWRITE_REF` +environment variable, which must be a colon separated list of refs or +globs. + pack.window:: The size of the window used by linkgit:git-pack-objects[1] when no window size is given on the command line. Defaults to 10. @@ -1386,6 +1499,16 @@ pager.<cmd>:: it takes precedence over this option. To disable pagination for all commands, set `core.pager` or `GIT_PAGER` to `cat`. +pretty.<name>:: + Alias for a --pretty= format string, as specified in + linkgit:git-log[1]. Any aliases defined here can be used just + as the built-in pretty formats could. For example, + running `git config pretty.changelog "format:{asterisk} %H %s"` + would cause the invocation `git log --pretty=changelog` + to be equivalent to running `git log "--pretty=format:{asterisk} %H %s"`. + Note that an alias with the same name as a built-in format + will be silently ignored. + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. @@ -1435,8 +1558,12 @@ receive.denyDeletes:: If set to true, git-receive-pack will deny a ref update that deletes the ref. Use this to prevent such a ref deletion via a push. +receive.denyDeleteCurrent:: + If set to true, git-receive-pack will deny a ref update that + deletes the currently checked out branch of a non-bare repository. + receive.denyCurrentBranch:: - If set to true or "refuse", receive-pack will deny a ref update + If set to true or "refuse", git-receive-pack will deny a ref update to the currently checked out branch of a non-bare repository. Such a push is potentially dangerous because it brings the HEAD out of sync with the index and working tree. If set to "warn", @@ -1498,7 +1625,9 @@ remote.<name>.uploadpack:: remote.<name>.tagopt:: Setting this value to \--no-tags disables automatic tag following when - fetching from remote <name> + fetching from remote <name>. Setting it to \--tags will fetch every + tag from remote <name>, even if they are not reachable from remote + branch heads. remote.<name>.vcs:: Setting this to a value <vcs> will cause git to interact with @@ -1562,6 +1691,7 @@ sendemail.smtppass:: sendemail.suppresscc:: sendemail.suppressfrom:: sendemail.to:: +sendemail.smtpdomain:: sendemail.smtpserver:: sendemail.smtpserverport:: sendemail.smtpuser:: @@ -1608,6 +1738,15 @@ status.submodulesummary:: summary of commits for modified submodules will be shown (see --summary-limit option of linkgit:git-submodule[1]). +submodule.<name>.path:: +submodule.<name>.url:: +submodule.<name>.update:: + The path within this project, URL, and the updating strategy + for a submodule. These variables are initially populated + by 'git submodule init'; edit them to override the + URL and other values found in the `.gitmodules` file. See + linkgit:git-submodule[1] and linkgit:gitmodules[5] for details. + tar.umask:: This variable can be used to restrict the permission bits of tar archive entries. The default is 0002, which turns off the diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt index 0f25ba7e38..8f9a2412fd 100644 --- a/Documentation/diff-generate-patch.txt +++ b/Documentation/diff-generate-patch.txt @@ -56,7 +56,8 @@ combined diff format "git-diff-tree", "git-diff-files" and "git-diff" can take '-c' or '--cc' option to produce 'combined diff'. For showing a merge commit -with "git log -p", this is the default format. +with "git log -p", this is the default format; you can force showing +full diff with the '-m' option. A 'combined diff' format looks like this: ------------ diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index a1191d647b..eecedaab6e 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -21,6 +21,7 @@ endif::git-format-patch[] ifndef::git-format-patch[] -p:: -u:: +--patch:: Generate patch (see section on generating patches). {git-diff? This is the default.} endif::git-format-patch[] @@ -117,18 +118,48 @@ any of those replacements occurred. option and lists the commits in that commit range like the 'summary' option of linkgit:git-submodule[1] does. ---color:: +--color[=<when>]:: Show colored diff. + The value must be always (the default), never, or auto. --no-color:: Turn off colored diff, even when the configuration file gives the default to color output. + Same as `--color=never`. ---color-words[=<regex>]:: - Show colored word diff, i.e., color words which have changed. - By default, words are separated by whitespace. +--word-diff[=<mode>]:: + Show a word diff, using the <mode> to delimit changed words. + By default, words are delimited by whitespace; see + `--word-diff-regex` below. The <mode> defaults to 'plain', and + must be one of: ++ +-- +color:: + Highlight changed words using only colors. Implies `--color`. +plain:: + Show words as `[-removed-]` and `{+added+}`. Makes no + attempts to escape the delimiters if they appear in the input, + so the output may be ambiguous. +porcelain:: + Use a special line-based format intended for script + consumption. Added/removed/unchanged runs are printed in the + usual unified diff format, starting with a `+`/`-`/` ` + character at the beginning of the line and extending to the + end of the line. Newlines in the input are represented by a + tilde `~` on a line of its own. +none:: + Disable word diff again. +-- ++ +Note that despite the name of the first mode, color is used to +highlight the changed parts in all modes if enabled. + +--word-diff-regex=<regex>:: + Use <regex> to decide what a word is, instead of considering + runs of non-whitespace to be a word. Also implies + `--word-diff` unless it was already enabled. + -When a <regex> is specified, every non-overlapping match of the +Every non-overlapping match of the <regex> is considered a word. Anything between these matches is considered whitespace and ignored(!) for the purposes of finding differences. You may want to append `|[^[:space:]]` to your regular @@ -140,6 +171,10 @@ The regex can also be set via a diff driver or configuration option, see linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly overrides any diff driver or configuration setting. Diff drivers override configuration settings. + +--color-words[=<regex>]:: + Equivalent to `--word-diff=color` plus (if a regex was + specified) `--word-diff-regex=<regex>`. endif::git-format-patch[] --no-renames:: @@ -171,14 +206,46 @@ endif::git-format-patch[] the diff-patch output format. Non default number of digits can be specified with `--abbrev=<n>`. --B:: - Break complete rewrite changes into pairs of delete and create. - --M:: +-B[<n>][/<m>]:: + Break complete rewrite changes into pairs of delete and + create. This serves two purposes: ++ +It affects the way a change that amounts to a total rewrite of a file +not as a series of deletion and insertion mixed together with a very +few lines that happen to match textually as the context, but as a +single deletion of everything old followed by a single insertion of +everything new, and the number `m` controls this aspect of the -B +option (defaults to 60%). `-B/70%` specifies that less than 30% of the +original should remain in the result for git to consider it a total +rewrite (i.e. otherwise the resulting patch will be a series of +deletion and insertion mixed together with context lines). ++ +When used with -M, a totally-rewritten file is also considered as the +source of a rename (usually -M only considers a file that disappeared +as the source of a rename), and the number `n` controls this aspect of +the -B option (defaults to 50%). `-B20%` specifies that a change with +addition and deletion compared to 20% or more of the file's size are +eligible for being picked up as a possible source of a rename to +another file. + +-M[<n>]:: +ifndef::git-log[] Detect renames. +endif::git-log[] +ifdef::git-log[] + If generating diffs, detect and report renames for each commit. + For following files across renames while traversing history, see + `--follow`. +endif::git-log[] + If `n` is specified, it is a is a threshold on the similarity + index (i.e. amount of addition/deletions compared to the + file's size). For example, `-M90%` means git should consider a + delete/add pair to be a rename if more than 90% of the file + hasn't changed. --C:: +-C[<n>]:: Detect copies as well as renames. See also `--find-copies-harder`. + If `n` is specified, it has the same meaning as for `-M<n>`. ifndef::git-format-patch[] --diff-filter=[ACDMRTUXB*]:: @@ -286,8 +353,14 @@ endif::git-format-patch[] --no-ext-diff:: Disallow external diff drivers. ---ignore-submodules:: - Ignore changes to submodules in the diff generation. +--ignore-submodules[=<when>]:: + Ignore changes to submodules in the diff generation. <when> can be + either "untracked", "dirty" or "all", which is the default. When + "untracked" is used submodules are not considered dirty when they only + contain untracked content (but they are still scanned for modified + content). Using "dirty" ignores all changes to the work tree of submodules, + only changes to the commits stored in the superproject are shown (this was + the behavior until 1.7.0). Using "all" hides all changes to submodules. --src-prefix=<prefix>:: Show the given source prefix instead of "a/". diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index 9310b650d3..e0ba8cc075 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -1,13 +1,8 @@ Everyday GIT With 20 Commands Or So =================================== -<<Basic Repository>> commands are needed by people who have a -repository --- that is everybody, because every working tree of -git is a repository. - -In addition, <<Individual Developer (Standalone)>> commands are -essential for anybody who makes a commit, even for somebody who -works alone. +<<Individual Developer (Standalone)>> commands are essential for +anybody who makes a commit, even for somebody who works alone. If you work with other people, you will need commands listed in the <<Individual Developer (Participant)>> section as well. @@ -20,46 +15,6 @@ administrators who are responsible for the care and feeding of git repositories. -Basic Repository[[Basic Repository]] ------------------------------------- - -Everybody uses these commands to maintain git repositories. - - * linkgit:git-init[1] or linkgit:git-clone[1] to create a - new repository. - - * linkgit:git-fsck[1] to check the repository for errors. - - * linkgit:git-gc[1] to do common housekeeping tasks such as - repack and prune. - -Examples -~~~~~~~~ - -Check health and remove cruft.:: -+ ------------- -$ git fsck <1> -$ git count-objects <2> -$ git gc <3> ------------- -+ -<1> running without `\--full` is usually cheap and assures the -repository health reasonably well. -<2> check how many loose objects there are and how much -disk space is wasted by not repacking. -<3> repacks the local repository and performs other housekeeping tasks. - -Repack a small project into single pack.:: -+ ------------- -$ git gc <1> ------------- -+ -<1> pack all the objects reachable from the refs into one pack, -then remove the other packs. - - Individual Developer (Standalone)[[Individual Developer (Standalone)]] ---------------------------------------------------------------------- @@ -67,6 +22,8 @@ A standalone individual developer does not exchange patches with other people, and works alone in a single repository, using the following commands. + * linkgit:git-init[1] to create a new repository. + * linkgit:git-show-branch[1] to see where you are. * linkgit:git-log[1] to see what happened. diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index fe716b2e42..9333c42c55 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -34,6 +34,7 @@ ifndef::git-pull[] Allow several <repository> and <group> arguments to be specified. No <refspec>s may be specified. +-p:: --prune:: After fetching, remove any remote tracking branches which no longer exist on the remote. @@ -78,9 +79,16 @@ ifndef::git-pull[] -q:: --quiet:: Pass --quiet to git-fetch-pack and silence any other internally - used git commands. + used git commands. Progress is not reported to the standard error + stream. -v:: --verbose:: Be verbose. endif::git-pull[] + +--progress:: + Progress status is reported on the standard error stream + by default when it is attached to a terminal, unless -q + is specified. This flag forces progress status even if the + standard error stream is not directed to a terminal. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 51cbeb7032..e22a62f065 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -10,7 +10,8 @@ SYNOPSIS [verse] 'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p] [--edit | -e] [--all | [--update | -u]] [--intent-to-add | -N] - [--refresh] [--ignore-errors] [--] [<filepattern>...] + [--refresh] [--ignore-errors] [--ignore-missing] [--] + [<filepattern>...] DESCRIPTION ----------- @@ -57,7 +58,8 @@ OPTIONS -n:: --dry-run:: - Don't actually add the file(s), just show if they exist. + Don't actually add the file(s), just show if they exist and/or will + be ignored. -v:: --verbose:: @@ -131,6 +133,12 @@ subdirectories. them, do not abort the operation, but continue adding the others. The command shall still exit with non-zero status. +--ignore-missing:: + This option can only be used together with --dry-run. By using + this option the user can check if any of the given files would + be ignored, no matter if they are already present in the work + tree or not. + \--:: This option can be used to separate command-line options from the list of files, (useful when filenames might be mistaken @@ -266,9 +274,9 @@ patch:: y - stage this hunk n - do not stage this hunk - q - quit, do not stage this hunk nor any of the remaining ones - a - stage this and all the remaining hunks in the file - d - do not stage this hunk nor any of the remaining hunks in the file + q - quit; do not stage this hunk nor any of the remaining ones + a - stage this hunk and all later hunks in the file + d - do not stage this hunk nor any of the later hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 23864df8da..9e62f8778f 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -9,7 +9,7 @@ git-am - Apply a series of patches from a mailbox SYNOPSIS -------- [verse] -'git am' [--signoff] [--keep] [--utf8 | --no-utf8] +'git am' [--signoff] [--keep] [--keep-cr | --no-keep-cr] [--utf8 | --no-utf8] [--3way] [--interactive] [--committer-date-is-author-date] [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] @@ -39,6 +39,13 @@ OPTIONS --keep:: Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). +--keep-cr:: +--no-keep-cr:: + With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1]) + with the same option, to prevent it from stripping CR at the end of + lines. `am.keepcr` configuration variable can be used to specify the + default behaviour. `--no-keep-cr` is useful to override `am.keepcr`. + -c:: --scissors:: Remove everything in body before a scissors line (see diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 8463439ac5..4a74b23d40 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -26,6 +26,10 @@ with the `--cache` option the patch is only applied to the index. Without these options, the command applies the patch only to files, and does not require them to be in a git repository. +This command applies the patch but does not create a commit. Use +linkgit:git-am[1] to create commits from patches generated by +linkgit:git-format-patch[1] and/or received by email. + OPTIONS ------- <patch>...:: @@ -242,6 +246,12 @@ If `--index` is not specified, then the submodule commits in the patch are ignored and only the absence or presence of the corresponding subdirectory is checked and (if possible) updated. + +SEE ALSO +-------- +linkgit:git-am[1]. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 60fa684b1d..1940256930 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,7 +8,7 @@ git-branch - List, create, or delete branches SYNOPSIS -------- [verse] -'git branch' [--color | --no-color] [-r | -a] +'git branch' [--color[=<when>] | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]] [(--merged | --no-merged | --contains) [<commit>]] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>] @@ -88,12 +88,14 @@ OPTIONS -M:: Move/rename a branch even if the new branch name already exists. ---color:: +--color[=<when>]:: Color branches to highlight current, local, and remote branches. + The value must be always (the default), never, or auto. --no-color:: Turn off branch colors, even when the configuration file gives the default to color output. + Same as `--color=never`. -r:: List or delete (if used with -d) the remote-tracking branches. diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 58c8d65772..a3f56b07fd 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -9,14 +9,15 @@ git-cat-file - Provide content or type and size information for repository objec SYNOPSIS -------- [verse] -'git cat-file' (-t | -s | -e | -p | <type>) <object> +'git cat-file' (-t | -s | -e | -p | <type> | --textconv ) <object> 'git cat-file' (--batch | --batch-check) < <list-of-objects> DESCRIPTION ----------- In its first form, the command provides the content or the type of an object in the repository. The type is required unless '-t' or '-p' is used to find the -object type, or '-s' is used to find the object size. +object type, or '-s' is used to find the object size, or '--textconv' is used +(which implies type "blob"). In the second form, a list of objects (separated by linefeeds) is provided on stdin, and the SHA1, type, and size of each object is printed on stdout. @@ -26,7 +27,7 @@ OPTIONS <object>:: The name of the object to show. For a more complete list of ways to spell object names, see - the "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. + the "SPECIFYING REVISIONS" section in linkgit:gitrevisions[1]. -t:: Instead of the content, show the object type identified by @@ -51,6 +52,11 @@ OPTIONS or to ask for a "blob" with <object> being a tag object that points at it. +--textconv:: + Show the content as transformed by a textconv filter. In this case, + <object> has be of the form <treeish>:<path>, or :<path> in order + to apply the filter to the content recorded in the index at <path>. + --batch:: Print the SHA1, type, size, and contents of each object provided on stdin. May not be combined with any other options or arguments. diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index 379eee6734..f5c2e0601d 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -49,7 +49,7 @@ git imposes the following rules on how references are named: These rules make it easy for shell script based tools to parse reference names, pathname expansion by the shell when a reference name is used unquoted (by mistake), and also avoids ambiguities in certain -reference name expressions (see linkgit:git-rev-parse[1]): +reference name expressions (see linkgit:gitrevisions[1]): . A double-dot `..` is often used as in `ref1..ref2`, and in some contexts this notation means `{caret}ref1 ref2` (i.e. not in diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 37c1810e3f..1bacd2e104 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -9,39 +9,47 @@ SYNOPSIS -------- [verse] 'git checkout' [-q] [-f] [-m] [<branch>] -'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>] +'git checkout' [-q] [-f] [-m] [[-b|--orphan] <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... 'git checkout' --patch [<tree-ish>] [--] [<paths>...] DESCRIPTION ----------- - -When <paths> are not given, this command switches branches by -updating the index, working tree, and HEAD to reflect the specified +Updates files in the working tree to match the version in the index +or the specified tree. If no paths are given, 'git checkout' will +also update `HEAD` to set the specified branch as the current branch. -If `-b` is given, a new branch is created and checked out, as if -linkgit:git-branch[1] were called; in this case you can -use the --track or --no-track options, which will be passed to `git -branch`. As a convenience, --track without `-b` implies branch -creation; see the description of --track below. - -When <paths> or --patch are given, this command does *not* switch -branches. It updates the named paths in the working tree from -the index file, or from a named <tree-ish> (most often a commit). In -this case, the `-b` and `--track` options are meaningless and giving -either of them results in an error. The <tree-ish> argument can be -used to specify a specific tree-ish (i.e. commit, tag or tree) -to update the index for the given paths before updating the -working tree. - -The index may contain unmerged entries after a failed merge. By -default, if you try to check out such an entry from the index, the +'git checkout' [<branch>]:: +'git checkout' -b <new branch> [<start point>]:: + + This form switches branches by updating the index, working + tree, and HEAD to reflect the specified branch. ++ +If `-b` is given, a new branch is created as if linkgit:git-branch[1] +were called and then checked out; in this case you can +use the `--track` or `--no-track` options, which will be passed to +'git branch'. As a convenience, `--track` without `-b` implies branch +creation; see the description of `--track` below. + +'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...:: + + When <paths> or `--patch` are given, 'git checkout' *not* switch + branches. It updates the named paths in the working tree from + the index file or from a named <tree-ish> (most often a commit). In + this case, the `-b` and `--track` options are meaningless and giving + either of them results in an error. The <tree-ish> argument can be + used to specify a specific tree-ish (i.e. commit, tag or tree) + to update the index for the given paths before updating the + working tree. ++ +The index may contain unmerged entries because of a previous failed merge. +By default, if you try to check out such an entry from the index, the checkout operation will fail and nothing will be checked out. -Using -f will ignore these unmerged entries. The contents from a +Using `-f` will ignore these unmerged entries. The contents from a specific side of the merge can be checked out of the index by -using --ours or --theirs. With -m, changes made to the working tree -file can be discarded to recreate the original conflicted merge result. +using `--ours` or `--theirs`. With `-m`, changes made to the working tree +file can be discarded to re-create the original conflicted merge result. OPTIONS ------- @@ -90,6 +98,31 @@ explicitly give a name with '-b' in such a case. Create the new branch's reflog; see linkgit:git-branch[1] for details. +--orphan:: + Create a new 'orphan' branch, named <new_branch>, started from + <start_point> and switch to it. The first commit made on this + new branch will have no parents and it will be the root of a new + history totally disconnected from all the other branches and + commits. ++ +The index and the working tree are adjusted as if you had previously run +"git checkout <start_point>". This allows you to start a new history +that records a set of paths similar to <start_point> by easily running +"git commit -a" to make the root commit. ++ +This can be useful when you want to publish the tree from a commit +without exposing its full history. You might want to do this to publish +an open source branch of a project whose current tree is "clean", but +whose full history contains proprietary or otherwise encumbered bits of +code. ++ +If you want to start a disconnected history that records a set of paths +that is totally different from the one of <start_point>, then you should +clear the index and the working tree right after creating the orphan +branch by running "git rm -rf ." from the top level of the working tree. +Afterwards you will be ready to prepare your new files, repopulating the +working tree, by copying them from elsewhere, extracting a tarball, etc. + -m:: --merge:: When switching branches, @@ -136,6 +169,10 @@ edits from your current working tree. As a special case, the `"@\{-N\}"` syntax for the N-th last branch checks out the branch (instead of detaching). You may also specify `-` which is synonymous with `"@\{-1\}"`. ++ +As a further special case, you may use `"A...B"` as a shortcut for the +merge base of `A` and `B` if there is exactly one merge base. You can +leave out at most one of `A` and `B`, in which case it defaults to `HEAD`. <new_branch>:: Name for the new branch. @@ -226,7 +263,7 @@ the above checkout would fail like this: + ------------ $ git checkout mytopic -fatal: Entry 'frotz' not uptodate. Cannot merge. +error: You have local changes to 'frotz'; not switching branches. ------------ + You can give the `-m` flag to the command, which would try a diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 78f4714da0..2cef579316 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -3,24 +3,28 @@ git-cherry-pick(1) NAME ---- -git-cherry-pick - Apply the change introduced by an existing commit +git-cherry-pick - Apply the changes introduced by some existing commits SYNOPSIS -------- -'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] <commit> +'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>... DESCRIPTION ----------- -Given one existing commit, apply the change the patch introduces, and record a -new commit that records it. This requires your working tree to be clean (no -modifications from the HEAD commit). + +Given one or more existing commits, apply the change each one +introduces, recording a new commit for each. This requires your +working tree to be clean (no modifications from the HEAD commit). OPTIONS ------- -<commit>:: - Commit to cherry-pick. - For a more complete list of ways to spell commits, see the - "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. +<commit>...:: + Commits to cherry-pick. + For a more complete list of ways to spell commits, see + linkgit:gitrevisions[1]. + Sets of commits can be passed but no traversal is done by + default, as if the '--no-walk' option was specified, see + linkgit:git-rev-list[1]. -e:: --edit:: @@ -55,10 +59,10 @@ OPTIONS -n:: --no-commit:: - Usually the command automatically creates a commit. - This flag applies the change necessary to cherry-pick - the named commit to your working tree and the index, - but does not make the commit. In addition, when this + Usually the command automatically creates a sequence of commits. + This flag applies the changes necessary to cherry-pick + each named commit to your working tree and the index, + without making any commit. In addition, when this option is used, your index does not have to match the HEAD commit. The cherry-pick is done against the beginning state of your index. @@ -70,6 +74,51 @@ effect to your index in a row. --signoff:: Add Signed-off-by line at the end of the commit message. +--ff:: + If the current HEAD is the same as the parent of the + cherry-pick'ed commit, then a fast forward to this commit will + be performed. + +EXAMPLES +-------- +git cherry-pick master:: + + Apply the change introduced by the commit at the tip of the + master branch and create a new commit with this change. + +git cherry-pick ..master:: +git cherry-pick ^HEAD master:: + + Apply the changes introduced by all commits that are ancestors + of master but not of HEAD to produce new commits. + +git cherry-pick master\~4 master~2:: + + Apply the changes introduced by the fifth and third last + commits pointed to by master and create 2 new commits with + these changes. + +git cherry-pick -n master~1 next:: + + Apply to the working tree and the index the changes introduced + by the second last commit pointed to by master and by the last + commit pointed to by next, but do not create any commit with + these changes. + +git cherry-pick --ff ..next:: + + If history is linear and HEAD is an ancestor of next, update + the working tree and advance the HEAD pointer to match next. + Otherwise, apply the changes introduced by those commits that + are in next but not HEAD to the current branch, creating a new + commit for each new change. + +git rev-list --reverse master \-- README | git cherry-pick -n --stdin:: + + Apply the changes introduced by all commits on the master + branch that touched README to the working tree and index, + so the result can be inspected and made into a single new + commit if suitable. Author ------ @@ -79,6 +128,10 @@ Documentation -------------- Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. +SEE ALSO +-------- +linkgit:git-revert[1] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index d15cb17d78..dc7d3d17b1 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -102,7 +102,8 @@ objects from the source repository into a pack in the cloned repository. --verbose:: -v:: - Run verbosely. + Run verbosely. Does not affect the reporting of progress status + to the standard error stream. --progress:: Progress status is reported on the standard error stream @@ -149,8 +150,7 @@ objects from the source repository into a pack in the cloned repository. --template=<template_directory>:: Specify the directory from which templates will be used; - if unset the templates are taken from the installation - defined default, typically `/usr/share/git-core/templates`. + (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].) --depth <depth>:: Create a 'shallow' clone with a history truncated to the diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 64fb458b45..c28603ecf5 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run] [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author] - [--allow-empty] [--no-verify] [-e] [--author=<author>] + [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] [--date=<date>] [--cleanup=<mode>] [--status | --no-status] [--] [[-i | -o ]<file>...] @@ -95,10 +95,11 @@ OPTIONS read the message from the standard input. --author=<author>:: - Override the author name used in the commit. You can use the - standard `A U Thor <author@example.com>` format. Otherwise, - an existing commit that matches the given string and its author - name is used. + Override the commit author. Specify an explicit author using the + standard `A U Thor <author@example.com>` format. Otherwise <author> + is assumed to be a pattern and is used to search for an existing + commit by that author (i.e. rev-list --all -i --author=<author>); + the commit author is then copied from the first such commit found. --date=<date>:: Override the author date used in the commit. @@ -131,6 +132,12 @@ OPTIONS from making such a commit. This option bypasses the safety, and is primarily for use by foreign scm interface scripts. +--allow-empty-message:: + Like --allow-empty this command is primarily for use by foreign + scm interface scripts. It allows you to create a commit with an + empty commit message without using plumbing commands like + linkgit:git-commit-tree[1]. + --cleanup=<mode>:: This option sets how the commit message is cleaned up. The '<mode>' can be one of 'verbatim', 'whitespace', 'strip', diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index ddfcb3d143..8bcd875a67 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -13,7 +13,7 @@ SYNOPSIS [-A <author-conv-file>] [-p <options-for-cvsps>] [-P <file>] [-C <git_repository>] [-z <fuzz>] [-i] [-k] [-u] [-s <subst>] [-a] [-m] [-M <regex>] [-S <regex>] [-L <commitlimit>] - [-r <remote>] [<CVS_module>] + [-r <remote>] [-R] [<CVS_module>] DESCRIPTION @@ -157,6 +157,22 @@ It is not recommended to use this feature if you intend to export changes back to CVS again later with 'git cvsexportcommit'. +-R:: + Generate a `$GIT_DIR/cvs-revisions` file containing a mapping from CVS + revision numbers to newly-created Git commit IDs. The generated file + will contain one line for each (filename, revision) pair imported; + each line will look like ++ +--------- +src/widget.c 1.1 1d862f173cdc7325b6fa6d2ae1cfd61fd1b512b7 +--------- ++ +The revision data is appended to the file if it already exists, for use when +doing incremental imports. ++ +This option may be useful if you have CVS revision numbers stored in commit +messages, bug-tracking systems, email archives, and the like. + -h:: Print a short usage message and exit. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index dbb053ee17..7004dd2dec 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -72,9 +72,6 @@ plugin. Most functionality works fine with both of these clients. LIMITATIONS ----------- -Currently cvsserver works over SSH connections for read/write clients, and -over pserver for anonymous CVS access. - CVS clients cannot tag, branch or perform GIT merges. 'git-cvsserver' maps GIT branches to CVS modules. This is very different @@ -84,7 +81,7 @@ one or more directories. INSTALLATION ------------ -1. If you are going to offer anonymous CVS access via pserver, add a line in +1. If you are going to offer CVS access via pserver, add a line in /etc/inetd.conf like + -- @@ -101,6 +98,38 @@ looks like cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver ------ + +Only anonymous access is provided by pserve by default. To commit you +will have to create pserver accounts, simply add a gitcvs.authdb +setting in the config file of the repositories you want the cvsserver +to allow writes to, for example: + +------ + + [gitcvs] + authdb = /etc/cvsserver/passwd + +------ +The format of these files is username followed by the crypted password, +for example: + +------ + myuser:$1Oyx5r9mdGZ2 + myuser:$1$BA)@$vbnMJMDym7tA32AamXrm./ +------ +You can use the 'htpasswd' facility that comes with Apache to make these +files, but Apache's MD5 crypt method differs from the one used by most C +library's crypt() function, so don't use the -m option. + +Alternatively you can produce the password with perl's crypt() operator: +----- + perl -e 'my ($user, $pass) = @ARGV; printf "%s:%s\n", $user, crypt($user, $pass)' $USER password +----- + +Then provide your password via the pserver method, for example: +------ + cvs -d:pserver:someuser:somepassword <at> server/path/repo.git co <HEAD_name> +------ No special setup is needed for SSH access, other than having GIT tools in the PATH. If you have clients that do not accept the CVS_SERVER environment variable, you can rename 'git-cvsserver' to `cvs`. @@ -340,16 +369,13 @@ By default the server leaves the '-k' mode blank for all files, which causes the cvs client to treat them as a text files, subject to crlf conversion on some platforms. -You can make the server use `crlf` attributes to set the '-k' modes -for files by setting the `gitcvs.usecrlfattr` config variable. -In this case, if `crlf` is explicitly unset ('-crlf'), then the -server will set '-kb' mode for binary files. If `crlf` is set, -then the '-k' mode will explicitly be left blank. See -also linkgit:gitattributes[5] for more information about the `crlf` -attribute. +You can make the server use the end-of-line conversion attributes to +set the '-k' modes for files by setting the `gitcvs.usecrlfattr` +config variable. See linkgit:gitattributes[5] for more information +about end-of-line conversion. Alternatively, if `gitcvs.usecrlfattr` config is not enabled -or if the `crlf` attribute is unspecified for a filename, then +or the attributes do not allow automatic detection for a filename, then the server uses the `gitcvs.allbinary` config for the default setting. If `gitcvs.allbinary` is set, then file not otherwise specified will default to '-kb' mode. Otherwise the '-k' mode diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 723a64872f..08fd4099ad 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -68,11 +68,11 @@ for the last two forms that use ".." notations, can be any <tree-ish>. For a more complete list of ways to spell <commit>, see -"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. +"SPECIFYING REVISIONS" section in linkgit:gitrevisions[1]. However, "diff" is about comparing two _endpoints_, not ranges, and the range notations ("<commit>..<commit>" and "<commit>\...<commit>") do not mean a range as defined in the -"SPECIFYING RANGES" section in linkgit:git-rev-parse[1]. +"SPECIFYING RANGES" section in linkgit:gitrevisions[1]. OPTIONS ------- diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 19082b04eb..77a0a2481a 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -439,7 +439,7 @@ Marks must be declared (via `mark`) before they can be used. * A complete 40 byte or abbreviated commit SHA-1 in hex. * Any valid Git SHA-1 expression that resolves to a commit. See - ``SPECIFYING REVISIONS'' in linkgit:git-rev-parse[1] for details. + ``SPECIFYING REVISIONS'' in linkgit:gitrevisions[1] for details. The special case of restarting an incremental import from the current branch value should be written as: diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 7e83288d18..390d85ccae 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -86,6 +86,7 @@ objectsize:: objectname:: The object name (aka SHA-1). + For a non-ambiguous abbreviation of the object name append `:short`. upstream:: The name of a local ref which can be considered ``upstream'' diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 9674f9de67..4b3f5ba535 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -13,12 +13,13 @@ SYNOPSIS [--no-thread | --thread[=<style>]] [(--attach|--inline)[=<boundary>] | --no-attach] [-s | --signoff] + [--signature=<signature> | --no-signature] [-n | --numbered | -N | --no-numbered] [--start-number <n>] [--numbered-files] [--in-reply-to=Message-Id] [--suffix=.<sfx>] [--ignore-if-in-upstream] [--subject-prefix=Subject-Prefix] - [--cc=<email>] + [--to=<email>] [--cc=<email>] [--cover-letter] [<common diff options>] [ <since> | <revision range> ] @@ -38,7 +39,7 @@ There are two ways to specify which commits to operate on. that leads to the <since> to be output. 2. Generic <revision range> expression (see "SPECIFYING - REVISIONS" section in linkgit:git-rev-parse[1]) means the + REVISIONS" section in linkgit:gitrevisions[1]) means the commits in the specified range. The first rule takes precedence in the case of a single <commit>. To @@ -162,6 +163,10 @@ will want to ensure that threading is disabled for `git send-email`. allows for useful naming of a patch series, and can be combined with the `--numbered` option. +--to=<email>:: + Add a `To:` header to the email headers. This is in addition + to any configured headers, and may be used multiple times. + --cc=<email>:: Add a `Cc:` header to the email headers. This is in addition to any configured headers, and may be used multiple times. @@ -176,6 +181,12 @@ will want to ensure that threading is disabled for `git send-email`. containing the shortlog and the overall diffstat. You can fill in a description in the file before sending it out. +--[no]-signature=<signature>:: + Add a signature to each message produced. Per RFC 3676 the signature + is separated from the body by a line with '-- ' on it. If the + signature option is omitted the signature defaults to the git version + number. + --suffix=.<sfx>:: Instead of using `.patch` as the suffix for generated filenames, use specified suffix. A common alternative is @@ -202,8 +213,8 @@ CONFIGURATION ------------- You can specify extra mail header lines to be added to each message, defaults for the subject prefix and file suffix, number patches when -outputting more than one patch, add "Cc:" headers, configure attachments, -and sign off patches with configuration variables. +outputting more than one patch, add "To" or "Cc:" headers, configure +attachments, and sign off patches with configuration variables. ------------ [format] @@ -211,6 +222,7 @@ and sign off patches with configuration variables. subjectprefix = CHANGE suffix = .txt numbered = auto + to = <email> cc = <email> attach [ = mime-boundary-string ] signoff = true diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 189573a3b3..315f07ef1c 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -88,6 +88,16 @@ commits prior to the amend or rebase occurring. Since these changes are not part of the current project most users will want to expire them sooner. This option defaults to '30 days'. +The above two configuration variables can be given to a pattern. For +example, this sets non-default expiry values only to remote tracking +branches: + +------------ +[gc "refs/remotes/*"] + reflogExpire = never + reflogexpireUnreachable = 3 days +------------ + The optional configuration variable 'gc.rerereresolved' indicates how long records of conflicted merge you resolved earlier are kept. This defaults to 60 days. @@ -127,6 +137,13 @@ If you are expecting some objects to be collected and they aren't, check all of those locations and decide whether it makes sense in your case to remove those references. +HOOKS +----- + +The 'git gc --auto' command will run the 'pre-auto-gc' hook. See +linkgit:githooks[5] for more information. + + SEE ALSO -------- linkgit:git-prune[1] diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index ee506e67f0..5474dd7f94 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -14,10 +14,11 @@ SYNOPSIS [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] + [(-O | --open-files-in-pager) [<pager>]] [-z | --null] [-c | --count] [--all-match] [-q | --quiet] [--max-depth <depth>] - [--color | --no-color] + [--color[=<when>] | --no-color] [-A <post-context>] [-B <pre-context>] [-C <context>] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] @@ -104,6 +105,13 @@ OPTIONS For better compatibility with 'git diff', `--name-only` is a synonym for `--files-with-matches`. +-O [<pager>]:: +--open-files-in-pager [<pager>]:: + Open the matching files in the pager (not the output of 'grep'). + If the pager happens to be "less" or "vi", and the user + specified only one pattern, the first file is positioned at + the first match automatically. + -z:: --null:: Output \0 instead of the character that normally follows a @@ -114,12 +122,14 @@ OPTIONS Instead of showing every matched line, show the number of lines that match. ---color:: +--color[=<when>]:: Show colored matches. + The value must be always (the default), never, or auto. --no-color:: Turn off match highlighting, even when the configuration file gives the default to color output. + Same as `--color=never`. -[ABC] <context>:: Show `context` trailing (`A` -- after), or leading (`B` @@ -181,7 +191,7 @@ OPTIONS Examples -------- -git grep 'time_t' -- '*.[ch]':: +git grep 'time_t' \-- '*.[ch]':: Looks for `time_t` in all tracked .c and .h files in the working directory and its subdirectories. diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt index 479fce4693..6904739a48 100644 --- a/Documentation/git-hash-object.txt +++ b/Documentation/git-hash-object.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git hash-object' [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>... -'git hash-object' [-t <type>] [-w] --stdin-paths < <list-of-paths> +'git hash-object' [-t <type>] [-w] --stdin-paths [--no-filters] < <list-of-paths> DESCRIPTION ----------- diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index ad446b0e8b..57aba42e66 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -73,6 +73,10 @@ imap.preformattedHTML:: option causes Thunderbird to send the patch as a plain/text, format=fixed email. Default is `false`. +imap.authMethod:: + Specify authenticate method for authentication with IMAP server. + Current supported method is 'CRAM-MD5' only. + Examples ~~~~~~~~ diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 7ee102da48..246b07ebf9 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -28,14 +28,8 @@ current working directory. --template=<template_directory>:: -Provide the directory from which templates will be used. The default template -directory is `/usr/share/git-core/templates`. - -When specified, `<template_directory>` is used as the source of the template -files rather than the default. The template files include some directory -structure, some suggested "exclude patterns", and copies of non-executing -"hook" files. The suggested patterns and hook files are all modifiable and -extensible. +Specify the directory from which templates will be used. (See the "TEMPLATE +DIRECTORY" section below.) --shared[={false|true|umask|group|all|world|everybody|0xxx}]:: @@ -106,6 +100,25 @@ of the repository, such as installing the default hooks and setting the configuration variables. The old name is retained for backward compatibility reasons. +TEMPLATE DIRECTORY +------------------ + +The template directory contains files and directories that will be copied to +the `$GIT_DIR` after it is created. + +The template directory used will (in order): + + - The argument given with the `--template` option. + + - The contents of the `$GIT_TEMPLATE_DIR` environment variable. + + - The `init.templatedir` configuration variable. + + - The default template directory: `/usr/share/git-core/templates`. + +The default template directory includes some directory structure, some +suggested "exclude patterns", and copies of sample "hook" files. +The suggested patterns and hook files are all modifiable and extensible. EXAMPLES -------- diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index a1f17df074..e70cea9320 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -29,7 +29,7 @@ OPTIONS The HTTP daemon command-line that will be executed. Command-line options may be specified here, and the configuration file will be added at the end of the command-line. - Currently apache2, lighttpd, mongoose and webrick are supported. + Currently apache2, lighttpd, mongoose, plackup and webrick are supported. (Default: lighttpd) -m:: @@ -49,15 +49,18 @@ OPTIONS linkgit:git-web--browse[1] for more information about this. If the script fails, the URL will be printed to stdout. +start:: --start:: Start the httpd instance and exit. This does not generate any of the configuration files for spawning a new instance. +stop:: --stop:: Stop the httpd instance and exit. This does not generate any of the configuration files for spawning a new instance, nor does it close the browser. +restart:: --restart:: Restart the httpd instance and exit. This does not generate any of the configuration files for spawning a new instance. diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 0e39bb61ee..c213bdbdc5 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -23,9 +23,6 @@ each commit introduces are shown. OPTIONS ------- -:git-log: 1 -include::diff-options.txt[] - -<n>:: Limits the number of commits to show. @@ -34,10 +31,14 @@ include::diff-options.txt[] either <since> or <until> is omitted, it defaults to `HEAD`, i.e. the tip of the current branch. For a more complete list of ways to spell <since> - and <until>, see "SPECIFYING REVISIONS" section in - linkgit:git-rev-parse[1]. + and <until>, see linkgit:gitrevisions[1]. ---decorate[=short|full]:: +--follow:: + Continue listing the history of a file beyond renames + (works only for a single file). + +--no-decorate:: +--decorate[=short|full|no]:: Print out the ref names of any commits that are shown. If 'short' is specified, the ref name prefixes 'refs/heads/', 'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is specified, the @@ -54,9 +55,9 @@ include::diff-options.txt[] paths. With this, the full diff is shown for commits that touch the specified paths; this means that "<path>..." limits only commits, and doesn't limit diff for those commits. - ---follow:: - Continue listing the history of a file beyond renames. ++ +Note that this affects all diff-based output types, e.g. those +produced by --stat etc. --log-size:: Before the log message print out its size in bytes. Intended @@ -71,6 +72,11 @@ include::diff-options.txt[] to be prefixed with "\-- " to separate them from options or refnames. +Common diff options +~~~~~~~~~~~~~~~~~~~ + +:git-log: 1 +include::diff-options.txt[] include::rev-list-options.txt[] @@ -118,11 +124,62 @@ git log master --not --remotes=*/master:: Shows all commits that are in local master but not in any remote repository master branches. +git log -p -m --first-parent:: + + Shows the history including change diffs, but only from the + "main branch" perspective, skipping commits that come from merged + branches, and showing full diffs of changes introduced by the merges. + This makes sense only when following a strict policy of merging all + topic branches when staying on a single integration branch. + + Discussion ---------- include::i18n.txt[] +Configuration +------------- + +See linkgit:git-config[1] for core variables and linkgit:git-diff[1] +for settings related to diff generation. + +format.pretty:: + Default for the `--format` option. (See "PRETTY FORMATS" above.) + Defaults to "medium". + +i18n.logOutputEncoding:: + Encoding to use when displaying logs. (See "Discussion", above.) + Defaults to the value of `i18n.commitEncoding` if set, UTF-8 + otherwise. + +log.date:: + Default format for human-readable dates. (Compare the + `--date` option.) Defaults to "default", which means to write + dates like `Sat May 8 19:35:34 2010 -0500`. + +log.showroot:: + If `false`, 'git log' and related commands will not treat the + initial commit as a big creation event. Any root commits in + `git log -p` output would be shown without a diff attached. + The default is `true`. + +mailmap.file:: + See linkgit:git-shortlog[1]. + +notes.displayRef:: + Which refs, in addition to the default set by `core.notesRef` + or 'GIT_NOTES_REF', to read notes from when showing commit + messages with the 'log' family of commands. See + linkgit:git-notes[1]. ++ +May be an unabbreviated ref name or a glob and may be specified +multiple times. A warning will be issued for refs that do not exist, +but a glob that does not match any refs is silently ignored. ++ +This setting can be disabled by the `--no-standard-notes` option, +overridden by the 'GIT_NOTES_DISPLAY_REF' environment variable, +and supplemented by the `--show-notes` option. Author ------ diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 3521637b58..a7c8174d01 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -106,8 +106,16 @@ OPTIONS with `-s` or `-u` options does not make any sense. -t:: - Identify the file status with the following tags (followed by - a space) at the start of each line: + This feature is semi-deprecated. For scripting purpose, + linkgit:git-status[1] `--porcelain` and + linkgit:git-diff-files[1] `--name-status` are almost always + superior alternatives, and users should look at + linkgit:git-status[1] `--short` or linkgit:git-diff[1] + `--name-status` for more user-friendly alternatives. ++ +This option identifies the file status with the following tags (followed by +a space) at the start of each line: + H:: cached S:: skip-worktree M:: unmerged @@ -132,6 +140,12 @@ OPTIONS lines, show only a partial prefix. Non default number of digits can be specified with --abbrev=<n>. +--debug:: + After each line that describes a file, add more data about its + cache entry. This is intended to show as much information as + possible for manual inspection; the exact format may change at + any time. + \--:: Do not interpret any more arguments as options. diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index e3d58cbac3..3ea5aad56c 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -40,16 +40,16 @@ OPTIONS -u:: The commit log message, author name and author email are taken from the e-mail, and after minimally decoding MIME - transfer encoding, re-coded in UTF-8 by transliterating + transfer encoding, re-coded in the charset specified by + i18n.commitencoding (defaulting to UTF-8) by transliterating them. This used to be optional but now it is the default. + Note that the patch is always used as-is without charset conversion, even with this flag. --encoding=<encoding>:: - Similar to -u but if the local convention is different - from what is specified by i18n.commitencoding, this flag - can be used to override it. + Similar to -u. But when re-coding, the charset specified here is + used instead of the one specified by i18n.commitencoding or UTF-8. -n:: Disable all charset re-coding of the metadata. diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt index 5cc94ec53d..a634485281 100644 --- a/Documentation/git-mailsplit.txt +++ b/Documentation/git-mailsplit.txt @@ -7,7 +7,7 @@ git-mailsplit - Simple UNIX mbox splitter program SYNOPSIS -------- -'git mailsplit' [-b] [-f<nn>] [-d<prec>] -o<directory> [--] [<mbox>|<Maildir>...] +'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [<mbox>|<Maildir>...] DESCRIPTION ----------- @@ -43,6 +43,9 @@ OPTIONS Skip the first <nn> numbers, for example if -f3 is specified, start the numbering with 0004. +--keep-cr:: + Do not remove `\r` from lines ending with `\r\n`. + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt index 234269ae59..f334d694e0 100644 --- a/Documentation/git-merge-file.txt +++ b/Documentation/git-merge-file.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]] - [--ours|--theirs] [-p|--stdout] [-q|--quiet] + [--ours|--theirs|--union] [-p|--stdout] [-q|--quiet] [--marker-size=<n>] <current-file> <base-file> <other-file> @@ -35,9 +35,10 @@ normally outputs a warning and brackets the conflict with lines containing >>>>>>> B If there are conflicts, the user should edit the result and delete one of -the alternatives. When `--ours` or `--theirs` option is in effect, however, -these conflicts are resolved favouring lines from `<current-file>` or -lines from `<other-file>` respectively. +the alternatives. When `--ours`, `--theirs`, or `--union` option is in effect, +however, these conflicts are resolved favouring lines from `<current-file>`, +lines from `<other-file>`, or lines from both respectively. The length of the +conflict markers can be given with the `--marker-size` option. The exit value of this program is negative on error, and the number of conflicts otherwise. If the merge was clean, the exit value is 0. @@ -67,8 +68,9 @@ OPTIONS --ours:: --theirs:: +--union:: Instead of leaving conflicts in the file, resolve conflicts - favouring our (or their) side of the lines. + favouring our (or their or both) side of the lines. EXAMPLES diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index c2325ef90e..84043cc5b2 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -58,7 +58,12 @@ include::merge-options.txt[] -m <msg>:: Set the commit message to be used for the merge commit (in - case one is created). The 'git fmt-merge-msg' command can be + case one is created). + + If `--log` is specified, a shortlog of the commits being merged + will be appended to the specified message. + + The 'git fmt-merge-msg' command can be used to give a good default for automated 'git merge' invocations. diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 55735faf7b..e4ed016146 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -72,6 +72,16 @@ success of the resolution after the custom tool has exited. This is the default behaviour; the option is provided to override any configuration settings. +TEMPORARY FILES +--------------- +`git mergetool` creates `*.orig` backup files while resolving merges. +These are safe to remove once a file has been merged and its +`git mergetool` session has completed. + +Setting the `mergetool.keepBackup` configuration variable to `false` +causes `git mergetool` to automatically remove the backup as files +are successfully merged. + Author ------ Written by Theodore Y Ts'o <tytso@mit.edu> diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index d4487cab52..2981d8c5ef 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -3,57 +3,284 @@ git-notes(1) NAME ---- -git-notes - Add/inspect commit notes +git-notes - Add or inspect object notes SYNOPSIS -------- [verse] -'git notes' (edit [-F <file> | -m <msg>] | show) [commit] +'git notes' [list [<object>]] +'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>] +'git notes' copy [-f] ( --stdin | <from-object> <to-object> ) +'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>] +'git notes' edit [<object>] +'git notes' show [<object>] +'git notes' remove [<object>] +'git notes' prune [-n | -v] + DESCRIPTION ----------- -This command allows you to add notes to commit messages, without -changing the commit. To discern these notes from the message stored -in the commit object, the notes are indented like the message, after -an unindented line saying "Notes:". +Adds, removes, or reads notes attached to objects, without touching +the objects themselves. + +By default, notes are saved to and read from `refs/notes/commits`, but +this default can be overridden. See the OPTIONS, CONFIGURATION, and +ENVIRONMENT sections below. If this ref does not exist, it will be +quietly created when it is first needed to store a note. + +A typical use of notes is to supplement a commit message without +changing the commit itself. Notes can be shown by 'git log' along with +the original commit message. To distinguish these notes from the +message stored in the commit object, the notes are indented like the +message, after an unindented line saying "Notes (<refname>):" (or +"Notes:" for `refs/notes/commits`). -To disable commit notes, you have to set the config variable -core.notesRef to the empty string. Alternatively, you can set it -to a different ref, something like "refs/notes/bugzilla". This setting -can be overridden by the environment variable "GIT_NOTES_REF". +To change which notes are shown by 'git log', see the +"notes.displayRef" configuration in linkgit:git-log[1]. + +See the "notes.rewrite.<command>" configuration for a way to carry +notes across commands that rewrite commits. SUBCOMMANDS ----------- +list:: + List the notes object for a given object. If no object is + given, show a list of all note objects and the objects they + annotate (in the format "<note object> <annotated object>"). + This is the default subcommand if no subcommand is given. + +add:: + Add notes for a given object (defaults to HEAD). Abort if the + object already has notes (use `-f` to overwrite an + existing note). + +copy:: + Copy the notes for the first object onto the second object. + Abort if the second object already has notes, or if the first + object has none (use -f to overwrite existing notes to the + second object). This subcommand is equivalent to: + `git notes add [-f] -C $(git notes list <from-object>) <to-object>` ++ +In `\--stdin` mode, take lines in the format ++ +---------- +<from-object> SP <to-object> [ SP <rest> ] LF +---------- ++ +on standard input, and copy the notes from each <from-object> to its +corresponding <to-object>. (The optional `<rest>` is ignored so that +the command can read the input given to the `post-rewrite` hook.) + +append:: + Append to the notes of an existing object (defaults to HEAD). + Creates a new notes object if needed. + edit:: - Edit the notes for a given commit (defaults to HEAD). + Edit the notes for a given object (defaults to HEAD). show:: - Show the notes for a given commit (defaults to HEAD). + Show the notes for a given object (defaults to HEAD). + +remove:: + Remove the notes for a given object (defaults to HEAD). + This is equivalent to specifying an empty note message to + the `edit` subcommand. +prune:: + Remove all notes for non-existing/unreachable objects. OPTIONS ------- +-f:: +--force:: + When adding notes to an object that already has notes, + overwrite the existing notes (instead of aborting). + -m <msg>:: +--message=<msg>:: Use the given note message (instead of prompting). - If multiple `-m` (or `-F`) options are given, their - values are concatenated as separate paragraphs. + If multiple `-m` options are given, their values + are concatenated as separate paragraphs. + Lines starting with `#` and empty lines other than a + single line between paragraphs will be stripped out. -F <file>:: +--file=<file>:: Take the note message from the given file. Use '-' to read the note message from the standard input. - If multiple `-F` (or `-m`) options are given, their - values are concatenated as separate paragraphs. + Lines starting with `#` and empty lines other than a + single line between paragraphs will be stripped out. + +-C <object>:: +--reuse-message=<object>:: + Take the note message from the given blob object (for + example, another note). + +-c <object>:: +--reedit-message=<object>:: + Like '-C', but with '-c' the editor is invoked, so that + the user can further edit the note message. + +--ref <ref>:: + Manipulate the notes tree in <ref>. This overrides + 'GIT_NOTES_REF' and the "core.notesRef" configuration. The ref + is taken to be in `refs/notes/` if it is not qualified. + +-n:: +--dry-run:: + Do not remove anything; just report the object names whose notes + would be removed. + +-v:: +--verbose:: + Report all object names whose notes are removed. + + +DISCUSSION +---------- + +Commit notes are blobs containing extra information about an object +(usually information to supplement a commit's message). These blobs +are taken from notes refs. A notes ref is usually a branch which +contains "files" whose paths are the object names for the objects +they describe, with some directory separators included for performance +reasons footnote:[Permitted pathnames have the form +'ab'`/`'cd'`/`'ef'`/`'...'`/`'abcdef...': a sequence of directory +names of two hexadecimal digits each followed by a filename with the +rest of the object ID.]. + +Every notes change creates a new commit at the specified notes ref. +You can therefore inspect the history of the notes by invoking, e.g., +`git log -p notes/commits`. Currently the commit message only records +which operation triggered the update, and the commit authorship is +determined according to the usual rules (see linkgit:git-commit[1]). +These details may change in the future. + +It is also permitted for a notes ref to point directly to a tree +object, in which case the history of the notes can be read with +`git log -p -g <refname>`. + + +EXAMPLES +-------- + +You can use notes to add annotations with information that was not +available at the time a commit was written. + +------------ +$ git notes add -m 'Tested-by: Johannes Sixt <j6t@kdbg.org>' 72a144e2 +$ git show -s 72a144e +[...] + Signed-off-by: Junio C Hamano <gitster@pobox.com> + +Notes: + Tested-by: Johannes Sixt <j6t@kdbg.org> +------------ + +In principle, a note is a regular Git blob, and any kind of +(non-)format is accepted. You can binary-safely create notes from +arbitrary files using 'git hash-object': + +------------ +$ cc *.c +$ blob=$(git hash-object -w a.out) +$ git notes --ref=built add -C "$blob" HEAD +------------ + +Of course, it doesn't make much sense to display non-text-format notes +with 'git log', so if you use such notes, you'll probably need to write +some special-purpose tools to do something useful with them. + + +CONFIGURATION +------------- + +core.notesRef:: + Notes ref to read and manipulate instead of + `refs/notes/commits`. Must be an unabbreviated ref name. + This setting can be overridden through the environment and + command line. + +notes.displayRef:: + Which ref (or refs, if a glob or specified more than once), in + addition to the default set by `core.notesRef` or + 'GIT_NOTES_REF', to read notes from when showing commit + messages with the 'git log' family of commands. + This setting can be overridden on the command line or by the + 'GIT_NOTES_DISPLAY_REF' environment variable. + See linkgit:git-log[1]. + +notes.rewrite.<command>:: + When rewriting commits with <command> (currently `amend` or + `rebase`), if this variable is `false`, git will not copy + notes from the original to the rewritten commit. Defaults to + `true`. See also "`notes.rewriteRef`" below. ++ +This setting can be overridden by the 'GIT_NOTES_REWRITE_REF' +environment variable. + +notes.rewriteMode:: + When copying notes during a rewrite, what to do if the target + commit already has a note. Must be one of `overwrite`, + `concatenate`, and `ignore`. Defaults to `concatenate`. ++ +This setting can be overridden with the `GIT_NOTES_REWRITE_MODE` +environment variable. + +notes.rewriteRef:: + When copying notes during a rewrite, specifies the (fully + qualified) ref whose notes should be copied. May be a glob, + in which case notes in all matching refs will be copied. You + may also specify this configuration several times. ++ +Does not have a default value; you must configure this variable to +enable note rewriting. ++ +Can be overridden with the 'GIT_NOTES_REWRITE_REF' environment variable. + + +ENVIRONMENT +----------- + +'GIT_NOTES_REF':: + Which ref to manipulate notes from, instead of `refs/notes/commits`. + This overrides the `core.notesRef` setting. + +'GIT_NOTES_DISPLAY_REF':: + Colon-delimited list of refs or globs indicating which refs, + in addition to the default from `core.notesRef` or + 'GIT_NOTES_REF', to read notes from when showing commit + messages. + This overrides the `notes.displayRef` setting. ++ +A warning will be issued for refs that do not exist, but a glob that +does not match any refs is silently ignored. + +'GIT_NOTES_REWRITE_MODE':: + When copying notes during a rewrite, what to do if the target + commit already has a note. + Must be one of `overwrite`, `concatenate`, and `ignore`. + This overrides the `core.rewriteMode` setting. + +'GIT_NOTES_REWRITE_REF':: + When rewriting commits, which notes to copy from the original + to the rewritten commit. Must be a colon-delimited list of + refs or globs. ++ +If not set in the environment, the list of notes to copy depends +on the `notes.rewrite.<command>` and `notes.rewriteRef` settings. Author ------ -Written by Johannes Schindelin <johannes.schindelin@gmx.de> +Written by Johannes Schindelin <johannes.schindelin@gmx.de> and +Johan Herland <johan@herland.net> Documentation ------------- -Documentation by Johannes Schindelin +Documentation by Johannes Schindelin and Johan Herland GIT --- diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index 15cfb7a8dc..4d673a5686 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -31,10 +31,12 @@ OPTIONS ------- -n:: +--dry-run:: Do not remove anything; just report what it would remove. -v:: +--verbose:: Report all removed objects. \--:: diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 31f42ea21a..c50f7dcb89 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -8,29 +8,82 @@ git-pull - Fetch from and merge with another repository or a local branch SYNOPSIS -------- -'git pull' <options> <repository> <refspec>... +'git pull' [options] [<repository> [<refspec>...]] DESCRIPTION ----------- -Runs 'git fetch' with the given parameters, and calls 'git merge' -to merge the retrieved head(s) into the current branch. -With `--rebase`, calls 'git rebase' instead of 'git merge'. -Note that you can use `.` (current directory) as the -<repository> to pull from the local repository -- this is useful -when merging local branches into the current branch. +Incorporates changes from a remote repository into the current +branch. In its default mode, `git pull` is shorthand for +`git fetch` followed by `git merge FETCH_HEAD`. -Also note that options meant for 'git pull' itself and underlying -'git merge' must be given before the options meant for 'git fetch'. +More precisely, 'git pull' runs 'git fetch' with the given +parameters and calls 'git merge' to merge the retrieved branch +heads into the current branch. +With `--rebase`, it runs 'git rebase' instead of 'git merge'. -*Warning*: Running 'git pull' (actually, the underlying 'git merge') +<repository> should be the name of a remote repository as +passed to linkgit:git-fetch[1]. <refspec> can name an +arbitrary remote ref (for example, the name of a tag) or even +a collection of refs with corresponding remote tracking branches +(e.g., refs/heads/*:refs/remotes/origin/*), but usually it is +the name of a branch in the remote repository. + +Default values for <repository> and <branch> are read from the +"remote" and "merge" configuration for the current branch +as set by linkgit:git-branch[1] `--track`. + +Assume the following history exists and the current branch is +"`master`": + +------------ + A---B---C master on origin + / + D---E---F---G master +------------ + +Then "`git pull`" will fetch and replay the changes from the remote +`master` branch since it diverged from the local `master` (i.e., `E`) +until its current commit (`C`) on top of `master` and record the +result in a new commit along with the names of the two parent commits +and a log message from the user describing the changes. + +------------ + A---B---C remotes/origin/master + / \ + D---E---F---G---H master +------------ + +See linkgit:git-merge[1] for details, including how conflicts +are presented and handled. + +In git 1.7.0 or later, to cancel a conflicting merge, use +`git reset --merge`. *Warning*: In older versions of git, running 'git pull' with uncommitted changes is discouraged: while possible, it leaves you -in a state that is hard to back out of in the case of a conflict. +in a state that may be hard to back out of in the case of a conflict. + +If any of the remote changes overlap with local uncommitted changes, +the merge will be automatically cancelled and the work tree untouched. +It is generally best to get any local changes in working order before +pulling or stash them away with linkgit:git-stash[1]. OPTIONS ------- +Options meant for 'git pull' itself and the underlying 'git merge' +must be given before the options meant for 'git fetch'. + +-q:: +--quiet:: + This is passed to both underlying git-fetch to squelch reporting of + during transfer, and underlying git-merge to squelch output during + merging. + +-v:: +--verbose:: + Pass --verbose to git-fetch and git-merge. + Options related to merging ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 7a4e507c4b..658ff2ff67 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -41,7 +41,7 @@ OPTIONS[[OPTIONS]] + The <src> is often the name of the branch you would want to push, but it can be any arbitrary "SHA-1 expression", such as `master~4` or -`HEAD` (see linkgit:git-rev-parse[1]). +`HEAD` (see linkgit:gitrevisions[1]). + The <dst> tells which ref on the remote side is updated with this push. Arbitrary expressions cannot be used here, an actual ref must @@ -146,14 +146,21 @@ useful if you write an alias or script around 'git push'. receiver share many of the same objects in common. The default is \--thin. +-q:: +--quiet:: + Suppress all output, including the listing of updated refs, + unless an error occurs. Progress is not reported to the standard + error stream. + -v:: --verbose:: Run verbosely. --q:: ---quiet:: - Suppress all output, including the listing of updated refs, - unless an error occurs. +--progress:: + Progress status is reported on the standard error stream + by default when it is attached to a terminal, unless -q + is specified. This flag forces progress status even if the + standard error stream is not directed to a terminal. include::urls-remotes.txt[] @@ -193,16 +200,29 @@ summary:: For a successfully pushed ref, the summary shows the old and new values of the ref in a form suitable for using as an argument to `git log` (this is `<old>..<new>` in most cases, and - `<old>...<new>` for forced non-fast-forward updates). For a - failed update, more details are given for the failure. - The string `rejected` indicates that git did not try to send the - ref at all (typically because it is not a fast-forward). The - string `remote rejected` indicates that the remote end refused - the update; this rejection is typically caused by a hook on the - remote side. The string `remote failure` indicates that the - remote end did not report the successful update of the ref - (perhaps because of a temporary error on the remote side, a - break in the network connection, or other transient error). + `<old>...<new>` for forced non-fast-forward updates). ++ +For a failed update, more details are given: ++ +-- +rejected:: + Git did not try to send the ref at all, typically because it + is not a fast-forward and you did not force the update. + +remote rejected:: + The remote end refused the update. Usually caused by a hook + on the remote side, or because the remote repository has one + of the following safety options in effect: + `receive.denyCurrentBranch` (for pushes to the checked out + branch), `receive.denyNonFastForwards` (for forced + non-fast-forward updates), `receive.denyDeletes` or + `receive.denyDeleteCurrent`. See linkgit:git-config[1]. + +remote failure:: + The remote end did not report the successful update of the ref, + perhaps because of a temporary error on the remote side, a + break in the network connection, or other transient error. +-- from:: The name of the local ref being pushed, minus its diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index f6037c4f6a..2e78da448f 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -412,6 +412,13 @@ turn `core.sparseCheckout` on in order to have sparse checkout support. +BUGS +---- +In order to match a directory with $GIT_DIR/info/sparse-checkout, +trailing slash must be used. The form without trailing slash, while +works with .gitignore, does not work with sparse checkout. + + SEE ALSO -------- linkgit:git-write-tree[1]; linkgit:git-ls-files[1]; diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 823f2a4638..be23ad2359 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -206,6 +206,10 @@ OPTIONS --onto option is not specified, the starting point is <upstream>. May be any valid commit, and not just an existing branch name. ++ +As a special case, you may use "A...B" as a shortcut for the +merge base of A and B if there is exactly one merge base. You can +leave out at most one of A and B, in which case it defaults to HEAD. <upstream>:: Upstream branch to compare against. May be any valid commit, @@ -274,9 +278,16 @@ which makes little sense. -f:: --force-rebase:: Force the rebase even if the current branch is a descendant - of the commit you are rebasing onto. Normally the command will + of the commit you are rebasing onto. Normally non-interactive rebase will exit with the message "Current branch is up to date" in such a situation. + Incompatible with the --interactive option. ++ +You may find this (or --no-ff with an interactive rebase) helpful after +reverting a topic branch merge, as this option recreates the topic branch with +fresh commits so it can be remerged successfully without needing to "revert +the reversion" (see the +link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details). --ignore-whitespace:: --whitespace=<option>:: @@ -288,6 +299,7 @@ which makes little sense. --ignore-date:: These flags are passed to 'git am' to easily change the dates of the rebased commits (see linkgit:git-am[1]). + Incompatible with the --interactive option. -i:: --interactive:: @@ -298,6 +310,11 @@ which makes little sense. -p:: --preserve-merges:: Instead of ignoring merges, try to recreate them. ++ +This uses the `--interactive` machinery internally, but combining it +with the `--interactive` option explicitly is generally not a good +idea unless you know what you are doing (see BUGS below). + --root:: Rebase all commits reachable from <branch>, instead of @@ -316,7 +333,19 @@ which makes little sense. commit to be modified, and change the action of the moved commit from `pick` to `squash` (or `fixup`). + -This option is only valid when '--interactive' option is used. +This option is only valid when the '--interactive' option is used. + +--no-ff:: + With --interactive, cherry-pick all rebased commits instead of + fast-forwarding over the unchanged ones. This ensures that the + entire history of the rebased branch is composed of new commits. ++ +Without --interactive, this is a synonym for --force-rebase. ++ +You may find this helpful after reverting a topic branch merge, as this option +recreates the topic branch with fresh commits so it can be remerged +successfully without needing to "revert the reversion" (see the +link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details). include::merge-strategies.txt[] @@ -587,6 +616,28 @@ The ripple effect of a "hard case" recovery is especially bad: case" recovery too! +BUGS +---- +The todo list presented by `--preserve-merges --interactive` does not +represent the topology of the revision graph. Editing commits and +rewording their commit messages should work fine, but attempts to +reorder commits tend to produce counterintuitive results. + +For example, an attempt to rearrange +------------ +1 --- 2 --- 3 --- 4 --- 5 +------------ +to +------------ +1 --- 2 --- 4 --- 3 --- 5 +------------ +by moving the "pick 4" line will result in the following history: +------------ + 3 + / +1 --- 2 --- 4 --- 5 +------------ + Authors ------ Written by Junio C Hamano <gitster@pobox.com> and diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index 4eaa62b691..5a0451aaf3 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -40,7 +40,7 @@ see linkgit:git-log[1]. The reflog is useful in various git commands, to specify the old value of a reference. For example, `HEAD@\{2\}` means "where HEAD used to be two moves ago", `master@\{one.week.ago\}` means "where master used to -point to one week ago", and so on. See linkgit:git-rev-parse[1] for +point to one week ago", and so on. See linkgit:gitrevisions[1] for more details. To delete single entries from the reflog, use the subcommand "delete" diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 1b5f61aa0b..3a23477ce7 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -3,20 +3,69 @@ git-remote-helpers(1) NAME ---- -git-remote-helpers - Helper programs for interoperation with remote git +git-remote-helpers - Helper programs to interact with remote repositories SYNOPSIS -------- -'git remote-<transport>' <remote> +'git remote-<transport>' <repository> [<URL>] DESCRIPTION ----------- -These programs are normally not used directly by end users, but are -invoked by various git programs that interact with remote repositories -when the repository they would operate on will be accessed using -transport code not linked into the main git binary. Various particular -helper programs will behave as documented here. +Remote helper programs are normally not used directly by end users, +but they are invoked by git when it needs to interact with remote +repositories git does not support natively. A given helper will +implement a subset of the capabilities documented here. When git +needs to interact with a repository using a remote helper, it spawns +the helper as an independent process, sends commands to the helper's +standard input, and expects results from the helper's standard +output. Because a remote helper runs as an independent process from +git, there is no need to re-link git to add a new helper, nor any +need to link the helper with the implementation of git. + +Every helper must support the "capabilities" command, which git will +use to determine what other commands the helper will accept. Other +commands generally concern facilities like discovering and updating +remote refs, transporting objects between the object database and +the remote repository, and updating the local object store. + +Helpers supporting the 'fetch' capability can discover refs from the +remote repository and transfer objects reachable from those refs to +the local object store. Helpers supporting the 'push' capability can +transfer local objects to the remote repository and update remote refs. + +Git comes with a "curl" family of remote helpers, that handle various +transport protocols, such as 'git-remote-http', 'git-remote-https', +'git-remote-ftp' and 'git-remote-ftps'. They implement the capabilities +'fetch', 'option', and 'push'. + +INVOCATION +---------- + +Remote helper programs are invoked with one or (optionally) two +arguments. The first argument specifies a remote repository as in git; +it is either the name of a configured remote or a URL. The second +argument specifies a URL; it is usually of the form +'<transport>://<address>', but any arbitrary string is possible. + +When git encounters a URL of the form '<transport>://<address>', where +'<transport>' is a protocol that it cannot handle natively, it +automatically invokes 'git remote-<transport>' with the full URL as +the second argument. If such a URL is encountered directly on the +command line, the first argument is the same as the second, and if it +is encountered in a configured remote, the first argument is the name +of that remote. + +A URL of the form '<transport>::<address>' explicitly instructs git to +invoke 'git remote-<transport>' with '<address>' as the second +argument. If such a URL is encountered directly on the command line, +the first argument is '<address>', and if it is encountered in a +configured remote, the first argument is the name of that remote. + +Additionally, when a configured remote has 'remote.<name>.vcs' set to +'<transport>', git explicitly invokes 'git remote-<transport>' with +'<name>' as the first argument. If set, the second argument is +'remote.<name>.url'; otherwise, the second argument is omitted. COMMANDS -------- @@ -25,8 +74,8 @@ Commands are given by the caller on the helper's standard input, one per line. 'capabilities':: Lists the capabilities of the helper, one per line, ending - with a blank line. Each capability may be preceded with '*'. - This marks them mandatory for git version using the remote + with a blank line. Each capability may be preceded with '*', + which marks them mandatory for git version using the remote helper to understand (unknown mandatory capability is fatal error). @@ -35,27 +84,27 @@ Commands are given by the caller on the helper's standard input, one per line. [<attr> ...]". The value may be a hex sha1 hash, "@<dest>" for a symref, or "?" to indicate that the helper could not get the value of the ref. A space-separated list of attributes follows - the name; unrecognized attributes are ignored. After the - complete list, outputs a blank line. + the name; unrecognized attributes are ignored. The list ends + with a blank line. + If 'push' is supported this may be called as 'list for-push' to obtain the current refs prior to sending one or more 'push' commands to the helper. 'option' <name> <value>:: - Set the transport helper option <name> to <value>. Outputs a + Sets the transport helper option <name> to <value>. Outputs a single line containing one of 'ok' (option successfully set), 'unsupported' (option not recognized) or 'error <msg>' - (option <name> is supported but <value> is not correct + (option <name> is supported but <value> is not valid for it). Options should be set before other commands, - and may how those commands behave. + and may influence the behavior of those commands. + Supported if the helper has the "option" capability. 'fetch' <sha1> <name>:: Fetches the given object, writing the necessary objects to the database. Fetch commands are sent in a batch, one - per line, and the batch is terminated with a blank line. + per line, terminated with a blank line. Outputs a single blank line when all fetch commands in the same batch are complete. Only objects which were reported in the ref list with a sha1 may be fetched this way. @@ -67,7 +116,7 @@ suitably updated. Supported if the helper has the "fetch" capability. 'push' +<src>:<dst>:: - Pushes the given <src> commit or branch locally to the + Pushes the given local <src> commit or branch to the remote branch described by <dst>. A batch sequence of one or more push commands is terminated with a blank line. + @@ -91,6 +140,9 @@ Supported if the helper has the "push" capability. by applying the refspecs from the "refspec" capability to the name of the ref. + +Especially useful for interoperability with a foreign versioning +system. ++ Supported if the helper has the "import" capability. 'connect' <service>:: @@ -119,16 +171,11 @@ CAPABILITIES ------------ 'fetch':: - This helper supports the 'fetch' command. - 'option':: - This helper supports the option command. - 'push':: - This helper supports the 'push' command. - 'import':: - This helper supports the 'import' command. +'connect':: + This helper supports the corresponding command with the same name. 'refspec' 'spec':: When using the import command, expect the source ref to have @@ -140,9 +187,6 @@ CAPABILITIES all, it must cover all refs reported by the list command; if it is not used, it is effectively "*:*" -'connect':: - This helper supports the 'connect' command. - REF LIST ATTRIBUTES ------------------- @@ -158,19 +202,19 @@ REF LIST ATTRIBUTES OPTIONS ------- 'option verbosity' <N>:: - Change the level of messages displayed by the helper. - When N is 0 the end-user has asked the process to be - quiet, and the helper should produce only error output. - N of 1 is the default level of verbosity, higher values + Changes the verbosity of messages displayed by the helper. + A value of 0 for N means that processes operate + quietly, and the helper produces only error output. + 1 is the default level of verbosity, and higher values of N correspond to the number of -v flags passed on the command line. 'option progress' \{'true'|'false'\}:: - Enable (or disable) progress messages displayed by the + Enables (or disables) progress messages displayed by the transport helper during a command. 'option depth' <depth>:: - Deepen the history of a shallow repository. + Deepens the history of a shallow repository. 'option followtags' \{'true'|'false'\}:: If enabled the helper should automatically fetch annotated @@ -186,11 +230,15 @@ OPTIONS helpers this only applies to the 'push', if supported. 'option servpath <c-style-quoted-path>':: - Set service path (--upload-pack, --receive-pack etc.) for - next connect. Remote helper MAY support this option. Remote - helper MUST NOT rely on this option being set before + Sets service path (--upload-pack, --receive-pack etc.) for + next connect. Remote helper may support this option, but + must not rely on this option being set before connect request occurs. +SEE ALSO +-------- +linkgit:git-remote[1] + Documentation ------------- Documentation by Daniel Barkalow and Ilari Liusvaara diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 3fc599c0c7..aa021b0cb8 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -10,10 +10,11 @@ SYNOPSIS -------- [verse] 'git remote' [-v | --verbose] -'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url> +'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror] <name> <url> 'git remote rename' <old> <new> 'git remote rm' <name> 'git remote set-head' <name> (-a | -d | <branch>) +'git remote set-branches' <name> [--add] <branch>... 'git remote set-url' [--push] <name> <newurl> [<oldurl>] 'git remote set-url --add' [--push] <name> <newurl> 'git remote set-url --delete' [--push] <name> <url> @@ -51,6 +52,12 @@ update remote-tracking branches <name>/<branch>. With `-f` option, `git fetch <name>` is run immediately after the remote information is set up. + +With `--tags` option, `git fetch <name>` imports every tag from the +remote repository. ++ +With `--no-tags` option, `git fetch <name>` does not import tags from +the remote repository. ++ With `-t <branch>` option, instead of the default glob refspec for the remote to track all branches under `$GIT_DIR/remotes/<name>/`, a refspec to track only `<branch>` @@ -104,6 +111,18 @@ remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to `refs/remotes/origin/master` already exists; if not it must be fetched first. + +'set-branches':: + +Changes the list of branches tracked by the named remote. +This can be used to track a subset of the available remote branches +after the initial setup for a remote. ++ +The named branches will be interpreted as if specified with the +`-t` option on the 'git remote add' command line. ++ +With `--add`, instead of replacing the list of currently tracked +branches, adds to that list. + 'set-url':: Changes URL remote points to. Sets first URL remote points to matching diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index acc220a00f..db99d4786e 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -7,7 +7,7 @@ git-rerere - Reuse recorded resolution of conflicted merges SYNOPSIS -------- -'git rerere' ['clear'|'diff'|'status'|'gc'] +'git rerere' ['clear'|'forget' [<pathspec>]|'diff'|'status'|'gc'] DESCRIPTION ----------- @@ -40,6 +40,11 @@ This resets the metadata used by rerere if a merge resolution is to be aborted. Calling 'git am [--skip|--abort]' or 'git rebase [--skip|--abort]' will automatically invoke this command. +'forget' <pathspec>:: + +This resets the conflict resolutions which rerere has recorded for the current +conflict in <pathspec>. The <pathspec> is optional. + 'diff':: This displays diffs for the current state of the resolution. It is diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 168db08627..b04d129b3f 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -8,40 +8,50 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- [verse] -'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>] 'git reset' [-q] [<commit>] [--] <paths>... 'git reset' --patch [<commit>] [--] [<paths>...] +'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>] DESCRIPTION ----------- -Sets the current head to the specified commit and optionally resets the -index and working tree to match. - -This command is useful if you notice some small error in a recent -commit (or set of commits) and want to redo that part without showing -the undo in the history. - -If you want to undo a commit other than the latest on a branch, -linkgit:git-revert[1] is your friend. - -The second and third forms with 'paths' and/or --patch are used to -revert selected paths in the index from a given commit, without moving -HEAD. - +In the first and second form, copy entries from <commit> to the index. +In the third form, set the current branch to <commit>, optionally +modifying index and worktree to match. The <commit> defaults to HEAD +in all forms. + +'git reset' [-q] [<commit>] [--] <paths>...:: + This form resets the index entries for all <paths> to their + state at the <commit>. (It does not affect the worktree, nor + the current branch.) ++ +This means that `git reset <paths>` is the opposite of `git add +<paths>`. -OPTIONS -------- ---mixed:: - Resets the index but not the working tree (i.e., the changed files - are preserved but not marked for commit) and reports what has not - been updated. This is the default action. +'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. ++ +This means that `git reset -p` is the opposite of `git add -p` (see +linkgit:git-add[1]). +'git reset' [--<mode>] [<commit>]:: + This form points the current branch to <commit> and then + updates index and working tree according to <mode>, which must + be one of the following: ++ +-- --soft:: Does not touch the index file nor the working tree at all, but requires them to be in a good order. This leaves all your changed files "Changes to be committed", as 'git status' would put it. +--mixed:: + Resets the index but not the working tree (i.e., the changed files + are preserved but not marked for commit) and reports what has not + been updated. This is the default action. + --hard:: Matches the working tree and index to that of the tree being switched to. Any changes to tracked files in the working tree @@ -52,114 +62,53 @@ OPTIONS and updates the files that are different between the named commit and the current commit in the working tree. --p:: ---patch:: - Interactively select hunks in the difference between the index - and <commit> (defaults to HEAD). The chosen hunks are applied - in reverse to the index. -+ -This means that `git reset -p` is the opposite of `git add -p` (see -linkgit:git-add[1]). - --q:: ---quiet:: - Be quiet, only report errors. - -<commit>:: - Commit to make the current HEAD. If not given defaults to HEAD. - -DISCUSSION ----------- - -The tables below show what happens when running: - ----------- -git reset --option target ----------- - -to reset the HEAD to another commit (`target`) with the different -reset options depending on the state of the files. - -In these tables, A, B, C and D are some different states of a -file. For example, the first line of the first table means that if a -file is in state A in the working tree, in state B in the index, in -state C in HEAD and in state D in the target, then "git reset --soft -target" will put the file in state A in the working tree, in state B -in the index and in state D in HEAD. - - working index HEAD target working index HEAD - ---------------------------------------------------- - A B C D --soft A B D - --mixed A D D - --hard D D D - --merge (disallowed) - - working index HEAD target working index HEAD - ---------------------------------------------------- - A B C C --soft A B C - --mixed A C C - --hard C C C - --merge (disallowed) - - working index HEAD target working index HEAD - ---------------------------------------------------- - B B C D --soft B B D - --mixed B D D - --hard D D D - --merge D D D - - working index HEAD target working index HEAD - ---------------------------------------------------- - B B C C --soft B B C - --mixed B C C - --hard C C C - --merge C C C - - working index HEAD target working index HEAD - ---------------------------------------------------- - B C C D --soft B C D - --mixed B D D - --hard D D D - --merge (disallowed) - - working index HEAD target working index HEAD - ---------------------------------------------------- - B C C C --soft B C C - --mixed B C C - --hard C C C - --merge B C C +--keep:: + Reset the index to the given commit, keeping local changes in + the working tree since the current commit, while updating + working tree files without local changes to what appears in + the given commit. If a file that is different between the + current commit and the given commit has local changes, reset + is aborted. +-- -"reset --merge" is meant to be used when resetting out of a conflicted -merge. Any mergy operation guarantees that the work tree file that is -involved in the merge does not have local change wrt the index before -it starts, and that it writes the result out to the work tree. So if -we see some difference between the index and the target and also -between the index and the work tree, then it means that we are not -resetting out from a state that a mergy operation left after failing -with a conflict. That is why we disallow --merge option in this case. +If you want to undo a commit other than the latest on a branch, +linkgit:git-revert[1] is your friend. -The following tables show what happens when there are unmerged -entries: - working index HEAD target working index HEAD - ---------------------------------------------------- - X U A B --soft (disallowed) - --mixed X B B - --hard B B B - --merge B B B +OPTIONS +------- - working index HEAD target working index HEAD - ---------------------------------------------------- - X U A A --soft (disallowed) - --mixed X A A - --hard A A A - --merge A A A +-q:: +--quiet:: + Be quiet, only report errors. -X means any state and U means an unmerged index. -Examples +EXAMPLES -------- +Undo add:: ++ +------------ +$ edit <1> +$ git add frotz.c filfre.c +$ mailx <2> +$ git reset <3> +$ git pull git://info.example.com/ nitfol <4> +------------ ++ +<1> You are happily working on something, and find the changes +in these files are in good order. You do not want to see them +when you run "git diff", because you plan to work on other files +and changes with these files are distracting. +<2> Somebody asks you to pull, and the changes sounds worthy of merging. +<3> However, you already dirtied the index (i.e. your index does +not match the HEAD commit). But you know the pull you are going +to make does not affect frotz.c nor filfre.c, so you revert the +index changes for these two files. Your changes in working tree +remain there. +<4> Then you can pull and merge, leaving frotz.c and filfre.c +changes still in the working tree. + Undo a commit and redo:: + ------------ @@ -179,19 +128,6 @@ edit the message further, you can give -C option instead. + See also the --amend option to linkgit:git-commit[1]. -Undo commits permanently:: -+ ------------- -$ git commit ... -$ git reset --hard HEAD~3 <1> ------------- -+ -<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad -and you do not want to ever see them again. Do *not* do this if -you have already given these commits to somebody else. (See the -"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for -the implications of doing so.) - Undo a commit, making it a topic branch:: + ------------ @@ -207,28 +143,18 @@ current HEAD. <2> Rewind the master branch to get rid of those three commits. <3> Switch to "topic/wip" branch and keep working. -Undo add:: +Undo commits permanently:: + ------------ -$ edit <1> -$ git add frotz.c filfre.c -$ mailx <2> -$ git reset <3> -$ git pull git://info.example.com/ nitfol <4> +$ git commit ... +$ git reset --hard HEAD~3 <1> ------------ + -<1> You are happily working on something, and find the changes -in these files are in good order. You do not want to see them -when you run "git diff", because you plan to work on other files -and changes with these files are distracting. -<2> Somebody asks you to pull, and the changes sounds worthy of merging. -<3> However, you already dirtied the index (i.e. your index does -not match the HEAD commit). But you know the pull you are going -to make does not affect frotz.c nor filfre.c, so you revert the -index changes for these two files. Your changes in working tree -remain there. -<4> Then you can pull and merge, leaving frotz.c and filfre.c -changes still in the working tree. +<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad +and you do not want to ever see them again. Do *not* do this if +you have already given these commits to somebody else. (See the +"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for +the implications of doing so.) Undo a merge or pull:: + @@ -325,6 +251,140 @@ $ git add frotz.c <3> <2> This commits all other changes in the index. <3> Adds the file to the index again. +Keep changes in working tree while discarding some previous commits:: ++ +Suppose you are working on something and you commit it, and then you +continue working a bit more, but now you think that what you have in +your working tree should be in another branch that has nothing to do +with what you commited previously. You can start a new branch and +reset it while keeping the changes in your work tree. ++ +------------ +$ git tag start +$ git checkout -b branch1 +$ edit +$ git commit ... <1> +$ edit +$ git checkout -b branch2 <2> +$ git reset --keep start <3> +------------ ++ +<1> This commits your first edits in branch1. +<2> In the ideal world, you could have realized that the earlier + commit did not belong to the new topic when you created and switched + to branch2 (i.e. "git checkout -b branch2 start"), but nobody is + perfect. +<3> But you can use "reset --keep" to remove the unwanted commit after + you switched to "branch2". + + +DISCUSSION +---------- + +The tables below show what happens when running: + +---------- +git reset --option target +---------- + +to reset the HEAD to another commit (`target`) with the different +reset options depending on the state of the files. + +In these tables, A, B, C and D are some different states of a +file. For example, the first line of the first table means that if a +file is in state A in the working tree, in state B in the index, in +state C in HEAD and in state D in the target, then "git reset --soft +target" will put the file in state A in the working tree, in state B +in the index and in state D in HEAD. + + working index HEAD target working index HEAD + ---------------------------------------------------- + A B C D --soft A B D + --mixed A D D + --hard D D D + --merge (disallowed) + --keep (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + A B C C --soft A B C + --mixed A C C + --hard C C C + --merge (disallowed) + --keep A C C + + working index HEAD target working index HEAD + ---------------------------------------------------- + B B C D --soft B B D + --mixed B D D + --hard D D D + --merge D D D + --keep (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + B B C C --soft B B C + --mixed B C C + --hard C C C + --merge C C C + --keep B C C + + working index HEAD target working index HEAD + ---------------------------------------------------- + B C C D --soft B C D + --mixed B D D + --hard D D D + --merge (disallowed) + --keep (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + B C C C --soft B C C + --mixed B C C + --hard C C C + --merge B C C + --keep B C C + +"reset --merge" is meant to be used when resetting out of a conflicted +merge. Any mergy operation guarantees that the work tree file that is +involved in the merge does not have local change wrt the index before +it starts, and that it writes the result out to the work tree. So if +we see some difference between the index and the target and also +between the index and the work tree, then it means that we are not +resetting out from a state that a mergy operation left after failing +with a conflict. That is why we disallow --merge option in this case. + +"reset --keep" is meant to be used when removing some of the last +commits in the current branch while keeping changes in the working +tree. If there could be conflicts between the changes in the commit we +want to remove and the changes in the working tree we want to keep, +the reset is disallowed. That's why it is disallowed if there are both +changes between the working tree and HEAD, and between HEAD and the +target. To be safe, it is also disallowed when there are unmerged +entries. + +The following tables show what happens when there are unmerged +entries: + + working index HEAD target working index HEAD + ---------------------------------------------------- + X U A B --soft (disallowed) + --mixed X B B + --hard B B B + --merge B B B + --keep (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + X U A A --soft (disallowed) + --mixed X A A + --hard A A A + --merge A A A + --keep (disallowed) + +X means any state and U means an unmerged index. + + Author ------ Written by Junio C Hamano <gitster@pobox.com> and Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 8db600f6ba..be4c053360 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -174,202 +174,7 @@ shown. If the pattern does not contain a globbing character (`?`, Flags and parameters to be parsed. -SPECIFYING REVISIONS --------------------- - -A revision parameter typically, but not necessarily, names a -commit object. They use what is called an 'extended SHA1' -syntax. Here are various ways to spell object names. The -ones listed near the end of this list are to name trees and -blobs contained in a commit. - -* The full SHA1 object name (40-byte hexadecimal string), or - a substring of such that is unique within the repository. - E.g. dae86e1950b1277e545cee180551750029cfe735 and dae86e both - name the same commit object if there are no other object in - your repository whose object name starts with dae86e. - -* An output from 'git describe'; i.e. a closest tag, optionally - followed by a dash and a number of commits, followed by a dash, a - `g`, and an abbreviated object name. - -* A symbolic ref name. E.g. 'master' typically means the commit - object referenced by refs/heads/master. If you - happen to have both heads/master and tags/master, you can - explicitly say 'heads/master' to tell git which one you mean. - When ambiguous, a `<name>` is disambiguated by taking the - first match in the following rules: - - . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually - useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` and `MERGE_HEAD`); - - . otherwise, `refs/<name>` if exists; - - . otherwise, `refs/tags/<name>` if exists; - - . otherwise, `refs/heads/<name>` if exists; - - . otherwise, `refs/remotes/<name>` if exists; - - . otherwise, `refs/remotes/<name>/HEAD` if exists. -+ -HEAD names the commit your changes in the working tree is based on. -FETCH_HEAD records the branch you fetched from a remote repository -with your last 'git fetch' invocation. -ORIG_HEAD is created by commands that moves your HEAD in a drastic -way, to record the position of the HEAD before their operation, so that -you can change the tip of the branch back to the state before you ran -them easily. -MERGE_HEAD records the commit(s) you are merging into your branch -when you run 'git merge'. -+ -Note that any of the `refs/*` cases above may come either from -the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file. - -* A ref followed by the suffix '@' with a date specification - enclosed in a brace - pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1 - second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value - of the ref at a prior point in time. This suffix may only be - used immediately following a ref name and the ref must have an - existing log ($GIT_DIR/logs/<ref>). Note that this looks up the state - of your *local* ref at a given time; e.g., what was in your local - `master` branch last week. If you want to look at commits made during - certain times, see `--since` and `--until`. - -* A ref followed by the suffix '@' with an ordinal specification - enclosed in a brace pair (e.g. '\{1\}', '\{15\}') to specify - the n-th prior value of that ref. For example 'master@\{1\}' - is the immediate prior value of 'master' while 'master@\{5\}' - is the 5th prior value of 'master'. This suffix may only be used - immediately following a ref name and the ref must have an existing - log ($GIT_DIR/logs/<ref>). - -* You can use the '@' construct with an empty ref part to get at a - reflog of the current branch. For example, if you are on the - branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'. - -* The special construct '@\{-<n>\}' means the <n>th branch checked out - before the current one. - -* The suffix '@\{upstream\}' to a ref (short form 'ref@\{u\}') refers to - the branch the ref is set to build on top of. Missing ref defaults - to the current branch. - -* A suffix '{caret}' to a revision parameter means the first parent of - that commit object. '{caret}<n>' means the <n>th parent (i.e. - 'rev{caret}' - is equivalent to 'rev{caret}1'). As a special rule, - 'rev{caret}0' means the commit itself and is used when 'rev' is the - object name of a tag object that refers to a commit object. - -* A suffix '{tilde}<n>' to a revision parameter means the commit - object that is the <n>th generation grand-parent of the named - commit object, following only the first parent. I.e. rev~3 is - equivalent to rev{caret}{caret}{caret} which is equivalent to - rev{caret}1{caret}1{caret}1. See below for a illustration of - the usage of this form. - -* A suffix '{caret}' followed by an object type name enclosed in - brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object - could be a tag, and dereference the tag recursively until an - object of that type is found or the object cannot be - dereferenced anymore (in which case, barf). `rev{caret}0` - introduced earlier is a short-hand for `rev{caret}\{commit\}`. - -* A suffix '{caret}' followed by an empty brace pair - (e.g. `v0.99.8{caret}\{\}`) means the object could be a tag, - and dereference the tag recursively until a non-tag object is - found. - -* A colon, followed by a slash, followed by a text: this names - a commit whose commit message starts with the specified text. - This name returns the youngest matching commit which is - reachable from any ref. If the commit message starts with a - '!', you have to repeat that; the special sequence ':/!', - followed by something else than '!' is reserved for now. - -* A suffix ':' followed by a path; this names the blob or tree - at the given path in the tree-ish object named by the part - before the colon. - -* A colon, optionally followed by a stage number (0 to 3) and a - colon, followed by a path; this names a blob object in the - index at the given path. Missing stage number (and the colon - that follows it) names a stage 0 entry. During a merge, stage - 1 is the common ancestor, stage 2 is the target branch's version - (typically the current branch), and stage 3 is the version from - the branch being merged. - -Here is an illustration, by Jon Loeliger. Both commit nodes B -and C are parents of commit node A. Parent commits are ordered -left-to-right. - -........................................ -G H I J - \ / \ / - D E F - \ | / \ - \ | / | - \|/ | - B C - \ / - \ / - A -........................................ - - A = = A^0 - B = A^ = A^1 = A~1 - C = A^2 = A^2 - D = A^^ = A^1^1 = A~2 - E = B^2 = A^^2 - F = B^3 = A^^3 - G = A^^^ = A^1^1^1 = A~3 - H = D^2 = B^^2 = A^^^2 = A~2^2 - I = F^ = B^3^ = A^^3^ - J = F^2 = B^3^2 = A^^3^2 - - -SPECIFYING RANGES ------------------ - -History traversing commands such as 'git log' operate on a set -of commits, not just a single commit. To these commands, -specifying a single revision with the notation described in the -previous section means the set of commits reachable from that -commit, following the commit ancestry chain. - -To exclude commits reachable from a commit, a prefix `{caret}` -notation is used. E.g. `{caret}r1 r2` means commits reachable -from `r2` but exclude the ones reachable from `r1`. - -This set operation appears so often that there is a shorthand -for it. When you have two commits `r1` and `r2` (named according -to the syntax explained in SPECIFYING REVISIONS above), you can ask -for commits that are reachable from r2 excluding those that are reachable -from r1 by `{caret}r1 r2` and it can be written as `r1..r2`. - -A similar notation `r1\...r2` is called symmetric difference -of `r1` and `r2` and is defined as -`r1 r2 --not $(git merge-base --all r1 r2)`. -It is the set of commits that are reachable from either one of -`r1` or `r2` but not from both. - -Two other shorthands for naming a set that is formed by a commit -and its parent commits exist. The `r1{caret}@` notation means all -parents of `r1`. `r1{caret}!` includes commit `r1` but excludes -all of its parents. - -Here are a handful of examples: - - D G H D - D F G H I J D F - ^G D H D - ^D B E I J F B - B...C G H D E B C - ^D B C E I J F B C - C^@ I J F - F^! D G H D F +include::revisions.txt[] PARSEOPT -------- @@ -379,10 +184,13 @@ scripts the same facilities C builtins have. It works as an option normalizer (e.g. splits single switches aggregate values), a bit like `getopt(1)` does. It takes on the standard input the specification of the options to parse and -understand, and echoes on the standard output a line suitable for `sh(1)` `eval` +understand, and echoes on the standard output a string suitable for `sh(1)` `eval` to replace the arguments with normalized ones. In case of error, it outputs usage on the standard error stream, and exits with code 129. +Note: Make sure you quote the result when passing it to `eval`. See +below for an example. + Input Format ~~~~~~~~~~~~ @@ -439,7 +247,7 @@ bar= some cool option --bar with an argument An option group Header C? option C with an optional argument" -eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?` +eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)" ------------ SQ-QUOTE diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index c66bf8072e..b7d9ef7e47 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -3,20 +3,22 @@ git-revert(1) NAME ---- -git-revert - Revert an existing commit +git-revert - Revert some existing commits SYNOPSIS -------- -'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit> +'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>... DESCRIPTION ----------- -Given one existing commit, revert the change the patch introduces, and record a -new commit that records it. This requires your working tree to be clean (no -modifications from the HEAD commit). -Note: 'git revert' is used to record a new commit to reverse the -effect of an earlier commit (often a faulty one). If you want to +Given one or more existing commits, revert the changes that the +related patches introduce, and record some new commits that record +them. This requires your working tree to be clean (no modifications +from the HEAD commit). + +Note: 'git revert' is used to record some new commits to reverse the +effect of some earlier commits (often only a faulty one). If you want to throw away all uncommitted changes in your working directory, you should see linkgit:git-reset[1], particularly the '--hard' option. If you want to extract specific files as they were in another commit, you @@ -26,10 +28,13 @@ both will discard uncommitted changes in your working directory. OPTIONS ------- -<commit>:: - Commit to revert. +<commit>...:: + Commits to revert. For a more complete list of ways to spell commit names, see - "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. + linkgit:gitrevisions[1]. + Sets of commits can also be given but no traversal is done by + default, see linkgit:git-rev-list[1] and its '--no-walk' + option. -e:: --edit:: @@ -59,11 +64,11 @@ more details. -n:: --no-commit:: - Usually the command automatically creates a commit with - a commit log message stating which commit was - reverted. This flag applies the change necessary - to revert the named commit to your working tree - and the index, but does not make the commit. In addition, + Usually the command automatically creates some commits with + commit log messages stating which commits were + reverted. This flag applies the changes necessary + to revert the named commits to your working tree + and the index, but does not make the commits. In addition, when this option is used, your index does not have to match the HEAD commit. The revert is done against the beginning state of your index. @@ -75,6 +80,20 @@ effect to your index in a row. --signoff:: Add Signed-off-by line at the end of the commit message. +EXAMPLES +-------- +git revert HEAD~3:: + + Revert the changes specified by the fourth last commit in HEAD + and create a new commit with the reverted changes. + +git revert -n master\~5..master~2:: + + Revert the changes done by commits from the fifth last commit + in master (included) to the third last commit in master + (included), but do not create any commit with the reverted + changes. The revert only modifies the working tree and the + index. Author ------ @@ -84,6 +103,10 @@ Documentation -------------- Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. +SEE ALSO +-------- +linkgit:git-cherry-pick[1] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index ced35b2f53..c283084272 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -101,6 +101,15 @@ See the CONFIGURATION section for 'sendemail.multiedit'. + The --to option must be repeated for each user you want on the to list. +--8bit-encoding=<encoding>:: + When encountering a non-ASCII message or subject that does not + declare its encoding, add headers/quoting to indicate it is + encoded in <encoding>. Default is the value of the + 'sendemail.assume8bitEncoding'; if that is unspecified, this + will be prompted for if any non-ASCII files are encountered. ++ +Note that no attempts whatsoever are made to validate the encoding. + Sending ~~~~~~~ @@ -119,6 +128,13 @@ Sending value reverts to plain SMTP. Default is the value of 'sendemail.smtpencryption'. +--smtp-domain=<FQDN>:: + Specifies the Fully Qualified Domain Name (FQDN) used in the + HELO/EHLO command to the SMTP server. Some servers require the + FQDN to match your IP address. If not set, git send-email attempts + to determine your FQDN automatically. Default is the value of + 'sendemail.smtpdomain'. + --smtp-pass[=<password>]:: Password for SMTP-AUTH. The argument is optional: If no argument is specified, then the empty string is used as @@ -300,6 +316,21 @@ sendemail.confirm:: in the previous section for the meaning of these values. +Use gmail as the smtp server +---------------------------- + +Add the following section to the config file: + + [sendemail] + smtpencryption = tls + smtpserver = smtp.gmail.com + smtpuser = yourname@gmail.com + smtpserverport = 587 + +Note: the following perl modules are required + Net::SMTP::SSL, MIME::Base64 and Authen::SASL + + Author ------ Written by Ryan Anderson <ryan@michonline.com> diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index dfd4d0c223..bc1ac77495 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] git log --pretty=short | 'git shortlog' [-h] [-n] [-s] [-e] [-w] -'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...] +'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] <commit>... DESCRIPTION ----------- @@ -19,6 +19,11 @@ the first line of the commit message will be shown. Additionally, "[PATCH]" will be stripped from the commit description. +If no revisions are passed on the command line and either standard input +is not a terminal or there is no current branch, 'git shortlog' will +output a summary of the log read from standard input, without +reference to the current repository. + OPTIONS ------- @@ -39,6 +44,14 @@ OPTIONS --email:: Show the email address of each author. +--format[='<format>']:: + Instead of the commit subject, use some other information to + describe each commit. '<format>' can be any string accepted + by the `--format` option of 'git log', such as '{asterisk} [%h] %s'. + (See the "PRETTY FORMATS" section of linkgit:git-log[1].) + + Each pretty-printed commit will be rewrapped before it is shown. + -w[<width>[,<indent1>[,<indent2>]]]:: Linewrap the output by wrapping each line at `width`. The first line of each entry is indented by `indent1` spaces, and the second diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index b9c4154e73..81ba29669c 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git show-branch' [-a|--all] [-r|--remotes] [--topo-order | --date-order] - [--current] [--color | --no-color] [--sparse] + [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]... @@ -32,7 +32,7 @@ no <rev> nor <glob> is given on the command line. OPTIONS ------- <rev>:: - Arbitrary extended SHA1 expression (see linkgit:git-rev-parse[1]) + Arbitrary extended SHA1 expression (see linkgit:gitrevisions[1]) that typically names a branch head or a tag. <glob>:: @@ -117,13 +117,15 @@ OPTIONS When no explicit <ref> parameter is given, it defaults to the current branch (or `HEAD` if it is detached). ---color:: +--color[=<when>]:: Color the status sign (one of these: `*` `!` `+` `-`) of each commit corresponding to the branch it's in. + The value must be always (the default), never, or auto. --no-color:: Turn off colored output, even when the configuration file gives the default to color output. + Same as `--color=never`. Note that --more, --list, --independent and --merge-base options are mutually exclusive. diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index 3f9d9c6db3..75780d7d63 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -163,9 +163,15 @@ flag, so you can do to get a listing of all tags together with what they dereference. +FILES +----- +`.git/refs/*`, `.git/packed-refs` + SEE ALSO -------- -linkgit:git-ls-remote[1] +linkgit:git-ls-remote[1], +linkgit:git-update-ref[1], +linkgit:gitrepository-layout[5] AUTHORS ------- diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index 55e687a7c7..0002bfb045 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -36,7 +36,7 @@ OPTIONS <object>...:: The names of objects to show. For a more complete list of ways to spell object names, see - "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. + "SPECIFYING REVISIONS" section in linkgit:gitrevisions[1]. include::pretty-options.txt[] diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 2d4bbfcaf4..2fd054c104 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -27,6 +27,10 @@ OPTIONS --short:: Give the output in the short-format. +-b:: +--branch:: + Show the branch and tracking info even in short-format. + --porcelain:: Give the output in a stable, easy-to-parse format for scripts. Currently this is identical to --short output, but is guaranteed @@ -49,6 +53,17 @@ See linkgit:git-config[1] for configuration variable used to change the default for when the option is not specified. +--ignore-submodules[=<when>]:: + Ignore changes to submodules when looking for changes. <when> can be + either "untracked", "dirty" or "all", which is the default. When + "untracked" is used submodules are not considered dirty when they only + contain untracked content (but they are still scanned for modified + content). Using "dirty" ignores all changes to the work tree of submodules, + only changes to the commits stored in the superproject are shown (this was + the behavior before 1.7.0). Using "all" hides all changes to submodules + (and suppresses the output of submodule summaries when the config option + `status.submodulesummary` is set). + -z:: Terminate entries with NUL, instead of LF. This implies the `--porcelain` output format if no other format is given. @@ -120,6 +135,10 @@ Ignored files are not listed. ? ? untracked ------------------------------------------------- +If -b is used the short-format status is preceded by a line + +## branchname tracking info + There is an alternate -z format recommended for machine parsing. In that format, the status field is the same, but some other things change. First, the '->' is omitted from rename entries and the field @@ -128,7 +147,7 @@ order is reversed (e.g 'from -> to' becomes 'to from'). Second, a NUL and the terminating newline (but a space still separates the status field from the first filename). Third, filenames containing special characters are not specially formatted; no quoting or -backslash-escaping is performed. +backslash-escaping is performed. Fourth, there is no branch line. CONFIGURATION ------------- diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 2502531a3d..1ed331c599 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,7 +9,7 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] -'git submodule' [--quiet] add [-b branch] +'git submodule' [--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <repository> [<path>] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...] 'git submodule' [--quiet] init [--] [<path>...] @@ -145,10 +145,12 @@ summary:: foreach:: Evaluates an arbitrary shell command in each checked out submodule. - The command has access to the variables $name, $path and $sha1: + The command has access to the variables $name, $path, $sha1 and + $toplevel: $name is the name of the relevant submodule section in .gitmodules, $path is the name of the submodule directory relative to the - superproject, and $sha1 is the commit as recorded in the superproject. + superproject, $sha1 is the commit as recorded in the superproject, + and $toplevel is the absolute path to the top-level of the superproject. Any submodules defined in the superproject but not checked out are ignored by this command. Unless given --quiet, foreach prints the name of each submodule before evaluating the command. @@ -181,6 +183,11 @@ OPTIONS --branch:: Branch of repository to add as submodule. +-f:: +--force:: + This option is only valid for the add command. + Allow adding an otherwise ignored submodule path. + --cached:: This option is only valid for status and summary commands. These commands typically use the commit found in the submodule HEAD, but diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 99f3c1ea6c..4b84d08fc8 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -243,7 +243,7 @@ where <name> is the name of the SVN repository as specified by the -R option to --username;; Specify the SVN username to perform the commit as. This option overrides - configuration property 'username'. + the 'username' configuration property. --commit-url;; Use the specified URL to connect to the destination Subversion @@ -646,6 +646,12 @@ svn.brokenSymlinkWorkaround:: revision fetched. If unset, 'git svn' assumes this option to be "true". +svn.pathnameencoding:: + This instructs git svn to recode pathnames to a given encoding. + It can be used by windows users and by those who work in non-utf8 + locales to avoid corrupted file names with non-ASCII characters. + Valid encodings are the ones supported by Perl's Encode module. + Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps options all affect the metadata generated and used by 'git svn'; they *must* be set in the configuration file before any history is imported diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 68dc1879fe..765d4b312e 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -93,8 +93,6 @@ OPTIONS This option can be also used as a coarse file-level mechanism to ignore uncommitted changes in tracked files (akin to what `.gitignore` does for untracked files). -You should remember that an explicit 'git add' operation will -still cause the file to be refreshed from the working tree. Git will fail (gracefully) in case it needs to modify this file in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, diff --git a/Documentation/git.txt b/Documentation/git.txt index 46a215824e..c28a7ecc4d 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] + [-c name=value] [--help] COMMAND [ARGS] DESCRIPTION @@ -43,6 +44,19 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: +* link:v1.7.2.1/git.html[documentation for release 1.7.2.1] + +* release notes for + link:RelNotes-1.7.2.1.txt[1.7.2.1], + link:RelNotes-1.7.2.txt[1.7.2]. + +* link:v1.7.1.2/git.html[documentation for release 1.7.1.2] + +* release notes for + link:RelNotes-1.7.1.2.txt[1.7.1.2], + link:RelNotes-1.7.1.1.txt[1.7.1.1], + link:RelNotes-1.7.1.txt[1.7.1]. + * link:v1.7.0.7/git.html[documentation for release 1.7.0.7] * release notes for @@ -224,6 +238,12 @@ displayed. See linkgit:git-help[1] for more information, because `git --help ...` is converted internally into `git help ...`. +-c <name>=<value>:: + Pass a configuration parameter to the command. The value + given will override values from configuration files. + The <name> is expected in the same format as listed by + 'git config' (subkeys separated by dots). + --exec-path:: Path to wherever your core git programs are installed. This can also be controlled by setting the GIT_EXEC_PATH @@ -467,7 +487,7 @@ HEAD:: (i.e. the contents of `$GIT_DIR/refs/heads/<head>`). For a more complete list of ways to spell object names, see -"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. +"SPECIFYING REVISIONS" section in linkgit:gitrevisions[1]. File/Directory Structure @@ -534,6 +554,16 @@ git so take care if using Cogito etc. a GIT_DIR set on the command line or in the environment. (Useful for excluding slow-loading network directories.) +'GIT_DISCOVERY_ACROSS_FILESYSTEM':: + When run in a directory that does not have ".git" repository + directory, git tries to find such a directory in the parent + directories to find the top of the working tree, but by default it + does not cross filesystem boundaries. This environment variable + can be set to true to tell git not to stop at filesystem + boundaries. Like 'GIT_CEILING_DIRECTORIES', this will not affect + an explicit repository directory set via 'GIT_DIR' or on the + command line. + git Commits ~~~~~~~~~~~ 'GIT_AUTHOR_NAME':: @@ -697,6 +727,13 @@ The documentation for git suite was started by David Greaves <david@dgreaves.com>, and later enhanced greatly by the contributors on the git-list <git@vger.kernel.org>. +Reporting Bugs +-------------- + +Report bugs to the Git mailing list <git@vger.kernel.org> where the +development and maintenance is primarily done. You do not have to be +subscribed to the list to send a message there. + SEE ALSO -------- linkgit:gittutorial[7], linkgit:gittutorial-2[7], diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index d892e642ed..564586b943 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -92,53 +92,154 @@ such as 'git checkout' and 'git merge' run. They also affect how git stores the contents you prepare in the working tree in the repository upon 'git add' and 'git commit'. -`crlf` +`text` ^^^^^^ -This attribute controls the line-ending convention. +This attribute enables and controls end-of-line normalization. When a +text file is normalized, its line endings are converted to LF in the +repository. To control what line ending style is used in the working +directory, use the `eol` attribute for a single file and the +`core.eol` configuration variable for all text files. Set:: - Setting the `crlf` attribute on a path is meant to mark - the path as a "text" file. 'core.autocrlf' conversion - takes place without guessing the content type by - inspection. + Setting the `text` attribute on a path enables end-of-line + normalization and marks the path as a text file. End-of-line + conversion takes place without guessing the content type. Unset:: - Unsetting the `crlf` attribute on a path tells git not to + Unsetting the `text` attribute on a path tells git not to attempt any end-of-line conversion upon checkin or checkout. +Set to string value "auto":: + + When `text` is set to "auto", the path is marked for automatic + end-of-line normalization. If git decides that the content is + text, its line endings are normalized to LF on checkin. + Unspecified:: - Unspecified `crlf` attribute tells git to apply the - `core.autocrlf` conversion when the file content looks - like text. + If the `text` attribute is unspecified, git uses the + `core.autocrlf` configuration variable to determine if the + file should be converted. -Set to string value "input":: +Any other value causes git to act as if `text` has been left +unspecified. - This is similar to setting the attribute to `true`, but - also forces git to act as if `core.autocrlf` is set to - `input` for the path. +`eol` +^^^^^ -Any other value set to `crlf` attribute is ignored and git acts -as if the attribute is left unspecified. +This attribute sets a specific line-ending style to be used in the +working directory. It enables end-of-line normalization without any +content checks, effectively setting the `text` attribute. +Set to string value "crlf":: -The `core.autocrlf` conversion -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + This setting forces git to normalize line endings for this + file on checkin and convert them to CRLF when the file is + checked out. + +Set to string value "lf":: + + This setting forces git to normalize line endings to LF on + checkin and prevents conversion to CRLF when the file is + checked out. + +Backwards compatibility with `crlf` attribute +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For backwards compatibility, the `crlf` attribute is interpreted as +follows: + +------------------------ +crlf text +-crlf -text +crlf=input eol=lf +------------------------ + +End-of-line conversion +^^^^^^^^^^^^^^^^^^^^^^ + +While git normally leaves file contents alone, it can be configured to +normalize line endings to LF in the repository and, optionally, to +convert them to CRLF when files are checked out. + +Here is an example that will make git normalize .txt, .vcproj and .sh +files, ensure that .vcproj files have CRLF and .sh files have LF in +the working directory, and prevent .jpg files from being normalized +regardless of their content. + +------------------------ +*.txt text +*.vcproj eol=crlf +*.sh eol=lf +*.jpg -text +------------------------ + +Other source code management systems normalize all text files in their +repositories, and there are two ways to enable similar automatic +normalization in git. + +If you simply want to have CRLF line endings in your working directory +regardless of the repository you are working with, you can set the +config variable "core.autocrlf" without changing any attributes. -If the configuration variable `core.autocrlf` is false, no -conversion is done. +------------------------ +[core] + autocrlf = true +------------------------ + +This does not force normalization of all text files, but does ensure +that text files that you introduce to the repository have their line +endings normalized to LF when they are added, and that files that are +already normalized in the repository stay normalized. + +If you want to interoperate with a source code management system that +enforces end-of-line normalization, or you simply want all text files +in your repository to be normalized, you should instead set the `text` +attribute to "auto" for _all_ files. + +------------------------ +* text=auto +------------------------ + +This ensures that all files that git considers to be text will have +normalized (LF) line endings in the repository. The `core.eol` +configuration variable controls which line endings git will use for +normalized files in your working directory; the default is to use the +native line ending for your platform, or CRLF if `core.autocrlf` is +set. + +NOTE: When `text=auto` normalization is enabled in an existing +repository, any text files containing CRLFs should be normalized. If +they are not they will be normalized the next time someone tries to +change them, causing unfortunate misattribution. From a clean working +directory: + +------------------------------------------------- +$ echo "* text=auto" >>.gitattributes +$ rm .git/index # Remove the index to force git to +$ git reset # re-scan the working directory +$ git status # Show files that will be normalized +$ git add -u +$ git add .gitattributes +$ git commit -m "Introduce end-of-line normalization" +------------------------------------------------- + +If any files that should not be normalized show up in 'git status', +unset their `text` attribute before running 'git add -u'. -When `core.autocrlf` is true, it means that the platform wants -CRLF line endings for files in the working tree, and you want to -convert them back to the normal LF line endings when checking -in to the repository. +------------------------ +manual.pdf -text +------------------------ -When `core.autocrlf` is set to "input", line endings are -converted to LF upon checkin, but there is no conversion done -upon checkout. +Conversely, text files that git does not detect can have normalization +enabled manually. + +------------------------ +weirdchars.txt text +------------------------ If `core.safecrlf` is set to "true" or "warn", git verifies if the conversion is reversible for the current setting of @@ -223,11 +324,11 @@ Interaction between checkin/checkout attributes In the check-in codepath, the worktree file is first converted with `filter` driver (if specified and corresponding driver defined), then the result is processed with `ident` (if -specified), and then finally with `crlf` (again, if specified +specified), and then finally with `text` (again, if specified and applicable). In the check-out codepath, the blob content is first converted -with `crlf`, and then `ident` and fed to `filter`. +with `text`, and then `ident` and fed to `filter`. Generating diff text @@ -360,7 +461,7 @@ patterns are available: Customizing word diff ^^^^^^^^^^^^^^^^^^^^^ -You can customize the rules that `git diff --color-words` uses to +You can customize the rules that `git diff --word-diff` uses to split words in a line, by specifying an appropriate regular expression in the "diff.*.wordRegex" configuration variable. For example, in TeX a backslash followed by a sequence of letters forms a command, but @@ -414,6 +515,26 @@ because it quickly conveys the changes you have made), you should generate it separately and send it as a comment _in addition to_ the usual binary diff that you might send. +Because text conversion can be slow, especially when doing a +large number of them with `git log -p`, git provides a mechanism +to cache the output and use it in future diffs. To enable +caching, set the "cachetextconv" variable in your diff driver's +config. For example: + +------------------------ +[diff "jpg"] + textconv = exif + cachetextconv = true +------------------------ + +This will cache the result of running "exif" on each blob +indefinitely. If you change the textconv config variable for a +diff driver, git will automatically invalidate the cache entries +and re-run the textconv filter. If you want to invalidate the +cache manually (e.g., because your version of "exif" was updated +and now produces better output), you can remove the cache +manually with `git update-ref -d refs/notes/textconv/jpg` (where +"jpg" is the name of the diff driver, as in the example above). Performing a three-way merge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -631,7 +752,7 @@ You do not want any end-of-line conversions applied to, nor textual diffs produced for, any binary file you track. You would need to specify e.g. ------------ -*.jpg -crlf -diff +*.jpg -text -diff ------------ but that may become cumbersome, when you have many attributes. Using @@ -644,7 +765,7 @@ the same time. The system knows a built-in attribute macro, `binary`: which is equivalent to the above. Note that the attribute macros can only be "Set" (see the above example that sets "binary" macro as if it were an -ordinary attribute --- setting it in turn unsets "crlf" and "diff"). +ordinary attribute --- setting it in turn unsets "text" and "diff"). DEFINING ATTRIBUTE MACROS @@ -655,7 +776,7 @@ at the toplevel (i.e. not in any subdirectory). The built-in attribute macro "binary" is equivalent to: ------------ -[attr]binary -diff -crlf +[attr]binary -diff -text ------------ diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index f7815e96a2..ed3ddc92cb 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -971,7 +971,7 @@ commits from the master branch. The string inside brackets before the commit log message is a short name you can use to name the commit. In the above example, 'master' and 'mybranch' are branch heads. 'master^' is the first parent of 'master' -branch head. Please see linkgit:git-rev-parse[1] if you want to +branch head. Please see linkgit:gitrevisions[1] if you want to see more complex cases. [NOTE] diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index 9de8caf5d1..5d91a7e5b3 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -227,8 +227,8 @@ changes that touch a specified string, and is controlled by the commands. When diffcore-pickaxe is in use, it checks if there are -filepairs whose "original" side has the specified string and -whose "result" side does not. Such a filepair represents "the +filepairs whose "result" side has the specified string and +whose "origin" side does not. Such a filepair represents "the string appeared in this changeset". It also checks for the opposite case that loses the specified string. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 87e2c035a7..7183aa9abb 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -317,6 +317,44 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and exiting with non-zero status from this script causes the 'git gc --auto' to abort. +post-rewrite +~~~~~~~~~~~~ + +This hook is invoked by commands that rewrite commits (`git commit +--amend`, 'git-rebase'; currently 'git-filter-branch' does 'not' call +it!). Its first argument denotes the command it was invoked by: +currently one of `amend` or `rebase`. Further command-dependent +arguments may be passed in the future. + +The hook receives a list of the rewritten commits on stdin, in the +format + + <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF + +The 'extra-info' is again command-dependent. If it is empty, the +preceding SP is also omitted. Currently, no commands pass any +'extra-info'. + +The hook always runs after the automatic note copying (see +"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and +thus has access to these notes. + +The following command-specific comments apply: + +rebase:: + For the 'squash' and 'fixup' operation, all commits that were + squashed are listed as being rewritten to the squashed commit. + This means that there will be several lines sharing the same + 'new-sha1'. ++ +The commits are guaranteed to be listed in the order that they were +processed by rebase. + +There is no default 'post-rewrite' hook, but see the +`post-receive-copy-notes` script in `contrib/hooks` for an example +that copies your git-notes to the rewritten commits. + + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 98c459dc82..e10fa88b8c 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -83,16 +83,20 @@ Patterns have the following format: - If the pattern does not contain a slash '/', git treats it as a shell glob pattern and checks for a match against the - pathname without leading directories. + pathname relative to the location of the `.gitignore` file + (relative to the toplevel of the work tree if not from a + `.gitignore` file). - Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, "Documentation/\*.html" matches - "Documentation/git.html" but not - "Documentation/ppc/ppc.html". A leading slash matches the - beginning of the pathname; for example, "/*.c" matches - "cat-file.c" but not "mozilla-sha1/sha1.c". + "Documentation/git.html" but not "Documentation/ppc/ppc.html" + or "tools/perf/Documentation/perf.html". + + - A leading slash matches the beginning of the pathname. + For example, "/*.c" matches "cat-file.c" but not + "mozilla-sha1/sha1.c". An example: diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 99baa24a2d..05ac1c79f7 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -69,7 +69,7 @@ frequently used options. the form "'<from>'..'<to>'" to show all revisions between '<from>' and back to '<to>'. Note, more advanced revision selection can be applied. For a more complete list of ways to spell object names, see - "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. + linkgit:gitrevisions[1]. <path>...:: diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 5daf750d19..72a13d18e0 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -29,6 +29,9 @@ submodule.<name>.path:: submodule.<name>.url:: Defines an url from where the submodule repository can be cloned. + This may be either an absolute URL ready to be passed to + linkgit:git-clone[1] or (if it begins with ./ or ../) a location + relative to the superproject's origin repository. submodule.<name>.update:: Defines what to do when the submodule is updated by the superproject. diff --git a/Documentation/gitrevisions.txt b/Documentation/gitrevisions.txt new file mode 100644 index 0000000000..fc4789f98e --- /dev/null +++ b/Documentation/gitrevisions.txt @@ -0,0 +1,35 @@ +gitrevisions(7) +================ + +NAME +---- +gitrevisions - specifying revisions and ranges for git + +SYNOPSIS +-------- +gitrevisions + + +DESCRIPTION +----------- + +Many Git commands take revision parameters as arguments. Depending on +the command, they denote a specific commit or, for commands which +walk the revision graph (such as linkgit:git-log[1]), all commits which can +be reached from that commit. In the latter case one can also specify a +range of revisions explicitly. + +In addition, some Git commands (such as linkgit:git-show[1]) also take +revision parameters which denote other objects than commits, e.g. blobs +("files") or trees ("directories of files"). + +include::revisions.txt[] + + +SEE ALSO +-------- +linkgit:git-rev-parse[1] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/howto/revert-a-faulty-merge.txt b/Documentation/howto/revert-a-faulty-merge.txt index 3b4a390005..ff5c0bc27a 100644 --- a/Documentation/howto/revert-a-faulty-merge.txt +++ b/Documentation/howto/revert-a-faulty-merge.txt @@ -142,6 +142,8 @@ different resolution strategies: revert of a merge was rebuilt from scratch (i.e. rebasing and fixing, as you seem to have interpreted), then re-merging the result without doing anything else fancy would be the right thing to do. + (See the ADDENDUM below for how to rebuild a branch from scratch + without changing its original branching-off point.) However, there are things to keep in mind when reverting a merge (and reverting such a revert). @@ -177,3 +179,91 @@ the answer is: "oops, I really shouldn't have merged it, because it wasn't ready yet, and I really need to undo _all_ of the merge"). So then you really should revert the merge, but when you want to re-do the merge, you now need to do it by reverting the revert. + +ADDENDUM + +Sometimes you have to rewrite one of a topic branch's commits *and* you can't +change the topic's branching-off point. Consider the following situation: + + P---o---o---M---x---x---W---x + \ / + A---B---C + +where commit W reverted commit M because it turned out that commit B was wrong +and needs to be rewritten, but you need the rewritten topic to still branch +from commit P (perhaps P is a branching-off point for yet another branch, and +you want be able to merge the topic into both branches). + +The natural thing to do in this case is to checkout the A-B-C branch and use +"rebase -i P" to change commit B. However this does not rewrite commit A, +because "rebase -i" by default fast-forwards over any initial commits selected +with the "pick" command. So you end up with this: + + P---o---o---M---x---x---W---x + \ / + A---B---C <-- old branch + \ + B'---C' <-- naively rewritten branch + +To merge A-B'-C' into the mainline branch you would still have to first revert +commit W in order to pick up the changes in A, but then it's likely that the +changes in B' will conflict with the original B changes re-introduced by the +reversion of W. + +However, you can avoid these problems if you recreate the entire branch, +including commit A: + + A'---B'---C' <-- completely rewritten branch + / + P---o---o---M---x---x---W---x + \ / + A---B---C + +You can merge A'-B'-C' into the mainline branch without worrying about first +reverting W. Mainline's history would look like this: + + A'---B'---C'------------------ + / \ + P---o---o---M---x---x---W---x---M2 + \ / + A---B---C + +But if you don't actually need to change commit A, then you need some way to +recreate it as a new commit with the same changes in it. The rebase commmand's +--no-ff option provides a way to do this: + + $ git rebase [-i] --no-ff P + +The --no-ff option creates a new branch A'-B'-C' with all-new commits (all the +SHA IDs will be different) even if in the interactive case you only actually +modify commit B. You can then merge this new branch directly into the mainline +branch and be sure you'll get all of the branch's changes. + +You can also use --no-ff in cases where you just add extra commits to the topic +to fix it up. Let's revisit the situation discussed at the start of this howto: + + P---o---o---M---x---x---W---x + \ / + A---B---C----------------D---E <-- fixed-up topic branch + +At this point, you can use --no-ff to recreate the topic branch: + + $ git checkout E + $ git rebase --no-ff P + +yielding + + A'---B'---C'------------D'---E' <-- recreated topic branch + / + P---o---o---M---x---x---W---x + \ / + A---B---C----------------D---E + +You can merge the recreated branch into the mainline without reverting commit W, +and mainline's history will look like this: + + A'---B'---C'------------D'---E' + / \ + P---o---o---M---x---x---W---x---M2 + \ / + A---B---C diff --git a/Documentation/install-webdoc.sh b/Documentation/install-webdoc.sh index 2135a8ee1f..34d02a2418 100755 --- a/Documentation/install-webdoc.sh +++ b/Documentation/install-webdoc.sh @@ -12,7 +12,7 @@ do then : did not match elif test -f "$T/$h" && - diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h" + $DIFF -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h" then :; # up to date else diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 81ac823964..722d704ff2 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -72,6 +72,7 @@ option can be used to override --squash. Synonyms to --stat and --no-stat; these are deprecated and will be removed in the future. +ifndef::git-pull[] -q:: --quiet:: Operate quietly. @@ -79,3 +80,4 @@ option can be used to override --squash. -v:: --verbose:: Be verbose. +endif::git-pull[] diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 1686a54d22..561cc9f7d7 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -11,7 +11,12 @@ have limited your view of history: for example, if you are only interested in changes related to a certain directory or file. -Here are some additional details for each format: +There are several built-in formats, and you can define +additional formats by setting a pretty.<name> +config option to either another format name, or a +'format:' string, as described below (see +linkgit:git-config[1]). Here are the details of the +built-in formats: * 'oneline' @@ -76,9 +81,9 @@ displayed in full, regardless of whether --abbrev or true parent commits, without taking grafts nor history simplification into account. -* 'format:' +* 'format:<string>' + -The 'format:' format allows you to specify which information +The 'format:<string>' format allows you to specify which information you want to show. It works a little bit like printf format, with the notable exception that you get a newline with '%n' instead of '\n'. @@ -123,6 +128,7 @@ The placeholders are: - '%s': subject - '%f': sanitized subject line, suitable for a filename - '%b': body +- '%B': raw body (unwrapped subject and body) - '%N': commit notes - '%gD': reflog selector, e.g., `refs/stash@\{1\}` - '%gd': shortened reflog selector, e.g., `stash@\{1\}` @@ -153,6 +159,10 @@ If you add a `-` (minus sign) after '%' of a placeholder, line-feeds that immediately precede the expansion are deleted if and only if the placeholder expands to an empty string. +If you add a ` ` (space) after '%' of a placeholder, a space +is inserted immediately before the expansion if and only if the +placeholder expands to a non-empty string. + * 'tformat:' + The 'tformat:' format works exactly like 'format:', except that it diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index aa96caeab2..9b6f3899ec 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -1,10 +1,11 @@ --pretty[='<format>']:: ---format[='<format>']:: +--format='<format>':: Pretty-print the contents of the commit logs in a given format, where '<format>' can be one of 'oneline', 'short', 'medium', - 'full', 'fuller', 'email', 'raw' and 'format:<string>'. - When omitted, the format defaults to 'medium'. + 'full', 'fuller', 'email', 'raw' and 'format:<string>'. See + the "PRETTY FORMATS" section for some additional details for each + format. When omitted, the format defaults to 'medium'. + Note: you can specify the default pretty format in the repository configuration (see linkgit:git-config[1]). @@ -30,9 +31,18 @@ people using 80-column terminals. defaults to UTF-8. --no-notes:: ---show-notes:: +--show-notes[=<ref>]:: Show the notes (see linkgit:git-notes[1]) that annotate the commit, when showing the commit log message. This is the default for `git log`, `git show` and `git whatchanged` commands when there is no `--pretty`, `--format` nor `--oneline` option is given on the command line. ++ +With an optional argument, add this ref to the list of notes. The ref +is taken to be in `refs/notes/` if it is not qualified. + +--[no-]standard-notes:: + Enable or disable populating the notes ref list from the + 'core.notesRef' and 'notes.displayRef' variables (or + corresponding environment overrides). Enabled by default. + See linkgit:git-config[1]. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 81c0e6f184..cc562a057a 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -98,6 +98,15 @@ you would get an output like this: This implies the '--topo-order' option by default, but the '--date-order' option may also be specified. +ifdef::git-rev-list[] +--count:: + Print a number stating how many commits would have been + listed, and suppress all other output. When used together + with '--left-right', instead print the counts for left and + right commits, separated by a tab. +endif::git-rev-list[] + + ifndef::git-rev-list[] Diff Formatting ~~~~~~~~~~~~~~~ @@ -108,8 +117,8 @@ options may be given. See linkgit:git-diff-files[1] for more options. -c:: - This flag changes the way a merge commit is displayed. It shows - the differences from each of the parents to the merge result + With this option, diff output for a merge commit + shows the differences from each of the parents to the merge result simultaneously instead of showing pairwise diff between a parent and the result one at a time. Furthermore, it lists only files which were modified from all parents. @@ -121,6 +130,15 @@ options may be given. See linkgit:git-diff-files[1] for more options. the parents have only two variants and the merge result picks one of them without modification. +-m:: + + This flag makes the merge commits show the full diff like + regular commits; for each merge parent, a separate log entry + and diff is generated. An exception is that only diff against + the first parent is shown when '--first-parent' option is given; + in that case, the output represents the changes the merge + brought _into_ the then-current branch. + -r:: Show recursive diffs. @@ -375,6 +393,14 @@ Default mode:: merges from the resulting history, as there are no selected commits contributing to this merge. +--ancestry-path:: + + When given a range of commits to display (e.g. 'commit1..commit2' + or 'commit2 {caret}commit1'), only display commits that exist + directly on the ancestry chain between the 'commit1' and + 'commit2', i.e. commits that are both descendants of 'commit1', + and ancestors of 'commit2'. + A more detailed explanation follows. Suppose you specified `foo` as the <paths>. We shall call commits @@ -431,7 +457,7 @@ This results in: + ----------------------------------------------------------------------- .-A---N---O - / / + / / / I---------D ----------------------------------------------------------------------- + @@ -502,8 +528,6 @@ Note that without '\--full-history', this still simplifies merges: if one of the parents is TREESAME, we follow only that one, so the other sides of the merge are never walked. -Finally, there is a fourth simplification mode available: - --simplify-merges:: First, build a history graph in the same way that @@ -545,6 +569,46 @@ Note the major differences in `N` and `P` over '\--full-history': removed completely, because it had one parent and is TREESAME. -- +Finally, there is a fifth simplification mode available: + +--ancestry-path:: + + Limit the displayed commits to those directly on the ancestry + chain between the "from" and "to" commits in the given commit + range. I.e. only display commits that are ancestor of the "to" + commit, and descendants of the "from" commit. ++ +As an example use case, consider the following commit history: ++ +----------------------------------------------------------------------- + D---E-------F + / \ \ + B---C---G---H---I---J + / \ + A-------K---------------L--M +----------------------------------------------------------------------- ++ +A regular 'D..M' computes the set of commits that are ancestors of `M`, +but excludes the ones that are ancestors of `D`. This is useful to see +what happened to the history leading to `M` since `D`, in the sense +that "what does `M` have that did not exist in `D`". The result in this +example would be all the commits, except `A` and `B` (and `D` itself, +of course). ++ +When we want to find out what commits in `M` are contaminated with the +bug introduced by `D` and need fixing, however, we might want to view +only the subset of 'D..M' that are actually descendants of `D`, i.e. +excluding `C` and `K`. This is exactly what the '\--ancestry-path' +option does. Applied to the 'D..M' range, it results in: ++ +----------------------------------------------------------------------- + E-------F + \ \ + G---H---I---J + \ + L--M +----------------------------------------------------------------------- + The '\--simplify-by-decoration' option allows you to view only the big picture of the topology of the history, by omitting commits that are not referenced by tags. Commits are marked as !TREESAME diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt new file mode 100644 index 0000000000..fe846f043c --- /dev/null +++ b/Documentation/revisions.txt @@ -0,0 +1,199 @@ +SPECIFYING REVISIONS +-------------------- + +A revision parameter typically, but not necessarily, names a +commit object. They use what is called an 'extended SHA1' +syntax. Here are various ways to spell object names. The +ones listed near the end of this list are to name trees and +blobs contained in a commit. + +* The full SHA1 object name (40-byte hexadecimal string), or + a substring of such that is unique within the repository. + E.g. dae86e1950b1277e545cee180551750029cfe735 and dae86e both + name the same commit object if there are no other object in + your repository whose object name starts with dae86e. + +* An output from 'git describe'; i.e. a closest tag, optionally + followed by a dash and a number of commits, followed by a dash, a + `g`, and an abbreviated object name. + +* A symbolic ref name. E.g. 'master' typically means the commit + object referenced by refs/heads/master. If you + happen to have both heads/master and tags/master, you can + explicitly say 'heads/master' to tell git which one you mean. + When ambiguous, a `<name>` is disambiguated by taking the + first match in the following rules: + + . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually + useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` and `MERGE_HEAD`); + + . otherwise, `refs/<name>` if exists; + + . otherwise, `refs/tags/<name>` if exists; + + . otherwise, `refs/heads/<name>` if exists; + + . otherwise, `refs/remotes/<name>` if exists; + + . otherwise, `refs/remotes/<name>/HEAD` if exists. ++ +HEAD names the commit your changes in the working tree is based on. +FETCH_HEAD records the branch you fetched from a remote repository +with your last 'git fetch' invocation. +ORIG_HEAD is created by commands that moves your HEAD in a drastic +way, to record the position of the HEAD before their operation, so that +you can change the tip of the branch back to the state before you ran +them easily. +MERGE_HEAD records the commit(s) you are merging into your branch +when you run 'git merge'. ++ +Note that any of the `refs/*` cases above may come either from +the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file. + +* A ref followed by the suffix '@' with a date specification + enclosed in a brace + pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1 + second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value + of the ref at a prior point in time. This suffix may only be + used immediately following a ref name and the ref must have an + existing log ($GIT_DIR/logs/<ref>). Note that this looks up the state + of your *local* ref at a given time; e.g., what was in your local + `master` branch last week. If you want to look at commits made during + certain times, see `--since` and `--until`. + +* A ref followed by the suffix '@' with an ordinal specification + enclosed in a brace pair (e.g. '\{1\}', '\{15\}') to specify + the n-th prior value of that ref. For example 'master@\{1\}' + is the immediate prior value of 'master' while 'master@\{5\}' + is the 5th prior value of 'master'. This suffix may only be used + immediately following a ref name and the ref must have an existing + log ($GIT_DIR/logs/<ref>). + +* You can use the '@' construct with an empty ref part to get at a + reflog of the current branch. For example, if you are on the + branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'. + +* The special construct '@\{-<n>\}' means the <n>th branch checked out + before the current one. + +* The suffix '@\{upstream\}' to a ref (short form 'ref@\{u\}') refers to + the branch the ref is set to build on top of. Missing ref defaults + to the current branch. + +* A suffix '{caret}' to a revision parameter (e.g. 'HEAD{caret}') means the first parent of + that commit object. '{caret}<n>' means the <n>th parent (i.e. + 'rev{caret}' + is equivalent to 'rev{caret}1'). As a special rule, + 'rev{caret}0' means the commit itself and is used when 'rev' is the + object name of a tag object that refers to a commit object. + +* A suffix '{tilde}<n>' to a revision parameter means the commit + object that is the <n>th generation grand-parent of the named + commit object, following only the first parent. I.e. rev~3 is + equivalent to rev{caret}{caret}{caret} which is equivalent to + rev{caret}1{caret}1{caret}1. See below for a illustration of + the usage of this form. + +* A suffix '{caret}' followed by an object type name enclosed in + brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object + could be a tag, and dereference the tag recursively until an + object of that type is found or the object cannot be + dereferenced anymore (in which case, barf). `rev{caret}0` + introduced earlier is a short-hand for `rev{caret}\{commit\}`. + +* A suffix '{caret}' followed by an empty brace pair + (e.g. `v0.99.8{caret}\{\}`) means the object could be a tag, + and dereference the tag recursively until a non-tag object is + found. + +* A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names + a commit whose commit message starts with the specified text. + This name returns the youngest matching commit which is + reachable from any ref. If the commit message starts with a + '!', you have to repeat that; the special sequence ':/!', + followed by something else than '!' is reserved for now. + +* A suffix ':' followed by a path (e.g. `HEAD:README`); this names the blob or tree + at the given path in the tree-ish object named by the part + before the colon. + ':path' (with an empty part before the colon, e.g. `:README`) + is a special case of the syntax described next: content + recorded in the index at the given path. + +* A colon, optionally followed by a stage number (0 to 3) and a + colon, followed by a path (e.g. `:0:README`); this names a blob object in the + index at the given path. Missing stage number (and the colon + that follows it, e.g. `:README`) names a stage 0 entry. During a merge, stage + 1 is the common ancestor, stage 2 is the target branch's version + (typically the current branch), and stage 3 is the version from + the branch being merged. + +Here is an illustration, by Jon Loeliger. Both commit nodes B +and C are parents of commit node A. Parent commits are ordered +left-to-right. + +........................................ +G H I J + \ / \ / + D E F + \ | / \ + \ | / | + \|/ | + B C + \ / + \ / + A +........................................ + + A = = A^0 + B = A^ = A^1 = A~1 + C = A^2 = A^2 + D = A^^ = A^1^1 = A~2 + E = B^2 = A^^2 + F = B^3 = A^^3 + G = A^^^ = A^1^1^1 = A~3 + H = D^2 = B^^2 = A^^^2 = A~2^2 + I = F^ = B^3^ = A^^3^ + J = F^2 = B^3^2 = A^^3^2 + + +SPECIFYING RANGES +----------------- + +History traversing commands such as 'git log' operate on a set +of commits, not just a single commit. To these commands, +specifying a single revision with the notation described in the +previous section means the set of commits reachable from that +commit, following the commit ancestry chain. + +To exclude commits reachable from a commit, a prefix `{caret}` +notation is used. E.g. `{caret}r1 r2` means commits reachable +from `r2` but exclude the ones reachable from `r1`. + +This set operation appears so often that there is a shorthand +for it. When you have two commits `r1` and `r2` (named according +to the syntax explained in SPECIFYING REVISIONS above), you can ask +for commits that are reachable from r2 excluding those that are reachable +from r1 by `{caret}r1 r2` and it can be written as `r1..r2`. + +A similar notation `r1\...r2` is called symmetric difference +of `r1` and `r2` and is defined as +`r1 r2 --not $(git merge-base --all r1 r2)`. +It is the set of commits that are reachable from either one of +`r1` or `r2` but not from both. + +Two other shorthands for naming a set that is formed by a commit +and its parent commits exist. The `r1{caret}@` notation means all +parents of `r1`. `r1{caret}!` includes commit `r1` but excludes +all of its parents. + +Here are a handful of examples: + + D G H D + D F G H I J D F + ^G D H D + ^D B E I J F B + B...C G H D E B C + ^D B C E I J F B C + C^@ I J F + F^! D G H D F diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 50f9e9ac17..312e3b2e2b 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -115,6 +115,9 @@ There are some macros to easily define options: `OPT__ABBREV(&int_var)`:: Add `\--abbrev[=<n>]`. +`OPT__COLOR(&int_var, description)`:: + Add `\--color[=<when>]` and `--no-color`. + `OPT__DRY_RUN(&int_var)`:: Add `-n, \--dry-run`. @@ -183,6 +186,15 @@ There are some macros to easily define options: arguments. Short options that happen to be digits take precedence over it. +`OPT_COLOR_FLAG(short, long, &int_var, description)`:: + Introduce an option that takes an optional argument that can + have one of three values: "always", "never", or "auto". If the + argument is not given, it defaults to "always". The `--no-` form + works like `--long=never`; it cannot take an argument. If + "always", set `int_var` to 1; if "never", set `int_var` to 0; if + "auto", set `int_var` to 1 if stdout is a tty or a pager, + 0 otherwise. + The last element of the array must be `OPT_END()`. diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index 44876fa703..f18b4f4817 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -231,8 +231,9 @@ The function pointer in .proc has the following signature: There are serious restrictions on what the asynchronous function can do -because this facility is implemented by a pipe to a forked process on -UNIX, but by a thread in the same address space on Windows: +because this facility is implemented by a thread in the same address +space on most platforms (when pthreads is available), but by a pipe to +a forked process otherwise: . It cannot change the program's state (global variables, environment, etc.) in a way that the caller notices; in other words, .in and .out diff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt index 293bb15d20..3f575bdcff 100644 --- a/Documentation/technical/api-string-list.txt +++ b/Documentation/technical/api-string-list.txt @@ -38,8 +38,8 @@ struct string_list list; int i; memset(&list, 0, sizeof(struct string_list)); -string_list_append("foo", &list); -string_list_append("bar", &list); +string_list_append(&list, "foo"); +string_list_append(&list, "bar"); for (i = 0; i < list.nr; i++) printf("%s\n", list.items[i].string) ---- @@ -104,8 +104,12 @@ write `string_list_insert(...)->util = ...;`. `unsorted_string_list_has_string`:: It's like `string_list_has_string()` but for unsorted lists. + +`unsorted_string_list_lookup`:: + + It's like `string_list_lookup()` but for unsorted lists. + -This function needs to look through all items, as opposed to its +The above two functions need to look through all items, as opposed to their counterpart for sorted lists, which performs a binary search. Data structures diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt index fd1a593149..b15517fa06 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/technical/protocol-capabilities.txt @@ -119,7 +119,7 @@ both. ofs-delta --------- -Server can send, and client understand PACKv2 with delta refering to +Server can send, and client understand PACKv2 with delta referring to its base by position in pack rather than by an obj-id. That is, they can send/read OBJ_OFS_DELTA (aka type 6) in a packfile. diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 459a394dc0..1dcd1e7f1e 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -1,44 +1,57 @@ GIT URLS[[URLS]] ---------------- -One of the following notations can be used -to name the remote repository: +In general, URLs contain information about the transport protocol, the +address of the remote server, and the path to the repository. +Depending on the transport protocol, some of this information may be +absent. + +Git natively supports ssh, git, http, https, ftp, ftps, and rsync +protocols. The following syntaxes may be used with them: -- rsync://host.xz/path/to/repo.git/ -- http://host.xz{startsb}:port{endsb}/path/to/repo.git/ -- https://host.xz{startsb}:port{endsb}/path/to/repo.git/ -- git://host.xz{startsb}:port{endsb}/path/to/repo.git/ -- git://host.xz{startsb}:port{endsb}/~user/path/to/repo.git/ - ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/path/to/repo.git/ -- ssh://{startsb}user@{endsb}host.xz/path/to/repo.git/ -- ssh://{startsb}user@{endsb}host.xz/~user/path/to/repo.git/ -- ssh://{startsb}user@{endsb}host.xz/~/path/to/repo.git +- git://host.xz{startsb}:port{endsb}/path/to/repo.git/ +- http{startsb}s{endsb}://host.xz{startsb}:port{endsb}/path/to/repo.git/ +- ftp{startsb}s{endsb}://host.xz{startsb}:port{endsb}/path/to/repo.git/ +- rsync://host.xz/path/to/repo.git/ -SSH is the default transport protocol over the network. You can -optionally specify which user to log-in as, and an alternate, -scp-like syntax is also supported. Both syntaxes support -username expansion, as does the native git protocol, but -only the former supports port specification. The following -three are identical to the last three above, respectively: +An alternative scp-like syntax may also be used with the ssh protocol: -- {startsb}user@{endsb}host.xz:/path/to/repo.git/ -- {startsb}user@{endsb}host.xz:~user/path/to/repo.git/ -- {startsb}user@{endsb}host.xz:path/to/repo.git +- {startsb}user@{endsb}host.xz:path/to/repo.git/ -To sync with a local directory, you can use: +The ssh and git protocols additionally support ~username expansion: + +- ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/ +- git://host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/ +- {startsb}user@{endsb}host.xz:/~{startsb}user{endsb}/path/to/repo.git/ + +For local respositories, also supported by git natively, the following +syntaxes may be used: - /path/to/repo.git/ - file:///path/to/repo.git/ ifndef::git-clone[] -They are mostly equivalent, except when cloning. See -linkgit:git-clone[1] for details. +These two syntaxes are mostly equivalent, except when cloning, when +the former implies --local option. See linkgit:git-clone[1] for +details. endif::git-clone[] ifdef::git-clone[] -They are equivalent, except the former implies --local option. +These two syntaxes are mostly equivalent, except the former implies +--local option. endif::git-clone[] +When git doesn't know how to handle a certain transport protocol, it +attempts to use the 'remote-<transport>' remote helper, if one +exists. To explicitly request a remote helper, the following syntax +may be used: + +- <transport>::<address> + +where <address> may be a path, a server and path, or an arbitrary +URL-like string recognized by the specific remote helper being +invoked. See linkgit:git-remote-helpers[1] for details. If there are a large number of similarly-named remote repositories and you want to use a different format for them (such that the URLs you diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index fe6fb722da..22aee34d4a 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -397,7 +397,7 @@ is usually a shortcut for the HEAD branch in the repository "origin". For the complete list of paths which git checks for references, and the order it uses to decide which to choose when there are multiple references with the same shorthand name, see the "SPECIFYING -REVISIONS" section of linkgit:git-rev-parse[1]. +REVISIONS" section of linkgit:gitrevisions[1]. [[Updating-a-repository-With-git-fetch]] Updating a repository with git fetch @@ -568,7 +568,7 @@ We have seen several ways of naming commits already: - HEAD: refers to the head of the current branch There are many more; see the "SPECIFYING REVISIONS" section of the -linkgit:git-rev-parse[1] man page for the complete list of ways to +linkgit:gitrevisions[1] man page for the complete list of ways to name revisions. Some examples: ------------------------------------------------- @@ -909,7 +909,7 @@ commits reachable from some head but not from any tag in the repository: $ gitk $( git show-ref --heads ) --not $( git show-ref --tags ) ------------------------------------------------- -(See linkgit:git-rev-parse[1] for explanations of commit-selecting +(See linkgit:gitrevisions[1] for explanations of commit-selecting syntax such as `--not`.) [[making-a-release]] @@ -1635,7 +1635,7 @@ you've checked out. The reflogs are kept by default for 30 days, after which they may be pruned. See linkgit:git-reflog[1] and linkgit:git-gc[1] to learn how to control this pruning, and see the "SPECIFYING REVISIONS" -section of linkgit:git-rev-parse[1] for details. +section of linkgit:gitrevisions[1] for details. Note that the reflog history is very different from normal git history. While normal history is shared by every repository that works on the diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 6a3f945636..f6d301a10f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.0.7 +DEF_VER=v1.7.2.GIT LF=' ' @@ -12,7 +12,7 @@ if test -f version then VN=$(cat version) || VN="$DEF_VER" elif test -d .git -o -f .git && - VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && + VN=$(git describe --match "v[0-9]*" --abbrev=4 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) @@ -157,3 +157,36 @@ Issues of note: 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 the patch in contrib/patches/docbook-xsl-manpages-charmap.patch + + Users attempting to build the documentation on Cygwin may need to ensure + that the /etc/xml/catalog file looks something like this: + + <?xml version="1.0"?> + <!DOCTYPE catalog PUBLIC + "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" + "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd" + > + <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"> + <rewriteURI + uriStartString = "http://docbook.sourceforge.net/release/xsl/current" + rewritePrefix = "/usr/share/sgml/docbook/xsl-stylesheets" + /> + <rewriteURI + uriStartString="http://www.oasis-open.org/docbook/xml/4.5" + rewritePrefix="/usr/share/sgml/docbook/xml-dtd-4.5" + /> + </catalog> + + This can be achieved with the following two xmlcatalog commands: + + xmlcatalog --noout \ + --add rewriteURI \ + http://docbook.sourceforge.net/release/xsl/current \ + /usr/share/sgml/docbook/xsl-stylesheets \ + /etc/xml/catalog + + xmlcatalog --noout \ + --add rewriteURI \ + http://www.oasis-open.org/docbook/xml/4.5/xsl/current \ + /usr/share/sgml/docbook/xml-dtd-4.5 \ + /etc/xml/catalog @@ -8,6 +8,12 @@ all:: # Define SANE_TOOL_PATH to a colon-separated list of paths to prepend # to PATH if your tools in /usr/bin are broken. # +# Define SOCKLEN_T to a suitable type (such as 'size_t') if your +# system headers do not define a socklen_t type. +# +# Define INLINE to a suitable substitute (such as '__inline' or '') if git +# fails to compile with errors about undefined inline functions or similar. +# # Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf() # or vsnprintf() return -1 instead of number of characters which would # have been written to the final string if enough space had been available. @@ -31,10 +37,13 @@ all:: # Define EXPATDIR=/foo/bar if your expat header and library files are in # /foo/bar/include and /foo/bar/lib directories. # +# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH +# it specifies. +# # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks -# d_type in struct dirent (latest Cygwin -- will be fixed soonish). +# d_type in struct dirent (Cygwin 1.5, fixed in Cygwin 1.7). # # Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) # do not support the 'size specifiers' introduced by C99, namely ll, hh, @@ -109,7 +118,7 @@ all:: # Define NO_PTHREADS if you do not have or do not want to use Pthreads. # # Define NO_PREAD if you have a problem with pread() system call (e.g. -# cygwin.dll before v1.5.22). +# cygwin1.dll before v1.5.22). # # Define NO_FAST_WORKING_DIRECTORY if accessing objects in pack files is # generally faster on your platform than accessing the working directory. @@ -203,6 +212,9 @@ all:: # Define JSMIN to point to JavaScript minifier that functions as # a filter to have gitweb.js minified. # +# Define CSSMIN to point to a CSS minifier in order to generate a minified +# version of gitweb.css +# # Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if # you want to use something different. The value will be interpreted by the # shell at runtime when it is used. @@ -214,6 +226,15 @@ all:: # DEFAULT_EDITOR='~/bin/vi', # 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 CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded +# dependency rules. +# +# Define NATIVE_CRLF if your platform uses CRLF for line endings. GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -236,7 +257,7 @@ endif CFLAGS = -g -O2 -Wall LDFLAGS = -ALL_CFLAGS = $(CFLAGS) +ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) STRIP ?= strip @@ -259,6 +280,7 @@ mandir = share/man infodir = share/info gitexecdir = libexec/git-core sharedir = $(prefix)/share +gitwebdir = $(sharedir)/gitweb template_dir = share/git-core/templates htmldir = share/doc/git-doc ifeq ($(prefix),/usr) @@ -272,14 +294,12 @@ lib = lib # DESTDIR= pathsep = : -# JavaScript minifier invocation that can function as filter -JSMIN = - -export prefix bindir sharedir sysconfdir +export prefix bindir sharedir sysconfdir gitwebdir CC = gcc AR = ar RM = rm -f +DIFF = diff TAR = tar FIND = find INSTALL = install @@ -287,6 +307,7 @@ RPMBUILD = rpmbuild TCL_PATH = tclsh TCLTK_PATH = wish PTHREAD_LIBS = -lpthread +PTHREAD_CFLAGS = export TCL_PATH TCLTK_PATH @@ -301,7 +322,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ # Those must not be GNU-specific; they are shared with perl/ which may # be built by a different compiler. (Note that this is an artifact now # but it still might be nice to keep that distinction.) -BASIC_CFLAGS = +BASIC_CFLAGS = -I. BASIC_LDFLAGS = # Guard against environment variables @@ -309,13 +330,16 @@ BUILTIN_OBJS = BUILT_INS = COMPAT_CFLAGS = COMPAT_OBJS = +EXTRA_CPPFLAGS = LIB_H = LIB_OBJS = +PROGRAM_OBJS = PROGRAMS = SCRIPT_PERL = SCRIPT_PYTHON = SCRIPT_SH = -TEST_PROGRAMS = +SCRIPT_LIB = +TEST_PROGRAMS_NEED_X = # Having this variable in your environment would break pipelines because # you cause "cd" to echo its destination to stdout. It can also take @@ -332,20 +356,20 @@ SCRIPT_SH += git-merge-octopus.sh SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh -SCRIPT_SH += git-mergetool--lib.sh -SCRIPT_SH += git-notes.sh -SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-rebase--interactive.sh SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-repack.sh SCRIPT_SH += git-request-pull.sh -SCRIPT_SH += git-sh-setup.sh SCRIPT_SH += git-stash.sh SCRIPT_SH += git-submodule.sh SCRIPT_SH += git-web--browse.sh +SCRIPT_LIB += git-mergetool--lib +SCRIPT_LIB += git-parse-remote +SCRIPT_LIB += git-sh-setup + SCRIPT_PERL += git-add--interactive.perl SCRIPT_PERL += git-difftool.perl SCRIPT_PERL += git-archimport.perl @@ -356,6 +380,8 @@ SCRIPT_PERL += git-relink.perl SCRIPT_PERL += git-send-email.perl SCRIPT_PERL += git-svn.perl +SCRIPT_PYTHON += git-remote-testgit.py + SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ @@ -366,16 +392,35 @@ EXTRA_PROGRAMS = # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS += $(EXTRA_PROGRAMS) -PROGRAMS += git-fast-import$X -PROGRAMS += git-imap-send$X -PROGRAMS += git-shell$X -PROGRAMS += git-show-index$X -PROGRAMS += git-upload-pack$X -PROGRAMS += git-http-backend$X + +PROGRAM_OBJS += fast-import.o +PROGRAM_OBJS += imap-send.o +PROGRAM_OBJS += shell.o +PROGRAM_OBJS += show-index.o +PROGRAM_OBJS += upload-pack.o +PROGRAM_OBJS += http-backend.o + +PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS)) + +TEST_PROGRAMS_NEED_X += test-chmtime +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-genrandom +TEST_PROGRAMS_NEED_X += test-match-trees +TEST_PROGRAMS_NEED_X += test-parse-options +TEST_PROGRAMS_NEED_X += test-path-utils +TEST_PROGRAMS_NEED_X += test-run-command +TEST_PROGRAMS_NEED_X += test-sha1 +TEST_PROGRAMS_NEED_X += test-sigchain +TEST_PROGRAMS_NEED_X += test-index-version + +TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) # List built-in command $C whose implementation cmd_$C() is not in -# builtin-$C.o but is linked in as part of some other command. -BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) +# builtin/$C.o but is linked in as part of some other command. +BUILT_INS += $(patsubst builtin/%.o,git-%$X,$(BUILTIN_OBJS)) BUILT_INS += git-cherry$X BUILT_INS += git-cherry-pick$X @@ -431,6 +476,7 @@ LIB_H += blob.h LIB_H += builtin.h LIB_H += cache.h LIB_H += cache-tree.h +LIB_H += color.h LIB_H += commit.h LIB_H += compat/bswap.h LIB_H += compat/cygwin.h @@ -442,6 +488,7 @@ LIB_H += delta.h LIB_H += diffcore.h LIB_H += diff.h LIB_H += dir.h +LIB_H += exec_cmd.h LIB_H += fsck.h LIB_H += git-compat-util.h LIB_H += graph.h @@ -455,6 +502,7 @@ LIB_H += log-tree.h LIB_H += mailmap.h LIB_H += merge-recursive.h LIB_H += notes.h +LIB_H += notes-cache.h LIB_H += object.h LIB_H += pack.h LIB_H += pack-refs.h @@ -484,7 +532,8 @@ LIB_H += tree-walk.h LIB_H += unpack-trees.h LIB_H += userdiff.h LIB_H += utf8.h -LIB_H += wt-status.h +LIB_H += xdiff-interface.h +LIB_H += xdiff/xdiff.h LIB_OBJS += abspath.o LIB_OBJS += advice.o @@ -543,6 +592,7 @@ LIB_OBJS += merge-file.o LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o LIB_OBJS += notes.o +LIB_OBJS += notes-cache.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-refs.o @@ -588,6 +638,7 @@ LIB_OBJS += tree-diff.o LIB_OBJS += tree.o LIB_OBJS += tree-walk.o LIB_OBJS += unpack-trees.o +LIB_OBJS += url.o LIB_OBJS += usage.o LIB_OBJS += userdiff.o LIB_OBJS += utf8.o @@ -598,95 +649,96 @@ LIB_OBJS += ws.o LIB_OBJS += wt-status.o LIB_OBJS += xdiff-interface.o -BUILTIN_OBJS += builtin-add.o -BUILTIN_OBJS += builtin-annotate.o -BUILTIN_OBJS += builtin-apply.o -BUILTIN_OBJS += builtin-archive.o -BUILTIN_OBJS += builtin-bisect--helper.o -BUILTIN_OBJS += builtin-blame.o -BUILTIN_OBJS += builtin-branch.o -BUILTIN_OBJS += builtin-bundle.o -BUILTIN_OBJS += builtin-cat-file.o -BUILTIN_OBJS += builtin-check-attr.o -BUILTIN_OBJS += builtin-check-ref-format.o -BUILTIN_OBJS += builtin-checkout-index.o -BUILTIN_OBJS += builtin-checkout.o -BUILTIN_OBJS += builtin-clean.o -BUILTIN_OBJS += builtin-clone.o -BUILTIN_OBJS += builtin-commit-tree.o -BUILTIN_OBJS += builtin-commit.o -BUILTIN_OBJS += builtin-config.o -BUILTIN_OBJS += builtin-count-objects.o -BUILTIN_OBJS += builtin-describe.o -BUILTIN_OBJS += builtin-diff-files.o -BUILTIN_OBJS += builtin-diff-index.o -BUILTIN_OBJS += builtin-diff-tree.o -BUILTIN_OBJS += builtin-diff.o -BUILTIN_OBJS += builtin-fast-export.o -BUILTIN_OBJS += builtin-fetch-pack.o -BUILTIN_OBJS += builtin-fetch.o -BUILTIN_OBJS += builtin-fmt-merge-msg.o -BUILTIN_OBJS += builtin-for-each-ref.o -BUILTIN_OBJS += builtin-fsck.o -BUILTIN_OBJS += builtin-gc.o -BUILTIN_OBJS += builtin-grep.o -BUILTIN_OBJS += builtin-hash-object.o -BUILTIN_OBJS += builtin-help.o -BUILTIN_OBJS += builtin-index-pack.o -BUILTIN_OBJS += builtin-init-db.o -BUILTIN_OBJS += builtin-log.o -BUILTIN_OBJS += builtin-ls-files.o -BUILTIN_OBJS += builtin-ls-remote.o -BUILTIN_OBJS += builtin-ls-tree.o -BUILTIN_OBJS += builtin-mailinfo.o -BUILTIN_OBJS += builtin-mailsplit.o -BUILTIN_OBJS += builtin-merge.o -BUILTIN_OBJS += builtin-merge-base.o -BUILTIN_OBJS += builtin-merge-file.o -BUILTIN_OBJS += builtin-merge-index.o -BUILTIN_OBJS += builtin-merge-ours.o -BUILTIN_OBJS += builtin-merge-recursive.o -BUILTIN_OBJS += builtin-merge-tree.o -BUILTIN_OBJS += builtin-mktag.o -BUILTIN_OBJS += builtin-mktree.o -BUILTIN_OBJS += builtin-mv.o -BUILTIN_OBJS += builtin-name-rev.o -BUILTIN_OBJS += builtin-pack-objects.o -BUILTIN_OBJS += builtin-pack-redundant.o -BUILTIN_OBJS += builtin-pack-refs.o -BUILTIN_OBJS += builtin-patch-id.o -BUILTIN_OBJS += builtin-prune-packed.o -BUILTIN_OBJS += builtin-prune.o -BUILTIN_OBJS += builtin-push.o -BUILTIN_OBJS += builtin-read-tree.o -BUILTIN_OBJS += builtin-receive-pack.o -BUILTIN_OBJS += builtin-reflog.o -BUILTIN_OBJS += builtin-remote.o -BUILTIN_OBJS += builtin-replace.o -BUILTIN_OBJS += builtin-rerere.o -BUILTIN_OBJS += builtin-reset.o -BUILTIN_OBJS += builtin-rev-list.o -BUILTIN_OBJS += builtin-rev-parse.o -BUILTIN_OBJS += builtin-revert.o -BUILTIN_OBJS += builtin-rm.o -BUILTIN_OBJS += builtin-send-pack.o -BUILTIN_OBJS += builtin-shortlog.o -BUILTIN_OBJS += builtin-show-branch.o -BUILTIN_OBJS += builtin-show-ref.o -BUILTIN_OBJS += builtin-stripspace.o -BUILTIN_OBJS += builtin-symbolic-ref.o -BUILTIN_OBJS += builtin-tag.o -BUILTIN_OBJS += builtin-tar-tree.o -BUILTIN_OBJS += builtin-unpack-file.o -BUILTIN_OBJS += builtin-unpack-objects.o -BUILTIN_OBJS += builtin-update-index.o -BUILTIN_OBJS += builtin-update-ref.o -BUILTIN_OBJS += builtin-update-server-info.o -BUILTIN_OBJS += builtin-upload-archive.o -BUILTIN_OBJS += builtin-var.o -BUILTIN_OBJS += builtin-verify-pack.o -BUILTIN_OBJS += builtin-verify-tag.o -BUILTIN_OBJS += builtin-write-tree.o +BUILTIN_OBJS += builtin/add.o +BUILTIN_OBJS += builtin/annotate.o +BUILTIN_OBJS += builtin/apply.o +BUILTIN_OBJS += builtin/archive.o +BUILTIN_OBJS += builtin/bisect--helper.o +BUILTIN_OBJS += builtin/blame.o +BUILTIN_OBJS += builtin/branch.o +BUILTIN_OBJS += builtin/bundle.o +BUILTIN_OBJS += builtin/cat-file.o +BUILTIN_OBJS += builtin/check-attr.o +BUILTIN_OBJS += builtin/check-ref-format.o +BUILTIN_OBJS += builtin/checkout-index.o +BUILTIN_OBJS += builtin/checkout.o +BUILTIN_OBJS += builtin/clean.o +BUILTIN_OBJS += builtin/clone.o +BUILTIN_OBJS += builtin/commit-tree.o +BUILTIN_OBJS += builtin/commit.o +BUILTIN_OBJS += builtin/config.o +BUILTIN_OBJS += builtin/count-objects.o +BUILTIN_OBJS += builtin/describe.o +BUILTIN_OBJS += builtin/diff-files.o +BUILTIN_OBJS += builtin/diff-index.o +BUILTIN_OBJS += builtin/diff-tree.o +BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/fast-export.o +BUILTIN_OBJS += builtin/fetch-pack.o +BUILTIN_OBJS += builtin/fetch.o +BUILTIN_OBJS += builtin/fmt-merge-msg.o +BUILTIN_OBJS += builtin/for-each-ref.o +BUILTIN_OBJS += builtin/fsck.o +BUILTIN_OBJS += builtin/gc.o +BUILTIN_OBJS += builtin/grep.o +BUILTIN_OBJS += builtin/hash-object.o +BUILTIN_OBJS += builtin/help.o +BUILTIN_OBJS += builtin/index-pack.o +BUILTIN_OBJS += builtin/init-db.o +BUILTIN_OBJS += builtin/log.o +BUILTIN_OBJS += builtin/ls-files.o +BUILTIN_OBJS += builtin/ls-remote.o +BUILTIN_OBJS += builtin/ls-tree.o +BUILTIN_OBJS += builtin/mailinfo.o +BUILTIN_OBJS += builtin/mailsplit.o +BUILTIN_OBJS += builtin/merge.o +BUILTIN_OBJS += builtin/merge-base.o +BUILTIN_OBJS += builtin/merge-file.o +BUILTIN_OBJS += builtin/merge-index.o +BUILTIN_OBJS += builtin/merge-ours.o +BUILTIN_OBJS += builtin/merge-recursive.o +BUILTIN_OBJS += builtin/merge-tree.o +BUILTIN_OBJS += builtin/mktag.o +BUILTIN_OBJS += builtin/mktree.o +BUILTIN_OBJS += builtin/mv.o +BUILTIN_OBJS += builtin/name-rev.o +BUILTIN_OBJS += builtin/notes.o +BUILTIN_OBJS += builtin/pack-objects.o +BUILTIN_OBJS += builtin/pack-redundant.o +BUILTIN_OBJS += builtin/pack-refs.o +BUILTIN_OBJS += builtin/patch-id.o +BUILTIN_OBJS += builtin/prune-packed.o +BUILTIN_OBJS += builtin/prune.o +BUILTIN_OBJS += builtin/push.o +BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/receive-pack.o +BUILTIN_OBJS += builtin/reflog.o +BUILTIN_OBJS += builtin/remote.o +BUILTIN_OBJS += builtin/replace.o +BUILTIN_OBJS += builtin/rerere.o +BUILTIN_OBJS += builtin/reset.o +BUILTIN_OBJS += builtin/rev-list.o +BUILTIN_OBJS += builtin/rev-parse.o +BUILTIN_OBJS += builtin/revert.o +BUILTIN_OBJS += builtin/rm.o +BUILTIN_OBJS += builtin/send-pack.o +BUILTIN_OBJS += builtin/shortlog.o +BUILTIN_OBJS += builtin/show-branch.o +BUILTIN_OBJS += builtin/show-ref.o +BUILTIN_OBJS += builtin/stripspace.o +BUILTIN_OBJS += builtin/symbolic-ref.o +BUILTIN_OBJS += builtin/tag.o +BUILTIN_OBJS += builtin/tar-tree.o +BUILTIN_OBJS += builtin/unpack-file.o +BUILTIN_OBJS += builtin/unpack-objects.o +BUILTIN_OBJS += builtin/update-index.o +BUILTIN_OBJS += builtin/update-ref.o +BUILTIN_OBJS += builtin/update-server-info.o +BUILTIN_OBJS += builtin/upload-archive.o +BUILTIN_OBJS += builtin/var.o +BUILTIN_OBJS += builtin/verify-pack.o +BUILTIN_OBJS += builtin/verify-tag.o +BUILTIN_OBJS += builtin/write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) EXTLIBS = @@ -699,13 +751,22 @@ EXTLIBS = # because maintaining the nesting to match is a pain. If # we had "elif" things would have been much nicer... +ifeq ($(uname_S),OSF1) + # Need this for u_short definitions et al + BASIC_CFLAGS += -D_OSF_SOURCE + SOCKLEN_T = int + NO_STRTOULL = YesPlease + NO_NSEC = YesPlease +endif ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),UnixWare) CC = cc @@ -771,6 +832,18 @@ ifeq ($(uname_S),SunOS) NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease + ifeq ($(uname_R),5.6) + SOCKLEN_T = int + NO_HSTRERROR = YesPlease + NO_IPV6 = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRLCPY = YesPlease + NO_C99_FORMAT = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif ifeq ($(uname_R),5.7) NEEDS_RESOLV = YesPlease NO_IPV6 = YesPlease @@ -780,40 +853,45 @@ ifeq ($(uname_S),SunOS) NO_STRLCPY = YesPlease NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp endif ifeq ($(uname_R),5.8) NO_UNSETENV = YesPlease NO_SETENV = YesPlease NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp endif ifeq ($(uname_R),5.9) NO_UNSETENV = YesPlease NO_SETENV = YesPlease NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp endif INSTALL = /usr/ucb/install TAR = gtar BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H endif ifeq ($(uname_O),Cygwin) - NO_D_TYPE_IN_DIRENT = YesPlease - NO_D_INO_IN_DIRENT = YesPlease - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_SYMLINK_HEAD = YesPlease + ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4) + NO_D_TYPE_IN_DIRENT = YesPlease + NO_D_INO_IN_DIRENT = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_SYMLINK_HEAD = YesPlease + NO_IPV6 = YesPlease + OLD_ICONV = UnfortunatelyYes + endif NEEDS_LIBICONV = YesPlease NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes NO_TRUSTABLE_FILEMODE = UnfortunatelyYes - OLD_ICONV = UnfortunatelyYes NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease # There are conflicting reports about this. # On some boxes NO_MMAP is needed, and not so elsewhere. # Try commenting this out if you suspect MMAP is more efficient NO_MMAP = YesPlease - NO_IPV6 = YesPlease X = .exe COMPAT_OBJS += compat/cygwin.o UNRELIABLE_FSTAT = UnfortunatelyYes @@ -832,6 +910,7 @@ ifeq ($(uname_S),FreeBSD) NO_STRTOUMAX = YesPlease endif PYTHON_PATH = /usr/local/bin/python + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease @@ -840,6 +919,7 @@ ifeq ($(uname_S),OpenBSD) NEEDS_LIBICONV = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) @@ -849,8 +929,10 @@ ifeq ($(uname_S),NetBSD) BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib USE_ST_TIMESPEC = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),AIX) + DEFAULT_PAGER = more NO_STRCASESTR=YesPlease NO_MEMMEM = YesPlease NO_MKDTEMP = YesPlease @@ -863,12 +945,19 @@ ifeq ($(uname_S),AIX) BASIC_CFLAGS += -D_LARGE_FILES ifeq ($(shell expr "$(uname_V)" : '[1234]'),1) NO_PTHREADS = YesPlease + else + PTHREAD_LIBS = -lpthread + endif + ifeq ($(shell expr "$(uname_V).$(uname_R)" : '5\.1'),3) + INLINE='' endif + GIT_TEST_CMP = cmp endif ifeq ($(uname_S),GNU) # GNU/Hurd NO_STRLCPY=YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),IRIX) NO_SETENV = YesPlease @@ -887,7 +976,6 @@ ifeq ($(uname_S),IRIX) SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH = /usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease - NEEDS_LIBICONV = YesPlease endif ifeq ($(uname_S),IRIX64) NO_SETENV=YesPlease @@ -906,9 +994,9 @@ ifeq ($(uname_S),IRIX64) SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH=/usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease - NEEDS_LIBICONV = YesPlease endif ifeq ($(uname_S),HP-UX) + INLINE = __inline NO_IPV6=YesPlease NO_SETENV=YesPlease NO_STRCASESTR=YesPlease @@ -920,6 +1008,20 @@ ifeq ($(uname_S),HP-UX) NO_HSTRERROR = YesPlease NO_SYS_SELECT_H = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease + NO_NSEC = YesPlease + ifeq ($(uname_R),B.11.00) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif + ifeq ($(uname_R),B.10.20) + # Override HP-UX 11.x setting: + INLINE = + SOCKLEN_T = size_t + NO_PREAD = YesPlease + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif + GIT_TEST_CMP = cmp endif ifeq ($(uname_S),Windows) GIT_VERSION := $(GIT_VERSION).MSVC @@ -956,6 +1058,7 @@ ifeq ($(uname_S),Windows) NO_CURL = YesPlease NO_PYTHON = YesPlease BLK_SHA1 = YesPlease + NATIVE_CRLF = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl @@ -993,7 +1096,6 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_STRTOUMAX = YesPlease NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease NO_SVN_TESTS = YesPlease NO_PERL_MAKEMAKER = YesPlease RUNTIME_PREFIX = YesPlease @@ -1029,6 +1131,15 @@ endif -include config.mak.autogen -include config.mak +ifdef CHECK_HEADER_DEPENDENCIES +COMPUTE_HEADER_DEPENDENCIES = +USE_COMPUTED_HEADER_DEPENDENCIES = +endif + +ifdef COMPUTE_HEADER_DEPENDENCIES +USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease +endif + ifdef SANE_TOOL_PATH SANE_TOOL_PATH_SQ = $(subst ','\'',$(SANE_TOOL_PATH)) BROKEN_PATH_FIX = 's|^\# @@BROKEN_PATH_FIX@@$$|git_broken_path_fix $(SANE_TOOL_PATH_SQ)|' @@ -1037,6 +1148,14 @@ else BROKEN_PATH_FIX = '/^\# @@BROKEN_PATH_FIX@@$$/d' endif +ifneq (,$(INLINE)) + BASIC_CFLAGS += -Dinline=$(INLINE) +endif + +ifneq (,$(SOCKLEN_T)) + BASIC_CFLAGS += -Dsocklen_t=$(SOCKLEN_T) +endif + ifeq ($(uname_S),Darwin) ifndef NO_FINK ifeq ($(shell test -d /sw/lib && echo y),y) @@ -1084,11 +1203,12 @@ else REMOTE_CURL_PRIMARY = git-remote-http$X REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES) - PROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X + PROGRAM_OBJS += http-fetch.o + PROGRAMS += $(REMOTE_CURL_NAMES) curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" ifndef NO_EXPAT - PROGRAMS += git-http-push$X + PROGRAM_OBJS += http-push.o endif endif ifndef NO_EXPAT @@ -1108,7 +1228,7 @@ endif EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS - PROGRAMS += git-daemon$X + PROGRAM_OBJS += daemon.o endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl @@ -1274,10 +1394,12 @@ endif ifdef BLK_SHA1 SHA1_HEADER = "block-sha1/sha1.h" LIB_OBJS += block-sha1/sha1.o + LIB_H += block-sha1/sha1.h else ifdef PPC_SHA1 SHA1_HEADER = "ppc/sha1.h" LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o + LIB_H += ppc/sha1.h else SHA1_HEADER = <openssl/sha.h> EXTLIBS += $(LIB_4_CRYPTO) @@ -1305,10 +1427,15 @@ endif ifdef NO_PTHREADS BASIC_CFLAGS += -DNO_PTHREADS else + BASIC_CFLAGS += $(PTHREAD_CFLAGS) EXTLIBS += $(PTHREAD_LIBS) LIB_OBJS += thread-utils.o endif +ifdef HAVE_PATHS_H + BASIC_CFLAGS += -DHAVE_PATHS_H +endif + ifdef DIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS endif @@ -1325,6 +1452,10 @@ ifdef USE_NED_ALLOCATOR COMPAT_OBJS += compat/nedmalloc/nedmalloc.o endif +ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT + export GIT_TEST_CMP_USE_COPIED_CONTEXT +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks endif @@ -1381,11 +1512,13 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) template_dir_SQ = $(subst ','\'',$(template_dir)) htmldir_SQ = $(subst ','\'',$(htmldir)) prefix_SQ = $(subst ','\'',$(prefix)) +gitwebdir_SQ = $(subst ','\'',$(gitwebdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) +DIFF_SQ = $(subst ','\'',$(DIFF)) LIBS = $(GITLIBS) $(EXTLIBS) @@ -1412,14 +1545,14 @@ endif ALL_CFLAGS += $(BASIC_CFLAGS) ALL_LDFLAGS += $(BASIC_LDFLAGS) -export TAR INSTALL DESTDIR SHELL_PATH +export DIFF TAR INSTALL DESTDIR SHELL_PATH ### Build rules SHELL = $(SHELL_PATH) -all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS +all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) endif @@ -1435,7 +1568,7 @@ endif ifndef NO_PYTHON $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all endif - $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) + $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)' please_set_SHELL_PATH_to_a_more_modern_shell: @$$(:) @@ -1446,15 +1579,15 @@ strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X git.o: common-cmds.h -git.s git.o: ALL_CFLAGS += -DGIT_VERSION='"$(GIT_VERSION)"' \ +git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -builtin-help.o: common-cmds.h -builtin-help.s builtin-help.o: ALL_CFLAGS += \ +builtin/help.o: common-cmds.h +builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' @@ -1470,17 +1603,26 @@ common-cmds.h: ./generate-cmdlist.sh command-list.txt common-cmds.h: $(wildcard Documentation/git-*.txt) $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@ +define cmd_munge_script +$(RM) $@ $@+ && \ +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/@@NO_CURL@@/$(NO_CURL)/g' \ + -e $(BROKEN_PATH_FIX) \ + $@.sh >$@+ +endef + $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh - $(QUIET_GEN)$(RM) $@ $@+ && \ - sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ - -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ - -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e $(BROKEN_PATH_FIX) \ - $@.sh >$@+ && \ + $(QUIET_GEN)$(cmd_munge_script) && \ chmod +x $@+ && \ mv $@+ $@ +$(SCRIPT_LIB) : % : %.sh + $(QUIET_GEN)$(cmd_munge_script) && \ + mv $@+ $@ + ifndef NO_PERL $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak @@ -1493,11 +1635,10 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl sed -e '1{' \ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e ' h' \ - -e ' s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \ + -e ' s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "'"$$INSTLIBDIR"'"));=' \ -e ' H' \ -e ' x' \ -e '}' \ - -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ $@.perl >$@+ && \ chmod +x $@+ && \ @@ -1509,31 +1650,37 @@ gitweb: $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all ifdef JSMIN -OTHER_PROGRAMS += gitweb/gitweb.cgi gitweb/gitweb.min.js -gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js +GITWEB_PROGRAMS += gitweb/static/gitweb.min.js +GITWEB_JS = gitweb/static/gitweb.min.js else -OTHER_PROGRAMS += gitweb/gitweb.cgi -gitweb/gitweb.cgi: gitweb/gitweb.perl +GITWEB_JS = gitweb/static/gitweb.js endif +ifdef CSSMIN +GITWEB_PROGRAMS += gitweb/static/gitweb.min.css +GITWEB_CSS = gitweb/static/gitweb.min.css +else +GITWEB_CSS = gitweb/static/gitweb.css +endif +OTHER_PROGRAMS += gitweb/gitweb.cgi $(GITWEB_PROGRAMS) +gitweb/gitweb.cgi: gitweb/gitweb.perl $(GITWEB_PROGRAMS) $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) ifdef JSMIN -gitweb/gitweb.min.js: gitweb/gitweb.js +gitweb/static/gitweb.min.js: gitweb/static/gitweb.js $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) endif # JSMIN +ifdef CSSMIN +gitweb/static/gitweb.min.css: gitweb/static/gitweb.css + $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) +endif # CSSMIN -git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js +git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/static/gitweb.css gitweb/static/gitweb.js $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \ - -e '/@@GITWEB_CGI@@/d' \ - -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ - -e '/@@GITWEB_CSS@@/d' \ - -e '/@@GITWEB_JS@@/r gitweb/gitweb.js' \ - -e '/@@GITWEB_JS@@/d' \ + -e 's|@@GITWEBDIR@@|$(gitwebdir_SQ)|g' \ -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ $@.sh > $@+ && \ chmod +x $@+ && \ @@ -1555,13 +1702,8 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \ --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \ instlibdir` && \ - sed -e '1{' \ - -e ' s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ - -e '}' \ - -e 's|^import sys.*|&; \\\ - import os; \\\ - sys.path.insert(0, os.getenv("GITPYTHONLIB",\ - "@@INSTLIBDIR@@"));|' \ + sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ + -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \ -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ $@.py >$@+ && \ chmod +x $@+ && \ @@ -1589,28 +1731,152 @@ git.o git.spec \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ : GIT-VERSION-FILE -%.o: %.c GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< +TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS)) +GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \ + git.o +ifndef NO_CURL + GIT_OBJS += http.o http-walker.o remote-curl.o +endif +XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ + xdiff/xmerge.o xdiff/xpatience.o +OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) + +dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) +dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) + +ifdef COMPUTE_HEADER_DEPENDENCIES +$(dep_dirs): + mkdir -p $@ + +missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs)) +dep_file = $(dir $@).depend/$(notdir $@).d +dep_args = -MF $(dep_file) -MMD -MP +ifdef CHECK_HEADER_DEPENDENCIES +$(error cannot compute header dependencies outside a normal build. \ +Please unset CHECK_HEADER_DEPENDENCIES and try again) +endif +endif + +ifndef COMPUTE_HEADER_DEPENDENCIES +ifndef CHECK_HEADER_DEPENDENCIES +dep_dirs = +missing_dep_dirs = +dep_args = +endif +endif + +ifdef CHECK_HEADER_DEPENDENCIES +ifndef PRINT_HEADER_DEPENDENCIES +missing_deps = $(filter-out $(notdir $^), \ + $(notdir $(shell $(MAKE) -s $@ \ + CHECK_HEADER_DEPENDENCIES=YesPlease \ + USE_COMPUTED_HEADER_DEPENDENCIES=YesPlease \ + PRINT_HEADER_DEPENDENCIES=YesPlease))) +endif +endif + +ASM_SRC := $(wildcard $(OBJECTS:o=S)) +ASM_OBJ := $(ASM_SRC:S=o) +C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS)) + +.SUFFIXES: + +ifdef PRINT_HEADER_DEPENDENCIES +$(C_OBJ): %.o: %.c FORCE + echo $^ +$(ASM_OBJ): %.o: %.S FORCE + echo $^ + +ifndef CHECK_HEADER_DEPENDENCIES +$(error cannot print header dependencies during a normal build. \ +Please set CHECK_HEADER_DEPENDENCIES and try again) +endif +endif + +ifndef PRINT_HEADER_DEPENDENCIES +ifdef CHECK_HEADER_DEPENDENCIES +$(C_OBJ): %.o: %.c $(dep_files) FORCE + @set -e; echo CHECK $@; \ + missing_deps="$(missing_deps)"; \ + if test "$$missing_deps"; \ + then \ + echo missing dependencies: $$missing_deps; \ + false; \ + fi +$(ASM_OBJ): %.o: %.S $(dep_files) FORCE + @set -e; echo CHECK $@; \ + missing_deps="$(missing_deps)"; \ + if test "$$missing_deps"; \ + then \ + echo missing dependencies: $$missing_deps; \ + false; \ + fi +endif +endif + +ifndef CHECK_HEADER_DEPENDENCIES +$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< +$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< +endif + %.s: %.c GIT-CFLAGS FORCE - $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $< -%.o: %.S GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< + $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< -exec_cmd.s exec_cmd.o: ALL_CFLAGS += \ +ifdef USE_COMPUTED_HEADER_DEPENDENCIES +# Take advantage of gcc's on-the-fly dependency generation +# See <http://gcc.gnu.org/gcc-3.0/features.html>. +dep_files_present := $(wildcard $(dep_files)) +ifneq ($(dep_files_present),) +include $(dep_files_present) +endif +else +# Dependencies on header files, for platforms that do not support +# the gcc -MMD option. +# +# Dependencies on automatically generated headers such as common-cmds.h +# should _not_ be included here, since they are necessary even when +# building an object for the first time. +# +# XXX. Please check occasionally that these include all dependencies +# gcc detects! + +$(GIT_OBJS): $(LIB_H) +builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o transport.o: branch.h +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: 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 +builtin/pack-objects.o: thread-utils.h +connect.o transport.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 + +xdiff-interface.o $(XDIFF_OBJS): \ + xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ + xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h +endif + +exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ '-DBINDIR="$(bindir_relative_SQ)"' \ '-DPREFIX="$(prefix_SQ)"' -builtin-init-db.s builtin-init-db.o: ALL_CFLAGS += \ +builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \ -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' -config.s config.o: ALL_CFLAGS += -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' +config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' -http.s http.o: ALL_CFLAGS += -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' +http.s http.o: EXTRA_CPPFLAGS = -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' ifdef NO_EXPAT -http-walker.o: http.h -http-walker.s http-walker.o: ALL_CFLAGS += -DNO_EXPAT +http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT endif git-%$X: %.o $(GITLIBS) @@ -1620,10 +1886,6 @@ git-imap-send$X: imap-send.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) -http.o http-walker.o http-push.o: http.h - -http.o http-walker.o: $(LIB_H) - git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) @@ -1641,18 +1903,9 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) -$(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h) -builtin-revert.o wt-status.o: wt-status.h - $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) -XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ - xdiff/xmerge.o xdiff/xpatience.o -$(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ - xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h - $(XDIFF_LIB): $(XDIFF_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS) @@ -1701,10 +1954,18 @@ GIT-CFLAGS: FORCE GIT-BUILD-OPTIONS: FORCE @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@ @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@ + @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@ + @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@ @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@ +ifdef GIT_TEST_CMP + @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@ +endif +ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT + @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@ +endif ### Detect Tck/Tk interpreter path changes ifndef NO_TCLTK @@ -1718,24 +1979,6 @@ GIT-GUI-VARS: FORCE fi endif -### Testing rules - -TEST_PROGRAMS_NEED_X += test-chmtime -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-genrandom -TEST_PROGRAMS_NEED_X += test-match-trees -TEST_PROGRAMS_NEED_X += test-parse-options -TEST_PROGRAMS_NEED_X += test-path-utils -TEST_PROGRAMS_NEED_X += test-run-command -TEST_PROGRAMS_NEED_X += test-sha1 -TEST_PROGRAMS_NEED_X += test-sigchain -TEST_PROGRAMS_NEED_X += test-index-version - -TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) - test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X)) all:: $(TEST_PROGRAMS) $(test_bindir_programs) @@ -1753,6 +1996,8 @@ bin-wrappers/%: wrap-for-bin.sh export NO_SVN_TESTS +### Testing rules + test: all $(MAKE) -C t/ all @@ -1764,9 +2009,7 @@ test-delta$X: diff-delta.o patch-delta.o test-parse-options$X: parse-options.o -test-parse-options.o: parse-options.h - -.PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS)) +.PRECIOUS: $(TEST_OBJS) test-%$X: test-%.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) @@ -1812,10 +2055,12 @@ install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' + $(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install + $(MAKE) -C gitweb install endif ifndef NO_PYTHON $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install @@ -1831,25 +2076,37 @@ endif bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \ execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \ { test "$$bindir/" = "$$execdir/" || \ - { $(RM) "$$execdir/git$X" && \ + for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \ + $(RM) "$$execdir/$$p" && \ test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \ - ln "$$bindir/git$X" "$$execdir/git$X" 2>/dev/null || \ - cp "$$bindir/git$X" "$$execdir/git$X"; } ; } && \ - { for p in $(BUILT_INS); do \ + ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \ + cp "$$bindir/$$p" "$$execdir/$$p" || exit; \ + done; \ + } && \ + for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \ + $(RM) "$$bindir/$$p" && \ + ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \ + ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \ + cp "$$bindir/git$X" "$$bindir/$$p" || exit; \ + done && \ + for p in $(BUILT_INS); do \ $(RM) "$$execdir/$$p" && \ ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \ ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \ cp "$$execdir/git$X" "$$execdir/$$p" || exit; \ - done; } && \ - { test x"$(REMOTE_CURL_ALIASES)" = x || \ - { for p in $(REMOTE_CURL_ALIASES); do \ + done && \ + remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \ + for p in $$remote_curl_aliases; do \ $(RM) "$$execdir/$$p" && \ ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \ ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \ cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \ - done; } ; } && \ + done && \ ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X" +install-gitweb: + $(MAKE) -C gitweb install + install-doc: $(MAKE) -C Documentation install @@ -1931,10 +2188,11 @@ distclean: clean clean: $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ - $(LIB_FILE) $(XDIFF_LIB) - $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X + builtin/*.o $(LIB_FILE) $(XDIFF_LIB) + $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers + $(RM) -r $(dep_dirs) $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache @@ -1943,7 +2201,7 @@ clean: $(RM) $(htmldocs).tar.gz $(manpages).tar.gz $(MAKE) -C Documentation/ clean ifndef NO_PERL - $(RM) gitweb/gitweb.cgi + $(MAKE) -C gitweb clean $(MAKE) -C perl clean endif ifndef NO_PYTHON @@ -1964,12 +2222,13 @@ endif ### Check documentation # check-docs:: - @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \ + @(for v in $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk; \ do \ case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ git-merge-resolve | git-merge-subtree | \ git-fsck-objects | git-init-db | \ + git-remote-* | git-stage | \ git-?*--?* ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ @@ -2005,11 +2264,15 @@ check-docs:: documented,gitglossary | \ documented,githooks | \ documented,gitrepository-layout | \ + documented,gitrevisions | \ documented,gittutorial | \ documented,gittutorial-2 | \ + documented,git-bisect-lk2009 | \ + documented,git-remote-helpers | \ + documented,gitworkflows | \ sentinel,not,matching,is,ok ) continue ;; \ esac; \ - case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \ + case " $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk " in \ *" $$cmd "*) ;; \ *) echo "removed but $$how: $$cmd" ;; \ esac; \ @@ -1 +1 @@ -Documentation/RelNotes-1.7.0.7.txt
\ No newline at end of file +Documentation/RelNotes-1.7.2.1.txt
\ No newline at end of file @@ -54,8 +54,9 @@ const char *make_absolute_path(const char *path) if (len + strlen(last_elem) + 2 > PATH_MAX) die ("Too long path name: '%s/%s'", buf, last_elem); - buf[len] = '/'; - strcpy(buf + len + 1, last_elem); + if (len && buf[len-1] != '/') + buf[len++] = '/'; + strcpy(buf + len, last_elem); free(last_elem); last_elem = NULL; } diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000000..d399de2671 --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,40 @@ +dnl Check for socklen_t: historically on BSD it is an int, and in +dnl POSIX 1g it is a type of its own, but some platforms use different +dnl types for the argument to getsockopt, getpeername, etc. So we +dnl have to test to find something that will work. +AC_DEFUN([TYPE_SOCKLEN_T], +[ + AC_CHECK_TYPE([socklen_t], ,[ + AC_MSG_CHECKING([for socklen_t equivalent]) + AC_CACHE_VAL([git_cv_socklen_t_equiv], + [ + # Systems have either "struct sockaddr *" or + # "void *" as the second argument to getpeername + git_cv_socklen_t_equiv= + for arg2 in "struct sockaddr" void; do + for t in int size_t unsigned long "unsigned long"; do + AC_TRY_COMPILE([ + #include <sys/types.h> + #include <sys/socket.h> + + int getpeername (int, $arg2 *, $t *); + ],[ + $t len; + getpeername(0,0,&len); + ],[ + git_cv_socklen_t_equiv="$t" + break 2 + ]) + done + done + + if test "x$git_cv_socklen_t_equiv" = x; then + AC_MSG_ERROR([Cannot find a type to use in place of socklen_t]) + fi + ]) + AC_MSG_RESULT($git_cv_socklen_t_equiv) + AC_DEFINE_UNQUOTED(socklen_t, $git_cv_socklen_t_equiv, + [type to use in place of socklen_t if not defined])], + [#include <sys/types.h> +#include <sys/socket.h>]) +]) @@ -287,7 +287,7 @@ static void free_attr_elem(struct attr_stack *e) } static const char *builtin_attr[] = { - "[attr]binary -diff -crlf", + "[attr]binary -diff -text", NULL, }; @@ -594,20 +594,25 @@ static int path_matches(const char *pathname, int pathlen, return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0; } +static int macroexpand_one(int attr_nr, int rem); + static int fill_one(const char *what, struct match_attr *a, int rem) { struct git_attr_check *check = check_all_attr; int i; - for (i = 0; 0 < rem && i < a->num_attr; i++) { + for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) { struct git_attr *attr = a->state[i].attr; const char **n = &(check[attr->attr_nr].value); const char *v = a->state[i].setto; if (*n == ATTR__UNKNOWN) { - debug_set(what, a->u.pattern, attr, v); + debug_set(what, + a->is_macro ? a->u.attr->name : a->u.pattern, + attr, v); *n = v; rem--; + rem = macroexpand_one(attr->attr_nr, rem); } } return rem; @@ -629,19 +634,27 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem) return rem; } -static int macroexpand(struct attr_stack *stk, int rem) +static int macroexpand_one(int attr_nr, int rem) { + struct attr_stack *stk; + struct match_attr *a = NULL; int i; - struct git_attr_check *check = check_all_attr; - for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) { - struct match_attr *a = stk->attrs[i]; - if (!a->is_macro) - continue; - if (check[a->u.attr->attr_nr].value != ATTR__TRUE) - continue; + if (check_all_attr[attr_nr].value != ATTR__TRUE) + return rem; + + for (stk = attr_stack; !a && stk; stk = stk->prev) + for (i = stk->num_matches - 1; !a && 0 <= i; i--) { + struct match_attr *ma = stk->attrs[i]; + if (!ma->is_macro) + continue; + if (ma->u.attr->attr_nr == attr_nr) + a = ma; + } + + if (a) rem = fill_one("expand", a, rem); - } + return rem; } @@ -666,9 +679,6 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) rem = fill(path, pathlen, stk, rem); - for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) - rem = macroexpand(stk, rem); - for (i = 0; i < num; i++) { const char *value = check_all_attr[check[i].attr->attr_nr].value; if (value == ATTR__UNKNOWN) @@ -34,7 +34,7 @@ int git_checkattr(const char *path, int, struct git_attr_check *); enum git_attr_direction { GIT_ATTR_CHECKIN, GIT_ATTR_CHECKOUT, - GIT_ATTR_INDEX, + GIT_ATTR_INDEX }; void git_attr_set_direction(enum git_attr_direction, struct index_state *); @@ -7,9 +7,9 @@ #define say1(a,b) fprintf(stderr, a, b) #define say2(a,b,c) fprintf(stderr, a, b, c) #else -#define say(a) do {} while(0) -#define say1(a,b) do {} while(0) -#define say2(a,b,c) do {} while(0) +#define say(a) do { /* nothing */ } while (0) +#define say1(a,b) do { /* nothing */ } while (0) +#define say2(a,b,c) do { /* nothing */ } while (0) #endif static const char en85[] = { diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c index d8934757a5..c0054a0b0a 100644 --- a/block-sha1/sha1.c +++ b/block-sha1/sha1.c @@ -70,6 +70,7 @@ */ #if defined(__i386__) || defined(__x86_64__) || \ + defined(_M_IX86) || defined(_M_X64) || \ defined(__ppc__) || defined(__ppc64__) || \ defined(__powerpc__) || defined(__powerpc64__) || \ defined(__s390__) || defined(__s390x__) @@ -236,13 +237,13 @@ void blk_SHA1_Init(blk_SHA_CTX *ctx) void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) { - int lenW = ctx->size & 63; + unsigned int lenW = ctx->size & 63; ctx->size += len; /* Read the data into W and process blocks as they get full */ if (lenW) { - int left = 64 - lenW; + unsigned int left = 64 - lenW; if (len < left) left = len; memcpy(lenW + (char *)ctx->W, data, left); @@ -269,8 +270,8 @@ void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx) int i; /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ - padlen[0] = htonl(ctx->size >> 29); - padlen[1] = htonl(ctx->size << 3); + padlen[0] = htonl((uint32_t)(ctx->size >> 29)); + padlen[1] = htonl((uint32_t)(ctx->size << 3)); i = ctx->size & 63; blk_SHA1_Update(ctx, pad, 1+ (63 & (55 - i))); diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c deleted file mode 100644 index 90dac349a3..0000000000 --- a/builtin-commit-tree.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "commit.h" -#include "tree.h" -#include "builtin.h" -#include "utf8.h" - -/* - * FIXME! Share the code with "write-tree.c" - */ -static void check_valid(unsigned char *sha1, enum object_type expect) -{ - enum object_type type = sha1_object_info(sha1, NULL); - if (type < 0) - die("%s is not a valid object", sha1_to_hex(sha1)); - if (type != expect) - die("%s is not a valid '%s' object", sha1_to_hex(sha1), - typename(expect)); -} - -static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog"; - -static void new_parent(struct commit *parent, struct commit_list **parents_p) -{ - unsigned char *sha1 = parent->object.sha1; - struct commit_list *parents; - for (parents = *parents_p; parents; parents = parents->next) { - if (parents->item == parent) { - error("duplicate parent %s ignored", sha1_to_hex(sha1)); - return; - } - parents_p = &parents->next; - } - commit_list_insert(parent, parents_p); -} - -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 result; - int encoding_is_utf8; - struct strbuf buffer; - - check_valid(tree, OBJ_TREE); - - /* Not having i18n.commitencoding is the same as having utf-8 */ - encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); - - strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ - strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree)); - - /* - * NOTE! This ordering means that the same exact tree merged with a - * different order of parents will be a _different_ changeset even - * if everything else stays the same. - */ - while (parents) { - struct commit_list *next = parents->next; - strbuf_addf(&buffer, "parent %s\n", - sha1_to_hex(parents->item->object.sha1)); - free(parents); - parents = next; - } - - /* Person/date information */ - if (!author) - author = git_author_info(IDENT_ERROR_ON_NO_NAME); - strbuf_addf(&buffer, "author %s\n", author); - 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); - strbuf_addch(&buffer, '\n'); - - /* And add the comment */ - strbuf_addstr(&buffer, msg); - - /* And check the encoding */ - if (encoding_is_utf8 && !is_utf8(buffer.buf)) - fprintf(stderr, commit_utf8_warn); - - result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret); - strbuf_release(&buffer); - return result; -} - -int cmd_commit_tree(int argc, const char **argv, const char *prefix) -{ - int i; - struct commit_list *parents = NULL; - unsigned char tree_sha1[20]; - unsigned char commit_sha1[20]; - struct strbuf buffer = STRBUF_INIT; - - git_config(git_default_config, NULL); - - 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); - - if (get_sha1(b, sha1)) - die("Not a valid object name %s", b); - check_valid(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); - } - - 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)) { - printf("%s\n", sha1_to_hex(commit_sha1)); - return 0; - } - else - return 1; -} diff --git a/builtin-patch-id.c b/builtin-patch-id.c deleted file mode 100644 index af0911e4bd..0000000000 --- a/builtin-patch-id.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "cache.h" -#include "exec_cmd.h" - -static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) -{ - unsigned char result[20]; - char name[50]; - - if (!patchlen) - return; - - git_SHA1_Final(result, c); - memcpy(name, sha1_to_hex(id), 41); - printf("%s %s\n", sha1_to_hex(result), name); - git_SHA1_Init(c); -} - -static int remove_space(char *line) -{ - char *src = line; - char *dst = line; - unsigned char c; - - while ((c = *src++) != '\0') { - if (!isspace(c)) - *dst++ = c; - } - return dst - line; -} - -static void generate_id_list(void) -{ - static unsigned char sha1[20]; - static char line[1000]; - git_SHA_CTX ctx; - int patchlen = 0; - - git_SHA1_Init(&ctx); - while (fgets(line, sizeof(line), stdin) != NULL) { - unsigned char n[20]; - char *p = line; - int len; - - if (!memcmp(line, "diff-tree ", 10)) - p += 10; - else if (!memcmp(line, "commit ", 7)) - p += 7; - - if (!get_sha1_hex(p, n)) { - flush_current_id(patchlen, sha1, &ctx); - hashcpy(sha1, n); - patchlen = 0; - continue; - } - - /* Ignore commit comments */ - if (!patchlen && memcmp(line, "diff ", 5)) - continue; - - /* Ignore git-diff index header */ - if (!memcmp(line, "index ", 6)) - continue; - - /* Ignore line numbers when computing the SHA1 of the patch */ - if (!memcmp(line, "@@ -", 4)) - continue; - - /* Compute the sha without whitespace */ - len = remove_space(line); - patchlen += len; - git_SHA1_Update(&ctx, line, len); - } - flush_current_id(patchlen, sha1, &ctx); -} - -static const char patch_id_usage[] = "git patch-id < patch"; - -int cmd_patch_id(int argc, const char **argv, const char *prefix) -{ - if (argc != 1) - usage(patch_id_usage); - - generate_id_list(); - return 0; -} @@ -5,6 +5,7 @@ #include "strbuf.h" #include "cache.h" #include "commit.h" +#include "notes.h" extern const char git_version_string[]; extern const char git_usage_string[]; @@ -15,11 +16,29 @@ extern const char *help_unknown_cmd(const char *cmd); extern void prune_packed_objects(int); extern int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out); -extern int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author); +extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out); +extern int commit_notes(struct notes_tree *t, const char *msg); + +struct notes_rewrite_cfg { + struct notes_tree **trees; + const char *cmd; + int enabled; + combine_notes_fn combine; + struct string_list *refs; + int refs_from_env; + int mode_from_env; +}; + +combine_notes_fn parse_combine_notes_fn(const char *v); +struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd); +int copy_note_for_rewrite(struct notes_rewrite_cfg *c, + const unsigned char *from_obj, const unsigned char *to_obj); +void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c); + extern int check_pager_config(const char *cmd); +extern int textconv_object(const char *path, const unsigned char *sha1, char **buf, unsigned long *buf_size); + extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); @@ -78,6 +97,7 @@ extern int cmd_mktag(int argc, const char **argv, const char *prefix); extern int cmd_mktree(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); +extern int cmd_notes(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix); extern int cmd_patch_id(int argc, const char **argv, const char *prefix); diff --git a/builtin-add.c b/builtin/add.c index 87d2980313..56a4e0af6b 100644 --- a/builtin-add.c +++ b/builtin/add.c @@ -261,12 +261,14 @@ static int edit_patch(int argc, const char **argv, const char *prefix) { char *file = xstrdup(git_path("ADD_EDIT.patch")); const char *apply_argv[] = { "apply", "--recount", "--cached", - file, NULL }; + NULL, NULL }; struct child_process child; struct rev_info rev; int out; struct stat st; + apply_argv[3] = file; + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ if (read_cache() < 0) @@ -308,7 +310,7 @@ static const char ignore_error[] = "The following paths are ignored by one of your .gitignore files:\n"; static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; -static int ignore_add_errors, addremove, intent_to_add; +static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0; static struct option builtin_add_options[] = { OPT__DRY_RUN(&show_only), @@ -323,6 +325,7 @@ static struct option builtin_add_options[] = { OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"), OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"), + OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"), OPT_END(), }; @@ -383,6 +386,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (addremove && take_worktree_changes) die("-A and -u are mutually incompatible"); + if (!show_only && ignore_missing) + die("Option --ignore-missing can only be used together with --dry-run"); if ((addremove || take_worktree_changes) && !argc) { static const char *here[2] = { ".", NULL }; argc = 1; @@ -439,9 +444,14 @@ int cmd_add(int argc, const char **argv, const char *prefix) seen = find_used_pathspec(pathspec); for (i = 0; pathspec[i]; i++) { if (!seen[i] && pathspec[i][0] - && !file_exists(pathspec[i])) - die("pathspec '%s' did not match any files", - pathspec[i]); + && !file_exists(pathspec[i])) { + if (ignore_missing) { + if (excluded(&dir, pathspec[i], DT_UNKNOWN)) + dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); + } else + die("pathspec '%s' did not match any files", + pathspec[i]); + } } free(seen); } diff --git a/builtin-annotate.c b/builtin/annotate.c index fc43eed36b..fc43eed36b 100644 --- a/builtin-annotate.c +++ b/builtin/annotate.c diff --git a/builtin-apply.c b/builtin/apply.c index 771c972c55..12ef9ea8af 100644 --- a/builtin-apply.c +++ b/builtin/apply.c @@ -56,7 +56,7 @@ static enum ws_error_action { nowarn_ws_error, warn_on_ws_error, die_on_ws_error, - correct_ws_error, + correct_ws_error } ws_error_action = warn_on_ws_error; static int whitespace_error; static int squelch_whitespace_errors = 5; @@ -64,7 +64,7 @@ static int applied_after_fixing_ws; static enum ws_ignore { ignore_ws_none, - ignore_ws_change, + ignore_ws_change } ws_ignore_action = ignore_ws_none; @@ -1854,6 +1854,8 @@ static int match_fragment(struct image *img, { int i; char *fixed_buf, *buf, *orig, *target; + struct strbuf fixed; + size_t fixed_len; int preimage_limit; if (preimage->nr + try_lno <= img->nr) { @@ -1864,13 +1866,13 @@ static int match_fragment(struct image *img, if (match_end && (preimage->nr + try_lno != img->nr)) return 0; } else if (ws_error_action == correct_ws_error && - (ws_rule & WS_BLANK_AT_EOF) && match_end) { + (ws_rule & WS_BLANK_AT_EOF)) { /* - * This hunk that matches at the end extends beyond - * the end of img, and we are removing blank lines - * at the end of the file. This many lines from the - * beginning of the preimage must match with img, and - * the remainder of the preimage must be blank. + * This hunk extends beyond the end of img, and we are + * removing blank lines at the end of the file. This + * many lines from the beginning of the preimage must + * match with img, and the remainder of the preimage + * must be blank. */ preimage_limit = img->nr - try_lno; } else { @@ -1977,12 +1979,12 @@ static int match_fragment(struct image *img, * use the whitespace from the preimage. */ extra_chars = preimage_end - preimage_eof; - fixed_buf = xmalloc(imgoff + extra_chars); - memcpy(fixed_buf, img->buf + try, imgoff); - memcpy(fixed_buf + imgoff, preimage_eof, extra_chars); - imgoff += extra_chars; + strbuf_init(&fixed, imgoff + extra_chars); + strbuf_add(&fixed, img->buf + try, imgoff); + strbuf_add(&fixed, preimage_eof, extra_chars); + fixed_buf = strbuf_detach(&fixed, &fixed_len); update_pre_post_images(preimage, postimage, - fixed_buf, imgoff, postlen); + fixed_buf, fixed_len, postlen); return 1; } @@ -1999,27 +2001,22 @@ static int match_fragment(struct image *img, * but in this loop we will only handle the part of the * preimage that falls within the file. */ - fixed_buf = xmalloc(preimage->len + 1); - buf = fixed_buf; + strbuf_init(&fixed, preimage->len + 1); orig = preimage->buf; target = img->buf + try; for (i = 0; i < preimage_limit; i++) { - size_t fixlen; /* length after fixing the preimage */ size_t oldlen = preimage->line[i].len; size_t tgtlen = img->line[try_lno + i].len; - size_t tgtfixlen; /* length after fixing the target line */ - char tgtfixbuf[1024], *tgtfix; + size_t fixstart = fixed.len; + struct strbuf tgtfix; int match; /* Try fixing the line in the preimage */ - fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL); + ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); /* Try fixing the line in the target */ - if (sizeof(tgtfixbuf) > tgtlen) - tgtfix = tgtfixbuf; - else - tgtfix = xmalloc(tgtlen); - tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL); + strbuf_init(&tgtfix, tgtlen); + ws_fix_copy(&tgtfix, target, tgtlen, ws_rule, NULL); /* * If they match, either the preimage was based on @@ -2031,15 +2028,15 @@ static int match_fragment(struct image *img, * so we might as well take the fix together with their * real change. */ - match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen)); + match = (tgtfix.len == fixed.len - fixstart && + !memcmp(tgtfix.buf, fixed.buf + fixstart, + fixed.len - fixstart)); - if (tgtfix != tgtfixbuf) - free(tgtfix); + strbuf_release(&tgtfix); if (!match) goto unmatch_exit; orig += oldlen; - buf += fixlen; target += tgtlen; } @@ -2051,19 +2048,18 @@ static int match_fragment(struct image *img, * false). */ for ( ; i < preimage->nr; i++) { - size_t fixlen; /* length after fixing the preimage */ + size_t fixstart = fixed.len; /* start of the fixed preimage */ size_t oldlen = preimage->line[i].len; int j; /* Try fixing the line in the preimage */ - fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL); + ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); - for (j = 0; j < fixlen; j++) - if (!isspace(buf[j])) + for (j = fixstart; j < fixed.len; j++) + if (!isspace(fixed.buf[j])) goto unmatch_exit; orig += oldlen; - buf += fixlen; } /* @@ -2071,12 +2067,13 @@ static int match_fragment(struct image *img, * has whitespace breakages unfixed, and fixing them makes the * hunk match. Update the context lines in the postimage. */ + fixed_buf = strbuf_detach(&fixed, &fixed_len); update_pre_post_images(preimage, postimage, - fixed_buf, buf - fixed_buf, 0); + fixed_buf, fixed_len, 0); return 1; unmatch_exit: - free(fixed_buf); + strbuf_release(&fixed); return 0; } @@ -2244,7 +2241,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, int match_beginning, match_end; const char *patch = frag->patch; int size = frag->size; - char *old, *new, *oldlines, *newlines; + char *old, *oldlines; + struct strbuf newlines; int new_blank_lines_at_end = 0; unsigned long leading, trailing; int pos, applied_pos; @@ -2254,16 +2252,16 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, memset(&preimage, 0, sizeof(preimage)); memset(&postimage, 0, sizeof(postimage)); oldlines = xmalloc(size); - newlines = xmalloc(size); + strbuf_init(&newlines, size); old = oldlines; - new = newlines; while (size > 0) { char first; int len = linelen(patch, size); - int plen, added; + int plen; int added_blank_line = 0; int is_blank_context = 0; + size_t start; if (!len) break; @@ -2293,7 +2291,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, /* ... followed by '\No newline'; nothing */ break; *old++ = '\n'; - *new++ = '\n'; + strbuf_addch(&newlines, '\n'); add_line_info(&preimage, "\n", 1, LINE_COMMON); add_line_info(&postimage, "\n", 1, LINE_COMMON); is_blank_context = 1; @@ -2315,18 +2313,17 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, if (first == '+' && no_add) break; + start = newlines.len; if (first != '+' || !whitespace_error || ws_error_action != correct_ws_error) { - memcpy(new, patch + 1, plen); - added = plen; + strbuf_add(&newlines, patch + 1, plen); } else { - added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws); + ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &applied_after_fixing_ws); } - add_line_info(&postimage, new, added, + add_line_info(&postimage, newlines.buf + start, newlines.len - start, (first == '+' ? 0 : LINE_COMMON)); - new += added; if (first == '+' && (ws_rule & WS_BLANK_AT_EOF) && ws_blank_line(patch + 1, plen, ws_rule)) @@ -2351,9 +2348,9 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (inaccurate_eof && old > oldlines && old[-1] == '\n' && - new > newlines && new[-1] == '\n') { + newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') { old--; - new--; + strbuf_setlen(&newlines, newlines.len - 1); } leading = frag->leading; @@ -2385,8 +2382,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, pos = frag->newpos ? (frag->newpos - 1) : 0; preimage.buf = oldlines; preimage.len = old - oldlines; - postimage.buf = newlines; - postimage.len = new - newlines; + postimage.buf = newlines.buf; + postimage.len = newlines.len; preimage.line = preimage.line_allocated; postimage.line = postimage.line_allocated; @@ -2462,7 +2459,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } free(oldlines); - free(newlines); + strbuf_release(&newlines); free(preimage.line_allocated); free(postimage.line_allocated); @@ -2631,7 +2628,7 @@ static struct patch *in_fn_table(const char *name) if (name == NULL) return NULL; - item = string_list_lookup(name, &fn_table); + item = string_list_lookup(&fn_table, name); if (item != NULL) return (struct patch *)item->util; @@ -2667,7 +2664,7 @@ static void add_to_fn_table(struct patch *patch) * file creations and copies */ if (patch->new_name != NULL) { - item = string_list_insert(patch->new_name, &fn_table); + item = string_list_insert(&fn_table, patch->new_name); item->util = patch; } @@ -2676,7 +2673,7 @@ static void add_to_fn_table(struct patch *patch) * later chunks shouldn't patch old names */ if ((patch->new_name == NULL) || (patch->is_rename)) { - item = string_list_insert(patch->old_name, &fn_table); + item = string_list_insert(&fn_table, patch->old_name); item->util = PATH_WAS_DELETED; } } @@ -2689,7 +2686,7 @@ static void prepare_fn_table(struct patch *patch) while (patch) { if ((patch->new_name == NULL) || (patch->is_rename)) { struct string_list_item *item; - item = string_list_insert(patch->old_name, &fn_table); + item = string_list_insert(&fn_table, patch->old_name); item->util = PATH_TO_BE_DELETED; } patch = patch->next; @@ -3141,11 +3138,7 @@ static void remove_file(struct patch *patch, int rmdir_empty) die("unable to remove %s from index", patch->old_name); } if (!cached) { - if (S_ISGITLINK(patch->old_mode)) { - if (rmdir(patch->old_name)) - warning("unable to remove submodule %s", - patch->old_name); - } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) { + if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) { remove_path(patch->old_name); } } @@ -3401,7 +3394,7 @@ static void add_name_limit(const char *name, int exclude) { struct string_list_item *it; - it = string_list_append(name, &limit_by_name); + it = string_list_append(&limit_by_name, name); it->util = exclude ? NULL : (void *) 1; } diff --git a/builtin-archive.c b/builtin/archive.c index 6a887f5a9d..6a887f5a9d 100644 --- a/builtin-archive.c +++ b/builtin/archive.c diff --git a/builtin-bisect--helper.c b/builtin/bisect--helper.c index 5b226399e1..5b226399e1 100644 --- a/builtin-bisect--helper.c +++ b/builtin/bisect--helper.c diff --git a/builtin-blame.c b/builtin/blame.c index fc1586350f..01e62fdeb0 100644 --- a/builtin-blame.c +++ b/builtin/blame.c @@ -20,6 +20,7 @@ #include "mailmap.h" #include "parse-options.h" #include "utf8.h" +#include "userdiff.h" static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file"; @@ -39,7 +40,7 @@ static int show_root; static int reverse; static int blank_boundary; static int incremental; -static int xdl_opts = XDF_NEED_MINIMAL; +static int xdl_opts; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; @@ -86,16 +87,50 @@ struct origin { }; /* + * Prepare diff_filespec and convert it using diff textconv API + * if the textconv driver exists. + * Return 1 if the conversion succeeds, 0 otherwise. + */ +int textconv_object(const char *path, + const unsigned char *sha1, + char **buf, + unsigned long *buf_size) +{ + struct diff_filespec *df; + struct userdiff_driver *textconv; + + df = alloc_filespec(path); + fill_filespec(df, sha1, S_IFREG | 0664); + textconv = get_textconv(df); + if (!textconv) { + free_filespec(df); + return 0; + } + + *buf_size = fill_textconv(textconv, df, buf); + free_filespec(df); + return 1; +} + +/* * Given an origin, prepare mmfile_t structure to be used by the * diff machinery */ -static void fill_origin_blob(struct origin *o, mmfile_t *file) +static void fill_origin_blob(struct diff_options *opt, + struct origin *o, mmfile_t *file) { if (!o->file.ptr) { enum object_type type; + unsigned long file_size; + num_read_blob++; - file->ptr = read_sha1_file(o->blob_sha1, &type, - (unsigned long *)(&(file->size))); + if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && + textconv_object(o->path, o->blob_sha1, &file->ptr, &file_size)) + ; + else + file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size); + file->size = file_size; + if (!file->ptr) die("Cannot read blob %s for path %s", sha1_to_hex(o->blob_sha1), @@ -282,7 +317,6 @@ static struct origin *get_origin(struct scoreboard *sb, static int fill_blob_sha1(struct origin *origin) { unsigned mode; - if (!is_null_sha1(origin->blob_sha1)) return 0; if (get_tree_entry(origin->commit->object.sha1, @@ -733,16 +767,17 @@ static int pass_blame_to_parent(struct scoreboard *sb, { int last_in_target; mmfile_t file_p, file_o; - struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 }; + struct blame_chunk_cb_data d; xpparam_t xpp; xdemitconf_t xecfg; - + memset(&d, 0, sizeof(d)); + d.sb = sb; d.target = target; d.parent = parent; last_in_target = find_last_in_target(sb, target); if (last_in_target < 0) return 1; /* nothing remains for this target */ - fill_origin_blob(parent, &file_p); - fill_origin_blob(target, &file_o); + fill_origin_blob(&sb->revs->diffopt, parent, &file_p); + fill_origin_blob(&sb->revs->diffopt, target, &file_o); num_get_patch++; memset(&xpp, 0, sizeof(xpp)); @@ -875,10 +910,11 @@ static void find_copy_in_blob(struct scoreboard *sb, const char *cp; int cnt; mmfile_t file_o; - struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 }; + struct handle_split_cb_data d; xpparam_t xpp; xdemitconf_t xecfg; - + memset(&d, 0, sizeof(d)); + d.sb = sb; d.ent = ent; d.parent = parent; d.split = split; /* * Prepare mmfile that contains only the lines in ent. */ @@ -922,7 +958,7 @@ static int find_move_in_parent(struct scoreboard *sb, if (last_in_target < 0) return 1; /* nothing remains for this target */ - fill_origin_blob(parent, &file_p); + fill_origin_blob(&sb->revs->diffopt, parent, &file_p); if (!file_p.ptr) return 0; @@ -1063,7 +1099,7 @@ static int find_copy_in_parent(struct scoreboard *sb, norigin = get_origin(sb, parent, p->one->path); hashcpy(norigin->blob_sha1, p->one->sha1); - fill_origin_blob(norigin, &file_p); + fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); if (!file_p.ptr) continue; @@ -1589,7 +1625,7 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); printf("%s%c%d %d %d\n", hex, - ent->guilty ? ' ' : '*', // purely for debugging + ent->guilty ? ' ' : '*', /* purely for debugging */ ent->s_lno + 1, ent->lno + 1, ent->num_lines); @@ -1983,6 +2019,16 @@ static int git_blame_config(const char *var, const char *value, void *cb) blame_date_mode = parse_date_format(value); return 0; } + + switch (userdiff_config(var, value)) { + case 0: + break; + case -1: + return -1; + default: + return 0; + } + return git_default_config(var, value, cb); } @@ -1990,7 +2036,9 @@ static int git_blame_config(const char *var, const char *value, void *cb) * Prepare a dummy commit that represents the work tree (or staged) item. * Note that annotating work tree item never works in the reverse. */ -static struct commit *fake_working_tree_commit(const char *path, const char *contents_from) +static struct commit *fake_working_tree_commit(struct diff_options *opt, + const char *path, + const char *contents_from) { struct commit *commit; struct origin *origin; @@ -2018,6 +2066,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con if (!contents_from || strcmp("-", contents_from)) { struct stat st; const char *read_from; + unsigned long buf_len; if (contents_from) { if (stat(contents_from, &st) < 0) @@ -2030,9 +2079,13 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con read_from = path; } mode = canon_mode(st.st_mode); + switch (st.st_mode & S_IFMT) { case S_IFREG: - if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) + if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && + textconv_object(read_from, null_sha1, &buf.buf, &buf_len)) + buf.len = buf_len; + else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) die_errno("cannot open or read '%s'", read_from); break; case S_IFLNK: @@ -2248,6 +2301,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) git_config(git_blame_config, NULL); init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; + DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); save_commit_buffer = 0; dashdash_pos = 0; @@ -2384,7 +2438,8 @@ parse_done: * or "--contents". */ setup_work_tree(); - sb.final = fake_working_tree_commit(path, contents_from); + sb.final = fake_working_tree_commit(&sb.revs->diffopt, + path, contents_from); add_pending_object(&revs, &(sb.final->object), ":"); } else if (contents_from) @@ -2411,8 +2466,14 @@ parse_done: if (fill_blob_sha1(o)) die("no such path %s in %s", path, final_commit_name); - sb.final_buf = read_sha1_file(o->blob_sha1, &type, - &sb.final_buf_size); + if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && + textconv_object(path, o->blob_sha1, (char **) &sb.final_buf, + &sb.final_buf_size)) + ; + else + sb.final_buf = read_sha1_file(o->blob_sha1, &type, + &sb.final_buf_size); + if (!sb.final_buf) die("Cannot read blob %s for path %s", sha1_to_hex(o->blob_sha1), diff --git a/builtin-branch.c b/builtin/branch.c index a28a13986d..87976f0921 100644 --- a/builtin-branch.c +++ b/builtin/branch.c @@ -43,13 +43,13 @@ enum color_branch { BRANCH_COLOR_PLAIN = 1, BRANCH_COLOR_REMOTE = 2, BRANCH_COLOR_LOCAL = 3, - BRANCH_COLOR_CURRENT = 4, + BRANCH_COLOR_CURRENT = 4 }; static enum merge_filter { NO_FILTER = 0, SHOW_NOT_MERGED, - SHOW_MERGED, + SHOW_MERGED } merge_filter; static unsigned char merge_filter_ref[20]; @@ -257,9 +257,15 @@ static char *resolve_symref(const char *src, const char *prefix) return xstrdup(dst); } +struct append_ref_cb { + struct ref_list *ref_list; + int ret; +}; + static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { - struct ref_list *ref_list = (struct ref_list*)(cb_data); + struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data); + struct ref_list *ref_list = cb->ref_list; struct ref_item *newitem; struct commit *commit; int kind, i; @@ -293,8 +299,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, commit = NULL; if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { commit = lookup_commit_reference_gently(sha1, 1); - if (!commit) - return error("branch '%s' does not point at a commit", refname); + if (!commit) { + cb->ret = error("branch '%s' does not point at a commit", refname); + return 0; + } /* Filter with with_commit if specified */ if (!is_descendant_of(commit, ref_list->with_commit)) @@ -484,9 +492,10 @@ static void show_detached(struct ref_list *ref_list) } } -static void 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) { int i; + struct append_ref_cb cb; struct ref_list ref_list; memset(&ref_list, 0, sizeof(ref_list)); @@ -496,7 +505,9 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str ref_list.with_commit = with_commit; if (merge_filter != NO_FILTER) init_revisions(&ref_list.revs, NULL); - for_each_rawref(append_ref, &ref_list); + cb.ref_list = &ref_list; + cb.ret = 0; + for_each_rawref(append_ref, &cb); if (merge_filter != NO_FILTER) { struct commit *filter; filter = lookup_commit_reference_gently(merge_filter_ref, 0); @@ -527,6 +538,11 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str } free_ref_list(&ref_list); + + if (cb.ret) + error("some refs could not be read"); + + return cb.ret; } static void rename_branch(const char *oldname, const char *newname, int force) @@ -610,7 +626,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) BRANCH_TRACK_EXPLICIT), OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", BRANCH_TRACK_OVERRIDE), - OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), + OPT__COLOR(&branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), { @@ -679,7 +695,7 @@ 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) - print_ref_list(kinds, detached, verbose, abbrev, with_commit); + 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)) diff --git a/builtin-bundle.c b/builtin/bundle.c index 2006cc5cd5..2006cc5cd5 100644 --- a/builtin-bundle.c +++ b/builtin/bundle.c diff --git a/builtin-cat-file.c b/builtin/cat-file.c index a933eaa043..76ec3fec92 100644 --- a/builtin-cat-file.c +++ b/builtin/cat-file.c @@ -9,6 +9,8 @@ #include "tree.h" #include "builtin.h" #include "parse-options.h" +#include "diff.h" +#include "userdiff.h" #define BATCH 1 #define BATCH_CHECK 2 @@ -84,10 +86,11 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) { unsigned char sha1[20]; enum object_type type; - void *buf; + char *buf; unsigned long size; + struct object_context obj_context; - if (get_sha1(obj_name, sha1)) + if (get_sha1_with_context(obj_name, sha1, &obj_context)) die("Not a valid object name %s", obj_name); buf = NULL; @@ -118,7 +121,9 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) /* custom pretty-print here */ if (type == OBJ_TREE) { - const char *ls_args[3] = {"ls-tree", obj_name, NULL}; + const char *ls_args[3] = { NULL }; + ls_args[0] = "ls-tree"; + ls_args[1] = obj_name; return cmd_ls_tree(2, ls_args, NULL); } @@ -132,6 +137,17 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) /* otherwise just spit out the data */ break; + + case 'c': + if (!obj_context.path[0]) + die("git cat-file --textconv %s: <object> must be <sha1:path>", + obj_name); + + if (!textconv_object(obj_context.path, sha1, &buf, &size)) + die("git cat-file --textconv: unable to run textconv on %s", + obj_name); + break; + case 0: buf = read_object_with_reference(sha1, exp_type, &size, NULL); break; @@ -201,11 +217,25 @@ static int batch_objects(int print_contents) } static const char * const cat_file_usage[] = { - "git cat-file (-t|-s|-e|-p|<type>) <object>", + "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>", "git cat-file (--batch|--batch-check) < <list_of_objects>", NULL }; +static int git_cat_file_config(const char *var, const char *value, void *cb) +{ + switch (userdiff_config(var, value)) { + case 0: + break; + case -1: + return -1; + default: + return 0; + } + + return git_default_config(var, value, cb); +} + int cmd_cat_file(int argc, const char **argv, const char *prefix) { int opt = 0, batch = 0; @@ -218,6 +248,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_SET_INT('e', NULL, &opt, "exit with zero when there's no error", 'e'), OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'), + OPT_SET_INT(0, "textconv", &opt, + "for blob objects, run textconv on object's content", 'c'), OPT_SET_INT(0, "batch", &batch, "show info and content of objects fed from the standard input", BATCH), @@ -227,7 +259,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_END() }; - git_config(git_default_config, NULL); + git_config(git_cat_file_config, NULL); if (argc != 3 && argc != 2) usage_with_options(cat_file_usage, options); diff --git a/builtin-check-attr.c b/builtin/check-attr.c index 3016d29caa..3016d29caa 100644 --- a/builtin-check-attr.c +++ b/builtin/check-attr.c diff --git a/builtin-check-ref-format.c b/builtin/check-ref-format.c index b106c65d80..ae3f28115a 100644 --- a/builtin-check-ref-format.c +++ b/builtin/check-ref-format.c @@ -33,28 +33,38 @@ static void collapse_slashes(char *dst, const char *src) *dst = '\0'; } +static int check_ref_format_branch(const char *arg) +{ + struct strbuf sb = STRBUF_INIT; + int nongit; + + setup_git_directory_gently(&nongit); + if (strbuf_check_branch_ref(&sb, arg)) + die("'%s' is not a valid branch name", arg); + printf("%s\n", sb.buf + 11); + 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) { if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_check_ref_format_usage); - if (argc == 3 && !strcmp(argv[1], "--branch")) { - struct strbuf sb = STRBUF_INIT; - - if (strbuf_check_branch_ref(&sb, argv[2])) - die("'%s' is not a valid branch name", argv[2]); - printf("%s\n", sb.buf + 11); - exit(0); - } - if (argc == 3 && !strcmp(argv[1], "--print")) { - char *refname = xmalloc(strlen(argv[2]) + 1); - - if (check_ref_format(argv[2])) - exit(1); - collapse_slashes(refname, argv[2]); - printf("%s\n", refname); - exit(0); - } + 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) usage(builtin_check_ref_format_usage); return !!check_ref_format(argv[1]); diff --git a/builtin-checkout-index.c b/builtin/checkout-index.c index a7a5ee10f3..a7a5ee10f3 100644 --- a/builtin-checkout-index.c +++ b/builtin/checkout-index.c diff --git a/builtin-checkout.c b/builtin/checkout.c index c5ab7835e1..1994be92c6 100644 --- a/builtin-checkout.c +++ b/builtin/checkout.c @@ -33,6 +33,7 @@ struct checkout_opts { int writeout_error; const char *new_branch; + const char *new_orphan_branch; int new_branch_log; enum branch_track track; }; @@ -128,24 +129,6 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, (stage == 2) ? "our" : "their"); } -/* NEEDSWORK: share with merge-recursive */ -static void fill_mm(const unsigned char *sha1, mmfile_t *mm) -{ - unsigned long size; - enum object_type type; - - if (!hashcmp(sha1, null_sha1)) { - mm->ptr = xstrdup(""); - mm->size = 0; - return; - } - - mm->ptr = read_sha1_file(sha1, &type, &size); - if (!mm->ptr || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); - mm->size = size; -} - static int checkout_merged(int pos, struct checkout *state) { struct cache_entry *ce = active_cache[pos]; @@ -163,11 +146,11 @@ static int checkout_merged(int pos, struct checkout *state) ce_stage(active_cache[pos+2]) != 3) return error("path '%s' does not have all 3 versions", path); - fill_mm(active_cache[pos]->sha1, &ancestor); - fill_mm(active_cache[pos+1]->sha1, &ours); - fill_mm(active_cache[pos+2]->sha1, &theirs); + read_mmblob(&ancestor, active_cache[pos]->sha1); + read_mmblob(&ours, active_cache[pos+1]->sha1); + read_mmblob(&theirs, active_cache[pos+2]->sha1); - status = ll_merge(&result_buf, path, &ancestor, + status = ll_merge(&result_buf, path, &ancestor, "base", &ours, "ours", &theirs, "theirs", 0); free(ancestor.ptr); free(ours.ptr); @@ -457,6 +440,7 @@ static int merge_working_tree(struct checkout_opts *opts, ret = reset_tree(new->commit->tree, opts, 1); if (ret) return ret; + o.ancestor = old->name; o.branch1 = new->name; o.branch2 = "local"; merge_trees(&o, new->commit->tree, work, @@ -509,8 +493,26 @@ static void update_refs_for_switch(struct checkout_opts *opts, struct strbuf msg = STRBUF_INIT; const char *old_desc; if (opts->new_branch) { - create_branch(old->name, opts->new_branch, new->name, 0, - opts->new_branch_log, opts->track); + if (opts->new_orphan_branch) { + if (opts->new_branch_log && !log_all_ref_updates) { + int temp; + char log_file[PATH_MAX]; + char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); + + temp = log_all_ref_updates; + log_all_ref_updates = 1; + if (log_ref_setup(ref_name, log_file, sizeof(log_file))) { + fprintf(stderr, "Can not do reflog for '%s'\n", + opts->new_orphan_branch); + log_all_ref_updates = temp; + return; + } + log_all_ref_updates = temp; + } + } + else + create_branch(old->name, opts->new_branch, new->name, 0, + opts->new_branch_log, opts->track); new->name = opts->new_branch; setup_branch_path(new); } @@ -532,6 +534,14 @@ static void update_refs_for_switch(struct checkout_opts *opts, opts->new_branch ? " a new" : "", new->name); } + if (old->path && old->name) { + char log_file[PATH_MAX], ref_file[PATH_MAX]; + + git_snpath(log_file, sizeof(log_file), "logs/%s", old->path); + git_snpath(ref_file, sizeof(ref_file), "%s", old->path); + if (!file_exists(ref_file) && file_exists(log_file)) + remove_path(log_file); + } } else if (strcmp(new->name, "HEAD")) { update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, REF_NODEREF, DIE_ON_ERR); @@ -626,7 +636,8 @@ static int check_tracking_name(const char *refname, const unsigned char *sha1, static const char *unique_tracking_name(const char *name) { - struct tracking_name_data cb_data = { name, NULL, 1 }; + struct tracking_name_data cb_data = { NULL, NULL, 1 }; + cb_data.name = name; for_each_ref(check_tracking_name, &cb_data); if (cb_data.unique) return cb_data.remote; @@ -650,6 +661,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), OPT_SET_INT('t', "track", &opts.track, "track", BRANCH_TRACK_EXPLICIT), + OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"), OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage", 2), OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", @@ -695,6 +707,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.new_branch = argv0 + 1; } + if (opts.new_orphan_branch) { + if (opts.new_branch) + die("--orphan and -b are mutually exclusive"); + if (opts.track > 0) + die("--orphan cannot be used with -t"); + opts.new_branch = opts.new_orphan_branch; + } + if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); diff --git a/builtin-clean.c b/builtin/clean.c index fac64e6cd3..fac64e6cd3 100644 --- a/builtin-clean.c +++ b/builtin/clean.c diff --git a/builtin-clone.c b/builtin/clone.c index 58bacbd552..efb1e6faa5 100644 --- a/builtin-clone.c +++ b/builtin/clone.c @@ -37,18 +37,17 @@ static const char * const builtin_clone_usage[] = { NULL }; -static int option_quiet, option_no_checkout, option_bare, option_mirror; +static int option_no_checkout, option_bare, option_mirror; static int option_local, option_no_hardlinks, option_shared, option_recursive; static char *option_template, *option_reference, *option_depth; static char *option_origin = NULL; static char *option_branch = NULL; static char *option_upload_pack = "git-upload-pack"; -static int option_verbose; +static int option_verbosity; static int option_progress; static struct option builtin_clone_options[] = { - OPT__QUIET(&option_quiet), - OPT__VERBOSE(&option_verbose), + OPT__VERBOSITY(&option_verbosity), OPT_BOOLEAN(0, "progress", &option_progress, "force progress reporting"), OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, @@ -303,6 +302,8 @@ static const struct ref *clone_local(const char *src_repo, transport = transport_get(remote, src_repo); ret = transport_get_remote_refs(transport); transport_disconnect(transport); + if (0 <= option_verbosity) + printf("done.\n"); return ret; } @@ -462,7 +463,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die("could not create leading directories of '%s'", git_dir); set_git_dir(make_absolute_path(git_dir)); - init_db(option_template, option_quiet ? INIT_DB_QUIET : 0); + if (0 <= option_verbosity) + printf("Cloning into %s%s...\n", + option_bare ? "bare repository " : "", dir); + init_db(option_template, INIT_DB_QUIET); /* * At this point, the config exists, so we do not need the @@ -471,9 +475,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) */ unsetenv(CONFIG_ENVIRONMENT); - if (option_reference) - setup_reference(git_dir); - git_config(git_default_config, NULL); if (option_bare) { @@ -499,12 +500,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, "true"); strbuf_reset(&key); } - - strbuf_addf(&key, "remote.%s.url", option_origin); - git_config_set(key.buf, repo); - strbuf_reset(&key); } + strbuf_addf(&key, "remote.%s.url", option_origin); + git_config_set(key.buf, repo); + strbuf_reset(&key); + + if (option_reference) + setup_reference(git_dir); + fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); @@ -514,7 +518,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) refs = clone_local(path, git_dir); mapped_refs = wanted_peer_refs(refs, refspec); } else { - struct remote *remote = remote_get(argv[0]); + struct remote *remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); if (!transport->get_refs_list || !transport->fetch) @@ -526,13 +530,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_set_option(transport, TRANS_OPT_DEPTH, option_depth); - if (option_quiet) - transport->verbose = -1; - else if (option_verbose) - transport->verbose = 1; - - if (option_progress) - transport->progress = 1; + transport_set_verbosity(transport, option_verbosity, option_progress); if (option_upload_pack) transport_set_option(transport, TRANS_OPT_UPLOADPACK, @@ -641,7 +639,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) opts.update = 1; opts.merge = 1; opts.fn = oneway_merge; - opts.verbose_update = !option_quiet; + opts.verbose_update = (option_verbosity > 0); opts.src_index = &the_index; opts.dst_index = &the_index; diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c new file mode 100644 index 0000000000..87f0591c2f --- /dev/null +++ b/builtin/commit-tree.c @@ -0,0 +1,65 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ +#include "cache.h" +#include "commit.h" +#include "tree.h" +#include "builtin.h" +#include "utf8.h" + +static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog"; + +static void new_parent(struct commit *parent, struct commit_list **parents_p) +{ + unsigned char *sha1 = parent->object.sha1; + struct commit_list *parents; + for (parents = *parents_p; parents; parents = parents->next) { + if (parents->item == parent) { + error("duplicate parent %s ignored", sha1_to_hex(sha1)); + return; + } + parents_p = &parents->next; + } + commit_list_insert(parent, parents_p); +} + +int cmd_commit_tree(int argc, const char **argv, const char *prefix) +{ + int i; + struct commit_list *parents = NULL; + unsigned char tree_sha1[20]; + unsigned char commit_sha1[20]; + struct strbuf buffer = STRBUF_INIT; + + git_config(git_default_config, NULL); + + 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); + + 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 (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)) { + printf("%s\n", sha1_to_hex(commit_sha1)); + return 0; + } + else + return 1; +} diff --git a/builtin-commit.c b/builtin/commit.c index f4c73442cf..2bb30c0e80 100644 --- a/builtin-commit.c +++ b/builtin/commit.c @@ -48,6 +48,11 @@ static const char implicit_ident_advice[] = "\n" " git commit --amend --author='Your Name <you@example.com>'\n"; +static const char empty_amend_advice[] = +"You asked to amend the most recent commit, but doing so would make\n" +"it empty. You can repeat your command with --allow-empty, or you can\n" +"remove the commit entirely with \"git reset HEAD^\".\n"; + static unsigned char head_sha1[20]; static char *use_message_buffer; @@ -57,7 +62,7 @@ static struct lock_file false_lock; /* used only for partial commits */ static enum { COMMIT_AS_IS = 1, COMMIT_NORMAL, - COMMIT_PARTIAL, + COMMIT_PARTIAL } commit_style; static const char *logfile, *force_author; @@ -66,7 +71,8 @@ static char *edit_message, *use_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; -static char *untracked_files_arg, *force_date; +static int no_post_rewrite, allow_empty_message; +static char *untracked_files_arg, *force_date, *ignore_submodule_arg; /* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing @@ -77,11 +83,12 @@ static char *untracked_files_arg, *force_date; static enum { CLEANUP_SPACE, CLEANUP_NONE, - CLEANUP_ALL, + CLEANUP_ALL } cleanup_mode; static char *cleanup_arg; static int use_editor = 1, initial_commit, in_merge, include_status = 1; +static int show_ignored_in_status; static const char *only_include_assumed; static struct strbuf message; @@ -89,8 +96,9 @@ static int null_termination; static enum { STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN, + STATUS_FORMAT_PORCELAIN } status_format = STATUS_FORMAT_LONG; +static int status_show_branch; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -132,15 +140,23 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), OPT_SET_INT(0, "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), + OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, "show porcelain output format", STATUS_FORMAT_PORCELAIN), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), + OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"), + { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */ + { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, + "ok to record an empty change", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, + "ok to record a change with an empty message", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_END() }; @@ -203,7 +219,7 @@ static int list_paths(struct string_list *list, const char *with_tree, continue; if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) continue; - item = string_list_insert(ce->name, list); + item = string_list_insert(list, ce->name); if (ce_skip_worktree(ce)) item->util = item; /* better a valid pointer than a fake one */ } @@ -305,7 +321,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int * (B) on failure, rollback the real index. */ if (all || (also && pathspec && *pathspec)) { - int fd = hold_locked_index(&index_lock, 1); + fd = hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, pathspec, 0); refresh_cache_or_die(refresh_flags); if (write_cache(fd, active_cache, active_nr) || @@ -320,16 +336,20 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int * * (1) return the name of the real index file. * - * The caller should run hooks on the real index, and run - * hooks on the real index, and create commit from the_index. + * The caller should run hooks on the real index, + * and create commit from the_index. * We still need to refresh the index here. */ if (!pathspec || !*pathspec) { fd = hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(&index_lock)) - die("unable to write new_index file"); + if (active_cache_changed) { + if (write_cache(fd, active_cache, active_nr) || + commit_locked_index(&index_lock)) + die("unable to write new_index file"); + } else { + rollback_lock_file(&index_lock); + } commit_style = COMMIT_AS_IS; return get_index_file(); } @@ -415,7 +435,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int switch (status_format) { case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s, null_termination); + wt_shortstatus_print(s, null_termination, status_show_branch); break; case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(s, null_termination); @@ -453,15 +473,21 @@ static void determine_author_info(void) if (!a) die("invalid commit: %s", use_message); - lb = strstr(a + 8, " <"); - rb = strstr(a + 8, "> "); - eol = strchr(a + 8, '\n'); - if (!lb || !rb || !eol) + lb = strchrnul(a + strlen("\nauthor "), '<'); + rb = strchrnul(lb, '>'); + eol = strchrnul(rb, '\n'); + if (!*lb || !*rb || !*eol) die("invalid commit: %s", use_message); - name = xstrndup(a + 8, lb - (a + 8)); - email = xstrndup(lb + 2, rb - (lb + 2)); - date = xstrndup(rb + 2, eol - (rb + 2)); + if (lb == a + strlen("\nauthor ")) + /* \nauthor <foo@example.com> */ + name = xcalloc(1, 1); + else + name = xmemdupz(a + strlen("\nauthor "), + (lb - strlen(" ") - + (a + strlen("\nauthor ")))); + email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); + date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> "))); } if (force_author) { @@ -691,6 +717,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (!commitable && !in_merge && !allow_empty && !(amend && is_a_merge(head_sha1))) { run_status(stdout, index_file, prefix, 0, s); + if (amend) + fputs(empty_amend_advice, stderr); return 0; } @@ -715,7 +743,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (use_editor) { char index[PATH_MAX]; - const char *env[2] = { index, NULL }; + const char *env[2] = { NULL }; + env[0] = index; snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); if (launch_editor(git_path(commit_editmsg), NULL, env)) { fprintf(stderr, @@ -1015,11 +1044,14 @@ static int git_status_config(const char *k, const char *v, void *cb) int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; + int fd; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), OPT_SET_INT('s', "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), + OPT_BOOLEAN('b', "branch", &status_show_branch, + "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, "show porcelain output format", STATUS_FORMAT_PORCELAIN), @@ -1029,6 +1061,11 @@ int cmd_status(int argc, const char **argv, const char *prefix) "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_BOOLEAN(0, "ignored", &show_ignored_in_status, + "show ignored files"), + { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when", + "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)", + PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_END(), }; @@ -1042,14 +1079,26 @@ int cmd_status(int argc, const char **argv, const char *prefix) builtin_status_options, builtin_status_usage, 0); handle_untracked_files_arg(&s); - + if (show_ignored_in_status) + s.show_ignored_files = 1; if (*argv) s.pathspec = get_pathspec(prefix, argv); read_cache_preload(s.pathspec); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); + + fd = hold_locked_index(&index_lock, 0); + if (0 <= fd) { + if (active_cache_changed && + !write_cache(fd, active_cache, active_nr)) + commit_locked_index(&index_lock); + else + rollback_lock_file(&index_lock); + } + s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.in_merge = in_merge; + s.ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(&s); if (s.relative_paths) @@ -1061,13 +1110,14 @@ int cmd_status(int argc, const char **argv, const char *prefix) switch (status_format) { case STATUS_FORMAT_SHORT: - wt_shortstatus_print(&s, null_termination); + wt_shortstatus_print(&s, null_termination, status_show_branch); break; case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(&s, null_termination); break; case STATUS_FORMAT_LONG: s.verbose = verbose; + s.ignore_submodule_arg = ignore_submodule_arg; wt_status_print(&s); break; } @@ -1136,13 +1186,11 @@ static void print_summary(const char *prefix, const unsigned char *sha1) initial_commit ? " (root-commit)" : ""); if (!log_tree_commit(&rev, commit)) { - struct pretty_print_context ctx = {0}; - struct strbuf buf = STRBUF_INIT; - ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, format.buf + 7, &buf, &ctx); - printf("%s\n", buf.buf); - strbuf_release(&buf); + rev.always_show_header = 1; + rev.use_terminator = 1; + log_tree_commit(&rev, commit); } + strbuf_release(&format); } @@ -1160,6 +1208,40 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } +static const char post_rewrite_hook[] = "hooks/post-rewrite"; + +static int run_rewrite_hook(const unsigned char *oldsha1, + const unsigned char *newsha1) +{ + /* oldsha1 SP newsha1 LF NUL */ + static char buf[2*40 + 3]; + struct child_process proc; + const char *argv[3]; + int code; + size_t n; + + if (access(git_path(post_rewrite_hook), X_OK) < 0) + return 0; + + argv[0] = git_path(post_rewrite_hook); + argv[1] = "amend"; + argv[2] = NULL; + + memset(&proc, 0, sizeof(proc)); + proc.argv = argv; + proc.in = -1; + proc.stdout_to_stderr = 1; + + code = start_command(&proc); + if (code) + return code; + n = snprintf(buf, sizeof(buf), "%s %s\n", + sha1_to_hex(oldsha1), sha1_to_hex(newsha1)); + write_in_full(proc.in, buf, n); + close(proc.in); + return finish_command(&proc); +} + int cmd_commit(int argc, const char **argv, const char *prefix) { struct strbuf sb = STRBUF_INIT; @@ -1196,13 +1278,16 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } /* Determine parents */ + reflog_msg = getenv("GIT_REFLOG_ACTION"); if (initial_commit) { - reflog_msg = "commit (initial)"; + if (!reflog_msg) + reflog_msg = "commit (initial)"; } else if (amend) { struct commit_list *c; struct commit *commit; - reflog_msg = "commit (amend)"; + if (!reflog_msg) + reflog_msg = "commit (amend)"; commit = lookup_commit(head_sha1); if (!commit || parse_commit(commit)) die("could not parse HEAD commit"); @@ -1213,7 +1298,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct strbuf m = STRBUF_INIT; FILE *fp; - reflog_msg = "commit (merge)"; + if (!reflog_msg) + reflog_msg = "commit (merge)"; pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; fp = fopen(git_path("MERGE_HEAD"), "r"); if (fp == NULL) @@ -1236,7 +1322,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (allow_fast_forward) parents = reduce_heads(parents); } else { - reflog_msg = "commit"; + if (!reflog_msg) + reflog_msg = "commit"; pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; } @@ -1257,7 +1344,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (cleanup_mode != CLEANUP_NONE) stripspace(&sb, cleanup_mode == CLEANUP_ALL); - if (message_is_empty(&sb)) { + if (message_is_empty(&sb) && !allow_empty_message) { rollback_index_files(); fprintf(stderr, "Aborting commit due to empty commit message.\n"); exit(1); @@ -1303,6 +1390,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix) rerere(0); run_hook(get_index_file(), "post-commit", NULL); + if (amend && !no_post_rewrite) { + struct notes_rewrite_cfg *cfg; + cfg = init_copy_notes_for_rewrite("amend"); + if (cfg) { + copy_note_for_rewrite(cfg, head_sha1, commit_sha1); + finish_copy_notes_for_rewrite(cfg); + } + run_rewrite_hook(head_sha1, commit_sha1); + } if (!quiet) print_summary(prefix, commit_sha1); diff --git a/builtin-config.c b/builtin/config.c index 4bc46b15fd..f3d1660d02 100644 --- a/builtin-config.c +++ b/builtin/config.c @@ -197,7 +197,11 @@ static int get_value(const char *key_, const char *regex_) git_config_from_file(show_config, system_wide, NULL); if (do_all && global) git_config_from_file(show_config, global, NULL); - git_config_from_file(show_config, local, NULL); + if (do_all) + git_config_from_file(show_config, local, NULL); + git_config_from_parameters(show_config, NULL); + if (!do_all && !seen) + git_config_from_file(show_config, local, NULL); if (!do_all && !seen && global) git_config_from_file(show_config, global, NULL); if (!do_all && !seen && system_wide) diff --git a/builtin-count-objects.c b/builtin/count-objects.c index 2bdd8ebde1..2bdd8ebde1 100644 --- a/builtin-count-objects.c +++ b/builtin/count-objects.c diff --git a/builtin-describe.c b/builtin/describe.c index 71be2a9364..43caff2ffe 100644 --- a/builtin-describe.c +++ b/builtin/describe.c @@ -35,7 +35,8 @@ static const char *diff_index_args[] = { struct commit_name { struct tag *tag; - int prio; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned name_checked:1; unsigned char sha1[20]; char path[FLEX_ARRAY]; /* more */ }; @@ -43,18 +44,53 @@ static const char *prio_names[] = { "head", "lightweight", "annotated", }; +static int replace_name(struct commit_name *e, + int prio, + const unsigned char *sha1, + struct tag **tag) +{ + if (!e || e->prio < prio) + return 1; + + if (e->prio == 2 && prio == 2) { + /* Multiple annotated tags point to the same commit. + * Select one to keep based upon their tagger date. + */ + struct tag *t; + + if (!e->tag) { + t = lookup_tag(e->sha1); + if (!t || parse_tag(t)) + return 1; + e->tag = t; + } + + t = lookup_tag(sha1); + if (!t || parse_tag(t)) + return 0; + *tag = t; + + if (e->tag->date < t->date) + return 1; + } + + return 0; +} + static void add_to_known_names(const char *path, struct commit *commit, int prio, const unsigned char *sha1) { struct commit_name *e = commit->util; - if (!e || e->prio < prio) { + struct tag *tag = NULL; + if (replace_name(e, prio, sha1, &tag)) { size_t len = strlen(path)+1; free(e); e = xmalloc(sizeof(struct commit_name) + len); - e->tag = NULL; + e->tag = tag; e->prio = prio; + e->name_checked = 0; hashcpy(e->sha1, sha1); memcpy(e->path, path, len); commit->util = e; @@ -165,10 +201,15 @@ static void display_name(struct commit_name *n) { if (n->prio == 2 && !n->tag) { n->tag = lookup_tag(n->sha1); - if (!n->tag || parse_tag(n->tag) || !n->tag->tag) + if (!n->tag || parse_tag(n->tag)) die("annotated tag %s not available", n->path); + } + if (n->tag && !n->name_checked) { + if (!n->tag->tag) + die("annotated tag %s has no embedded name", n->path); if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) warning("tag '%s' is really '%s' here", n->tag->tag, n->path); + n->name_checked = 1; } if (n->tag) diff --git a/builtin-diff-files.c b/builtin/diff-files.c index 5b64011de8..5b64011de8 100644 --- a/builtin-diff-files.c +++ b/builtin/diff-files.c diff --git a/builtin-diff-index.c b/builtin/diff-index.c index 04837494fe..04837494fe 100644 --- a/builtin-diff-index.c +++ b/builtin/diff-index.c diff --git a/builtin-diff-tree.c b/builtin/diff-tree.c index 2380c21951..3c78bda566 100644 --- a/builtin-diff-tree.c +++ b/builtin/diff-tree.c @@ -92,12 +92,23 @@ static const char diff_tree_usage[] = " --root include the initial commit as diff against /dev/null\n" COMMON_DIFF_OPTIONS_HELP; +static void diff_tree_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt) +{ + if (!rev->diffopt.output_format) { + if (rev->dense_combined_merges) + rev->diffopt.output_format = DIFF_FORMAT_PATCH; + else + rev->diffopt.output_format = DIFF_FORMAT_RAW; + } +} + int cmd_diff_tree(int argc, const char **argv, const char *prefix) { int nr_sha1; char line[1000]; struct object *tree1, *tree2; static struct rev_info *opt = &log_tree_opt; + struct setup_revision_opt s_r_opt; int read_stdin = 0; init_revisions(opt, prefix); @@ -105,7 +116,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) opt->abbrev = 0; opt->diff = 1; opt->disable_stdin = 1; - argc = setup_revisions(argc, argv, opt, NULL); + memset(&s_r_opt, 0, sizeof(s_r_opt)); + s_r_opt.tweak = diff_tree_tweak_rev; + argc = setup_revisions(argc, argv, opt, &s_r_opt); while (--argc > 0) { const char *arg = *++argv; @@ -117,9 +130,6 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) usage(diff_tree_usage); } - if (!opt->diffopt.output_format) - opt->diffopt.output_format = DIFF_FORMAT_RAW; - /* * NOTE! We expect "a ^b" to be equal to "a..b", so we * reverse the order of the objects if the second one diff --git a/builtin-diff.c b/builtin/diff.c index ffcdd055ca..89ae89cde1 100644 --- a/builtin-diff.c +++ b/builtin/diff.c @@ -407,17 +407,19 @@ int cmd_diff(int argc, const char **argv, const char *prefix) result = builtin_diff_index(&rev, argc, argv); else if (ents == 2) result = builtin_diff_tree(&rev, argc, argv, ent); - else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) { - /* diff A...B where there is one sane merge base between - * A and B. We have ent[0] == merge-base, ent[1] == A, - * and ent[2] == B. Show diff between the base and B. + else if (ent[0].item->flags & UNINTERESTING) { + /* + * diff A...B where there is at least one merge base + * between A and B. We have ent[0] == merge-base, + * ent[ents-2] == A, and ent[ents-1] == B. Show diff + * between the base and B. Note that we pick one + * merge base at random if there are more than one. */ - ent[1] = ent[2]; + ent[1] = ent[ents-1]; result = builtin_diff_tree(&rev, argc, argv, ent); - } - else + } else result = builtin_diff_combined(&rev, argc, argv, - ent, ents); + ent, ents); result = diff_result_code(&rev.diffopt, result); if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); diff --git a/builtin-fast-export.c b/builtin/fast-export.c index c6dd71a7bc..9fe25ff0b3 100644 --- a/builtin-fast-export.c +++ b/builtin/fast-export.c @@ -438,7 +438,7 @@ static void get_tags_and_duplicates(struct object_array *pending, /* handle nested tags */ while (tag && tag->object.type == OBJ_TAG) { parse_object(tag->object.sha1); - string_list_append(full_name, extra_refs)->util = tag; + string_list_append(extra_refs, full_name)->util = tag; tag = (struct tag *)tag->tagged; } if (!tag) @@ -464,7 +464,7 @@ static void get_tags_and_duplicates(struct object_array *pending, } if (commit->util) /* more than one name for the same object */ - string_list_append(full_name, extra_refs)->util = commit; + string_list_append(extra_refs, full_name)->util = commit; else commit->util = full_name; } diff --git a/builtin-fetch-pack.c b/builtin/fetch-pack.c index dbd8b7bcc8..dbd8b7bcc8 100644 --- a/builtin-fetch-pack.c +++ b/builtin/fetch-pack.c diff --git a/builtin-fetch.c b/builtin/fetch.c index 007dabf62f..1b67f5fda5 100644 --- a/builtin-fetch.c +++ b/builtin/fetch.c @@ -11,6 +11,7 @@ #include "run-command.h" #include "parse-options.h" #include "sigchain.h" +#include "transport.h" static const char * const builtin_fetch_usage[] = { "git fetch [<options>] [<repository> [<refspec>...]]", @@ -27,6 +28,7 @@ enum { }; static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; +static int progress; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -56,6 +58,7 @@ static struct option builtin_fetch_options[] = { OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), + OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), OPT_STRING(0, "depth", &depth, "DEPTH", "deepen history of shallow clone"), OPT_END() @@ -203,7 +206,6 @@ static int s_update_ref(const char *action, return 0; } -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) #define REFCOL_WIDTH 10 static int update_local_ref(struct ref *ref, @@ -222,7 +224,7 @@ static int update_local_ref(struct ref *ref, if (!hashcmp(ref->old_sha1, ref->new_sha1)) { if (verbosity > 0) - sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH, + sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH, "[up to date]", REFCOL_WIDTH, remote, pretty_ref); return 0; @@ -237,7 +239,7 @@ static int update_local_ref(struct ref *ref, * the head, and the old value of the head isn't empty... */ sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)", - SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -247,7 +249,7 @@ static int update_local_ref(struct ref *ref, int r; r = s_update_ref("updating tag", ref, 0); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-', - SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); return r; } @@ -269,7 +271,7 @@ static int update_local_ref(struct ref *ref, r = s_update_ref(msg, ref, 0); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', - SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, + TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); return r; } @@ -282,7 +284,7 @@ static int update_local_ref(struct ref *ref, strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); r = s_update_ref("fast-forward", ref, 1); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', - SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); return r; } else if (force || ref->force) { @@ -293,13 +295,13 @@ static int update_local_ref(struct ref *ref, strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); r = s_update_ref("forced-update", ref, 1); sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', - SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, + 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 (non-fast-forward)", - SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -392,7 +394,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, free(ref); } else sprintf(note, "* %-*s %-*s -> FETCH_HEAD", - SUMMARY_WIDTH, *kind ? kind : "branch", + TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch", REFCOL_WIDTH, *what ? what : "HEAD"); if (*note) { if (verbosity >= 0 && !shown_url) { @@ -513,7 +515,7 @@ static int prune_refs(struct transport *transport, struct ref *ref_map) result |= delete_ref(ref->name, NULL, 0); if (verbosity >= 0) { fprintf(stderr, " x %-*s %-*s -> %s\n", - SUMMARY_WIDTH, "[deleted]", + TRANSPORT_SUMMARY_WIDTH, "[deleted]", REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); warn_dangling_symref(stderr, dangling_msg, ref->name); } @@ -526,7 +528,7 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; - struct string_list_item *item = string_list_insert(refname, list); + struct string_list_item *item = string_list_insert(list, refname); item->util = (void *)sha1; return 0; } @@ -572,9 +574,10 @@ static void find_non_local_tags(struct transport *transport, { struct string_list existing_refs = { NULL, 0, 0, 0 }; struct string_list remote_refs = { NULL, 0, 0, 0 }; - struct tag_data data = {head, tail}; + struct tag_data data; const struct ref *ref; struct string_list_item *item = NULL; + data.head = head; data.tail = tail; for_each_ref(add_existing, &existing_refs); for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { @@ -614,7 +617,7 @@ static void find_non_local_tags(struct transport *transport, string_list_has_string(&existing_refs, ref->name)) continue; - item = string_list_insert(ref->name, &remote_refs); + item = string_list_insert(&remote_refs, ref->name); item->util = (void *)ref->old_sha1; } string_list_clear(&existing_refs, 0); @@ -631,7 +634,7 @@ static void find_non_local_tags(struct transport *transport, * For all the tags in the remote_refs string list, call * add_to_tail to add them to the list of refs to be fetched */ - for_each_string_list(add_to_tail, &remote_refs, &data); + for_each_string_list(&remote_refs, add_to_tail, &data); string_list_clear(&remote_refs, 0); } @@ -693,8 +696,8 @@ static int do_fetch(struct transport *transport, for (rm = ref_map; rm; rm = rm->next) { if (rm->peer_ref) { - peer_item = string_list_lookup(rm->peer_ref->name, - &existing_refs); + peer_item = string_list_lookup(&existing_refs, + rm->peer_ref->name); if (peer_item) hashcpy(rm->peer_ref->old_sha1, peer_item->util); @@ -743,7 +746,7 @@ static int get_one_remote_for_fetch(struct remote *remote, void *priv) { struct string_list *list = priv; if (!remote->skip_default_update) - string_list_append(remote->name, list); + string_list_append(list, remote->name); return 0; } @@ -762,8 +765,8 @@ static int get_remote_group(const char *key, const char *value, void *priv) int space = strcspn(value, " \t\n"); while (*value) { if (space > 1) { - string_list_append(xstrndup(value, space), - g->list); + string_list_append(g->list, + xstrndup(value, space)); } value += space + (value[space] != '\0'); space = strcspn(value, " \t\n"); @@ -776,7 +779,8 @@ static int get_remote_group(const char *key, const char *value, void *priv) static int add_remote_or_group(const char *name, struct string_list *list) { int prev_nr = list->nr; - struct remote_group_data g = { name, list }; + struct remote_group_data g; + g.name = name; g.list = list; git_config(get_remote_group, &g); if (list->nr == prev_nr) { @@ -784,7 +788,7 @@ static int add_remote_or_group(const char *name, struct string_list *list) if (!remote_is_configured(name)) return 0; remote = remote_get(name); - string_list_append(remote->name, list); + string_list_append(list, remote->name); } return 1; } @@ -841,13 +845,11 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) int exit_code; if (!remote) - die("Where do you want to fetch from today?"); + die("No remote repository specified. Please, specify either a URL or a\n" + "remote name from which new revisions should be fetched."); transport = transport_get(remote, NULL); - if (verbosity >= 2) - transport->verbose = verbosity <= 3 ? verbosity : 3; - if (verbosity < 0) - transport->verbose = -1; + transport_set_verbosity(transport, verbosity, progress); if (upload_pack) set_option(TRANS_OPT_UPLOADPACK, upload_pack); if (keep) diff --git a/builtin-fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 9d524000b5..bc3c5e6d3e 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -4,6 +4,7 @@ #include "diff.h" #include "revision.h" #include "tag.h" +#include "string-list.h" static const char * const fmt_merge_msg_usage[] = { "git fmt-merge-msg [--log|--no-log] [--file <file>]", @@ -24,58 +25,21 @@ static int fmt_merge_msg_config(const char *key, const char *value, void *cb) return 0; } -struct list { - char **list; - void **payload; - unsigned nr, alloc; +struct src_data { + struct string_list branch, tag, r_branch, generic; + int head_status; }; -static void append_to_list(struct list *list, char *value, void *payload) -{ - if (list->nr == list->alloc) { - list->alloc += 32; - list->list = xrealloc(list->list, sizeof(char *) * list->alloc); - list->payload = xrealloc(list->payload, - sizeof(char *) * list->alloc); - } - list->payload[list->nr] = payload; - list->list[list->nr++] = value; -} - -static int find_in_list(struct list *list, char *value) -{ - int i; - - for (i = 0; i < list->nr; i++) - if (!strcmp(list->list[i], value)) - return i; - - return -1; -} - -static void free_list(struct list *list) +void init_src_data(struct src_data *data) { - int i; - - if (list->alloc == 0) - return; - - for (i = 0; i < list->nr; i++) { - free(list->list[i]); - free(list->payload[i]); - } - free(list->list); - free(list->payload); - list->nr = list->alloc = 0; + data->branch.strdup_strings = 1; + data->tag.strdup_strings = 1; + data->r_branch.strdup_strings = 1; + data->generic.strdup_strings = 1; } -struct src_data { - struct list branch, tag, r_branch, generic; - int head_status; -}; - -static struct list srcs = { NULL, NULL, 0, 0}; -static struct list origins = { NULL, NULL, 0, 0}; +static struct string_list srcs = { NULL, 0, 0, 1 }; +static struct string_list origins = { NULL, 0, 0, 1 }; static int handle_line(char *line) { @@ -83,6 +47,7 @@ static int handle_line(char *line) unsigned char *sha1; char *src, *origin; struct src_data *src_data; + struct string_list_item *item; int pulling_head = 0; if (len < 43 || line[40] != '\t') @@ -115,64 +80,62 @@ static int handle_line(char *line) pulling_head = 1; } - i = find_in_list(&srcs, src); - if (i < 0) { - i = srcs.nr; - append_to_list(&srcs, xstrdup(src), - xcalloc(1, sizeof(struct src_data))); + item = unsorted_string_list_lookup(&srcs, src); + if (!item) { + item = string_list_append(&srcs, src); + item->util = xcalloc(1, sizeof(struct src_data)); + init_src_data(item->util); } - src_data = srcs.payload[i]; + src_data = item->util; if (pulling_head) { - origin = xstrdup(src); + origin = src; src_data->head_status |= 1; } else if (!prefixcmp(line, "branch ")) { - origin = xstrdup(line + 7); - append_to_list(&src_data->branch, origin, NULL); + origin = line + 7; + string_list_append(&src_data->branch, origin); src_data->head_status |= 2; } else if (!prefixcmp(line, "tag ")) { origin = line; - append_to_list(&src_data->tag, xstrdup(origin + 4), NULL); + string_list_append(&src_data->tag, origin + 4); src_data->head_status |= 2; } else if (!prefixcmp(line, "remote branch ")) { - origin = xstrdup(line + 14); - append_to_list(&src_data->r_branch, origin, NULL); + origin = line + 14; + string_list_append(&src_data->r_branch, origin); src_data->head_status |= 2; } else { - origin = xstrdup(src); - append_to_list(&src_data->generic, xstrdup(line), NULL); + origin = src; + string_list_append(&src_data->generic, line); src_data->head_status |= 2; } if (!strcmp(".", src) || !strcmp(src, origin)) { int len = strlen(origin); - if (origin[0] == '\'' && origin[len - 1] == '\'') { + if (origin[0] == '\'' && origin[len - 1] == '\'') origin = xmemdupz(origin + 1, len - 2); - } else { - origin = xstrdup(origin); - } } else { char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); sprintf(new_origin, "%s of %s", origin, src); origin = new_origin; } - append_to_list(&origins, origin, sha1); + string_list_append(&origins, origin)->util = sha1; return 0; } static void print_joined(const char *singular, const char *plural, - struct list *list, struct strbuf *out) + struct string_list *list, struct strbuf *out) { if (list->nr == 0) return; if (list->nr == 1) { - strbuf_addf(out, "%s%s", singular, list->list[0]); + strbuf_addf(out, "%s%s", singular, list->items[0].string); } else { int i; strbuf_addstr(out, plural); for (i = 0; i < list->nr - 1; i++) - strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]); - strbuf_addf(out, " and %s", list->list[list->nr - 1]); + strbuf_addf(out, "%s%s", i > 0 ? ", " : "", + list->items[i].string); + strbuf_addf(out, " and %s", list->items[list->nr - 1].string); } } @@ -183,8 +146,9 @@ static void shortlog(const char *name, unsigned char *sha1, int i, count = 0; struct commit *commit; struct object *branch; - struct list subjects = { NULL, NULL, 0, 0 }; + struct string_list subjects = { NULL, 0, 0, 1 }; int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; + struct strbuf sb = STRBUF_INIT; branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); if (!branch || branch->type != OBJ_COMMIT) @@ -198,7 +162,7 @@ static void shortlog(const char *name, unsigned char *sha1, if (prepare_revision_walk(rev)) die("revision walk setup failed"); while ((commit = get_revision(rev)) != NULL) { - char *oneline, *bol, *eol; + struct pretty_print_context ctx = {0}; /* ignore merges */ if (commit->parents && commit->parents->next) @@ -208,30 +172,14 @@ static void shortlog(const char *name, unsigned char *sha1, if (subjects.nr > limit) continue; - bol = strstr(commit->buffer, "\n\n"); - if (bol) { - unsigned char c; - do { - c = *++bol; - } while (isspace(c)); - if (!c) - bol = NULL; - } + format_commit_message(commit, "%s", &sb, &ctx); + strbuf_ltrim(&sb); - if (!bol) { - append_to_list(&subjects, xstrdup(sha1_to_hex( - commit->object.sha1)), - NULL); - continue; - } - - eol = strchr(bol, '\n'); - if (eol) { - oneline = xmemdupz(bol, eol - bol); - } else { - oneline = xstrdup(bol); - } - append_to_list(&subjects, oneline, NULL); + if (!sb.len) + string_list_append(&subjects, + sha1_to_hex(commit->object.sha1)); + else + string_list_append(&subjects, strbuf_detach(&sb, NULL)); } if (count > limit) @@ -243,7 +191,7 @@ static void shortlog(const char *name, unsigned char *sha1, if (i >= limit) strbuf_addf(out, " ...\n"); else - strbuf_addf(out, " %s\n", subjects.list[i]); + strbuf_addf(out, " %s\n", subjects.items[i].string); clear_commit_marks((struct commit *)branch, flags); clear_commit_marks(head, flags); @@ -251,46 +199,24 @@ static void shortlog(const char *name, unsigned char *sha1, rev->commits = NULL; rev->pending.nr = 0; - free_list(&subjects); + string_list_clear(&subjects, 0); } -int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { - int limit = 20, i = 0, pos = 0; +static void do_fmt_merge_msg_title(struct strbuf *out, + const char *current_branch) { + int i = 0; char *sep = ""; - unsigned char head_sha1[20]; - const char *current_branch; - - /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); - if (!current_branch) - die("No current branch"); - if (!prefixcmp(current_branch, "refs/heads/")) - current_branch += 11; - - /* get a line */ - while (pos < in->len) { - int len; - char *newline, *p = in->buf + pos; - - newline = strchr(p, '\n'); - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - i++; - p[len] = 0; - if (handle_line(p)) - die ("Error in line %d: %.*s", i, len, p); - } strbuf_addstr(out, "Merge "); for (i = 0; i < srcs.nr; i++) { - struct src_data *src_data = srcs.payload[i]; + struct src_data *src_data = srcs.items[i].util; const char *subsep = ""; strbuf_addstr(out, sep); sep = "; "; if (src_data->head_status == 1) { - strbuf_addstr(out, srcs.list[i]); + strbuf_addstr(out, srcs.items[i].string); continue; } if (src_data->head_status == 3) { @@ -319,14 +245,48 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { print_joined("commit ", "commits ", &src_data->generic, out); } - if (strcmp(".", srcs.list[i])) - strbuf_addf(out, " of %s", srcs.list[i]); + if (strcmp(".", srcs.items[i].string)) + strbuf_addf(out, " of %s", srcs.items[i].string); } if (!strcmp("master", current_branch)) strbuf_addch(out, '\n'); else strbuf_addf(out, " into %s\n", current_branch); +} + +static int do_fmt_merge_msg(int merge_title, int merge_summary, + struct strbuf *in, struct strbuf *out) { + int limit = 20, i = 0, pos = 0; + unsigned char head_sha1[20]; + const char *current_branch; + + /* get current branch */ + current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); + if (!current_branch) + die("No current branch"); + if (!prefixcmp(current_branch, "refs/heads/")) + current_branch += 11; + + /* get a line */ + while (pos < in->len) { + int len; + char *newline, *p = in->buf + pos; + + newline = strchr(p, '\n'); + len = newline ? newline - p : strlen(p); + pos += len + !!newline; + i++; + p[len] = 0; + if (handle_line(p)) + die ("Error in line %d: %.*s", i, len, p); + } + + if (!srcs.nr) + return 0; + + if (merge_title) + do_fmt_merge_msg_title(out, current_branch); if (merge_summary) { struct commit *head; @@ -338,19 +298,32 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { rev.ignore_merges = 1; rev.limited = 1; + if (suffixcmp(out->buf, "\n")) + strbuf_addch(out, '\n'); + for (i = 0; i < origins.nr; i++) - shortlog(origins.list[i], origins.payload[i], + shortlog(origins.items[i].string, origins.items[i].util, head, &rev, limit, out); } return 0; } +int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { + return do_fmt_merge_msg(1, merge_summary, in, out); +} + +int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) { + return do_fmt_merge_msg(0, 1, in, out); +} + int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; struct option options[] = { OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"), - OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"), + { OPTION_BOOLEAN, 0, "summary", &merge_summary, NULL, + "alias for --log (deprecated)", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_FILENAME('F', "file", &inpath, "file to read from"), OPT_END() }; diff --git a/builtin-for-each-ref.c b/builtin/for-each-ref.c index a5a83f1469..a2b28c6962 100644 --- a/builtin-for-each-ref.c +++ b/builtin/for-each-ref.c @@ -33,6 +33,8 @@ struct ref_sort { struct refinfo { char *refname; unsigned char objectname[20]; + int flag; + const char *symref; struct atom_value *value; }; @@ -68,6 +70,8 @@ static struct { { "body" }, { "contents" }, { "upstream" }, + { "symref" }, + { "flag" }, }; /* @@ -82,7 +86,7 @@ static struct { */ static const char **used_atom; static cmp_type *used_atom_type; -static int used_atom_cnt, sort_atom_limit, need_tagged; +static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref; /* * Used to parse format string and sort specifiers @@ -133,6 +137,10 @@ static int parse_atom(const char *atom, const char *ep) (sizeof(*used_atom_type) * used_atom_cnt)); used_atom[at] = xmemdupz(atom, ep - atom); used_atom_type[at] = valid_atom[i].cmp_type; + if (*atom == '*') + need_tagged = 1; + if (!strcmp(used_atom[at], "symref")) + need_symref = 1; return at; } @@ -143,7 +151,8 @@ static const char *find_next(const char *cp) { while (*cp) { if (*cp == '%') { - /* %( is the start of an atom; + /* + * %( is the start of an atom; * %% is a quoted per-cent. */ if (cp[1] == '(') @@ -218,6 +227,9 @@ static void grab_common_values(struct atom_value *val, int deref, struct object strcpy(s, sha1_to_hex(obj->sha1)); v->s = s; } + else if (!strcmp(name, "objectname:short")) { + v->s = find_unique_abbrev(obj->sha1, DEFAULT_ABBREV); + } } } @@ -420,7 +432,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru grab_date(wholine, v, name); } - /* For a tag or a commit object, if "creator" or "creatordate" is + /* + * For a tag or a commit object, if "creator" or "creatordate" is * requested, do something special. */ if (strcmp(who, "tagger") && strcmp(who, "committer")) @@ -502,7 +515,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj } } -/* We want to have empty print-string for field requests +/* + * We want to have empty print-string for field requests * that do not apply (e.g. "authordate" for a tag object) */ static void fill_missing_values(struct atom_value *val) @@ -538,16 +552,23 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v grab_person("committer", val, deref, obj, buf, sz); break; case OBJ_TREE: - // grab_tree_values(val, deref, obj, buf, sz); + /* grab_tree_values(val, deref, obj, buf, sz); */ break; case OBJ_BLOB: - // grab_blob_values(val, deref, obj, buf, sz); + /* grab_blob_values(val, deref, obj, buf, sz); */ break; default: die("Eh? Object of type %d?", obj->type); } } +static inline char *copy_advance(char *dst, const char *src) +{ + while (*src) + *dst++ = *src++; + return dst; +} + /* * Parse the object referred by ref, and grab needed value. */ @@ -561,6 +582,16 @@ static void populate_value(struct refinfo *ref) ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt); + 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 = ""; + } + /* Fill in specials first */ for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; @@ -576,6 +607,8 @@ static void populate_value(struct refinfo *ref) if (!prefixcmp(name, "refname")) refname = ref->refname; + else if (!prefixcmp(name, "symref")) + refname = ref->symref ? ref->symref : ""; else if (!prefixcmp(name, "upstream")) { struct branch *branch; /* only local branches may have an upstream */ @@ -588,6 +621,20 @@ static void populate_value(struct refinfo *ref) continue; refname = branch->merge[0]->dst; } + else if (!strcmp(name, "flag")) { + char buf[256], *cp = buf; + if (ref->flag & REF_ISSYMREF) + cp = copy_advance(cp, ",symref"); + if (ref->flag & REF_ISPACKED) + cp = copy_advance(cp, ",packed"); + if (cp == buf) + v->s = ""; + else { + *cp = '\0'; + v->s = xstrdup(buf + 1); + } + continue; + } else continue; @@ -633,18 +680,21 @@ static void populate_value(struct refinfo *ref) if (!eaten) free(buf); - /* If there is no atom that wants to know about tagged + /* + * If there is no atom that wants to know about tagged * object, we are done. */ if (!need_tagged || (obj->type != OBJ_TAG)) return; - /* If it is a tag object, see if we use a value that derefs + /* + * If it is a tag object, see if we use a value that derefs * the object, and if we do grab the object it refers to. */ tagged = ((struct tag *)obj)->tagged->sha1; - /* NEEDSWORK: This derefs tag only once, which + /* + * NEEDSWORK: This derefs tag only once, which * is good to deal with chains of trust, but * is not consistent with what deref_tag() does * which peels the onion to the core. @@ -681,9 +731,8 @@ struct grab_ref_cbdata { }; /* - * A call-back given to for_each_ref(). It is unfortunate that we - * need to use global variables to pass extra information to this - * function. + * A call-back given to for_each_ref(). Filter refs and keep them for + * later object processing. */ static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { @@ -711,13 +760,15 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f return 0; } - /* We do not open the object yet; sort may only need refname + /* + * We do not open the object yet; sort may only need refname * to do its job and the resulting list may yet to be pruned * by maxcount logic. */ ref = xcalloc(1, sizeof(*ref)); ref->refname = xstrdup(refname); hashcpy(ref->objectname, sha1); + ref->flag = flag; cnt = cb->grab_cnt; cb->grab_array = xrealloc(cb->grab_array, @@ -938,13 +989,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) refs = cbdata.grab_array; num_refs = cbdata.grab_cnt; - for (i = 0; i < used_atom_cnt; i++) { - if (used_atom[i][0] == '*') { - need_tagged = 1; - break; - } - } - sort_refs(sort, refs, num_refs); if (!maxcount || num_refs < maxcount) diff --git a/builtin-fsck.c b/builtin/fsck.c index 0929c7f245..0929c7f245 100644 --- a/builtin-fsck.c +++ b/builtin/fsck.c diff --git a/builtin-gc.c b/builtin/gc.c index c304638b78..c304638b78 100644 --- a/builtin-gc.c +++ b/builtin/gc.c diff --git a/builtin-grep.c b/builtin/grep.c index 371db0aa9e..597f76bc42 100644 --- a/builtin-grep.c +++ b/builtin/grep.c @@ -11,13 +11,16 @@ #include "tree-walk.h" #include "builtin.h" #include "parse-options.h" +#include "string-list.h" +#include "run-command.h" #include "userdiff.h" #include "grep.h" #include "quote.h" +#include "dir.h" #ifndef NO_PTHREADS -#include "thread-utils.h" #include <pthread.h> +#include "thread-utils.h" #endif static char const * const grep_usage[] = { @@ -95,6 +98,9 @@ static pthread_cond_t cond_write; /* Signalled when we are finished with everything. */ static pthread_cond_t cond_result; +static int print_hunk_marks_between_files; +static int printed_something; + static void add_work(enum work_type type, char *name, void *id) { grep_lock(); @@ -158,7 +164,12 @@ static void work_done(struct work_item *w) for(; todo[todo_done].done && todo_done != todo_start; todo_done = (todo_done+1) % ARRAY_SIZE(todo)) { w = &todo[todo_done]; - write_or_die(1, w->out.buf, w->out.len); + if (w->out.len) { + if (print_hunk_marks_between_files && printed_something) + write_or_die(1, "--\n", 3); + write_or_die(1, w->out.buf, w->out.len); + printed_something = 1; + } free(w->name); free(w->identifier); } @@ -288,6 +299,7 @@ static int wait_all(void) static int grep_config(const char *var, const char *value, void *cb) { struct grep_opt *opt = cb; + char *color = NULL; switch (userdiff_config(var, value)) { case 0: break; @@ -295,17 +307,30 @@ static int grep_config(const char *var, const char *value, void *cb) default: return 0; } - if (!strcmp(var, "color.grep")) { + if (!strcmp(var, "color.grep")) opt->color = git_config_colorbool(var, value, -1); - return 0; - } - if (!strcmp(var, "color.grep.match")) { + else if (!strcmp(var, "color.grep.context")) + color = opt->color_context; + else if (!strcmp(var, "color.grep.filename")) + color = opt->color_filename; + else if (!strcmp(var, "color.grep.function")) + color = opt->color_function; + else if (!strcmp(var, "color.grep.linenumber")) + color = opt->color_lineno; + else if (!strcmp(var, "color.grep.match")) + color = opt->color_match; + else if (!strcmp(var, "color.grep.selected")) + color = opt->color_selected; + else if (!strcmp(var, "color.grep.separator")) + color = opt->color_sep; + else + return git_color_default_config(var, value, cb); + if (color) { if (!value) return config_error_nonbool(var); - color_parse(value, var, opt->color_match); - return 0; + color_parse(value, var, color); } - return git_color_default_config(var, value, cb); + return 0; } /* @@ -533,6 +558,33 @@ static int grep_file(struct grep_opt *opt, const char *filename) } } +static void append_path(struct grep_opt *opt, const void *data, size_t len) +{ + struct string_list *path_list = opt->output_priv; + + if (len == 1 && *(const char *)data == '\0') + return; + string_list_append(path_list, xstrndup(data, len)); +} + +static void run_pager(struct grep_opt *opt, const char *prefix) +{ + struct string_list *path_list = opt->output_priv; + const char **argv = xmalloc(sizeof(const char *) * (path_list->nr + 1)); + int i, status; + + for (i = 0; i < path_list->nr; i++) + argv[i] = path_list->items[i].string; + argv[path_list->nr] = NULL; + + if (prefix && chdir(prefix)) + die("Failed to chdir: %s", prefix); + status = run_command_v_opt(argv, RUN_USING_SHELL); + if (status) + exit(status); + free(argv); +} + static int grep_cache(struct grep_opt *opt, const char **paths, int cached) { int hit = 0; @@ -567,7 +619,6 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) if (hit && opt->status_only) break; } - free_grep_patterns(opt); return hit; } @@ -652,6 +703,42 @@ static int grep_object(struct grep_opt *opt, const char **paths, die("unable to grep from object of type %s", typename(obj->type)); } +static int grep_objects(struct grep_opt *opt, const char **paths, + const struct object_array *list) +{ + unsigned int i; + int hit = 0; + const unsigned int nr = list->nr; + + for (i = 0; i < nr; i++) { + struct object *real_obj; + real_obj = deref_tag(list->objects[i].item, NULL, 0); + if (grep_object(opt, paths, real_obj, list->objects[i].name)) { + hit = 1; + if (opt->status_only) + break; + } + } + return hit; +} + +static int grep_directory(struct grep_opt *opt, const char **paths) +{ + struct dir_struct dir; + int i, hit = 0; + + memset(&dir, 0, sizeof(dir)); + setup_standard_excludes(&dir); + + fill_directory(&dir, paths); + for (i = 0; i < dir.nr; i++) { + hit |= grep_file(opt, dir.entries[i]->name); + if (hit && opt->status_only) + break; + } + return hit; +} + static int context_callback(const struct option *opt, const char *arg, int unset) { @@ -683,11 +770,15 @@ static int file_callback(const struct option *opt, const char *arg, int unset) if (!patterns) die_errno("cannot open '%s'", arg); while (strbuf_getline(&sb, patterns, '\n') == 0) { + char *s; + size_t len; + /* ignore empty line like grep does */ if (sb.len == 0) continue; - append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg, - ++lno, GREP_PATTERN); + + s = strbuf_detach(&sb, &len); + append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN); } fclose(patterns); strbuf_release(&sb); @@ -741,14 +832,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int cached = 0; int seen_dashdash = 0; int external_grep_allowed__ignored; + const char *show_in_pager = NULL, *default_pager = "dummy"; struct grep_opt opt; struct object_array list = { 0, 0, NULL }; const char **paths = NULL; + struct string_list path_list = { NULL, 0, 0, 0 }; int i; int dummy; + int nongit = 0, use_index = 1; 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"), OPT_GROUP(""), OPT_BOOLEAN('v', "invert-match", &opt.invert, "show non-matching lines"), @@ -789,7 +885,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) "print NUL after filenames"), OPT_BOOLEAN('c', "count", &opt.count, "show the number of matches instead of matching lines"), - OPT_SET_INT(0, "color", &opt.color, "highlight matches", 1), + OPT__COLOR(&opt.color, "highlight matches"), OPT_GROUP(""), OPT_CALLBACK('C', NULL, &opt, "n", "show <n> context lines before and after matches", @@ -824,6 +920,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "all-match", &opt.all_match, "show only matches from files that match all patterns"), OPT_GROUP(""), + { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, + "pager", "show matching files in the pager", + PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager }, OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored, "allow calling of grep(1) (ignored by this build)"), { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage", @@ -831,6 +930,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; + prefix = setup_git_directory_gently(&nongit); + /* * 'git grep -h', unlike 'git grep -h <pattern>', is a request * to show usage information and exit. @@ -848,7 +949,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.regflags = REG_NEWLINE; opt.max_depth = -1; - strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD); + strcpy(opt.color_context, ""); + strcpy(opt.color_filename, ""); + strcpy(opt.color_function, ""); + strcpy(opt.color_lineno, ""); + strcpy(opt.color_match, GIT_COLOR_BOLD_RED); + strcpy(opt.color_selected, ""); + strcpy(opt.color_sep, GIT_COLOR_CYAN); opt.color = -1; git_config(grep_config, &opt); if (opt.color == -1) @@ -869,6 +976,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + if (use_index && nongit) + /* die the same way as if we did it at the beginning */ + setup_git_directory(); + /* * skip a -- separator; we know it cannot be * separating revisions from pathnames if @@ -887,6 +998,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix) argc--; } + if (show_in_pager == default_pager) + show_in_pager = git_pager(1); + if (show_in_pager) { + opt.color = 0; + opt.name_only = 1; + opt.null_following_name = 1; + opt.output_priv = &path_list; + opt.output = append_path; + string_list_append(&path_list, show_in_pager); + use_threads = 0; + } + if (!opt.pattern_list) die("no pattern given."); if (!opt.fixed && opt.ignore_case) @@ -898,8 +1021,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (online_cpus() == 1 || !grep_threads_ok(&opt)) use_threads = 0; - if (use_threads) + if (use_threads) { + if (opt.pre_context || opt.post_context) + print_hunk_marks_between_files = 1; start_threads(&opt); + } #else use_threads = 0; #endif @@ -940,32 +1066,51 @@ int cmd_grep(int argc, const char **argv, const char *prefix) paths[1] = NULL; } - if (!list.nr) { - int hit; - if (!cached) - setup_work_tree(); + if (show_in_pager && (cached || list.nr)) + die("--open-files-in-pager only works on the worktree"); - hit = grep_cache(&opt, paths, cached); - if (use_threads) - hit |= wait_all(); - return !hit; - } + if (show_in_pager && opt.pattern_list && !opt.pattern_list->next) { + const char *pager = path_list.items[0].string; + int len = strlen(pager); - if (cached) - die("both --cached and trees are given."); + if (len > 4 && is_dir_sep(pager[len - 5])) + pager += len - 4; - for (i = 0; i < list.nr; i++) { - struct object *real_obj; - real_obj = deref_tag(list.objects[i].item, NULL, 0); - if (grep_object(&opt, paths, real_obj, list.objects[i].name)) { - hit = 1; - if (opt.status_only) - break; + if (!strcmp("less", pager) || !strcmp("vi", pager)) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "+/%s%s", + strcmp("less", pager) ? "" : "*", + opt.pattern_list->pattern); + string_list_append(&path_list, buf.buf); + strbuf_detach(&buf, NULL); } } + if (!show_in_pager) + setup_pager(); + + + if (!use_index) { + if (cached) + die("--cached cannot be used with --no-index."); + if (list.nr) + die("--no-index cannot be used with revs."); + hit = grep_directory(&opt, paths); + } else if (!list.nr) { + if (!cached) + setup_work_tree(); + + hit = grep_cache(&opt, paths, cached); + } else { + if (cached) + die("both --cached and trees are given."); + hit = grep_objects(&opt, paths, &list); + } + if (use_threads) hit |= wait_all(); + if (hit && show_in_pager) + run_pager(&opt, prefix); free_grep_patterns(&opt); return !hit; } diff --git a/builtin-hash-object.c b/builtin/hash-object.c index 6a5f5b5f0e..080af1a01b 100644 --- a/builtin-hash-object.c +++ b/builtin/hash-object.c @@ -33,6 +33,8 @@ static void hash_object(const char *path, const char *type, int write_object, hash_fd(fd, type, write_object, vpath); } +static int no_filters; + static void hash_stdin_paths(const char *type, int write_objects) { struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; @@ -44,7 +46,8 @@ static void hash_stdin_paths(const char *type, int write_objects) die("line is badly quoted"); strbuf_swap(&buf, &nbuf); } - hash_object(buf.buf, type, write_objects, buf.buf); + hash_object(buf.buf, type, write_objects, + no_filters ? NULL : buf.buf); } strbuf_release(&buf); strbuf_release(&nbuf); @@ -60,7 +63,6 @@ static const char *type; static int write_object; static int hashstdin; static int stdin_paths; -static int no_filters; static const char *vpath; static const struct option hash_object_options[] = { @@ -100,8 +102,6 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) errstr = "Can't specify files with --stdin-paths"; else if (vpath) errstr = "Can't use --stdin-paths with --path"; - else if (no_filters) - errstr = "Can't use --stdin-paths with --no-filters"; } else { if (hashstdin > 1) diff --git a/builtin-help.c b/builtin/help.c index 3182a2bec4..61ff79839b 100644 --- a/builtin-help.c +++ b/builtin/help.c @@ -26,7 +26,7 @@ enum help_format { HELP_FORMAT_NONE, HELP_FORMAT_MAN, HELP_FORMAT_INFO, - HELP_FORMAT_WEB, + HELP_FORMAT_WEB }; static int show_all = 0; @@ -120,7 +120,7 @@ static void exec_woman_emacs(const char *path, const char *page) if (!path) path = "emacsclient"; strbuf_addf(&man_page, "(woman \"%s\")", page); - execlp(path, "emacsclient", "-e", man_page.buf, NULL); + execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } } @@ -148,7 +148,7 @@ static void exec_man_konqueror(const char *path, const char *page) } else path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); - execlp(path, filename, "newTab", man_page.buf, NULL); + execlp(path, filename, "newTab", man_page.buf, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } } @@ -157,7 +157,7 @@ static void exec_man_man(const char *path, const char *page) { if (!path) path = "man"; - execlp(path, "man", page, NULL); + execlp(path, "man", page, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } @@ -165,7 +165,7 @@ static void exec_man_cmd(const char *cmd, const char *page) { struct strbuf shell_cmd = STRBUF_INIT; strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); + execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL); warning("failed to exec '%s': %s", cmd, strerror(errno)); } @@ -372,7 +372,7 @@ static void show_info_page(const char *git_cmd) { const char *page = cmd_to_page(git_cmd); setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); - execlp("info", "info", "gitman", page, NULL); + execlp("info", "info", "gitman", page, (char *)NULL); die("no info viewer handled the request"); } @@ -398,7 +398,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page) #ifndef open_html static void open_html(const char *path) { - execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); + execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL); } #endif diff --git a/builtin-index-pack.c b/builtin/index-pack.c index 03d0cd2f47..fad76bf7a8 100644 --- a/builtin-index-pack.c +++ b/builtin/index-pack.c @@ -266,26 +266,23 @@ static void unlink_base_data(struct base_data *c) static void *unpack_entry_data(unsigned long offset, unsigned long size) { + int status; z_stream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); + git_inflate_init(&stream); stream.next_out = buf; stream.avail_out = size; - stream.next_in = fill(1); - stream.avail_in = input_len; - git_inflate_init(&stream); - for (;;) { - int ret = git_inflate(&stream, 0); - use(input_len - stream.avail_in); - if (stream.total_out == size && ret == Z_STREAM_END) - break; - if (ret != Z_OK) - bad_object(offset, "inflate returned %d", ret); + do { stream.next_in = fill(1); stream.avail_in = input_len; - } + status = git_inflate(&stream, 0); + use(input_len - stream.avail_in); + } while (status == Z_OK); + if (stream.total_out != size || status != Z_STREAM_END) + bad_object(offset, "inflate returned %d", status); git_inflate_end(&stream); return buf; } @@ -359,34 +356,38 @@ static void *get_data_from_pack(struct object_entry *obj) { off_t from = obj[0].idx.offset + obj[0].hdr_size; unsigned long len = obj[1].idx.offset - from; - unsigned long rdy = 0; - unsigned char *src, *data; + unsigned char *data, *inbuf; z_stream stream; - int st; + int status; - src = xmalloc(len); - data = src; - do { - ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy); - if (n < 0) - die_errno("cannot pread pack file"); - if (!n) - die("premature end of pack file, %lu bytes missing", - len - rdy); - rdy += n; - } while (rdy < len); data = xmalloc(obj->size); + inbuf = xmalloc((len < 64*1024) ? len : 64*1024); + memset(&stream, 0, sizeof(stream)); + git_inflate_init(&stream); stream.next_out = data; stream.avail_out = obj->size; - stream.next_in = src; - stream.avail_in = len; - git_inflate_init(&stream); - while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK); - git_inflate_end(&stream); - if (st != Z_STREAM_END || stream.total_out != obj->size) + + do { + ssize_t n = (len < 64*1024) ? len : 64*1024; + n = pread(pack_fd, inbuf, n, from); + if (n < 0) + die_errno("cannot pread pack file"); + if (!n) + die("premature end of pack file, %lu bytes missing", len); + from += n; + len -= n; + stream.next_in = inbuf; + stream.avail_in = n; + status = git_inflate(&stream, 0); + } while (len && status == Z_OK && !stream.avail_in); + + /* This has been inflated OK when first encountered, so... */ + if (status != Z_STREAM_END || stream.total_out != obj->size) die("serious inflate inconsistency"); - free(src); + + git_inflate_end(&stream); + free(inbuf); return data; } @@ -668,25 +669,25 @@ static void parse_pack_objects(unsigned char *sha1) static int write_compressed(struct sha1file *f, void *in, unsigned int size) { z_stream stream; - unsigned long maxsize; - void *out; + int status; + unsigned char outbuf[4096]; memset(&stream, 0, sizeof(stream)); deflateInit(&stream, zlib_compression_level); - maxsize = deflateBound(&stream, size); - out = xmalloc(maxsize); - - /* Compress it */ stream.next_in = in; stream.avail_in = size; - stream.next_out = out; - stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK); - deflateEnd(&stream); + do { + stream.next_out = outbuf; + stream.avail_out = sizeof(outbuf); + status = deflate(&stream, Z_FINISH); + sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out); + } while (status == Z_OK); + + if (status != Z_STREAM_END) + die("unable to deflate appended object (%d)", status); size = stream.total_out; - sha1write(f, out, size); - free(out); + deflateEnd(&stream); return size; } @@ -883,6 +884,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage(index_pack_usage); + read_replace_refs = 0; + /* * We wish to read the repository's config file if any, and * for that it is necessary to call setup_git_directory_gently(). diff --git a/builtin-init-db.c b/builtin/init-db.c index dd84caecbc..0271285fad 100644 --- a/builtin-init-db.c +++ b/builtin/init-db.c @@ -20,6 +20,7 @@ static int init_is_bare_repository = 0; static int init_shared_repository = -1; +static const char *init_db_template_dir; static void safe_create_dir(const char *dir, int share) { @@ -121,6 +122,8 @@ static void copy_templates(const char *template_dir) if (!template_dir) template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); if (!template_dir) + template_dir = init_db_template_dir; + if (!template_dir) template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR); if (!template_dir[0]) return; @@ -165,6 +168,14 @@ static void copy_templates(const char *template_dir) closedir(dir); } +static int git_init_db_config(const char *k, const char *v, void *cb) +{ + if (!strcmp(k, "init.templatedir")) + return git_config_pathname(&init_db_template_dir, k, v); + + return 0; +} + static int create_default_files(const char *template_path) { const char *git_dir = get_git_dir(); @@ -190,6 +201,9 @@ static int create_default_files(const char *template_path) safe_create_dir(git_path("refs/heads"), 1); safe_create_dir(git_path("refs/tags"), 1); + /* Just look for `init.templatedir` */ + git_config(git_init_db_config, NULL); + /* First copy the templates -- we might have the default * config file there, in which case we would want to read * from it after installing. @@ -331,11 +345,14 @@ int init_db(const char *template_dir, unsigned int flags) git_config_set("receive.denyNonFastforwards", "true"); } - if (!(flags & INIT_DB_QUIET)) - printf("%s%s Git repository in %s/\n", + if (!(flags & INIT_DB_QUIET)) { + const char *git_dir = get_git_dir(); + int len = strlen(git_dir); + printf("%s%s Git repository in %s%s\n", reinit ? "Reinitialized existing" : "Initialized empty", shared_repository ? " shared" : "", - get_git_dir()); + git_dir, len && git_dir[len-1] != '/' ? "/" : ""); + } return 0; } @@ -446,7 +463,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) static char git_dir[PATH_MAX+1]; setenv(GIT_DIR_ENVIRONMENT, - getcwd(git_dir, sizeof(git_dir)), 0); + getcwd(git_dir, sizeof(git_dir)), argc > 0); } if (init_shared_repository != -1) diff --git a/builtin-log.c b/builtin/log.c index 76962e1b08..08b872263c 100644 --- a/builtin-log.c +++ b/builtin/log.c @@ -24,6 +24,7 @@ static const char *default_date_mode = NULL; static int default_show_root = 1; +static int decoration_style; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; @@ -31,11 +32,29 @@ static const char * const builtin_log_usage = "git log [<options>] [<since>..<until>] [[--] <path>...]\n" " or: git show [options] <object>..."; +static int parse_decoration_style(const char *var, const char *value) +{ + switch (git_config_maybe_bool(var, value)) { + case 1: + return DECORATE_SHORT_REFS; + case 0: + return 0; + default: + break; + } + if (!strcmp(value, "full")) + return DECORATE_FULL_REFS; + else if (!strcmp(value, "short")) + return DECORATE_SHORT_REFS; + return -1; +} + static void cmd_log_init(int argc, const char **argv, const char *prefix, - struct rev_info *rev) + struct rev_info *rev, struct setup_revision_opt *opt) { int i; - int decoration_style = 0; + int decoration_given = 0; + struct userformat_want w; rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; @@ -56,10 +75,15 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, */ if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_log_usage); - argc = setup_revisions(argc, argv, rev, "HEAD"); + argc = setup_revisions(argc, argv, rev, opt); + + memset(&w, 0, sizeof(w)); + userformat_find_requirements(NULL, &w); - if (!rev->show_notes_given && !rev->pretty_given) + if (!rev->show_notes_given && (!rev->pretty_given || w.notes)) rev->show_notes = 1; + if (rev->show_notes) + init_display_notes(&rev->notes_opt); if (rev->diffopt.pickaxe || rev->diffopt.filter) rev->always_show_header = 0; @@ -72,14 +96,15 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, const char *arg = argv[i]; if (!strcmp(arg, "--decorate")) { decoration_style = DECORATE_SHORT_REFS; + decoration_given = 1; } else if (!prefixcmp(arg, "--decorate=")) { const char *v = skip_prefix(arg, "--decorate="); - if (!strcmp(v, "full")) - decoration_style = DECORATE_FULL_REFS; - else if (!strcmp(v, "short")) - decoration_style = DECORATE_SHORT_REFS; - else + decoration_style = parse_decoration_style(arg, v); + if (decoration_style < 0) die("invalid --decorate option: %s", arg); + decoration_given = 1; + } else if (!strcmp(arg, "--no-decorate")) { + decoration_style = 0; } else if (!strcmp(arg, "--source")) { rev->show_source = 1; } else if (!strcmp(arg, "-h")) { @@ -87,6 +112,15 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, } else die("unrecognized argument: %s", arg); } + + /* + * defeat log.decorate configuration interacting with --pretty=raw + * from the command line. + */ + if (!decoration_given && rev->pretty_given + && rev->commit_format == CMIT_FMT_RAW) + decoration_style = 0; + if (decoration_style) { rev->show_decorations = 1; load_ref_decorations(decoration_style); @@ -252,16 +286,26 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_patch_subject_prefix, var, value); if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); + if (!strcmp(var, "log.decorate")) { + decoration_style = parse_decoration_style(var, value); + if (decoration_style < 0) + decoration_style = 0; /* maybe warn? */ + return 0; + } if (!strcmp(var, "log.showroot")) { default_show_root = git_config_bool(var, value); return 0; } + if (!prefixcmp(var, "color.decorate.")) + return parse_decorate_color_config(var, 15, value); + return git_diff_ui_config(var, value, cb); } int cmd_whatchanged(int argc, const char **argv, const char *prefix) { struct rev_info rev; + struct setup_revision_opt opt; git_config(git_log_config, NULL); @@ -271,7 +315,9 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); rev.diff = 1; rev.simplify_history = 0; - cmd_log_init(argc, argv, prefix, &rev); + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; return cmd_log_walk(&rev); @@ -324,10 +370,26 @@ static int show_tree_object(const unsigned char *sha1, return 0; } +static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt) +{ + if (rev->ignore_merges) { + /* There was no "-m" on the command line */ + rev->ignore_merges = 0; + if (!rev->first_parent_only && !rev->combine_merges) { + /* No "--first-parent", "-c", nor "--cc" */ + rev->combine_merges = 1; + rev->dense_combined_merges = 1; + } + } + if (!rev->diffopt.output_format) + rev->diffopt.output_format = DIFF_FORMAT_PATCH; +} + int cmd_show(int argc, const char **argv, const char *prefix) { struct rev_info rev; struct object_array_entry *objects; + struct setup_revision_opt opt; int i, count, ret = 0; git_config(git_log_config, NULL); @@ -337,12 +399,12 @@ int cmd_show(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); rev.diff = 1; - rev.combine_merges = 1; - rev.dense_combined_merges = 1; rev.always_show_header = 1; - rev.ignore_merges = 0; rev.no_walk = 1; - cmd_log_init(argc, argv, prefix, &rev); + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + opt.tweak = show_rev_tweak_rev; + cmd_log_init(argc, argv, prefix, &rev, &opt); count = rev.pending.nr; objects = rev.pending.objects; @@ -405,6 +467,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) int cmd_log_reflog(int argc, const char **argv, const char *prefix) { struct rev_info rev; + struct setup_revision_opt opt; git_config(git_log_config, NULL); @@ -415,7 +478,9 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) init_reflog_walk(&rev.reflog_info); rev.abbrev_commit = 1; rev.verbose_header = 1; - cmd_log_init(argc, argv, prefix, &rev); + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + cmd_log_init(argc, argv, prefix, &rev, &opt); /* * This means that we override whatever commit format the user gave @@ -438,6 +503,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) int cmd_log(int argc, const char **argv, const char *prefix) { struct rev_info rev; + struct setup_revision_opt opt; git_config(git_log_config, NULL); @@ -446,7 +512,9 @@ int cmd_log(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); rev.always_show_header = 1; - cmd_log_init(argc, argv, prefix, &rev); + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + cmd_log_init(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -458,41 +526,35 @@ static int auto_number = 1; static char *default_attach = NULL; -static char **extra_hdr; -static int extra_hdr_nr; -static int extra_hdr_alloc; - -static char **extra_to; -static int extra_to_nr; -static int extra_to_alloc; - -static char **extra_cc; -static int extra_cc_nr; -static int extra_cc_alloc; +static struct string_list extra_hdr; +static struct string_list extra_to; +static struct string_list extra_cc; static void add_header(const char *value) { + struct string_list_item *item; int len = strlen(value); while (len && value[len - 1] == '\n') len--; + if (!strncasecmp(value, "to: ", 4)) { - ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc); - extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4); - return; - } - if (!strncasecmp(value, "cc: ", 4)) { - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4); - return; + item = string_list_append(&extra_to, value + 4); + len -= 4; + } else if (!strncasecmp(value, "cc: ", 4)) { + item = string_list_append(&extra_cc, value + 4); + len -= 4; + } else { + item = string_list_append(&extra_hdr, value); } - ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc); - extra_hdr[extra_hdr_nr++] = xstrndup(value, len); + + item->string[len] = '\0'; } #define THREAD_SHALLOW 1 #define THREAD_DEEP 2 -static int thread = 0; -static int do_signoff = 0; +static int thread; +static int do_signoff; +static const char *signature = git_version_string; static int git_format_config(const char *var, const char *value, void *cb) { @@ -504,11 +566,16 @@ static int git_format_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "format.suffix")) return git_config_string(&fmt_patch_suffix, var, value); + if (!strcmp(var, "format.to")) { + if (!value) + return config_error_nonbool(var); + string_list_append(&extra_to, value); + return 0; + } if (!strcmp(var, "format.cc")) { if (!value) return config_error_nonbool(var); - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrdup(value); + string_list_append(&extra_cc, value); return 0; } if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { @@ -546,6 +613,8 @@ static int git_format_config(const char *var, const char *value, void *cb) do_signoff = git_config_bool(var, value); return 0; } + if (!strcmp(var, "format.signature")) + return git_config_string(&signature, var, value); return git_log_config(var, value, cb); } @@ -640,6 +709,12 @@ static void gen_message_id(struct rev_info *info, char *base) info->message_id = strbuf_detach(&buf, NULL); } +static void print_signature(void) +{ + if (signature && *signature) + printf("-- \n%s\n\n", signature); +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, int numbered, int numbered_files, struct commit *origin, @@ -733,6 +808,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, diff_flush(&opts); printf("\n"); + print_signature(); } static const char *clean_message_id(const char *msg_id) @@ -871,14 +947,31 @@ static int inline_callback(const struct option *opt, const char *arg, int unset) static int header_callback(const struct option *opt, const char *arg, int unset) { - add_header(arg); + if (unset) { + string_list_clear(&extra_hdr, 0); + string_list_clear(&extra_to, 0); + string_list_clear(&extra_cc, 0); + } else { + add_header(arg); + } + return 0; +} + +static int to_callback(const struct option *opt, const char *arg, int unset) +{ + if (unset) + string_list_clear(&extra_to, 0); + else + string_list_append(&extra_to, arg); return 0; } static int cc_callback(const struct option *opt, const char *arg, int unset) { - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrdup(arg); + if (unset) + string_list_clear(&extra_cc, 0); + else + string_list_append(&extra_cc, arg); return 0; } @@ -887,6 +980,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct commit *commit; struct commit **list = NULL; struct rev_info rev; + struct setup_revision_opt s_r_opt; int nr = 0, total, i; int use_stdout = 0; int start_number = -1; @@ -937,10 +1031,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_NONEG | PARSE_OPT_NOARG }, OPT_GROUP("Messaging"), { OPTION_CALLBACK, 0, "add-header", NULL, "header", - "add email header", PARSE_OPT_NONEG, - header_callback }, + "add email header", 0, header_callback }, + { OPTION_CALLBACK, 0, "to", NULL, "email", "add To: header", + 0, to_callback }, { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header", - PARSE_OPT_NONEG, cc_callback }, + 0, cc_callback }, OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id", "make first mail a reply to <message-id>"), { OPTION_CALLBACK, 0, "attach", &rev, "boundary", @@ -953,9 +1048,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "thread", &thread, "style", "enable message threading, styles: shallow, deep", PARSE_OPT_OPTARG, thread_callback }, + OPT_STRING(0, "signature", &signature, "signature", + "add a signature"), OPT_END() }; + extra_hdr.strdup_strings = 1; + extra_to.strdup_strings = 1; + extra_cc.strdup_strings = 1; git_config(git_format_config, NULL); init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_EMAIL; @@ -964,8 +1064,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.combine_merges = 0; rev.ignore_merges = 1; DIFF_OPT_SET(&rev.diffopt, RECURSIVE); - rev.subject_prefix = fmt_patch_subject_prefix; + memset(&s_r_opt, 0, sizeof(s_r_opt)); + s_r_opt.def = "HEAD"; if (default_attach) { rev.mime_boundary = default_attach; @@ -992,29 +1093,29 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) add_signoff = xmemdupz(committer, endpos - committer + 1); } - for (i = 0; i < extra_hdr_nr; i++) { - strbuf_addstr(&buf, extra_hdr[i]); + for (i = 0; i < extra_hdr.nr; i++) { + strbuf_addstr(&buf, extra_hdr.items[i].string); strbuf_addch(&buf, '\n'); } - if (extra_to_nr) + if (extra_to.nr) strbuf_addstr(&buf, "To: "); - for (i = 0; i < extra_to_nr; i++) { + for (i = 0; i < extra_to.nr; i++) { if (i) strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_to[i]); - if (i + 1 < extra_to_nr) + strbuf_addstr(&buf, extra_to.items[i].string); + if (i + 1 < extra_to.nr) strbuf_addch(&buf, ','); strbuf_addch(&buf, '\n'); } - if (extra_cc_nr) + if (extra_cc.nr) strbuf_addstr(&buf, "Cc: "); - for (i = 0; i < extra_cc_nr; i++) { + for (i = 0; i < extra_cc.nr; i++) { if (i) strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_cc[i]); - if (i + 1 < extra_cc_nr) + strbuf_addstr(&buf, extra_cc.items[i].string); + if (i + 1 < extra_cc.nr) strbuf_addch(&buf, ','); strbuf_addch(&buf, '\n'); } @@ -1037,7 +1138,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (keep_subject && subject_prefix) die ("--subject-prefix and -k are mutually exclusive."); - argc = setup_revisions(argc, argv, &rev, "HEAD"); + argc = setup_revisions(argc, argv, &rev, &s_r_opt); if (argc > 1) die ("unrecognized argument: %s", argv[1]); @@ -1059,6 +1160,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) DIFF_OPT_SET(&rev.diffopt, BINARY); + if (rev.show_notes) + init_display_notes(&rev.notes_opt); + if (!use_stdout) output_directory = set_outdir(prefix, output_directory); @@ -1150,7 +1254,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); if (in_reply_to) { const char *msgid = clean_message_id(in_reply_to); - string_list_append(msgid, rev.ref_message_ids); + string_list_append(rev.ref_message_ids, msgid); } rev.numbered_files = numbered_files; rev.patch_suffix = fmt_patch_suffix; @@ -1197,8 +1301,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) && (!cover_letter || rev.nr > 1)) free(rev.message_id); else - string_list_append(rev.message_id, - rev.ref_message_ids); + string_list_append(rev.ref_message_ids, + rev.message_id); } gen_message_id(&rev, sha1_to_hex(commit->object.sha1)); } @@ -1224,12 +1328,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) mime_boundary_leader, rev.mime_boundary); else - printf("-- \n%s\n\n", git_version_string); + print_signature(); } if (!use_stdout) fclose(stdout); } free(list); + string_list_clear(&extra_to, 0); + string_list_clear(&extra_cc, 0); + string_list_clear(&extra_hdr, 0); if (ignore_if_in_upstream) free_patch_ids(&ids); return 0; @@ -1249,8 +1356,11 @@ static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) return -1; } -static const char cherry_usage[] = -"git cherry [-v] [<upstream> [<head> [<limit>]]]"; +static const char * const cherry_usage[] = { + "git cherry [-v] [<upstream> [<head> [<limit>]]]", + NULL +}; + int cmd_cherry(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1261,26 +1371,25 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) const char *upstream; const char *head = "HEAD"; const char *limit = NULL; - int verbose = 0; + int verbose = 0, abbrev = 0; - if (argc > 1 && !strcmp(argv[1], "-v")) { - verbose = 1; - argc--; - argv++; - } + struct option options[] = { + OPT__ABBREV(&abbrev), + OPT__VERBOSE(&verbose), + OPT_END() + }; - if (argc > 1 && !strcmp(argv[1], "-h")) - usage(cherry_usage); + argc = parse_options(argc, argv, prefix, options, cherry_usage, 0); switch (argc) { - case 4: - limit = argv[3]; - /* FALLTHROUGH */ case 3: - head = argv[2]; + limit = argv[2]; /* FALLTHROUGH */ case 2: - upstream = argv[1]; + head = argv[1]; + /* FALLTHROUGH */ + case 1: + upstream = argv[0]; break; default: current_branch = branch_get(NULL); @@ -1290,7 +1399,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) fprintf(stderr, "Could not find a tracked" " remote branch, please" " specify <upstream> manually.\n"); - usage(cherry_usage); + usage_with_options(cherry_usage, options); } upstream = current_branch->merge[0]->dst; @@ -1343,12 +1452,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) pretty_print_commit(CMIT_FMT_ONELINE, commit, &buf, &ctx); printf("%c %s %s\n", sign, - sha1_to_hex(commit->object.sha1), buf.buf); + find_unique_abbrev(commit->object.sha1, abbrev), + buf.buf); strbuf_release(&buf); } else { printf("%c %s\n", sign, - sha1_to_hex(commit->object.sha1)); + find_unique_abbrev(commit->object.sha1, abbrev)); } list = list->next; diff --git a/builtin-ls-files.c b/builtin/ls-files.c index b065061392..cc202c5f6f 100644 --- a/builtin-ls-files.c +++ b/builtin/ls-files.c @@ -25,9 +25,11 @@ static int show_modified; static int show_killed; static int show_valid_bit; static int line_terminator = '\n'; +static int debug_mode; +static const char *prefix; +static int max_prefix_len; static int prefix_len; -static int prefix_offset; static const char **pathspec; static int error_unmatch; static char *ps_matched; @@ -43,10 +45,15 @@ static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; +static void write_name(const char* name, size_t len) +{ + write_name_quoted_relative(name, len, prefix, prefix_len, stdout, + line_terminator); +} + static void show_dir_entry(const char *tag, struct dir_entry *ent) { - int len = prefix_len; - int offset = prefix_offset; + int len = max_prefix_len; if (len >= ent->len) die("git ls-files: internal error - directory entry not superset of prefix"); @@ -55,7 +62,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) return; fputs(tag, stdout); - write_name_quoted(ent->name + offset, stdout, line_terminator); + write_name(ent->name, ent->len); } static void show_other_files(struct dir_struct *dir) @@ -121,8 +128,7 @@ static void show_killed_files(struct dir_struct *dir) static void show_ce_entry(const char *tag, struct cache_entry *ce) { - int len = prefix_len; - int offset = prefix_offset; + int len = max_prefix_len; if (len >= ce_namelen(ce)) die("git ls-files: internal error - cache entry not superset of prefix"); @@ -153,46 +159,49 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) printf("%s%06o %s %d\t", tag, ce->ce_mode, - abbrev ? find_unique_abbrev(ce->sha1,abbrev) - : sha1_to_hex(ce->sha1), + find_unique_abbrev(ce->sha1,abbrev), ce_stage(ce)); } - write_name_quoted(ce->name + offset, stdout, line_terminator); + write_name(ce->name, ce_namelen(ce)); + if (debug_mode) { + printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec); + printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec); + printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino); + printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid); + printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags); + } } static int show_one_ru(struct string_list_item *item, void *cbdata) { - int offset = prefix_offset; const char *path = item->string; struct resolve_undo_info *ui = item->util; int i, len; len = strlen(path); - if (len < prefix_len) + if (len < max_prefix_len) return 0; /* outside of the prefix */ - if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched)) + if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched)) return 0; /* uninterested */ for (i = 0; i < 3; i++) { if (!ui->mode[i]) continue; printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], - abbrev - ? find_unique_abbrev(ui->sha1[i], abbrev) - : sha1_to_hex(ui->sha1[i]), + find_unique_abbrev(ui->sha1[i], abbrev), i + 1); - write_name_quoted(path + offset, stdout, line_terminator); + write_name(path, len); } return 0; } -static void show_ru_info(const char *prefix) +static void show_ru_info(void) { if (!the_index.resolve_undo) return; - for_each_string_list(show_one_ru, the_index.resolve_undo, NULL); + for_each_string_list(the_index.resolve_undo, show_one_ru, NULL); } -static void show_files(struct dir_struct *dir, const char *prefix) +static void show_files(struct dir_struct *dir) { int i; @@ -246,7 +255,7 @@ static void show_files(struct dir_struct *dir, const char *prefix) */ static void prune_cache(const char *prefix) { - int pos = cache_name_pos(prefix, prefix_len); + int pos = cache_name_pos(prefix, max_prefix_len); unsigned int first, last; if (pos < 0) @@ -259,7 +268,7 @@ static void prune_cache(const char *prefix) while (last > first) { int next = (last + first) >> 1; struct cache_entry *ce = active_cache[next]; - if (!strncmp(ce->name, prefix, prefix_len)) { + if (!strncmp(ce->name, prefix, max_prefix_len)) { first = next+1; continue; } @@ -268,11 +277,16 @@ static void prune_cache(const char *prefix) active_nr = last; } -static const char *verify_pathspec(const char *prefix) +static const char *pathspec_prefix(const char *prefix) { const char **p, *n, *prev; unsigned long max; + if (!pathspec) { + max_prefix_len = prefix ? strlen(prefix) : 0; + return prefix; + } + prev = NULL; max = PATH_MAX; for (p = pathspec; (n = *p) != NULL; p++) { @@ -294,10 +308,7 @@ static const char *verify_pathspec(const char *prefix) } } - if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) - die("git ls-files: cannot generate relative filenames containing '..'"); - - prefix_len = max; + max_prefix_len = max; return max ? xmemdupz(prev, max) : NULL; } @@ -377,7 +388,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } } -int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset) +int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len) { /* * Make sure all pathspec matched; otherwise it is an error. @@ -407,7 +418,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_ continue; error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_offset); + pathspec[num] + prefix_len); errors++; } return errors; @@ -459,9 +470,10 @@ static int option_parse_exclude_standard(const struct option *opt, return 0; } -int cmd_ls_files(int argc, const char **argv, const char *prefix) +int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) { int require_work_tree = 0, show_tag = 0; + const char *max_prefix; struct dir_struct dir; struct option builtin_ls_files_options[] = { { OPTION_CALLBACK, 'z', NULL, NULL, NULL, @@ -507,7 +519,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, "add the standard git exclusions", PARSE_OPT_NOARG, option_parse_exclude_standard }, - { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL, + { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL, "make the output relative to the project top directory", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, OPT_BOOLEAN(0, "error-unmatch", &error_unmatch, @@ -515,12 +527,14 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) OPT_STRING(0, "with-tree", &with_tree, "tree-ish", "pretend that paths removed since <tree-ish> are still present"), OPT__ABBREV(&abbrev), + OPT_BOOLEAN(0, "debug", &debug_mode, "show debugging data"), OPT_END() }; memset(&dir, 0, sizeof(dir)); + prefix = cmd_prefix; if (prefix) - prefix_offset = strlen(prefix); + prefix_len = strlen(prefix); git_config(git_default_config, NULL); if (read_cache() < 0) @@ -558,9 +572,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) if (pathspec) strip_trailing_slash_from_submodules(); - /* Verify that the pathspec matches the prefix */ - if (pathspec) - prefix = verify_pathspec(prefix); + /* Find common prefix for all pathspec's */ + max_prefix = pathspec_prefix(prefix); /* Treat unmatching pathspec elements as errors */ if (pathspec && error_unmatch) { @@ -578,8 +591,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) show_killed | show_modified | show_resolve_undo)) show_cached = 1; - if (prefix) - prune_cache(prefix); + if (max_prefix) + prune_cache(max_prefix); if (with_tree) { /* * Basic sanity check; show-stages and show-unmerged @@ -587,15 +600,15 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) */ if (show_stage || show_unmerged) die("ls-files --with-tree is incompatible with -s or -u"); - overlay_tree_on_cache(with_tree, prefix); + overlay_tree_on_cache(with_tree, max_prefix); } - show_files(&dir, prefix); + show_files(&dir); if (show_resolve_undo) - show_ru_info(prefix); + show_ru_info(); if (ps_matched) { int bad; - bad = report_path_error(ps_matched, pathspec, prefix_offset); + bad = report_path_error(ps_matched, pathspec, prefix_len); if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); diff --git a/builtin-ls-remote.c b/builtin/ls-remote.c index 70f5622d9d..34480cfad6 100644 --- a/builtin-ls-remote.c +++ b/builtin/ls-remote.c @@ -4,7 +4,8 @@ #include "remote.h" static const char ls_remote_usage[] = -"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>] <repository> <refs>..."; +"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" +" [-q|--quiet] [<repository> [<refs>...]]"; /* * Is there one among the list of patterns that match the tail part @@ -33,6 +34,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) const char *dest = NULL; int nongit; unsigned flags = 0; + int quiet = 0; const char *uploadpack = NULL; const char **pattern = NULL; @@ -66,6 +68,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) flags |= REF_NORMAL; continue; } + if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { + quiet = 1; + continue; + } usage(ls_remote_usage); } dest = arg; @@ -73,9 +79,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) break; } - if (!dest) - usage(ls_remote_usage); - if (argv[i]) { int j; pattern = xcalloc(sizeof(const char *), argc - i + 1); @@ -87,6 +90,11 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } } remote = remote_get(dest); + if (!remote) { + if (dest) + die("bad repository '%s'", dest); + die("No remote configured to list refs from."); + } if (!remote->url_nr) die("remote %s has no configured URL", dest); transport = transport_get(remote, NULL); @@ -96,6 +104,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) ref = transport_get_remote_refs(transport); if (transport_disconnect(transport)) return 1; + + if (!dest && !quiet) + fprintf(stderr, "From %s\n", *remote->url); for ( ; ref; ref = ref->next) { if (!check_ref_type(ref, flags)) continue; diff --git a/builtin-ls-tree.c b/builtin/ls-tree.c index 4484185afc..dc86b0d9a9 100644 --- a/builtin-ls-tree.c +++ b/builtin/ls-tree.c @@ -103,13 +103,11 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, } else strcpy(size_text, "-"); printf("%06o %s %s %7s\t", mode, type, - abbrev ? find_unique_abbrev(sha1, abbrev) - : sha1_to_hex(sha1), + find_unique_abbrev(sha1, abbrev), size_text); } else printf("%06o %s %s\t", mode, type, - abbrev ? find_unique_abbrev(sha1, abbrev) - : sha1_to_hex(sha1)); + find_unique_abbrev(sha1, abbrev)); } write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix, pathname, stdout, line_termination); diff --git a/builtin-mailinfo.c b/builtin/mailinfo.c index ce2ef6bede..2320d981ce 100644 --- a/builtin-mailinfo.c +++ b/builtin/mailinfo.c @@ -17,10 +17,10 @@ static struct strbuf name = STRBUF_INIT; static struct strbuf email = STRBUF_INIT; static enum { - TE_DONTCARE, TE_QP, TE_BASE64, + TE_DONTCARE, TE_QP, TE_BASE64 } transfer_encoding; static enum { - TYPE_TEXT, TYPE_OTHER, + TYPE_TEXT, TYPE_OTHER } message_type; static struct strbuf charset = STRBUF_INIT; @@ -746,7 +746,8 @@ static int is_scissors_line(const struct strbuf *line) continue; } if (i + 1 < len && - (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) { + (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) || + !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) { in_perforation = 1; perforation += 2; scissors += 2; diff --git a/builtin-mailsplit.c b/builtin/mailsplit.c index 207e358ed1..e4560da191 100644 --- a/builtin-mailsplit.c +++ b/builtin/mailsplit.c @@ -10,7 +10,7 @@ #include "strbuf.h" static const char git_mailsplit_usage[] = -"git mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> [<mbox>|<Maildir>...]"; +"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [<mbox>|<Maildir>...]"; static int is_from_line(const char *line, int len) { @@ -121,7 +121,7 @@ static int populate_maildir_list(struct string_list *list, const char *path) if (dent->d_name[0] == '.') continue; snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name); - string_list_insert(name, list); + string_list_insert(list, name); } closedir(dir); diff --git a/builtin-merge-base.c b/builtin/merge-base.c index 54e7ec2237..54e7ec2237 100644 --- a/builtin-merge-base.c +++ b/builtin/merge-base.c diff --git a/builtin-merge-file.c b/builtin/merge-file.c index 1e70073a7e..b8e9e5ba01 100644 --- a/builtin-merge-file.c +++ b/builtin/merge-file.c @@ -25,32 +25,37 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) const char *names[3] = { NULL, NULL, NULL }; mmfile_t mmfs[3]; mmbuffer_t result = {NULL, 0}; - xmparam_t xmp = {{XDF_NEED_MINIMAL}}; + xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; - int level = XDL_MERGE_ZEALOUS_ALNUM; - int style = 0, quiet = 0; - int favor = 0; + int quiet = 0; int nongit; - struct option options[] = { OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"), - OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3), - OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version", + OPT_SET_INT(0, "diff3", &xmp.style, "use a diff3 based merge", XDL_MERGE_DIFF3), + OPT_SET_INT(0, "ours", &xmp.favor, "for conflicts, use our version", XDL_MERGE_FAVOR_OURS), - OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version", + OPT_SET_INT(0, "theirs", &xmp.favor, "for conflicts, use their version", XDL_MERGE_FAVOR_THEIRS), + OPT_SET_INT(0, "union", &xmp.favor, "for conflicts, use a union version", + XDL_MERGE_FAVOR_UNION), + OPT_INTEGER(0, "marker-size", &xmp.marker_size, + "for conflicts, use this marker size"), OPT__QUIET(&quiet), OPT_CALLBACK('L', NULL, names, "name", "set labels for file1/orig_file/file2", &label_cb), OPT_END(), }; + xmp.level = XDL_MERGE_ZEALOUS_ALNUM; + xmp.style = 0; + xmp.favor = 0; + prefix = setup_git_directory_gently(&nongit); if (!nongit) { /* Read the configuration file */ git_config(git_xmerge_config, NULL); if (0 <= git_xmerge_style) - style = git_xmerge_style; + xmp.style = git_xmerge_style; } argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0); @@ -72,8 +77,10 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) argv[i]); } - ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], - &xmp, XDL_MERGE_FLAGS(level, style, favor), &result); + xmp.ancestor = names[1]; + xmp.file1 = names[0]; + xmp.file2 = names[2]; + ret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result); for (i = 0; i < 3; i++) free(mmfs[i].ptr); diff --git a/builtin-merge-index.c b/builtin/merge-index.c index 2c4cf5e559..2c4cf5e559 100644 --- a/builtin-merge-index.c +++ b/builtin/merge-index.c diff --git a/builtin-merge-ours.c b/builtin/merge-ours.c index 684411694f..684411694f 100644 --- a/builtin-merge-ours.c +++ b/builtin/merge-ours.c diff --git a/builtin-merge-recursive.c b/builtin/merge-recursive.c index d8875d5892..d8875d5892 100644 --- a/builtin-merge-recursive.c +++ b/builtin/merge-recursive.c diff --git a/builtin-merge-tree.c b/builtin/merge-tree.c index a4a4f2ce4c..9b25ddc979 100644 --- a/builtin-merge-tree.c +++ b/builtin/merge-tree.c @@ -60,6 +60,7 @@ static void *result(struct merge_list *entry, unsigned long *size) { enum object_type type; struct blob *base, *our, *their; + const char *path = entry->path; if (!entry->stage) return read_sha1_file(entry->blob->object.sha1, &type, size); @@ -76,7 +77,7 @@ static void *result(struct merge_list *entry, unsigned long *size) their = NULL; if (entry) their = entry->blob; - return merge_file(entry->path, base, our, their, size); + return merge_file(path, base, our, their, size); } static void *origin(struct merge_list *entry, unsigned long *size) @@ -106,7 +107,7 @@ static void show_diff(struct merge_list *entry) xdemitconf_t xecfg; xdemitcb_t ecb; - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = show_outf; diff --git a/builtin-merge.c b/builtin/merge.c index 3aaec7bed7..37ce4f589f 100644 --- a/builtin-merge.c +++ b/builtin/merge.c @@ -548,13 +548,53 @@ static void write_tree_trivial(unsigned char *sha1) die("git write-tree failed to write a tree"); } -static int try_merge_strategy(const char *strategy, struct commit_list *common, - const char *head_arg) +int try_merge_command(const char *strategy, struct commit_list *common, + const char *head_arg, struct commit_list *remotes) { const char **args; int i = 0, x = 0, ret; struct commit_list *j; struct strbuf buf = STRBUF_INIT; + + args = xmalloc((4 + xopts_nr + commit_list_count(common) + + commit_list_count(remotes)) * sizeof(char *)); + strbuf_addf(&buf, "merge-%s", strategy); + args[i++] = buf.buf; + for (x = 0; x < xopts_nr; x++) { + char *s = xmalloc(strlen(xopts[x])+2+1); + strcpy(s, "--"); + strcpy(s+2, xopts[x]); + args[i++] = s; + } + for (j = common; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = "--"; + args[i++] = head_arg; + for (j = remotes; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i] = NULL; + ret = run_command_v_opt(args, RUN_GIT_CMD); + strbuf_release(&buf); + i = 1; + for (x = 0; x < xopts_nr; x++) + free((void *)args[i++]); + for (j = common; j; j = j->next) + free((void *)args[i++]); + i += 2; + for (j = remotes; j; j = j->next) + free((void *)args[i++]); + free(args); + discard_cache(); + if (read_cache() < 0) + die("failed to read the cache"); + resolve_undo_clear(); + + return ret; +} + +static int try_merge_strategy(const char *strategy, struct commit_list *common, + const char *head_arg) +{ int index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); @@ -567,12 +607,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, rollback_lock_file(lock); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { - int clean; + int clean, x; struct commit *result; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int index_fd; struct commit_list *reversed = NULL; struct merge_options o; + struct commit_list *j; if (remoteheads->next) { error("Not handling anything other than two heads merge."); @@ -612,39 +653,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, rollback_lock_file(lock); return clean ? 0 : 1; } else { - args = xmalloc((4 + xopts_nr + commit_list_count(common) + - commit_list_count(remoteheads)) * sizeof(char *)); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (x = 0; x < xopts_nr; x++) { - char *s = xmalloc(strlen(xopts[x])+2+1); - strcpy(s, "--"); - strcpy(s+2, xopts[x]); - args[i++] = s; - } - for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remoteheads; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (x = 0; x < xopts_nr; x++) - free((void *)args[i++]); - for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; - for (j = remoteheads; j; j = j->next) - free((void *)args[i++]); - free(args); - discard_cache(); - if (read_cache() < 0) - die("failed to read the cache"); - resolve_undo_clear(); - return ret; + return try_merge_command(strategy, common, head_arg, remoteheads); } } @@ -667,7 +676,7 @@ static int count_unmerged_entries(void) return ret; } -static int checkout_fast_forward(unsigned char *head, unsigned char *remote) +int checkout_fast_forward(const unsigned char *head, const unsigned char *remote) { struct tree *trees[MAX_UNPACK_TREES]; struct unpack_trees_options opts; @@ -973,7 +982,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) reset_hard(remote_head->sha1, 0); return 0; } else { - struct strbuf msg = STRBUF_INIT; + struct strbuf merge_names = STRBUF_INIT; /* We are invoked directly as the first-class UI. */ head_arg = "HEAD"; @@ -986,13 +995,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * codepath so we discard the error in this * loop. */ - if (!have_message) { - for (i = 0; i < argc; i++) - merge_name(argv[i], &msg); - fmt_merge_msg(option_log, &msg, &merge_msg); - if (merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len-1); - } + for (i = 0; i < argc; i++) + merge_name(argv[i], &merge_names); + + if (have_message && option_log) + fmt_merge_msg_shortlog(&merge_names, &merge_msg); + else if (!have_message) + fmt_merge_msg(option_log, &merge_names, &merge_msg); + + + if (!(have_message && !option_log) && merge_msg.len) + strbuf_setlen(&merge_msg, merge_msg.len-1); } if (head_invalid || !argc) diff --git a/builtin-mktag.c b/builtin/mktag.c index 1cb0f3f2a7..1cb0f3f2a7 100644 --- a/builtin-mktag.c +++ b/builtin/mktag.c diff --git a/builtin-mktree.c b/builtin/mktree.c index 098395fda1..098395fda1 100644 --- a/builtin-mktree.c +++ b/builtin/mktree.c diff --git a/builtin-mv.c b/builtin/mv.c index c07f53b343..38574b89f7 100644 --- a/builtin-mv.c +++ b/builtin/mv.c @@ -180,7 +180,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } else if (string_list_has_string(&src_for_dst, dst)) bad = "multiple sources for the same target"; else - string_list_insert(dst, &src_for_dst); + string_list_insert(&src_for_dst, dst); if (bad) { if (ignore_errors) { diff --git a/builtin-name-rev.c b/builtin/name-rev.c index 06a38ac8c1..06a38ac8c1 100644 --- a/builtin-name-rev.c +++ b/builtin/name-rev.c diff --git a/builtin/notes.c b/builtin/notes.c new file mode 100644 index 0000000000..fbc347c9f0 --- /dev/null +++ b/builtin/notes.c @@ -0,0 +1,872 @@ +/* + * Builtin "git notes" + * + * Copyright (c) 2010 Johan Herland <johan@herland.net> + * + * Based on git-notes.sh by Johannes Schindelin, + * and builtin-tag.c by Kristian Høgsberg and Carlos Rica. + */ + +#include "cache.h" +#include "builtin.h" +#include "notes.h" +#include "blob.h" +#include "commit.h" +#include "refs.h" +#include "exec_cmd.h" +#include "run-command.h" +#include "parse-options.h" +#include "string-list.h" + +static const char * const git_notes_usage[] = { + "git notes [--ref <notes_ref>] [list [<object>]]", + "git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]", + "git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>", + "git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]", + "git notes [--ref <notes_ref>] edit [<object>]", + "git notes [--ref <notes_ref>] show [<object>]", + "git notes [--ref <notes_ref>] remove [<object>]", + "git notes [--ref <notes_ref>] prune [-n | -v]", + NULL +}; + +static const char * const git_notes_list_usage[] = { + "git notes [list [<object>]]", + NULL +}; + +static const char * const git_notes_add_usage[] = { + "git notes add [<options>] [<object>]", + NULL +}; + +static const char * const git_notes_copy_usage[] = { + "git notes copy [<options>] <from-object> <to-object>", + "git notes copy --stdin [<from-object> <to-object>]...", + NULL +}; + +static const char * const git_notes_append_usage[] = { + "git notes append [<options>] [<object>]", + NULL +}; + +static const char * const git_notes_edit_usage[] = { + "git notes edit [<object>]", + NULL +}; + +static const char * const git_notes_show_usage[] = { + "git notes show [<object>]", + NULL +}; + +static const char * const git_notes_remove_usage[] = { + "git notes remove [<object>]", + NULL +}; + +static const char * const git_notes_prune_usage[] = { + "git notes prune [<options>]", + NULL +}; + +static const char note_template[] = + "\n" + "#\n" + "# Write/edit the notes for the following object:\n" + "#\n"; + +struct msg_arg { + int given; + int use_editor; + struct strbuf buf; +}; + +static int list_each_note(const unsigned char *object_sha1, + const unsigned char *note_sha1, char *note_path, + void *cb_data) +{ + printf("%s %s\n", sha1_to_hex(note_sha1), sha1_to_hex(object_sha1)); + return 0; +} + +static void write_note_data(int fd, const unsigned char *sha1) +{ + unsigned long size; + enum object_type type; + char *buf = read_sha1_file(sha1, &type, &size); + if (buf) { + if (size) + write_or_die(fd, buf, size); + free(buf); + } +} + +static void write_commented_object(int fd, const unsigned char *object) +{ + const char *show_args[5] = + {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL}; + struct child_process show; + struct strbuf buf = STRBUF_INIT; + FILE *show_out; + + /* Invoke "git show --stat --no-notes $object" */ + memset(&show, 0, sizeof(show)); + show.argv = show_args; + show.no_stdin = 1; + show.out = -1; + show.err = 0; + show.git_cmd = 1; + if (start_command(&show)) + die("unable to start 'show' for object '%s'", + sha1_to_hex(object)); + + /* Open the output as FILE* so strbuf_getline() can be used. */ + show_out = xfdopen(show.out, "r"); + if (show_out == NULL) + die_errno("can't fdopen 'show' output fd"); + + /* Prepend "# " to each output line and write result to 'fd' */ + while (strbuf_getline(&buf, show_out, '\n') != EOF) { + write_or_die(fd, "# ", 2); + write_or_die(fd, buf.buf, buf.len); + write_or_die(fd, "\n", 1); + } + strbuf_release(&buf); + if (fclose(show_out)) + die_errno("failed to close pipe to 'show' for object '%s'", + sha1_to_hex(object)); + if (finish_command(&show)) + die("failed to finish 'show' for object '%s'", + sha1_to_hex(object)); +} + +static void create_note(const unsigned char *object, struct msg_arg *msg, + int append_only, const unsigned char *prev, + unsigned char *result) +{ + char *path = NULL; + + if (msg->use_editor || !msg->given) { + int fd; + + /* write the template message before editing: */ + path = git_pathdup("NOTES_EDITMSG"); + fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (fd < 0) + die_errno("could not create file '%s'", path); + + if (msg->given) + write_or_die(fd, msg->buf.buf, msg->buf.len); + else if (prev && !append_only) + write_note_data(fd, prev); + write_or_die(fd, note_template, strlen(note_template)); + + write_commented_object(fd, object); + + close(fd); + strbuf_reset(&(msg->buf)); + + if (launch_editor(path, &(msg->buf), NULL)) { + die("Please supply the note contents using either -m" \ + " or -F option"); + } + stripspace(&(msg->buf), 1); + } + + if (prev && append_only) { + /* Append buf to previous note contents */ + unsigned long size; + enum object_type type; + char *prev_buf = read_sha1_file(prev, &type, &size); + + strbuf_grow(&(msg->buf), size + 1); + if (msg->buf.len && prev_buf && size) + strbuf_insert(&(msg->buf), 0, "\n", 1); + if (prev_buf && size) + strbuf_insert(&(msg->buf), 0, prev_buf, size); + free(prev_buf); + } + + if (!msg->buf.len) { + fprintf(stderr, "Removing note for object %s\n", + sha1_to_hex(object)); + hashclr(result); + } else { + if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) { + error("unable to write note object"); + if (path) + error("The note contents has been left in %s", + path); + exit(128); + } + } + + if (path) { + unlink_or_warn(path); + free(path); + } +} + +static int parse_msg_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + + strbuf_grow(&(msg->buf), strlen(arg) + 2); + if (msg->buf.len) + strbuf_addch(&(msg->buf), '\n'); + strbuf_addstr(&(msg->buf), arg); + stripspace(&(msg->buf), 0); + + msg->given = 1; + return 0; +} + +static int parse_file_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + + if (msg->buf.len) + strbuf_addch(&(msg->buf), '\n'); + if (!strcmp(arg, "-")) { + if (strbuf_read(&(msg->buf), 0, 1024) < 0) + die_errno("cannot read '%s'", arg); + } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0) + die_errno("could not open or read '%s'", arg); + stripspace(&(msg->buf), 0); + + msg->given = 1; + return 0; +} + +static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + char *buf; + unsigned char object[20]; + enum object_type type; + unsigned long len; + + if (msg->buf.len) + strbuf_addch(&(msg->buf), '\n'); + + if (get_sha1(arg, object)) + die("Failed to resolve '%s' as a valid ref.", arg); + if (!(buf = read_sha1_file(object, &type, &len)) || !len) { + free(buf); + die("Failed to read object '%s'.", arg);; + } + strbuf_add(&(msg->buf), buf, len); + free(buf); + + msg->given = 1; + return 0; +} + +static int parse_reedit_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + msg->use_editor = 1; + return parse_reuse_arg(opt, arg, unset); +} + +int commit_notes(struct notes_tree *t, const char *msg) +{ + struct commit_list *parent; + unsigned char tree_sha1[20], prev_commit[20], new_commit[20]; + struct strbuf buf = STRBUF_INIT; + + if (!t) + t = &default_notes_tree; + if (!t->initialized || !t->ref || !*t->ref) + die("Cannot commit uninitialized/unreferenced notes tree"); + if (!t->dirty) + return 0; /* don't have to commit an unchanged tree */ + + /* Prepare commit message and reflog message */ + strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */ + strbuf_addstr(&buf, msg); + if (buf.buf[buf.len - 1] != '\n') + strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ + + /* Convert notes tree to tree object */ + if (write_notes_tree(t, tree_sha1)) + die("Failed to write current notes tree to database"); + + /* Create new commit for the tree object */ + if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */ + parent = xmalloc(sizeof(*parent)); + parent->item = lookup_commit(prev_commit); + parent->next = NULL; + } else { + hashclr(prev_commit); + parent = NULL; + } + if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL)) + die("Failed to commit notes tree to database"); + + /* Update notes ref with new commit */ + update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR); + + strbuf_release(&buf); + return 0; +} + +combine_notes_fn parse_combine_notes_fn(const char *v) +{ + if (!strcasecmp(v, "overwrite")) + return combine_notes_overwrite; + else if (!strcasecmp(v, "ignore")) + return combine_notes_ignore; + else if (!strcasecmp(v, "concatenate")) + return combine_notes_concatenate; + else + return NULL; +} + +static int notes_rewrite_config(const char *k, const char *v, void *cb) +{ + struct notes_rewrite_cfg *c = cb; + if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) { + c->enabled = git_config_bool(k, v); + return 0; + } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) { + if (!v) + config_error_nonbool(k); + c->combine = parse_combine_notes_fn(v); + if (!c->combine) { + error("Bad notes.rewriteMode value: '%s'", v); + return 1; + } + return 0; + } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) { + /* note that a refs/ prefix is implied in the + * underlying for_each_glob_ref */ + if (!prefixcmp(v, "refs/notes/")) + string_list_add_refs_by_glob(c->refs, v); + else + warning("Refusing to rewrite notes in %s" + " (outside of refs/notes/)", v); + return 0; + } + + return 0; +} + + +struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) +{ + struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg)); + const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT); + const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT); + c->cmd = cmd; + c->enabled = 1; + c->combine = combine_notes_concatenate; + c->refs = xcalloc(1, sizeof(struct string_list)); + c->refs->strdup_strings = 1; + c->refs_from_env = 0; + c->mode_from_env = 0; + if (rewrite_mode_env) { + c->mode_from_env = 1; + c->combine = parse_combine_notes_fn(rewrite_mode_env); + if (!c->combine) + error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT + " value: '%s'", rewrite_mode_env); + } + if (rewrite_refs_env) { + c->refs_from_env = 1; + string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env); + } + git_config(notes_rewrite_config, c); + if (!c->enabled || !c->refs->nr) { + string_list_clear(c->refs, 0); + free(c->refs); + free(c); + return NULL; + } + c->trees = load_notes_trees(c->refs); + string_list_clear(c->refs, 0); + free(c->refs); + return c; +} + +int copy_note_for_rewrite(struct notes_rewrite_cfg *c, + const unsigned char *from_obj, const unsigned char *to_obj) +{ + int ret = 0; + int i; + for (i = 0; c->trees[i]; i++) + ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret; + return ret; +} + +void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c) +{ + int i; + for (i = 0; c->trees[i]; i++) { + commit_notes(c->trees[i], "Notes added by 'git notes copy'"); + free_notes(c->trees[i]); + } + free(c->trees); + free(c); +} + +int notes_copy_from_stdin(int force, const char *rewrite_cmd) +{ + struct strbuf buf = STRBUF_INIT; + struct notes_rewrite_cfg *c = NULL; + struct notes_tree *t = NULL; + int ret = 0; + + if (rewrite_cmd) { + c = init_copy_notes_for_rewrite(rewrite_cmd); + if (!c) + return 0; + } else { + init_notes(NULL, NULL, NULL, 0); + t = &default_notes_tree; + } + + while (strbuf_getline(&buf, stdin, '\n') != EOF) { + unsigned char from_obj[20], to_obj[20]; + struct strbuf **split; + int err; + + split = strbuf_split(&buf, ' '); + if (!split[0] || !split[1]) + die("Malformed input line: '%s'.", buf.buf); + strbuf_rtrim(split[0]); + strbuf_rtrim(split[1]); + if (get_sha1(split[0]->buf, from_obj)) + die("Failed to resolve '%s' as a valid ref.", split[0]->buf); + if (get_sha1(split[1]->buf, to_obj)) + die("Failed to resolve '%s' as a valid ref.", split[1]->buf); + + if (rewrite_cmd) + err = copy_note_for_rewrite(c, from_obj, to_obj); + else + err = copy_note(t, from_obj, to_obj, force, + combine_notes_overwrite); + + if (err) { + error("Failed to copy notes from '%s' to '%s'", + split[0]->buf, split[1]->buf); + ret = 1; + } + + strbuf_list_free(split); + } + + if (!rewrite_cmd) { + commit_notes(t, "Notes added by 'git notes copy'"); + free_notes(t); + } else { + finish_copy_notes_for_rewrite(c); + } + return ret; +} + +static struct notes_tree *init_notes_check(const char *subcommand) +{ + struct notes_tree *t; + init_notes(NULL, NULL, NULL, 0); + t = &default_notes_tree; + + if (prefixcmp(t->ref, "refs/notes/")) + die("Refusing to %s notes in %s (outside of refs/notes/)", + subcommand, t->ref); + return t; +} + +static int list(int argc, const char **argv, const char *prefix) +{ + struct notes_tree *t; + unsigned char object[20]; + const unsigned char *note; + int retval = -1; + struct option options[] = { + OPT_END() + }; + + if (argc) + argc = parse_options(argc, argv, prefix, options, + git_notes_list_usage, 0); + + if (1 < argc) { + error("too many parameters"); + usage_with_options(git_notes_list_usage, options); + } + + t = init_notes_check("list"); + if (argc) { + if (get_sha1(argv[0], object)) + die("Failed to resolve '%s' as a valid ref.", argv[0]); + note = get_note(t, object); + if (note) { + puts(sha1_to_hex(note)); + retval = 0; + } else + retval = error("No note found for object %s.", + sha1_to_hex(object)); + } else + retval = for_each_note(t, 0, list_each_note, NULL); + + free_notes(t); + return retval; +} + +static int add(int argc, const char **argv, const char *prefix) +{ + int retval = 0, force = 0; + const char *object_ref; + struct notes_tree *t; + unsigned char object[20], new_note[20]; + char logmsg[100]; + const unsigned char *note; + struct msg_arg msg = { 0, 0, STRBUF_INIT }; + struct option options[] = { + { OPTION_CALLBACK, 'm', "message", &msg, "MSG", + "note contents as a string", PARSE_OPT_NONEG, + parse_msg_arg}, + { OPTION_CALLBACK, 'F', "file", &msg, "FILE", + "note contents in a file", PARSE_OPT_NONEG, + parse_file_arg}, + { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT", + "reuse and edit specified note object", PARSE_OPT_NONEG, + parse_reedit_arg}, + { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT", + "reuse specified note object", PARSE_OPT_NONEG, + parse_reuse_arg}, + OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, git_notes_add_usage, + 0); + + if (1 < argc) { + error("too many parameters"); + usage_with_options(git_notes_add_usage, options); + } + + object_ref = argc ? argv[0] : "HEAD"; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + + t = init_notes_check("add"); + note = get_note(t, object); + + if (note) { + if (!force) { + retval = error("Cannot add notes. Found existing notes " + "for object %s. Use '-f' to overwrite " + "existing notes", sha1_to_hex(object)); + goto out; + } + fprintf(stderr, "Overwriting existing notes for object %s\n", + sha1_to_hex(object)); + } + + create_note(object, &msg, 0, note, new_note); + + if (is_null_sha1(new_note)) + remove_note(t, object); + else + add_note(t, object, new_note, combine_notes_overwrite); + + snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", + is_null_sha1(new_note) ? "removed" : "added", "add"); + commit_notes(t, logmsg); +out: + free_notes(t); + strbuf_release(&(msg.buf)); + return retval; +} + +static int copy(int argc, const char **argv, const char *prefix) +{ + int retval = 0, force = 0, from_stdin = 0; + const unsigned char *from_note, *note; + const char *object_ref; + unsigned char object[20], from_obj[20]; + struct notes_tree *t; + const char *rewrite_cmd = NULL; + struct option options[] = { + OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"), + OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command", + "load rewriting config for <command> (implies " + "--stdin)"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, git_notes_copy_usage, + 0); + + if (from_stdin || rewrite_cmd) { + if (argc) { + error("too many parameters"); + usage_with_options(git_notes_copy_usage, options); + } else { + return notes_copy_from_stdin(force, rewrite_cmd); + } + } + + if (argc < 2) { + error("too few parameters"); + usage_with_options(git_notes_copy_usage, options); + } + if (2 < argc) { + error("too many parameters"); + usage_with_options(git_notes_copy_usage, options); + } + + if (get_sha1(argv[0], from_obj)) + die("Failed to resolve '%s' as a valid ref.", argv[0]); + + object_ref = 1 < argc ? argv[1] : "HEAD"; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + + t = init_notes_check("copy"); + note = get_note(t, object); + + if (note) { + if (!force) { + retval = error("Cannot copy notes. Found existing " + "notes for object %s. Use '-f' to " + "overwrite existing notes", + sha1_to_hex(object)); + goto out; + } + fprintf(stderr, "Overwriting existing notes for object %s\n", + sha1_to_hex(object)); + } + + from_note = get_note(t, from_obj); + if (!from_note) { + retval = error("Missing notes on source object %s. Cannot " + "copy.", sha1_to_hex(from_obj)); + goto out; + } + + add_note(t, object, from_note, combine_notes_overwrite); + commit_notes(t, "Notes added by 'git notes copy'"); +out: + free_notes(t); + return retval; +} + +static int append_edit(int argc, const char **argv, const char *prefix) +{ + const char *object_ref; + struct notes_tree *t; + unsigned char object[20], new_note[20]; + const unsigned char *note; + char logmsg[100]; + const char * const *usage; + struct msg_arg msg = { 0, 0, STRBUF_INIT }; + struct option options[] = { + { OPTION_CALLBACK, 'm', "message", &msg, "MSG", + "note contents as a string", PARSE_OPT_NONEG, + parse_msg_arg}, + { OPTION_CALLBACK, 'F', "file", &msg, "FILE", + "note contents in a file", PARSE_OPT_NONEG, + parse_file_arg}, + { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT", + "reuse and edit specified note object", PARSE_OPT_NONEG, + parse_reedit_arg}, + { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT", + "reuse specified note object", PARSE_OPT_NONEG, + parse_reuse_arg}, + OPT_END() + }; + int edit = !strcmp(argv[0], "edit"); + + usage = edit ? git_notes_edit_usage : git_notes_append_usage; + argc = parse_options(argc, argv, prefix, options, usage, + PARSE_OPT_KEEP_ARGV0); + + if (2 < argc) { + error("too many parameters"); + usage_with_options(usage, options); + } + + if (msg.given && edit) + fprintf(stderr, "The -m/-F/-c/-C options have been deprecated " + "for the 'edit' subcommand.\n" + "Please use 'git notes add -f -m/-F/-c/-C' instead.\n"); + + object_ref = 1 < argc ? argv[1] : "HEAD"; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + + t = init_notes_check(argv[0]); + note = get_note(t, object); + + create_note(object, &msg, !edit, note, new_note); + + if (is_null_sha1(new_note)) + remove_note(t, object); + else + add_note(t, object, new_note, combine_notes_overwrite); + + snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", + is_null_sha1(new_note) ? "removed" : "added", argv[0]); + commit_notes(t, logmsg); + free_notes(t); + strbuf_release(&(msg.buf)); + return 0; +} + +static int show(int argc, const char **argv, const char *prefix) +{ + const char *object_ref; + struct notes_tree *t; + unsigned char object[20]; + const unsigned char *note; + int retval; + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, git_notes_show_usage, + 0); + + if (1 < argc) { + error("too many parameters"); + usage_with_options(git_notes_show_usage, options); + } + + object_ref = argc ? argv[0] : "HEAD"; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + + t = init_notes_check("show"); + note = get_note(t, object); + + if (!note) + retval = error("No note found for object %s.", + sha1_to_hex(object)); + else { + const char *show_args[3] = {"show", sha1_to_hex(note), NULL}; + retval = execv_git_cmd(show_args); + } + free_notes(t); + return retval; +} + +static int remove_cmd(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + const char *object_ref; + struct notes_tree *t; + unsigned char object[20]; + + argc = parse_options(argc, argv, prefix, options, + git_notes_remove_usage, 0); + + if (1 < argc) { + error("too many parameters"); + usage_with_options(git_notes_remove_usage, options); + } + + object_ref = argc ? argv[0] : "HEAD"; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + + t = init_notes_check("remove"); + + fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object)); + remove_note(t, object); + + commit_notes(t, "Notes removed by 'git notes remove'"); + free_notes(t); + return 0; +} + +static int prune(int argc, const char **argv, const char *prefix) +{ + struct notes_tree *t; + int show_only = 0, verbose = 0; + struct option options[] = { + OPT_BOOLEAN('n', "dry-run", &show_only, + "do not remove, show only"), + OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, git_notes_prune_usage, + 0); + + if (argc) { + error("too many parameters"); + usage_with_options(git_notes_prune_usage, options); + } + + t = init_notes_check("prune"); + + prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) | + (show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) ); + if (!show_only) + commit_notes(t, "Notes removed by 'git notes prune'"); + free_notes(t); + return 0; +} + +int cmd_notes(int argc, const char **argv, const char *prefix) +{ + int result; + const char *override_notes_ref = NULL; + struct option options[] = { + OPT_STRING(0, "ref", &override_notes_ref, "notes_ref", + "use notes from <notes_ref>"), + OPT_END() + }; + + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, options, git_notes_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (override_notes_ref) { + struct strbuf sb = STRBUF_INIT; + if (!prefixcmp(override_notes_ref, "refs/notes/")) + /* we're happy */; + else if (!prefixcmp(override_notes_ref, "notes/")) + strbuf_addstr(&sb, "refs/"); + else + strbuf_addstr(&sb, "refs/notes/"); + strbuf_addstr(&sb, override_notes_ref); + setenv("GIT_NOTES_REF", sb.buf, 1); + strbuf_release(&sb); + } + + if (argc < 1 || !strcmp(argv[0], "list")) + result = list(argc, argv, prefix); + else if (!strcmp(argv[0], "add")) + result = add(argc, argv, prefix); + else if (!strcmp(argv[0], "copy")) + result = copy(argc, argv, prefix); + else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit")) + result = append_edit(argc, argv, prefix); + else if (!strcmp(argv[0], "show")) + result = show(argc, argv, prefix); + else if (!strcmp(argv[0], "remove")) + result = remove_cmd(argc, argv, prefix); + else if (!strcmp(argv[0], "prune")) + result = prune(argc, argv, prefix); + else { + result = error("Unknown subcommand: %s", argv[0]); + usage_with_options(git_notes_usage, options); + } + + return result ? 1 : 0; +} diff --git a/builtin-pack-objects.c b/builtin/pack-objects.c index 539e75d56f..0e81673118 100644 --- a/builtin-pack-objects.c +++ b/builtin/pack-objects.c @@ -18,8 +18,8 @@ #include "refs.h" #ifndef NO_PTHREADS -#include "thread-utils.h" #include <pthread.h> +#include "thread-utils.h" #endif static const char pack_usage[] = @@ -155,33 +155,6 @@ static unsigned long do_compress(void **pptr, unsigned long size) } /* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", then three bits of "type", - * and the high bit is "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr) -{ - int n = 1; - unsigned char c; - - if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) - die("bad type %d", type); - - c = (type << 4) | (size & 15); - size >>= 4; - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - n++; - } - *hdr = c; - return n; -} - -/* * we are going to reuse the existing object data as is. make * sure it is not corrupt. */ @@ -321,7 +294,7 @@ static unsigned long write_object(struct sha1file *f, * The object header is a byte of 'type' followed by zero or * more bytes of length. */ - hdrlen = encode_header(type, size, header); + hdrlen = encode_in_pack_object_header(type, size, header); if (type == OBJ_OFS_DELTA) { /* @@ -372,7 +345,7 @@ static unsigned long write_object(struct sha1file *f, if (entry->delta) type = (allow_ofs_delta && entry->delta->idx.offset) ? OBJ_OFS_DELTA : OBJ_REF_DELTA; - hdrlen = encode_header(type, entry->size, header); + hdrlen = encode_in_pack_object_header(type, entry->size, header); offset = entry->in_pack_offset; revidx = find_pack_revindex(p, offset); @@ -1549,6 +1522,15 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, #ifndef NO_PTHREADS +static void try_to_free_from_threads(size_t size) +{ + read_lock(); + release_pack_memory(size, -1); + read_unlock(); +} + +try_to_free_t old_try_to_free_routine; + /* * The main thread waits on the condition that (at least) one of the workers * has stopped working (which is indicated in the .working member of @@ -1579,14 +1561,16 @@ static pthread_cond_t progress_cond; */ static void init_threaded_search(void) { - pthread_mutex_init(&read_mutex, NULL); + init_recursive_mutex(&read_mutex); pthread_mutex_init(&cache_mutex, NULL); pthread_mutex_init(&progress_mutex, NULL); pthread_cond_init(&progress_cond, NULL); + old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads); } static void cleanup_threaded_search(void) { + set_try_to_free_routine(old_try_to_free_routine); pthread_cond_destroy(&progress_cond); pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&cache_mutex); diff --git a/builtin-pack-redundant.c b/builtin/pack-redundant.c index 41e1615a28..41e1615a28 100644 --- a/builtin-pack-redundant.c +++ b/builtin/pack-redundant.c diff --git a/builtin-pack-refs.c b/builtin/pack-refs.c index 091860b2e3..091860b2e3 100644 --- a/builtin-pack-refs.c +++ b/builtin/pack-refs.c diff --git a/builtin/patch-id.c b/builtin/patch-id.c new file mode 100644 index 0000000000..512530022e --- /dev/null +++ b/builtin/patch-id.c @@ -0,0 +1,154 @@ +#include "cache.h" +#include "exec_cmd.h" + +static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) +{ + unsigned char result[20]; + char name[50]; + + if (!patchlen) + return; + + git_SHA1_Final(result, c); + memcpy(name, sha1_to_hex(id), 41); + printf("%s %s\n", sha1_to_hex(result), name); + git_SHA1_Init(c); +} + +static int remove_space(char *line) +{ + char *src = line; + char *dst = line; + unsigned char c; + + while ((c = *src++) != '\0') { + if (!isspace(c)) + *dst++ = c; + } + return dst - line; +} + +static int scan_hunk_header(const char *p, int *p_before, int *p_after) +{ + static const char digits[] = "0123456789"; + const char *q, *r; + int n; + + q = p + 4; + n = strspn(q, digits); + if (q[n] == ',') { + q += n + 1; + n = strspn(q, digits); + } + if (n == 0 || q[n] != ' ' || q[n+1] != '+') + return 0; + + r = q + n + 2; + n = strspn(r, digits); + if (r[n] == ',') { + r += n + 1; + n = strspn(r, digits); + } + if (n == 0) + return 0; + + *p_before = atoi(q); + *p_after = atoi(r); + return 1; +} + +int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) +{ + static char line[1000]; + int patchlen = 0, found_next = 0; + int before = -1, after = -1; + + while (fgets(line, sizeof(line), stdin) != NULL) { + char *p = line; + int len; + + if (!memcmp(line, "diff-tree ", 10)) + p += 10; + else if (!memcmp(line, "commit ", 7)) + p += 7; + else if (!memcmp(line, "From ", 5)) + p += 5; + + if (!get_sha1_hex(p, next_sha1)) { + found_next = 1; + break; + } + + /* Ignore commit comments */ + if (!patchlen && memcmp(line, "diff ", 5)) + continue; + + /* Parsing diff header? */ + if (before == -1) { + if (!memcmp(line, "index ", 6)) + continue; + else if (!memcmp(line, "--- ", 4)) + before = after = 1; + else if (!isalpha(line[0])) + break; + } + + /* Looking for a valid hunk header? */ + if (before == 0 && after == 0) { + if (!memcmp(line, "@@ -", 4)) { + /* Parse next hunk, but ignore line numbers. */ + scan_hunk_header(line, &before, &after); + continue; + } + + /* Split at the end of the patch. */ + if (memcmp(line, "diff ", 5)) + break; + + /* Else we're parsing another header. */ + before = after = -1; + } + + /* If we get here, we're inside a hunk. */ + if (line[0] == '-' || line[0] == ' ') + before--; + if (line[0] == '+' || line[0] == ' ') + after--; + + /* Compute the sha without whitespace */ + len = remove_space(line); + patchlen += len; + git_SHA1_Update(ctx, line, len); + } + + if (!found_next) + hashclr(next_sha1); + + return patchlen; +} + +static void generate_id_list(void) +{ + unsigned char sha1[20], n[20]; + git_SHA_CTX ctx; + int patchlen; + + git_SHA1_Init(&ctx); + hashclr(sha1); + while (!feof(stdin)) { + patchlen = get_one_patchid(n, &ctx); + flush_current_id(patchlen, sha1, &ctx); + hashcpy(sha1, n); + } +} + +static const char patch_id_usage[] = "git patch-id < patch"; + +int cmd_patch_id(int argc, const char **argv, const char *prefix) +{ + if (argc != 1) + usage(patch_id_usage); + + generate_id_list(); + return 0; +} diff --git a/builtin-prune-packed.c b/builtin/prune-packed.c index f9463deec2..f9463deec2 100644 --- a/builtin-prune-packed.c +++ b/builtin/prune-packed.c diff --git a/builtin-prune.c b/builtin/prune.c index 81f915ec31..99218ba49e 100644 --- a/builtin-prune.c +++ b/builtin/prune.c @@ -125,10 +125,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; const struct option options[] = { - OPT_BOOLEAN('n', NULL, &show_only, + OPT_BOOLEAN('n', "dry-run", &show_only, "do not remove, show only"), - OPT_BOOLEAN('v', NULL, &verbose, - "report pruned objects"), + OPT_BOOLEAN('v', "verbose", &verbose, "report pruned objects"), OPT_DATE(0, "expire", &expire, "expire objects older than <time>"), OPT_END() diff --git a/builtin-push.c b/builtin/push.c index 9fc6931247..e655eb7695 100644 --- a/builtin-push.c +++ b/builtin/push.c @@ -17,16 +17,18 @@ static const char * const push_usage[] = { static int thin; static int deleterefs; static const char *receivepack; +static int verbosity; +static int progress; static const char **refspec; static int refspec_nr; +static int refspec_alloc; static void add_refspec(const char *ref) { - int nr = refspec_nr + 1; - refspec = xrealloc(refspec, nr * sizeof(char *)); - refspec[nr-1] = ref; - refspec_nr = nr; + refspec_nr++; + ALLOC_GROW(refspec, refspec_nr, refspec_alloc); + refspec[refspec_nr-1] = ref; } static void set_refspecs(const char **refs, int nr) @@ -105,13 +107,16 @@ static int push_with_options(struct transport *transport, int flags) { int err; int nonfastforward; + + transport_set_verbosity(transport, verbosity, progress); + if (receivepack) transport_set_option(transport, TRANS_OPT_RECEIVEPACK, receivepack); if (thin) transport_set_option(transport, TRANS_OPT_THIN, "yes"); - if (flags & TRANSPORT_PUSH_VERBOSE) + if (verbosity > 0) fprintf(stderr, "Pushing to %s\n", transport->url); err = transport_push(transport, refspec_nr, refspec, flags, &nonfastforward); @@ -124,9 +129,9 @@ static int push_with_options(struct transport *transport, int flags) return 0; if (nonfastforward && advice_push_nonfastforward) { - printf("To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes before pushing again. See the 'Note about\n" - "fast-forwards' section of 'git push --help' for details.\n"); + fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\n" + "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n" + "'Note about fast-forwards' section of 'git push --help' for details.\n"); } return 1; @@ -204,8 +209,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) int rc; const char *repo = NULL; /* default repository */ struct option options[] = { - OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET), - OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE), + OPT__VERBOSITY(&verbosity), OPT_STRING( 0 , "repo", &repo, "repository", "repository"), OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, "mirror all refs", @@ -220,6 +224,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status", TRANSPORT_PUSH_SET_UPSTREAM), + OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), OPT_END() }; diff --git a/builtin-read-tree.c b/builtin/read-tree.c index 8bdcab1138..9ad1e66916 100644 --- a/builtin-read-tree.c +++ b/builtin/read-tree.c @@ -219,14 +219,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * "-m ent" or "--reset ent" form), we can obtain a fully * valid cache-tree because the index must match exactly * what came from the tree. - * - * The same holds true if we are switching between two trees - * using read-tree -m A B. The index must match B after that. */ if (nr_trees == 1 && !opts.prefix) prime_cache_tree(&active_cache_tree, trees[0]); - else if (nr_trees == 2 && opts.merge) - prime_cache_tree(&active_cache_tree, trees[1]); if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) diff --git a/builtin-receive-pack.c b/builtin/receive-pack.c index 0559fcc871..d634b5a3d5 100644 --- a/builtin-receive-pack.c +++ b/builtin/receive-pack.c @@ -9,6 +9,7 @@ #include "object.h" #include "remote.h" #include "transport.h" +#include "string-list.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -16,7 +17,7 @@ enum deny_action { DENY_UNCONFIGURED, DENY_IGNORE, DENY_WARN, - DENY_REFUSE, + DENY_REFUSE }; static int deny_deletes; @@ -129,13 +130,12 @@ static void write_head_info(void) struct command { struct command *next; const char *error_string; + unsigned int skip_update; unsigned char old_sha1[20]; unsigned char new_sha1[20]; char ref_name[FLEX_ARRAY]; /* more */ }; -static struct command *commands; - static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; @@ -188,7 +188,7 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } -static int run_receive_hook(const char *hook_name) +static int run_receive_hook(struct command *commands, const char *hook_name) { static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; struct command *cmd; @@ -447,15 +447,15 @@ static const char *update(struct command *cmd) static char update_post_hook[] = "hooks/post-update"; -static void run_update_post_hook(struct command *cmd) +static void run_update_post_hook(struct command *commands) { - struct command *cmd_p; + struct command *cmd; int argc; const char **argv; struct child_process proc; - for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { - if (cmd_p->error_string) + for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { + if (cmd->error_string) continue; argc++; } @@ -464,12 +464,12 @@ static void run_update_post_hook(struct command *cmd) argv = xmalloc(sizeof(*argv) * (2 + argc)); argv[0] = update_post_hook; - for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { + for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { char *p; - if (cmd_p->error_string) + if (cmd->error_string) continue; - p = xmalloc(strlen(cmd_p->ref_name) + 1); - strcpy(p, cmd_p->ref_name); + p = xmalloc(strlen(cmd->ref_name) + 1); + strcpy(p, cmd->ref_name); argv[argc] = p; argc++; } @@ -488,37 +488,92 @@ static void run_update_post_hook(struct command *cmd) } } -static void execute_commands(const char *unpacker_error) +static void check_aliased_update(struct command *cmd, struct string_list *list) +{ + struct string_list_item *item; + struct command *dst_cmd; + unsigned char sha1[20]; + char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41]; + int flag; + + const char *dst_name = resolve_ref(cmd->ref_name, sha1, 0, &flag); + + if (!(flag & REF_ISSYMREF)) + return; + + if ((item = string_list_lookup(list, dst_name)) == NULL) + return; + + cmd->skip_update = 1; + + dst_cmd = (struct command *) item->util; + + if (!hashcmp(cmd->old_sha1, dst_cmd->old_sha1) && + !hashcmp(cmd->new_sha1, dst_cmd->new_sha1)) + return; + + dst_cmd->skip_update = 1; + + strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV)); + strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV)); + strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV)); + strcpy(dst_newh, find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV)); + rp_error("refusing inconsistent update between symref '%s' (%s..%s) and" + " its target '%s' (%s..%s)", + cmd->ref_name, cmd_oldh, cmd_newh, + dst_cmd->ref_name, dst_oldh, dst_newh); + + cmd->error_string = dst_cmd->error_string = + "inconsistent aliased update"; +} + +static void check_aliased_updates(struct command *commands) +{ + struct command *cmd; + struct string_list ref_list = { NULL, 0, 0, 0 }; + + for (cmd = commands; cmd; cmd = cmd->next) { + struct string_list_item *item = + string_list_append(&ref_list, cmd->ref_name); + item->util = (void *)cmd; + } + sort_string_list(&ref_list); + + for (cmd = commands; cmd; cmd = cmd->next) + check_aliased_update(cmd, &ref_list); + + string_list_clear(&ref_list, 0); +} + +static void execute_commands(struct command *commands, const char *unpacker_error) { - struct command *cmd = commands; + struct command *cmd; unsigned char sha1[20]; if (unpacker_error) { - while (cmd) { + for (cmd = commands; cmd; cmd = cmd->next) cmd->error_string = "n/a (unpacker error)"; - cmd = cmd->next; - } return; } - if (run_receive_hook(pre_receive_hook)) { - while (cmd) { + if (run_receive_hook(commands, pre_receive_hook)) { + for (cmd = commands; cmd; cmd = cmd->next) cmd->error_string = "pre-receive hook declined"; - cmd = cmd->next; - } return; } + check_aliased_updates(commands); + head_name = resolve_ref("HEAD", sha1, 0, NULL); - while (cmd) { - cmd->error_string = update(cmd); - cmd = cmd->next; - } + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->skip_update) + cmd->error_string = update(cmd); } -static void read_head_info(void) +static struct command *read_head_info(void) { + struct command *commands = NULL; struct command **p = &commands; for (;;) { static char line[1000]; @@ -548,15 +603,14 @@ static void read_head_info(void) if (strstr(refname + reflen + 1, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; } - cmd = xmalloc(sizeof(struct command) + len - 80); + cmd = xcalloc(1, sizeof(struct command) + len - 80); hashcpy(cmd->old_sha1, old_sha1); hashcpy(cmd->new_sha1, new_sha1); memcpy(cmd->ref_name, line + 82, len - 81); - cmd->error_string = NULL; - cmd->next = NULL; *p = cmd; p = &cmd->next; } + return commands; } static const char *parse_pack_header(struct pack_header *hdr) @@ -643,7 +697,7 @@ static const char *unpack(void) } } -static void report(const char *unpack_status) +static void report(struct command *commands, const char *unpack_status) { struct command *cmd; struct strbuf buf = STRBUF_INIT; @@ -667,12 +721,12 @@ static void report(const char *unpack_status) strbuf_release(&buf); } -static int delete_only(struct command *cmd) +static int delete_only(struct command *commands) { - while (cmd) { + struct command *cmd; + for (cmd = commands; cmd; cmd = cmd->next) { if (!is_null_sha1(cmd->new_sha1)) return 0; - cmd = cmd->next; } return 1; } @@ -722,6 +776,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) int stateless_rpc = 0; int i; char *dir = NULL; + struct command *commands; argv++; for (i = 1; i < argc; i++) { @@ -772,18 +827,17 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) if (advertise_refs) return 0; - read_head_info(); - if (commands) { + if ((commands = read_head_info()) != NULL) { const char *unpack_status = NULL; if (!delete_only(commands)) unpack_status = unpack(); - execute_commands(unpack_status); + execute_commands(commands, unpack_status); if (pack_lockfile) unlink_or_warn(pack_lockfile); if (report_status) - report(unpack_status); - run_receive_hook(post_receive_hook); + report(commands, unpack_status); + run_receive_hook(commands, post_receive_hook); run_update_post_hook(commands); if (auto_gc) { const char *argv_gc_auto[] = { diff --git a/builtin-reflog.c b/builtin/reflog.c index bd7880dc04..ebf610e64a 100644 --- a/builtin-reflog.c +++ b/builtin/reflog.c @@ -34,8 +34,13 @@ struct cmd_reflog_expire_cb { struct expire_reflog_cb { FILE *newlog; - const char *ref; - struct commit *ref_commit; + enum { + UE_NORMAL, + UE_ALWAYS, + UE_HEAD + } unreachable_expire_kind; + struct commit_list *mark_list; + unsigned long mark_limit; struct cmd_reflog_expire_cb *cmd; unsigned char last_kept_sha1[20]; }; @@ -210,46 +215,23 @@ static int keep_entry(struct commit **it, unsigned char *sha1) return 1; } -static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +/* + * Starting from commits in the cb->mark_list, mark commits that are + * reachable from them. Stop the traversal at commits older than + * the expire_limit and queue them back, so that the caller can call + * us again to restart the traversal with longer expire_limit. + */ +static void mark_reachable(struct expire_reflog_cb *cb) { - /* - * We may or may not have the commit yet - if not, look it - * up using the supplied sha1. - */ - if (!commit) { - if (is_null_sha1(sha1)) - return 0; - - commit = lookup_commit_reference_gently(sha1, 1); - - /* Not a commit -- keep it */ - if (!commit) - return 0; - } - - /* Reachable from the current ref? Don't prune. */ - if (commit->object.flags & REACHABLE) - return 0; - if (in_merge_bases(commit, &cb->ref_commit, 1)) - return 0; - - /* We can't reach it - prune it. */ - return 1; -} + struct commit *commit; + struct commit_list *pending; + unsigned long expire_limit = cb->mark_limit; + struct commit_list *leftover = NULL; -static void mark_reachable(struct commit *commit, unsigned long expire_limit) -{ - /* - * We need to compute whether the commit on either side of a reflog - * entry is reachable from the tip of the ref for all entries. - * Mark commits that are reachable from the tip down to the - * time threshold first; we know a commit marked thusly is - * reachable from the tip without running in_merge_bases() - * at all. - */ - struct commit_list *pending = NULL; + for (pending = cb->mark_list; pending; pending = pending->next) + pending->item->object.flags &= ~REACHABLE; - commit_list_insert(commit, &pending); + pending = cb->mark_list; while (pending) { struct commit_list *entry = pending; struct commit_list *parent; @@ -261,8 +243,11 @@ static void mark_reachable(struct commit *commit, unsigned long expire_limit) if (parse_commit(commit)) continue; commit->object.flags |= REACHABLE; - if (commit->date < expire_limit) + if (commit->date < expire_limit) { + commit_list_insert(commit, &leftover); continue; + } + commit->object.flags |= REACHABLE; parent = commit->parents; while (parent) { commit = parent->item; @@ -272,6 +257,36 @@ static void mark_reachable(struct commit *commit, unsigned long expire_limit) commit_list_insert(commit, &pending); } } + cb->mark_list = leftover; +} + +static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +{ + /* + * We may or may not have the commit yet - if not, look it + * up using the supplied sha1. + */ + if (!commit) { + if (is_null_sha1(sha1)) + return 0; + + commit = lookup_commit_reference_gently(sha1, 1); + + /* Not a commit -- keep it */ + if (!commit) + return 0; + } + + /* Reachable from the current ref? Don't prune. */ + if (commit->object.flags & REACHABLE) + return 0; + + if (cb->mark_list && cb->mark_limit) { + cb->mark_limit = 0; /* dig down to the root */ + mark_reachable(cb); + } + + return !(commit->object.flags & REACHABLE); } static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, @@ -293,7 +308,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, goto prune; if (timestamp < cb->cmd->expire_unreachable) { - if (!cb->ref_commit) + if (cb->unreachable_expire_kind == UE_ALWAYS) goto prune; if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1)) goto prune; @@ -320,12 +335,27 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, return 0; } +static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +{ + struct commit_list **list = cb_data; + struct commit *tip_commit; + if (flags & REF_ISSYMREF) + return 0; + tip_commit = lookup_commit_reference_gently(sha1, 1); + if (!tip_commit) + return 0; + commit_list_insert(tip_commit, list); + return 0; +} + static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) { struct cmd_reflog_expire_cb *cmd = cb_data; struct expire_reflog_cb cb; struct ref_lock *lock; char *log_file, *newlog_path = NULL; + struct commit *tip_commit; + struct commit_list *tips; int status = 0; memset(&cb, 0, sizeof(cb)); @@ -345,14 +375,49 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, cb.newlog = fopen(newlog_path, "w"); } - cb.ref_commit = lookup_commit_reference_gently(sha1, 1); - cb.ref = ref; cb.cmd = cmd; - if (cb.ref_commit) - mark_reachable(cb.ref_commit, cmd->expire_total); + + if (!cmd->expire_unreachable || !strcmp(ref, "HEAD")) { + tip_commit = NULL; + cb.unreachable_expire_kind = UE_HEAD; + } else { + tip_commit = lookup_commit_reference_gently(sha1, 1); + if (!tip_commit) + cb.unreachable_expire_kind = UE_ALWAYS; + else + cb.unreachable_expire_kind = UE_NORMAL; + } + + if (cmd->expire_unreachable <= cmd->expire_total) + cb.unreachable_expire_kind = UE_ALWAYS; + + cb.mark_list = NULL; + tips = NULL; + if (cb.unreachable_expire_kind != UE_ALWAYS) { + if (cb.unreachable_expire_kind == UE_HEAD) { + struct commit_list *elem; + for_each_ref(push_tip_to_list, &tips); + for (elem = tips; elem; elem = elem->next) + commit_list_insert(elem->item, &cb.mark_list); + } else { + commit_list_insert(tip_commit, &cb.mark_list); + } + cb.mark_limit = cmd->expire_total; + mark_reachable(&cb); + } + for_each_reflog_ent(ref, expire_reflog_ent, &cb); - if (cb.ref_commit) - clear_commit_marks(cb.ref_commit, REACHABLE); + + if (cb.unreachable_expire_kind != UE_ALWAYS) { + if (cb.unreachable_expire_kind == UE_HEAD) { + struct commit_list *elem; + for (elem = tips; elem; elem = elem->next) + clear_commit_marks(tip_commit, REACHABLE); + free_commit_list(tips); + } else { + clear_commit_marks(tip_commit, REACHABLE); + } + } finish: if (cb.newlog) { if (fclose(cb.newlog)) { diff --git a/builtin-remote.c b/builtin/remote.c index 277765b864..6699bc5712 100644 --- a/builtin-remote.c +++ b/builtin/remote.c @@ -16,6 +16,7 @@ static const char * const builtin_remote_usage[] = { "git remote [-v | --verbose] show [-n] <name>", "git remote prune [-n | --dry-run] <name>", "git remote [-v | --verbose] update [-p | --prune] [group | remote]", + "git remote set-branches <name> [--add] <branch>...", "git remote set-url <name> <newurl> [<oldurl>]", "git remote set-url --add <name> <newurl>", "git remote set-url --delete <name> <url>", @@ -42,6 +43,12 @@ static const char * const builtin_remote_sethead_usage[] = { NULL }; +static const char * const builtin_remote_setbranches_usage[] = { + "git remote set-branches <name> <branch>...", + "git remote set-branches --add <name> <branch>...", + NULL +}; + static const char * const builtin_remote_show_usage[] = { "git remote show [<options>] <name>", NULL @@ -87,7 +94,7 @@ static int opt_parse_track(const struct option *opt, const char *arg, int not) if (not) string_list_clear(list, 0); else - string_list_append(arg, list); + string_list_append(list, arg); return 0; } @@ -104,9 +111,29 @@ static int fetch_remote(const char *name) return 0; } +enum { + TAGS_UNSET = 0, + TAGS_DEFAULT = 1, + TAGS_SET = 2 +}; + +static int add_branch(const char *key, const char *branchname, + const char *remotename, int mirror, struct strbuf *tmp) +{ + strbuf_reset(tmp); + strbuf_addch(tmp, '+'); + if (mirror) + strbuf_addf(tmp, "refs/%s:refs/%s", + branchname, branchname); + else + strbuf_addf(tmp, "refs/heads/%s:refs/remotes/%s/%s", + branchname, remotename, branchname); + return git_config_set_multivar(key, tmp->buf, "^$", 0); +} + static int add(int argc, const char **argv) { - int fetch = 0, mirror = 0; + int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT; struct string_list track = { NULL, 0, 0 }; const char *master = NULL; struct remote *remote; @@ -116,6 +143,11 @@ static int add(int argc, const char **argv) struct option options[] = { OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"), + OPT_SET_INT(0, "tags", &fetch_tags, + "import all tags and associated objects when fetching", + TAGS_SET), + OPT_SET_INT(0, NULL, &fetch_tags, + "or do not fetch any tag at all (--no-tags)", TAGS_UNSET), OPT_CALLBACK('t', "track", &track, "branch", "branch(es) to track", opt_parse_track), OPT_STRING('m', "master", &master, "branch", "master branch"), @@ -149,19 +181,10 @@ static int add(int argc, const char **argv) strbuf_addf(&buf, "remote.%s.fetch", name); if (track.nr == 0) - string_list_append("*", &track); + string_list_append(&track, "*"); for (i = 0; i < track.nr; i++) { - struct string_list_item *item = track.items + i; - - strbuf_reset(&buf2); - strbuf_addch(&buf2, '+'); - if (mirror) - strbuf_addf(&buf2, "refs/%s:refs/%s", - item->string, item->string); - else - strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s", - item->string, name, item->string); - if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0)) + if (add_branch(buf.buf, track.items[i].string, + name, mirror, &buf2)) return 1; } @@ -172,6 +195,14 @@ static int add(int argc, const char **argv) return 1; } + if (fetch_tags != TAGS_DEFAULT) { + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.tagopt", name); + if (git_config_set(buf.buf, + fetch_tags == TAGS_SET ? "--tags" : "--no-tags")) + return 1; + } + if (fetch && fetch_remote(name)) return 1; @@ -232,7 +263,7 @@ static int config_read_branches(const char *key, const char *value, void *cb) } else return 0; - item = string_list_insert(name, &branch_list); + item = string_list_insert(&branch_list, name); if (!item->util) item->util = xcalloc(sizeof(struct branch_info), 1); @@ -247,11 +278,11 @@ static int config_read_branches(const char *key, const char *value, void *cb) while (space) { char *merge; merge = xstrndup(value, space - value); - string_list_append(merge, &info->merge); + string_list_append(&info->merge, merge); value = abbrev_branch(space + 1); space = strchr(value, ' '); } - string_list_append(xstrdup(value), &info->merge); + string_list_append(&info->merge, xstrdup(value)); } else info->rebase = git_config_bool(orig_key, value); } @@ -288,14 +319,14 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat for (ref = fetch_map; ref; ref = ref->next) { unsigned char sha1[20]; if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) - string_list_append(abbrev_branch(ref->name), &states->new); + string_list_append(&states->new, abbrev_branch(ref->name)); else - string_list_append(abbrev_branch(ref->name), &states->tracked); + string_list_append(&states->tracked, abbrev_branch(ref->name)); } stale_refs = get_stale_heads(states->remote, fetch_map); for (ref = stale_refs; ref; ref = ref->next) { struct string_list_item *item = - string_list_append(abbrev_branch(ref->name), &states->stale); + string_list_append(&states->stale, abbrev_branch(ref->name)); item->util = xstrdup(ref->name); } free_refs(stale_refs); @@ -317,7 +348,7 @@ struct push_info { PUSH_STATUS_UPTODATE, PUSH_STATUS_FASTFORWARD, PUSH_STATUS_OUTOFDATE, - PUSH_STATUS_NOTQUERIED, + PUSH_STATUS_NOTQUERIED } status; }; @@ -344,8 +375,8 @@ static int get_push_ref_states(const struct ref *remote_refs, continue; hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - item = string_list_append(abbrev_branch(ref->peer_ref->name), - &states->push); + item = string_list_append(&states->push, + abbrev_branch(ref->peer_ref->name)); item->util = xcalloc(sizeof(struct push_info), 1); info = item->util; info->forced = ref->force; @@ -380,7 +411,7 @@ static int get_push_ref_states_noquery(struct ref_states *states) states->push.strdup_strings = 1; if (!remote->push_refspec_nr) { - item = string_list_append("(matching)", &states->push); + item = string_list_append(&states->push, "(matching)"); info = item->util = xcalloc(sizeof(struct push_info), 1); info->status = PUSH_STATUS_NOTQUERIED; info->dest = xstrdup(item->string); @@ -388,11 +419,11 @@ static int get_push_ref_states_noquery(struct ref_states *states) for (i = 0; i < remote->push_refspec_nr; i++) { struct refspec *spec = remote->push + i; if (spec->matching) - item = string_list_append("(matching)", &states->push); + item = string_list_append(&states->push, "(matching)"); else if (strlen(spec->src)) - item = string_list_append(spec->src, &states->push); + item = string_list_append(&states->push, spec->src); else - item = string_list_append("(delete)", &states->push); + item = string_list_append(&states->push, "(delete)"); info = item->util = xcalloc(sizeof(struct push_info), 1); info->forced = spec->force; @@ -416,7 +447,7 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), fetch_map, 1); for (ref = matches; ref; ref = ref->next) - string_list_append(abbrev_branch(ref->name), &states->heads); + string_list_append(&states->heads, abbrev_branch(ref->name)); free_refs(fetch_map); free_refs(matches); @@ -480,8 +511,8 @@ static int add_branch_for_removal(const char *refname, if (prefixcmp(refname, "refs/remotes")) { /* advise user how to delete local branches */ if (!prefixcmp(refname, "refs/heads/")) - string_list_append(abbrev_branch(refname), - branches->skipped); + string_list_append(branches->skipped, + abbrev_branch(refname)); /* silently skip over other non-remote refs */ return 0; } @@ -490,7 +521,7 @@ static int add_branch_for_removal(const char *refname, if (flags & REF_ISSYMREF) return unlink(git_path("%s", refname)); - item = string_list_append(refname, branches->branches); + item = string_list_append(branches->branches, refname); item->util = xmalloc(20); hashcpy(item->util, sha1); @@ -515,7 +546,7 @@ static int read_remote_branches(const char *refname, strbuf_addf(&buf, "refs/remotes/%s", rename->old); if (!prefixcmp(refname, buf.buf)) { - item = string_list_append(xstrdup(refname), rename->remote_branches); + item = string_list_append(rename->remote_branches, xstrdup(refname)); symref = resolve_ref(refname, orig_sha1, 1, &flag); if (flag & REF_ISSYMREF) item->util = xstrdup(symref); @@ -705,11 +736,14 @@ static int rm(int argc, const char **argv) struct known_remotes known_remotes = { NULL, NULL }; struct string_list branches = { NULL, 0, 0, 1 }; struct string_list skipped = { NULL, 0, 0, 1 }; - struct branches_for_remote cb_data = { - NULL, &branches, &skipped, &known_remotes - }; + struct branches_for_remote cb_data; int i, result; + memset(&cb_data, 0, sizeof(cb_data)); + cb_data.branches = &branches; + cb_data.skipped = &skipped; + cb_data.keep = &known_remotes; + if (argc != 2) usage_with_options(builtin_remote_rm_usage, options); @@ -798,7 +832,7 @@ static int append_ref_to_tracked_list(const char *refname, memset(&refspec, 0, sizeof(refspec)); refspec.dst = (char *)refname; if (!remote_find_tracking(states->remote, &refspec)) - string_list_append(abbrev_branch(refspec.src), &states->tracked); + string_list_append(&states->tracked, abbrev_branch(refspec.src)); return 0; } @@ -851,7 +885,7 @@ static int add_remote_to_show_info(struct string_list_item *item, void *cb_data) int n = strlen(item->string); if (n > info->width) info->width = n; - string_list_insert(item->string, info->list); + string_list_insert(info->list, item->string); return 0; } @@ -898,7 +932,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb if (branch_info->rebase) show_info->any_rebase = 1; - item = string_list_insert(branch_item->string, show_info->list); + item = string_list_insert(show_info->list, branch_item->string); item->util = branch_info; return 0; @@ -946,7 +980,7 @@ static int add_push_to_show_info(struct string_list_item *push_item, void *cb_da show_info->width = n; if ((n = strlen(push_info->dest)) > show_info->width2) show_info->width2 = n; - item = string_list_append(push_item->string, show_info->list); + item = string_list_append(show_info->list, push_item->string); item->util = push_item->util; return 0; } @@ -1062,24 +1096,24 @@ static int show(int argc, const char **argv) /* remote branch info */ info.width = 0; - for_each_string_list(add_remote_to_show_info, &states.new, &info); - for_each_string_list(add_remote_to_show_info, &states.tracked, &info); - for_each_string_list(add_remote_to_show_info, &states.stale, &info); + for_each_string_list(&states.new, add_remote_to_show_info, &info); + for_each_string_list(&states.tracked, add_remote_to_show_info, &info); + for_each_string_list(&states.stale, add_remote_to_show_info, &info); if (info.list->nr) printf(" Remote branch%s:%s\n", info.list->nr > 1 ? "es" : "", no_query ? " (status not queried)" : ""); - for_each_string_list(show_remote_info_item, info.list, &info); + for_each_string_list(info.list, show_remote_info_item, &info); string_list_clear(info.list, 0); /* git pull info */ info.width = 0; info.any_rebase = 0; - for_each_string_list(add_local_to_show_info, &branch_list, &info); + for_each_string_list(&branch_list, add_local_to_show_info, &info); if (info.list->nr) printf(" Local branch%s configured for 'git pull':\n", info.list->nr > 1 ? "es" : ""); - for_each_string_list(show_local_info_item, info.list, &info); + for_each_string_list(info.list, show_local_info_item, &info); string_list_clear(info.list, 0); /* git push info */ @@ -1087,14 +1121,14 @@ static int show(int argc, const char **argv) printf(" Local refs will be mirrored by 'git push'\n"); info.width = info.width2 = 0; - for_each_string_list(add_push_to_show_info, &states.push, &info); + for_each_string_list(&states.push, add_push_to_show_info, &info); qsort(info.list->items, info.list->nr, sizeof(*info.list->items), cmp_string_with_push); if (info.list->nr) printf(" Local ref%s configured for 'git push'%s:\n", info.list->nr > 1 ? "s" : "", no_query ? " (status not queried)" : ""); - for_each_string_list(show_push_info_item, info.list, &info); + for_each_string_list(info.list, show_push_info_item, &info); string_list_clear(info.list, 0); free_remote_ref_states(&states); @@ -1265,6 +1299,72 @@ static int update(int argc, const char **argv) return run_command_v_opt(fetch_argv, RUN_GIT_CMD); } +static int remove_all_fetch_refspecs(const char *remote, const char *key) +{ + return git_config_set_multivar(key, NULL, NULL, 1); +} + +static int add_branches(struct remote *remote, const char **branches, + const char *key) +{ + const char *remotename = remote->name; + int mirror = remote->mirror; + struct strbuf refspec = STRBUF_INIT; + + for (; *branches; branches++) + if (add_branch(key, *branches, remotename, mirror, &refspec)) { + strbuf_release(&refspec); + return 1; + } + + strbuf_release(&refspec); + return 0; +} + +static int set_remote_branches(const char *remotename, const char **branches, + int add_mode) +{ + struct strbuf key = STRBUF_INIT; + struct remote *remote; + + strbuf_addf(&key, "remote.%s.fetch", remotename); + + if (!remote_is_configured(remotename)) + die("No such remote '%s'", remotename); + remote = remote_get(remotename); + + if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) { + strbuf_release(&key); + return 1; + } + if (add_branches(remote, branches, key.buf)) { + strbuf_release(&key); + return 1; + } + + strbuf_release(&key); + return 0; +} + +static int set_branches(int argc, const char **argv) +{ + int add_mode = 0; + struct option options[] = { + OPT_BOOLEAN('\0', "add", &add_mode, "add branch"), + OPT_END() + }; + + argc = parse_options(argc, argv, NULL, options, + builtin_remote_setbranches_usage, 0); + if (argc == 0) { + error("no remote specified"); + usage_with_options(builtin_remote_seturl_usage, options); + } + argv[argc] = NULL; + + return set_remote_branches(argv[0], argv + 1, add_mode); +} + static int set_url(int argc, const char **argv) { int i, push_mode = 0, add_mode = 0, delete_mode = 0; @@ -1360,10 +1460,10 @@ static int get_one_entry(struct remote *remote, void *priv) if (remote->url_nr > 0) { strbuf_addf(&url_buf, "%s (fetch)", remote->url[0]); - string_list_append(remote->name, list)->util = + string_list_append(list, remote->name)->util = strbuf_detach(&url_buf, NULL); } else - string_list_append(remote->name, list)->util = NULL; + string_list_append(list, remote->name)->util = NULL; if (remote->pushurl_nr) { url = remote->pushurl; url_nr = remote->pushurl_nr; @@ -1374,7 +1474,7 @@ static int get_one_entry(struct remote *remote, void *priv) for (i = 0; i < url_nr; i++) { strbuf_addf(&url_buf, "%s (push)", url[i]); - string_list_append(remote->name, list)->util = + string_list_append(list, remote->name)->util = strbuf_detach(&url_buf, NULL); } @@ -1430,6 +1530,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix) result = rm(argc, argv); else if (!strcmp(argv[0], "set-head")) result = set_head(argc, argv); + else if (!strcmp(argv[0], "set-branches")) + result = set_branches(argc, argv); else if (!strcmp(argv[0], "set-url")) result = set_url(argc, argv); else if (!strcmp(argv[0], "show")) diff --git a/builtin-replace.c b/builtin/replace.c index fe3a647a36..fe3a647a36 100644 --- a/builtin-replace.c +++ b/builtin/replace.c diff --git a/builtin-rerere.c b/builtin/rerere.c index 34f9acee91..39ad60169d 100644 --- a/builtin-rerere.c +++ b/builtin/rerere.c @@ -59,7 +59,7 @@ static void garbage_collect(struct string_list *rr) cutoff = (has_rerere_resolution(e->d_name) ? cutoff_resolve : cutoff_noresolve); if (then < now - cutoff * 86400) - string_list_append(e->d_name, &to_remove); + string_list_append(&to_remove, e->d_name); } for (i = 0; i < to_remove.nr; i++) unlink_rr_item(to_remove.items[i].string); @@ -89,7 +89,7 @@ static int diff_two(const char *file1, const char *label1, printf("--- a/%s\n+++ b/%s\n", label1, label2); fflush(stdout); memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = outf; @@ -135,7 +135,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) if (!has_rerere_resolution(name)) unlink_rr_item(name); } - unlink_or_warn(git_path("rr-cache/MERGE_RR")); + unlink_or_warn(git_path("MERGE_RR")); } else if (!strcmp(argv[1], "gc")) garbage_collect(&merge_rr); else if (!strcmp(argv[1], "status")) diff --git a/builtin-reset.c b/builtin/reset.c index a174a31610..1283068fd2 100644 --- a/builtin-reset.c +++ b/builtin/reset.c @@ -22,14 +22,16 @@ #include "cache-tree.h" static const char * const git_reset_usage[] = { - "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]", + "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]", "git reset [-q] <commit> [--] <paths>...", "git reset --patch [<commit>] [--] [<paths>...]", NULL }; -enum reset_type { MIXED, SOFT, HARD, MERGE, NONE }; -static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL }; +enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE }; +static const char *reset_type_names[] = { + "mixed", "soft", "hard", "merge", "keep", NULL +}; static char *args_to_str(const char **argv) { @@ -72,6 +74,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet if (!quiet) opts.verbose_update = 1; switch (reset_type) { + case KEEP: case MERGE: opts.update = 1; break; @@ -86,6 +89,16 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet read_cache_unmerged(); + if (reset_type == KEEP) { + unsigned char head_sha1[20]; + if (get_sha1("HEAD", head_sha1)) + return error("You do not have a valid HEAD."); + if (!fill_tree_descriptor(desc, head_sha1)) + return error("Failed to find tree of HEAD."); + nr++; + opts.fn = twoway_merge; + } + if (!fill_tree_descriptor(desc + nr - 1, sha1)) return error("Failed to find tree of %s.", sha1_to_hex(sha1)); if (unpack_trees(nr, desc, &opts)) @@ -212,6 +225,14 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size) warning("Reflog action message too long: %.*s...", 50, buf); } +static void die_if_unmerged_cache(int reset_type) +{ + if (is_merge() || read_cache() < 0 || unmerged_cache()) + die("Cannot do a %s reset in the middle of a merge.", + reset_type_names[reset_type]); + +} + int cmd_reset(int argc, const char **argv, const char *prefix) { int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0; @@ -230,6 +251,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) "reset HEAD, index and working tree", HARD), OPT_SET_INT(0, "merge", &reset_type, "reset HEAD, index and working tree", MERGE), + OPT_SET_INT(0, "keep", &reset_type, + "reset HEAD but keep local changes", KEEP), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END() }; @@ -305,7 +328,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type == NONE) reset_type = MIXED; /* by default */ - if (reset_type == HARD || reset_type == MERGE) + if (reset_type != SOFT && reset_type != MIXED) setup_work_tree(); if (reset_type == MIXED && is_bare_repository()) @@ -315,12 +338,18 @@ int cmd_reset(int argc, const char **argv, const char *prefix) /* Soft reset does not touch the index file nor the working tree * at all, but requires them in a good order. Other resets reset * the index file to the tree object we are switching to. */ - if (reset_type == SOFT) { - if (is_merge() || read_cache() < 0 || unmerged_cache()) - die("Cannot do a soft reset in the middle of a merge."); + if (reset_type == SOFT) + die_if_unmerged_cache(reset_type); + else { + int err; + if (reset_type == KEEP) + die_if_unmerged_cache(reset_type); + err = reset_index_file(sha1, reset_type, quiet); + if (reset_type == KEEP) + err = err || reset_index_file(sha1, MIXED, quiet); + if (err) + die("Could not reset index file to revision '%s'.", rev); } - else if (reset_index_file(sha1, reset_type, quiet)) - die("Could not reset index file to revision '%s'.", rev); /* Any resets update HEAD to the head being switched to, * saving the previous head in ORIG_HEAD before. */ diff --git a/builtin-rev-list.c b/builtin/rev-list.c index 51ceb19d88..efe9360e2f 100644 --- a/builtin-rev-list.c +++ b/builtin/rev-list.c @@ -50,6 +50,15 @@ static void show_commit(struct commit *commit, void *data) graph_show_commit(revs->graph); + if (revs->count) { + if (commit->object.flags & SYMMETRIC_LEFT) + revs->count_left++; + else + revs->count_right++; + finish_commit(commit, data); + return; + } + if (info->show_timestamp) printf("%lu ", commit->date); if (info->header_prefix) @@ -400,5 +409,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) quiet ? finish_object : show_object, &info); + if (revs.count) { + if (revs.left_right) + printf("%d\t%d\n", revs.count_left, revs.count_right); + else + printf("%d\n", revs.count_left + revs.count_right); + } + return 0; } diff --git a/builtin-rev-parse.c b/builtin/rev-parse.c index 935fdeb45e..a5a1c86e92 100644 --- a/builtin-rev-parse.c +++ b/builtin/rev-parse.c @@ -408,7 +408,8 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) memset(opts + onb, 0, sizeof(opts[onb])); argc = parse_options(argc, argv, prefix, opts, usage, (keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0) | - (stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0)); + (stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) | + PARSE_OPT_SHELL_EVAL); strbuf_addf(&parsed, " --"); sq_quote_argv(&parsed, argv, 0); @@ -644,6 +645,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--git-dir")) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); static char cwd[PATH_MAX]; + int len; if (gitdir) { puts(gitdir); continue; @@ -654,7 +656,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } if (!getcwd(cwd, PATH_MAX)) die_errno("unable to get current working directory"); - printf("%s/.git\n", cwd); + len = strlen(cwd); + printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : ""); continue; } if (!strcmp(arg, "--is-inside-git-dir")) { diff --git a/builtin-revert.c b/builtin/revert.c index eff52687a8..8b9d829a73 100644 --- a/builtin-revert.c +++ b/builtin/revert.c @@ -13,6 +13,7 @@ #include "revision.h" #include "rerere.h" #include "merge-recursive.h" +#include "refs.h" /* * This implements the builtins revert and cherry-pick. @@ -35,71 +36,124 @@ static const char * const cherry_pick_usage[] = { NULL }; -static int edit, no_replay, no_commit, mainline, signoff; +static int edit, no_replay, no_commit, mainline, signoff, allow_ff; static enum { REVERT, CHERRY_PICK } action; static struct commit *commit; -static const char *commit_name; +static int commit_argc; +static const char **commit_argv; static int allow_rerere_auto; static const char *me; +static const char *strategy; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" +static char *get_encoding(const char *message); + +static const char * const *revert_or_cherry_pick_usage(void) +{ + return action == REVERT ? revert_usage : cherry_pick_usage; +} + static void parse_args(int argc, const char **argv) { - const char * const * usage_str = - action == REVERT ? revert_usage : cherry_pick_usage; - unsigned char sha1[20]; + const char * const * usage_str = revert_or_cherry_pick_usage(); int noop; struct option options[] = { OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"), OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"), - OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"), OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), 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_END(), + OPT_END(), OPT_END(), }; - if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1) + if (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_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); - commit_name = argv[0]; - if (get_sha1(commit_name, sha1)) - die ("Cannot find '%s'", commit_name); - commit = lookup_commit_reference(sha1); - if (!commit) - exit(1); + commit_argv = argv; } -static char *get_oneline(const char *message) +struct commit_message { + char *parent_label; + const char *label; + const char *subject; + char *reencoded_message; + const char *message; +}; + +static int get_message(const char *raw_message, struct commit_message *out) { - char *result; - const char *p = message, *abbrev, *eol; + const char *encoding; + const char *p, *abbrev, *eol; + char *q; int abbrev_len, oneline_len; - if (!p) - die ("Could not read commit message of %s", - sha1_to_hex(commit->object.sha1)); + if (!raw_message) + return -1; + encoding = get_encoding(raw_message); + if (!encoding) + encoding = "UTF-8"; + if (!git_commit_encoding) + git_commit_encoding = "UTF-8"; + + out->reencoded_message = NULL; + out->message = raw_message; + if (strcmp(encoding, git_commit_encoding)) + out->reencoded_message = reencode_string(raw_message, + git_commit_encoding, encoding); + if (out->reencoded_message) + out->message = out->reencoded_message; + + abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); + abbrev_len = strlen(abbrev); + + /* Find beginning and end of commit subject. */ + p = out->message; while (*p && (*p != '\n' || p[1] != '\n')) p++; - if (*p) { p += 2; for (eol = p + 1; *eol && *eol != '\n'; eol++) ; /* do nothing */ } else eol = p; - abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); - abbrev_len = strlen(abbrev); oneline_len = eol - p; - result = xmalloc(abbrev_len + 5 + oneline_len); - memcpy(result, abbrev, abbrev_len); - memcpy(result + abbrev_len, "... ", 4); - memcpy(result + abbrev_len + 4, p, oneline_len); - result[abbrev_len + 4 + oneline_len] = '\0'; - return result; + + out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + + strlen("... ") + oneline_len + 1); + q = out->parent_label; + q = mempcpy(q, "parent of ", strlen("parent of ")); + out->label = q; + q = mempcpy(q, abbrev, abbrev_len); + q = mempcpy(q, "... ", strlen("... ")); + out->subject = q; + q = mempcpy(q, p, oneline_len); + *q = '\0'; + return 0; +} + +static void free_message(struct commit_message *msg) +{ + free(msg->parent_label); + free(msg->reencoded_message); } static char *get_encoding(const char *message) @@ -124,28 +178,17 @@ static char *get_encoding(const char *message) return NULL; } -static struct lock_file msg_file; -static int msg_fd; - -static void add_to_msg(const char *string) -{ - int len = strlen(string); - if (write_in_full(msg_fd, string, len) < 0) - die_errno ("Could not write to MERGE_MSG"); -} - -static void add_message_to_msg(const char *message) +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) - add_to_msg(sha1_to_hex(commit->object.sha1)); + strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1)); p += 2; - add_to_msg(p); - return; + strbuf_addstr(msgbuf, p); } static void set_author_ident_env(const char *message) @@ -198,7 +241,7 @@ static void set_author_ident_env(const char *message) sha1_to_hex(commit->object.sha1)); } -static char *help_msg(const char *name) +static char *help_msg(void) { struct strbuf helpbuf = STRBUF_INIT; char *msg = getenv("GIT_CHERRY_PICK_HELP"); @@ -214,13 +257,26 @@ static char *help_msg(const char *name) strbuf_addf(&helpbuf, " with: \n" "\n" " git commit -c %s\n", - name); + sha1_to_hex(commit->object.sha1)); } else strbuf_addch(&helpbuf, '.'); return strbuf_detach(&helpbuf, NULL); } +static void write_message(struct strbuf *msgbuf, const char *filename) +{ + static struct lock_file msg_file; + + 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); + strbuf_release(msgbuf); + if (commit_lock_file(&msg_file) < 0) + die("Error wrapping up %s", filename); +} + static struct tree *empty_tree(void) { struct tree *tree = xcalloc(1, sizeof(struct tree)); @@ -244,29 +300,82 @@ static NORETURN void die_dirty_index(const char *me) } } -static int revert_or_cherry_pick(int argc, const char **argv) +static int fast_forward_to(const unsigned char *to, const unsigned char *from) +{ + struct ref_lock *ref_lock; + + read_cache(); + if (checkout_fast_forward(from, to)) + exit(1); /* the callee should have complained already */ + ref_lock = lock_any_ref_for_update("HEAD", from, 0); + return write_ref_sha1(ref_lock, to, "cherry-pick"); +} + +static void do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf, + char *defmsg) { - unsigned char head[20]; - struct commit *base, *next, *parent; - int i, index_fd, clean; - char *oneline, *reencoded_message = NULL; - const char *message, *encoding; - char *defmsg = git_pathdup("MERGE_MSG"); struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; + int clean, index_fd; static struct lock_file index_lock; - git_config(git_default_config, NULL); - me = action == REVERT ? "revert" : "cherry-pick"; - setenv(GIT_REFLOG_ACTION, me, 0); - parse_args(argc, argv); + index_fd = hold_locked_index(&index_lock, 1); - /* this is copied from the shell script, but it's never triggered... */ - if (action == REVERT && !no_replay) - die("revert is incompatible with replay"); + read_cache(); + init_merge_options(&o); + o.ancestor = base ? base_label : "(empty tree)"; + o.branch1 = "HEAD"; + o.branch2 = next ? next_label : "(empty tree)"; + + head_tree = parse_tree_indirect(head); + next_tree = next ? next->tree : empty_tree(); + base_tree = base ? base->tree : empty_tree(); + + clean = merge_trees(&o, + head_tree, + next_tree, base_tree, &result); + + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(&index_lock))) + die("%s: Unable to write new index file", me); + rollback_lock_file(&index_lock); + + if (!clean) { + int i; + strbuf_addstr(msgbuf, "\nConflicts:\n\n"); + for (i = 0; i < active_nr;) { + struct cache_entry *ce = active_cache[i++]; + if (ce_stage(ce)) { + strbuf_addch(msgbuf, '\t'); + strbuf_addstr(msgbuf, ce->name); + strbuf_addch(msgbuf, '\n'); + while (i < active_nr && !strcmp(ce->name, + active_cache[i]->name)) + i++; + } + } + write_message(msgbuf, defmsg); + fprintf(stderr, "Automatic %s failed.%s\n", + me, help_msg()); + rerere(allow_rerere_auto); + exit(1); + } + write_message(msgbuf, defmsg); + fprintf(stderr, "Finished one %s.\n", me); +} + +static int do_pick_commit(void) +{ + unsigned char head[20]; + struct commit *base, *next, *parent; + const char *base_label, *next_label; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; + char *defmsg = NULL; + struct strbuf msgbuf = STRBUF_INIT; - if (read_cache() < 0) - die("git %s: failed to read the index", me); if (no_commit) { /* * We do not intend to commit immediately. We just want to @@ -284,8 +393,6 @@ static int revert_or_cherry_pick(int argc, const char **argv) } discard_cache(); - index_fd = hold_locked_index(&index_lock, 1); - if (!commit->parents) { if (action == REVERT) die ("Cannot revert a root commit"); @@ -314,14 +421,17 @@ static int revert_or_cherry_pick(int argc, const char **argv) else parent = commit->parents->item; - if (!(message = commit->buffer)) - die ("Cannot get commit message for %s", - sha1_to_hex(commit->object.sha1)); + if (allow_ff && !hashcmp(parent->object.sha1, head)) + return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) die("%s: cannot parse parent commit %s", me, 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)); + /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -329,89 +439,60 @@ static int revert_or_cherry_pick(int argc, const char **argv) * reverse of it if we are revert. */ - msg_fd = hold_lock_file_for_update(&msg_file, defmsg, - LOCK_DIE_ON_ERROR); - - encoding = get_encoding(message); - if (!encoding) - encoding = "UTF-8"; - if (!git_commit_encoding) - git_commit_encoding = "UTF-8"; - if ((reencoded_message = reencode_string(message, - git_commit_encoding, encoding))) - message = reencoded_message; - - oneline = get_oneline(message); + defmsg = git_pathdup("MERGE_MSG"); if (action == REVERT) { - char *oneline_body = strchr(oneline, ' '); - base = commit; + base_label = msg.label; next = parent; - add_to_msg("Revert \""); - add_to_msg(oneline_body + 1); - add_to_msg("\"\n\nThis reverts commit "); - add_to_msg(sha1_to_hex(commit->object.sha1)); + next_label = msg.parent_label; + strbuf_addstr(&msgbuf, "Revert \""); + strbuf_addstr(&msgbuf, msg.subject); + strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); + strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); if (commit->parents->next) { - add_to_msg(", reversing\nchanges made to "); - add_to_msg(sha1_to_hex(parent->object.sha1)); + strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); + strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); } - add_to_msg(".\n"); + strbuf_addstr(&msgbuf, ".\n"); } else { base = parent; + base_label = msg.parent_label; next = commit; - set_author_ident_env(message); - add_message_to_msg(message); + next_label = msg.label; + set_author_ident_env(msg.message); + add_message_to_msg(&msgbuf, msg.message); if (no_replay) { - add_to_msg("(cherry picked from commit "); - add_to_msg(sha1_to_hex(commit->object.sha1)); - add_to_msg(")\n"); + strbuf_addstr(&msgbuf, "(cherry picked from commit "); + strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); + strbuf_addstr(&msgbuf, ")\n"); } } - read_cache(); - init_merge_options(&o); - o.branch1 = "HEAD"; - o.branch2 = oneline; - - head_tree = parse_tree_indirect(head); - next_tree = next ? next->tree : empty_tree(); - base_tree = base ? base->tree : empty_tree(); - - clean = merge_trees(&o, - head_tree, - next_tree, base_tree, &result); - - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(&index_lock))) - die("%s: Unable to write new index file", me); - rollback_lock_file(&index_lock); - - if (!clean) { - add_to_msg("\nConflicts:\n\n"); - for (i = 0; i < active_nr;) { - struct cache_entry *ce = active_cache[i++]; - if (ce_stage(ce)) { - add_to_msg("\t"); - add_to_msg(ce->name); - add_to_msg("\n"); - while (i < active_nr && !strcmp(ce->name, - active_cache[i]->name)) - i++; - } + if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) + do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf, defmsg); + else { + int res; + struct commit_list *common = NULL; + struct commit_list *remotes = NULL; + write_message(&msgbuf, defmsg); + commit_list_insert(base, &common); + commit_list_insert(next, &remotes); + res = try_merge_command(strategy, common, + sha1_to_hex(head), remotes); + free_commit_list(common); + free_commit_list(remotes); + if (res) { + fprintf(stderr, "Automatic %s with strategy %s failed.%s\n", + me, strategy, help_msg()); + rerere(allow_rerere_auto); + exit(1); } - if (commit_lock_file(&msg_file) < 0) - die ("Error wrapping up %s", defmsg); - fprintf(stderr, "Automatic %s failed.%s\n", - me, help_msg(commit_name)); - rerere(allow_rerere_auto); - exit(1); } - if (commit_lock_file(&msg_file) < 0) - die ("Error wrapping up %s", defmsg); - fprintf(stderr, "Finished one %s.\n", me); + + free_message(&msg); /* * @@ -425,7 +506,9 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (!no_commit) { /* 6 is max possible length of our args array including NULL */ const char *args[6]; + int res; int i = 0; + args[i++] = "commit"; args[i++] = "-n"; if (signoff) @@ -435,26 +518,81 @@ static int revert_or_cherry_pick(int argc, const char **argv) args[i++] = defmsg; } args[i] = NULL; - return execv_git_cmd(args); + res = run_command_v_opt(args, RUN_GIT_CMD); + free(defmsg); + + return res; } - free(reencoded_message); + free(defmsg); return 0; } +static void prepare_revs(struct rev_info *revs) +{ + 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 (prepare_revision_walk(revs)) + die("revision walk setup failed"); + + if (!revs->commits) + die("empty commit set passed"); +} + +static int revert_or_cherry_pick(int argc, const char **argv) +{ + struct rev_info revs; + + git_config(git_default_config, NULL); + me = action == REVERT ? "revert" : "cherry-pick"; + setenv(GIT_REFLOG_ACTION, me, 0); + parse_args(argc, argv); + + 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"); + } + + if (read_cache() < 0) + die("git %s: failed to read the index", me); + + prepare_revs(&revs); + + while ((commit = get_revision(&revs))) { + int res = do_pick_commit(); + if (res) + return res; + } + + return 0; +} + int cmd_revert(int argc, const char **argv, const char *prefix) { if (isatty(0)) edit = 1; - no_replay = 1; action = REVERT; return revert_or_cherry_pick(argc, argv); } int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - no_replay = 0; action = CHERRY_PICK; return revert_or_cherry_pick(argc, argv); } diff --git a/builtin-rm.c b/builtin/rm.c index f3772c84de..f3772c84de 100644 --- a/builtin-rm.c +++ b/builtin/rm.c diff --git a/builtin-send-pack.c b/builtin/send-pack.c index 2183a47052..481602d8ae 100644 --- a/builtin-send-pack.c +++ b/builtin/send-pack.c @@ -7,6 +7,7 @@ #include "remote.h" #include "send-pack.h" #include "quote.h" +#include "transport.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" @@ -169,156 +170,6 @@ static int receive_status(int in, struct ref *refs) return ret; } -static void update_tracking_ref(struct remote *remote, struct ref *ref) -{ - struct refspec rs; - - if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE) - return; - - rs.src = ref->name; - rs.dst = NULL; - - if (!remote_find_tracking(remote, &rs)) { - if (args.verbose) - fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); - if (ref->deletion) { - delete_ref(rs.dst, NULL, 0); - } else - update_ref("update by push", rs.dst, - ref->new_sha1, NULL, 0, 0); - free(rs.dst); - } -} - -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) - -static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg) -{ - fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); - if (from) - fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); - else - fputs(prettify_refname(to->name), stderr); - if (msg) { - fputs(" (", stderr); - fputs(msg, stderr); - fputc(')', stderr); - } - fputc('\n', stderr); -} - -static const char *status_abbrev(unsigned char sha1[20]) -{ - return find_unique_abbrev(sha1, DEFAULT_ABBREV); -} - -static void print_ok_ref_status(struct ref *ref) -{ - if (ref->deletion) - print_ref_status('-', "[deleted]", ref, NULL, NULL); - else if (is_null_sha1(ref->old_sha1)) - print_ref_status('*', - (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" : - "[new branch]"), - ref, ref->peer_ref, NULL); - else { - char quickref[84]; - char type; - const char *msg; - - strcpy(quickref, status_abbrev(ref->old_sha1)); - if (ref->nonfastforward) { - strcat(quickref, "..."); - type = '+'; - msg = "forced update"; - } else { - strcat(quickref, ".."); - type = ' '; - msg = NULL; - } - strcat(quickref, status_abbrev(ref->new_sha1)); - - print_ref_status(type, quickref, ref, ref->peer_ref, msg); - } -} - -static int print_one_push_status(struct ref *ref, const char *dest, int count) -{ - if (!count) - fprintf(stderr, "To %s\n", dest); - - switch(ref->status) { - case REF_STATUS_NONE: - print_ref_status('X', "[no match]", ref, NULL, NULL); - break; - case REF_STATUS_REJECT_NODELETE: - print_ref_status('!', "[rejected]", ref, NULL, - "remote does not support deleting refs"); - break; - case REF_STATUS_UPTODATE: - print_ref_status('=', "[up to date]", ref, - ref->peer_ref, NULL); - break; - case REF_STATUS_REJECT_NONFASTFORWARD: - print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast-forward"); - break; - case REF_STATUS_REMOTE_REJECT: - print_ref_status('!', "[remote rejected]", ref, - ref->deletion ? NULL : ref->peer_ref, - ref->remote_status); - break; - case REF_STATUS_EXPECTING_REPORT: - print_ref_status('!', "[remote failure]", ref, - ref->deletion ? NULL : ref->peer_ref, - "remote failed to report status"); - break; - case REF_STATUS_OK: - print_ok_ref_status(ref); - break; - } - - return 1; -} - -static void print_push_status(const char *dest, struct ref *refs) -{ - struct ref *ref; - int n = 0; - - if (args.verbose) { - for (ref = refs; ref; ref = ref->next) - if (ref->status == REF_STATUS_UPTODATE) - n += print_one_push_status(ref, dest, n); - } - - for (ref = refs; ref; ref = ref->next) - if (ref->status == REF_STATUS_OK) - n += print_one_push_status(ref, dest, n); - - for (ref = refs; ref; ref = ref->next) { - if (ref->status != REF_STATUS_NONE && - ref->status != REF_STATUS_UPTODATE && - ref->status != REF_STATUS_OK) - n += print_one_push_status(ref, dest, n); - } -} - -static int refs_pushed(struct ref *ref) -{ - for (; ref; ref = ref->next) { - switch(ref->status) { - case REF_STATUS_NONE: - case REF_STATUS_UPTODATE: - break; - default: - return 1; - } - } - return 0; -} - static void print_helper_status(struct ref *ref) { struct strbuf buf = STRBUF_INIT; @@ -510,6 +361,10 @@ int send_pack(struct send_pack_args *args, if (ret < 0) return ret; + + if (args->porcelain) + return 0; + for (ref = remote_refs; ref; ref = ref->next) { switch (ref->status) { case REF_STATUS_NONE: @@ -523,37 +378,6 @@ int send_pack(struct send_pack_args *args, return 0; } -static void verify_remote_names(int nr_heads, const char **heads) -{ - int i; - - for (i = 0; i < nr_heads; i++) { - const char *local = heads[i]; - const char *remote = strrchr(heads[i], ':'); - - if (*local == '+') - local++; - - /* A matching refspec is okay. */ - if (remote == local && remote[1] == '\0') - continue; - - remote = remote ? (remote + 1) : local; - switch (check_ref_format(remote)) { - case 0: /* ok */ - case CHECK_REF_FORMAT_ONELEVEL: - /* ok but a single level -- that is fine for - * a match pattern. - */ - case CHECK_REF_FORMAT_WILDCARD: - /* ok but ends with a pattern-match character */ - continue; - } - die("remote part of refspec is not a valid name in %s", - heads[i]); - } -} - int cmd_send_pack(int argc, const char **argv, const char *prefix) { int i, nr_refspecs = 0; @@ -570,6 +394,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int send_all = 0; const char *receivepack = "git-receive-pack"; int flags; + int nonfastforward = 0; argv++; for (i = 1; i < argc; i++, argv++) { @@ -662,7 +487,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL, &extra_have); - verify_remote_names(nr_refspecs, refspecs); + transport_verify_remote_names(nr_refspecs, refspecs); local_refs = get_local_heads(); @@ -691,15 +516,15 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) ret |= finish_connect(conn); if (!helper_status) - print_push_status(dest, remote_refs); + transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward); if (!args.dry_run && remote) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) - update_tracking_ref(remote, ref); + transport_update_tracking_ref(remote, ref, args.verbose); } - if (!ret && !refs_pushed(remote_refs)) + if (!ret && !transport_refs_pushed(remote_refs)) fprintf(stderr, "Everything up-to-date\n"); return ret; diff --git a/builtin-shortlog.c b/builtin/shortlog.c index ecd2d45a00..0a9681ba7e 100644 --- a/builtin-shortlog.c +++ b/builtin/shortlog.c @@ -84,7 +84,7 @@ static void insert_one_record(struct shortlog *log, snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf); } - item = string_list_insert(namebuf, &log->list); + item = string_list_insert(&log->list, namebuf); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); @@ -115,7 +115,7 @@ static void insert_one_record(struct shortlog *log, } } - string_list_append(buffer, item->util); + string_list_append(item->util, buffer); } static void read_from_stdin(struct shortlog *log) @@ -162,7 +162,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct pretty_print_context ctx = {0}; - ctx.abbrev = DEFAULT_ABBREV; + ctx.abbrev = log->abbrev; ctx.subject = ""; ctx.after_subject = ""; ctx.date_mode = DATE_NORMAL; @@ -290,11 +290,14 @@ parse_done: } log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT; + log.abbrev = rev.abbrev; /* assume HEAD if from a tty */ if (!nongit && !rev.pending.nr && isatty(0)) add_head_to_pending(&rev); if (rev.pending.nr == 0) { + if (isatty(0)) + fprintf(stderr, "(reading log message from standard input)\n"); read_from_stdin(&log); } else diff --git a/builtin-show-branch.c b/builtin/show-branch.c index 35a709e630..e8719aa9e9 100644 --- a/builtin-show-branch.c +++ b/builtin/show-branch.c @@ -6,7 +6,7 @@ #include "parse-options.h" static const char* show_branch_usage[] = { - "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...", + "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...", "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]", NULL }; @@ -313,7 +313,8 @@ static void show_one_commit(struct commit *commit, int no_name) } else printf("[%s] ", - find_unique_abbrev(commit->object.sha1, 7)); + find_unique_abbrev(commit->object.sha1, + DEFAULT_ABBREV)); } puts(pretty_str); strbuf_release(&pretty); @@ -661,7 +662,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) "show remote-tracking and local branches"), OPT_BOOLEAN('r', "remotes", &all_remotes, "show remote-tracking branches"), - OPT_BOOLEAN(0, "color", &showbranch_use_color, + OPT__COLOR(&showbranch_use_color, "color '*!+-' corresponding to the branch"), { OPTION_INTEGER, 0, "more", &extra, "n", "show <n> more commits after the common ancestor", diff --git a/builtin-show-ref.c b/builtin/show-ref.c index 17ada88dfb..0b2a9ad1a9 100644 --- a/builtin-show-ref.c +++ b/builtin/show-ref.c @@ -105,7 +105,7 @@ match: static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; - string_list_insert(refname, list); + string_list_insert(list, refname); return 0; } diff --git a/builtin-stripspace.c b/builtin/stripspace.c index 4d3b93fedb..4d3b93fedb 100644 --- a/builtin-stripspace.c +++ b/builtin/stripspace.c diff --git a/builtin-symbolic-ref.c b/builtin/symbolic-ref.c index ca855a5eb2..ca855a5eb2 100644 --- a/builtin-symbolic-ref.c +++ b/builtin/symbolic-ref.c diff --git a/builtin-tag.c b/builtin/tag.c index 4ef1c4f508..d311491e49 100644 --- a/builtin-tag.c +++ b/builtin/tag.c @@ -147,11 +147,11 @@ static int delete_tag(const char *name, const char *ref, static int verify_tag(const char *name, const char *ref, const unsigned char *sha1) { - const char *argv_verify_tag[] = {"git-verify-tag", + const char *argv_verify_tag[] = {"verify-tag", "-v", "SHA1_HEX", NULL}; argv_verify_tag[2] = sha1_to_hex(sha1); - if (run_command_v_opt(argv_verify_tag, 0)) + if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD)) return error("could not verify the tag '%s'", name); return 0; } diff --git a/builtin-tar-tree.c b/builtin/tar-tree.c index 3f1e7012db..3f1e7012db 100644 --- a/builtin-tar-tree.c +++ b/builtin/tar-tree.c diff --git a/builtin-unpack-file.c b/builtin/unpack-file.c index 608590ada8..608590ada8 100644 --- a/builtin-unpack-file.c +++ b/builtin/unpack-file.c diff --git a/builtin-unpack-objects.c b/builtin/unpack-objects.c index 685566e0b5..685566e0b5 100644 --- a/builtin-unpack-objects.c +++ b/builtin/unpack-objects.c diff --git a/builtin-update-index.c b/builtin/update-index.c index 3ab214d24e..3ab214d24e 100644 --- a/builtin-update-index.c +++ b/builtin/update-index.c diff --git a/builtin-update-ref.c b/builtin/update-ref.c index 76ba1d5881..76ba1d5881 100644 --- a/builtin-update-ref.c +++ b/builtin/update-ref.c diff --git a/builtin-update-server-info.c b/builtin/update-server-info.c index 2b3fddcc69..2b3fddcc69 100644 --- a/builtin-update-server-info.c +++ b/builtin/update-server-info.c diff --git a/builtin-upload-archive.c b/builtin/upload-archive.c index 73f788ef24..73f788ef24 100644 --- a/builtin-upload-archive.c +++ b/builtin/upload-archive.c diff --git a/builtin-var.c b/builtin/var.c index 70fdb4dec7..70fdb4dec7 100644 --- a/builtin-var.c +++ b/builtin/var.c diff --git a/builtin-verify-pack.c b/builtin/verify-pack.c index b6079ae6cb..b6079ae6cb 100644 --- a/builtin-verify-pack.c +++ b/builtin/verify-pack.c diff --git a/builtin-verify-tag.c b/builtin/verify-tag.c index 9f482c29f5..9f482c29f5 100644 --- a/builtin-verify-tag.c +++ b/builtin/verify-tag.c diff --git a/builtin-write-tree.c b/builtin/write-tree.c index b223af416f..b223af416f 100644 --- a/builtin-write-tree.c +++ b/builtin/write-tree.c @@ -361,7 +361,7 @@ enum object_type { OBJ_OFS_DELTA = 6, OBJ_REF_DELTA = 7, OBJ_ANY, - OBJ_MAX, + OBJ_MAX }; static inline enum object_type object_type(unsigned int mode) @@ -387,6 +387,9 @@ static inline enum object_type object_type(unsigned int mode) #define ATTRIBUTE_MACRO_PREFIX "[attr]" #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF" #define GIT_NOTES_DEFAULT_REF "refs/notes/commits" +#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF" +#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF" +#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE" /* * Repository-local GIT_* environment variables @@ -446,7 +449,7 @@ extern int init_db(const char *template_dir, unsigned int flags); alloc = alloc_nr(alloc); \ x = xrealloc((x), alloc * sizeof(*(x))); \ } \ - } while(0) + } while (0) /* Initialize and use the cache information */ extern int read_index(struct index_state *); @@ -544,7 +547,6 @@ extern int core_compression_seen; extern size_t packed_git_window_size; extern size_t packed_git_limit; extern size_t delta_base_cache_limit; -extern int auto_crlf; extern int read_replace_refs; extern int fsync_object_files; extern int core_preload_index; @@ -553,32 +555,53 @@ extern int core_apply_sparse_checkout; enum safe_crlf { SAFE_CRLF_FALSE = 0, SAFE_CRLF_FAIL = 1, - SAFE_CRLF_WARN = 2, + SAFE_CRLF_WARN = 2 }; extern enum safe_crlf safe_crlf; +enum auto_crlf { + AUTO_CRLF_FALSE = 0, + AUTO_CRLF_TRUE = 1, + AUTO_CRLF_INPUT = -1, +}; + +extern enum auto_crlf auto_crlf; + +enum eol { + EOL_UNSET, + EOL_CRLF, + EOL_LF, +#ifdef NATIVE_CRLF + EOL_NATIVE = EOL_CRLF +#else + EOL_NATIVE = EOL_LF +#endif +}; + +extern enum eol eol; + enum branch_track { BRANCH_TRACK_UNSPECIFIED = -1, BRANCH_TRACK_NEVER = 0, BRANCH_TRACK_REMOTE, BRANCH_TRACK_ALWAYS, BRANCH_TRACK_EXPLICIT, - BRANCH_TRACK_OVERRIDE, + BRANCH_TRACK_OVERRIDE }; enum rebase_setup_type { AUTOREBASE_NEVER = 0, AUTOREBASE_LOCAL, AUTOREBASE_REMOTE, - AUTOREBASE_ALWAYS, + AUTOREBASE_ALWAYS }; enum push_default_type { PUSH_DEFAULT_NOTHING = 0, PUSH_DEFAULT_MATCHING, PUSH_DEFAULT_TRACKING, - PUSH_DEFAULT_CURRENT, + PUSH_DEFAULT_CURRENT }; extern enum branch_track git_branch_track; @@ -587,7 +610,7 @@ extern enum push_default_type push_default; enum object_creation_mode { OBJECT_CREATION_USES_HARDLINKS = 0, - OBJECT_CREATION_USES_RENAMES = 1, + OBJECT_CREATION_USES_RENAMES = 1 }; extern enum object_creation_mode object_creation_mode; @@ -667,7 +690,7 @@ enum sharedrepo { OLD_PERM_GROUP = 1, OLD_PERM_EVERYBODY = 2, PERM_GROUP = 0660, - PERM_EVERYBODY = 0664, + PERM_EVERYBODY = 0664 }; int git_config_perm(const char *var, const char *value); int set_shared_perm(const char *path, int mode); @@ -688,6 +711,7 @@ int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); char *strip_path_suffix(const char *path, const char *suffix); int daemon_avoid_alias(const char *path); +int offset_1st_component(const char *path); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); @@ -697,7 +721,7 @@ static inline void *read_sha1_file(const unsigned char *sha1, enum object_type * return read_sha1_file_repl(sha1, type, size, NULL); } extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1); -extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); +extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1); extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); @@ -714,6 +738,8 @@ extern int has_loose_object_nonlocal(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); +extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect); + extern const signed char hexval_table[256]; static inline unsigned int hexval(unsigned char c) { @@ -724,12 +750,23 @@ static inline unsigned int hexval(unsigned char c) #define MINIMUM_ABBREV 4 #define DEFAULT_ABBREV 7 +struct object_context { + unsigned char tree[20]; + char path[PATH_MAX]; + unsigned mode; +}; + extern int get_sha1(const char *str, unsigned char *sha1); extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix); static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode) { return get_sha1_with_mode_1(str, sha1, mode, 1, NULL); } +extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int gently, const char *prefix); +static inline int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc) +{ + return get_sha1_with_context_1(str, sha1, orc, 1, NULL); +} 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); @@ -774,6 +811,7 @@ const char *show_date_relative(unsigned long time, int tz, char *timebuf, size_t timebuf_size); int parse_date(const char *date, char *buf, int bufsize); +int parse_date_basic(const char *date, unsigned long *timestamp, int *offset); void datestamp(char *buf, int bufsize); #define approxidate(s) approxidate_careful((s), NULL) unsigned long approxidate_careful(const char *, int *); @@ -876,7 +914,7 @@ struct ref { REF_STATUS_REJECT_NODELETE, REF_STATUS_UPTODATE, REF_STATUS_REMOTE_REJECT, - REF_STATUS_EXPECTING_REPORT, + REF_STATUS_EXPECTING_REPORT } status; char *remote_status; struct ref *peer_ref; /* when renaming */ @@ -890,6 +928,7 @@ 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 path_match(const char *path, int nr, char **match); @@ -900,7 +939,7 @@ struct extra_have_objects { extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *); extern int server_supports(const char *feature); -extern struct packed_git *parse_pack_index(unsigned char *sha1); +extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); extern void prepare_packed_git(void); extern void reprepare_packed_git(void); @@ -911,6 +950,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1, extern void pack_report(void); extern int open_pack_index(struct packed_git *); +extern void close_pack_index(struct packed_git *); extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); extern void close_pack_windows(struct packed_git *); extern void unuse_pack(struct pack_window **); @@ -931,12 +971,15 @@ extern int update_server_info(int); typedef int (*config_fn_t)(const char *, const char *, void *); extern int git_default_config(const char *, const char *, void *); extern int git_config_from_file(config_fn_t fn, const char *, void *); +extern int git_config_parse_parameter(const char *text); +extern int git_config_from_parameters(config_fn_t fn, void *data); extern int git_config(config_fn_t fn, void *); extern int git_parse_ulong(const char *, unsigned long *); extern int git_config_int(const char *, const char *); extern unsigned long git_config_ulong(const char *, const char *); extern int git_config_bool_or_int(const char *, const char *, int *); 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(const char *, const char *); @@ -944,6 +987,7 @@ extern int git_config_set_multivar(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); +extern int git_env_bool(const char *, int); extern int git_config_system(void); extern int git_config_global(void); extern int config_error_nonbool(const char *); @@ -1035,6 +1079,7 @@ void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char * #define WS_INDENT_WITH_NON_TAB 04 #define WS_CR_AT_EOL 010 #define WS_BLANK_AT_EOF 020 +#define WS_TAB_IN_INDENT 040 #define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) extern unsigned whitespace_rule_cfg; @@ -1043,7 +1088,7 @@ extern unsigned parse_whitespace_rule(const char *); extern unsigned ws_check(const char *line, int len, unsigned ws_rule); extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws); extern char *whitespace_error_string(unsigned ws); -extern int ws_fix_copy(char *, const char *, int, unsigned, int *); +extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *); extern int ws_blank_line(const char *line, int len, unsigned ws_rule); /* ls-files */ @@ -1053,4 +1098,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix); char *alias_lookup(const char *alias); int split_cmdline(char *cmdline, const char ***argv); +/* builtin/merge.c */ +int checkout_fast_forward(const unsigned char *from, const unsigned char *to); + #endif /* CACHE_H */ @@ -146,6 +146,9 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) goto auto_color; } + if (!var) + return -1; + /* Missing or explicit false to turn off colorization */ if (!git_config_bool(var, value)) return 0; @@ -208,31 +211,3 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) va_end(args); return r; } - -/* - * This function splits the buffer by newlines and colors the lines individually. - * - * Returns 0 on success. - */ -int color_fwrite_lines(FILE *fp, const char *color, - size_t count, const char *buf) -{ - if (!*color) - return fwrite(buf, count, 1, fp) != 1; - while (count) { - char *p = memchr(buf, '\n', count); - if (p != buf && (fputs(color, fp) < 0 || - fwrite(buf, p ? p - buf : count, 1, fp) != 1 || - fputs(GIT_COLOR_RESET, fp) < 0)) - return -1; - if (!p) - return 0; - if (fputc('\n', fp) < 0) - return -1; - count -= p + 1 - buf; - buf = p + 1; - } - return 0; -} - - @@ -30,7 +30,18 @@ #define GIT_COLOR_BLUE "\033[34m" #define GIT_COLOR_MAGENTA "\033[35m" #define GIT_COLOR_CYAN "\033[36m" +#define GIT_COLOR_BOLD_RED "\033[1;31m" +#define GIT_COLOR_BOLD_GREEN "\033[1;32m" +#define GIT_COLOR_BOLD_YELLOW "\033[1;33m" +#define GIT_COLOR_BOLD_BLUE "\033[1;34m" +#define GIT_COLOR_BOLD_MAGENTA "\033[1;35m" +#define GIT_COLOR_BOLD_CYAN "\033[1;36m" #define GIT_COLOR_BG_RED "\033[41m" +#define GIT_COLOR_BG_GREEN "\033[42m" +#define GIT_COLOR_BG_YELLOW "\033[43m" +#define GIT_COLOR_BG_BLUE "\033[44m" +#define GIT_COLOR_BG_MAGENTA "\033[45m" +#define GIT_COLOR_BG_CYAN "\033[46m" /* * This variable stores the value of color.ui @@ -50,6 +61,5 @@ __attribute__((format (printf, 3, 4))) int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); __attribute__((format (printf, 3, 4))) int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); -int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf); #endif /* COLOR_H */ diff --git a/combine-diff.c b/combine-diff.c index 7557136c82..655fa89d8a 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -220,7 +220,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, parent_file.ptr = grab_blob(parent, mode, &sz); parent_file.size = sz; memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); memset(&state, 0, sizeof(state)); state.nmask = nmask; @@ -790,3 +790,58 @@ struct commit_list *reduce_heads(struct commit_list *heads) free(other); 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 result; + int encoding_is_utf8; + struct strbuf buffer; + + assert_sha1_type(tree, OBJ_TREE); + + /* Not having i18n.commitencoding is the same as having utf-8 */ + encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); + + strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ + strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree)); + + /* + * NOTE! This ordering means that the same exact tree merged with a + * different order of parents will be a _different_ changeset even + * if everything else stays the same. + */ + while (parents) { + struct commit_list *next = parents->next; + strbuf_addf(&buffer, "parent %s\n", + sha1_to_hex(parents->item->object.sha1)); + free(parents); + parents = next; + } + + /* Person/date information */ + if (!author) + author = git_author_info(IDENT_ERROR_ON_NO_NAME); + strbuf_addf(&buffer, "author %s\n", author); + 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); + strbuf_addch(&buffer, '\n'); + + /* And add the comment */ + strbuf_addstr(&buffer, msg); + + /* And check the encoding */ + if (encoding_is_utf8 && !is_utf8(buffer.buf)) + fprintf(stderr, commit_utf8_warn); + + result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret); + strbuf_release(&buffer); + return result; +} @@ -28,6 +28,7 @@ extern const char *commit_type; extern struct decoration name_decoration; struct name_decoration { struct name_decoration *next; + int type; char name[1]; }; @@ -60,7 +61,7 @@ enum cmit_fmt { CMIT_FMT_EMAIL, CMIT_FMT_USERFORMAT, - CMIT_FMT_UNSPECIFIED, + CMIT_FMT_UNSPECIFIED }; struct pretty_print_context @@ -74,11 +75,16 @@ struct pretty_print_context struct reflog_walk_info *reflog_info; }; +struct userformat_want { + unsigned notes:1; +}; + extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ extern char *reencode_commit_message(const struct commit *commit, const char **encoding_p); extern void get_commit_format(const char *arg, struct rev_info *); +extern void userformat_find_requirements(const char *fmt, struct userformat_want *w); extern void format_commit_message(const struct commit *commit, const char *format, struct strbuf *sb, const struct pretty_print_context *context); @@ -158,4 +164,8 @@ static inline int single_parent(struct commit *commit) struct commit_list *reduce_heads(struct commit_list *heads); +extern int commit_tree(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author); + #endif /* COMMIT_H */ diff --git a/compat/mingw.c b/compat/mingw.c index 59b18dc7ca..9a8e336582 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -140,6 +140,39 @@ int mingw_open (const char *filename, int oflags, ...) return fd; } +#undef write +ssize_t mingw_write(int fd, const void *buf, size_t count) +{ + /* + * While write() calls to a file on a local disk are translated + * into WriteFile() calls with a maximum size of 64KB on Windows + * XP and 256KB on Vista, no such cap is placed on writes to + * files over the network on Windows XP. Unfortunately, there + * seems to be a limit of 32MB-28KB on X64 and 64MB-32KB on x86; + * bigger writes fail on Windows XP. + * So we cap to a nice 31MB here to avoid write failures over + * the net without changing the number of WriteFile() calls in + * the local case. + */ + return write(fd, buf, min(count, 31 * 1024 * 1024)); +} + +#undef fopen +FILE *mingw_fopen (const char *filename, const char *otype) +{ + if (!strcmp(filename, "/dev/null")) + filename = "nul"; + return fopen(filename, otype); +} + +#undef freopen +FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) +{ + if (filename && !strcmp(filename, "/dev/null")) + filename = "nul"; + return freopen(filename, otype, stream); +} + /* * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. @@ -634,6 +667,7 @@ static int env_compare(const void *a, const void *b) } static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, + const char *dir, int prepend_cmd, int fhin, int fhout, int fherr) { STARTUPINFO si; @@ -713,7 +747,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, memset(&pi, 0, sizeof(pi)); ret = CreateProcess(cmd, args.buf, NULL, NULL, TRUE, flags, - env ? envblk.buf : NULL, NULL, &si, &pi); + env ? envblk.buf : NULL, dir, &si, &pi); if (env) strbuf_release(&envblk); @@ -730,10 +764,11 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, int prepend_cmd) { - return mingw_spawnve_fd(cmd, argv, env, prepend_cmd, 0, 1, 2); + return mingw_spawnve_fd(cmd, argv, env, NULL, prepend_cmd, 0, 1, 2); } pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + const char *dir, int fhin, int fhout, int fherr) { pid_t pid; @@ -756,14 +791,14 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, pid = -1; } else { - pid = mingw_spawnve_fd(iprog, argv, env, 1, + pid = mingw_spawnve_fd(iprog, argv, env, dir, 1, fhin, fhout, fherr); free(iprog); } argv[0] = argv0; } else - pid = mingw_spawnve_fd(prog, argv, env, 0, + pid = mingw_spawnve_fd(prog, argv, env, dir, 0, fhin, fhout, fherr); free(prog); } diff --git a/compat/mingw.h b/compat/mingw.h index e254fb4e06..3b2477be5f 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -80,7 +80,7 @@ static inline int fork(void) static inline unsigned int alarm(unsigned int seconds) { return 0; } static inline int fsync(int fd) -{ return 0; } +{ return _commit(fd); } static inline int getppid(void) { return 1; } static inline void sync(void) @@ -89,7 +89,7 @@ static inline int getuid() { return 1; } static inline struct passwd *getpwnam(const char *name) { return NULL; } -static inline int fcntl(int fd, int cmd, long arg) +static inline int fcntl(int fd, int cmd, ...) { if (cmd == F_GETFD || cmd == F_SETFD) return 0; @@ -170,6 +170,15 @@ int link(const char *oldpath, const char *newpath); int mingw_open (const char *filename, int oflags, ...); #define open mingw_open +ssize_t mingw_write(int fd, const void *buf, size_t count); +#define write mingw_write + +FILE *mingw_fopen (const char *filename, const char *otype); +#define fopen mingw_fopen + +FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream); +#define freopen mingw_freopen + char *mingw_getcwd(char *pointer, int len); #define getcwd mingw_getcwd @@ -223,6 +232,7 @@ int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + const char *dir, int fhin, int fhout, int fherr); void mingw_execvp(const char *cmd, char *const *argv); #define execvp mingw_execvp diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index 0f949fc425..010e875ec4 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c @@ -16,6 +16,7 @@ static unsigned __stdcall win32_start_routine(void *arg) { pthread_t *thread = arg; + thread->tid = GetCurrentThreadId(); thread->arg = thread->start_routine(thread->arg); return 0; } @@ -49,6 +50,13 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr) } } +pthread_t pthread_self(void) +{ + pthread_t t = { 0 }; + t.tid = GetCurrentThreadId(); + return t; +} + int pthread_cond_init(pthread_cond_t *cond, const void *unused) { cond->waiters = 0; diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h index c72f100f40..2e20548557 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h @@ -18,11 +18,17 @@ */ #define pthread_mutex_t CRITICAL_SECTION -#define pthread_mutex_init(a,b) InitializeCriticalSection((a)) +#define pthread_mutex_init(a,b) (InitializeCriticalSection((a)), 0) #define pthread_mutex_destroy(a) DeleteCriticalSection((a)) #define pthread_mutex_lock EnterCriticalSection #define pthread_mutex_unlock LeaveCriticalSection +typedef int pthread_mutexattr_t; +#define pthread_mutexattr_init(a) (*(a) = 0) +#define pthread_mutexattr_destroy(a) do {} while (0) +#define pthread_mutexattr_settype(a, t) 0 +#define PTHREAD_MUTEX_RECURSIVE 0 + /* * Implement simple condition variable for Windows threads, based on ACE * implementation. @@ -52,6 +58,7 @@ typedef struct { HANDLE handle; void *(*start_routine)(void*); void *arg; + DWORD tid; } pthread_t; extern int pthread_create(pthread_t *thread, const void *unused, @@ -65,4 +72,28 @@ extern int pthread_create(pthread_t *thread, const void *unused, extern int win32_pthread_join(pthread_t *thread, void **value_ptr); +#define pthread_equal(t1, t2) ((t1).tid == (t2).tid) +extern pthread_t pthread_self(void); + +static inline int pthread_exit(void *ret) +{ + ExitThread((DWORD)ret); +} + +typedef DWORD pthread_key_t; +static inline int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *value)) +{ + return (*keyp = TlsAlloc()) == TLS_OUT_OF_INDEXES ? EAGAIN : 0; +} + +static inline int pthread_setspecific(pthread_key_t key, const void *value) +{ + return TlsSetValue(key, (void *)value) ? 0 : EINVAL; +} + +static inline void *pthread_getspecific(pthread_key_t key) +{ + return TlsGetValue(key); +} + #endif /* PTHREAD_H */ diff --git a/compat/win32mmap.c b/compat/win32mmap.c index 1c5a14922f..b58aa69fa0 100644 --- a/compat/win32mmap.c +++ b/compat/win32mmap.c @@ -4,19 +4,19 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of { HANDLE hmap; void *temp; - size_t len; + off_t len; struct stat st; uint64_t o = offset; uint32_t l = o & 0xFFFFFFFF; uint32_t h = (o >> 32) & 0xFFFFFFFF; if (!fstat(fd, &st)) - len = xsize_t(st.st_size); + len = st.st_size; else die("mmap: could not determine filesize"); if ((length + offset) > len) - length = len - offset; + length = xsize_t(len - offset); if (!(flags & MAP_PRIVATE)) die("Invalid usage of mmap when built with USE_WIN32_MMAP"); @@ -7,6 +7,7 @@ */ #include "cache.h" #include "exec_cmd.h" +#include "strbuf.h" #define MAXNAME (256) @@ -18,6 +19,48 @@ static int zlib_compression_seen; const char *config_exclusive_filename = NULL; +struct config_item +{ + struct config_item *next; + char *name; + char *value; +}; +static struct config_item *config_parameters; +static struct config_item **config_parameters_tail = &config_parameters; + +static void lowercase(char *p) +{ + for (; *p; p++) + *p = tolower(*p); +} + +int git_config_parse_parameter(const char *text) +{ + struct config_item *ct; + struct strbuf tmp = STRBUF_INIT; + struct strbuf **pair; + strbuf_addstr(&tmp, text); + pair = strbuf_split(&tmp, '='); + if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') + strbuf_setlen(pair[0], pair[0]->len - 1); + strbuf_trim(pair[0]); + if (!pair[0]->len) { + strbuf_list_free(pair); + return -1; + } + ct = xcalloc(1, sizeof(struct config_item)); + ct->name = strbuf_detach(pair[0], NULL); + if (pair[1]) { + strbuf_trim(pair[1]); + ct->value = strbuf_detach(pair[1], NULL); + } + strbuf_list_free(pair); + lowercase(ct->name); + *config_parameters_tail = ct; + config_parameters_tail = &ct->next; + return 0; +} + static int get_next_char(void) { int c; @@ -322,17 +365,30 @@ unsigned long git_config_ulong(const char *name, const char *value) return ret; } -int git_config_bool_or_int(const char *name, const char *value, int *is_bool) +int git_config_maybe_bool(const char *name, const char *value) { - *is_bool = 1; if (!value) return 1; if (!*value) return 0; - if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) + if (!strcasecmp(value, "true") + || !strcasecmp(value, "yes") + || !strcasecmp(value, "on")) return 1; - if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off")) + if (!strcasecmp(value, "false") + || !strcasecmp(value, "no") + || !strcasecmp(value, "off")) return 0; + return -1; +} + +int git_config_bool_or_int(const char *name, const char *value, int *is_bool) +{ + int v = git_config_maybe_bool(name, value); + if (0 <= v) { + *is_bool = 1; + return v; + } *is_bool = 0; return git_config_int(name, value); } @@ -461,7 +517,9 @@ static int git_default_core_config(const char *var, const char *value) if (!strcmp(var, "core.autocrlf")) { if (value && !strcasecmp(value, "input")) { - auto_crlf = -1; + if (eol == EOL_CRLF) + return error("core.autocrlf=input conflicts with core.eol=crlf"); + auto_crlf = AUTO_CRLF_INPUT; return 0; } auto_crlf = git_config_bool(var, value); @@ -477,6 +535,20 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.eol")) { + if (value && !strcasecmp(value, "lf")) + eol = EOL_LF; + else if (value && !strcasecmp(value, "crlf")) + eol = EOL_CRLF; + else if (value && !strcasecmp(value, "native")) + eol = EOL_NATIVE; + else + eol = EOL_UNSET; + if (eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT) + return error("core.autocrlf=input conflicts with core.eol=crlf"); + return 0; + } + if (!strcmp(var, "core.notesref")) { notes_ref_name = xstrdup(value); return 0; @@ -683,7 +755,7 @@ const char *git_etc_gitconfig(void) return system_wide; } -static int git_env_bool(const char *k, int def) +int git_env_bool(const char *k, int def) { const char *v = getenv(k); return v ? git_config_bool(k, v) : def; @@ -699,6 +771,15 @@ int git_config_global(void) return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0); } +int git_config_from_parameters(config_fn_t fn, void *data) +{ + const struct config_item *ct; + for (ct = config_parameters; ct; ct = ct->next) + if (fn(ct->name, ct->value, data) < 0) + return -1; + return 0; +} + int git_config(config_fn_t fn, void *data) { int ret = 0, found = 0; @@ -730,6 +811,12 @@ int git_config(config_fn_t fn, void *data) found += 1; } free(repo_config); + + if (config_parameters) { + ret += git_config_from_parameters(fn, data); + found += 1; + } + if (found == 0) return -1; return ret; diff --git a/config.mak.in b/config.mak.in index 6008ac9f1b..b4e65c32b2 100644 --- a/config.mak.in +++ b/config.mak.in @@ -3,10 +3,12 @@ CC = @CC@ CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ LDFLAGS = @LDFLAGS@ CC_LD_DYNPATH = @CC_LD_DYNPATH@ AR = @AR@ TAR = @TAR@ +DIFF = @DIFF@ #INSTALL = @INSTALL@ # needs install-sh or install.sh in sources TCLTK_PATH = @TCLTK_PATH@ @@ -31,6 +33,7 @@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ NO_EXPAT=@NO_EXPAT@ NO_LIBGEN_H=@NO_LIBGEN_H@ +HAVE_PATHS_H=@HAVE_PATHS_H@ NEEDS_LIBICONV=@NEEDS_LIBICONV@ NEEDS_SOCKET=@NEEDS_SOCKET@ NEEDS_RESOLV=@NEEDS_RESOLV@ @@ -41,6 +44,7 @@ NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@ NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@ NO_IPV6=@NO_IPV6@ NO_C99_FORMAT=@NO_C99_FORMAT@ +NO_HSTRERROR=@NO_HSTRERROR@ NO_STRCASESTR=@NO_STRCASESTR@ NO_MEMMEM=@NO_MEMMEM@ NO_STRLCPY=@NO_STRLCPY@ @@ -50,10 +54,15 @@ NO_SETENV=@NO_SETENV@ NO_UNSETENV=@NO_UNSETENV@ NO_MKDTEMP=@NO_MKDTEMP@ NO_MKSTEMPS=@NO_MKSTEMPS@ +NO_INET_NTOP=@NO_INET_NTOP@ +NO_INET_PTON=@NO_INET_PTON@ NO_ICONV=@NO_ICONV@ OLD_ICONV=@OLD_ICONV@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@ +INLINE=@INLINE@ +SOCKLEN_T=@SOCKLEN_T@ FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@ SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@ NO_PTHREADS=@NO_PTHREADS@ +PTHREAD_CFLAGS=@PTHREAD_CFLAGS@ PTHREAD_LIBS=@PTHREAD_LIBS@ diff --git a/configure.ac b/configure.ac index 914ae5759f..5601e8bac9 100644 --- a/configure.ac +++ b/configure.ac @@ -179,6 +179,26 @@ fi], AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads.]) ]) +# Define option to enable JavaScript minification +AC_ARG_ENABLE([jsmin], +[AS_HELP_STRING([--enable-jsmin=PATH], + [PATH is the name of a JavaScript minifier or the absolute path to one.])], +[ + JSMIN=$enableval; + AC_MSG_NOTICE([Setting JSMIN to '$JSMIN' to enable JavaScript minifying]) + GIT_CONF_APPEND_LINE(JSMIN=$enableval); +]) + +# Define option to enable CSS minification +AC_ARG_ENABLE([cssmin], +[AS_HELP_STRING([--enable-cssmin=PATH], + [PATH is the name of a CSS minifier or the absolute path to one.])], +[ + CSSMIN=$enableval; + AC_MSG_NOTICE([Setting CSSMIN to '$CSSMIN' to enable CSS minifying]) + GIT_CONF_APPEND_LINE(CSSMIN=$enableval); +]) + ## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE AC_MSG_NOTICE([CHECKS for site configuration]) @@ -307,6 +327,12 @@ GIT_PARSE_WITH(tcltk)) AC_MSG_NOTICE([CHECKS for programs]) # AC_PROG_CC([cc gcc]) +AC_C_INLINE +case $ac_cv_c_inline in + inline | yes | no) ;; + *) AC_SUBST([INLINE], [$ac_cv_c_inline]) ;; +esac + # which switch to pass runtime path to dynamic libraries to the linker AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [ SAVE_LDFLAGS="${LDFLAGS}" @@ -342,6 +368,7 @@ fi #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOLS(AR, [gar ar], :) AC_CHECK_PROGS(TAR, [gtar tar]) +AC_CHECK_PROGS(DIFF, [gnudiff gdiff diff]) # TCLTK_PATH will be set to some value if we want Tcl/Tk # or will be empty otherwise. if test -z "$NO_TCLTK"; then @@ -524,13 +551,47 @@ AC_SUBST(NEEDS_SOCKET) test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket" # -# Define NEEDS_RESOLV if linking with -lnsl and/or -lsocket is not enough. -# Notably on Solaris hstrerror resides in libresolv and on Solaris 7 -# inet_ntop and inet_pton additionally reside there. -AC_CHECK_LIB([c], [hstrerror], -[NEEDS_RESOLV=], -[NEEDS_RESOLV=YesPlease]) +# The next few tests will define NEEDS_RESOLV if linking with +# libresolv provides some of the functions we would normally get +# from libc. +NEEDS_RESOLV= AC_SUBST(NEEDS_RESOLV) +# +# Define NO_INET_NTOP if linking with -lresolv is not enough. +# Solaris 2.7 in particular hos inet_ntop in -lresolv. +NO_INET_NTOP= +AC_SUBST(NO_INET_NTOP) +AC_CHECK_FUNC([inet_ntop], + [], + [AC_CHECK_LIB([resolv], [inet_ntop], + [NEEDS_RESOLV=YesPlease], + [NO_INET_NTOP=YesPlease]) +]) +# +# Define NO_INET_PTON if linking with -lresolv is not enough. +# Solaris 2.7 in particular hos inet_pton in -lresolv. +NO_INET_PTON= +AC_SUBST(NO_INET_PTON) +AC_CHECK_FUNC([inet_pton], + [], + [AC_CHECK_LIB([resolv], [inet_pton], + [NEEDS_RESOLV=YesPlease], + [NO_INET_PTON=YesPlease]) +]) +# +# Define NO_HSTRERROR if linking with -lresolv is not enough. +# Solaris 2.6 in particular has no hstrerror, even in -lresolv. +NO_HSTRERROR= +AC_CHECK_FUNC([hstrerror], + [], + [AC_CHECK_LIB([resolv], [hstrerror], + [NEEDS_RESOLV=YesPlease], + [NO_HSTRERROR=YesPlease]) +]) +AC_SUBST(NO_HSTRERROR) +# +# If any of the above tests determined that -lresolv is needed at +# build-time, also set it here for remaining configure-time checks. test -n "$NEEDS_RESOLV" && LIBS="$LIBS -lresolv" AC_CHECK_LIB([c], [basename], @@ -578,6 +639,12 @@ AC_SUBST(OLD_ICONV) ## Checks for typedefs, structures, and compiler characteristics. AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics]) # +TYPE_SOCKLEN_T +case $ac_cv_type_socklen_t in + yes) ;; + *) AC_SUBST([SOCKLEN_T], [$git_cv_socklen_t_equiv]) ;; +esac + # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. AC_CHECK_MEMBER(struct dirent.d_ino, [NO_D_INO_IN_DIRENT=], @@ -704,6 +771,12 @@ AC_CHECK_HEADER([libgen.h], [NO_LIBGEN_H=YesPlease]) AC_SUBST(NO_LIBGEN_H) # +# Define HAVE_PATHS_H if you have paths.h. +AC_CHECK_HEADER([paths.h], +[HAVE_PATHS_H=YesPlease], +[HAVE_PATHS_H=]) +AC_SUBST(HAVE_PATHS_H) +# # Define NO_STRCASESTR if you don't have strcasestr. GIT_CHECK_FUNC(strcasestr, [NO_STRCASESTR=], @@ -782,7 +855,11 @@ AC_DEFUN([PTHREADTEST_SRC], [ int main(void) { pthread_mutex_t test_mutex; - return (0); + int retcode = 0; + retcode |= pthread_mutex_init(&test_mutex,(void *)0); + retcode |= pthread_mutex_lock(&test_mutex); + retcode |= pthread_mutex_unlock(&test_mutex); + return retcode; } ]) @@ -799,7 +876,8 @@ if test -n "$USER_NOPTHREAD"; then # handle these separately since PTHREAD_CFLAGS could be '-lpthreads # -D_REENTRANT' or some such. elif test -z "$PTHREAD_CFLAGS"; then - for opt in -pthread -lpthread; do + threads_found=no + for opt in -mt -pthread -lpthread; do old_CFLAGS="$CFLAGS" CFLAGS="$opt $CFLAGS" AC_MSG_CHECKING([Checking for POSIX Threads with '$opt']) @@ -807,11 +885,18 @@ elif test -z "$PTHREAD_CFLAGS"; then [AC_MSG_RESULT([yes]) NO_PTHREADS= PTHREAD_LIBS="$opt" + PTHREAD_CFLAGS="$opt" + threads_found=yes break ], [AC_MSG_RESULT([no])]) CFLAGS="$old_CFLAGS" done + if test $threads_found != yes; then + AC_CHECK_LIB([pthread], [pthread_create], + [PTHREAD_LIBS="-lpthread"], + [NO_PTHREADS=UnfortunatelyYes]) + fi else old_CFLAGS="$CFLAGS" CFLAGS="$PTHREAD_CFLAGS $CFLAGS" @@ -828,6 +913,7 @@ fi CFLAGS="$old_CFLAGS" +AC_SUBST(PTHREAD_CFLAGS) AC_SUBST(PTHREAD_LIBS) AC_SUBST(NO_PTHREADS) @@ -5,6 +5,7 @@ #include "refs.h" #include "run-command.h" #include "remote.h" +#include "url.h" static char *server_capabilities; @@ -131,7 +132,7 @@ int path_match(const char *path, int nr, char **match) enum protocol { PROTO_LOCAL = 1, PROTO_SSH, - PROTO_GIT, + PROTO_GIT }; static enum protocol get_protocol(const char *name) @@ -152,6 +153,28 @@ static enum protocol get_protocol(const char *name) #define STR_(s) # s #define STR(s) STR_(s) +static void get_host_and_port(char **host, const char **port) +{ + char *colon, *end; + + if (*host[0] == '[') { + end = strchr(*host + 1, ']'); + if (end) { + *end = 0; + end++; + (*host)++; + } else + end = *host; + } else + end = *host; + colon = strchr(end, ':'); + + if (colon) { + *colon = 0; + *port = colon + 1; + } +} + #ifndef NO_IPV6 static const char *ai_name(const struct addrinfo *ai) @@ -170,30 +193,14 @@ static const char *ai_name(const struct addrinfo *ai) static int git_tcp_connect_sock(char *host, int flags) { int sockfd = -1, saved_errno = 0; - char *colon, *end; const char *port = STR(DEFAULT_GIT_PORT); struct addrinfo hints, *ai0, *ai; int gai; int cnt = 0; - if (host[0] == '[') { - end = strchr(host + 1, ']'); - if (end) { - *end = 0; - end++; - host++; - } else - end = host; - } else - end = host; - colon = strchr(end, ':'); - - if (colon) { - *colon = 0; - port = colon + 1; - if (!*port) - port = "<none>"; - } + get_host_and_port(&host, &port); + if (!*port) + port = "<none>"; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; @@ -251,30 +258,15 @@ static int git_tcp_connect_sock(char *host, int flags) static int git_tcp_connect_sock(char *host, int flags) { int sockfd = -1, saved_errno = 0; - char *colon, *end; - char *port = STR(DEFAULT_GIT_PORT), *ep; + const char *port = STR(DEFAULT_GIT_PORT); + char *ep; struct hostent *he; struct sockaddr_in sa; char **ap; unsigned int nport; int cnt; - if (host[0] == '[') { - end = strchr(host + 1, ']'); - if (end) { - *end = 0; - end++; - host++; - } else - end = host; - } else - end = host; - colon = strchr(end, ':'); - - if (colon) { - *colon = 0; - port = colon + 1; - } + get_host_and_port(&host, &port); if (flags & CONNECT_VERBOSE) fprintf(stderr, "Looking up %s ... ", host); @@ -406,26 +398,10 @@ static int git_use_proxy(const char *host) static void git_proxy_connect(int fd[2], char *host) { const char *port = STR(DEFAULT_GIT_PORT); - char *colon, *end; const char *argv[4]; struct child_process proxy; - if (host[0] == '[') { - end = strchr(host + 1, ']'); - if (end) { - *end = 0; - end++; - host++; - } else - end = host; - } else - end = host; - colon = strchr(end, ':'); - - if (colon) { - *colon = 0; - port = colon + 1; - } + get_host_and_port(&host, &port); argv[0] = git_proxy_command; argv[1] = host; @@ -475,7 +451,7 @@ static struct child_process no_fork; struct child_process *git_connect(int fd[2], const char *url_orig, const char *prog, int flags) { - char *url = xstrdup(url_orig); + char *url; char *host, *path; char *end; int c; @@ -491,6 +467,11 @@ struct child_process *git_connect(int fd[2], const char *url_orig, */ signal(SIGCHLD, SIG_DFL); + if (is_url(url_orig)) + url = url_decode(url_orig); + else + url = xstrdup(url_orig); + host = strstr(url, "://"); if (host) { *host = '\0'; @@ -637,3 +618,40 @@ int finish_connect(struct child_process *conn) free(conn); return code; } + +char *git_getpass(const char *prompt) +{ + char *askpass; + struct child_process pass; + const char *args[3]; + static struct strbuf buffer = STRBUF_INIT; + + askpass = getenv("GIT_ASKPASS"); + + if (!askpass || !(*askpass)) + return getpass(prompt); + + 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/contrib/ciabot/README b/contrib/ciabot/README new file mode 100644 index 0000000000..3b916acece --- /dev/null +++ b/contrib/ciabot/README @@ -0,0 +1,12 @@ +These are hook scripts for the CIA notification service at <http://cia.vc/> + +They are maintained by Eric S. Raymond <esr@thyrsus.com>. There is an +upstream resource page for them at <http://www.catb.org/esr/ciabot/>, +but they are unlikely to change rapidly. + +You probably want the Python version; it's faster, more capable, and +better documented. The shell version is maintained only as a fallback +for use on hosting sites that don't permit Python hook scripts. + +You will find installation instructions for each script in its comment +header. diff --git a/contrib/ciabot/ciabot.py b/contrib/ciabot/ciabot.py new file mode 100755 index 0000000000..d0627e0852 --- /dev/null +++ b/contrib/ciabot/ciabot.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python +# Copyright (c) 2010 Eric S. Raymond <esr@thyrsus.com> +# Distributed under BSD terms. +# +# This script contains porcelain and porcelain byproducts. +# It's Python because the Python standard libraries avoid portability/security +# issues raised by callouts in the ancestral Perl and sh scripts. It should +# be compatible back to Python 2.1.5 +# +# usage: ciabot.py [-V] [-n] [-p projectname] [refname [commits...]] +# +# This script is meant to be run either in a post-commit hook or in an +# update hook. If there's nothing unusual about your hosting setup, +# you can specify the project name with a -p option and avoid having +# to modify this script. Try it with -n to see the notification mail +# dumped to stdout and verify that it looks sane. With -V it dumps its +# version and exits. +# +# In post-commit, run it without arguments (other than possibly a -p +# option). It will query for current HEAD and the latest commit ID to +# get the information it needs. +# +# In update, call it with a refname followed by a list of commits: +# You want to reverse the order git rev-list emits becxause it lists +# from most recent to oldest. +# +# /path/to/ciabot.py ${refname} $(git rev-list ${oldhead}..${newhead} | tac) +# +# Note: this script uses mail, not XML-RPC, in order to avoid stalling +# until timeout when the CIA XML-RPC server is down. +# + +# +# The project as known to CIA. You will either want to change this +# or invoke the script with a -p option to set it. +# +project=None + +# +# You may not need to change these: +# +import os, sys, commands, socket, urllib + +# Name of the repository. +# You can hardwire this to make the script faster. +repo = os.path.basename(os.getcwd()) + +# Fully-qualified domain name of this host. +# You can hardwire this to make the script faster. +host = socket.getfqdn() + +# Changeset URL prefix for your repo: when the commit ID is appended +# to this, it should point at a CGI that will display the commit +# through gitweb or something similar. The defaults will probably +# work if you have a typical gitweb/cgit setup. +# +#urlprefix="http://%(host)s/cgi-bin/gitweb.cgi?p=%(repo)s;a=commit;h=" +urlprefix="http://%(host)s/cgi-bin/cgit.cgi/%(repo)s/commit/?id=" + +# The service used to turn your gitwebbish URL into a tinyurl so it +# will take up less space on the IRC notification line. +tinyifier = "http://tinyurl.com/api-create.php?url=" + +# The template used to generate the XML messages to CIA. You can make +# visible changes to the IRC-bot notification lines by hacking this. +# The default will produce a notfication line that looks like this: +# +# ${project}: ${author} ${repo}:${branch} * ${rev} ${files}: ${logmsg} ${url} +# +# By omitting $files you can collapse the files part to a single slash. +xml = '''\ +<message> + <generator> + <name>CIA Python client for Git</name> + <version>%(gitver)s</version> + <url>%(generator)s</url> + </generator> + <source> + <project>%(project)s</project> + <branch>%(repo)s:%(branch)s</branch> + </source> + <timestamp>%(ts)s</timestamp> + <body> + <commit> + <author>%(author)s</author> + <revision>%(rev)s</revision> + <files> + %(files)s + </files> + <log>%(logmsg)s %(url)s</log> + <url>%(url)s</url> + </commit> + </body> +</message> +''' + +# +# No user-serviceable parts below this line: +# + +# Addresses for the e-mail. The from address is a dummy, since CIA +# will never reply to this mail. +fromaddr = "CIABOT-NOREPLY@" + host +toaddr = "cia@cia.navi.cx" + +# Identify the generator script. +# Should only change when the script itself gets a new home and maintainer. +generator="http://www.catb.org/~esr/ciabot.py" + +def do(command): + return commands.getstatusoutput(command)[1] + +def report(refname, merged): + "Generate a commit notification to be reported to CIA" + + # Try to tinyfy a reference to a web view for this commit. + try: + url = open(urllib.urlretrieve(tinyifier + urlprefix + merged)[0]).read() + except: + url = urlprefix + merged + + branch = os.path.basename(refname) + + # Compute a shortnane for the revision + rev = do("git describe ${merged} 2>/dev/null") or merged[:12] + + # Extract the neta-information for the commit + rawcommit = do("git cat-file commit " + merged) + files=do("git diff-tree -r --name-only '"+ merged +"' | sed -e '1d' -e 's-.*-<file>&</file>-'") + inheader = True + headers = {} + logmsg = "" + for line in rawcommit.split("\n"): + if inheader: + if line: + fields = line.split() + headers[fields[0]] = " ".join(fields[1:]) + else: + inheader = False + else: + logmsg = line + break + (author, ts) = headers["author"].split(">") + + # This discards the part of the authors addrsss after @. + # Might be bnicece to ship the full email address, if not + # for spammers' address harvesters - getting this wrong + # would make the freenode #commits channel into harvester heaven. + author = author.replace("<", "").split("@")[0].split()[-1] + + # This ignores the timezone. Not clear what to do with it... + ts = ts.strip().split()[0] + + context = locals() + context.update(globals()) + + out = xml % context + + message = '''\ +Message-ID: <%(merged)s.%(author)s@%(project)s> +From: %(fromaddr)s +To: %(toaddr)s +Content-type: text/xml +Subject: DeliverXML + +%(out)s''' % locals() + + return message + +if __name__ == "__main__": + import getopt + + try: + (options, arguments) = getopt.getopt(sys.argv[1:], "np:V") + except getopt.GetoptError, msg: + print "ciabot.py: " + str(msg) + raise SystemExit, 1 + + mailit = True + for (switch, val) in options: + if switch == '-p': + project = val + elif switch == '-n': + mailit = False + elif switch == '-V': + print "ciabot.py: version 3.2" + sys.exit(0) + + # Cough and die if user has not specified a project + if not project: + sys.stderr.write("ciabot.py: no project specified, bailing out.\n") + sys.exit(1) + + # We'll need the git version number. + gitver = do("git --version").split()[0] + + urlprefix = urlprefix % globals() + + # The script wants a reference to head followed by the list of + # commit ID to report about. + if len(arguments) == 0: + refname = do("git symbolic-ref HEAD 2>/dev/null") + merges = [do("git rev-parse HEAD")] + else: + refname = arguments[0] + merges = arguments[1:] + + if mailit: + import smtplib + server = smtplib.SMTP('localhost') + + for merged in merges: + message = report(refname, merged) + if mailit: + server.sendmail(fromaddr, [toaddr], message) + else: + print message + + if mailit: + server.quit() + +#End diff --git a/contrib/ciabot/ciabot.sh b/contrib/ciabot/ciabot.sh new file mode 100755 index 0000000000..eb87bba38e --- /dev/null +++ b/contrib/ciabot/ciabot.sh @@ -0,0 +1,192 @@ +#!/bin/sh +# Distributed under the terms of the GNU General Public License v2 +# Copyright (c) 2006 Fernando J. Pereda <ferdy@gentoo.org> +# Copyright (c) 2008 Natanael Copa <natanael.copa@gmail.com> +# Copyright (c) 2010 Eric S. Raymond <esr@thyrsus.com> +# +# This is a version 3.x of ciabot.sh; use -V to find the exact +# version. Versions 1 and 2 were shipped in 2006 and 2008 and are not +# version-stamped. The version 2 maintainer has passed the baton. +# +# Note: This script should be considered obsolete. +# There is a faster, better-documented rewrite in Python: find it as ciabot.py +# Use this only if your hosting site forbids Python hooks. +# +# Originally based on Git ciabot.pl by Petr Baudis. +# This script contains porcelain and porcelain byproducts. +# +# usage: ciabot.sh [-V] [-n] [-p projectname] [refname commit] +# +# This script is meant to be run either in a post-commit hook or in an +# update hook. If there's nothing unusual about your hosting setup, +# you can specify the project name with a -p option and avoid having +# to modify this script. Try it with -n first to see the notification +# mail dumped to stdout and verify that it looks sane. Use -V to dump +# the version and exit. +# +# In post-commit, run it without arguments (other than possibly a -p +# option). It will query for current HEAD and the latest commit ID to +# get the information it needs. +# +# In update, you have to call it once per merged commit: +# +# refname=$1 +# oldhead=$2 +# newhead=$3 +# for merged in $(git rev-list ${oldhead}..${newhead} | tac) ; do +# /path/to/ciabot.bash ${refname} ${merged} +# done +# +# The reason for the tac call ids that git rev-list emits commits from +# most recent to least - better to ship notifactions from oldest to newest. +# +# Note: this script uses mail, not XML-RPC, in order to avoid stalling +# until timeout when the CIA XML-RPC server is down. +# + +# +# The project as known to CIA. You will either want to change this +# or set the project name with a -p option. +# +project= + +# +# You may not need to change these: +# + +# Name of the repository. +# You can hardwire this to make the script faster. +repo="`basename ${PWD}`" + +# Fully qualified domain name of the repo host. +# You can hardwire this to make the script faster. +host=`hostname --fqdn` + +# Changeset URL prefix for your repo: when the commit ID is appended +# to this, it should point at a CGI that will display the commit +# through gitweb or something similar. The defaults will probably +# work if you have a typical gitweb/cgit setup. +#urlprefix="http://${host}/cgi-bin/gitweb.cgi?p=${repo};a=commit;h=" +urlprefix="http://${host}/cgi-bin/cgit.cgi/${repo}/commit/?id=" + +# +# You probably will not need to change the following: +# + +# Identify the script. Should change only when the script itself +# gets a new home and maintainer. +generator="http://www.catb.org/~esr/ciabot/ciabot.sh" + +# Addresses for the e-mail +from="CIABOT-NOREPLY@${host}" +to="cia@cia.navi.cx" + +# SMTP client to use - may need to edit the absolute pathname for your system +sendmail="sendmail -t -f ${from}" + +# +# No user-serviceable parts below this line: +# + +# Should include all places sendmail is likely to lurk. +PATH="$PATH:/usr/sbin/" + +mode=mailit +while getopts pnV opt +do + case $opt in + p) project=$2; shift ; shift ;; + n) mode=dumpit; shift ;; + V) echo "ciabot.sh: version 3.2"; exit 0; shift ;; + esac +done + +# Cough and die if user has not specified a project +if [ -z "$project" ] +then + echo "ciabot.sh: no project specified, bailing out." >&2 + exit 1 +fi + +if [ $# -eq 0 ] ; then + refname=$(git symbolic-ref HEAD 2>/dev/null) + merged=$(git rev-parse HEAD) +else + refname=$1 + merged=$2 +fi + +# This tries to turn your gitwebbish URL into a tinyurl so it will take up +# less space on the IRC notification line. Some repo sites (I'm looking at +# you, berlios.de!) forbid wget calls for security reasons. On these, +# the code will fall back to the full un-tinyfied URL. +longurl=${urlprefix}${merged} +url=$(wget -O - -q http://tinyurl.com/api-create.php?url=${longurl} 2>/dev/null) +if [ -z "$url" ]; then + url="${longurl}" +fi + +refname=${refname##refs/heads/} + +gitver=$(git --version) +gitver=${gitver##* } + +rev=$(git describe ${merged} 2>/dev/null) +# ${merged:0:12} was the only bashism left in the 2008 version of this +# script, according to checkbashisms. Replace it with ${merged} here +# because it was just a fallback anyway, and it's worth accepting a +# longer fallback for faster execution and removing the bash +# dependency. +[ -z ${rev} ] && rev=${merged} + +# This discards the part of the author's address after @. +# Might be nice to ship the full email address, if not +# for spammers' address harvesters - getting this wrong +# would make the freenode #commits channel into harvester heaven. +rawcommit=$(git cat-file commit ${merged}) +author=$(echo "$rawcommit" | sed -n -e '/^author .*<\([^@]*\).*$/s--\1-p') +logmessage=$(echo "$rawcommit" | sed -e '1,/^$/d' | head -n 1) +logmessage=$(echo "$logmessage" | sed 's/\&/&\;/g; s/</<\;/g; s/>/>\;/g') +ts=$(echo "$rawcommit" | sed -n -e '/^author .*> \([0-9]\+\).*$/s--\1-p') +files=$(git diff-tree -r --name-only ${merged} | sed -e '1d' -e 's-.*-<file>&</file>-') + +out=" +<message> + <generator> + <name>CIA Shell client for Git</name> + <version>${gitver}</version> + <url>${generator}</url> + </generator> + <source> + <project>${project}</project> + <branch>$repo:${refname}</branch> + </source> + <timestamp>${ts}</timestamp> + <body> + <commit> + <author>${author}</author> + <revision>${rev}</revision> + <files> + ${files} + </files> + <log>${logmessage} ${url}</log> + <url>${url}</url> + </commit> + </body> +</message>" + +if [ "$mode" = "dumpit" ] +then + sendmail=cat +fi + +${sendmail} << EOM +Message-ID: <${merged}.${author}@${project}> +From: ${from} +To: ${to} +Content-type: text/xml +Subject: DeliverXML +${out} +EOM + +# vim: set tw=70 : diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 733ac39a32..67569901e7 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -42,6 +42,24 @@ # set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're # untracked files, then a '%' will be shown next to the branch name. # +# If you would like to see the difference between HEAD and its +# upstream, set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates +# you are behind, ">" indicates you are ahead, and "<>" +# indicates you have diverged. You can further control +# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated +# list of values: +# verbose show number of commits ahead/behind (+/-) upstream +# legacy don't use the '--count' option available in recent +# versions of git-rev-list +# git always compare HEAD to @{upstream} +# svn always compare HEAD to your SVN upstream +# By default, __git_ps1 will compare HEAD to your SVN upstream +# if it can find one, or @{upstream} otherwise. Once you have +# set GIT_PS1_SHOWUPSTREAM, you can override it on a +# per-repository basis by setting the bash.showUpstream config +# variable. +# +# # To submit patches: # # *) Read Documentation/SubmittingPatches @@ -78,14 +96,133 @@ __gitdir () fi } +# stores the divergence from upstream in $p +# used by GIT_PS1_SHOWUPSTREAM +__git_ps1_show_upstream () +{ + local key value + local svn_remote=() svn_url_pattern count n + local upstream=git legacy="" verbose="" + + # get some config options from git-config + while read key value; do + case "$key" in + bash.showupstream) + GIT_PS1_SHOWUPSTREAM="$value" + if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then + p="" + return + fi + ;; + svn-remote.*.url) + svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value" + svn_url_pattern+="\\|$value" + 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 ') + + # parse configuration values + for option in ${GIT_PS1_SHOWUPSTREAM}; do + case "$option" in + git|svn) upstream="$option" ;; + verbose) verbose=1 ;; + legacy) legacy=1 ;; + esac + done + + # Find our upstream + case "$upstream" in + git) upstream="@{upstream}" ;; + svn*) + # get the upstream from the "git-svn-id: ..." in a commit message + # (git-svn uses essentially the same procedure internally) + local svn_upstream=($(git log --first-parent -1 \ + --grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null)) + if [[ 0 -ne ${#svn_upstream[@]} ]]; then + svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} + svn_upstream=${svn_upstream%@*} + for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do + svn_upstream=${svn_upstream#${svn_remote[$n]}} + done + + if [[ -z "$svn_upstream" ]]; then + # default branch name for checkouts with no layout: + upstream=${GIT_SVN_ID:-git-svn} + else + upstream=${svn_upstream#/} + fi + elif [[ "svn+git" = "$upstream" ]]; then + upstream="@{upstream}" + fi + ;; + esac + + # Find how many commits we are ahead/behind our upstream + if [[ -z "$legacy" ]]; then + count="$(git rev-list --count --left-right \ + "$upstream"...HEAD 2>/dev/null)" + else + # produce equivalent output to --count for older versions of git + local commits + if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)" + then + local commit behind=0 ahead=0 + for commit in $commits + do + case "$commit" in + "<"*) let ++behind + ;; + *) let ++ahead + ;; + esac + done + count="$behind $ahead" + else + count="" + fi + fi + + # calculate the result + if [[ -z "$verbose" ]]; then + case "$count" in + "") # no upstream + p="" ;; + "0 0") # equal to upstream + p="=" ;; + "0 "*) # ahead of upstream + p=">" ;; + *" 0") # behind upstream + p="<" ;; + *) # diverged from upstream + p="<>" ;; + esac + else + case "$count" in + "") # no upstream + p="" ;; + "0 0") # equal to upstream + p=" u=" ;; + "0 "*) # ahead of upstream + p=" u+${count#0 }" ;; + *" 0") # behind upstream + p=" u-${count% 0}" ;; + *) # diverged from upstream + p=" u+${count#* }-${count% *}" ;; + esac + fi + +} + + # __git_ps1 accepts 0 or 1 arguments (i.e., format string) # returns text to add to bash PS1 prompt (includes branch name) __git_ps1 () { local g="$(__gitdir)" if [ -n "$g" ]; then - local r - local b + local r="" + local b="" if [ -f "$g/rebase-merge/interactive" ]; then r="|REBASE-i" b="$(cat "$g/rebase-merge/head-name")" @@ -127,11 +264,12 @@ __git_ps1 () } fi - local w - local i - local s - local u - local c + local w="" + local i="" + local s="" + local u="" + local c="" + local p="" if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then @@ -159,10 +297,14 @@ __git_ps1 () u="%" fi fi + + if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then + __git_ps1_show_upstream + fi fi local f="$w$i$s$u" - printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r" + printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p" fi } @@ -627,10 +769,19 @@ __git_aliased_command () local word cmdline=$(git --git-dir="$(__gitdir)" \ config --get "alias.$1") for word in $cmdline; do - if [ "${word##-*}" ]; then - echo $word + case "$word" in + \!gitk|gitk) + echo "gitk" return - fi + ;; + \!*) : shell command alias ;; + -*) : option ;; + *=*) : setting env ;; + git) : git itself ;; + *) + echo "$word" + return + esac done } @@ -788,6 +939,7 @@ _git_branch () __gitcomp " --color --no-color --verbose --abbrev= --no-abbrev --track --no-track --contains --merged --no-merged + --set-upstream " ;; *) @@ -832,7 +984,7 @@ _git_checkout () --*) __gitcomp " --quiet --ours --theirs --track --no-track --merge - --conflict= --patch + --conflict= --orphan --patch " ;; *) @@ -1042,7 +1194,7 @@ _git_format_patch () --numbered --start-number --numbered-files --keep-subject - --signoff + --signoff --signature --no-signature --in-reply-to= --cc= --full-index --binary --not --all @@ -1084,6 +1236,11 @@ _git_gc () COMPREPLY=() } +_git_gitk () +{ + _gitk +} + _git_grep () { __git_has_doubledash && return @@ -1436,6 +1593,11 @@ _git_send_email () COMPREPLY=() } +_git_stage () +{ + _git_add +} + __git_config_get_set_variables () { local prevword word config_file= c=$COMP_CWORD @@ -1706,6 +1868,7 @@ _git_config () format.headers format.numbered format.pretty + format.signature format.signoff format.subjectprefix format.suffix @@ -2167,6 +2330,11 @@ _git_tag () esac } +_git_whatchanged () +{ + _git_log +} + _git () { local i c=1 command __git_dir @@ -2203,64 +2371,14 @@ _git () return fi + local completion_func="_git_${command//-/_}" + declare -F $completion_func >/dev/null && $completion_func && return + local expansion=$(__git_aliased_command "$command") - [ "$expansion" ] && command="$expansion" - - case "$command" in - am) _git_am ;; - add) _git_add ;; - apply) _git_apply ;; - archive) _git_archive ;; - bisect) _git_bisect ;; - bundle) _git_bundle ;; - branch) _git_branch ;; - checkout) _git_checkout ;; - cherry) _git_cherry ;; - cherry-pick) _git_cherry_pick ;; - clean) _git_clean ;; - clone) _git_clone ;; - commit) _git_commit ;; - config) _git_config ;; - describe) _git_describe ;; - diff) _git_diff ;; - difftool) _git_difftool ;; - fetch) _git_fetch ;; - format-patch) _git_format_patch ;; - fsck) _git_fsck ;; - gc) _git_gc ;; - grep) _git_grep ;; - help) _git_help ;; - init) _git_init ;; - log) _git_log ;; - ls-files) _git_ls_files ;; - ls-remote) _git_ls_remote ;; - ls-tree) _git_ls_tree ;; - merge) _git_merge;; - mergetool) _git_mergetool;; - merge-base) _git_merge_base ;; - mv) _git_mv ;; - name-rev) _git_name_rev ;; - notes) _git_notes ;; - pull) _git_pull ;; - push) _git_push ;; - rebase) _git_rebase ;; - remote) _git_remote ;; - replace) _git_replace ;; - reset) _git_reset ;; - revert) _git_revert ;; - rm) _git_rm ;; - send-email) _git_send_email ;; - shortlog) _git_shortlog ;; - show) _git_show ;; - show-branch) _git_show_branch ;; - stash) _git_stash ;; - stage) _git_add ;; - submodule) _git_submodule ;; - svn) _git_svn ;; - tag) _git_tag ;; - whatchanged) _git_log ;; - *) COMPREPLY=() ;; - esac + if [ -n "$expansion" ]; then + completion_func="_git_${expansion//-/_}" + declare -F $completion_func >/dev/null && $completion_func + fi } _gitk () diff --git a/contrib/examples/git-fetch.sh b/contrib/examples/git-fetch.sh index e44af2c86d..a314273bd5 100755 --- a/contrib/examples/git-fetch.sh +++ b/contrib/examples/git-fetch.sh @@ -127,10 +127,12 @@ then orig_head=$(git rev-parse --verify HEAD 2>/dev/null) fi -# Allow --notags from remote.$1.tagopt +# Allow --tags/--notags from remote.$1.tagopt case "$tags$no_tags" in '') case "$(git config --get "remote.$1.tagopt")" in + --tags) + tags=t ;; --no-tags) no_tags=t ;; esac diff --git a/git-notes.sh b/contrib/examples/git-notes.sh index e642e47d9f..e642e47d9f 100755 --- a/git-notes.sh +++ b/contrib/examples/git-notes.sh diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index cd96c6f81f..c1ea643ace 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -802,7 +802,7 @@ class P4Submit(Command): self.oldWorkingDirectory = os.getcwd() chdir(self.clientPath) - print "Syncronizing p4 checkout..." + print "Synchronizing p4 checkout..." p4_system("sync ...") self.check() diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh index c364dda696..a4ed4c3c62 100755 --- a/contrib/git-resurrect.sh +++ b/contrib/git-resurrect.sh @@ -9,6 +9,7 @@ other/Merge <other> into <name> (respectively) commit subjects, which is rather slow but allows you to resurrect other people's topic branches." +OPTIONS_KEEPDASHDASH= OPTIONS_SPEC="\ git resurrect $USAGE -- diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 58a35c8287..09c524105c 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -23,6 +23,13 @@ # possible for the email to be from someone other than the person doing the # push. # +# To help with debugging and use on pre-v1.5.1 git servers, this script will +# also obey the interface of hooks/update, taking its arguments on the +# command line. Unfortunately, hooks/update is called once for each ref. +# To avoid firing one email per ref, this script just prints its output to +# the screen when used in this mode. The output can then be redirected if +# wanted. +# # Config # ------ # hooks.mailinglist @@ -196,7 +203,7 @@ generate_email_header() # Generate header cat <<-EOF To: $recipients - Subject: ${emailprefix}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe + Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe X-Git-Refname: $refname X-Git-Reftype: $refname_type X-Git-Oldrev: $oldrev diff --git a/contrib/svn-fe/.gitignore b/contrib/svn-fe/.gitignore new file mode 100644 index 0000000000..02a7791585 --- /dev/null +++ b/contrib/svn-fe/.gitignore @@ -0,0 +1,4 @@ +/*.xml +/*.1 +/*.html +/svn-fe diff --git a/contrib/svn-fe/Makefile b/contrib/svn-fe/Makefile new file mode 100644 index 0000000000..360d8da417 --- /dev/null +++ b/contrib/svn-fe/Makefile @@ -0,0 +1,63 @@ +all:: svn-fe$X + +CC = gcc +RM = rm -f +MV = mv + +CFLAGS = -g -O2 -Wall +LDFLAGS = +ALL_CFLAGS = $(CFLAGS) +ALL_LDFLAGS = $(LDFLAGS) +EXTLIBS = + +GIT_LIB = ../../libgit.a +VCSSVN_LIB = ../../vcs-svn/lib.a +LIBS = $(VCSSVN_LIB) $(GIT_LIB) $(EXTLIBS) + +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring $(MAKEFLAGS),w),w) +PRINT_DIR = --no-print-directory +else # "make -w" +NO_SUBDIR = : +endif + +ifneq ($(findstring $(MAKEFLAGS),s),s) +ifndef V + QUIET_CC = @echo ' ' CC $@; + QUIET_LINK = @echo ' ' LINK $@; + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir +endif +endif + +svn-fe$X: svn-fe.o $(VCSSVN_LIB) $(GIT_LIB) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fe.o \ + $(ALL_LDFLAGS) $(LIBS) + +svn-fe.o: svn-fe.c ../../vcs-svn/svndump.h + $(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $< + +svn-fe.html: svn-fe.txt + $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \ + MAN_TXT=../contrib/svn-fe/svn-fe.txt \ + ../contrib/svn-fe/$@ + +svn-fe.1: svn-fe.txt + $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \ + MAN_TXT=../contrib/svn-fe/svn-fe.txt \ + ../contrib/svn-fe/$@ + $(MV) ../../Documentation/svn-fe.1 . + +../../vcs-svn/lib.a: FORCE + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) vcs-svn/lib.a + +../../libgit.a: FORCE + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) libgit.a + +clean: + $(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1 + +.PHONY: all clean FORCE diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c new file mode 100644 index 0000000000..e9b9ba4da4 --- /dev/null +++ b/contrib/svn-fe/svn-fe.c @@ -0,0 +1,15 @@ +/* + * This file is in the public domain. + * You may freely use, modify, distribute, and relicense it. + */ + +#include <stdlib.h> +#include "svndump.h" + +int main(int argc, char **argv) +{ + svndump_init(NULL); + svndump_read((argc > 1) ? argv[1] : NULL); + svndump_reset(); + return 0; +} diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt new file mode 100644 index 0000000000..de30f83a1f --- /dev/null +++ b/contrib/svn-fe/svn-fe.txt @@ -0,0 +1,66 @@ +svn-fe(1) +========= + +NAME +---- +svn-fe - convert an SVN "dumpfile" to a fast-import stream + +SYNOPSIS +-------- +svnadmin dump --incremental REPO | svn-fe [url] | git fast-import + +DESCRIPTION +----------- + +Converts a Subversion dumpfile (version: 2) into input suitable for +git-fast-import(1) and similar importers. REPO is a path to a +Subversion repository mirrored on the local disk. Remote Subversion +repositories can be mirrored on local disk using the `svnsync` +command. + +INPUT FORMAT +------------ +Subversion's repository dump format is documented in full in +`notes/dump-load-format.txt` from the Subversion source tree. +Files in this format can be generated using the 'svnadmin dump' or +'svk admin dump' command. + +OUTPUT FORMAT +------------- +The fast-import format is documented by the git-fast-import(1) +manual page. + +NOTES +----- +Subversion dumps do not record a separate author and committer for +each revision, nor a separate display name and email address for +each author. Like git-svn(1), 'svn-fe' will use the name + +--------- +user <user@UUID> +--------- + +as committer, where 'user' is the value of the `svn:author` property +and 'UUID' the repository's identifier. + +To support incremental imports, 'svn-fe' will put a `git-svn-id` +line at the end of each commit log message if passed an url on the +command line. This line has the form `git-svn-id: URL@REVNO UUID`. + +Empty directories and unknown properties are silently discarded. + +The resulting repository will generally require further processing +to put each project in its own repository and to separate the history +of each branch. The 'git filter-branch --subdirectory-filter' command +may be useful for this purpose. + +BUGS +---- +Litters the current working directory with .bin files for +persistence. Will be fixed when the svn-fe infrastructure is aware of +a Git working directory. + +SEE ALSO +-------- +git-svn(1), svn2git(1), svk(1), git-filter-branch(1), git-fast-import(1), +https://svn.apache.org/repos/asf/subversion/trunk/notes/dump-load-format.txt @@ -8,13 +8,17 @@ * This should use the pathname to decide on whether it wants to do some * more interesting conversions (automatic gzip/unzip, general format * conversions etc etc), but by default it just does automatic CRLF<->LF - * translation when the "auto_crlf" option is set. + * translation when the "text" attribute or "auto_crlf" option is set. */ -#define CRLF_GUESS (-1) -#define CRLF_BINARY 0 -#define CRLF_TEXT 1 -#define CRLF_INPUT 2 +enum action { + CRLF_GUESS = -1, + CRLF_BINARY = 0, + CRLF_TEXT, + CRLF_INPUT, + CRLF_CRLF, + CRLF_AUTO, +}; struct text_stat { /* NUL, CR, LF and CRLF counts */ @@ -89,49 +93,111 @@ static int is_binary(unsigned long size, struct text_stat *stats) return 0; } -static void check_safe_crlf(const char *path, int action, +static enum eol determine_output_conversion(enum action action) { + switch (action) { + case CRLF_BINARY: + return EOL_UNSET; + case CRLF_CRLF: + return EOL_CRLF; + case CRLF_INPUT: + return EOL_LF; + case CRLF_GUESS: + if (!auto_crlf) + return EOL_UNSET; + /* fall through */ + case CRLF_TEXT: + case CRLF_AUTO: + if (auto_crlf == AUTO_CRLF_TRUE) + return EOL_CRLF; + else if (auto_crlf == AUTO_CRLF_INPUT) + return EOL_LF; + else if (eol == EOL_UNSET) + return EOL_NATIVE; + } + return eol; +} + +static void check_safe_crlf(const char *path, enum action action, struct text_stat *stats, enum safe_crlf checksafe) { if (!checksafe) return; - if (action == CRLF_INPUT || auto_crlf <= 0) { + if (determine_output_conversion(action) == EOL_LF) { /* * CRLFs would not be restored by checkout: * check if we'd remove CRLFs */ if (stats->crlf) { if (checksafe == SAFE_CRLF_WARN) - warning("CRLF will be replaced by LF in %s.", path); + warning("CRLF will be replaced by LF in %s.\nThe file will have its original line endings in your working directory.", path); else /* i.e. SAFE_CRLF_FAIL */ die("CRLF would be replaced by LF in %s.", path); } - } else if (auto_crlf > 0) { + } else if (determine_output_conversion(action) == EOL_CRLF) { /* * CRLFs would be added by checkout: * check if we have "naked" LFs */ if (stats->lf != stats->crlf) { if (checksafe == SAFE_CRLF_WARN) - warning("LF will be replaced by CRLF in %s", path); + warning("LF will be replaced by CRLF in %s.\nThe file will have its original line endings in your working directory.", path); else /* i.e. SAFE_CRLF_FAIL */ die("LF would be replaced by CRLF in %s", path); } } } +static int has_cr_in_index(const char *path) +{ + int pos, len; + unsigned long sz; + enum object_type type; + void *data; + int has_cr; + struct index_state *istate = &the_index; + + len = strlen(path); + pos = index_name_pos(istate, path, len); + if (pos < 0) { + /* + * We might be in the middle of a merge, in which + * case we would read stage #2 (ours). + */ + int i; + for (i = -pos - 1; + (pos < 0 && i < istate->cache_nr && + !strcmp(istate->cache[i]->name, path)); + i++) + if (ce_stage(istate->cache[i]) == 2) + pos = i; + } + if (pos < 0) + return 0; + data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return 0; + } + + has_cr = memchr(data, '\r', sz) != NULL; + free(data); + return has_cr; +} + static int crlf_to_git(const char *path, const char *src, size_t len, - struct strbuf *buf, int action, enum safe_crlf checksafe) + struct strbuf *buf, enum action action, enum safe_crlf checksafe) { struct text_stat stats; char *dst; - if ((action == CRLF_BINARY) || !auto_crlf || !len) + if (action == CRLF_BINARY || + (action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len) return 0; gather_stats(src, len, &stats); - if (action == CRLF_GUESS) { + if (action == CRLF_AUTO || action == CRLF_GUESS) { /* * We're currently not going to even try to convert stuff * that has bare CR characters. Does anybody do that crazy @@ -145,6 +211,15 @@ static int crlf_to_git(const char *path, const char *src, size_t len, */ if (is_binary(len, &stats)) return 0; + + if (action == CRLF_GUESS) { + /* + * If the file in the index has any CR in it, do not convert. + * This is the new safer autocrlf handling. + */ + if (has_cr_in_index(path)) + return 0; + } } check_safe_crlf(path, action, &stats, checksafe); @@ -157,7 +232,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len, if (strbuf_avail(buf) + buf->len < len) strbuf_grow(buf, len - buf->len); dst = buf->buf; - if (action == CRLF_GUESS) { + if (action == CRLF_AUTO || action == CRLF_GUESS) { /* * If we guessed, we already know we rejected a file with * lone CR, and we can strip a CR without looking at what @@ -180,16 +255,12 @@ static int crlf_to_git(const char *path, const char *src, size_t len, } static int crlf_to_worktree(const char *path, const char *src, size_t len, - struct strbuf *buf, int action) + struct strbuf *buf, enum action action) { char *to_free = NULL; struct text_stat stats; - if ((action == CRLF_BINARY) || (action == CRLF_INPUT) || - auto_crlf <= 0) - return 0; - - if (!len) + if (!len || determine_output_conversion(action) != EOL_CRLF) return 0; gather_stats(src, len, &stats); @@ -202,7 +273,14 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len, if (stats.lf == stats.crlf) return 0; - if (action == CRLF_GUESS) { + if (action == CRLF_AUTO || action == CRLF_GUESS) { + if (action == CRLF_GUESS) { + /* If we have any CR or CRLF line endings, we do not touch it */ + /* This is the new safer autocrlf-handling */ + if (stats.cr > 0 || stats.crlf > 0) + return 0; + } + /* If we have any bare CR characters, we're not going to touch it */ if (stats.cr != stats.crlf) return 0; @@ -249,7 +327,9 @@ static int filter_buffer(int in, int out, void *data) struct child_process child_process; struct filter_params *params = (struct filter_params *)data; int write_err, status; - const char *argv[] = { params->cmd, NULL }; + const char *argv[] = { NULL, NULL }; + + argv[0] = params->cmd; memset(&child_process, 0, sizeof(child_process)); child_process.argv = argv; @@ -374,12 +454,16 @@ static int read_convert_config(const char *var, const char *value, void *cb) static void setup_convert_check(struct git_attr_check *check) { + static struct git_attr *attr_text; static struct git_attr *attr_crlf; + static struct git_attr *attr_eol; static struct git_attr *attr_ident; static struct git_attr *attr_filter; - if (!attr_crlf) { + if (!attr_text) { + attr_text = git_attr("text"); attr_crlf = git_attr("crlf"); + attr_eol = git_attr("eol"); attr_ident = git_attr("ident"); attr_filter = git_attr("filter"); user_convert_tail = &user_convert; @@ -388,6 +472,8 @@ static void setup_convert_check(struct git_attr_check *check) check[0].attr = attr_crlf; check[1].attr = attr_ident; check[2].attr = attr_filter; + check[3].attr = attr_eol; + check[4].attr = attr_text; } static int count_ident(const char *cp, unsigned long size) @@ -425,6 +511,8 @@ static int count_ident(const char *cp, unsigned long size) cnt++; break; } + if (ch == '\n') + break; } } return cnt; @@ -455,6 +543,11 @@ static int ident_to_git(const char *path, const char *src, size_t len, dollar = memchr(src + 3, '$', len - 3); if (!dollar) break; + if (memchr(src + 3, '\n', dollar - src - 3)) { + /* Line break before the next dollar. */ + continue; + } + memcpy(dst, "Id$", 3); dst += 3; len -= dollar + 1 - src; @@ -470,7 +563,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len, struct strbuf *buf, int ident) { unsigned char sha1[20]; - char *to_free = NULL, *dollar; + char *to_free = NULL, *dollar, *spc; int cnt; if (!ident) @@ -506,7 +599,10 @@ static int ident_to_worktree(const char *path, const char *src, size_t len, } else if (src[2] == ':') { /* * It's possible that an expanded Id has crept its way into the - * repository, we cope with that by stripping the expansion out + * repository, we cope with that by stripping the expansion out. + * This is probably not a good idea, since it will cause changes + * on checkout, which won't go away by stash, but let's keep it + * for git-style ids. */ dollar = memchr(src + 3, '$', len - 3); if (!dollar) { @@ -514,6 +610,20 @@ static int ident_to_worktree(const char *path, const char *src, size_t len, break; } + if (memchr(src + 3, '\n', dollar - src - 3)) { + /* Line break before the next dollar. */ + continue; + } + + spc = memchr(src + 4, ' ', dollar - src - 4); + if (spc && spc < dollar-1) { + /* There are spaces in unexpected places. + * This is probably an id from some other + * versioning system. Keep it for now. + */ + continue; + } + len -= dollar + 1 - src; src = dollar + 1; } else { @@ -544,9 +654,24 @@ static int git_path_check_crlf(const char *path, struct git_attr_check *check) ; else if (!strcmp(value, "input")) return CRLF_INPUT; + else if (!strcmp(value, "auto")) + return CRLF_AUTO; return CRLF_GUESS; } +static int git_path_check_eol(const char *path, struct git_attr_check *check) +{ + const char *value = check->value; + + if (ATTR_UNSET(value)) + ; + else if (!strcmp(value, "lf")) + return EOL_LF; + else if (!strcmp(value, "crlf")) + return EOL_CRLF; + return EOL_UNSET; +} + static struct convert_driver *git_path_check_convert(const char *path, struct git_attr_check *check) { @@ -568,20 +693,34 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check) return !!ATTR_TRUE(value); } +enum action determine_action(enum action text_attr, enum eol eol_attr) { + if (text_attr == CRLF_BINARY) + return CRLF_BINARY; + if (eol_attr == EOL_LF) + return CRLF_INPUT; + if (eol_attr == EOL_CRLF) + return CRLF_CRLF; + return text_attr; +} + int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst, enum safe_crlf checksafe) { - struct git_attr_check check[3]; - int crlf = CRLF_GUESS; + struct git_attr_check check[5]; + enum action action = CRLF_GUESS; + enum eol eol_attr = EOL_UNSET; int ident = 0, ret = 0; const char *filter = NULL; setup_convert_check(check); if (!git_checkattr(path, ARRAY_SIZE(check), check)) { struct convert_driver *drv; - crlf = git_path_check_crlf(path, check + 0); + action = git_path_check_crlf(path, check + 4); + if (action == CRLF_GUESS) + action = git_path_check_crlf(path, check + 0); ident = git_path_check_ident(path, check + 1); drv = git_path_check_convert(path, check + 2); + eol_attr = git_path_check_eol(path, check + 3); if (drv && drv->clean) filter = drv->clean; } @@ -591,7 +730,8 @@ int convert_to_git(const char *path, const char *src, size_t len, src = dst->buf; len = dst->len; } - ret |= crlf_to_git(path, src, len, dst, crlf, checksafe); + action = determine_action(action, eol_attr); + ret |= crlf_to_git(path, src, len, dst, action, checksafe); if (ret) { src = dst->buf; len = dst->len; @@ -601,17 +741,21 @@ int convert_to_git(const char *path, const char *src, size_t len, int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst) { - struct git_attr_check check[3]; - int crlf = CRLF_GUESS; + struct git_attr_check check[5]; + enum action action = CRLF_GUESS; + enum eol eol_attr = EOL_UNSET; int ident = 0, ret = 0; const char *filter = NULL; setup_convert_check(check); if (!git_checkattr(path, ARRAY_SIZE(check), check)) { struct convert_driver *drv; - crlf = git_path_check_crlf(path, check + 0); + action = git_path_check_crlf(path, check + 4); + if (action == CRLF_GUESS) + action = git_path_check_crlf(path, check + 0); ident = git_path_check_ident(path, check + 1); drv = git_path_check_convert(path, check + 2); + eol_attr = git_path_check_eol(path, check + 3); if (drv && drv->smudge) filter = drv->smudge; } @@ -621,7 +765,8 @@ int convert_to_working_tree(const char *path, const char *src, size_t len, struc src = dst->buf; len = dst->len; } - ret |= crlf_to_worktree(path, src, len, dst, crlf); + action = determine_action(action, eol_attr); + ret |= crlf_to_worktree(path, src, len, dst, action); if (ret) { src = dst->buf; len = dst->len; @@ -10,7 +10,7 @@ enum { A = GIT_ALPHA, D = GIT_DIGIT, G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ - R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ + R = GIT_REGEX_SPECIAL /* $, (, ), +, ., ^, {, | */ }; unsigned char sane_ctype[256] = { @@ -141,15 +141,14 @@ static char *path_ok(char *directory) } else if (interpolated_path && saw_extended_args) { struct strbuf expanded_path = STRBUF_INIT; - struct strbuf_expand_dict_entry dict[] = { - { "H", hostname }, - { "CH", canon_hostname }, - { "IP", ip_address }, - { "P", tcp_port }, - { "D", directory }, - { NULL } - }; - + struct strbuf_expand_dict_entry dict[6]; + + dict[0].placeholder = "H"; dict[0].value = hostname; + dict[1].placeholder = "CH"; dict[1].value = canon_hostname; + dict[2].placeholder = "IP"; dict[2].value = ip_address; + dict[3].placeholder = "P"; dict[3].value = tcp_port; + dict[4].placeholder = "D"; dict[4].value = directory; + dict[5].placeholder = NULL; dict[5].value = NULL; if (*dir != '/') { /* Allow only absolute */ logerror("'%s': Non-absolute path denied (interpolated-path active)", dir); @@ -343,7 +342,9 @@ static int upload_pack(void) { /* Timeout as string */ char timeout_buf[64]; - const char *argv[] = { "upload-pack", "--strict", timeout_buf, ".", NULL }; + const char *argv[] = { "upload-pack", "--strict", NULL, ".", NULL }; + + argv[2] = timeout_buf; snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout); return run_service_command(argv); @@ -229,6 +229,7 @@ static const struct { { "GMT", 0, 0, }, /* Greenwich Mean */ { "UTC", 0, 0, }, /* Universal (Coordinated) */ + { "Z", 0, 0, }, /* Zulu, alias for UTC */ { "WET", 0, 0, }, /* Western European */ { "BST", 0, 1, }, /* British Summer */ @@ -305,7 +306,7 @@ static int match_alpha(const char *date, struct tm *tm, int *offset) for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { int match = match_string(date, timezone_names[i].name); - if (match >= 3) { + if (match >= 3 || match == strlen(timezone_names[i].name)) { int off = timezone_names[i].offset; /* This is bogus, but we like summer */ @@ -585,11 +586,17 @@ static int date_string(unsigned long date, int offset, char *buf, int len) /* Gr. strptime is crap for this; it doesn't have a way to require RFC2822 (i.e. English) day/month names, and it doesn't work correctly with %z. */ -int parse_date(const char *date, char *result, int maxlen) +int parse_date_basic(const char *date, unsigned long *timestamp, int *offset) { struct tm tm; - int offset, tm_gmt; - time_t then; + int tm_gmt; + unsigned long dummy_timestamp; + int dummy_offset; + + if (!timestamp) + timestamp = &dummy_timestamp; + if (!offset) + offset = &dummy_offset; memset(&tm, 0, sizeof(tm)); tm.tm_year = -1; @@ -599,7 +606,7 @@ int parse_date(const char *date, char *result, int maxlen) tm.tm_hour = -1; tm.tm_min = -1; tm.tm_sec = -1; - offset = -1; + *offset = -1; tm_gmt = 0; for (;;) { @@ -611,11 +618,11 @@ int parse_date(const char *date, char *result, int maxlen) break; if (isalpha(c)) - match = match_alpha(date, &tm, &offset); + match = match_alpha(date, &tm, offset); else if (isdigit(c)) - match = match_digit(date, &tm, &offset, &tm_gmt); + match = match_digit(date, &tm, offset, &tm_gmt); else if ((c == '-' || c == '+') && isdigit(date[1])) - match = match_tz(date, &offset); + match = match_tz(date, offset); if (!match) { /* BAD CRAP */ @@ -626,16 +633,25 @@ int parse_date(const char *date, char *result, int maxlen) } /* mktime uses local timezone */ - then = tm_to_time_t(&tm); - if (offset == -1) - offset = (then - mktime(&tm)) / 60; + *timestamp = tm_to_time_t(&tm); + if (*offset == -1) + *offset = ((time_t)*timestamp - mktime(&tm)) / 60; - if (then == -1) + if (*timestamp == -1) return -1; if (!tm_gmt) - then -= offset * 60; - return date_string(then, offset, result, maxlen); + *timestamp -= *offset * 60; + return 0; /* success */ +} + +int parse_date(const char *date, char *result, int maxlen) +{ + unsigned long timestamp; + int offset; + if (parse_date_basic(date, ×tamp, &offset)) + return -1; + return date_string(timestamp, offset, result, maxlen); } enum date_mode parse_date_format(const char *format) @@ -983,26 +999,27 @@ static unsigned long approxidate_str(const char *date, unsigned long approxidate_relative(const char *date, const struct timeval *tv) { - char buffer[50]; + unsigned long timestamp; + int offset; int errors = 0; - if (parse_date(date, buffer, sizeof(buffer)) > 0) - return strtoul(buffer, NULL, 0); - + if (!parse_date_basic(date, ×tamp, &offset)) + return timestamp; return approxidate_str(date, tv, &errors); } unsigned long approxidate_careful(const char *date, int *error_ret) { struct timeval tv; - char buffer[50]; + unsigned long timestamp; + int offset; int dummy = 0; if (!error_ret) error_ret = &dummy; - if (parse_date(date, buffer, sizeof(buffer)) > 0) { + if (!parse_date_basic(date, ×tamp, &offset)) { *error_ret = 0; - return strtoul(buffer, NULL, 0); + return timestamp; } gettimeofday(&tv, NULL); diff --git a/diff-lib.c b/diff-lib.c index d7e13cb177..8b8978ae6d 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -55,6 +55,28 @@ static int check_removed(const struct cache_entry *ce, struct stat *st) return 0; } +/* + * Has a file changed or has a submodule new commits or a dirty work tree? + * + * Return 1 when changes are detected, 0 otherwise. If the DIRTY_SUBMODULES + * option is set, the caller does not only want to know if a submodule is + * modified at all but wants to know all the conditions that are met (new + * commits, untracked content and/or modified content). + */ +static int match_stat_with_submodule(struct diff_options *diffopt, + struct cache_entry *ce, struct stat *st, + unsigned ce_option, unsigned *dirty_submodule) +{ + int changed = ce_match_stat(ce, st, ce_option); + if (S_ISGITLINK(ce->ce_mode) + && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) + && !DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES) + && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { + *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES)); + } + return changed; +} + int run_diff_files(struct rev_info *revs, unsigned int option) { int entries, i; @@ -177,15 +199,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option) ce->sha1, ce->name, 0); continue; } - changed = ce_match_stat(ce, &st, ce_option); - if (S_ISGITLINK(ce->ce_mode) - && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES) - && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) - && is_submodule_modified(ce->name)) { - changed = 1; - dirty_submodule = 1; - } - if (!changed) { + changed = match_stat_with_submodule(&revs->diffopt, ce, &st, + ce_option, &dirty_submodule); + if (!changed && !dirty_submodule) { ce_mark_uptodate(ce); if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) continue; @@ -240,14 +256,8 @@ static int get_stat_data(struct cache_entry *ce, } return -1; } - changed = ce_match_stat(ce, &st, 0); - if (S_ISGITLINK(ce->ce_mode) - && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) - && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH)) - && is_submodule_modified(ce->name)) { - changed = 1; - *dirty_submodule = 1; - } + changed = match_stat_with_submodule(diffopt, ce, &st, + 0, dirty_submodule); if (changed) { mode = ce_mode_from_stat(ce, st.st_mode); sha1 = null_sha1; @@ -322,7 +332,7 @@ static int show_modified(struct rev_info *revs, } oldmode = old->ce_mode; - if (mode == oldmode && !hashcmp(sha1, old->sha1) && + if (mode == oldmode && !hashcmp(sha1, old->sha1) && !dirty_submodule && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) return 0; @@ -510,9 +520,12 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) int index_differs_from(const char *def, int diff_flags) { struct rev_info rev; + struct setup_revision_opt opt; init_revisions(&rev, NULL); - setup_revisions(0, NULL, &rev, def); + memset(&opt, 0, sizeof(opt)); + opt.def = def; + setup_revisions(0, NULL, &rev, &opt); DIFF_OPT_SET(&rev.diffopt, QUICK); DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); rev.diffopt.flags |= diff_flags; diff --git a/diff-no-index.c b/diff-no-index.c index aae8e7accc..43aeeba2e0 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -26,7 +26,7 @@ static int read_directory(const char *path, struct string_list *list) while ((e = readdir(dir))) if (strcmp(".", e->d_name) && strcmp("..", e->d_name)) - string_list_insert(e->d_name, list); + string_list_insert(list, e->d_name); closedir(dir); return 0; @@ -150,16 +150,14 @@ static int queue_diff(struct diff_options *o, static int path_outside_repo(const char *path) { - /* - * We have already done setup_git_directory_gently() so we - * know we are inside a git work tree already. - */ const char *work_tree; size_t len; if (!is_absolute_path(path)) return 0; work_tree = get_git_work_tree(); + if (!work_tree) + return 1; len = strlen(work_tree); if (strncmp(path, work_tree, len) || (path[len] != '\0' && path[len] != '/')) @@ -30,6 +30,7 @@ static const char *diff_word_regex_cfg; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; +static int diff_no_prefix; static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_RESET, @@ -43,9 +44,6 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* FUNCINFO */ }; -static void diff_filespec_load_driver(struct diff_filespec *one); -static char *run_textconv(const char *, struct diff_filespec *, size_t *); - static int parse_diff_color_slot(const char *var, int ofs) { if (!strcasecmp(var+ofs, "plain")) @@ -100,6 +98,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_mnemonic_prefix = git_config_bool(var, value); return 0; } + if (!strcmp(var, "diff.noprefix")) { + diff_no_prefix = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "diff.external")) return git_config_string(&external_diff_cmd_cfg, var, value); if (!strcmp(var, "diff.wordregex")) @@ -193,8 +195,8 @@ struct emit_callback { sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; + struct diff_options *opt; int *found_changesp; - FILE *file; struct strbuf *header; }; @@ -281,11 +283,19 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; } -static void emit_line_0(FILE *file, const char *set, const char *reset, +static void emit_line_0(struct diff_options *o, const char *set, const char *reset, int first, const char *line, int len) { int has_trailing_newline, has_trailing_carriage_return; int nofirst; + FILE *file = o->file; + + if (o->output_prefix) { + struct strbuf *msg = NULL; + msg = o->output_prefix(o, o->output_prefix_data); + assert(msg); + fwrite(msg->buf, msg->len, 1, file); + } if (len == 0) { has_trailing_newline = (first == '\n'); @@ -315,10 +325,10 @@ static void emit_line_0(FILE *file, const char *set, const char *reset, fputc('\n', file); } -static void emit_line(FILE *file, const char *set, const char *reset, +static void emit_line(struct diff_options *o, const char *set, const char *reset, const char *line, int len) { - emit_line_0(file, set, reset, line[0], line+1, len-1); + emit_line_0(o, set, reset, line[0], line+1, len-1); } static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) @@ -340,15 +350,15 @@ static void emit_add_line(const char *reset, const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); if (!*ws) - emit_line_0(ecbdata->file, set, reset, '+', line, len); + emit_line_0(ecbdata->opt, set, reset, '+', line, len); else if (new_blank_line_at_eof(ecbdata, line, len)) /* Blank line at EOF - paint '+' as well */ - emit_line_0(ecbdata->file, ws, reset, '+', line, len); + emit_line_0(ecbdata->opt, ws, reset, '+', line, len); else { /* Emit just the prefix, then the rest. */ - emit_line_0(ecbdata->file, set, reset, '+', "", 0); + emit_line_0(ecbdata->opt, set, reset, '+', "", 0); ws_check_emit(line, len, ecbdata->ws_rule, - ecbdata->file, set, reset, ws); + ecbdata->opt->file, set, reset, ws); } } @@ -361,6 +371,9 @@ static void emit_hunk_header(struct emit_callback *ecbdata, const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); static const char atat[2] = { '@', '@' }; const char *cp, *ep; + struct strbuf msgbuf = STRBUF_INIT; + int org_len = len; + int i = 1; /* * As a hunk header must begin with "@@ -<old>, +<new> @@", @@ -369,23 +382,42 @@ static void emit_hunk_header(struct emit_callback *ecbdata, if (len < 10 || memcmp(line, atat, 2) || !(ep = memmem(line + 2, len - 2, atat, 2))) { - emit_line(ecbdata->file, plain, reset, line, len); + emit_line(ecbdata->opt, plain, reset, line, len); return; } ep += 2; /* skip over @@ */ /* The hunk header in fraginfo color */ - emit_line(ecbdata->file, frag, reset, line, ep - line); + strbuf_add(&msgbuf, frag, strlen(frag)); + strbuf_add(&msgbuf, line, ep - line); + strbuf_add(&msgbuf, reset, strlen(reset)); + + /* + * trailing "\r\n" + */ + for ( ; i < 3; i++) + if (line[len - i] == '\r' || line[len - i] == '\n') + len--; /* blank before the func header */ for (cp = ep; ep - line < len; ep++) if (*ep != ' ' && *ep != '\t') break; - if (ep != cp) - emit_line(ecbdata->file, plain, reset, cp, ep - cp); + if (ep != cp) { + strbuf_add(&msgbuf, plain, strlen(plain)); + strbuf_add(&msgbuf, cp, ep - cp); + strbuf_add(&msgbuf, reset, strlen(reset)); + } - if (ep < line + len) - emit_line(ecbdata->file, func, reset, ep, line + len - ep); + if (ep < line + len) { + strbuf_add(&msgbuf, func, strlen(func)); + strbuf_add(&msgbuf, ep, line + len - ep); + strbuf_add(&msgbuf, reset, strlen(reset)); + } + + strbuf_add(&msgbuf, line + len, org_len - len); + emit_line(ecbdata->opt, "", "", msgbuf.buf, msgbuf.len); + strbuf_release(&msgbuf); } static struct diff_tempfile *claim_diff_tempfile(void) { @@ -445,7 +477,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb, len = endp ? (endp - data + 1) : size; if (prefix != '+') { ecb->lno_in_preimage++; - emit_line_0(ecb->file, old, reset, '-', + emit_line_0(ecb->opt, old, reset, '-', data, len); } else { ecb->lno_in_postimage++; @@ -457,7 +489,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb, if (!endp) { const char *plain = diff_get_color(ecb->color_diff, DIFF_PLAIN); - emit_line_0(ecb->file, plain, reset, '\\', + emit_line_0(ecb->opt, plain, reset, '\\', nneof, strlen(nneof)); } } @@ -466,8 +498,8 @@ static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, struct diff_filespec *two, - const char *textconv_one, - const char *textconv_two, + struct userdiff_driver *textconv_one, + struct userdiff_driver *textconv_two, struct diff_options *o) { int lc_a, lc_b; @@ -478,9 +510,16 @@ static void emit_rewrite_diff(const char *name_a, const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; const char *a_prefix, *b_prefix; - const char *data_one, *data_two; + char *data_one, *data_two; size_t size_one, size_two; struct emit_callback ecbdata; + char *line_prefix = ""; + struct strbuf *msgbuf; + + if (o && o->output_prefix) { + msgbuf = o->output_prefix(o, o->output_prefix_data); + line_prefix = msgbuf->buf; + } if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { a_prefix = o->b_prefix; @@ -500,32 +539,14 @@ static void emit_rewrite_diff(const char *name_a, quote_two_c_style(&a_name, a_prefix, name_a, 0); quote_two_c_style(&b_name, b_prefix, name_b, 0); - diff_populate_filespec(one, 0); - diff_populate_filespec(two, 0); - if (textconv_one) { - data_one = run_textconv(textconv_one, one, &size_one); - if (!data_one) - die("unable to read files to diff"); - } - else { - data_one = one->data; - size_one = one->size; - } - if (textconv_two) { - data_two = run_textconv(textconv_two, two, &size_two); - if (!data_two) - die("unable to read files to diff"); - } - else { - data_two = two->data; - size_two = two->size; - } + size_one = fill_textconv(textconv_one, one, &data_one); + size_two = fill_textconv(textconv_two, two, &data_two); memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.color_diff = color_diff; ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); - ecbdata.file = o->file; + ecbdata.opt = o; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { mmfile_t mf1, mf2; mf1.ptr = (char *)data_one; @@ -540,9 +561,10 @@ static void emit_rewrite_diff(const char *name_a, lc_a = count_lines(data_one, size_one); lc_b = count_lines(data_two, size_two); fprintf(o->file, - "%s--- %s%s%s\n%s+++ %s%s%s\n%s@@ -", - metainfo, a_name.buf, name_a_tab, reset, - metainfo, b_name.buf, name_b_tab, reset, fraginfo); + "%s%s--- %s%s%s\n%s%s+++ %s%s%s\n%s%s@@ -", + line_prefix, metainfo, a_name.buf, name_a_tab, reset, + line_prefix, metainfo, b_name.buf, name_b_tab, reset, + line_prefix, fraginfo); print_line_count(o->file, lc_a); fprintf(o->file, " +"); print_line_count(o->file, lc_b); @@ -551,6 +573,10 @@ static void emit_rewrite_diff(const char *name_a, emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) emit_rewrite_lines(&ecbdata, '+', data_two, size_two); + if (textconv_one) + free((char *)data_one); + if (textconv_two) + free((char *)data_two); } struct diff_words_buffer { @@ -573,23 +599,134 @@ static void diff_words_append(char *line, unsigned long len, buffer->text.ptr[buffer->text.size] = '\0'; } +struct diff_words_style_elem +{ + const char *prefix; + const char *suffix; + const char *color; /* NULL; filled in by the setup code if + * color is enabled */ +}; + +struct diff_words_style +{ + enum diff_words_type type; + struct diff_words_style_elem new, old, ctx; + const char *newline; +}; + +struct diff_words_style diff_words_styles[] = { + { DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" }, + { DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" }, + { DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" } +}; + struct diff_words_data { struct diff_words_buffer minus, plus; const char *current_plus; - FILE *file; + int last_minus; + struct diff_options *opt; regex_t *word_regex; + enum diff_words_type type; + struct diff_words_style *style; }; +static int fn_out_diff_words_write_helper(FILE *fp, + struct diff_words_style_elem *st_el, + const char *newline, + size_t count, const char *buf, + const char *line_prefix) +{ + int print = 0; + + while (count) { + char *p = memchr(buf, '\n', count); + if (print) + fputs(line_prefix, fp); + if (p != buf) { + if (st_el->color && fputs(st_el->color, fp) < 0) + return -1; + if (fputs(st_el->prefix, fp) < 0 || + fwrite(buf, p ? p - buf : count, 1, fp) != 1 || + fputs(st_el->suffix, fp) < 0) + return -1; + if (st_el->color && *st_el->color + && fputs(GIT_COLOR_RESET, fp) < 0) + return -1; + } + if (!p) + return 0; + if (fputs(newline, fp) < 0) + return -1; + count -= p + 1 - buf; + buf = p + 1; + print = 1; + } + return 0; +} + +/* + * '--color-words' algorithm can be described as: + * + * 1. collect a the minus/plus lines of a diff hunk, divided into + * minus-lines and plus-lines; + * + * 2. break both minus-lines and plus-lines into words and + * place them into two mmfile_t with one word for each line; + * + * 3. use xdiff to run diff on the two mmfile_t to get the words level diff; + * + * And for the common parts of the both file, we output the plus side text. + * diff_words->current_plus is used to trace the current position of the plus file + * which printed. diff_words->last_minus is used to trace the last minus word + * printed. + * + * For '--graph' to work with '--color-words', we need to output the graph prefix + * on each line of color words output. Generally, there are two conditions on + * which we should output the prefix. + * + * 1. diff_words->last_minus == 0 && + * diff_words->current_plus == diff_words->plus.text.ptr + * + * that is: the plus text must start as a new line, and if there is no minus + * word printed, a graph prefix must be printed. + * + * 2. diff_words->current_plus > diff_words->plus.text.ptr && + * *(diff_words->current_plus - 1) == '\n' + * + * that is: a graph prefix must be printed following a '\n' + */ +static int color_words_output_graph_prefix(struct diff_words_data *diff_words) +{ + if ((diff_words->last_minus == 0 && + diff_words->current_plus == diff_words->plus.text.ptr) || + (diff_words->current_plus > diff_words->plus.text.ptr && + *(diff_words->current_plus - 1) == '\n')) { + return 1; + } else { + return 0; + } +} + static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) { struct diff_words_data *diff_words = priv; + struct diff_words_style *style = diff_words->style; int minus_first, minus_len, plus_first, plus_len; const char *minus_begin, *minus_end, *plus_begin, *plus_end; + struct diff_options *opt = diff_words->opt; + struct strbuf *msgbuf; + char *line_prefix = ""; if (line[0] != '@' || parse_hunk_header(line, len, &minus_first, &minus_len, &plus_first, &plus_len)) return; + assert(opt); + if (opt->output_prefix) { + msgbuf = opt->output_prefix(opt, opt->output_prefix_data); + line_prefix = msgbuf->buf; + } + /* POSIX requires that first be decremented by one if len == 0... */ if (minus_len) { minus_begin = diff_words->minus.orig[minus_first].begin; @@ -605,20 +742,32 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) } else plus_begin = plus_end = diff_words->plus.orig[plus_first].end; - if (diff_words->current_plus != plus_begin) - fwrite(diff_words->current_plus, - plus_begin - diff_words->current_plus, 1, - diff_words->file); - if (minus_begin != minus_end) - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_OLD), - minus_end - minus_begin, minus_begin); - if (plus_begin != plus_end) - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_NEW), - plus_end - plus_begin, plus_begin); + if (color_words_output_graph_prefix(diff_words)) { + fputs(line_prefix, diff_words->opt->file); + } + if (diff_words->current_plus != plus_begin) { + fn_out_diff_words_write_helper(diff_words->opt->file, + &style->ctx, style->newline, + plus_begin - diff_words->current_plus, + diff_words->current_plus, line_prefix); + if (*(plus_begin - 1) == '\n') + fputs(line_prefix, diff_words->opt->file); + } + if (minus_begin != minus_end) { + fn_out_diff_words_write_helper(diff_words->opt->file, + &style->old, style->newline, + minus_end - minus_begin, minus_begin, + line_prefix); + } + if (plus_begin != plus_end) { + fn_out_diff_words_write_helper(diff_words->opt->file, + &style->new, style->newline, + plus_end - plus_begin, plus_begin, + line_prefix); + } diff_words->current_plus = plus_end; + diff_words->last_minus = minus_first; } /* This function starts looking at *begin, and returns 0 iff a word was found. */ @@ -697,23 +846,37 @@ static void diff_words_show(struct diff_words_data *diff_words) xpparam_t xpp; xdemitconf_t xecfg; mmfile_t minus, plus; + struct diff_words_style *style = diff_words->style; + + struct diff_options *opt = diff_words->opt; + struct strbuf *msgbuf; + char *line_prefix = ""; + + assert(opt); + if (opt->output_prefix) { + msgbuf = opt->output_prefix(opt, opt->output_prefix_data); + line_prefix = msgbuf->buf; + } /* special case: only removal */ if (!diff_words->plus.text.size) { - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_OLD), - diff_words->minus.text.size, diff_words->minus.text.ptr); + fputs(line_prefix, diff_words->opt->file); + fn_out_diff_words_write_helper(diff_words->opt->file, + &style->old, style->newline, + diff_words->minus.text.size, + diff_words->minus.text.ptr, line_prefix); diff_words->minus.text.size = 0; return; } diff_words->current_plus = diff_words->plus.text.ptr; + diff_words->last_minus = 0; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex); diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; /* as only the hunk header will be parsed, we need a 0-context */ xecfg.ctxlen = 0; xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, @@ -721,11 +884,15 @@ static void diff_words_show(struct diff_words_data *diff_words) free(minus.ptr); free(plus.ptr); if (diff_words->current_plus != diff_words->plus.text.ptr + - diff_words->plus.text.size) - fwrite(diff_words->current_plus, + diff_words->plus.text.size) { + if (color_words_output_graph_prefix(diff_words)) + fputs(line_prefix, diff_words->opt->file); + fn_out_diff_words_write_helper(diff_words->opt->file, + &style->ctx, style->newline, diff_words->plus.text.ptr + diff_words->plus.text.size - - diff_words->current_plus, 1, - diff_words->file); + - diff_words->current_plus, diff_words->current_plus, + line_prefix); + } diff_words->minus.text.size = diff_words->plus.text.size = 0; } @@ -797,9 +964,17 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); + struct diff_options *o = ecbdata->opt; + char *line_prefix = ""; + struct strbuf *msgbuf; + + if (o && o->output_prefix) { + msgbuf = o->output_prefix(o, o->output_prefix_data); + line_prefix = msgbuf->buf; + } if (ecbdata->header) { - fprintf(ecbdata->file, "%s", ecbdata->header->buf); + fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf); strbuf_reset(ecbdata->header); ecbdata->header = NULL; } @@ -811,10 +986,10 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : ""; name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : ""; - fprintf(ecbdata->file, "%s--- %s%s%s\n", - meta, ecbdata->label_path[0], reset, name_a_tab); - fprintf(ecbdata->file, "%s+++ %s%s%s\n", - meta, ecbdata->label_path[1], reset, name_b_tab); + fprintf(ecbdata->opt->file, "%s%s--- %s%s%s\n", + line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab); + fprintf(ecbdata->opt->file, "%s%s+++ %s%s%s\n", + line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab); ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } @@ -831,12 +1006,15 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) find_lno(line, ecbdata); emit_hunk_header(ecbdata, line, len); if (line[len-1] != '\n') - putc('\n', ecbdata->file); + putc('\n', ecbdata->opt->file); return; } if (len < 1) { - emit_line(ecbdata->file, reset, reset, line, len); + emit_line(ecbdata->opt, reset, reset, line, len); + if (ecbdata->diff_words + && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) + fputs("~\n", ecbdata->opt->file); return; } @@ -851,9 +1029,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } diff_words_flush(ecbdata); - line++; - len--; - emit_line(ecbdata->file, plain, reset, line, len); + if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) { + emit_line(ecbdata->opt, plain, reset, line, len); + fputs("~\n", ecbdata->opt->file); + } else { + /* don't print the prefix character */ + emit_line(ecbdata->opt, plain, reset, line+1, len-1); + } return; } @@ -864,7 +1046,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ecbdata->lno_in_preimage++; if (line[0] == ' ') ecbdata->lno_in_postimage++; - emit_line(ecbdata->file, color, reset, line, len); + emit_line(ecbdata->opt, color, reset, line, len); } else { ecbdata->lno_in_postimage++; emit_add_line(reset, ecbdata, line + 1, len - 1); @@ -1044,10 +1226,17 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) int total_files = data->nr; int width, name_width; const char *reset, *set, *add_c, *del_c; + const char *line_prefix = ""; + struct strbuf *msg = NULL; if (data->nr == 0) return; + if (options->output_prefix) { + msg = options->output_prefix(options, options->output_prefix_data); + line_prefix = msg->buf; + } + width = options->stat_width ? options->stat_width : 80; name_width = options->stat_name_width ? options->stat_name_width : 50; @@ -1117,6 +1306,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) } if (data->files[i]->is_binary) { + fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); fprintf(options->file, " Bin "); fprintf(options->file, "%s%"PRIuMAX"%s", @@ -1129,6 +1319,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) continue; } else if (data->files[i]->is_unmerged) { + fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); fprintf(options->file, " Unmerged\n"); continue; @@ -1151,6 +1342,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) add = scale_linear(add, width, max_change); del = scale_linear(del, width, max_change); } + fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); fprintf(options->file, "%5"PRIuMAX"%s", added + deleted, added + deleted ? " " : ""); @@ -1158,6 +1350,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) show_graph(options->file, '-', del, del_c, reset); fprintf(options->file, "\n"); } + fprintf(options->file, "%s", line_prefix); fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n", total_files, adds, dels); @@ -1184,6 +1377,12 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option } } } + if (options->output_prefix) { + struct strbuf *msg = NULL; + msg = options->output_prefix(options, + options->output_prefix_data); + fprintf(options->file, "%s", msg->buf); + } fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n", total_files, adds, dels); } @@ -1198,6 +1397,13 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; + if (options->output_prefix) { + struct strbuf *msg = NULL; + msg = options->output_prefix(options, + options->output_prefix_data); + fprintf(options->file, "%s", msg->buf); + } + if (file->is_binary) fprintf(options->file, "-\t-\t"); else @@ -1233,10 +1439,18 @@ struct dirstat_dir { int alloc, nr, percent, cumulative; }; -static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long changed, const char *base, int baselen) +static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, + unsigned long changed, const char *base, int baselen) { unsigned long this_dir = 0; unsigned int sources = 0; + const char *line_prefix = ""; + struct strbuf *msg = NULL; + + if (opt->output_prefix) { + msg = opt->output_prefix(opt, opt->output_prefix_data); + line_prefix = msg->buf; + } while (dir->nr) { struct dirstat_file *f = dir->files; @@ -1251,7 +1465,7 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch slash = strchr(f->name + baselen, '/'); if (slash) { int newbaselen = slash + 1 - f->name; - this = gather_dirstat(file, dir, changed, f->name, newbaselen); + this = gather_dirstat(opt, dir, changed, f->name, newbaselen); sources++; } else { this = f->changed; @@ -1273,7 +1487,8 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch if (permille) { int percent = permille / 10; if (percent >= dir->percent) { - fprintf(file, "%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base); + fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix, + percent, permille % 10, baselen, base); if (!dir->cumulative) return 0; } @@ -1353,7 +1568,7 @@ static void show_dirstat(struct diff_options *options) /* Show all directories with more than x% of the changes */ qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare); - gather_dirstat(options->file, &dir, changed, "", 0); + gather_dirstat(options, &dir, changed, "", 0); } static void free_diffstat_info(struct diffstat_t *diffstat) @@ -1411,6 +1626,15 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) const char *reset = diff_get_color(color_diff, DIFF_RESET); const char *set = diff_get_color(color_diff, DIFF_FILE_NEW); char *err; + char *line_prefix = ""; + struct strbuf *msgbuf; + + assert(data->o); + if (data->o->output_prefix) { + msgbuf = data->o->output_prefix(data->o, + data->o->output_prefix_data); + line_prefix = msgbuf->buf; + } if (line[0] == '+') { unsigned bad; @@ -1418,18 +1642,18 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (is_conflict_marker(line + 1, marker_size, len - 1)) { data->status |= 1; fprintf(data->o->file, - "%s:%d: leftover conflict marker\n", - data->filename, data->lineno); + "%s%s:%d: leftover conflict marker\n", + line_prefix, data->filename, data->lineno); } bad = ws_check(line + 1, len - 1, data->ws_rule); if (!bad) return; data->status |= bad; err = whitespace_error_string(bad); - fprintf(data->o->file, "%s:%d: %s.\n", - data->filename, data->lineno, err); + fprintf(data->o->file, "%s%s:%d: %s.\n", + line_prefix, data->filename, data->lineno, err); free(err); - emit_line(data->o->file, set, reset, line, 1); + emit_line(data->o, set, reset, line, 1); ws_check_emit(line + 1, len - 1, data->ws_rule, data->o->file, set, reset, ws); } else if (line[0] == ' ') { @@ -1467,7 +1691,7 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two) +static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) { void *cp; void *delta; @@ -1496,13 +1720,13 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two) } if (delta && delta_size < deflate_size) { - fprintf(file, "delta %lu\n", orig_size); + fprintf(file, "%sdelta %lu\n", prefix, orig_size); free(deflated); data = delta; data_size = delta_size; } else { - fprintf(file, "literal %lu\n", two->size); + fprintf(file, "%sliteral %lu\n", prefix, two->size); free(delta); data = deflated; data_size = deflate_size; @@ -1520,18 +1744,19 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two) line[0] = bytes - 26 + 'a' - 1; encode_85(line + 1, cp, bytes); cp = (char *) cp + bytes; + fprintf(file, "%s", prefix); fputs(line, file); fputc('\n', file); } - fprintf(file, "\n"); + fprintf(file, "%s\n", prefix); free(data); } -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two) +static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) { - fprintf(file, "GIT binary patch\n"); - emit_binary_diff_body(file, one, two); - emit_binary_diff_body(file, two, one); + fprintf(file, "%sGIT binary patch\n", prefix); + emit_binary_diff_body(file, one, two, prefix); + emit_binary_diff_body(file, two, one, prefix); } static void diff_filespec_load_driver(struct diff_filespec *one) @@ -1581,14 +1806,26 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const options->b_prefix = b; } -static const char *get_textconv(struct diff_filespec *one) +struct userdiff_driver *get_textconv(struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) return NULL; if (!S_ISREG(one->mode)) return NULL; diff_filespec_load_driver(one); - return one->driver->textconv; + if (!one->driver->textconv) + return NULL; + + if (one->driver->textconv_want_cache && !one->driver->textconv_cache) { + struct notes_cache *c = xmalloc(sizeof(*c)); + struct strbuf name = STRBUF_INIT; + + strbuf_addf(&name, "textconv/%s", one->driver->name); + notes_cache_init(c, name.buf, one->driver->textconv); + one->driver->textconv_cache = c; + } + + return one->driver; } static void builtin_diff(const char *name_a, @@ -1596,6 +1833,7 @@ static void builtin_diff(const char *name_a, struct diff_filespec *one, struct diff_filespec *two, const char *xfrm_msg, + int must_show_header, struct diff_options *o, int complete_rewrite) { @@ -1605,8 +1843,16 @@ static void builtin_diff(const char *name_a, const char *set = diff_get_color_opt(o, DIFF_METAINFO); const char *reset = diff_get_color_opt(o, DIFF_RESET); const char *a_prefix, *b_prefix; - const char *textconv_one = NULL, *textconv_two = NULL; + struct userdiff_driver *textconv_one = NULL; + struct userdiff_driver *textconv_two = NULL; struct strbuf header = STRBUF_INIT; + struct strbuf *msgbuf; + char *line_prefix = ""; + + if (o->output_prefix) { + msgbuf = o->output_prefix(o, o->output_prefix_data); + line_prefix = msgbuf->buf; + } if (DIFF_OPT_TST(o, SUBMODULE_LOG) && (!one->mode || S_ISGITLINK(one->mode)) && @@ -1641,25 +1887,28 @@ static void builtin_diff(const char *name_a, b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); + strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, set, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); + must_show_header = 1; } else if (lbl[1][0] == '/') { - strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset); - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, set, one->mode, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); + must_show_header = 1; } else { if (one->mode != two->mode) { - strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset); - strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, set, one->mode, reset); + strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, set, two->mode, reset); + must_show_header = 1; } - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); /* * we do not run diff between different kind @@ -1679,23 +1928,25 @@ static void builtin_diff(const char *name_a, } } - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) - die("unable to read files to diff"); - if (!DIFF_OPT_TST(o, TEXT) && - ( (diff_filespec_is_binary(one) && !textconv_one) || - (diff_filespec_is_binary(two) && !textconv_two) )) { + ( (!textconv_one && diff_filespec_is_binary(one)) || + (!textconv_two && diff_filespec_is_binary(two)) )) { + if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + die("unable to read files to diff"); /* Quite common confusing case */ if (mf1.size == mf2.size && - !memcmp(mf1.ptr, mf2.ptr, mf1.size)) + !memcmp(mf1.ptr, mf2.ptr, mf1.size)) { + if (must_show_header) + fprintf(o->file, "%s", header.buf); goto free_ab_and_return; + } fprintf(o->file, "%s", header.buf); strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) - emit_binary_diff(o->file, &mf1, &mf2); + emit_binary_diff(o->file, &mf1, &mf2, line_prefix); else - fprintf(o->file, "Binary files %s and %s differ\n", - lbl[0], lbl[1]); + fprintf(o->file, "%sBinary files %s and %s differ\n", + line_prefix, lbl[0], lbl[1]); o->found_changes = 1; } else { @@ -1706,25 +1957,13 @@ static void builtin_diff(const char *name_a, struct emit_callback ecbdata; const struct userdiff_funcname *pe; - if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) { + if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) { fprintf(o->file, "%s", header.buf); strbuf_reset(&header); } - if (textconv_one) { - size_t size; - mf1.ptr = run_textconv(textconv_one, one, &size); - if (!mf1.ptr) - die("unable to read files to diff"); - mf1.size = size; - } - if (textconv_two) { - size_t size; - mf2.ptr = run_textconv(textconv_two, two, &size); - if (!mf2.ptr) - die("unable to read files to diff"); - mf2.size = size; - } + mf1.size = fill_textconv(textconv_one, one, &mf1.ptr); + mf2.size = fill_textconv(textconv_two, two, &mf2.ptr); pe = diff_funcname_pattern(one); if (!pe) @@ -1739,9 +1978,9 @@ static void builtin_diff(const char *name_a, ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); - ecbdata.file = o->file; + ecbdata.opt = o; ecbdata.header = header.len ? &header : NULL; - xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; + xpp.flags = o->xdl_opts; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; @@ -1753,10 +1992,13 @@ static void builtin_diff(const char *name_a, xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); else if (!prefixcmp(diffopts, "-u")) xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) { + if (o->word_diff) { + int i; + ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); - ecbdata.diff_words->file = o->file; + ecbdata.diff_words->type = o->word_diff; + ecbdata.diff_words->opt = o; if (!o->word_regex) o->word_regex = userdiff_word_regex(one); if (!o->word_regex) @@ -1772,10 +2014,23 @@ static void builtin_diff(const char *name_a, die ("Invalid regular expression: %s", o->word_regex); } + for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) { + if (o->word_diff == diff_words_styles[i].type) { + ecbdata.diff_words->style = + &diff_words_styles[i]; + break; + } + } + if (DIFF_OPT_TST(o, COLOR_DIFF)) { + struct diff_words_style *st = ecbdata.diff_words->style; + st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD); + st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW); + st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN); + } } xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, &xpp, &xecfg); - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) + if (o->word_diff) free_diff_words_data(&ecbdata); if (textconv_one) free(mf1.ptr); @@ -1830,7 +2085,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); - xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; + xpp.flags = o->xdl_opts; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, &xpp, &xecfg); } @@ -1878,7 +2133,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, &xpp, &xecfg); @@ -2029,7 +2284,7 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only) char *data = xmalloc(100), *dirty = ""; /* Are we looking at the work tree? */ - if (!s->sha1_valid && s->dirty_submodule) + if (s->dirty_submodule) dirty = "-dirty"; len = snprintf(data, 100, @@ -2315,36 +2570,53 @@ static void fill_metainfo(struct strbuf *msg, struct diff_filespec *one, struct diff_filespec *two, struct diff_options *o, - struct diff_filepair *p) + struct diff_filepair *p, + int *must_show_header, + int use_color) { + const char *set = diff_get_color(use_color, DIFF_METAINFO); + const char *reset = diff_get_color(use_color, DIFF_RESET); + struct strbuf *msgbuf; + char *line_prefix = ""; + + *must_show_header = 1; + if (o->output_prefix) { + msgbuf = o->output_prefix(o, o->output_prefix_data); + line_prefix = msgbuf->buf; + } strbuf_init(msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\ncopy from "); + strbuf_addf(msg, "%s%ssimilarity index %d%%", + line_prefix, set, similarity_index(p)); + strbuf_addf(msg, "%s\n%s%scopy from ", + reset, line_prefix, set); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\ncopy to "); + strbuf_addf(msg, "%s\n%s%scopy to ", reset, line_prefix, set); quote_c_style(other, msg, NULL, 0); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); break; case DIFF_STATUS_RENAMED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\nrename from "); + strbuf_addf(msg, "%s%ssimilarity index %d%%", + line_prefix, set, similarity_index(p)); + strbuf_addf(msg, "%s\n%s%srename from ", + reset, line_prefix, set); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\nrename to "); + strbuf_addf(msg, "%s\n%s%srename to ", + reset, line_prefix, set); quote_c_style(other, msg, NULL, 0); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); break; case DIFF_STATUS_MODIFIED: if (p->score) { - strbuf_addf(msg, "dissimilarity index %d%%\n", - similarity_index(p)); + strbuf_addf(msg, "%s%sdissimilarity index %d%%%s\n", + line_prefix, + set, similarity_index(p), reset); break; } /* fallthru */ default: - /* nothing */ - ; + *must_show_header = 0; } if (one && two && hashcmp(one->sha1, two->sha1)) { int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; @@ -2355,15 +2627,13 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "index %.*s..%.*s", - abbrev, sha1_to_hex(one->sha1), - abbrev, sha1_to_hex(two->sha1)); + strbuf_addf(msg, "%s%sindex %s..", line_prefix, set, + find_unique_abbrev(one->sha1, abbrev)); + strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); } - if (msg->len) - strbuf_setlen(msg, msg->len - 1); } static void run_diff_cmd(const char *pgm, @@ -2378,11 +2648,7 @@ static void run_diff_cmd(const char *pgm, { const char *xfrm_msg = NULL; int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score; - - if (msg) { - fill_metainfo(msg, name, other, one, two, o, p); - xfrm_msg = msg->len ? msg->buf : NULL; - } + int must_show_header = 0; if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) pgm = NULL; @@ -2392,6 +2658,17 @@ static void run_diff_cmd(const char *pgm, pgm = drv->external; } + if (msg) { + /* + * don't use colors when the header is intended for an + * external diff driver + */ + fill_metainfo(msg, name, other, one, two, o, p, + &must_show_header, + DIFF_OPT_TST(o, COLOR_DIFF) && !pgm); + xfrm_msg = msg->len ? msg->buf : NULL; + } + if (pgm) { run_external_diff(pgm, name, other, one, two, xfrm_msg, complete_rewrite); @@ -2399,7 +2676,8 @@ static void run_diff_cmd(const char *pgm, } if (one && two) builtin_diff(name, other ? other : name, - one, two, xfrm_msg, o, complete_rewrite); + one, two, xfrm_msg, must_show_header, + o, complete_rewrite); else fprintf(o->file, "* Unmerged path %s\n", name); } @@ -2426,10 +2704,16 @@ static void diff_fill_sha1_info(struct diff_filespec *one) static void strip_prefix(int prefix_length, const char **namep, const char **otherp) { /* Strip the prefix but do not molest /dev/null and absolute paths */ - if (*namep && **namep != '/') + if (*namep && **namep != '/') { *namep += prefix_length; - if (*otherp && **otherp != '/') + if (**namep == '/') + ++*namep; + } + if (*otherp && **otherp != '/') { *otherp += prefix_length; + if (**otherp == '/') + ++*otherp; + } } static void run_diff(struct diff_filepair *p, struct diff_options *o) @@ -2551,7 +2835,9 @@ void diff_setup(struct diff_options *options) DIFF_OPT_SET(options, COLOR_DIFF); options->detect_rename = diff_detect_rename_default; - if (!diff_mnemonic_prefix) { + if (diff_no_prefix) { + options->a_prefix = options->b_prefix = ""; + } else if (!diff_mnemonic_prefix) { options->a_prefix = "a/"; options->b_prefix = "b/"; } @@ -2625,6 +2911,12 @@ int diff_setup_done(struct diff_options *options) */ if (options->pickaxe) DIFF_OPT_SET(options, RECURSIVE); + /* + * When patches are generated, submodules diffed against the work tree + * must be checked for dirtiness too so it can be shown in the output + */ + if (options->output_format & DIFF_FORMAT_PATCH) + DIFF_OPT_SET(options, DIRTY_SUBMODULES); if (options->detect_rename && options->rename_limit < 0) options->rename_limit = diff_rename_limit_default; @@ -2708,7 +3000,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) const char *arg = av[0]; /* Output format options */ - if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) + if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch")) options->output_format |= DIFF_FORMAT_PATCH; else if (opt_arg(arg, 'U', "unified", &options->context)) options->output_format |= DIFF_FORMAT_PATCH; @@ -2823,17 +3115,50 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, FOLLOW_RENAMES); else if (!strcmp(arg, "--color")) DIFF_OPT_SET(options, COLOR_DIFF); + else if (!prefixcmp(arg, "--color=")) { + int value = git_config_colorbool(NULL, arg+8, -1); + if (value == 0) + DIFF_OPT_CLR(options, COLOR_DIFF); + else if (value > 0) + DIFF_OPT_SET(options, COLOR_DIFF); + else + return error("option `color' expects \"always\", \"auto\", or \"never\""); + } else if (!strcmp(arg, "--no-color")) DIFF_OPT_CLR(options, COLOR_DIFF); else if (!strcmp(arg, "--color-words")) { DIFF_OPT_SET(options, COLOR_DIFF); - DIFF_OPT_SET(options, COLOR_DIFF_WORDS); + options->word_diff = DIFF_WORDS_COLOR; } else if (!prefixcmp(arg, "--color-words=")) { DIFF_OPT_SET(options, COLOR_DIFF); - DIFF_OPT_SET(options, COLOR_DIFF_WORDS); + options->word_diff = DIFF_WORDS_COLOR; options->word_regex = arg + 14; } + else if (!strcmp(arg, "--word-diff")) { + if (options->word_diff == DIFF_WORDS_NONE) + options->word_diff = DIFF_WORDS_PLAIN; + } + else if (!prefixcmp(arg, "--word-diff=")) { + const char *type = arg + 12; + if (!strcmp(type, "plain")) + options->word_diff = DIFF_WORDS_PLAIN; + else if (!strcmp(type, "color")) { + DIFF_OPT_SET(options, COLOR_DIFF); + options->word_diff = DIFF_WORDS_COLOR; + } + else if (!strcmp(type, "porcelain")) + options->word_diff = DIFF_WORDS_PORCELAIN; + else if (!strcmp(type, "none")) + options->word_diff = DIFF_WORDS_NONE; + else + die("bad --word-diff argument: %s", type); + } + else if (!prefixcmp(arg, "--word-diff-regex=")) { + if (options->word_diff == DIFF_WORDS_NONE) + options->word_diff = DIFF_WORDS_PLAIN; + options->word_regex = arg + 18; + } else if (!strcmp(arg, "--exit-code")) DIFF_OPT_SET(options, EXIT_WITH_STATUS); else if (!strcmp(arg, "--quiet")) @@ -2847,7 +3172,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--no-textconv")) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--ignore-submodules")) - DIFF_OPT_SET(options, IGNORE_SUBMODULES); + handle_ignore_submodules_arg(options, "all"); + else if (!prefixcmp(arg, "--ignore-submodules=")) + handle_ignore_submodules_arg(options, arg + 20); else if (!strcmp(arg, "--submodule")) DIFF_OPT_SET(options, SUBMODULE_LOG); else if (!prefixcmp(arg, "--submodule=")) { @@ -3020,6 +3347,11 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) { int line_termination = opt->line_termination; int inter_name_termination = line_termination ? '\t' : '\0'; + if (opt->output_prefix) { + struct strbuf *msg = NULL; + msg = opt->output_prefix(opt, opt->output_prefix_data); + fprintf(opt->file, "%s", msg->buf); + } if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) { fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode, @@ -3074,7 +3406,8 @@ int diff_unmodified_pair(struct diff_filepair *p) * dealing with a change. */ if (one->sha1_valid && two->sha1_valid && - !hashcmp(one->sha1, two->sha1)) + !hashcmp(one->sha1, two->sha1) && + !one->dirty_submodule && !two->dirty_submodule) return 1; /* no change */ if (!one->sha1_valid && !two->sha1_valid) return 1; /* both look at the same file on the filesystem. */ @@ -3209,6 +3542,8 @@ static void diff_resolve_rename_copy(void) } else if (hashcmp(p->one->sha1, p->two->sha1) || p->one->mode != p->two->mode || + p->one->dirty_submodule || + p->two->dirty_submodule || is_null_sha1(p->one->sha1)) p->status = DIFF_STATUS_MODIFIED; else { @@ -3262,48 +3597,62 @@ static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_f } -static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name) +static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name, + const char *line_prefix) { if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) { - fprintf(file, " mode change %06o => %06o%c", p->one->mode, p->two->mode, - show_name ? ' ' : '\n'); + fprintf(file, "%s mode change %06o => %06o%c", line_prefix, p->one->mode, + p->two->mode, show_name ? ' ' : '\n'); if (show_name) { write_name_quoted(p->two->path, file, '\n'); } } } -static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p) +static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p, + const char *line_prefix) { char *names = pprint_rename(p->one->path, p->two->path); fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); free(names); - show_mode_change(file, p, 0); + show_mode_change(file, p, 0, line_prefix); } -static void diff_summary(FILE *file, struct diff_filepair *p) +static void diff_summary(struct diff_options *opt, struct diff_filepair *p) { + FILE *file = opt->file; + char *line_prefix = ""; + + if (opt->output_prefix) { + struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data); + line_prefix = buf->buf; + } + switch(p->status) { case DIFF_STATUS_DELETED: + fputs(line_prefix, file); show_file_mode_name(file, "delete", p->one); break; case DIFF_STATUS_ADDED: + fputs(line_prefix, file); show_file_mode_name(file, "create", p->two); break; case DIFF_STATUS_COPIED: - show_rename_copy(file, "copy", p); + fputs(line_prefix, file); + show_rename_copy(file, "copy", p, line_prefix); break; case DIFF_STATUS_RENAMED: - show_rename_copy(file, "rename", p); + fputs(line_prefix, file); + show_rename_copy(file, "rename", p, line_prefix); break; default: if (p->score) { - fputs(" rewrite ", file); + fprintf(file, "%s rewrite ", line_prefix); write_name_quoted(p->two->path, file, ' '); fprintf(file, "(%d%%)\n", similarity_index(p)); } - show_mode_change(file, p, !p->score); + show_mode_change(file, p, !p->score, line_prefix); break; } } @@ -3414,7 +3763,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) len2, p->two->path); git_SHA1_Update(&ctx, buffer, len1); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, @@ -3435,8 +3784,7 @@ int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1) diff_free_filepair(q->queue[i]); free(q->queue); - q->queue = NULL; - q->nr = q->alloc = 0; + DIFF_QUEUE_CLEAR(q); return result; } @@ -3513,8 +3861,9 @@ void diff_flush(struct diff_options *options) show_dirstat(options); if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) { - for (i = 0; i < q->nr; i++) - diff_summary(options->file, q->queue[i]); + for (i = 0; i < q->nr; i++) { + diff_summary(options, q->queue[i]); + } separator++; } @@ -3564,8 +3913,7 @@ void diff_flush(struct diff_options *options) diff_free_filepair(q->queue[i]); free_queue: free(q->queue); - q->queue = NULL; - q->nr = q->alloc = 0; + DIFF_QUEUE_CLEAR(q); if (options->close_file) fclose(options->file); @@ -3587,8 +3935,7 @@ static void diffcore_apply_filter(const char *filter) int i; struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); if (!filter) return; @@ -3656,8 +4003,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) int i; struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -3718,6 +4064,12 @@ void diffcore_fix_diff_index(struct diff_options *options) void diffcore_std(struct diff_options *options) { + /* We never run this function more than one time, because the + * rename/copy detection logic can only run once. + */ + if (diff_queued_diff.run) + return; + if (options->skip_stat_unmatch) diffcore_skip_stat_unmatch(options); if (options->break_opt != -1) @@ -3737,6 +4089,8 @@ void diffcore_std(struct diff_options *options) DIFF_OPT_SET(options, HAS_CHANGES); else DIFF_OPT_CLR(options, HAS_CHANGES); + + diff_queued_diff.run = 1; } int diff_result_code(struct diff_options *opt, int status) @@ -3890,3 +4244,47 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, return strbuf_detach(&buf, outsize); } + +size_t fill_textconv(struct userdiff_driver *driver, + struct diff_filespec *df, + char **outbuf) +{ + size_t size; + + if (!driver || !driver->textconv) { + if (!DIFF_FILE_VALID(df)) { + *outbuf = ""; + return 0; + } + if (diff_populate_filespec(df, 0)) + die("unable to read files to diff"); + *outbuf = df->data; + return df->size; + } + + if (driver->textconv_cache) { + *outbuf = notes_cache_get(driver->textconv_cache, df->sha1, + &size); + if (*outbuf) + return size; + } + + *outbuf = run_textconv(driver->textconv, df, &size); + if (!*outbuf) + die("unable to read files to diff"); + + if (driver->textconv_cache) { + /* ignore errors, as we might be in a readonly repository */ + notes_cache_put(driver->textconv_cache, df->sha1, *outbuf, + size); + /* + * we could save up changes and flush them all at the end, + * but we would need an extra call after all diffing is done. + * Since generating a cache entry is the slow path anyway, + * this extra overhead probably isn't a big deal. + */ + notes_cache_write(driver->textconv_cache); + } + + return size; +} @@ -9,6 +9,9 @@ struct rev_info; struct diff_options; struct diff_queue_struct; +struct strbuf; +struct diff_filespec; +struct userdiff_driver; typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, @@ -25,6 +28,8 @@ typedef void (*add_remove_fn_t)(struct diff_options *options, typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, struct diff_options *options, void *data); +typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data); + #define DIFF_FORMAT_RAW 0x0001 #define DIFF_FORMAT_DIFFSTAT 0x0002 #define DIFF_FORMAT_NUMSTAT 0x0004 @@ -54,7 +59,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_FIND_COPIES_HARDER (1 << 6) #define DIFF_OPT_FOLLOW_RENAMES (1 << 7) #define DIFF_OPT_COLOR_DIFF (1 << 8) -#define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9) +/* (1 << 9) unused */ #define DIFF_OPT_HAS_CHANGES (1 << 10) #define DIFF_OPT_QUICK (1 << 11) #define DIFF_OPT_NO_INDEX (1 << 12) @@ -69,6 +74,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_ALLOW_TEXTCONV (1 << 21) #define DIFF_OPT_DIFF_FROM_CONTENTS (1 << 22) #define DIFF_OPT_SUBMODULE_LOG (1 << 23) +#define DIFF_OPT_DIRTY_SUBMODULES (1 << 24) +#define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) +#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) @@ -77,6 +85,13 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_XDL_SET(opts, flag) ((opts)->xdl_opts |= XDF_##flag) #define DIFF_XDL_CLR(opts, flag) ((opts)->xdl_opts &= ~XDF_##flag) +enum diff_words_type { + DIFF_WORDS_NONE = 0, + DIFF_WORDS_PORCELAIN, + DIFF_WORDS_PLAIN, + DIFF_WORDS_COLOR +}; + struct diff_options { const char *filter; const char *orderfile; @@ -106,6 +121,7 @@ struct diff_options { int stat_width; int stat_name_width; const char *word_regex; + enum diff_words_type word_diff; /* this is set by diffcore for DIFF_FORMAT_PATCH */ int found_changes; @@ -120,6 +136,8 @@ struct diff_options { add_remove_fn_t add_remove; diff_format_fn_t format_callback; void *format_callback_data; + diff_prefix_fn_t output_prefix; + void *output_prefix_data; }; enum color_diff { @@ -131,7 +149,7 @@ enum color_diff { DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, DIFF_WHITESPACE = 7, - DIFF_FUNCINFO = 8, + DIFF_FUNCINFO = 8 }; const char *diff_get_color(int diff_use_color, enum color_diff ix); #define diff_get_color_opt(o, ix) \ @@ -277,4 +295,10 @@ extern void diff_no_index(struct rev_info *, int, const char **, int, const char extern int index_differs_from(const char *def, int diff_flags); +extern size_t fill_textconv(struct userdiff_driver *driver, + struct diff_filespec *df, + char **outbuf); + +extern struct userdiff_driver *get_textconv(struct diff_filespec *one); + #endif /* DIFF_H */ diff --git a/diffcore-break.c b/diffcore-break.c index 3a7b60a037..44f8678d22 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -162,8 +162,7 @@ void diffcore_break(int break_score) if (!merge_score) merge_score = DEFAULT_MERGE_SCORE; - outq.nr = outq.alloc = 0; - outq.queue = NULL; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -256,8 +255,7 @@ void diffcore_merge_broken(void) struct diff_queue_struct outq; int i, j; - outq.nr = outq.alloc = 0; - outq.queue = NULL; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index d0ef839700..929de15aa9 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -55,8 +55,7 @@ void diffcore_pickaxe(const char *needle, int opts) int i, has_changes; regex_t regex, *regexp = NULL; struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); if (opts & DIFF_PICKAXE_REGEX) { int err; diff --git a/diffcore-rename.c b/diffcore-rename.c index d6fd3cacd6..df41be56de 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -569,8 +569,7 @@ void diffcore_rename(struct diff_options *options) /* At this point, we have found some renames and copies and they * are recorded in rename_dst. The original list is still in *q. */ - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; struct diff_filepair *pair_to_free = NULL; diff --git a/diffcore.h b/diffcore.h index 66687c3fe5..05ebc115a1 100644 --- a/diffcore.h +++ b/diffcore.h @@ -18,7 +18,7 @@ #define MAX_SCORE 60000.0 #define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */ #define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%) */ -#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen 60%) */ +#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen (60%) */ #define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */ @@ -42,7 +42,9 @@ struct diff_filespec { #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0) unsigned should_free : 1; /* data should be free()'ed */ unsigned should_munmap : 1; /* data should be munmap()'ed */ - unsigned dirty_submodule : 1; /* For submodules: its work tree is dirty */ + unsigned dirty_submodule : 2; /* For submodules: its work tree is dirty */ +#define DIRTY_SUBMODULE_UNTRACKED 1 +#define DIRTY_SUBMODULE_MODIFIED 2 struct userdiff_driver *driver; /* data should be considered "binary"; -1 means "don't know yet" */ @@ -89,7 +91,14 @@ struct diff_queue_struct { struct diff_filepair **queue; int alloc; int nr; + int run; }; +#define DIFF_QUEUE_CLEAR(q) \ + do { \ + (q)->queue = NULL; \ + (q)->nr = (q)->alloc = 0; \ + (q)->run = 0; \ + } while (0) extern struct diff_queue_struct diff_queued_diff; extern struct diff_filepair *diff_queue(struct diff_queue_struct *, @@ -109,9 +118,9 @@ void diff_debug_filespec(struct diff_filespec *, int, const char *); void diff_debug_filepair(const struct diff_filepair *, int); void diff_debug_queue(const char *, struct diff_queue_struct *); #else -#define diff_debug_filespec(a,b,c) do {} while(0) -#define diff_debug_filepair(a,b) do {} while(0) -#define diff_debug_queue(a,b) do {} while(0) +#define diff_debug_filespec(a,b,c) do { /* nothing */ } while (0) +#define diff_debug_filepair(a,b) do { /* nothing */ } while (0) +#define diff_debug_queue(a,b) do { /* nothing */ } while (0) #endif extern int diffcore_count_changes(struct diff_filespec *src, @@ -31,22 +31,22 @@ static int common_prefix(const char **pathspec) if (!slash) return 0; + /* + * 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 = strlen(next); - if (len >= prefix && !memcmp(path, next, prefix)) + 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; - len = prefix - 1; - for (;;) { - if (!len) - return 0; - if (next[--len] != '/') - continue; - if (memcmp(path, next, len+1)) - continue; - prefix = len + 1; - break; - } + if (last_matching_slash < 0) + return 0; + prefix = last_matching_slash + 1; } return prefix; } @@ -453,7 +453,7 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathna return dir->entries[dir->nr++] = dir_entry_new(pathname, len); } -static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len) +struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len) { if (!cache_name_is_other(pathname, len)) return NULL; @@ -465,7 +465,7 @@ static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pat enum exist_status { index_nonexistent = 0, index_directory, - index_gitdir, + index_gitdir }; /* @@ -533,7 +533,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) enum directory_treatment { show_directory, ignore_directory, - recurse_into_directory, + recurse_into_directory }; static enum directory_treatment treat_directory(struct dir_struct *dir, @@ -684,7 +684,7 @@ static int get_dtype(struct dirent *de, const char *path, int len) enum path_treatment { path_ignored, path_handled, - path_recurse, + path_recurse }; static enum path_treatment treat_one_path(struct dir_struct *dir, @@ -958,9 +958,14 @@ char *get_relative_cwd(char *buffer, int size, const char *dir) } if (*dir) return NULL; - if (*cwd == '/') + switch (*cwd) { + case '\0': + return cwd; + case '/': return cwd + 1; - return cwd; + default: + return NULL; + } } int is_inside_dir(const char *dir) @@ -72,6 +72,7 @@ extern int read_directory(struct dir_struct *, const char *path, int len, const extern int excluded_from_list(const char *pathname, int pathlen, const char *basename, int *dtype, struct exclude_list *el); extern int excluded(struct dir_struct *, const char *, int *); +struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len); extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, char **buf_p, struct exclude_list *which, int check_index); extern void add_excludes_from_file(struct dir_struct *, const char *fname); diff --git a/environment.c b/environment.c index 876c5e5341..83d38d3c23 100644 --- a/environment.c +++ b/environment.c @@ -38,8 +38,9 @@ const char *pager_program; int pager_use_color = 1; const char *editor_program; const char *excludes_file; -int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ +enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; int read_replace_refs = 1; +enum eol eol = EOL_UNSET; enum safe_crlf safe_crlf = SAFE_CRLF_WARN; unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; diff --git a/exec_cmd.c b/exec_cmd.c index 408e4e55e1..bf225706ee 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -28,7 +28,7 @@ const char *system_path(const char *path) !(prefix = strip_path_suffix(argv0_path, BINDIR)) && !(prefix = strip_path_suffix(argv0_path, "git"))) { prefix = PREFIX; - fprintf(stderr, "RUNTIME_PREFIX requested, " + trace_printf("RUNTIME_PREFIX requested, " "but prefix computation failed. " "Using static fallback '%s'.\n", prefix); } @@ -107,7 +107,7 @@ void setup_path(void) if (old_path) strbuf_addstr(&new_path, old_path); else - strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin"); + strbuf_addstr(&new_path, _PATH_DEFPATH); setenv("PATH", new_path.buf, 1); diff --git a/fast-import.c b/fast-import.c index 74f08bd554..ddad289dae 100644 --- a/fast-import.c +++ b/fast-import.c @@ -267,7 +267,7 @@ struct hash_list typedef enum { WHENSPEC_RAW = 1, WHENSPEC_RFC2822, - WHENSPEC_NOW, + WHENSPEC_NOW } whenspec_type; struct recent_command @@ -980,29 +980,6 @@ static void cycle_packfile(void) start_packfile(); } -static size_t encode_header( - enum object_type type, - uintmax_t size, - unsigned char *hdr) -{ - int n = 1; - unsigned char c; - - if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) - die("bad type %d", type); - - c = (type << 4) | (size & 15); - size >>= 4; - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - n++; - } - *hdr = c; - return n; -} - static int store_object( enum object_type type, struct strbuf *dat, @@ -1103,7 +1080,7 @@ static int store_object( delta_count_by_type[type]++; e->depth = last->depth + 1; - hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr); + hdrlen = encode_in_pack_object_header(OBJ_OFS_DELTA, deltalen, hdr); sha1write(pack_file, hdr, hdrlen); pack_size += hdrlen; @@ -1114,7 +1091,7 @@ static int store_object( pack_size += sizeof(hdr) - pos; } else { e->depth = 0; - hdrlen = encode_header(type, dat->len, hdr); + hdrlen = encode_in_pack_object_header(type, dat->len, hdr); sha1write(pack_file, hdr, hdrlen); pack_size += hdrlen; } @@ -1188,7 +1165,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) memset(&s, 0, sizeof(s)); deflateInit(&s, pack_compression_level); - hdrlen = encode_header(OBJ_BLOB, len, out_buf); + hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf); if (out_sz <= hdrlen) die("impossibly large object header"); @@ -1689,7 +1666,7 @@ static void dump_marks_helper(FILE *f, if (m->shift) { for (k = 0; k < 1024; k++) { if (m->data.sets[k]) - dump_marks_helper(f, (base + k) << m->shift, + dump_marks_helper(f, base + (k << m->shift), m->data.sets[k]); } } else { @@ -2730,6 +2707,7 @@ static void option_import_marks(const char *marks, int from_stream) } import_marks_file = make_fast_import_path(marks); + safe_create_leading_directories_const(import_marks_file); import_marks_file_from_stream = from_stream; } @@ -2760,6 +2738,7 @@ static void option_active_branches(const char *branches) static void option_export_marks(const char *marks) { export_marks_file = make_fast_import_path(marks); + safe_create_leading_directories_const(export_marks_file); } static void option_export_pack_edges(const char *edges) @@ -222,12 +222,47 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) return retval; } +static int fsck_ident(char **ident, struct object *obj, fsck_error error_func) +{ + if (**ident == '<' || **ident == '\n') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); + *ident += strcspn(*ident, "<\n"); + if ((*ident)[-1] != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); + if (**ident != '<') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email"); + (*ident)++; + *ident += strcspn(*ident, "<>\n"); + if (**ident != '>') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad email"); + (*ident)++; + if (**ident != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before date"); + (*ident)++; + if (**ident == '0' && (*ident)[1] != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - zero-padded date"); + *ident += strspn(*ident, "0123456789"); + if (**ident != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad date"); + (*ident)++; + if ((**ident != '+' && **ident != '-') || + !isdigit((*ident)[1]) || + !isdigit((*ident)[2]) || + !isdigit((*ident)[3]) || + !isdigit((*ident)[4]) || + ((*ident)[5] != '\n')) + return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad time zone"); + (*ident) += 6; + return 0; +} + static int fsck_commit(struct commit *commit, fsck_error error_func) { char *buffer = commit->buffer; unsigned char tree_sha1[20], sha1[20]; struct commit_graft *graft; int parents = 0; + int err; if (commit->date == ULONG_MAX) return error_func(&commit->object, FSCK_ERROR, "invalid author/committer line"); @@ -266,6 +301,16 @@ static int fsck_commit(struct commit *commit, fsck_error error_func) } if (memcmp(buffer, "author ", 7)) return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'author' line"); + buffer += 7; + err = fsck_ident(&buffer, &commit->object, error_func); + if (err) + return err; + if (memcmp(buffer, "committer ", strlen("committer "))) + return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'committer' line"); + buffer += strlen("committer "); + err = fsck_ident(&buffer, &commit->object, error_func); + if (err) + return err; if (!commit->tree) return error_func(&commit->object, FSCK_ERROR, "could not load commit's tree %s", sha1_to_hex(tree_sha1)); diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 21f1330a5b..27fc79347a 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1111,9 +1111,9 @@ sub help_patch_cmd { print colored $help_color, <<EOF ; y - $verb this hunk$target n - do not $verb this hunk$target -q - quit, do not $verb this hunk nor any of the remaining ones -a - $verb this and all the remaining hunks in the file -d - do not $verb this hunk nor any of the remaining hunks in the file +q - quit; do not $verb this hunk nor any of the remaining ones +a - $verb this hunk and all later hunks in the file +d - do not $verb this hunk nor any of the later hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk @@ -15,6 +15,8 @@ q,quiet be quiet s,signoff add a Signed-off-by line to the commit message u,utf8 recode into utf8 (default) k,keep pass -k flag to git-mailinfo +keep-cr pass --keep-cr flag to git-mailsplit for mbox format +no-keep-cr do not pass --keep-cr flag to git-mailsplit independent of am.keepcr c,scissors strip everything before a scissors line whitespace= pass it through git-apply ignore-space-change pass it through git-apply @@ -50,6 +52,16 @@ else HAS_HEAD= fi +cmdline="git am" +if test '' != "$interactive" +then + cmdline="$cmdline -i" +fi +if test '' != "$threeway" +then + cmdline="$cmdline -3" +fi + sq () { git rev-parse --sq-quote "$@" } @@ -64,15 +76,6 @@ stop_here_user_resolve () { printf '%s\n' "$resolvemsg" stop_here $1 fi - cmdline="git am" - if test '' != "$interactive" - then - cmdline="$cmdline -i" - fi - if test '' != "$threeway" - then - cmdline="$cmdline -3" - fi echo "When you have resolved this problem run \"$cmdline --resolved\"." echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"." echo "To restore the original branch and stop patching run \"$cmdline --abort\"." @@ -217,12 +220,12 @@ check_patch_format () { split_patches () { case "$patch_format" in mbox) - case "$rebasing" in - '') - keep_cr= ;; - ?*) - keep_cr=--keep-cr ;; - esac + if test -n "$rebasing" || test t = "$keepcr" + then + keep_cr=--keep-cr + else + keep_cr= + fi git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" || clean_abort ;; @@ -291,13 +294,18 @@ split_patches () { prec=4 dotest="$GIT_DIR/rebase-apply" -sign= utf8=t keep= skip= interactive= resolved= rebasing= abort= +sign= utf8=t keep= keepcr= skip= interactive= resolved= rebasing= abort= resolvemsg= resume= scissors= no_inbody_headers= git_apply_opt= committer_date_is_author_date= ignore_date= allow_rerere_autoupdate= +if test "$(git config --bool --get am.keepcr)" = true +then + keepcr=t +fi + while test $# != 0 do case "$1" in @@ -348,6 +356,10 @@ do allow_rerere_autoupdate="$1" ;; -q|--quiet) GIT_QUIET=t ;; + --keep-cr) + keepcr=t ;; + --no-keep-cr) + keepcr=f ;; --) shift; break ;; *) @@ -453,6 +465,7 @@ 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" @@ -496,6 +509,12 @@ 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 ;; @@ -573,8 +592,11 @@ do test -s "$dotest/patch" || { echo "Patch is empty. Was it split wrong?" + echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"." + echo "To restore the original branch and stop patching run \"$cmdline --abort\"." stop_here $this } + rm -f "$dotest/original-commit" "$dotest/author-script" if test -f "$dotest/rebasing" && commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \ -e q "$dotest/$msgnum") && @@ -582,6 +604,8 @@ do then git cat-file commit "$commit" | sed -e '1,/^$/d' >"$dotest/msg-clean" + echo "$commit" > "$dotest/original-commit" + get_author_ident_from_commit "$commit" > "$dotest/author-script" else { sed -n '/^Subject/ s/Subject: //p' "$dotest/info" @@ -593,9 +617,14 @@ do ;; esac - GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")" - GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")" - GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")" + if test -f "$dotest/author-script" + then + eval $(cat "$dotest/author-script") + else + GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")" + GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")" + GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")" + fi if test -z "$GIT_AUTHOR_EMAIL" then @@ -670,7 +699,13 @@ do else action=yes fi - FIRSTLINE=$(sed 1q "$dotest/final-commit") + + if test -f "$dotest/final-commit" + then + FIRSTLINE=$(sed 1q "$dotest/final-commit") + else + FIRSTLINE="" + fi if test $action = skip then @@ -706,6 +741,8 @@ do resolved= git diff-index --quiet --cached HEAD -- && { echo "No changes - did you forget to use 'git add'?" + echo "If there is nothing left to stage, chances are that something else" + echo "already introduced the same changes; you might want to skip this patch." stop_here_user_resolve $this } unmerged=$(git ls-files -u) @@ -720,7 +757,7 @@ do ;; esac - if test $apply_status = 1 && test "$threeway" = t + if test $apply_status != 0 && test "$threeway" = t then if (fall_back_3way) then @@ -765,6 +802,10 @@ do git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent || stop_here $this + if test -f "$dotest/original-commit"; then + echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten" + fi + if test -x "$GIT_DIR"/hooks/post-applypatch then "$GIT_DIR"/hooks/post-applypatch @@ -773,5 +814,12 @@ do go_next done +if test -s "$dotest"/rewritten; then + git notes copy --for-rewrite=rebase < "$dotest"/rewritten + if test -x "$GIT_DIR"/hooks/post-rewrite; then + "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten + fi +fi + rm -fr "$dotest" git gc --auto diff --git a/git-compat-util.h b/git-compat-util.h index a3c4537366..02a73eeb66 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -55,7 +55,8 @@ # else # define _XOPEN_SOURCE 500 # endif -#elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && !defined(_M_UNIX) && !defined(sgi) +#elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && \ + !defined(_M_UNIX) && !defined(__sgi) && !defined(__DragonFly__) #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */ #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ #endif @@ -163,6 +164,13 @@ extern char *gitbasename(char *); #define PATH_SEP ':' #endif +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif +#ifndef _PATH_DEFPATH +#define _PATH_DEFPATH "/usr/local/bin:/usr/bin:/bin" +#endif + #ifndef STRIP_EXTENSION #define STRIP_EXTENSION "" #endif @@ -192,6 +200,7 @@ extern char *gitbasename(char *); #include "compat/bswap.h" /* General helper functions */ +extern void vreportf(const char *prefix, const char *err, va_list params); extern NORETURN void usage(const char *err); extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2))); extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2))); @@ -216,7 +225,6 @@ static inline const char *skip_prefix(const char *str, const char *prefix) #define PROT_READ 1 #define PROT_WRITE 2 #define MAP_PRIVATE 1 -#define MAP_FAILED ((void*)-1) #endif #define mmap git_mmap @@ -245,6 +253,10 @@ extern int git_munmap(void *start, size_t length); #endif /* NO_MMAP */ +#ifndef MAP_FAILED +#define MAP_FAILED ((void *)-1) +#endif + #ifdef NO_ST_BLOCKS_IN_STRUCT_STAT #define on_disk_bytes(st) ((st).st_size) #else @@ -331,6 +343,7 @@ extern int git_vsnprintf(char *str, size_t maxsize, #ifdef __GLIBC_PREREQ #if __GLIBC_PREREQ(2, 1) #define HAVE_STRCHRNUL +#define HAVE_MEMPCPY #endif #endif @@ -344,8 +357,19 @@ static inline char *gitstrchrnul(const char *s, int c) } #endif +#ifndef HAVE_MEMPCPY +#define mempcpy gitmempcpy +static inline void *gitmempcpy(void *dest, const void *src, size_t n) +{ + return (char *)memcpy(dest, src, n) + n; +} +#endif + extern void release_pack_memory(size_t, int); +typedef void (*try_to_free_t)(size_t); +extern try_to_free_t set_try_to_free_routine(try_to_free_t); + extern char *xstrdup(const char *str); extern void *xmalloc(size_t size); extern void *xmallocz(size_t size); @@ -469,5 +493,14 @@ void git_qsort(void *base, size_t nmemb, size_t size, * Always returns the return value of unlink(2). */ int unlink_or_warn(const char *path); +/* + * Likewise for rmdir(2). + */ +int rmdir_or_warn(const char *path); +/* + * Calls the correct function out of {unlink,rmdir}_or_warn based on + * the supplied file mode. + */ +int remove_or_warn(unsigned int mode, const char *path); #endif diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 4853bf7a0d..9e03eee458 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -29,7 +29,7 @@ use IPC::Open2; $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; -our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r); +our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R); my (%conv_author_name, %conv_author_email); sub usage(;$) { @@ -40,7 +40,7 @@ Usage: git cvsimport # fetch/update GIT from CVS [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file] [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k] [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit] - [-r remote] [CVS_module] + [-r remote] [-R] [CVS_module] END exit(1); } @@ -110,7 +110,7 @@ sub read_repo_config { } } -my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:"; +my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R"; read_repo_config($opts); Getopt::Long::Configure( 'no_ignore_case', 'bundling' ); @@ -659,6 +659,11 @@ if ($opt_A) { write_author_info("$git_dir/cvs-authors"); } +# open .git/cvs-revisions, if requested +open my $revision_map, '>>', "$git_dir/cvs-revisions" + or die "Can't open $git_dir/cvs-revisions for appending: $!\n" + if defined $opt_R; + # # run cvsps into a file unless we are getting @@ -742,7 +747,7 @@ sub write_tree () { } my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); -my (@old,@new,@skipped,%ignorebranch); +my (@old,@new,@skipped,%ignorebranch,@commit_revisions); # commits that cvsps cannot place anywhere... $ignorebranch{'#CVSPS_NO_BRANCH'} = 1; @@ -825,6 +830,11 @@ sub commit { system('git' , 'update-ref', "$remote/$branch", $cid) == 0 or die "Cannot write branch $branch for update: $!\n"; + if ($revision_map) { + print $revision_map "@$_ $cid\n" for @commit_revisions; + } + @commit_revisions = (); + if ($tag) { my ($xtag) = $tag; $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY ** @@ -959,6 +969,7 @@ while (<CVS>) { push(@skipped, $fn); next; } + push @commit_revisions, [$fn, $rev]; print "Fetching $fn v $rev\n" if $opt_v; my ($tmpname, $size) = $cvs->file($fn,$rev); if ($size == -1) { @@ -981,7 +992,9 @@ while (<CVS>) { unlink($tmpname); } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) { my $fn = $1; + my $rev = $2; $fn =~ s#^/+##; + push @commit_revisions, [$fn, $rev]; push(@old,$fn); print "Delete $fn\n" if $opt_v; } elsif ($state == 9 and /^\s*$/) { diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 13751db882..e9f3037df3 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -183,12 +183,58 @@ if ($state->{method} eq 'pserver') { exit 1; } $line = <STDIN>; chomp $line; - unless ($line eq 'anonymous') { - print "E Only anonymous user allowed via pserver\n"; - print "I HATE YOU\n"; - exit 1; + my $user = $line; + $line = <STDIN>; chomp $line; + my $password = $line; + + if ($user eq 'anonymous') { + # "A" will be 1 byte, use length instead in case the + # encryption method ever changes (yeah, right!) + if (length($password) > 1 ) { + print "E Don't supply a password for the `anonymous' user\n"; + print "I HATE YOU\n"; + exit 1; + } + + # Fall through to LOVE + } else { + # Trying to authenticate a user + if (not exists $cfg->{gitcvs}->{authdb}) { + print "E the repo config file needs a [gitcvs] section with an 'authdb' parameter set to the filename of the authentication database\n"; + print "I HATE YOU\n"; + exit 1; + } + + my $authdb = $cfg->{gitcvs}->{authdb}; + + unless (-e $authdb) { + print "E The authentication database specified in [gitcvs.authdb] does not exist\n"; + print "I HATE YOU\n"; + exit 1; + } + + my $auth_ok; + open my $passwd, "<", $authdb or die $!; + while (<$passwd>) { + if (m{^\Q$user\E:(.*)}) { + if (crypt($user, descramble($password)) eq $1) { + $auth_ok = 1; + } + }; + } + close $passwd; + + unless ($auth_ok) { + print "I HATE YOU\n"; + exit 1; + } + + # Fall through to LOVE } - $line = <STDIN>; chomp $line; # validate the password? + + # For checking whether the user is anonymous on commit + $state->{user} = $user; + $line = <STDIN>; chomp $line; unless ($line eq "END $request REQUEST") { die "E Do not understand $line -- expecting END $request REQUEST\n"; @@ -1271,9 +1317,9 @@ sub req_ci $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" )); - if ( $state->{method} eq 'pserver') + if ( $state->{method} eq 'pserver' and $state->{user} eq 'anonymous' ) { - print "error 1 pserver access cannot commit\n"; + print "error 1 anonymous user cannot commit via pserver\n"; cleanupWorkTree(); exit; } @@ -2369,15 +2415,20 @@ sub kopts_from_path if ( defined ( $cfg->{gitcvs}{usecrlfattr} ) and $cfg->{gitcvs}{usecrlfattr} =~ /\s*(1|true|yes)\s*$/i ) { - my ($val) = check_attr( "crlf", $path ); - if ( $val eq "set" ) + my ($val) = check_attr( "text", $path ); + if ( $val eq "unspecified" ) { - return ""; + $val = check_attr( "crlf", $path ); } - elsif ( $val eq "unset" ) + if ( $val eq "unset" ) { return "-kb" } + elsif ( check_attr( "eol", $path ) ne "unspecified" || + $val eq "set" || $val eq "input" ) + { + return ""; + } else { $log->info("Unrecognized check_attr crlf $path : $val"); @@ -2586,6 +2637,43 @@ sub cvs_author $author; } + +sub descramble +{ + # This table is from src/scramble.c in the CVS source + my @SHIFTS = ( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87, + 111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105, + 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35, + 125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56, + 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, + 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223, + 225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190, + 199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193, + 174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212, + 207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246, + 192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176, + 227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127, + 182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195, + 243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152 + ); + my ($str) = @_; + + # This should never happen, the same password format (A) has been + # used by CVS since the beginning of time + { + my $fmt = substr($str, 0, 1); + die "invalid password format `$fmt'" unless $fmt eq 'A'; + } + + my @str = unpack "C*", substr($str, 1); + my $ret = join '', map { chr $SHIFTS[$_] } @str; + return $ret; +} + + package GITCVS::log; #### diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 7d5451198c..bb104895a9 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -38,7 +38,7 @@ if {[catch {package require Tcl 8.4} err] tk_messageBox \ -icon error \ -type ok \ - -title [mc "git-gui: fatal error"] \ + -title "git-gui: fatal error" \ -message $err exit 1 } @@ -269,6 +269,17 @@ proc is_config_true {name} { } } +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}} { + return 1 + } else { + return 0 + } +} + proc get_config {name} { global repo_config if {[catch {set v $repo_config($name)}]} { @@ -323,6 +334,8 @@ proc _trace_exec {cmd} { puts stderr $d } +#'" fix poor old emacs font-lock mode + proc _git_cmd {name} { global _git_cmd_path @@ -416,6 +429,9 @@ proc _lappend_nice {cmd_var} { if {![info exists _nice]} { set _nice [_which nice] + if {[catch {exec $_nice git version}]} { + set _nice {} + } } if {$_nice ne {}} { lappend cmd $_nice @@ -634,6 +650,7 @@ proc rmsel_tag {text} { return $text } +wm withdraw . set root_exists 0 bind . <Visibility> { bind . <Visibility> {} @@ -782,6 +799,7 @@ set default_config(user.email) {} set default_config(gui.encoding) [encoding system] set default_config(gui.matchtrackingbranch) false +set default_config(gui.textconv) true set default_config(gui.pruneduringfetch) false set default_config(gui.trustmtime) false set default_config(gui.fastcopyblame) false @@ -1155,6 +1173,9 @@ apply_config # try to set work tree from environment, falling back to core.worktree if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} { set _gitworktree [get_config core.worktree] + if {$_gitworktree eq ""} { + set _gitworktree [file dirname [file normalize $_gitdir]] + } } if {$_prefix ne {}} { if {$_gitworktree eq {}} { @@ -2098,7 +2119,7 @@ proc do_explore {} { # freedesktop.org-conforming system is our best shot set explorer "xdg-open" } - eval exec $explorer $_gitworktree & + eval exec $explorer [list [file nativename $_gitworktree]] & } set is_quitting 0 @@ -2901,6 +2922,7 @@ blame { set current_branch $head } + wm deiconify . switch -- $subcommand { browser { if {$jump_spec ne {}} usage @@ -3405,6 +3427,19 @@ lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] $ctxmsm add separator create_common_diff_popup $ctxmsm +proc has_textconv {path} { + if {[is_config_false gui.textconv]} { + return 0 + } + set filter [gitattr $path diff set] + set textconv [get_config [join [list diff $filter textconv] .]] + if {$filter ne {set} && $textconv ne {}} { + return 1 + } else { + return 0 + } +} + proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} { global current_diff_path file_states set ::cursorX $x @@ -3440,7 +3475,8 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} { || {__} eq $state || {_O} eq $state || {_T} eq $state - || {T_} eq $state} { + || {T_} eq $state + || [has_textconv $current_diff_path]} { set s disabled } else { set s normal @@ -3460,29 +3496,44 @@ $main_status show [mc "Initializing..."] # -- Load geometry # -catch { -set gm $repo_config(gui.geometry) -wm geometry . [lindex $gm 0] -if {$use_ttk} { - .vpane sashpos 0 [lindex $gm 1] - .vpane.files sashpos 0 [lindex $gm 2] -} else { - .vpane sash place 0 \ - [lindex $gm 1] \ - [lindex [.vpane sash coord 0] 1] - .vpane.files sash place 0 \ - [lindex [.vpane.files sash coord 0] 0] \ - [lindex $gm 2] +proc on_ttk_pane_mapped {w pane pos} { + bind $w <Map> {} + after 0 [list after idle [list $w sashpos $pane $pos]] +} +proc on_tk_pane_mapped {w pane x y} { + bind $w <Map> {} + after 0 [list after idle [list $w sash place $pane $x $y]] +} +proc on_application_mapped {} { + global repo_config use_ttk + bind . <Map> {} + set gm $repo_config(gui.geometry) + if {$use_ttk} { + bind .vpane <Map> \ + [list on_ttk_pane_mapped %W 0 [lindex $gm 1]] + bind .vpane.files <Map> \ + [list on_ttk_pane_mapped %W 0 [lindex $gm 2]] + } else { + bind .vpane <Map> \ + [list on_tk_pane_mapped %W 0 \ + [lindex $gm 1] \ + [lindex [.vpane sash coord 0] 1]] + bind .vpane.files <Map> \ + [list on_tk_pane_mapped %W 0 \ + [lindex [.vpane.files sash coord 0] 0] \ + [lindex $gm 2]] + } + wm geometry . [lindex $gm 0] } -unset gm +if {[info exists repo_config(gui.geometry)]} { + bind . <Map> [list on_application_mapped] + wm geometry . [lindex $repo_config(gui.geometry) 0] } # -- Load window state # -catch { -set gws $repo_config(gui.wmstate) -wm state . $gws -unset gws +if {[info exists repo_config(gui.wmstate)]} { + catch {wm state . $repo_config(gui.wmstate)} } # -- Key Bindings diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 786b50b8c2..2137ec9684 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -449,11 +449,28 @@ method _load {jump} { $status show [mc "Reading %s..." "$commit:[escape_path $path]"] $w_path conf -text [escape_path $path] + + set do_textconv 0 + if {![is_config_false gui.textconv] && [git-version >= 1.7.2]} { + set filter [gitattr $path diff set] + set textconv [get_config [join [list diff $filter textconv] .]] + if {$filter ne {set} && $textconv ne {}} { + set do_textconv 1 + } + } if {$commit eq {}} { - set fd [open $path r] + if {$do_textconv ne 0} { + set fd [open |[list $textconv $path] r] + } else { + set fd [open $path r] + } fconfigure $fd -eofchar {} } else { - set fd [git_read cat-file blob "$commit:$path"] + if {$do_textconv ne 0} { + set fd [git_read cat-file --textconv "$commit:$path"] + } else { + set fd [git_read cat-file blob "$commit:$path"] + } } fconfigure $fd \ -blocking 0 \ diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index 64f06748b6..fae119286d 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -100,12 +100,17 @@ constructor pick {} { $opts insert end [mc "Clone Existing Repository"] link_clone $opts insert end "\n" if {$m_repo ne {}} { + if {[tk windowingsystem] eq "win32"} { + set key L + } else { + set key C + } $m_repo add command \ -command [cb _next clone] \ - -accelerator $M1T-C \ + -accelerator $M1T-$key \ -label [mc "Clone..."] - bind $top <$M1B-c> [cb _next clone] - bind $top <$M1B-C> [cb _next clone] + bind $top <$M1B-[string tolower $key]> [cb _next clone] + bind $top <$M1B-[string toupper $key]> [cb _next clone] } $opts tag conf link_open -foreground blue -underline 1 diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index ec8c11eeb7..c628750276 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -55,7 +55,7 @@ proc handle_empty_diff {} { set path $current_diff_path set s $file_states($path) - if {[lindex $s 0] ne {_M}} return + if {[lindex $s 0] ne {_M} || [has_textconv $path]} return # Prevent infinite rescan loops incr diff_empty_count @@ -280,6 +280,9 @@ proc start_show_diff {cont_info {add_opts {}}} { lappend cmd diff-files } } + if {![is_config_false gui.textconv] && [git-version >= 1.6.1]} { + lappend cmd --textconv + } if {[string match {160000 *} [lindex $s 2]] || [string match {160000 *} [lindex $s 3]]} { diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index d4c5e45c8a..3807c8d283 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -148,6 +148,7 @@ proc do_options {} { {b gui.trustmtime {mc "Trust File Modification Timestamps"}} {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}} {b gui.matchtrackingbranch {mc "Match Tracking Branches"}} + {b gui.textconv {mc "Use Textconv For Diffs and Blames"}} {b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}} {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}} {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}} diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl index 79c1888e11..78878ef89d 100644 --- a/git-gui/lib/shortcut.tcl +++ b/git-gui/lib/shortcut.tcl @@ -16,7 +16,7 @@ proc do_windows_shortcut {} { [info nameofexecutable] \ [file normalize $::argv0] \ ] \ - [file normalize [$_gitworktree]] + [file normalize $_gitworktree] } err]} { error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } @@ -57,7 +57,7 @@ proc do_cygwin_shortcut {} { $sh -c \ "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \ ] \ - [file normalize [$_gitworktree]] + [file normalize $_gitworktree] } err]} { error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl index 5fe3aad382..95cb44991f 100644 --- a/git-gui/lib/status_bar.tcl +++ b/git-gui/lib/status_bar.tcl @@ -39,6 +39,7 @@ method _oneline_pack {} { } constructor two_line {path} { + global NS set w $path set w_l $w.l set w_c $w.c diff --git a/git-gui/lib/win32.tcl b/git-gui/lib/win32.tcl index d7f93d045d..db91ab84a5 100644 --- a/git-gui/lib/win32.tcl +++ b/git-gui/lib/win32.tcl @@ -18,9 +18,9 @@ proc win32_create_lnk {lnk_path lnk_exec lnk_dir} { eval [list exec wscript.exe \ /E:jscript \ /nologo \ - [file join $oguilib win32_shortcut.js] \ + [file nativename [file join $oguilib win32_shortcut.js]] \ $lnk_path \ - [file join $oguilib git-gui.ico] \ + [file nativename [file join $oguilib git-gui.ico]] \ $lnk_dir \ $lnk_exec] $lnk_args } diff --git a/git-gui/windows/git-gui.sh b/git-gui/windows/git-gui.sh index 66bbb2f8fa..b1845c5055 100644 --- a/git-gui/windows/git-gui.sh +++ b/git-gui/windows/git-gui.sh @@ -13,10 +13,11 @@ if { $argc >=2 && [lindex $argv 0] == "--working-dir" } { incr argc -2 } -set bindir [file dirname \ +set basedir [file dirname \ [file dirname \ [file dirname [info script]]]] -set bindir [file join $bindir bin] +set bindir [file join $basedir bin] +set bindir "$bindir;[file join $basedir mingw bin]" regsub -all ";" $bindir "\\;" bindir set env(PATH) "$bindir;$env(PATH)" unset bindir diff --git a/git-instaweb.sh b/git-instaweb.sh index 6a65f255cc..e6f6ecda17 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -24,6 +24,7 @@ restart restart the web server fqgitdir="$GIT_DIR" local="$(git config --bool --get instaweb.local)" httpd="$(git config --get instaweb.httpd)" +root="$(git config --get instaweb.gitwebdir)" port=$(git config --get instaweb.port) module_path="$(git config --get instaweb.modulepath)" @@ -34,18 +35,35 @@ conf="$GIT_DIR/gitweb/httpd.conf" # if installed, it doesn't need further configuration (module_path) test -z "$httpd" && httpd='lighttpd -f' +# Default is @@GITWEBDIR@@ +test -z "$root" && root='@@GITWEBDIR@@' + # any untaken local port will do... test -z "$port" && port=1234 resolve_full_httpd () { case "$httpd" in - *apache2*|*lighttpd*) + *apache2*|*lighttpd*|*httpd*) + # yes, *httpd* covers *lighttpd* above, but it is there for clarity # ensure that the apache2/lighttpd command ends with "-f" if ! echo "$httpd" | sane_grep -- '-f *$' >/dev/null 2>&1 then httpd="$httpd -f" fi ;; + *plackup*) + # server is started by running via generated gitweb.psgi in $fqgitdir/gitweb + full_httpd="$fqgitdir/gitweb/gitweb.psgi" + httpd_only="${httpd%% *}" # cut on first space + return + ;; + *webrick*) + # server is started by running via generated webrick.rb in + # $fqgitdir/gitweb + full_httpd="$fqgitdir/gitweb/webrick.rb" + httpd_only="${httpd%% *}" # cut on first space + return + ;; esac httpd_only="$(echo $httpd | cut -f1 -d' ')" @@ -57,7 +75,7 @@ resolve_full_httpd () { # these days and those are not in most users $PATHs # in addition, we may have generated a server script # in $fqgitdir/gitweb. - for i in /usr/local/sbin /usr/sbin "$fqgitdir/gitweb" + for i in /usr/local/sbin /usr/sbin "$root" "$fqgitdir/gitweb" do if test -x "$i/$httpd_only" then @@ -83,8 +101,8 @@ start_httpd () { # don't quote $full_httpd, there can be arguments to it (-f) case "$httpd" in - *mongoose*) - #The mongoose server doesn't have a daemon mode so we'll have to fork it + *mongoose*|*plackup*) + #These servers don't have a daemon mode so we'll have to fork it $full_httpd "$fqgitdir/gitweb/httpd.conf" & #Save the pid before doing anything else (we'll print it later) pid=$! @@ -110,6 +128,20 @@ EOF stop_httpd () { test -f "$fqgitdir/pid" && kill $(cat "$fqgitdir/pid") + rm -f "$fqgitdir/pid" +} + +httpd_is_ready () { + "$PERL" -MIO::Socket::INET -e " +local \$| = 1; # turn on autoflush +exit if (IO::Socket::INET->new('127.0.0.1:$port')); +print 'Waiting for \'$httpd\' to start ..'; +do { + print '.'; + sleep(1); +} until (IO::Socket::INET->new('127.0.0.1:$port')); +print qq! (done)\n!; +" } while test $# != 0 @@ -159,60 +191,73 @@ done mkdir -p "$GIT_DIR/gitweb/tmp" GIT_EXEC_PATH="$(git --exec-path)" GIT_DIR="$fqgitdir" -export GIT_EXEC_PATH GIT_DIR - +GITWEB_CONFIG="$fqgitdir/gitweb/gitweb_config.perl" +export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG webrick_conf () { + # webrick seems to have no way of passing arbitrary environment + # variables to the underlying CGI executable, so we wrap the + # actual gitweb.cgi using a shell script to force it + wrapper="$fqgitdir/gitweb/$httpd/wrapper.sh" + cat > "$wrapper" <<EOF +#!/bin/sh +# we use this shell script wrapper around the real gitweb.cgi since +# there appears to be no other way to pass arbitrary environment variables +# into the CGI process +GIT_EXEC_PATH=$GIT_EXEC_PATH GIT_DIR=$GIT_DIR GITWEB_CONFIG=$GITWEB_CONFIG +export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG +exec $root/gitweb.cgi +EOF + chmod +x "$wrapper" + + # This assumes _ruby_ is in the user's $PATH. that's _one_ + # portable way to run ruby, which could be installed anywhere, really. # generate a standalone server script in $fqgitdir/gitweb. cat >"$fqgitdir/gitweb/$httpd.rb" <<EOF +#!/usr/bin/env ruby require 'webrick' -require 'yaml' -options = YAML::load_file(ARGV[0]) -options[:StartCallback] = proc do - File.open(options[:PidFile],"w") do |f| - f.puts Process.pid - end -end -options[:ServerType] = WEBrick::Daemon +require 'logger' +options = { + :Port => $port, + :DocumentRoot => "$root", + :Logger => Logger.new('$fqgitdir/gitweb/error.log'), + :AccessLog => [ + [ Logger.new('$fqgitdir/gitweb/access.log'), + WEBrick::AccessLog::COMBINED_LOG_FORMAT ] + ], + :DirectoryIndex => ["gitweb.cgi"], + :CGIInterpreter => "$wrapper", + :StartCallback => lambda do + File.open("$fqgitdir/pid", "w") { |f| f.puts Process.pid } + end, + :ServerType => WEBrick::Daemon, +} +options[:BindAddress] = '127.0.0.1' if "$local" == "true" server = WEBrick::HTTPServer.new(options) ['INT', 'TERM'].each do |signal| trap(signal) {server.shutdown} end server.start EOF - # generate a shell script to invoke the above ruby script, - # which assumes _ruby_ is in the user's $PATH. that's _one_ - # portable way to run ruby, which could be installed anywhere, - # really. - cat >"$fqgitdir/gitweb/$httpd" <<EOF -#!/bin/sh -exec ruby "$fqgitdir/gitweb/$httpd.rb" \$* -EOF - chmod +x "$fqgitdir/gitweb/$httpd" - - cat >"$conf" <<EOF -:Port: $port -:DocumentRoot: "$fqgitdir/gitweb" -:DirectoryIndex: ["gitweb.cgi"] -:PidFile: "$fqgitdir/pid" -EOF - test "$local" = true && echo ':BindAddress: "127.0.0.1"' >> "$conf" + chmod +x "$fqgitdir/gitweb/$httpd.rb" + # configuration is embedded in server script file, webrick.rb + rm -f "$conf" } lighttpd_conf () { cat > "$conf" <<EOF -server.document-root = "$fqgitdir/gitweb" +server.document-root = "$root" server.port = $port server.modules = ( "mod_setenv", "mod_cgi" ) server.indexfiles = ( "gitweb.cgi" ) server.pid-file = "$fqgitdir/pid" -server.errorlog = "$fqgitdir/gitweb/error.log" +server.errorlog = "$fqgitdir/gitweb/$httpd_only/error.log" # to enable, add "mod_access", "mod_accesslog" to server.modules # variable above and uncomment this -#accesslog.filename = "$fqgitdir/gitweb/access.log" +#accesslog.filename = "$fqgitdir/gitweb/$httpd_only/access.log" -setenv.add-environment = ( "PATH" => "/usr/local/bin:/usr/bin:/bin" ) +setenv.add-environment = ( "PATH" => env.PATH, "GITWEB_CONFIG" => env.GITWEB_CONFIG ) cgi.assign = ( ".cgi" => "" ) @@ -276,21 +321,30 @@ EOF } apache2_conf () { - test -z "$module_path" && module_path=/usr/lib/apache2/modules - mkdir -p "$GIT_DIR/gitweb/logs" + if test -z "$module_path" + then + test -d "/usr/lib/httpd/modules" && + module_path="/usr/lib/httpd/modules" + test -d "/usr/lib/apache2/modules" && + module_path="/usr/lib/apache2/modules" + fi bind= test x"$local" = xtrue && bind='127.0.0.1:' echo 'text/css css' > "$fqgitdir/mime.types" cat > "$conf" <<EOF ServerName "git-instaweb" -ServerRoot "$fqgitdir/gitweb" -DocumentRoot "$fqgitdir/gitweb" +ServerRoot "$root" +DocumentRoot "$root" +ErrorLog "$fqgitdir/gitweb/$httpd_only/error.log" +CustomLog "$fqgitdir/gitweb/$httpd_only/access.log" combined PidFile "$fqgitdir/pid" Listen $bind$port EOF - for mod in mime dir; do - if test -e $module_path/mod_${mod}.so; then + for mod in mime dir env log_config + do + if test -e $module_path/mod_${mod}.so + then echo "LoadModule ${mod}_module " \ "$module_path/mod_${mod}.so" >> "$conf" fi @@ -303,13 +357,14 @@ EOF # check to see if Dennis Stosberg's mod_perl compatibility patch # (<20060621130708.Gcbc6e5c@leonov.stosberg.net>) has been applied if test -f "$module_path/mod_perl.so" && - sane_grep 'MOD_PERL' "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null + sane_grep 'MOD_PERL' "$root/gitweb.cgi" >/dev/null then # favor mod_perl if available cat >> "$conf" <<EOF LoadModule perl_module $module_path/mod_perl.so PerlPassEnv GIT_DIR -PerlPassEnv GIT_EXEC_DIR +PerlPassEnv GIT_EXEC_PATH +PerlPassEnv GITWEB_CONFIG <Location /gitweb.cgi> SetHandler perl-script PerlResponseHandler ModPerl::Registry @@ -338,6 +393,9 @@ EOF echo "ScriptSock logs/gitweb.sock" >> "$conf" fi cat >> "$conf" <<EOF +PassEnv GIT_DIR +PassEnv GIT_EXEC_PATH +PassEnv GITWEB_CONFIG AddHandler cgi-script .cgi <Location /gitweb.cgi> Options +ExecCGI @@ -353,15 +411,15 @@ mongoose_conf() { # For detailed description of every option, visit # http://code.google.com/p/mongoose/wiki/MongooseManual -root $fqgitdir/gitweb +root $root ports $port index_files gitweb.cgi #ssl_cert $fqgitdir/gitweb/ssl_cert.pem -error_log $fqgitdir/gitweb/error.log -access_log $fqgitdir/gitweb/access.log +error_log $fqgitdir/gitweb/$httpd_only/error.log +access_log $fqgitdir/gitweb/$httpd_only/access.log #cgi setup -cgi_env PATH=/usr/local/bin:/usr/bin:/bin,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH +cgi_env PATH=$PATH,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH,GITWEB_CONFIG=$GITWEB_CONFIG cgi_interp $PERL cgi_ext cgi,pl @@ -370,45 +428,171 @@ mime_types .gz=application/x-gzip,.tar.gz=application/x-tgz,.tgz=application/x-t EOF } - -script=' -s#^(my|our) \$projectroot =.*#$1 \$projectroot = "'$(dirname "$fqgitdir")'";#; -s#(my|our) \$gitbin =.*#$1 \$gitbin = "'$GIT_EXEC_PATH'";#; -s#(my|our) \$projects_list =.*#$1 \$projects_list = \$projectroot;#; -s#(my|our) \$git_temp =.*#$1 \$git_temp = "'$fqgitdir/gitweb/tmp'";#;' - -gitweb_cgi () { - cat > "$1.tmp" <<\EOFGITWEB -@@GITWEB_CGI@@ -EOFGITWEB - # Use the configured full path to perl to match the generated - # scripts' 'hashpling' line - "$PERL" -p -e "$script" "$1.tmp" > "$1" - chmod +x "$1" - rm -f "$1.tmp" +plackup_conf () { + # generate a standalone 'plackup' server script in $fqgitdir/gitweb + # with embedded configuration; it does not use "$conf" file + cat > "$fqgitdir/gitweb/gitweb.psgi" <<EOF +#!$PERL + +# gitweb - simple web interface to track changes in git repositories +# PSGI wrapper and server starter (see http://plackperl.org) + +use strict; + +use IO::Handle; +use Plack::MIME; +use Plack::Builder; +use Plack::App::WrapCGI; +use CGI::Emulate::PSGI 0.07; # minimum version required to work with gitweb + +# mimetype mapping (from lighttpd_conf) +Plack::MIME->add_type( + ".pdf" => "application/pdf", + ".sig" => "application/pgp-signature", + ".spl" => "application/futuresplash", + ".class" => "application/octet-stream", + ".ps" => "application/postscript", + ".torrent" => "application/x-bittorrent", + ".dvi" => "application/x-dvi", + ".gz" => "application/x-gzip", + ".pac" => "application/x-ns-proxy-autoconfig", + ".swf" => "application/x-shockwave-flash", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".tar" => "application/x-tar", + ".zip" => "application/zip", + ".mp3" => "audio/mpeg", + ".m3u" => "audio/x-mpegurl", + ".wma" => "audio/x-ms-wma", + ".wax" => "audio/x-ms-wax", + ".ogg" => "application/ogg", + ".wav" => "audio/x-wav", + ".gif" => "image/gif", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".xbm" => "image/x-xbitmap", + ".xpm" => "image/x-xpixmap", + ".xwd" => "image/x-xwindowdump", + ".css" => "text/css", + ".html" => "text/html", + ".htm" => "text/html", + ".js" => "text/javascript", + ".asc" => "text/plain", + ".c" => "text/plain", + ".cpp" => "text/plain", + ".log" => "text/plain", + ".conf" => "text/plain", + ".text" => "text/plain", + ".txt" => "text/plain", + ".dtd" => "text/xml", + ".xml" => "text/xml", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".mov" => "video/quicktime", + ".qt" => "video/quicktime", + ".avi" => "video/x-msvideo", + ".asf" => "video/x-ms-asf", + ".asx" => "video/x-ms-asf", + ".wmv" => "video/x-ms-wmv", + ".bz2" => "application/x-bzip", + ".tbz" => "application/x-bzip-compressed-tar", + ".tar.bz2" => "application/x-bzip-compressed-tar", + "" => "text/plain" +); + +my \$app = builder { + # to be able to override \$SIG{__WARN__} to log build time warnings + use CGI::Carp; # it sets \$SIG{__WARN__} itself + + my \$logdir = "$fqgitdir/gitweb/$httpd_only"; + open my \$access_log_fh, '>>', "\$logdir/access.log" + or die "Couldn't open access log '\$logdir/access.log': \$!"; + open my \$error_log_fh, '>>', "\$logdir/error.log" + or die "Couldn't open error log '\$logdir/error.log': \$!"; + + \$access_log_fh->autoflush(1); + \$error_log_fh->autoflush(1); + + # redirect build time warnings to error.log + \$SIG{'__WARN__'} = sub { + my \$msg = shift; + # timestamp warning like in CGI::Carp::warn + my \$stamp = CGI::Carp::stamp(); + \$msg =~ s/^/\$stamp/gm; + print \$error_log_fh \$msg; + }; + + # write errors to error.log, access to access.log + enable 'AccessLog', + format => "combined", + logger => sub { print \$access_log_fh @_; }; + enable sub { + my \$app = shift; + sub { + my \$env = shift; + \$env->{'psgi.errors'} = \$error_log_fh; + \$app->(\$env); + } + }; + # gitweb currently doesn't work with $SIG{CHLD} set to 'IGNORE', + # because it uses 'close $fd or die...' on piped filehandle $fh + # (which causes the parent process to wait for child to finish). + enable_if { \$SIG{'CHLD'} eq 'IGNORE' } sub { + my \$app = shift; + sub { + my \$env = shift; + local \$SIG{'CHLD'} = 'DEFAULT'; + local \$SIG{'CLD'} = 'DEFAULT'; + \$app->(\$env); + } + }; + # serve static files, i.e. stylesheet, images, script + enable 'Static', + path => sub { m!\.(js|css|png)\$! && s!^/gitweb/!! }, + root => "$root/", + encoding => 'utf-8'; # encoding for 'text/plain' files + # convert CGI application to PSGI app + Plack::App::WrapCGI->new(script => "$root/gitweb.cgi")->to_app; +}; + +# make it runnable as standalone app, +# like it would be run via 'plackup' utility +if (__FILE__ eq \$0) { + require Plack::Runner; + + my \$runner = Plack::Runner->new(); + \$runner->parse_options(qw(--env deployment --port $port), + "$local" ? qw(--host 127.0.0.1) : ()); + \$runner->run(\$app); } +__END__ +EOF -gitweb_css () { - cat > "$1" <<\EOFGITWEB -@@GITWEB_CSS@@ -EOFGITWEB + chmod a+x "$fqgitdir/gitweb/gitweb.psgi" + # configuration is embedded in server script file, gitweb.psgi + rm -f "$conf" } -gitweb_js () { - cat > "$1" <<\EOFGITWEB -@@GITWEB_JS@@ -EOFGITWEB +gitweb_conf() { + cat > "$fqgitdir/gitweb/gitweb_config.perl" <<EOF +#!/usr/bin/perl +our \$projectroot = "$(dirname "$fqgitdir")"; +our \$git_temp = "$fqgitdir/gitweb/tmp"; +our \$projects_list = \$projectroot; +EOF } -gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi" -gitweb_css "$GIT_DIR/gitweb/gitweb.css" -gitweb_js "$GIT_DIR/gitweb/gitweb.js" +gitweb_conf + +resolve_full_httpd +mkdir -p "$fqgitdir/gitweb/$httpd_only" case "$httpd" in *lighttpd*) lighttpd_conf ;; -*apache2*) +*apache2*|*httpd*) apache2_conf ;; webrick) @@ -417,6 +601,9 @@ webrick) *mongoose*) mongoose_conf ;; +*plackup*) + plackup_conf + ;; *) echo "Unknown httpd specified: $httpd" exit 1 @@ -427,7 +614,7 @@ start_httpd url=http://127.0.0.1:$port if test -n "$browser"; then - git web--browse -b "$browser" $url || echo $url + httpd_is_ready && git web--browse -b "$browser" $url || echo $url else - git web--browse -c "instaweb.browser" $url || echo $url + httpd_is_ready && git web--browse -c "instaweb.browser" $url || echo $url fi diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index d067894bf4..b86402afa5 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -107,7 +107,7 @@ case "${1:-.}${2:-.}${3:-.}" in # remove lines that are unique to ours. orig=`git-unpack-file $2` sz0=`wc -c <"$orig"` - diff -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add + @@DIFF@@ -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add sz1=`wc -c <"$orig"` # If we do not have enough common material, it is not diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 5f47b18141..5f47b18141 100755..100644 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh diff --git a/git-pull.sh b/git-pull.sh index 246a3a4b37..a09a44ec4c 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -38,11 +38,12 @@ test -z "$(git ls-files -u)" || die_conflict test -f "$GIT_DIR/MERGE_HEAD" && die_merge strategy_args= diffstat= no_commit= squash= no_ff= ff_only= -log_arg= verbosity= +log_arg= verbosity= progress= 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) +dry_run= while : do case "$1" in @@ -50,6 +51,8 @@ do verbosity="$verbosity -q" ;; -v|--verbose) verbosity="$verbosity -v" ;; + --progress) + progress=--progress ;; -n|--no-stat|--no-summary) diffstat=--no-stat ;; --stat|--summary) @@ -102,6 +105,9 @@ do --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase) rebase=false ;; + --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run) + dry_run=--dry-run + ;; -h|--h|--he|--hel|--help) usage ;; @@ -214,7 +220,8 @@ test true = "$rebase" && { done } orig_head=$(git rev-parse -q --verify HEAD) -git fetch $verbosity --update-head-ok "$@" || exit 1 +git fetch $verbosity $progress $dry_run --update-head-ok "$@" || exit 1 +test -z "$dry_run" || exit 0 curr_head=$(git rev-parse -q --verify HEAD) if test -n "$orig_head" && test "$curr_head" != "$orig_head" diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 3e4fd1456f..b94c2a0386 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -20,6 +20,7 @@ v,verbose display a diffstat of what changed upstream onto= rebase onto given branch instead of upstream p,preserve-merges try to recreate merges instead of ignoring them s,strategy= use the given merge strategy +no-ff cherry-pick all commits, even if unchanged m,merge always used (no-op) i,interactive always used (no-op) Actions: @@ -96,6 +97,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script # command is processed, this file is deleted. AMEND="$DOTEST"/amend +# For the post-rewrite hook, we make a list of rewritten commits and +# their new sha1s. The rewritten-pending list keeps the sha1s of +# commits that have been processed, but not committed yet, +# e.g. because they are waiting for a 'squash' command. +REWRITTEN_LIST="$DOTEST"/rewritten-list +REWRITTEN_PENDING="$DOTEST"/rewritten-pending + PRESERVE_MERGES= STRATEGY= ONTO= @@ -103,6 +111,7 @@ VERBOSE= OK_TO_SKIP_PRE_REBASE= REBASE_ROOT= AUTOSQUASH= +NEVER_FF= GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add <paths>', and @@ -110,7 +119,7 @@ run 'git rebase --continue'" export GIT_CHERRY_PICK_HELP warn () { - echo "$*" >&2 + printf '%s\n' "$*" >&2 } output () { @@ -198,6 +207,7 @@ make_patch () { } die_with_patch () { + echo "$1" > "$DOTEST"/stopped-sha make_patch "$1" git rerere die "$2" @@ -222,8 +232,9 @@ do_with_author () { } pick_one () { - no_ff= - case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac + ff=--ff + case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac + case "$NEVER_FF" in '') ;; ?*) ff= ;; esac output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return @@ -232,16 +243,7 @@ pick_one () { output git cherry-pick "$@" return fi - parent_sha1=$(git rev-parse --verify $sha1^) || - die "Could not get the parent of $sha1" - current_sha1=$(git rev-parse --verify HEAD) - if test -z "$no_ff" && test "$current_sha1" = "$parent_sha1" - then - output git reset --hard $sha1 - output warn Fast-forward to $(git rev-parse --short $sha1) - else - output git cherry-pick "$@" - fi + output git cherry-pick $ff "$@" } pick_one_preserving_merges () { @@ -261,10 +263,10 @@ pick_one_preserving_merges () { then if test "$fast_forward" = t then - cat "$DOTEST"/current-commit | while read current_commit + while read current_commit do git rev-parse HEAD > "$REWRITTEN"/$current_commit - done + done <"$DOTEST"/current-commit rm "$DOTEST"/current-commit || die "Cannot write current commit's replacement sha1" fi @@ -348,6 +350,7 @@ pick_one_preserving_merges () { printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "Error redoing merge $sha1" fi + echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST" ;; *) output git cherry-pick "$@" || @@ -425,9 +428,29 @@ die_failed_squash() { die_with_patch $1 "" } +flush_rewritten_pending() { + test -s "$REWRITTEN_PENDING" || return + newsha1="$(git rev-parse HEAD^0)" + sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST" + rm -f "$REWRITTEN_PENDING" +} + +record_in_rewritten() { + oldsha1="$(git rev-parse $1)" + echo "$oldsha1" >> "$REWRITTEN_PENDING" + + case "$(peek_next_command)" in + squash|s|fixup|f) + ;; + *) + flush_rewritten_pending + ;; + esac +} + do_next () { rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit - read command sha1 rest < "$TODO" + read -r command sha1 rest < "$TODO" case "$command" in '#'*|''|noop) mark_action_done @@ -438,6 +461,7 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword @@ -445,7 +469,8 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" - git commit --amend + git commit --amend --no-post-rewrite + record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit @@ -453,6 +478,7 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + echo "$sha1" > "$DOTEST"/stopped-sha make_patch $sha1 git rev-parse --verify HEAD > "$AMEND" warn "Stopped at $sha1... $rest" @@ -509,6 +535,7 @@ do_next () { rm -f "$SQUASH_MSG" "$FIXUP_MSG" ;; esac + record_in_rewritten $sha1 ;; *) warn "Unknown command: $command $sha1 $rest" @@ -537,6 +564,16 @@ do_next () { test ! -f "$DOTEST"/verbose || git diff-tree --stat $(cat "$DOTEST"/head)..HEAD } && + { + test -s "$REWRITTEN_LIST" && + git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" || + true # we don't care if this copying failed + } && + if test -x "$GIT_DIR"/hooks/post-rewrite && + test -s "$REWRITTEN_LIST"; then + "$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST" + true # we don't care if this hook failed + fi && rm -rf "$DOTEST" && git gc --auto && warn "Successfully rebased and updated $HEADNAME." @@ -554,7 +591,7 @@ do_rest () { # skip picking commits whose parents are unchanged skip_unnecessary_picks () { fd=3 - while read command sha1 rest + while read -r command sha1 rest do # fd=3 means we skip the command case "$fd,$command,$(git rev-parse --verify --quiet $sha1^)" in @@ -569,9 +606,14 @@ skip_unnecessary_picks () { fd=1 ;; esac - echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd + printf '%s\n' "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd done <"$TODO" >"$TODO.new" 3>>"$DONE" && - mv -f "$TODO".new "$TODO" || + mv -f "$TODO".new "$TODO" && + case "$(peek_next_command)" in + squash|s|fixup|f) + record_in_rewritten "$ONTO" + ;; + esac || die "Could not skip unnecessary pick commands" } @@ -602,17 +644,17 @@ rearrange_squash () { test -s "$1.sq" || return used= - while read pick sha1 message + while read -r pick sha1 message do case " $used" in *" $sha1 "*) continue ;; esac - echo "$pick $sha1 $message" - while read squash action msg + printf '%s\n' "$pick $sha1 $message" + while read -r squash action msg do case "$message" in "$msg"*) - echo "$action $squash $action! $msg" + printf '%s\n' "$action $squash $action! $msg" used="$used$squash " ;; esac @@ -687,6 +729,8 @@ first and then run 'git rebase --continue' again." } fi + record_in_rewritten "$(cat "$DOTEST"/stopped-sha)" + require_clean_work_tree do_rest ;; @@ -742,6 +786,9 @@ first and then run 'git rebase --continue' again." -i) # yeah, we know ;; + --no-ff) + NEVER_FF=t + ;; --root) REBASE_ROOT=t ;; @@ -783,8 +830,6 @@ first and then run 'git rebase --continue' again." if test ! -z "$1" then - output git show-ref --verify --quiet "refs/heads/$1" || - die "Invalid branchname: $1" output git checkout "$1" || die "Could not checkout $1" fi @@ -845,11 +890,12 @@ first and then run 'git rebase --continue' again." git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ --abbrev=7 --reverse --left-right --topo-order \ $REVISIONS | \ - sed -n "s/^>//p" | while read shortsha1 rest + sed -n "s/^>//p" | + while read -r shortsha1 rest do if test t != "$PRESERVE_MERGES" then - echo "pick $shortsha1 $rest" >> "$TODO" + printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" else sha1=$(git rev-parse $shortsha1) if test -z "$REBASE_ROOT" @@ -868,7 +914,7 @@ first and then run 'git rebase --continue' again." if test f = "$preserve" then touch "$REWRITTEN"/$sha1 - echo "pick $shortsha1 $rest" >> "$TODO" + printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" fi fi done @@ -927,10 +973,11 @@ EOF has_action "$TODO" || die_abort "Nothing to do" - test -d "$REWRITTEN" || skip_unnecessary_picks + test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks + output git checkout $ONTO || die_abort "could not detach HEAD" git update-ref ORIG_HEAD $HEAD - output git checkout $ONTO && do_rest + do_rest ;; esac shift diff --git a/git-rebase.sh b/git-rebase.sh index fb4fef7b1d..1d38afdb10 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]' +USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] (<upstream>|--root) [<branch>] [--quiet | -q]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> @@ -79,6 +79,7 @@ continue_merge () { then printf "Committed: %0${prec}d " $msgnum fi + echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten" else if test -z "$GIT_QUIET" then @@ -151,6 +152,11 @@ move_to_original_branch () { finish_rb_merge () { move_to_original_branch + git notes copy --for-rewrite=rebase < "$dotest"/rewritten + if test -x "$GIT_DIR"/hooks/post-rewrite && + test -s "$dotest"/rewritten; then + "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten + fi rm -r "$dotest" say All done. } @@ -192,14 +198,6 @@ test -f "$GIT_DIR"/rebase-apply/applying && is_interactive "$@" && exec git-rebase--interactive "$@" -if test $# -eq 0 -then - test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage - test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing && - die 'A rebase is in progress, try --continue, --skip or --abort.' - die "No arguments given and $GIT_DIR/rebase-apply already exists." -fi - while test $# != 0 do case "$1" in @@ -210,6 +208,7 @@ do test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || die "No rebase in progress?" + git update-index --ignore-submodules --refresh && git diff-files --quiet --ignore-submodules || { echo "You must edit all merge conflicts and then" echo "mark them as resolved using git add" @@ -347,7 +346,7 @@ do --root) rebase_root=t ;; - -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase) + -f|--f|--fo|--for|--forc|--force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff) force_rebase=t ;; --rerere-autoupdate|--no-rerere-autoupdate) @@ -364,6 +363,13 @@ do done test $# -gt 2 && usage +if test $# -eq 0 && test -z "$rebase_root" +then + test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage + test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing && + die 'A rebase is in progress, try --continue, --skip or --abort.' +fi + # Make sure we do not have $GIT_DIR/rebase-apply if test -z "$do_merge" then diff --git a/git-remote-testgit.py b/git-remote-testgit.py new file mode 100644 index 0000000000..df9d512f1a --- /dev/null +++ b/git-remote-testgit.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python + +# hashlib is only available in python >= 2.5 +try: + import hashlib + _digest = hashlib.sha1 +except ImportError: + import sha + _digest = sha.new +import sys +import os +sys.path.insert(0, os.getenv("GITPYTHONLIB",".")) + +from git_remote_helpers.util import die, debug, warn +from git_remote_helpers.git.repo import GitRepo +from git_remote_helpers.git.exporter import GitExporter +from git_remote_helpers.git.importer import GitImporter +from git_remote_helpers.git.non_local import NonLocalGit + +def get_repo(alias, url): + """Returns a git repository object initialized for usage. + """ + + repo = GitRepo(url) + repo.get_revs() + repo.get_head() + + hasher = _digest() + hasher.update(repo.path) + repo.hash = hasher.hexdigest() + + repo.get_base_path = lambda base: os.path.join( + base, 'info', 'fast-import', repo.hash) + + prefix = 'refs/testgit/%s/' % alias + debug("prefix: '%s'", prefix) + + repo.gitdir = "" + repo.alias = alias + repo.prefix = prefix + + repo.exporter = GitExporter(repo) + repo.importer = GitImporter(repo) + repo.non_local = NonLocalGit(repo) + + return repo + + +def local_repo(repo, path): + """Returns a git repository object initalized for usage. + """ + + local = GitRepo(path) + + local.non_local = None + local.gitdir = repo.gitdir + local.alias = repo.alias + local.prefix = repo.prefix + local.hash = repo.hash + local.get_base_path = repo.get_base_path + local.exporter = GitExporter(local) + local.importer = GitImporter(local) + + return local + + +def do_capabilities(repo, args): + """Prints the supported capabilities. + """ + + print "import" + print "export" + print "gitdir" + print "refspec refs/heads/*:%s*" % repo.prefix + + print # end capabilities + + +def do_list(repo, args): + """Lists all known references. + + Bug: This will always set the remote head to master for non-local + repositories, since we have no way of determining what the remote + head is at clone time. + """ + + for ref in repo.revs: + debug("? refs/heads/%s", ref) + print "? refs/heads/%s" % ref + + if repo.head: + debug("@refs/heads/%s HEAD" % repo.head) + print "@refs/heads/%s HEAD" % repo.head + else: + debug("@refs/heads/master HEAD") + print "@refs/heads/master HEAD" + + print # end list + + +def update_local_repo(repo): + """Updates (or clones) a local repo. + """ + + if repo.local: + return repo + + path = repo.non_local.clone(repo.gitdir) + repo.non_local.update(repo.gitdir) + repo = local_repo(repo, path) + return repo + + +def do_import(repo, args): + """Exports a fast-import stream from testgit for git to import. + """ + + if len(args) != 1: + die("Import needs exactly one ref") + + if not repo.gitdir: + die("Need gitdir to import") + + repo = update_local_repo(repo) + repo.exporter.export_repo(repo.gitdir) + + +def do_export(repo, args): + """Imports a fast-import stream from git to testgit. + """ + + if not repo.gitdir: + die("Need gitdir to export") + + dirname = repo.get_base_path(repo.gitdir) + + if not os.path.exists(dirname): + os.makedirs(dirname) + + path = os.path.join(dirname, 'testgit.marks') + print path + if os.path.exists(path): + print path + else: + print "" + sys.stdout.flush() + + update_local_repo(repo) + repo.importer.do_import(repo.gitdir) + repo.non_local.push(repo.gitdir) + + +def do_gitdir(repo, args): + """Stores the location of the gitdir. + """ + + if not args: + die("gitdir needs an argument") + + repo.gitdir = ' '.join(args) + + +COMMANDS = { + 'capabilities': do_capabilities, + 'list': do_list, + 'import': do_import, + 'export': do_export, + 'gitdir': do_gitdir, +} + + +def sanitize(value): + """Cleans up the url. + """ + + if value.startswith('testgit::'): + value = value[9:] + + return value + + +def read_one_line(repo): + """Reads and processes one command. + """ + + line = sys.stdin.readline() + + cmdline = line + + if not cmdline: + warn("Unexpected EOF") + return False + + cmdline = cmdline.strip().split() + if not cmdline: + # Blank line means we're about to quit + return False + + cmd = cmdline.pop(0) + debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline)) + + if cmd not in COMMANDS: + die("Unknown command, %s", cmd) + + func = COMMANDS[cmd] + func(repo, cmdline) + sys.stdout.flush() + + return True + + +def main(args): + """Starts a new remote helper for the specified repository. + """ + + if len(args) != 3: + die("Expecting exactly three arguments.") + sys.exit(1) + + if os.getenv("GIT_DEBUG_TESTGIT"): + import git_remote_helpers.util + git_remote_helpers.util.DEBUG = True + + alias = sanitize(args[1]) + url = sanitize(args[2]) + + if not alias.isalnum(): + warn("non-alnum alias '%s'", alias) + alias = "tmp" + + args[1] = alias + args[2] = url + + repo = get_repo(alias, url) + + debug("Got arguments %s", args[1:]) + + more = True + + while (more): + more = read_one_line(repo) + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/git-request-pull.sh b/git-request-pull.sh index 630ceddf03..6fdea397dd 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/bin/sh # Copyright 2005, Ryan Anderson <ryan@michonline.com> # # This file is licensed under the GPL v2, or a later version @@ -8,6 +8,7 @@ USAGE='<start> <url> [<end>]' LONG_USAGE='Summarizes the changes between two commits to the standard output, and includes the given URL in the generated summary.' SUBDIRECTORY_OK='Yes' +OPTIONS_KEEPDASHDASH= OPTIONS_SPEC='git request-pull [options] start url [end] -- p show patch text as well @@ -65,14 +66,14 @@ if [ -z "$branch" ]; then status=1 fi -echo "The following changes since commit $baserev:" -git shortlog --max-count=1 $baserev | sed -e 's/^\(.\)/ \1/' +git show -s --format='The following changes since commit %H: -echo "are available in the git repository at:" -echo -echo " $url $branch" -echo + %s (%ci) -git shortlog ^$baserev $headrev -git diff -M --stat --summary $patch $merge_base..$headrev +are available in the git repository at:' $baserev && +echo " $url $branch" && +echo && + +git shortlog ^$baserev $headrev && +git diff -M --stat --summary $patch $merge_base..$headrev || exit exit $status diff --git a/git-send-email.perl b/git-send-email.perl index 1b99f40390..6dab3bf6a7 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -47,13 +47,14 @@ git send-email [options] <file | directory | rev-list options > Composing: --from <str> * Email From: - --to <str> * Email To: - --cc <str> * Email Cc: - --bcc <str> * Email Bcc: + --[no-]to <str> * Email To: + --[no-]cc <str> * Email Cc: + --[no-]bcc <str> * Email Bcc: --subject <str> * Email "Subject:" --in-reply-to <str> * Email "In-Reply-To:" --annotate * Review each patch that will be sent in an editor. --compose * Open an editor for introduction. + --8bit-encoding <str> * Encoding to assume 8bit mails if undeclared Sending: --envelope-sender <str> * Email envelope sender. @@ -64,6 +65,8 @@ git send-email [options] <file | directory | rev-list options > --smtp-pass <str> * Password for SMTP-AUTH; not necessary. --smtp-encryption <str> * tls or ssl; anything else disables. --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'. + --smtp-domain <str> * The domain name sent to HELO/EHLO handshake + --smtp-debug <0|1> * Disable, enable Net::SMTP debug. Automating: --identity <str> * Use the sendemail.<id> options. @@ -135,7 +138,7 @@ sub unique_email_list(@); sub cleanup_compose_files(); # Variables we fill in automatically, or via prompting: -my (@to,@cc,@initial_cc,@bcclist,@xh, +my (@to,$no_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh, $initial_reply_to,$initial_subject,@files, $author,$sender,$smtp_authpass,$annotate,$compose,$time); @@ -186,9 +189,12 @@ sub do_edit { # Variables with corresponding config settings my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd); my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption); -my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts); +my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain); my ($validate, $confirm); my (@suppress_cc); +my ($auto_8bit_encoding); + +my ($debug_net_smtp) = 0; # Net::SMTP, see send_message() my $not_set_by_user = "true but not set by the user"; @@ -206,6 +212,7 @@ my %config_settings = ( "smtpserverport" => \$smtp_server_port, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, + "smtpdomain" => \$smtp_domain, "to" => \@to, "cc" => \@initial_cc, "cccmd" => \$cc_cmd, @@ -217,6 +224,7 @@ my %config_settings = ( "multiedit" => \$multiedit, "confirm" => \$confirm, "from" => \$sender, + "assume8bitencoding" => \$auto_8bit_encoding, ); # Help users prepare for 1.7.0 @@ -264,8 +272,11 @@ my $rc = GetOptions("sender|from=s" => \$sender, "in-reply-to=s" => \$initial_reply_to, "subject=s" => \$initial_subject, "to=s" => \@to, + "no-to" => \$no_to, "cc=s" => \@initial_cc, + "no-cc" => \$no_cc, "bcc=s" => \@bcclist, + "no-bcc" => \$no_bcc, "chain-reply-to!" => \$chain_reply_to, "smtp-server=s" => \$smtp_server, "smtp-server-port=s" => \$smtp_server_port, @@ -273,6 +284,8 @@ my $rc = GetOptions("sender|from=s" => \$sender, "smtp-pass:s" => \$smtp_authpass, "smtp-ssl" => sub { $smtp_encryption = 'ssl' }, "smtp-encryption=s" => \$smtp_encryption, + "smtp-debug:i" => \$debug_net_smtp, + "smtp-domain:s" => \$smtp_domain, "identity=s" => \$identity, "annotate" => \$annotate, "compose" => \$compose, @@ -287,6 +300,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "thread!" => \$thread, "validate!" => \$validate, "format-patch!" => \$format_patch, + "8bit-encoding=s" => \$auto_8bit_encoding, ); unless ($rc) { @@ -308,6 +322,9 @@ sub read_config { foreach my $setting (keys %config_settings) { my $target = $config_settings{$setting}; + next if $setting eq "to" and defined $no_to; + next if $setting eq "cc" and defined $no_cc; + next if $setting eq "bcc" and defined $no_bcc; if (ref($target) eq "ARRAY") { unless (@$target) { my @values = Git::config(@repo, "$prefix.$setting"); @@ -656,6 +673,35 @@ sub ask { return undef; } +my %broken_encoding; + +sub file_declares_8bit_cte($) { + my $fn = shift; + open (my $fh, '<', $fn); + while (my $line = <$fh>) { + last if ($line =~ /^$/); + return 1 if ($line =~ /^Content-Transfer-Encoding: .*8bit.*$/); + } + close $fh; + return 0; +} + +foreach my $f (@files) { + next unless (body_or_subject_has_nonascii($f) + && !file_declares_8bit_cte($f)); + $broken_encoding{$f} = 1; +} + +if (!defined $auto_8bit_encoding && scalar %broken_encoding) { + print "The following files are 8bit, but do not declare " . + "a Content-Transfer-Encoding.\n"; + foreach my $f (sort keys %broken_encoding) { + print " $f\n"; + } + $auto_8bit_encoding = ask("Which 8bit encoding should I declare [UTF-8]? ", + default => "UTF-8"); +} + my $prompting = 0; if (!defined $sender) { $sender = $repoauthor || $repocommitter || ''; @@ -747,8 +793,7 @@ sub extract_valid_address { # We'll setup a template for the message id, using the "from" address: my ($message_id_stamp, $message_id_serial); -sub make_message_id -{ +sub make_message_id { my $uniq; if (!defined $message_id_stamp) { $message_id_stamp = sprintf("%s-%s", time, $$); @@ -803,8 +848,7 @@ sub is_rfc2047_quoted { } # use the simplest quoting being able to handle the recipient -sub sanitize_address -{ +sub sanitize_address { my ($recipient) = @_; my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/); @@ -833,12 +877,67 @@ sub sanitize_address } +# Returns the local Fully Qualified Domain Name (FQDN) if available. +# +# Tightly configured MTAa require that a caller sends a real DNS +# domain name that corresponds the IP address in the HELO/EHLO +# handshake. This is used to verify the connection and prevent +# spammers from trying to hide their identity. If the DNS and IP don't +# match, the receiveing MTA may deny the connection. +# +# Here is a deny example of Net::SMTP with the default "localhost.localdomain" +# +# Net::SMTP=GLOB(0x267ec28)>>> EHLO localhost.localdomain +# Net::SMTP=GLOB(0x267ec28)<<< 550 EHLO argument does not match calling host +# +# This maildomain*() code is based on ideas in Perl library Test::Reporter +# /usr/share/perl5/Test/Reporter/Mail/Util.pm ==> sub _maildomain () + +sub valid_fqdn { + my $domain = shift; + return !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./; +} + +sub maildomain_net { + my $maildomain; + + if (eval { require Net::Domain; 1 }) { + my $domain = Net::Domain::domainname(); + $maildomain = $domain if valid_fqdn($domain); + } + + return $maildomain; +} + +sub maildomain_mta { + my $maildomain; + + if (eval { require Net::SMTP; 1 }) { + for my $host (qw(mailhost localhost)) { + my $smtp = Net::SMTP->new($host); + if (defined $smtp) { + my $domain = $smtp->domain; + $smtp->quit; + + $maildomain = $domain if valid_fqdn($domain); + + last if $maildomain; + } + } + } + + return $maildomain; +} + +sub maildomain { + return maildomain_net() || maildomain_mta() || 'localhost.localdomain'; +} + # Returns 1 if the message was sent, and 0 otherwise. # In actuality, the whole program dies when there # is an error sending a message. -sub send_message -{ +sub send_message { my @recipients = unique_email_list(@to); @cc = (grep { my $cc = extract_valid_address($_); not grep { $cc eq $_ } @recipients @@ -935,13 +1034,19 @@ X-Mailer: git-send-email $gitversion if ($smtp_encryption eq 'ssl') { $smtp_server_port ||= 465; # ssmtp require Net::SMTP::SSL; - $smtp ||= Net::SMTP::SSL->new($smtp_server, Port => $smtp_server_port); + $smtp_domain ||= maildomain(); + $smtp ||= Net::SMTP::SSL->new($smtp_server, + Hello => $smtp_domain, + Port => $smtp_server_port); } else { require Net::SMTP; + $smtp_domain ||= maildomain(); $smtp ||= Net::SMTP->new((defined $smtp_server_port) ? "$smtp_server:$smtp_server_port" - : $smtp_server); + : $smtp_server, + Hello => $smtp_domain, + Debug => $debug_net_smtp); if ($smtp_encryption eq 'tls' && $smtp) { require Net::SMTP::SSL; $smtp->command('STARTTLS'); @@ -960,7 +1065,11 @@ X-Mailer: git-send-email $gitversion } if (!$smtp) { - die "Unable to initialize SMTP properly. Is there something wrong with your config?"; + die "Unable to initialize SMTP properly. Check config and use --smtp-debug. ", + "VALUES: server=$smtp_server ", + "encryption=$smtp_encryption ", + "hello=$smtp_domain", + defined $smtp_server_port ? "port=$smtp_server_port" : ""; } if (defined $smtp_authuser) { @@ -1145,6 +1254,18 @@ foreach my $t (@files) { or die "(cc-cmd) failed to close pipe to '$cc_cmd'"; } + if ($broken_encoding{$t} && !$has_content_type) { + $has_content_type = 1; + push @xh, "MIME-Version: 1.0", + "Content-Type: text/plain; charset=$auto_8bit_encoding", + "Content-Transfer-Encoding: 8bit"; + $body_encoding = $auto_8bit_encoding; + } + + if ($broken_encoding{$t} && !is_rfc2047_quoted($subject)) { + $subject = quote_rfc2047($subject, $auto_8bit_encoding); + } + if (defined $author and $author ne $sender) { $message = "From: $author\n\n$message"; if (defined $author_encoding) { @@ -1157,6 +1278,7 @@ foreach my $t (@files) { } } else { + $has_content_type = 1; push @xh, 'MIME-Version: 1.0', "Content-Type: text/plain; charset=$author_encoding", @@ -1234,3 +1356,17 @@ sub file_has_nonascii { } return 0; } + +sub body_or_subject_has_nonascii { + my $fn = shift; + open(my $fh, '<', $fn) + or die "unable to open $fn: $!\n"; + while (my $line = <$fh>) { + last if $line =~ /^$/; + return 1 if $line =~ /^Subject.*[^[:ascii:]]/; + } + while (my $line = <$fh>) { + return 1 if $line =~ /[^[:ascii:]]/; + } + return 0; +} diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 6131670860..6131670860 100755..100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh diff --git a/git-stash.sh b/git-stash.sh index 59db3dc38e..1d95447d03 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -57,7 +57,7 @@ create_stash () { # state of the base commit if b_commit=$(git rev-parse --verify HEAD) then - head=$(git log --no-color --abbrev-commit --pretty=oneline -n 1 HEAD --) + head=$(git rev-list --oneline -n 1 HEAD --) else die "You do not have the initial commit yet" fi @@ -86,7 +86,7 @@ create_stash () { GIT_INDEX_FILE="$TMP-index" && export GIT_INDEX_FILE && git read-tree -m $i_tree && - git add -u && + git diff --name-only -z HEAD | git update-index -z --add --remove --stdin && git write-tree && rm -f "$TMP-index" ) ) || diff --git a/git-submodule.sh b/git-submodule.sh index d3c583d011..170186f494 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,7 +5,7 @@ # Copyright (c) 2007 Lars Hjemli dashless=$(basename "$0" | sed -e 's/-/ /') -USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> [<path>] +USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <repository> [<path>] or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...] or: $dashless [--quiet] init [--] [<path>...] or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] @@ -19,6 +19,7 @@ require_work_tree command= branch= +force= reference= cached= recursive= @@ -133,6 +134,9 @@ cmd_add() branch=$2 shift ;; + -f | --force) + force=$1 + ;; -q|--quiet) GIT_QUIET=1 ;; @@ -201,6 +205,14 @@ cmd_add() git ls-files --error-unmatch "$path" > /dev/null 2>&1 && die "'$path' already exists in the index" + if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1 + then + echo >&2 "The following path is ignored by one of your .gitignore files:" && + echo >&2 $path && + echo >&2 "Use -f if you really want to add it." + exit 1 + fi + # perhaps the path exists and is already a git repo, else clone it if test -e "$path" then @@ -234,12 +246,12 @@ cmd_add() ) || die "Unable to checkout submodule '$path'" fi - git add "$path" || + git add $force "$path" || die "Failed to add submodule '$path'" git config -f .gitmodules submodule."$path".path "$path" && git config -f .gitmodules submodule."$path".url "$repo" && - git add .gitmodules || + git add --force .gitmodules || die "Failed to register submodule '$path'" } @@ -271,6 +283,8 @@ cmd_foreach() shift done + toplevel=$(pwd) + module_list | while read mode sha1 stage path do @@ -555,12 +569,17 @@ cmd_summary() { test $summary_limit = 0 && return - if rev=$(git rev-parse -q --verify "$1^0") + if rev=$(git rev-parse -q --verify --default HEAD ${1+"$1"}) then head=$rev - shift + test $# = 0 || shift + elif test -z "$1" -o "$1" = "HEAD" + then + # before the first commit: compare with an empty tree + head=$(git hash-object -w -t tree --stdin </dev/null) + test -z "$1" || shift else - head=HEAD + head="HEAD" fi if [ -n "$files" ] @@ -573,7 +592,7 @@ cmd_summary() { cd_to_toplevel # Get modified modules cared by user - modules=$(git $diff_cmd $cached --raw $head -- "$@" | + modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" | sane_egrep '^:([0-7]* )?160000' | while read mod_src mod_dst sha1_src sha1_dst status name do @@ -587,7 +606,7 @@ cmd_summary() { test -z "$modules" && return - git $diff_cmd $cached --raw $head -- $modules | + git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- $modules | sane_egrep '^:([0-7]* )?160000' | cut -c2- | while read mod_src mod_dst sha1_src sha1_dst status name @@ -645,7 +664,7 @@ cmd_summary() { range=$sha1_dst fi GIT_DIR="$name/.git" \ - git log --pretty=oneline --first-parent $range | wc -l + git rev-list --first-parent $range -- | wc -l ) total_commits=" ($(($total_commits + 0)))" ;; @@ -753,7 +772,7 @@ cmd_status() continue; fi set_name_rev "$path" "$sha1" - if git diff-files --quiet -- "$path" + if git diff-files --ignore-submodules=dirty --quiet -- "$path" then say " $sha1 $displaypath$revname" else diff --git a/git-svn.perl b/git-svn.perl index 473a0b9d55..34884b8fce 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -36,11 +36,13 @@ $ENV{TZ} = 'UTC'; $| = 1; # unbuffer STDOUT sub fatal (@) { print STDERR "@_\n"; exit 1 } -require SVN::Core; # use()-ing this causes segfaults for me... *shrug* -require SVN::Ra; -require SVN::Delta; -if ($SVN::Core::VERSION lt '1.1.0') { - fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)"; +sub _req_svn { + require SVN::Core; # use()-ing this causes segfaults for me... *shrug* + require SVN::Ra; + require SVN::Delta; + if ($SVN::Core::VERSION lt '1.1.0') { + fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)"; + } } my $can_compress = eval { require Compress::Zlib; 1}; push @Git::SVN::Ra::ISA, 'SVN::Ra'; @@ -349,6 +351,7 @@ information. } sub version { + ::_req_svn(); print "git-svn version $VERSION (svn $SVN::Core::VERSION)\n"; exit 0; } @@ -367,7 +370,6 @@ sub do_git_init_db { command_noisy(@init_db); $_repository = Git->repository(Repository => ".git"); } - command_noisy('config', 'core.autocrlf', 'false'); my $set; my $pfx = "svn-remote.$Git::SVN::default_repo_id"; foreach my $i (keys %icv) { @@ -492,6 +494,7 @@ sub cmd_set_tree { sub cmd_dcommit { my $head = shift; + command_noisy(qw/update-index --refresh/); git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) } 'Cannot dcommit with a dirty index. Commit your changes first, ' . "or stash them with `git stash'.\n"; @@ -730,6 +733,8 @@ sub cmd_branch { $src=~s/^http:/https:/; } + ::_req_svn(); + my $ctx = SVN::Client->new( auth => Git::SVN::Ra::_auth_providers(), log_msg => sub { @@ -959,6 +964,7 @@ sub cmd_multi_init { } do_git_init_db(); if (defined $_trunk) { + $_trunk =~ s#^/+##; my $trunk_ref = 'refs/remotes/' . $_prefix . 'trunk'; # try both old-style and new-style lookups: my $gs_trunk = eval { Git::SVN->new($trunk_ref) }; @@ -1098,6 +1104,7 @@ sub cmd_info { if ($@) { $result .= "Repository Root: (offline)\n"; } + ::_req_svn(); $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" && ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir"); $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n"; @@ -1180,6 +1187,7 @@ sub cmd_reset { "history\n"; } my ($r, $c) = $gs->find_rev_before($target, not $_fetch_parent); + die "Cannot find SVN revision $target\n" unless defined($c); $gs->rev_map_set($r, $c, 'reset', $uuid); print "r$r = $c ($gs->{ref_id})\n"; } @@ -1812,6 +1820,7 @@ sub read_all_remotes { die("svn-remote.$remote: remote ref '$remote_ref' " . "must start with 'refs/'\n") unless $remote_ref =~ m{^refs/}; + $local_ref = uri_decode($local_ref); $r->{$remote}->{fetch}->{$local_ref} = $remote_ref; $r->{$remote}->{svm} = {} if $use_svm_props; } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) { @@ -1824,6 +1833,7 @@ sub read_all_remotes { die("svn-remote.$remote: remote ref '$remote_ref' ($t) " . "must start with 'refs/'\n") unless $remote_ref =~ m{^refs/}; + $local_ref = uri_decode($local_ref); my $rs = { t => $t, remote => $remote, @@ -2048,6 +2058,9 @@ sub new { "\":$ref_id\$\" in config\n"; ($self->{path}, undef) = split(/\s*:\s*/, $fetch); } + $self->{path} =~ s{/+}{/}g; + $self->{path} =~ s{\A/}{}; + $self->{path} =~ s{/\z}{}; $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; @@ -2081,6 +2094,14 @@ sub refname { # .. becomes %2E%2E $refname =~ s{\.\.}{%2E%2E}g; + # trailing dots and .lock are not allowed + # .$ becomes %2E and .lock becomes %2Elock + $refname =~ s{\.(?=$|lock$)}{%2E}; + + # the sequence @{ is used to access the reflog + # @{ becomes %40{ + $refname =~ s{\@\{}{%40\{}g; + return $refname; } @@ -2822,8 +2843,9 @@ sub mkemptydirs { foreach my $d (sort keys %empty_dirs) { $d = uri_decode($d); $d =~ s/$strip//; + next unless length($d); next if -d $d; - if (-e _) { + if (-e $d) { warn "$d exists but is not a directory\n"; } else { print "creating empty directory: $d\n"; @@ -2993,7 +3015,7 @@ sub find_extra_svk_parents { for my $ticket ( @tickets ) { my ($uuid, $path, $rev) = split /:/, $ticket; if ( $uuid eq $self->ra_uuid ) { - my $url = $self->rewrite_root || $self->{url}; + my $url = $self->{url}; my $repos_root = $url; my $branch_from = $path; $branch_from =~ s{^/}{}; @@ -3150,6 +3172,22 @@ sub has_no_changes { LIST_CACHE => 'FAULT', ; } + + sub unmemoize_svn_mergeinfo_functions { + return if not $memoized; + $memoized = 0; + + Memoize::unmemoize 'lookup_svn_merge'; + Memoize::unmemoize 'check_cherry_pick'; + Memoize::unmemoize 'has_no_changes'; + } +} + +END { + # Force cache writeout explicitly instead of waiting for + # global destruction to avoid segfault in Storable: + # http://rt.cpan.org/Public/Bug/Display.html?id=36087 + unmemoize_svn_mergeinfo_functions(); } sub parents_exclude { @@ -3201,7 +3239,7 @@ sub find_extra_svn_parents { # are now marked as merge, we can add the tip as a parent. my @merges = split "\n", $mergeinfo; my @merge_tips; - my $url = $self->rewrite_root || $self->{url}; + my $url = $self->{url}; my $uuid = $self->ra_uuid; my %ranges; for my $merge ( @merges ) { @@ -3273,7 +3311,7 @@ sub find_extra_svn_parents { "$new_parents[$i]..$new_parents[$j]", ); if ( !$revs ) { - undef($new_parents[$i]); + undef($new_parents[$j]); } } } @@ -3600,6 +3638,7 @@ sub mkfile { sub rev_map_set { my ($self, $rev, $commit, $update_ref, $uuid) = @_; + defined $commit or die "missing arg3\n"; length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n"; my $db = $self->map_path($uuid); my $db_lock = "$db.lock"; @@ -3966,18 +4005,25 @@ sub username { sub _read_password { my ($prompt, $realm) = @_; - print STDERR $prompt; - STDERR->flush; - require Term::ReadKey; - Term::ReadKey::ReadMode('noecho'); my $password = ''; - while (defined(my $key = Term::ReadKey::ReadKey(0))) { - last if $key =~ /[\012\015]/; # \n\r - $password .= $key; + if (exists $ENV{GIT_ASKPASS}) { + open(PH, "-|", $ENV{GIT_ASKPASS}, $prompt); + $password = <PH>; + $password =~ s/[\012\015]//; # \n\r + close(PH); + } else { + print STDERR $prompt; + STDERR->flush; + require Term::ReadKey; + Term::ReadKey::ReadMode('noecho'); + while (defined(my $key = Term::ReadKey::ReadKey(0))) { + last if $key =~ /[\012\015]/; # \n\r + $password .= $key; + } + Term::ReadKey::ReadMode('restore'); + print STDERR "\n"; + STDERR->flush; } - Term::ReadKey::ReadMode('restore'); - print STDERR "\n"; - STDERR->flush; $password; } @@ -3986,7 +4032,6 @@ use vars qw/@ISA/; use strict; use warnings; use Carp qw/croak/; -use File::Temp qw/tempfile/; use IO::File qw//; use vars qw/$_ignore_regex/; @@ -4008,6 +4053,7 @@ sub new { $self->{absent_dir} = {}; $self->{absent_file} = {}; $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new }); + $self->{pathnameencoding} = Git::config('svn.pathnameencoding'); $self; } @@ -4091,6 +4137,10 @@ sub open_directory { sub git_path { my ($self, $path) = @_; + if (my $enc = $self->{pathnameencoding}) { + require Encode; + Encode::from_to($path, 'UTF-8', $enc); + } if ($self->{path_strip}) { $path =~ s!$self->{path_strip}!! or die "Failed to strip path '$path' ($self->{path_strip})\n"; @@ -4479,6 +4529,10 @@ sub split_path { sub repo_path { my ($self, $path) = @_; + if (my $enc = $self->{pathnameencoding}) { + require Encode; + Encode::from_to($path, $enc, 'UTF-8'); + } $self->{path_prefix}.(defined $path ? $path : ''); } @@ -4859,6 +4913,8 @@ sub new { $url =~ s!/+$!!; return $RA if ($RA && $RA->{url} eq $url); + ::_req_svn(); + SVN::_Core::svn_config_ensure($config_dir, undef); my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers); my $config = SVN::Core::config_get_config($config_dir); diff --git a/git-web--browse.sh b/git-web--browse.sh index a578c3a732..3fc4166b25 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -31,7 +31,7 @@ valid_custom_tool() valid_tool() { case "$1" in - firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open | start) + firefox | iceweasel | chrome | google-chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start) ;; # happy *) valid_custom_tool "$1" || return 1 @@ -103,7 +103,7 @@ fi if test -z "$browser" ; then if test -n "$DISPLAY"; then - browser_candidates="firefox iceweasel konqueror w3m links lynx dillo" + browser_candidates="firefox iceweasel google-chrome chrome chromium konqueror w3m links lynx dillo" if test "$KDE_FULL_SESSION" = "true"; then browser_candidates="konqueror $browser_candidates" fi @@ -146,6 +146,11 @@ case "$browser" in test "$vers" -lt 2 && NEWTAB='' "$browser_path" $NEWTAB "$@" & ;; + google-chrome|chrome|chromium) + # Actual command for chromium is chromium-browser. + # No need to specify newTab. It's default in chromium + eval "$browser_path" "$@" & + ;; konqueror) case "$(basename "$browser_path")" in konqueror) @@ -8,7 +8,8 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" " [-p|--paginate|--no-pager] [--no-replace-objects]\n" " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" - " [--help] COMMAND [ARGS]"; + " [-c name=value] [--help]\n" + " COMMAND [ARGS]"; const char git_more_info_string[] = "See 'git help COMMAND' for more information on a specific command."; @@ -54,6 +55,9 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) { int handled = 0; + if (!getenv("GIT_ASKPASS") && getenv("SSH_ASKPASS")) + setenv("GIT_ASKPASS", getenv("SSH_ASKPASS"), 1); + while (*argc > 0) { const char *cmd = (*argv)[0]; if (cmd[0] != '-') @@ -127,6 +131,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0); if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "-c")) { + if (*argc < 2) { + fprintf(stderr, "-c expects a configuration string\n" ); + usage(git_usage_string); + } + git_config_parse_parameter((*argv)[1]); + (*argv)++; + (*argc)--; } else { fprintf(stderr, "Unknown option: %s\n", cmd); usage(git_usage_string); @@ -155,6 +167,7 @@ static int handle_alias(int *argcp, const char ***argv) alias_string = alias_lookup(alias_command); if (alias_string) { if (alias_string[0] == '!') { + commit_pager_choice(); if (*argcp > 1) { struct strbuf buf; @@ -317,7 +330,7 @@ static void handle_internal_command(int argc, const char **argv) { "fsck-objects", cmd_fsck, RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, + { "grep", cmd_grep }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, { "index-pack", cmd_index_pack }, @@ -343,6 +356,7 @@ static void handle_internal_command(int argc, const char **argv) { "mktree", cmd_mktree, RUN_SETUP }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, + { "notes", cmd_notes, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, { "pack-redundant", cmd_pack_redundant, RUN_SETUP }, { "patch-id", cmd_patch_id }, @@ -419,6 +433,8 @@ static void execv_dashed_external(const char **argv) const char *tmp; int status; + commit_pager_choice(); + strbuf_addf(&cmd, "git-%s", argv[0]); /* @@ -498,12 +514,12 @@ int main(int argc, const char **argv) argv++; argc--; handle_options(&argv, &argc, NULL); - commit_pager_choice(); if (argc > 0) { if (!prefixcmp(argv[0], "--")) argv[0] += 2; } else { /* The user didn't specify a command; give them help */ + commit_pager_choice(); printf("usage: %s\n\n", git_usage_string); list_common_cmds_help(); printf("\n%s\n", git_more_info_string); diff --git a/git.spec.in b/git.spec.in index ee74a5eed7..91c8462b7d 100644 --- a/git.spec.in +++ b/git.spec.in @@ -35,6 +35,7 @@ Requires: git-cvs = %{version}-%{release} Requires: git-arch = %{version}-%{release} Requires: git-email = %{version}-%{release} Requires: gitk = %{version}-%{release} +Requires: gitweb = %{version}-%{release} Requires: git-gui = %{version}-%{release} Obsoletes: git <= 1.5.4.2 @@ -87,6 +88,13 @@ Requires: git = %{version}-%{release}, tk >= 8.4 %description -n gitk Git revision tree visualiser ('gitk') +%package -n gitweb +Summary: Git web interface +Group: Development/Tools +Requires: git = %{version}-%{release} +%description -n gitweb +Browsing git repository on the web + %package -n perl-Git Summary: Perl interface to Git Group: Development/Libraries @@ -127,6 +135,9 @@ find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';' rm -rf $RPM_BUILD_ROOT%{_mandir} %endif +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d +install -m 644 -T contrib/completion/git-completion.bash $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/git + %clean rm -rf $RPM_BUILD_ROOT @@ -136,6 +147,7 @@ rm -rf $RPM_BUILD_ROOT %doc README COPYING Documentation/*.txt %{!?_without_docs: %doc Documentation/*.html Documentation/howto} %{!?_without_docs: %doc Documentation/technical} +%{_sysconfdir}/bash_completion.d %files svn %defattr(-,root,root) @@ -185,6 +197,10 @@ rm -rf $RPM_BUILD_ROOT %{!?_without_docs: %{_mandir}/man1/*gitk*.1*} %{!?_without_docs: %doc Documentation/*gitk*.html } +%files -n gitweb +%defattr(-,root,root) +%{_datadir}/gitweb + %files -n perl-Git -f perl-files %defattr(-,root,root) @@ -192,6 +208,12 @@ rm -rf $RPM_BUILD_ROOT # No files for you! %changelog +* Wed Jun 30 2010 Junio C Hamano <gitster@pobox.com> +- Add 'gitweb' subpackage. + +* Fri Mar 26 2010 Ian Ward Comfort <icomfort@stanford.edu> +- Ship bash completion support from contrib/ in the core package. + * Sun Jan 31 2010 Junio C Hamano <gitster@pobox.com> - Do not use %define inside %{!?...} construct. diff --git a/git_remote_helpers/git/exporter.py b/git_remote_helpers/git/exporter.py new file mode 100644 index 0000000000..f40f9d6a29 --- /dev/null +++ b/git_remote_helpers/git/exporter.py @@ -0,0 +1,53 @@ +import os +import subprocess +import sys + + +class GitExporter(object): + """An exporter for testgit repositories. + + The exporter simply delegates to git fast-export. + """ + + def __init__(self, repo): + """Creates a new exporter for the specified repo. + """ + + self.repo = repo + + def export_repo(self, base): + """Exports a fast-export stream for the given directory. + + Simply delegates to git fast-epxort and pipes it through sed + to make the refs show up under the prefix rather than the + default refs/heads. This is to demonstrate how the export + data can be stored under it's own ref (using the refspec + capability). + """ + + dirname = self.repo.get_base_path(base) + path = os.path.abspath(os.path.join(dirname, 'testgit.marks')) + + if not os.path.exists(dirname): + os.makedirs(dirname) + + print "feature relative-marks" + if os.path.exists(os.path.join(dirname, 'git.marks')): + print "feature import-marks=%s/git.marks" % self.repo.hash + print "feature export-marks=%s/git.marks" % self.repo.hash + sys.stdout.flush() + + args = ["git", "--git-dir=" + self.repo.gitpath, "fast-export", "--export-marks=" + path] + + if os.path.exists(path): + args.append("--import-marks=" + path) + + args.append("HEAD") + + p1 = subprocess.Popen(args, stdout=subprocess.PIPE) + + args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"] + + child = subprocess.Popen(args, stdin=p1.stdout) + if child.wait() != 0: + raise CalledProcessError diff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py new file mode 100644 index 0000000000..70a712729b --- /dev/null +++ b/git_remote_helpers/git/importer.py @@ -0,0 +1,40 @@ +import os +import subprocess + + +class GitImporter(object): + """An importer for testgit repositories. + + This importer simply delegates to git fast-import. + """ + + def __init__(self, repo): + """Creates a new importer for the specified repo. + """ + + self.repo = repo + + def do_import(self, base): + """Imports a fast-import stream to the given directory. + + Simply delegates to git fast-import. + """ + + dirname = self.repo.get_base_path(base) + if self.repo.local: + gitdir = self.repo.gitpath + else: + gitdir = os.path.abspath(os.path.join(dirname, '.git')) + path = os.path.abspath(os.path.join(dirname, 'git.marks')) + + if not os.path.exists(dirname): + os.makedirs(dirname) + + args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path] + + if os.path.exists(path): + args.append("--import-marks=" + path) + + child = subprocess.Popen(args) + if child.wait() != 0: + raise CalledProcessError diff --git a/git_remote_helpers/git/non_local.py b/git_remote_helpers/git/non_local.py new file mode 100644 index 0000000000..f27389bb94 --- /dev/null +++ b/git_remote_helpers/git/non_local.py @@ -0,0 +1,69 @@ +import os +import subprocess + +from git_remote_helpers.util import die, warn + + +class NonLocalGit(object): + """Handler to interact with non-local repos. + """ + + def __init__(self, repo): + """Creates a new non-local handler for the specified repo. + """ + + self.repo = repo + + def clone(self, base): + """Clones the non-local repo to base. + + Does nothing if a clone already exists. + """ + + path = os.path.join(self.repo.get_base_path(base), '.git') + + # already cloned + if os.path.exists(path): + return path + + os.makedirs(path) + args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path] + + child = subprocess.Popen(args) + if child.wait() != 0: + raise CalledProcessError + + return path + + def update(self, base): + """Updates checkout of the non-local repo in base. + """ + + path = os.path.join(self.repo.get_base_path(base), '.git') + + if not os.path.exists(path): + die("could not find repo at %s", path) + + args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath] + child = subprocess.Popen(args) + if child.wait() != 0: + raise CalledProcessError + + args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"] + child = subprocess.Popen(args) + if child.wait() != 0: + raise CalledProcessError + + def push(self, base): + """Pushes from the non-local repo to base. + """ + + path = os.path.join(self.repo.get_base_path(base), '.git') + + if not os.path.exists(path): + die("could not find repo at %s", path) + + args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath] + child = subprocess.Popen(args) + if child.wait() != 0: + raise CalledProcessError diff --git a/git_remote_helpers/git/repo.py b/git_remote_helpers/git/repo.py new file mode 100644 index 0000000000..58e1cdb560 --- /dev/null +++ b/git_remote_helpers/git/repo.py @@ -0,0 +1,75 @@ +import os +import subprocess + +def sanitize(rev, sep='\t'): + """Converts a for-each-ref line to a name/value pair. + """ + + splitrev = rev.split(sep) + branchval = splitrev[0] + branchname = splitrev[1].strip() + if branchname.startswith("refs/heads/"): + branchname = branchname[11:] + + return branchname, branchval + +def is_remote(url): + """Checks whether the specified value is a remote url. + """ + + prefixes = ["http", "file", "git"] + + for prefix in prefixes: + if url.startswith(prefix): + return True + return False + +class GitRepo(object): + """Repo object representing a repo. + """ + + def __init__(self, path): + """Initializes a new repo at the given path. + """ + + self.path = path + self.head = None + self.revmap = {} + self.local = not is_remote(self.path) + + if(self.path.endswith('.git')): + self.gitpath = self.path + else: + self.gitpath = os.path.join(self.path, '.git') + + if self.local and not os.path.exists(self.gitpath): + os.makedirs(self.gitpath) + + def get_revs(self): + """Fetches all revs from the remote. + """ + + args = ["git", "ls-remote", self.gitpath] + path = ".cached_revs" + ofile = open(path, "w") + + child = subprocess.Popen(args, stdout=ofile) + if child.wait() != 0: + raise CalledProcessError + output = open(path).readlines() + self.revmap = dict(sanitize(i) for i in output) + if "HEAD" in self.revmap: + del self.revmap["HEAD"] + self.revs = self.revmap.keys() + ofile.close() + + def get_head(self): + """Determines the head of a local repo. + """ + + if not self.local: + return + + path = os.path.join(self.gitpath, "HEAD") + head = open(path).readline() + self.head, _ = sanitize(head, ' ') diff --git a/gitk-git/gitk b/gitk-git/gitk index 1f36a3e815..1b0e09a561 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -1877,8 +1877,11 @@ proc setoptions {} { option add *Menubutton.font uifont startupFile option add *Label.font uifont startupFile option add *Message.font uifont startupFile - option add *Entry.font uifont startupFile + option add *Entry.font textfont startupFile + option add *Text.font textfont startupFile option add *Labelframe.font uifont startupFile + option add *Spinbox.font textfont startupFile + option add *Listbox.font mainfont startupFile } # Make a menu and submenus. @@ -2174,7 +2177,7 @@ proc makewindow {} { set findstring {} set fstring .tf.lbar.findstring lappend entries $fstring - ${NS}::entry $fstring -width 30 -font textfont -textvariable findstring + ${NS}::entry $fstring -width 30 -textvariable findstring trace add variable findstring write find_change set findtype [mc "Exact"] set findtypemenu [makedroplist .tf.lbar.findtype \ @@ -2217,7 +2220,7 @@ proc makewindow {} { pack .bleft.top.search -side left -padx 5 set sstring .bleft.top.sstring set searchstring "" - ${NS}::entry $sstring -width 20 -font textfont -textvariable searchstring + ${NS}::entry $sstring -width 20 -textvariable searchstring lappend entries $sstring trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x @@ -2229,7 +2232,7 @@ proc makewindow {} { -command changediffdisp -variable diffelide -value {1 0} ${NS}::label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: " pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left - spinbox .bleft.mid.diffcontext -width 5 -font textfont \ + spinbox .bleft.mid.diffcontext -width 5 \ -from 0 -increment 1 -to 10000000 \ -validate all -validatecommand "diffcontextvalidate %P" \ -textvariable diffcontextstring @@ -2383,6 +2386,8 @@ proc makewindow {} { } bindall <$::BM> "canvscan mark %W %x %y" bindall <B$::BM-Motion> "canvscan dragto %W %x %y" + bind all <$M1B-Key-w> {destroy [winfo toplevel %W]} + bind . <$M1B-Key-w> doquit bindkey <Home> selfirstline bindkey <End> sellastline bind . <Key-Up> "selnextline -1" @@ -2782,7 +2787,7 @@ proc about {} { message $w.m -text [mc " Gitk - a commit viewer for git -Copyright © 2005-2009 Paul Mackerras +Copyright \u00a9 2005-2010 Paul Mackerras Use and redistribute under the terms of the GNU General Public License"] \ -justify center -aspect 400 -border 2 -bg white -relief groove @@ -2814,6 +2819,7 @@ proc keys {} { [mc "Gitk key bindings:"] [mc "<%s-Q> Quit" $M1T] +[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"] @@ -3805,10 +3811,10 @@ proc newview {ishighlight} { raise $top return } + decode_view_opts $nextviewnum $revtreeargs set newviewname($nextviewnum) "[mc "View"] $nextviewnum" set newviewopts($nextviewnum,perm) 0 set newviewopts($nextviewnum,cmd) $viewargscmd($curview) - decode_view_opts $nextviewnum $revtreeargs vieweditor $top $nextviewnum [mc "Gitk view definition"] } @@ -3845,6 +3851,7 @@ set known_view_options { {cmd t50= + {} {mc "Command to generate more commits to include:"}} } +# Convert $newviewopts($n, ...) into args for git log. proc encode_view_opts {n} { global known_view_options newviewopts @@ -3878,6 +3885,7 @@ proc encode_view_opts {n} { return [concat $rargs [shellsplit $newviewopts($n,args)]] } +# Fill $newviewopts($n, ...) based on args for git log. proc decode_view_opts {n view_args} { global known_view_options newviewopts @@ -3960,10 +3968,10 @@ proc editview {} { raise $top return } + decode_view_opts $curview $viewargs($curview) set newviewname($curview) $viewname($curview) set newviewopts($curview,perm) $viewperm($curview) set newviewopts($curview,cmd) $viewargscmd($curview) - decode_view_opts $curview $viewargs($curview) vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)" } @@ -4037,7 +4045,7 @@ proc vieweditor {top n title} { } elseif {$type eq "path"} { ${NS}::label $top.l -text $title pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3 - text $top.t -width 40 -height 5 -background $bgcolor -font uifont + text $top.t -width 40 -height 5 -background $bgcolor if {[info exists viewfiles($n)]} { foreach f $viewfiles($n) { $top.t insert end $f @@ -7501,7 +7509,7 @@ proc getblobdiffs {ids} { global ignorespace global limitdiffs vfilelimit curview global diffencoding targetline diffnparents - global git_version + global git_version currdiffsubmod set textconv {} if {[package vcompare $git_version "1.6.1"] >= 0} { @@ -7528,6 +7536,7 @@ proc getblobdiffs {ids} { set diffencoding [get_path_encoding {}] fconfigure $bdf -blocking 0 -encoding binary -eofchar {} set blobdifffd($ids) $bdf + set currdiffsubmod "" filerun $bdf [list getblobdiffline $bdf $diffids] } @@ -7598,7 +7607,7 @@ proc getblobdiffline {bdf ids} { global diffnexthead diffnextnote difffilestart global ctext_file_names ctext_file_lines global diffinhdr treediffs mergemax diffnparents - global diffencoding jump_to_here targetline diffline + global diffencoding jump_to_here targetline diffline currdiffsubmod set nr 0 $ctext conf -state normal @@ -7679,19 +7688,30 @@ proc getblobdiffline {bdf ids} { } elseif {![string compare -length 10 "Submodule " $line]} { # start of a new submodule - if {[string compare [$ctext get "end - 4c" end] "\n \n\n"]} { + if {[regexp -indices "\[0-9a-f\]+\\.\\." $line nameend]} { + set fname [string range $line 10 [expr [lindex $nameend 0] - 2]] + } else { + set fname [string range $line 10 [expr [string first "contains " $line] - 2]] + } + if {$currdiffsubmod != $fname} { $ctext insert end "\n"; # Add newline after commit message } set curdiffstart [$ctext index "end - 1c"] lappend ctext_file_names "" - set fname [string range $line 10 [expr [string last " " $line] - 1]] - lappend ctext_file_lines $fname - makediffhdr $fname $ids - $ctext insert end "\n$line\n" filesep + if {$currdiffsubmod != $fname} { + lappend ctext_file_lines $fname + makediffhdr $fname $ids + set currdiffsubmod $fname + $ctext insert end "\n$line\n" filesep + } else { + $ctext insert end "$line\n" filesep + } } elseif {![string compare -length 3 " >" $line]} { + set $currdiffsubmod "" set line [encoding convertfrom $diffencoding $line] $ctext insert end "$line\n" dresult } elseif {![string compare -length 3 " <" $line]} { + set $currdiffsubmod "" set line [encoding convertfrom $diffencoding $line] $ctext insert end "$line\n" d0 } elseif {$diffinhdr} { @@ -8527,7 +8547,7 @@ proc do_cmp_commits {a b} { } proc diffcommits {a b} { - global diffcontext diffids blobdifffd diffinhdr + global diffcontext diffids blobdifffd diffinhdr currdiffsubmod set tmpdir [gitknewtmpdir] set fna [file join $tmpdir "commit-[string range $a 0 7]"] @@ -8548,6 +8568,7 @@ proc diffcommits {a b} { set diffids [list commits $a $b] set blobdifffd($diffids) $fd set diffinhdr 0 + set currdiffsubmod "" filerun $fd [list getblobdiffline $fd $diffids] } @@ -10528,7 +10549,6 @@ proc mkfontdisp {font top which} { set fontpref($font) [set $font] ${NS}::button $top.${font}but -text $which \ -command [list choosefont $font $which] - if {!$use_ttk} {$top.${font}but configure -font optionfont} ${NS}::label $top.$font -relief flat -font $font \ -text $fontattr($font,family) -justify left grid x $top.${font}but $top.$font -sticky w @@ -10791,15 +10811,6 @@ proc doprefs {} { mkfontdisp textfont $top [mc "Diff display font"] mkfontdisp uifont $top [mc "User interface font"] - if {!$use_ttk} { - foreach w {maxpctl maxwidthl showlocal autoselect tabstopl ntag - ldiff lattr extdifff.l extdifff.b bgbut fgbut - diffoldbut diffnewbut hunksepbut markbgbut selbgbut - want_ttk ttk_note} { - $top.$w configure -font optionfont - } - } - ${NS}::frame $top.buts ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal @@ -10849,6 +10860,7 @@ proc setselbg {c} { # radiobuttons look bad. This chooses white for selectColor if the # background color is light, or black if it is dark. proc setui {c} { + if {[tk windowingsystem] eq "win32"} { return } set bg [winfo rgb . $c] set selc black if {[lindex $bg 0] + 1.5 * [lindex $bg 1] + 0.5 * [lindex $bg 2] > 100000} { @@ -11411,8 +11423,6 @@ namespace import ::msgcat::mc catch {source ~/.gitk} -font create optionfont -family sans-serif -size -12 - parsefont mainfont $mainfont eval font create mainfont [fontflags mainfont] eval font create mainfontbold [fontflags mainfont 1] @@ -11613,3 +11623,9 @@ if {[tk windowingsystem] eq "win32"} { } getcommits {} + +# Local variables: +# mode: tcl +# indent-tabs-mode: t +# tab-width: 8 +# End: diff --git a/gitk-git/po/de.po b/gitk-git/po/de.po index c79aa9cbc8..bd194a3dff 100644 --- a/gitk-git/po/de.po +++ b/gitk-git/po/de.po @@ -334,14 +334,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright ©9 2005-2009 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - eine Visualisierung der Git-Historie\n" "\n" -"Copyright © 2005-2009 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public License" diff --git a/gitk-git/po/es.po b/gitk-git/po/es.po index 0e19b5eae2..0471dd0672 100644 --- a/gitk-git/po/es.po +++ b/gitk-git/po/es.po @@ -281,14 +281,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - un visualizador de revisiones para git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Uso y redistribución permitidos según los términos de la Licencia Pública " "General de GNU (GNU GPL)" diff --git a/gitk-git/po/fr.po b/gitk-git/po/fr.po index cb0e1edc63..5370ddc393 100644 --- a/gitk-git/po/fr.po +++ b/gitk-git/po/fr.po @@ -334,14 +334,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - visualisateur de commit pour git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Utilisation et redistribution soumises aux termes de la GNU General Public " "License" diff --git a/gitk-git/po/hu.po b/gitk-git/po/hu.po index 1df212e881..7262b610dc 100644 --- a/gitk-git/po/hu.po +++ b/gitk-git/po/hu.po @@ -333,14 +333,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright ©9 2005-2009 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - commit nézegető a githez\n" "\n" -"Szerzői jog ©9 2005-2009 Paul Mackerras\n" +"Szerzői jog \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Használd és terjeszd a GNU General Public License feltételei mellett" diff --git a/gitk-git/po/it.po b/gitk-git/po/it.po index 4818652309..a730d63a42 100644 --- a/gitk-git/po/it.po +++ b/gitk-git/po/it.po @@ -334,14 +334,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2009 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - un visualizzatore di revisioni per git\n" "\n" -"Copyright © 2005-2009 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Utilizzo e redistribuzione permessi sotto i termini della GNU General Public " "License" diff --git a/gitk-git/po/ja.po b/gitk-git/po/ja.po index c0c92addb4..4f4705164c 100644 --- a/gitk-git/po/ja.po +++ b/gitk-git/po/ja.po @@ -335,14 +335,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - gitコミットビューア\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "使用および再配布は GNU General Public License に従ってください" diff --git a/gitk-git/po/ru.po b/gitk-git/po/ru.po index 704eba8f9d..c3d0285b24 100644 --- a/gitk-git/po/ru.po +++ b/gitk-git/po/ru.po @@ -313,14 +313,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - программа просмотра истории репозиториев Git\n" "\n" -"Copyright (c) 2005-2008 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Использование и распространение согласно условиям GNU General Public License" diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po index 0f5e2fd8d7..386763ade7 100644 --- a/gitk-git/po/sv.po +++ b/gitk-git/po/sv.po @@ -334,14 +334,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright ©9 2005-2009 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - en incheckningsvisare för git\n" "\n" -"Copyright © 2005-2009 Paul Mackerras\n" +"Copyright \\u00a9 2005-2010 Paul Mackerras\n" "\n" "Använd och vidareförmedla enligt villkoren i GNU General Public License" diff --git a/gitweb/INSTALL b/gitweb/INSTALL index b76a0cffff..823053173c 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -2,12 +2,13 @@ GIT web Interface (gitweb) Installation ======================================= First you have to generate gitweb.cgi from gitweb.perl using -"make gitweb/gitweb.cgi", then copy appropriate files (gitweb.cgi, -gitweb.css, git-logo.png and git-favicon.png) to their destination. -For example if git was (or is) installed with /usr prefix, you can do +"make gitweb", then "make install-gitweb" appropriate files +(gitweb.cgi, gitweb.js, gitweb.css, git-logo.png and git-favicon.png) +to their destination. For example if git was (or is) installed with +/usr prefix and gitwebdir is /var/www/cgi-bin, you can do - $ make prefix=/usr gitweb/gitweb.cgi ;# as yourself - # cp gitweb/git* /var/www/cgi-bin/ ;# as root + $ make prefix=/usr gitweb ;# as yourself + # make gitwebdir=/var/www/cgi-bin install-gitweb ;# as root Alternatively you can use autoconf generated ./configure script to set up path to git binaries (via config.mak.autogen), so you can write @@ -15,8 +16,9 @@ instead $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself - $ make gitweb/gitweb.cgi ;# as yourself - # cp gitweb/git* /var/www/cgi-bin/ ;# as root + $ make gitweb ;# as yourself + # make gitwebdir=/var/www/cgi-bin \ + install-gitweb ;# as root The above example assumes that your web server is configured to run [executable] files in /var/www/cgi-bin/ as server scripts (as CGI @@ -31,8 +33,7 @@ file for gitweb (in gitweb/README). - There are many configuration variables which affect building of gitweb.cgi; see "default configuration for gitweb" section in main - (top dir) Makefile, and instructions for building gitweb/gitweb.cgi - target. + (top dir) Makefile, and instructions for building gitweb target. One of the most important is where to find the git wrapper binary. Gitweb tries to find the git wrapper at $(bindir)/git, so you have to set $bindir @@ -62,27 +63,33 @@ file for gitweb (in gitweb/README). a suggestion). - You can control where gitweb tries to find its main CSS style file, - its favicon and logo with the GITWEB_CSS, GITWEB_FAVICON and GITWEB_LOGO - build configuration variables. By default gitweb tries to find them - in the same directory as gitweb.cgi script. + its JavaScript file, its favicon and logo with the GITWEB_CSS, GITWEB_JS + GITWEB_FAVICON and GITWEB_LOGO build configuration variables. By default + gitweb tries to find them in the same directory as gitweb.cgi script. + +- You can optionally generate minified versions of gitweb.js and gitweb.css + by defining the JSMIN and CSSMIN build configuration variables. By default + the non-minified versions will be used. NOTE: if you enable this option, + substitute gitweb.min.js and gitweb.min.css for all uses of gitweb.js and + gitweb.css in the help files. Build example ~~~~~~~~~~~~~ -- To install gitweb to /var/www/cgi-bin/gitweb/ when git wrapper - is installed at /usr/local/bin/git and the repositories (projects) - we want to display are under /home/local/scm, you can do +- To install gitweb to /var/www/cgi-bin/gitweb/, when git wrapper + is installed at /usr/local/bin/git, the repositories (projects) + we want to display are under /home/local/scm, and you do not use + minifiers, you can do make GITWEB_PROJECTROOT="/home/local/scm" \ - GITWEB_CSS="/gitweb/gitweb.css" \ - GITWEB_LOGO="/gitweb/git-logo.png" \ - GITWEB_FAVICON="/gitweb/git-favicon.png" \ + GITWEB_JS="gitweb/static/gitweb.js" \ + GITWEB_CSS="gitweb/static/gitweb.css" \ + GITWEB_LOGO="gitweb/static/git-logo.png" \ + GITWEB_FAVICON="gitweb/static/git-favicon.png" \ bindir=/usr/local/bin \ - gitweb/gitweb.cgi + gitweb - cp -fv ~/git/gitweb/gitweb.{cgi,css} \ - ~/git/gitweb/git-{favicon,logo}.png \ - /var/www/cgi-bin/gitweb/ + make gitwebdir=/var/www/cgi-bin/gitweb install-gitweb Gitweb config file diff --git a/gitweb/Makefile b/gitweb/Makefile index c9eb1ee667..2fb7c2d77b 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -4,15 +4,18 @@ all:: # Define V=1 to have a more verbose compile. # # Define JSMIN to point to JavaScript minifier that functions as -# a filter to have gitweb.js minified. +# a filter to have static/gitweb.js minified. +# +# Define CSSMIN to point to a CSS minifier in order to generate a minified +# version of static/gitweb.css # prefix ?= $(HOME) bindir ?= $(prefix)/bin -RM ?= rm -f +gitwebdir ?= /var/www/cgi-bin -# JavaScript minifier invocation that can function as filter -JSMIN ?= +RM ?= rm -f +INSTALL ?= install # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl @@ -26,14 +29,10 @@ GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = GITWEB_LIST = GITWEB_HOMETEXT = indextext.html -GITWEB_CSS = gitweb.css -GITWEB_LOGO = git-logo.png -GITWEB_FAVICON = git-favicon.png -ifdef JSMIN -GITWEB_JS = gitweb.min.js -else -GITWEB_JS = gitweb.js -endif +GITWEB_CSS = static/gitweb.css +GITWEB_LOGO = static/git-logo.png +GITWEB_FAVICON = static/git-favicon.png +GITWEB_JS = static/gitweb.js GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = @@ -53,9 +52,12 @@ SHELL_PATH ?= $(SHELL) PERL_PATH ?= /usr/bin/perl # Shell quote; -bindir_SQ = $(subst ','\'',$(bindir)) #' -SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) #' -PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) #' +bindir_SQ = $(subst ','\'',$(bindir))#' +gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#' +gitwebstaticdir_SQ = $(subst ','\'',$(gitwebdir)/static)#' +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#' +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))#' +DESTDIR_SQ = $(subst ','\'',$(DESTDIR))#' # Quiet generation (unless V=1) QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir @@ -84,46 +86,75 @@ endif all:: gitweb.cgi +GITWEB_PROGRAMS = gitweb.cgi + ifdef JSMIN -FILES=gitweb.cgi gitweb.min.js -gitweb.cgi: gitweb.perl gitweb.min.js -else # !JSMIN -FILES=gitweb.cgi -gitweb.cgi: gitweb.perl -endif # JSMIN - -gitweb.cgi: +GITWEB_FILES += static/gitweb.min.js +GITWEB_JS = static/gitweb.min.js +all:: static/gitweb.min.js +static/gitweb.min.js: static/gitweb.js GITWEB-BUILD-OPTIONS + $(QUIET_GEN)$(JSMIN) <$< >$@ +else +GITWEB_FILES += static/gitweb.js +endif + +ifdef CSSMIN +GITWEB_FILES += static/gitweb.min.css +GITWEB_CSS = static/gitweb.min.css +all:: static/gitweb.min.css +static/gitweb.min.css: static/gitweb.css GITWEB-BUILD-OPTIONS + $(QUIET_GEN)$(CSSMIN) <$< >$@ +else +GITWEB_FILES += static/gitweb.css +endif + +GITWEB_FILES += static/git-logo.png static/git-favicon.png + +GITWEB_REPLACE = \ + -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ + -e 's|++GIT_BINDIR++|$(bindir)|g' \ + -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \ + -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ + -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ + -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \ + -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ + -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ + -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ + -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ + -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ + -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ + -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_HEADER++|$(GITWEB_SITE_HEADER)|g' \ + -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' + +GITWEB-BUILD-OPTIONS: FORCE + @rm -f $@+ + @echo "x" '$(PERL_PATH_SQ)' $(GITWEB_REPLACE) "$(JSMIN)|$(CSSMIN)" >$@+ + @cmp -s $@+ $@ && rm -f $@+ || mv -f $@+ $@ + +gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ - -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ - -e 's|++GIT_BINDIR++|$(bindir)|g' \ - -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ - -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \ - -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ - -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ - -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ - -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \ - -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ - -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ - -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ - -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ - -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ - -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ - -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_HEADER++|$(GITWEB_SITE_HEADER)|g' \ - -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ - $< >$@+ && \ + $(GITWEB_REPLACE) $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ -ifdef JSMIN -gitweb.min.js: gitweb.js - $(QUIET_GEN)$(JSMIN) <$< >$@ -endif # JSMIN +### Installation rules + +install: all + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' + $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' + +### Cleaning rules clean: - $(RM) $(FILES) + $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS + +.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE -.PHONY: all clean .FORCE-GIT-VERSION-FILE diff --git a/gitweb/README b/gitweb/README index ad6a04c464..0e19be8d21 100644 --- a/gitweb/README +++ b/gitweb/README @@ -80,23 +80,26 @@ You can specify the following configuration variables when building GIT: Points to the location where you put gitweb.css on your web server (or to be more generic, the URI of gitweb stylesheet). Relative to the base URI of gitweb. Note that you can setup multiple stylesheets from - the gitweb config file. [Default: gitweb.css] + the gitweb config file. [Default: static/gitweb.css (or + static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier + is used)] * GITWEB_LOGO Points to the location where you put git-logo.png on your web server (or to be more generic URI of logo, 72x27 size, displayed in top right corner of each gitweb page, and used as logo for Atom feed). Relative - to base URI of gitweb. [Default: git-logo.png] + to base URI of gitweb. [Default: static/git-logo.png] * GITWEB_FAVICON Points to the location where you put git-favicon.png on your web server (or to be more generic URI of favicon, assumed to be image/png type; web browsers that support favicons (website icons) may display them in the browser's URL bar and next to site name in bookmarks). Relative - to base URI of gitweb. [Default: git-favicon.png] + to base URI of gitweb. [Default: static/git-favicon.png] * GITWEB_JS Points to the localtion where you put gitweb.js on your web server (or to be more generic URI of JavaScript code used by gitweb). - Relative to base URI of gitweb. [Default: gitweb.js (or gitweb.min.js - if JSMIN build variable is defined / JavaScript minifier is used)] + Relative to base URI of gitweb. [Default: static/gitweb.js (or + static/gitweb.min.js if JSMIN build variable is defined / JavaScript + minifier is used)] * GITWEB_CONFIG This Perl file will be loaded using 'do' and can be used to override any of the options above as well as some other options -- see the "Runtime diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9d4c58238e..898b121b50 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -11,7 +11,7 @@ use strict; use warnings; use CGI qw(:standard :escapeHTML -nosticky); use CGI::Util qw(unescape); -use CGI::Carp qw(fatalsToBrowser); +use CGI::Carp qw(fatalsToBrowser set_message); use Encode; use Fcntl ':mode'; use File::Find qw(); @@ -28,34 +28,42 @@ BEGIN { CGI->compile() if $ENV{'MOD_PERL'}; } -our $cgi = new CGI; our $version = "++GIT_VERSION++"; -our $my_url = $cgi->url(); -our $my_uri = $cgi->url(-absolute => 1); -# Base URL for relative URLs in gitweb ($logo, $favicon, ...), -# needed and used only for URLs with nonempty PATH_INFO -our $base_url = $my_url; +our ($my_url, $my_uri, $base_url, $path_info, $home_link); +sub evaluate_uri { + our $cgi; -# When the script is used as DirectoryIndex, the URL does not contain the name -# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we -# have to do it ourselves. We make $path_info global because it's also used -# later on. -# -# Another issue with the script being the DirectoryIndex is that the resulting -# $my_url data is not the full script URL: this is good, because we want -# generated links to keep implying the script name if it wasn't explicitly -# indicated in the URL we're handling, but it means that $my_url cannot be used -# as base URL. -# Therefore, if we needed to strip PATH_INFO, then we know that we have -# to build the base URL ourselves: -our $path_info = $ENV{"PATH_INFO"}; -if ($path_info) { - if ($my_url =~ s,\Q$path_info\E$,, && - $my_uri =~ s,\Q$path_info\E$,, && - defined $ENV{'SCRIPT_NAME'}) { - $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'}; + our $my_url = $cgi->url(); + our $my_uri = $cgi->url(-absolute => 1); + + # Base URL for relative URLs in gitweb ($logo, $favicon, ...), + # needed and used only for URLs with nonempty PATH_INFO + our $base_url = $my_url; + + # When the script is used as DirectoryIndex, the URL does not contain the name + # of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we + # have to do it ourselves. We make $path_info global because it's also used + # later on. + # + # Another issue with the script being the DirectoryIndex is that the resulting + # $my_url data is not the full script URL: this is good, because we want + # generated links to keep implying the script name if it wasn't explicitly + # indicated in the URL we're handling, but it means that $my_url cannot be used + # as base URL. + # Therefore, if we needed to strip PATH_INFO, then we know that we have + # to build the base URL ourselves: + our $path_info = $ENV{"PATH_INFO"}; + if ($path_info) { + if ($my_url =~ s,\Q$path_info\E$,, && + $my_uri =~ s,\Q$path_info\E$,, && + defined $ENV{'SCRIPT_NAME'}) { + $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'}; + } } + + # target of the home link on top of all pages + our $home_link = $my_uri || "/"; } # core git executable to use @@ -70,9 +78,6 @@ our $projectroot = "++GITWEB_PROJECTROOT++"; # the number is relative to the projectroot our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++"; -# target of the home link on top of all pages -our $home_link = $my_uri || "/"; - # string of the home link on top of all pages our $home_link_str = "++GITWEB_HOME_LINK_STR++"; @@ -227,6 +232,29 @@ our %avatar_size = ( # Leave it undefined (or set to 'undef') to turn off load checking. our $maxload = 300; +# configuration for 'highlight' (http://www.andre-simon.de/) +# match by basename +our %highlight_basename = ( + #'Program' => 'py', + #'Library' => 'py', + 'SConstruct' => 'py', # SCons equivalent of Makefile + 'Makefile' => 'make', +); +# match by extension +our %highlight_ext = ( + # main extensions, defining name of syntax; + # see files in /usr/share/highlight/langDefs/ directory + map { $_ => $_ } + qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl), + # alternate extensions, see /etc/highlight/filetypes.conf + 'h' => 'c', + map { $_ => 'cpp' } qw(cxx c++ cc), + map { $_ => 'php' } qw(php3 php4), + map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi' + 'mak' => 'make', + map { $_ => 'xml' } qw(xhtml html htm), +); + # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( @@ -445,6 +473,19 @@ our %feature = ( 'javascript-actions' => { 'override' => 0, 'default' => [0]}, + + # Syntax highlighting support. This is based on Daniel Svensson's + # and Sham Chukoury's work in gitweb-xmms2.git. + # It requires the 'highlight' program present in $PATH, + # and therefore is disabled by default. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'highlight'}{'default'} = [1]; + + 'highlight' => { + 'sub' => sub { feature_bool('highlight', @_) }, + 'override' => 0, + 'default' => [0]}, ); sub gitweb_get_feature { @@ -553,15 +594,18 @@ sub filter_snapshot_fmts { !$known_snapshot_formats{$_}{'disabled'}} @fmts; } -our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; -our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; -# die if there are errors parsing config file -if (-e $GITWEB_CONFIG) { - do $GITWEB_CONFIG; - die $@ if $@; -} elsif (-e $GITWEB_CONFIG_SYSTEM) { - do $GITWEB_CONFIG_SYSTEM; - die $@ if $@; +our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM); +sub evaluate_gitweb_config { + our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; + our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; + # die if there are errors parsing config file + if (-e $GITWEB_CONFIG) { + do $GITWEB_CONFIG; + die $@ if $@; + } elsif (-e $GITWEB_CONFIG_SYSTEM) { + do $GITWEB_CONFIG_SYSTEM; + die $@ if $@; + } } # Get loadavg of system, to compare against $maxload. @@ -587,13 +631,16 @@ sub get_loadavg { } # version of the core git binary -our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; -$number_of_git_cmds++; - -$projects_list ||= $projectroot; +our $git_version; +sub evaluate_git_version { + our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; + $number_of_git_cmds++; +} -if (defined $maxload && get_loadavg() > $maxload) { - die_error(503, "The load average on the server is too high"); +sub check_loadavg { + if (defined $maxload && get_loadavg() > $maxload) { + die_error(503, "The load average on the server is too high"); + } } # ====================================================================== @@ -680,11 +727,15 @@ our %allowed_options = ( # should be single values, but opt can be an array. We should probably # build an array of parameters that can be multi-valued, but since for the time # being it's only this one, we just single it out -while (my ($name, $symbol) = each %cgi_param_mapping) { - if ($symbol eq 'opt') { - $input_params{$name} = [ $cgi->param($symbol) ]; - } else { - $input_params{$name} = $cgi->param($symbol); +sub evaluate_query_params { + our $cgi; + + while (my ($name, $symbol) = each %cgi_param_mapping) { + if ($symbol eq 'opt') { + $input_params{$name} = [ $cgi->param($symbol) ]; + } else { + $input_params{$name} = $cgi->param($symbol); + } } } @@ -831,152 +882,277 @@ sub evaluate_path_info { } } } -evaluate_path_info(); -our $action = $input_params{'action'}; -if (defined $action) { - if (!validate_action($action)) { - die_error(400, "Invalid action parameter"); +our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base, + $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp, + $searchtext, $search_regexp); +sub evaluate_and_validate_params { + our $action = $input_params{'action'}; + if (defined $action) { + if (!validate_action($action)) { + die_error(400, "Invalid action parameter"); + } } -} -# parameters which are pathnames -our $project = $input_params{'project'}; -if (defined $project) { - if (!validate_project($project)) { - undef $project; - die_error(404, "No such project"); + # parameters which are pathnames + our $project = $input_params{'project'}; + if (defined $project) { + if (!validate_project($project)) { + undef $project; + die_error(404, "No such project"); + } } -} -our $file_name = $input_params{'file_name'}; -if (defined $file_name) { - if (!validate_pathname($file_name)) { - die_error(400, "Invalid file parameter"); + our $file_name = $input_params{'file_name'}; + if (defined $file_name) { + if (!validate_pathname($file_name)) { + die_error(400, "Invalid file parameter"); + } } -} -our $file_parent = $input_params{'file_parent'}; -if (defined $file_parent) { - if (!validate_pathname($file_parent)) { - die_error(400, "Invalid file parent parameter"); + our $file_parent = $input_params{'file_parent'}; + if (defined $file_parent) { + if (!validate_pathname($file_parent)) { + die_error(400, "Invalid file parent parameter"); + } } -} -# parameters which are refnames -our $hash = $input_params{'hash'}; -if (defined $hash) { - if (!validate_refname($hash)) { - die_error(400, "Invalid hash parameter"); + # parameters which are refnames + our $hash = $input_params{'hash'}; + if (defined $hash) { + if (!validate_refname($hash)) { + die_error(400, "Invalid hash parameter"); + } } -} -our $hash_parent = $input_params{'hash_parent'}; -if (defined $hash_parent) { - if (!validate_refname($hash_parent)) { - die_error(400, "Invalid hash parent parameter"); + our $hash_parent = $input_params{'hash_parent'}; + if (defined $hash_parent) { + if (!validate_refname($hash_parent)) { + die_error(400, "Invalid hash parent parameter"); + } } -} -our $hash_base = $input_params{'hash_base'}; -if (defined $hash_base) { - if (!validate_refname($hash_base)) { - die_error(400, "Invalid hash base parameter"); + our $hash_base = $input_params{'hash_base'}; + if (defined $hash_base) { + if (!validate_refname($hash_base)) { + die_error(400, "Invalid hash base parameter"); + } } -} -our @extra_options = @{$input_params{'extra_options'}}; -# @extra_options is always defined, since it can only be (currently) set from -# CGI, and $cgi->param() returns the empty array in array context if the param -# is not set -foreach my $opt (@extra_options) { - if (not exists $allowed_options{$opt}) { - die_error(400, "Invalid option parameter"); - } - if (not grep(/^$action$/, @{$allowed_options{$opt}})) { - die_error(400, "Invalid option parameter for this action"); + our @extra_options = @{$input_params{'extra_options'}}; + # @extra_options is always defined, since it can only be (currently) set from + # CGI, and $cgi->param() returns the empty array in array context if the param + # is not set + foreach my $opt (@extra_options) { + if (not exists $allowed_options{$opt}) { + die_error(400, "Invalid option parameter"); + } + if (not grep(/^$action$/, @{$allowed_options{$opt}})) { + die_error(400, "Invalid option parameter for this action"); + } } -} -our $hash_parent_base = $input_params{'hash_parent_base'}; -if (defined $hash_parent_base) { - if (!validate_refname($hash_parent_base)) { - die_error(400, "Invalid hash parent base parameter"); + our $hash_parent_base = $input_params{'hash_parent_base'}; + if (defined $hash_parent_base) { + if (!validate_refname($hash_parent_base)) { + die_error(400, "Invalid hash parent base parameter"); + } } -} -# other parameters -our $page = $input_params{'page'}; -if (defined $page) { - if ($page =~ m/[^0-9]/) { - die_error(400, "Invalid page parameter"); + # other parameters + our $page = $input_params{'page'}; + if (defined $page) { + if ($page =~ m/[^0-9]/) { + die_error(400, "Invalid page parameter"); + } } -} -our $searchtype = $input_params{'searchtype'}; -if (defined $searchtype) { - if ($searchtype =~ m/[^a-z]/) { - die_error(400, "Invalid searchtype parameter"); + our $searchtype = $input_params{'searchtype'}; + if (defined $searchtype) { + if ($searchtype =~ m/[^a-z]/) { + die_error(400, "Invalid searchtype parameter"); + } } -} -our $search_use_regexp = $input_params{'search_use_regexp'}; + our $search_use_regexp = $input_params{'search_use_regexp'}; -our $searchtext = $input_params{'searchtext'}; -our $search_regexp; -if (defined $searchtext) { - if (length($searchtext) < 2) { - die_error(403, "At least two characters are required for search parameter"); + our $searchtext = $input_params{'searchtext'}; + our $search_regexp; + if (defined $searchtext) { + if (length($searchtext) < 2) { + die_error(403, "At least two characters are required for search parameter"); + } + $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext; } - $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext; } # path to the current git repository our $git_dir; -$git_dir = "$projectroot/$project" if $project; - -# list of supported snapshot formats -our @snapshot_fmts = gitweb_get_feature('snapshot'); -@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts); - -# check that the avatar feature is set to a known provider name, -# and for each provider check if the dependencies are satisfied. -# if the provider name is invalid or the dependencies are not met, -# reset $git_avatar to the empty string. -our ($git_avatar) = gitweb_get_feature('avatar'); -if ($git_avatar eq 'gravatar') { - $git_avatar = '' unless (eval { require Digest::MD5; 1; }); -} elsif ($git_avatar eq 'picon') { - # no dependencies -} else { - $git_avatar = ''; +sub evaluate_git_dir { + our $git_dir = "$projectroot/$project" if $project; } -# dispatch -if (!defined $action) { - if (defined $hash) { - $action = git_get_type($hash); - } elsif (defined $hash_base && defined $file_name) { - $action = git_get_type("$hash_base:$file_name"); - } elsif (defined $project) { - $action = 'summary'; +our (@snapshot_fmts, $git_avatar); +sub configure_gitweb_features { + # list of supported snapshot formats + our @snapshot_fmts = gitweb_get_feature('snapshot'); + @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts); + + # check that the avatar feature is set to a known provider name, + # and for each provider check if the dependencies are satisfied. + # if the provider name is invalid or the dependencies are not met, + # reset $git_avatar to the empty string. + our ($git_avatar) = gitweb_get_feature('avatar'); + if ($git_avatar eq 'gravatar') { + $git_avatar = '' unless (eval { require Digest::MD5; 1; }); + } elsif ($git_avatar eq 'picon') { + # no dependencies } else { - $action = 'project_list'; + $git_avatar = ''; + } +} + +# custom error handler: 'die <message>' is Internal Server Error +sub handle_errors_html { + my $msg = shift; # it is already HTML escaped + + # to avoid infinite loop where error occurs in die_error, + # change handler to default handler, disabling handle_errors_html + set_message("Error occured when inside die_error:\n$msg"); + + # you cannot jump out of die_error when called as error handler; + # the subroutine set via CGI::Carp::set_message is called _after_ + # HTTP headers are already written, so it cannot write them itself + die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1); +} +set_message(\&handle_errors_html); + +# dispatch +sub dispatch { + if (!defined $action) { + if (defined $hash) { + $action = git_get_type($hash); + } elsif (defined $hash_base && defined $file_name) { + $action = git_get_type("$hash_base:$file_name"); + } elsif (defined $project) { + $action = 'summary'; + } else { + $action = 'project_list'; + } + } + if (!defined($actions{$action})) { + die_error(400, "Unknown action"); + } + if ($action !~ m/^(?:opml|project_list|project_index)$/ && + !$project) { + die_error(400, "Project needed"); } + $actions{$action}->(); } -if (!defined($actions{$action})) { - die_error(400, "Unknown action"); + +sub reset_timer { + our $t0 = [Time::HiRes::gettimeofday()] + if defined $t0; + our $number_of_git_cmds = 0; } -if ($action !~ m/^(?:opml|project_list|project_index)$/ && - !$project) { - die_error(400, "Project needed"); + +sub run_request { + reset_timer(); + + evaluate_uri(); + evaluate_gitweb_config(); + check_loadavg(); + + # $projectroot and $projects_list might be set in gitweb config file + $projects_list ||= $projectroot; + + evaluate_query_params(); + evaluate_path_info(); + evaluate_and_validate_params(); + evaluate_git_dir(); + + configure_gitweb_features(); + + dispatch(); +} + +our $is_last_request = sub { 1 }; +our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook); +our $CGI = 'CGI'; +our $cgi; +sub configure_as_fcgi { + require CGI::Fast; + our $CGI = 'CGI::Fast'; + + my $request_number = 0; + # let each child service 100 requests + our $is_last_request = sub { ++$request_number > 100 }; +} +sub evaluate_argv { + my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__; + configure_as_fcgi() + if $script_name =~ /\.fcgi$/; + + return unless (@ARGV); + + require Getopt::Long; + Getopt::Long::GetOptions( + 'fastcgi|fcgi|f' => \&configure_as_fcgi, + 'nproc|n=i' => sub { + my ($arg, $val) = @_; + return unless eval { require FCGI::ProcManager; 1; }; + my $proc_manager = FCGI::ProcManager->new({ + n_processes => $val, + }); + our $pre_listen_hook = sub { $proc_manager->pm_manage() }; + our $pre_dispatch_hook = sub { $proc_manager->pm_pre_dispatch() }; + our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() }; + }, + ); +} + +sub run { + evaluate_argv(); + evaluate_git_version(); + + $pre_listen_hook->() + if $pre_listen_hook; + + REQUEST: + while ($cgi = $CGI->new()) { + $pre_dispatch_hook->() + if $pre_dispatch_hook; + + run_request(); + + $post_dispatch_hook->() + if $post_dispatch_hook; + + last REQUEST if ($is_last_request->()); + } + + DONE_GITWEB: + 1; +} + +run(); + +if (defined caller) { + # wrapped in a subroutine processing requests, + # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI + return; +} else { + # pure CGI script, serving single request + exit; } -$actions{$action}->(); -exit; ## ====================================================================== ## action links +# possible values of extra options +# -full => 0|1 - use absolute/full URL ($my_uri/$my_url as base) +# -replay => 1 - start from a current view (replay with modifications) +# -path_info => 0|1 - don't use/use path_info URL (if possible) sub href { my %params = @_; # default is to use -absolute url() i.e. $my_uri @@ -993,7 +1169,8 @@ sub href { } my $use_pathinfo = gitweb_check_feature('pathinfo'); - if ($use_pathinfo and defined $params{'project'}) { + if (defined $params{'project'} && + (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) { # try to put as many parameters as possible in PATH_INFO: # - project name # - action @@ -1150,6 +1327,7 @@ sub validate_refname { # in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning sub to_utf8 { my $str = shift; + return undef unless defined $str; if (utf8::valid($str)) { utf8::decode($str); return $str; @@ -1162,6 +1340,7 @@ sub to_utf8 { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; + return undef unless defined $str; $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg; $str =~ s/ /\+/g; return $str; @@ -1170,8 +1349,8 @@ sub esc_param { # quote unsafe chars in whole URL, so some charactrs cannot be quoted sub esc_url { my $str = shift; - $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; - $str =~ s/\+/%2B/g; + return undef unless defined $str; + $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg; $str =~ s/ /\+/g; return $str; } @@ -1181,6 +1360,8 @@ sub esc_html { my $str = shift; my %opts = @_; + return undef unless defined $str; + $str = to_utf8($str); $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { @@ -1195,6 +1376,8 @@ sub esc_path { my $str = shift; my %opts = @_; + return undef unless defined $str; + $str = to_utf8($str); $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { @@ -2413,6 +2596,9 @@ sub git_get_projects_list { follow_skip => 2, # ignore duplicates dangling_symlinks => 0, # ignore dangling symlinks, silently wanted => sub { + # global variables + our $project_maxdepth; + our $projectroot; # skip project-list toplevel, if we get it. return if (m!^[/.]$!); # only directories can be git repositories @@ -3148,26 +3334,64 @@ sub blob_contenttype { return $type; } +# guess file syntax for syntax highlighting; return undef if no highlighting +# the name of syntax can (in the future) depend on syntax highlighter used +sub guess_file_syntax { + my ($highlight, $mimetype, $file_name) = @_; + return undef unless ($highlight && defined $file_name); + my $basename = basename($file_name, '.in'); + return $highlight_basename{$basename} + if exists $highlight_basename{$basename}; + + $basename =~ /\.([^.]*)$/; + my $ext = $1 or return undef; + return $highlight_ext{$ext} + if exists $highlight_ext{$ext}; + + return undef; +} + +# run highlighter and return FD of its output, +# or return original FD if no highlighting +sub run_highlighter { + my ($fd, $highlight, $syntax) = @_; + return $fd unless ($highlight && defined $syntax); + + close $fd + or die_error(404, "Reading blob failed"); + open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". + "highlight --xhtml --fragment --syntax $syntax |" + or die_error(500, "Couldn't open file or run syntax highlighter"); + return $fd; +} + ## ====================================================================== ## functions printing HTML: header, footer, error page +sub get_page_title { + my $title = to_utf8($site_name); + + return $title unless (defined $project); + $title .= " - " . to_utf8($project); + + return $title unless (defined $action); + $title .= "/$action"; # $action is US-ASCII (7bit ASCII) + + return $title unless (defined $file_name); + $title .= " - " . esc_path($file_name); + if ($action eq "tree" && $file_name !~ m|/$|) { + $title .= "/"; + } + + return $title; +} + sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; + my %opts = @_; - my $title = "$site_name"; - if (defined $project) { - $title .= " - " . to_utf8($project); - if (defined $action) { - $title .= "/$action"; - if (defined $file_name) { - $title .= " - " . esc_path($file_name); - if ($action eq "tree" && $file_name !~ m|/$|) { - $title .= "/"; - } - } - } - } + my $title = get_page_title(); my $content_type; # require explicit support from the UA if we are to send the page as # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. @@ -3181,7 +3405,8 @@ sub git_header_html { $content_type = 'text/html'; } print $cgi->header(-type=>$content_type, -charset => 'utf-8', - -status=> $status, -expires => $expires); + -status=> $status, -expires => $expires) + unless ($opts{'-no_http_header'}); my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : ''; print <<EOF; <?xml version="1.0" encoding="utf-8"?> @@ -3381,7 +3606,7 @@ sub git_footer_html { "</html>"; } -# die_error(<http_status_code>, <error_message>) +# die_error(<http_status_code>, <error_message>[, <detailed_html_description>]) # Example: die_error(404, 'Hash not found') # By convention, use the following status codes (as defined in RFC 2616): # 400: Invalid or missing CGI parameters, or @@ -3396,8 +3621,9 @@ sub git_footer_html { # or down for maintenance). Generally, this is a temporary state. sub die_error { my $status = shift || 500; - my $error = shift || "Internal server error"; + my $error = esc_html(shift) || "Internal Server Error"; my $extra = shift; + my %opts = @_; my %http_responses = ( 400 => '400 Bad Request', @@ -3406,7 +3632,7 @@ sub die_error { 500 => '500 Internal Server Error', 503 => '503 Service Unavailable', ); - git_header_html($http_responses{$status}); + git_header_html($http_responses{$status}, undef, %opts); print <<EOF; <div class="page_body"> <br /><br /> @@ -3420,7 +3646,8 @@ EOF print "</div>\n"; git_footer_html(); - exit; + goto DONE_GITWEB + unless ($opts{'-error_handler'}); } ## ---------------------------------------------------------------------- @@ -5339,6 +5566,7 @@ sub git_blob { open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(500, "Couldn't cat $file_name, $hash"); my $mimetype = blob_mimetype($fd, $file_name); + # use 'blob_plain' (aka 'raw') view for files that cannot be displayed if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) { close $fd; return git_blob_plain($mimetype); @@ -5346,6 +5574,11 @@ sub git_blob { # we can have blame only for text/* mimetype $have_blame &&= ($mimetype =~ m!^text/!); + my $highlight = gitweb_check_feature('highlight'); + my $syntax = guess_file_syntax($highlight, $mimetype, $file_name); + $fd = run_highlighter($fd, $highlight, $syntax) + if $syntax; + git_header_html(undef, $expires); my $formats_nav = ''; if (defined $hash_base && (my %co = parse_commit($hash_base))) { @@ -5395,9 +5628,8 @@ sub git_blob { chomp $line; $nr++; $line = untabify($line); - printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1) - . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n", - $nr, $nr, $nr, esc_html($line, -nbsp=>1); + printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!, + $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1); } } close $fd @@ -6110,8 +6342,8 @@ sub git_commitdiff { } push @commit_spec, '--root', $hash; } - open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8', - '--stdout', @commit_spec + open $fd, "-|", git_cmd(), "format-patch", @diff_opts, + '--encoding=utf8', '--stdout', @commit_spec or die_error(500, "Open git-format-patch failed"); } else { die_error(400, "Unknown commitdiff format"); @@ -6289,12 +6521,13 @@ sub git_search { $paging_nav .= " ⋅ next"; } - if ($#commitlist >= 100) { - } - git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash); - git_search_grep_body(\@commitlist, 0, 99, $next_link); + if ($page == 0 && !@commitlist) { + print "<p>No match.</p>\n"; + } else { + git_search_grep_body(\@commitlist, 0, 99, $next_link); + } } if ($searchtype eq 'pickaxe') { diff --git a/gitweb/git-favicon.png b/gitweb/static/git-favicon.png Binary files differindex aae35a70e7..aae35a70e7 100644 --- a/gitweb/git-favicon.png +++ b/gitweb/static/git-favicon.png diff --git a/gitweb/git-logo.png b/gitweb/static/git-logo.png Binary files differindex f4ede2e944..f4ede2e944 100644 --- a/gitweb/git-logo.png +++ b/gitweb/static/git-logo.png diff --git a/gitweb/gitweb.css b/gitweb/static/gitweb.css index 50067f2e0d..4132aabcdb 100644 --- a/gitweb/gitweb.css +++ b/gitweb/static/gitweb.css @@ -572,3 +572,21 @@ span.match { div.binary { font-style: italic; } + +/* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */ + +/* Highlighting theme definition: */ + +.num { color:#2928ff; } +.esc { color:#ff00ff; } +.str { color:#ff0000; } +.dstr { color:#818100; } +.slc { color:#838183; font-style:italic; } +.com { color:#838183; font-style:italic; } +.dir { color:#008200; } +.sym { color:#000000; } +.line { color:#555555; } +.kwa { color:#000000; font-weight:bold; } +.kwb { color:#830000; } +.kwc { color:#000000; font-weight:bold; } +.kwd { color:#010181; } diff --git a/gitweb/gitweb.js b/gitweb/static/gitweb.js index 9c66928c4a..9c66928c4a 100644 --- a/gitweb/gitweb.js +++ b/gitweb/static/gitweb.js @@ -80,12 +80,12 @@ static char column_colors[][COLOR_MAXLEN] = { GIT_COLOR_BLUE, GIT_COLOR_MAGENTA, GIT_COLOR_CYAN, - GIT_COLOR_BOLD GIT_COLOR_RED, - GIT_COLOR_BOLD GIT_COLOR_GREEN, - GIT_COLOR_BOLD GIT_COLOR_YELLOW, - GIT_COLOR_BOLD GIT_COLOR_BLUE, - GIT_COLOR_BOLD GIT_COLOR_MAGENTA, - GIT_COLOR_BOLD GIT_COLOR_CYAN, + GIT_COLOR_BOLD_RED, + GIT_COLOR_BOLD_GREEN, + GIT_COLOR_BOLD_YELLOW, + GIT_COLOR_BOLD_BLUE, + GIT_COLOR_BOLD_MAGENTA, + GIT_COLOR_BOLD_CYAN, }; #define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) @@ -211,6 +211,18 @@ struct git_graph { unsigned short default_column_color; }; +static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void *data) +{ + struct git_graph *graph = data; + static struct strbuf msgbuf = STRBUF_INIT; + + assert(graph); + + strbuf_reset(&msgbuf); + graph_padding_line(graph, &msgbuf); + return &msgbuf; +} + struct git_graph *graph_init(struct rev_info *opt) { struct git_graph *graph = xmalloc(sizeof(struct git_graph)); @@ -244,6 +256,13 @@ struct git_graph *graph_init(struct rev_info *opt) graph->mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity); graph->new_mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity); + /* + * The diff output prefix callback, with this we can make + * all the diff output to align with the graph lines. + */ + opt->diffopt.output_prefix = diff_output_prefix_callback; + opt->diffopt.output_prefix_data = graph; + return graph; } @@ -7,6 +7,7 @@ void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field fie { struct grep_pat *p = xcalloc(1, sizeof(*p)); p->pattern = pat; + p->patternlen = strlen(pat); p->origin = "header"; p->no = 0; p->token = GREP_PATTERN_HEAD; @@ -19,8 +20,15 @@ void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field fie void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t) { + append_grep_pat(opt, pat, strlen(pat), origin, no, t); +} + +void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, + const char *origin, int no, enum grep_pat_token t) +{ struct grep_pat *p = xcalloc(1, sizeof(*p)); p->pattern = pat; + p->patternlen = patlen; p->origin = origin; p->no = no; p->token = t; @@ -44,8 +52,8 @@ struct grep_opt *grep_opt_dup(const struct grep_opt *opt) append_header_grep_pattern(ret, pat->field, pat->pattern); else - append_grep_pattern(ret, pat->pattern, pat->origin, - pat->no, pat->token); + append_grep_pat(ret, pat->pattern, pat->patternlen, + pat->origin, pat->no, pat->token); } return ret; @@ -304,20 +312,46 @@ static int word_char(char ch) return isalnum(ch) || ch == '_'; } +static void output_color(struct grep_opt *opt, const void *data, size_t size, + const char *color) +{ + if (opt->color && color && color[0]) { + opt->output(opt, color, strlen(color)); + opt->output(opt, data, size); + opt->output(opt, GIT_COLOR_RESET, strlen(GIT_COLOR_RESET)); + } else + opt->output(opt, data, size); +} + +static void output_sep(struct grep_opt *opt, char sign) +{ + if (opt->null_following_name) + opt->output(opt, "\0", 1); + else + output_color(opt, &sign, 1, opt->color_sep); +} + static void show_name(struct grep_opt *opt, const char *name) { - opt->output(opt, name, strlen(name)); + output_color(opt, name, strlen(name), opt->color_filename); opt->output(opt, opt->null_following_name ? "\0" : "\n", 1); } - -static int fixmatch(const char *pattern, char *line, int ignore_case, regmatch_t *match) +static int fixmatch(struct grep_pat *p, char *line, char *eol, + regmatch_t *match) { char *hit; - if (ignore_case) - hit = strcasestr(line, pattern); - else - hit = strstr(line, pattern); + + if (p->ignore_case) { + char *s = line; + do { + hit = strcasestr(s, p->pattern); + if (hit) + break; + s += strlen(s) + 1; + } while (s < eol); + } else + hit = memmem(line, eol - line, p->pattern, p->patternlen); if (!hit) { match->rm_so = match->rm_eo = -1; @@ -325,11 +359,22 @@ static int fixmatch(const char *pattern, char *line, int ignore_case, regmatch_t } else { match->rm_so = hit - line; - match->rm_eo = match->rm_so + strlen(pattern); + match->rm_eo = match->rm_so + p->patternlen; return 0; } } +static int regmatch(const regex_t *preg, char *line, char *eol, + regmatch_t *match, int eflags) +{ +#ifdef REG_STARTEND + match->rm_so = 0; + match->rm_eo = eol - line; + eflags |= REG_STARTEND; +#endif + return regexec(preg, line, 1, match, eflags); +} + static int strip_timestamp(char *bol, char **eol_p) { char *eol = *eol_p; @@ -380,9 +425,9 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, again: if (p->fixed) - hit = !fixmatch(p->pattern, bol, p->ignore_case, pmatch); + hit = !fixmatch(p, bol, eol, pmatch); else - hit = !regexec(&p->regexp, bol, 1, pmatch, eflags); + hit = !regmatch(&p->regexp, bol, eol, pmatch, eflags); if (hit && p->word_regexp) { if ((pmatch[0].rm_so < 0) || @@ -544,31 +589,30 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, const char *name, unsigned lno, char sign) { int rest = eol - bol; - char sign_str[1]; + char *line_color = NULL; - sign_str[0] = sign; if (opt->pre_context || opt->post_context) { if (opt->last_shown == 0) { - if (opt->show_hunk_mark) - opt->output(opt, "--\n", 3); - else - opt->show_hunk_mark = 1; - } else if (lno > opt->last_shown + 1) - opt->output(opt, "--\n", 3); + if (opt->show_hunk_mark) { + output_color(opt, "--", 2, opt->color_sep); + opt->output(opt, "\n", 1); + } + } else if (lno > opt->last_shown + 1) { + output_color(opt, "--", 2, opt->color_sep); + opt->output(opt, "\n", 1); + } } opt->last_shown = lno; - if (opt->null_following_name) - sign_str[0] = '\0'; if (opt->pathname) { - opt->output(opt, name, strlen(name)); - opt->output(opt, sign_str, 1); + output_color(opt, name, strlen(name), opt->color_filename); + output_sep(opt, sign); } if (opt->linenum) { char buf[32]; snprintf(buf, sizeof(buf), "%d", lno); - opt->output(opt, buf, strlen(buf)); - opt->output(opt, sign_str, 1); + output_color(opt, buf, strlen(buf), opt->color_lineno); + output_sep(opt, sign); } if (opt->color) { regmatch_t match; @@ -576,25 +620,28 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, int ch = *eol; int eflags = 0; + if (sign == ':') + line_color = opt->color_selected; + else if (sign == '-') + line_color = opt->color_context; + else if (sign == '=') + line_color = opt->color_function; *eol = '\0'; while (next_match(opt, bol, eol, ctx, &match, eflags)) { if (match.rm_so == match.rm_eo) break; - opt->output(opt, bol, match.rm_so); - opt->output(opt, opt->color_match, - strlen(opt->color_match)); - opt->output(opt, bol + match.rm_so, - (int)(match.rm_eo - match.rm_so)); - opt->output(opt, GIT_COLOR_RESET, - strlen(GIT_COLOR_RESET)); + output_color(opt, bol, match.rm_so, line_color); + output_color(opt, bol + match.rm_so, + match.rm_eo - match.rm_so, + opt->color_match); bol += match.rm_eo; rest -= match.rm_eo; eflags = REG_NOTBOL; } *eol = ch; } - opt->output(opt, bol, rest); + output_color(opt, bol, rest, line_color); opt->output(opt, "\n", 1); } @@ -705,16 +752,9 @@ static int look_ahead(struct grep_opt *opt, regmatch_t m; if (p->fixed) - hit = !fixmatch(p->pattern, bol, p->ignore_case, &m); - else { -#ifdef REG_STARTEND - m.rm_so = 0; - m.rm_eo = *left_p; - hit = !regexec(&p->regexp, bol, 1, &m, REG_STARTEND); -#else - hit = !regexec(&p->regexp, bol, 1, &m, 0); -#endif - } + hit = !fixmatch(p, bol, bol + *left_p, &m); + else + hit = !regmatch(&p->regexp, bol, bol + *left_p, &m, 0); if (!hit || m.rm_so < 0 || m.rm_eo < 0) continue; if (earliest < 0 || m.rm_so < earliest) @@ -750,14 +790,6 @@ int grep_threads_ok(const struct grep_opt *opt) !opt->name_only) return 0; - /* If we are showing hunk marks, we should not do it for the - * first match. The synchronization problem we get for this - * constraint is not yet solved, so we disable threading in - * this case. - */ - if (opt->pre_context || opt->post_context) - return 0; - return 1; } @@ -779,22 +811,27 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, enum grep_context ctx = GREP_CONTEXT_HEAD; xdemitconf_t xecfg; - opt->last_shown = 0; - if (!opt->output) opt->output = std_output; - if (buffer_is_binary(buf, size)) { - switch (opt->binary) { - case GREP_BINARY_DEFAULT: + if (opt->last_shown && (opt->pre_context || opt->post_context) && + opt->output == std_output) + opt->show_hunk_mark = 1; + opt->last_shown = 0; + + switch (opt->binary) { + case GREP_BINARY_DEFAULT: + if (buffer_is_binary(buf, size)) binary_match_only = 1; - break; - case GREP_BINARY_NOMATCH: + break; + case GREP_BINARY_NOMATCH: + if (buffer_is_binary(buf, size)) return 0; /* Assume unmatch */ - break; - default: - break; - } + break; + case GREP_BINARY_TEXT: + break; + default: + die("bug: unknown binary handling mode"); } memset(&xecfg, 0, sizeof(xecfg)); @@ -855,28 +892,27 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, count++; if (opt->status_only) return 1; + if (opt->name_only) { + show_name(opt, name); + return 1; + } + if (opt->count) + goto next_line; if (binary_match_only) { opt->output(opt, "Binary file ", 12); - opt->output(opt, name, strlen(name)); + output_color(opt, name, strlen(name), + opt->color_filename); opt->output(opt, " matches\n", 9); return 1; } - if (opt->name_only) { - show_name(opt, name); - return 1; - } /* Hit at this line. If we haven't shown the * pre-context lines, we would need to show them. - * When asked to do "count", this still show - * the context which is nonsense, but the user - * deserves to get that ;-). */ if (opt->pre_context) show_pre_context(opt, name, buf, bol, lno); else if (opt->funcname) show_funcname_line(opt, name, buf, bol, lno); - if (!opt->count) - show_line(opt, bol, eol, name, lno, ':'); + show_line(opt, bol, eol, name, lno, ':'); last_hit = lno; } else if (last_hit && @@ -916,10 +952,11 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, */ if (opt->count && count) { char buf[32]; - opt->output(opt, name, strlen(name)); - snprintf(buf, sizeof(buf), "%c%u\n", - opt->null_following_name ? '\0' : ':', count); + output_color(opt, name, strlen(name), opt->color_filename); + output_sep(opt, ':'); + snprintf(buf, sizeof(buf), "%u\n", count); opt->output(opt, buf, strlen(buf)); + return 1; } return !!last_hit; } @@ -10,17 +10,17 @@ enum grep_pat_token { GREP_OPEN_PAREN, GREP_CLOSE_PAREN, GREP_NOT, - GREP_OR, + GREP_OR }; enum grep_context { GREP_CONTEXT_HEAD, - GREP_CONTEXT_BODY, + GREP_CONTEXT_BODY }; enum grep_header_field { GREP_HEADER_AUTHOR = 0, - GREP_HEADER_COMMITTER, + GREP_HEADER_COMMITTER }; struct grep_pat { @@ -29,6 +29,7 @@ struct grep_pat { int no; enum grep_pat_token token; const char *pattern; + size_t patternlen; enum grep_header_field field; regex_t regexp; unsigned fixed:1; @@ -40,7 +41,7 @@ enum grep_expr_node { GREP_NODE_ATOM, GREP_NODE_NOT, GREP_NODE_AND, - GREP_NODE_OR, + GREP_NODE_OR }; struct grep_expr { @@ -86,7 +87,13 @@ struct grep_opt { int color; int max_depth; int funcname; + char color_context[COLOR_MAXLEN]; + char color_filename[COLOR_MAXLEN]; + char color_function[COLOR_MAXLEN]; + char color_lineno[COLOR_MAXLEN]; char color_match[COLOR_MAXLEN]; + char color_selected[COLOR_MAXLEN]; + char color_sep[COLOR_MAXLEN]; int regflags; unsigned pre_context; unsigned post_context; @@ -98,6 +105,7 @@ struct grep_opt { void *output_priv; }; +extern void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t); extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *); extern void compile_grep_patterns(struct grep_opt *opt); diff --git a/http-backend.c b/http-backend.c index d1e83d0906..14c90c2e84 100644 --- a/http-backend.c +++ b/http-backend.c @@ -6,6 +6,7 @@ #include "exec_cmd.h" #include "run-command.h" #include "string-list.h" +#include "url.h" static const char content_type[] = "Content-Type"; static const char content_length[] = "Content-Length"; @@ -25,60 +26,6 @@ static struct rpc_service rpc_service[] = { { "receive-pack", "receivepack", -1 }, }; -static int decode_char(const char *q) -{ - int i; - unsigned char val = 0; - for (i = 0; i < 2; i++) { - unsigned char c = *q++; - val <<= 4; - if (c >= '0' && c <= '9') - val += c - '0'; - else if (c >= 'a' && c <= 'f') - val += c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - val += c - 'A' + 10; - else - return -1; - } - return val; -} - -static char *decode_parameter(const char **query, int is_name) -{ - const char *q = *query; - struct strbuf out; - - strbuf_init(&out, 16); - do { - unsigned char c = *q; - - if (!c) - break; - if (c == '&' || (is_name && c == '=')) { - q++; - break; - } - - if (c == '%') { - int val = decode_char(q + 1); - if (0 <= val) { - strbuf_addch(&out, val); - q += 3; - continue; - } - } - - if (c == '+') - strbuf_addch(&out, ' '); - else - strbuf_addch(&out, c); - q++; - } while (1); - *query = q; - return strbuf_detach(&out, NULL); -} - static struct string_list *get_parameters(void) { if (!query_params) { @@ -86,13 +33,13 @@ static struct string_list *get_parameters(void) query_params = xcalloc(1, sizeof(*query_params)); while (query && *query) { - char *name = decode_parameter(&query, 1); - char *value = decode_parameter(&query, 0); + char *name = url_decode_parameter_name(&query); + char *value = url_decode_parameter_value(&query); struct string_list_item *i; - i = string_list_lookup(name, query_params); + i = string_list_lookup(query_params, name); if (!i) - i = string_list_insert(name, query_params); + i = string_list_insert(query_params, name); else free(i->util); i->util = value; @@ -104,7 +51,7 @@ static struct string_list *get_parameters(void) static const char *get_parameter(const char *name) { struct string_list_item *i; - i = string_list_lookup(name, get_parameters()); + i = string_list_lookup(get_parameters(), name); return i ? i->util : NULL; } @@ -541,14 +488,12 @@ static NORETURN void die_webcgi(const char *err, va_list params) static int dead; if (!dead) { - char buffer[1000]; dead = 1; - - vsnprintf(buffer, sizeof(buffer), err, params); - fprintf(stderr, "fatal: %s\n", buffer); http_status(500, "Internal Server Error"); hdr_nocache(); end_headers(); + + vreportf("fatal: ", err, params); } exit(0); /* we successfully reported a failure ;-) */ } diff --git a/http-fetch.c b/http-fetch.c index ffd0ad7e29..762c750d7a 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -1,5 +1,6 @@ #include "cache.h" #include "exec_cmd.h" +#include "http.h" #include "walker.h" static const char http_fetch_usage[] = "git http-fetch " @@ -69,7 +70,8 @@ int main(int argc, const char **argv) url = rewritten_url; } - walker = get_http_walker(url, NULL); + http_init(NULL); + walker = get_http_walker(url); walker->get_tree = get_tree; walker->get_history = get_history; walker->get_all = get_all; @@ -89,6 +91,7 @@ int main(int argc, const char **argv) } walker_free(walker); + http_cleanup(); free(rewritten_url); diff --git a/http-push.c b/http-push.c index 432b20f2d9..c9bcd11697 100644 --- a/http-push.c +++ b/http-push.c @@ -105,7 +105,7 @@ enum transfer_state { RUN_PUT, RUN_MOVE, ABORTED, - COMPLETE, + COMPLETE }; struct transfer_request @@ -1965,7 +1965,7 @@ int main(int argc, char **argv) } if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { - if (push_verbosely || 1) + if (push_verbosely) fprintf(stderr, "'%s': up-to-date\n", ref->name); if (helper_status) printf("ok %s up to date\n", ref->name); diff --git a/http-walker.c b/http-walker.c index 700bc13112..18bd6504be 100644 --- a/http-walker.c +++ b/http-walker.c @@ -15,7 +15,7 @@ enum object_request_state { WAITING, ABORTED, ACTIVE, - COMPLETE, + COMPLETE }; struct object_request @@ -510,7 +510,7 @@ static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned c ret = error("File %s has bad hash", hex); } else if (req->rename < 0) { ret = error("unable to write sha1 filename %s", - req->filename); + sha1_file_name(req->sha1)); } release_http_object_request(req); @@ -543,17 +543,30 @@ static int fetch_ref(struct walker *walker, struct ref *ref) static void cleanup(struct walker *walker) { - http_cleanup(); + struct walker_data *data = walker->data; + struct alt_base *alt, *alt_next; + + if (data) { + alt = data->alt; + while (alt) { + alt_next = alt->next; + + free(alt->base); + free(alt); + + alt = alt_next; + } + free(data); + walker->data = NULL; + } } -struct walker *get_http_walker(const char *url, struct remote *remote) +struct walker *get_http_walker(const char *url) { char *s; struct walker_data *data = xmalloc(sizeof(struct walker_data)); struct walker *walker = xmalloc(sizeof(struct walker)); - http_init(remote); - data->alt = xmalloc(sizeof(*data->alt)); data->alt->base = xmalloc(strlen(url) + 1); strcpy(data->alt->base, url); @@ -1,6 +1,7 @@ #include "http.h" #include "pack.h" #include "sideband.h" +#include "run-command.h" int data_received; int active_requests; @@ -204,7 +205,7 @@ static void init_curl_http_auth(CURL *result) if (user_name) { struct strbuf up = STRBUF_INIT; if (!user_pass) - user_pass = xstrdup(getpass("Password: ")); + user_pass = xstrdup(git_getpass("Password: ")); strbuf_addf(&up, "%s:%s", user_name, user_pass); curl_easy_setopt(result, CURLOPT_USERPWD, strbuf_detach(&up, NULL)); @@ -219,7 +220,7 @@ static int has_cert_password(void) return 0; /* Only prompt the user once. */ ssl_cert_password_required = -1; - ssl_cert_password = getpass("Certificate Password: "); + ssl_cert_password = git_getpass("Certificate Password: "); if (ssl_cert_password != NULL) { ssl_cert_password = xstrdup(ssl_cert_password); return 1; @@ -720,7 +721,7 @@ static inline int hex(int v) return 'A' + v - 10; } -static void end_url_with_slash(struct strbuf *buf, const char *url) +void end_url_with_slash(struct strbuf *buf, const char *url) { strbuf_addstr(buf, url); if (buf->len && buf->buf[buf->len - 1] != '/') @@ -815,7 +816,21 @@ static int http_request(const char *url, void *result, int target, int options) ret = HTTP_OK; else if (missing_target(&results)) ret = HTTP_MISSING_TARGET; - else + else if (results.http_code == 401) { + if (user_name) { + 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: ")); + init_curl_http_auth(slot->curl); + ret = HTTP_REAUTH; + } + } else ret = HTTP_ERROR; } else { error("Unable to start HTTP request for %s", url); @@ -831,7 +846,11 @@ static int http_request(const char *url, void *result, int target, int options) int http_get_strbuf(const char *url, struct strbuf *result, int options) { - return http_request(url, result, HTTP_REQUEST_STRBUF, 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; } /* @@ -896,47 +915,67 @@ int http_fetch_ref(const char *base, struct ref *ref) } /* Helpers for fetching packs */ -static int fetch_pack_index(unsigned char *sha1, const char *base_url) +static char *fetch_pack_index(unsigned char *sha1, const char *base_url) { - int ret = 0; - char *hex = xstrdup(sha1_to_hex(sha1)); - char *filename; - char *url = NULL; + char *url, *tmp; struct strbuf buf = STRBUF_INIT; - if (has_pack_index(sha1)) { - ret = 0; - goto cleanup; - } - if (http_is_verbose) - fprintf(stderr, "Getting index for pack %s\n", hex); + fprintf(stderr, "Getting index for pack %s\n", sha1_to_hex(sha1)); end_url_with_slash(&buf, base_url); - strbuf_addf(&buf, "objects/pack/pack-%s.idx", hex); + strbuf_addf(&buf, "objects/pack/pack-%s.idx", sha1_to_hex(sha1)); url = strbuf_detach(&buf, NULL); - filename = sha1_pack_index_name(sha1); - if (http_get_file(url, filename, 0) != HTTP_OK) - ret = error("Unable to get pack index %s\n", url); + strbuf_addf(&buf, "%s.temp", sha1_pack_index_name(sha1)); + tmp = strbuf_detach(&buf, NULL); + + if (http_get_file(url, tmp, 0) != HTTP_OK) { + error("Unable to get pack index %s\n", url); + free(tmp); + tmp = NULL; + } -cleanup: - free(hex); free(url); - return ret; + return tmp; } static int fetch_and_setup_pack_index(struct packed_git **packs_head, unsigned char *sha1, const char *base_url) { struct packed_git *new_pack; + char *tmp_idx = NULL; + int ret; - if (fetch_pack_index(sha1, base_url)) + if (has_pack_index(sha1)) { + new_pack = parse_pack_index(sha1, NULL); + if (!new_pack) + return -1; /* parse_pack_index() already issued error message */ + goto add_pack; + } + + tmp_idx = fetch_pack_index(sha1, base_url); + if (!tmp_idx) return -1; - new_pack = parse_pack_index(sha1); - if (!new_pack) + new_pack = parse_pack_index(sha1, tmp_idx); + if (!new_pack) { + unlink(tmp_idx); + free(tmp_idx); + return -1; /* parse_pack_index() already issued error message */ + } + + ret = verify_pack_index(new_pack); + if (!ret) { + close_pack_index(new_pack); + ret = move_temp_to_file(tmp_idx, sha1_pack_index_name(sha1)); + } + free(tmp_idx); + if (ret) + return -1; + +add_pack: new_pack->next = *packs_head; *packs_head = new_pack; return 0; @@ -1000,37 +1039,62 @@ void release_http_pack_request(struct http_pack_request *preq) int finish_http_pack_request(struct http_pack_request *preq) { - int ret; struct packed_git **lst; + struct packed_git *p = preq->target; + char *tmp_idx; + struct child_process ip; + const char *ip_argv[8]; - preq->target->pack_size = ftell(preq->packfile); - - if (preq->packfile != NULL) { - fclose(preq->packfile); - preq->packfile = NULL; - preq->slot->local = NULL; - } + close_pack_index(p); - ret = move_temp_to_file(preq->tmpfile, preq->filename); - if (ret) - return ret; + fclose(preq->packfile); + preq->packfile = NULL; + preq->slot->local = NULL; lst = preq->lst; - while (*lst != preq->target) + while (*lst != p) lst = &((*lst)->next); *lst = (*lst)->next; - if (verify_pack(preq->target)) + tmp_idx = xstrdup(preq->tmpfile); + strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"), + ".idx.temp"); + + ip_argv[0] = "index-pack"; + ip_argv[1] = "-o"; + ip_argv[2] = tmp_idx; + ip_argv[3] = preq->tmpfile; + ip_argv[4] = NULL; + + memset(&ip, 0, sizeof(ip)); + ip.argv = ip_argv; + ip.git_cmd = 1; + ip.no_stdin = 1; + ip.no_stdout = 1; + + if (run_command(&ip)) { + unlink(preq->tmpfile); + unlink(tmp_idx); + free(tmp_idx); + return -1; + } + + unlink(sha1_pack_index_name(p->sha1)); + + if (move_temp_to_file(preq->tmpfile, sha1_pack_name(p->sha1)) + || move_temp_to_file(tmp_idx, sha1_pack_index_name(p->sha1))) { + free(tmp_idx); return -1; - install_packed_git(preq->target); + } + install_packed_git(p); + free(tmp_idx); return 0; } struct http_pack_request *new_http_pack_request( struct packed_git *target, const char *base_url) { - char *filename; long prev_posn = 0; char range[RANGE_HEADER_SIZE]; struct strbuf buf = STRBUF_INIT; @@ -1045,9 +1109,8 @@ struct http_pack_request *new_http_pack_request( sha1_to_hex(target->sha1)); preq->url = strbuf_detach(&buf, NULL); - filename = sha1_pack_name(target->sha1); - snprintf(preq->filename, sizeof(preq->filename), "%s", filename); - snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", filename); + snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", + sha1_pack_name(target->sha1)); preq->packfile = fopen(preq->tmpfile, "a"); if (!preq->packfile) { error("Unable to open local file %s for pack", @@ -1082,7 +1145,6 @@ struct http_pack_request *new_http_pack_request( return preq; abort: - free(filename); free(preq->url); free(preq); return NULL; @@ -1137,7 +1199,6 @@ struct http_object_request *new_http_object_request(const char *base_url, freq->localfile = -1; filename = sha1_file_name(sha1); - snprintf(freq->filename, sizeof(freq->filename), "%s", filename); snprintf(freq->tmpfile, sizeof(freq->tmpfile), "%s.temp", filename); @@ -1166,8 +1227,8 @@ struct http_object_request *new_http_object_request(const char *base_url, } if (freq->localfile < 0) { - error("Couldn't create temporary file %s for %s: %s", - freq->tmpfile, freq->filename, strerror(errno)); + error("Couldn't create temporary file %s: %s", + freq->tmpfile, strerror(errno)); goto abort; } @@ -1214,8 +1275,8 @@ struct http_object_request *new_http_object_request(const char *base_url, prev_posn = 0; lseek(freq->localfile, 0, SEEK_SET); if (ftruncate(freq->localfile, 0) < 0) { - error("Couldn't truncate temporary file %s for %s: %s", - freq->tmpfile, freq->filename, strerror(errno)); + error("Couldn't truncate temporary file %s: %s", + freq->tmpfile, strerror(errno)); goto abort; } } @@ -1291,7 +1352,7 @@ int finish_http_object_request(struct http_object_request *freq) return -1; } freq->rename = - move_temp_to_file(freq->tmpfile, freq->filename); + move_temp_to_file(freq->tmpfile, sha1_file_name(freq->sha1)); return freq->rename; } @@ -23,10 +23,10 @@ #endif #if LIBCURL_VERSION_NUM < 0x070704 -#define curl_global_cleanup() do { /* nothing */ } while(0) +#define curl_global_cleanup() do { /* nothing */ } while (0) #endif #if LIBCURL_VERSION_NUM < 0x070800 -#define curl_global_init(a) do { /* nothing */ } while(0) +#define curl_global_init(a) do { /* nothing */ } while (0) #endif #if (LIBCURL_VERSION_NUM < 0x070c04) || (LIBCURL_VERSION_NUM == 0x071000) @@ -117,6 +117,7 @@ extern void append_remote_object_url(struct strbuf *buf, const char *url, int only_two_digit_prefix); extern char *get_remote_object_url(const char *url, const char *hex, int only_two_digit_prefix); +extern void end_url_with_slash(struct strbuf *buf, const char *url); /* Options for http_request_*() */ #define HTTP_NO_CACHE 1 @@ -126,6 +127,8 @@ extern char *get_remote_object_url(const char *url, const char *hex, #define HTTP_MISSING_TARGET 1 #define HTTP_ERROR 2 #define HTTP_START_FAILED 3 +#define HTTP_REAUTH 4 +#define HTTP_NOAUTH 5 /* * Requests an url and stores the result in a strbuf. @@ -152,7 +155,6 @@ struct http_pack_request struct packed_git *target; struct packed_git **lst; FILE *packfile; - char filename[PATH_MAX]; char tmpfile[PATH_MAX]; struct curl_slist *range_header; struct active_request_slot *slot; @@ -167,7 +169,6 @@ extern void release_http_pack_request(struct http_pack_request *preq); struct http_object_request { char *url; - char filename[PATH_MAX]; char tmpfile[PATH_MAX]; int localfile; CURLcode curl_result; diff --git a/imap-send.c b/imap-send.c index 379dec491f..71506a8dd3 100644 --- a/imap-send.c +++ b/imap-send.c @@ -27,6 +27,9 @@ #include "run-command.h" #ifdef NO_OPENSSL typedef void *SSL; +#else +#include <openssl/evp.h> +#include <openssl/hmac.h> #endif struct store_conf { @@ -139,6 +142,20 @@ struct imap_server_conf { int use_ssl; int ssl_verify; int use_html; + char *auth_method; +}; + +static struct imap_server_conf server = { + NULL, /* name */ + NULL, /* tunnel */ + NULL, /* host */ + 0, /* port */ + NULL, /* user */ + NULL, /* pass */ + 0, /* use_ssl */ + 1, /* ssl_verify */ + 0, /* use_html */ + NULL, /* auth_method */ }; struct imap_store_conf { @@ -213,6 +230,7 @@ enum CAPABILITY { LITERALPLUS, NAMESPACE, STARTTLS, + AUTH_CRAM_MD5 }; static const char *cap_list[] = { @@ -221,6 +239,7 @@ static const char *cap_list[] = { "LITERAL+", "NAMESPACE", "STARTTLS", + "AUTH=CRAM-MD5", }; #define RESP_OK 0 @@ -524,9 +543,13 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx, while (imap->literal_pending) get_cmd_result(ctx, NULL); - bufl = nfsnprintf(buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ? - "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n", - cmd->tag, cmd->cmd, cmd->cb.dlen); + if (!cmd->cb.data) + bufl = nfsnprintf(buf, sizeof(buf), "%d %s\r\n", cmd->tag, cmd->cmd); + else + bufl = nfsnprintf(buf, sizeof(buf), "%d %s{%d%s}\r\n", + cmd->tag, cmd->cmd, cmd->cb.dlen, + CAP(LITERALPLUS) ? "+" : ""); + if (Verbose) { if (imap->num_in_progress) printf("(%d in progress) ", imap->num_in_progress); @@ -948,6 +971,87 @@ static void imap_close_store(struct store *ctx) free(ctx); } +#ifndef NO_OPENSSL + +/* + * hexchar() and cram() functions are based on the code from the isync + * project (http://isync.sf.net/). + */ +static char hexchar(unsigned int b) +{ + return b < 10 ? '0' + b : 'a' + (b - 10); +} + +#define ENCODED_SIZE(n) (4*((n+2)/3)) +static char *cram(const char *challenge_64, const char *user, const char *pass) +{ + int i, resp_len, encoded_len, decoded_len; + HMAC_CTX hmac; + unsigned char hash[16]; + char hex[33]; + char *response, *response_64, *challenge; + + /* + * length of challenge_64 (i.e. base-64 encoded string) is a good + * enough upper bound for challenge (decoded result). + */ + encoded_len = strlen(challenge_64); + challenge = xmalloc(encoded_len); + decoded_len = EVP_DecodeBlock((unsigned char *)challenge, + (unsigned char *)challenge_64, encoded_len); + if (decoded_len < 0) + die("invalid challenge %s", challenge_64); + HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5()); + HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len); + HMAC_Final(&hmac, hash, NULL); + HMAC_CTX_cleanup(&hmac); + + hex[32] = 0; + for (i = 0; i < 16; i++) { + hex[2 * i] = hexchar((hash[i] >> 4) & 0xf); + hex[2 * i + 1] = hexchar(hash[i] & 0xf); + } + + /* response: "<user> <digest in hex>" */ + resp_len = strlen(user) + 1 + strlen(hex) + 1; + response = xmalloc(resp_len); + sprintf(response, "%s %s", user, hex); + + response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1); + encoded_len = EVP_EncodeBlock((unsigned char *)response_64, + (unsigned char *)response, resp_len); + if (encoded_len < 0) + die("EVP_EncodeBlock error"); + response_64[encoded_len] = '\0'; + return (char *)response_64; +} + +#else + +static char *cram(const char *challenge_64, const char *user, const char *pass) +{ + die("If you want to use CRAM-MD5 authenticate method, " + "you have to build git-imap-send with OpenSSL library."); +} + +#endif + +static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt) +{ + int ret; + char *response; + + response = cram(prompt, server.user, server.pass); + + ret = socket_write(&ctx->imap->buf.sock, response, strlen(response)); + if (ret != strlen(response)) + return error("IMAP error: sending response failed\n"); + + free(response); + + return 0; +} + static struct store *imap_open_store(struct imap_server_conf *srvc) { struct imap_store *ctx; @@ -986,7 +1090,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) int gai; char portstr[6]; - snprintf(portstr, sizeof(portstr), "%hu", srvc->port); + snprintf(portstr, sizeof(portstr), "%d", srvc->port); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; @@ -1107,7 +1211,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) if (!srvc->pass) { char prompt[80]; sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host); - arg = getpass(prompt); + arg = git_getpass(prompt); if (!arg) { perror("getpass"); exit(1); @@ -1126,12 +1230,37 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host); goto bail; } - if (!imap->buf.sock.ssl) - imap_warn("*** IMAP Warning *** Password is being " - "sent in the clear\n"); - if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) { - fprintf(stderr, "IMAP error: LOGIN failed\n"); - goto bail; + + if (srvc->auth_method) { + struct imap_cmd_cb cb; + + if (!strcmp(srvc->auth_method, "CRAM-MD5")) { + if (!CAP(AUTH_CRAM_MD5)) { + fprintf(stderr, "You specified" + "CRAM-MD5 as authentication method, " + "but %s doesn't support it.\n", srvc->host); + goto bail; + } + /* CRAM-MD5 */ + + memset(&cb, 0, sizeof(cb)); + cb.cont = auth_cram_md5; + if (imap_exec(ctx, &cb, "AUTHENTICATE CRAM-MD5") != RESP_OK) { + fprintf(stderr, "IMAP error: AUTHENTICATE CRAM-MD5 failed\n"); + goto bail; + } + } else { + fprintf(stderr, "Unknown authentication method:%s\n", srvc->host); + goto bail; + } + } else { + if (!imap->buf.sock.ssl) + imap_warn("*** IMAP Warning *** Password is being " + "sent in the clear\n"); + if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) { + fprintf(stderr, "IMAP error: LOGIN failed\n"); + goto bail; + } } } /* !preauth */ @@ -1354,18 +1483,6 @@ static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs) return 1; } -static struct imap_server_conf server = { - NULL, /* name */ - NULL, /* tunnel */ - NULL, /* host */ - 0, /* port */ - NULL, /* user */ - NULL, /* pass */ - 0, /* use_ssl */ - 1, /* ssl_verify */ - 0, /* use_html */ -}; - static char *imap_folder; static int git_imap_config(const char *key, const char *val, void *cb) @@ -1405,6 +1522,9 @@ static int git_imap_config(const char *key, const char *val, void *cb) server.port = git_config_int(key, val); else if (!strcmp("tunnel", key)) server.tunnel = xstrdup(val); + else if (!strcmp("authmethod", key)) + server.auth_method = xstrdup(val); + return 0; } diff --git a/ll-merge.c b/ll-merge.c index 4c7f11ba84..3764a1ab72 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -15,7 +15,7 @@ struct ll_merge_driver; typedef int (*ll_merge_fn)(const struct ll_merge_driver *, mmbuffer_t *result, const char *path, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, @@ -36,7 +36,7 @@ struct ll_merge_driver { static int ll_binary_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path_unused, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, int marker_size) @@ -57,14 +57,12 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, int marker_size) { xmparam_t xmp; - int style = 0; - int favor = (flag >> 1) & 03; if (buffer_is_binary(orig->ptr, orig->size) || buffer_is_binary(src1->ptr, src1->size) || @@ -73,69 +71,38 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, path, name1, name2); return ll_binary_merge(drv_unused, result, path, - orig, src1, name1, + orig, orig_name, + src1, name1, src2, name2, flag, marker_size); } memset(&xmp, 0, sizeof(xmp)); + xmp.level = XDL_MERGE_ZEALOUS; + xmp.favor= (flag >> 1) & 03; if (git_xmerge_style >= 0) - style = git_xmerge_style; + xmp.style = git_xmerge_style; if (marker_size > 0) xmp.marker_size = marker_size; - return xdl_merge(orig, - src1, name1, - src2, name2, - &xmp, XDL_MERGE_FLAGS(XDL_MERGE_ZEALOUS, style, favor), - result); + xmp.ancestor = orig_name; + xmp.file1 = name1; + xmp.file2 = name2; + return xdl_merge(orig, src1, src2, &xmp, result); } static int ll_union_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path_unused, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, int marker_size) { - char *src, *dst; - long size; - int status, saved_style; - - /* We have to force the RCS "merge" style */ - saved_style = git_xmerge_style; - git_xmerge_style = 0; - status = ll_xdl_merge(drv_unused, result, path_unused, - orig, src1, NULL, src2, NULL, - flag, marker_size); - git_xmerge_style = saved_style; - if (status <= 0) - return status; - size = result->size; - src = dst = result->ptr; - while (size) { - char ch; - if ((marker_size < size) && - (*src == '<' || *src == '=' || *src == '>')) { - int i; - ch = *src; - for (i = 0; i < marker_size; i++) - if (src[i] != ch) - goto not_a_marker; - if (src[marker_size] != '\n') - goto not_a_marker; - src += marker_size + 1; - size -= marker_size + 1; - continue; - } - not_a_marker: - do { - ch = *src++; - *dst++ = ch; - size--; - } while (ch != '\n' && size); - } - result->size = dst - result->ptr; + /* Use union favor */ + flag = (flag & 1) | (XDL_MERGE_FAVOR_UNION << 1); + return ll_xdl_merge(drv_unused, result, path_unused, + orig, NULL, src1, NULL, src2, NULL, + flag, marker_size); return 0; } @@ -165,24 +132,24 @@ static void create_temp(mmfile_t *src, char *path) static int ll_ext_merge(const struct ll_merge_driver *fn, mmbuffer_t *result, const char *path, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, int marker_size) { char temp[4][50]; struct strbuf cmd = STRBUF_INIT; - struct strbuf_expand_dict_entry dict[] = { - { "O", temp[0] }, - { "A", temp[1] }, - { "B", temp[2] }, - { "L", temp[3] }, - { NULL } - }; + struct strbuf_expand_dict_entry dict[5]; const char *args[] = { NULL, NULL }; int status, fd, i; struct stat st; + dict[0].placeholder = "O"; dict[0].value = temp[0]; + dict[1].placeholder = "A"; dict[1].value = temp[1]; + dict[2].placeholder = "B"; dict[2].value = temp[2]; + dict[3].placeholder = "L"; dict[3].value = temp[3]; + dict[4].placeholder = NULL; dict[4].value = NULL; + if (fn->cmdline == NULL) die("custom merge driver %s lacks command line.", fn->name); @@ -356,7 +323,7 @@ static int git_path_check_merge(const char *path, struct git_attr_check check[2] int ll_merge(mmbuffer_t *result_buf, const char *path, - mmfile_t *ancestor, + mmfile_t *ancestor, const char *ancestor_label, mmfile_t *ours, const char *our_label, mmfile_t *theirs, const char *their_label, int flag) @@ -378,7 +345,7 @@ int ll_merge(mmbuffer_t *result_buf, driver = find_ll_merge_driver(ll_driver_name); if (virtual_ancestor && driver->recursive) driver = find_ll_merge_driver(driver->recursive); - return driver->fn(driver, result_buf, path, ancestor, + return driver->fn(driver, result_buf, path, ancestor, ancestor_label, ours, our_label, theirs, their_label, flag, marker_size); } diff --git a/ll-merge.h b/ll-merge.h index 57889227b1..57754cc8ca 100644 --- a/ll-merge.h +++ b/ll-merge.h @@ -7,7 +7,7 @@ int ll_merge(mmbuffer_t *result_buf, const char *path, - mmfile_t *ancestor, + mmfile_t *ancestor, const char *ancestor_label, mmfile_t *ours, const char *our_label, mmfile_t *theirs, const char *their_label, int flag); diff --git a/log-tree.c b/log-tree.c index 27afcf6972..b46ed3baef 100644 --- a/log-tree.c +++ b/log-tree.c @@ -7,32 +7,113 @@ #include "reflog-walk.h" #include "refs.h" #include "string-list.h" +#include "color.h" struct decoration name_decoration = { "object names" }; -static void add_name_decoration(const char *prefix, const char *name, struct object *obj) +enum decoration_type { + DECORATION_NONE = 0, + DECORATION_REF_LOCAL, + DECORATION_REF_REMOTE, + DECORATION_REF_TAG, + DECORATION_REF_STASH, + DECORATION_REF_HEAD, +}; + +static char decoration_colors[][COLOR_MAXLEN] = { + GIT_COLOR_RESET, + GIT_COLOR_BOLD_GREEN, /* REF_LOCAL */ + GIT_COLOR_BOLD_RED, /* REF_REMOTE */ + GIT_COLOR_BOLD_YELLOW, /* REF_TAG */ + GIT_COLOR_BOLD_MAGENTA, /* REF_STASH */ + GIT_COLOR_BOLD_CYAN, /* REF_HEAD */ +}; + +static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix) +{ + if (decorate_use_color) + return decoration_colors[ix]; + return ""; +} + +static int parse_decorate_color_slot(const char *slot) +{ + /* + * We're comparing with 'ignore-case' on + * (because config.c sets them all tolower), + * but let's match the letters in the literal + * string values here with how they are + * documented in Documentation/config.txt, for + * consistency. + * + * We love being consistent, don't we? + */ + if (!strcasecmp(slot, "branch")) + return DECORATION_REF_LOCAL; + if (!strcasecmp(slot, "remoteBranch")) + return DECORATION_REF_REMOTE; + if (!strcasecmp(slot, "tag")) + return DECORATION_REF_TAG; + if (!strcasecmp(slot, "stash")) + return DECORATION_REF_STASH; + if (!strcasecmp(slot, "HEAD")) + return DECORATION_REF_HEAD; + return -1; +} + +int parse_decorate_color_config(const char *var, const int ofs, const char *value) +{ + int slot = parse_decorate_color_slot(var + ofs); + if (slot < 0) + return 0; + if (!value) + return config_error_nonbool(var); + color_parse(value, var, decoration_colors[slot]); + return 0; +} + +/* + * log-tree.c uses DIFF_OPT_TST for determining whether to use color + * for showing the commit sha1, use the same check for --decorate + */ +#define decorate_get_color_opt(o, ix) \ + decorate_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix) + +static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj) { - int plen = strlen(prefix); int nlen = strlen(name); - struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + plen + nlen); - memcpy(res->name, prefix, plen); - memcpy(res->name + plen, name, nlen + 1); + struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + nlen); + memcpy(res->name, name, nlen + 1); + res->type = type; res->next = add_decoration(&name_decoration, obj, res); } static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct object *obj = parse_object(sha1); + enum decoration_type type = DECORATION_NONE; if (!obj) return 0; + + if (!prefixcmp(refname, "refs/heads")) + type = DECORATION_REF_LOCAL; + else if (!prefixcmp(refname, "refs/remotes")) + type = DECORATION_REF_REMOTE; + else if (!prefixcmp(refname, "refs/tags")) + type = DECORATION_REF_TAG; + else if (!prefixcmp(refname, "refs/stash")) + type = DECORATION_REF_STASH; + else if (!prefixcmp(refname, "HEAD")) + type = DECORATION_REF_HEAD; + if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS) refname = prettify_refname(refname); - add_name_decoration("", refname, obj); + add_name_decoration(type, refname, obj); while (obj->type == OBJ_TAG) { obj = ((struct tag *)obj)->tagged; if (!obj) break; - add_name_decoration("tag: ", refname, obj); + add_name_decoration(DECORATION_REF_TAG, refname, obj); } return 0; } @@ -60,6 +141,10 @@ void show_decorations(struct rev_info *opt, struct commit *commit) { const char *prefix; struct name_decoration *decoration; + const char *color_commit = + diff_get_color_opt(&opt->diffopt, DIFF_COMMIT); + const char *color_reset = + decorate_get_color_opt(&opt->diffopt, DECORATION_NONE); if (opt->show_source && commit->util) printf("\t%s", (char *) commit->util); @@ -70,7 +155,14 @@ void show_decorations(struct rev_info *opt, struct commit *commit) return; prefix = " ("; while (decoration) { - printf("%s%s", prefix, decoration->name); + printf("%s", prefix); + fputs(decorate_get_color_opt(&opt->diffopt, decoration->type), + stdout); + if (decoration->type == DECORATION_REF_TAG) + fputs("tag: ", stdout); + printf("%s", decoration->name); + fputs(color_reset, stdout); + fputs(color_commit, stdout); prefix = ", "; decoration = decoration->next; } @@ -469,6 +561,12 @@ int log_tree_diff_flush(struct rev_info *opt) int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH; if ((pch & opt->diffopt.output_format) == pch) printf("---"); + if (opt->diffopt.output_prefix) { + struct strbuf *msg = NULL; + msg = opt->diffopt.output_prefix(&opt->diffopt, + opt->diffopt.output_prefix_data); + fwrite(msg->buf, msg->len, 1, stdout); + } putchar('\n'); } } @@ -514,6 +612,16 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log return 0; else if (opt->combine_merges) return do_diff_combined(opt, commit); + else if (opt->first_parent_only) { + /* + * Generate merge log entry only for the first + * parent, showing summary diff of the others + * we merged _in_. + */ + diff_tree_sha1(parents->item->object.sha1, sha1, "", &opt->diffopt); + log_tree_diff_flush(opt); + return !opt->loginfo; + } /* If we show individual diffs, show the parent info */ log->parent = parents->item; diff --git a/log-tree.h b/log-tree.h index 3f7b40027b..5c4cf7cac3 100644 --- a/log-tree.h +++ b/log-tree.h @@ -7,6 +7,7 @@ struct log_info { struct commit *commit, *parent; }; +int parse_decorate_color_config(const char *var, const int ofs, const char *value); void init_log_tree_opt(struct rev_info *); int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); @@ -69,7 +69,7 @@ static void add_mapping(struct string_list *map, index = -1 - index; } else { /* create mailmap entry */ - struct string_list_item *item = string_list_insert_at_index(index, old_email, map); + 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)); ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1; @@ -92,7 +92,7 @@ static void add_mapping(struct string_list *map, mi->name = xstrdup(new_name); if (new_email) mi->email = xstrdup(new_email); - string_list_insert(old_name, &me->namemap)->util = mi; + string_list_insert(&me->namemap, old_name)->util = mi; } debug_mm("mailmap: '%s' <%s> -> '%s' <%s>\n", @@ -214,13 +214,13 @@ int map_user(struct string_list *map, mailbuf[i] = 0; debug_mm("map_user: map '%s' <%s>\n", name, mailbuf); - item = string_list_lookup(mailbuf, map); + item = string_list_lookup(map, mailbuf); if (item != NULL) { me = (struct mailmap_entry *)item->util; if (me->namemap.nr) { /* The item has multiple items, so we'll look up on name too */ /* If the name is not found, we choose the simple entry */ - struct string_list_item *subitem = string_list_lookup(name, &me->namemap); + struct string_list_item *subitem = string_list_lookup(&me->namemap, name); if (subitem) item = subitem; } diff --git a/merge-file.c b/merge-file.c index fd34d76e15..db4d0d50d3 100644 --- a/merge-file.c +++ b/merge-file.c @@ -30,7 +30,13 @@ static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our int merge_status; mmbuffer_t res; - merge_status = ll_merge(&res, path, base, + /* + * This function is only used by cmd_merge_tree, which + * does not respect the merge.conflictstyle option. + * There is no need to worry about a label for the + * common ancestor. + */ + merge_status = ll_merge(&res, path, base, NULL, our, ".our", their, ".their", 0); if (merge_status < 0) return NULL; @@ -60,7 +66,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2) xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_COMMON; diff --git a/merge-recursive.c b/merge-recursive.c index cb53b01c19..fb6aa4a551 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -238,9 +238,9 @@ static int save_files_dirs(const unsigned char *sha1, newpath[baselen + len] = '\0'; if (S_ISDIR(mode)) - string_list_insert(newpath, &o->current_directory_set); + string_list_insert(&o->current_directory_set, newpath); else - string_list_insert(newpath, &o->current_file_set); + string_list_insert(&o->current_file_set, newpath); free(newpath); return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); @@ -271,7 +271,7 @@ static struct stage_data *insert_stage_data(const char *path, e->stages[2].sha, &e->stages[2].mode); get_tree_entry(b->object.sha1, path, e->stages[3].sha, &e->stages[3].mode); - item = string_list_insert(path, entries); + item = string_list_insert(entries, path); item->util = e; return e; } @@ -294,9 +294,9 @@ static struct string_list *get_unmerged(void) if (!ce_stage(ce)) continue; - item = string_list_lookup(ce->name, unmerged); + item = string_list_lookup(unmerged, ce->name); if (!item) { - item = string_list_insert(ce->name, unmerged); + item = string_list_insert(unmerged, ce->name); item->util = xcalloc(1, sizeof(struct stage_data)); } e = item->util; @@ -356,20 +356,20 @@ static struct string_list *get_renames(struct merge_options *o, re = xmalloc(sizeof(*re)); re->processed = 0; re->pair = pair; - item = string_list_lookup(re->pair->one->path, entries); + item = string_list_lookup(entries, re->pair->one->path); if (!item) re->src_entry = insert_stage_data(re->pair->one->path, o_tree, a_tree, b_tree, entries); else re->src_entry = item->util; - item = string_list_lookup(re->pair->two->path, entries); + item = string_list_lookup(entries, re->pair->two->path); if (!item) re->dst_entry = insert_stage_data(re->pair->two->path, o_tree, a_tree, b_tree, entries); else re->dst_entry = item->util; - item = string_list_insert(pair->one->path, renames); + item = string_list_insert(renames, pair->one->path); item->util = re; } opts.output_format = DIFF_FORMAT_NO_OUTPUT; @@ -409,7 +409,7 @@ static int remove_file(struct merge_options *o, int clean, return -1; } if (update_working_directory) { - if (remove_path(path) && errno != ENOENT) + if (remove_path(path)) return -1; } return 0; @@ -432,7 +432,7 @@ static char *unique_path(struct merge_options *o, const char *path, const char * lstat(newpath, &st) == 0) sprintf(p, "_%d", suffix++); - string_list_insert(newpath, &o->current_file_set); + string_list_insert(&o->current_file_set, newpath); return newpath; } @@ -599,23 +599,6 @@ struct merge_file_info merge:1; }; -static void fill_mm(const unsigned char *sha1, mmfile_t *mm) -{ - unsigned long size; - enum object_type type; - - if (!hashcmp(sha1, null_sha1)) { - mm->ptr = xstrdup(""); - mm->size = 0; - return; - } - - mm->ptr = read_sha1_file(sha1, &type, &size); - if (!mm->ptr || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); - mm->size = size; -} - static int merge_3way(struct merge_options *o, mmbuffer_t *result_buf, struct diff_filespec *one, @@ -625,7 +608,7 @@ static int merge_3way(struct merge_options *o, const char *branch2) { mmfile_t orig, src1, src2; - char *name1, *name2; + char *base_name, *name1, *name2; int merge_status; int favor; @@ -645,19 +628,24 @@ static int merge_3way(struct merge_options *o, } } - if (strcmp(a->path, b->path)) { + if (strcmp(a->path, b->path) || + (o->ancestor != NULL && strcmp(a->path, one->path) != 0)) { + base_name = o->ancestor == NULL ? NULL : + xstrdup(mkpath("%s:%s", o->ancestor, one->path)); name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); } else { + base_name = o->ancestor == NULL ? NULL : + xstrdup(mkpath("%s", o->ancestor)); name1 = xstrdup(mkpath("%s", branch1)); name2 = xstrdup(mkpath("%s", branch2)); } - fill_mm(one->sha1, &orig); - fill_mm(a->sha1, &src1); - fill_mm(b->sha1, &src2); + read_mmblob(&orig, one->sha1); + read_mmblob(&src1, a->sha1); + read_mmblob(&src2, b->sha1); - merge_status = ll_merge(result_buf, a->path, &orig, + merge_status = ll_merge(result_buf, a->path, &orig, base_name, &src1, name1, &src2, name2, (!!o->call_depth) | (favor << 1)); @@ -823,12 +811,12 @@ static int process_renames(struct merge_options *o, for (i = 0; i < a_renames->nr; i++) { sre = a_renames->items[i].util; - string_list_insert(sre->pair->two->path, &a_by_dst)->util + string_list_insert(&a_by_dst, sre->pair->two->path)->util = sre->dst_entry; } for (i = 0; i < b_renames->nr; i++) { sre = b_renames->items[i].util; - string_list_insert(sre->pair->two->path, &b_by_dst)->util + string_list_insert(&b_by_dst, sre->pair->two->path)->util = sre->dst_entry; } @@ -1000,7 +988,7 @@ static int process_renames(struct merge_options *o, output(o, 1, "Adding as %s instead", new_path); update_file(o, 0, dst_other.sha1, dst_other.mode, new_path); } - } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { + } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) { ren2 = item->util; clean_merge = 0; ren2->processed = 1; @@ -1226,7 +1214,7 @@ int merge_trees(struct merge_options *o, } if (sha_eq(common->object.sha1, merge->object.sha1)) { - output(o, 0, "Already uptodate!"); + output(o, 0, "Already up-to-date!"); *result = head; return 1; } @@ -1359,6 +1347,7 @@ int merge_recursive(struct merge_options *o, if (!o->call_depth) read_cache(); + o->ancestor = "merged common ancestors"; clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree, &mrtree); diff --git a/merge-recursive.h b/merge-recursive.h index be8410ad18..b831293b38 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -4,12 +4,13 @@ #include "string-list.h" struct merge_options { + const char *ancestor; const char *branch1; const char *branch2; enum { MERGE_RECURSIVE_NORMAL = 0, MERGE_RECURSIVE_OURS, - MERGE_RECURSIVE_THEIRS, + MERGE_RECURSIVE_THEIRS } recursive_variant; const char *subtree_shift; unsigned buffer_output : 1; @@ -53,4 +54,7 @@ int merge_recursive_generic(struct merge_options *o, void init_merge_options(struct merge_options *o); struct tree *write_tree_from_memory(struct merge_options *o); +/* builtin/merge.c */ +int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes); + #endif diff --git a/notes-cache.c b/notes-cache.c new file mode 100644 index 0000000000..dee6d62e72 --- /dev/null +++ b/notes-cache.c @@ -0,0 +1,94 @@ +#include "cache.h" +#include "notes-cache.h" +#include "commit.h" +#include "refs.h" + +static int notes_cache_match_validity(const char *ref, const char *validity) +{ + unsigned char sha1[20]; + struct commit *commit; + struct pretty_print_context pretty_ctx; + struct strbuf msg = STRBUF_INIT; + int ret; + + if (read_ref(ref, sha1) < 0) + return 0; + + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + return 0; + + memset(&pretty_ctx, 0, sizeof(pretty_ctx)); + format_commit_message(commit, "%s", &msg, &pretty_ctx); + strbuf_trim(&msg); + + ret = !strcmp(msg.buf, validity); + strbuf_release(&msg); + + return ret; +} + +void notes_cache_init(struct notes_cache *c, const char *name, + const char *validity) +{ + struct strbuf ref = STRBUF_INIT; + int flags = 0; + + memset(c, 0, sizeof(*c)); + c->validity = xstrdup(validity); + + strbuf_addf(&ref, "refs/notes/%s", name); + if (!notes_cache_match_validity(ref.buf, validity)) + flags = NOTES_INIT_EMPTY; + init_notes(&c->tree, ref.buf, combine_notes_overwrite, flags); + strbuf_release(&ref); +} + +int notes_cache_write(struct notes_cache *c) +{ + unsigned char tree_sha1[20]; + unsigned char commit_sha1[20]; + + if (!c || !c->tree.initialized || !c->tree.ref || !*c->tree.ref) + return -1; + if (!c->tree.dirty) + return 0; + + if (write_notes_tree(&c->tree, tree_sha1)) + return -1; + if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0) + return -1; + if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL, + 0, QUIET_ON_ERR) < 0) + return -1; + + return 0; +} + +char *notes_cache_get(struct notes_cache *c, unsigned char key_sha1[20], + size_t *outsize) +{ + const unsigned char *value_sha1; + enum object_type type; + char *value; + unsigned long size; + + value_sha1 = get_note(&c->tree, key_sha1); + if (!value_sha1) + return NULL; + value = read_sha1_file(value_sha1, &type, &size); + + *outsize = size; + return value; +} + +int notes_cache_put(struct notes_cache *c, unsigned char key_sha1[20], + const char *data, size_t size) +{ + unsigned char value_sha1[20]; + + if (write_sha1_file(data, size, "blob", value_sha1) < 0) + return -1; + add_note(&c->tree, key_sha1, value_sha1, NULL); + return 0; +} diff --git a/notes-cache.h b/notes-cache.h new file mode 100644 index 0000000000..356f88fb3c --- /dev/null +++ b/notes-cache.h @@ -0,0 +1,20 @@ +#ifndef NOTES_CACHE_H +#define NOTES_CACHE_H + +#include "notes.h" + +struct notes_cache { + struct notes_tree tree; + char *validity; +}; + +void notes_cache_init(struct notes_cache *c, const char *name, + const char *validity); +int notes_cache_write(struct notes_cache *c); + +char *notes_cache_get(struct notes_cache *c, unsigned char sha1[20], size_t + *outsize); +int notes_cache_put(struct notes_cache *c, unsigned char sha1[20], + const char *data, size_t size); + +#endif /* NOTES_CACHE_H */ @@ -1,10 +1,12 @@ #include "cache.h" -#include "commit.h" #include "notes.h" -#include "refs.h" +#include "blob.h" +#include "tree.h" #include "utf8.h" #include "strbuf.h" #include "tree-walk.h" +#include "string-list.h" +#include "refs.h" /* * Use a non-balancing simple 16-tree structure with struct int_node as @@ -25,10 +27,10 @@ struct int_node { /* * Leaf nodes come in two variants, note entries and subtree entries, * distinguished by the LSb of the leaf node pointer (see above). - * As a note entry, the key is the SHA1 of the referenced commit, and the + * As a note entry, the key is the SHA1 of the referenced object, and the * value is the SHA1 of the note object. * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the - * referenced commit, using the last byte of the key to store the length of + * referenced object, using the last byte of the key to store the length of * the prefix. The value is the SHA1 of the tree object containing the notes * subtree. */ @@ -37,6 +39,21 @@ struct leaf_node { unsigned char val_sha1[20]; }; +/* + * A notes tree may contain entries that are not notes, and that do not follow + * the naming conventions of notes. There are typically none/few of these, but + * we still need to keep track of them. Keep a simple linked list sorted alpha- + * betically on the non-note path. The list is populated when parsing tree + * objects in load_subtree(), and the non-notes are correctly written back into + * the tree objects produced by write_notes_tree(). + */ +struct non_note { + struct non_note *next; /* grounded (last->next == NULL) */ + char *path; + unsigned int mode; + unsigned char sha1[20]; +}; + #define PTR_TYPE_NULL 0 #define PTR_TYPE_INTERNAL 1 #define PTR_TYPE_NOTE 2 @@ -46,17 +63,18 @@ struct leaf_node { #define CLR_PTR_TYPE(ptr) ((void *) ((uintptr_t) (ptr) & ~3)) #define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type))) -#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((~n & 0x01) << 2)) & 0x0f) +#define GET_NIBBLE(n, sha1) (((sha1[(n) >> 1]) >> ((~(n) & 0x01) << 2)) & 0x0f) #define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \ (memcmp(key_sha1, subtree_sha1, subtree_sha1[19])) -static struct int_node root_node; +struct notes_tree default_notes_tree; -static int initialized; +static struct string_list display_notes_refs; +static struct notes_tree **display_notes_trees; -static void load_subtree(struct leaf_node *subtree, struct int_node *node, - unsigned int n); +static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, + struct int_node *node, unsigned int n); /* * Search the tree until the appropriate location for the given key is found: @@ -73,7 +91,7 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, * - an unused leaf node (NULL) * In any case, set *tree and *n, and return pointer to the tree location. */ -static void **note_tree_search(struct int_node **tree, +static void **note_tree_search(struct notes_tree *t, struct int_node **tree, unsigned char *n, const unsigned char *key_sha1) { struct leaf_node *l; @@ -85,27 +103,27 @@ static void **note_tree_search(struct int_node **tree, if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { /* unpack tree and resume search */ (*tree)->a[0] = NULL; - load_subtree(l, *tree, *n); + load_subtree(t, l, *tree, *n); free(l); - return note_tree_search(tree, n, key_sha1); + return note_tree_search(t, tree, n, key_sha1); } } i = GET_NIBBLE(*n, key_sha1); p = (*tree)->a[i]; - switch(GET_PTR_TYPE(p)) { + switch (GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: *tree = CLR_PTR_TYPE(p); (*n)++; - return note_tree_search(tree, n, key_sha1); + return note_tree_search(t, tree, n, key_sha1); case PTR_TYPE_SUBTREE: l = (struct leaf_node *) CLR_PTR_TYPE(p); if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { /* unpack tree and resume search */ (*tree)->a[i] = NULL; - load_subtree(l, *tree, *n); + load_subtree(t, l, *tree, *n); free(l); - return note_tree_search(tree, n, key_sha1); + return note_tree_search(t, tree, n, key_sha1); } /* fall through */ default: @@ -118,10 +136,11 @@ static void **note_tree_search(struct int_node **tree, * Search to the tree location appropriate for the given key: * If a note entry with matching key, return the note entry, else return NULL. */ -static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, +static struct leaf_node *note_tree_find(struct notes_tree *t, + struct int_node *tree, unsigned char n, const unsigned char *key_sha1) { - void **p = note_tree_search(&tree, &n, key_sha1); + void **p = note_tree_search(t, &tree, &n, key_sha1); if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) { struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p); if (!hashcmp(key_sha1, l->key_sha1)) @@ -130,55 +149,12 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, return NULL; } -/* Create a new blob object by concatenating the two given blob objects */ -static int concatenate_notes(unsigned char *cur_sha1, - const unsigned char *new_sha1) -{ - char *cur_msg, *new_msg, *buf; - unsigned long cur_len, new_len, buf_len; - enum object_type cur_type, new_type; - int ret; - - /* read in both note blob objects */ - new_msg = read_sha1_file(new_sha1, &new_type, &new_len); - if (!new_msg || !new_len || new_type != OBJ_BLOB) { - free(new_msg); - return 0; - } - cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len); - if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) { - free(cur_msg); - free(new_msg); - hashcpy(cur_sha1, new_sha1); - return 0; - } - - /* we will separate the notes by a newline anyway */ - if (cur_msg[cur_len - 1] == '\n') - cur_len--; - - /* concatenate cur_msg and new_msg into buf */ - buf_len = cur_len + 1 + new_len; - buf = (char *) xmalloc(buf_len); - memcpy(buf, cur_msg, cur_len); - buf[cur_len] = '\n'; - memcpy(buf + cur_len + 1, new_msg, new_len); - - free(cur_msg); - free(new_msg); - - /* create a new blob object from buf */ - ret = write_sha1_file(buf, buf_len, "blob", cur_sha1); - free(buf); - return ret; -} - /* * To insert a leaf_node: * Search to the tree location appropriate for the given leaf_node's key: * - If location is unused (NULL), store the tweaked pointer directly there * - If location holds a note entry that matches the note-to-be-inserted, then - * concatenate the two notes. + * combine the two notes (by calling the given combine_notes function). * - If location holds a note entry that matches the subtree-to-be-inserted, * then unpack the subtree-to-be-inserted into the location. * - If location holds a matching subtree entry, unpack the subtree at that @@ -186,16 +162,17 @@ static int concatenate_notes(unsigned char *cur_sha1, * - Else, create a new int_node, holding both the node-at-location and the * node-to-be-inserted, and store the new int_node into the location. */ -static void note_tree_insert(struct int_node *tree, unsigned char n, - struct leaf_node *entry, unsigned char type) +static void note_tree_insert(struct notes_tree *t, struct int_node *tree, + unsigned char n, struct leaf_node *entry, unsigned char type, + combine_notes_fn combine_notes) { struct int_node *new_node; struct leaf_node *l; - void **p = note_tree_search(&tree, &n, entry->key_sha1); + void **p = note_tree_search(t, &tree, &n, entry->key_sha1); assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ l = (struct leaf_node *) CLR_PTR_TYPE(*p); - switch(GET_PTR_TYPE(*p)) { + switch (GET_PTR_TYPE(*p)) { case PTR_TYPE_NULL: assert(!*p); *p = SET_PTR_TYPE(entry, type); @@ -208,12 +185,11 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, if (!hashcmp(l->val_sha1, entry->val_sha1)) return; - if (concatenate_notes(l->val_sha1, - entry->val_sha1)) - die("failed to concatenate note %s " - "into note %s for commit %s", - sha1_to_hex(entry->val_sha1), + if (combine_notes(l->val_sha1, entry->val_sha1)) + die("failed to combine notes %s and %s" + " for object %s", sha1_to_hex(l->val_sha1), + sha1_to_hex(entry->val_sha1), sha1_to_hex(l->key_sha1)); free(entry); return; @@ -223,7 +199,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, entry->key_sha1)) { /* unpack 'entry' */ - load_subtree(entry, tree, n); + load_subtree(t, entry, tree, n); free(entry); return; } @@ -234,9 +210,10 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { /* unpack 'l' and restart insert */ *p = NULL; - load_subtree(l, tree, n); + load_subtree(t, l, tree, n); free(l); - note_tree_insert(tree, n, entry, type); + note_tree_insert(t, tree, n, entry, type, + combine_notes); return; } break; @@ -246,9 +223,83 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); - note_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p)); + note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p), + combine_notes); *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); - note_tree_insert(new_node, n + 1, entry, type); + note_tree_insert(t, new_node, n + 1, entry, type, combine_notes); +} + +/* + * How to consolidate an int_node: + * If there are > 1 non-NULL entries, give up and return non-zero. + * Otherwise replace the int_node at the given index in the given parent node + * with the only entry (or a NULL entry if no entries) from the given tree, + * and return 0. + */ +static int note_tree_consolidate(struct int_node *tree, + struct int_node *parent, unsigned char index) +{ + unsigned int i; + void *p = NULL; + + assert(tree && parent); + assert(CLR_PTR_TYPE(parent->a[index]) == tree); + + for (i = 0; i < 16; i++) { + if (GET_PTR_TYPE(tree->a[i]) != PTR_TYPE_NULL) { + if (p) /* more than one entry */ + return -2; + p = tree->a[i]; + } + } + + /* replace tree with p in parent[index] */ + parent->a[index] = p; + free(tree); + return 0; +} + +/* + * To remove a leaf_node: + * Search to the tree location appropriate for the given leaf_node's key: + * - If location does not hold a matching entry, abort and do nothing. + * - Replace the matching leaf_node with a NULL entry (and free the leaf_node). + * - Consolidate int_nodes repeatedly, while walking up the tree towards root. + */ +static void note_tree_remove(struct notes_tree *t, struct int_node *tree, + unsigned char n, struct leaf_node *entry) +{ + struct leaf_node *l; + struct int_node *parent_stack[20]; + unsigned char i, j; + void **p = note_tree_search(t, &tree, &n, entry->key_sha1); + + assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ + if (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE) + return; /* type mismatch, nothing to remove */ + l = (struct leaf_node *) CLR_PTR_TYPE(*p); + if (hashcmp(l->key_sha1, entry->key_sha1)) + return; /* key mismatch, nothing to remove */ + + /* we have found a matching entry */ + free(l); + *p = SET_PTR_TYPE(NULL, PTR_TYPE_NULL); + + /* consolidate this tree level, and parent levels, if possible */ + if (!n) + return; /* cannot consolidate top level */ + /* first, build stack of ancestors between root and current node */ + parent_stack[0] = t->root; + for (i = 0; i < n; i++) { + j = GET_NIBBLE(i, entry->key_sha1); + parent_stack[i + 1] = CLR_PTR_TYPE(parent_stack[i]->a[j]); + } + assert(i == n && parent_stack[i] == tree); + /* next, unwind stack until note_tree_consolidate() is done */ + while (i > 0 && + !note_tree_consolidate(parent_stack[i], parent_stack[i - 1], + GET_NIBBLE(i - 1, entry->key_sha1))) + i--; } /* Free the entire notes data contained in the given tree */ @@ -257,7 +308,7 @@ static void note_tree_free(struct int_node *tree) unsigned int i; for (i = 0; i < 16; i++) { void *p = tree->a[i]; - switch(GET_PTR_TYPE(p)) { + switch (GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: note_tree_free(CLR_PTR_TYPE(p)); /* fall through */ @@ -274,7 +325,7 @@ static void note_tree_free(struct int_node *tree) * - hex_len - Length of above segment. Must be multiple of 2 between 0 and 40 * - sha1 - Partial SHA1 value is written here * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20 - * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format). + * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format)). * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2). * Pads sha1 with NULs up to sha1_len (not included in returned length). */ @@ -296,14 +347,67 @@ static int get_sha1_hex_segment(const char *hex, unsigned int hex_len, return len; } -static void load_subtree(struct leaf_node *subtree, struct int_node *node, - unsigned int n) +static int non_note_cmp(const struct non_note *a, const struct non_note *b) +{ + return strcmp(a->path, b->path); +} + +static void add_non_note(struct notes_tree *t, const char *path, + unsigned int mode, const unsigned char *sha1) +{ + struct non_note *p = t->prev_non_note, *n; + n = (struct non_note *) xmalloc(sizeof(struct non_note)); + n->next = NULL; + n->path = xstrdup(path); + n->mode = mode; + hashcpy(n->sha1, sha1); + t->prev_non_note = n; + + if (!t->first_non_note) { + t->first_non_note = n; + return; + } + + if (non_note_cmp(p, n) < 0) + ; /* do nothing */ + else if (non_note_cmp(t->first_non_note, n) <= 0) + p = t->first_non_note; + else { + /* n sorts before t->first_non_note */ + n->next = t->first_non_note; + t->first_non_note = n; + return; + } + + /* n sorts equal or after p */ + while (p->next && non_note_cmp(p->next, n) <= 0) + p = p->next; + + if (non_note_cmp(p, n) == 0) { /* n ~= p; overwrite p with n */ + assert(strcmp(p->path, n->path) == 0); + p->mode = n->mode; + hashcpy(p->sha1, n->sha1); + free(n); + t->prev_non_note = p; + return; + } + + /* n sorts between p and p->next */ + n->next = p->next; + p->next = n; +} + +static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, + struct int_node *node, unsigned int n) { - unsigned char commit_sha1[20]; + unsigned char object_sha1[20]; unsigned int prefix_len; void *buf; struct tree_desc desc; struct name_entry entry; + int len, path_len; + unsigned char type; + struct leaf_node *l; buf = fill_tree_descriptor(&desc, subtree->val_sha1); if (!buf) @@ -312,86 +416,724 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, prefix_len = subtree->key_sha1[19]; assert(prefix_len * 2 >= n); - memcpy(commit_sha1, subtree->key_sha1, prefix_len); + memcpy(object_sha1, subtree->key_sha1, prefix_len); while (tree_entry(&desc, &entry)) { - int len = get_sha1_hex_segment(entry.path, strlen(entry.path), - commit_sha1 + prefix_len, 20 - prefix_len); + path_len = strlen(entry.path); + len = get_sha1_hex_segment(entry.path, path_len, + object_sha1 + prefix_len, 20 - prefix_len); if (len < 0) - continue; /* entry.path is not a SHA1 sum. Skip */ + goto handle_non_note; /* entry.path is not a SHA1 */ len += prefix_len; /* - * If commit SHA1 is complete (len == 20), assume note object - * If commit SHA1 is incomplete (len < 20), assume note subtree + * If object SHA1 is complete (len == 20), assume note object + * If object SHA1 is incomplete (len < 20), and current + * component consists of 2 hex chars, assume note subtree */ if (len <= 20) { - unsigned char type = PTR_TYPE_NOTE; - struct leaf_node *l = (struct leaf_node *) + type = PTR_TYPE_NOTE; + l = (struct leaf_node *) xcalloc(sizeof(struct leaf_node), 1); - hashcpy(l->key_sha1, commit_sha1); + hashcpy(l->key_sha1, object_sha1); hashcpy(l->val_sha1, entry.sha1); if (len < 20) { - if (!S_ISDIR(entry.mode)) - continue; /* entry cannot be subtree */ + if (!S_ISDIR(entry.mode) || path_len != 2) + goto handle_non_note; /* not subtree */ l->key_sha1[19] = (unsigned char) len; type = PTR_TYPE_SUBTREE; } - note_tree_insert(node, n, l, type); + note_tree_insert(t, node, n, l, type, + combine_notes_concatenate); + } + continue; + +handle_non_note: + /* + * Determine full path for this non-note entry: + * The filename is already found in entry.path, but the + * directory part of the path must be deduced from the subtree + * containing this entry. We assume here that the overall notes + * tree follows a strict byte-based progressive fanout + * structure (i.e. using 2/38, 2/2/36, etc. fanouts, and not + * e.g. 4/36 fanout). This means that if a non-note is found at + * path "dead/beef", the following code will register it as + * being found on "de/ad/beef". + * On the other hand, if you use such non-obvious non-note + * paths in the middle of a notes tree, you deserve what's + * coming to you ;). Note that for non-notes that are not + * SHA1-like at the top level, there will be no problems. + * + * To conclude, it is strongly advised to make sure non-notes + * have at least one non-hex character in the top-level path + * component. + */ + { + char non_note_path[PATH_MAX]; + char *p = non_note_path; + const char *q = sha1_to_hex(subtree->key_sha1); + int i; + for (i = 0; i < prefix_len; i++) { + *p++ = *q++; + *p++ = *q++; + *p++ = '/'; + } + strcpy(p, entry.path); + add_non_note(t, non_note_path, entry.mode, entry.sha1); + } + } + free(buf); +} + +/* + * Determine optimal on-disk fanout for this part of the notes tree + * + * Given a (sub)tree and the level in the internal tree structure, determine + * whether or not the given existing fanout should be expanded for this + * (sub)tree. + * + * Values of the 'fanout' variable: + * - 0: No fanout (all notes are stored directly in the root notes tree) + * - 1: 2/38 fanout + * - 2: 2/2/36 fanout + * - 3: 2/2/2/34 fanout + * etc. + */ +static unsigned char determine_fanout(struct int_node *tree, unsigned char n, + unsigned char fanout) +{ + /* + * The following is a simple heuristic that works well in practice: + * For each even-numbered 16-tree level (remember that each on-disk + * fanout level corresponds to _two_ 16-tree levels), peek at all 16 + * entries at that tree level. If all of them are either int_nodes or + * subtree entries, then there are likely plenty of notes below this + * level, so we return an incremented fanout. + */ + unsigned int i; + if ((n % 2) || (n > 2 * fanout)) + return fanout; + for (i = 0; i < 16; i++) { + switch (GET_PTR_TYPE(tree->a[i])) { + case PTR_TYPE_SUBTREE: + case PTR_TYPE_INTERNAL: + continue; + default: + return fanout; + } + } + return fanout + 1; +} + +static void construct_path_with_fanout(const unsigned char *sha1, + unsigned char fanout, char *path) +{ + unsigned int i = 0, j = 0; + const char *hex_sha1 = sha1_to_hex(sha1); + assert(fanout < 20); + while (fanout) { + path[i++] = hex_sha1[j++]; + path[i++] = hex_sha1[j++]; + path[i++] = '/'; + fanout--; + } + strcpy(path + i, hex_sha1 + j); +} + +static int for_each_note_helper(struct notes_tree *t, struct int_node *tree, + unsigned char n, unsigned char fanout, int flags, + each_note_fn fn, void *cb_data) +{ + unsigned int i; + void *p; + int ret = 0; + struct leaf_node *l; + static char path[40 + 19 + 1]; /* hex SHA1 + 19 * '/' + NUL */ + + fanout = determine_fanout(tree, n, fanout); + for (i = 0; i < 16; i++) { +redo: + p = tree->a[i]; + switch (GET_PTR_TYPE(p)) { + case PTR_TYPE_INTERNAL: + /* recurse into int_node */ + ret = for_each_note_helper(t, CLR_PTR_TYPE(p), n + 1, + fanout, flags, fn, cb_data); + break; + case PTR_TYPE_SUBTREE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + /* + * Subtree entries in the note tree represent parts of + * the note tree that have not yet been explored. There + * is a direct relationship between subtree entries at + * level 'n' in the tree, and the 'fanout' variable: + * Subtree entries at level 'n <= 2 * fanout' should be + * preserved, since they correspond exactly to a fanout + * directory in the on-disk structure. However, subtree + * entries at level 'n > 2 * fanout' should NOT be + * preserved, but rather consolidated into the above + * notes tree level. We achieve this by unconditionally + * unpacking subtree entries that exist below the + * threshold level at 'n = 2 * fanout'. + */ + if (n <= 2 * fanout && + flags & FOR_EACH_NOTE_YIELD_SUBTREES) { + /* invoke callback with subtree */ + unsigned int path_len = + l->key_sha1[19] * 2 + fanout; + assert(path_len < 40 + 19); + construct_path_with_fanout(l->key_sha1, fanout, + path); + /* Create trailing slash, if needed */ + if (path[path_len - 1] != '/') + path[path_len++] = '/'; + path[path_len] = '\0'; + ret = fn(l->key_sha1, l->val_sha1, path, + cb_data); + } + if (n > fanout * 2 || + !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) { + /* unpack subtree and resume traversal */ + tree->a[i] = NULL; + load_subtree(t, l, tree, n); + free(l); + goto redo; + } + break; + case PTR_TYPE_NOTE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + construct_path_with_fanout(l->key_sha1, fanout, path); + ret = fn(l->key_sha1, l->val_sha1, path, cb_data); + break; + } + if (ret) + return ret; + } + return 0; +} + +struct tree_write_stack { + struct tree_write_stack *next; + struct strbuf buf; + char path[2]; /* path to subtree in next, if any */ +}; + +static inline int matches_tree_write_stack(struct tree_write_stack *tws, + const char *full_path) +{ + return full_path[0] == tws->path[0] && + full_path[1] == tws->path[1] && + full_path[2] == '/'; +} + +static void write_tree_entry(struct strbuf *buf, unsigned int mode, + const char *path, unsigned int path_len, const + unsigned char *sha1) +{ + strbuf_addf(buf, "%o %.*s%c", mode, path_len, path, '\0'); + strbuf_add(buf, sha1, 20); +} + +static void tree_write_stack_init_subtree(struct tree_write_stack *tws, + const char *path) +{ + struct tree_write_stack *n; + assert(!tws->next); + assert(tws->path[0] == '\0' && tws->path[1] == '\0'); + n = (struct tree_write_stack *) + xmalloc(sizeof(struct tree_write_stack)); + n->next = NULL; + strbuf_init(&n->buf, 256 * (32 + 40)); /* assume 256 entries per tree */ + n->path[0] = n->path[1] = '\0'; + tws->next = n; + tws->path[0] = path[0]; + tws->path[1] = path[1]; +} + +static int tree_write_stack_finish_subtree(struct tree_write_stack *tws) +{ + int ret; + struct tree_write_stack *n = tws->next; + unsigned char s[20]; + if (n) { + ret = tree_write_stack_finish_subtree(n); + if (ret) + return ret; + ret = write_sha1_file(n->buf.buf, n->buf.len, tree_type, s); + if (ret) + return ret; + strbuf_release(&n->buf); + free(n); + tws->next = NULL; + write_tree_entry(&tws->buf, 040000, tws->path, 2, s); + tws->path[0] = tws->path[1] = '\0'; + } + return 0; +} + +static int write_each_note_helper(struct tree_write_stack *tws, + const char *path, unsigned int mode, + const unsigned char *sha1) +{ + size_t path_len = strlen(path); + unsigned int n = 0; + int ret; + + /* Determine common part of tree write stack */ + while (tws && 3 * n < path_len && + matches_tree_write_stack(tws, path + 3 * n)) { + n++; + tws = tws->next; + } + + /* tws point to last matching tree_write_stack entry */ + ret = tree_write_stack_finish_subtree(tws); + if (ret) + return ret; + + /* Start subtrees needed to satisfy path */ + while (3 * n + 2 < path_len && path[3 * n + 2] == '/') { + tree_write_stack_init_subtree(tws, path + 3 * n); + n++; + tws = tws->next; + } + + /* There should be no more directory components in the given path */ + assert(memchr(path + 3 * n, '/', path_len - (3 * n)) == NULL); + + /* Finally add given entry to the current tree object */ + write_tree_entry(&tws->buf, mode, path + 3 * n, path_len - (3 * n), + sha1); + + return 0; +} + +struct write_each_note_data { + struct tree_write_stack *root; + struct non_note *next_non_note; +}; + +static int write_each_non_note_until(const char *note_path, + struct write_each_note_data *d) +{ + struct non_note *n = d->next_non_note; + int cmp = 0, ret; + while (n && (!note_path || (cmp = strcmp(n->path, note_path)) <= 0)) { + if (note_path && cmp == 0) + ; /* do nothing, prefer note to non-note */ + else { + ret = write_each_note_helper(d->root, n->path, n->mode, + n->sha1); + if (ret) + return ret; } + n = n->next; + } + d->next_non_note = n; + return 0; +} + +static int write_each_note(const unsigned char *object_sha1, + const unsigned char *note_sha1, char *note_path, + void *cb_data) +{ + struct write_each_note_data *d = + (struct write_each_note_data *) cb_data; + size_t note_path_len = strlen(note_path); + unsigned int mode = 0100644; + + if (note_path[note_path_len - 1] == '/') { + /* subtree entry */ + note_path_len--; + note_path[note_path_len] = '\0'; + mode = 040000; } + assert(note_path_len <= 40 + 19); + + /* Weave non-note entries into note entries */ + return write_each_non_note_until(note_path, d) || + write_each_note_helper(d->root, note_path, mode, note_sha1); +} + +struct note_delete_list { + struct note_delete_list *next; + const unsigned char *sha1; +}; + +static int prune_notes_helper(const unsigned char *object_sha1, + const unsigned char *note_sha1, char *note_path, + void *cb_data) +{ + struct note_delete_list **l = (struct note_delete_list **) cb_data; + struct note_delete_list *n; + + if (has_sha1_file(object_sha1)) + return 0; /* nothing to do for this note */ + + /* failed to find object => prune this note */ + n = (struct note_delete_list *) xmalloc(sizeof(*n)); + n->next = *l; + n->sha1 = object_sha1; + *l = n; + return 0; +} + +int combine_notes_concatenate(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + char *cur_msg = NULL, *new_msg = NULL, *buf; + unsigned long cur_len, new_len, buf_len; + enum object_type cur_type, new_type; + int ret; + + /* read in both note blob objects */ + if (!is_null_sha1(new_sha1)) + new_msg = read_sha1_file(new_sha1, &new_type, &new_len); + if (!new_msg || !new_len || new_type != OBJ_BLOB) { + free(new_msg); + return 0; + } + if (!is_null_sha1(cur_sha1)) + cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len); + if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) { + free(cur_msg); + free(new_msg); + hashcpy(cur_sha1, new_sha1); + return 0; + } + + /* we will separate the notes by a newline anyway */ + if (cur_msg[cur_len - 1] == '\n') + cur_len--; + + /* concatenate cur_msg and new_msg into buf */ + buf_len = cur_len + 1 + new_len; + buf = (char *) xmalloc(buf_len); + memcpy(buf, cur_msg, cur_len); + buf[cur_len] = '\n'; + memcpy(buf + cur_len + 1, new_msg, new_len); + free(cur_msg); + free(new_msg); + + /* create a new blob object from buf */ + ret = write_sha1_file(buf, buf_len, blob_type, cur_sha1); free(buf); + return ret; +} + +int combine_notes_overwrite(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + hashcpy(cur_sha1, new_sha1); + return 0; +} + +int combine_notes_ignore(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + return 0; +} + +static int string_list_add_one_ref(const char *path, const unsigned char *sha1, + int flag, void *cb) +{ + struct string_list *refs = cb; + if (!unsorted_string_list_has_string(refs, path)) + string_list_append(refs, path); + return 0; +} + +void string_list_add_refs_by_glob(struct string_list *list, const char *glob) +{ + if (has_glob_specials(glob)) { + for_each_glob_ref(string_list_add_one_ref, glob, list); + } else { + unsigned char sha1[20]; + if (get_sha1(glob, sha1)) + warning("notes ref %s is invalid", glob); + if (!unsorted_string_list_has_string(list, glob)) + string_list_append(list, glob); + } +} + +void string_list_add_refs_from_colon_sep(struct string_list *list, + const char *globs) +{ + struct strbuf globbuf = STRBUF_INIT; + struct strbuf **split; + int i; + + strbuf_addstr(&globbuf, globs); + split = strbuf_split(&globbuf, ':'); + + for (i = 0; split[i]; i++) { + if (!split[i]->len) + continue; + if (split[i]->buf[split[i]->len-1] == ':') + strbuf_setlen(split[i], split[i]->len-1); + string_list_add_refs_by_glob(list, split[i]->buf); + } + + strbuf_list_free(split); + strbuf_release(&globbuf); +} + +static int string_list_add_refs_from_list(struct string_list_item *item, + void *cb) +{ + struct string_list *list = cb; + string_list_add_refs_by_glob(list, item->string); + return 0; +} + +static int notes_display_config(const char *k, const char *v, void *cb) +{ + int *load_refs = cb; + + if (*load_refs && !strcmp(k, "notes.displayref")) { + if (!v) + config_error_nonbool(k); + string_list_add_refs_by_glob(&display_notes_refs, v); + } + + return 0; } -static void initialize_notes(const char *notes_ref_name) +static const char *default_notes_ref(void) { - unsigned char sha1[20], commit_sha1[20]; + const char *notes_ref = NULL; + if (!notes_ref) + notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT); + if (!notes_ref) + notes_ref = notes_ref_name; /* value of core.notesRef config */ + if (!notes_ref) + notes_ref = GIT_NOTES_DEFAULT_REF; + return notes_ref; +} + +void init_notes(struct notes_tree *t, const char *notes_ref, + combine_notes_fn combine_notes, int flags) +{ + unsigned char sha1[20], object_sha1[20]; unsigned mode; struct leaf_node root_tree; - if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) || - get_tree_entry(commit_sha1, "", sha1, &mode)) + if (!t) + t = &default_notes_tree; + assert(!t->initialized); + + if (!notes_ref) + notes_ref = default_notes_ref(); + + if (!combine_notes) + combine_notes = combine_notes_concatenate; + + t->root = (struct int_node *) xcalloc(sizeof(struct int_node), 1); + t->first_non_note = NULL; + t->prev_non_note = NULL; + t->ref = notes_ref ? xstrdup(notes_ref) : NULL; + t->combine_notes = combine_notes; + t->initialized = 1; + t->dirty = 0; + + if (flags & NOTES_INIT_EMPTY || !notes_ref || + read_ref(notes_ref, object_sha1)) return; + if (get_tree_entry(object_sha1, "", sha1, &mode)) + die("Failed to read notes tree referenced by %s (%s)", + notes_ref, object_sha1); hashclr(root_tree.key_sha1); hashcpy(root_tree.val_sha1, sha1); - load_subtree(&root_tree, &root_node, 0); + load_subtree(t, &root_tree, t->root, 0); } -static unsigned char *lookup_notes(const unsigned char *commit_sha1) +struct load_notes_cb_data { + int counter; + struct notes_tree **trees; +}; + +static int load_one_display_note_ref(struct string_list_item *item, + void *cb_data) { - struct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1); - if (found) - return found->val_sha1; - return NULL; + struct load_notes_cb_data *c = cb_data; + struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree)); + init_notes(t, item->string, combine_notes_ignore, 0); + c->trees[c->counter++] = t; + return 0; } -void free_notes(void) +struct notes_tree **load_notes_trees(struct string_list *refs) { - note_tree_free(&root_node); - memset(&root_node, 0, sizeof(struct int_node)); - initialized = 0; + struct notes_tree **trees; + struct load_notes_cb_data cb_data; + trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *)); + cb_data.counter = 0; + cb_data.trees = trees; + for_each_string_list(refs, load_one_display_note_ref, &cb_data); + trees[cb_data.counter] = NULL; + return trees; +} + +void init_display_notes(struct display_notes_opt *opt) +{ + char *display_ref_env; + int load_config_refs = 0; + display_notes_refs.strdup_strings = 1; + + assert(!display_notes_trees); + + if (!opt || !opt->suppress_default_notes) { + string_list_append(&display_notes_refs, default_notes_ref()); + display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT); + if (display_ref_env) { + string_list_add_refs_from_colon_sep(&display_notes_refs, + display_ref_env); + load_config_refs = 0; + } else + load_config_refs = 1; + } + + git_config(notes_display_config, &load_config_refs); + + if (opt && opt->extra_notes_refs) + for_each_string_list(opt->extra_notes_refs, + string_list_add_refs_from_list, + &display_notes_refs); + + display_notes_trees = load_notes_trees(&display_notes_refs); + string_list_clear(&display_notes_refs, 0); +} + +void add_note(struct notes_tree *t, const unsigned char *object_sha1, + const unsigned char *note_sha1, combine_notes_fn combine_notes) +{ + struct leaf_node *l; + + if (!t) + t = &default_notes_tree; + assert(t->initialized); + t->dirty = 1; + if (!combine_notes) + combine_notes = t->combine_notes; + l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node)); + hashcpy(l->key_sha1, object_sha1); + hashcpy(l->val_sha1, note_sha1); + note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes); +} + +void remove_note(struct notes_tree *t, const unsigned char *object_sha1) +{ + struct leaf_node l; + + if (!t) + t = &default_notes_tree; + assert(t->initialized); + t->dirty = 1; + hashcpy(l.key_sha1, object_sha1); + hashclr(l.val_sha1); + note_tree_remove(t, t->root, 0, &l); } -void get_commit_notes(const struct commit *commit, struct strbuf *sb, - const char *output_encoding, int flags) +const unsigned char *get_note(struct notes_tree *t, + const unsigned char *object_sha1) +{ + struct leaf_node *found; + + if (!t) + t = &default_notes_tree; + assert(t->initialized); + found = note_tree_find(t, t->root, 0, object_sha1); + return found ? found->val_sha1 : NULL; +} + +int for_each_note(struct notes_tree *t, int flags, each_note_fn fn, + void *cb_data) +{ + if (!t) + t = &default_notes_tree; + assert(t->initialized); + return for_each_note_helper(t, t->root, 0, 0, flags, fn, cb_data); +} + +int write_notes_tree(struct notes_tree *t, unsigned char *result) +{ + struct tree_write_stack root; + struct write_each_note_data cb_data; + int ret; + + if (!t) + t = &default_notes_tree; + assert(t->initialized); + + /* Prepare for traversal of current notes tree */ + root.next = NULL; /* last forward entry in list is grounded */ + strbuf_init(&root.buf, 256 * (32 + 40)); /* assume 256 entries */ + root.path[0] = root.path[1] = '\0'; + cb_data.root = &root; + cb_data.next_non_note = t->first_non_note; + + /* Write tree objects representing current notes tree */ + ret = for_each_note(t, FOR_EACH_NOTE_DONT_UNPACK_SUBTREES | + FOR_EACH_NOTE_YIELD_SUBTREES, + write_each_note, &cb_data) || + write_each_non_note_until(NULL, &cb_data) || + tree_write_stack_finish_subtree(&root) || + write_sha1_file(root.buf.buf, root.buf.len, tree_type, result); + strbuf_release(&root.buf); + return ret; +} + +void prune_notes(struct notes_tree *t, int flags) +{ + struct note_delete_list *l = NULL; + + if (!t) + t = &default_notes_tree; + assert(t->initialized); + + for_each_note(t, 0, prune_notes_helper, &l); + + while (l) { + if (flags & NOTES_PRUNE_VERBOSE) + printf("%s\n", sha1_to_hex(l->sha1)); + if (!(flags & NOTES_PRUNE_DRYRUN)) + remove_note(t, l->sha1); + l = l->next; + } +} + +void free_notes(struct notes_tree *t) +{ + if (!t) + t = &default_notes_tree; + if (t->root) + note_tree_free(t->root); + free(t->root); + while (t->first_non_note) { + t->prev_non_note = t->first_non_note->next; + free(t->first_non_note->path); + free(t->first_non_note); + t->first_non_note = t->prev_non_note; + } + free(t->ref); + memset(t, 0, sizeof(struct notes_tree)); +} + +void format_note(struct notes_tree *t, const unsigned char *object_sha1, + struct strbuf *sb, const char *output_encoding, int flags) { static const char utf8[] = "utf-8"; - unsigned char *sha1; + const unsigned char *sha1; char *msg, *msg_p; unsigned long linelen, msglen; enum object_type type; - if (!initialized) { - const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT); - if (env) - notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); - else if (!notes_ref_name) - notes_ref_name = GIT_NOTES_DEFAULT_REF; - initialize_notes(notes_ref_name); - initialized = 1; - } + if (!t) + t = &default_notes_tree; + if (!t->initialized) + init_notes(t, NULL, NULL, 0); - sha1 = lookup_notes(commit->object.sha1); + sha1 = get_note(t, object_sha1); if (!sha1) return; @@ -415,8 +1157,18 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, if (msglen && msg[msglen - 1] == '\n') msglen--; - if (flags & NOTES_SHOW_HEADER) - strbuf_addstr(sb, "\nNotes:\n"); + if (flags & NOTES_SHOW_HEADER) { + const char *ref = t->ref; + if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) { + strbuf_addstr(sb, "\nNotes:\n"); + } else { + if (!prefixcmp(ref, "refs/")) + ref += 5; + if (!prefixcmp(ref, "notes/")) + ref += 6; + strbuf_addf(sb, "\nNotes (%s):\n", ref); + } + } for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { linelen = strchrnul(msg_p, '\n') - msg_p; @@ -429,3 +1181,31 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, free(msg); } + +void format_display_notes(const unsigned char *object_sha1, + struct strbuf *sb, const char *output_encoding, int flags) +{ + int i; + assert(display_notes_trees); + for (i = 0; display_notes_trees[i]; i++) + format_note(display_notes_trees[i], object_sha1, sb, + output_encoding, flags); +} + +int copy_note(struct notes_tree *t, + const unsigned char *from_obj, const unsigned char *to_obj, + int force, combine_notes_fn combine_fn) +{ + const unsigned char *note = get_note(t, from_obj); + const unsigned char *existing_note = get_note(t, to_obj); + + if (!force && existing_note) + return 1; + + if (note) + add_note(t, to_obj, note, combine_fn); + else if (existing_note) + add_note(t, to_obj, null_sha1, combine_fn); + + return 0; +} @@ -1,13 +1,269 @@ #ifndef NOTES_H #define NOTES_H -/* Free (and de-initialize) the internal notes tree structure */ -void free_notes(void); +/* + * Function type for combining two notes annotating the same object. + * + * When adding a new note annotating the same object as an existing note, it is + * up to the caller to decide how to combine the two notes. The decision is + * made by passing in a function of the following form. The function accepts + * two SHA1s -- of the existing note and the new note, respectively. The + * function then combines the notes in whatever way it sees fit, and writes the + * resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return + * value indicates failure. + * + * The two given SHA1s must both be non-NULL and different from each other. + * + * The default combine_notes function (you get this when passing NULL) is + * combine_notes_concatenate(), which appends the contents of the new note to + * the contents of the existing note. + */ +typedef int (*combine_notes_fn)(unsigned char *cur_sha1, const unsigned char *new_sha1); +/* Common notes combinators */ +int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1); +int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1); +int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1); + +/* + * Notes tree object + * + * Encapsulates the internal notes tree structure associated with a notes ref. + * Whenever a struct notes_tree pointer is required below, you may pass NULL in + * order to use the default/internal notes tree. E.g. you only need to pass a + * non-NULL value if you need to refer to several different notes trees + * simultaneously. + */ +extern struct notes_tree { + struct int_node *root; + struct non_note *first_non_note, *prev_non_note; + char *ref; + combine_notes_fn combine_notes; + int initialized; + int dirty; +} default_notes_tree; + +/* + * Flags controlling behaviour of notes tree initialization + * + * Default behaviour is to initialize the notes tree from the tree object + * specified by the given (or default) notes ref. + */ +#define NOTES_INIT_EMPTY 1 + +/* + * Initialize the given notes_tree with the notes tree structure at the given + * ref. If given ref is NULL, the value of the $GIT_NOTES_REF environment + * variable is used, and if that is missing, the default notes ref is used + * ("refs/notes/commits"). + * + * If you need to re-intialize a notes_tree structure (e.g. when switching from + * one notes ref to another), you must first de-initialize the notes_tree + * structure by calling free_notes(struct notes_tree *). + * + * If you pass t == NULL, the default internal notes_tree will be initialized. + * + * The combine_notes function that is passed becomes the default combine_notes + * function for the given notes_tree. If NULL is passed, the default + * combine_notes function is combine_notes_concatenate(). + * + * Precondition: The notes_tree structure is zeroed (this can be achieved with + * memset(t, 0, sizeof(struct notes_tree))) + */ +void init_notes(struct notes_tree *t, const char *notes_ref, + combine_notes_fn combine_notes, int flags); + +/* + * Add the given note object to the given notes_tree structure + * + * IMPORTANT: The changes made by add_note() to the given notes_tree structure + * are not persistent until a subsequent call to write_notes_tree() returns + * zero. + */ +void add_note(struct notes_tree *t, const unsigned char *object_sha1, + const unsigned char *note_sha1, combine_notes_fn combine_notes); + +/* + * Remove the given note object from the given notes_tree structure + * + * IMPORTANT: The changes made by remove_note() to the given notes_tree + * structure are not persistent until a subsequent call to write_notes_tree() + * returns zero. + */ +void remove_note(struct notes_tree *t, const unsigned char *object_sha1); + +/* + * Get the note object SHA1 containing the note data for the given object + * + * Return NULL if the given object has no notes. + */ +const unsigned char *get_note(struct notes_tree *t, + const unsigned char *object_sha1); + +/* + * Copy a note from one object to another in the given notes_tree. + * + * Fails if the to_obj already has a note unless 'force' is true. + */ +int copy_note(struct notes_tree *t, + const unsigned char *from_obj, const unsigned char *to_obj, + int force, combine_notes_fn combine_fn); + +/* + * Flags controlling behaviour of for_each_note() + * + * Default behaviour of for_each_note() is to traverse every single note object + * in the given notes tree, unpacking subtree entries along the way. + * The following flags can be used to alter the default behaviour: + * + * - DONT_UNPACK_SUBTREES causes for_each_note() NOT to unpack and recurse into + * subtree entries while traversing the notes tree. This causes notes within + * those subtrees NOT to be passed to the callback. Use this flag if you + * don't want to traverse _all_ notes, but only want to traverse the parts + * of the notes tree that have already been unpacked (this includes at least + * all notes that have been added/changed). + * + * - YIELD_SUBTREES causes any subtree entries that are encountered to be + * passed to the callback, before recursing into them. Subtree entries are + * not note objects, but represent intermediate directories in the notes + * tree. When passed to the callback, subtree entries will have a trailing + * slash in their path, which the callback may use to differentiate between + * note entries and subtree entries. Note that already-unpacked subtree + * entries are not part of the notes tree, and will therefore not be yielded. + * If this flag is used together with DONT_UNPACK_SUBTREES, for_each_note() + * will yield the subtree entry, but not recurse into it. + */ +#define FOR_EACH_NOTE_DONT_UNPACK_SUBTREES 1 +#define FOR_EACH_NOTE_YIELD_SUBTREES 2 + +/* + * Invoke the specified callback function for each note in the given notes_tree + * + * If the callback returns nonzero, the note walk is aborted, and the return + * value from the callback is returned from for_each_note(). Hence, a zero + * return value from for_each_note() indicates that all notes were walked + * successfully. + * + * IMPORTANT: The callback function is NOT allowed to change the notes tree. + * In other words, the following functions can NOT be invoked (on the current + * notes tree) from within the callback: + * - add_note() + * - remove_note() + * - free_notes() + */ +typedef int each_note_fn(const unsigned char *object_sha1, + const unsigned char *note_sha1, char *note_path, + void *cb_data); +int for_each_note(struct notes_tree *t, int flags, each_note_fn fn, + void *cb_data); + +/* + * Write the given notes_tree structure to the object database + * + * Creates a new tree object encapsulating the current state of the given + * notes_tree, and stores its SHA1 into the 'result' argument. + * + * Returns zero on success, non-zero on failure. + * + * IMPORTANT: Changes made to the given notes_tree are not persistent until + * this function has returned zero. Please also remember to create a + * corresponding commit object, and update the appropriate notes ref. + */ +int write_notes_tree(struct notes_tree *t, unsigned char *result); + +/* Flags controlling the operation of prune */ +#define NOTES_PRUNE_VERBOSE 1 +#define NOTES_PRUNE_DRYRUN 2 +/* + * Remove all notes annotating non-existing objects from the given notes tree + * + * All notes in the given notes_tree that are associated with objects that no + * longer exist in the database, are removed from the notes tree. + * + * IMPORTANT: The changes made by prune_notes() to the given notes_tree + * structure are not persistent until a subsequent call to write_notes_tree() + * returns zero. + */ +void prune_notes(struct notes_tree *t, int flags); + +/* + * Free (and de-initialize) the given notes_tree structure + * + * IMPORTANT: Changes made to the given notes_tree since the last, successful + * call to write_notes_tree() will be lost. + */ +void free_notes(struct notes_tree *t); + +/* Flags controlling how notes are formatted */ #define NOTES_SHOW_HEADER 1 #define NOTES_INDENT 2 -void get_commit_notes(const struct commit *commit, struct strbuf *sb, - const char *output_encoding, int flags); +/* + * Fill the given strbuf with the notes associated with the given object. + * + * If the given notes_tree structure is not initialized, it will be auto- + * initialized to the default value (see documentation for init_notes() above). + * If the given notes_tree is NULL, the internal/default notes_tree will be + * used instead. + * + * 'flags' is a bitwise combination of the above formatting flags. + */ +void format_note(struct notes_tree *t, const unsigned char *object_sha1, + struct strbuf *sb, const char *output_encoding, int flags); + + +struct string_list; + +struct display_notes_opt { + unsigned int suppress_default_notes:1; + struct string_list *extra_notes_refs; +}; + +/* + * Load the notes machinery for displaying several notes trees. + * + * If 'opt' is not NULL, then it specifies additional settings for the + * displaying: + * + * - suppress_default_notes indicates that the notes from + * core.notesRef and notes.displayRef should not be loaded. + * + * - extra_notes_refs may contain a list of globs (in the same style + * as notes.displayRef) where notes should be loaded from. + */ +void init_display_notes(struct display_notes_opt *opt); + +/* + * Append notes for the given 'object_sha1' from all trees set up by + * init_display_notes() to 'sb'. The 'flags' are a bitwise + * combination of + * + * - NOTES_SHOW_HEADER: add a 'Notes (refname):' header + * + * - NOTES_INDENT: indent the notes by 4 places + * + * You *must* call init_display_notes() before using this function. + */ +void format_display_notes(const unsigned char *object_sha1, + struct strbuf *sb, const char *output_encoding, int flags); + +/* + * Load the notes tree from each ref listed in 'refs'. The output is + * an array of notes_tree*, terminated by a NULL. + */ +struct notes_tree **load_notes_trees(struct string_list *refs); + +/* + * Add all refs that match 'glob' to the 'list'. + */ +void string_list_add_refs_by_glob(struct string_list *list, const char *glob); + +/* + * Add all refs from a colon-separated glob list 'globs' to the end of + * 'list'. Empty components are ignored. This helper is used to + * parse GIT_NOTES_DISPLAY_REF style environment variables. + */ +void string_list_add_refs_from_colon_sep(struct string_list *list, + const char *globs); #endif @@ -252,10 +252,10 @@ void add_object_array_with_mode(struct object *obj, const char *name, struct obj void object_array_remove_duplicates(struct object_array *array) { - int ref, src, dst; + unsigned int ref, src, dst; struct object_array_entry *objects = array->objects; - for (ref = 0; ref < array->nr - 1; ref++) { + for (ref = 0; ref + 1 < array->nr; ref++) { for (src = ref + 1, dst = src; src < array->nr; src++) { diff --git a/pack-check.c b/pack-check.c index 166ca703c1..395fb9527a 100644 --- a/pack-check.c +++ b/pack-check.c @@ -133,14 +133,13 @@ static int verify_packfile(struct packed_git *p, return err; } -int verify_pack(struct packed_git *p) +int verify_pack_index(struct packed_git *p) { off_t index_size; const unsigned char *index_base; git_SHA_CTX ctx; unsigned char sha1[20]; int err = 0; - struct pack_window *w_curs = NULL; if (open_pack_index(p)) return error("packfile %s index not opened", p->pack_name); @@ -154,8 +153,18 @@ int verify_pack(struct packed_git *p) if (hashcmp(sha1, index_base + index_size - 20)) err = error("Packfile index for %s SHA1 mismatch", p->pack_name); + return err; +} + +int verify_pack(struct packed_git *p) +{ + int err = 0; + struct pack_window *w_curs = NULL; + + err |= verify_pack_index(p); + if (!p->index_data) + return -1; - /* Verify pack file */ err |= verify_packfile(p, &w_curs); unuse_pack(&w_curs); diff --git a/pack-refs.c b/pack-refs.c index 7f43f8ac33..1290570260 100644 --- a/pack-refs.c +++ b/pack-refs.c @@ -60,6 +60,37 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, return 0; } +/* + * Remove empty parents, but spare refs/ and immediate subdirs. + * Note: munges *name. + */ +static void try_remove_empty_parents(char *name) +{ + char *p, *q; + int i; + p = name; + for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ + while (*p && *p != '/') + p++; + /* tolerate duplicate slashes; see check_ref_format() */ + while (*p == '/') + p++; + } + for (q = p; *q; q++) + ; + while (1) { + while (q > p && *q != '/') + q--; + while (q > p && *(q-1) == '/') + q--; + if (q == p) + break; + *q = '\0'; + if (rmdir(git_path("%s", name))) + break; + } +} + /* make sure nobody touched the ref, and unlink */ static void prune_ref(struct ref_to_prune *r) { @@ -68,6 +99,7 @@ static void prune_ref(struct ref_to_prune *r) if (lock) { unlink_or_warn(git_path("%s", r->name)); unlock_ref(lock); + try_remove_empty_parents(r->name); } } diff --git a/pack-write.c b/pack-write.c index 9f47cf9961..a905ca4486 100644 --- a/pack-write.c +++ b/pack-write.c @@ -253,3 +253,30 @@ char *index_pack_lockfile(int ip_out) } return NULL; } + +/* + * The per-object header is a pretty dense thing, which is + * - first byte: low four bits are "size", then three bits of "type", + * and the high bit is "size continues". + * - each byte afterwards: low seven bits are size continuation, + * with the high bit being "size continues" + */ +int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned char *hdr) +{ + int n = 1; + unsigned char c; + + if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) + die("bad type %d", type); + + c = (type << 4) | (size & 15); + size >>= 4; + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + n++; + } + *hdr = c; + return n; +} @@ -57,9 +57,11 @@ struct pack_idx_entry { extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, 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 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 *); #define PH_ERROR_EOF (-1) #define PH_ERROR_PACK_SIGNATURE (-2) diff --git a/parse-options.c b/parse-options.c index d218122af5..0fa79bc31d 100644 --- a/parse-options.c +++ b/parse-options.c @@ -2,9 +2,11 @@ #include "parse-options.h" #include "cache.h" #include "commit.h" +#include "color.h" -static int parse_options_usage(const char * const *usagestr, - const struct option *opts); +static int parse_options_usage(struct parse_opt_ctx_t *ctx, + const char * const *usagestr, + const struct option *opts, int err); #define OPT_SHORT 1 #define OPT_UNSET 2 @@ -350,8 +352,9 @@ void parse_options_start(struct parse_opt_ctx_t *ctx, die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together"); } -static int usage_with_options_internal(const char * const *, - const struct option *, int); +static int usage_with_options_internal(struct parse_opt_ctx_t *, + const char * const *, + const struct option *, int, int); int parse_options_step(struct parse_opt_ctx_t *ctx, const struct option *options, @@ -379,10 +382,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, if (arg[1] != '-') { ctx->opt = arg + 1; if (internal_help && *ctx->opt == 'h') - return parse_options_usage(usagestr, options); + return parse_options_usage(ctx, usagestr, options, 0); switch (parse_short_opt(ctx, options)) { case -1: - return parse_options_usage(usagestr, options); + return parse_options_usage(ctx, usagestr, options, 1); case -2: goto unknown; } @@ -390,10 +393,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, check_typos(arg + 1, options); while (ctx->opt) { if (internal_help && *ctx->opt == 'h') - return parse_options_usage(usagestr, options); + return parse_options_usage(ctx, usagestr, options, 0); switch (parse_short_opt(ctx, options)) { case -1: - return parse_options_usage(usagestr, options); + return parse_options_usage(ctx, usagestr, options, 1); case -2: /* fake a short option thing to hide the fact that we may have * started to parse aggregated stuff @@ -417,12 +420,12 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, } if (internal_help && !strcmp(arg + 2, "help-all")) - return usage_with_options_internal(usagestr, options, 1); + return usage_with_options_internal(ctx, usagestr, options, 1, 0); if (internal_help && !strcmp(arg + 2, "help")) - return parse_options_usage(usagestr, options); + return parse_options_usage(ctx, usagestr, options, 0); switch (parse_long_opt(ctx, arg + 2, options)) { case -1: - return parse_options_usage(usagestr, options); + return parse_options_usage(ctx, usagestr, options, 1); case -2: goto unknown; } @@ -467,7 +470,7 @@ int parse_options(int argc, const char **argv, const char *prefix, return parse_options_end(&ctx); } -static int usage_argh(const struct option *opts) +static int usage_argh(const struct option *opts, FILE *outfile) { const char *s; int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) || !opts->argh; @@ -478,72 +481,81 @@ static int usage_argh(const struct option *opts) s = literal ? "[%s]" : "[<%s>]"; else s = literal ? " %s" : " <%s>"; - return fprintf(stderr, s, opts->argh ? opts->argh : "..."); + return fprintf(outfile, s, opts->argh ? opts->argh : "..."); } #define USAGE_OPTS_WIDTH 24 #define USAGE_GAP 2 -static int usage_with_options_internal(const char * const *usagestr, - const struct option *opts, int full) +static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, + const char * const *usagestr, + const struct option *opts, int full, int err) { + FILE *outfile = err ? stderr : stdout; + if (!usagestr) return PARSE_OPT_HELP; - fprintf(stderr, "usage: %s\n", *usagestr++); + if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL) + fprintf(outfile, "cat <<\\EOF\n"); + + fprintf(outfile, "usage: %s\n", *usagestr++); while (*usagestr && **usagestr) - fprintf(stderr, " or: %s\n", *usagestr++); + fprintf(outfile, " or: %s\n", *usagestr++); while (*usagestr) { - fprintf(stderr, "%s%s\n", + fprintf(outfile, "%s%s\n", **usagestr ? " " : "", *usagestr); usagestr++; } if (opts->type != OPTION_GROUP) - fputc('\n', stderr); + fputc('\n', outfile); for (; opts->type != OPTION_END; opts++) { size_t pos; int pad; if (opts->type == OPTION_GROUP) { - fputc('\n', stderr); + fputc('\n', outfile); if (*opts->help) - fprintf(stderr, "%s\n", opts->help); + fprintf(outfile, "%s\n", opts->help); continue; } if (!full && (opts->flags & PARSE_OPT_HIDDEN)) continue; - pos = fprintf(stderr, " "); + pos = fprintf(outfile, " "); if (opts->short_name && !(opts->flags & PARSE_OPT_NEGHELP)) { if (opts->flags & PARSE_OPT_NODASH) - pos += fprintf(stderr, "%c", opts->short_name); + pos += fprintf(outfile, "%c", opts->short_name); else - pos += fprintf(stderr, "-%c", opts->short_name); + pos += fprintf(outfile, "-%c", opts->short_name); } if (opts->long_name && opts->short_name) - pos += fprintf(stderr, ", "); + pos += fprintf(outfile, ", "); if (opts->long_name) - pos += fprintf(stderr, "--%s%s", + pos += fprintf(outfile, "--%s%s", (opts->flags & PARSE_OPT_NEGHELP) ? "no-" : "", opts->long_name); if (opts->type == OPTION_NUMBER) - pos += fprintf(stderr, "-NUM"); + pos += fprintf(outfile, "-NUM"); if (!(opts->flags & PARSE_OPT_NOARG)) - pos += usage_argh(opts); + pos += usage_argh(opts, outfile); if (pos <= USAGE_OPTS_WIDTH) pad = USAGE_OPTS_WIDTH - pos; else { - fputc('\n', stderr); + fputc('\n', outfile); pad = USAGE_OPTS_WIDTH; } - fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help); + fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", opts->help); } - fputc('\n', stderr); + fputc('\n', outfile); + + if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL) + fputs("EOF\n", outfile); return PARSE_OPT_HELP; } @@ -551,7 +563,7 @@ static int usage_with_options_internal(const char * const *usagestr, void usage_with_options(const char * const *usagestr, const struct option *opts) { - usage_with_options_internal(usagestr, opts, 0); + usage_with_options_internal(NULL, usagestr, opts, 0, 1); exit(129); } @@ -563,10 +575,11 @@ void usage_msg_opt(const char *msg, usage_with_options(usagestr, options); } -static int parse_options_usage(const char * const *usagestr, - const struct option *opts) +static int parse_options_usage(struct parse_opt_ctx_t *ctx, + const char * const *usagestr, + const struct option *opts, int err) { - return usage_with_options_internal(usagestr, opts, 0); + return usage_with_options_internal(ctx, usagestr, opts, 0, err); } @@ -599,6 +612,21 @@ int parse_opt_approxidate_cb(const struct option *opt, const char *arg, return 0; } +int parse_opt_color_flag_cb(const struct option *opt, const char *arg, + int unset) +{ + int value; + + if (!arg) + arg = unset ? "never" : (const char *)opt->defval; + value = git_config_colorbool(NULL, arg, -1); + if (value < 0) + return opterror(opt, + "expects \"always\", \"auto\", or \"never\"", 0); + *(int *)opt->value = value; + return 0; +} + int parse_opt_verbosity_cb(const struct option *opt, const char *arg, int unset) { @@ -643,3 +671,18 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset) *target = unset ? 2 : 1; return 0; } + +int parse_options_concat(struct option *dst, size_t dst_size, struct option *src) +{ + int i, j; + + for (i = 0; i < dst_size; i++) + if (dst[i].type == OPTION_END) + break; + for (j = 0; i < dst_size; i++, j++) { + dst[i] = src[j]; + if (src[j].type == OPTION_END) + return 0; + } + return -1; +} diff --git a/parse-options.h b/parse-options.h index 0c996916b6..7435cdbf1d 100644 --- a/parse-options.h +++ b/parse-options.h @@ -25,7 +25,7 @@ enum parse_opt_flags { PARSE_OPT_STOP_AT_NON_OPTION = 2, PARSE_OPT_KEEP_ARGV0 = 4, PARSE_OPT_KEEP_UNKNOWN = 8, - PARSE_OPT_NO_INTERNAL_HELP = 16, + PARSE_OPT_NO_INTERNAL_HELP = 16 }; enum parse_opt_option_flags { @@ -37,6 +37,7 @@ enum parse_opt_option_flags { PARSE_OPT_NODASH = 32, PARSE_OPT_LITERAL_ARGHELP = 64, PARSE_OPT_NEGHELP = 128, + PARSE_OPT_SHELL_EVAL = 256 }; struct option; @@ -135,6 +136,10 @@ struct option { PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } #define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \ "FILE", (h) } +#define OPT_COLOR_FLAG(s, l, v, h) \ + { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \ + parse_opt_color_flag_cb, (intptr_t)"always" } + /* parse_options() will filter out the processed options and leave the * non-option arguments in argv[]. @@ -156,7 +161,7 @@ extern NORETURN void usage_msg_opt(const char *msg, enum { PARSE_OPT_HELP = -1, PARSE_OPT_DONE, - PARSE_OPT_UNKNOWN, + PARSE_OPT_UNKNOWN }; /* @@ -183,10 +188,12 @@ extern int parse_options_step(struct parse_opt_ctx_t *ctx, extern int parse_options_end(struct parse_opt_ctx_t *ctx); +extern int parse_options_concat(struct option *dst, size_t, struct option *src); /*----- some often used options -----*/ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); +extern int parse_opt_color_flag_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); @@ -203,5 +210,7 @@ extern int parse_opt_tertiary(const struct option *, const char *, int); { OPTION_CALLBACK, 0, "abbrev", (var), "n", \ "use <n> digits to display SHA-1s", \ PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } +#define OPT__COLOR(var, h) \ + OPT_COLOR_FLAG(0, "color", (var), (h)) #endif @@ -417,7 +417,7 @@ char *enter_repo(char *path, int strict) if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && validate_headref("HEAD") == 0) { - setenv(GIT_DIR_ENVIRONMENT, ".", 1); + set_git_dir("."); check_repository_format(); return path; } @@ -730,3 +730,10 @@ int daemon_avoid_alias(const char *p) } } } + +int offset_1st_component(const char *path) +{ + if (has_dos_drive_prefix(path)) + return 2 + is_dir_sep(path[2]); + return is_dir_sep(path[0]); +} diff --git a/perl/Git.pm b/perl/Git.pm index 970fe434ed..6cb0dd1934 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -172,7 +172,7 @@ sub repository { } if (defined $opts{Directory}) { - -d $opts{Directory} or throw Error::Simple("Directory not found: $!"); + -d $opts{Directory} or throw Error::Simple("Directory not found: $opts{Directory} $!"); my $search = Git->repository(WorkingCopy => $opts{Directory}); my $dir; @@ -545,7 +545,7 @@ sub wc_chdir { or throw Error::Simple("bare repository"); -d $self->wc_path().'/'.$subdir - or throw Error::Simple("subdir not found: $!"); + or throw Error::Simple("subdir not found: $subdir $!"); # Of course we will not "hold" the subdirectory so anyone # can delete it now and we will never know. But at least we tried. @@ -842,7 +842,7 @@ sub _open_hash_and_insert_object_if_needed { ($self->{hash_object_pid}, $self->{hash_object_in}, $self->{hash_object_out}, $self->{hash_object_ctx}) = - command_bidi_pipe(qw(hash-object -w --stdin-paths)); + command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters)); } sub _close_hash_and_insert_object { @@ -11,6 +11,17 @@ #include "reflog-walk.h" static char *user_format; +static struct cmt_fmt_map { + const char *name; + enum cmit_fmt format; + int is_tformat; + int is_alias; + const char *user_format; +} *commit_formats; +static size_t builtin_formats_len; +static size_t commit_formats_len; +static size_t commit_formats_alloc; +static struct cmt_fmt_map *find_commit_format(const char *sought); static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat) { @@ -21,22 +32,118 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma rev->commit_format = CMIT_FMT_USERFORMAT; } -void get_commit_format(const char *arg, struct rev_info *rev) +static int git_pretty_formats_config(const char *var, const char *value, void *cb) { + struct cmt_fmt_map *commit_format = NULL; + const char *name; + const char *fmt; int i; - static struct cmt_fmt_map { - const char *n; - size_t cmp_len; - enum cmit_fmt v; - } cmt_fmts[] = { - { "raw", 1, CMIT_FMT_RAW }, - { "medium", 1, CMIT_FMT_MEDIUM }, - { "short", 1, CMIT_FMT_SHORT }, - { "email", 1, CMIT_FMT_EMAIL }, - { "full", 5, CMIT_FMT_FULL }, - { "fuller", 5, CMIT_FMT_FULLER }, - { "oneline", 1, CMIT_FMT_ONELINE }, + + if (prefixcmp(var, "pretty.")) + return 0; + + name = var + strlen("pretty."); + for (i = 0; i < builtin_formats_len; i++) { + if (!strcmp(commit_formats[i].name, name)) + return 0; + } + + for (i = builtin_formats_len; i < commit_formats_len; i++) { + if (!strcmp(commit_formats[i].name, name)) { + commit_format = &commit_formats[i]; + break; + } + } + + if (!commit_format) { + ALLOC_GROW(commit_formats, commit_formats_len+1, + commit_formats_alloc); + commit_format = &commit_formats[commit_formats_len]; + memset(commit_format, 0, sizeof(*commit_format)); + commit_formats_len++; + } + + commit_format->name = xstrdup(name); + commit_format->format = CMIT_FMT_USERFORMAT; + git_config_string(&fmt, var, value); + if (!prefixcmp(fmt, "format:") || !prefixcmp(fmt, "tformat:")) { + commit_format->is_tformat = fmt[0] == 't'; + fmt = strchr(fmt, ':') + 1; + } else if (strchr(fmt, '%')) + commit_format->is_tformat = 1; + else + commit_format->is_alias = 1; + commit_format->user_format = fmt; + + return 0; +} + +static void setup_commit_formats(void) +{ + struct cmt_fmt_map builtin_formats[] = { + { "raw", CMIT_FMT_RAW, 0 }, + { "medium", CMIT_FMT_MEDIUM, 0 }, + { "short", CMIT_FMT_SHORT, 0 }, + { "email", CMIT_FMT_EMAIL, 0 }, + { "fuller", CMIT_FMT_FULLER, 0 }, + { "full", CMIT_FMT_FULL, 0 }, + { "oneline", CMIT_FMT_ONELINE, 1 } }; + commit_formats_len = ARRAY_SIZE(builtin_formats); + builtin_formats_len = commit_formats_len; + ALLOC_GROW(commit_formats, commit_formats_len, commit_formats_alloc); + memcpy(commit_formats, builtin_formats, + sizeof(*builtin_formats)*ARRAY_SIZE(builtin_formats)); + + git_config(git_pretty_formats_config, NULL); +} + +static struct cmt_fmt_map *find_commit_format_recursive(const char *sought, + const char *original, + int num_redirections) +{ + struct cmt_fmt_map *found = NULL; + size_t found_match_len = 0; + int i; + + if (num_redirections >= commit_formats_len) + die("invalid --pretty format: " + "'%s' references an alias which points to itself", + original); + + for (i = 0; i < commit_formats_len; i++) { + size_t match_len; + + if (prefixcmp(commit_formats[i].name, sought)) + continue; + + match_len = strlen(commit_formats[i].name); + if (found == NULL || found_match_len > match_len) { + found = &commit_formats[i]; + found_match_len = match_len; + } + } + + if (found && found->is_alias) { + found = find_commit_format_recursive(found->user_format, + original, + num_redirections+1); + } + + return found; +} + +static struct cmt_fmt_map *find_commit_format(const char *sought) +{ + if (!commit_formats) + setup_commit_formats(); + + return find_commit_format_recursive(sought, sought, 0); +} + +void get_commit_format(const char *arg, struct rev_info *rev) +{ + struct cmt_fmt_map *commit_format; rev->use_terminator = 0; if (!arg || !*arg) { @@ -47,21 +154,22 @@ void get_commit_format(const char *arg, struct rev_info *rev) save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't'); return; } - for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) { - if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) && - !strncmp(arg, cmt_fmts[i].n, strlen(arg))) { - if (cmt_fmts[i].v == CMIT_FMT_ONELINE) - rev->use_terminator = 1; - rev->commit_format = cmt_fmts[i].v; - return; - } - } + if (strchr(arg, '%')) { save_user_format(rev, arg, 1); return; } - die("invalid --pretty format: %s", arg); + commit_format = find_commit_format(arg); + if (!commit_format) + die("invalid --pretty format: %s", arg); + + rev->commit_format = commit_format->format; + rev->use_terminator = commit_format->is_tformat; + if (commit_format->format == CMIT_FMT_USERFORMAT) { + save_user_format(rev, commit_format->user_format, + commit_format->is_tformat); + } } /* @@ -716,7 +824,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (add_again(sb, &c->abbrev_commit_hash)) return 1; strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1, - DEFAULT_ABBREV)); + c->pretty_ctx->abbrev)); c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off; return 1; case 'T': /* tree hash */ @@ -726,7 +834,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (add_again(sb, &c->abbrev_tree_hash)) return 1; strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1, - DEFAULT_ABBREV)); + c->pretty_ctx->abbrev)); c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off; return 1; case 'P': /* parent hashes */ @@ -743,7 +851,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_addstr(sb, find_unique_abbrev( - p->item->object.sha1, DEFAULT_ABBREV)); + p->item->object.sha1, + c->pretty_ctx->abbrev)); } c->abbrev_parent_hashes.len = sb->len - c->abbrev_parent_hashes.off; @@ -775,9 +884,13 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, } return 0; /* unknown %g placeholder */ case 'N': - get_commit_notes(commit, sb, git_log_output_encoding ? - git_log_output_encoding : git_commit_encoding, 0); - return 1; + if (c->pretty_ctx->show_notes) { + format_display_notes(commit->object.sha1, sb, + git_log_output_encoding ? git_log_output_encoding + : git_commit_encoding, 0); + return 1; + } + return 0; } /* For the rest we have to parse the commit header. */ @@ -796,6 +909,10 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, case 'e': /* encoding */ strbuf_add(sb, msg + c->encoding.off, c->encoding.len); return 1; + case 'B': /* raw body */ + /* message_off is always left at the initial newline */ + strbuf_addstr(sb, msg + c->message_off + 1); + return 1; } /* Now we need to parse the commit message. */ @@ -825,6 +942,7 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, NO_MAGIC, ADD_LF_BEFORE_NON_EMPTY, DEL_LF_BEFORE_EMPTY, + ADD_SP_BEFORE_NON_EMPTY } magic = NO_MAGIC; switch (placeholder[0]) { @@ -834,6 +952,9 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case '+': magic = ADD_LF_BEFORE_NON_EMPTY; break; + case ' ': + magic = ADD_SP_BEFORE_NON_EMPTY; + break; default: break; } @@ -848,12 +969,44 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) { while (sb->len && sb->buf[sb->len - 1] == '\n') strbuf_setlen(sb, sb->len - 1); - } else if ((orig_len != sb->len) && magic == ADD_LF_BEFORE_NON_EMPTY) { - strbuf_insert(sb, orig_len, "\n", 1); + } else if (orig_len != sb->len) { + if (magic == ADD_LF_BEFORE_NON_EMPTY) + strbuf_insert(sb, orig_len, "\n", 1); + else if (magic == ADD_SP_BEFORE_NON_EMPTY) + strbuf_insert(sb, orig_len, " ", 1); } return consumed + 1; } +static size_t userformat_want_item(struct strbuf *sb, const char *placeholder, + void *context) +{ + struct userformat_want *w = context; + + if (*placeholder == '+' || *placeholder == '-' || *placeholder == ' ') + placeholder++; + + switch (*placeholder) { + case 'N': + w->notes = 1; + break; + } + return 0; +} + +void userformat_find_requirements(const char *fmt, struct userformat_want *w) +{ + struct strbuf dummy = STRBUF_INIT; + + if (!fmt) { + if (!user_format) + return; + fmt = user_format; + } + strbuf_expand(&dummy, user_format, userformat_want_item, w); + strbuf_release(&dummy); +} + void format_commit_message(const struct commit *commit, const char *format, struct strbuf *sb, const struct pretty_print_context *pretty_ctx) @@ -1095,8 +1248,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, strbuf_addch(sb, '\n'); if (context->show_notes) - get_commit_notes(commit, sb, encoding, - NOTES_SHOW_HEADER | NOTES_INDENT); + format_display_notes(commit->object.sha1, sb, encoding, + NOTES_SHOW_HEADER | NOTES_INDENT); free(reencoded); } @@ -295,42 +295,75 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen, fputc(terminator, fp); } -/* quote path as relative to the given prefix */ -char *quote_path_relative(const char *in, int len, - struct strbuf *out, const char *prefix) +static const char *path_relative(const char *in, int len, + struct strbuf *sb, const char *prefix, + int prefix_len); + +void write_name_quoted_relative(const char *name, size_t len, + const char *prefix, size_t prefix_len, + FILE *fp, int terminator) { - int needquote; + struct strbuf sb = STRBUF_INIT; + + name = path_relative(name, len, &sb, prefix, prefix_len); + write_name_quoted(name, fp, terminator); + + strbuf_release(&sb); +} + +/* + * Give path as relative to prefix. + * + * The strbuf may or may not be used, so do not assume it contains the + * returned path. + */ +static const char *path_relative(const char *in, int len, + struct strbuf *sb, const char *prefix, + int prefix_len) +{ + int off, i; if (len < 0) len = strlen(in); + if (prefix && prefix_len < 0) + prefix_len = strlen(prefix); + + off = 0; + i = 0; + while (i < prefix_len && i < len && prefix[i] == in[i]) { + if (prefix[i] == '/') + off = i + 1; + i++; + } + in += off; + len -= off; + + if (i >= prefix_len) + return in; - /* "../" prefix itself does not need quoting, but "in" might. */ - needquote = next_quote_pos(in, len) < len; - strbuf_setlen(out, 0); - strbuf_grow(out, len); - - if (needquote) - strbuf_addch(out, '"'); - if (prefix) { - int off = 0; - while (prefix[off] && off < len && prefix[off] == in[off]) - if (prefix[off] == '/') { - prefix += off + 1; - in += off + 1; - len -= off + 1; - off = 0; - } else - off++; - - for (; *prefix; prefix++) - if (*prefix == '/') - strbuf_addstr(out, "../"); + strbuf_reset(sb); + strbuf_grow(sb, len); + + while (i < prefix_len) { + if (prefix[i] == '/') + strbuf_addstr(sb, "../"); + i++; } + strbuf_add(sb, in, len); + + return sb->buf; +} - quote_c_style_counted (in, len, out, NULL, 1); +/* quote path as relative to the given prefix */ +char *quote_path_relative(const char *in, int len, + struct strbuf *out, const char *prefix) +{ + struct strbuf sb = STRBUF_INIT; + const char *rel = path_relative(in, len, &sb, prefix, -1); + strbuf_reset(out); + quote_c_style_counted(rel, strlen(rel), out, NULL, 0); + strbuf_release(&sb); - if (needquote) - strbuf_addch(out, '"'); if (!out->len) strbuf_addstr(out, "./"); @@ -54,9 +54,12 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int); extern void write_name_quoted(const char *name, FILE *, int terminator); extern void write_name_quotedpfx(const char *pfx, size_t pfxlen, const char *name, FILE *, int terminator); +extern void write_name_quoted_relative(const char *name, size_t len, + const char *prefix, size_t prefix_len, + FILE *fp, int terminator); /* quote path as relative to the given prefix */ -char *quote_path_relative(const char *in, int len, +extern char *quote_path_relative(const char *in, int len, struct strbuf *out, const char *prefix); /* quoting as a string literal for other languages */ diff --git a/reflog-walk.c b/reflog-walk.c index caba4f743f..4879615cad 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -162,7 +162,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info, } else recno = 0; - item = string_list_lookup(branch, &info->complete_reflogs); + item = string_list_lookup(&info->complete_reflogs, branch); if (item) reflogs = item->util; else { @@ -190,7 +190,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info, } if (!reflogs || reflogs->nr == 0) return -1; - string_list_insert(branch, &info->complete_reflogs)->util + string_list_insert(&info->complete_reflogs, branch)->util = reflogs; } @@ -314,7 +314,11 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname) { - struct warn_if_dangling_data data = { fp, refname, msg_fmt }; + struct warn_if_dangling_data data; + + data.fp = fp; + data.refname = refname; + data.msg_fmt = msg_fmt; for_each_rawref(warn_if_dangling_symref, &data); } @@ -698,7 +702,6 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, { struct strbuf real_pattern = STRBUF_INIT; struct ref_filter filter; - const char *has_glob_specials; int ret; if (!prefix && prefixcmp(pattern, "refs/")) @@ -707,8 +710,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, strbuf_addstr(&real_pattern, prefix); strbuf_addstr(&real_pattern, pattern); - has_glob_specials = strpbrk(pattern, "?*["); - if (!has_glob_specials) { + if (!has_glob_specials(pattern)) { /* Append implied '/' '*' if not present. */ if (real_pattern.buf[real_pattern.len - 1] != '/') strbuf_addch(&real_pattern, '/'); @@ -1088,6 +1090,15 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) return ret; } +/* + * People using contrib's git-new-workdir have .git/logs/refs -> + * /some/other/path/.git/logs/refs, and that may live on another device. + * + * IOW, to avoid cross device rename errors, the temporary renamed log must + * live into logs/refs. + */ +#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log" + int rename_ref(const char *oldref, const char *newref, const char *logmsg) { static const char renamed_ref[] = "RENAMED-REF"; @@ -1121,8 +1132,8 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) 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"))) - return error("unable to move logfile logs/%s to tmp-renamed-log: %s", + if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG))) + return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s", oldref, strerror(errno)); if (delete_ref(oldref, orig_sha1, REF_NODEREF)) { @@ -1148,7 +1159,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) } 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", newref))) { if (errno==EISDIR || errno==ENOTDIR) { /* * rename(a, b) when b is an existing @@ -1161,7 +1172,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) } goto retry; } else { - error("unable to move logfile tmp-renamed-log to logs/%s: %s", + error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", newref, strerror(errno)); goto rollback; } @@ -1201,8 +1212,8 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) error("unable to restore logfile %s from %s: %s", oldref, newref, strerror(errno)); if (!logmoved && log && - rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref))) - error("unable to restore logfile %s from tmp-renamed-log: %s", + rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s", oldref, strerror(errno)); return 1; @@ -1260,51 +1271,65 @@ static int copy_msg(char *buf, const char *msg) return cp - buf; } -static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg) +int log_ref_setup(const char *ref_name, char *logfile, int bufsize) { - int logfd, written, oflags = O_APPEND | O_WRONLY; - unsigned maxlen, len; - int msglen; - char log_file[PATH_MAX]; - char *logrec; - const char *committer; - - if (log_all_ref_updates < 0) - log_all_ref_updates = !is_bare_repository(); - - git_snpath(log_file, sizeof(log_file), "logs/%s", ref_name); + int logfd, oflags = O_APPEND | O_WRONLY; + git_snpath(logfile, bufsize, "logs/%s", ref_name); if (log_all_ref_updates && (!prefixcmp(ref_name, "refs/heads/") || !prefixcmp(ref_name, "refs/remotes/") || + !prefixcmp(ref_name, "refs/notes/") || !strcmp(ref_name, "HEAD"))) { - if (safe_create_leading_directories(log_file) < 0) + if (safe_create_leading_directories(logfile) < 0) return error("unable to create directory for %s", - log_file); + logfile); oflags |= O_CREAT; } - logfd = open(log_file, oflags, 0666); + logfd = open(logfile, oflags, 0666); if (logfd < 0) { if (!(oflags & O_CREAT) && errno == ENOENT) return 0; if ((oflags & O_CREAT) && errno == EISDIR) { - if (remove_empty_directories(log_file)) { + if (remove_empty_directories(logfile)) { return error("There are still logs under '%s'", - log_file); + logfile); } - logfd = open(log_file, oflags, 0666); + logfd = open(logfile, oflags, 0666); } if (logfd < 0) return error("Unable to append to %s: %s", - log_file, strerror(errno)); + logfile, strerror(errno)); } - adjust_shared_perm(log_file); + adjust_shared_perm(logfile); + close(logfd); + return 0; +} + +static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg) +{ + int logfd, result, written, oflags = O_APPEND | O_WRONLY; + unsigned maxlen, len; + int msglen; + char log_file[PATH_MAX]; + char *logrec; + const char *committer; + + if (log_all_ref_updates < 0) + log_all_ref_updates = !is_bare_repository(); + + result = log_ref_setup(ref_name, log_file, sizeof(log_file)); + if (result) + return result; + logfd = open(log_file, oflags); + if (logfd < 0) + return 0; msglen = msg ? strlen(msg) : 0; committer = git_committer_info(0); maxlen = strlen(committer) + msglen + 100; @@ -28,6 +28,11 @@ extern int for_each_replace_ref(each_ref_fn, void *); extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *); extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *); +static inline const char *has_glob_specials(const char *pattern) +{ + return strpbrk(pattern, "?*["); +} + /* can be used to learn about broken ref and symref */ extern int for_each_rawref(each_ref_fn, void *); @@ -63,6 +68,9 @@ 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); +/** 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); diff --git a/remote-curl.c b/remote-curl.c index d388120851..04d4813e41 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -9,8 +9,7 @@ #include "sideband.h" static struct remote *remote; -static const char *url; -static struct walker *walker; +static const char *url; /* always ends with a trailing slash */ struct options { int verbosity; @@ -22,12 +21,6 @@ struct options { }; static struct options options; -static void init_walker(void) -{ - if (!walker) - walker = get_http_walker(url, remote); -} - static int set_option(const char *name, const char *value) { if (!strcmp(name, "verbosity")) { @@ -108,7 +101,7 @@ static struct discovery* discover_refs(const char *service) return last; free_discovery(last); - strbuf_addf(&buffer, "%s/info/refs", url); + strbuf_addf(&buffer, "%sinfo/refs", url); if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) { is_http = 1; if (!strchr(url, '?')) @@ -119,7 +112,6 @@ static struct discovery* discover_refs(const char *service) } refs_url = strbuf_detach(&buffer, NULL); - init_walker(); http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); /* try again with "plain" url (no ? or & appended) */ @@ -128,7 +120,7 @@ static struct discovery* discover_refs(const char *service) strbuf_reset(&buffer); proto_git_candidate = 0; - strbuf_addf(&buffer, "%s/info/refs", url); + strbuf_addf(&buffer, "%sinfo/refs", url); refs_url = strbuf_detach(&buffer, NULL); http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); @@ -140,6 +132,8 @@ static struct discovery* discover_refs(const char *service) case HTTP_MISSING_TARGET: die("%s not found: did you run git update-server-info on the" " server?", refs_url); + case HTTP_NOAUTH: + die("Authentication failed"); default: http_error(refs_url, http_ret); die("HTTP request failed"); @@ -250,9 +244,8 @@ static struct ref *parse_info_refs(struct discovery *heads) i++; } - init_walker(); ref = alloc_ref("HEAD"); - if (!walker->fetch_ref(walker, ref) && + if (!http_fetch_ref(url, ref) && !resolve_remote_symref(ref, refs)) { ref->next = refs; refs = ref; @@ -502,7 +495,6 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) struct child_process client; int err = 0; - init_walker(); memset(&client, 0, sizeof(client)); client.in = -1; client.out = -1; @@ -519,7 +511,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->out = client.out; strbuf_init(&rpc->result, 0); - strbuf_addf(&buf, "%s/%s", url, svc); + strbuf_addf(&buf, "%s%s", url, svc); rpc->service_url = strbuf_detach(&buf, NULL); strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc); @@ -536,11 +528,12 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->len = n; err |= post_rpc(rpc); } - strbuf_read(&rpc->result, client.out, 0); close(client.in); - close(client.out); client.in = -1; + strbuf_read(&rpc->result, client.out, 0); + + close(client.out); client.out = -1; err |= finish_command(&client); @@ -554,6 +547,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) static int fetch_dumb(int nr_heads, struct ref **to_fetch) { + struct walker *walker; char **targets = xmalloc(nr_heads * sizeof(char*)); int ret, i; @@ -562,13 +556,14 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) for (i = 0; i < nr_heads; i++) targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); - init_walker(); + walker = get_http_walker(url); walker->get_all = 1; walker->get_tree = 1; walker->get_history = 1; walker->get_verbosely = options.verbosity >= 3; walker->get_recover = 0; ret = walker_fetch(walker, nr_heads, targets, NULL, NULL); + walker_free(walker); for (i = 0; i < nr_heads; i++) free(targets[i]); @@ -806,11 +801,15 @@ int main(int argc, const char **argv) remote = remote_get(argv[1]); if (argc > 2) { - url = argv[2]; + end_url_with_slash(&buf, argv[2]); } else { - url = remote->url[0]; + end_url_with_slash(&buf, remote->url[0]); } + url = strbuf_detach(&buf, NULL); + + http_init(remote); + do { if (strbuf_getline(&buf, stdin, '\n') == EOF) break; @@ -856,5 +855,8 @@ int main(int argc, const char **argv) } strbuf_reset(&buf); } while (1); + + http_cleanup(); + return 0; } @@ -443,6 +443,8 @@ static int handle_config(const char *key, const char *value, void *cb) } else if (!strcmp(subkey, ".tagopt")) { if (!strcmp(value, "--no-tags")) remote->fetch_tags = -1; + else if (!strcmp(value, "--tags")) + remote->fetch_tags = 2; } else if (!strcmp(subkey, ".proxy")) { return git_config_string((const char **)&remote->http_proxy, key, value); @@ -476,7 +478,7 @@ static void read_config(void) unsigned char sha1[20]; const char *head_ref; int flag; - if (default_remote_name) // did this already + if (default_remote_name) /* did this already */ return; default_remote_name = xstrdup("origin"); current_branch = NULL; @@ -657,10 +659,9 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp int valid_fetch_refspec(const char *fetch_refspec_str) { - const char *fetch_refspec[] = { fetch_refspec_str }; struct refspec *refspec; - refspec = parse_refspec_internal(1, fetch_refspec, 1, 1); + refspec = parse_refspec_internal(1, &fetch_refspec_str, 1, 1); free_refspecs(refspec, 1); return !!refspec; } @@ -761,7 +762,7 @@ void ref_remove_duplicates(struct ref *ref_map) if (!ref_map->peer_ref) continue; - item = string_list_lookup(ref_map->peer_ref->name, &refs); + item = string_list_lookup(&refs, ref_map->peer_ref->name); if (item) { if (strcmp(((struct ref *)item->util)->name, ref_map->name)) @@ -776,7 +777,7 @@ void ref_remove_duplicates(struct ref *ref_map) continue; } - item = string_list_insert(ref_map->peer_ref->name, &refs); + item = string_list_insert(&refs, ref_map->peer_ref->name); item->util = ref_map; } string_list_clear(&refs, 0); @@ -1709,7 +1710,7 @@ struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map) info.ref_names = &ref_names; info.stale_refs_tail = &stale_refs; for (ref = fetch_map; ref; ref = ref->next) - string_list_append(ref->name, &ref_names); + string_list_append(&ref_names, ref->name); sort_string_list(&ref_names); for_each_ref(get_stale_heads_cb, &info); string_list_clear(&ref_names, 0); @@ -145,7 +145,7 @@ int branch_merge_matches(struct branch *, int n, const char *); enum match_refs_flags { MATCH_REFS_NONE = 0, MATCH_REFS_ALL = (1 << 0), - MATCH_REFS_MIRROR = (1 << 1), + MATCH_REFS_MIRROR = (1 << 1) }; /* Reporting of tracking info */ @@ -46,7 +46,7 @@ static void read_rr(struct string_list *rr) ; /* do nothing */ if (i == sizeof(buf)) die("filename too long"); - string_list_insert(buf, rr)->util = name; + string_list_insert(rr, buf)->util = name; } fclose(in); } @@ -153,7 +153,7 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz git_SHA_CTX ctx; int hunk_no = 0; enum { - RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL, + RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL } hunk = RR_CONTEXT; struct strbuf one = STRBUF_INIT, two = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; @@ -319,7 +319,7 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu if (!mmfile[i].ptr && !mmfile[i].size) mmfile[i].ptr = xstrdup(""); } - ll_merge(&result, path, &mmfile[0], + ll_merge(&result, path, &mmfile[0], NULL, &mmfile[1], "ours", &mmfile[2], "theirs", 0); for (i = 0; i < 3; i++) @@ -354,7 +354,7 @@ static int find_conflict(struct string_list *conflict) ce_same_name(e2, e3) && S_ISREG(e2->ce_mode) && S_ISREG(e3->ce_mode)) { - string_list_insert((const char *)e2->name, conflict); + string_list_insert(conflict, (const char *)e2->name); i++; /* skip over both #2 and #3 */ } } @@ -376,7 +376,7 @@ static int merge(const char *name, const char *path) ret = 1; goto out; } - ret = ll_merge(&result, path, &base, &cur, "", &other, "", 0); + ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", 0); if (!ret) { FILE *f = fopen(path, "w"); if (!f) @@ -449,7 +449,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) if (ret < 1) continue; hex = xstrdup(sha1_to_hex(sha1)); - string_list_insert(path, rr)->util = hex; + string_list_insert(rr, path)->util = hex; if (mkdir(git_path("rr-cache/%s", hex), 0755)) continue; handle_file(path, NULL, rerere_path(hex, "preimage")); @@ -471,7 +471,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) if (has_rerere_resolution(name)) { if (!merge(name, path)) { if (rerere_autoupdate) - string_list_insert(path, &update); + string_list_insert(&update, path); fprintf(stderr, "%s '%s' using previous resolution.\n", rerere_autoupdate @@ -577,7 +577,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) fprintf(stderr, "Updated preimage for '%s'\n", path); - string_list_insert(path, rr)->util = hex; + string_list_insert(rr, path)->util = hex; fprintf(stderr, "Forgot resolution for %s\n", path); return 0; } diff --git a/resolve-undo.c b/resolve-undo.c index 0f50ee0484..174ebec9e5 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -20,7 +20,7 @@ void record_resolve_undo(struct index_state *istate, struct cache_entry *ce) istate->resolve_undo = resolve_undo; } resolve_undo = istate->resolve_undo; - lost = string_list_insert(ce->name, resolve_undo); + lost = string_list_insert(resolve_undo, ce->name); if (!lost->util) lost->util = xcalloc(1, sizeof(*ui)); ui = lost->util; @@ -50,7 +50,7 @@ static int write_one(struct string_list_item *item, void *cbdata) void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo) { - for_each_string_list(write_one, resolve_undo, sb); + for_each_string_list(resolve_undo, write_one, sb); } struct string_list *resolve_undo_read(const char *data, unsigned long size) @@ -70,7 +70,7 @@ struct string_list *resolve_undo_read(const char *data, unsigned long size) len = strlen(data) + 1; if (size <= len) goto error; - lost = string_list_insert(data, resolve_undo); + lost = string_list_insert(resolve_undo, data); if (!lost->util) lost->util = xcalloc(1, sizeof(*ui)); ui = lost->util; @@ -135,7 +135,7 @@ int unmerge_index_entry_at(struct index_state *istate, int pos) pos++; return pos - 1; /* return the last entry processed */ } - item = string_list_lookup(ce->name, istate->resolve_undo); + item = string_list_lookup(istate->resolve_undo, ce->name); if (!item) return pos; ru = item->util; diff --git a/revision.c b/revision.c index 490b484084..7e82efd932 100644 --- a/revision.c +++ b/revision.c @@ -12,6 +12,7 @@ #include "patch-ids.h" #include "decorate.h" #include "log-tree.h" +#include "string-list.h" volatile show_early_output_fn_t show_early_output; @@ -645,6 +646,93 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl return slop-1; } +/* + * "rev-list --ancestry-path A..B" computes commits that are ancestors + * of B but not ancestors of A but further limits the result to those + * that are descendants of A. This takes the list of bottom commits and + * the result of "A..B" without --ancestry-path, and limits the latter + * further to the ones that can reach one of the commits in "bottom". + */ +static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *list) +{ + struct commit_list *p; + struct commit_list *rlist = NULL; + int made_progress; + + /* + * Reverse the list so that it will be likely that we would + * process parents before children. + */ + for (p = list; p; p = p->next) + commit_list_insert(p->item, &rlist); + + for (p = bottom; p; p = p->next) + p->item->object.flags |= TMP_MARK; + + /* + * Mark the ones that can reach bottom commits in "list", + * in a bottom-up fashion. + */ + do { + made_progress = 0; + for (p = rlist; p; p = p->next) { + struct commit *c = p->item; + struct commit_list *parents; + if (c->object.flags & (TMP_MARK | UNINTERESTING)) + continue; + for (parents = c->parents; + parents; + parents = parents->next) { + if (!(parents->item->object.flags & TMP_MARK)) + continue; + c->object.flags |= TMP_MARK; + made_progress = 1; + break; + } + } + } while (made_progress); + + /* + * NEEDSWORK: decide if we want to remove parents that are + * not marked with TMP_MARK from commit->parents for commits + * in the resulting list. We may not want to do that, though. + */ + + /* + * The ones that are not marked with TMP_MARK are uninteresting + */ + for (p = list; p; p = p->next) { + struct commit *c = p->item; + if (c->object.flags & TMP_MARK) + continue; + c->object.flags |= UNINTERESTING; + } + + /* We are done with the TMP_MARK */ + for (p = list; p; p = p->next) + p->item->object.flags &= ~TMP_MARK; + for (p = bottom; p; p = p->next) + p->item->object.flags &= ~TMP_MARK; + free_commit_list(rlist); +} + +/* + * Before walking the history, keep the set of "negative" refs the + * caller has asked to exclude. + * + * This is used to compute "rev-list --ancestry-path A..B", as we need + * 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) +{ + 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); + return bottom; +} + static int limit_list(struct rev_info *revs) { int slop = SLOP; @@ -652,6 +740,13 @@ static int limit_list(struct rev_info *revs) struct commit_list *list = revs->commits; struct commit_list *newlist = NULL; struct commit_list **p = &newlist; + struct commit_list *bottom = NULL; + + if (revs->ancestry_path) { + bottom = collect_bottom_commits(list); + if (!bottom) + die("--ancestry-path given but there are no bottom commits"); + } while (list) { struct commit_list *entry = list; @@ -693,6 +788,11 @@ static int limit_list(struct rev_info *revs) if (revs->cherry_pick) cherry_pick_list(newlist, revs); + if (bottom) { + limit_to_ancestry(bottom, newlist); + free_commit_list(bottom); + } + revs->commits = newlist; return 0; } @@ -1062,18 +1162,22 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg if (!prefixcmp(arg, "--max-count=")) { revs->max_count = atoi(arg + 12); + revs->no_walk = 0; } else if (!prefixcmp(arg, "--skip=")) { revs->skip_count = atoi(arg + 7); } else if ((*arg == '-') && isdigit(arg[1])) { /* accept -<digit>, like traditional "head" */ revs->max_count = atoi(arg + 1); + revs->no_walk = 0; } else if (!strcmp(arg, "-n")) { if (argc <= 1) return error("-n requires an argument"); revs->max_count = atoi(argv[1]); + revs->no_walk = 0; return 2; } else if (!prefixcmp(arg, "-n")) { revs->max_count = atoi(arg + 2); + revs->no_walk = 0; } else if (!prefixcmp(arg, "--max-age=")) { revs->max_age = atoi(arg + 10); } else if (!prefixcmp(arg, "--since=")) { @@ -1088,6 +1192,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->min_age = approxidate(arg + 8); } else if (!strcmp(arg, "--first-parent")) { revs->first_parent_only = 1; + } else if (!strcmp(arg, "--ancestry-path")) { + revs->ancestry_path = 1; + revs->simplify_history = 0; + revs->limited = 1; } else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) { init_reflog_walk(&revs->reflog_info); } else if (!strcmp(arg, "--default")) { @@ -1145,6 +1253,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->boundary = 1; } else if (!strcmp(arg, "--left-right")) { revs->left_right = 1; + } else if (!strcmp(arg, "--count")) { + revs->count = 1; } else if (!strcmp(arg, "--cherry-pick")) { revs->cherry_pick = 1; revs->limited = 1; @@ -1191,9 +1301,29 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--show-notes")) { revs->show_notes = 1; revs->show_notes_given = 1; + } else if (!prefixcmp(arg, "--show-notes=")) { + struct strbuf buf = STRBUF_INIT; + revs->show_notes = 1; + revs->show_notes_given = 1; + if (!revs->notes_opt.extra_notes_refs) + revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list)); + if (!prefixcmp(arg+13, "refs/")) + /* happy */; + else if (!prefixcmp(arg+13, "notes/")) + strbuf_addstr(&buf, "refs/"); + else + strbuf_addstr(&buf, "refs/notes/"); + strbuf_addstr(&buf, arg+13); + string_list_append(revs->notes_opt.extra_notes_refs, + strbuf_detach(&buf, NULL)); } else if (!strcmp(arg, "--no-notes")) { revs->show_notes = 0; revs->show_notes_given = 1; + } else if (!strcmp(arg, "--standard-notes")) { + revs->show_notes_given = 1; + revs->notes_opt.suppress_default_notes = 0; + } else if (!strcmp(arg, "--no-standard-notes")) { + revs->notes_opt.suppress_default_notes = 1; } else if (!strcmp(arg, "--oneline")) { revs->verbose_header = 1; get_commit_format("oneline", revs); @@ -1332,7 +1462,7 @@ static void append_prune_data(const char ***prune_data, const char **av) * Returns the number of arguments left that weren't recognized * (which are also moved to the head of the argument list) */ -int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def) +int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt) { int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0; const char **prune_data = NULL; @@ -1468,7 +1598,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->prune_data = get_pathspec(revs->prefix, prune_data); if (revs->def == NULL) - revs->def = def; + revs->def = opt ? opt->def : NULL; + if (opt && opt->tweak) + opt->tweak(revs, opt); if (revs->show_merge) prepare_show_merge(revs); if (revs->def && !revs->pending.nr && !got_rev_arg) { @@ -1502,11 +1634,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (!revs->full_diff) diff_tree_setup_paths(revs->prune_data, &revs->diffopt); } - if (revs->combine_merges) { + if (revs->combine_merges) revs->ignore_merges = 0; - if (revs->dense_combined_merges && !revs->diffopt.output_format) - revs->diffopt.output_format = DIFF_FORMAT_PATCH; - } revs->diffopt.abbrev = revs->abbrev; if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); @@ -1761,7 +1890,7 @@ int prepare_revision_walk(struct rev_info *revs) enum rewrite_result { rewrite_one_ok, rewrite_one_noparents, - rewrite_one_error, + rewrite_one_error }; static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp) diff --git a/revision.h b/revision.h index a14deefc25..36fdf22b29 100644 --- a/revision.h +++ b/revision.h @@ -3,6 +3,7 @@ #include "parse-options.h" #include "grep.h" +#include "notes.h" #define SEEN (1u<<0) #define UNINTERESTING (1u<<1) @@ -20,6 +21,7 @@ struct rev_info; struct log_info; +struct string_list; struct rev_info { /* Starting list */ @@ -55,6 +57,7 @@ struct rev_info { limited:1, unpacked:1, boundary:2, + count:1, left_right:1, rewrite_parents:1, print_parents:1, @@ -64,6 +67,7 @@ struct rev_info { reverse_output_stage:1, cherry_pick:1, bisect:1, + ancestry_path:1, first_parent_only:1; /* Diff flags */ @@ -126,6 +130,13 @@ struct rev_info { struct reflog_walk_info *reflog_info; struct decoration children; struct decoration merge_simplification; + + /* notes-specific options: which refs to show */ + struct display_notes_opt notes_opt; + + /* commit counts */ + int count_left; + int count_right; }; #define REV_TREE_SAME 0 @@ -137,8 +148,13 @@ struct rev_info { typedef void (*show_early_output_fn_t)(struct rev_info *, struct commit_list *); extern volatile show_early_output_fn_t show_early_output; +struct setup_revision_opt { + const char *def; + void (*tweak)(struct rev_info *, struct setup_revision_opt *); +}; + extern void init_revisions(struct rev_info *revs, const char *prefix); -extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); +extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *); extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, const struct option *options, const char * const usagestr[]); diff --git a/run-command.c b/run-command.c index c8d53795ec..2a1041ef65 100644 --- a/run-command.c +++ b/run-command.c @@ -84,6 +84,7 @@ static NORETURN void die_child(const char *err, va_list params) unused = write(child_err, "\n", 1); exit(128); } +#endif static inline void set_cloexec(int fd) { @@ -91,7 +92,6 @@ static inline void set_cloexec(int fd) if (flags >= 0) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } -#endif static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) { @@ -342,8 +342,6 @@ fail_pipe: else if (cmd->out > 1) fhout = dup(cmd->out); - if (cmd->dir) - die("chdir in start_command() not implemented"); if (cmd->env) env = make_augmented_environ(cmd->env); @@ -353,7 +351,7 @@ fail_pipe: cmd->argv = prepare_shell_cmd(cmd->argv); } - cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, + cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, cmd->dir, fhin, fhout, fherr); failed_errno = errno; if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) @@ -385,6 +383,8 @@ fail_pipe: close(cmd->out); if (need_err) close_pair(fderr); + else if (cmd->err) + close(cmd->err); errno = failed_errno; return -1; } @@ -449,11 +449,35 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const return run_command(&cmd); } -#ifdef WIN32 -static unsigned __stdcall run_thread(void *data) +#ifndef NO_PTHREADS +static pthread_t main_thread; +static int main_thread_set; +static pthread_key_t async_key; + +static void *run_thread(void *data) { struct async *async = data; - return async->proc(async->proc_in, async->proc_out, async->data); + intptr_t ret; + + pthread_setspecific(async_key, async); + ret = async->proc(async->proc_in, async->proc_out, async->data); + return (void *)ret; +} + +static NORETURN void die_async(const char *err, va_list params) +{ + vreportf("fatal: ", err, params); + + if (!pthread_equal(main_thread, pthread_self())) { + struct async *async = pthread_getspecific(async_key); + if (async->proc_in >= 0) + close(async->proc_in); + if (async->proc_out >= 0) + close(async->proc_out); + pthread_exit((void *)128); + } + + exit(128); } #endif @@ -499,7 +523,7 @@ int start_async(struct async *async) else proc_out = -1; -#ifndef WIN32 +#ifdef NO_PTHREADS /* Flush stdio before fork() to avoid cloning buffers */ fflush(NULL); @@ -526,12 +550,29 @@ int start_async(struct async *async) else if (async->out) close(async->out); #else + if (!main_thread_set) { + /* + * We assume that the first time that start_async is called + * it is from the main thread. + */ + main_thread_set = 1; + main_thread = pthread_self(); + pthread_key_create(&async_key, NULL); + set_die_routine(die_async); + } + + if (proc_in >= 0) + set_cloexec(proc_in); + if (proc_out >= 0) + set_cloexec(proc_out); async->proc_in = proc_in; async->proc_out = proc_out; - async->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL); - if (!async->tid) { - error("cannot create thread: %s", strerror(errno)); - goto error; + { + int err = pthread_create(&async->tid, NULL, run_thread, async); + if (err) { + error("cannot create thread: %s", strerror(err)); + goto error; + } } #endif return 0; @@ -551,17 +592,15 @@ error: int finish_async(struct async *async) { -#ifndef WIN32 - int ret = wait_or_whine(async->pid, "child process", 0); +#ifdef NO_PTHREADS + return wait_or_whine(async->pid, "child process", 0); #else - DWORD ret = 0; - if (WaitForSingleObject(async->tid, INFINITE) != WAIT_OBJECT_0) - ret = error("waiting for thread failed: %lu", GetLastError()); - else if (!GetExitCodeThread(async->tid, &ret)) - ret = error("cannot get thread exit code: %lu", GetLastError()); - CloseHandle(async->tid); + void *ret = (void *)(intptr_t)(-1); + + if (pthread_join(async->tid, &ret)) + error("pthread_join failed"); + return (int)(intptr_t)ret; #endif - return ret; } int run_hook(const char *index_file, const char *name, ...) diff --git a/run-command.h b/run-command.h index 94619f52d9..56491b9f23 100644 --- a/run-command.h +++ b/run-command.h @@ -1,6 +1,10 @@ #ifndef RUN_COMMAND_H #define RUN_COMMAND_H +#ifndef NO_PTHREADS +#include <pthread.h> +#endif + struct child_process { const char **argv; pid_t pid; @@ -74,10 +78,10 @@ struct async { void *data; int in; /* caller writes here and closes it */ int out; /* caller reads from here and closes it */ -#ifndef WIN32 +#ifdef NO_PTHREADS pid_t pid; #else - HANDLE tid; + pthread_t tid; int proc_in; int proc_out; #endif diff --git a/send-pack.h b/send-pack.h index 28141ac913..60b4ba66eb 100644 --- a/send-pack.h +++ b/send-pack.h @@ -4,6 +4,7 @@ struct send_pack_args { unsigned verbose:1, quiet:1, + porcelain:1, send_mirror:1, force_update:1, use_thin_pack:1, diff --git a/server-info.c b/server-info.c index 4098ca2b5c..9ec744e9f2 100644 --- a/server-info.c +++ b/server-info.c @@ -113,11 +113,8 @@ static int read_pack_info_file(const char *infofile) goto out_stale; break; case 'D': /* we used to emit D but that was misguided. */ - goto out_stale; - break; case 'T': /* we used to emit T but nobody uses it. */ goto out_stale; - break; default: error("unrecognized: %s", line); break; @@ -18,14 +18,15 @@ const char *prefix_path(const char *prefix, int len, const char *path) if (normalize_path_copy(sanitized, sanitized)) goto error_out; if (is_absolute_path(orig)) { - size_t len, total; + size_t root_len, len, total; const char *work_tree = get_git_work_tree(); if (!work_tree) goto error_out; len = strlen(work_tree); + root_len = offset_1st_component(work_tree); total = strlen(sanitized) + 1; if (strncmp(sanitized, work_tree, len) || - (sanitized[len] != '\0' && sanitized[len] != '/')) { + (len > root_len && sanitized[len] != '\0' && sanitized[len] != '/')) { error_out: die("'%s' is outside repository", orig); } @@ -323,7 +324,10 @@ const char *setup_git_directory_gently(int *nongit_ok) static char cwd[PATH_MAX+1]; const char *gitdirenv; const char *gitfile_dir; - int len, offset, ceil_offset; + int len, offset, ceil_offset, root_len; + dev_t current_device = 0; + int one_filesystem = 1; + struct stat buf; /* * Let's assume that we are in a git repository. @@ -391,6 +395,12 @@ const char *setup_git_directory_gently(int *nongit_ok) * etc. */ offset = len = strlen(cwd); + one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); + if (one_filesystem) { + if (stat(".", &buf)) + die_errno("failed to stat '.'"); + current_device = buf.st_dev; + } for (;;) { gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); if (gitfile_dir) { @@ -405,10 +415,11 @@ const char *setup_git_directory_gently(int *nongit_ok) if (!work_tree_env) inside_work_tree = 0; if (offset != len) { - cwd[offset] = '\0'; - setenv(GIT_DIR_ENVIRONMENT, cwd, 1); + root_len = offset_1st_component(cwd); + cwd[offset > root_len ? offset : root_len] = '\0'; + set_git_dir(cwd); } else - setenv(GIT_DIR_ENVIRONMENT, ".", 1); + set_git_dir("."); check_repository_format_gently(nongit_ok); return NULL; } @@ -422,14 +433,34 @@ const char *setup_git_directory_gently(int *nongit_ok) } die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT); } - if (chdir("..")) + if (one_filesystem) { + if (stat("..", &buf)) { + cwd[offset] = '\0'; + die_errno("failed to stat '%s/..'", cwd); + } + if (buf.st_dev != current_device) { + if (nongit_ok) { + if (chdir(cwd)) + die_errno("Cannot come back to cwd"); + *nongit_ok = 1; + return NULL; + } + cwd[offset] = '\0'; + die("Not a git repository (or any parent up to mount parent %s)\n" + "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd); + } + } + if (chdir("..")) { + cwd[offset] = '\0'; die_errno("Cannot change to '%s/..'", cwd); + } } inside_git_dir = 0; if (!work_tree_env) inside_work_tree = 1; - git_work_tree_cfg = xstrndup(cwd, offset); + root_len = offset_1st_component(cwd); + git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len); if (check_repository_format_gently(nongit_ok)) return NULL; if (offset == len) @@ -518,6 +549,12 @@ int check_repository_format(void) return check_repository_format_gently(NULL); } +/* + * Returns the "prefix", a path to the current working directory + * relative to the work tree root, or NULL, if the current working + * directory is not a strict subdirectory of the work tree root. The + * prefix always ends with a '/' character. + */ const char *setup_git_directory(void) { const char *retval = setup_git_directory_gently(NULL); diff --git a/sha1_file.c b/sha1_file.c index c23cc5e6e1..e42ef96d45 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -35,13 +35,6 @@ static size_t sz_fmt(size_t s) { return s; } const unsigned char null_sha1[20]; -static inline int offset_1st_component(const char *path) -{ - if (has_dos_drive_prefix(path)) - return 2 + (path[2] == '/'); - return *path == '/'; -} - int safe_create_leading_directories(char *path) { char *pos = path + offset_1st_component(path); @@ -109,20 +102,22 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) */ char *sha1_file_name(const unsigned char *sha1) { - static char *name, *base; + static char buf[PATH_MAX]; + const char *objdir; + int len; - if (!base) { - const char *sha1_file_directory = get_object_directory(); - int len = strlen(sha1_file_directory); - base = xmalloc(len + 60); - memcpy(base, sha1_file_directory, len); - memset(base+len, 0, 60); - base[len] = '/'; - base[len+3] = '/'; - name = base + len + 1; - } - fill_sha1_path(name, sha1); - return base; + objdir = get_object_directory(); + len = strlen(objdir); + + /* '/' + sha1(2) + '/' + sha1(38) + '\0' */ + if (len + 43 > PATH_MAX) + die("insanely long object directory %s", objdir); + memcpy(buf, objdir, len); + buf[len] = '/'; + buf[len+3] = '/'; + buf[len+42] = '\0'; + fill_sha1_path(buf + len + 1, sha1); + return buf; } static char *sha1_get_pack_name(const unsigned char *sha1, @@ -606,6 +601,14 @@ void unuse_pack(struct pack_window **w_cursor) } } +void close_pack_index(struct packed_git *p) +{ + if (p->index_data) { + munmap((void *)p->index_data, p->index_size); + p->index_data = NULL; + } +} + /* * This is used by git-repack in case a newly created pack happens to * contain the same set of objects as an existing one. In that case @@ -627,8 +630,7 @@ void free_pack_by_name(const char *pack_name) close_pack_windows(p); if (p->pack_fd != -1) close(p->pack_fd); - if (p->index_data) - munmap((void *)p->index_data, p->index_size); + close_pack_index(p); free(p->bad_object_sha1); *pp = p->next; free(p); @@ -838,9 +840,8 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) return p; } -struct packed_git *parse_pack_index(unsigned char *sha1) +struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path) { - const char *idx_path = sha1_pack_index_name(sha1); const char *path = sha1_pack_name(sha1); struct packed_git *p = alloc_packed_git(strlen(path) + 1); @@ -2278,7 +2279,7 @@ static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename) } static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, - void *buf, unsigned long len, time_t mtime) + const void *buf, unsigned long len, time_t mtime) { int fd, ret; unsigned char compressed[4096]; @@ -2314,7 +2315,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, git_SHA1_Update(&c, hdr, hdrlen); /* Then the data itself.. */ - stream.next_in = buf; + stream.next_in = (void *)buf; stream.avail_in = len; do { unsigned char *in0 = stream.next_in; @@ -2349,7 +2350,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, return move_temp_to_file(tmpfile, filename); } -int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) +int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { unsigned char sha1[20]; char hdr[32]; @@ -2455,6 +2456,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, else ret = -1; strbuf_release(&sbuf); + } else if (!size) { + ret = index_mem(sha1, NULL, size, write_object, type, path); } else if (size <= SMALL_FILE_SIZE) { char *buf = xmalloc(size); if (size == read_in_full(fd, buf, size)) @@ -2463,12 +2466,11 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, else ret = error("short read %s", strerror(errno)); free(buf); - } else if (size) { + } else { void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); ret = index_mem(sha1, buf, size, write_object, type, path); munmap(buf, size); - } else - ret = index_mem(sha1, NULL, size, write_object, type, path); + } close(fd); return ret; } @@ -2523,3 +2525,13 @@ int read_pack_header(int fd, struct pack_header *header) return PH_ERROR_PROTOCOL; return 0; } + +void assert_sha1_type(const unsigned char *sha1, enum object_type expect) +{ + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("%s is not a valid object", sha1_to_hex(sha1)); + if (type != expect) + die("%s is not a valid '%s' object", sha1_to_hex(sha1), + typename(expect)); +} diff --git a/sha1_name.c b/sha1_name.c index 8a551025f0..4af94fa598 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -662,7 +662,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) /* * This interprets names like ':/Initial revision of "git"' by searching * through history and returning the first commit whose message starts - * with the given string. + * the given regular expression. * * For future extension, ':/!' is reserved. If you want to match a message * beginning with a '!', you have to repeat the exclamation mark. @@ -693,12 +693,17 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) struct commit_list *list = NULL, *backup = NULL, *l; int retval = -1; char *temp_commit_buffer = NULL; + regex_t regex; if (prefix[0] == '!') { if (prefix[1] != '!') die ("Invalid search pattern: %s", prefix); prefix++; } + + if (regcomp(®ex, prefix, REG_EXTENDED)) + die("Invalid search pattern: %s", prefix); + for_each_ref(handle_one_ref, &list); for (l = list; l; l = l->next) commit_list_insert(l->item, &backup); @@ -722,12 +727,13 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) } if (!(p = strstr(p, "\n\n"))) continue; - if (!prefixcmp(p + 2, prefix)) { + if (!regexec(®ex, p + 2, 0, NULL, 0)) { hashcpy(sha1, commit->object.sha1); retval = 0; break; } } + regfree(®ex); free(temp_commit_buffer); free_commit_list(list); for (l = backup; l; l = l->next) @@ -934,8 +940,8 @@ int interpret_branch_name(const char *name, struct strbuf *buf) */ int get_sha1(const char *name, unsigned char *sha1) { - unsigned unused; - return get_sha1_with_mode(name, sha1, &unused); + struct object_context unused; + return get_sha1_with_context(name, sha1, &unused); } /* Must be called only when object_name:filename doesn't exist. */ @@ -1033,11 +1039,23 @@ static void diagnose_invalid_index_path(int stage, int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix) { + struct object_context oc; + int ret; + ret = get_sha1_with_context_1(name, sha1, &oc, gently, prefix); + *mode = oc.mode; + return ret; +} + +int get_sha1_with_context_1(const char *name, unsigned char *sha1, + struct object_context *oc, + int gently, const char *prefix) +{ int ret, bracket_depth; int namelen = strlen(name); const char *cp; - *mode = S_IFINVALID; + memset(oc, 0, sizeof(*oc)); + oc->mode = S_IFINVALID; ret = get_sha1_1(name, namelen, sha1); if (!ret) return ret; @@ -1060,6 +1078,11 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, cp = name + 3; } namelen = namelen - (cp - name); + + strncpy(oc->path, cp, + sizeof(oc->path)); + oc->path[sizeof(oc->path)-1] = '\0'; + if (!active_cache) read_cache(); pos = cache_name_pos(cp, namelen); @@ -1072,7 +1095,6 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, break; if (ce_stage(ce) == stage) { hashcpy(sha1, ce->sha1); - *mode = ce->ce_mode; return 0; } pos++; @@ -1099,12 +1121,17 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, } if (!get_sha1_1(name, cp-name, tree_sha1)) { const char *filename = cp+1; - ret = get_tree_entry(tree_sha1, filename, sha1, mode); + ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode); if (!gently) { diagnose_invalid_sha1_path(prefix, filename, tree_sha1, object_name); free(object_name); } + hashcpy(oc->tree, tree_sha1); + strncpy(oc->path, filename, + sizeof(oc->path)); + oc->path[sizeof(oc->path)-1] = '\0'; + return ret; } else { if (!gently) diff --git a/shortlog.h b/shortlog.h index bc02cc29ef..de4f86fb97 100644 --- a/shortlog.h +++ b/shortlog.h @@ -12,6 +12,7 @@ struct shortlog { int in1; int in2; int user_format; + int abbrev; char *common_repo_prefix; int email; diff --git a/string-list.c b/string-list.c index 1ac536e638..9b023a2584 100644 --- a/string-list.c +++ b/string-list.c @@ -51,13 +51,13 @@ static int add_entry(int insert_at, struct string_list *list, const char *string return index; } -struct string_list_item *string_list_insert(const char *string, struct string_list *list) +struct string_list_item *string_list_insert(struct string_list *list, const char *string) { - return string_list_insert_at_index(-1, string, list); + return string_list_insert_at_index(list, -1, string); } -struct string_list_item *string_list_insert_at_index(int insert_at, - const char *string, struct string_list *list) +struct string_list_item *string_list_insert_at_index(struct string_list *list, + int insert_at, const char *string) { int index = add_entry(insert_at, list, string); @@ -84,7 +84,7 @@ int string_list_find_insert_index(const struct string_list *list, const char *st return index; } -struct string_list_item *string_list_lookup(const char *string, struct string_list *list) +struct string_list_item *string_list_lookup(struct string_list *list, const char *string) { int exact_match, i = get_entry_index(list, string, &exact_match); if (!exact_match) @@ -92,8 +92,8 @@ struct string_list_item *string_list_lookup(const char *string, struct string_li return list->items + i; } -int for_each_string_list(string_list_each_func_t fn, - struct string_list *list, void *cb_data) +int for_each_string_list(struct string_list *list, + string_list_each_func_t fn, void *cb_data) { int i, ret = 0; for (i = 0; i < list->nr; i++) @@ -139,7 +139,7 @@ void string_list_clear_func(struct string_list *list, string_list_clear_func_t c } -void print_string_list(const char *text, const struct string_list *p) +void print_string_list(const struct string_list *p, const char *text) { int i; if ( text ) @@ -148,7 +148,7 @@ void print_string_list(const char *text, const struct string_list *p) printf("%s:%p\n", p->items[i].string, p->items[i].util); } -struct string_list_item *string_list_append(const char *string, struct string_list *list) +struct string_list_item *string_list_append(struct string_list *list, const char *string) { ALLOC_GROW(list->items, list->nr + 1, list->alloc); list->items[list->nr].string = @@ -168,12 +168,19 @@ void sort_string_list(struct string_list *list) qsort(list->items, list->nr, sizeof(*list->items), cmp_items); } -int unsorted_string_list_has_string(struct string_list *list, const char *string) +struct string_list_item *unsorted_string_list_lookup(struct string_list *list, + const char *string) { int i; for (i = 0; i < list->nr; i++) if (!strcmp(string, list->items[i].string)) - return 1; - return 0; + return list->items + i; + return NULL; +} + +int unsorted_string_list_has_string(struct string_list *list, + const char *string) +{ + return unsorted_string_list_lookup(list, string) != NULL; } diff --git a/string-list.h b/string-list.h index 6569cf607b..680d600d16 100644 --- a/string-list.h +++ b/string-list.h @@ -12,7 +12,7 @@ struct string_list unsigned int strdup_strings:1; }; -void print_string_list(const char *text, const struct string_list *p); +void print_string_list(const struct string_list *p, const char *text); void string_list_clear(struct string_list *list, int free_util); /* Use this function to call a custom clear function on each util pointer */ @@ -22,21 +22,22 @@ void string_list_clear_func(struct string_list *list, string_list_clear_func_t c /* Use this function to iterate over each item */ typedef int (*string_list_each_func_t)(struct string_list_item *, void *); -int for_each_string_list(string_list_each_func_t, - struct string_list *list, void *cb_data); +int for_each_string_list(struct string_list *list, + string_list_each_func_t, void *cb_data); /* Use these functions only on sorted lists: */ int string_list_has_string(const struct string_list *list, const char *string); int string_list_find_insert_index(const struct string_list *list, const char *string, int negative_existing_index); -struct string_list_item *string_list_insert(const char *string, struct string_list *list); -struct string_list_item *string_list_insert_at_index(int insert_at, - const char *string, struct string_list *list); -struct string_list_item *string_list_lookup(const char *string, struct string_list *list); +struct string_list_item *string_list_insert(struct string_list *list, const char *string); +struct string_list_item *string_list_insert_at_index(struct string_list *list, + int insert_at, const char *string); +struct string_list_item *string_list_lookup(struct string_list *list, const char *string); /* Use these functions only on unsorted lists: */ -struct string_list_item *string_list_append(const char *string, struct string_list *list); +struct string_list_item *string_list_append(struct string_list *list, const char *string); void sort_string_list(struct string_list *list); int unsorted_string_list_has_string(struct string_list *list, const char *string); - +struct string_list_item *unsorted_string_list_lookup(struct string_list *list, + const char *string); #endif /* STRING_LIST_H */ diff --git a/submodule.c b/submodule.c index 5d286e409e..61cb6e21dd 100644 --- a/submodule.c +++ b/submodule.c @@ -5,14 +5,22 @@ #include "commit.h" #include "revision.h" #include "run-command.h" +#include "diffcore.h" static int add_submodule_odb(const char *path) { struct strbuf objects_directory = STRBUF_INIT; struct alternate_object_database *alt_odb; int ret = 0; + const char *git_dir; - strbuf_addf(&objects_directory, "%s/.git/objects/", path); + strbuf_addf(&objects_directory, "%s/.git", path); + git_dir = read_gitfile_gently(objects_directory.buf); + if (git_dir) { + strbuf_reset(&objects_directory); + strbuf_addstr(&objects_directory, git_dir); + } + strbuf_addstr(&objects_directory, "/objects/"); if (!is_directory(objects_directory.buf)) { ret = -1; goto done; @@ -38,6 +46,19 @@ done: return ret; } +void handle_ignore_submodules_arg(struct diff_options *diffopt, + const char *arg) +{ + if (!strcmp(arg, "all")) + DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); + else if (!strcmp(arg, "untracked")) + DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); + else if (!strcmp(arg, "dirty")) + DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES); + else + die("bad --ignore-submodules argument: %s", arg); +} + void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, @@ -85,13 +106,21 @@ void show_submodule_summary(FILE *f, const char *path, message = "(revision walker failed)"; } + if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) + fprintf(f, "Submodule %s contains untracked content\n", path); + if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) + fprintf(f, "Submodule %s contains modified content\n", path); + + if (!hashcmp(one, two)) { + strbuf_release(&sb); + return; + } + strbuf_addf(&sb, "Submodule %s %s..", path, find_unique_abbrev(one, DEFAULT_ABBREV)); if (!fast_backward && !fast_forward) strbuf_addch(&sb, '.'); strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV)); - if (dirty_submodule) - strbuf_add(&sb, "-dirty", 6); if (message) strbuf_addf(&sb, " %s\n", message); else @@ -121,23 +150,26 @@ void show_submodule_summary(FILE *f, const char *path, strbuf_release(&sb); } -int is_submodule_modified(const char *path) +unsigned is_submodule_modified(const char *path, int ignore_untracked) { - int len, i; + ssize_t len; struct child_process cp; const char *argv[] = { "status", "--porcelain", NULL, + NULL, }; - const char *env[LOCAL_REPO_ENV_SIZE + 3]; struct strbuf buf = STRBUF_INIT; - - for (i = 0; i < LOCAL_REPO_ENV_SIZE; i++) - env[i] = local_repo_env[i]; - - strbuf_addf(&buf, "%s/.git/", path); - if (!is_directory(buf.buf)) { + unsigned dirty_submodule = 0; + const char *line, *next_line; + const char *git_dir; + + strbuf_addf(&buf, "%s/.git", path); + git_dir = read_gitfile_gently(buf.buf); + if (!git_dir) + git_dir = buf.buf; + if (!is_directory(git_dir)) { strbuf_release(&buf); /* The submodule is not checked out, so it is not modified */ return 0; @@ -145,29 +177,44 @@ int is_submodule_modified(const char *path) } strbuf_reset(&buf); - strbuf_addf(&buf, "GIT_WORK_TREE=%s", path); - env[i++] = strbuf_detach(&buf, NULL); - strbuf_addf(&buf, "GIT_DIR=%s/.git", path); - env[i++] = strbuf_detach(&buf, NULL); - env[i] = NULL; + if (ignore_untracked) + argv[2] = "-uno"; memset(&cp, 0, sizeof(cp)); cp.argv = argv; - cp.env = env; + cp.env = local_repo_env; cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; + cp.dir = path; if (start_command(&cp)) die("Could not run git status --porcelain"); len = strbuf_read(&buf, cp.out, 1024); + line = buf.buf; + while (len > 2) { + if ((line[0] == '?') && (line[1] == '?')) { + dirty_submodule |= DIRTY_SUBMODULE_UNTRACKED; + if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) + break; + } else { + dirty_submodule |= DIRTY_SUBMODULE_MODIFIED; + if (ignore_untracked || + (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)) + break; + } + next_line = strchr(line, '\n'); + if (!next_line) + break; + next_line++; + len -= (next_line - line); + line = next_line; + } close(cp.out); if (finish_command(&cp)) die("git status --porcelain failed"); - for (i = LOCAL_REPO_ENV_SIZE; env[i]; i++) - free((char *)env[i]); strbuf_release(&buf); - return len != 0; + return dirty_submodule; } diff --git a/submodule.h b/submodule.h index 233696555e..6fd3bb4070 100644 --- a/submodule.h +++ b/submodule.h @@ -1,10 +1,13 @@ #ifndef SUBMODULE_H #define SUBMODULE_H +struct diff_options; + +void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *del, const char *add, const char *reset); -int is_submodule_modified(const char *path); +unsigned is_submodule_modified(const char *path, int ignore_untracked); #endif diff --git a/t/.gitignore b/t/.gitignore index 7dcbb232cd..4e731dc1e3 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -1,2 +1,3 @@ /trash directory* /test-results +/.prove diff --git a/t/Makefile b/t/Makefile index 25c559bb49..819b936870 100644 --- a/t/Makefile +++ b/t/Makefile @@ -3,6 +3,7 @@ # Copyright (c) 2005 Junio C Hamano # +-include ../config.mak.autogen -include ../config.mak #GIT_TEST_OPTS=--verbose --debug @@ -29,13 +30,16 @@ clean: $(RM) -r 'trash directory'.* test-results $(RM) t????/cvsroot/CVSROOT/?* $(RM) -r valgrind/bin + $(RM) .prove aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results $(MAKE) clean aggregate-results: - '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-* + for f in test-results/t*-*.counts; do \ + echo "$$f"; \ + done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL full-svn-test: @@ -18,25 +18,48 @@ The easiest way to run tests is to say "make". This runs all the tests. *** t0000-basic.sh *** - * ok 1: .git/objects should be empty after git-init in an empty repo. - * ok 2: .git/objects should have 256 subdirectories. - * ok 3: git-update-index without --add should fail adding. + ok 1 - .git/objects should be empty after git init in an empty repo. + ok 2 - .git/objects should have 3 subdirectories. + ok 3 - success is reported like this ... - * ok 23: no diff after checkout and git-update-index --refresh. - * passed all 23 test(s) - *** t0100-environment-names.sh *** - * ok 1: using old names should issue warnings. - * ok 2: using old names but having new names should not issue warnings. - ... - -Or you can run each test individually from command line, like -this: - - $ sh ./t3001-ls-files-killed.sh - * ok 1: git-update-index --add to add various paths. - * ok 2: git-ls-files -k to show killed files. - * ok 3: validate git-ls-files -k output. - * passed all 3 test(s) + ok 43 - very long name in the index handled sanely + # fixed 1 known breakage(s) + # still have 1 known breakage(s) + # passed all remaining 42 test(s) + 1..43 + *** t0001-init.sh *** + ok 1 - plain + ok 2 - plain with GIT_WORK_TREE + ok 3 - plain bare + +Since the tests all output TAP (see http://testanything.org) they can +be run with any TAP harness. Here's an example of parallel testing +powered by a recent version of prove(1): + + $ prove --timer --jobs 15 ./t[0-9]*.sh + [19:17:33] ./t0005-signals.sh ................................... ok 36 ms + [19:17:33] ./t0022-crlf-rename.sh ............................... ok 69 ms + [19:17:33] ./t0024-crlf-archive.sh .............................. ok 154 ms + [19:17:33] ./t0004-unwritable.sh ................................ ok 289 ms + [19:17:33] ./t0002-gitfile.sh ................................... ok 480 ms + ===( 102;0 25/? 6/? 5/? 16/? 1/? 4/? 2/? 1/? 3/? 1... )=== + +prove and other harnesses come with a lot of useful options. The +--state option in particular is very useful: + + # Repeat until no more failures + $ prove -j 15 --state=failed,save ./t[0-9]*.sh + +You can also run each test individually from command line, like this: + + $ sh ./t3010-ls-files-killed-modified.sh + ok 1 - git update-index --add to add various paths. + ok 2 - git ls-files -k to show killed files. + ok 3 - validate git ls-files -k output. + ok 4 - git ls-files -m to show modified files. + ok 5 - validate git ls-files -m output. + # passed all 5 test(s) + 1..5 You can pass --verbose (or -v), --debug (or -d), and --immediate (or -i) command line argument to the test, or by setting GIT_TEST_OPTS @@ -84,6 +107,12 @@ appropriately before running "make". implied by other options like --valgrind and GIT_TEST_INSTALLED. +--root=<directory>:: + Create "trash" directories used to store all temporary data during + testing under <directory>, instead of the t/ directory. + Using this option with a RAM-based filesystem (such as tmpfs) + can massively speed up the test suite. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various @@ -192,15 +221,101 @@ This test harness library does the following things: - If the script is invoked with command line argument --help (or -h), it shows the test_description and exits. - - Creates an empty test directory with an empty .git/objects - database and chdir(2) into it. This directory is 't/trash directory' - if you must know, but I do not think you care. + - Creates an empty test directory with an empty .git/objects database + and chdir(2) into it. This directory is 't/trash + directory.$test_name_without_dotsh', with t/ subject to change by + the --root option documented above. - Defines standard test helper functions for your scripts to use. These functions are designed to make all scripts behave consistently when command line arguments --verbose (or -v), --debug (or -d), and --immediate (or -i) is given. +Do's, don'ts & things to keep in mind +------------------------------------- + +Here are a few examples of things you probably should and shouldn't do +when writing tests. + +Do: + + - Put all code inside test_expect_success and other assertions. + + Even code that isn't a test per se, but merely some setup code + should be inside a test assertion. + + - Chain your test assertions + + Write test code like this: + + git merge foo && + git push bar && + test ... + + Instead of: + + git merge hla + git push gh + test ... + + That way all of the commands in your tests will succeed or fail. If + you must ignore the return value of something (e.g., the return + after unsetting a variable that was already unset is unportable) it's + best to indicate so explicitly with a semicolon: + + unset HLAGH; + git merge hla && + git push gh && + test ... + +Don't: + + - exit() within a <script> part. + + The harness will catch this as a programming error of the test. + Use test_done instead if you need to stop the tests early (see + "Skipping tests" below). + + - Break the TAP output + + The raw output from your test may be interpreted by a TAP harness. TAP + harnesses will ignore everything they don't know about, but don't step + on their toes in these areas: + + - Don't print lines like "$x..$y" where $x and $y are integers. + + - Don't print lines that begin with "ok" or "not ok". + + TAP harnesses expect a line that begins with either "ok" and "not + ok" to signal a test passed or failed (and our harness already + produces such lines), so your script shouldn't emit such lines to + their output. + + You can glean some further possible issues from the TAP grammar + (see http://search.cpan.org/perldoc?TAP::Parser::Grammar#TAP_Grammar) + but the best indication is to just run the tests with prove(1), + it'll complain if anything is amiss. + +Keep in mind: + + - Inside <script> part, the standard output and standard error + streams are discarded, and the test harness only reports "ok" or + "not ok" to the end user running the tests. Under --verbose, they + are shown to help debugging the tests. + + +Skipping tests +-------------- + +If you need to skip all the remaining tests you should set skip_all +and immediately call test_done. The string you give to skip_all will +be used as an explanation for why the test was skipped. for instance: + + if ! test_have_prereq PERL + then + skip_all='skipping perl interface tests, perl not available' + test_done + fi End with test_done ------------------ @@ -216,9 +331,9 @@ Test harness library There are a handful helper functions defined in the test harness library for your script to use. - - test_expect_success <message> <script> + - test_expect_success [<prereq>] <message> <script> - This takes two strings as parameter, and evaluates the + Usually takes two strings as parameter, and evaluates the <script>. If it yields success, test is considered successful. <message> should state what it is testing. @@ -228,7 +343,14 @@ library for your script to use. 'git-write-tree should be able to write an empty tree.' \ 'tree=$(git-write-tree)' - - test_expect_failure <message> <script> + If you supply three parameters the first will be taken to be a + prerequisite, see the test_set_prereq and test_have_prereq + documentation below: + + test_expect_success TTY 'git --paginate rev-list uses a pager' \ + ' ... ' + + - test_expect_failure [<prereq>] <message> <script> This is NOT the opposite of test_expect_success, but is used to mark a test that demonstrates a known breakage. Unlike @@ -237,6 +359,16 @@ library for your script to use. success and "still broken" on failure. Failures from these tests won't cause -i (immediate) to stop. + Like test_expect_success this function can optionally use a three + argument invocation with a prerequisite as the first argument. + + - test_expect_code [<prereq>] <code> <message> <script> + + Analogous to test_expect_success, but pass the test if it exits + with a given exit <code> + + test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master' + - test_debug <script> This takes a single argument, <script>, and evaluates it only @@ -269,6 +401,87 @@ library for your script to use. Merges the given rev using the given message. Like test_commit, creates a tag and calls test_tick before committing. + - test_set_prereq SOME_PREREQ + + Set a test prerequisite to be used later with test_have_prereq. The + test-lib will set some prerequisites for you, e.g. PERL and PYTHON + which are derived from ./GIT-BUILD-OPTIONS (grep test_set_prereq + test-lib.sh for more). Others you can set yourself and use later + with either test_have_prereq directly, or the three argument + invocation of test_expect_success and test_expect_failure. + + - test_have_prereq SOME PREREQ + + Check if we have a prerequisite previously set with + test_set_prereq. The most common use of this directly is to skip + all the tests if we don't have some essential prerequisite: + + if ! test_have_prereq PERL + then + skip_all='skipping perl interface tests, perl not available' + test_done + fi + + - test_external [<prereq>] <message> <external> <script> + + Execute a <script> with an <external> interpreter (like perl). This + was added for tests like t9700-perl-git.sh which do most of their + work in an external test script. + + test_external \ + 'GitwebCache::*FileCache*' \ + "$PERL_PATH" "$TEST_DIRECTORY"/t9503/test_cache_interface.pl + + If the test is outputting its own TAP you should set the + test_external_has_tap variable somewhere before calling the first + test_external* function. See t9700-perl-git.sh for an example. + + # The external test will outputs its own plan + test_external_has_tap=1 + + - test_external_without_stderr [<prereq>] <message> <external> <script> + + Like test_external but fail if there's any output on stderr, + instead of checking the exit code. + + test_external_without_stderr \ + 'Perl API' \ + "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl + + - test_must_fail <git-command> + + Run a git command and ensure it fails in a controlled way. Use + this instead of "! <git-command>". When git-command dies due to a + segfault, test_must_fail diagnoses it as an error; "! <git-command>" + treats it as just another expected failure, which would let such a + bug go unnoticed. + + - test_might_fail <git-command> + + Similar to test_must_fail, but tolerate success, too. Use this + instead of "<git-command> || :" to catch failures due to segv. + + - test_cmp <expected> <actual> + + Check whether the content of the <actual> file matches the + <expected> file. This behaves like "cmp" but produces more + helpful output when the test is run with "-v" option. + + - test_when_finished <script> + + Prepend <script> to a list of commands to run to clean up + at the end of the current test. If some clean-up command + fails, the test will not pass. + + Example: + + test_expect_success 'branch pointing to non-commit' ' + git rev-parse HEAD^{tree} >.git/refs/heads/invalid && + test_when_finished "git update-ref -d refs/heads/invalid" && + ... + ' + + Tips for Writing Tests ---------------------- diff --git a/t/aggregate-results.sh b/t/aggregate-results.sh index d5bab75d7d..d206b7c4cf 100755 --- a/t/aggregate-results.sh +++ b/t/aggregate-results.sh @@ -6,7 +6,7 @@ failed=0 broken=0 total=0 -for file +while read file do while read type value do diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 5a734b1b7b..81ef2a0969 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -19,9 +19,9 @@ our \$site_name = '[localhost]'; our \$site_header = ''; our \$site_footer = ''; our \$home_text = 'indextext.html'; -our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/gitweb.css'); -our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/git-logo.png'; -our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/git-favicon.png'; +our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/static/gitweb.css'); +our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/static/git-logo.png'; +our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/static/git-favicon.png'; our \$projects_list = ''; our \$export_ok = ''; our \$strict_export = ''; @@ -76,12 +76,12 @@ gitweb_run () { . ./test-lib.sh if ! test_have_prereq PERL; then - say 'skipping gitweb tests, perl not available' + skip_all='skipping gitweb tests, perl not available' test_done fi perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || { - say 'skipping gitweb tests, perl version is too old' + skip_all='skipping gitweb tests, perl version is too old' test_done } diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh index 4b3b793730..648d1619c8 100644 --- a/t/lib-cvs.sh +++ b/t/lib-cvs.sh @@ -9,7 +9,7 @@ export HOME if ! type cvs >/dev/null 2>&1 then - say 'skipping cvsimport tests, cvs not found' + skip_all='skipping cvsimport tests, cvs not found' test_done fi @@ -21,11 +21,11 @@ case "$cvsps_version" in 2.1 | 2.2*) ;; '') - say 'skipping cvsimport tests, cvsps not found' + skip_all='skipping cvsimport tests, cvsps not found' test_done ;; *) - say 'skipping cvsimport tests, unsupported cvsps version' + skip_all='skipping cvsimport tests, unsupported cvsps version' test_done ;; esac diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 0f7f35ccc9..92d6d31942 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -5,23 +5,22 @@ git_svn_id=git""-svn-id if test -n "$NO_SVN_TESTS" then - say 'skipping git svn tests, NO_SVN_TESTS defined' + skip_all='skipping git svn tests, NO_SVN_TESTS defined' test_done fi if ! test_have_prereq PERL; then - say 'skipping git svn tests, perl not available' + skip_all='skipping git svn tests, perl not available' test_done fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree -PERL=${PERL:-perl} svn >/dev/null 2>&1 if test $? -ne 1 then - say 'skipping git svn tests, svn not found' + skip_all='skipping git svn tests, svn not found' test_done fi @@ -30,7 +29,7 @@ export svnrepo svnconf=$PWD/svnconf export svnconf -$PERL -w -e " +"$PERL_PATH" -w -e " use SVN::Core; use SVN::Repos; \$SVN::Core::VERSION gt '1.1.0' or exit(42); @@ -40,13 +39,12 @@ x=$? if test $x -ne 0 then if test $x -eq 42; then - err='Perl SVN libraries must be >= 1.1.0' + skip_all='Perl SVN libraries must be >= 1.1.0' elif test $x -eq 41; then - err='svnadmin failed to create fsfs repository' + skip_all='svnadmin failed to create fsfs repository' else - err='Perl SVN libraries not found or unusable, skipping test' + skip_all='Perl SVN libraries not found or unusable' fi - say "$err" test_done fi @@ -131,7 +129,7 @@ stop_httpd () { } convert_to_rev_db () { - $PERL -w -- - "$@" <<\EOF + "$PERL_PATH" -w -- - "$@" <<\EOF use strict; @ARGV == 2 or die "Usage: convert_to_rev_db <input> <output>"; open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]"; @@ -159,7 +157,7 @@ EOF require_svnserve () { if test -z "$SVNSERVE_PORT" then - say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)' + skip_all='skipping svnserve test. (set $SVNSERVE_PORT to enable)' test_done fi } diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 28aff887b5..e733f6516f 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -5,8 +5,7 @@ if test -z "$GIT_TEST_HTTPD" then - say "skipping test, network testing disabled by default" - say "(define GIT_TEST_HTTPD to enable)" + skip_all="Network testing disabled (define GIT_TEST_HTTPD to enable)" test_done fi @@ -46,7 +45,7 @@ HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www if ! test -x "$LIB_HTTPD_PATH" then - say "skipping test, no web server found at '$LIB_HTTPD_PATH'" + skip_all="skipping test, no web server found at '$LIB_HTTPD_PATH'" test_done fi @@ -59,12 +58,12 @@ then then if ! test $HTTPD_VERSION -ge 2 then - say "skipping test, at least Apache version 2 is required" + skip_all="skipping test, at least Apache version 2 is required" test_done fi if ! test -d "$DEFAULT_HTTPD_MODULE_PATH" then - say "Apache module directory not found. Skipping tests." + skip_all="Apache module directory not found. Skipping tests." test_done fi @@ -119,7 +118,7 @@ start_httpd() { >&3 2>&4 if test $? -ne 0 then - say "skipping test, web server setup failed" + skip_all="skipping test, web server setup failed" trap 'die' EXIT test_done fi @@ -131,3 +130,32 @@ stop_httpd() { "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop } + +test_http_push_nonff() { + REMOTE_REPO=$1 + LOCAL_REPO=$2 + BRANCH=$3 + + test_expect_success 'non-fast-forward push fails' ' + cd "$REMOTE_REPO" && + HEAD=$(git rev-parse --verify HEAD) && + + cd "$LOCAL_REPO" && + git checkout $BRANCH && + echo "changed" > path2 && + git commit -a -m path2 --amend && + + test_must_fail git push -v origin >output 2>&1 && + (cd "$REMOTE_REPO" && + test $HEAD = $(git rev-parse --verify HEAD)) + ' + + test_expect_success 'non-fast-forward push show ref status' ' + grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" output + ' + + test_expect_success 'non-fast-forward push shows help message' ' + grep "To prevent you from losing history, non-fast-forward updates were rejected" \ + output + ' +} diff --git a/t/lib-pager.sh b/t/lib-pager.sh new file mode 100644 index 0000000000..ba03eab14f --- /dev/null +++ b/t/lib-pager.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +test_expect_success 'determine default pager' ' + test_might_fail git config --unset core.pager && + less=$( + unset PAGER GIT_PAGER; + git var GIT_PAGER + ) && + test -n "$less" +' + +if expr "$less" : '[a-z][a-z]*$' >/dev/null +then + test_set_prereq SIMPLEPAGER +fi diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh index ce36f34d03..375e248651 100644 --- a/t/lib-patch-mode.sh +++ b/t/lib-patch-mode.sh @@ -3,7 +3,7 @@ . ./test-lib.sh if ! test_have_prereq PERL; then - say 'skipping --patch tests, perl not available' + skip_all='skipping --patch tests, perl not available' test_done fi diff --git a/t/t6000lib.sh b/t/lib-t6000.sh index 985d517a1c..ea25dd89e5 100644 --- a/t/t6000lib.sh +++ b/t/lib-t6000.sh @@ -91,7 +91,7 @@ check_output() shift 1 if eval "$*" | entag > $_name.actual then - diff $_name.expected $_name.actual + test_cmp $_name.expected $_name.actual else return 1; fi diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index f4ca4fc85c..f2c73369a5 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -73,6 +73,27 @@ then exit 1 fi +clean=no +test_expect_success 'tests clean up after themselves' ' + test_when_finished clean=yes +' + +cleaner=no +test_expect_code 1 'tests clean up even after a failure' ' + test_when_finished cleaner=yes && + (exit 1) +' + +if test $clean$cleaner != yesyes +then + say "bug in test framework: cleanup commands do not work reliably" + exit 1 +fi + +test_expect_code 2 'failure to clean up causes the test to fail' ' + test_when_finished "(exit 2)" +' + ################################################################ # Basics of the basics @@ -280,7 +301,7 @@ $expectfilter >expected <<\EOF EOF test_expect_success \ 'validate git diff-files output for a know cache/work tree state.' \ - 'git diff-files >current && diff >/dev/null -b current expected' + 'git diff-files >current && test_cmp current expected >/dev/null' test_expect_success \ 'git update-index --refresh should succeed.' \ diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 5386504790..7c0a698b92 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -167,6 +167,25 @@ test_expect_success 'init with --template (blank)' ' ! test -f template-blank/.git/info/exclude ' +test_expect_success 'init with init.templatedir set' ' + mkdir templatedir-source && + echo Content >templatedir-source/file && + ( + HOME="`pwd`" && + export HOME && + test_config="${HOME}/.gitconfig" && + git config -f "$test_config" init.templatedir "${HOME}/templatedir-source" && + mkdir templatedir-set && + cd templatedir-set && + unset GIT_CONFIG_NOGLOBAL && + unset GIT_TEMPLATE_DIR && + NO_SET_GIT_TEMPLATE_DIR=t && + export NO_SET_GIT_TEMPLATE_DIR && + git init + ) && + test_cmp templatedir-source/file templatedir-set/.git/file +' + test_expect_success 'init --bare/--shared overrides system/global config' ' ( HOME="`pwd`" && @@ -291,4 +310,18 @@ test_expect_success POSIXPERM 'init notices EPERM' ' ) ' +test_expect_success 'init creates a new bare directory with global --bare' ' + rm -rf newdir && + git --bare init newdir && + test -d newdir/refs +' + +test_expect_success 'init prefers command line to GIT_DIR' ' + rm -rf newdir && + mkdir otherdir && + GIT_DIR=otherdir git --bare init newdir && + test -d newdir/refs && + ! test -d otherdir/refs +' + test_done diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 1c77192eb3..53bd7fcc4a 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -20,8 +20,12 @@ test_expect_success 'setup' ' mkdir -p a/b/d a/c && ( + echo "[attr]notest !test" echo "f test=f" echo "a/i test=a/i" + echo "onoff test -test" + echo "offon -test test" + echo "no notest" ) >.gitattributes && ( echo "g test=a/g" && @@ -30,6 +34,7 @@ test_expect_success 'setup' ' ( echo "h test=a/b/h" && echo "d/* test=a/b/d/*" + echo "d/yes notest" ) >a/b/.gitattributes ' @@ -44,6 +49,11 @@ test_expect_success 'attribute test' ' attr_check b/g unspecified && attr_check a/b/h a/b/h && attr_check a/b/d/g "a/b/d/*" + attr_check onoff unset + attr_check offon set + attr_check no unspecified + attr_check a/b/d/no "a/b/d/*" + attr_check a/b/d/yes unspecified ' @@ -58,6 +68,11 @@ 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: test: unspecified +a/b/d/no: test: a/b/d/* +a/b/d/yes: test: unspecified EOF sed -e "s/:.*//" < expect | git check-attr --stdin test > actual && diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh index 09f855af3e..93e58c00e8 100755 --- a/t/t0005-signals.sh +++ b/t/t0005-signals.sh @@ -13,6 +13,7 @@ test_expect_success 'sigchain works' ' test-sigchain >actual case "$?" in 143) true ;; # POSIX w/ SIGTERM=15 + 271) true ;; # ksh w/ SIGTERM=15 3) true ;; # Windows *) false ;; esac && diff --git a/t/t0006-date.sh b/t/t0006-date.sh index 75b02af86d..1d4d0a5c7d 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -28,8 +28,8 @@ check_show 31449600 '12 months ago' check_parse() { echo "$1 -> $2" >expect - test_expect_${3:-success} "parse date ($1)" " - test-date parse '$1' >actual && + test_expect_${4:-success} "parse date ($1${3:+ TZ=$3})" " + TZ=${3:-$TZ} test-date parse '$1' >actual && test_cmp expect actual " } @@ -38,6 +38,8 @@ check_parse 2008 bad 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' '2008-02-14 20:30:45 -0500' EST5 check_approxidate() { echo "$1 -> $2 +0000" >expect diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index c3e7e322a8..234a94f3e6 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -453,5 +453,57 @@ test_expect_success 'invalid .gitattributes (must not crash)' ' git diff ' +# Some more tests here to add new autocrlf functionality. +# We want to have a known state here, so start a bit from scratch + +test_expect_success 'setting up for new autocrlf tests' ' + git config core.autocrlf false && + git config core.safecrlf false && + rm -rf .????* * && + for w in I am all LF; do echo $w; done >alllf && + for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed && + for w in I am all CRLF; do echo $w; done | append_cr >allcrlf && + git add -A . && + git commit -m "alllf, allcrlf and mixed only" && + git tag -a -m "message" autocrlf-checkpoint +' + +test_expect_success 'report no change after setting autocrlf' ' + git config core.autocrlf true && + touch * && + git diff --exit-code +' + +test_expect_success 'files are clean after checkout' ' + rm * && + git checkout -f && + git diff --exit-code +' + +cr_to_Q_no_NL () { + tr '\015' Q | tr -d '\012' +} + +test_expect_success 'LF only file gets CRLF with autocrlf' ' + test "$(cr_to_Q_no_NL < alllf)" = "IQamQallQLFQ" +' + +test_expect_success 'Mixed file is still mixed with autocrlf' ' + test "$(cr_to_Q_no_NL < mixed)" = "OhhereisCRLFQintext" +' + +test_expect_success 'CRLF only file has CRLF with autocrlf' ' + test "$(cr_to_Q_no_NL < allcrlf)" = "IQamQallQCRLFQ" +' + +test_expect_success 'New CRLF file gets LF in repo' ' + tr -d "\015" < alllf | append_cr > alllf2 && + git add alllf2 && + git commit -m "alllf2 added" && + git config core.autocrlf false && + rm * && + git checkout -f && + test_cmp alllf alllf2 +' test_done diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 6cb8d60ea2..828e35baf7 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -65,17 +65,21 @@ test_expect_success expanded_in_repo ' echo "\$Id:NoSpaceAtFront \$" echo "\$Id:NoSpaceAtEitherEnd\$" echo "\$Id: NoTerminatingSymbol" + echo "\$Id: Foreign Commit With Spaces \$" + echo "\$Id: NoTerminatingSymbolAtEOF" } > expanded-keywords && { echo "File with expanded keywords" - echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$" - echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$" - echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$" - echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$" - echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$" - echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$" + echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" + echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" + echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" + echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" + echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" + echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" echo "\$Id: NoTerminatingSymbol" + echo "\$Id: Foreign Commit With Spaces \$" + echo "\$Id: NoTerminatingSymbolAtEOF" } > expected-output && git add expanded-keywords && diff --git a/t/t0025-crlf-auto.sh b/t/t0025-crlf-auto.sh new file mode 100755 index 0000000000..f5f67a6337 --- /dev/null +++ b/t/t0025-crlf-auto.sh @@ -0,0 +1,155 @@ +#!/bin/sh + +test_description='CRLF conversion' + +. ./test-lib.sh + +has_cr() { + tr '\015' Q <"$1" | grep Q >/dev/null +} + +test_expect_success setup ' + + git config core.autocrlf false && + + for w in Hello world how are you; do echo $w; done >one && + for w in I am very very fine thank you; do echo ${w}Q; done | q_to_cr >two && + for w in Oh here is a QNUL byte how alarming; do echo ${w}; done | q_to_nul >three && + git add . && + + git commit -m initial && + + one=`git rev-parse HEAD:one` && + two=`git rev-parse HEAD:two` && + three=`git rev-parse HEAD:three` && + + echo happy. +' + +test_expect_success 'default settings cause no changes' ' + + rm -f .gitattributes tmp one two three && + git read-tree --reset -u HEAD && + + ! has_cr one && + has_cr two && + onediff=`git diff one` && + twodiff=`git diff two` && + threediff=`git diff three` && + test -z "$onediff" -a -z "$twodiff" -a -z "$threediff" +' + +test_expect_success 'crlf=true causes a CRLF file to be normalized' ' + + # Backwards compatibility check + rm -f .gitattributes tmp one two three && + echo "two crlf" > .gitattributes && + git read-tree --reset -u HEAD && + + # Note, "normalized" means that git will normalize it if added + has_cr two && + twodiff=`git diff two` && + test -n "$twodiff" +' + +test_expect_success 'text=true causes a CRLF file to be normalized' ' + + rm -f .gitattributes tmp one two three && + echo "two text" > .gitattributes && + git read-tree --reset -u HEAD && + + # Note, "normalized" means that git will normalize it if added + has_cr two && + twodiff=`git diff two` && + test -n "$twodiff" +' + +test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=false' ' + + rm -f .gitattributes tmp one two three && + git config core.autocrlf false && + echo "one eol=crlf" > .gitattributes && + git read-tree --reset -u HEAD && + + has_cr one && + onediff=`git diff one` && + test -z "$onediff" +' + +test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=input' ' + + rm -f .gitattributes tmp one two three && + git config core.autocrlf input && + echo "one eol=crlf" > .gitattributes && + git read-tree --reset -u HEAD && + + has_cr one && + onediff=`git diff one` && + test -z "$onediff" +' + +test_expect_success 'eol=lf gives a normalized file LFs with autocrlf=true' ' + + rm -f .gitattributes tmp one two three && + git config core.autocrlf true && + echo "one eol=lf" > .gitattributes && + git read-tree --reset -u HEAD && + + ! has_cr one && + onediff=`git diff one` && + test -z "$onediff" +' + +test_expect_success 'autocrlf=true does not normalize CRLF files' ' + + rm -f .gitattributes tmp one two three && + git config core.autocrlf true && + git read-tree --reset -u HEAD && + + has_cr one && + has_cr two && + onediff=`git diff one` && + twodiff=`git diff two` && + threediff=`git diff three` && + test -z "$onediff" -a -z "$twodiff" -a -z "$threediff" +' + +test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' ' + + rm -f .gitattributes tmp one two three && + git config core.autocrlf true && + echo "* text=auto" > .gitattributes && + git read-tree --reset -u HEAD && + + has_cr one && + has_cr two && + onediff=`git diff one` && + twodiff=`git diff two` && + threediff=`git diff three` && + test -z "$onediff" -a -n "$twodiff" -a -z "$threediff" +' + +test_expect_success 'text=auto, autocrlf=true does not normalize binary files' ' + + rm -f .gitattributes tmp one two three && + git config core.autocrlf true && + echo "* text=auto" > .gitattributes && + git read-tree --reset -u HEAD && + + ! has_cr three && + threediff=`git diff three` && + test -z "$threediff" +' + +test_expect_success 'eol=crlf _does_ normalize binary files' ' + + rm -f .gitattributes tmp one two three && + echo "three eol=crlf" > .gitattributes && + git read-tree --reset -u HEAD && + + has_cr three && + threediff=`git diff three` && + test -z "$threediff" +' + +test_done diff --git a/t/t0026-eol-config.sh b/t/t0026-eol-config.sh new file mode 100755 index 0000000000..f37ac8fa0b --- /dev/null +++ b/t/t0026-eol-config.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +test_description='CRLF conversion' + +. ./test-lib.sh + +has_cr() { + tr '\015' Q <"$1" | grep Q >/dev/null +} + +test_expect_success setup ' + + git config core.autocrlf false && + + echo "one text" > .gitattributes + + for w in Hello world how are you; do echo $w; done >one && + for w in I am very very fine thank you; do echo $w; done >two && + git add . && + + git commit -m initial && + + one=`git rev-parse HEAD:one` && + two=`git rev-parse HEAD:two` && + + echo happy. +' + +test_expect_success 'eol=lf puts LFs in normalized file' ' + + rm -f .gitattributes tmp one two && + git config core.eol lf && + git read-tree --reset -u HEAD && + + ! has_cr one && + ! has_cr two && + onediff=`git diff one` && + twodiff=`git diff two` && + test -z "$onediff" -a -z "$twodiff" +' + +test_expect_success 'eol=crlf puts CRLFs in normalized file' ' + + rm -f .gitattributes tmp one two && + git config core.eol crlf && + git read-tree --reset -u HEAD && + + has_cr one && + ! has_cr two && + onediff=`git diff one` && + twodiff=`git diff two` && + test -z "$onediff" -a -z "$twodiff" +' + +test_expect_success 'autocrlf=true overrides eol=lf' ' + + rm -f .gitattributes tmp one two && + git config core.eol lf && + git config core.autocrlf true && + git read-tree --reset -u HEAD && + + has_cr one && + has_cr two && + onediff=`git diff one` && + twodiff=`git diff two` && + test -z "$onediff" -a -z "$twodiff" +' + +test_expect_success 'autocrlf=true overrides unset eol' ' + + rm -f .gitattributes tmp one two && + git config --unset-all core.eol && + git config core.autocrlf true && + git read-tree --reset -u HEAD && + + has_cr one && + has_cr two && + onediff=`git diff one` && + twodiff=`git diff two` && + test -z "$onediff" -a -z "$twodiff" +' + +test_done diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 3d450ed379..20924506af 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -7,7 +7,7 @@ test_description='our own option parser' . ./test-lib.sh -cat > expect.err << EOF +cat > expect << EOF usage: test-parse-options <options> -b, --boolean get a boolean @@ -46,10 +46,12 @@ EOF test_expect_success 'test help' ' test_must_fail test-parse-options -h > output 2> output.err && - test ! -s output && - test_cmp expect.err output.err + test ! -s output.err && + test_cmp expect output ' +mv expect expect.err + cat > expect << EOF boolean: 2 integer: 1729 diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 6327d205cb..0c562bb820 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -390,4 +390,20 @@ test_expect_success \ git ls-files --stage | tee >treeMcheck.out && test_cmp treeM.out treeMcheck.out' +test_expect_success '-m references the correct modified tree' ' + echo >file-a && + echo >file-b && + git add file-a file-b && + git commit -a -m "test for correct modified tree" + git branch initial-mod && + echo b >file-b && + git commit -a -m "B" && + echo a >file-a && + git add file-a && + git ls-tree $(git write-tree) file-a >expect && + git read-tree -m HEAD initial-mod && + git ls-tree $(git write-tree) file-a >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index fd98e445bf..dd32432d62 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -65,10 +65,6 @@ test_expect_success "Can't use --path with --stdin-paths" ' echo example | test_must_fail git hash-object --stdin-paths --path=foo ' -test_expect_success "Can't use --stdin-paths with --no-filters" ' - echo example | test_must_fail git hash-object --stdin-paths --no-filters -' - test_expect_success "Can't use --path with --no-filters" ' test_must_fail git hash-object --no-filters --path=foo ' @@ -141,6 +137,20 @@ test_expect_success 'check that --no-filters option works' ' git config --unset core.autocrlf ' +test_expect_success 'check that --no-filters option works with --stdin-paths' ' + echo fooQ | tr Q "\\015" >file0 && + cp file0 file1 && + echo "file0 -crlf" >.gitattributes && + echo "file1 crlf" >>.gitattributes && + git config core.autocrlf true && + file0_sha=$(git hash-object file0) && + file1_sha=$(git hash-object file1) && + test "$file0_sha" != "$file1_sha" && + nofilters_file1=$(echo "file1" | git hash-object --stdin-paths --no-filters) && + test "$file0_sha" = "$nofilters_file1" && + git config --unset core.autocrlf +' + pop_repo for args in "-w --stdin" "--stdin -w"; do diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index 210e594f6f..56874996a6 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -24,18 +24,18 @@ test_expect_success 'update-index and ls-files' ' cd "$HERE" && git update-index --add one && case "`git ls-files`" in - one) echo ok one ;; + one) echo pass one ;; *) echo bad one; exit 1 ;; esac && cd dir && git update-index --add two && case "`git ls-files`" in - two) echo ok two ;; + two) echo pass two ;; *) echo bad two; exit 1 ;; esac && cd .. && case "`git ls-files`" in - dir/two"$LF"one) echo ok both ;; + dir/two"$LF"one) echo pass both ;; *) echo bad; exit 1 ;; esac ' @@ -58,17 +58,17 @@ test_expect_success 'diff-files' ' echo a >>one && echo d >>dir/two && case "`git diff-files --name-only`" in - dir/two"$LF"one) echo ok top ;; + dir/two"$LF"one) echo pass top ;; *) echo bad top; exit 1 ;; esac && # diff should not omit leading paths cd dir && case "`git diff-files --name-only`" in - dir/two"$LF"one) echo ok subdir ;; + dir/two"$LF"one) echo pass subdir ;; *) echo bad subdir; exit 1 ;; esac && case "`git diff-files --name-only .`" in - dir/two) echo ok subdir limited ;; + dir/two) echo pass subdir limited ;; *) echo bad subdir limited; exit 1 ;; esac ' diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 7ddab5fb76..074f2f2e3e 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -846,4 +846,12 @@ test_expect_success 'check split_cmdline return' " test_must_fail git merge master " +test_expect_success 'git -c "key=value" support' ' + test "z$(git -c name=value config name)" = zvalue && + test "z$(git -c core.name=value config core.name)" = zvalue && + test "z$(git -c CamelCase=value config camelcase)" = zvalue && + test "z$(git -c flag config --bool flag)" = ztrue && + test_must_fail git -c core.name=value config name +' + test_done diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index cc30be4a65..97ab02aceb 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -15,39 +15,31 @@ umask 077 # is a good candidate: exists on all unices, and it has permission # anyway, so we don't create a security hole running the testsuite. -if ! setfacl -m u:root:rwx .; then - say "Skipping ACL tests: unable to use setfacl" - test_done -fi +setfacl_out="$(setfacl -m u:root:rwx . 2>&1)" +setfacl_ret=$? -modebits () { - ls -l "$1" | sed -e 's|^\(..........\).*|\1|' -} +if [ $setfacl_ret != 0 ]; then + skip_all="Skipping ACL tests: unable to use setfacl (output: '$setfacl_out'; return code: '$setfacl_ret')" + test_done +fi check_perms_and_acl () { - actual=$(modebits "$1") && - case "$actual" in - -r--r-----*) - : happy - ;; - *) - echo "Got permission '$actual', expected '-r--r-----'" - false - ;; - esac && + test -r "$1" && getfacl "$1" > actual && grep -q "user:root:rwx" actual && grep -q "user:${LOGNAME}:rwx" actual && - grep -q "mask::r--" actual && + egrep "mask::?r--" actual > /dev/null 2>&1 && grep -q "group::---" actual || false } dirs_to_set="./ .git/ .git/objects/ .git/objects/pack/" test_expect_success 'Setup test repo' ' + setfacl -m d:u::rwx,d:g::---,d:o:---,d:m:rwx $dirs_to_set && + setfacl -m m:rwx $dirs_to_set && setfacl -m u:root:rwx $dirs_to_set && - setfacl -d -m u:"$LOGNAME":rwx $dirs_to_set && - setfacl -d -m u:root:rwx $dirs_to_set && + setfacl -m d:u:"$LOGNAME":rwx $dirs_to_set && + setfacl -m d:u:root:rwx $dirs_to_set && touch file.txt && git add file.txt && diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index eb45afb018..782e75d000 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -41,6 +41,23 @@ test_expect_success "check-ref-format --branch @{-1}" ' refname2=$(git check-ref-format --branch @{-2}) && test "$refname2" = master' +test_expect_success 'check-ref-format --branch from subdir' ' + mkdir subdir && + + T=$(git write-tree) && + sha1=$(echo A | git commit-tree $T) && + git update-ref refs/heads/master $sha1 && + git update-ref refs/remotes/origin/master $sha1 + git checkout master && + git checkout origin/master && + git checkout master && + refname=$( + cd subdir && + git check-ref-format --branch @{-1} + ) && + test "$refname" = "$sha1" +' + valid_ref_normalized() { test_expect_success "ref name '$1' simplifies to '$2'" " refname=\$(git check-ref-format --print '$1') && diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 49cae3ed52..759cf12e16 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -5,7 +5,9 @@ test_description='git fsck random collection of tests' . ./test-lib.sh test_expect_success setup ' + git config i18n.commitencoding ISO-8859-1 && test_commit A fileA one && + git config --unset i18n.commitencoding && git checkout HEAD^0 && test_commit B fileB two && git tag -d A B && @@ -28,6 +30,12 @@ test_expect_success 'loose objects borrowed from alternate are not missing' ' ) ' +test_expect_success 'valid objects appear valid' ' + { git fsck 2>out; true; } && + ! grep error out && + ! grep fatal out +' + # Corruption tests follow. Make sure to remove all traces of the # specific corruption you test afterwards, lest a later test trip over # it. @@ -57,6 +65,34 @@ test_expect_success 'branch pointing to non-commit' ' git update-ref -d refs/heads/invalid ' +new=nothing +test_expect_success 'email without @ is okay' ' + git cat-file commit HEAD >basis && + sed "s/@/AT/" basis >okay && + new=$(git hash-object -t commit -w --stdin <okay) && + echo "$new" && + git update-ref refs/heads/bogus "$new" && + git fsck 2>out && + cat out && + ! grep "error in commit $new" out +' +git update-ref -d refs/heads/bogus +rm -f ".git/objects/$new" + +new=nothing +test_expect_success 'email with embedded > is not okay' ' + git cat-file commit HEAD >basis && + sed "s/@[a-z]/&>/" basis >bad-email && + new=$(git hash-object -t commit -w --stdin <bad-email) && + echo "$new" && + git update-ref refs/heads/bogus "$new" && + git fsck 2>out && + cat out && + grep "error in commit $new" out +' +git update-ref -d refs/heads/bogus +rm -f ".git/objects/$new" + cat > invalid-tag <<EOF object ffffffffffffffffffffffffffffffffffffffff type commit diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 9df301211c..bd8b60732b 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -30,6 +30,7 @@ test_rev_parse() { EMPTY_TREE=$(git write-tree) mkdir -p work/sub/dir || exit 1 +mkdir -p work2 || exit 1 mv .git repo.git || exit 1 say "core.worktree = relative path" @@ -54,7 +55,9 @@ GIT_DIR=$(pwd)/repo.git GIT_CONFIG=$GIT_DIR/config git config core.worktree "$(pwd)/work" test_rev_parse 'outside' false false false -cd work || exit 1 +cd work2 +test_rev_parse 'outside2' false false false +cd ../work || exit 1 test_rev_parse 'inside' false false true '' cd sub/dir || exit 1 test_rev_parse 'subdirectory' false false true sub/dir/ @@ -67,7 +70,9 @@ git config core.worktree non-existent GIT_WORK_TREE=work export GIT_WORK_TREE test_rev_parse 'outside' false false false -cd work || exit 1 +cd work2 +test_rev_parse 'outside' false false false +cd ../work || exit 1 GIT_WORK_TREE=. test_rev_parse 'inside' false false true '' cd sub/dir || exit 1 @@ -76,6 +81,7 @@ test_rev_parse 'subdirectory' false false true sub/dir/ cd ../../.. || exit 1 mv work repo.git/work +mv work2 repo.git/work2 say "GIT_WORK_TREE=absolute path, work tree below git dir" GIT_DIR=$(pwd)/repo.git @@ -86,6 +92,8 @@ cd repo.git || exit 1 test_rev_parse 'in repo.git' false true false cd objects || exit 1 test_rev_parse 'in repo.git/objects' false true false +cd ../work2 || exit 1 +test_rev_parse 'in repo.git/work2' false true false cd ../work || exit 1 test_rev_parse 'in repo.git/work' false true true '' cd sub/dir || exit 1 diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index 3b612c67be..b3195c4707 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -3,7 +3,8 @@ test_description='test git rev-parse --parseopt' . ./test-lib.sh -cat > expect.err <<EOF +cat > expect <<\END_EXPECT +cat <<\EOF usage: some-command [options] <args>... some-command does foo and bar! @@ -19,6 +20,7 @@ Extras --extra1 line above used to cause a segfault but no longer does EOF +END_EXPECT cat > optionspec << EOF some-command [options] <args>... @@ -38,8 +40,8 @@ extra1 line above used to cause a segfault but no longer does EOF test_expect_success 'test --parseopt help output' ' - git rev-parse --parseopt -- -h 2> output.err < optionspec - test_cmp expect.err output.err + git rev-parse --parseopt -- -h > output < optionspec + test_cmp expect output ' cat > expect <<EOF diff --git a/t/t1509-root-worktree.sh b/t/t1509-root-worktree.sh new file mode 100755 index 0000000000..7f60fd0b2e --- /dev/null +++ b/t/t1509-root-worktree.sh @@ -0,0 +1,249 @@ +#!/bin/sh + +test_description='Test Git when git repository is located at root + +This test requires write access in root. Do not bother if you do not +have a throwaway chroot or VM. + +Script t1509/prepare-chroot.sh may help you setup chroot, then you +can chroot in and execute this test from there. +' + +. ./test-lib.sh + +test_cmp_val() { + echo "$1" > expected + echo "$2" > result + test_cmp expected result +} + +test_vars() { + test_expect_success "$1: gitdir" ' + test_cmp_val "'"$2"'" "$(git rev-parse --git-dir)" + ' + + test_expect_success "$1: worktree" ' + test_cmp_val "'"$3"'" "$(git rev-parse --show-toplevel)" + ' + + test_expect_success "$1: prefix" ' + test_cmp_val "'"$4"'" "$(git rev-parse --show-prefix)" + ' +} + +test_foobar_root() { + test_expect_success 'add relative' ' + test -z "$(cd / && git ls-files)" && + git add foo/foome && + git add foo/bar/barme && + git add me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' + + test_expect_success 'add absolute' ' + test -z "$(cd / && git ls-files)" && + git add /foo/foome && + git add /foo/bar/barme && + git add /me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' + +} + +test_foobar_foo() { + test_expect_success 'add relative' ' + test -z "$(cd / && git ls-files)" && + git add foome && + git add bar/barme && + git add ../me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' + + test_expect_success 'add absolute' ' + test -z "$(cd / && git ls-files)" && + git add /foo/foome && + git add /foo/bar/barme && + git add /me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' +} + +test_foobar_foobar() { + test_expect_success 'add relative' ' + test -z "$(cd / && git ls-files)" && + git add ../foome && + git add barme && + git add ../../me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' + + test_expect_success 'add absolute' ' + test -z "$(cd / && git ls-files)" && + git add /foo/foome && + git add /foo/bar/barme && + git add /me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' +} + +if ! test_have_prereq POSIXPERM || ! [ -w / ]; then + skip_all="Dangerous test skipped. Read this test if you want to execute it" + test_done +fi + +if [ "$IKNOWWHATIAMDOING" != "YES" ]; then + skip_all="You must set env var IKNOWWHATIAMDOING=YES in order to run this test" + test_done +fi + +if [ "$UID" = 0 ]; then + skip_all="No you can't run this with root" + test_done +fi + +ONE_SHA1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d + +test_expect_success 'setup' ' + rm -rf /foo + mkdir /foo && + mkdir /foo/bar && + echo 1 > /foo/foome && + echo 1 > /foo/bar/barme && + echo 1 > /me +' + +say "GIT_DIR absolute, GIT_WORK_TREE set" + +test_expect_success 'go to /' 'cd /' + +cat >ls.expected <<EOF +100644 $ONE_SHA1 0 foo/bar/barme +100644 $ONE_SHA1 0 foo/foome +100644 $ONE_SHA1 0 me +EOF + +export GIT_DIR="$TRASH_DIRECTORY/.git" +export GIT_WORK_TREE=/ + +test_vars 'abs gitdir, root' "$GIT_DIR" "/" "" +test_foobar_root + +test_expect_success 'go to /foo' 'cd /foo' + +test_vars 'abs gitdir, foo' "$GIT_DIR" "/" "foo/" +test_foobar_foo + +test_expect_success 'go to /foo/bar' 'cd /foo/bar' + +test_vars 'abs gitdir, foo/bar' "$GIT_DIR" "/" "foo/bar/" +test_foobar_foobar + +say "GIT_DIR relative, GIT_WORK_TREE set" + +test_expect_success 'go to /' 'cd /' + +export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git" +export GIT_WORK_TREE=/ + +test_vars 'rel gitdir, root' "$GIT_DIR" "/" "" +test_foobar_root + +test_expect_success 'go to /foo' 'cd /foo' + +export GIT_DIR="../$TRASH_DIRECTORY/.git" +export GIT_WORK_TREE=/ + +test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/" +test_foobar_foo + +test_expect_success 'go to /foo/bar' 'cd /foo/bar' + +export GIT_DIR="../../$TRASH_DIRECTORY/.git" +export GIT_WORK_TREE=/ + +test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/" +test_foobar_foobar + +say "GIT_DIR relative, GIT_WORK_TREE relative" + +test_expect_success 'go to /' 'cd /' + +export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git" +export GIT_WORK_TREE=. + +test_vars 'rel gitdir, root' "$GIT_DIR" "/" "" +test_foobar_root + +test_expect_success 'go to /' 'cd /foo' + +export GIT_DIR="../$TRASH_DIRECTORY/.git" +export GIT_WORK_TREE=.. + +test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/" +test_foobar_foo + +test_expect_success 'go to /foo/bar' 'cd /foo/bar' + +export GIT_DIR="../../$TRASH_DIRECTORY/.git" +export GIT_WORK_TREE=../.. + +test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/" +test_foobar_foobar + +say ".git at root" + +unset GIT_DIR +unset GIT_WORK_TREE + +test_expect_success 'go to /' 'cd /' +test_expect_success 'setup' ' + rm -rf /.git + echo "Initialized empty Git repository in /.git/" > expected && + git init > result && + test_cmp expected result +' + +test_vars 'auto gitdir, root' ".git" "/" "" +test_foobar_root + +test_expect_success 'go to /foo' 'cd /foo' +test_vars 'auto gitdir, foo' "/.git" "/" "foo/" +test_foobar_foo + +test_expect_success 'go to /foo/bar' 'cd /foo/bar' +test_vars 'auto gitdir, foo/bar' "/.git" "/" "foo/bar/" +test_foobar_foobar + +test_expect_success 'cleanup' 'rm -rf /.git' + +say "auto bare gitdir" + +# DESTROYYYYY!!!!! +test_expect_success 'setup' ' + rm -rf /refs /objects /info /hooks + rm /* + cd / && + echo "Initialized empty Git repository in /" > expected && + git init --bare > result && + test_cmp expected result +' + +test_vars 'auto gitdir, root' "." "" "" + +test_expect_success 'go to /foo' 'cd /foo' + +test_vars 'auto gitdir, root' "/" "" "" + +test_done diff --git a/t/t1509/excludes b/t/t1509/excludes new file mode 100644 index 0000000000..d4d21d31a9 --- /dev/null +++ b/t/t1509/excludes @@ -0,0 +1,14 @@ +*.o +*~ +*.bak +*.c +*.h +.git +contrib +Documentation +git-gui +gitk-git +gitweb +t/t4013 +t/t5100 +t/t5515 diff --git a/t/t1509/prepare-chroot.sh b/t/t1509/prepare-chroot.sh new file mode 100755 index 0000000000..c5334a8fa4 --- /dev/null +++ b/t/t1509/prepare-chroot.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +die() { + echo >&2 "$@" + exit 1 +} + +xmkdir() { + while [ -n "$1" ]; do + [ -d "$1" ] || mkdir "$1" || die "Unable to mkdir $1" + shift + done +} + +R="$1" + +[ -n "$R" ] || die "Usage: prepare-chroot.sh <root>" +[ -x git ] || die "This script needs to be executed at git source code's top directory" +[ -x /bin/busybox ] || die "You need busybox" + +xmkdir "$R" "$R/bin" "$R/etc" "$R/lib" "$R/dev" +[ -c "$R/dev/null" ] || die "/dev/null is missing. Do mknod $R/dev/null c 1 3 && chmod 666 $R/dev/null" +echo "root:x:0:0:root:/:/bin/sh" > "$R/etc/passwd" +echo "$(id -nu):x:$(id -u):$(id -g)::$(pwd)/t:/bin/sh" >> "$R/etc/passwd" +echo "root::0:root" > "$R/etc/group" +echo "$(id -ng)::$(id -g):$(id -nu)" >> "$R/etc/group" + +[ -x "$R/bin/busybox" ] || cp /bin/busybox "$R/bin/busybox" +[ -x "$R/bin/sh" ] || ln -s /bin/busybox "$R/bin/sh" +[ -x "$R/bin/su" ] || ln -s /bin/busybox "$R/bin/su" + +mkdir -p "$R$(pwd)" +rsync --exclude-from t/t1509/excludes -Ha . "$R$(pwd)" +ldd git | grep '/' | sed 's,.*\s\(/[^ ]*\).*,\1,' | while read i; do + mkdir -p "$R$(dirname $i)" + cp "$i" "$R/$i" +done +echo "Execute this in root: 'chroot $R /bin/su - $(id -nu)'" diff --git a/t/t2007-checkout-symlink.sh b/t/t2007-checkout-symlink.sh index 20f33436d0..05cc8fdd01 100755 --- a/t/t2007-checkout-symlink.sh +++ b/t/t2007-checkout-symlink.sh @@ -8,7 +8,7 @@ test_description='git checkout to switch between branches with symlink<->dir' if ! test_have_prereq SYMLINKS then - say "symbolic links not supported - skipping tests" + skip_all="symbolic links not supported - skipping tests" test_done fi @@ -44,8 +44,10 @@ test_expect_success 'switch from symlink to dir' ' ' -rm -fr frotz xyzzy nitfol && -git checkout -f master || exit +test_expect_success 'Remove temporary directories & switch to master' ' + rm -fr frotz xyzzy nitfol && + git checkout -f master +' test_expect_success 'switch from dir to symlink' ' diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh new file mode 100755 index 0000000000..2d2f63f22e --- /dev/null +++ b/t/t2017-checkout-orphan.sh @@ -0,0 +1,119 @@ +#!/bin/sh +# +# Copyright (c) 2010 Erick Mattos +# + +test_description='git checkout --orphan + +Main Tests for --orphan functionality.' + +. ./test-lib.sh + +TEST_FILE=foo + +test_expect_success 'Setup' ' + echo "Initial" >"$TEST_FILE" && + git add "$TEST_FILE" && + git commit -m "First Commit" + test_tick && + echo "State 1" >>"$TEST_FILE" && + git add "$TEST_FILE" && + test_tick && + git commit -m "Second Commit" +' + +test_expect_success '--orphan creates a new orphan branch from HEAD' ' + git checkout --orphan alpha && + test_must_fail git rev-parse --verify HEAD && + test "refs/heads/alpha" = "$(git symbolic-ref HEAD)" && + test_tick && + git commit -m "Third Commit" && + test_must_fail git rev-parse --verify HEAD^ && + git diff-tree --quiet master alpha +' + +test_expect_success '--orphan creates a new orphan branch from <start_point>' ' + git checkout master && + git checkout --orphan beta master^ && + test_must_fail git rev-parse --verify HEAD && + test "refs/heads/beta" = "$(git symbolic-ref HEAD)" && + test_tick && + git commit -m "Fourth Commit" && + test_must_fail git rev-parse --verify HEAD^ && + git diff-tree --quiet master^ beta +' + +test_expect_success '--orphan must be rejected with -b' ' + git checkout master && + test_must_fail git checkout --orphan new -b newer && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + +test_expect_success '--orphan must be rejected with -t' ' + git checkout master && + test_must_fail git checkout --orphan new -t master && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + +test_expect_success '--orphan ignores branch.autosetupmerge' ' + git checkout master && + git config branch.autosetupmerge always && + git checkout --orphan gamma && + test -z "$(git config branch.gamma.merge)" && + test refs/heads/gamma = "$(git symbolic-ref HEAD)" && + test_must_fail git rev-parse --verify HEAD^ +' + +test_expect_success '--orphan makes reflog by default' ' + git checkout master && + git config --unset core.logAllRefUpdates && + git checkout --orphan delta && + test_must_fail git rev-parse --verify delta@{0} && + git commit -m Delta && + git rev-parse --verify delta@{0} +' + +test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' ' + git checkout master && + git config core.logAllRefUpdates false && + git checkout --orphan epsilon && + test_must_fail git rev-parse --verify epsilon@{0} && + git commit -m Epsilon && + test_must_fail git rev-parse --verify epsilon@{0} +' + +test_expect_success '--orphan with -l makes reflog when core.logAllRefUpdates = false' ' + git checkout master && + git checkout -l --orphan zeta && + test_must_fail git rev-parse --verify zeta@{0} && + git commit -m Zeta && + git rev-parse --verify zeta@{0} +' + +test_expect_success 'giving up --orphan not committed when -l and core.logAllRefUpdates = false deletes reflog' ' + git checkout master && + git checkout -l --orphan eta && + test_must_fail git rev-parse --verify eta@{0} && + git checkout master && + test_must_fail git rev-parse --verify eta@{0} +' + +test_expect_success '--orphan is rejected with an existing name' ' + git checkout master && + test_must_fail git checkout --orphan master && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + +test_expect_success '--orphan refuses to switch if a merge is needed' ' + git checkout master && + git reset --hard && + echo local >>"$TEST_FILE" && + cat "$TEST_FILE" >"$TEST_FILE.saved" && + test_must_fail git checkout --orphan new master^ && + test refs/heads/master = "$(git symbolic-ref HEAD)" && + test_cmp "$TEST_FILE" "$TEST_FILE.saved" && + git diff-index --quiet --cached HEAD && + git reset --hard +' + +test_done diff --git a/t/t2102-update-index-symlinks.sh b/t/t2102-update-index-symlinks.sh index 1ed44ee503..4d0d0a3515 100755 --- a/t/t2102-update-index-symlinks.sh +++ b/t/t2102-update-index-symlinks.sh @@ -24,7 +24,7 @@ git update-index symlink' test_expect_success \ 'the index entry must still be a symbolic link' ' case "`git ls-files --stage --cached symlink`" in -120000" "*symlink) echo ok;; +120000" "*symlink) echo pass;; *) echo fail; git ls-files --stage --cached symlink; (exit 1);; esac' diff --git a/t/t2106-update-index-assume-unchanged.sh b/t/t2106-update-index-assume-unchanged.sh new file mode 100755 index 0000000000..99d858c6b7 --- /dev/null +++ b/t/t2106-update-index-assume-unchanged.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description='git update-index --assume-unchanged test. +' + +. ./test-lib.sh + +test_expect_success 'setup' \ + ': >file && + git add file && + git commit -m initial && + git branch other && + echo upstream >file && + git add file && + git commit -m upstream' + +test_expect_success 'do not switch branches with dirty file' \ + 'git reset --hard && + git checkout other && + echo dirt >file && + git update-index --assume-unchanged file && + test_must_fail git checkout master' + +test_done diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh index 86291e8399..2eec0118c4 100755 --- a/t/t3000-ls-files-others.sh +++ b/t/t3000-ls-files-others.sh @@ -17,57 +17,52 @@ filesystem. ' . ./test-lib.sh -date >path0 -if test_have_prereq SYMLINKS -then - ln -s xyzzy path1 -else - date > path1 -fi -mkdir path2 path3 path4 -date >path2/file2 -date >path2-junk -date >path3/file3 -date >path3-junk -git update-index --add path3-junk path3/file3 - -cat >expected1 <<EOF -expected1 -expected2 -expected3 -output -path0 -path1 -path2-junk -path2/file2 -EOF -sed -e 's|path2/file2|path2/|' <expected1 >expected2 -cat <expected2 >expected3 -echo path4/ >>expected2 - -test_expect_success \ - 'git ls-files --others to show output.' \ - 'git ls-files --others >output' - -test_expect_success \ - 'git ls-files --others should pick up symlinks.' \ - 'test_cmp expected1 output' +test_expect_success 'setup ' ' + date >path0 && + if test_have_prereq SYMLINKS + then + ln -s xyzzy path1 + else + date >path1 + fi && + mkdir path2 path3 path4 && + date >path2/file2 && + date >path2-junk && + date >path3/file3 && + date >path3-junk && + git update-index --add path3-junk path3/file3 +' -test_expect_success \ - 'git ls-files --others --directory to show output.' \ - 'git ls-files --others --directory >output' +test_expect_success 'setup: expected output' ' + cat >expected1 <<-\EOF && + expected1 + expected2 + expected3 + output + path0 + path1 + path2-junk + path2/file2 + EOF + sed -e "s|path2/file2|path2/|" <expected1 >expected2 && + cp expected2 expected3 && + echo path4/ >>expected2 +' -test_expect_success \ - 'git ls-files --others --directory should not get confused.' \ - 'test_cmp expected2 output' +test_expect_success 'ls-files --others' ' + git ls-files --others >output && + test_cmp expected1 output +' -test_expect_success \ - 'git ls-files --others --directory --no-empty-directory to show output.' \ - 'git ls-files --others --directory --no-empty-directory >output' +test_expect_success 'ls-files --others --directory' ' + git ls-files --others --directory >output && + test_cmp expected2 output +' -test_expect_success \ - '--no-empty-directory hides empty directory' \ - 'test_cmp expected3 output' +test_expect_success '--no-empty-directory hides empty directory' ' + git ls-files --others --directory --no-empty-directory >output && + test_cmp expected3 output +' test_done diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh index f4066cbc09..a7d8187169 100755 --- a/t/t3020-ls-files-error-unmatch.sh +++ b/t/t3020-ls-files-error-unmatch.sh @@ -11,9 +11,11 @@ line. ' . ./test-lib.sh -touch foo bar -git update-index --add foo bar -git commit -m "add foo bar" +test_expect_success 'setup' ' + touch foo bar && + git update-index --add foo bar && + git commit -m "add foo bar" +' test_expect_success \ 'git ls-files --error-unmatch should fail with unmatched path.' \ diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 9929f82021..d541544537 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -22,6 +22,7 @@ test_expect_success 'setup 1' ' git branch df-2 && git branch df-3 && git branch remove && + git branch submod && echo hello >>a && cp a d/e && @@ -236,6 +237,17 @@ test_expect_success 'setup 6' ' test_cmp expected actual ' +test_expect_success 'setup 7' ' + + git checkout submod && + git rm d/e && + test_tick && + git commit -m "remove d/e" && + git update-index --add --cacheinfo 160000 $c1 d && + test_tick && + git commit -m "make d/ a submodule" +' + test_expect_success 'merge-recursive simple' ' rm -fr [abcd] && @@ -551,4 +563,21 @@ test_expect_success 'merge removes empty directories' ' test_must_fail test -d d ' +test_expect_failure 'merge-recursive simple w/submodule' ' + + git checkout submod && + git merge remove +' + +test_expect_failure 'merge-recursive simple w/submodule result' ' + + git ls-files -s >actual && + ( + echo "100644 $o5 0 a" + echo "100644 $o0 0 c" + echo "160000 $c1 0 d" + ) >expected && + test_cmp expected actual +' + test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index e0b760513c..f54a533456 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -43,7 +43,7 @@ test_expect_success \ git branch -l d/e/f && test -f .git/refs/heads/d/e/f && test -f .git/logs/refs/heads/d/e/f && - diff expect .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' \ @@ -222,7 +222,28 @@ test_expect_success \ 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 && - diff expect .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' ' + git checkout master && + git config --unset core.logAllRefUpdates && + git checkout -b alpha && + git rev-parse --verify alpha@{0} +' + +test_expect_success 'checkout -b does not make reflog when core.logAllRefUpdates = false' ' + git checkout master && + git config core.logAllRefUpdates false && + git checkout -b beta && + test_must_fail git rev-parse --verify beta@{0} +' + +test_expect_success 'checkout -b with -l makes reflog when core.logAllRefUpdates = false' ' + git checkout master && + git checkout -lb gamma && + git config --unset core.logAllRefUpdates && + git rev-parse --verify gamma@{0} +' test_expect_success 'avoid ambiguous track' ' git config branch.autosetupmerge true && diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 413019acaf..cd04361df8 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -28,7 +28,7 @@ test_expect_success \ SHA1=`cat .git/refs/heads/a` && echo "$SHA1 refs/heads/a" >expect && git show-ref a >result && - diff expect result' + test_cmp expect result' test_expect_success \ 'see if a branch still exists when packed' \ @@ -37,7 +37,7 @@ test_expect_success \ rm -f .git/refs/heads/b && echo "$SHA1 refs/heads/b" >expect && git show-ref b >result && - diff expect result' + test_cmp expect result' test_expect_success 'git branch c/d should barf if branch c exists' ' git branch c && @@ -52,7 +52,7 @@ test_expect_success \ git pack-refs --all --prune && echo "$SHA1 refs/heads/e" >expect && git show-ref e >result && - diff expect result' + test_cmp expect result' test_expect_success 'see if git pack-refs --prune remove ref files' ' git branch f && @@ -60,6 +60,12 @@ test_expect_success 'see if git pack-refs --prune remove ref files' ' ! test -f .git/refs/heads/f ' +test_expect_success 'see if git pack-refs --prune removes empty dirs' ' + git branch r/s/t && + git pack-refs --all --prune && + ! test -e .git/refs/heads/r +' + test_expect_success \ 'git branch g should work when git branch g/h has been deleted' \ 'git branch g/h && @@ -109,7 +115,7 @@ test_expect_success 'pack, prune and repack' ' git show-ref >all-of-them && git pack-refs && git show-ref >again && - diff all-of-them again + test_cmp all-of-them again ' test_done diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh index db46d53e82..a99e4d8b92 100755 --- a/t/t3300-funny-names.sh +++ b/t/t3300-funny-names.sh @@ -26,7 +26,7 @@ echo 'Foo Bar Baz' >"$p2" test -f "$p1" && cmp "$p0" "$p1" || { # since FAT/NTFS does not allow tabs in filenames, skip this test - say 'Your filesystem does not allow tabs in filenames, test skipped.' + skip_all='Your filesystem does not allow tabs in filenames, test skipped.' test_done } diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 714626d2d6..1d82f79ee0 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -13,11 +13,11 @@ echo "$MSG" > "$1" echo "$MSG" >& 2 EOF chmod a+x fake_editor.sh -VISUAL=./fake_editor.sh -export VISUAL +GIT_EDITOR=./fake_editor.sh +export GIT_EDITOR test_expect_success 'cannot annotate non-existing HEAD' ' - (MSG=3 && export MSG && test_must_fail git notes edit) + (MSG=3 && export MSG && test_must_fail git notes add) ' test_expect_success setup ' @@ -33,18 +33,18 @@ test_expect_success setup ' test_expect_success 'need valid notes ref' ' (MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && - test_must_fail git notes edit) && + test_must_fail git notes add) && (MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && test_must_fail git notes show) ' -test_expect_success 'refusing to edit in refs/heads/' ' +test_expect_success 'refusing to add notes in refs/heads/' ' (MSG=1 GIT_NOTES_REF=refs/heads/bogus && export MSG GIT_NOTES_REF && - test_must_fail git notes edit) + test_must_fail git notes add) ' -test_expect_success 'refusing to edit in refs/remotes/' ' +test_expect_success 'refusing to edit notes in refs/remotes/' ' (MSG=1 GIT_NOTES_REF=refs/remotes/bogus && export MSG GIT_NOTES_REF && test_must_fail git notes edit) @@ -55,10 +55,64 @@ test_expect_success 'handle empty notes gracefully' ' git notes show ; test 1 = $? ' +test_expect_success 'show non-existent notes entry with %N' ' + for l in A B + do + echo "$l" + done >expect && + git show -s --format='A%n%NB' >output && + test_cmp expect output +' + test_expect_success 'create notes' ' git config core.notesRef refs/notes/commits && - MSG=b1 git notes edit && - test ! -f .git/new-notes && + MSG=b4 git notes add && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b4 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +test_expect_success 'show notes entry with %N' ' + for l in A b4 B + do + echo "$l" + done >expect && + git show -s --format='A%n%NB' >output && + test_cmp expect output +' + +cat >expect <<EOF +d423f8c refs/notes/commits@{0}: notes: Notes added by 'git notes add' +EOF + +test_expect_success 'create reflog entry' ' + git reflog show refs/notes/commits >output && + test_cmp expect output +' + +test_expect_success 'edit existing notes' ' + MSG=b3 git notes edit && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b3 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +test_expect_success 'cannot add note where one exists' ' + ! MSG=b2 git notes add && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b3 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +test_expect_success 'can overwrite existing note with "git notes add -f"' ' + MSG=b1 git notes add -f && + test ! -f .git/NOTES_EDITMSG && test 1 = $(git ls-tree refs/notes/commits | wc -l) && test b1 = $(git notes show) && git show HEAD^ && @@ -81,6 +135,7 @@ test_expect_success 'show notes' ' git log -1 > output && test_cmp expect output ' + test_expect_success 'create multi-line notes (setup)' ' : > a3 && git add a3 && @@ -88,7 +143,7 @@ test_expect_success 'create multi-line notes (setup)' ' git commit -m 3rd && MSG="b3 c3c3c3c3 -d3d3d3" git notes edit +d3d3d3" git notes add ' cat > expect-multiline << EOF @@ -111,19 +166,16 @@ test_expect_success 'show multi-line notes' ' git log -2 > output && test_cmp expect-multiline output ' -test_expect_success 'create -m and -F notes (setup)' ' +test_expect_success 'create -F notes (setup)' ' : > a4 && git add a4 && test_tick && git commit -m 4th && echo "xyzzy" > note5 && - git notes edit -m spam -F note5 -m "foo -bar -baz" + git notes add -F note5 ' -whitespace=" " -cat > expect-m-and-F << EOF +cat > expect-F << EOF commit 15023535574ded8b1a89052b32673f84cf9582b8 Author: A U Thor <author@example.com> Date: Thu Apr 7 15:16:13 2005 -0700 @@ -131,21 +183,15 @@ Date: Thu Apr 7 15:16:13 2005 -0700 4th Notes: - spam -$whitespace xyzzy -$whitespace - foo - bar - baz EOF -printf "\n" >> expect-m-and-F -cat expect-multiline >> expect-m-and-F +printf "\n" >> expect-F +cat expect-multiline >> expect-F -test_expect_success 'show -m and -F notes' ' +test_expect_success 'show -F notes' ' git log -3 > output && - test_cmp expect-m-and-F output + test_cmp expect-F output ' cat >expect << EOF @@ -165,13 +211,7 @@ test_expect_success 'git log --pretty=raw does not show notes' ' cat >>expect <<EOF Notes: - spam -$whitespace xyzzy -$whitespace - foo - bar - baz EOF test_expect_success 'git log --show-notes' ' git log -1 --pretty=raw --show-notes >output && @@ -180,17 +220,17 @@ test_expect_success 'git log --show-notes' ' test_expect_success 'git log --no-notes' ' git log -1 --no-notes >output && - ! grep spam output + ! grep xyzzy output ' test_expect_success 'git format-patch does not show notes' ' git format-patch -1 --stdout >output && - ! grep spam output + ! grep xyzzy output ' test_expect_success 'git format-patch --show-notes does show notes' ' git format-patch --show-notes -1 --stdout >output && - grep spam output + grep xyzzy output ' for pretty in \ @@ -203,8 +243,815 @@ do esac test_expect_success "git show $pretty does$not show notes" ' git show $p >output && - eval "$negate grep spam output" + eval "$negate grep xyzzy output" ' done +test_expect_success 'create -m notes (setup)' ' + : > a5 && + git add a5 && + test_tick && + git commit -m 5th && + git notes add -m spam -m "foo +bar +baz" +' + +whitespace=" " +cat > expect-m << EOF +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th + +Notes: + spam +$whitespace + foo + bar + baz +EOF + +printf "\n" >> expect-m +cat expect-F >> expect-m + +test_expect_success 'show -m notes' ' + git log -4 > output && + test_cmp expect-m output +' + +test_expect_success 'remove note with add -f -F /dev/null (setup)' ' + git notes add -f -F /dev/null +' + +cat > expect-rm-F << EOF +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th +EOF + +printf "\n" >> expect-rm-F +cat expect-F >> expect-rm-F + +test_expect_success 'verify note removal with -F /dev/null' ' + git log -4 > output && + test_cmp expect-rm-F output && + ! git notes show +' + +test_expect_success 'do not create empty note with -m "" (setup)' ' + git notes add -m "" +' + +test_expect_success 'verify non-creation of note with -m ""' ' + git log -4 > output && + test_cmp expect-rm-F output && + ! git notes show +' + +cat > expect-combine_m_and_F << EOF +foo + +xyzzy + +bar + +zyxxy + +baz +EOF + +test_expect_success 'create note with combination of -m and -F' ' + echo "xyzzy" > note_a && + echo "zyxxy" > note_b && + git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" && + git notes show > output && + test_cmp expect-combine_m_and_F output +' + +test_expect_success 'remove note with "git notes remove" (setup)' ' + git notes remove HEAD^ && + git notes remove +' + +cat > expect-rm-remove << EOF +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th + +commit 15023535574ded8b1a89052b32673f84cf9582b8 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:16:13 2005 -0700 + + 4th +EOF + +printf "\n" >> expect-rm-remove +cat expect-multiline >> expect-rm-remove + +test_expect_success 'verify note removal with "git notes remove"' ' + git log -4 > output && + test_cmp expect-rm-remove output && + ! git notes show HEAD^ +' + +cat > expect << EOF +c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75 +c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5 +EOF + +test_expect_success 'list notes with "git notes list"' ' + git notes list > output && + test_cmp expect output +' + +test_expect_success 'list notes with "git notes"' ' + git notes > output && + test_cmp expect output +' + +cat > expect << EOF +c18dc024e14f08d18d14eea0d747ff692d66d6a3 +EOF + +test_expect_success 'list specific note with "git notes list <object>"' ' + git notes list HEAD^^ > output && + test_cmp expect output +' + +cat > expect << EOF +EOF + +test_expect_success 'listing non-existing notes fails' ' + test_must_fail git notes list HEAD > output && + test_cmp expect output +' + +cat > expect << EOF +Initial set of notes + +More notes appended with git notes append +EOF + +test_expect_success 'append to existing note with "git notes append"' ' + git notes add -m "Initial set of notes" && + git notes append -m "More notes appended with git notes append" && + git notes show > output && + test_cmp expect output +' + +cat > expect_list << EOF +c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75 +c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5 +4b6ad22357cc8a1296720574b8d2fbc22fab0671 bd1753200303d0a0344be813e504253b3d98e74d +EOF + +test_expect_success '"git notes list" does not expand to "git notes list HEAD"' ' + git notes list > output && + test_cmp expect_list output +' + +test_expect_success 'appending empty string does not change existing note' ' + git notes append -m "" && + git notes show > output && + test_cmp expect output +' + +test_expect_success 'git notes append == add when there is no existing note' ' + git notes remove HEAD && + test_must_fail git notes list HEAD && + git notes append -m "Initial set of notes + +More notes appended with git notes append" && + git notes show > output && + test_cmp expect output +' + +test_expect_success 'appending empty string to non-existing note does not create note' ' + git notes remove HEAD && + test_must_fail git notes list HEAD && + git notes append -m "" && + test_must_fail git notes list HEAD +' + +test_expect_success 'create other note on a different notes ref (setup)' ' + : > a6 && + git add a6 && + test_tick && + git commit -m 6th && + GIT_NOTES_REF="refs/notes/other" git notes add -m "other note" +' + +cat > expect-other << EOF +commit 387a89921c73d7ed72cd94d179c1c7048ca47756 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:18:13 2005 -0700 + + 6th + +Notes (other): + other note +EOF + +cat > expect-not-other << EOF +commit 387a89921c73d7ed72cd94d179c1c7048ca47756 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:18:13 2005 -0700 + + 6th +EOF + +test_expect_success 'Do not show note on other ref by default' ' + git log -1 > output && + test_cmp expect-not-other output +' + +test_expect_success 'Do show note when ref is given in GIT_NOTES_REF' ' + GIT_NOTES_REF="refs/notes/other" git log -1 > output && + test_cmp expect-other output +' + +test_expect_success 'Do show note when ref is given in core.notesRef config' ' + git config core.notesRef "refs/notes/other" && + git log -1 > output && + test_cmp expect-other output +' + +test_expect_success 'Do not show note when core.notesRef is overridden' ' + GIT_NOTES_REF="refs/notes/wrong" git log -1 > output && + test_cmp expect-not-other output +' + +cat > expect-both << EOF +commit 387a89921c73d7ed72cd94d179c1c7048ca47756 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:18:13 2005 -0700 + + 6th + +Notes: + order test + +Notes (other): + other note + +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th + +Notes: + replacement for deleted note +EOF + +test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' ' + GIT_NOTES_REF=refs/notes/commits git notes add \ + -m"replacement for deleted note" HEAD^ && + GIT_NOTES_REF=refs/notes/commits git notes add -m"order test" && + git config --unset core.notesRef && + git config notes.displayRef "refs/notes/*" && + git log -2 > output && + test_cmp expect-both output +' + +test_expect_success 'core.notesRef is implicitly in notes.displayRef' ' + git config core.notesRef refs/notes/commits && + git config notes.displayRef refs/notes/other && + git log -2 > output && + test_cmp expect-both output +' + +test_expect_success 'notes.displayRef can be given more than once' ' + git config --unset core.notesRef && + git config notes.displayRef refs/notes/commits && + git config --add notes.displayRef refs/notes/other && + git log -2 > output && + test_cmp expect-both output +' + +cat > expect-both-reversed << EOF +commit 387a89921c73d7ed72cd94d179c1c7048ca47756 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:18:13 2005 -0700 + + 6th + +Notes (other): + other note + +Notes: + order test +EOF + +test_expect_success 'notes.displayRef respects order' ' + git config core.notesRef refs/notes/other && + git config --unset-all notes.displayRef && + git config notes.displayRef refs/notes/commits && + git log -1 > output && + test_cmp expect-both-reversed output +' + +test_expect_success 'GIT_NOTES_DISPLAY_REF works' ' + git config --unset-all core.notesRef && + git config --unset-all notes.displayRef && + GIT_NOTES_DISPLAY_REF=refs/notes/commits:refs/notes/other \ + git log -2 > output && + test_cmp expect-both output +' + +cat > expect-none << EOF +commit 387a89921c73d7ed72cd94d179c1c7048ca47756 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:18:13 2005 -0700 + + 6th + +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th +EOF + +test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' ' + git config notes.displayRef "refs/notes/*" && + GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log -2 > output && + test_cmp expect-none output +' + +test_expect_success '--show-notes=* adds to GIT_NOTES_DISPLAY_REF' ' + GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log --show-notes=* -2 > output && + test_cmp expect-both output +' + +cat > expect-commits << EOF +commit 387a89921c73d7ed72cd94d179c1c7048ca47756 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:18:13 2005 -0700 + + 6th + +Notes: + order test +EOF + +test_expect_success '--no-standard-notes' ' + git log --no-standard-notes --show-notes=commits -1 > output && + test_cmp expect-commits output +' + +test_expect_success '--standard-notes' ' + git log --no-standard-notes --show-notes=commits \ + --standard-notes -2 > output && + test_cmp expect-both output +' + +test_expect_success '--show-notes=ref accumulates' ' + git log --show-notes=other --show-notes=commits \ + --no-standard-notes -1 > output && + test_cmp expect-both-reversed output +' + +test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' ' + git config core.notesRef refs/notes/other && + echo "Note on a tree" > expect + git notes add -m "Note on a tree" HEAD: && + git notes show HEAD: > actual && + test_cmp expect actual && + echo "Note on a blob" > expect + filename=$(git ls-tree --name-only HEAD | head -n1) && + git notes add -m "Note on a blob" HEAD:$filename && + git notes show HEAD:$filename > actual && + test_cmp expect actual && + echo "Note on a tag" > expect + git tag -a -m "This is an annotated tag" foobar HEAD^ && + git notes add -m "Note on a tag" foobar && + git notes show foobar > actual && + test_cmp expect actual +' + +cat > expect << EOF +commit 2ede89468182a62d0bde2583c736089bcf7d7e92 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:19:13 2005 -0700 + + 7th + +Notes (other): + other note +EOF + +test_expect_success 'create note from other note with "git notes add -C"' ' + : > a7 && + git add a7 && + test_tick && + git commit -m 7th && + git notes add -C $(git notes list HEAD^) && + git log -1 > actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD^)" +' + +test_expect_success 'create note from non-existing note with "git notes add -C" fails' ' + : > a8 && + git add a8 && + test_tick && + git commit -m 8th && + test_must_fail git notes add -C deadbeef && + test_must_fail git notes list HEAD +' + +cat > expect << EOF +commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:21:13 2005 -0700 + + 9th + +Notes (other): + yet another note +EOF + +test_expect_success 'create note from other note with "git notes add -c"' ' + : > a9 && + git add a9 && + test_tick && + git commit -m 9th && + MSG="yet another note" git notes add -c $(git notes list HEAD^^) && + git log -1 > actual && + test_cmp expect actual +' + +test_expect_success 'create note from non-existing note with "git notes add -c" fails' ' + : > a10 && + git add a10 && + test_tick && + git commit -m 10th && + ( + MSG="yet another note" && + export MSG && + test_must_fail git notes add -c deadbeef + ) && + test_must_fail git notes list HEAD +' + +cat > expect << EOF +commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:21:13 2005 -0700 + + 9th + +Notes (other): + yet another note +$whitespace + yet another note +EOF + +test_expect_success 'append to note from other note with "git notes append -C"' ' + git notes append -C $(git notes list HEAD^) HEAD^ && + git log -1 HEAD^ > actual && + test_cmp expect actual +' + +cat > expect << EOF +commit ffed603236bfa3891c49644257a83598afe8ae5a +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:22:13 2005 -0700 + + 10th + +Notes (other): + other note +EOF + +test_expect_success 'create note from other note with "git notes append -c"' ' + MSG="other note" git notes append -c $(git notes list HEAD^) && + git log -1 > actual && + test_cmp expect actual +' + +cat > expect << EOF +commit ffed603236bfa3891c49644257a83598afe8ae5a +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:22:13 2005 -0700 + + 10th + +Notes (other): + other note +$whitespace + yet another note +EOF + +test_expect_success 'append to note from other note with "git notes append -c"' ' + MSG="yet another note" git notes append -c $(git notes list HEAD) && + git log -1 > actual && + test_cmp expect actual +' + +cat > expect << EOF +commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:23:13 2005 -0700 + + 11th + +Notes (other): + other note +$whitespace + yet another note +EOF + +test_expect_success 'copy note with "git notes copy"' ' + : > a11 && + git add a11 && + test_tick && + git commit -m 11th && + git notes copy HEAD^ HEAD && + git log -1 > actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD^)" +' + +test_expect_success 'prevent overwrite with "git notes copy"' ' + test_must_fail git notes copy HEAD~2 HEAD && + git log -1 > actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD^)" +' + +cat > expect << EOF +commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:23:13 2005 -0700 + + 11th + +Notes (other): + yet another note +$whitespace + yet another note +EOF + +test_expect_success 'allow overwrite with "git notes copy -f"' ' + git notes copy -f HEAD~2 HEAD && + git log -1 > actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" +' + +test_expect_success 'cannot copy note from object without notes' ' + : > a12 && + git add a12 && + test_tick && + git commit -m 12th && + : > a13 && + git add a13 && + test_tick && + git commit -m 13th && + test_must_fail git notes copy HEAD^ HEAD +' + +cat > expect << EOF +commit e5d4fb5698d564ab8c73551538ecaf2b0c666185 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:25:13 2005 -0700 + + 13th + +Notes (other): + yet another note +$whitespace + yet another note + +commit 7038787dfe22a14c3867ce816dbba39845359719 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:24:13 2005 -0700 + + 12th + +Notes (other): + other note +$whitespace + yet another note +EOF + +test_expect_success 'git notes copy --stdin' ' + (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \ + echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | + git notes copy --stdin && + git log -2 > output && + test_cmp expect output && + test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" && + test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)" +' + +cat > expect << EOF +commit 37a0d4cba38afef96ba54a3ea567e6dac575700b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:27:13 2005 -0700 + + 15th + +commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:26:13 2005 -0700 + + 14th +EOF + +test_expect_success 'git notes copy --for-rewrite (unconfigured)' ' + test_commit 14th && + test_commit 15th && + (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \ + echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | + git notes copy --for-rewrite=foo && + git log -2 > output && + test_cmp expect output +' + +cat > expect << EOF +commit 37a0d4cba38afef96ba54a3ea567e6dac575700b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:27:13 2005 -0700 + + 15th + +Notes (other): + yet another note +$whitespace + yet another note + +commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:26:13 2005 -0700 + + 14th + +Notes (other): + other note +$whitespace + yet another note +EOF + +test_expect_success 'git notes copy --for-rewrite (enabled)' ' + git config notes.rewriteMode overwrite && + git config notes.rewriteRef "refs/notes/*" && + (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \ + echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | + git notes copy --for-rewrite=foo && + git log -2 > output && + test_cmp expect output +' + +test_expect_success 'git notes copy --for-rewrite (disabled)' ' + git config notes.rewrite.bar false && + echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) | + git notes copy --for-rewrite=bar && + git log -2 > output && + test_cmp expect output +' + +cat > expect << EOF +commit 37a0d4cba38afef96ba54a3ea567e6dac575700b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:27:13 2005 -0700 + + 15th + +Notes (other): + a fresh note +EOF + +test_expect_success 'git notes copy --for-rewrite (overwrite)' ' + git notes add -f -m"a fresh note" HEAD^ && + echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + git notes copy --for-rewrite=foo && + git log -1 > output && + test_cmp expect output +' + +test_expect_success 'git notes copy --for-rewrite (ignore)' ' + git config notes.rewriteMode ignore && + echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + git notes copy --for-rewrite=foo && + git log -1 > output && + test_cmp expect output +' + +cat > expect << EOF +commit 37a0d4cba38afef96ba54a3ea567e6dac575700b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:27:13 2005 -0700 + + 15th + +Notes (other): + a fresh note + another fresh note +EOF + +test_expect_success 'git notes copy --for-rewrite (append)' ' + git notes add -f -m"another fresh note" HEAD^ && + git config notes.rewriteMode concatenate && + echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + git notes copy --for-rewrite=foo && + git log -1 > output && + test_cmp expect output +' + +cat > expect << EOF +commit 37a0d4cba38afef96ba54a3ea567e6dac575700b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:27:13 2005 -0700 + + 15th + +Notes (other): + a fresh note + another fresh note + append 1 + append 2 +EOF + +test_expect_success 'git notes copy --for-rewrite (append two to one)' ' + git notes add -f -m"append 1" HEAD^ && + git notes add -f -m"append 2" HEAD^^ && + (echo $(git rev-parse HEAD^) $(git rev-parse HEAD); + echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) | + git notes copy --for-rewrite=foo && + git log -1 > output && + test_cmp expect output +' + +test_expect_success 'git notes copy --for-rewrite (append empty)' ' + git notes remove HEAD^ && + echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + git notes copy --for-rewrite=foo && + git log -1 > output && + test_cmp expect output +' + +cat > expect << EOF +commit 37a0d4cba38afef96ba54a3ea567e6dac575700b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:27:13 2005 -0700 + + 15th + +Notes (other): + replacement note 1 +EOF + +test_expect_success 'GIT_NOTES_REWRITE_MODE works' ' + git notes add -f -m"replacement note 1" HEAD^ && + echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo && + git log -1 > output && + test_cmp expect output +' + +cat > expect << EOF +commit 37a0d4cba38afef96ba54a3ea567e6dac575700b +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:27:13 2005 -0700 + + 15th + +Notes (other): + replacement note 2 +EOF + +test_expect_success 'GIT_NOTES_REWRITE_REF works' ' + git config notes.rewriteMode overwrite && + git notes add -f -m"replacement note 2" HEAD^ && + git config --unset-all notes.rewriteRef && + echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \ + git notes copy --for-rewrite=foo && + git log -1 > output && + test_cmp expect output +' + +test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' ' + git config notes.rewriteRef refs/notes/other && + git notes add -f -m"replacement note 3" HEAD^ && + echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + GIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo && + git log -1 > output && + test_cmp expect output +' + +test_expect_success 'git notes copy diagnoses too many or too few parameters' ' + test_must_fail git notes copy && + test_must_fail git notes copy one two three +' + test_done diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh index ee84fc4884..361a10aeb1 100755 --- a/t/t3302-notes-index-expensive.sh +++ b/t/t3302-notes-index-expensive.sh @@ -8,7 +8,7 @@ test_description='Test commit notes index (expensive!)' . ./test-lib.sh test -z "$GIT_NOTES_TIMING_TESTS" && { - say Skipping timing tests + skip_all="Skipping timing tests" test_done exit } diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh index edc4bc8841..75ec18778e 100755 --- a/t/t3303-notes-subtrees.sh +++ b/t/t3303-notes-subtrees.sh @@ -95,12 +95,12 @@ INPUT_END test_expect_success 'test notes in 2/38-fanout' 'test_sha1_based "s|^..|&/|"' test_expect_success 'verify notes in 2/38-fanout' 'verify_notes' -test_expect_success 'test notes in 4/36-fanout' 'test_sha1_based "s|^....|&/|"' -test_expect_success 'verify notes in 4/36-fanout' 'verify_notes' - test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based "s|^\(..\)\(..\)|\1/\2/|"' test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes' +test_expect_success 'test notes in 2/2/2/34-fanout' 'test_sha1_based "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|"' +test_expect_success 'verify notes in 2/2/2/34-fanout' 'verify_notes' + test_same_notes () { ( start_note_commit && @@ -128,14 +128,17 @@ INPUT_END git fast-import --quiet } -test_expect_success 'test same notes in 4/36-fanout and 2/38-fanout' 'test_same_notes "s|^..|&/|" "s|^....|&/|"' -test_expect_success 'verify same notes in 4/36-fanout and 2/38-fanout' 'verify_notes' +test_expect_success 'test same notes in no fanout and 2/38-fanout' 'test_same_notes "s|^..|&/|" ""' +test_expect_success 'verify same notes in no fanout and 2/38-fanout' 'verify_notes' + +test_expect_success 'test same notes in no fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" ""' +test_expect_success 'verify same notes in no fanout and 2/2/36-fanout' 'verify_notes' test_expect_success 'test same notes in 2/38-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"' test_expect_success 'verify same notes in 2/38-fanout and 2/2/36-fanout' 'verify_notes' -test_expect_success 'test same notes in 4/36-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"' -test_expect_success 'verify same notes in 4/36-fanout and 2/2/36-fanout' 'verify_notes' +test_expect_success 'test same notes in 2/2/2/34-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|"' +test_expect_success 'verify same notes in 2/2/2/34-fanout and 2/2/36-fanout' 'verify_notes' test_concatenated_notes () { ( @@ -176,13 +179,16 @@ verify_concatenated_notes () { test_cmp expect output } -test_expect_success 'test notes in 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" "s|^....|&/|"' -test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-fanout' 'verify_concatenated_notes' +test_expect_success 'test notes in no fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" ""' +test_expect_success 'verify notes in no fanout concatenated with 2/38-fanout' 'verify_concatenated_notes' + +test_expect_success 'test notes in no fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" ""' +test_expect_success 'verify notes in no fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"' test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' -test_expect_success 'test notes in 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"' -test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' +test_expect_success 'test notes in 2/2/36-fanout concatenated with 2/2/2/34-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|" "s|^\(..\)\(..\)|\1/\2/|"' +test_expect_success 'verify notes in 2/2/36-fanout concatenated with 2/2/2/34-fanout' 'verify_concatenated_notes' test_done diff --git a/t/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh index 256687ffb5..1709e8c00b 100755 --- a/t/t3304-notes-mixed.sh +++ b/t/t3304-notes-mixed.sh @@ -131,6 +131,17 @@ data <<EOF another non-note with SHA1-like name EOF +M 644 inline de/adbeefdeadbeefdeadbeefdeadbeefdeadbeef +data <<EOF +This is actually a valid note, albeit to a non-existing object. +It is needed in order to trigger the "mishandling" of the dead/beef non-note. +EOF + +M 644 inline dead/beef +data <<EOF +yet another non-note with SHA1-like name +EOF + INPUT_END git fast-import --quiet <input && git config core.notesRef refs/notes/commits @@ -158,6 +169,9 @@ EXPECT_END cat >expect_nn3 <<EXPECT_END another non-note with SHA1-like name EXPECT_END +cat >expect_nn4 <<EXPECT_END +yet another non-note with SHA1-like name +EXPECT_END test_expect_success "verify contents of non-notes" ' @@ -166,7 +180,27 @@ test_expect_success "verify contents of non-notes" ' git cat-file -p refs/notes/commits:deadbeef > actual_nn2 && test_cmp expect_nn2 actual_nn2 && git cat-file -p refs/notes/commits:de/adbeef > actual_nn3 && - test_cmp expect_nn3 actual_nn3 + test_cmp expect_nn3 actual_nn3 && + git cat-file -p refs/notes/commits:dead/beef > actual_nn4 && + test_cmp expect_nn4 actual_nn4 +' + +test_expect_success "git-notes preserves non-notes" ' + + test_tick && + git notes add -f -m "foo bar" +' + +test_expect_success "verify contents of non-notes after git-notes" ' + + git cat-file -p refs/notes/commits:foobar/non-note.txt > actual_nn1 && + test_cmp expect_nn1 actual_nn1 && + git cat-file -p refs/notes/commits:deadbeef > actual_nn2 && + test_cmp expect_nn2 actual_nn2 && + git cat-file -p refs/notes/commits:de/adbeef > actual_nn3 && + test_cmp expect_nn3 actual_nn3 && + git cat-file -p refs/notes/commits:dead/beef > actual_nn4 && + test_cmp expect_nn4 actual_nn4 ' test_done diff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh new file mode 100755 index 0000000000..b1ea64b213 --- /dev/null +++ b/t/t3305-notes-fanout.sh @@ -0,0 +1,95 @@ +#!/bin/sh + +test_description='Test that adding/removing many notes triggers automatic fanout restructuring' + +. ./test-lib.sh + +test_expect_success 'creating many notes with git-notes' ' + num_notes=300 && + i=0 && + while test $i -lt $num_notes + do + i=$(($i + 1)) && + test_tick && + echo "file for commit #$i" > file && + git add file && + git commit -q -m "commit #$i" && + git notes add -m "note #$i" || return 1 + done +' + +test_expect_success 'many notes created correctly with git-notes' ' + git log | grep "^ " > output && + i=300 && + while test $i -gt 0 + do + echo " commit #$i" && + echo " note #$i" && + i=$(($i - 1)); + done > expect && + test_cmp expect output +' + +test_expect_success 'many notes created with git-notes triggers fanout' ' + # Expect entire notes tree to have a fanout == 1 + git ls-tree -r --name-only refs/notes/commits | + while read path + do + case "$path" in + ??/??????????????????????????????????????) + : true + ;; + *) + echo "Invalid path \"$path\"" && + return 1 + ;; + esac + done +' + +test_expect_success 'deleting most notes with git-notes' ' + num_notes=250 && + i=0 && + git rev-list HEAD | + while read sha1 + do + i=$(($i + 1)) && + if test $i -gt $num_notes + then + break + fi && + test_tick && + git notes remove "$sha1" + done +' + +test_expect_success 'most notes deleted correctly with git-notes' ' + git log HEAD~250 | grep "^ " > output && + i=50 && + while test $i -gt 0 + do + echo " commit #$i" && + echo " note #$i" && + i=$(($i - 1)); + done > expect && + test_cmp expect output +' + +test_expect_success 'deleting most notes triggers fanout consolidation' ' + # Expect entire notes tree to have a fanout == 0 + git ls-tree -r --name-only refs/notes/commits | + while read path + do + case "$path" in + ????????????????????????????????????????) + : true + ;; + *) + echo "Invalid path \"$path\"" && + return 1 + ;; + esac + done +' + +test_done diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh new file mode 100755 index 0000000000..b4554041b4 --- /dev/null +++ b/t/t3306-notes-prune.sh @@ -0,0 +1,138 @@ +#!/bin/sh + +test_description='Test git notes prune' + +. ./test-lib.sh + +test_expect_success 'setup: create a few commits with notes' ' + + : > file1 && + git add file1 && + test_tick && + git commit -m 1st && + git notes add -m "Note #1" && + : > file2 && + git add file2 && + test_tick && + git commit -m 2nd && + git notes add -m "Note #2" && + : > file3 && + git add file3 && + test_tick && + git commit -m 3rd && + git notes add -m "Note #3" +' + +cat > expect <<END_OF_LOG +commit 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:15:13 2005 -0700 + + 3rd + +Notes: + Note #3 + +commit 08341ad9e94faa089d60fd3f523affb25c6da189 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:14:13 2005 -0700 + + 2nd + +Notes: + Note #2 + +commit ab5f302035f2e7aaf04265f08b42034c23256e1f +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:13:13 2005 -0700 + + 1st + +Notes: + Note #1 +END_OF_LOG + +test_expect_success 'verify commits and notes' ' + + git log > actual && + test_cmp expect actual +' + +test_expect_success 'remove some commits' ' + + git reset --hard HEAD~1 && + git reflog expire --expire=now HEAD && + git gc --prune=now +' + +test_expect_success 'verify that commits are gone' ' + + ! git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 && + git cat-file -p ab5f302035f2e7aaf04265f08b42034c23256e1f +' + +test_expect_success 'verify that notes are still present' ' + + git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 && + git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f +' + +test_expect_success 'prune -n does not remove notes' ' + + git notes list > expect && + git notes prune -n && + git notes list > actual && + test_cmp expect actual +' + +cat > expect <<EOF +5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 +EOF + +test_expect_success 'prune -n lists prunable notes' ' + + + git notes prune -n > actual && + test_cmp expect actual +' + + +test_expect_success 'prune notes' ' + + git notes prune +' + +test_expect_success 'verify that notes are gone' ' + + ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 && + git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f +' + +test_expect_success 'remove some commits' ' + + git reset --hard HEAD~1 && + git reflog expire --expire=now HEAD && + git gc --prune=now +' + +cat > expect <<EOF +08341ad9e94faa089d60fd3f523affb25c6da189 +EOF + +test_expect_success 'prune -v notes' ' + + git notes prune -v > actual && + test_cmp expect actual +' + +test_expect_success 'verify that notes are gone' ' + + ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + ! git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 && + git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f +' + +test_done diff --git a/t/t3307-notes-man.sh b/t/t3307-notes-man.sh new file mode 100755 index 0000000000..3269f2eebd --- /dev/null +++ b/t/t3307-notes-man.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +test_description='Examples from the git-notes man page + +Make sure the manual is not full of lies.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit A && + test_commit B && + test_commit C +' + +test_expect_success 'example 1: notes to add an Acked-by line' ' + cat <<-\EOF >expect && + B + + Notes: + Acked-by: A C Ker <acker@example.com> + EOF + git notes add -m "Acked-by: A C Ker <acker@example.com>" B && + git show -s B^{commit} >log && + tail -n 4 log >actual && + test_cmp expect actual +' + +test_expect_success 'example 2: binary notes' ' + cp "$TEST_DIRECTORY"/test4012.png . + git checkout B && + blob=$(git hash-object -w test4012.png) && + git notes --ref=logo add -C "$blob" && + git notes --ref=logo copy B C && + git notes --ref=logo show C >actual && + test_cmp test4012.png actual +' + +test_done diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 4314ad2d66..d98c7b5571 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -10,8 +10,9 @@ among other things. ' . ./test-lib.sh -GIT_AUTHOR_EMAIL=bogus_email_address -export GIT_AUTHOR_EMAIL +GIT_AUTHOR_NAME=author@name +GIT_AUTHOR_EMAIL=bogus@email@address +export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL test_expect_success \ 'prepare repository with topic branches' \ @@ -80,6 +81,10 @@ test_expect_success \ 'the rebase operation should not have destroyed author information' \ '! (git log | grep "Author:" | grep "<>")' +test_expect_success \ + 'the rebase operation should not have destroyed author information (2)' \ + "git log -1 | grep 'Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>'" + test_expect_success 'HEAD was detached during rebase' ' test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1}) ' @@ -126,9 +131,20 @@ test_expect_success 'Show verbose error when HEAD could not be detached' ' test_must_fail git rebase topic 2> output.err > output.out && grep "Untracked working tree file .B. would be overwritten" output.err ' +rm -f B + +test_expect_success 'dump usage when upstream arg is missing' ' + git checkout -b usage topic && + test_must_fail git rebase 2>error1 && + grep "[Uu]sage" error1 && + test_must_fail git rebase --abort 2>error2 && + grep "No rebase in progress" error2 && + test_must_fail git rebase --onto master 2>error3 && + grep "[Uu]sage" error3 && + ! grep "can.t shift" error3 +' test_expect_success 'rebase -q is quiet' ' - rm B && git checkout -b quiet topic && git rebase -q master > output.out 2>&1 && test ! -s output.out @@ -151,4 +167,21 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' ' git diff --exit-code file-with-cr:CR HEAD:CR ' +test_expect_success 'rebase can copy notes' ' + git config notes.rewrite.rebase true && + git config notes.rewriteRef "refs/notes/*" && + test_commit n1 && + test_commit n2 && + test_commit n3 && + git notes add -m"a note" n3 && + git rebase --onto n1 n2 && + test "a note" = "$(git notes show HEAD)" +' + +test_expect_success 'rebase -m can copy notes' ' + git reset --hard n3 && + git rebase -m --onto n1 n2 && + test "a note" = "$(git notes show HEAD)" +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 4e3513709e..9f03ce699e 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -22,12 +22,18 @@ set_fake_editor # | \ # | F - G - H (branch1) # | \ -# \ I (branch2) -# \ -# J - K - L - M (no-conflict-branch) +# |\ I (branch2) +# | \ +# | J - K - L - M (no-conflict-branch) +# \ +# N - O - P (no-ff-branch) # # where A, B, D and G all touch file1, and one, two, three, four all # touch file "conflict". +# +# WARNING: Modifications to the initial repository can change the SHA ID used +# in the expect2 file for the 'stop on conflicting pick' test. + test_expect_success 'setup' ' test_commit A file1 && @@ -50,6 +56,11 @@ test_expect_success 'setup' ' for n in J K L M do test_commit $n file$n + done && + git checkout -b no-ff-branch A && + for n in N O P + do + test_commit $n file$n done ' @@ -113,7 +124,7 @@ cat > expect2 << EOF D ======= G ->>>>>>> 51047de... G +>>>>>>> 5d18e54... G EOF test_expect_success 'stop on conflicting pick' ' @@ -135,6 +146,16 @@ test_expect_success 'abort' ' ! test -d .git/rebase-merge ' +test_expect_success 'abort with error when new base cannot be checked out' ' + git rm --cached file1 && + git commit -m "remove file in base" && + test_must_fail git rebase -i master > output 2>&1 && + grep "Untracked working tree file .file1. would be overwritten" \ + output && + ! test -d .git/rebase-merge && + git reset --hard HEAD^ +' + test_expect_success 'retain authorship' ' echo A > file7 && git add file7 && @@ -170,6 +191,12 @@ test_expect_success '-p handles "no changes" gracefully' ' test $HEAD = $(git rev-parse HEAD) ' +test_expect_failure 'exchange two commits with -p' ' + FAKE_LINES="2 1" git rebase -i -p HEAD~2 && + test H = $(git cat-file commit HEAD^ | sed -ne \$p) && + test G = $(git cat-file commit HEAD | sed -ne \$p) +' + test_expect_success 'preserve merges with -p' ' git checkout -b to-be-preserved master^ && : > unrelated-file && @@ -553,4 +580,78 @@ test_expect_success 'reword' ' git show HEAD~2 | grep "C changed" ' +test_expect_success 'rebase -i can copy notes' ' + git config notes.rewrite.rebase true && + git config notes.rewriteRef "refs/notes/*" && + test_commit n1 && + test_commit n2 && + test_commit n3 && + git notes add -m"a note" n3 && + git rebase --onto n1 n2 && + test "a note" = "$(git notes show HEAD)" +' + +cat >expect <<EOF +an earlier note +a note +EOF + +test_expect_success 'rebase -i can copy notes over a fixup' ' + git reset --hard n3 && + git notes add -m"an earlier note" n2 && + GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 && + git notes show > output && + test_cmp expect output +' + +test_expect_success 'rebase while detaching HEAD' ' + git symbolic-ref HEAD && + grandparent=$(git rev-parse HEAD~2) && + test_tick && + FAKE_LINES="2 1" git rebase -i HEAD~2 HEAD^0 && + test $grandparent = $(git rev-parse HEAD~2) && + test_must_fail git symbolic-ref HEAD +' + +test_tick # Ensure that the rebased commits get a different timestamp. +test_expect_success 'always cherry-pick with --no-ff' ' + git checkout no-ff-branch && + git tag original-no-ff-branch && + git rebase -i --no-ff A && + touch empty && + for p in 0 1 2 + do + test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) && + git diff HEAD~$p original-no-ff-branch~$p > out && + test_cmp empty out + done && + test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) && + git diff HEAD~3 original-no-ff-branch~3 > out && + test_cmp empty out +' + +test_expect_success 'set up commits with funny messages' ' + git checkout -b funny A && + echo >>file1 && + test_tick && + git commit -a -m "end with slash\\" && + echo >>file1 && + test_tick && + git commit -a -m "something (\000) that looks like octal" && + echo >>file1 && + test_tick && + git commit -a -m "something (\n) that looks like a newline" && + echo >>file1 && + test_tick && + git commit -a -m "another commit" +' + +test_expect_success 'rebase-i history with funny messages' ' + git rev-list A..funny >expect && + test_tick && + FAKE_LINES="1 2 3 4" git rebase -i A && + git rev-list A.. >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh new file mode 100755 index 0000000000..3b0d27350e --- /dev/null +++ b/t/t3418-rebase-continue.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='git rebase --continue tests' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-rebase.sh + +set_fake_editor + +test_expect_success 'setup' ' + test_commit "commit-new-file-F1" F1 1 && + test_commit "commit-new-file-F2" F2 2 && + + git checkout -b topic HEAD^ && + test_commit "commit-new-file-F2-on-topic-branch" F2 22 && + + git checkout master +' + +test_expect_success 'interactive rebase --continue works with touched file' ' + rm -fr .git/rebase-* && + git reset --hard && + git checkout master && + + FAKE_LINES="edit 1" git rebase -i HEAD^ && + test-chmtime =-60 F1 && + git rebase --continue +' + +test_expect_success 'non-interactive rebase --continue works with touched file' ' + rm -fr .git/rebase-* && + git reset --hard && + git checkout master && + + test_must_fail git rebase --onto master master topic && + echo "Resolved" >F2 && + git add F2 && + test-chmtime =-60 F1 && + git rebase --continue +' + +test_done diff --git a/t/t3500-cherry.sh b/t/t3500-cherry.sh index dadbbc2a9f..f038f34b7c 100755 --- a/t/t3500-cherry.sh +++ b/t/t3500-cherry.sh @@ -17,17 +17,19 @@ test_expect_success \ 'prepare repository with topic branch, and check cherry finds the 2 patches from there' \ 'echo First > A && git update-index --add A && + test_tick && git commit -m "Add A." && git checkout -b my-topic-branch && echo Second > B && git update-index --add B && + test_tick && git commit -m "Add B." && - sleep 2 && echo AnotherSecond > C && git update-index --add C && + test_tick && git commit -m "Add C." && git checkout -f master && @@ -35,6 +37,7 @@ test_expect_success \ echo Third >> A && git update-index A && + test_tick && git commit -m "Modify A." && expr "$(echo $(git cherry master my-topic-branch) )" : "+ [^ ]* + .*" diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 7f858151d4..bc7aedd048 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -41,13 +41,32 @@ test_expect_success setup ' git tag rename2 ' +test_expect_success 'cherry-pick --nonsense' ' + + pos=$(git rev-parse HEAD) && + git diff --exit-code HEAD && + test_must_fail git cherry-pick --nonsense 2>msg && + git diff --exit-code HEAD "$pos" && + grep '[Uu]sage:' msg +' + +test_expect_success 'revert --nonsense' ' + + pos=$(git rev-parse HEAD) && + git diff --exit-code HEAD && + test_must_fail git revert --nonsense 2>msg && + git diff --exit-code HEAD "$pos" && + grep '[Uu]sage:' msg +' + test_expect_success 'cherry-pick after renaming branch' ' git checkout rename2 && git cherry-pick added && test $(git rev-parse HEAD^) = $(git rev-parse rename2) && test -f opos && - grep "Add extra line at the end" opos + grep "Add extra line at the end" opos && + git reflog -1 | grep cherry-pick ' @@ -57,7 +76,8 @@ test_expect_success 'revert after renaming branch' ' git revert added && test $(git rev-parse HEAD^) = $(git rev-parse rename1) && test -f spoo && - ! grep "Add extra line at the end" spoo + ! grep "Add extra line at the end" spoo && + git reflog -1 | grep revert ' diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh new file mode 100755 index 0000000000..e17ae712b1 --- /dev/null +++ b/t/t3506-cherry-pick-ff.sh @@ -0,0 +1,98 @@ +#!/bin/sh + +test_description='test cherry-picking with --ff option' + +. ./test-lib.sh + +test_expect_success setup ' + echo first > file1 && + git add file1 && + test_tick && + git commit -m "first" && + git tag first && + + git checkout -b other && + echo second >> file1 && + git add file1 && + test_tick && + git commit -m "second" && + git tag second +' + +test_expect_success 'cherry-pick using --ff fast forwards' ' + git checkout master && + git reset --hard first && + test_tick && + git cherry-pick --ff second && + test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify second)" +' + +test_expect_success 'cherry-pick not using --ff does not fast forwards' ' + git checkout master && + git reset --hard first && + test_tick && + git cherry-pick second && + test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify second)" +' + +# +# We setup the following graph: +# +# B---C +# / / +# first---A +# +# (This has been taken from t3502-cherry-pick-merge.sh) +# +test_expect_success 'merge setup' ' + git checkout master && + git reset --hard first && + echo new line >A && + git add A && + test_tick && + git commit -m "add line to A" A && + git tag A && + git checkout -b side first && + echo new line >B && + git add B && + test_tick && + git commit -m "add line to B" B && + git tag B && + git checkout master && + git merge side && + git tag C && + git checkout -b new A +' + +test_expect_success 'cherry-pick a non-merge with --ff and -m should fail' ' + git reset --hard A -- && + test_must_fail git cherry-pick --ff -m 1 B && + git diff --exit-code A -- +' + +test_expect_success 'cherry pick a merge with --ff but without -m should fail' ' + git reset --hard A -- && + test_must_fail git cherry-pick --ff C && + git diff --exit-code A -- +' + +test_expect_success 'cherry pick with --ff a merge (1)' ' + git reset --hard A -- && + git cherry-pick --ff -m 1 C && + git diff --exit-code C && + test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)" +' + +test_expect_success 'cherry pick with --ff a merge (2)' ' + git reset --hard B -- && + git cherry-pick --ff -m 2 C && + git diff --exit-code C && + test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)" +' + +test_expect_success 'cherry pick a merge relative to nonexistent parent with --ff should fail' ' + git reset --hard B -- && + test_must_fail git cherry-pick --ff -m 3 C +' + +test_done diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh new file mode 100755 index 0000000000..e25cf8039a --- /dev/null +++ b/t/t3507-cherry-pick-conflict.sh @@ -0,0 +1,198 @@ +#!/bin/sh + +test_description='test cherry-pick and revert with conflicts + + - + + picked: rewrites foo to c + + base: rewrites foo to b + + initial: writes foo as a, unrelated as unrelated + +' + +. ./test-lib.sh + +test_expect_success setup ' + + echo unrelated >unrelated && + git add unrelated && + test_commit initial foo a && + test_commit base foo b && + test_commit picked foo c && + git config advice.detachedhead false + +' + +test_expect_success 'failed cherry-pick does not advance HEAD' ' + + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + head=$(git rev-parse HEAD) && + test_must_fail git cherry-pick picked && + newhead=$(git rev-parse HEAD) && + + test "$head" = "$newhead" +' + +test_expect_success 'failed cherry-pick produces dirty index' ' + + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git cherry-pick picked && + + test_must_fail git update-index --refresh -q && + test_must_fail git diff-index --exit-code HEAD +' + +test_expect_success 'failed cherry-pick registers participants in index' ' + + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + { + git checkout base -- foo && + git ls-files --stage foo && + git checkout initial -- foo && + git ls-files --stage foo && + git checkout picked -- foo && + git ls-files --stage foo + } > stages && + sed " + 1 s/ 0 / 1 / + 2 s/ 0 / 2 / + 3 s/ 0 / 3 / + " < stages > expected && + git checkout -f initial^0 && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git cherry-pick picked && + git ls-files --stage --unmerged > actual && + + test_cmp expected actual +' + +test_expect_success 'failed cherry-pick describes conflict in work tree' ' + + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + cat <<-EOF > expected && + <<<<<<< HEAD + a + ======= + c + >>>>>>> objid picked + EOF + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git cherry-pick picked && + + sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && + test_cmp expected actual +' + +test_expect_success 'diff3 -m style' ' + + git config merge.conflictstyle diff3 && + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + cat <<-EOF > expected && + <<<<<<< HEAD + a + ||||||| parent of objid picked + b + ======= + c + >>>>>>> objid picked + EOF + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git cherry-pick picked && + + sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && + test_cmp expected actual +' + +test_expect_success 'revert also handles conflicts sanely' ' + + git config --unset merge.conflictstyle && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + cat <<-EOF > expected && + <<<<<<< HEAD + a + ======= + b + >>>>>>> parent of objid picked + EOF + { + git checkout picked -- foo && + git ls-files --stage foo && + git checkout initial -- foo && + git ls-files --stage foo && + git checkout base -- foo && + git ls-files --stage foo + } > stages && + sed " + 1 s/ 0 / 1 / + 2 s/ 0 / 2 / + 3 s/ 0 / 3 / + " < stages > expected-stages && + git checkout -f initial^0 && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + head=$(git rev-parse HEAD) && + test_must_fail git revert picked && + newhead=$(git rev-parse HEAD) && + git ls-files --stage --unmerged > actual-stages && + + test "$head" = "$newhead" && + test_must_fail git update-index --refresh -q && + test_must_fail git diff-index --exit-code HEAD && + test_cmp expected-stages actual-stages && + sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && + test_cmp expected actual +' + +test_expect_success 'revert conflict, diff3 -m style' ' + git config merge.conflictstyle diff3 && + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + cat <<-EOF > expected && + <<<<<<< HEAD + a + ||||||| objid picked + c + ======= + b + >>>>>>> parent of objid picked + EOF + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git revert picked && + + sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && + test_cmp expected actual +' + +test_done diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh new file mode 100755 index 0000000000..f90ed3da3e --- /dev/null +++ b/t/t3508-cherry-pick-many-commits.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +test_description='test cherry-picking many commits' + +. ./test-lib.sh + +test_expect_success setup ' + echo first > file1 && + git add file1 && + test_tick && + git commit -m "first" && + git tag first && + + git checkout -b other && + for val in second third fourth + do + echo $val >> file1 && + git add file1 && + test_tick && + git commit -m "$val" && + git tag $val + done +' + +test_expect_success 'cherry-pick first..fourth works' ' + git checkout -f master && + git reset --hard first && + test_tick && + git cherry-pick first..fourth && + git diff --quiet other && + git diff --quiet HEAD other && + test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)" +' + +test_expect_success 'cherry-pick --ff first..fourth works' ' + git checkout -f master && + git reset --hard first && + test_tick && + git cherry-pick --ff first..fourth && + git diff --quiet other && + git diff --quiet HEAD other && + test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify fourth)" +' + +test_expect_success 'cherry-pick -n first..fourth works' ' + git checkout -f master && + git reset --hard first && + test_tick && + git cherry-pick -n first..fourth && + git diff --quiet other && + git diff --cached --quiet other && + git diff --quiet HEAD first +' + +test_expect_success 'revert first..fourth works' ' + git checkout -f master && + git reset --hard fourth && + test_tick && + git revert first..fourth && + git diff --quiet first && + git diff --cached --quiet first && + git diff --quiet HEAD first +' + +test_expect_success 'revert ^first fourth works' ' + git checkout -f master && + git reset --hard fourth && + test_tick && + git revert ^first fourth && + git diff --quiet first && + git diff --cached --quiet first && + git diff --quiet HEAD first +' + +test_expect_success 'revert fourth fourth~1 fourth~2 works' ' + git checkout -f master && + git reset --hard fourth && + test_tick && + git revert fourth fourth~1 fourth~2 && + git diff --quiet first && + git diff --cached --quiet first && + git diff --quiet HEAD first +' + +test_expect_success 'cherry-pick -3 fourth works' ' + git checkout -f master && + git reset --hard first && + test_tick && + git cherry-pick -3 fourth && + git diff --quiet other && + git diff --quiet HEAD other && + test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)" +' + +test_expect_success 'cherry-pick --stdin works' ' + git checkout -f master && + git reset --hard first && + test_tick && + git rev-list --reverse first..fourth | git cherry-pick --stdin && + git diff --quiet other && + git diff --quiet HEAD other && + test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)" +' + +test_done diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 0aaf0ad84b..b514cbb606 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -39,7 +39,7 @@ if test -f test-file then test_set_prereq RO_DIR else - say 'skipping removal failure test (perhaps running as root?)' + skip_all='skipping removal failure test (perhaps running as root?)' fi chmod 775 . rm -f test-file diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 525c9a8fdf..7d7140db38 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -26,7 +26,7 @@ test_expect_success \ chmod 755 xfoo1 && git add xfoo1 && case "`git ls-files --stage xfoo1`" in - 100644" "*xfoo1) echo ok;; + 100644" "*xfoo1) echo pass;; *) echo fail; git ls-files --stage xfoo1; (exit 1);; esac' @@ -35,7 +35,7 @@ test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by sym ln -s foo xfoo1 && git add xfoo1 && case "`git ls-files --stage xfoo1`" in - 120000" "*xfoo1) echo ok;; + 120000" "*xfoo1) echo pass;; *) echo fail; git ls-files --stage xfoo1; (exit 1);; esac ' @@ -47,7 +47,7 @@ test_expect_success \ chmod 755 xfoo2 && git update-index --add xfoo2 && case "`git ls-files --stage xfoo2`" in - 100644" "*xfoo2) echo ok;; + 100644" "*xfoo2) echo pass;; *) echo fail; git ls-files --stage xfoo2; (exit 1);; esac' @@ -56,7 +56,7 @@ test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by sym ln -s foo xfoo2 && git update-index --add xfoo2 && case "`git ls-files --stage xfoo2`" in - 120000" "*xfoo2) echo ok;; + 120000" "*xfoo2) echo pass;; *) echo fail; git ls-files --stage xfoo2; (exit 1);; esac ' @@ -67,7 +67,7 @@ test_expect_success SYMLINKS \ ln -s xfoo2 xfoo3 && git update-index --add xfoo3 && case "`git ls-files --stage xfoo3`" in - 120000" "*xfoo3) echo ok;; + 120000" "*xfoo3) echo pass;; *) echo fail; git ls-files --stage xfoo3; (exit 1);; esac' @@ -172,7 +172,7 @@ test_expect_success 'git add --refresh' ' test -z "`git diff-index HEAD -- foo`" && git read-tree HEAD && case "`git diff-index HEAD -- foo`" in - :100644" "*"M foo") echo ok;; + :100644" "*"M foo") echo pass;; *) echo fail; (exit 1);; esac && git add --refresh -- foo && @@ -260,4 +260,32 @@ test_expect_success '"add non-existent" should fail' ' ! (git ls-files | grep "non-existent") ' +test_expect_success 'git add --dry-run of existing changed file' " + echo new >>track-this && + git add --dry-run track-this >actual 2>&1 && + echo \"add 'track-this'\" | test_cmp - actual +" + +test_expect_success 'git add --dry-run of non-existing file' " + echo ignored-file >>.gitignore && + test_must_fail git add --dry-run track-this ignored-file >actual 2>&1 && + echo \"fatal: pathspec 'ignored-file' did not match any files\" | test_cmp - actual +" + +cat >expect.err <<\EOF +The following paths are ignored by one of your .gitignore files: +ignored-file +Use -f if you really want to add them. +fatal: no files added +EOF +cat >expect.out <<\EOF +add 'track-this' +EOF + +test_expect_success 'git add --dry-run --ignore-missing of non-existing file' ' + test_must_fail git add --dry-run --ignore-missing track-this ignored-file >actual.out 2>actual.err && + test_cmp expect.out actual.out && + test_cmp expect.err actual.err +' + test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index b6eba6a839..7ad8465f8f 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -4,7 +4,7 @@ test_description='add -i basic tests' . ./test-lib.sh if ! test_have_prereq PERL; then - say 'skipping git add -i tests, perl not available' + skip_all='skipping git add -i tests, perl not available' test_done fi @@ -154,7 +154,7 @@ rm -f .gitignore if test "$(git config --bool core.filemode)" = false then - say 'skipping filemode tests (filesystem does not properly support modes)' + say '# skipping filemode tests (filesystem does not properly support modes)' else test_set_prereq FILEMODE fi diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh index 6fb027ba57..8eb47942e2 100755 --- a/t/t3800-mktag.sh +++ b/t/t3800-mktag.sh @@ -22,10 +22,12 @@ check_verify_failure () { ########################################################### # first create a commit, so we have a valid object/type # for the tag. -echo Hello >A -git update-index --add A -git commit -m "Initial commit" -head=$(git rev-parse --verify HEAD) +test_expect_success 'setup' ' + echo Hello >A && + git update-index --add A && + git commit -m "Initial commit" && + head=$(git rev-parse --verify HEAD) +' ############################################################ # 1. length check diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh index 29103f65dc..147e634cd6 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -17,7 +17,7 @@ DQ='"' echo foo 2>/dev/null > "Name and an${HT}HT" test -f "Name and an${HT}HT" || { # since FAT/NTFS does not allow tabs in filenames, skip this test - say 'Your filesystem does not allow tabs in filenames, test skipped.' + skip_all='Your filesystem does not allow tabs in filenames, test skipped.' test_done } diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 476e5ec038..62e208aadd 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -81,7 +81,7 @@ test_expect_success 'drop top stash' ' git stash && git stash drop && git stash list > stashlist2 && - diff stashlist1 stashlist2 && + test_cmp stashlist1 stashlist2 && git stash apply && test 3 = $(cat file) && test 1 = $(git show :file) && @@ -228,4 +228,154 @@ test_expect_success 'stash --invalid-option' ' test bar,bar2 = $(cat file),$(cat file2) ' +test_expect_success 'stash an added file' ' + git reset --hard && + echo new >file3 && + git add file3 && + git stash save "added file" && + ! test -r file3 && + git stash apply && + test new = "$(cat file3)" +' + +test_expect_success 'stash rm then recreate' ' + git reset --hard && + git rm file && + echo bar7 >file && + git stash save "rm then recreate" && + test bar = "$(cat file)" && + git stash apply && + test bar7 = "$(cat file)" +' + +test_expect_success 'stash rm and ignore' ' + git reset --hard && + git rm file && + echo file >.gitignore && + git stash save "rm and ignore" && + test bar = "$(cat file)" && + test file = "$(cat .gitignore)" + git stash apply && + ! test -r file && + test file = "$(cat .gitignore)" +' + +test_expect_success 'stash rm and ignore (stage .gitignore)' ' + git reset --hard && + git rm file && + echo file >.gitignore && + git add .gitignore && + git stash save "rm and ignore (stage .gitignore)" && + test bar = "$(cat file)" && + ! test -r .gitignore + git stash apply && + ! test -r file && + test file = "$(cat .gitignore)" +' + +test_expect_success SYMLINKS 'stash file to symlink' ' + git reset --hard && + rm file && + ln -s file2 file && + git stash save "file to symlink" && + test -f file && + test bar = "$(cat file)" && + git stash apply && + case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac +' + +test_expect_success SYMLINKS 'stash file to symlink (stage rm)' ' + git reset --hard && + git rm file && + ln -s file2 file && + git stash save "file to symlink (stage rm)" && + test -f file && + test bar = "$(cat file)" && + git stash apply && + case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac +' + +test_expect_success SYMLINKS 'stash file to symlink (full stage)' ' + git reset --hard && + rm file && + ln -s file2 file && + git add file && + git stash save "file to symlink (full stage)" && + test -f file && + test bar = "$(cat file)" && + git stash apply && + case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac +' + +# This test creates a commit with a symlink used for the following tests + +test_expect_success SYMLINKS 'stash symlink to file' ' + git reset --hard && + ln -s file filelink && + git add filelink && + git commit -m "Add symlink" && + rm filelink && + cp file filelink && + git stash save "symlink to file" && + test -h filelink && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + git stash apply && + ! test -h filelink && + test bar = "$(cat file)" +' + +test_expect_success SYMLINKS 'stash symlink to file (stage rm)' ' + git reset --hard && + git rm filelink && + cp file filelink && + git stash save "symlink to file (stage rm)" && + test -h filelink && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + git stash apply && + ! test -h filelink && + test bar = "$(cat file)" +' + +test_expect_success SYMLINKS 'stash symlink to file (full stage)' ' + git reset --hard && + rm filelink && + cp file filelink && + git add filelink && + git stash save "symlink to file (full stage)" && + test -h filelink && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + git stash apply && + ! test -h filelink && + test bar = "$(cat file)" +' + +test_expect_failure 'stash directory to file' ' + git reset --hard && + mkdir dir && + echo foo >dir/file && + git add dir/file && + git commit -m "Add file in dir" && + rm -fr dir && + echo bar >dir && + git stash save "directory to file" && + test -d dir && + test foo = "$(cat dir/file)" && + test_must_fail git stash apply && + test bar = "$(cat dir)" && + git reset --soft HEAD^ +' + +test_expect_failure 'stash file to directory' ' + git reset --hard && + rm file && + mkdir file && + echo foo >file/file && + git stash save "file to directory" && + test -f file && + test bar = "$(cat file)" && + git stash apply && + test -f file/file && + test foo = "$(cat file/file)" +' + test_done diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index 18695ce821..73441a5165 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -135,7 +135,7 @@ cmp_diff_files_output () { # filesystem. sed <"$2" >.test-tmp \ -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\) /'$z40'\1 /' && - diff "$1" .test-tmp + test_cmp "$1" .test-tmp } test_expect_success \ diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh index a4da1196a9..1a09e8db40 100755 --- a/t/t4004-diff-rename-symlink.sh +++ b/t/t4004-diff-rename-symlink.sh @@ -14,7 +14,7 @@ by an edit for them. if ! test_have_prereq SYMLINKS then - say 'Symbolic links not supported, skipping tests.' + skip_all='Symbolic links not supported, skipping tests.' test_done fi diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index d7e327cc5b..918a21a2f4 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -11,7 +11,7 @@ test_description='Test diff of symlinks. if ! test_have_prereq SYMLINKS then - say 'Symbolic links not supported, skipping tests.' + skip_all='Symbolic links not supported, skipping tests.' test_done fi @@ -54,7 +54,7 @@ EOF test_expect_success \ 'diff removed symlink' \ - 'rm frotz && + 'mv frotz frotz2 && git diff-index -M -p $tree > current && compare_diff_patch current expected' @@ -64,8 +64,7 @@ EOF test_expect_success \ 'diff identical, but newly created symlink' \ - 'sleep 3 && - ln -s xyzzy frotz && + 'ln -s xyzzy frotz && git diff-index -M -p $tree > current && compare_diff_patch current expected' diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 8e3694ed5b..dae6358516 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -204,6 +204,9 @@ log --root --patch-with-stat --summary master log --root -c --patch-with-stat --summary master # improved by Timo's patch log --root --cc --patch-with-stat --summary master +log -p --first-parent master +log -m -p --first-parent master +log -m -p master log -SF master log -SF -p master log --decorate --all @@ -235,6 +238,9 @@ show initial show --root initial show side show master +show -c master +show -m master +show --first-parent master show --stat side show --stat --summary side show --patch-with-stat side diff --git a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ index 8dab4bf93e..1f0f9ad44b 100644 --- a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ +++ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ @@ -18,6 +18,9 @@ A U Thor (2): create mode 100644 file1 delete mode 100644 file2 +-- +g-i-t--v-e-r-s-i-o-n + From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001 From: A U Thor <author@example.com> Date: Mon, 26 Jun 2006 00:01:00 +0000 diff --git a/t/t4013/diff.log_-m_-p_--first-parent_master b/t/t4013/diff.log_-m_-p_--first-parent_master new file mode 100644 index 0000000000..7a0073f529 --- /dev/null +++ b/t/t4013/diff.log_-m_-p_--first-parent_master @@ -0,0 +1,100 @@ +$ git log -m -p --first-parent master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index cead32e..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -4,3 +4,5 @@ C + D + E + F ++1 ++2 +diff --git a/file0 b/file0 +index b414108..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -4,3 +4,6 @@ + 4 + 5 + 6 ++A ++B ++C + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.log_-m_-p_master b/t/t4013/diff.log_-m_-p_master new file mode 100644 index 0000000000..9ca62a01ed --- /dev/null +++ b/t/t4013/diff.log_-m_-p_master @@ -0,0 +1,200 @@ +$ git log -m -p master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index cead32e..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -4,3 +4,5 @@ C + D + E + F ++1 ++2 +diff --git a/file0 b/file0 +index b414108..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -4,3 +4,6 @@ + 4 + 5 + 6 ++A ++B ++C + +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index 7289e35..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,4 +1,8 @@ + A + B ++C ++D ++E ++F + 1 + 2 +diff --git a/file0 b/file0 +index f4615da..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -1,6 +1,9 @@ + 1 + 2 + 3 ++4 ++5 ++6 + A + B + C +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 +diff --git a/file3 b/file3 +deleted file mode 100644 +index 7289e35..0000000 +--- a/file3 ++++ /dev/null +@@ -1,4 +0,0 @@ +-A +-B +-1 +-2 + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.log_-p_--first-parent_master b/t/t4013/diff.log_-p_--first-parent_master new file mode 100644 index 0000000000..3fc896d424 --- /dev/null +++ b/t/t4013/diff.log_-p_--first-parent_master @@ -0,0 +1,78 @@ +$ git log -p --first-parent master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.show_--first-parent_master b/t/t4013/diff.show_--first-parent_master new file mode 100644 index 0000000000..3dcbe473a0 --- /dev/null +++ b/t/t4013/diff.show_--first-parent_master @@ -0,0 +1,30 @@ +$ git show --first-parent master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index cead32e..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -4,3 +4,5 @@ C + D + E + F ++1 ++2 +diff --git a/file0 b/file0 +index b414108..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -4,3 +4,6 @@ + 4 + 5 + 6 ++A ++B ++C +$ diff --git a/t/t4013/diff.show_-c_master b/t/t4013/diff.show_-c_master new file mode 100644 index 0000000000..81aba8da96 --- /dev/null +++ b/t/t4013/diff.show_-c_master @@ -0,0 +1,36 @@ +$ git show -c master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --combined dir/sub +index cead32e,7289e35..992913c +--- a/dir/sub ++++ b/dir/sub +@@@ -1,6 -1,4 +1,8 @@@ + A + B + +C + +D + +E + +F ++ 1 ++ 2 +diff --combined file0 +index b414108,f4615da..10a8a9f +--- a/file0 ++++ b/file0 +@@@ -1,6 -1,6 +1,9 @@@ + 1 + 2 + 3 + +4 + +5 + +6 ++ A ++ B ++ C +$ diff --git a/t/t4013/diff.show_-m_master b/t/t4013/diff.show_-m_master new file mode 100644 index 0000000000..4ea2ee453d --- /dev/null +++ b/t/t4013/diff.show_-m_master @@ -0,0 +1,93 @@ +$ git show -m master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index cead32e..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -4,3 +4,5 @@ C + D + E + F ++1 ++2 +diff --git a/file0 b/file0 +index b414108..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -4,3 +4,6 @@ + 4 + 5 + 6 ++A ++B ++C + +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index 7289e35..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,4 +1,8 @@ + A + B ++C ++D ++E ++F + 1 + 2 +diff --git a/file0 b/file0 +index f4615da..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -1,6 +1,9 @@ + 1 + 2 + 3 ++4 ++5 ++6 + A + B + C +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 +diff --git a/file3 b/file3 +deleted file mode 100644 +index 7289e35..0000000 +--- a/file3 ++++ /dev/null +@@ -1,4 +0,0 @@ +-A +-B +-1 +-2 +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 843ef7f88c..f87434b9f8 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -143,6 +143,58 @@ test_expect_success 'configuration headers and command line headers' ' grep "^ *S. E. Cipient <scipient@example.com>\$" patch7 ' +test_expect_success 'command line To: header' ' + + git config --unset-all format.headers && + git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 && + grep "^To: R. E. Cipient <rcipient@example.com>\$" patch8 +' + +test_expect_success 'configuration To: header' ' + + git config format.to "R. E. Cipient <rcipient@example.com>" && + git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 && + grep "^To: R. E. Cipient <rcipient@example.com>\$" patch9 +' + +test_expect_success '--no-to overrides config.to' ' + + git config --replace-all format.to \ + "R. E. Cipient <rcipient@example.com>" && + git format-patch --no-to --stdout master..side | + sed -e "/^\$/q" >patch10 && + ! grep "^To: R. E. Cipient <rcipient@example.com>\$" patch10 +' + +test_expect_success '--no-to and --to replaces config.to' ' + + git config --replace-all format.to \ + "Someone <someone@out.there>" && + git format-patch --no-to --to="Someone Else <else@out.there>" \ + --stdout master..side | + sed -e "/^\$/q" >patch11 && + ! grep "^To: Someone <someone@out.there>\$" patch11 && + grep "^To: Someone Else <else@out.there>\$" patch11 +' + +test_expect_success '--no-cc overrides config.cc' ' + + git config --replace-all format.cc \ + "C. E. Cipient <rcipient@example.com>" && + git format-patch --no-cc --stdout master..side | + sed -e "/^\$/q" >patch12 && + ! grep "^Cc: C. E. Cipient <rcipient@example.com>\$" patch12 +' + +test_expect_success '--no-add-headers overrides config.headers' ' + + git config --replace-all format.headers \ + "Header1: B. E. Cipient <rcipient@example.com>" && + git format-patch --no-add-headers --stdout master..side | + sed -e "/^\$/q" >patch13 && + ! grep "^Header1: B. E. Cipient <rcipient@example.com>\$" patch13 +' + test_expect_success 'multiple files' ' rm -rf patches/ && @@ -561,4 +613,56 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' ' git format-patch --ignore-if-in-upstream HEAD ' +test_expect_success 'format-patch --signature' ' + git format-patch --stdout --signature="my sig" -1 >output && + grep "my sig" output +' + +test_expect_success 'format-patch with format.signature config' ' + git config format.signature "config sig" && + git format-patch --stdout -1 >output && + grep "config sig" output +' + +test_expect_success 'format-patch --signature overrides format.signature' ' + git config format.signature "config sig" && + git format-patch --stdout --signature="overrides" -1 >output && + ! grep "config sig" output && + grep "overrides" output +' + +test_expect_success 'format-patch --no-signature ignores format.signature' ' + git config format.signature "config sig" && + git format-patch --stdout --signature="my sig" --no-signature \ + -1 >output && + ! grep "config sig" output && + ! grep "my sig" output && + ! grep "^-- \$" output +' + +test_expect_success 'format-patch --signature --cover-letter' ' + git config --unset-all format.signature && + git format-patch --stdout --signature="my sig" --cover-letter \ + -1 >output && + grep "my sig" output && + test 2 = $(grep "my sig" output | wc -l) +' + +test_expect_success 'format.signature="" supresses signatures' ' + git config format.signature "" && + git format-patch --stdout -1 >output && + ! grep "^-- \$" output +' + +test_expect_success 'format-patch --no-signature supresses signatures' ' + git config --unset-all format.signature && + git format-patch --stdout --no-signature -1 >output && + ! grep "^-- \$" output +' + +test_expect_success 'format-patch --signature="" supresses signatures' ' + git format-patch --signature="" -1 >output && + ! grep "^-- \$" output +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 90f3342373..935d101fe8 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -352,6 +352,48 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab: ' +test_expect_success 'check tabs as indentation (tab-in-indent: off)' ' + + git config core.whitespace "-tab-in-indent" && + echo " foo ();" > x && + git diff --check + +' + +test_expect_success 'check tabs as indentation (tab-in-indent: on)' ' + + git config core.whitespace "tab-in-indent" && + echo " foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' ' + + git config core.whitespace "tab-in-indent" && + echo " foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tab-in-indent and indent-with-non-tab conflict' ' + + git config core.whitespace "tab-in-indent,indent-with-non-tab" && + echo "foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tab-in-indent excluded from wildcard whitespace attribute' ' + + git config --unset core.whitespace && + echo "x whitespace" > .gitattributes && + echo " foo ();" > x && + git diff --check && + rm -f .gitattributes + +' + test_expect_success 'line numbers in --check output are correct' ' echo "" > x && @@ -396,6 +438,43 @@ test_expect_success 'whitespace-only changes not reported' ' test_cmp expect actual ' +cat <<EOF >expect +diff --git a/x b/z +similarity index NUM% +rename from x +rename to z +index 380c32a..a97b785 100644 +EOF +test_expect_success 'whitespace-only changes reported across renames' ' + git reset --hard && + for i in 1 2 3 4 5 6 7 8 9; do echo "$i$i$i$i$i$i"; done >x && + git add x && + git commit -m "base" && + sed -e "5s/^/ /" x >z && + git rm x && + git add z && + git diff -w -M --cached | + sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" >actual && + test_cmp expect actual +' + +cat >expected <<\EOF +diff --git a/empty b/void +similarity index 100% +rename from empty +rename to void +EOF + +test_expect_success 'rename empty' ' + git reset --hard && + >empty && + git add empty && + git commit -m empty && + git mv empty void && + git diff -w --cached -M >current && + test_cmp expected current +' + test_expect_success 'combined diff with autocrlf conversion' ' git reset --hard && diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh index 55eb5f83f1..34e5144eed 100755 --- a/t/t4016-diff-quote.sh +++ b/t/t4016-diff-quote.sh @@ -14,7 +14,7 @@ P2='pathname with SP' P3='pathname with LF' : 2>/dev/null >"$P1" && test -f "$P1" && rm -f "$P1" || { - say 'Your filesystem does not allow tabs in filenames, test skipped.' + skip_all='Your filesystem does not allow tabs in filenames, test skipped.' test_done } diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh index 9bdf6596d8..40a95a149e 100755 --- a/t/t4023-diff-rename-typechange.sh +++ b/t/t4023-diff-rename-typechange.sh @@ -6,7 +6,7 @@ test_description='typechange rename detection' if ! test_have_prereq SYMLINKS then - say 'Symbolic links not supported, skipping tests.' + skip_all='Symbolic links not supported, skipping tests.' test_done fi diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 83c1914771..1bd8e5ee3a 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -103,7 +103,15 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' git diff HEAD >actual && sed -e "1,/^@@/d" actual >actual.body && expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body + test_cmp expect.body actual.body && + git diff --ignore-submodules HEAD >actual2 && + ! test -s actual2 && + git diff --ignore-submodules=untracked HEAD >actual3 && + sed -e "1,/^@@/d" actual3 >actual3.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual3.body && + git diff --ignore-submodules=dirty HEAD >actual4 && + ! test -s actual4 ' test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' ' @@ -129,7 +137,13 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' git diff HEAD >actual && sed -e "1,/^@@/d" actual >actual.body && expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body + test_cmp expect.body actual.body && + git diff --ignore-submodules=all HEAD >actual2 && + ! test -s actual2 && + git diff --ignore-submodules=untracked HEAD >actual3 && + ! test -s actual3 && + git diff --ignore-submodules=dirty HEAD >actual4 && + ! test -s actual4 ' test_expect_success 'git diff (empty submodule dir)' ' diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 2e2e103b31..6f7548c3a1 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -55,6 +55,93 @@ test_expect_success 'word diff with runs of whitespace' ' ' +test_expect_success '--word-diff=color' ' + + word_diff --word-diff=color + +' + +test_expect_success '--color --word-diff=color' ' + + word_diff --color --word-diff=color + +' + +sed 's/#.*$//' > expect <<EOF +diff --git a/pre b/post +index 330b04f..5ed8eff 100644 +--- a/pre ++++ b/post +@@ -1,3 +1,7 @@ +-h(4) ++h(4),hh[44] +~ + # significant space +~ + a = b + c +~ +~ ++aa = a +~ +~ ++aeff = aeff * ( aaa ) +~ +EOF + +test_expect_success '--word-diff=porcelain' ' + + word_diff --word-diff=porcelain + +' + +cat > expect <<EOF +diff --git a/pre b/post +index 330b04f..5ed8eff 100644 +--- a/pre ++++ b/post +@@ -1,3 +1,7 @@ +[-h(4)-]{+h(4),hh[44]+} + +a = b + c + +{+aa = a+} + +{+aeff = aeff * ( aaa )+} +EOF + +test_expect_success '--word-diff=plain' ' + + word_diff --word-diff=plain + +' + +test_expect_success '--word-diff=plain --no-color' ' + + word_diff --word-diff=plain --no-color + +' + +cat > expect <<EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<CYAN>@@ -1,3 +1,7 @@<RESET> +<RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET> + +a = b + c<RESET> + +<GREEN>{+aa = a+}<RESET> + +<GREEN>{+aeff = aeff * ( aaa )+}<RESET> +EOF + +test_expect_success '--word-diff=plain --color' ' + + word_diff --word-diff=plain --color + +' + cat > expect <<\EOF <WHITE>diff --git a/pre b/post<RESET> <WHITE>index 330b04f..5ed8eff 100644<RESET> @@ -143,6 +230,25 @@ test_expect_success 'command-line overrides config' ' word_diff --color-words="[a-z]+" ' +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<CYAN>@@ -1,3 +1,7 @@<RESET> +h(4),<GREEN>{+hh+}<RESET>[44] + +a = b + c<RESET> + +<GREEN>{+aa = a+}<RESET> + +<GREEN>{+aeff = aeff * ( aaa+}<RESET> ) +EOF + +test_expect_success 'command-line overrides config: --word-diff-regex' ' + word_diff --color --word-diff-regex="[a-z]+" +' + cp expect.non-whitespace-is-word expect test_expect_success '.gitattributes override config' ' @@ -209,4 +315,20 @@ test_expect_success 'test when words are only removed at the end' ' ' +cat > expect <<\EOF +diff --git a/pre b/post +index 289cb9d..2d06f37 100644 +--- a/pre ++++ b/post +@@ -1 +1 @@ +-(: ++( +EOF + +test_expect_success '--word-diff=none' ' + + word_diff --word-diff=plain --word-diff=none + +' + test_done diff --git a/t/t4041-diff-submodule.sh b/t/t4041-diff-submodule-option.sh index 464305405a..8e391cf9a7 100755 --- a/t/t4041-diff-submodule.sh +++ b/t/t4041-diff-submodule-option.sh @@ -201,23 +201,59 @@ test_expect_success 'submodule contains untracked content' " echo new > sm1/new-file && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head6-dirty: +Submodule sm1 contains untracked content EOF " +test_expect_success 'submodule contains untracked content (untracked ignored)' " + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + ! test -s actual +" + +test_expect_success 'submodule contains untracked content (dirty ignored)' " + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + ! test -s actual +" + +test_expect_success 'submodule contains untracked content (all ignored)' " + git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual && + ! test -s actual +" + test_expect_success 'submodule contains untracked and modifed content' " echo new > sm1/foo6 && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head6-dirty: +Submodule sm1 contains untracked content +Submodule sm1 contains modified content EOF " +test_expect_success 'submodule contains untracked and modifed content (untracked ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 contains modified content +EOF +" + +test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + ! test -s actual +" + +test_expect_success 'submodule contains untracked and modifed content (all ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=log HEAD >actual && + ! test -s actual +" + test_expect_success 'submodule contains modifed content' " rm -f sm1/new-file && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head6-dirty: +Submodule sm1 contains modified content EOF " @@ -235,25 +271,75 @@ test_expect_success 'modified submodule contains untracked content' " echo new > sm1/new-file && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head8-dirty: +Submodule sm1 contains untracked content +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked content (untracked ignored)' " + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked content (dirty ignored)' " + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: > change EOF " +test_expect_success 'modified submodule contains untracked content (all ignored)' " + git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual && + ! test -s actual +" + test_expect_success 'modified submodule contains untracked and modifed content' " echo modification >> sm1/foo6 && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head8-dirty: +Submodule sm1 contains untracked content +Submodule sm1 contains modified content +Submodule sm1 $head6..$head8: > change EOF " +test_expect_success 'modified submodule contains untracked and modifed content (untracked ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 contains modified content +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked and modifed content (dirty ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=log HEAD >actual && + ! test -s actual +" + test_expect_success 'modified submodule contains modifed content' " rm -f sm1/new-file && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head8-dirty: +Submodule sm1 contains modified content +Submodule sm1 $head6..$head8: > change EOF " @@ -324,4 +410,19 @@ index 0000000..$head7 EOF " +test_expect_success 'setup .git file for sm2' ' + (cd sm2 && + REAL="$(pwd)/../.real" && + mv .git "$REAL" + echo "gitdir: $REAL" >.git) +' + +test_expect_success 'diff --submodule with .git file' ' + git diff --submodule HEAD^ >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +' + test_done diff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh new file mode 100755 index 0000000000..91f8198f05 --- /dev/null +++ b/t/t4042-diff-textconv-caching.sh @@ -0,0 +1,109 @@ +#!/bin/sh + +test_description='test textconv caching' +. ./test-lib.sh + +cat >helper <<'EOF' +#!/bin/sh +sed 's/^/converted: /' "$@" >helper.out +cat helper.out +EOF +chmod +x helper + +test_expect_success 'setup' ' + echo foo content 1 >foo.bin && + echo bar content 1 >bar.bin && + git add . && + git commit -m one && + echo foo content 2 >foo.bin && + echo bar content 2 >bar.bin && + git commit -a -m two && + echo "*.bin diff=magic" >.gitattributes && + git config diff.magic.textconv ./helper && + git config diff.magic.cachetextconv true +' + +cat >expect <<EOF +diff --git a/bar.bin b/bar.bin +index fcf9166..28283d5 100644 +--- a/bar.bin ++++ b/bar.bin +@@ -1 +1 @@ +-converted: bar content 1 ++converted: bar content 2 +diff --git a/foo.bin b/foo.bin +index d5b9fe3..1345db2 100644 +--- a/foo.bin ++++ b/foo.bin +@@ -1 +1 @@ +-converted: foo content 1 ++converted: foo content 2 +EOF + +test_expect_success 'first textconv works' ' + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'cached textconv produces same output' ' + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'cached textconv does not run helper' ' + rm -f helper.out && + git diff HEAD^ HEAD >actual && + test_cmp expect actual && + ! test -r helper.out +' + +cat >expect <<EOF +diff --git a/bar.bin b/bar.bin +index fcf9166..28283d5 100644 +--- a/bar.bin ++++ b/bar.bin +@@ -1,2 +1,2 @@ + converted: other +-converted: bar content 1 ++converted: bar content 2 +diff --git a/foo.bin b/foo.bin +index d5b9fe3..1345db2 100644 +--- a/foo.bin ++++ b/foo.bin +@@ -1,2 +1,2 @@ + converted: other +-converted: foo content 1 ++converted: foo content 2 +EOF +test_expect_success 'changing textconv invalidates cache' ' + echo other >other && + git config diff.magic.textconv "./helper other" && + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +cat >expect <<EOF +diff --git a/bar.bin b/bar.bin +index fcf9166..28283d5 100644 +--- a/bar.bin ++++ b/bar.bin +@@ -1,2 +1,2 @@ + converted: other +-converted: bar content 1 ++converted: bar content 2 +diff --git a/foo.bin b/foo.bin +index d5b9fe3..1345db2 100644 +--- a/foo.bin ++++ b/foo.bin +@@ -1 +1 @@ +-converted: foo content 1 ++converted: foo content 2 +EOF +test_expect_success 'switching diff driver produces correct results' ' + git config diff.moremagic.textconv ./helper && + echo foo.bin diff=moremagic >>.gitattributes && + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4043-diff-rename-binary.sh b/t/t4043-diff-rename-binary.sh new file mode 100755 index 0000000000..06012811a1 --- /dev/null +++ b/t/t4043-diff-rename-binary.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Copyright (c) 2010 Jakub Narebski, Christian Couder +# + +test_description='Move a binary file' + +. ./test-lib.sh + + +test_expect_success 'prepare repository' ' + git init && + echo foo > foo && + echo "barQ" | q_to_nul > bar && + git add . && + git commit -m "Initial commit" +' + +test_expect_success 'move the files into a "sub" directory' ' + mkdir sub && + git mv bar foo sub/ && + git commit -m "Moved to sub/" +' + +cat > expected <<\EOF + bar => sub/bar | Bin 5 -> 5 bytes + foo => sub/foo | 0 + 2 files changed, 0 insertions(+), 0 deletions(-) + +diff --git a/bar b/sub/bar +similarity index 100% +rename from bar +rename to sub/bar +diff --git a/foo b/sub/foo +similarity index 100% +rename from foo +rename to sub/foo +EOF + +test_expect_success 'git show -C -C report renames' ' + git show -C -C --raw --binary --stat | tail -n 12 > current && + test_cmp expected current +' + +test_done diff --git a/t/t4044-diff-index-unique-abbrev.sh b/t/t4044-diff-index-unique-abbrev.sh new file mode 100755 index 0000000000..d5ce72be63 --- /dev/null +++ b/t/t4044-diff-index-unique-abbrev.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description='test unique sha1 abbreviation on "index from..to" line' +. ./test-lib.sh + +cat >expect_initial <<EOF +100644 blob 51d2738463ea4ca66f8691c91e33ce64b7d41bb1 foo +EOF + +cat >expect_update <<EOF +100644 blob 51d2738efb4ad8a1e40bed839ab8e116f0a15e47 foo +EOF + +test_expect_success 'setup' ' + echo 4827 > foo && + git add foo && + git commit -m "initial" && + git cat-file -p HEAD: > actual && + test_cmp expect_initial actual && + echo 11742 > foo && + git commit -a -m "update" && + git cat-file -p HEAD: > actual && + test_cmp expect_update actual +' + +cat >expect <<EOF +index 51d27384..51d2738e 100644 +EOF + +test_expect_success 'diff does not produce ambiguous index line' ' + git diff HEAD^..HEAD | grep index > actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh new file mode 100755 index 0000000000..8a3c63b9e2 --- /dev/null +++ b/t/t4045-diff-relative.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='diff --relative tests' +. ./test-lib.sh + +test_expect_success 'setup' ' + git commit --allow-empty -m empty && + echo content >file1 && + mkdir subdir && + echo other content >subdir/file2 && + git add . && + git commit -m one +' + +check_diff() { +expect=$1; shift +cat >expected <<EOF +diff --git a/$expect b/$expect +new file mode 100644 +index 0000000..25c05ef +--- /dev/null ++++ b/$expect +@@ -0,0 +1 @@ ++other content +EOF +test_expect_success "-p $*" " + git diff -p $* HEAD^ >actual && + test_cmp expected actual +" +} + +check_stat() { +expect=$1; shift +cat >expected <<EOF + $expect | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) +EOF +test_expect_success "--stat $*" " + git diff --stat $* HEAD^ >actual && + test_cmp expected actual +" +} + +check_raw() { +expect=$1; shift +cat >expected <<EOF +:000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A $expect +EOF +test_expect_success "--raw $*" " + git diff --no-abbrev --raw $* HEAD^ >actual && + test_cmp expected actual +" +} + +for type in diff stat raw; do + check_$type file2 --relative=subdir/ + check_$type file2 --relative=subdir + check_$type dir/file2 --relative=sub +done + +test_done diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index ad4cc1a757..9692f16f35 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -20,23 +20,25 @@ EOF cat file1 >file2 cat file1 >file4 -git update-index --add --remove file1 file2 file4 -git commit -m 'Initial Version' 2>/dev/null - -git checkout -b binary -perl -pe 'y/x/\000/' <file1 >file3 -cat file3 >file4 -git add file2 -perl -pe 'y/\000/v/' <file3 >file1 -rm -f file2 -git update-index --add --remove file1 file2 file3 file4 -git commit -m 'Second Version' - -git diff-tree -p master binary >B.diff -git diff-tree -p -C master binary >C.diff - -git diff-tree -p --binary master binary >BF.diff -git diff-tree -p --binary -C master binary >CF.diff +test_expect_success 'setup' " + git update-index --add --remove file1 file2 file4 && + git commit -m 'Initial Version' 2>/dev/null && + + git checkout -b binary && + perl -pe 'y/x/\000/' <file1 >file3 && + cat file3 >file4 && + git add file2 && + perl -pe 'y/\000/v/' <file3 >file1 && + rm -f file2 && + git update-index --add --remove file1 file2 file3 file4 && + git commit -m 'Second Version' && + + git diff-tree -p master binary >B.diff && + git diff-tree -p -C master binary >C.diff && + + git diff-tree -p --binary master binary >BF.diff && + git diff-tree -p --binary -C master binary >CF.diff +" test_expect_success 'stat binary diff -- should not fail.' \ 'git checkout master diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh index 99ec13dd53..164d58c222 100755 --- a/t/t4114-apply-typechange.sh +++ b/t/t4114-apply-typechange.sh @@ -11,7 +11,7 @@ test_description='git apply should not get confused with type changes. if ! test_have_prereq SYMLINKS then - say 'Symbolic links not supported, skipping tests.' + skip_all='Symbolic links not supported, skipping tests.' test_done fi diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh index b852e58980..aff4348034 100755 --- a/t/t4115-apply-symlink.sh +++ b/t/t4115-apply-symlink.sh @@ -11,7 +11,7 @@ test_description='git apply symlinks and partial files if ! test_have_prereq SYMLINKS then - say 'Symbolic links not supported, skipping tests.' + skip_all='Symbolic links not supported, skipping tests.' test_done fi diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh index 0d3c1d5dd5..923fcab7f9 100755 --- a/t/t4122-apply-symlink-inside.sh +++ b/t/t4122-apply-symlink-inside.sh @@ -5,7 +5,7 @@ test_description='apply to deeper directory without getting fooled with symlink' if ! test_have_prereq SYMLINKS then - say 'Symbolic links not supported, skipping tests.' + skip_all='Symbolic links not supported, skipping tests.' test_done fi diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fb9ad247bf..8a676a5dcd 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -11,21 +11,22 @@ prepare_test_file () { # ! trailing-space # @ space-before-tab # # indent-with-non-tab + # % tab-in-indent sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF An_SP in an ordinary line>and a HT. - >A HT. - _>A SP and a HT (@). - _>_A SP, a HT and a SP (@). + >A HT (%). + _>A SP and a HT (@%). + _>_A SP, a HT and a SP (@%). _______Seven SP. ________Eight SP (#). - _______>Seven SP and a HT (@). - ________>Eight SP and a HT (@#). - _______>_Seven SP, a HT and a SP (@). - ________>_Eight SP, a HT and a SP (@#). + _______>Seven SP and a HT (@%). + ________>Eight SP and a HT (@#%). + _______>_Seven SP, a HT and a SP (@%). + ________>_Eight SP, a HT and a SP (@#%). _______________Fifteen SP (#). - _______________>Fifteen SP and a HT (@#). + _______________>Fifteen SP and a HT (@#%). ________________Sixteen SP (#). - ________________>Sixteen SP and a HT (@#). + ________________>Sixteen SP and a HT (@#%). _____a__Five SP, a non WS, two SP. A line with a (!) trailing SP_ A line with a (!) trailing HT> @@ -39,12 +40,11 @@ apply_patch () { } test_fix () { - # fix should not barf apply_patch --whitespace=fix || return 1 # find touched lines - diff file target | sed -n -e "s/^> //p" >fixed + $DIFF file target | sed -n -e "s/^> //p" >fixed # the changed lines are all expeced to change fixed_cnt=$(wc -l <fixed) @@ -85,14 +85,14 @@ test_expect_success setup ' test_expect_success 'whitespace=nowarn, default rule' ' apply_patch --whitespace=nowarn && - diff file target + test_cmp file target ' test_expect_success 'whitespace=warn, default rule' ' apply_patch --whitespace=warn && - diff file target + test_cmp file target ' @@ -108,7 +108,7 @@ test_expect_success 'whitespace=error-all, no rule' ' git config core.whitespace -trailing,-space-before,-indent && apply_patch --whitespace=error-all && - diff file target + test_cmp file target ' @@ -117,7 +117,7 @@ test_expect_success 'whitespace=error-all, no rule (attribute)' ' git config --unset core.whitespace && echo "target -whitespace" >.gitattributes && apply_patch --whitespace=error-all && - diff file target + test_cmp file target ' @@ -130,20 +130,25 @@ do for i in - '' do case "$i" in '') ti='#' ;; *) ti= ;; esac - rule=${t}trailing,${s}space,${i}indent - - rm -f .gitattributes - test_expect_success "rule=$rule" ' - git config core.whitespace "$rule" && - test_fix "$tt$ts$ti" - ' - - test_expect_success "rule=$rule (attributes)" ' - git config --unset core.whitespace && - echo "target whitespace=$rule" >.gitattributes && - test_fix "$tt$ts$ti" - ' - + for h in - '' + do + [ -z "$h$i" ] && continue + case "$h" in '') th='%' ;; *) th= ;; esac + rule=${t}trailing,${s}space,${i}indent,${h}tab + + rm -f .gitattributes + test_expect_success "rule=$rule" ' + git config core.whitespace "$rule" && + test_fix "$tt$ts$ti$th" + ' + + test_expect_success "rule=$rule (attributes)" ' + git config --unset core.whitespace && + echo "target whitespace=$rule" >.gitattributes && + test_fix "$tt$ts$ti$th" + ' + + done done done done @@ -325,6 +330,18 @@ test_expect_success 'two missing blank lines at end with --whitespace=fix' ' test_cmp one expect ' +test_expect_success 'missing blank line at end, insert before end, --whitespace=fix' ' + { echo a; echo; } >one && + git add one && + { echo b; echo a; echo; } >one && + cp one expect && + git diff -- one >patch && + echo a >one && + test_must_fail git apply patch && + git apply --whitespace=fix patch && + test_cmp one expect +' + test_expect_success 'shrink file with tons of missing blanks at end of file' ' { echo a; echo b; echo c; } >one && cp one no-blank-lines && diff --git a/t/t4127-apply-same-fn.sh b/t/t4127-apply-same-fn.sh index 3a8202ea93..77200c0b2d 100755 --- a/t/t4127-apply-same-fn.sh +++ b/t/t4127-apply-same-fn.sh @@ -27,7 +27,7 @@ test_expect_success 'apply same filename with independent changes' ' cp same_fn same_fn2 && git reset --hard && git apply patch0 && - diff same_fn same_fn2 + test_cmp same_fn same_fn2 ' test_expect_success 'apply same filename with overlapping changes' ' @@ -40,7 +40,7 @@ test_expect_success 'apply same filename with overlapping changes' ' cp same_fn same_fn2 && git reset --hard && git apply patch0 && - diff same_fn same_fn2 + test_cmp same_fn same_fn2 ' test_expect_success 'apply same new filename after rename' ' @@ -54,7 +54,7 @@ test_expect_success 'apply same new filename after rename' ' cp new_fn new_fn2 && git reset --hard && git apply --index patch1 && - diff new_fn new_fn2 + test_cmp new_fn new_fn2 ' test_expect_success 'apply same old filename after rename -- should fail.' ' diff --git a/t/t4134-apply-submodule.sh b/t/t4134-apply-submodule.sh new file mode 100755 index 0000000000..1b82f93cff --- /dev/null +++ b/t/t4134-apply-submodule.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2010 Peter Collingbourne +# + +test_description='git apply submodule tests' + +. ./test-lib.sh + +test_expect_success setup ' + cat > create-sm.patch <<EOF +diff --git a/dir/sm b/dir/sm +new file mode 160000 +index 0000000..0123456 +--- /dev/null ++++ b/dir/sm +@@ -0,0 +1 @@ ++Subproject commit 0123456789abcdef0123456789abcdef01234567 +EOF + cat > remove-sm.patch <<EOF +diff --git a/dir/sm b/dir/sm +deleted file mode 160000 +index 0123456..0000000 +--- a/dir/sm ++++ /dev/null +@@ -1 +0,0 @@ +-Subproject commit 0123456789abcdef0123456789abcdef01234567 +EOF +' + +test_expect_success 'removing a submodule also removes all leading subdirectories' ' + git apply --index create-sm.patch && + test -d dir/sm && + git apply --index remove-sm.patch && + test \! -d dir +' + +test_done diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh index 2b912d7728..b55c411788 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -47,7 +47,7 @@ do test_must_fail git am$with3 --skip >output && test "$(grep "^Applying" output)" = "Applying: 6" && test_cmp file-2-expect file-2 && - test ! -f .git/rr-cache/MERGE_RR + test ! -f .git/MERGE_RR ' test_expect_success "am --abort goes back after failed am$with3" ' @@ -57,7 +57,7 @@ do test_cmp expect actual && test_cmp file-2-expect file-2 && git diff-index --exit-code --cached HEAD && - test ! -f .git/rr-cache/MERGE_RR + test ! -f .git/MERGE_RR ' done diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index bb402c3780..70856d07ed 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -8,40 +8,42 @@ test_description='git rerere . ./test-lib.sh -cat > a1 << EOF -Some title -========== -Whether 'tis nobler in the mind to suffer -The slings and arrows of outrageous fortune, -Or to take arms against a sea of troubles, -And by opposing end them? To die: to sleep; -No more; and by a sleep to say we end -The heart-ache and the thousand natural shocks -That flesh is heir to, 'tis a consummation -Devoutly to be wish'd. -EOF - -git add a1 -git commit -q -a -m initial - -git checkout -b first -cat >> a1 << EOF -Some title -========== -To die, to sleep; -To sleep: perchance to dream: ay, there's the rub; -For in that sleep of death what dreams may come -When we have shuffled off this mortal coil, -Must give us pause: there's the respect -That makes calamity of so long life; -EOF -git commit -q -a -m first - -git checkout -b second master -git show first:a1 | -sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1 -echo "* END *" >>a1 -git commit -q -a -m second +test_expect_success 'setup' " + cat > a1 <<- EOF && + Some title + ========== + Whether 'tis nobler in the mind to suffer + The slings and arrows of outrageous fortune, + Or to take arms against a sea of troubles, + And by opposing end them? To die: to sleep; + No more; and by a sleep to say we end + The heart-ache and the thousand natural shocks + That flesh is heir to, 'tis a consummation + Devoutly to be wish'd. + EOF + + git add a1 && + git commit -q -a -m initial && + + git checkout -b first && + cat >> a1 <<- EOF && + Some title + ========== + To die, to sleep; + To sleep: perchance to dream: ay, there's the rub; + For in that sleep of death what dreams may come + When we have shuffled off this mortal coil, + Must give us pause: there's the respect + That makes calamity of so long life; + EOF + git commit -q -a -m first && + + git checkout -b second master && + git show first:a1 | + sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1 && + echo '* END *' >>a1 && + git commit -q -a -m second +" test_expect_success 'nothing recorded without rerere' ' (rm -rf .git/rr-cache; git config rerere.enabled false) && diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index a01e55bf6b..cdb70b4b33 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -8,30 +8,93 @@ test_description='git shortlog . ./test-lib.sh -echo 1 > a1 -git add a1 -tree=$(git write-tree) -commit=$( (echo "Test"; echo) | git commit-tree $tree ) -git update-ref HEAD $commit +test_expect_success 'setup' ' + echo 1 >a1 && + git add a1 && + tree=$(git write-tree) && + commit=$(printf "%s\n" "Test" "" | git commit-tree "$tree") && + git update-ref HEAD "$commit" && + + echo 2 >a1 && + git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1 && + + # test if the wrapping is still valid + # when replacing all is by treble clefs. + echo 3 >a1 && + git commit --quiet -m "$( + echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | + sed "s/i/1234/g" | + tr 1234 "\360\235\204\236")" a1 && + + # now fsck up the utf8 + git config i18n.commitencoding non-utf-8 && + echo 4 >a1 && + git commit --quiet -m "$( + echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | + sed "s/i/1234/g" | + tr 1234 "\370\235\204\236")" a1 && + + echo 5 >a1 && + git commit --quiet -m "a 12 34 56 78" a1 + + echo 6 >a1 && + git commit --quiet -m "Commit by someone else" \ + --author="Someone else <not!me>" a1 && + + cat >expect.template <<-\EOF + A U Thor (5): + SUBJECT + SUBJECT + SUBJECT + SUBJECT + SUBJECT + + Someone else (1): + SUBJECT + + EOF +' -echo 2 > a1 -git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1 +fuzz() { + file=$1 && + sed " + s/$_x40/OBJECT_NAME/g + s/$_x05/OBJID/g + s/^ \{6\}[CTa].*/ SUBJECT/g + s/^ \{8\}[^ ].*/ CONTINUATION/g + " <"$file" >"$file.fuzzy" && + sed "/CONTINUATION/ d" <"$file.fuzzy" +} -# test if the wrapping is still valid when replacing all i's by treble clefs. -echo 3 > a1 -git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\360\235\204\236')" a1 +test_expect_success 'default output format' ' + git shortlog HEAD >log && + fuzz log >log.predictable && + test_cmp expect.template log.predictable +' -# now fsck up the utf8 -git config i18n.commitencoding non-utf-8 -echo 4 > a1 -git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\370\235\204\236')" a1 +test_expect_success 'pretty format' ' + sed s/SUBJECT/OBJECT_NAME/ expect.template >expect && + git shortlog --format="%H" HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -echo 5 > a1 -git commit --quiet -m "a 12 34 56 78" a1 +test_expect_success '--abbrev' ' + sed s/SUBJECT/OBJID/ expect.template >expect && + git shortlog --format="%h" --abbrev=5 HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -git shortlog -w HEAD > out +test_expect_success 'output from user-defined format is re-wrapped' ' + sed "s/SUBJECT/two lines/" expect.template >expect && + git shortlog --format="two%nlines" HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -cat > expect << EOF +test_expect_success 'shortlog wrapping' ' + cat >expect <<\EOF && A U Thor (5): Test This is a very, very long first line for the commit message to see if @@ -43,14 +106,19 @@ A U Thor (5): a 12 34 56 78 -EOF - -test_expect_success 'shortlog wrapping' 'test_cmp expect out' +Someone else (1): + Commit by someone else -git log HEAD > log -GIT_DIR=non-existing git shortlog -w < log > out +EOF + git shortlog -w HEAD >out && + test_cmp expect out +' -test_expect_success 'shortlog from non-git directory' 'test_cmp expect out' +test_expect_success 'shortlog from non-git directory' ' + git log HEAD >log && + GIT_DIR=non-existing git shortlog -w <log >out && + test_cmp expect out +' iconvfromutf8toiso88591() { printf "%s" "$*" | iconv -f UTF-8 -t ISO8859-1 diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 1dc224f6fb..2230e606ed 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -387,5 +387,54 @@ test_expect_success 'log --graph with merge' ' test_cmp expect actual ' +test_expect_success 'log.decorate configuration' ' + git config --unset-all log.decorate || : + + git log --oneline >expect.none && + git log --oneline --decorate >expect.short && + git log --oneline --decorate=full >expect.full && + + echo "[log] decorate" >>.git/config && + git log --oneline >actual && + test_cmp expect.short actual && + + git config --unset-all log.decorate && + git config log.decorate true && + git log --oneline >actual && + test_cmp expect.short actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + git log --oneline --decorate=no >actual && + test_cmp expect.none actual && + + git config --unset-all log.decorate && + git config log.decorate no && + git log --oneline >actual && + test_cmp expect.none actual && + git log --oneline --decorate >actual && + test_cmp expect.short actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + + git config --unset-all log.decorate && + git config log.decorate short && + git log --oneline >actual && + test_cmp expect.short actual && + git log --oneline --no-decorate >actual && + test_cmp expect.none actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + + git config --unset-all log.decorate && + git config log.decorate full && + git log --oneline >actual && + test_cmp expect.full actual && + git log --oneline --no-decorate >actual && + test_cmp expect.none actual && + git log --oneline --decorate >actual && + test_cmp expect.short actual + +' + test_done diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index 04f7bae850..68e2652814 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -18,6 +18,11 @@ test_expect_success 'patch-id output is well-formed' ' grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output ' +calc_patch_id () { + git patch-id | + sed "s# .*##" > patch-id_"$1" +} + get_patch_id () { git log -p -1 "$1" | git patch-id | sed "s# .*##" > patch-id_"$1" @@ -35,4 +40,27 @@ test_expect_success 'patch-id detects inequality' ' ! test_cmp patch-id_master patch-id_notsame ' +test_expect_success 'patch-id supports git-format-patch output' ' + get_patch_id master && + git checkout same && + git format-patch -1 --stdout | calc_patch_id same && + test_cmp patch-id_master patch-id_same && + set `git format-patch -1 --stdout | git patch-id` && + test "$2" = `git rev-parse HEAD` +' + +test_expect_success 'whitespace is irrelevant in footer' ' + get_patch_id master && + git checkout same && + git format-patch -1 --stdout | sed "s/ \$//" | calc_patch_id same && + test_cmp patch-id_master patch-id_same +' + +test_expect_success 'patch-id supports git-format-patch MIME output' ' + get_patch_id master && + git checkout same && + git format-patch -1 --attach --stdout | calc_patch_id same && + test_cmp patch-id_master patch-id_same +' + test_done diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh new file mode 100755 index 0000000000..cb9f2bdd29 --- /dev/null +++ b/t/t4205-log-pretty-formats.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# +# Copyright (c) 2010, Will Palmer +# + +test_description='Test pretty formats' +. ./test-lib.sh + +test_expect_success 'set up basic repos' ' + >foo && + >bar && + git add foo && + test_tick && + git commit -m initial && + git add bar && + test_tick && + git commit -m "add bar" +' + +test_expect_success 'alias builtin format' ' + git log --pretty=oneline >expected && + git config pretty.test-alias oneline && + git log --pretty=test-alias >actual && + test_cmp expected actual +' + +test_expect_success 'alias masking builtin format' ' + git log --pretty=oneline >expected && + git config pretty.oneline "%H" && + git log --pretty=oneline >actual && + test_cmp expected actual +' + +test_expect_success 'alias user-defined format' ' + git log --pretty="format:%h" >expected && + git config pretty.test-alias "format:%h" && + git log --pretty=test-alias >actual && + test_cmp expected actual +' + +test_expect_success 'alias user-defined tformat' ' + git log --pretty="tformat:%h" >expected && + git config pretty.test-alias "tformat:%h" && + git log --pretty=test-alias >actual && + test_cmp expected actual +' + +test_expect_success 'alias non-existant format' ' + git config pretty.test-alias format-that-will-never-exist && + test_must_fail git log --pretty=test-alias +' + +test_expect_success 'alias of an alias' ' + git log --pretty="tformat:%h" >expected && + git config pretty.test-foo "tformat:%h" && + git config pretty.test-bar test-foo && + git log --pretty=test-bar >actual && test_cmp expected actual +' + +test_expect_success 'alias masking an alias' ' + git log --pretty=format:"Two %H" >expected && + git config pretty.duplicate "format:One %H" && + git config --add pretty.duplicate "format:Two %H" && + git log --pretty=duplicate >actual && + test_cmp expected actual +' + +test_expect_success 'alias loop' ' + git config pretty.test-foo test-bar && + git config pretty.test-bar test-foo && + test_must_fail git log --pretty=test-foo +' + +test_done diff --git a/t/t4206-log-follow-harder-copies.sh b/t/t4206-log-follow-harder-copies.sh new file mode 100755 index 0000000000..ad29e65fcb --- /dev/null +++ b/t/t4206-log-follow-harder-copies.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# +# Copyright (c) 2010 Bo Yang +# + +test_description='Test --follow should always find copies hard in git log. + +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh + +echo >path0 'Line 1 +Line 2 +Line 3 +' + +test_expect_success \ + 'add a file path0 and commit.' \ + 'git add path0 && + git commit -m "Add path0"' + +echo >path0 'New line 1 +New line 2 +New line 3 +' +test_expect_success \ + 'Change path0.' \ + 'git add path0 && + git commit -m "Change path0"' + +cat <path0 >path1 +test_expect_success \ + 'copy path0 to path1.' \ + 'git add path1 && + git commit -m "Copy path1 from path0"' + +test_expect_success \ + 'find the copy path0 -> path1 harder' \ + 'git log --follow --name-status --pretty="format:%s" path1 > current' + +cat >expected <<\EOF +Copy path1 from path0 +C100 path0 path1 + +Change path0 +M path0 + +Add path0 +A path0 +EOF + +test_expect_success \ + 'validate the output.' \ + 'compare_diff_patch current expected' + +test_done diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh new file mode 100755 index 0000000000..bbde31b019 --- /dev/null +++ b/t/t4207-log-decoration-colors.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# +# Copyright (c) 2010 Nazri Ramliy +# + +test_description='Test for "git log --decorate" colors' + +. ./test-lib.sh + +get_color () +{ + git config --get-color no.such.slot "$1" +} + +test_expect_success setup ' + git config diff.color.commit yellow && + git config color.decorate.branch green && + git config color.decorate.remoteBranch red && + git config color.decorate.tag "reverse bold yellow" && + git config color.decorate.stash magenta && + git config color.decorate.HEAD cyan && + + c_reset=$(get_color reset) && + + c_commit=$(get_color yellow) && + c_branch=$(get_color green) && + c_remoteBranch=$(get_color red) && + c_tag=$(get_color "reverse bold yellow") && + c_stash=$(get_color magenta) && + c_HEAD=$(get_color cyan) && + + test_commit A && + git clone . other && + ( + cd other && + test_commit A1 + ) && + + git remote add -f other ./other && + test_commit B && + git tag v1.0 && + echo >>A.t && + git stash save Changes to A.t +' + +cat >expected <<EOF +${c_commit}COMMIT_ID (${c_HEAD}HEAD${c_reset}${c_commit},\ + ${c_tag}tag: v1.0${c_reset}${c_commit},\ + ${c_tag}tag: B${c_reset}${c_commit},\ + ${c_branch}master${c_reset}${c_commit})${c_reset} B +${c_commit}COMMIT_ID (${c_tag}tag: A1${c_reset}${c_commit},\ + ${c_remoteBranch}other/master${c_reset}${c_commit})${c_reset} A1 +${c_commit}COMMIT_ID (${c_stash}refs/stash${c_reset}${c_commit})${c_reset}\ + On master: Changes to A.t +${c_commit}COMMIT_ID (${c_tag}tag: A${c_reset}${c_commit})${c_reset} A +EOF + +# We want log to show all, but the second parent to refs/stash is irrelevant +# to this test since it does not contain any decoration, hence --first-parent +test_expect_success 'Commit Decorations Colored Correctly' ' + git log --first-parent --abbrev=10 --all --decorate --oneline --color=always | + sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" >out && + test_cmp expected out +' + +test_done diff --git a/t/t4253-am-keep-cr-dos.sh b/t/t4253-am-keep-cr-dos.sh new file mode 100755 index 0000000000..735e55d77c --- /dev/null +++ b/t/t4253-am-keep-cr-dos.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# +# Copyright (c) 2010 Stefan-W. Hahn +# + +test_description='git-am mbox with dos line ending. + +' +. ./test-lib.sh + +# Three patches which will be added as files with dos line ending. + +cat >file1 <<\EOF +line 1 +EOF + +cat >file1a <<\EOF +line 1 +line 4 +EOF + +cat >file2 <<\EOF +line 1 +line 2 +EOF + +cat >file3 <<\EOF +line 1 +line 2 +line 3 +EOF + +test_expect_success 'setup repository with dos files' ' + append_cr <file1 >file && + git add file && + git commit -m Initial && + git tag initial && + append_cr <file2 >file && + git commit -a -m Second && + append_cr <file3 >file && + git commit -a -m Third +' + +test_expect_success 'am with dos files without --keep-cr' ' + git checkout -b dosfiles initial && + git format-patch -k initial..master && + test_must_fail git am -k -3 000*.patch && + git am --abort && + rm -rf .git/rebase-apply 000*.patch +' + +test_expect_success 'am with dos files with --keep-cr' ' + git checkout -b dosfiles-keep-cr initial && + git format-patch -k --stdout initial..master | git am --keep-cr -k -3 && + git diff --exit-code master +' + +test_expect_success 'am with dos files config am.keepcr' ' + git config am.keepcr 1 && + git checkout -b dosfiles-conf-keepcr initial && + git format-patch -k --stdout initial..master | git am -k -3 && + git diff --exit-code master +' + +test_expect_success 'am with dos files config am.keepcr overriden by --no-keep-cr' ' + git config am.keepcr 1 && + git checkout -b dosfiles-conf-keepcr-override initial && + git format-patch -k initial..master && + test_must_fail git am -k -3 --no-keep-cr 000*.patch && + git am --abort && + rm -rf .git/rebase-apply 000*.patch +' + +test_expect_success 'am with dos files with --keep-cr continue' ' + git checkout -b dosfiles-keep-cr-continue initial && + git format-patch -k initial..master && + append_cr <file1a >file && + git commit -m "different patch" file && + test_must_fail git am --keep-cr -k -3 000*.patch && + append_cr <file2 >file && + git add file && + git am -3 --resolved && + git diff --exit-code master +' + +test_expect_success 'am with unix files config am.keepcr overriden by --no-keep-cr' ' + git config am.keepcr 1 && + git checkout -b unixfiles-conf-keepcr-override initial && + cp -f file1 file && + git commit -m "line ending to unix" file && + git format-patch -k initial..master && + git am -k -3 --no-keep-cr 000*.patch && + git diff --exit-code -w master +' + +test_done diff --git a/t/t4300-merge-tree.sh b/t/t4300-merge-tree.sh new file mode 100755 index 0000000000..46c3fe76d3 --- /dev/null +++ b/t/t4300-merge-tree.sh @@ -0,0 +1,257 @@ +#!/bin/sh +# +# Copyright (c) 2010 Will Palmer +# + +test_description='git merge-tree' +. ./test-lib.sh + +test_expect_success setup ' + test_commit "initial" "initial-file" "initial" +' + +test_expect_success 'file add A, !B' ' + cat >expected <<\EXPECTED && +added in remote + their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE +@@ -0,0 +1 @@ ++AAA +EXPECTED + + git reset --hard initial && + test_commit "add-a-not-b" "ONE" "AAA" && + git merge-tree initial initial add-a-not-b >actual && + test_cmp expected actual +' + +test_expect_success 'file add !A, B' ' + cat >expected <<\EXPECTED && +added in local + our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE +EXPECTED + + git reset --hard initial && + test_commit "add-not-a-b" "ONE" "AAA" && + git merge-tree initial add-not-a-b initial >actual && + test_cmp expected actual +' + +test_expect_success 'file add A, B (same)' ' + cat >expected <<\EXPECTED && +added in both + our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE + their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE +EXPECTED + + git reset --hard initial && + test_commit "add-a-b-same-A" "ONE" "AAA" && + git reset --hard initial && + test_commit "add-a-b-same-B" "ONE" "AAA" && + git merge-tree initial add-a-b-same-A add-a-b-same-B >actual && + test_cmp expected actual +' + +test_expect_success 'file add A, B (different)' ' + cat >expected <<\EXPECTED && +added in both + our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE + their 100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE +@@ -1 +1,5 @@ ++<<<<<<< .our + AAA ++======= ++BBB ++>>>>>>> .their +EXPECTED + + git reset --hard initial && + test_commit "add-a-b-diff-A" "ONE" "AAA" && + git reset --hard initial && + test_commit "add-a-b-diff-B" "ONE" "BBB" && + git merge-tree initial add-a-b-diff-A add-a-b-diff-B >actual && + test_cmp expected actual +' + +test_expect_success 'file change A, !B' ' + cat >expected <<\EXPECTED && +EXPECTED + + git reset --hard initial && + test_commit "change-a-not-b" "initial-file" "BBB" && + git merge-tree initial change-a-not-b initial >actual && + test_cmp expected actual +' + +test_expect_success 'file change !A, B' ' + cat >expected <<\EXPECTED && +merged + result 100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file + our 100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file +@@ -1 +1 @@ +-initial ++BBB +EXPECTED + + git reset --hard initial && + test_commit "change-not-a-b" "initial-file" "BBB" && + git merge-tree initial initial change-not-a-b >actual && + test_cmp expected actual +' + +test_expect_success 'file change A, B (same)' ' + cat >expected <<\EXPECTED && +EXPECTED + + git reset --hard initial && + test_commit "change-a-b-same-A" "initial-file" "AAA" && + git reset --hard initial && + test_commit "change-a-b-same-B" "initial-file" "AAA" && + git merge-tree initial change-a-b-same-A change-a-b-same-B >actual && + test_cmp expected actual +' + +test_expect_success 'file change A, B (different)' ' + cat >expected <<\EXPECTED && +changed in both + base 100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file + our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d initial-file + their 100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file +@@ -1 +1,5 @@ ++<<<<<<< .our + AAA ++======= ++BBB ++>>>>>>> .their +EXPECTED + + git reset --hard initial && + test_commit "change-a-b-diff-A" "initial-file" "AAA" && + git reset --hard initial && + test_commit "change-a-b-diff-B" "initial-file" "BBB" && + git merge-tree initial change-a-b-diff-A change-a-b-diff-B >actual && + test_cmp expected actual +' + +test_expect_success 'file change A, B (mixed)' ' + cat >expected <<\EXPECTED && +changed in both + base 100644 f4f1f998c7776568c4ff38f516d77fef9399b5a7 ONE + our 100644 af14c2c3475337c73759d561ef70b59e5c731176 ONE + their 100644 372d761493f524d44d59bd24700c3bdf914c973c ONE +@@ -7,7 +7,11 @@ + AAA + AAA + AAA ++<<<<<<< .our + BBB ++======= ++CCC ++>>>>>>> .their + AAA + AAA + AAA +EXPECTED + + git reset --hard initial && + test_commit "change-a-b-mix-base" "ONE" " +AAA +AAA +AAA +AAA +AAA +AAA +AAA +AAA +AAA +AAA +AAA +AAA +AAA +AAA +AAA" && + test_commit "change-a-b-mix-A" "ONE" \ + "$(sed -e "1{s/AAA/BBB/;}" -e "10{s/AAA/BBB/;}" <ONE)" && + git reset --hard change-a-b-mix-base && + test_commit "change-a-b-mix-B" "ONE" \ + "$(sed -e "1{s/AAA/BBB/;}" -e "10{s/AAA/CCC/;}" <ONE)" && + git merge-tree change-a-b-mix-base change-a-b-mix-A change-a-b-mix-B \ + >actual && + test_cmp expected actual +' + +test_expect_success 'file remove A, !B' ' + cat >expected <<\EXPECTED && +removed in local + base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE + their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE +EXPECTED + + git reset --hard initial && + test_commit "rm-a-not-b-base" "ONE" "AAA" && + git rm ONE && + git commit -m "rm-a-not-b" && + git tag "rm-a-not-b" && + git merge-tree rm-a-not-b-base rm-a-not-b rm-a-not-b-base >actual && + test_cmp expected actual +' + +test_expect_success 'file remove !A, B' ' + cat >expected <<\EXPECTED && +removed in remote + base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE + our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE +@@ -1 +0,0 @@ +-AAA +EXPECTED + + git reset --hard initial && + test_commit "rm-not-a-b-base" "ONE" "AAA" && + git rm ONE && + git commit -m "rm-not-a-b" && + git tag "rm-not-a-b" && + git merge-tree rm-a-not-b-base rm-a-not-b-base rm-a-not-b >actual && + test_cmp expected actual +' + +test_expect_success 'file change A, remove B' ' + cat >expected <<\EXPECTED && +removed in remote + base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE + our 100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE +@@ -1 +0,0 @@ +-BBB +EXPECTED + + git reset --hard initial && + test_commit "change-a-rm-b-base" "ONE" "AAA" && + test_commit "change-a-rm-b-A" "ONE" "BBB" && + git reset --hard change-a-rm-b-base && + git rm ONE && + git commit -m "change-a-rm-b-B" && + git tag "change-a-rm-b-B" && + git merge-tree change-a-rm-b-base change-a-rm-b-A change-a-rm-b-B \ + >actual && + test_cmp expected actual +' + +test_expect_success 'file remove A, change B' ' + cat >expected <<\EXPECTED && +removed in local + base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE + their 100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE +EXPECTED + + git reset --hard initial && + test_commit "rm-a-change-b-base" "ONE" "AAA" && + + git rm ONE && + git commit -m "rm-a-change-b-A" && + git tag "rm-a-change-b-A" && + git reset --hard rm-a-change-b-base && + test_commit "rm-a-change-b-B" "ONE" "BBB" && + git merge-tree rm-a-change-b-base rm-a-change-b-A rm-a-change-b-B \ + >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh new file mode 100755 index 0000000000..9cc0a42ea9 --- /dev/null +++ b/t/t5150-request-pull.sh @@ -0,0 +1,228 @@ +#!/bin/sh + +test_description='Test workflows involving pull request.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + + git init --bare upstream.git && + git init --bare downstream.git && + git clone upstream.git upstream-private && + git clone downstream.git local && + + trash_url="file://$TRASH_DIRECTORY" && + downstream_url="$trash_url/downstream.git/" && + upstream_url="$trash_url/upstream.git/" && + + ( + cd upstream-private && + cat <<-\EOT >mnemonic.txt && + Thirtey days hath November, + Aprile, June, and September: + EOT + git add mnemonic.txt && + test_tick && + git commit -m "\"Thirty days\", a reminder of month lengths" && + git tag -m "version 1" -a initial && + git push --tags origin master + ) && + ( + cd local && + git remote add upstream "$trash_url/upstream.git" && + git fetch upstream && + git pull upstream master && + cat <<-\EOT >>mnemonic.txt && + Of twyecescore-eightt is but eine, + And all the remnante be thrycescore-eine. + O’course Leap yare comes an’pynes, + Ev’rie foure yares, gote it ryghth. + An’twyecescore-eight is but twyecescore-nyne. + EOT + git add mnemonic.txt && + test_tick && + git commit -m "More detail" && + git tag -m "version 2" -a full && + git checkout -b simplify HEAD^ && + mv mnemonic.txt mnemonic.standard && + cat <<-\EOT >mnemonic.clarified && + Thirty days has September, + All the rest I can’t remember. + EOT + git add -N mnemonic.standard mnemonic.clarified && + git commit -a -m "Adapt to use modern, simpler English + +But keep the old version, too, in case some people prefer it." && + git checkout master + ) + +' + +test_expect_success 'setup: two scripts for reading pull requests' ' + + downstream_url_for_sed=$( + printf "%s\n" "$downstream_url" | + sed -e '\''s/\\/\\\\/g'\'' -e '\''s/[[/.*^$]/\\&/g'\'' + ) && + + cat <<-\EOT >read-request.sed && + #!/bin/sed -nf + / in the git repository at:$/!d + n + /^$/ n + s/^[ ]*\(.*\) \([^ ]*\)/please pull\ + \1\ + \2/p + q + EOT + + cat <<-EOT >fuzz.sed + #!/bin/sed -nf + s/$_x40/OBJECT_NAME/g + s/A U Thor/AUTHOR/g + s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g + s/ [^ ].*/ SUBJECT/g + s/ [^ ].* (DATE)/ SUBJECT (DATE)/g + s/$downstream_url_for_sed/URL/g + s/for-upstream/BRANCH/g + s/mnemonic.txt/FILENAME/g + /^ FILENAME | *[0-9]* [-+]*\$/ b diffstat + /^AUTHOR ([0-9]*):\$/ b shortlog + p + b + : diffstat + n + / [0-9]* files changed/ { + a\\ + DIFFSTAT + b + } + b diffstat + : shortlog + /^ [a-zA-Z]/ n + /^[a-zA-Z]* ([0-9]*):\$/ n + /^\$/ N + /^\n[a-zA-Z]* ([0-9]*):\$/!{ + a\\ + SHORTLOG + D + } + n + b shortlog + EOT + +' + +test_expect_success 'pull request when forgot to push' ' + + rm -fr downstream.git && + git init --bare downstream.git && + ( + cd local && + git checkout initial && + git merge --ff-only master && + test_must_fail git request-pull initial "$downstream_url" \ + 2>../err + ) && + grep "No branch of.*is at:\$" err && + grep "Are you sure you pushed" err + +' + +test_expect_success 'pull request after push' ' + + rm -fr downstream.git && + git init --bare downstream.git && + ( + cd local && + git checkout initial && + git merge --ff-only master && + git push origin master:for-upstream && + git request-pull initial origin >../request + ) && + sed -nf read-request.sed <request >digest && + cat digest && + { + read task && + read repository && + read branch + } <digest && + ( + cd upstream-private && + git checkout initial && + git pull --ff-only "$repository" "$branch" + ) && + test "$branch" = for-upstream && + test_cmp local/mnemonic.txt upstream-private/mnemonic.txt + +' + +test_expect_success 'request names an appropriate branch' ' + + rm -fr downstream.git && + git init --bare downstream.git && + ( + cd local && + git checkout initial && + git merge --ff-only master && + git push --tags origin master simplify && + git push origin master:for-upstream && + git request-pull initial "$downstream_url" >../request + ) && + sed -nf read-request.sed <request >digest && + cat digest && + { + read task && + read repository && + read branch + } <digest && + { + test "$branch" = master || + test "$branch" = for-upstream + } + +' + +test_expect_success 'pull request format' ' + + rm -fr downstream.git && + git init --bare downstream.git && + cat <<-\EOT >expect && + The following changes since commit OBJECT_NAME: + + SUBJECT (DATE) + + are available in the git repository at: + URL BRANCH + + SHORTLOG + + DIFFSTAT + EOT + ( + cd local && + git checkout initial && + git merge --ff-only master && + git push origin master:for-upstream && + git request-pull initial "$downstream_url" >../request + ) && + <request sed -nf fuzz.sed >request.fuzzy && + test_cmp expect request.fuzzy + +' + +test_expect_success 'request-pull ignores OPTIONS_KEEPDASHDASH poison' ' + + ( + cd local && + OPTIONS_KEEPDASHDASH=Yes && + export OPTIONS_KEEPDASHDASH && + git checkout initial && + git merge --ff-only master && + git push origin master:for-upstream && + git request-pull -- initial "$downstream_url" >../request + ) + +' + +test_done diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 7649b810b1..bbb9c1251d 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -147,7 +147,7 @@ test_expect_success \ git cat-file $t $object || return 1 done <obj-list } >current && - diff expect current' + test_cmp expect current' test_expect_success \ 'use packed deltified (REF_DELTA) objects' \ @@ -162,7 +162,7 @@ test_expect_success \ git cat-file $t $object || return 1 done <obj-list } >current && - diff expect current' + test_cmp expect current' test_expect_success \ 'use packed deltified (OFS_DELTA) objects' \ @@ -177,7 +177,7 @@ test_expect_success \ git cat-file $t $object || return 1 done <obj-list } >current && - diff expect current' + test_cmp expect current' unset GIT_OBJECT_DIRECTORY diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 4360e77d31..fb3a270822 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -74,7 +74,7 @@ if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) || then test_set_prereq OFF64_T else - say "skipping tests concerning 64-bit offsets" + say "# skipping tests concerning 64-bit offsets" fi test_expect_success OFF64_T \ diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh new file mode 100755 index 0000000000..552da65a61 --- /dev/null +++ b/t/t5407-post-rewrite-hook.sh @@ -0,0 +1,199 @@ +#!/bin/sh +# +# Copyright (c) 2010 Thomas Rast +# + +test_description='Test the post-rewrite hook.' +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit A foo A && + test_commit B foo B && + test_commit C foo C && + test_commit D foo D +' + +mkdir .git/hooks + +cat >.git/hooks/post-rewrite <<EOF +#!/bin/sh +echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args +cat > "$TRASH_DIRECTORY"/post-rewrite.data +EOF +chmod u+x .git/hooks/post-rewrite + +clear_hook_input () { + rm -f post-rewrite.args post-rewrite.data +} + +verify_hook_input () { + test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args && + test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data +} + +test_expect_success 'git commit --amend' ' + clear_hook_input && + echo "D new message" > newmsg && + oldsha=$(git rev-parse HEAD^0) && + git commit -Fnewmsg --amend && + echo amend > expected.args && + echo $oldsha $(git rev-parse HEAD^0) > expected.data && + verify_hook_input +' + +test_expect_success 'git commit --amend --no-post-rewrite' ' + clear_hook_input && + echo "D new message again" > newmsg && + git commit --no-post-rewrite -Fnewmsg --amend && + test ! -f post-rewrite.args && + test ! -f post-rewrite.data +' + +test_expect_success 'git rebase' ' + git reset --hard D && + clear_hook_input && + test_must_fail git rebase --onto A B && + echo C > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse C) $(git rev-parse HEAD^) +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +test_expect_success 'git rebase --skip' ' + git reset --hard D && + clear_hook_input && + test_must_fail git rebase --onto A B && + test_must_fail git rebase --skip && + echo D > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +test_expect_success 'git rebase -m' ' + git reset --hard D && + clear_hook_input && + test_must_fail git rebase -m --onto A B && + echo C > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse C) $(git rev-parse HEAD^) +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +test_expect_success 'git rebase -m --skip' ' + git reset --hard D && + clear_hook_input && + test_must_fail git rebase --onto A B && + test_must_fail git rebase --skip && + echo D > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +. "$TEST_DIRECTORY"/lib-rebase.sh + +set_fake_editor + +# Helper to work around the lack of one-shot exporting for +# test_must_fail (as it is a shell function) +test_fail_interactive_rebase () { + ( + FAKE_LINES="$1" && + shift && + export FAKE_LINES && + test_must_fail git rebase -i "$@" + ) +} + +test_expect_success 'git rebase -i (unchanged)' ' + git reset --hard D && + clear_hook_input && + test_fail_interactive_rebase "1 2" --onto A B && + echo C > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse C) $(git rev-parse HEAD^) +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +test_expect_success 'git rebase -i (skip)' ' + git reset --hard D && + clear_hook_input && + test_fail_interactive_rebase "2" --onto A B && + echo D > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +test_expect_success 'git rebase -i (squash)' ' + git reset --hard D && + clear_hook_input && + test_fail_interactive_rebase "1 squash 2" --onto A B && + echo C > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse C) $(git rev-parse HEAD) +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +test_expect_success 'git rebase -i (fixup without conflict)' ' + git reset --hard D && + clear_hook_input && + FAKE_LINES="1 fixup 2" git rebase -i B && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse C) $(git rev-parse HEAD) +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +test_expect_success 'git rebase -i (double edit)' ' + git reset --hard D && + clear_hook_input && + FAKE_LINES="edit 1 edit 2" git rebase -i B && + git rebase --continue && + echo something > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse C) $(git rev-parse HEAD^) +$(git rev-parse D) $(git rev-parse HEAD) +EOF + verify_hook_input +' + +test_done diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index d5db75d826..bab1a536f5 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -6,7 +6,7 @@ test_description='test automatic tag following' case $(uname -s) in *MINGW*) - say "GIT_DEBUG_SEND_PACK not supported - skipping tests" + skip_all="GIT_DEBUG_SEND_PACK not supported - skipping tests" test_done esac diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 2692050209..4c498b1902 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -110,17 +110,18 @@ test_expect_success 'remove remote' ' test_expect_success 'remove remote protects non-remote branches' ' ( cd test && - (cat >expect1 <<EOF + { cat >expect1 <<EOF Note: A non-remote branch was not removed; to delete it, use: git branch -d master EOF - cat >expect2 <<EOF + } && + { cat >expect2 <<EOF Note: Non-remote branches were not removed; to delete them, use: git branch -d foobranch git branch -d master EOF -) && - git tag footag + } && + git tag footag && git config --add remote.oops.fetch "+refs/*:refs/*" && git remote rm oops 2>actual1 && git branch foobranch && @@ -319,6 +320,69 @@ test_expect_success 'add alt && prune' ' git rev-parse --verify refs/remotes/origin/side2) ' +cat >test/expect <<\EOF +some-tag +EOF + +test_expect_success 'add with reachable tags (default)' ' + (cd one && + >foobar && + git add foobar && + git commit -m "Foobar" && + git tag -a -m "Foobar tag" foobar-tag && + git reset --hard HEAD~1 && + git tag -a -m "Some tag" some-tag) && + (mkdir add-tags && + cd add-tags && + git init && + git remote add -f origin ../one && + git tag -l some-tag >../test/output && + git tag -l foobar-tag >>../test/output && + test_must_fail git config remote.origin.tagopt) && + test_cmp test/expect test/output +' + +cat >test/expect <<\EOF +some-tag +foobar-tag +--tags +EOF + +test_expect_success 'add --tags' ' + (rm -rf add-tags && + mkdir add-tags && + cd add-tags && + git init && + git remote add -f --tags origin ../one && + git tag -l some-tag >../test/output && + git tag -l foobar-tag >>../test/output && + git config remote.origin.tagopt >>../test/output) && + test_cmp test/expect test/output +' + +cat >test/expect <<\EOF +--no-tags +EOF + +test_expect_success 'add --no-tags' ' + (rm -rf add-tags && + mkdir add-no-tags && + cd add-no-tags && + git init && + git remote add -f --no-tags origin ../one && + git tag -l some-tag >../test/output && + git tag -l foobar-tag >../test/output && + git config remote.origin.tagopt >>../test/output) && + (cd one && + git tag -d some-tag foobar-tag) && + test_cmp test/expect test/output +' + +test_expect_success 'reject --no-no-tags' ' + (cd add-no-tags && + test_must_fail git remote add -f --no-no-tags neworigin ../one) +' + cat > one/expect << EOF apis/master apis/side @@ -533,44 +597,123 @@ test_expect_success 'show empty remote' ' ) ' +test_expect_success 'remote set-branches requires a remote' ' + test_must_fail git remote set-branches && + test_must_fail git remote set-branches --add +' + +test_expect_success 'remote set-branches' ' + echo "+refs/heads/*:refs/remotes/scratch/*" >expect.initial && + sort <<-\EOF >expect.add && + +refs/heads/*:refs/remotes/scratch/* + +refs/heads/other:refs/remotes/scratch/other + EOF + sort <<-\EOF >expect.replace && + +refs/heads/maint:refs/remotes/scratch/maint + +refs/heads/master:refs/remotes/scratch/master + +refs/heads/next:refs/remotes/scratch/next + EOF + sort <<-\EOF >expect.add-two && + +refs/heads/maint:refs/remotes/scratch/maint + +refs/heads/master:refs/remotes/scratch/master + +refs/heads/next:refs/remotes/scratch/next + +refs/heads/pu:refs/remotes/scratch/pu + +refs/heads/t/topic:refs/remotes/scratch/t/topic + EOF + sort <<-\EOF >expect.setup-ffonly && + refs/heads/master:refs/remotes/scratch/master + +refs/heads/next:refs/remotes/scratch/next + EOF + sort <<-\EOF >expect.respect-ffonly && + refs/heads/master:refs/remotes/scratch/master + +refs/heads/next:refs/remotes/scratch/next + +refs/heads/pu:refs/remotes/scratch/pu + EOF + + git clone .git/ setbranches && + ( + cd setbranches && + git remote rename origin scratch && + git config --get-all remote.scratch.fetch >config-result && + sort <config-result >../actual.initial && + + git remote set-branches scratch --add other && + git config --get-all remote.scratch.fetch >config-result && + sort <config-result >../actual.add && + + git remote set-branches scratch maint master next && + git config --get-all remote.scratch.fetch >config-result && + sort <config-result >../actual.replace && + + git remote set-branches --add scratch pu t/topic && + git config --get-all remote.scratch.fetch >config-result && + sort <config-result >../actual.add-two && + + git config --unset-all remote.scratch.fetch && + git config remote.scratch.fetch \ + refs/heads/master:refs/remotes/scratch/master && + git config --add remote.scratch.fetch \ + +refs/heads/next:refs/remotes/scratch/next && + git config --get-all remote.scratch.fetch >config-result && + sort <config-result >../actual.setup-ffonly && + + git remote set-branches --add scratch pu && + git config --get-all remote.scratch.fetch >config-result && + sort <config-result >../actual.respect-ffonly + ) && + test_cmp expect.initial actual.initial && + test_cmp expect.add actual.add && + test_cmp expect.replace actual.replace && + test_cmp expect.add-two actual.add-two && + test_cmp expect.setup-ffonly actual.setup-ffonly && + test_cmp expect.respect-ffonly actual.respect-ffonly +' + +test_expect_success 'remote set-branches with --mirror' ' + echo "+refs/*:refs/*" >expect.initial && + echo "+refs/heads/master:refs/heads/master" >expect.replace && + git clone --mirror .git/ setbranches-mirror && + ( + cd setbranches-mirror && + git remote rename origin scratch && + git config --get-all remote.scratch.fetch >../actual.initial && + + git remote set-branches scratch heads/master && + git config --get-all remote.scratch.fetch >../actual.replace + ) && + test_cmp expect.initial actual.initial && + test_cmp expect.replace actual.replace +' + test_expect_success 'new remote' ' -( git remote add someremote foo && echo foo >expect && git config --get-all remote.someremote.url >actual && cmp expect actual -) ' test_expect_success 'remote set-url bar' ' -( git remote set-url someremote bar && echo bar >expect && git config --get-all remote.someremote.url >actual && cmp expect actual -) ' test_expect_success 'remote set-url baz bar' ' -( git remote set-url someremote baz bar && echo baz >expect && git config --get-all remote.someremote.url >actual && cmp expect actual -) ' test_expect_success 'remote set-url zot bar' ' -( test_must_fail git remote set-url someremote zot bar && echo baz >expect && git config --get-all remote.someremote.url >actual && cmp expect actual -) ' test_expect_success 'remote set-url --push zot baz' ' -( test_must_fail git remote set-url --push someremote zot baz && echo "YYY" >expect && echo baz >>expect && @@ -578,11 +721,9 @@ test_expect_success 'remote set-url --push zot baz' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --push zot' ' -( git remote set-url --push someremote zot && echo zot >expect && echo "YYY" >>expect && @@ -591,11 +732,9 @@ test_expect_success 'remote set-url --push zot' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --push qux zot' ' -( git remote set-url --push someremote qux zot && echo qux >expect && echo "YYY" >>expect && @@ -604,11 +743,9 @@ test_expect_success 'remote set-url --push qux zot' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --push foo qu+x' ' -( git remote set-url --push someremote foo qu+x && echo foo >expect && echo "YYY" >>expect && @@ -617,11 +754,9 @@ test_expect_success 'remote set-url --push foo qu+x' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --push --add aaa' ' -( git remote set-url --push --add someremote aaa && echo foo >expect && echo aaa >>expect && @@ -631,11 +766,9 @@ test_expect_success 'remote set-url --push --add aaa' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --push bar aaa' ' -( git remote set-url --push someremote bar aaa && echo foo >expect && echo bar >>expect && @@ -645,11 +778,9 @@ test_expect_success 'remote set-url --push bar aaa' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --push --delete bar' ' -( git remote set-url --push --delete someremote bar && echo foo >expect && echo "YYY" >>expect && @@ -658,11 +789,9 @@ test_expect_success 'remote set-url --push --delete bar' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --push --delete foo' ' -( git remote set-url --push --delete someremote foo && echo "YYY" >expect && echo baz >>expect && @@ -670,11 +799,9 @@ test_expect_success 'remote set-url --push --delete foo' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --add bbb' ' -( git remote set-url --add someremote bbb && echo "YYY" >expect && echo baz >>expect && @@ -683,12 +810,10 @@ test_expect_success 'remote set-url --add bbb' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --delete .*' ' -( - test_must_fail git remote set-url --delete someremote .* && + test_must_fail git remote set-url --delete someremote .\* && echo "YYY" >expect && echo baz >>expect && echo bbb >>expect && @@ -696,11 +821,9 @@ test_expect_success 'remote set-url --delete .*' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --delete bbb' ' -( git remote set-url --delete someremote bbb && echo "YYY" >expect && echo baz >>expect && @@ -708,11 +831,9 @@ test_expect_success 'remote set-url --delete bbb' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --delete baz' ' -( test_must_fail git remote set-url --delete someremote baz && echo "YYY" >expect && echo baz >>expect && @@ -720,11 +841,9 @@ test_expect_success 'remote set-url --delete baz' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --add ccc' ' -( git remote set-url --add someremote ccc && echo "YYY" >expect && echo baz >>expect && @@ -733,11 +852,9 @@ test_expect_success 'remote set-url --add ccc' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_expect_success 'remote set-url --delete baz' ' -( git remote set-url --delete someremote baz && echo "YYY" >expect && echo ccc >>expect && @@ -745,7 +862,6 @@ test_expect_success 'remote set-url --delete baz' ' echo "YYY" >>actual && git config --get-all remote.someremote.url >>actual && cmp expect actual -) ' test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 169af1edde..4eb10f602f 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -71,7 +71,7 @@ test_expect_success "fetch test for-merge" ' echo "$one_in_two " } >expected && cut -f -2 .git/FETCH_HEAD >actual && - diff expected actual' + test_cmp expected actual' test_expect_success 'fetch tags when there is no tags' ' @@ -341,6 +341,13 @@ test_expect_success 'fetch into the current branch with --update-head-ok' ' ' +test_expect_success 'fetch --dry-run' ' + + rm -f .git/FETCH_HEAD && + git fetch --dry-run . && + ! test -f .git/FETCH_HEAD +' + test_expect_success "should be able to fetch with duplicate refspecs" ' mkdir dups && cd dups && diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 1dd8eed5bb..d1912351db 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -49,4 +49,78 @@ test_expect_success 'ls-remote self' ' ' +test_expect_success 'dies when no remote specified and no default remotes found' ' + + test_must_fail git ls-remote + +' + +test_expect_success 'use "origin" when no remote specified' ' + + URL="$(pwd)/.git" && + echo "From $URL" >exp_err && + + git remote add origin "$URL" && + git ls-remote 2>actual_err >actual && + + test_cmp exp_err actual_err && + test_cmp expected.all actual + +' + +test_expect_success 'suppress "From <url>" with -q' ' + + git ls-remote -q 2>actual_err && + test_must_fail test_cmp exp_err actual_err + +' + +test_expect_success 'use branch.<name>.remote if possible' ' + + # + # Test that we are indeed using branch.<name>.remote, not "origin", even + # though the "origin" remote has been set. + # + + # setup a new remote to differentiate from "origin" + git clone . other.git && + ( + cd other.git && + echo "$(git rev-parse HEAD) HEAD" + git show-ref | sed -e "s/ / /" + ) >exp && + + URL="other.git" && + echo "From $URL" >exp_err && + + git remote add other $URL && + git config branch.master.remote other && + + git ls-remote 2>actual_err >actual && + test_cmp exp_err actual_err && + test_cmp exp actual + +' + +cat >exp <<EOF +fatal: 'refs*master' does not appear to be a git repository +fatal: The remote end hung up unexpectedly +EOF +test_expect_success 'confuses pattern as remote when no remote specified' ' + # + # Do not expect "git ls-remote <pattern>" to work; ls-remote, correctly, + # confuses <pattern> for <remote>. Although ugly, this behaviour is akin + # to the confusion of refspecs for remotes by git-fetch and git-push, + # eg: + # + # $ git fetch branch + # + + # We could just as easily have used "master"; the "*" emphasizes its + # role as a pattern. + test_must_fail git ls-remote refs*master >actual 2>&1 && + test_cmp exp actual + +' + test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 0f04b2e894..b11da79c9c 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -64,13 +64,13 @@ check_push_result () { test_expect_success setup ' - : >path1 && + >path1 && git add path1 && test_tick && git commit -a -m repo && the_first_commit=$(git show-ref -s --verify refs/heads/master) && - : >path2 && + >path2 && git add path2 && test_tick && git commit -a -m second && @@ -483,8 +483,10 @@ git config --remove-section remote.there test_expect_success 'push with dry-run' ' mk_test heads/master && - (cd testrepo && - old_commit=$(git show-ref -s --verify refs/heads/master)) && + ( + cd testrepo && + old_commit=$(git show-ref -s --verify refs/heads/master) + ) && git push --dry-run testrepo && check_push_result $old_commit heads/master ' @@ -493,10 +495,13 @@ test_expect_success 'push updates local refs' ' mk_test heads/master && mk_child child && - (cd child && + ( + cd child && git pull .. master && git push && - test $(git rev-parse master) = $(git rev-parse remotes/origin/master)) + test $(git rev-parse master) = \ + $(git rev-parse remotes/origin/master) + ) ' @@ -506,10 +511,13 @@ test_expect_success 'push updates up-to-date local refs' ' mk_child child1 && mk_child child2 && (cd child1 && git pull .. master && git push) && - (cd child2 && + ( + cd child2 && git pull ../child1 master && git push && - test $(git rev-parse master) = $(git rev-parse remotes/origin/master)) + test $(git rev-parse master) = \ + $(git rev-parse remotes/origin/master) + ) ' @@ -517,9 +525,11 @@ test_expect_success 'push preserves up-to-date packed refs' ' mk_test heads/master && mk_child child && - (cd child && + ( + cd child && git push && - ! test -f .git/refs/remotes/origin/master) + ! test -f .git/refs/remotes/origin/master + ) ' @@ -528,13 +538,15 @@ test_expect_success 'push does not update local refs on failure' ' mk_test heads/master && mk_child child && mkdir testrepo/.git/hooks && - echo exit 1 >testrepo/.git/hooks/pre-receive && + echo "#!/no/frobnication/today" >testrepo/.git/hooks/pre-receive && chmod +x testrepo/.git/hooks/pre-receive && - (cd child && + ( + cd child && git pull .. master test_must_fail git push && test $(git rev-parse master) != \ - $(git rev-parse remotes/origin/master)) + $(git rev-parse remotes/origin/master) + ) ' @@ -575,34 +587,41 @@ test_expect_success 'push --delete refuses src:dest refspecs' ' test_expect_success 'warn on push to HEAD of non-bare repository' ' mk_test heads/master - (cd testrepo && + ( + cd testrepo && git checkout master && - git config receive.denyCurrentBranch warn) && + git config receive.denyCurrentBranch warn + ) && git push testrepo master 2>stderr && grep "warning: updating the current branch" stderr ' test_expect_success 'deny push to HEAD of non-bare repository' ' mk_test heads/master - (cd testrepo && + ( + cd testrepo && git checkout master && - git config receive.denyCurrentBranch true) && + git config receive.denyCurrentBranch true + ) && test_must_fail git push testrepo master ' test_expect_success 'allow push to HEAD of bare repository (bare)' ' mk_test heads/master - (cd testrepo && + ( + cd testrepo && git checkout master && git config receive.denyCurrentBranch true && - git config core.bare true) && + git config core.bare true + ) && git push testrepo master 2>stderr && ! grep "warning: updating the current branch" stderr ' test_expect_success 'allow push to HEAD of non-bare repository (config)' ' mk_test heads/master - (cd testrepo && + ( + cd testrepo && git checkout master && git config receive.denyCurrentBranch false ) && @@ -615,7 +634,8 @@ test_expect_success 'fetch with branches' ' git branch second $the_first_commit && git checkout second && echo ".." > testrepo/.git/branches/branch1 && - (cd testrepo && + ( + cd testrepo && git fetch branch1 && r=$(git show-ref -s --verify refs/heads/branch1) && test "z$r" = "z$the_commit" && @@ -627,7 +647,8 @@ test_expect_success 'fetch with branches' ' test_expect_success 'fetch with branches containing #' ' mk_empty && echo "..#second" > testrepo/.git/branches/branch2 && - (cd testrepo && + ( + cd testrepo && git fetch branch2 && r=$(git show-ref -s --verify refs/heads/branch2) && test "z$r" = "z$the_first_commit" && @@ -641,7 +662,8 @@ test_expect_success 'push with branches' ' git checkout second && echo "testrepo" > .git/branches/branch1 && git push branch1 && - (cd testrepo && + ( + cd testrepo && r=$(git show-ref -s --verify refs/heads/master) && test "z$r" = "z$the_first_commit" && test 1 = $(git for-each-ref refs/heads | wc -l) @@ -652,7 +674,8 @@ test_expect_success 'push with branches containing #' ' mk_empty && echo "testrepo#branch3" > .git/branches/branch2 && git push branch2 && - (cd testrepo && + ( + cd testrepo && r=$(git show-ref -s --verify refs/heads/branch3) && test "z$r" = "z$the_first_commit" && test 1 = $(git for-each-ref refs/heads | wc -l) @@ -660,4 +683,103 @@ test_expect_success 'push with branches containing #' ' git checkout master ' +test_expect_success 'push into aliased refs (consistent)' ' + mk_test heads/master && + mk_child child1 && + mk_child child2 && + ( + cd child1 && + git branch foo && + git symbolic-ref refs/heads/bar refs/heads/foo + git config receive.denyCurrentBranch false + ) && + ( + cd child2 && + >path2 && + git add path2 && + test_tick && + git commit -a -m child2 && + git branch foo && + git branch bar && + git push ../child1 foo bar + ) +' + +test_expect_success 'push into aliased refs (inconsistent)' ' + mk_test heads/master && + mk_child child1 && + mk_child child2 && + ( + cd child1 && + git branch foo && + git symbolic-ref refs/heads/bar refs/heads/foo + git config receive.denyCurrentBranch false + ) && + ( + cd child2 && + >path2 && + git add path2 && + test_tick && + git commit -a -m child2 && + git branch foo && + >path3 && + git add path3 && + test_tick && + git commit -a -m child2 && + git branch bar && + test_must_fail git push ../child1 foo bar 2>stderr && + grep "refusing inconsistent update" stderr + ) +' + +test_expect_success 'push --porcelain' ' + mk_empty && + echo >.git/foo "To testrepo" && + echo >>.git/foo "* refs/heads/master:refs/remotes/origin/master [new branch]" && + echo >>.git/foo "Done" && + git push >.git/bar --porcelain testrepo refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) && + test_cmp .git/foo .git/bar +' + +test_expect_success 'push --porcelain bad url' ' + mk_empty && + test_must_fail git push >.git/bar --porcelain asdfasdfasd refs/heads/master:refs/remotes/origin/master && + test_must_fail grep -q Done .git/bar +' + +test_expect_success 'push --porcelain rejected' ' + mk_empty && + git push testrepo refs/heads/master:refs/remotes/origin/master && + (cd testrepo && + git reset --hard origin/master^ + git config receive.denyCurrentBranch true) && + + echo >.git/foo "To testrepo" && + echo >>.git/foo "! refs/heads/master:refs/heads/master [remote rejected] (branch is currently checked out)" && + + test_must_fail git push >.git/bar --porcelain testrepo refs/heads/master:refs/heads/master && + test_cmp .git/foo .git/bar +' + +test_expect_success 'push --porcelain --dry-run rejected' ' + mk_empty && + git push testrepo refs/heads/master:refs/remotes/origin/master && + (cd testrepo && + git reset --hard origin/master + git config receive.denyCurrentBranch true) && + + echo >.git/foo "To testrepo" && + echo >>.git/foo "! refs/heads/master^:refs/heads/master [rejected] (non-fast-forward)" && + echo >>.git/foo "Done" && + + test_must_fail git push >.git/bar --porcelain --dry-run testrepo refs/heads/master^:refs/heads/master && + test_cmp .git/foo .git/bar +' + test_done diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index dd2ee842e0..319e389ed0 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -26,7 +26,7 @@ cd "$D" test_expect_success 'checking the results' ' test -f file && test -f cloned/file && - diff file cloned/file + test_cmp file cloned/file ' test_expect_success 'pulling into void using master:master' ' diff --git a/t/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh index 7206817ca1..298200fa4c 100755 --- a/t/t5522-pull-symlink.sh +++ b/t/t5522-pull-symlink.sh @@ -6,7 +6,7 @@ test_description='pulling from symlinked subdir' if ! test_have_prereq SYMLINKS then - say 'Symbolic links not supported, skipping tests.' + skip_all='Symbolic links not supported, skipping tests.' test_done fi diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh index a696b8791b..6b2a5f4a65 100755 --- a/t/t5530-upload-pack-error.sh +++ b/t/t5530-upload-pack-error.sh @@ -32,9 +32,9 @@ test_expect_success 'fsck fails' ' test_expect_success 'upload-pack fails due to error in pack-objects packing' ' - ! echo "0032want $(git rev-parse HEAD) -00000009done -0000" | git upload-pack . > /dev/null 2> output.err && + printf "0032want %s\n00000009done\n0000" \ + $(git rev-parse HEAD) >input && + test_must_fail git upload-pack . <input >/dev/null 2>output.err && grep "unable to read" output.err && grep "pack-objects died" output.err ' @@ -51,20 +51,29 @@ test_expect_success 'fsck fails' ' ' test_expect_success 'upload-pack fails due to error in rev-list' ' - ! echo "0032want $(git rev-parse HEAD) -0034shallow $(git rev-parse HEAD^)00000009done -0000" | git upload-pack . > /dev/null 2> output.err && + printf "0032want %s\n0034shallow %s00000009done\n0000" \ + $(git rev-parse HEAD) $(git rev-parse HEAD^) >input && + test_must_fail git upload-pack . <input >/dev/null 2>output.err && # pack-objects survived grep "Total.*, reused" output.err && # but there was an error, which must have been in rev-list grep "bad tree object" output.err ' +test_expect_success 'upload-pack error message when bad ref requested' ' + + printf "0045want %s multi_ack_detailed\n00000009done\n0000" \ + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" >input && + test_must_fail git upload-pack . <input >output 2>output.err && + grep -q "not our ref" output.err && + ! grep -q multi_ack_detailed output.err +' + test_expect_success 'upload-pack fails due to error in pack-objects enumeration' ' - ! echo "0032want $(git rev-parse HEAD) -00000009done -0000" | git upload-pack . > /dev/null 2> output.err && + printf "0032want %s\n00000009done\n0000" \ + $(git rev-parse HEAD) >input && + test_must_fail git upload-pack . <input >/dev/null 2>output.err && grep "bad tree object" output.err && grep "pack-objects died" output.err ' diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index bb18f8bfc4..a266ca5636 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -11,7 +11,7 @@ This test runs various sanity checks on http-push.' if git http-push > /dev/null 2>&1 || [ $? -eq 128 ] then - say "skipping test, USE_CURL_MULTI is not defined" + skip_all="skipping test, USE_CURL_MULTI is not defined" test_done fi @@ -137,6 +137,9 @@ 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 + stop_httpd test_done diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 53f54a2789..b0c2a2c3ae 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -7,7 +7,7 @@ test_description='test smart pushing over http via http-backend' . ./test-lib.sh if test -n "$NO_CURL"; then - say 'skipping test, git built without http support' + skip_all='skipping test, git built without http support' test_done fi @@ -34,8 +34,34 @@ test_expect_success 'setup remote repository' ' mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" ' -test_expect_success 'clone remote repository' ' +cat >exp <<EOF +GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 +POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 +EOF +test_expect_success 'no empty path components' ' + # In the URL, add a trailing slash, and see if git appends yet another + # slash. cd "$ROOT_PATH" && + git clone $HTTPD_URL/smart/test_repo.git/ test_repo_clone && + + sed -e " + s/^.* \"// + s/\"// + s/ [1-9][0-9]*\$// + s/^GET /GET / + " >act <"$HTTPD_ROOT_PATH"/access.log && + + # Clear the log, so that it does not affect the "used receive-pack + # service" test which reads the log too. + # + # We do this before the actual comparison to ensure the log is cleared. + echo > "$HTTPD_ROOT_PATH"/access.log && + + test_cmp exp act +' + +test_expect_success 'clone remote repository' ' + rm -rf test_repo_clone && git clone $HTTPD_URL/smart/test_repo.git test_repo_clone ' @@ -68,6 +94,7 @@ test_expect_success 'create and delete remote branch' ' ' cat >exp <<EOF + GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 @@ -88,26 +115,8 @@ test_expect_success 'used receive-pack service' ' test_cmp exp act ' -test_expect_success 'non-fast-forward push fails' ' - cd "$ROOT_PATH"/test_repo_clone && - git checkout master && - echo "changed" > path2 && - git commit -a -m path2 --amend && - - HEAD=$(git rev-parse --verify HEAD) && - !(git push -v origin >output 2>&1) && - (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && - test $HEAD != $(git rev-parse --verify HEAD)) -' - -test_expect_success 'non-fast-forward push show ref status' ' - grep "^ ! \[rejected\][ ]*master -> master (non-fast-forward)$" output -' - -test_expect_success 'non-fast-forward push shows help message' ' - grep "To prevent you from losing history, non-fast-forward updates were rejected" \ - output -' +test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ + "$ROOT_PATH"/test_repo_clone master test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' ' # create a dissimilarly-named remote ref so that git is unable to match the @@ -119,7 +128,7 @@ test_expect_success 'push fails for non-fast-forward refs unmatched by remote he # push master too; this ensures there is at least one '"'push'"' command to # the remote helper and triggers interaction with the helper. - !(git push -v origin +master master:retsam >output 2>&1) && + test_must_fail git push -v origin +master master:retsam >output 2>&1 && grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output && grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output && diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 8cfce969bc..2fb48d09ed 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -4,7 +4,7 @@ test_description='test dumb fetching over http via static file' . ./test-lib.sh if test -n "$NO_CURL"; then - say 'skipping test, git built without http support' + skip_all='skipping test, git built without http support' test_done fi @@ -55,12 +55,43 @@ test_expect_success 'http remote detects correct HEAD' ' test_expect_success 'fetch packed objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && - cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && - git --bare repack && - git --bare prune-packed && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && + git --bare repack && + git --bare prune-packed + ) && git clone $HTTPD_URL/dumb/repo_pack.git ' +test_expect_success 'fetch notices corrupt pack' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && + p=`ls objects/pack/pack-*.pack` && + chmod u+w $p && + printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc + ) && + mkdir repo_bad1.git && + (cd repo_bad1.git && + git --bare init && + test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad1.git && + test 0 = `ls objects/pack/pack-*.pack | wc -l` + ) +' + +test_expect_success 'fetch notices corrupt idx' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && + p=`ls objects/pack/pack-*.idx` && + chmod u+w $p && + printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc + ) && + mkdir repo_bad2.git && + (cd repo_bad2.git && + git --bare init && + test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad2.git && + test 0 = `ls objects/pack | wc -l` + ) +' + test_expect_success 'did not use upload-pack service' ' grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act : >exp diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index 7faa31a299..fd19121372 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -4,7 +4,7 @@ test_description='test smart fetching over http via http-backend' . ./test-lib.sh if test -n "$NO_CURL"; then - say 'skipping test, git built without http support' + skip_all='skipping test, git built without http support' test_done fi diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh index 8c6d0b2f20..b5d7fbc381 100755 --- a/t/t5561-http-backend.sh +++ b/t/t5561-http-backend.sh @@ -4,7 +4,7 @@ test_description='test git-http-backend' . ./test-lib.sh if test -n "$NO_CURL"; then - say 'skipping test, git built without http support' + skip_all='skipping test, git built without http support' test_done fi diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 214756731b..8abb71afcd 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -34,7 +34,7 @@ test_expect_success 'clone with excess parameters (2)' ' test_expect_success 'output from clone' ' rm -fr dst && git clone -n "file://$(pwd)/src" dst >output && - test $(grep Initialized output | wc -l) = 1 + test $(grep Clon output | wc -l) = 1 ' test_expect_success 'clone does not keep pack' ' @@ -176,4 +176,16 @@ test_expect_success 'clone respects global branch.autosetuprebase' ' ) ' +test_expect_success 'respect url-encoding of file://' ' + git init x+y && + test_must_fail git clone "file://$PWD/x+y" xy-url && + git clone "file://$PWD/x%2By" xy-url +' + +test_expect_success 'do not respect url-encoding of non-url path' ' + git init x+y && + test_must_fail git clone x%2By xy-regular && + git clone x+y xy-regular +' + test_done diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index 1c10916069..895f5595ae 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -48,7 +48,7 @@ test_expect_success 'that reference gets used' \ 'cd C && echo "0 objects, 0 kilobytes" > expected && git count-objects > current && -diff expected current' +test_cmp expected current' cd "$base_dir" @@ -75,7 +75,7 @@ cd "$base_dir" test_expect_success 'that reference gets used' \ 'cd D && echo "0 objects, 0 kilobytes" > expected && git count-objects > current && -diff expected current' +test_cmp expected current' cd "$base_dir" @@ -100,7 +100,7 @@ test_expect_success 'that alternate to origin gets used' \ 'cd C && echo "2 objects" > expected && git count-objects | cut -d, -f1 > current && -diff expected current' +test_cmp expected current' cd "$base_dir" @@ -116,7 +116,7 @@ test_expect_success 'check objects expected to exist locally' \ 'cd D && echo "5 objects" > expected && git count-objects | cut -d, -f1 > current && -diff expected current' +test_cmp expected current' cd "$base_dir" diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh index a8f4419e61..ddc3dc52f4 100755 --- a/t/t5704-bundle.sh +++ b/t/t5704-bundle.sh @@ -30,4 +30,20 @@ test_expect_success 'tags can be excluded by rev-list options' ' ' +test_expect_failure 'bundle --stdin' ' + + echo master | git bundle create stdin-bundle.bdl --stdin && + git ls-remote stdin-bundle.bdl >output && + grep master output + +' + +test_expect_failure 'bundle --stdin <rev-list options>' ' + + echo master | git bundle create hybrid-bundle.bdl --stdin tag && + git ls-remote hybrid-bundle.bdl >output && + grep master output + +' + test_done diff --git a/t/t5705-clone-2gb.sh b/t/t5705-clone-2gb.sh index adfaae8c5b..e4d1b6a0fa 100755 --- a/t/t5705-clone-2gb.sh +++ b/t/t5705-clone-2gb.sh @@ -4,7 +4,7 @@ test_description='Test cloning a repository larger than 2 gigabyte' . ./test-lib.sh test -z "$GIT_TEST_CLONE_2GB" && -say "Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t" && +skip_all="Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t" && test_done && exit @@ -12,7 +12,7 @@ test_expect_success 'setup' ' git config pack.compression 0 && git config pack.depth 0 && - blobsize=$((20*1024*1024)) && + blobsize=$((100*1024*1024)) && blobcount=$((2*1024*1024*1024/$blobsize+1)) && i=1 && (while test $i -le $blobcount @@ -36,9 +36,15 @@ test_expect_success 'setup' ' ' -test_expect_success 'clone' ' +test_expect_success 'clone - bare' ' - git clone --bare --no-hardlinks . clone + git clone --bare --no-hardlinks . clone-bare + +' + +test_expect_success 'clone - with worktree, file:// protocol' ' + + git clone file://. clone-wt ' diff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh new file mode 100755 index 0000000000..637d8e97ac --- /dev/null +++ b/t/t5800-remote-helpers.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# +# Copyright (c) 2010 Sverre Rabbelier +# + +test_description='Test remote-helper import and export commands' + +. ./test-lib.sh + +if test_have_prereq PYTHON && "$PYTHON_PATH" -c ' +import sys +if sys.hexversion < 0x02040000: + sys.exit(1) +' +then + : +else + skip_all='skipping git remote-testgit tests: requires Python 2.4 or newer' + test_done +fi + +test_expect_success 'setup repository' ' + git init --bare server/.git && + git clone server public && + (cd public && + echo content >file && + git add file && + git commit -m one && + git push origin master) +' + +test_expect_success 'cloning from local repo' ' + git clone "testgit::${PWD}/server" localclone && + test_cmp public/file localclone/file +' + +test_expect_success 'cloning from remote repo' ' + git clone "testgit::file://${PWD}/server" clone && + test_cmp public/file clone/file +' + +test_expect_success 'create new commit on remote' ' + (cd public && + echo content >>file && + git commit -a -m two && + git push) +' + +test_expect_success 'pulling from local repo' ' + (cd localclone && git pull) && + test_cmp public/file localclone/file +' + +test_expect_success 'pulling from remote remote' ' + (cd clone && git pull) && + test_cmp public/file clone/file +' + +test_expect_success 'pushing to local repo' ' + (cd localclone && + echo content >>file && + git commit -a -m three && + git push) && + HEAD=$(git --git-dir=localclone/.git rev-parse --verify HEAD) && + test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD) +' + +test_expect_success 'synch with changes from localclone' ' + (cd clone && + git pull) +' + +test_expect_success 'pushing remote local repo' ' + (cd clone && + echo content >>file && + git commit -a -m four && + git push) && + HEAD=$(git --git-dir=clone/.git rev-parse --verify HEAD) && + test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD) +' + +test_done diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh index b2131cdacd..fc57e7d3fd 100755 --- a/t/t6001-rev-list-graft.sh +++ b/t/t6001-rev-list-graft.sh @@ -84,7 +84,7 @@ check () { git rev-list --parents --pretty=raw $arg | sed -n -e 's/^commit //p' >test.actual fi - diff test.expect test.actual + test_cmp test.expect test.actual } for type in basic parents parents-raw diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index b4e8fbaa5e..fb07536a0f 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh @@ -5,7 +5,7 @@ test_description='Tests git rev-list --bisect functionality' . ./test-lib.sh -. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions # usage: test_bisection max-diff bisect-option head ^prune... # diff --git a/t/t6003-rev-list-topo-order.sh b/t/t6003-rev-list-topo-order.sh index 2c73f2da7b..e4c52b0214 100755 --- a/t/t6003-rev-list-topo-order.sh +++ b/t/t6003-rev-list-topo-order.sh @@ -6,7 +6,7 @@ test_description='Tests git rev-list --topo-order functionality' . ./test-lib.sh -. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions list_duplicates() { diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index d24ca5c077..cccacd4add 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -101,6 +101,15 @@ commit 131a310eb913d107dd3c09a65d1651175898735d commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 EOF +test_format raw-body %B <<'EOF' +commit 131a310eb913d107dd3c09a65d1651175898735d +changed foo + +commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +added foo + +EOF + test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<'EOF' commit 131a310eb913d107dd3c09a65d1651175898735d [31mfoo[32mbar[34mbaz[mxyzzy @@ -191,6 +200,41 @@ test_expect_success 'add LF before non-empty (2)' ' grep "^$" actual ' +test_expect_success 'add SP before non-empty (1)' ' + git show -s --pretty=format:"%s% bThanks" HEAD^^ >actual && + test $(wc -w <actual) = 2 +' + +test_expect_success 'add SP before non-empty (2)' ' + git show -s --pretty=format:"%s% sThanks" HEAD^^ >actual && + test $(wc -w <actual) = 4 +' + +test_expect_success '--abbrev' ' + echo SHORT SHORT SHORT >expect2 && + echo LONG LONG LONG >expect3 && + git log -1 --format="%h %h %h" HEAD >actual1 && + git log -1 --abbrev=5 --format="%h %h %h" HEAD >actual2 && + git log -1 --abbrev=5 --format="%H %H %H" HEAD >actual3 && + sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 && + sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 && + test_cmp expect2 fuzzy2 && + test_cmp expect3 fuzzy3 && + ! test_cmp actual1 actual2 +' + +test_expect_success '%H is not affected by --abbrev-commit' ' + git log -1 --format=%H --abbrev-commit --abbrev=20 HEAD >actual && + len=$(wc -c <actual) && + test $len = 41 +' + +test_expect_success '%h is not affected by --abbrev-commit' ' + git log -1 --format=%h --abbrev-commit --abbrev=20 HEAD >actual && + len=$(wc -c <actual) && + test $len = 21 +' + test_expect_success '"%h %gD: %gs" is same as git-reflog' ' git reflog >expect && git log -g --format="%h %gD: %gs" >actual && @@ -203,6 +247,12 @@ test_expect_success '"%h %gD: %gs" is same as git-reflog (with date)' ' test_cmp expect actual ' +test_expect_success '"%h %gD: %gs" is same as git-reflog (with --abbrev)' ' + git reflog --abbrev=13 --date=raw >expect && + git log -g --abbrev=13 --format="%h %gD: %gs" --date=raw >actual && + test_cmp expect actual +' + test_expect_success '%gd shortens ref name' ' echo "master@{0}" >expect.gd-short && git log -g -1 --format=%gd refs/heads/master >actual.gd-short && @@ -213,7 +263,7 @@ test_expect_success 'oneline with empty message' ' git commit -m "dummy" --allow-empty && git commit -m "dummy" --allow-empty && git filter-branch --msg-filter "sed -e s/dummy//" HEAD^^.. && - git rev-list --oneline HEAD > /tmp/test.txt && + git rev-list --oneline HEAD >test.txt && test $(git rev-list --oneline HEAD | wc -l) -eq 5 && test $(git rev-list --oneline --graph HEAD | wc -l) -eq 5 ' diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh index 4b8611ce20..b565638e92 100755 --- a/t/t6007-rev-list-cherry-pick-file.sh +++ b/t/t6007-rev-list-cherry-pick-file.sh @@ -32,6 +32,23 @@ test_expect_success setup ' git tag B ' +cat >expect <<EOF +<tags/B +>tags/C +EOF + +test_expect_success '--left-right' ' + git rev-list --left-right B...C > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +test_expect_success '--count' ' + git rev-list --count B...C > actual && + test "$(cat actual)" = 2 +' + test_expect_success '--cherry-pick foo comes up empty' ' test -z "$(git rev-list --left-right --cherry-pick B...C -- foo)" ' @@ -54,4 +71,16 @@ test_expect_success '--cherry-pick with independent, but identical branches' ' HEAD...master -- foo)" ' +cat >expect <<EOF +1 2 +EOF + +# Insert an extra commit to break the symmetry +test_expect_success '--count --left-right' ' + git checkout branch && + test_commit D && + git rev-list --count --left-right B...D > actual && + test_cmp expect actual +' + test_done diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index 8d3fa7d014..58428d9f5c 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -34,7 +34,9 @@ test_expect_success 'setup' ' git checkout master && commit master2 && git tag foo/bar master && - git update-ref refs/remotes/foo/baz master + commit master3 && + git update-ref refs/remotes/foo/baz master && + commit master4 ' test_expect_success 'rev-parse --glob=refs/heads/subspace/*' ' @@ -162,6 +164,13 @@ test_expect_success 'rev-list --branches=subspace' ' compare rev-list "subspace/one subspace/two" "--branches=subspace" ' + +test_expect_success 'rev-list --branches' ' + + compare rev-list "master subspace-x someref other/three subspace/one subspace/two" "--branches" + +' + test_expect_success 'rev-list --glob=heads/someref/* master' ' compare rev-list "master" "--glob=heads/someref/* master" @@ -186,6 +195,12 @@ test_expect_success 'rev-list --tags=foo' ' ' +test_expect_success 'rev-list --tags' ' + + compare rev-list "foo/bar" "--tags" + +' + test_expect_success 'rev-list --remotes=foo' ' compare rev-list "foo/baz" "--remotes=foo" diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh new file mode 100755 index 0000000000..76410293b3 --- /dev/null +++ b/t/t6019-rev-list-ancestry-path.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +test_description='--ancestry-path' + +# D---E-------F +# / \ \ +# B---C---G---H---I---J +# / \ +# A-------K---------------L--M +# +# D..M == E F G H I J K L M +# --ancestry-path D..M == E F H I J L M +# +# D..M -- M.t == M +# --ancestry-path D..M -- M.t == M + +. ./test-lib.sh + +test_merge () { + test_tick && + git merge -s ours -m "$2" "$1" && + git tag "$2" +} + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + test_commit E && + test_commit F && + git reset --hard C && + test_commit G && + test_merge E H && + test_commit I && + test_merge F J && + git reset --hard A && + test_commit K && + test_merge J L && + test_commit M +' + +test_expect_success 'rev-list D..M' ' + for c in E F G H I J K L M; do echo $c; done >expect && + git rev-list --format=%s D..M | + sed -e "/^commit /d" | + sort >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list --ancestry-path D..M' ' + for c in E F H I J L M; do echo $c; done >expect && + git rev-list --ancestry-path --format=%s D..M | + sed -e "/^commit /d" | + sort >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list D..M -- M.t' ' + echo M >expect && + git rev-list --format=%s D..M -- M.t | + sed -e "/^commit /d" >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list --ancestry-patch D..M -- M.t' ' + echo M >expect && + git rev-list --ancestry-path --format=%s D..M -- M.t | + sed -e "/^commit /d" >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index e3f7ae8120..b66544b76d 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -280,7 +280,7 @@ test_expect_success 'updated working tree file should prevent the merge' ' echo "BAD: should have complained" return 1 } - diff M M.saved || { + test_cmp M M.saved || { echo "BAD: should have left M intact" return 1 } @@ -301,7 +301,7 @@ test_expect_success 'updated working tree file should prevent the merge' ' echo "BAD: should have complained" return 1 } - diff M M.saved || { + test_cmp M M.saved || { echo "BAD: should have left M intact" return 1 } diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 6291307cd0..d486d73994 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -64,6 +64,10 @@ cp new1.txt test.txt test_expect_success "merge without conflict" \ "git merge-file test.txt orig.txt new2.txt" +cp new1.txt test.txt +test_expect_success "merge without conflict (--quiet)" \ + "git merge-file --quiet test.txt orig.txt new2.txt" + cp new1.txt test2.txt test_expect_success "merge without conflict (missing LF at EOF)" \ "git merge-file test2.txt orig.txt new2.txt" @@ -177,7 +181,7 @@ et nihil mihi deerit; In loco pascuae ibi me collocavit; super aquam refectionis educavit me. -||||||| +||||||| new5.txt et nihil mihi deerit. In loco pascuae ibi me collocavit, super aquam refectionis educavit me; @@ -211,4 +215,41 @@ test_expect_success '"diff3 -m" style output (2)' ' test_cmp expect actual ' +cat >expect <<\EOF +Dominus regit me, +<<<<<<<<<< new8.txt +et nihil mihi deerit; + + + + +In loco pascuae ibi me collocavit; +super aquam refectionis educavit me. +|||||||||| new5.txt +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +========== +et nihil mihi deerit, + + + + +In loco pascuae ibi me collocavit -- +super aquam refectionis educavit me, +>>>>>>>>>> new9.txt +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam TU mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success 'marker size' ' + test_must_fail git merge-file -p --marker-size=10 \ + new8.txt new5.txt new9.txt >actual && + test_cmp expect actual +' + test_done diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh index 3202e1de6d..cd3190c4a6 100755 --- a/t/t6035-merge-dir-to-symlink.sh +++ b/t/t6035-merge-dir-to-symlink.sh @@ -5,7 +5,7 @@ test_description='merging when a directory was replaced with a symlink' if ! test_have_prereq SYMLINKS then - say 'Symbolic links not supported, skipping tests.' + skip_all='Symbolic links not supported, skipping tests.' test_done fi diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 203ffdb17a..4185b7ca1d 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -219,6 +219,12 @@ test_expect_success 'bisect and replacements' ' git bisect reset ' +test_expect_success 'index-pack and replacements' ' + git --no-replace-objects rev-list --objects HEAD | + git --no-replace-objects pack-objects test- && + git index-pack test-*.pack +' + # # test_done diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index f105fab98e..e673c25e94 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -6,7 +6,7 @@ test_description='Test git rev-parse with different parent options' . ./test-lib.sh -. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions date >path0 git update-index --add path0 diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 065deadc29..876d1ab743 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -8,7 +8,7 @@ test_description='test describe o----o----o----o----o----. / \ A c / .------------o---o---o - D e + D,R e ' . ./test-lib.sh @@ -68,6 +68,8 @@ test_expect_success setup ' echo D >another && git add another && git commit -m D && test_tick && git tag -a -m D D && + test_tick && + git tag -a -m R R && test_tick && echo DD >another && git commit -a -m another && @@ -89,10 +91,10 @@ test_expect_success setup ' check_describe A-* HEAD check_describe A-* HEAD^ -check_describe D-* HEAD^^ +check_describe R-* HEAD^^ check_describe A-* HEAD^^2 check_describe B HEAD^^2^ -check_describe D-* HEAD^^^ +check_describe R-* HEAD^^^ check_describe c-* --tags HEAD check_describe c-* --tags HEAD^ diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index 42f6fff373..42f8ece097 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -7,65 +7,69 @@ test_description='fmt-merge-msg test' . ./test-lib.sh -datestamp=1151939923 -setdate () { - GIT_COMMITTER_DATE="$datestamp +0200" - GIT_AUTHOR_DATE="$datestamp +0200" - datestamp=`expr "$datestamp" + 1` - export GIT_COMMITTER_DATE GIT_AUTHOR_DATE -} - test_expect_success setup ' echo one >one && git add one && - setdate && + test_tick && git commit -m "Initial" && + git clone . remote && + echo uno >one && echo dos >two && git add two && - setdate && + test_tick && git commit -a -m "Second" && git checkout -b left && - echo $datestamp >one && - setdate && + echo "c1" >one && + test_tick && git commit -a -m "Common #1" && - echo $datestamp >one && - setdate && + echo "c2" >one && + test_tick && git commit -a -m "Common #2" && git branch right && - echo $datestamp >two && - setdate && + echo "l3" >two && + test_tick && git commit -a -m "Left #3" && - echo $datestamp >two && - setdate && + echo "l4" >two && + test_tick && git commit -a -m "Left #4" && - echo $datestamp >two && - setdate && + echo "l5" >two && + test_tick && git commit -a -m "Left #5" && + git tag tag-l5 && git checkout right && - echo $datestamp >three && + echo "r3" >three && git add three && - setdate && + test_tick && git commit -a -m "Right #3" && + git tag tag-r3 && - echo $datestamp >three && - setdate && + echo "r4" >three && + test_tick && git commit -a -m "Right #4" && - echo $datestamp >three && - setdate && + echo "r5" >three && + test_tick && git commit -a -m "Right #5" && + git checkout -b long && + i=0 && + while test $i -lt 30 + do + test_commit $i one && + i=$(($i+1)) + done && + git show-branch ' @@ -113,7 +117,7 @@ test_expect_success 'merge-msg test #3-1' ' git config merge.log true && git checkout master && - setdate && + test_tick && git fetch . left && git fmt-merge-msg <.git/FETCH_HEAD >actual && @@ -127,7 +131,7 @@ test_expect_success 'merge-msg test #3-2' ' git config merge.summary true && git checkout master && - setdate && + test_tick && git fetch . left && git fmt-merge-msg <.git/FETCH_HEAD >actual && @@ -159,7 +163,7 @@ test_expect_success 'merge-msg test #4-1' ' git config merge.log true && git checkout master && - setdate && + test_tick && git fetch . left right && git fmt-merge-msg <.git/FETCH_HEAD >actual && @@ -173,7 +177,7 @@ test_expect_success 'merge-msg test #4-2' ' git config merge.summary true && git checkout master && - setdate && + test_tick && git fetch . left right && git fmt-merge-msg <.git/FETCH_HEAD >actual && @@ -187,7 +191,7 @@ test_expect_success 'merge-msg test #5-1' ' git config merge.log yes && git checkout master && - setdate && + test_tick && git fetch . left right && git fmt-merge-msg <.git/FETCH_HEAD >actual && @@ -201,7 +205,7 @@ test_expect_success 'merge-msg test #5-2' ' git config merge.summary yes && git checkout master && - setdate && + test_tick && git fetch . left right && git fmt-merge-msg <.git/FETCH_HEAD >actual && @@ -215,7 +219,7 @@ test_expect_success 'merge-msg -F' ' git config merge.summary yes && git checkout master && - setdate && + test_tick && git fetch . left right && git fmt-merge-msg -F .git/FETCH_HEAD >actual && @@ -229,7 +233,7 @@ test_expect_success 'merge-msg -F in subdirectory' ' git config merge.summary yes && git checkout master && - setdate && + test_tick && git fetch . left right && mkdir sub && cp .git/FETCH_HEAD sub/FETCH_HEAD && @@ -240,4 +244,128 @@ test_expect_success 'merge-msg -F in subdirectory' ' test_cmp expected actual ' +test_expect_success 'merge-msg with nothing to merge' ' + + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.summary yes && + + ( + cd remote && + git checkout -b unrelated && + test_tick && + git fetch origin && + git fmt-merge-msg <.git/FETCH_HEAD >../actual + ) && + + test_cmp /dev/null actual +' + +cat >expected <<\EOF +Merge tag 'tag-r3' + +* tag 'tag-r3': + Right #3 + Common #2 + Common #1 +EOF + +test_expect_success 'merge-msg tag' ' + + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.summary yes && + + git checkout master && + test_tick && + git fetch . tag tag-r3 && + + git fmt-merge-msg <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +Merge tags 'tag-r3' and 'tag-l5' + +* tag 'tag-r3': + Right #3 + Common #2 + Common #1 + +* tag 'tag-l5': + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 +EOF + +test_expect_success 'merge-msg two tags' ' + + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.summary yes && + + git checkout master && + test_tick && + git fetch . tag tag-r3 tag tag-l5 && + + git fmt-merge-msg <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +Merge branch 'left', tag 'tag-r3' + +* tag 'tag-r3': + Right #3 + Common #2 + Common #1 + +* left: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 +EOF + +test_expect_success 'merge-msg tag and branch' ' + + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.summary yes && + + git checkout master && + test_tick && + git fetch . tag tag-r3 left && + + git fmt-merge-msg <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +Merge branch 'long' + +* long: (35 commits) +EOF + +test_expect_success 'merge-msg lots of commits' ' + + git checkout master && + test_tick && + git fetch . long && + + i=29 && + while test $i -gt 9 + do + echo " $i" && + i=$(($i-1)) + done >>expected && + echo " ..." >>expected + + git fmt-merge-msg <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 8052c86ad3..7dc8a510c7 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -295,6 +295,15 @@ test_expect_success 'Check short upstream format' ' test_cmp expected actual ' +cat >expected <<EOF +67a36f1 +EOF + +test_expect_success 'Check short objectname format' ' + git for-each-ref --format="%(objectname:short)" refs/heads >actual && + test_cmp expected actual +' + test_expect_success 'Check for invalid refname format' ' test_must_fail git for-each-ref --format="%(refname:INVALID)" ' diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 0da13a8d6b..2c55801ee8 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -143,11 +143,12 @@ test_expect_success 'more setup' ' test_expect_success 'use index-filter to move into a subdirectory' ' git branch directorymoved && git filter-branch -f --index-filter \ - "git ls-files -s | sed \"s-\\t-&newsubdir/-\" | + "git ls-files -s | sed \"s- -&newsubdir/-\" | GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \ git update-index --index-info && mv \"\$GIT_INDEX_FILE.new\" \"\$GIT_INDEX_FILE\"" directorymoved && - test -z "$(git diff HEAD directorymoved:newsubdir)"' + git diff --exit-code HEAD directorymoved:newsubdir +' test_expect_success 'stops when msg filter fails' ' old=$(git rev-parse HEAD) && diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 73dbc4360b..ac943f5eee 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -583,7 +583,7 @@ test_expect_success \ # 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" + 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 diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 5257f4d261..26ddf9d496 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -13,7 +13,7 @@ test_expect_success 'determine default editor' ' ' -if ! expr "$vi" : '^[a-z]*$' >/dev/null +if ! expr "$vi" : '[a-z]*$' >/dev/null then vi= fi @@ -38,7 +38,7 @@ test_expect_success setup ' test_commit "$msg" && echo "$msg" >expect && git show -s --format=%s > actual && - diff actual expect + test_cmp actual expect ' @@ -85,7 +85,7 @@ do git --exec-path=. commit --amend && git show -s --pretty=oneline | sed -e "s/^[0-9a-f]* //" >actual && - diff actual expect + test_cmp actual expect ' done @@ -107,13 +107,13 @@ do git --exec-path=. commit --amend && git show -s --pretty=oneline | sed -e "s/^[0-9a-f]* //" >actual && - diff actual expect + test_cmp actual expect ' done if ! echo 'echo space > "$1"' > "e space.sh" then - say "Skipping; FS does not support spaces in filenames" + skip_all="Skipping; FS does not support spaces in filenames" test_done fi diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index d9202d5af5..71d3ceff8f 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -3,18 +3,26 @@ test_description='Test automatic use of a pager.' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-pager.sh + +cleanup_fail() { + echo >&2 cleanup failed + (exit 1) +} -rm -f stdout_is_tty test_expect_success 'set up terminal for tests' ' + rm -f stdout_is_tty || + cleanup_fail && + if test -t 1 then - : > stdout_is_tty + >stdout_is_tty elif test_have_prereq PERL && "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl \ sh -c "test -t 1" then - : > test_terminal_works + >test_terminal_works fi ' @@ -29,56 +37,71 @@ then } test_set_prereq TTY else - say no usable terminal, so skipping some tests + say "# no usable terminal, so skipping some tests" fi -unset GIT_PAGER GIT_PAGER_IN_USE -git config --unset core.pager -PAGER='cat > paginated.out' -export PAGER - test_expect_success 'setup' ' + unset GIT_PAGER GIT_PAGER_IN_USE; + test_might_fail git config --unset core.pager && + + PAGER="cat >paginated.out" && + export PAGER && + test_commit initial ' -rm -f paginated.out test_expect_success TTY 'some commands use a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git log && test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'some commands do not use a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git rev-list HEAD && ! test -e paginated.out ' -rm -f paginated.out test_expect_success 'no pager when stdout is a pipe' ' + rm -f paginated.out || + cleanup_fail && + git log | cat && ! test -e paginated.out ' -rm -f paginated.out test_expect_success 'no pager when stdout is a regular file' ' - git log > file && + rm -f paginated.out || + cleanup_fail && + + git log >file && ! test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'git --paginate rev-list uses a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git --paginate rev-list HEAD && test -e paginated.out ' -rm -f file paginated.out test_expect_success 'no pager even with --paginate when stdout is a pipe' ' + rm -f file paginated.out || + cleanup_fail && + git --paginate log | cat && ! test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'no pager with --no-pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git --no-pager log && ! test -e paginated.out ' @@ -86,91 +109,264 @@ test_expect_success TTY 'no pager with --no-pager' ' # A colored commit log will begin with an appropriate ANSI escape # for the first color; the text "commit" comes later. colorful() { - read firstline < $1 - ! expr "$firstline" : "^[a-zA-Z]" >/dev/null + read firstline <$1 + ! expr "$firstline" : "[a-zA-Z]" >/dev/null } -rm -f colorful.log colorless.log test_expect_success 'tests can detect color' ' - git log --no-color > colorless.log && - git log --color > colorful.log && + rm -f colorful.log colorless.log || + cleanup_fail && + + git log --no-color >colorless.log && + git log --color >colorful.log && ! colorful colorless.log && colorful colorful.log ' -rm -f colorless.log -git config color.ui auto test_expect_success 'no color when stdout is a regular file' ' - git log > colorless.log && + rm -f colorless.log && + git config color.ui auto || + cleanup_fail && + + git log >colorless.log && ! colorful colorless.log ' -rm -f paginated.out -git config color.ui auto test_expect_success TTY 'color when writing to a pager' ' - TERM=vt100 test_terminal git log && + rm -f paginated.out && + git config color.ui auto || + cleanup_fail && + + ( + TERM=vt100 && + export TERM && + test_terminal git log + ) && colorful paginated.out ' -rm -f colorful.log -git config color.ui auto test_expect_success 'color when writing to a file intended for a pager' ' - TERM=vt100 GIT_PAGER_IN_USE=true git log > colorful.log && - colorful colorful.log -' + rm -f colorful.log && + git config color.ui auto || + cleanup_fail && -unset PAGER GIT_PAGER -git config --unset core.pager -test_expect_success 'determine default pager' ' - less=$(git var GIT_PAGER) && - test -n "$less" + ( + TERM=vt100 && + GIT_PAGER_IN_USE=true && + export TERM GIT_PAGER_IN_USE && + git log >colorful.log + ) && + colorful colorful.log ' -if expr "$less" : '^[a-z]*$' > /dev/null && test_have_prereq TTY +if test_have_prereq SIMPLEPAGER && test_have_prereq TTY then - test_set_prereq SIMPLEPAGER + test_set_prereq SIMPLEPAGERTTY fi -unset PAGER GIT_PAGER -git config --unset core.pager -rm -f default_pager_used -test_expect_success SIMPLEPAGER 'default pager is used by default' ' - cat > $less <<-EOF && - #!$SHELL_PATH - wc > default_pager_used - EOF - chmod +x $less && - PATH=.:$PATH test_terminal git log && - test -e default_pager_used -' +# Use this helper to make it easy for the caller of your +# terminal-using function to specify whether it should fail. +# If you write +# +# your_test() { +# parse_args "$@" +# +# $test_expectation "$cmd - behaves well" " +# ... +# $full_command && +# ... +# " +# } +# +# then your test can be used like this: +# +# your_test expect_(success|failure) [test_must_fail] 'git foo' +# +parse_args() { + test_expectation="test_$1" + shift + if test "$1" = test_must_fail + then + full_command="test_must_fail test_terminal " + shift + else + full_command="test_terminal " + fi + cmd=$1 + full_command="$full_command $1" +} -unset GIT_PAGER -git config --unset core.pager -rm -f PAGER_used -test_expect_success TTY 'PAGER overrides default pager' ' - PAGER="wc > PAGER_used" && - export PAGER && - test_terminal git log && - test -e PAGER_used -' +test_default_pager() { + parse_args "$@" -unset GIT_PAGER -rm -f core.pager_used -test_expect_success TTY 'core.pager overrides PAGER' ' - PAGER=wc && - export PAGER && - git config core.pager "wc > core.pager_used" && - test_terminal git log && - test -e core.pager_used -' + $test_expectation SIMPLEPAGERTTY "$cmd - default pager is used by default" " + unset PAGER GIT_PAGER; + test_might_fail git config --unset core.pager && + rm -f default_pager_used || + cleanup_fail && -rm -f GIT_PAGER_used -test_expect_success TTY 'GIT_PAGER overrides core.pager' ' - git config core.pager wc && - GIT_PAGER="wc > GIT_PAGER_used" && - export GIT_PAGER && - test_terminal git log && - test -e GIT_PAGER_used + cat >\$less <<-\EOF && + #!/bin/sh + wc >default_pager_used + EOF + chmod +x \$less && + ( + PATH=.:\$PATH && + export PATH && + $full_command + ) && + test -e default_pager_used + " +} + +test_PAGER_overrides() { + parse_args "$@" + + $test_expectation TTY "$cmd - PAGER overrides default pager" " + unset GIT_PAGER; + test_might_fail git config --unset core.pager && + rm -f PAGER_used || + cleanup_fail && + + PAGER='wc >PAGER_used' && + export PAGER && + $full_command && + test -e PAGER_used + " +} + +test_core_pager_overrides() { + if_local_config= + used_if_wanted='overrides PAGER' + test_core_pager "$@" +} + +test_local_config_ignored() { + if_local_config='! ' + used_if_wanted='is not used' + test_core_pager "$@" +} + +test_core_pager() { + parse_args "$@" + + $test_expectation TTY "$cmd - repository-local core.pager setting $used_if_wanted" " + unset GIT_PAGER; + rm -f core.pager_used || + cleanup_fail && + + PAGER=wc && + export PAGER && + git config core.pager 'wc >core.pager_used' && + $full_command && + ${if_local_config}test -e core.pager_used + " +} + +test_core_pager_subdir() { + if_local_config= + used_if_wanted='overrides PAGER' + test_pager_subdir_helper "$@" +} + +test_no_local_config_subdir() { + if_local_config='! ' + used_if_wanted='is not used' + test_pager_subdir_helper "$@" +} + +test_pager_subdir_helper() { + parse_args "$@" + + $test_expectation TTY "$cmd - core.pager $used_if_wanted from subdirectory" " + unset GIT_PAGER; + rm -f core.pager_used && + rm -fr sub || + cleanup_fail && + + PAGER=wc && + stampname=\$(pwd)/core.pager_used && + export PAGER stampname && + git config core.pager 'wc >\"\$stampname\"' && + mkdir sub && + ( + cd sub && + $full_command + ) && + ${if_local_config}test -e core.pager_used + " +} + +test_GIT_PAGER_overrides() { + parse_args "$@" + + $test_expectation TTY "$cmd - GIT_PAGER overrides core.pager" " + rm -f GIT_PAGER_used || + cleanup_fail && + + git config core.pager wc && + GIT_PAGER='wc >GIT_PAGER_used' && + export GIT_PAGER && + $full_command && + test -e GIT_PAGER_used + " +} + +test_doesnt_paginate() { + parse_args "$@" + + $test_expectation TTY "no pager for '$cmd'" " + rm -f GIT_PAGER_used || + cleanup_fail && + + GIT_PAGER='wc >GIT_PAGER_used' && + export GIT_PAGER && + $full_command && + ! test -e GIT_PAGER_used + " +} + +test_pager_choices() { + test_default_pager expect_success "$@" + test_PAGER_overrides expect_success "$@" + test_core_pager_overrides expect_success "$@" + test_core_pager_subdir expect_success "$@" + test_GIT_PAGER_overrides expect_success "$@" +} + +test_expect_success 'setup: some aliases' ' + git config alias.aliasedlog log && + git config alias.true "!true" ' +test_pager_choices 'git log' +test_pager_choices 'git -p log' +test_pager_choices 'git aliasedlog' + +test_default_pager expect_success 'git -p aliasedlog' +test_PAGER_overrides expect_success 'git -p aliasedlog' +test_core_pager_overrides expect_success 'git -p aliasedlog' +test_core_pager_subdir expect_failure 'git -p aliasedlog' +test_GIT_PAGER_overrides expect_success 'git -p aliasedlog' + +test_default_pager expect_success 'git -p true' +test_PAGER_overrides expect_success 'git -p true' +test_core_pager_overrides expect_success 'git -p true' +test_core_pager_subdir expect_failure 'git -p true' +test_GIT_PAGER_overrides expect_success 'git -p true' + +test_default_pager expect_success test_must_fail 'git -p request-pull' +test_PAGER_overrides expect_success test_must_fail 'git -p request-pull' +test_core_pager_overrides expect_success test_must_fail 'git -p request-pull' +test_core_pager_subdir expect_failure test_must_fail 'git -p request-pull' +test_GIT_PAGER_overrides expect_success test_must_fail 'git -p request-pull' + +test_default_pager expect_success test_must_fail 'git -p' +test_PAGER_overrides expect_success test_must_fail 'git -p' +test_local_config_ignored expect_failure test_must_fail 'git -p' +test_no_local_config_subdir expect_success test_must_fail 'git -p' +test_GIT_PAGER_overrides expect_success test_must_fail 'git -p' + +test_doesnt_paginate expect_failure test_must_fail 'git -p nonsense' + test_done diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh new file mode 100755 index 0000000000..eb8ca88cce --- /dev/null +++ b/t/t7008-grep-binary.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +test_description='git grep in binary files' + +. ./test-lib.sh + +test_expect_success 'setup' " + printf 'binary\000file\n' >a && + git add a && + git commit -m. +" + +test_expect_success 'git grep ina a' ' + echo Binary file a matches >expect && + git grep ina a >actual && + test_cmp expect actual +' + +test_expect_success 'git grep -ah ina a' ' + git grep -ah ina a >actual && + test_cmp a actual +' + +test_expect_success 'git grep -I ina a' ' + : >expect && + test_must_fail git grep -I ina a >actual && + test_cmp expect actual +' + +test_expect_success 'git grep -c ina a' ' + echo a:1 >expect && + git grep -c ina a >actual && + test_cmp expect actual +' + +test_expect_success 'git grep -l ina a' ' + echo a >expect && + git grep -l ina a >actual && + test_cmp expect actual +' + +test_expect_success 'git grep -L bar a' ' + echo a >expect && + git grep -L bar a >actual && + test_cmp expect actual +' + +test_expect_success 'git grep -q ina a' ' + : >expect && + git grep -q ina a >actual && + test_cmp expect actual +' + +test_expect_success 'git grep -F ile a' ' + git grep -F ile a +' + +test_expect_success 'git grep -Fi iLE a' ' + git grep -Fi iLE a +' + +# This test actually passes on platforms where regexec() supports the +# flag REG_STARTEND. +test_expect_failure 'git grep ile a' ' + git grep ile a +' + +test_expect_failure 'git grep .fi a' ' + git grep .fi a +' + +test_expect_success 'git grep -F y<NUL>f a' " + printf 'y\000f' >f && + git grep -f f -F a +" + +test_expect_success 'git grep -F y<NUL>x a' " + printf 'y\000x' >f && + test_must_fail git grep -f f -F a +" + +test_expect_success 'git grep -Fi Y<NUL>f a' " + printf 'Y\000f' >f && + git grep -f f -Fi a +" + +test_expect_failure 'git grep -Fi Y<NUL>x a' " + printf 'Y\000x' >f && + test_must_fail git grep -f f -Fi a +" + +test_expect_success 'git grep y<NUL>f a' " + printf 'y\000f' >f && + git grep -f f a +" + +test_expect_failure 'git grep y<NUL>x a' " + printf 'y\000x' >f && + test_must_fail git grep -f f a +" + +test_done diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh index d8a7c79852..0335a9a158 100755 --- a/t/t7010-setup.sh +++ b/t/t7010-setup.sh @@ -103,14 +103,10 @@ test_expect_success 'git ls-files (relative #3)' ' git add a && ( cd a/b && - if git ls-files "../e/f" - then - echo Gaah, should have failed - exit 1 - else - : happy - fi - ) + git ls-files "../e/f" + ) >current && + echo ../e/f >expect && + test_cmp expect current ' diff --git a/t/t7103-reset-bare.sh b/t/t7103-reset-bare.sh index afb55b3a46..1eef93c2b2 100755 --- a/t/t7103-reset-bare.sh +++ b/t/t7103-reset-bare.sh @@ -11,21 +11,26 @@ test_expect_success 'setup non-bare' ' git commit -a -m two ' -test_expect_success 'hard reset requires a worktree' ' +test_expect_success '"hard" reset requires a worktree' ' (cd .git && test_must_fail git reset --hard) ' -test_expect_success 'merge reset requires a worktree' ' +test_expect_success '"merge" reset requires a worktree' ' (cd .git && test_must_fail git reset --merge) ' -test_expect_success 'mixed reset is ok' ' +test_expect_success '"keep" reset requires a worktree' ' + (cd .git && + test_must_fail git reset --keep) +' + +test_expect_success '"mixed" reset is ok' ' (cd .git && git reset) ' -test_expect_success 'soft reset is ok' ' +test_expect_success '"soft" reset is ok' ' (cd .git && git reset --soft) ' @@ -40,19 +45,23 @@ test_expect_success 'setup bare' ' cd bare.git ' -test_expect_success 'hard reset is not allowed in bare' ' +test_expect_success '"hard" reset is not allowed in bare' ' test_must_fail git reset --hard HEAD^ ' -test_expect_success 'merge reset is not allowed in bare' ' +test_expect_success '"merge" reset is not allowed in bare' ' test_must_fail git reset --merge HEAD^ ' -test_expect_success 'mixed reset is not allowed in bare' ' +test_expect_success '"keep" reset is not allowed in bare' ' + test_must_fail git reset --keep HEAD^ +' + +test_expect_success '"mixed" reset is not allowed in bare' ' test_must_fail git reset --mixed HEAD^ ' -test_expect_success 'soft reset is allowed in bare' ' +test_expect_success '"soft" reset is allowed in bare' ' git reset --soft HEAD^ && test "`git show --pretty=format:%s | head -n 1`" = "one" ' diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh index 8704d00196..70cdd8e618 100755 --- a/t/t7110-reset-merge.sh +++ b/t/t7110-reset-merge.sh @@ -3,7 +3,7 @@ # Copyright (c) 2009 Christian Couder # -test_description='Tests for "git reset --merge"' +test_description='Tests for "git reset" with "--merge" and "--keep" options' . ./test-lib.sh @@ -47,6 +47,30 @@ test_expect_success 'reset --merge is ok when switching back' ' # # working index HEAD target working index HEAD # ---------------------------------------------------- +# file1: C C C D --keep D D D +# file2: C D D D --keep C D D +test_expect_success 'reset --keep is ok with changes in file it does not touch' ' + git reset --hard second && + cat file1 >file2 && + git reset --keep HEAD^ && + ! grep 4 file1 && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" && + test -z "$(git diff --cached)" +' + +test_expect_success 'reset --keep is ok when switching back' ' + git reset --keep second && + grep 4 file1 && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse second)" && + test -z "$(git diff --cached)" +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- # file1: B B C D --merge D D D # file2: C D D D --merge C D D test_expect_success 'reset --merge discards changes added to index (1)' ' @@ -78,6 +102,18 @@ test_expect_success 'reset --merge is ok again when switching back (1)' ' # # working index HEAD target working index HEAD # ---------------------------------------------------- +# file1: B B C D --keep (disallowed) +test_expect_success 'reset --keep fails with changes in index in files it touches' ' + git reset --hard second && + echo "line 5" >> file1 && + git add file1 && + test_must_fail git reset --keep HEAD^ +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- # file1: C C C D --merge D D D # file2: C C D D --merge D D D test_expect_success 'reset --merge discards changes added to index (2)' ' @@ -104,6 +140,30 @@ test_expect_success 'reset --merge is ok again when switching back (2)' ' # # working index HEAD target working index HEAD # ---------------------------------------------------- +# file1: C C C D --keep D D D +# file2: C C D D --keep C D D +test_expect_success 'reset --keep keeps changes it does not touch' ' + git reset --hard second && + echo "line 4" >> file2 && + git add file2 && + git reset --keep HEAD^ && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" && + test -z "$(git diff --cached)" +' + +test_expect_success 'reset --keep keeps changes when switching back' ' + git reset --keep second && + grep 4 file2 && + grep 4 file1 && + test "$(git rev-parse HEAD)" = "$(git rev-parse second)" && + test -z "$(git diff --cached)" +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- # file1: A B B C --merge (disallowed) test_expect_success 'reset --merge fails with changes in file it touches' ' git reset --hard second && @@ -116,6 +176,22 @@ test_expect_success 'reset --merge fails with changes in file it touches' ' grep file1 err.log | grep "not uptodate" ' +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: A B B C --keep (disallowed) +test_expect_success 'reset --keep fails with changes in file it touches' ' + git reset --hard second && + echo "line 5" >> file1 && + test_tick && + git commit -m "add line 5" file1 && + sed -e "s/line 1/changed line 1/" <file1 >file3 && + mv file3 file1 && + test_must_fail git reset --keep HEAD^ 2>err.log && + grep file1 err.log | grep "not uptodate" +' + test_expect_success 'setup 3 different branches' ' git reset --hard second && git branch branch1 && @@ -156,6 +232,18 @@ test_expect_success '"reset --merge HEAD^" is ok with pending merge' ' # # working index HEAD target working index HEAD # ---------------------------------------------------- +# file1: X U B C --keep (disallowed) +test_expect_success '"reset --keep HEAD^" fails with pending merge' ' + git reset --hard third && + test_must_fail git merge branch1 && + test_must_fail git reset --keep HEAD^ 2>err.log && + grep "middle of a merge" err.log +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- # file1: X U B B --merge B B B test_expect_success '"reset --merge HEAD" is ok with pending merge' ' git reset --hard third && @@ -166,7 +254,19 @@ test_expect_success '"reset --merge HEAD" is ok with pending merge' ' test -z "$(git diff)" ' -test_expect_success '--merge with added/deleted' ' +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: X U B B --keep (disallowed) +test_expect_success '"reset --keep HEAD" fails with pending merge' ' + git reset --hard third && + test_must_fail git merge branch1 && + test_must_fail git reset --keep HEAD 2>err.log && + grep "middle of a merge" err.log +' + +test_expect_success '--merge is ok with added/deleted merge' ' git reset --hard third && rm -f file2 && test_must_fail git merge branch3 && @@ -180,4 +280,16 @@ test_expect_success '--merge with added/deleted' ' git diff --exit-code --cached ' +test_expect_success '--keep fails with added/deleted merge' ' + git reset --hard third && + rm -f file2 && + test_must_fail git merge branch3 && + ! test -f file2 && + test -f file3 && + git diff --exit-code file3 && + git diff --exit-code branch3 file3 && + test_must_fail git reset --keep HEAD 2>err.log && + grep "middle of a merge" err.log +' + test_done diff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh index de896c948d..ce421ad5ac 100755 --- a/t/t7111-reset-table.sh +++ b/t/t7111-reset-table.sh @@ -44,26 +44,32 @@ A B C D soft A B D A B C D mixed A D D A B C D hard D D D A B C D merge XXXXX +A B C D keep XXXXX A B C C soft A B C A B C C mixed A C C A B C C hard C C C A B C C merge XXXXX +A B C C keep A C C B B C D soft B B D B B C D mixed B D D B B C D hard D D D B B C D merge D D D +B B C D keep XXXXX B B C C soft B B C B B C C mixed B C C B B C C hard C C C B B C C merge C C C +B B C C keep B C C B C C D soft B C D B C C D mixed B D D B C C D hard D D D B C C D merge XXXXX +B C C D keep XXXXX B C C C soft B C C B C C C mixed B C C B C C C hard C C C B C C C merge B C C +B C C C keep B C C EOF test_expect_success 'setting up branches to test with unmerged entries' ' @@ -104,10 +110,12 @@ X U B C soft XXXXX X U B C mixed X C C X U B C hard C C C X U B C merge C C C +X U B C keep XXXXX X U B B soft XXXXX X U B B mixed X B B X U B B hard B B B X U B B merge B B B +X U B B keep XXXXX EOF test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index d20ed61b48..1337fa5a22 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -11,10 +11,12 @@ Test switching across them. ! [master] Initial A one, A two * [renamer] Renamer R one->uno, M two ! [side] Side M one, D two, A three - --- - + [side] Side M one, D two, A three - * [renamer] Renamer R one->uno, M two - +*+ [master] Initial A one, A two + ! [simple] Simple D one, M two + ---- + + [simple] Simple D one, M two + + [side] Side M one, D two, A three + * [renamer] Renamer R one->uno, M two + +*++ [master] Initial A one, A two ' @@ -52,6 +54,11 @@ test_expect_success setup ' git update-index --add --remove one two three && git commit -m "Side M one, D two, A three" && + git checkout -b simple master && + rm -f one && + fill a c e > two && + git commit -a -m "Simple D one, M two" && + git checkout master ' @@ -166,6 +173,56 @@ test_expect_success 'checkout -m with merge conflict' ' ! test -s current ' +test_expect_success 'format of merge conflict from checkout -m' ' + + git checkout -f master && git clean -f && + + fill b d > two && + git checkout -m simple && + + git ls-files >current && + fill same two two two >expect && + test_cmp current expect && + + cat <<-EOF >expect && + <<<<<<< simple + a + c + e + ======= + b + d + >>>>>>> local + EOF + test_cmp two expect +' + +test_expect_success 'checkout --merge --conflict=diff3 <branch>' ' + + git checkout -f master && git reset --hard && git clean -f && + + fill b d > two && + git checkout --merge --conflict=diff3 simple && + + cat <<-EOF >expect && + <<<<<<< simple + a + c + e + ||||||| master + a + b + c + d + e + ======= + b + d + >>>>>>> local + EOF + test_cmp two expect +' + test_expect_success 'checkout to detach HEAD (with advice declined)' ' git config advice.detachedHead false && @@ -481,7 +538,7 @@ test_expect_success 'checkout with --merge, in diff3 -m style' ' ( echo "<<<<<<< ours" echo ourside - echo "|||||||" + echo "||||||| base" echo original echo "=======" echo theirside @@ -525,7 +582,7 @@ test_expect_success 'checkout --conflict=diff3' ' ( echo "<<<<<<< ours" echo ourside - echo "|||||||" + echo "||||||| base" echo original echo "=======" echo theirside diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 1a4dc5f893..9bda970584 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -11,226 +11,317 @@ subcommands of git submodule. . ./test-lib.sh -# -# Test setup: -# -create a repository in directory init -# -add a couple of files -# -add directory init to 'superproject', this creates a DIRLINK entry -# -add a couple of regular files to enable testing of submodule filtering -# -mv init subrepo -# -add an entry to .gitmodules for submodule 'example' -# -test_expect_success 'Prepare submodule testing' ' - : > t && +test_expect_success 'setup - initial commit' ' + >t && git add t && git commit -m "initial commit" && - git branch initial HEAD && + git branch initial +' + +test_expect_success 'setup - repository in init subdirectory' ' mkdir init && - cd init && - git init && - echo a >a && - git add a && - git commit -m "submodule commit 1" && - git tag -a -m "rev-1" rev-1 && - rev1=$(git rev-parse HEAD) && - if test -z "$rev1" - then - echo "[OOPS] submodule git rev-parse returned nothing" - false - fi && - cd .. && + ( + cd init && + git init && + echo a >a && + git add a && + git commit -m "submodule commit 1" && + git tag -a -m "rev-1" rev-1 + ) +' + +test_expect_success 'setup - commit with gitlink' ' echo a >a && echo z >z && git add a init z && - git commit -m "super commit 1" && - mv init .subrepo && - GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/init.git + git commit -m "super commit 1" +' + +test_expect_success 'setup - hide init subdirectory' ' + mv init .subrepo +' + +test_expect_success 'setup - repository to add submodules to' ' + git init addtest && + git init addtest-ignore ' -test_expect_success 'Prepare submodule add testing' ' - submodurl=$(pwd) +# The 'submodule add' tests need some repository to add as a submodule. +# The trash directory is a good one as any. +submodurl=$TRASH_DIRECTORY + +listbranches() { + git for-each-ref --format='%(refname)' 'refs/heads/*' +} + +inspect() { + dir=$1 && + dotdot="${2:-..}" && + ( - mkdir addtest && - cd addtest && - git init + cd "$dir" && + listbranches >"$dotdot/heads" && + { git symbolic-ref HEAD || :; } >"$dotdot/head" && + git rev-parse HEAD >"$dotdot/head-sha1" && + git update-index --refresh && + git diff-files --exit-code && + git clean -n -d -x >"$dotdot/untracked" ) -' +} test_expect_success 'submodule add' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" submod && git submodule init + ) && + + rm -f heads head untracked && + inspect addtest/submod ../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked +' + +test_expect_success 'submodule add to .gitignored path fails' ' + ( + cd addtest-ignore && + cat <<-\EOF >expect && + The following path is ignored by one of your .gitignore files: + submod + Use -f if you really want to add it. + EOF + # Does not use test_commit due to the ignore + echo "*" > .gitignore && + git add --force .gitignore && + git commit -m"Ignore everything" && + ! git submodule add "$submodurl" submod >actual 2>&1 && + test_cmp expect actual + ) +' + +test_expect_success 'submodule add to .gitignored path with --force' ' + ( + cd addtest-ignore && + git submodule add --force "$submodurl" submod ) ' test_expect_success 'submodule add --branch' ' + echo "refs/heads/initial" >expect-head && + cat <<-\EOF >expect-heads && + refs/heads/initial + refs/heads/master + EOF + >empty && + ( cd addtest && git submodule add -b initial "$submodurl" submod-branch && - git submodule init && - cd submod-branch && - git branch | grep initial - ) + git submodule init + ) && + + rm -f heads head untracked && + inspect addtest/submod-branch ../.. && + test_cmp expect-heads heads && + test_cmp expect-head head && + test_cmp empty untracked ' test_expect_success 'submodule add with ./ in path' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" ././dotsubmod/./frotz/./ && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/dotsubmod/frotz ../../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked ' test_expect_success 'submodule add with // in path' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" slashslashsubmod///frotz// && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/slashslashsubmod/frotz ../../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked ' test_expect_success 'submodule add with /.. in path' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" dotdotsubmod/../realsubmod/frotz/.. && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/realsubmod ../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked ' test_expect_success 'submodule add with ./, /.. and // in path' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" dot/dotslashsubmod/./../..////realsubmod2/a/b/c/d/../../../../frotz//.. && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/realsubmod2 ../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked +' + +test_expect_success 'setup - add an example entry to .gitmodules' ' + GIT_CONFIG=.gitmodules \ + git config submodule.example.url git://example.com/init.git ' test_expect_success 'status should fail for unmapped paths' ' - if git submodule status - then - echo "[OOPS] submodule status succeeded" - false - elif ! GIT_CONFIG=.gitmodules git config submodule.example.path init - then - echo "[OOPS] git config failed to update .gitmodules" - false - fi + test_must_fail git submodule status +' + +test_expect_success 'setup - map path in .gitmodules' ' + cat <<\EOF >expect && +[submodule "example"] + url = git://example.com/init.git + path = init +EOF + + GIT_CONFIG=.gitmodules git config submodule.example.path init && + + test_cmp expect .gitmodules ' test_expect_success 'status should only print one line' ' - lines=$(git submodule status | wc -l) && - test $lines = 1 + git submodule status >lines && + test $(wc -l <lines) = 1 +' + +test_expect_success 'setup - fetch commit name from submodule' ' + rev1=$(cd .subrepo && git rev-parse HEAD) && + printf "rev1: %s\n" "$rev1" && + test -n "$rev1" ' test_expect_success 'status should initially be "missing"' ' - git submodule status | grep "^-$rev1" + git submodule status >lines && + grep "^-$rev1" lines ' test_expect_success 'init should register submodule url in .git/config' ' + echo git://example.com/init.git >expect && + git submodule init && - url=$(git config submodule.example.url) && - if test "$url" != "git://example.com/init.git" - then - echo "[OOPS] init succeeded but submodule url is wrong" - false - elif test_must_fail git config submodule.example.url ./.subrepo - then - echo "[OOPS] init succeeded but update of url failed" - false - fi + git config submodule.example.url >url && + git config submodule.example.url ./.subrepo && + + test_cmp expect url ' test_expect_success 'update should fail when path is used by a file' ' + echo hello >expect && + echo "hello" >init && - if git submodule update - then - echo "[OOPS] update should have failed" - false - elif test "$(cat init)" != "hello" - then - echo "[OOPS] update failed but init file was molested" - false - else - rm init - fi + test_must_fail git submodule update && + + test_cmp expect init ' test_expect_success 'update should fail when path is used by a nonempty directory' ' + echo hello >expect && + + rm -fr init && mkdir init && echo "hello" >init/a && - if git submodule update - then - echo "[OOPS] update should have failed" - false - elif test "$(cat init/a)" != "hello" - then - echo "[OOPS] update failed but init/a was molested" - false - else - rm init/a - fi + + test_must_fail git submodule update && + + test_cmp expect init/a ' test_expect_success 'update should work when path is an empty dir' ' - rm -rf init && + rm -fr init && + rm -f head-sha1 && + echo "$rev1" >expect && + mkdir init && git submodule update && - head=$(cd init && git rev-parse HEAD) && - if test -z "$head" - then - echo "[OOPS] Failed to obtain submodule head" - false - elif test "$head" != "$rev1" - then - echo "[OOPS] Submodule head is $head but should have been $rev1" - false - fi + + inspect init && + test_cmp expect head-sha1 ' test_expect_success 'status should be "up-to-date" after update' ' - git submodule status | grep "^ $rev1" + git submodule status >list && + grep "^ $rev1" list ' test_expect_success 'status should be "modified" after submodule commit' ' - cd init && - echo b >b && - git add b && - git commit -m "submodule commit 2" && - rev2=$(git rev-parse HEAD) && - cd .. && - if test -z "$rev2" - then - echo "[OOPS] submodule git rev-parse returned nothing" - false - fi && - git submodule status | grep "^+$rev2" + ( + cd init && + echo b >b && + git add b && + git commit -m "submodule commit 2" + ) && + + rev2=$(cd init && git rev-parse HEAD) && + test -n "$rev2" && + git submodule status >list && + + grep "^+$rev2" list ' test_expect_success 'the --cached sha1 should be rev1' ' - git submodule --cached status | grep "^+$rev1" + git submodule --cached status >list && + grep "^+$rev1" list ' test_expect_success 'git diff should report the SHA1 of the new submodule commit' ' - git diff | grep "^+Subproject commit $rev2" + git diff >diff && + grep "^+Subproject commit $rev2" diff ' test_expect_success 'update should checkout rev1' ' + rm -f head-sha1 && + echo "$rev1" >expect && + git submodule update init && - head=$(cd init && git rev-parse HEAD) && - if test -z "$head" - then - echo "[OOPS] submodule git rev-parse returned nothing" - false - elif test "$head" != "$rev1" - then - echo "[OOPS] init did not checkout correct head" - false - fi + inspect init && + + test_cmp expect head-sha1 ' test_expect_success 'status should be "up-to-date" after update' ' - git submodule status | grep "^ $rev1" + git submodule status >list && + grep "^ $rev1" list ' test_expect_success 'checkout superproject with subproject already present' ' @@ -239,6 +330,8 @@ test_expect_success 'checkout superproject with subproject already present' ' ' test_expect_success 'apply submodule diff' ' + >empty && + git branch second && ( cd init && @@ -251,21 +344,24 @@ test_expect_success 'apply submodule diff' ' git format-patch -1 --stdout >P.diff && git checkout second && git apply --index P.diff && - D=$(git diff --cached master) && - test -z "$D" + + git diff --cached master >staged && + test_cmp empty staged ' test_expect_success 'update --init' ' - mv init init2 && git config -f .gitmodules submodule.example.url "$(pwd)/init2" && - git config --remove-section submodule.example + git config --remove-section submodule.example && + test_must_fail git config submodule.example.url && + git submodule update init > update.out && + cat update.out && grep "not initialized" update.out && - test ! -d init/.git && + ! test -d init/.git && + git submodule update --init init && test -d init/.git - ' test_expect_success 'do not add files from a submodule' ' diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index d3c039f724..cee319da0a 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -227,4 +227,11 @@ test_expect_success 'fail when using --files together with --cached' " test_must_fail git submodule summary --files --cached " +test_expect_success 'should not fail in an empty repo' " + git init xyzzy && + cd xyzzy && + git submodule summary >output 2>&1 && + test_cmp output /dev/null +" + test_done diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh index 9a21f783d3..4a7b8933f4 100755 --- a/t/t7405-submodule-merge.sh +++ b/t/t7405-submodule-merge.sh @@ -45,7 +45,7 @@ test_expect_success setup ' git commit -m sub-b) && git add sub && test_tick && - git commit -m b + git commit -m b && git checkout -b c a && git merge -s ours b && diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index 2a527750ce..db9365b645 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -59,11 +59,13 @@ test_expect_success 'setup a submodule tree' ' sub1sha1=$(cd super/sub1 && git rev-parse HEAD) sub3sha1=$(cd super/sub3 && git rev-parse HEAD) +pwd=$(pwd) + cat > expect <<EOF Entering 'sub1' -foo1-sub1-$sub1sha1 +$pwd/clone-foo1-sub1-$sub1sha1 Entering 'sub3' -foo3-sub3-$sub3sha1 +$pwd/clone-foo3-sub3-$sub3sha1 EOF test_expect_success 'test basic "submodule foreach" usage' ' @@ -71,7 +73,9 @@ test_expect_success 'test basic "submodule foreach" usage' ' ( cd clone && git submodule update --init -- sub1 sub3 && - git submodule foreach "echo \$name-\$path-\$sha1" > ../actual + git submodule foreach "echo \$toplevel-\$name-\$path-\$sha1" > ../actual && + git config foo.bar zar && + git submodule foreach "git config --file \"\$toplevel/.git/config\" foo.bar" ) && test_cmp expect actual ' diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 9f5c3edb03..aa9c577e9e 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -193,4 +193,26 @@ test_expect_success 'commit -F overrides -t' ' commit_msg_is "-F log" ' +test_expect_success 'Commit without message is allowed with --allow-empty-message' ' + echo "more content" >>foo && + git add foo && + >empty && + git commit --allow-empty-message <empty && + commit_msg_is "" +' + +test_expect_success 'Commit without message is no-no without --allow-empty-message' ' + echo "more content" >>foo && + git add foo && + >empty && + test_must_fail git commit <empty +' + +test_expect_success 'Commit a message with --allow-empty-message' ' + echo "even more content" >>foo && + git add foo && + git commit --allow-empty-message -m"hello there" && + commit_msg_is "hello there" +' + test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 7940901d47..8297cb4f1e 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -425,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' ' ' +test_expect_success 'amend can copy notes' ' + + git config notes.rewrite.amend true && + git config notes.rewriteRef "refs/notes/*" && + test_commit foo && + git notes add -m"a note" && + test_tick && + git commit --amend -m"new foo" && + test "$(git notes show)" = "a note" + +' + test_done diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 844fb43c6d..ac2e187a57 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -4,8 +4,76 @@ test_description='git commit porcelain-ish' . ./test-lib.sh +# Arguments: [<prefix] [<commit message>] [<commit options>] +check_summary_oneline() { + test_tick && + git commit ${3+"$3"} -m "$2" | head -1 > act && + + # branch name + SUMMARY_PREFIX="$(git name-rev --name-only HEAD)" && + + # append the "special" prefix, like "root-commit", "detached HEAD" + if test -n "$1" + then + SUMMARY_PREFIX="$SUMMARY_PREFIX ($1)" + fi + + # abbrev SHA-1 + SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')" + echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp && + + test_cmp exp act +} + +test_expect_success 'output summary format' ' + + echo new >file1 && + git add file1 && + check_summary_oneline "root-commit" "initial" && + + echo change >>file1 && + git add file1 && + check_summary_oneline "" "a change" +' + +test_expect_success 'output summary format for commit with an empty diff' ' + + check_summary_oneline "" "empty" "--allow-empty" +' + +test_expect_success 'output summary format for merges' ' + + git checkout -b recursive-base && + test_commit base file1 && + + git checkout -b recursive-a recursive-base && + test_commit commit-a file1 && + + git checkout -b recursive-b recursive-base && + test_commit commit-b file1 && + + # conflict + git checkout recursive-a && + test_must_fail git merge recursive-b && + # resolve the conflict + echo commit-a > file1 && + git add file1 && + check_summary_oneline "" "Merge" +' + +output_tests_cleanup() { + # this is needed for "do not fire editor in the presence of conflicts" + git checkout master && + + # this is needed for the "partial removal" test to pass + git rm file1 && + git commit -m "cleanup" +} + test_expect_success 'the basics' ' + output_tests_cleanup && + echo doing partial >"commit is" && mkdir not && echo very much encouraged but we should >not/forbid && @@ -35,7 +103,7 @@ test_expect_success 'partial' ' ' -test_expect_success 'partial modification in a subdirecotry' ' +test_expect_success 'partial modification in a subdirectory' ' test_tick && git commit -m "partial commit to subdirectory" not && diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 253c334319..3d4f85d74f 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -34,7 +34,7 @@ test_expect_success 'status with modified file in submodule' ' (cd sub && git reset --hard) && echo "changed" >sub/foo && git status >output && - grep "modified: sub" output + grep "modified: sub (modified content)" output ' test_expect_success 'status with modified file in submodule (porcelain)' ' @@ -49,7 +49,7 @@ test_expect_success 'status with modified file in submodule (porcelain)' ' test_expect_success 'status with added file in submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && git status >output && - grep "modified: sub" output + grep "modified: sub (modified content)" output ' test_expect_success 'status with added file in submodule (porcelain)' ' @@ -64,7 +64,12 @@ test_expect_success 'status with untracked file in submodule' ' (cd sub && git reset --hard) && echo "content" >sub/new-file && git status >output && - grep "modified: sub" output + grep "modified: sub (untracked content)" output +' + +test_expect_success 'status -uno with untracked file in submodule' ' + git status -uno >output && + grep "^nothing to commit" output ' test_expect_success 'status with untracked file in submodule (porcelain)' ' @@ -74,6 +79,100 @@ test_expect_success 'status with untracked file in submodule (porcelain)' ' EOF ' +test_expect_success 'status with added and untracked file in submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (modified content, untracked content)" output +' + +test_expect_success 'status with added and untracked file in submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with modified file in modified submodule' ' + (cd sub && git reset --hard) && + rm sub/new-file && + (cd sub && echo "next change" >foo && git commit -m "next change" foo) && + echo "changed" >sub/foo && + git status >output && + grep "modified: sub (new commits, modified content)" output +' + +test_expect_success 'status with modified file in modified submodule (porcelain)' ' + (cd sub && git reset --hard) && + echo "changed" >sub/foo && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with added file in modified submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status >output && + grep "modified: sub (new commits, modified content)" output +' + +test_expect_success 'status with added file in modified submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with untracked file in modified submodule' ' + (cd sub && git reset --hard) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (new commits, untracked content)" output +' + +test_expect_success 'status with untracked file in modified submodule (porcelain)' ' + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with added and untracked file in modified submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (new commits, modified content, untracked content)" output +' + +test_expect_success 'status with added and untracked file in modified submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'setup .git file for sub' ' + (cd sub && + rm -f new-file + REAL="$(pwd)/../.real" && + mv .git "$REAL" + echo "gitdir: $REAL" >.git) && + echo .real >>.gitignore && + git commit -m "added .real to .gitignore" .gitignore +' + +test_expect_success 'status with added file in modified submodule with .git file' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status >output && + grep "modified: sub (new commits, modified content)" output +' + test_expect_success 'rm submodule contents' ' rm -rf sub/* sub/.git ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 556d0faa77..a72fe3ae64 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -69,6 +69,34 @@ test_expect_success 'status (2)' ' ' cat >expect <<\EOF +# On branch master +# Changes to be committed: +# new file: dir2/added +# +# Changed but not updated: +# modified: dir1/modified +# +# Untracked files: +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +EOF + +git config advice.statusHints false + +test_expect_success 'status (advice.statusHints false)' ' + + git status >output && + test_cmp expect output + +' + +git config --unset advice.statusHints + +cat >expect <<\EOF M dir1/modified A dir2/added ?? dir1/untracked @@ -79,13 +107,32 @@ A dir2/added ?? untracked EOF -test_expect_success 'status -s (2)' ' +test_expect_success 'status -s' ' git status -s >output && test_cmp expect output ' +cat >expect <<\EOF +## master + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF + +test_expect_success 'status -s -b' ' + + git status -s -b >output && + test_cmp expect output + +' + cat >expect <<EOF # On branch master # Changes to be committed: @@ -115,6 +162,23 @@ test_expect_success 'status (status.showUntrackedFiles no)' ' test_cmp expect output ' +cat >expect <<EOF +# On branch master +# Changes to be committed: +# new file: dir2/added +# +# Changed but not updated: +# modified: dir1/modified +# +# Untracked files not listed +EOF +git config advice.statusHints false +test_expect_success 'status -uno (advice.statusHints false)' ' + git status -uno >output && + test_cmp expect output +' +git config --unset advice.statusHints + cat >expect << EOF M dir1/modified A dir2/added @@ -392,6 +456,25 @@ test_expect_success 'status -s with color.status' ' ' cat >expect <<\EOF +## <GREEN>master<RESET> + <RED>M<RESET> dir1/modified +<GREEN>A<RESET> dir2/added +<BLUE>??<RESET> dir1/untracked +<BLUE>??<RESET> dir2/modified +<BLUE>??<RESET> dir2/untracked +<BLUE>??<RESET> expect +<BLUE>??<RESET> output +<BLUE>??<RESET> untracked +EOF + +test_expect_success 'status -s -b with color.status' ' + + git status -s -b | test_decode_color >output && + test_cmp expect output + +' + +cat >expect <<\EOF M dir1/modified A dir2/added ?? dir1/untracked @@ -424,6 +507,13 @@ test_expect_success 'status --porcelain ignores color.status' ' git config --unset color.status git config --unset color.ui +test_expect_success 'status --porcelain ignores -b' ' + + git status --porcelain -b >output && + test_cmp expect output + +' + cat >expect <<\EOF # On branch master # Changes to be committed: @@ -496,6 +586,16 @@ test_expect_success 'dry-run of partial commit excluding new file in index' ' test_cmp expect output ' +cat >expect <<EOF +:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M dir1/modified +EOF +test_expect_success 'status refreshes the index' ' + touch dir2/added && + git status && + git diff-files >output && + test_cmp expect output +' + test_expect_success 'setup status submodule summary' ' test_create_repo sm && ( cd sm && @@ -693,4 +793,146 @@ test_expect_success 'commit --dry-run submodule summary (--amend)' ' test_cmp expect output ' +test_expect_success POSIXPERM 'status succeeds in a read-only repository' ' + ( + chmod a-w .git && + # make dir1/tracked stat-dirty + >dir1/tracked1 && mv -f dir1/tracked1 dir1/tracked && + git status -s >output && + ! grep dir1/tracked output && + # make sure "status" succeeded without writing index out + git diff-files | grep dir1/tracked + ) + status=$? + chmod 775 .git + (exit $status) +' + +cat > expect << EOF +# On branch master +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# (use "git checkout -- <file>..." to discard changes in working directory) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' ' + echo modified > sm/untracked && + git status --ignore-submodules=untracked > output && + test_cmp expect output +' + +test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' ' + git status --ignore-submodules=dirty > output && + test_cmp expect output +' + +test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' ' + echo modified > sm/foo && + git status --ignore-submodules=dirty > output && + test_cmp expect output +' + +cat > expect << EOF +# On branch master +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# (use "git checkout -- <file>..." to discard changes in working directory) +# (commit or discard the untracked or modified content in submodules) +# +# modified: dir1/modified +# modified: sm (modified content) +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" ' + git status --ignore-submodules=untracked > output && + test_cmp expect output +' + +head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 --verify HEAD) + +cat > expect << EOF +# On branch master +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# (use "git checkout -- <file>..." to discard changes in working directory) +# +# modified: dir1/modified +# modified: sm (new commits) +# +# Submodules changed but not updated: +# +# * sm $head...$head2 (1): +# > 2nd commit +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" ' + git status --ignore-submodules=untracked > output && + test_cmp expect output +' + +test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" ' + git status --ignore-submodules=dirty > output && + test_cmp expect output +' + +cat > expect << EOF +# On branch master +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# (use "git checkout -- <file>..." to discard changes in working directory) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success "--ignore-submodules=all suppresses submodule summary" ' + git status --ignore-submodules=all > output && + test_cmp expect output +' + test_done diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh index d52c060b06..3ea33db6c7 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit.sh @@ -83,6 +83,52 @@ test_expect_success '--amend option copies authorship' ' test_cmp expect actual ' +sha1_file() { + echo "$*" | sed "s#..#.git/objects/&/#" +} +remove_object() { + rm -f $(sha1_file "$*") +} +no_reflog() { + cp .git/config .git/config.saved && + echo "[core] logallrefupdates = false" >>.git/config && + test_when_finished "mv -f .git/config.saved .git/config" && + + if test -e .git/logs + then + mv .git/logs . && + test_when_finished "mv logs .git/" + fi +} + +test_expect_success '--amend option with empty author' ' + git cat-file commit Initial >tmp && + sed "s/author [^<]* </author </" tmp >empty-author && + no_reflog && + sha=$(git hash-object -t commit -w empty-author) && + test_when_finished "remove_object $sha" && + git checkout $sha && + test_when_finished "git checkout Initial" && + echo "Empty author test" >>foo && + test_tick && + ! git commit -a -m "empty author" --amend 2>err && + grep "empty ident" err +' + +test_expect_success '--amend option with missing author' ' + git cat-file commit Initial >tmp && + sed "s/author [^<]* </author </" tmp >malformed && + no_reflog && + sha=$(git hash-object -t commit -w malformed) && + test_when_finished "remove_object $sha" && + git checkout $sha && + test_when_finished "git checkout Initial" && + echo "Missing author test" >>foo && + test_tick && + ! git commit -a -m "malformed author" --amend 2>err && + grep "empty ident" err +' + test_expect_success '--reset-author makes the commit ours even with --amend option' ' git checkout Initial && echo "Test 6" >>foo && diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 57f6d2bae7..cde8390c1b 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -554,8 +554,7 @@ test_debug 'gitk --all' test_expect_success 'refresh the index before merging' ' git reset --hard c1 && - sleep 1 && - touch file && + cp file file.n && mv -f file.n file && git merge c3 ' diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh index 269cfdf267..9114785ef7 100755 --- a/t/t7604-merge-custom-message.sh +++ b/t/t7604-merge-custom-message.sh @@ -6,6 +6,15 @@ Testing merge when using a custom message for the merge commit.' . ./test-lib.sh +create_merge_msgs() { + echo >exp.subject "custom message" + + cp exp.subject exp.log && + echo >>exp.log "" && + echo >>exp.log "* commit 'c2':" && + echo >>exp.log " c2" +} + test_expect_success 'setup' ' echo c0 > c0.c && git add c0.c && @@ -19,16 +28,23 @@ test_expect_success 'setup' ' echo c2 > c2.c && git add c2.c && git commit -m c2 && - git tag c2 + git tag c2 && + create_merge_msgs ' test_expect_success 'merge c2 with a custom message' ' git reset --hard c1 && - echo >expected "custom message" && - git merge -m "custom message" c2 && + git merge -m "$(cat exp.subject)" c2 && + git cat-file commit HEAD | sed -e "1,/^$/d" >actual && + test_cmp exp.subject actual +' + +test_expect_success 'merge --log appends to custom message' ' + git reset --hard c1 && + git merge --log -m "$(cat exp.subject)" c2 && git cat-file commit HEAD | sed -e "1,/^$/d" >actual && - test_cmp expected actual + test_cmp exp.log actual ' test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index f4aa054750..c2f66ff170 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -8,6 +8,7 @@ test_expect_success 'objects in packs marked .keep are not repacked' ' echo content1 > file1 && echo content2 > file2 && git add . && + test_tick && git commit -m initial_commit && # Create two packs # The first pack will contain all of the objects except one @@ -40,6 +41,7 @@ test_expect_success 'loose objects in alternate ODB are not repacked' ' echo content3 > file3 && objsha1=$(GIT_OBJECT_DIRECTORY=alt_objects git hash-object -w file3) && git add file3 && + test_tick && git commit -m commit_file3 && git repack -a -d -l && git prune-packed && @@ -73,6 +75,7 @@ test_expect_success 'packed obs in alt ODB are repacked when local repo has pack rm -f .git/objects/pack/* && echo new_content >> file1 && git add file1 && + test_tick && git commit -m more_content && git repack && git repack -a -d && @@ -118,8 +121,8 @@ test_expect_success 'packed unreachable obs in alternate ODB are not loosened' ' mv .git/objects/pack/* alt_objects/pack/ && csha1=$(git rev-parse HEAD^{commit}) && git reset --hard HEAD^ && - sleep 1 && - git reflog expire --expire=now --expire-unreachable=now --all && + test_tick && + git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all && # The pack-objects call on the next line is equivalent to # git repack -A -d without the call to prune-packed git pack-objects --honor-pack-keep --non-empty --all --reflog \ @@ -156,7 +159,7 @@ test_expect_success 'objects made unreachable by grafts only are kept' ' H1=$(git rev-parse HEAD^) && H2=$(git rev-parse HEAD^^) && echo "$H0 $H2" > .git/info/grafts && - git reflog expire --expire=now --expire-unreachable=now --all && + git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all && git repack -a -d && git cat-file -t $H1 ' diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index 5babdf26e6..200ab61278 100755 --- a/t/t7701-repack-unpack-unreachable.sh +++ b/t/t7701-repack-unpack-unreachable.sh @@ -11,17 +11,20 @@ tsha1= test_expect_success '-A with -d option leaves unreachable objects unpacked' ' echo content > file1 && git add . && + test_tick && git commit -m initial_commit && # create a transient branch with unique content git checkout -b transient_branch && echo more content >> file1 && # record the objects created in the database for file, commit, tree fsha1=$(git hash-object file1) && + test_tick && git commit -a -m more_content && csha1=$(git rev-parse HEAD^{commit}) && tsha1=$(git rev-parse HEAD^{tree}) && git checkout master && echo even more content >> file1 && + test_tick && git commit -a -m even_more_content && # delete the transient branch git branch -D transient_branch && @@ -34,9 +37,11 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' ' git show $fsha1 && git show $csha1 && git show $tsha1 && - # now expire the reflog - sleep 1 && - git reflog expire --expire-unreachable=now --all && + # now expire the reflog, while keeping reachable ones but expiring + # unreachables immediately + test_tick && + sometimeago=$(( $test_tick - 10000 )) && + git reflog expire --expire=$sometimeago --expire-unreachable=$test_tick --all && # and repack git repack -A -d -l && # verify objects are retained unpacked @@ -71,7 +76,7 @@ test_expect_success '-A without -d option leaves unreachable objects packed' ' test 1 = $(ls -1 .git/objects/pack/pack-*.pack | wc -l) && packfile=$(ls .git/objects/pack/pack-*.pack) && git branch -D transient_branch && - sleep 1 && + test_tick && git repack -A -l && test ! -f "$fsha1path" && test ! -f "$csha1path" && diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 1de83ef98f..196827e7ea 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -11,7 +11,7 @@ Testing basic diff tool invocation . ./test-lib.sh if ! test_have_prereq PERL; then - say 'skipping difftool tests, perl not available' + skip_all='skipping difftool tests, perl not available' test_done fi diff --git a/t/t7002-grep.sh b/t/t7810-grep.sh index af63d6ec6d..8a6322765c 100755 --- a/t/t7002-grep.sh +++ b/t/t7810-grep.sh @@ -60,7 +60,7 @@ do echo ${HC}file:5:foo_mmap bar mmap baz } >expected && git grep -n -w -e mmap $H >actual && - diff expected actual + test_cmp expected actual ' test_expect_success "grep -w $L (w)" ' @@ -74,7 +74,7 @@ do echo ${HC}x:1:x x xx x } >expected && git grep -n -w -e "x xx* x" $H >actual && - diff expected actual + test_cmp expected actual ' test_expect_success "grep -w $L (y-1)" ' @@ -82,7 +82,7 @@ do echo ${HC}y:1:y yy } >expected && git grep -n -w -e "^y" $H >actual && - diff expected actual + test_cmp expected actual ' test_expect_success "grep -w $L (y-2)" ' @@ -93,7 +93,7 @@ do cat actual false else - diff expected actual + test_cmp expected actual fi ' @@ -105,14 +105,14 @@ do cat actual false else - diff expected actual + test_cmp expected actual fi ' test_expect_success "grep $L (t-1)" ' echo "${HC}t/t:1:test" >expected && git grep -n -e test $H >actual && - diff expected actual + test_cmp expected actual ' test_expect_success "grep $L (t-2)" ' @@ -121,7 +121,7 @@ do cd t && git grep -n -e test $H ) >actual && - diff expected actual + test_cmp expected actual ' test_expect_success "grep $L (t-3)" ' @@ -130,7 +130,7 @@ do cd t && git grep --full-name -n -e test $H ) >actual && - diff expected actual + test_cmp expected actual ' test_expect_success "grep -c $L (no /dev/null)" ' @@ -442,6 +442,58 @@ test_expect_success 'grep -Fi' ' test_cmp expected actual ' +test_expect_success 'outside of git repository' ' + rm -fr non && + 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 + } >non/expect.full && + echo file2:world >non/expect.sub + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non/git" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git grep o && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full + cd sub && + test_must_fail git grep o && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) +' + +test_expect_success 'inside git repository but with --no-index' ' + rm -fr is && + mkdir -p is/git/sub && + echo hello >is/git/file1 && + echo world >is/git/sub/file2 && + echo ".*o*" >is/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >is/expect.full && + : >is/expect.empty && + echo file2:world >is/expect.sub + ( + cd is/git && + git init && + test_must_fail git grep o >../actual.full && + test_cmp ../expect.empty ../actual.full && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full && + 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 + ) +' + test_expect_success 'setup double-dash tests' ' cat >double-dash <<EOF && -- diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh new file mode 100755 index 0000000000..568a6f2b69 --- /dev/null +++ b/t/t7811-grep-open.sh @@ -0,0 +1,168 @@ +#!/bin/sh + +test_description='git grep --open-files-in-pager +' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-pager.sh +unset PAGER GIT_PAGER + +test_expect_success 'setup' ' + test_commit initial grep.h " +enum grep_pat_token { + GREP_PATTERN, + GREP_PATTERN_HEAD, + GREP_PATTERN_BODY, + GREP_AND, + GREP_OPEN_PAREN, + GREP_CLOSE_PAREN, + GREP_NOT, + GREP_OR, +};" && + + test_commit add-user revision.c " + } + if (seen_dashdash) + read_pathspec_from_stdin(revs, &sb, prune); + strbuf_release(&sb); +} + +static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what) +{ + append_grep_pattern(&revs->grep_filter, ptn, \"command line\", 0, what); +" && + + mkdir subdir && + test_commit subdir subdir/grep.c "enum grep_pat_token" && + + test_commit uninteresting unrelated "hello, world" && + + echo GREP_PATTERN >untracked +' + +test_expect_success SIMPLEPAGER 'git grep -O' ' + cat >$less <<-\EOF && + #!/bin/sh + printf "%s\n" "$@" >pager-args + EOF + chmod +x $less && + cat >expect.less <<-\EOF && + +/*GREP_PATTERN + grep.h + EOF + echo grep.h >expect.notless && + >empty && + + PATH=.:$PATH git grep -O GREP_PATTERN >out && + { + test_cmp expect.less pager-args || + test_cmp expect.notless pager-args + } && + test_cmp empty out +' + +test_expect_success 'git grep -O --cached' ' + test_must_fail git grep --cached -O GREP_PATTERN >out 2>msg && + grep open-files-in-pager msg +' + +test_expect_success 'git grep -O --no-index' ' + rm -f expect.less pager-args out && + cat >expect <<-\EOF && + grep.h + untracked + EOF + >empty && + + ( + GIT_PAGER='\''printf "%s\n" >pager-args'\'' && + export GIT_PAGER && + git grep --no-index -O GREP_PATTERN >out + ) && + test_cmp expect pager-args && + test_cmp empty out +' + +test_expect_success 'setup: fake "less"' ' + cat >less <<-\EOF && + #!/bin/sh + printf "%s\n" "$@" >actual + EOF + chmod +x less +' + +test_expect_success 'git grep -O jumps to line in less' ' + cat >expect <<-\EOF && + +/*GREP_PATTERN + grep.h + EOF + >empty && + + GIT_PAGER=./less git grep -O GREP_PATTERN >out && + test_cmp expect actual && + test_cmp empty out && + + git grep -O./less GREP_PATTERN >out2 && + test_cmp expect actual && + test_cmp empty out2 +' + +test_expect_success 'modified file' ' + rm -f actual && + cat >expect <<-\EOF && + +/*enum grep_pat_token + grep.h + revision.c + subdir/grep.c + unrelated + EOF + >empty && + + echo "enum grep_pat_token" >unrelated && + test_when_finished "git checkout HEAD unrelated" && + GIT_PAGER=./less git grep -F -O "enum grep_pat_token" >out && + test_cmp expect actual && + test_cmp empty out +' + +test_config() { + git config "$1" "$2" && + test_when_finished "git config --unset $1" +} + +test_expect_success 'copes with color settings' ' + rm -f actual && + echo grep.h >expect && + test_config color.grep always && + test_config color.grep.filename yellow && + test_config color.grep.separator green && + git grep -O'\''printf "%s\n" >actual'\'' GREP_AND && + test_cmp expect actual +' + +test_expect_success 'run from subdir' ' + rm -f actual && + echo grep.c >expect && + >empty && + + ( + cd subdir && + export GIT_PAGER && + GIT_PAGER='\''printf "%s\n" >../args'\'' && + git grep -O "enum grep_pat_token" >../out && + git grep -O"pwd >../dir; :" "enum grep_pat_token" >../out2 + ) && + case $(cat dir) in + *subdir) + : good + ;; + *) + false + ;; + esac && + test_cmp expect args && + test_cmp empty out && + test_cmp empty out2 +' + +test_done diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh new file mode 100755 index 0000000000..9ad96d4d32 --- /dev/null +++ b/t/t8006-blame-textconv.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +test_description='git blame textconv support' +. ./test-lib.sh + +find_blame() { + sed -e 's/^[^(]*//' +} + +cat >helper <<'EOF' +#!/bin/sh +sed 's/^/converted: /' "$@" +EOF +chmod +x helper + +test_expect_success 'setup ' ' + echo test 1 >one.bin && + echo test number 2 >two.bin && + git add . && + GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" && + echo test 1 version 2 >one.bin && + echo test number 2 version 2 >>two.bin && + GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" +' + +cat >expected <<EOF +(Number2 2010-01-01 20:00:00 +0000 1) test 1 version 2 +EOF + +test_expect_success 'no filter specified' ' + git blame one.bin >blame && + find_blame Number2 <blame >result && + test_cmp expected result +' + +test_expect_success 'setup textconv filters' ' + echo "*.bin diff=test" >.gitattributes && + git config diff.test.textconv ./helper && + git config diff.test.cachetextconv false +' + +test_expect_success 'blame with --no-textconv' ' + git blame --no-textconv one.bin >blame && + find_blame <blame> result && + test_cmp expected result +' + +cat >expected <<EOF +(Number2 2010-01-01 20:00:00 +0000 1) converted: test 1 version 2 +EOF + +test_expect_success 'basic blame on last commit' ' + git blame one.bin >blame && + find_blame <blame >result && + test_cmp expected result +' + +cat >expected <<EOF +(Number1 2010-01-01 18:00:00 +0000 1) converted: test number 2 +(Number2 2010-01-01 20:00:00 +0000 2) converted: test number 2 version 2 +EOF + +test_expect_success 'blame --textconv going through revisions' ' + git blame --textconv two.bin >blame && + find_blame <blame >result && + test_cmp expected result +' + +test_expect_success 'make a new commit' ' + echo "test number 2 version 3" >>two.bin && + GIT_AUTHOR_NAME=Number3 git commit -a -m Third --date="2010-01-01 22:00:00" +' + +test_expect_success 'blame from previous revision' ' + git blame HEAD^ two.bin >blame && + find_blame <blame >result && + test_cmp expected result +' + +test_done diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh new file mode 100755 index 0000000000..38ac05e4a0 --- /dev/null +++ b/t/t8007-cat-file-textconv.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description='git cat-file textconv support' +. ./test-lib.sh + +cat >helper <<'EOF' +#!/bin/sh +sed 's/^/converted: /' "$@" +EOF +chmod +x helper + +test_expect_success 'setup ' ' + echo test >one.bin && + git add . && + GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" && + echo test version 2 >one.bin && + GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" +' + +cat >expected <<EOF +fatal: git cat-file --textconv: unable to run textconv on :one.bin +EOF + +test_expect_success 'no filter specified' ' + git cat-file --textconv :one.bin 2>result + test_cmp expected result +' + +test_expect_success 'setup textconv filters' ' + echo "*.bin diff=test" >.gitattributes && + git config diff.test.textconv ./helper && + git config diff.test.cachetextconv false +' + +cat >expected <<EOF +test version 2 +EOF + +test_expect_success 'cat-file without --textconv' ' + git cat-file blob :one.bin >result && + test_cmp expected result +' + +cat >expected <<EOF +test +EOF + +test_expect_success 'cat-file without --textconv on previous commit' ' + git cat-file -p HEAD^:one.bin >result && + test_cmp expected result +' + +cat >expected <<EOF +converted: test version 2 +EOF + +test_expect_success 'cat-file --textconv on last commit' ' + git cat-file --textconv :one.bin >result && + test_cmp expected result +' + +cat >expected <<EOF +converted: test +EOF + +test_expect_success 'cat-file --textconv on previous commit' ' + git cat-file --textconv HEAD^:one.bin >result && + test_cmp expected result +' +test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index c09f375288..23597cc407 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -4,7 +4,7 @@ test_description='git send-email' . ./test-lib.sh if ! test_have_prereq PERL; then - say 'skipping git send-email tests, perl not available' + skip_all='skipping git send-email tests, perl not available' test_done fi @@ -58,7 +58,7 @@ test_no_confirm () { # Exit immediately to prevent hang if a no-confirm test fails check_no_confirm () { test -f no_confirm_okay || { - say 'No confirm test failed; skipping remaining tests to prevent hanging' + skip_all='confirm test failed; skipping remaining tests to prevent hanging' test_done } } @@ -852,4 +852,147 @@ test_expect_success 'no warning with sendemail.chainreplyto = true' ' ! grep "no-chain-reply-to" errors ' +test_expect_success 'sendemail.to works' ' + git config --replace-all sendemail.to "Somebody <somebody@ex.com>" && + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + $patches $patches >stdout && + grep "To: Somebody <somebody@ex.com>" stdout +' + +test_expect_success '--no-to overrides sendemail.to' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --no-to \ + --to=nobody@example.com \ + $patches $patches >stdout && + grep "To: nobody@example.com" stdout && + ! grep "To: Somebody <somebody@ex.com>" stdout +' + +test_expect_success 'sendemail.cc works' ' + git config --replace-all sendemail.cc "Somebody <somebody@ex.com>" && + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + $patches $patches >stdout && + grep "Cc: Somebody <somebody@ex.com>" stdout +' + +test_expect_success '--no-cc overrides sendemail.cc' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --no-cc \ + --cc=bodies@example.com \ + --to=nobody@example.com \ + $patches $patches >stdout && + grep "Cc: bodies@example.com" stdout && + ! grep "Cc: Somebody <somebody@ex.com>" stdout +' + +test_expect_success 'sendemail.bcc works' ' + git config --replace-all sendemail.bcc "Other <other@ex.com>" && + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server relay.example.com \ + $patches $patches >stdout && + grep "RCPT TO:<other@ex.com>" stdout +' + +test_expect_success '--no-bcc overrides sendemail.bcc' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --no-bcc \ + --bcc=bodies@example.com \ + --to=nobody@example.com \ + --smtp-server relay.example.com \ + $patches $patches >stdout && + grep "RCPT TO:<bodies@example.com>" stdout && + ! grep "RCPT TO:<other@ex.com>" stdout +' + +cat >email-using-8bit <<EOF +From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001 +Message-Id: <bogus-message-id@example.com> +From: author@example.com +Date: Sat, 12 Jun 2010 15:53:58 +0200 +Subject: subject goes here + +Dieser deutsche Text enthält einen Umlaut! +EOF + +cat >content-type-decl <<EOF +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +EOF + +test_expect_success 'asks about and fixes 8bit encodings' ' + clean_fake_sendmail && + echo | + git send-email --from=author@example.com --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + email-using-8bit >stdout && + grep "do not declare a Content-Transfer-Encoding" stdout && + grep email-using-8bit stdout && + grep "Which 8bit encoding" stdout && + egrep "Content|MIME" msgtxt1 >actual && + test_cmp actual content-type-decl +' + +test_expect_success 'sendemail.8bitEncoding works' ' + clean_fake_sendmail && + git config sendemail.assume8bitEncoding UTF-8 && + echo bogus | + git send-email --from=author@example.com --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + email-using-8bit >stdout && + egrep "Content|MIME" msgtxt1 >actual && + test_cmp actual content-type-decl +' + +test_expect_success '--8bit-encoding overrides sendemail.8bitEncoding' ' + clean_fake_sendmail && + git config sendemail.assume8bitEncoding "bogus too" && + echo bogus | + git send-email --from=author@example.com --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + --8bit-encoding=UTF-8 \ + email-using-8bit >stdout && + egrep "Content|MIME" msgtxt1 >actual && + test_cmp actual content-type-decl +' + +cat >email-using-8bit <<EOF +From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001 +Message-Id: <bogus-message-id@example.com> +From: author@example.com +Date: Sat, 12 Jun 2010 15:53:58 +0200 +Subject: Dieser Betreff enthält auch einen Umlaut! + +Nothing to see here. +EOF + +cat >expected <<EOF +Subject: =?UTF-8?q?Dieser=20Betreff=20enth=C3=A4lt=20auch=20einen=20Umlaut!?= +EOF + +test_expect_success '--8bit-encoding also treats subject' ' + clean_fake_sendmail && + echo bogus | + git send-email --from=author@example.com --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + --8bit-encoding=UTF-8 \ + email-using-8bit >stdout && + grep "Subject" msgtxt1 >actual && + test_cmp expected actual +' + test_done diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 570e0359e4..d5adae640b 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -15,7 +15,7 @@ case "$GIT_SVN_LC_ALL" in test_set_prereq UTF8 ;; *) - say "UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)" + say "# UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)" ;; esac @@ -271,6 +271,17 @@ test_expect_success 'able to dcommit to a subdirectory' " test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" " +test_expect_success 'dcommit should not fail with a touched file' ' + test_commit "commit-new-file-foo2" foo2 && + test-chmtime =-60 foo && + git svn dcommit +' + +test_expect_success 'rebase should not fail with a touched file' ' + test-chmtime =-60 foo && + git svn rebase +' + test_expect_success 'able to set-tree to a subdirectory' " echo cba > d && git update-index d && diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh index ac52bff0ef..63fc982c8c 100755 --- a/t/t9118-git-svn-funky-branch-names.sh +++ b/t/t9118-git-svn-funky-branch-names.sh @@ -21,34 +21,59 @@ test_expect_success 'setup svnrepo' ' "$svnrepo/pr ject/branches/more fun plugin!" && svn_cmd cp -m "scary" "$svnrepo/pr ject/branches/fun plugin" \ "$svnrepo/pr ject/branches/$scary_uri" && + svn_cmd cp -m "leading dot" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/.leading_dot" && + svn_cmd cp -m "trailing dot" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/trailing_dot." && + svn_cmd cp -m "trailing .lock" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/trailing_dotlock.lock" && + svn_cmd cp -m "reflog" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/not-a%40{0}reflog" && start_httpd ' test_expect_success 'test clone with funky branch names' ' git svn clone -s "$svnrepo/pr ject" project && - cd project && + ( + cd project && git rev-parse "refs/remotes/fun%20plugin" && git rev-parse "refs/remotes/more%20fun%20plugin!" && git rev-parse "refs/remotes/$scary_ref" && - cd .. + git rev-parse "refs/remotes/%2Eleading_dot" && + git rev-parse "refs/remotes/trailing_dot%2E" && + git rev-parse "refs/remotes/trailing_dotlock%2Elock" && + git rev-parse "refs/remotes/not-a%40{0}reflog" + ) ' test_expect_success 'test dcommit to funky branch' " - cd project && - git reset --hard 'refs/remotes/more%20fun%20plugin!' && - echo hello >> foo && - git commit -m 'hello' -- foo && - git svn dcommit && - cd .. + ( + cd project && + git reset --hard 'refs/remotes/more%20fun%20plugin!' && + echo hello >> foo && + git commit -m 'hello' -- foo && + git svn dcommit + ) " test_expect_success 'test dcommit to scary branch' ' - cd project && - git reset --hard "refs/remotes/$scary_ref" && - echo urls are scary >> foo && - git commit -m "eep" -- foo && - git svn dcommit && - cd .. + ( + cd project && + git reset --hard "refs/remotes/$scary_ref" && + echo urls are scary >> foo && + git commit -m "eep" -- foo && + git svn dcommit + ) + ' + +test_expect_success 'test dcommit to trailing_dotlock branch' ' + ( + cd project && + git reset --hard "refs/remotes/trailing_dotlock%2Elock" && + echo who names branches like this anyway? >> foo && + git commit -m "bar" -- foo && + git svn dcommit + ) ' stop_httpd diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh index 95741cbbac..5fb94fb3d6 100755 --- a/t/t9119-git-svn-info.sh +++ b/t/t9119-git-svn-info.sh @@ -7,12 +7,13 @@ test_description='git svn info' . ./lib-git-svn.sh # Tested with: svn, version 1.4.4 (r25188) +# Tested with: svn, version 1.6.[12345689] v=`svn_cmd --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p'` case $v in -1.[45].*) +1.[456].*) ;; *) - say "skipping svn-info test (SVN version: $v not supported)" + skip_all="skipping svn-info test (SVN version: $v not supported)" test_done ;; esac diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh index b9224bdb20..8cfdfe790f 100755 --- a/t/t9129-git-svn-i18n-commitencoding.sh +++ b/t/t9129-git-svn-i18n-commitencoding.sh @@ -14,10 +14,22 @@ compare_git_head_with () { test_cmp current "$1" } +a_utf8_locale=$(locale -a | sed -n '/\.[uU][tT][fF]-*8$/{ + p + q +}') + +if test -n "$a_utf8_locale" +then + test_set_prereq UTF8 +else + say "# UTF-8 locale not available, some tests are skipped" +fi + compare_svn_head_with () { # extract just the log message and strip out committer info. # don't use --limit here since svn 1.1.x doesn't have it, - LC_ALL=en_US.UTF-8 svn log `git svn info --url` | perl -w -e ' + LC_ALL="$a_utf8_locale" svn log `git svn info --url` | perl -w -e ' use bytes; $/ = ("-"x72) . "\n"; my @x = <STDIN>; @@ -69,12 +81,6 @@ do ' done -if locale -a |grep -q en_US.utf8; then - test_set_prereq UTF8 -else - say "UTF-8 locale not available, test skipped" -fi - test_expect_success UTF8 'ISO-8859-1 should match UTF-8 in svn' ' ( cd ISO8859-1 && diff --git a/t/t9143-git-svn-gc.sh b/t/t9143-git-svn-gc.sh index 99f69c6a0b..337ea59711 100755 --- a/t/t9143-git-svn-gc.sh +++ b/t/t9143-git-svn-gc.sh @@ -43,7 +43,7 @@ then gunzip .git/svn/refs/remotes/git-svn/unhandled.log.gz ' else - say "Perl Compress::Zlib unavailable, skipping gunzip test" + say "# Perl Compress::Zlib unavailable, skipping gunzip test" fi test_expect_success 'git svn gc does not change unhandled.log files' ' diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh index 53581425c4..24c2421bfc 100755 --- a/t/t9150-svk-mergetickets.sh +++ b/t/t9150-svk-mergetickets.sh @@ -11,6 +11,7 @@ test_expect_success 'load svk depot' " svnadmin load -q '$rawsvnrepo' \ < '$TEST_DIRECTORY/t9150/svk-merge.dump' && git svn init --minimize-url -R svkmerge \ + --rewrite-root=http://svn.example.org \ -T trunk -b branches '$svnrepo' && git svn fetch --all " diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh index 3569c62096..250c651eae 100755 --- a/t/t9151-svn-mergeinfo.sh +++ b/t/t9151-svn-mergeinfo.sh @@ -11,6 +11,7 @@ test_expect_success 'load svn dump' " svnadmin load -q '$rawsvnrepo' \ < '$TEST_DIRECTORY/t9151/svn-mergeinfo.dump' && git svn init --minimize-url -R svnmerge \ + --rewrite-root=http://svn.example.org \ -T trunk -b branches '$svnrepo' && git svn fetch --all " @@ -33,6 +34,21 @@ test_expect_success 'svn non-merge merge commits did not become git merge commit [ -z "$bad_non_merges" ] ' +test_expect_success 'commit made to merged branch is reachable from the merge' ' + before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2") + merge_commit=$(git rev-list --all --grep="Merge trunk to b2") + not_reachable=$(git rev-list -1 $before_commit --not $merge_commit) + [ -z "$not_reachable" ] + ' + +test_expect_success 'merging two branches in one commit is detected correctly' ' + f1_commit=$(git rev-list --all --grep="make f1 branch from trunk") + f2_commit=$(git rev-list --all --grep="make f2 branch from trunk") + merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk") + not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit) + [ -z "$not_reachable" ] + ' + test_expect_failure 'everything got merged in the end' ' unmerged=$(git rev-list --all --not master) [ -z "$unmerged" ] diff --git a/t/t9151/make-svnmerge-dump b/t/t9151/make-svnmerge-dump index 3d73f140f8..e1e138cb1a 100644 --- a/t/t9151/make-svnmerge-dump +++ b/t/t9151/make-svnmerge-dump @@ -156,6 +156,89 @@ svn merge ../branches/right --accept postpone i=$(commit $i "non-merge right to trunk 2") cd .. +say "Branching b1 from trunk" +svn update +svn cp trunk branches/b1 +i=$(commit $i "make b1 branch from trunk") + +say "Branching b2 from trunk" +svn update +svn cp trunk branches/b2 +i=$(commit $i "make b2 branch from trunk") + +say "Make a commit to b2" +svn update +cd branches/b2 +echo "b2" > b2file +svn add b2file +i=$(commit $i "b2 update 1") +cd ../.. + +say "Make a commit to b1" +svn update +cd branches/b1 +echo "b1" > b1file +svn add b1file +i=$(commit $i "b1 update 1") +cd ../.. + +say "Merge b1 to trunk" +svn update +cd trunk +svn merge ../branches/b1/ --accept postpone +i=$(commit $i "Merge b1 to trunk") +cd .. + +say "Make a commit to trunk before merging trunk to b2" +svn update +cd trunk +echo "trunk" > trunkfile +svn add trunkfile +i=$(commit $i "trunk commit before merging trunk to b2") +cd .. + +say "Merge trunk to b2" +svn update +cd branches/b2 +svn merge ../../trunk/ --accept postpone +i=$(commit $i "Merge trunk to b2") +cd ../.. + +say "Merge b2 to trunk" +svn update +cd trunk +svn merge ../branches/b2/ --accept postpone +svn resolved b1file +svn resolved trunkfile +i=$(commit $i "Merge b2 to trunk") +cd .. + +say "Creating f1 from trunk with a new file" +svn update +svn cp trunk branches/f1 +cd branches/f1 +echo "f1" > f1file +svn add f1file +cd ../.. +i=$(commit $i "make f1 branch from trunk with a new file") + +say "Creating f2 from trunk with a new file" +svn update +svn cp trunk branches/f2 +cd branches/f2 +echo "f2" > f2file +svn add f2file +cd ../.. +i=$(commit $i "make f2 branch from trunk with a new file") + +say "Merge f1 and f2 to trunk in one go" +svn update +cd trunk +svn merge ../branches/f1/ --accept postpone +svn merge ../branches/f2/ --accept postpone +i=$(commit $i "Merge f1 and f2 to trunk") +cd .. + say "Adding subdirectory to LEFT" svn update cd branches/left @@ -174,8 +257,8 @@ cd .. say "Make PARTIAL branch" svn update -i=$(commit $i "make partial branch") svn cp trunk/subdir branches/partial +i=$(commit $i "make partial branch") say "Make a commit to PARTIAL" svn update @@ -194,13 +277,13 @@ cd ../../ say "Tagging trunk" svn update -i=$(commit $i "tagging v1.0") svn cp trunk tags/v1.0 +i=$(commit $i "tagging v1.0") say "Branching BUGFIX from v1.0" svn update -i=$(commit $i "make bugfix branch from tag") svn cp tags/v1.0 branches/bugfix +i=$(commit $i "make bugfix branch from tag") say "Make a commit to BUGFIX" svn update diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump index ebf386ebd5..47cafcf528 100644 --- a/t/t9151/svn-mergeinfo.dump +++ b/t/t9151/svn-mergeinfo.dump @@ -1633,13 +1633,427 @@ PROPS-END Revision-number: 25 +Prop-content-length: 129 +Content-length: 129 + +K 7 +svn:log +V 31 +(r25) make b1 branch from trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:18:56.084589Z +PROPS-END + +Node-path: branches/b1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 24 +Node-copyfrom-path: trunk + + +Revision-number: 26 +Prop-content-length: 129 +Content-length: 129 + +K 7 +svn:log +V 31 +(r26) make b2 branch from trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:18:59.076940Z +PROPS-END + +Node-path: branches/b2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 25 +Node-copyfrom-path: trunk + + +Revision-number: 27 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 17 +(r27) b2 update 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:01.095762Z +PROPS-END + +Node-path: branches/b2/b2file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 3 +Text-content-md5: 5edbdd57cba621eb3c6e601bf563b4dc +Text-content-sha1: 9d4b38049776bd0a2074d67cad23f8eaed35a3b3 +Content-length: 13 + +PROPS-END +b2 + + +Revision-number: 28 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 17 +(r28) b1 update 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:03.097465Z +PROPS-END + +Node-path: branches/b1/b1file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 3 +Text-content-md5: 08778dfd9ac4f603231896aba7aad523 +Text-content-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f +Content-length: 13 + +PROPS-END +b1 + + +Revision-number: 29 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 23 +(r29) Merge b1 to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:06.073175Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 118 +Content-length: 118 + +K 13 +svn:mergeinfo +V 83 +/branches/b1:25-28 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +PROPS-END + + +Node-path: trunk/b1file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 28 +Node-copyfrom-path: branches/b1/b1file +Text-copy-source-md5: 08778dfd9ac4f603231896aba7aad523 +Text-copy-source-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f + + +Revision-number: 30 +Prop-content-length: 143 +Content-length: 143 + +K 7 +svn:log +V 45 +(r30) trunk commit before merging trunk to b2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:08.096353Z +PROPS-END + +Node-path: trunk/trunkfile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 6 +Text-content-md5: edf45fe5c98c5367733b39bbb2bb20d9 +Text-content-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239 +Content-length: 16 + +PROPS-END +trunk + + +Revision-number: 31 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 23 +(r31) Merge trunk to b2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:11.081541Z +PROPS-END + +Node-path: branches/b2 +Node-kind: dir +Node-action: change +Prop-content-length: 131 +Content-length: 131 + +K 13 +svn:mergeinfo +V 96 +/branches/b1:25-28 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +/trunk:26-30 +PROPS-END + + +Node-path: branches/b2/b1file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 30 +Node-copyfrom-path: trunk/b1file +Text-copy-source-md5: 08778dfd9ac4f603231896aba7aad523 +Text-copy-source-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f + + +Node-path: branches/b2/trunkfile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 30 +Node-copyfrom-path: trunk/trunkfile +Text-copy-source-md5: edf45fe5c98c5367733b39bbb2bb20d9 +Text-copy-source-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239 + + +Revision-number: 32 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 23 +(r32) Merge b2 to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:14.117939Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 138 +Content-length: 138 + +K 13 +svn:mergeinfo +V 102 +/branches/b1:25-28 +/branches/b2:26-31 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +PROPS-END + + +Node-path: trunk/b2file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 31 +Node-copyfrom-path: branches/b2/b2file +Text-copy-source-md5: 5edbdd57cba621eb3c6e601bf563b4dc +Text-copy-source-sha1: 9d4b38049776bd0a2074d67cad23f8eaed35a3b3 + + +Revision-number: 33 +Prop-content-length: 145 +Content-length: 145 + +K 7 +svn:log +V 47 +(r33) make f1 branch from trunk with a new file +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:17.105832Z +PROPS-END + +Node-path: branches/f1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 32 +Node-copyfrom-path: trunk + + +Node-path: branches/f1/f1file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 3 +Text-content-md5: 2b1abc6b6c5c0018851f9f8e6475563b +Text-content-sha1: aece6dfba588900e00d95601d22b4408d49580af +Content-length: 13 + +PROPS-END +f1 + + +Revision-number: 34 +Prop-content-length: 145 +Content-length: 145 + +K 7 +svn:log +V 47 +(r34) make f2 branch from trunk with a new file +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:20.110057Z +PROPS-END + +Node-path: branches/f2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 33 +Node-copyfrom-path: trunk + + +Node-path: branches/f2/f2file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 3 +Text-content-md5: 575c5638d60271457e54ab7d07309502 +Text-content-sha1: 1c49a440c352f3473efa9512255033b94dc7def0 +Content-length: 13 + +PROPS-END +f2 + + +Revision-number: 35 +Prop-content-length: 128 +Content-length: 128 + +K 7 +svn:log +V 30 +(r35) Merge f1 and f2 to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:24.081490Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 173 +Content-length: 173 + +K 13 +svn:mergeinfo +V 137 +/branches/b1:25-28 +/branches/b2:26-31 +/branches/f1:33-34 +/branches/f2:34 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +PROPS-END + + +Node-path: trunk/f1file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 34 +Node-copyfrom-path: branches/f1/f1file +Text-copy-source-md5: 2b1abc6b6c5c0018851f9f8e6475563b +Text-copy-source-sha1: aece6dfba588900e00d95601d22b4408d49580af + + +Node-path: trunk/f2file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 34 +Node-copyfrom-path: branches/f2/f2file +Text-copy-source-md5: 575c5638d60271457e54ab7d07309502 +Text-copy-source-sha1: 1c49a440c352f3473efa9512255033b94dc7def0 + + +Revision-number: 36 Prop-content-length: 135 Content-length: 135 K 7 svn:log V 37 -(r25) add subdirectory to left branch +(r36) add subdirectory to left branch K 10 svn:author V 3 @@ -1647,7 +2061,7 @@ adm K 8 svn:date V 27 -2010-01-19T04:14:46.052649Z +2010-02-22T06:19:26.113516Z PROPS-END Node-path: branches/left/subdir @@ -1672,14 +2086,14 @@ PROPS-END Yeehaw -Revision-number: 26 +Revision-number: 37 Prop-content-length: 123 Content-length: 123 K 7 svn:log V 25 -(r26) merge left to trunk +(r37) merge left to trunk K 10 svn:author V 3 @@ -1687,19 +2101,23 @@ adm K 8 svn:date V 27 -2010-01-19T04:14:49.040783Z +2010-02-22T06:19:29.073699Z PROPS-END Node-path: trunk Node-kind: dir Node-action: change -Prop-content-length: 99 -Content-length: 99 +Prop-content-length: 173 +Content-length: 173 K 13 svn:mergeinfo -V 64 -/branches/left:2-25 +V 137 +/branches/b1:25-28 +/branches/b2:26-31 +/branches/f1:33-34 +/branches/f2:34 +/branches/left:2-36 /branches/left-sub:4-19 /branches/right:2-22 PROPS-END @@ -1708,18 +2126,18 @@ PROPS-END Node-path: trunk/subdir Node-kind: dir Node-action: add -Node-copyfrom-rev: 25 +Node-copyfrom-rev: 36 Node-copyfrom-path: branches/left/subdir -Revision-number: 27 -Prop-content-length: 118 -Content-length: 118 +Revision-number: 38 +Prop-content-length: 123 +Content-length: 123 K 7 svn:log -V 20 -(r28) partial update +V 25 +(r38) make partial branch K 10 svn:author V 3 @@ -1727,16 +2145,34 @@ adm K 8 svn:date V 27 -2010-01-19T04:14:53.049037Z +2010-02-22T06:19:32.072243Z PROPS-END Node-path: branches/partial Node-kind: dir Node-action: add -Node-copyfrom-rev: 26 +Node-copyfrom-rev: 37 Node-copyfrom-path: trunk/subdir +Revision-number: 39 +Prop-content-length: 118 +Content-length: 118 + +K 7 +svn:log +V 20 +(r39) partial update +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:34.097961Z +PROPS-END + Node-path: branches/partial/palindromes Node-kind: file Node-action: add @@ -1750,14 +2186,14 @@ PROPS-END racecar -Revision-number: 28 +Revision-number: 40 Prop-content-length: 126 Content-length: 126 K 7 svn:log V 28 -(r29) merge partial to trunk +(r40) merge partial to trunk K 10 svn:author V 3 @@ -1765,21 +2201,25 @@ adm K 8 svn:date V 27 -2010-01-19T04:14:56.041526Z +2010-02-22T06:19:37.080211Z PROPS-END Node-path: trunk/subdir Node-kind: dir Node-action: change -Prop-content-length: 142 -Content-length: 142 +Prop-content-length: 246 +Content-length: 246 K 13 svn:mergeinfo -V 106 -/branches/left/subdir:2-25 +V 210 +/branches/b1/subdir:25-28 +/branches/b2/subdir:26-31 +/branches/f1/subdir:33-34 +/branches/f2/subdir:34 +/branches/left/subdir:2-36 /branches/left-sub/subdir:4-19 -/branches/partial:27 +/branches/partial:38-39 /branches/right/subdir:2-22 PROPS-END @@ -1787,20 +2227,20 @@ PROPS-END Node-path: trunk/subdir/palindromes Node-kind: file Node-action: add -Node-copyfrom-rev: 27 +Node-copyfrom-rev: 39 Node-copyfrom-path: branches/partial/palindromes Text-copy-source-md5: 5d1c2024fb5efc4eef812856df1b080c Text-copy-source-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69 -Revision-number: 29 -Prop-content-length: 131 -Content-length: 131 +Revision-number: 41 +Prop-content-length: 116 +Content-length: 116 K 7 svn:log -V 33 -(r31) make bugfix branch from tag +V 18 +(r41) tagging v1.0 K 10 svn:author V 3 @@ -1808,24 +2248,24 @@ adm K 8 svn:date V 27 -2010-01-19T04:15:00.039761Z +2010-02-22T06:19:40.083460Z PROPS-END Node-path: tags/v1.0 Node-kind: dir Node-action: add -Node-copyfrom-rev: 28 +Node-copyfrom-rev: 40 Node-copyfrom-path: trunk -Revision-number: 30 -Prop-content-length: 120 -Content-length: 120 +Revision-number: 42 +Prop-content-length: 131 +Content-length: 131 K 7 svn:log -V 22 -(r32) commit to bugfix +V 33 +(r42) make bugfix branch from tag K 10 svn:author V 3 @@ -1833,16 +2273,34 @@ adm K 8 svn:date V 27 -2010-01-19T04:15:03.043218Z +2010-02-22T06:19:43.118075Z PROPS-END Node-path: branches/bugfix Node-kind: dir Node-action: add -Node-copyfrom-rev: 29 +Node-copyfrom-rev: 41 Node-copyfrom-path: tags/v1.0 +Revision-number: 43 +Prop-content-length: 120 +Content-length: 120 + +K 7 +svn:log +V 22 +(r43) commit to bugfix +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:45.079536Z +PROPS-END + Node-path: branches/bugfix/subdir/palindromes Node-kind: file Node-action: change @@ -1855,14 +2313,14 @@ racecar kayak -Revision-number: 31 +Revision-number: 44 Prop-content-length: 125 Content-length: 125 K 7 svn:log V 27 -(r33) Merge BUGFIX to TRUNK +(r44) Merge BUGFIX to TRUNK K 10 svn:author V 3 @@ -1870,41 +2328,49 @@ adm K 8 svn:date V 27 -2010-01-19T04:15:06.043723Z +2010-02-22T06:19:48.078914Z PROPS-END Node-path: trunk Node-kind: dir Node-action: change -Prop-content-length: 133 -Content-length: 133 +Prop-content-length: 210 +Content-length: 210 K 13 svn:mergeinfo -V 98 -/branches/bugfix:30 -/branches/left:2-25 +V 174 +/branches/b1:25-28 +/branches/b2:26-31 +/branches/bugfix:42-43 +/branches/f1:33-34 +/branches/f2:34 +/branches/left:2-36 /branches/left-sub:4-19 /branches/right:2-22 -/tags/v1.0:29 +/tags/v1.0:41 PROPS-END Node-path: trunk/subdir Node-kind: dir Node-action: change -Prop-content-length: 190 -Content-length: 190 +Prop-content-length: 297 +Content-length: 297 K 13 svn:mergeinfo -V 154 -/branches/bugfix/subdir:30 -/branches/left/subdir:2-25 +V 261 +/branches/b1/subdir:25-28 +/branches/b2/subdir:26-31 +/branches/bugfix/subdir:42-43 +/branches/f1/subdir:33-34 +/branches/f2/subdir:34 +/branches/left/subdir:2-36 /branches/left-sub/subdir:4-19 -/branches/partial:27 +/branches/partial:38-39 /branches/right/subdir:2-22 -/tags/v1.0/subdir:29 +/tags/v1.0/subdir:41 PROPS-END diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index fc3795dc98..ee39b36d78 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -7,14 +7,14 @@ test_description='Test export of commits to CVS' . ./test-lib.sh if ! test_have_prereq PERL; then - say 'skipping git cvsexportcommit tests, perl not available' + skip_all='skipping git cvsexportcommit tests, perl not available' test_done fi cvs >/dev/null 2>&1 if test $? -ne 1 then - say 'skipping git cvsexportcommit tests, cvs not found' + skip_all='skipping git cvsexportcommit tests, cvs not found' test_done fi @@ -63,10 +63,10 @@ test_expect_success \ check_entries B "newfile2.txt/1.1/" && check_entries C "newfile3.png/1.1/-kb" && check_entries D "newfile4.png/1.1/-kb" && - diff A/newfile1.txt ../A/newfile1.txt && - diff B/newfile2.txt ../B/newfile2.txt && - diff C/newfile3.png ../C/newfile3.png && - diff D/newfile4.png ../D/newfile4.png + test_cmp A/newfile1.txt ../A/newfile1.txt && + test_cmp B/newfile2.txt ../B/newfile2.txt && + test_cmp C/newfile3.png ../C/newfile3.png && + test_cmp D/newfile4.png ../D/newfile4.png )' test_expect_success \ @@ -89,10 +89,10 @@ test_expect_success \ check_entries D "newfile4.png/1.2/-kb" && check_entries E "newfile5.txt/1.1/" && check_entries F "newfile6.png/1.1/-kb" && - diff A/newfile1.txt ../A/newfile1.txt && - diff D/newfile4.png ../D/newfile4.png && - diff E/newfile5.txt ../E/newfile5.txt && - diff F/newfile6.png ../F/newfile6.png + test_cmp A/newfile1.txt ../A/newfile1.txt && + test_cmp D/newfile4.png ../D/newfile4.png && + test_cmp E/newfile5.txt ../E/newfile5.txt && + test_cmp F/newfile6.png ../F/newfile6.png )' # Should fail (but only on the git cvsexportcommit stage) @@ -137,9 +137,9 @@ test_expect_success \ check_entries D "" && check_entries E "newfile5.txt/1.1/" && check_entries F "newfile6.png/1.1/-kb" && - diff A/newfile1.txt ../A/newfile1.txt && - diff E/newfile5.txt ../E/newfile5.txt && - diff F/newfile6.png ../F/newfile6.png + test_cmp A/newfile1.txt ../A/newfile1.txt && + test_cmp E/newfile5.txt ../E/newfile5.txt && + test_cmp F/newfile6.png ../F/newfile6.png )' test_expect_success \ @@ -155,8 +155,8 @@ test_expect_success \ check_entries D "" && check_entries E "newfile5.txt/1.1/" && check_entries F "newfile6.png/1.1/-kb" && - diff E/newfile5.txt ../E/newfile5.txt && - diff F/newfile6.png ../F/newfile6.png + test_cmp E/newfile5.txt ../E/newfile5.txt && + test_cmp F/newfile6.png ../F/newfile6.png )' test_expect_success \ diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 131f032988..2aeed7bd06 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -166,6 +166,63 @@ test_expect_success \ test `git rev-parse --verify master:file2` \ = `git rev-parse --verify verify--import-marks:copy-of-file2`' +test_tick +mt=$(git hash-object --stdin < /dev/null) +: >input.blob +: >marks.exp +: >tree.exp + +cat >input.commit <<EOF +commit refs/heads/verify--dump-marks +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +test the sparse array dumping routines with exponentially growing marks +COMMIT +EOF + +i=0 +l=4 +m=6 +n=7 +while test "$i" -lt 27; do + cat >>input.blob <<EOF +blob +mark :$l +data 0 +blob +mark :$m +data 0 +blob +mark :$n +data 0 +EOF + echo "M 100644 :$l l$i" >>input.commit + echo "M 100644 :$m m$i" >>input.commit + echo "M 100644 :$n n$i" >>input.commit + + echo ":$l $mt" >>marks.exp + echo ":$m $mt" >>marks.exp + echo ":$n $mt" >>marks.exp + + printf "100644 blob $mt\tl$i\n" >>tree.exp + printf "100644 blob $mt\tm$i\n" >>tree.exp + printf "100644 blob $mt\tn$i\n" >>tree.exp + + l=$(($l + $l)) + m=$(($m + $m)) + n=$(($l + $n)) + + i=$((1 + $i)) +done + +sort tree.exp > tree.exp_s + +test_expect_success 'A: export marks with large values' ' + cat input.blob input.commit | git fast-import --export-marks=marks.large && + git ls-tree refs/heads/verify--dump-marks >tree.out && + test_cmp tree.exp_s tree.out && + test_cmp marks.exp marks.large' + ### ### series B ### diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index daef2d6c23..36c457e7f2 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -11,17 +11,17 @@ cvs CLI client via git-cvsserver server' . ./test-lib.sh if ! test_have_prereq PERL; then - say 'skipping git cvsserver tests, perl not available' + skip_all='skipping git cvsserver tests, perl not available' test_done fi cvs >/dev/null 2>&1 if test $? -ne 1 then - say 'skipping git-cvsserver tests, cvs not found' + skip_all='skipping git-cvsserver tests, cvs not found' test_done fi "$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { - say 'skipping git-cvsserver tests, Perl SQLite interface unavailable' + skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable' test_done } @@ -48,7 +48,9 @@ test_expect_success 'setup' ' git pull secondroot master && git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && - GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" + GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" && + GIT_DIR="$SERVERDIR" git config gitcvs.authdb "$SERVERDIR/auth.db" && + echo cvsuser:cvGVEarMLnhlA > "$SERVERDIR/auth.db" ' # note that cvs doesn't accept absolute pathnames @@ -94,6 +96,14 @@ git END VERIFICATION REQUEST EOF +cat >login-git-ok <<EOF +BEGIN VERIFICATION REQUEST +$SERVERDIR +cvsuser +Ah<Z:yZZ30 e +END VERIFICATION REQUEST +EOF + test_expect_success 'pserver authentication' \ 'cat request-anonymous | git-cvsserver pserver >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$"' @@ -107,6 +117,10 @@ test_expect_success 'pserver authentication failure (non-anonymous user)' \ fi && sed -ne \$p log | grep "^I HATE YOU\$"' +test_expect_success 'pserver authentication success (non-anonymous user with password)' \ + 'cat login-git-ok | git-cvsserver pserver >log 2>&1 && + sed -ne \$p log | grep "^I LOVE YOU\$"' + test_expect_success 'pserver authentication (login)' \ 'cat login-anonymous | git-cvsserver pserver >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$"' @@ -435,7 +449,7 @@ test_expect_success 'cvs update (-p)' ' rm -f failures && for i in merge no-lf empty really-empty; do GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out - diff $i.out ../$i >>failures 2>&1 + test_cmp $i.out ../$i >>failures 2>&1 done && test -z "$(cat failures)" ' diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index ed7b513f3e..925bd0fbed 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -41,16 +41,16 @@ not_present() { cvs >/dev/null 2>&1 if test $? -ne 1 then - say 'skipping git-cvsserver tests, cvs not found' + skip_all='skipping git-cvsserver tests, cvs not found' test_done fi if ! test_have_prereq PERL then - say 'skipping git-cvsserver tests, perl not available' + skip_all='skipping git-cvsserver tests, perl not available' test_done fi "$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { - say 'skipping git-cvsserver tests, Perl SQLite interface unavailable' + skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable' test_done } diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 63b6b060e6..4f2b9b062b 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -647,4 +647,33 @@ test_expect_success \ gitweb_run "p=.git;a=summary"' test_debug 'cat gitweb.log' +# ---------------------------------------------------------------------- +# syntax highlighting + +cat >>gitweb_config.perl <<\EOF +$feature{'highlight'}{'override'} = 1; +EOF + +highlight --version >/dev/null 2>&1 +if [ $? -eq 127 ]; then + say "Skipping syntax highlighting test, because 'highlight' was not found" +else + test_set_prereq HIGHLIGHT +fi + +test_expect_success HIGHLIGHT \ + 'syntax highlighting (no highlight)' \ + 'git config gitweb.highlight yes && + gitweb_run "p=.git;a=blob;f=file"' +test_debug 'cat gitweb.log' + +test_expect_success HIGHLIGHT \ + 'syntax highlighting (highlighted)' \ + 'git config gitweb.highlight yes && + echo "#!/usr/bin/sh" > test.sh && + git add test.sh && + git commit -m "Add test.sh" && + gitweb_run "p=.git;a=blob;f=test.sh"' +test_debug 'cat gitweb.log' + test_done diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh index d196cc5ca9..2487da1296 100755 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -15,9 +15,10 @@ code and message.' # ---------------------------------------------------------------------- # snapshot settings -test_commit \ - 'SnapshotTests' \ - 'i can has snapshot?' +test_expect_success 'setup' " + test_commit 'SnapshotTests' 'i can has snapshot?' +" + cat >>gitweb_config.perl <<\EOF $feature{'snapshot'}{'override'} = 0; diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 363345faef..2eff9cd68c 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -4,7 +4,7 @@ test_description='git cvsimport basic tests' . ./lib-cvs.sh if ! test_have_prereq PERL; then - say 'skipping git cvsimport tests, perl not available' + skip_all='skipping git cvsimport tests, perl not available' test_done fi @@ -47,13 +47,20 @@ EOF test_expect_success 'import a trivial module' ' - git cvsimport -a -z 0 -C module-git module && + git cvsimport -a -R -z 0 -C module-git module && test_cmp module-cvs/o_fortuna module-git/o_fortuna ' test_expect_success 'pack refs' 'cd module-git && git gc && cd ..' +test_expect_success 'initial import has correct .git/cvs-revisions' ' + + (cd module-git && + git log --format="o_fortuna 1.1 %H" -1) > expected && + test_cmp expected module-git/.git/cvs-revisions +' + test_expect_success 'update cvs module' ' cd module-cvs && @@ -86,13 +93,21 @@ EOF test_expect_success 'update git module' ' cd module-git && - git cvsimport -a -z 0 module && + git cvsimport -a -R -z 0 module && git merge origin && cd .. && test_cmp module-cvs/o_fortuna module-git/o_fortuna ' +test_expect_success 'update has correct .git/cvs-revisions' ' + + (cd module-git && + git log --format="o_fortuna 1.1 %H" -1 HEAD^ && + git log --format="o_fortuna 1.2 %H" -1 HEAD) > expected && + test_cmp expected module-git/.git/cvs-revisions +' + test_expect_success 'update cvs module' ' cd module-cvs && @@ -107,13 +122,22 @@ test_expect_success 'cvsimport.module config works' ' cd module-git && git config cvsimport.module module && - git cvsimport -a -z0 && + git cvsimport -a -R -z0 && git merge origin && cd .. && test_cmp module-cvs/tick module-git/tick ' +test_expect_success 'second update has correct .git/cvs-revisions' ' + + (cd module-git && + git log --format="o_fortuna 1.1 %H" -1 HEAD^^ && + git log --format="o_fortuna 1.2 %H" -1 HEAD^ + git log --format="tick 1.1 %H" -1 HEAD) > expected && + test_cmp expected module-git/.git/cvs-revisions +' + test_expect_success 'import from a CVS working tree' ' $CVS co -d import-from-wt module && @@ -126,6 +150,12 @@ test_expect_success 'import from a CVS working tree' ' ' +test_expect_success 'no .git/cvs-revisions created by default' ' + + ! test -e import-from-wt/.git/cvs-revisions + +' + test_expect_success 'test entire HEAD' 'test_cmp_branch_tree master' test_done diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh index 8686086dde..3787186703 100755 --- a/t/t9700-perl-git.sh +++ b/t/t9700-perl-git.sh @@ -7,12 +7,12 @@ test_description='perl interface (Git.pm)' . ./test-lib.sh if ! test_have_prereq PERL; then - say 'skipping perl interface tests, perl not available' + skip_all='skipping perl interface tests, perl not available' test_done fi "$PERL_PATH" -MTest::More -e 0 2>/dev/null || { - say "Perl Test::More unavailable, skipping test" + skip_all="Perl Test::More unavailable, skipping test" test_done } @@ -46,6 +46,9 @@ test_expect_success \ git config --add test.int 2k ' +# The external test will outputs its own plan +test_external_has_tap=1 + test_external_without_stderr \ 'Perl API' \ "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 666722d9bf..671f38db2b 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -7,6 +7,13 @@ use strict; use Test::More qw(no_plan); +BEGIN { + # t9700-perl-git.sh kicks off our testing, so we have to go from + # there. + Test::More->builder->current_test(1); + Test::More->builder->no_ending(1); +} + use Cwd; use File::Basename; @@ -105,3 +112,8 @@ my $last_commit = $r2->command_oneline(qw(rev-parse --verify HEAD)); like($last_commit, qr/^[0-9a-fA-F]{40}$/, 'rev-parse returned hash'); my $dir_commit = $r2->command_oneline('log', '-n1', '--pretty=format:%H', '.'); isnt($last_commit, $dir_commit, 'log . does not show last commit'); + +printf "1..%d\n", Test::More->builder->current_test; + +my $is_passing = eval { Test::More->is_passing }; +exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/; diff --git a/t/test-lib.sh b/t/test-lib.sh index a0e396a952..e8f21d577c 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -2,6 +2,18 @@ # # Copyright (c) 2005 Junio C Hamano # +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ . # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. @@ -54,12 +66,15 @@ unset GIT_OBJECT_DIRECTORY unset GIT_CEILING_DIRECTORIES unset SHA1_FILE_DIRECTORIES unset SHA1_FILE_DIRECTORY +unset GIT_NOTES_REF +unset GIT_NOTES_DISPLAY_REF +unset GIT_NOTES_REWRITE_REF +unset GIT_NOTES_REWRITE_MODE GIT_MERGE_VERBOSITY=5 export GIT_MERGE_VERBOSITY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR -GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u} # Protect ourselves from common misconfiguration to export # CDPATH into the environment @@ -112,14 +127,13 @@ do -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; -q|--q|--qu|--qui|--quie|--quiet) - quiet=t; shift ;; + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; --with-dashes) with_dashes=t; shift ;; --no-color) color=; shift ;; - --no-python) - # noop now... - shift ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) valgrind=t; verbose=t; shift ;; --tee) @@ -145,7 +159,7 @@ if test -n "$color"; then *) test -n "$quiet" && return;; esac shift - printf "* %s" "$*" + printf "%s" "$*" tput sgr0 echo ) @@ -154,7 +168,7 @@ else say_color() { test -z "$1" && test -n "$quiet" && return shift - echo "* $*" + echo "$*" } fi @@ -191,6 +205,8 @@ test_fixed=0 test_broken=0 test_success=0 +test_external_has_tap=0 + die () { code=$? if test -n "$GIT_EXIT_OK" @@ -324,25 +340,25 @@ test_have_prereq () { test_ok_ () { test_success=$(($test_success + 1)) - say_color "" " ok $test_count: $@" + say_color "" "ok $test_count - $@" } test_failure_ () { test_failure=$(($test_failure + 1)) - say_color error "FAIL $test_count: $1" + say_color error "not ok - $test_count $1" shift - echo "$@" | sed -e 's/^/ /' + echo "$@" | sed -e 's/^/# /' test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; } } test_known_broken_ok_ () { test_fixed=$(($test_fixed+1)) - say_color "" " FIXED $test_count: $@" + say_color "" "ok $test_count - $@ # TODO known breakage" } test_known_broken_failure_ () { test_broken=$(($test_broken+1)) - say_color skip " still broken $test_count: $@" + say_color skip "not ok $test_count - $@ # TODO known breakage" } test_debug () { @@ -350,8 +366,13 @@ test_debug () { } test_run_ () { + test_cleanup=: eval >&3 2>&4 "$1" - eval_ret="$?" + eval_ret=$? + eval >&3 2>&4 "$test_cleanup" + if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then + echo "" + fi return 0 } @@ -363,6 +384,7 @@ test_skip () { case $this_test.$test_count in $skp) to_skip=t + break esac done if test -z "$to_skip" && test -n "$prereq" && @@ -373,7 +395,7 @@ test_skip () { case "$to_skip" in t) say_color skip >&3 "skipping test: $@" - say_color skip "skip $test_count: $1" + say_color skip "ok $test_count # skip $1" : true ;; *) @@ -439,7 +461,7 @@ test_expect_code () { # test_external runs external test scripts that provide continuous # test output about their progress, and succeeds/fails on # zero/non-zero exit code. It outputs the test output on stdout even -# in non-verbose mode, and announces the external script with "* run +# in non-verbose mode, and announces the external script with "# run # <n>: ..." before running it. When providing relative paths, keep in # mind that all scripts run in "trash directory". # Usage: test_external description command arguments... @@ -454,16 +476,29 @@ test_external () { then # Announce the script to reduce confusion about the # test output that follows. - say_color "" " run $test_count: $descr ($*)" + say_color "" "# run $test_count: $descr ($*)" + # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG + # to be able to use them in script + export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG # Run command; redirect its stderr to &4 as in # test_run_, but keep its stdout on our stdout even in # non-verbose mode. "$@" 2>&4 if [ "$?" = 0 ] then - test_ok_ "$descr" + if test $test_external_has_tap -eq 0; then + test_ok_ "$descr" + else + say_color "" "# test_external test $descr was ok" + test_success=$(($test_success + 1)) + fi else - test_failure_ "$descr" "$@" + if test $test_external_has_tap -eq 0; then + test_failure_ "$descr" "$@" + else + say_color error "# test_external test $descr failed: $@" + test_failure=$(($test_failure + 1)) + fi fi fi } @@ -479,19 +514,30 @@ test_external_without_stderr () { [ -f "$stderr" ] || error "Internal error: $stderr disappeared." descr="no stderr: $1" shift - say >&3 "expecting no stderr from previous command" + say >&3 "# expecting no stderr from previous command" if [ ! -s "$stderr" ]; then rm "$stderr" - test_ok_ "$descr" + + if test $test_external_has_tap -eq 0; then + test_ok_ "$descr" + else + say_color "" "# test_external_without_stderr test $descr was ok" + test_success=$(($test_success + 1)) + fi else if [ "$verbose" = t ]; then - output=`echo; echo Stderr is:; cat "$stderr"` + output=`echo; echo "# Stderr is:"; cat "$stderr"` else output= fi # rm first in case test_failure exits. rm "$stderr" - test_failure_ "$descr" "$@" "$output" + if test $test_external_has_tap -eq 0; then + test_failure_ "$descr" "$@" "$output" + else + say_color error "# test_external_without_stderr test $descr failed: $@: $output" + test_failure=$(($test_failure + 1)) + fi fi } @@ -512,6 +558,22 @@ test_must_fail () { test $? -gt 0 -a $? -le 129 -o $? -gt 192 } +# Similar to test_must_fail, but tolerates success, too. This is +# meant to be used in contexts like: +# +# test_expect_success 'some command works without configuration' ' +# test_might_fail git config --unset all.configuration && +# do something +# ' +# +# Writing "git config --unset all.configuration || :" would be wrong, +# because we want to notice if it fails due to segv. + +test_might_fail () { + "$@" + test $? -ge 0 -a $? -le 129 -o $? -gt 192 +} + # test_cmp is a helper function to compare actual and expected output. # You can use it like: # @@ -529,6 +591,31 @@ test_cmp() { $GIT_TEST_CMP "$@" } +# This function can be used to schedule some commands to be run +# unconditionally at the end of the test to restore sanity: +# +# test_expect_success 'test core.capslock' ' +# git config core.capslock true && +# test_when_finished "git config --unset core.capslock" && +# hello world +# ' +# +# That would be roughly equivalent to +# +# test_expect_success 'test core.capslock' ' +# git config core.capslock true && +# hello world +# git config --unset core.capslock +# ' +# +# except that the greeting and config --unset must both succeed for +# the test to pass. + +test_when_finished () { + test_cleanup="{ $* + } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" +} + # Most tests can use the created repository, but some may need to create more. # Usage: test_create_repo <directory> test_create_repo () { @@ -548,7 +635,7 @@ test_done () { GIT_EXIT_OK=t test_results_dir="$TEST_DIRECTORY/test-results" mkdir -p "$test_results_dir" - test_results_path="$test_results_dir/${0%.sh}-$$" + test_results_path="$test_results_dir/${0%.sh}-$$.counts" echo "total $test_count" >> $test_results_path echo "success $test_success" >> $test_results_path @@ -559,18 +646,24 @@ test_done () { if test "$test_fixed" != 0 then - say_color pass "fixed $test_fixed known breakage(s)" + say_color pass "# fixed $test_fixed known breakage(s)" fi if test "$test_broken" != 0 then - say_color error "still have $test_broken known breakage(s)" + say_color error "# still have $test_broken known breakage(s)" msg="remaining $(($test_count-$test_broken)) test(s)" else msg="$test_count test(s)" fi case "$test_failure" in 0) - say_color pass "passed all $msg" + # Maybe print SKIP message + [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all" + + if test $test_external_has_tap -eq 0; then + say_color pass "# passed all $msg" + say "1..$test_count$skip_all" + fi test -d "$remove_trash" && cd "$(dirname "$remove_trash")" && @@ -579,7 +672,11 @@ test_done () { exit 0 ;; *) - say_color error "failed $test_failure among $msg" + if test $test_external_has_tap -eq 0; then + say_color error "# failed $test_failure among $msg" + say "1..$test_count" + fi + exit 1 ;; esac @@ -678,6 +775,16 @@ export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOB . ../GIT-BUILD-OPTIONS +if test -z "$GIT_TEST_CMP" +then + if test -n "$GIT_TEST_CMP_USE_COPIED_CONTEXT" + then + GIT_TEST_CMP="$DIFF -c" + else + GIT_TEST_CMP="$DIFF -u" + fi +fi + GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git export GITPERLLIB test -d ../templates/blt || { @@ -722,18 +829,10 @@ this_test=${0##*/} this_test=${this_test%%-*} for skp in $GIT_SKIP_TESTS do - to_skip= - for skp in $GIT_SKIP_TESTS - do - case "$this_test" in - $skp) - to_skip=t - esac - done - case "$to_skip" in - t) + case "$this_test" in + $skp) say_color skip >&3 "skipping test $this_test altogether" - say_color skip "skip all tests in $this_test" + skip_all="skip all tests in $this_test" test_done esac done @@ -36,43 +36,50 @@ struct tag *lookup_tag(const unsigned char *sha1) return (struct tag *) obj; } +static unsigned long parse_tag_date(const char *buf, const char *tail) +{ + const char *dateptr; + + while (buf < tail && *buf++ != '>') + /* nada */; + if (buf >= tail) + return 0; + dateptr = buf; + while (buf < tail && *buf++ != '\n') + /* nada */; + if (buf >= tail) + return 0; + /* dateptr < buf && buf[-1] == '\n', so strtoul will stop at buf-1 */ + return strtoul(dateptr, NULL, 10); +} + int parse_tag_buffer(struct tag *item, void *data, unsigned long size) { - int typelen, taglen; unsigned char sha1[20]; - const char *type_line, *tag_line, *sig_line; char type[20]; - const char *start = data; + const char *bufptr = data; + const char *tail = bufptr + size; + const char *nl; - if (item->object.parsed) - return 0; - item->object.parsed = 1; + if (item->object.parsed) + return 0; + item->object.parsed = 1; if (size < 64) return -1; - if (memcmp("object ", data, 7) || get_sha1_hex((char *) data + 7, sha1)) + if (memcmp("object ", bufptr, 7) || get_sha1_hex(bufptr + 7, sha1) || bufptr[47] != '\n') return -1; + bufptr += 48; /* "object " + sha1 + "\n" */ - type_line = (char *) data + 48; - if (memcmp("\ntype ", type_line-1, 6)) + if (prefixcmp(bufptr, "type ")) return -1; - - tag_line = memchr(type_line, '\n', size - (type_line - start)); - if (!tag_line || memcmp("tag ", ++tag_line, 4)) + bufptr += 5; + nl = memchr(bufptr, '\n', tail - bufptr); + if (!nl || sizeof(type) <= (nl - bufptr)) return -1; - - sig_line = memchr(tag_line, '\n', size - (tag_line - start)); - if (!sig_line) - return -1; - sig_line++; - - typelen = tag_line - type_line - strlen("type \n"); - if (typelen >= 20) - return -1; - memcpy(type, type_line + 5, typelen); - type[typelen] = '\0'; - taglen = sig_line - tag_line - strlen("tag \n"); - item->tag = xmemdupz(tag_line + 4, taglen); + strncpy(type, bufptr, nl - bufptr); + type[nl - bufptr] = '\0'; + bufptr = nl + 1; if (!strcmp(type, blob_type)) { item->tagged = &lookup_blob(sha1)->object; @@ -87,6 +94,20 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) item->tagged = NULL; } + if (prefixcmp(bufptr, "tag ")) + return -1; + bufptr += 4; + nl = memchr(bufptr, '\n', tail - bufptr); + if (!nl) + return -1; + item->tag = xmemdupz(bufptr, nl - bufptr); + bufptr = nl + 1; + + if (!prefixcmp(bufptr, "tagger ")) + item->date = parse_tag_date(bufptr, tail); + else + item->date = 0; + return 0; } @@ -9,7 +9,7 @@ struct tag { struct object object; struct object *tagged; char *tag; - char *signature; /* not actually implemented */ + unsigned long date; }; extern struct tag *lookup_tag(const unsigned char *sha1); diff --git a/templates/Makefile b/templates/Makefile index 408f0137a8..d22a71a399 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -11,6 +11,16 @@ prefix ?= $(HOME) template_instdir ?= $(prefix)/share/git-core/templates # DESTDIR= +ifndef SHELL_PATH + SHELL_PATH = /bin/sh +endif +ifndef PERL_PATH + PERL_PATH = perl +endif + +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) + # Shell quote (do not use $(call) to accommodate ancient setups); DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) template_instdir_SQ = $(subst ','\'',$(template_instdir)) @@ -33,8 +43,11 @@ boilerplates.made : $(bpsrc) case "$$boilerplate" in \ *--) continue;; \ esac && \ - cp $$boilerplate blt/$$dst && \ - if test -x "blt/$$dst"; then rx=rx; else rx=r; fi && \ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ + -e 's|@PERL_PATH@|$(PERL_PATH_SQ)|g' $$boilerplate > \ + blt/$$dst && \ + if test -x "$$boilerplate"; then rx=rx; else rx=r; fi && \ chmod a+$$rx "blt/$$dst" || exit; \ done && \ date >$@ diff --git a/templates/hooks--commit-msg.sample b/templates/hooks--commit-msg.sample index 6ef1d29d09..b58d1184a9 100755 --- a/templates/hooks--commit-msg.sample +++ b/templates/hooks--commit-msg.sample @@ -1,7 +1,7 @@ #!/bin/sh # # An example hook script to check the commit log message. -# Called by git-commit with one argument, the name of the file +# Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. diff --git a/templates/hooks--post-update.sample b/templates/hooks--post-update.sample index 5323b56b81..ec17ec1939 100755 --- a/templates/hooks--post-update.sample +++ b/templates/hooks--post-update.sample @@ -5,4 +5,4 @@ # # To enable this hook, rename this file to "post-update". -exec git-update-server-info +exec git update-server-info diff --git a/templates/hooks--pre-commit.sample b/templates/hooks--pre-commit.sample index 439eefda51..b187c4bb1f 100755 --- a/templates/hooks--pre-commit.sample +++ b/templates/hooks--pre-commit.sample @@ -1,13 +1,13 @@ #!/bin/sh # # An example hook script to verify what is about to be committed. -# Called by git-commit with no arguments. The hook should +# Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". -if git-rev-parse --verify HEAD >/dev/null 2>&1 +if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else diff --git a/templates/hooks--pre-rebase.sample b/templates/hooks--pre-rebase.sample index be1b06e250..053f1111c0 100755 --- a/templates/hooks--pre-rebase.sample +++ b/templates/hooks--pre-rebase.sample @@ -2,7 +2,7 @@ # # Copyright (c) 2006, 2008 Junio C Hamano # -# The "pre-rebase" hook is run just before "git-rebase" starts doing +# The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # @@ -43,7 +43,7 @@ git show-ref -q "$topic" || { } # Is topic fully merged to master? -not_in_master=`git-rev-list --pretty=oneline ^master "$topic"` +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." @@ -51,11 +51,11 @@ then fi # Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git-rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git-rev-list ^master ${publish} | sort` +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then - not_in_topic=`git-rev-list "^$topic" master` + not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" @@ -64,8 +64,8 @@ then exit 0 fi else - not_in_next=`git-rev-list --pretty=oneline ^${publish} "$topic"` - perl -e ' + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + @PERL_PATH@ -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { @@ -157,13 +157,13 @@ B to be deleted. To compute (1): - git-rev-list ^master ^topic next - git-rev-list ^master next + git rev-list ^master ^topic next + git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): - git-rev-list master..topic + git rev-list master..topic if this is empty, it is fully merged to "master". diff --git a/templates/hooks--prepare-commit-msg.sample b/templates/hooks--prepare-commit-msg.sample index 365242499d..86b8f227ec 100755 --- a/templates/hooks--prepare-commit-msg.sample +++ b/templates/hooks--prepare-commit-msg.sample @@ -1,7 +1,7 @@ #!/bin/sh # # An example hook script to prepare the commit log message. -# Called by git-commit with the name of the file that has the +# Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, @@ -22,10 +22,10 @@ case "$2,$3" in merge,) - perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + @PERL_PATH@ -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) -# perl -i.bak -pe ' +# @PERL_PATH@ -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; diff --git a/templates/hooks--update.sample b/templates/hooks--update.sample index fd63b2d662..71ab04edc0 100755 --- a/templates/hooks--update.sample +++ b/templates/hooks--update.sample @@ -1,7 +1,7 @@ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. -# Called by git-receive-pack with arguments: refname sha1-old sha1-new +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # @@ -64,7 +64,7 @@ zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else - newrev_type=$(git-cat-file -t $newrev) + newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in diff --git a/templates/info--exclude b/templates/info--exclude index 2c87b72dff..a5196d1be8 100644 --- a/templates/info--exclude +++ b/templates/info--exclude @@ -1,4 +1,4 @@ -# git-ls-files --others --exclude-from=.git/info/exclude +# git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): diff --git a/test-date.c b/test-date.c index a9e705f79a..6bcd5b03c0 100644 --- a/test-date.c +++ b/test-date.c @@ -20,13 +20,16 @@ static void parse_dates(char **argv, struct timeval *now) { for (; *argv; argv++) { char result[100]; - time_t t; + unsigned long t; + int tz; result[0] = 0; parse_date(*argv, result, sizeof(result)); - t = strtoul(result, NULL, 0); - printf("%s -> %s\n", *argv, - t ? show_date(t, 0, DATE_ISO8601) : "bad"); + if (sscanf(result, "%lu %d", &t, &tz) == 2) + printf("%s -> %s\n", + *argv, show_date(t, tz, DATE_ISO8601)); + else + printf("%s -> bad\n", *argv); } } diff --git a/thread-utils.c b/thread-utils.c index 4f9c829c2d..589f838f82 100644 --- a/thread-utils.c +++ b/thread-utils.c @@ -1,4 +1,5 @@ #include "cache.h" +#include <pthread.h> #if defined(hpux) || defined(__hpux) || defined(_hpux) # include <sys/pstat.h> @@ -43,3 +44,18 @@ int online_cpus(void) return 1; } + +int init_recursive_mutex(pthread_mutex_t *m) +{ + pthread_mutexattr_t a; + int ret; + + ret = pthread_mutexattr_init(&a); + if (!ret) { + ret = pthread_mutexattr_settype(&a, PTHREAD_MUTEX_RECURSIVE); + if (!ret) + ret = pthread_mutex_init(m, &a); + pthread_mutexattr_destroy(&a); + } + return ret; +} diff --git a/thread-utils.h b/thread-utils.h index cce4b77bd6..1727a03333 100644 --- a/thread-utils.h +++ b/thread-utils.h @@ -2,5 +2,6 @@ #define THREAD_COMPAT_H extern int online_cpus(void); +extern int init_recursive_mutex(pthread_mutex_t*); #endif /* THREAD_COMPAT_H */ @@ -25,6 +25,10 @@ #include "cache.h" #include "quote.h" +void do_nothing(size_t unused) +{ +} + /* Get a trace file descriptor from GIT_TRACE env variable. */ static int get_trace_fd(int *need_close) { @@ -72,6 +76,7 @@ void trace_printf(const char *fmt, ...) if (!fd) return; + set_try_to_free_routine(do_nothing); /* is never reset */ strbuf_init(&buf, 64); va_start(ap, fmt); len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); @@ -103,6 +108,7 @@ void trace_argv_printf(const char **argv, const char *fmt, ...) if (!fd) return; + set_try_to_free_routine(do_nothing); /* is never reset */ strbuf_init(&buf, 64); va_start(ap, fmt); len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); diff --git a/transport-helper.c b/transport-helper.c index f822972020..191fbf798a 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -7,6 +7,7 @@ #include "revision.h" #include "quote.h" #include "remote.h" +#include "string-list.h" static int debug; @@ -17,6 +18,7 @@ struct helper_data FILE *out; unsigned fetch : 1, import : 1, + export : 1, option : 1, push : 1, connect : 1, @@ -163,6 +165,8 @@ static struct child_process *get_helper(struct transport *transport) data->push = 1; else if (!strcmp(capname, "import")) data->import = 1; + else if (!strcmp(capname, "export")) + data->export = 1; else if (!data->refspecs && !prefixcmp(capname, "refspec ")) { ALLOC_GROW(refspecs, refspec_nr + 1, @@ -170,6 +174,11 @@ static struct child_process *get_helper(struct transport *transport) refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec ")); } else if (!strcmp(capname, "connect")) { data->connect = 1; + } else if (!strcmp(buf.buf, "gitdir")) { + struct strbuf gitdir = STRBUF_INIT; + strbuf_addf(&gitdir, "gitdir %s\n", get_git_dir()); + sendline(data, &gitdir); + strbuf_release(&gitdir); } else if (mandatory) { die("Unknown mandatory capability %s. This remote " "helper probably needs newer version of Git.\n", @@ -279,9 +288,8 @@ static void standard_options(struct transport *t) char buf[16]; int n; int v = t->verbose; - int no_progress = v < 0 || (!t->progress && !isatty(2)); - set_helper_option(t, "progress", !no_progress ? "true" : "false"); + set_helper_option(t, "progress", t->progress ? "true" : "false"); n = snprintf(buf, sizeof(buf), "%d", v + 1); if (n >= sizeof(buf)) @@ -352,6 +360,33 @@ static int get_importer(struct transport *transport, struct child_process *fasti return start_command(fastimport); } +static int get_exporter(struct transport *transport, + struct child_process *fastexport, + const char *export_marks, + const char *import_marks, + struct string_list *revlist_args) +{ + struct child_process *helper = get_helper(transport); + int argc = 0, i; + memset(fastexport, 0, sizeof(*fastexport)); + + /* we need to duplicate helper->in because we want to use it after + * fastexport is done with it. */ + fastexport->out = dup(helper->in); + fastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv)); + fastexport->argv[argc++] = "fast-export"; + if (export_marks) + fastexport->argv[argc++] = export_marks; + if (import_marks) + fastexport->argv[argc++] = import_marks; + + for (i = 0; i < revlist_args->nr; i++) + fastexport->argv[argc++] = revlist_args->items[i].string; + + fastexport->git_cmd = 1; + return start_command(fastexport); +} + static int fetch_with_import(struct transport *transport, int nr_heads, struct ref **to_fetch) { @@ -519,7 +554,7 @@ static int fetch(struct transport *transport, return -1; } -static int push_refs(struct transport *transport, +static int push_refs_with_push(struct transport *transport, struct ref *remote_refs, int flags) { int force_all = flags & TRANSPORT_PUSH_FORCE; @@ -529,17 +564,6 @@ static int push_refs(struct transport *transport, struct child_process *helper; struct ref *ref; - if (process_connect(transport, 1)) { - do_take_over(transport); - return transport->push_refs(transport, remote_refs, flags); - } - - if (!remote_refs) { - fprintf(stderr, "No refs in common and none specified; doing nothing.\n" - "Perhaps you should specify a branch such as 'master'.\n"); - return 0; - } - helper = get_helper(transport); if (!data->push) return 1; @@ -576,7 +600,6 @@ static int push_refs(struct transport *transport, if (buf.len == 0) return 0; - transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0; standard_options(transport); if (flags & TRANSPORT_PUSH_DRY_RUN) { @@ -659,6 +682,94 @@ static int push_refs(struct transport *transport, return 0; } +static int push_refs_with_export(struct transport *transport, + struct ref *remote_refs, int flags) +{ + struct ref *ref; + struct child_process *helper, exporter; + struct helper_data *data = transport->data; + char *export_marks = NULL, *import_marks = NULL; + struct string_list revlist_args = { NULL, 0, 0 }; + struct strbuf buf = STRBUF_INIT; + + helper = get_helper(transport); + + write_constant(helper->in, "export\n"); + + recvline(data, &buf); + if (debug) + fprintf(stderr, "Debug: Got export_marks '%s'\n", buf.buf); + if (buf.len) { + struct strbuf arg = STRBUF_INIT; + strbuf_addstr(&arg, "--export-marks="); + strbuf_addbuf(&arg, &buf); + export_marks = strbuf_detach(&arg, NULL); + } + + recvline(data, &buf); + if (debug) + fprintf(stderr, "Debug: Got import_marks '%s'\n", buf.buf); + if (buf.len) { + struct strbuf arg = STRBUF_INIT; + strbuf_addstr(&arg, "--import-marks="); + strbuf_addbuf(&arg, &buf); + import_marks = strbuf_detach(&arg, NULL); + } + + strbuf_reset(&buf); + + for (ref = remote_refs; ref; ref = ref->next) { + char *private; + unsigned char sha1[20]; + + if (!data->refspecs) + continue; + private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name); + if (private && !get_sha1(private, sha1)) { + strbuf_addf(&buf, "^%s", private); + string_list_append(&revlist_args, strbuf_detach(&buf, NULL)); + } + + string_list_append(&revlist_args, ref->name); + + } + + if (get_exporter(transport, &exporter, + export_marks, import_marks, &revlist_args)) + die("Couldn't run fast-export"); + + data->no_disconnect_req = 1; + finish_command(&exporter); + disconnect_helper(transport); + return 0; +} + +static int push_refs(struct transport *transport, + struct ref *remote_refs, int flags) +{ + struct helper_data *data = transport->data; + + if (process_connect(transport, 1)) { + do_take_over(transport); + return transport->push_refs(transport, remote_refs, flags); + } + + if (!remote_refs) { + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch such as 'master'.\n"); + return 0; + } + + if (data->push) + return push_refs_with_push(transport, remote_refs, flags); + + if (data->export) + return push_refs_with_export(transport, remote_refs, flags); + + return -1; +} + + static int has_attribute(const char *attrs, const char *attr) { int len; if (!attrs) diff --git a/transport.c b/transport.c index 08e4fa0354..4dba6f8815 100644 --- a/transport.c +++ b/transport.c @@ -9,6 +9,7 @@ #include "dir.h" #include "refs.h" #include "branch.h" +#include "url.h" /* rsync support */ @@ -526,7 +527,7 @@ static int fetch_refs_via_pack(struct transport *transport, args.include_tag = data->options.followtags; args.verbose = (transport->verbose > 0); args.quiet = (transport->verbose < 0); - args.no_progress = args.quiet || (!transport->progress && !isatty(2)); + args.no_progress = !transport->progress; args.depth = data->options.depth; for (i = 0; i < nr_heads; i++) @@ -573,7 +574,7 @@ static int push_had_errors(struct ref *ref) return 0; } -static int refs_pushed(struct ref *ref) +int transport_refs_pushed(struct ref *ref) { for (; ref; ref = ref->next) { switch(ref->status) { @@ -587,7 +588,7 @@ static int refs_pushed(struct ref *ref) return 0; } -static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) +void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) { struct refspec rs; @@ -609,8 +610,6 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref, int verb } } -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) - static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, int porcelain) { if (porcelain) { @@ -623,7 +622,7 @@ static void print_ref_status(char flag, const char *summary, struct ref *to, str else fprintf(stdout, "%s\n", summary); } else { - fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); + fprintf(stderr, " %c %-*s ", flag, TRANSPORT_SUMMARY_WIDTH, summary); if (from) fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); else @@ -675,7 +674,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain) static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain) { if (!count) - fprintf(stderr, "To %s\n", dest); + fprintf(porcelain ? stdout : stderr, "To %s\n", dest); switch(ref->status) { case REF_STATUS_NONE: @@ -711,8 +710,8 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i return 1; } -static void print_push_status(const char *dest, struct ref *refs, - int verbose, int porcelain, int * nonfastforward) +void transport_print_push_status(const char *dest, struct ref *refs, + int verbose, int porcelain, int *nonfastforward) { struct ref *ref; int n = 0; @@ -738,7 +737,7 @@ static void print_push_status(const char *dest, struct ref *refs, } } -static void verify_remote_names(int nr_heads, const char **heads) +void transport_verify_remote_names(int nr_heads, const char **heads) { int i; @@ -788,9 +787,10 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); args.use_thin_pack = data->options.thin; - args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE); - args.quiet = !!(flags & TRANSPORT_PUSH_QUIET); + args.verbose = (transport->verbose > 0); + args.quiet = (transport->verbose < 0); args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); + args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN); ret = send_pack(&args, data->fd, data->conn, remote_refs, &data->extra_have); @@ -872,39 +872,6 @@ static int is_file(const char *url) return S_ISREG(buf.st_mode); } -static 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) - 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 (!isalnum((unsigned char)*url2)) - return 0; - url2++; - } - - /* Valid enough. */ - return 1; -} - static int external_specification_len(const char *url) { return strchr(url, ':') - url; @@ -915,6 +882,8 @@ struct transport *transport_get(struct remote *remote, const char *url) const char *helper; struct transport *ret = xcalloc(1, sizeof(*ret)); + ret->progress = isatty(2); + if (!remote) die("No remote provided to transport_get()"); @@ -930,7 +899,7 @@ struct transport *transport_get(struct remote *remote, const char *url) if (url) { const char *p = url; - while (isalnum(*p)) + while (is_urlschemechar(p == url, *p)) p++; if (!prefixcmp(p, "::")) helper = xstrndup(url, p - url); @@ -1014,12 +983,31 @@ int transport_set_option(struct transport *transport, return 1; } +void transport_set_verbosity(struct transport *transport, int verbosity, + int force_progress) +{ + if (verbosity >= 2) + transport->verbose = verbosity <= 3 ? verbosity : 3; + if (verbosity < 0) + transport->verbose = -1; + + /** + * Rules used to determine whether to report progress (processing aborts + * when a rule is satisfied): + * + * 1. Report progress, if force_progress is 1 (ie. --progress). + * 2. Don't report progress, if verbosity < 0 (ie. -q/--quiet ). + * 3. Report progress if isatty(2) is 1. + **/ + transport->progress = force_progress || (verbosity >= 0 && isatty(2)); +} + int transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags, int *nonfastforward) { *nonfastforward = 0; - verify_remote_names(refspec_nr, refspec); + transport_verify_remote_names(refspec_nr, refspec); if (transport->push) { /* Maybe FIXME. But no important transport uses this case. */ @@ -1032,11 +1020,11 @@ int transport_push(struct transport *transport, transport->get_refs_list(transport, 1); struct ref *local_refs = get_local_heads(); int match_flags = MATCH_REFS_NONE; - int verbose = flags & TRANSPORT_PUSH_VERBOSE; - int quiet = flags & TRANSPORT_PUSH_QUIET; + int verbose = (transport->verbose > 0); + int quiet = (transport->verbose < 0); int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; int pretend = flags & TRANSPORT_PUSH_DRY_RUN; - int ret, err; + int push_ret, ret, err; if (flags & TRANSPORT_PUSH_ALL) match_flags |= MATCH_REFS_ALL; @@ -1052,13 +1040,12 @@ int transport_push(struct transport *transport, flags & TRANSPORT_PUSH_MIRROR, flags & TRANSPORT_PUSH_FORCE); - ret = transport->push_refs(transport, remote_refs, flags); + push_ret = transport->push_refs(transport, remote_refs, flags); err = push_had_errors(remote_refs); - - ret |= err; + ret = push_ret | err; if (!quiet || err) - print_push_status(transport->url, remote_refs, + transport_print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, nonfastforward); @@ -1068,11 +1055,14 @@ int transport_push(struct transport *transport, if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) - update_tracking_ref(transport->remote, ref, verbose); + transport_update_tracking_ref(transport->remote, ref, verbose); } - if (!quiet && !ret && !refs_pushed(remote_refs)) + if (porcelain && !push_ret) + puts("Done"); + else if (!quiet && !ret && !transport_refs_pushed(remote_refs)) fprintf(stderr, "Everything up-to-date\n"); + return ret; } return 1; diff --git a/transport.h b/transport.h index 6dd9ae182f..c59d97388e 100644 --- a/transport.h +++ b/transport.h @@ -80,7 +80,12 @@ struct transport { int (*disconnect)(struct transport *connection); char *pack_lockfile; signed verbose : 3; - /* Force progress even if stderr is not a tty */ + /** + * Transports should not set this directly, and should use this + * value without having to check isatty(2), -q/--quiet + * (transport->verbose < 0), etc. - checking has already been done + * in transport_set_verbosity(). + **/ unsigned progress : 1; /* * If transport is at least potentially smart, this points to @@ -94,10 +99,10 @@ struct transport { #define TRANSPORT_PUSH_FORCE 2 #define TRANSPORT_PUSH_DRY_RUN 4 #define TRANSPORT_PUSH_MIRROR 8 -#define TRANSPORT_PUSH_VERBOSE 16 -#define TRANSPORT_PUSH_PORCELAIN 32 -#define TRANSPORT_PUSH_QUIET 64 -#define TRANSPORT_PUSH_SET_UPSTREAM 128 +#define TRANSPORT_PUSH_PORCELAIN 16 +#define TRANSPORT_PUSH_SET_UPSTREAM 32 + +#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); @@ -128,6 +133,8 @@ struct transport *transport_get(struct remote *, const char *); **/ int transport_set_option(struct transport *transport, const char *name, const char *value); +void transport_set_verbosity(struct transport *transport, int verbosity, + int force_progress); int transport_push(struct transport *connection, int refspec_nr, const char **refspec, int flags, @@ -148,4 +155,14 @@ int transport_connect(struct transport *transport, const char *name, /* Transport methods defined outside transport.c */ int transport_helper_init(struct transport *transport, const char *name); +/* common methods used by transport.c and builtin-send-pack.c */ +void transport_verify_remote_names(int nr_heads, const char **heads); + +void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose); + +int transport_refs_pushed(struct ref *ref); + +void transport_print_push_status(const char *dest, struct ref *refs, + int verbose, int porcelain, int *nonfastforward); + #endif diff --git a/tree-diff.c b/tree-diff.c index fe9f52c479..1fb3e94614 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -346,7 +346,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co diff_setup(&diff_opts); DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = DIFF_DETECT_RENAME; + DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_opts.single_follow = opt->paths[0]; diff_opts.break_opt = opt->break_opt; diff --git a/unpack-trees.c b/unpack-trees.c index 75f54cac97..8cf0da317d 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -67,16 +67,8 @@ static void unlink_entry(struct cache_entry *ce) { if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return; - if (S_ISGITLINK(ce->ce_mode)) { - if (rmdir(ce->name)) { - warning("unable to rmdir %s: %s", - ce->name, strerror(errno)); - return; - } - } - else - if (unlink_or_warn(ce->name)) - return; + if (remove_or_warn(ce->ce_mode, ce->name)) + return; schedule_dir_for_removal(ce->name, ce_namelen(ce)); } @@ -287,9 +279,11 @@ static void add_same_unmerged(struct cache_entry *ce, static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o) { - struct cache_entry *src[5] = { ce, NULL, }; + struct cache_entry *src[5] = { NULL }; int ret; + src[0] = ce; + mark_ce_used(ce, o); if (ce_stage(ce)) { if (o->skip_unmerged) { @@ -528,9 +522,17 @@ static int find_cache_pos(struct traverse_info *info, const char *ce_name, *ce_slash; int cmp, ce_len; - if (!ce_in_traverse_path(ce, info)) + if (ce->ce_flags & CE_UNPACKED) { + /* + * cache_bottom entry is already unpacked, so + * we can never match it; don't check it + * again. + */ + if (pos == o->cache_bottom) + ++o->cache_bottom; continue; - if (ce->ce_flags & CE_UNPACKED) + } + if (!ce_in_traverse_path(ce, info)) continue; ce_name = ce->name + pfxlen; ce_slash = strchr(ce_name, '/'); @@ -862,7 +864,7 @@ static int verify_uptodate_1(struct cache_entry *ce, { struct stat st; - if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce)))) + if (o->index_only || (!((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) && (o->reset || ce_uptodate(ce)))) return 0; if (!lstat(ce->name, &st)) { diff --git a/upload-pack.c b/upload-pack.c index dc464d78b3..fc79ddef25 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -105,7 +105,7 @@ static void show_edge(struct commit *commit) fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1)); } -static int do_rev_list(int in, int out, void *create_full_pack) +static int do_rev_list(int in, int out, void *user_data) { int i; struct rev_info revs; @@ -118,23 +118,18 @@ static int do_rev_list(int in, int out, void *create_full_pack) if (use_thin_pack) revs.edge_hint = 1; - if (create_full_pack) { - const char *args[] = {"rev-list", "--all", NULL}; - setup_revisions(2, args, &revs, NULL); - } else { - for (i = 0; i < want_obj.nr; i++) { - struct object *o = want_obj.objects[i].item; - /* why??? */ - o->flags &= ~UNINTERESTING; - add_pending_object(&revs, o, NULL); - } - for (i = 0; i < have_obj.nr; i++) { - struct object *o = have_obj.objects[i].item; - o->flags |= UNINTERESTING; - add_pending_object(&revs, o, NULL); - } - setup_revisions(0, NULL, &revs, NULL); + for (i = 0; i < want_obj.nr; i++) { + struct object *o = want_obj.objects[i].item; + /* why??? */ + o->flags &= ~UNINTERESTING; + add_pending_object(&revs, o, NULL); + } + for (i = 0; i < have_obj.nr; i++) { + struct object *o = have_obj.objects[i].item; + o->flags |= UNINTERESTING; + add_pending_object(&revs, o, NULL); } + setup_revisions(0, NULL, &revs, NULL); if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); @@ -554,7 +549,8 @@ static void receive_needs(void) */ o = lookup_object(sha1_buf); if (!o || !(o->flags & OUR_REF)) - die("git upload-pack: not our ref %s", line+5); + die("git upload-pack: not our ref %s", + sha1_to_hex(sha1_buf)); if (!(o->flags & WANTED)) { o->flags |= WANTED; add_object_array(o, NULL, &want_obj); @@ -0,0 +1,126 @@ +#include "cache.h" + +int is_urlschemechar(int first_flag, int ch) +{ + /* + * The set of valid URL schemes, as per STD66 (RFC3986) is + * '[A-Za-z][A-Za-z0-9+.-]*'. But use sightly looser check + * of '[A-Za-z0-9][A-Za-z0-9+.-]*' because earlier version + * of check used '[A-Za-z0-9]+' so not to break any remote + * helpers. + */ + int alphanumeric, special; + alphanumeric = ch > 0 && isalnum(ch); + special = ch == '+' || ch == '-' || ch == '.'; + return alphanumeric || (!first_flag && special); +} + +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) + 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)) + return 0; + url2++; + } + + /* Valid enough. */ + return 1; +} + +static int url_decode_char(const char *q) +{ + int i; + unsigned char val = 0; + for (i = 0; i < 2; i++) { + unsigned char c = *q++; + val <<= 4; + if (c >= '0' && c <= '9') + val += c - '0'; + else if (c >= 'a' && c <= 'f') + val += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val += c - 'A' + 10; + else + return -1; + } + return val; +} + +static char *url_decode_internal(const char **query, const char *stop_at, struct strbuf *out) +{ + const char *q = *query; + + do { + unsigned char c = *q; + + if (!c) + break; + if (stop_at && strchr(stop_at, c)) { + q++; + break; + } + + if (c == '%') { + int val = url_decode_char(q + 1); + if (0 <= val) { + strbuf_addch(out, val); + q += 3; + continue; + } + } + + if (c == '+') + strbuf_addch(out, ' '); + else + strbuf_addch(out, c); + q++; + } while (1); + *query = q; + return strbuf_detach(out, NULL); +} + +char *url_decode(const char *url) +{ + struct strbuf out = STRBUF_INIT; + const char *colon = strchr(url, ':'); + + /* Skip protocol part if present */ + if (colon && url < colon) { + strbuf_add(&out, url, colon - url); + url = colon; + } + return url_decode_internal(&url, NULL, &out); +} + +char *url_decode_parameter_name(const char **query) +{ + struct strbuf out = STRBUF_INIT; + return url_decode_internal(query, "&=", &out); +} + +char *url_decode_parameter_value(const char **query) +{ + struct strbuf out = STRBUF_INIT; + return url_decode_internal(query, "&", &out); +} @@ -0,0 +1,10 @@ +#ifndef URL_H +#define URL_H + +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_parameter_name(const char **query); +extern char *url_decode_parameter_value(const char **query); + +#endif /* URL_H */ @@ -5,7 +5,7 @@ */ #include "git-compat-util.h" -static void report(const char *prefix, const char *err, va_list params) +void vreportf(const char *prefix, const char *err, va_list params) { char msg[4096]; vsnprintf(msg, sizeof(msg), err, params); @@ -14,24 +14,24 @@ static void report(const char *prefix, const char *err, va_list params) static NORETURN void usage_builtin(const char *err, va_list params) { - report("usage: ", err, params); + vreportf("usage: ", err, params); exit(129); } static NORETURN void die_builtin(const char *err, va_list params) { - report("fatal: ", err, params); + vreportf("fatal: ", err, params); exit(128); } static void error_builtin(const char *err, va_list params) { - report("error: ", err, params); + vreportf("error: ", err, params); } static void warn_builtin(const char *warn, va_list params) { - report("warning: ", warn, params); + vreportf("warning: ", warn, params); } /* If we are in a dlopen()ed .so write to a global variable would segfault diff --git a/userdiff.c b/userdiff.c index df992490d5..c49cc1b67e 100644 --- a/userdiff.c +++ b/userdiff.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "userdiff.h" #include "cache.h" #include "attr.h" @@ -44,7 +45,9 @@ PATTERNS("pascal", "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" "|<>|<=|>=|:=|\\.\\." "|[^[:space:]]|[\x80-\xff]+"), -PATTERNS("php", "^[\t ]*((function|class).*)", +PATTERNS("php", + "^[\t ]*(((public|protected|private|static)[\t ]+)*function.*)$\n" + "^[\t ]*(class.*)$", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" @@ -167,6 +170,12 @@ static int parse_tristate(int *b, const char *k, const char *v) return 1; } +static int parse_bool(int *b, const char *k, const char *v) +{ + *b = git_config_bool(k, v); + return 1; +} + int userdiff_config(const char *k, const char *v) { struct userdiff_driver *drv; @@ -181,6 +190,8 @@ int userdiff_config(const char *k, const char *v) return parse_string(&drv->external, k, v); if ((drv = parse_driver(k, v, "textconv"))) return parse_string(&drv->textconv, k, v); + if ((drv = parse_driver(k, v, "cachetextconv"))) + return parse_bool(&drv->textconv_want_cache, k, v); if ((drv = parse_driver(k, v, "wordregex"))) return parse_string(&drv->word_regex, k, v); diff --git a/userdiff.h b/userdiff.h index c3151594f5..942d594950 100644 --- a/userdiff.h +++ b/userdiff.h @@ -1,6 +1,8 @@ #ifndef USERDIFF_H #define USERDIFF_H +#include "notes-cache.h" + struct userdiff_funcname { const char *pattern; int cflags; @@ -13,6 +15,8 @@ struct userdiff_driver { struct userdiff_funcname funcname; const char *word_regex; const char *textconv; + struct notes_cache *textconv_cache; + int textconv_want_cache; }; int userdiff_config(const char *k, const char *v); @@ -34,6 +34,6 @@ int walker_fetch(struct walker *impl, int targets, char **target, void walker_free(struct walker *walker); -struct walker *get_http_walker(const char *url, struct remote *remote); +struct walker *get_http_walker(const char *url); #endif /* WALKER_H */ diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh index c5075c9c61..09feb1f737 100644 --- a/wrap-for-bin.sh +++ b/wrap-for-bin.sh @@ -7,9 +7,15 @@ # @@BUILD_DIR@@ and @@PROG@@. GIT_EXEC_PATH='@@BUILD_DIR@@' -GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt' +if test -n "$NO_SET_GIT_TEMPLATE_DIR" +then + unset GIT_TEMPLATE_DIR +else + GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt' + export GIT_TEMPLATE_DIR +fi GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib' PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH" -export GIT_EXEC_PATH GIT_TEMPLATE_DIR GITPERLLIB PATH +export GIT_EXEC_PATH GITPERLLIB PATH exec "${GIT_EXEC_PATH}/@@PROG@@" "$@" @@ -3,11 +3,25 @@ */ #include "cache.h" +static void try_to_free_builtin(size_t size) +{ + release_pack_memory(size, -1); +} + +static void (*try_to_free_routine)(size_t size) = try_to_free_builtin; + +try_to_free_t set_try_to_free_routine(try_to_free_t routine) +{ + try_to_free_t old = try_to_free_routine; + try_to_free_routine = routine; + return old; +} + char *xstrdup(const char *str) { char *ret = strdup(str); if (!ret) { - release_pack_memory(strlen(str) + 1, -1); + try_to_free_routine(strlen(str) + 1); ret = strdup(str); if (!ret) die("Out of memory, strdup failed"); @@ -21,7 +35,7 @@ void *xmalloc(size_t size) if (!ret && !size) ret = malloc(1); if (!ret) { - release_pack_memory(size, -1); + try_to_free_routine(size); ret = malloc(size); if (!ret && !size) ret = malloc(1); @@ -67,7 +81,7 @@ void *xrealloc(void *ptr, size_t size) if (!ret && !size) ret = realloc(ptr, 1); if (!ret) { - release_pack_memory(size, -1); + try_to_free_routine(size); ret = realloc(ptr, size); if (!ret && !size) ret = realloc(ptr, 1); @@ -83,7 +97,7 @@ void *xcalloc(size_t nmemb, size_t size) if (!ret && (!nmemb || !size)) ret = calloc(1, 1); if (!ret) { - release_pack_memory(nmemb * size, -1); + try_to_free_routine(nmemb * size); ret = calloc(nmemb, size); if (!ret && (!nmemb || !size)) ret = calloc(1, 1); @@ -311,18 +325,30 @@ int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1) return open(name, O_RDWR|O_CREAT|O_EXCL, 0600); } -int unlink_or_warn(const char *file) +static int warn_if_unremovable(const char *op, const char *file, int rc) { - int rc = unlink(file); - if (rc < 0) { int err = errno; if (ENOENT != err) { - warning("unable to unlink %s: %s", - file, strerror(errno)); + warning("unable to %s %s: %s", + op, file, strerror(errno)); errno = err; } } return rc; } +int unlink_or_warn(const char *file) +{ + return warn_if_unremovable("unlink", file, unlink(file)); +} + +int rmdir_or_warn(const char *file) +{ + return warn_if_unremovable("rmdir", file, rmdir(file)); +} + +int remove_or_warn(unsigned int mode, const char *file) +{ + return S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file); +} @@ -10,7 +10,8 @@ static struct whitespace_rule { const char *rule_name; unsigned rule_bits; - unsigned loosens_error; + unsigned loosens_error:1, + exclude_default:1; } whitespace_rule_names[] = { { "trailing-space", WS_TRAILING_SPACE, 0 }, { "space-before-tab", WS_SPACE_BEFORE_TAB, 0 }, @@ -18,6 +19,7 @@ static struct whitespace_rule { { "cr-at-eol", WS_CR_AT_EOL, 1 }, { "blank-at-eol", WS_BLANK_AT_EOL, 0 }, { "blank-at-eof", WS_BLANK_AT_EOF, 0 }, + { "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 }, }; unsigned parse_whitespace_rule(const char *string) @@ -56,6 +58,9 @@ unsigned parse_whitespace_rule(const char *string) } string = ep; } + + if (rule & WS_TAB_IN_INDENT && rule & WS_INDENT_WITH_NON_TAB) + die("cannot enforce both tab-in-indent and indent-with-non-tab"); return rule; } @@ -82,7 +87,8 @@ unsigned whitespace_rule(const char *pathname) unsigned all_rule = 0; int i; for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) - if (!whitespace_rule_names[i].loosens_error) + if (!whitespace_rule_names[i].loosens_error && + !whitespace_rule_names[i].exclude_default) all_rule |= whitespace_rule_names[i].rule_bits; return all_rule; } else if (ATTR_FALSE(value)) { @@ -125,6 +131,11 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "indent with spaces"); } + if (ws & WS_TAB_IN_INDENT) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "tab in indent"); + } return strbuf_detach(&err, NULL); } @@ -163,7 +174,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } } - /* Check for space before tab in initial indent. */ + /* Check indentation */ for (i = 0; i < len; i++) { if (line[i] == ' ') continue; @@ -175,11 +186,19 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, fputs(ws, stream); fwrite(line + written, i - written, 1, stream); fputs(reset, stream); + fwrite(line + i, 1, 1, stream); } - } else if (stream) - fwrite(line + written, i - written, 1, stream); - if (stream) - fwrite(line + i, 1, 1, stream); + } else if (ws_rule & WS_TAB_IN_INDENT) { + result |= WS_TAB_IN_INDENT; + if (stream) { + fwrite(line + written, i - written, 1, stream); + fputs(ws, stream); + fwrite(line + i, 1, 1, stream); + fputs(reset, stream); + } + } else if (stream) { + fwrite(line + written, i - written + 1, 1, stream); + } written = i + 1; } @@ -252,8 +271,8 @@ int ws_blank_line(const char *line, int len, unsigned ws_rule) return 1; } -/* Copy the line to the buffer while fixing whitespaces */ -int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *error_count) +/* Copy the line onto the end of the strbuf while fixing whitespaces */ +void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule, int *error_count) { /* * len is number of bytes to be copied from src, starting @@ -267,7 +286,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro int last_tab_in_indent = -1; int last_space_in_indent = -1; int need_fix_leading_space = 0; - char *buf; /* * Strip trailing whitespace @@ -307,7 +325,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro break; } - buf = dst; if (need_fix_leading_space) { /* Process indent ourselves */ int consecutive_spaces = 0; @@ -329,28 +346,41 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro char ch = src[i]; if (ch != ' ') { consecutive_spaces = 0; - *dst++ = ch; + strbuf_addch(dst, ch); } else { consecutive_spaces++; if (consecutive_spaces == 8) { - *dst++ = '\t'; + strbuf_addch(dst, '\t'); consecutive_spaces = 0; } } } while (0 < consecutive_spaces--) - *dst++ = ' '; + strbuf_addch(dst, ' '); + len -= last; + src += last; + fixed = 1; + } else if ((ws_rule & WS_TAB_IN_INDENT) && last_tab_in_indent >= 0) { + /* Expand tabs into spaces */ + int last = last_tab_in_indent + 1; + for (i = 0; i < last; i++) { + if (src[i] == '\t') + do { + strbuf_addch(dst, ' '); + } while (dst->len % 8); + else + strbuf_addch(dst, src[i]); + } len -= last; src += last; fixed = 1; } - memcpy(dst, src, len); + strbuf_add(dst, src, len); if (add_cr_to_tail) - dst[len++] = '\r'; + strbuf_addch(dst, '\r'); if (add_nl_to_tail) - dst[len++] = '\n'; + strbuf_addch(dst, '\n'); if (fixed && error_count) (*error_count)++; - return dst + len - buf; } diff --git a/wt-status.c b/wt-status.c index 5807fc3211..2f9e33c8fa 100644 --- a/wt-status.c +++ b/wt-status.c @@ -9,6 +9,8 @@ #include "quote.h" #include "run-command.h" #include "remote.h" +#include "refs.h" +#include "submodule.h" static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ @@ -17,6 +19,8 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_RED, /* WT_STATUS_UNTRACKED */ GIT_COLOR_RED, /* WT_STATUS_NOBRANCH */ GIT_COLOR_RED, /* WT_STATUS_UNMERGED */ + GIT_COLOR_GREEN, /* WT_STATUS_LOCAL_BRANCH */ + GIT_COLOR_RED, /* WT_STATUS_REMOTE_BRANCH */ }; static const char *color(int slot, struct wt_status *s) @@ -42,6 +46,7 @@ void wt_status_prepare(struct wt_status *s) s->index_file = get_index_file(); s->change.strdup_strings = 1; s->untracked.strdup_strings = 1; + s->ignored.strdup_strings = 1; } static void wt_status_print_unmerged_header(struct wt_status *s) @@ -78,7 +83,8 @@ static void wt_status_print_cached_header(struct wt_status *s) } static void wt_status_print_dirty_header(struct wt_status *s, - int has_deleted) + int has_deleted, + int has_dirty_submodules) { const char *c = color(WT_STATUS_HEADER, s); @@ -90,16 +96,20 @@ static void wt_status_print_dirty_header(struct wt_status *s, else color_fprintf_ln(s->fp, c, "# (use \"git add/rm <file>...\" to update what will be committed)"); color_fprintf_ln(s->fp, c, "# (use \"git checkout -- <file>...\" to discard changes in working directory)"); + if (has_dirty_submodules) + color_fprintf_ln(s->fp, c, "# (commit or discard the untracked or modified content in submodules)"); color_fprintf_ln(s->fp, c, "#"); } -static void wt_status_print_untracked_header(struct wt_status *s) +static void wt_status_print_other_header(struct wt_status *s, + const char *what, + const char *how) { const char *c = color(WT_STATUS_HEADER, s); - color_fprintf_ln(s->fp, c, "# Untracked files:"); + color_fprintf_ln(s->fp, c, "# %s files:", what); if (!advice_status_hints) return; - color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to include in what will be committed)"); + color_fprintf_ln(s->fp, c, "# (use \"git %s <file>...\" to include in what will be committed)", how); color_fprintf_ln(s->fp, c, "#"); } @@ -144,6 +154,7 @@ static void wt_status_print_change_data(struct wt_status *s, char *two_name; const char *one, *two; struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT; + struct strbuf extra = STRBUF_INIT; one_name = two_name = it->string; switch (change_type) { @@ -153,6 +164,17 @@ static void wt_status_print_change_data(struct wt_status *s, one_name = d->head_path; break; case WT_STATUS_CHANGED: + if (d->new_submodule_commits || d->dirty_submodule) { + strbuf_addstr(&extra, " ("); + if (d->new_submodule_commits) + strbuf_addf(&extra, "new commits, "); + if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) + strbuf_addf(&extra, "modified content, "); + if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) + strbuf_addf(&extra, "untracked content, "); + strbuf_setlen(&extra, extra.len - 2); + strbuf_addch(&extra, ')'); + } status = d->worktree_status; break; } @@ -189,6 +211,10 @@ static void wt_status_print_change_data(struct wt_status *s, default: die("bug: unhandled diff status %c", status); } + if (extra.len) { + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf); + strbuf_release(&extra); + } fprintf(s->fp, "\n"); strbuf_release(&onebuf); strbuf_release(&twobuf); @@ -210,7 +236,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, struct wt_status_change_data *d; p = q->queue[i]; - it = string_list_insert(p->one->path, &s->change); + it = string_list_insert(&s->change, p->one->path); d = it->util; if (!d) { d = xcalloc(1, sizeof(*d)); @@ -218,6 +244,9 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, } if (!d->worktree_status) d->worktree_status = p->status; + d->dirty_submodule = p->two->dirty_submodule; + if (S_ISGITLINK(p->two->mode)) + d->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1); } } @@ -254,7 +283,7 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q, struct wt_status_change_data *d; p = q->queue[i]; - it = string_list_insert(p->two->path, &s->change); + it = string_list_insert(&s->change, p->two->path); d = it->util; if (!d) { d = xcalloc(1, sizeof(*d)); @@ -281,6 +310,11 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, NULL); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES); + if (!s->show_untracked_files) + DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); + if (s->ignore_submodule_arg) + 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; rev.prune_data = s->pathspec; @@ -290,10 +324,16 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) static void wt_status_collect_changes_index(struct wt_status *s) { struct rev_info rev; + struct setup_revision_opt opt; init_revisions(&rev, NULL); - setup_revisions(0, NULL, &rev, - s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference); + memset(&opt, 0, sizeof(opt)); + opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; + setup_revisions(0, NULL, &rev, &opt); + + if (s->ignore_submodule_arg) + handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_collect_updated_cb; rev.diffopt.format_callback_data = s; @@ -315,7 +355,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s) if (!ce_path_match(ce, s->pathspec)) continue; - it = string_list_insert(ce->name, &s->change); + it = string_list_insert(&s->change, ce->name); d = it->util; if (!d) { d = xcalloc(1, sizeof(*d)); @@ -350,9 +390,26 @@ static void wt_status_collect_untracked(struct wt_status *s) continue; if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) continue; - s->workdir_untracked = 1; - string_list_insert(ent->name, &s->untracked); + string_list_insert(&s->untracked, ent->name); + free(ent); } + + if (s->show_ignored_files) { + dir.nr = 0; + dir.flags = DIR_SHOW_IGNORED | DIR_SHOW_OTHER_DIRECTORIES; + fill_directory(&dir, s->pathspec); + for (i = 0; i < dir.nr; i++) { + struct dir_entry *ent = dir.entries[i]; + if (!cache_name_is_other(ent->name, ent->len)) + continue; + if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + continue; + string_list_insert(&s->ignored, ent->name); + free(ent); + } + } + + free(dir.entries); } void wt_status_collect(struct wt_status *s) @@ -418,33 +475,39 @@ static void wt_status_print_updated(struct wt_status *s) * 0 : no change * 1 : some change but no delete */ -static int wt_status_check_worktree_changes(struct wt_status *s) +static int wt_status_check_worktree_changes(struct wt_status *s, + int *dirty_submodules) { int i; int changes = 0; + *dirty_submodules = 0; + for (i = 0; i < s->change.nr; i++) { struct wt_status_change_data *d; d = s->change.items[i].util; if (!d->worktree_status || d->worktree_status == DIFF_STATUS_UNMERGED) continue; - changes = 1; + if (!changes) + changes = 1; + if (d->dirty_submodule) + *dirty_submodules = 1; if (d->worktree_status == DIFF_STATUS_DELETED) - return -1; + changes = -1; } return changes; } static void wt_status_print_changed(struct wt_status *s) { - int i; - int worktree_changes = wt_status_check_worktree_changes(s); + int i, dirty_submodules; + int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules); if (!worktree_changes) return; - wt_status_print_dirty_header(s, worktree_changes < 0); + wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules); for (i = 0; i < s->change.nr; i++) { struct wt_status_change_data *d; @@ -464,17 +527,18 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt struct child_process sm_summary; char summary_limit[64]; char index[PATH_MAX]; - const char *env[] = { index, NULL }; - const char *argv[] = { - "submodule", - "summary", - uncommitted ? "--files" : "--cached", - "--for-status", - "--summary-limit", - summary_limit, - uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD"), - NULL - }; + const char *env[] = { NULL, NULL }; + const char *argv[8]; + + env[0] = index; + argv[0] = "submodule"; + argv[1] = "summary"; + argv[2] = uncommitted ? "--files" : "--cached"; + argv[3] = "--for-status"; + argv[4] = "--summary-limit"; + argv[5] = summary_limit; + argv[6] = uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD"); + argv[7] = NULL; sprintf(summary_limit, "%d", s->submodule_summary); snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file); @@ -489,7 +553,10 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt run_command(&sm_summary); } -static void wt_status_print_untracked(struct wt_status *s) +static void wt_status_print_other(struct wt_status *s, + struct string_list *l, + const char *what, + const char *how) { int i; struct strbuf buf = STRBUF_INIT; @@ -497,10 +564,11 @@ static void wt_status_print_untracked(struct wt_status *s) if (!s->untracked.nr) return; - wt_status_print_untracked_header(s); - for (i = 0; i < s->untracked.nr; i++) { + wt_status_print_other_header(s, what, how); + + for (i = 0; i < l->nr; i++) { struct string_list_item *it; - it = &(s->untracked.items[i]); + it = &(l->items[i]); color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", quote_path(it->string, strlen(it->string), @@ -512,11 +580,15 @@ static void wt_status_print_untracked(struct wt_status *s) static void wt_status_print_verbose(struct wt_status *s) { struct rev_info rev; + struct setup_revision_opt opt; init_revisions(&rev, NULL); DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); - setup_revisions(0, NULL, &rev, - s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference); + + memset(&opt, 0, sizeof(opt)); + opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; + setup_revisions(0, NULL, &rev, &opt); + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.diffopt.detect_rename = 1; rev.diffopt.file = s->fp; @@ -580,14 +652,20 @@ void wt_status_print(struct wt_status *s) wt_status_print_updated(s); wt_status_print_unmerged(s); wt_status_print_changed(s); - if (s->submodule_summary) { + if (s->submodule_summary && + (!s->ignore_submodule_arg || + strcmp(s->ignore_submodule_arg, "all"))) { wt_status_print_submodule_summary(s, 0); /* staged */ wt_status_print_submodule_summary(s, 1); /* unstaged */ } - if (s->show_untracked_files) - wt_status_print_untracked(s); - else if (s->commitable) - fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n"); + if (s->show_untracked_files) { + wt_status_print_other(s, &s->untracked, "Untracked", "add"); + if (s->show_ignored_files) + wt_status_print_other(s, &s->ignored, "Ignored", "add -f"); + } else if (s->commitable) + fprintf(s->fp, "# Untracked files not listed%s\n", + advice_status_hints + ? " (use -u option to show untracked files)" : ""); if (s->verbose) wt_status_print_verbose(s); @@ -597,15 +675,22 @@ void wt_status_print(struct wt_status *s) else if (s->nowarn) ; /* nothing */ else if (s->workdir_dirty) - printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); + printf("no changes added to commit%s\n", + advice_status_hints + ? " (use \"git add\" and/or \"git commit -a\")" : ""); else if (s->untracked.nr) - printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); + printf("nothing added to commit but untracked files present%s\n", + advice_status_hints + ? " (use \"git add\" to track)" : ""); else if (s->is_initial) - printf("nothing to commit (create/copy files and use \"git add\" to track)\n"); + printf("nothing to commit%s\n", advice_status_hints + ? " (create/copy files and use \"git add\" to track)" : ""); else if (!s->show_untracked_files) - printf("nothing to commit (use -u to show untracked files)\n"); + printf("nothing to commit%s\n", advice_status_hints + ? " (use -u to show untracked files)" : ""); else - printf("nothing to commit (working directory clean)\n"); + printf("nothing to commit%s\n", advice_status_hints + ? " (working directory clean)" : ""); } } @@ -668,24 +753,84 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item } } -static void wt_shortstatus_untracked(int null_termination, struct string_list_item *it, - struct wt_status *s) +static void wt_shortstatus_other(int null_termination, struct string_list_item *it, + struct wt_status *s, const char *sign) { if (null_termination) { - fprintf(stdout, "?? %s%c", it->string, 0); + fprintf(stdout, "%s %s%c", sign, it->string, 0); } else { struct strbuf onebuf = STRBUF_INIT; const char *one; one = quote_path(it->string, -1, &onebuf, s->prefix); - color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "??"); + color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign); printf(" %s\n", one); strbuf_release(&onebuf); } } -void wt_shortstatus_print(struct wt_status *s, int null_termination) +static void wt_shortstatus_print_tracking(struct wt_status *s) +{ + struct branch *branch; + const char *header_color = color(WT_STATUS_HEADER, s); + const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s); + const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s); + + const char *base; + const char *branch_name; + int num_ours, num_theirs; + + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## "); + + if (!s->branch) + return; + branch_name = s->branch; + + if (!prefixcmp(branch_name, "refs/heads/")) + branch_name += 11; + else if (!strcmp(branch_name, "HEAD")) { + branch_name = "HEAD (no branch)"; + branch_color_local = color(WT_STATUS_NOBRANCH, s); + } + + branch = branch_get(s->branch + 11); + if (s->is_initial) + color_fprintf(s->fp, header_color, "Initial commit on "); + if (!stat_tracking_info(branch, &num_ours, &num_theirs)) { + color_fprintf_ln(s->fp, branch_color_local, + "%s", branch_name); + return; + } + + base = branch->merge[0]->dst; + base = shorten_unambiguous_ref(base, 0); + color_fprintf(s->fp, branch_color_local, "%s", branch_name); + color_fprintf(s->fp, header_color, "..."); + color_fprintf(s->fp, branch_color_remote, "%s", base); + + color_fprintf(s->fp, header_color, " ["); + if (!num_ours) { + color_fprintf(s->fp, header_color, "behind "); + color_fprintf(s->fp, branch_color_remote, "%d", num_theirs); + } else if (!num_theirs) { + color_fprintf(s->fp, header_color, "ahead "); + color_fprintf(s->fp, branch_color_local, "%d", num_ours); + } else { + color_fprintf(s->fp, header_color, "ahead "); + color_fprintf(s->fp, branch_color_local, "%d", num_ours); + color_fprintf(s->fp, header_color, ", behind "); + color_fprintf(s->fp, branch_color_remote, "%d", num_theirs); + } + + color_fprintf_ln(s->fp, header_color, "]"); +} + +void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch) { int i; + + if (show_branch) + wt_shortstatus_print_tracking(s); + for (i = 0; i < s->change.nr; i++) { struct wt_status_change_data *d; struct string_list_item *it; @@ -701,7 +846,13 @@ void wt_shortstatus_print(struct wt_status *s, int null_termination) struct string_list_item *it; it = &(s->untracked.items[i]); - wt_shortstatus_untracked(null_termination, it, s); + wt_shortstatus_other(null_termination, it, s, "??"); + } + for (i = 0; i < s->ignored.nr; i++) { + struct string_list_item *it; + + it = &(s->ignored.items[i]); + wt_shortstatus_other(null_termination, it, s, "!!"); } } @@ -710,5 +861,5 @@ void wt_porcelain_print(struct wt_status *s, int null_termination) s->use_color = 0; s->relative_paths = 0; s->prefix = NULL; - wt_shortstatus_print(s, null_termination); + wt_shortstatus_print(s, null_termination, 0); } diff --git a/wt-status.h b/wt-status.h index c60f40a34a..9df9c9fad2 100644 --- a/wt-status.h +++ b/wt-status.h @@ -12,6 +12,8 @@ enum color_wt_status { WT_STATUS_UNTRACKED, WT_STATUS_NOBRANCH, WT_STATUS_UNMERGED, + WT_STATUS_LOCAL_BRANCH, + WT_STATUS_REMOTE_BRANCH }; enum untracked_status_type { @@ -25,6 +27,8 @@ struct wt_status_change_data { int index_status; int stagemask; char *head_path; + unsigned dirty_submodule : 2; + unsigned new_submodule_commits : 1; }; struct wt_status { @@ -39,25 +43,27 @@ struct wt_status { int use_color; int relative_paths; int submodule_summary; + int show_ignored_files; enum untracked_status_type show_untracked_files; - char color_palette[WT_STATUS_UNMERGED+1][COLOR_MAXLEN]; + const char *ignore_submodule_arg; + char color_palette[WT_STATUS_REMOTE_BRANCH+1][COLOR_MAXLEN]; /* These are computed during processing of the individual sections */ int commitable; int workdir_dirty; - int workdir_untracked; const char *index_file; FILE *fp; const char *prefix; struct string_list change; struct string_list untracked; + struct string_list ignored; }; void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); -void wt_shortstatus_print(struct wt_status *s, int null_termination); +void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch); void wt_porcelain_print(struct wt_status *s, int null_termination); #endif /* STATUS_H */ diff --git a/xdiff-interface.c b/xdiff-interface.c index 3cf39c39c4..cd2285de1c 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -219,6 +219,23 @@ int read_mmfile(mmfile_t *ptr, const char *filename) return 0; } +void read_mmblob(mmfile_t *ptr, const unsigned char *sha1) +{ + unsigned long size; + enum object_type type; + + if (!hashcmp(sha1, null_sha1)) { + ptr->ptr = xstrdup(""); + ptr->size = 0; + return; + } + + ptr->ptr = read_sha1_file(sha1, &type, &size); + if (!ptr->ptr || type != OBJ_BLOB) + die("unable to read blob object %s", sha1_to_hex(sha1)); + ptr->size = size; +} + #define FIRST_FEW_BYTES 8000 int buffer_is_binary(const char *ptr, unsigned long size) { diff --git a/xdiff-interface.h b/xdiff-interface.h index 0cd4511bf7..49d1116fc3 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -17,6 +17,7 @@ int parse_hunk_header(char *line, int len, int *ob, int *on, int *nb, int *nn); int read_mmfile(mmfile_t *ptr, const char *filename); +void read_mmblob(mmfile_t *ptr, const unsigned char *sha1); int buffer_is_binary(const char *ptr, unsigned long size); extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags); diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 3f6229edbe..711048ea36 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -56,17 +56,14 @@ extern "C" { #define XDL_MERGE_EAGER 1 #define XDL_MERGE_ZEALOUS 2 #define XDL_MERGE_ZEALOUS_ALNUM 3 -#define XDL_MERGE_LEVEL_MASK 0x0f /* merge favor modes */ #define XDL_MERGE_FAVOR_OURS 1 #define XDL_MERGE_FAVOR_THEIRS 2 -#define XDL_MERGE_FAVOR(flags) (((flags)>>4) & 3) -#define XDL_MERGE_FLAGS(level, style, favor) ((level)|(style)|((favor)<<4)) +#define XDL_MERGE_FAVOR_UNION 3 /* merge output styles */ -#define XDL_MERGE_DIFF3 0x8000 -#define XDL_MERGE_STYLE_MASK 0x8000 +#define XDL_MERGE_DIFF3 1 typedef struct s_mmfile { char *ptr; @@ -117,13 +114,18 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, typedef struct s_xmparam { xpparam_t xpp; int marker_size; + int level; + int favor; + int style; + const char *ancestor; /* label for orig */ + const char *file1; /* label for mf1 */ + const char *file2; /* label for mf2 */ } xmparam_t; #define DEFAULT_CONFLICT_MARKER_SIZE 7 -int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, - mmfile_t *mf2, const char *name2, - xmparam_t const *xmp, int flags, mmbuffer_t *result); +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result); #ifdef __cplusplus } diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 8cbe45e675..6d6fc1bc5e 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -28,6 +28,7 @@ typedef struct s_xdmerge { * 0 = conflict, * 1 = no conflict, take first, * 2 = no conflict, take second. + * 3 = no conflict, take both. */ int mode; /* @@ -144,12 +145,13 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, + const char *name3, int size, int i, int style, xdmerge_t *m, char *dest, int marker_size) { int marker1_size = (name1 ? strlen(name1) + 1 : 0); int marker2_size = (name2 ? strlen(name2) + 1 : 0); - int j; + int marker3_size = (name3 ? strlen(name3) + 1 : 0); if (marker_size <= 0) marker_size = DEFAULT_CONFLICT_MARKER_SIZE; @@ -161,8 +163,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (!dest) { size += marker_size + 1 + marker1_size; } else { - for (j = 0; j < marker_size; j++) - dest[size++] = '<'; + memset(dest + size, '<', marker_size); + size += marker_size; if (marker1_size) { dest[size] = ' '; memcpy(dest + size + 1, name1, marker1_size - 1); @@ -178,10 +180,15 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (style == XDL_MERGE_DIFF3) { /* Shared preimage */ if (!dest) { - size += marker_size + 1; + size += marker_size + 1 + marker3_size; } else { - for (j = 0; j < marker_size; j++) - dest[size++] = '|'; + memset(dest + size, '|', marker_size); + size += marker_size; + if (marker3_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name3, marker3_size - 1); + size += marker3_size; + } dest[size++] = '\n'; } size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, @@ -191,8 +198,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (!dest) { size += marker_size + 1; } else { - for (j = 0; j < marker_size; j++) - dest[size++] = '='; + memset(dest + size, '=', marker_size); + size += marker_size; dest[size++] = '\n'; } @@ -202,8 +209,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (!dest) { size += marker_size + 1 + marker2_size; } else { - for (j = 0; j < marker_size; j++) - dest[size++] = '>'; + memset(dest + size, '>', marker_size); + size += marker_size; if (marker2_size) { dest[size] = ' '; memcpy(dest + size + 1, name2, marker2_size - 1); @@ -216,6 +223,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, + const char *ancestor_name, int favor, xdmerge_t *m, char *dest, int style, int marker_size) @@ -228,16 +236,22 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, if (m->mode == 0) size = fill_conflict_hunk(xe1, name1, xe2, name2, + ancestor_name, size, i, style, m, dest, marker_size); - else if (m->mode == 1) - size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0, - dest ? dest + size : NULL); - else if (m->mode == 2) - size += xdl_recs_copy(xe2, m->i2 - m->i1 + i, - m->i1 + m->chg2 - i, 0, + else if (m->mode & 3) { + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, dest ? dest + size : NULL); - else + /* Postimage from side #1 */ + if (m->mode & 1) + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + /* Postimage from side #2 */ + if (m->mode & 2) + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + } else continue; i = m->i1 + m->chg1; } @@ -392,15 +406,19 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, * * returns < 0 on error, == 0 for no conflicts, else number of conflicts */ -static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, - xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, - int flags, xmparam_t const *xmp, mmbuffer_t *result) { +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, + xdfenv_t *xe2, xdchange_t *xscr2, + xmparam_t const *xmp, mmbuffer_t *result) +{ xdmerge_t *changes, *c; xpparam_t const *xpp = &xmp->xpp; + const char *const ancestor_name = xmp->ancestor; + const char *const name1 = xmp->file1; + const char *const name2 = xmp->file2; int i0, i1, i2, chg0, chg1, chg2; - int level = flags & XDL_MERGE_LEVEL_MASK; - int style = flags & XDL_MERGE_STYLE_MASK; - int favor = XDL_MERGE_FAVOR(flags); + int level = xmp->level; + int style = xmp->style; + int favor = xmp->favor; if (style == XDL_MERGE_DIFF3) { /* @@ -534,6 +552,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, if (result) { int marker_size = xmp->marker_size; int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, NULL, style, marker_size); result->ptr = xdl_malloc(size); @@ -542,15 +561,16 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, return -1; } result->size = size; - xdl_fill_merge_buffer(xe1, name1, xe2, name2, favor, changes, + xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, result->ptr, style, marker_size); } return xdl_cleanup_merge(changes); } -int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, - mmfile_t *mf2, const char *name2, - xmparam_t const *xmp, int flags, mmbuffer_t *result) { +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result) +{ xdchange_t *xscr1, *xscr2; xdfenv_t xe1, xe2; int status; @@ -585,9 +605,9 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, memcpy(result->ptr, mf1->ptr, mf1->size); result->size = mf1->size; } else { - status = xdl_do_merge(&xe1, xscr1, name1, - &xe2, xscr2, name2, - flags, xmp, result); + status = xdl_do_merge(&xe1, xscr1, + &xe2, xscr2, + xmp, result); } xdl_free_script(xscr1); xdl_free_script(xscr2); diff --git a/xdiff/xutils.c b/xdiff/xutils.c index bc12f29895..22f9bd692c 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -190,8 +190,10 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) { int i1, i2; + if (s1 == s2 && !memcmp(l1, l2, s1)) + return 1; if (!(flags & XDF_WHITESPACE_FLAGS)) - return s1 == s2 && !memcmp(l1, l2, s1); + return 0; i1 = 0; i2 = 0; |