diff options
642 files changed, 25759 insertions, 9529 deletions
diff --git a/.gitignore b/.gitignore index 3dd6ef7d25..acffdfaae6 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,6 @@ /git-fast-export /git-fast-import /git-fetch -/git-fetch--tool /git-fetch-pack /git-filter-branch /git-fmt-merge-msg @@ -102,12 +101,13 @@ /git-quiltimport /git-read-tree /git-rebase +/git-rebase--am /git-rebase--interactive +/git-rebase--merge /git-receive-pack /git-reflog /git-relink /git-remote -/git-remote-curl /git-remote-http /git-remote-https /git-remote-ftp @@ -127,7 +127,10 @@ /git-rm /git-send-email /git-send-pack +/git-sh-i18n +/git-sh-i18n--envsubst /git-sh-setup +/git-sh-i18n /git-shell /git-shortlog /git-show @@ -160,6 +163,7 @@ /gitk-git/gitk-wish /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi +/gitweb/static/gitweb.js /gitweb/static/gitweb.min.* /test-chmtime /test-ctype @@ -170,6 +174,7 @@ /test-index-version /test-line-buffer /test-match-trees +/test-mktemp /test-obj-pool /test-parse-options /test-path-utils diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index ba2006d892..fe1c1e5bc2 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -152,7 +152,7 @@ Writing Documentation: when writing or modifying command usage strings and synopsis sections in the manual pages: - Placeholders are enclosed in angle brackets: + Placeholders are spelled in lowercase and enclosed in angle brackets: <file> --sort=<key> --abbrev[=<n>] diff --git a/Documentation/RelNotes/1.7.4.1.txt b/Documentation/RelNotes/1.7.4.1.txt new file mode 100644 index 0000000000..79923a6d2f --- /dev/null +++ b/Documentation/RelNotes/1.7.4.1.txt @@ -0,0 +1,27 @@ +Git v1.7.4.1 Release Notes +========================== + +Fixes since v1.7.4 +------------------ + + * On Windows platform, the codepath to spawn a new child process forgot + to first flush the output buffer. + + * "git bundle" did not use OFS_DELTA encoding, making its output a few + per-cent larger than necessarily. + + * The option to tell "git clone" to recurse into the submodules was + misspelled with an underscore "--recurse_submodules". + + * "git diff --cached HEAD" before the first commit does what an end user + would expect (namely, show what would be committed without further "git + add"). + + * "git fast-import" didn't accept the command to ask for "notes" feature + to be present in its input stream, even though it was capable of the + feature. + + * "git fsck" gave up scanning loose object files in directories with + garbage files. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.4.2.txt b/Documentation/RelNotes/1.7.4.2.txt new file mode 100644 index 0000000000..ef4ce1fcd3 --- /dev/null +++ b/Documentation/RelNotes/1.7.4.2.txt @@ -0,0 +1,58 @@ +Git v1.7.4.2 Release Notes +========================== + +Fixes since v1.7.4.1 +-------------------- + + * Many documentation updates to match "git cmd -h" output and the + git-cmd manual page. + + * We used to keep one file descriptor open for each and every packfile + that we have a mmap window on it (read: "in use"), even when for very + tiny packfiles. We now close the file descriptor early when the entire + packfile fits inside one mmap window. + + * "git bisect visualize" tried to run "gitk" in windowing + environments even when "gitk" is not installed, resulting in a + strange error message. + + * "git clone /no/such/path" did not fail correctly. + + * "git commit" did not correctly error out when the user asked to use a + non existent file as the commit message template. + + * "git diff --stat -B" ran on binary files counted the changes in lines, + which was nonsensical. + + * "git diff -M" opportunistically detected copies, which was not + necessarily a good thing, especially when it is internally run by + recursive merge. + + * "git difftool" didn't tell (g)vimdiff that the files it is reading are + to be opened read-only. + + * "git merge" didn't pay attention to prepare-commit-msg hook, even + though if a merge is conflicted and manually resolved, the subsequent + "git commit" would have triggered the hook, which was inconsistent. + + * "git patch-id" (and commands like "format-patch --ignore-in-upstream" + that use it as their internal logic) handled changes to files that end + with incomplete lines incorrectly. + + * The official value to tell "git push" to push the current branch back + to update the upstream branch it forked from is now called "upstream". + The old name "tracking" is and will be supported. + + * "git submodule update" used to honor the --merge/--rebase option (or + corresponding configuration variables) even for a newly cloned + subproject, which made no sense (so/submodule-no-update-first-time). + + * gitweb's "highlight" interface mishandled tabs. + + * gitweb didn't understand timezones with GMT offset that is not + multiple of a whole hour. + + * gitweb had a few forward-incompatible syntactic constructs and + also used incorrect variable when showing the file mode in a diff. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.4.3.txt b/Documentation/RelNotes/1.7.4.3.txt new file mode 100644 index 0000000000..02a3d5bdf6 --- /dev/null +++ b/Documentation/RelNotes/1.7.4.3.txt @@ -0,0 +1,32 @@ +Git v1.7.4.3 Release Notes +========================== + +Fixes since v1.7.4.2 +-------------------- + + * "git apply" used to confuse lines updated by previous hunks as lines + that existed before when applying a hunk, contributing misapplication + of patches with offsets. + + * "git branch --track" (and "git checkout --track --branch") used to + allow setting up a random non-branch that does not make sense to follow + as the "upstream". The command correctly diagnoses it as an error. + + * "git checkout $other_branch" silently removed untracked symbolic links + in the working tree that are in the way in order to check out paths + under it from the named branch. + + * "git cvsimport" did not bail out immediately when the cvs server cannot + be reached, spewing unnecessary error messages that complain about the + server response that it never got. + + * "git diff --quiet" did not work very well with the "--diff-filter" + option. + + * "git grep -n" lacked a long-hand synonym --line-number. + + * "git stash apply" reported the result of its operation by running + "git status" from the top-level of the working tree; it should (and + now does) run it from the user's working directory. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.4.4.txt b/Documentation/RelNotes/1.7.4.4.txt new file mode 100644 index 0000000000..ff06e04a58 --- /dev/null +++ b/Documentation/RelNotes/1.7.4.4.txt @@ -0,0 +1,35 @@ +Git v1.7.4.4 Release Notes +========================== + +Fixes since v1.7.4.3 +-------------------- + + * Compilation of sha1_file.c on BSD platforms were broken due to our + recent use of getrlimit() without including <sys/resource.h>. + + * "git config" did not diagnose incorrect configuration variable names. + + * "git format-patch" did not wrap a long subject line that resulted from + rfc2047 encoding. + + * "git instaweb" should work better again with plackup. + + * "git log --max-count=4 -Sfoobar" now shows 4 commits that changes the + number of occurrences of string "foobar"; it used to scan only for 4 + commits and then emitted only matching ones. + + * "git log --first-parent --boundary $c^..$c" segfaulted on a merge. + + * "git pull" into an empty branch should have behaved as if + fast-forwarding from emptiness to the version being pulled, with + the usual protection against overwriting untracked files. + + * "git submodule" that is run while a merge in the superproject is in + conflicted state tried to process each conflicted submodule up to + three times. + + * "git status" spent all the effort to notice racily-clean index entries + but didn't update the index file to help later operations go faster in + some cases. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.4.5.txt b/Documentation/RelNotes/1.7.4.5.txt new file mode 100644 index 0000000000..b7a0eeb22f --- /dev/null +++ b/Documentation/RelNotes/1.7.4.5.txt @@ -0,0 +1,4 @@ +Git v1.7.4.5 Release Notes +========================== + +This contains only minor documentation fixes accumulated since 1.7.4.4. diff --git a/Documentation/RelNotes/1.7.5.1.txt b/Documentation/RelNotes/1.7.5.1.txt new file mode 100644 index 0000000000..c6ebd76d19 --- /dev/null +++ b/Documentation/RelNotes/1.7.5.1.txt @@ -0,0 +1,47 @@ +Git v1.7.5.1 Release Notes +========================== + +Fixes since v1.7.5 +------------------ + + * When an object "$tree:$path" does not exist, if $path does exist in the + subtree of $tree that corresponds to the subdirectory the user is in, + git now suggests using "$tree:./$path" in addition to the advice to use + the full path from the root of the working tree. + + * The "--date=relative" output format used to say "X years, 12 months" + when it should have said "X+1 years". + + * The smart-HTTP transfer was broken in 1.7.5 when the client needs + to issue a small POST (which uses content-length) and then a large + POST (which uses chunked) back to back. + + * "git clean" used to fail on an empty directory that is not readable, + even though rmdir(2) could remove such a directory. Now we attempt it + as the last resort. + + * The "--dirstat" option of "diff" family of commands used to totally + ignore a change that only rearranged lines within a file. Such a + change now counts as at least a minimum but non zero change. + + * The "--dirstat" option of "diff" family of commands used to use the + pathname in the original, instead of the pathname in the result, + when renames are involved. + + * "git pack-object" did not take core.bigfilethreashold into account + (unlike fast-import); now it does. + + * "git reflog" ignored options like "--format=.." on the command line. + + * "git stash apply" used to refuse to work if there was any change in + the working tree, even when the change did not overlap with the change + the stash recorded. + + * "git stash apply @{99999}" was not diagnosed as an error, even when you + did not have that many stash entries. + + * An error message from "git send-email" to diagnose a broken SMTP + connection configuration lacked a space between "hello=<smtp-domain>" + and "port=<smtp-server-port>". + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.5.2.txt b/Documentation/RelNotes/1.7.5.2.txt new file mode 100644 index 0000000000..951eb7cb08 --- /dev/null +++ b/Documentation/RelNotes/1.7.5.2.txt @@ -0,0 +1,57 @@ +Git v1.7.5.2 Release Notes +========================== + +The release notes to 1.7.5.1 forgot to mention: + + * "git stash -p --no-keep-index" and "git stash --no-keep-index -p" now + mean the same thing. + + * "git upload-pack" (hence "git push" over git native protocol) had a + subtle race condition that could lead to a deadlock. + +Fixes since v1.7.5.1 +-------------------- + + * "git add -p" did not work correctly when a hunk is split and then + one of them was given to the editor. + + * "git add -u" did not resolve a conflict where our history deleted and + their history modified the same file, and the working tree resolved to + keep a file. + + * "git cvsimport" did not know that CVSNT stores its password file in a + location different from the traditional CVS. + + * "git diff-files" did not show the mode information from the working + tree side of an unmerged path correctly. + + * "git diff -M --cached" used to use unmerged path as a possible rename + source candidate, which made no sense. + + * The option name parser in "git fast-import" used prefix matches for + some options where it shouldn't, and accepted non-existent options, + e.g. "--relative-marksmith" or "--forceps". + + * "git format-patch" did not quote RFC822 special characters in the + email address (e.g From: Junio C. Hamano <jch@example.com>, not + From: "Junio C. Hamano" <jch@example.com>). + + * "git format-patch" when run with "--quiet" option used to produce a + nonsense result that consists of alternating empty output. + + * In "git merge", per-branch branch.<name>.mergeoptions configuration + variables did not override the fallback default merge.<option> + configuration variables such as merge.ff, merge.log, etc. + + * "git merge-one-file" did not honor GIT_WORK_TREE settings when + handling a "both sides added, differently" conflict. + + * "git mergetool" did not handle conflicted submoudules gracefully. + + * "git-p4" (in contrib) used a wrong base image while merge a file that + was added on both branches differently. + + * "git rebase -i -p" failed to preserve the history when there is a + redundant merge created with the --no-ff option. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.5.3.txt b/Documentation/RelNotes/1.7.5.3.txt new file mode 100644 index 0000000000..9c03353af2 --- /dev/null +++ b/Documentation/RelNotes/1.7.5.3.txt @@ -0,0 +1,32 @@ +Git v1.7.5.3 Release Notes +========================== + +Fixes since v1.7.5.2 +-------------------- + + * The bash completion scripts should correctly work using zsh's bash + completion emulation layer now. + + * Setting $(prefix) in config.mak did not affect where etc/gitconfig + file is read from, even though passing it from the command line of + $(MAKE) did. + + * The logic to handle "&" (expand to UNIX username) in GECOS field + miscounted the length of the name it formatted. + + * "git cherry-pick -s resolve" failed to cherry-pick a root commit. + + * "git diff --word-diff" misbehaved when diff.suppress-blank-empty was + in effect. + + * "git log --stdin path" with an input that has additional pathspec + used to corrupt memory. + + * "git send-pack" (hence "git push") over smalt-HTTP protocol could + deadlock when the client side pack-object died early. + + * Compressed tarball gitweb generates used to be made with the timestamp + of the tarball generation; this was bad because snapshot from the same + tree should result in a same tarball. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.5.4.txt b/Documentation/RelNotes/1.7.5.4.txt new file mode 100644 index 0000000000..f3412ced33 --- /dev/null +++ b/Documentation/RelNotes/1.7.5.4.txt @@ -0,0 +1,18 @@ +Git v1.7.5.4 Release Notes +========================== + +Fixes since v1.7.5.3 +-------------------- + + * The single-key mode of "git add -p" was easily fooled into thinking + that it was told to add everthing ('a') when up-arrow was pressed by + mistake. + + * "git diff -C -C" used to disable the rename detection entirely when + there are too many copy candidate paths in the tree; now it falls + back to "-C" when doing so would keep the copy candidate paths + under the rename detection limit. + + * "git rerere" did not diagnose a corrupt MERGE_RR file in some cases. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.5.txt b/Documentation/RelNotes/1.7.5.txt new file mode 100644 index 0000000000..987919c321 --- /dev/null +++ b/Documentation/RelNotes/1.7.5.txt @@ -0,0 +1,132 @@ +Git v1.7.5 Release Notes +======================== + +Updates since v1.7.4 +-------------------- + + * Various MinGW portability fixes. + + * Various git-p4 enhancements (in contrib). + + * Various vcs-svn, git-svn and gitk enhancements and fixes. + + * Various git-gui updates (0.14.0). + + * Update to more modern HP-UX port. + + * The codebase is getting prepared for i18n/l10n; no translated + strings nor translation mechanism in the code yet, but the strings + are being marked for l10n. + + * The bash completion script can now complete symmetric difference + for "git diff" command, e.g. "git diff ...bra<TAB>". + + * The default minimum length of abbreviated and unique object names + can now be configured by setting the core.abbrev configuration + variable. + + * "git apply -v" reports offset lines when the patch does not apply at + the exact location recorded in the diff output. + + * "git config" used to be also known as "git repo-config", but the old + name is now officially deprecated. + + * "git checkout --detach <commit>" is a more user friendly synonym for + "git checkout <commit>^0". + + * "git checkout" performed on detached HEAD gives a warning and + advice when the commit being left behind will become unreachable from + any branch or tag. + + * "git cherry-pick" and "git revert" can be told to use a custom merge + strategy, similar to "git rebase". + + * "git cherry-pick" remembers which commit failed to apply when it is + stopped by conflicts, making it unnecessary to use "commit -c $commit" + to conclude it. + + * "git cvsimport" bails out immediately when the cvs server cannot be + reached, without spewing unnecessary error messages that complain about + the server response it never got. + + * "git fetch" vs "git upload-pack" transfer learned 'no-done' + protocol extension to save one round-trip after the content + negotiation is done. This saves one HTTP RPC, reducing the overall + latency for a trivial fetch. + + * "git fetch" can be told to recursively fetch submodules on-demand. + + * "git grep -f <filename>" learned to treat "-" as "read from the + standard input stream". + + * "git grep --no-index" did not honor pathspecs correctly, returning + paths outside the specified area. + + * "git init" learned the --separate-git-dir option to allow the git + directory for a new repository created elsewhere and linked via the + gitdir mechanism. This is primarily to help submodule support later + to switch between a branch of superproject that has the submodule + and another that does not. + + * "git log" type commands now understand globbing pathspecs. You + can say "git log -- '*.txt'" for example. + + * "git log" family of commands learned --cherry and --cherry-mark + options that can be used to view two diverged branches while omitting + or highlighting equivalent changes that appear on both sides of a + symmetric difference (e.g. "log --cherry A...B"). + + * A lazy "git merge" that didn't say what to merge used to be an error. + When run on a branch that has an upstream defined, however, the command + now merges from the configured upstream. + + * "git mergetool" learned how to drive "beyond compare 3" as well. + + * "git rerere forget" without pathspec used to forget all the saved + conflicts that relate to the current merge; it now requires you to + give it pathspecs. + + * "git rev-list --objects $revs -- $pathspec" now limits the objects listed + in its output properly with the pathspec, in preparation for narrow + clones. + + * "git push" with no parameters gives better advice messages when + "tracking" is used as the push.default semantics or there is no remote + configured yet. + + * A possible value to the "push.default" configuration variable, + 'tracking', gained a synonym that more naturally describes what it + does, 'upstream'. + + * "git rerere" learned a new subcommand "remaining" that is similar to + "status" and lists the paths that had conflicts which are known to + rerere, but excludes the paths that have already been marked as + resolved in the index from its output. "git mergetool" has been + updated to use this facility. + +Also contains various documentation updates. + + +Fixes since v1.7.4 +------------------ + +All of the fixes in the v1.7.4.X maintenance series are included in this +release, unless otherwise noted. + + * "git fetch" from a client that is mostly following the remote + needlessly told all of its refs to the server for both sides to + compute the set of objects that need to be transferred efficiently, + instead of stopping when the server heard enough. In a project with + many tags, this turns out to be extremely wasteful, especially over + the smart HTTP transport (sp/maint-{upload,fetch}-pack-stop-early~1). + + * "git fetch" run from a repository that uses the same repository as + its alternate object store as the repository it is fetching from + did not tell the server that it already has access to objects + reachable from the refs in their common alternate object store, + causing it to fetch unnecessary objects (jc/maint-fetch-alt). + + * "git remote add --mirror" created a configuration that is suitable for + doing both a mirror fetch and a mirror push at the same time, which + made little sense. We now warn and require the command line to specify + either --mirror=fetch or --mirror=push. diff --git a/Documentation/RelNotes/1.7.6.txt b/Documentation/RelNotes/1.7.6.txt new file mode 100644 index 0000000000..7e1c7f1773 --- /dev/null +++ b/Documentation/RelNotes/1.7.6.txt @@ -0,0 +1,131 @@ +Git v1.7.6 Release Notes (draft) +======================== + +Updates since v1.7.5 +-------------------- + + * Various git-svn updates. + + * Updates the way content tags are handled in gitweb. Also adds + a UI to choose common timezone for displaying the dates. + + * Similar to branch names, tagnames that begin with "-" are now + disallowed. + + * Clean-up of the C part of i18n (but not l10n---please wait) + continues. + + * The scripting part of the codebase is getting prepared for i18n/l10n. + + * Pushing and pulling from a repository with large number of refs that + point to identical commits are optimized by not listing the same commit + during the common ancestor negotiation exchange with the other side. + + * Adding a file larger than core.bigfilethreshold (defaults to 1/2 Gig) + using "git add" will send the contents straight to a packfile without + having to hold it and its compressed representation both at the same + time in memory. + + * Processes spawned by "[alias] <name> = !process" in the configuration + can inspect GIT_PREFIX environment variable to learn where in the + working tree the original command was invoked. + + * A magic pathspec ":/" tells a command that limits its operation to + the current directory when ran from a subdirectory to work on the + entire working tree. In general, ":/path/to/file" would be relative + to the root of the working tree hierarchy. + + After "git reset --hard; edit Makefile; cd t/", "git add -u" would + be a no-op, but "git add -u :/" would add the updated contents of + the Makefile at the top level. If you want to name a path in the + current subdirectory whose unusual name begins with ":/", you can + name it by "./:/that/path" or by "\:/that/path". + + * "git blame" learned "--abbrev[=<n>]" option to control the minimum + number of hexdigits shown for commit object names. + + * "git blame" learned "--line-porcelain" that is less efficient but is + easier to parse. + + * Aborting "git commit --interactive" discards updates to the index + made during the interctive session. + + * "git commit" learned a "--patch" option to directly jump to the + per-hunk selection UI of the interactive mode. + + * "git diff -C -C" used to disable the rename detection entirely when + there are too many copy candidate paths in the tree; now it falls + back to "-C" when doing so would keep the copy candidate paths + under the rename detection limit. + + * "git diff" and its family of commands learned --dirstat=0 to show + directories that contribute less than 0.1% of changes. + + * "git diff" and its family of commands learned --dirstat=lines mode to + assess damage to the directory based on number of lines in the patch + output, not based on the similarity numbers. + + * "git format-patch" learned "--quiet" option to suppress the output of + the names of generated files. + + * "git format-patch" quotes people's names when it has RFC822 special + characters in it, e.g. "Junio C. Hamano" <jch@example.com>. Earlier + it was up to the user to do this when using its output. + + * "git log" and friends learned a new "--notes" option to replace the + "--show-notes" option. Unlike "--show-notes", "--notes=<ref>" does + not imply showing the default notes. + + * They also learned a log.abbrevCommit configuration variable to augment + the --abbrev-commit command line option. + + * "git ls-remote" learned "--exit-code" option to consider it a + different kind of error when no remote ref to be shown. + + * "git merge" learned "-" as a short-hand for "the previous branch", just + like the way "git checkout -" works. + + * "git merge" uses "merge.ff" configuration variable to decide to always + create a merge commit (i.e. --no-ff, aka merge.ff=no), refuse to create + a merge commit (i.e. --ff-only, aka merge.ff=only). Setting merge.ff=yes + (or not setting it at all) restores the default behaviour of allowing + fast-forward to happen when possible. + + * p4-import (from contrib) learned a new option --preserve-user. + + * "git rebase" that does not specify on top of which branch to rebase + the current branch now uses @{upstream} of the current branch. + + * "git rev-list --count" used with "--cherry-mark" counts the cherry-picked + commits separately, producing more a useful output. + + * "git submodule update" learned "--force" option to get rid of local + changes in submodules and replace them with the up-to-date version. + + * "git status" and friends ignore .gitmodules file while the file is + still in a conflicted state during a merge, to avoid using information + that is not final and possibly corrupt with conflict markers. + +Also contains various documentation updates and minor miscellaneous +changes. + + +Fixes since v1.7.5 +------------------ + +Unless otherwise noted, all the fixes in 1.7.5.X maintenance track are +included in this release. + + * The single-key mode of "git add -p" was easily fooled into thinking + that it was told to add everthing ('a') when up-arrow was pressed by + mistake. + (merge tr/add-i-no-escape later) + + * "git config" used to choke with an insanely long line. + (merge ef/maint-strbuf-init later) + +--- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.7.5.3-365-g7eacc2b +git shortlog --no-merges ^maint ^$O master diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 72741ebda1..938eccf2a5 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -10,10 +10,18 @@ Checklist (and a short version for the impatient): 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 + . explains the problem the change tries to solve, iow, what + is wrong with the current code without the change. + . justifies the way the change solves the problem, iow, why + the result with the change is better. + . alternate solutions considered but discarded, if any. + - describe changes in imperative mood, e.g. "make xyzzy do frotz" + instead of "[This patch] makes xyzzy do frotz" or "[I] changed + xyzzy to do frotz", as if you are giving orders to the codebase + to change its behaviour. + - try to make sure your explanation can be understood without + external resources. Instead of giving a URL to a mailing list + archive, summarize the relevant points of the discussion. - 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 @@ -90,7 +98,10 @@ your commit head. Instead, always make a commit with complete commit message and generate a series of patches from your repository. It is a good discipline. -Describe the technical detail of the change(s). +Give an explanation for the change(s) that is detailed enough so +that people can judge if it is good thing to do, without reading +the actual patch text to determine how well the code does what +the explanation promises to do. If your description starts to get too long, that's a sign that you probably need to split up your commit to finer grained pieces. @@ -99,9 +110,8 @@ help reviewers check the patch, and future maintainers understand the code, are the most beautiful patches. Descriptions that summarise the point in the subject well, and describe the motivation for the change, the approach taken by the change, and if relevant how this -differs substantially from the prior version, can be found on Usenet -archives back into the late 80's. Consider it like good Netiquette, -but for code. +differs substantially from the prior version, are all good things +to have. Oh, another thing. I am picky about whitespaces. Make sure your changes do not trigger errors with the sample pre-commit hook shipped @@ -266,7 +276,7 @@ don't hide your real name. If you like, you can put extra tags at the end: -1. "Reported-by:" is used to to credit someone who found the bug that +1. "Reported-by:" is used to credit someone who found the bug that the patch attempts to fix. 2. "Acked-by:" says that the person who is more familiar with the area the patch attempts to modify liked the patch. @@ -334,50 +344,20 @@ MUA specific hints Some of patches I receive or pick up from the list share common patterns of breakage. Please make sure your MUA is set up -properly not to corrupt whitespaces. Here are two common ones -I have seen: +properly not to corrupt whitespaces. -* Empty context lines that do not have _any_ whitespace. +See the DISCUSSION section of git-format-patch(1) for hints on +checking your patch by mailing it to yourself and applying with +git-am(1). -* Non empty context lines that have one extra whitespace at the - beginning. - -One test you could do yourself if your MUA is set up correctly is: - -* Send the patch to yourself, exactly the way you would, except - To: and Cc: lines, which would not contain the list and - maintainer address. - -* Save that patch to a file in UNIX mailbox format. Call it say - a.patch. - -* Try to apply to the tip of the "master" branch from the - git.git public repository: - - $ git fetch http://kernel.org/pub/scm/git/git.git master:test-apply - $ git checkout test-apply - $ git reset --hard - $ git am a.patch - -If it does not apply correctly, there can be various reasons. - -* Your patch itself does not apply cleanly. That is _bad_ but - does not have much to do with your MUA. Please rebase the - patch appropriately. - -* Your MUA corrupted your patch; "am" would complain that - the patch does not apply. Look at .git/rebase-apply/ subdirectory and - see what 'patch' file contains and check for the common - corruption patterns mentioned above. - -* While you are at it, check what are in 'info' and - 'final-commit' files as well. If what is in 'final-commit' is - not exactly what you would want to see in the commit log - message, it is very likely that your maintainer would end up - hand editing the log message when he applies your patch. - Things like "Hi, this is my first patch.\n", if you really - want to put in the patch e-mail, should come after the - three-dash line that signals the end of the commit message. +While you are at it, check the resulting commit log message from +a trial run of applying the patch. If what is in the resulting +commit is not exactly what you would want to see, it is very +likely that your maintainer would end up hand editing the log +message when he applies your patch. Things like "Hi, this is my +first patch.\n", if you really want to put in the patch e-mail, +should come after the three-dash line that signals the end of the +commit message. Pine @@ -433,89 +413,10 @@ that or Gentoo did it.) So you need to set the it. -Thunderbird ------------ - -(A Large Angry SCM) - -By default, Thunderbird will both wrap emails as well as flag them as -being 'format=flowed', both of which will make the resulting email unusable -by git. - -Here are some hints on how to successfully submit patches inline using -Thunderbird. - -There are two different approaches. One approach is to configure -Thunderbird to not mangle patches. The second approach is to use -an external editor to keep Thunderbird from mangling the patches. - -Approach #1 (configuration): - -This recipe is current as of Thunderbird 2.0.0.19. Three steps: - 1. Configure your mail server composition as plain text - Edit...Account Settings...Composition & Addressing, - uncheck 'Compose Messages in HTML'. - 2. Configure your general composition window to not wrap - Edit..Preferences..Composition, wrap plain text messages at 0 - 3. Disable the use of format=flowed - Edit..Preferences..Advanced..Config Editor. Search for: - mailnews.send_plaintext_flowed - toggle it to make sure it is set to 'false'. - -After that is done, you should be able to compose email as you -otherwise would (cut + paste, git-format-patch | git-imap-send, etc), -and the patches should not be mangled. - -Approach #2 (external editor): - -This recipe appears to work with the current [*1*] Thunderbird from Suse. - -The following Thunderbird extensions are needed: - AboutConfig 0.5 - http://aboutconfig.mozdev.org/ - External Editor 0.7.2 - http://globs.org/articles.php?lng=en&pg=8 - -1) Prepare the patch as a text file using your method of choice. - -2) Before opening a compose window, use Edit->Account Settings to -uncheck the "Compose messages in HTML format" setting in the -"Composition & Addressing" panel of the account to be used to send the -patch. [*2*] - -3) In the main Thunderbird window, _before_ you open the compose window -for the patch, use Tools->about:config to set the following to the -indicated values: - mailnews.send_plaintext_flowed => false - mailnews.wraplength => 0 - -4) Open a compose window and click the external editor icon. - -5) In the external editor window, read in the patch file and exit the -editor normally. - -6) Back in the compose window: Add whatever other text you wish to the -message, complete the addressing and subject fields, and press send. - -7) Optionally, undo the about:config/account settings changes made in -steps 2 & 3. +Thunderbird, KMail, GMail +------------------------- - -[Footnotes] -*1* Version 1.0 (20041207) from the MozillaThunderbird-1.0-5 rpm of Suse -9.3 professional updates. - -*2* It may be possible to do this with about:config and the following -settings but I haven't tried, yet. - mail.html_compose => false - mail.identity.default.compose_html => false - mail.identity.id?.compose_html => false - -(Lukas Sandström) - -There is a script in contrib/thunderbird-patch-inline which can help -you include patches with Thunderbird in an easy way. To use it, do the -steps above and then use the script as the external editor. +See the MUA-SPECIFIC HINTS section of git-format-patch(1). Gnus ---- @@ -530,72 +431,3 @@ characters (most notably in people's names), and also whitespaces (fatal in patches). Running 'C-u g' to display the message in raw form before using '|' to run the pipe can work this problem around. - - -KMail ------ - -This should help you to submit patches inline using KMail. - -1) Prepare the patch as a text file. - -2) Click on New Mail. - -3) Go under "Options" in the Composer window and be sure that -"Word wrap" is not set. - -4) Use Message -> Insert file... and insert the patch. - -5) Back in the compose window: add whatever other text you wish to the -message, complete the addressing and subject fields, and press send. - - -Gmail ------ - -GMail does not appear to have any way to turn off line wrapping in the web -interface, so this will mangle any emails that you send. You can however -use "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 - -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] - folder = "[Gmail]/Drafts" - host = imaps://imap.gmail.com - user = user@gmail.com - pass = p4ssw0rd - port = 993 - sslverify = false - -You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error -that the "Folder doesn't exist". - -Once your commits are ready to be sent to the mailing list, run the -following commands: - - $ git format-patch --cover-letter -M --stdout origin/master | git imap-send - -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 16e3c68576..e76195ac97 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -52,6 +52,11 @@ of lines before or after the line given by <start>. --porcelain:: Show in a format designed for machine consumption. +--line-porcelain:: + Show the porcelain format, but output commit information for + each line, not just the first time a commit is referenced. + Implies --porcelain. + --incremental:: Show the result incrementally in a format designed for machine consumption. diff --git a/Documentation/config.txt b/Documentation/config.txt index c5e183516a..6b93777199 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -62,7 +62,7 @@ Internal whitespace within a variable value is retained verbatim. The values following the equals sign in variable assign are all either a string, an integer, or a boolean. Boolean values may be given as yes/no, -0/1, true/false or on/off. Case is not significant in boolean values, when +1/0, true/false or on/off. Case is not significant in boolean values, when converting value to the canonical form using '--bool' type specifier; 'git config' will ensure that the output is "true" or "false". @@ -320,7 +320,7 @@ core.worktree:: Set the path to the root of the working tree. This can be overridden by the GIT_WORK_TREE environment variable and the '--work-tree' command line option. - The value can an absolute path or relative to the path to + The value can be an absolute path or relative to the path to the .git directory, which is either specified by --git-dir or GIT_DIR, or automatically discovered. If --git-dir or GIT_DIR is specified but none of @@ -376,15 +376,6 @@ core.warnAmbiguousRefs:: If true, git will warn you if the ref name you passed it is ambiguous and might match multiple refs in the .git/refs/ tree. True by default. -core.abbrevguard:: - Even though git makes sure that it uses enough hexdigits to show - an abbreviated object name unambiguously, as more objects are - added to the repository over time, a short name that used to be - unique will stop being unique. Git uses this many extra hexdigits - that are more than necessary to make the object name currently - unique, in the hope that its output will stay unique a bit longer. - Defaults to 0. - core.compression:: An integer -1..9, indicating a default compression level. -1 is the zlib default. 0 means no compression, @@ -451,8 +442,6 @@ for most projects as source code and other text files can still be delta compressed, but larger binary media files won't be. + Common unit suffixes of 'k', 'm', or 'g' are supported. -+ -Currently only linkgit:git-fast-import[1] honors this setting. core.excludesfile:: In addition to '.gitignore' (per-directory) and @@ -567,6 +556,12 @@ core.sparseCheckout:: Enable "sparse checkout" feature. See section "Sparse checkout" in linkgit:git-read-tree[1] for more information. +core.abbrev:: + Set the length object names are abbreviated to. If unspecified, + many commands abbreviate to 7 hexdigits, which may not be enough + for abbreviated object names to stay unique for sufficiently long + time. + add.ignore-errors:: add.ignoreErrors:: Tells 'git add' to continue adding files when some files cannot be @@ -592,6 +587,8 @@ it will be treated as a shell command. For example, defining "gitk --all --not ORIG_HEAD". Note that shell commands will be executed from the top-level directory of a repository, which may not necessarily be the current directory. +'GIT_PREFIX' is set as returned by running 'git rev-parse --show-prefix' +from the original current directory. See linkgit:git-rev-parse[1]. am.keepcr:: If true, git-am will call git-mailsplit for patches in mbox format @@ -646,7 +643,7 @@ branch.<name>.remote:: branch.<name>.merge:: Defines, together with branch.<name>.remote, the upstream branch - for the given branch. It tells 'git fetch'/'git pull' which + for the given branch. It tells 'git fetch'/'git pull'/'git rebase' which branch to merge and can also affect 'git push' (see push.default). When in branch <name>, it tells 'git fetch' the default refspec to be marked for merging in FETCH_HEAD. The value is @@ -711,9 +708,16 @@ second is the background. The position of the attribute, if any, doesn't matter. color.diff:: - When set to `always`, always use colors in patch. - When false (or `never`), never. When set to `true` or `auto`, use - colors only when the output is to the terminal. Defaults to false. + Whether to use ANSI escape sequences to add color to patches. + If this is set to `always`, linkgit:git-diff[1], + linkgit:git-log[1], and linkgit:git-show[1] will use color + for all patches. If it is set to `true` or `auto`, those + commands will only use color when output is to the terminal. + Defaults to false. ++ +This does not affect linkgit:git-format-patch[1] nor the +'git-diff-{asterisk}' plumbing commands. Can be overridden on the +command line with the `--color[=<when>]` option. color.diff.<slot>:: Use customized color for diff colorization. `<slot>` specifies @@ -799,11 +803,15 @@ color.status.<slot>:: color.branch.<slot>. color.ui:: - When set to `always`, always use colors in all git commands which - are capable of colored output. When false (or `never`), never. When - set to `true` or `auto`, use colors only when the output is to the - terminal. When more specific variables of color.* are set, they always - take precedence over this setting. Defaults to false. + This variable determines the default value for variables such + as `color.diff` and `color.grep` that control the use of color + per command family. Its scope will expand as more commands learn + configuration to set a default for the `--color` option. Set it + to `always` if you want all output not intended for machine + consumption to use color, to `true` or `auto` if you want such + output to use color when written to the terminal, or to `false` or + `never` if you prefer git commands not to use color unless enabled + explicitly with some other configuration or the `--color` option. commit.status:: A boolean to enable/disable inclusion of status information in the @@ -815,68 +823,7 @@ commit.template:: "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the specified user's home directory. -diff.autorefreshindex:: - When using 'git diff' to compare with work tree - files, do not consider stat-only change as changed. - Instead, silently run `git update-index --refresh` to - update the cached stat information for paths whose - contents in the work tree match the contents in the - index. This option defaults to true. Note that this - affects only 'git diff' Porcelain, and not lower level - 'diff' commands such as 'git diff-files'. - -diff.external:: - If this config variable is set, diff generation is not - performed using the internal diff machinery, but using the - given command. Can be overridden with the `GIT_EXTERNAL_DIFF' - environment variable. The command is called with parameters - as described under "git Diffs" in linkgit:git[1]. Note: if - you want to use an external diff program only on a subset of - your files, you might want to use linkgit:gitattributes[5] instead. - -diff.mnemonicprefix:: - If set, 'git diff' uses a prefix pair that is different from the - 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: -`git diff`;; - compares the (i)ndex and the (w)ork tree; -`git diff HEAD`;; - compares a (c)ommit and the (w)ork tree; -`git diff --cached`;; - compares a (c)ommit and the (i)ndex; -`git diff HEAD:file1 file2`;; - compares an (o)bject and a (w)ork tree entity; -`git diff --no-index a b`;; - compares two non-git things (1) and (2). - -diff.noprefix:: - If set, 'git diff' does not show any source or destination prefix. - -diff.renameLimit:: - The number of files to consider when performing the copy/rename - detection; equivalent to the 'git diff' option '-l'. - -diff.renames:: - Tells git to detect renames. If set to any boolean value, it - will enable basic rename detection. If set to "copies" or - "copy", it will detect copies, as well. - -diff.ignoreSubmodules:: - Sets the default value of --ignore-submodules. Note that this - affects only 'git diff' Porcelain, and not lower level 'diff' - commands such as 'git diff-files'. 'git checkout' also honors - this setting when reporting uncommitted changes. - -diff.suppressBlankEmpty:: - A boolean to inhibit the standard behavior of printing a space - before each empty output line. Defaults to false. - -diff.tool:: - Controls which diff tool is used. `diff.tool` overrides - `merge.tool` when used by linkgit:git-difftool[1] and has - the same valid values as `merge.tool` minus "tortoisemerge" - and plus "kompare". +include::diff-config.txt[] difftool.<tool>.path:: Override the path for the given tool. This is useful in case @@ -900,9 +847,13 @@ diff.wordRegex:: characters are *ignorable* whitespace. fetch.recurseSubmodules:: - A boolean value which changes the behavior for fetch and pull, the - default is to not recursively fetch populated submodules unless - configured otherwise. + This option can be either set to a boolean value or to 'on-demand'. + Setting it to a boolean changes the behavior of fetch and pull to + unconditionally recurse into submodules when set to true or to not + recurse at all when set to false. When set to 'on-demand' (the default + value), fetch and pull will only recurse into a populated submodule + when its superproject retrieves a commit that updates the submodule's + reference. fetch.unpackLimit:: If the number of objects fetched over the git native @@ -976,6 +927,16 @@ format.signoff:: the rights to submit this work under the same open source license. Please see the 'SubmittingPatches' document for further discussion. +filter.<driver>.clean:: + The command which is used to convert the content of a worktree + file to a blob upon checkin. See linkgit:gitattributes[5] for + details. + +filter.<driver>.smudge:: + The command which is used to convert the content of a blob + object to a worktree file upon checkout. See + linkgit:gitattributes[5] for details. + gc.aggressiveWindow:: The window size parameter used in the delta compression algorithm used by 'git gc --aggressive'. This defaults @@ -1101,6 +1062,12 @@ All gitcvs variables except for 'gitcvs.usecrlfattr' and is one of "ext" and "pserver") to make them apply only for the given access method. +grep.lineNumber:: + If set to true, enable '-n' option by default. + +grep.extendedRegexp:: + If set to true, enable '--extended-regexp' option by default. + gui.commitmsgwidth:: Defines how wide the commit message window is in the linkgit:git-gui[1]. "75" is the default. @@ -1342,9 +1309,16 @@ instaweb.port:: interactive.singlekey:: In interactive commands, allow the user to provide one-letter input with a single key (i.e., without hitting enter). - Currently this is used only by the `\--patch` mode of - linkgit:git-add[1]. Note that this setting is silently - ignored if portable keystroke input is not available. + Currently this is used by the `\--patch` mode of + linkgit:git-add[1], linkgit:git-checkout[1], linkgit:git-commit[1], + linkgit:git-reset[1], and linkgit:git-stash[1]. Note that this + setting is silently ignored if portable keystroke input + is not available. + +log.abbrevCommit:: + If true, makes linkgit:git-log[1], linkgit:git-show[1], and + linkgit:git-whatchanged[1] assume `\--abbrev-commit`. You may + override this option with `\--no-abbrev-commit`. log.date:: Set the default date-time mode for the 'log' command. @@ -1591,7 +1565,8 @@ push.default:: * `matching` - push all matching branches. All branches having the same name in both ends are considered to be matching. This is the default. -* `tracking` - push the current branch to its upstream branch. +* `upstream` - push the current branch to its upstream branch. +* `tracking` - deprecated synonym for `upstream`. * `current` - push the current branch to a branch of the same name. rebase.stat:: @@ -1819,7 +1794,7 @@ submodule.<name>.update:: linkgit:git-submodule[1] and linkgit:gitmodules[5] for details. submodule.<name>.fetchRecurseSubmodules:: - This option can be used to enable/disable recursive fetching of this + This option can be used to control recursive fetching of this submodule. It can be overridden by using the --[no-]recurse-submodules command line option to "git fetch" and "git pull". This setting will override that from in the linkgit:gitmodules[5] diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt new file mode 100644 index 0000000000..1aed79e7dc --- /dev/null +++ b/Documentation/diff-config.txt @@ -0,0 +1,136 @@ +diff.autorefreshindex:: + When using 'git diff' to compare with work tree + files, do not consider stat-only change as changed. + Instead, silently run `git update-index --refresh` to + update the cached stat information for paths whose + contents in the work tree match the contents in the + index. This option defaults to true. Note that this + affects only 'git diff' Porcelain, and not lower level + 'diff' commands such as 'git diff-files'. + +diff.dirstat:: + A comma separated list of `--dirstat` parameters specifying the + default behavior of the `--dirstat` option to linkgit:git-diff[1]` + and friends. The defaults can be overridden on the command line + (using `--dirstat=<param1,param2,...>`). The fallback defaults + (when not changed by `diff.dirstat`) are `changes,noncumulative,3`. + The following parameters are available: ++ +-- +`changes`;; + Compute the dirstat numbers by counting the lines that have been + removed from the source, or added to the destination. This ignores + the amount of pure code movements within a file. In other words, + rearranging lines in a file is not counted as much as other changes. + This is the default behavior when no parameter is given. +`lines`;; + Compute the dirstat numbers by doing the regular line-based diff + analysis, and summing the removed/added line counts. (For binary + files, count 64-byte chunks instead, since binary files have no + natural concept of lines). This is a more expensive `--dirstat` + behavior than the `changes` behavior, but it does count rearranged + lines within a file as much as other changes. The resulting output + is consistent with what you get from the other `--*stat` options. +`files`;; + Compute the dirstat numbers by counting the number of files changed. + Each changed file counts equally in the dirstat analysis. This is + the computationally cheapest `--dirstat` behavior, since it does + not have to look at the file contents at all. +`cumulative`;; + Count changes in a child directory for the parent directory as well. + Note that when using `cumulative`, the sum of the percentages + reported may exceed 100%. The default (non-cumulative) behavior can + be specified with the `noncumulative` parameter. +<limit>;; + An integer parameter specifies a cut-off percent (3% by default). + Directories contributing less than this percentage of the changes + are not shown in the output. +-- ++ +Example: The following will count changed files, while ignoring +directories with less than 10% of the total amount of changed files, +and accumulating child directory counts in the parent directories: +`files,10,cumulative`. + +diff.external:: + If this config variable is set, diff generation is not + performed using the internal diff machinery, but using the + given command. Can be overridden with the `GIT_EXTERNAL_DIFF' + environment variable. The command is called with parameters + as described under "git Diffs" in linkgit:git[1]. Note: if + you want to use an external diff program only on a subset of + your files, you might want to use linkgit:gitattributes[5] instead. + +diff.ignoreSubmodules:: + Sets the default value of --ignore-submodules. Note that this + affects only 'git diff' Porcelain, and not lower level 'diff' + commands such as 'git diff-files'. 'git checkout' also honors + this setting when reporting uncommitted changes. + +diff.mnemonicprefix:: + If set, 'git diff' uses a prefix pair that is different from the + 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: +`git diff`;; + compares the (i)ndex and the (w)ork tree; +`git diff HEAD`;; + compares a (c)ommit and the (w)ork tree; +`git diff --cached`;; + compares a (c)ommit and the (i)ndex; +`git diff HEAD:file1 file2`;; + compares an (o)bject and a (w)ork tree entity; +`git diff --no-index a b`;; + compares two non-git things (1) and (2). + +diff.noprefix:: + If set, 'git diff' does not show any source or destination prefix. + +diff.renameLimit:: + The number of files to consider when performing the copy/rename + detection; equivalent to the 'git diff' option '-l'. + +diff.renames:: + Tells git to detect renames. If set to any boolean value, it + will enable basic rename detection. If set to "copies" or + "copy", it will detect copies, as well. + +diff.suppressBlankEmpty:: + A boolean to inhibit the standard behavior of printing a space + before each empty output line. Defaults to false. + +diff.<driver>.command:: + The custom diff driver command. See linkgit:gitattributes[5] + for details. + +diff.<driver>.xfuncname:: + The regular expression that the diff driver should use to + recognize the hunk header. A built-in pattern may also be used. + See linkgit:gitattributes[5] for details. + +diff.<driver>.binary:: + Set this option to true to make the diff driver treat files as + binary. See linkgit:gitattributes[5] for details. + +diff.<driver>.textconv:: + The command that the diff driver should call to generate the + text-converted version of a file. The result of the + conversion is used to generate a human-readable diff. See + linkgit:gitattributes[5] for details. + +diff.<driver>.wordregex:: + The regular expression that the diff driver should use to + split words in a line. See linkgit:gitattributes[5] for + details. + +diff.<driver>.cachetextconv:: + Set this option to true to make the diff driver cache the text + conversion outputs. See linkgit:gitattributes[5] for details. + +diff.tool:: + The diff tool to be used by linkgit:git-difftool[1]. This + option overrides `merge.tool`, and has the same valid built-in + values as `merge.tool` minus "tortoisemerge" and plus + "kompare". Any other value is treated as a custom diff tool, + and there must be a corresponding `difftool.<tool>.cmd` + option. diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt index 3ac2beac62..c57460c03d 100644 --- a/Documentation/diff-generate-patch.txt +++ b/Documentation/diff-generate-patch.txt @@ -74,10 +74,13 @@ separate lines indicate the old and the new mode. 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; you can force showing -full diff with the '-m' option. +Any diff-generating command can take the `-c` or `--cc` option to +produce a 'combined diff' when showing a merge. This is the default +format when showing merges with linkgit:git-diff[1] or +linkgit:git-show[1]. Note also that you can give the `-m' option to any +of these commands to force generation of diffs with individual parents +of a merge. + A 'combined diff' format looks like this: ------------ diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c93124be79..c7ed946357 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -66,15 +66,49 @@ endif::git-format-patch[] number of modified files, as well as number of added and deleted lines. ---dirstat[=<limit>]:: - Output the distribution of relative amount of changes (number of lines added or - removed) for each sub-directory. Directories with changes below - a cut-off percent (3% by default) are not shown. The cut-off percent - can be set with `--dirstat=<limit>`. Changes in a child directory are not - counted for the parent directory, unless `--cumulative` is used. - ---dirstat-by-file[=<limit>]:: - Same as `--dirstat`, but counts changed files instead of lines. +--dirstat[=<param1,param2,...>]:: + Output the distribution of relative amount of changes for each + sub-directory. The behavior of `--dirstat` can be customized by + passing it a comma separated list of parameters. + The defaults are controlled by the `diff.dirstat` configuration + variable (see linkgit:git-config[1]). + The following parameters are available: ++ +-- +`changes`;; + Compute the dirstat numbers by counting the lines that have been + removed from the source, or added to the destination. This ignores + the amount of pure code movements within a file. In other words, + rearranging lines in a file is not counted as much as other changes. + This is the default behavior when no parameter is given. +`lines`;; + Compute the dirstat numbers by doing the regular line-based diff + analysis, and summing the removed/added line counts. (For binary + files, count 64-byte chunks instead, since binary files have no + natural concept of lines). This is a more expensive `--dirstat` + behavior than the `changes` behavior, but it does count rearranged + lines within a file as much as other changes. The resulting output + is consistent with what you get from the other `--*stat` options. +`files`;; + Compute the dirstat numbers by counting the number of files changed. + Each changed file counts equally in the dirstat analysis. This is + the computationally cheapest `--dirstat` behavior, since it does + not have to look at the file contents at all. +`cumulative`;; + Count changes in a child directory for the parent directory as well. + Note that when using `cumulative`, the sum of the percentages + reported may exceed 100%. The default (non-cumulative) behavior can + be specified with the `noncumulative` parameter. +<limit>;; + An integer parameter specifies a cut-off percent (3% by default). + Directories contributing less than this percentage of the changes + are not shown in the output. +-- ++ +Example: The following will count changed files, while ignoring +directories with less than 10% of the total amount of changed files, +and accumulating child directory counts in the parent directories: +`--dirstat=files,10,cumulative`. --summary:: Output a condensed summary of extended header information @@ -120,12 +154,19 @@ any of those replacements occurred. --color[=<when>]:: Show colored diff. - The value must be always (the default), never, or auto. + The value must be `always` (the default for `<when>`), `never`, or `auto`. + The default value is `never`. +ifdef::git-diff[] + It can be changed by the `color.ui` and `color.diff` + configuration settings. +endif::git-diff[] --no-color:: - Turn off colored diff, even when the configuration file - gives the default to color output. - Same as `--color=never`. + Turn off colored diff. +ifdef::git-diff[] + This can be used to override configuration settings. +endif::git-diff[] + It is the same as `--color=never`. --word-diff[=<mode>]:: Show a word diff, using the <mode> to delimit changed words. @@ -239,7 +280,7 @@ ifdef::git-log[] 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 + If `n` is specified, it 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 @@ -259,6 +300,19 @@ endif::git-log[] projects, so use it with caution. Giving more than one `-C` option has the same effect. +-D:: +--irreversible-delete:: + Omit the preimage for deletes, i.e. print only the header but not + the diff between the preimage and `/dev/null`. The resulting patch + is not meant to be applied with `patch` nor `git apply`; this is + solely for people who want to just concentrate on reviewing the + text after the change. In addition, the output obviously lack + enough information to apply such a patch in reverse, even manually, + hence the name of the option. ++ +When used together with `-B`, omit also the preimage in the deletion part +of a delete/create pair. + -l<num>:: The `-M` and `-C` options require O(n^2) processing time where n is the number of potential rename/copy targets. This diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 695696da1b..39d326abc6 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -64,17 +64,34 @@ ifndef::git-pull[] downloaded. The default behavior for a remote may be specified with the remote.<name>.tagopt setting. See linkgit:git-config[1]. -endif::git-pull[] ---[no-]recurse-submodules:: - This option controls if new commits of all populated submodules should - be fetched too (see linkgit:git-config[1] and linkgit:gitmodules[5]). +--recurse-submodules[=yes|on-demand|no]:: + This option controls if and under what conditions new commits of + populated submodules should be fetched too. It can be used as a + boolean option to completely disable recursion when set to 'no' or to + unconditionally recurse into all populated submodules when set to + 'yes', which is the default when this option is used without any + value. Use 'on-demand' to only recurse into a populated submodule + when the superproject retrieves a commit that updates the submodule's + reference to a commit that isn't already in the local submodule + clone. + +--no-recurse-submodules:: + Disable recursive fetching of submodules (this has the same effect as + using the '--recurse-submodules=no' option). -ifndef::git-pull[] --submodule-prefix=<path>:: Prepend <path> to paths printed in informative messages such as "Fetching submodule foo". This option is used internally when recursing over submodules. + +--recurse-submodules-default=[yes|on-demand]:: + This option is used internally to temporarily provide a + non-negative default value for the --recurse-submodules + option. All other methods of configuring fetch's submodule + recursion (such as settings in linkgit:gitmodules[5] and + linkgit:git-config[1]) override this option, as does + specifying --[no-]recurse-submodules directly. endif::git-pull[] -u:: diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index a03448f923..9c1d395722 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -134,6 +134,8 @@ subdirectories. If some files could not be added because of errors indexing them, do not abort the operation, but continue adding the others. The command shall still exit with non-zero status. + The configuration variable `add.ignoreErrors` can be set to + true to make this the default behaviour. --ignore-missing:: This option can only be used together with --dry-run. By using @@ -272,7 +274,8 @@ patch:: This lets you choose one path out of a 'status' like selection. After choosing the path, it presents the diff between the index and the working tree file and asks you if you want to stage - the change of each hunk. You can say: + the change of each hunk. You can select one of the following + options and type return: y - stage this hunk n - do not stage this hunk @@ -291,6 +294,9 @@ patch:: + After deciding the fate for all hunks, if there is any hunk that was chosen, the index is updated with the selected hunks. ++ +You can omit having to type return here, by setting the configuration +variable `interactive.singlekey` to `true`. diff:: @@ -378,14 +384,6 @@ linkgit:git-mv[1] linkgit:git-commit[1] linkgit:git-update-index[1] -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 51297d09ec..6b1b5af64e 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -173,9 +173,9 @@ aborts in the middle. You can recover from this in one of two ways: the index file to bring it into a state that the patch should have produced. Then run the command with the '--resolved' option. -The command refuses to process new mailboxes while the `.git/rebase-apply` -directory exists, so if you decide to start over from scratch, -run `rm -f -r .git/rebase-apply` before running the command with mailbox +The command refuses to process new mailboxes until the current +operation is finished, so if you decide to start over from scratch, +run `git am --abort` before running the command with mailbox names. Before any patches are applied, ORIG_HEAD is set to the tip of the @@ -189,15 +189,6 @@ SEE ALSO -------- linkgit:git-apply[1]. - -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-annotate.txt b/Documentation/git-annotate.txt index 0590eec056..9eb75c37da 100644 --- a/Documentation/git-annotate.txt +++ b/Documentation/git-annotate.txt @@ -27,10 +27,6 @@ SEE ALSO -------- linkgit:git-blame[1] -AUTHOR ------- -Written by Ryan Anderson <ryan@michonline.com>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 881652f490..afd2c9ae59 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -22,7 +22,7 @@ DESCRIPTION ----------- Reads the supplied diff output (i.e. "a patch") and applies it to files. With the `--index` option the patch is also applied to the index, and -with the `--cache` option the patch is only applied to the index. +with the `--cached` 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. @@ -246,20 +246,10 @@ 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> - -Documentation --------------- -Documentation by Junio C Hamano - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt index 2411ce5bfe..f4504ba9bf 100644 --- a/Documentation/git-archimport.txt +++ b/Documentation/git-archimport.txt @@ -107,14 +107,6 @@ OPTIONS Archive/branch identifier in a format that `tla log` understands. -Author ------- -Written by Martin Langhoff <martin@laptop.org>. - -Documentation --------------- -Documentation by Junio C Hamano, Martin Langhoff and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index bf5037ab2a..9c750e2444 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -98,7 +98,8 @@ tar.umask:: tar archive entries. The default is 0002, which turns off the world write bit. The special value "user" indicates that the archiving user's umask will be used instead. See umask(2) for - details. + details. If `--remote` is used then only the configuration of + the remote repository takes effect. ATTRIBUTES ---------- @@ -153,14 +154,6 @@ SEE ALSO -------- linkgit:gitattributes[5] -Author ------- -Written by Franck Bui-Huu and Rene Scharfe. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index c39d957c3a..7b7bafba0c 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -241,7 +241,12 @@ exit(3) manual page), as the value is chopped with "& 0377". The special exit code 125 should be used when the current source code cannot be tested. If the script exits with this code, the current -revision will be skipped (see `git bisect skip` above). +revision will be skipped (see `git bisect skip` above). 125 was chosen +as the highest sensible value to use for this purpose, because 126 and 127 +are used by POSIX shells to signal specific error status (127 is for +command not found, 126 is for command found but not executable---these +details do not matter, as they are normal errors in the script, as far as +"bisect run" is concerned). You may often find that during a bisect session you want to have temporary modifications (e.g. s/#define DEBUG 0/#define DEBUG 1/ in a @@ -274,61 +279,68 @@ $ git bisect start HEAD origin -- # HEAD is bad, origin is good $ git bisect run make test # "make test" builds and tests ------------ -* Automatically bisect a broken test suite: +* Automatically bisect a broken test case: + ------------ $ cat ~/test.sh #!/bin/sh -make || exit 125 # this skips broken builds -make test # "make test" runs the test suite -$ git bisect start v1.3 v1.1 -- # v1.3 is bad, v1.1 is good +make || exit 125 # this skips broken builds +~/check_test_case.sh # does the test case pass? +$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 $ git bisect run ~/test.sh ------------ + Here we use a "test.sh" custom script. In this script, if "make" fails, we skip the current commit. +"check_test_case.sh" should "exit 0" if the test case passes, +and "exit 1" otherwise. + -It is safer to use a custom script outside the repository to prevent -interactions between the bisect, make and test processes and the -script. -+ -"make test" should "exit 0", if the test suite passes, and -"exit 1" otherwise. +It is safer if both "test.sh" and "check_test_case.sh" are +outside the repository to prevent interactions between the bisect, +make and test processes and the scripts. -* Automatically bisect a broken test case: +* Automatically bisect with temporary modifications (hot-fix): + ------------ $ cat ~/test.sh #!/bin/sh -make || exit 125 # this skips broken builds -~/check_test_case.sh # does the test case passes ? -$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 -$ git bisect run ~/test.sh + +# tweak the working tree by merging the hot-fix branch +# and then attempt a build +if git merge --no-commit hot-fix && + make +then + # run project specific test and report its status + ~/check_test_case.sh + status=$? +else + # tell the caller this is untestable + status=125 +fi + +# undo the tweak to allow clean flipping to the next commit +git reset --hard + +# return control +exit $status ------------ + -Here "check_test_case.sh" should "exit 0" if the test case passes, -and "exit 1" otherwise. -+ -It is safer if both "test.sh" and "check_test_case.sh" scripts are -outside the repository to prevent interactions between the bisect, -make and test processes and the scripts. +This applies modifications from a hot-fix branch before each test run, +e.g. in case your build or test environment changed so that older +revisions may need a fix which newer ones have already. (Make sure the +hot-fix branch is based off a commit which is contained in all revisions +which you are bisecting, so that the merge does not pull in too much, or +use `git cherry-pick` instead of `git merge`.) -* Automatically bisect a broken test suite: +* Automatically bisect a broken test case: + ------------ $ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 $ git bisect run sh -c "make || exit 125; ~/check_test_case.sh" ------------ + -Does the same as the previous example, but on a single line. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation -------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. +This shows that you can do without a run script if you write the test +on a single line. SEE ALSO -------- diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index c71671b4f9..9516914236 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m] - [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] + [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file> DESCRIPTION @@ -73,6 +73,11 @@ include::blame-options.txt[] Ignore whitespace when comparing the parent's version and the child's to find where the lines came from. +--abbrev=<n>:: + Instead of using the default 7+1 hexadecimal digits as the + abbreviated object name, use <n>+1 digits. Note that 1 column + is used for a caret to mark the boundary commit. + THE PORCELAIN FORMAT -------------------- @@ -100,6 +105,19 @@ The contents of the actual line is output after the above header, prefixed by a TAB. This is to allow adding more header elements later. +The porcelain format generally suppresses commit information that has +already been seen. For example, two lines that are blamed to the same +commit will both be shown, but the details for that commit will be shown +only once. This is more efficient, but may require more state be kept by +the reader. The `--line-porcelain` option can be used to output full +commit information for each line, allowing simpler (but less efficient) +usage like: + + # count the number of lines attributed to each author + git blame --line-porcelain file | + sed -n 's/^author //p' | + sort | uniq -c | sort -rn + SPECIFYING RANGES ----------------- @@ -198,10 +216,6 @@ SEE ALSO -------- linkgit:git-annotate[1] -AUTHOR ------- -Written by Junio C Hamano <gitster@pobox.com> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 9106d38e40..c50f189827 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -232,14 +232,6 @@ linkgit:git-remote[1], link:user-manual.html#what-is-a-branch[``Understanding history: What is a branch?''] in the Git User's Manual. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> and Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 299007b206..92b01ec25d 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -201,10 +201,6 @@ You can also see what references it offers: $ git ls-remote mybundle ---------------- -Author ------- -Written by Mark Levedahl <mdl123@verizon.net> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 544ba7ba21..2fb95bbd19 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -100,14 +100,6 @@ for each object specified on stdin that does not exist in the repository: <object> SP missing LF ------------ -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt index 50824e3a2d..30eca6cee6 100644 --- a/Documentation/git-check-attr.txt +++ b/Documentation/git-check-attr.txt @@ -86,15 +86,6 @@ SEE ALSO -------- linkgit:gitattributes[5]. - -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by James Bowes. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt index 0c0a9c14bc..4d33e7be0f 100644 --- a/Documentation/git-checkout-index.txt +++ b/Documentation/git-checkout-index.txt @@ -172,18 +172,6 @@ $ git checkout-index --prefix=.merged- Makefile This will check out the currently cached copy of `Makefile` into the file `.merged-Makefile`. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - - -Documentation --------------- -Documentation by David Greaves, -Junio C Hamano and the git-list <git@vger.kernel.org>. - - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 880763d391..c0a96e6c1e 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -9,9 +9,10 @@ SYNOPSIS -------- [verse] 'git checkout' [-q] [-f] [-m] [<branch>] +'git checkout' [-q] [-f] [-m] [--detach] [<commit>] 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... -'git checkout' --patch [<tree-ish>] [--] [<paths>...] +'git checkout' [-p|--patch] [<tree-ish>] [--] [<paths>...] DESCRIPTION ----------- @@ -22,9 +23,10 @@ branch. 'git checkout' [<branch>]:: 'git checkout' -b|-B <new_branch> [<start point>]:: +'git checkout' [--detach] [<commit>]:: This form switches branches by updating the index, working - tree, and HEAD to reflect the specified branch. + tree, and HEAD to reflect the specified branch or commit. + 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 @@ -43,7 +45,7 @@ $ git checkout <branch> that is to say, the branch is not reset/created unless "git checkout" is successful. -'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...:: +'git checkout' [-p|--patch] [<tree-ish>] [--] <pathspec>...:: When <paths> or `--patch` are given, 'git checkout' does *not* switch branches. It updates the named paths in the working tree @@ -115,6 +117,13 @@ explicitly give a name with '-b' in such a case. Create the new branch's reflog; see linkgit:git-branch[1] for details. +--detach:: + Rather than checking out a branch to work on it, check out a + commit for inspection and discardable experiments. + This is the default behavior of "git checkout <commit>" when + <commit> is not a branch name. See the "DETACHED HEAD" section + below 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 @@ -174,7 +183,8 @@ the conflicted merge in the specified paths. working tree (and if a <tree-ish> was specified, the index). + This means that you can use `git checkout -p` to selectively discard -edits from your current working tree. +edits from your current working tree. See the ``Interactive Mode'' +section of linkgit:git-add[1] to learn how to operate the `\--patch` mode. <branch>:: Branch to checkout; if it refers to a branch (i.e., a name that, @@ -204,42 +214,140 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`. -Detached HEAD +DETACHED HEAD ------------- +HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each +branch refers to a specific commit. Let's look at a repo with three +commits, one of them tagged, and with branch 'master' checked out: + +------------ + HEAD (refers to branch 'master') + | + v +a---b---c branch 'master' (refers to commit 'c') + ^ + | + tag 'v2.0' (refers to commit 'b') +------------ -It is sometimes useful to be able to 'checkout' a commit that is -not at the tip of one of your branches. The most obvious -example is to check out the commit at a tagged official release -point, like this: +When a commit is created in this state, the branch is updated to refer to +the new commit. Specifically, 'git commit' creates a new commit 'd', whose +parent is commit 'c', and then updates branch 'master' to refer to new +commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers +to commit 'd': ------------ -$ git checkout v2.6.18 +$ edit; git add; git commit + + HEAD (refers to branch 'master') + | + v +a---b---c---d branch 'master' (refers to commit 'd') + ^ + | + tag 'v2.0' (refers to commit 'b') ------------ -Earlier versions of git did not allow this and asked you to -create a temporary branch using the `-b` option, but starting from -version 1.5.0, the above command 'detaches' your HEAD from the -current branch and directly points at the commit named by the tag -(`v2.6.18` in the example above). +It is sometimes useful to be able to checkout a commit that is not at +the tip of any named branch, or even to create a new commit that is not +referenced by a named branch. Let's look at what happens when we +checkout commit 'b' (here we show two ways this may be done): -You can use all git commands while in this state. You can use -`git reset --hard $othercommit` to further move around, for -example. You can make changes and create a new commit on top of -a detached HEAD. You can even create a merge by using `git -merge $othercommit`. +------------ +$ git checkout v2.0 # or +$ git checkout master^^ + + HEAD (refers to commit 'b') + | + v +a---b---c---d branch 'master' (refers to commit 'd') + ^ + | + tag 'v2.0' (refers to commit 'b') +------------ -The state you are in while your HEAD is detached is not recorded -by any branch (which is natural --- you are not on any branch). -What this means is that you can discard your temporary commits -and merges by switching back to an existing branch (e.g. `git -checkout master`), and a later `git prune` or `git gc` would -garbage-collect them. If you did this by mistake, you can ask -the reflog for HEAD where you were, e.g. +Notice that regardless of which checkout command we use, HEAD now refers +directly to commit 'b'. This is known as being in detached HEAD state. +It means simply that HEAD refers to a specific commit, as opposed to +referring to a named branch. Let's see what happens when we create a commit: ------------ -$ git log -g -2 HEAD +$ edit; git add; git commit + + HEAD (refers to commit 'e') + | + v + e + / +a---b---c---d branch 'master' (refers to commit 'd') + ^ + | + tag 'v2.0' (refers to commit 'b') +------------ + +There is now a new commit 'e', but it is referenced only by HEAD. We can +of course add yet another commit in this state: + +------------ +$ edit; git add; git commit + + HEAD (refers to commit 'f') + | + v + e---f + / +a---b---c---d branch 'master' (refers to commit 'd') + ^ + | + tag 'v2.0' (refers to commit 'b') +------------ + +In fact, we can perform all the normal git operations. But, let's look +at what happens when we then checkout master: + +------------ +$ git checkout master + + HEAD (refers to branch 'master') + e---f | + / v +a---b---c---d branch 'master' (refers to commit 'd') + ^ + | + tag 'v2.0' (refers to commit 'b') ------------ +It is important to realize that at this point nothing refers to commit +'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted +by the routine git garbage collection process, unless we create a reference +before that happens. If we have not yet moved away from commit 'f', +any of these will create a reference to it: + +------------ +$ git checkout -b foo <1> +$ git branch foo <2> +$ git tag foo <3> +------------ + +<1> creates a new branch 'foo', which refers to commit 'f', and then +updates HEAD to refer to branch 'foo'. In other words, we'll no longer +be in detached HEAD state after this command. + +<2> similarly creates a new branch 'foo', which refers to commit 'f', +but leaves HEAD detached. + +<3> creates a new tag 'foo', which refers to commit 'f', +leaving HEAD detached. + +If we have moved away from commit 'f', then we must first recover its object +name (typically by using git reflog), and then we can create a reference to +it. For example, to see the last two commits to which HEAD referred, we +can use either of these commands: + +------------ +$ git reflog -2 HEAD # or +$ git log -g -2 HEAD +------------ EXAMPLES -------- @@ -315,15 +423,6 @@ $ edit frotz $ git add frotz ------------ - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 73008705eb..9d8fe0d261 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -16,6 +16,25 @@ 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). +When it is not obvious how to apply a change, the following +happens: + +1. The current branch and `HEAD` pointer stay at the last commit + successfully made. +2. The `CHERRY_PICK_HEAD` ref is set to point at the commit that + introduced the change that is difficult to apply. +3. Paths in which the change applied cleanly are updated both + in the index file and in your working tree. +4. For conflicting paths, the index file records up to three + versions, as described in the "TRUE MERGE" section of + linkgit:git-merge[1]. The working tree files will include + a description of the conflict bracketed by the usual + conflict markers `<<<<<<<` and `>>>>>>>`. +5. No other modifications are made. + +See linkgit:git-merge[1] for some hints on resolving such +conflicts. + OPTIONS ------- <commit>...:: @@ -32,9 +51,10 @@ OPTIONS message prior to committing. -x:: - When recording the commit, append to the original commit - message a note that indicates which commit this change - was cherry-picked from. Append the note only for cherry + When recording the commit, append a line that says + "(cherry picked from commit ...)" to the original commit + message in order to indicate which commit this change was + cherry-picked from. This is done only for cherry picks without conflicts. Do not use this option if you are cherry-picking from your private branch because the information is useless to the recipient. If on the @@ -79,6 +99,16 @@ effect to your index in a row. cherry-pick'ed commit, then a fast forward to this commit will be performed. +--strategy=<strategy>:: + Use the given merge strategy. Should only be used once. + See the MERGE STRATEGIES section in linkgit:git-merge[1] + for details. + +-X<option>:: +--strategy-option=<option>:: + Pass the merge strategy-specific option through to the + merge strategy. See linkgit:git-merge[1] for details. + EXAMPLES -------- git cherry-pick master:: @@ -120,13 +150,27 @@ git rev-list --reverse master \-- README | git cherry-pick -n --stdin:: so the result can be inspected and made into a single new commit if suitable. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. +The following sequence attempts to backport a patch, bails out because +the code the patch applies to has changed too much, and then tries +again, this time exercising more care about matching up context lines. + +------------ +$ git cherry-pick topic^ <1> +$ git diff <2> +$ git reset --merge ORIG_HEAD <3> +$ git cherry-pick -Xpatience topic^ <4> +------------ +<1> apply the change that would be shown by `git show topic^`. +In this example, the patch does not apply cleanly, so +information about the conflict is written to the index and +working tree and no new commit results. +<2> summarize changes to be reconciled +<3> cancel the cherry-pick. In other words, return to the +pre-cherry-pick state, preserving any local modifications you had in +the working tree. +<4> try to apply the change introduced by `topic^` again, +spending extra time to avoid mistakes based on incorrectly matching +context lines. SEE ALSO -------- diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt index fed115acd0..79448c505b 100644 --- a/Documentation/git-cherry.txt +++ b/Documentation/git-cherry.txt @@ -63,14 +63,6 @@ SEE ALSO -------- linkgit:git-patch-id[1] -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-citool.txt b/Documentation/git-citool.txt index fb2753c97e..6e5c8126f5 100644 --- a/Documentation/git-citool.txt +++ b/Documentation/git-citool.txt @@ -19,14 +19,6 @@ to the less interactive 'git commit' program. 'git citool' is actually a standard alias for `git gui citool`. See linkgit:git-gui[1] for more details. -Author ------- -Written by Shawn O. Pearce <spearce@spearce.org>. - -Documentation --------------- -Documentation by Shawn O. Pearce <spearce@spearce.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index 60e38e6e27..974e04ef1a 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -61,12 +61,6 @@ OPTIONS Remove only files ignored by git. This may be useful to rebuild everything from scratch, but keep manually created files. - -Author ------- -Written by Pavel Roskin <proski@gnu.org> - - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 42e7021215..b093e45497 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git clone' [--template=<template_directory>] [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror] [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>] + [--separate-git-dir <git dir>] [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository> [<directory>] @@ -176,6 +177,14 @@ objects from the source repository into a pack in the cloned repository. repository does not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) +--separate-git-dir=<git dir>:: + Instead of placing the cloned repository where it is supposed + to be, place the cloned repository at the specified directory, + then make a filesytem-agnostic git symbolic link to there. + The result is git repository can be separated from working + tree. + + <repository>:: The (possibly remote) repository to clone from. See the <<URLS,URLS>> section below for more information on specifying @@ -236,17 +245,6 @@ $ git clone --bare -l -s /pub/scm/.../torvalds/linux-2.6.git \ /pub/scm/.../me/subsys-2.6.git ------------ - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 5dcf4278fc..f524d76019 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -93,15 +93,6 @@ SEE ALSO -------- linkgit:git-write-tree[1] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index b586c0f442..7951cb7b00 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -8,11 +8,12 @@ git-commit - Record changes to the repository SYNOPSIS -------- [verse] -'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run] - [(-c | -C | --fixup | --squash) <commit>] [-F <file> | -m <msg>] - [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] - [-e] [--author=<author>] [--date=<date>] [--cleanup=<mode>] - [--status | --no-status] [-i | -o] [--] [<file>...] +'git commit' [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend] + [--dry-run] [(-c | -C | --fixup | --squash) <commit>] + [-F <file> | -m <msg>] [--reset-author] [--allow-empty] + [--allow-empty-message] [--no-verify] [-e] [--author=<author>] + [--date=<date>] [--cleanup=<mode>] [--status | --no-status] + [-i | -o] [--] [<file>...] DESCRIPTION ----------- @@ -39,9 +40,10 @@ The content to be added can be specified in several ways: that have been removed from the working tree, and then perform the actual commit; -5. by using the --interactive switch with the 'commit' command to decide one - by one which files should be part of the commit, before finalizing the - operation. Currently, this is done by invoking 'git add --interactive'. +5. by using the --interactive or --patch switches with the 'commit' command + to decide one by one which files or hunks should be part of the commit, + before finalizing the operation. See the ``Interactive Mode`` section of + linkgit:git-add[1] to learn how to operate these modes. The `--dry-run` option can be used to obtain a summary of what is included by any of the above for the next @@ -59,6 +61,12 @@ OPTIONS been modified and deleted, but new files you have not told git about are not affected. +-p:: +--patch:: + Use the interactive patch selection interface to chose + which changes to commit. See linkgit:git-add[1] for + details. + -C <commit>:: --reuse-message=<commit>:: Take an existing commit object, and reuse the log message @@ -84,9 +92,10 @@ OPTIONS linkgit:git-rebase[1] for details. --reset-author:: - When used with -C/-c/--amend options, declare that the - authorship of the resulting commit now belongs of the committer. - This also renews the author timestamp. + When used with -C/-c/--amend options, or when committing after a + a conflicting cherry-pick, declare that the authorship of the + resulting commit now belongs of the committer. This also renews + the author timestamp. --short:: When doing a dry-run, give the output in the short-format. See @@ -214,10 +223,11 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) -u[<mode>]:: --untracked-files[=<mode>]:: - Show untracked files (Default: 'all'). + Show untracked files. + -The mode parameter is optional, and is used to specify -the handling of untracked files. +The mode parameter is optional (defaults to 'all'), and is used to +specify the handling of untracked files; when -u is not used, the +default is 'normal', i.e. show untracked files and directories. + The possible options are: + @@ -225,9 +235,8 @@ The possible options are: - 'normal' - Shows untracked files and directories - 'all' - Also shows individual files in untracked directories. + -See linkgit:git-config[1] for configuration variable -used to change the default for when the option is not -specified. +The default can be changed using the status.showUntrackedFiles +configuration variable documented in linkgit:git-config[1]. -v:: --verbose:: @@ -396,12 +405,6 @@ linkgit:git-mv[1], linkgit:git-merge[1], linkgit:git-commit-tree[1] -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> and -Junio C Hamano <gitster@pobox.com> - - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 543dd64a46..e7ecf5d803 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -50,16 +50,18 @@ The default is to assume the config file of the current repository, .git/config unless defined otherwise with GIT_DIR and GIT_CONFIG (see <<FILES>>). -This command will fail if: +This command will fail (with exit code ret) if: -. The config file is invalid, -. Can not write to the config file, -. no section was provided, -. the section or key is invalid, -. you try to unset an option which does not exist, -. you try to unset/set an option for which multiple lines match, or -. you use '--global' option without $HOME being properly set. +. The config file is invalid (ret=3), +. can not write to the config file (ret=4), +. no section or name was provided (ret=2), +. the section or key is invalid (ret=1), +. you try to unset an option which does not exist (ret=5), +. you try to unset/set an option for which multiple lines match (ret=5), +. you try to use an invalid regexp (ret=6), or +. you use '--global' option without $HOME being properly set (ret=128). +On success, the command returns the exit code 0. OPTIONS ------- @@ -336,15 +338,6 @@ echo "${WS}your whitespace color or blue reverse${RESET}" include::config.txt[] - -Author ------- -Written by Johannes Schindelin <Johannes.Schindelin@gmx.de> - -Documentation --------------- -Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt index 6bc1c21e62..a73933a931 100644 --- a/Documentation/git-count-objects.txt +++ b/Documentation/git-count-objects.txt @@ -25,15 +25,6 @@ OPTIONS and number of objects that can be removed by running `git prune-packed`. - -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index d25661eb21..ad93a3e84e 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -112,14 +112,6 @@ $ cd ~/project_cvs_checkout $ git cherry cvshead myhead | sed -n 's/^+ //p' | xargs -l1 git cvsexportcommit -c -p -v ------------ -Author ------- -Written by Martin Langhoff <martin@laptop.org> and others. - -Documentation --------------- -Documentation by Martin Langhoff <martin@laptop.org> and others. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index 608cd63fc3..6695ab3b4b 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -217,15 +217,6 @@ more stable in practice: * cvs2git (part of cvs2svn), `http://cvs2svn.tigris.org` * parsecvs, `http://cgit.freedesktop.org/~keithp/parsecvs` -Author ------- -Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from -various participants of the git-list <git@vger.kernel.org>. - -Documentation --------------- -Documentation by Matthias Urlichs <smurf@smurf.noris.de>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 70cbb2cae7..827bc988ed 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -252,7 +252,7 @@ Configuring database backend 'git-cvsserver' uses the Perl DBI module. Please also read its documentation if changing these variables, especially -about `DBI->connect()`. +about `DBI\->connect()`. gitcvs.dbname:: Database name. The exact meaning depends on the @@ -391,22 +391,6 @@ Dependencies ------------ 'git-cvsserver' depends on DBD::SQLite. -Copyright and Authors ---------------------- - -This program is copyright The Open University UK - 2006. - -Authors: - -- Martyn Smith <martyn@catalyst.net.nz> -- Martin Langhoff <martin@laptop.org> - -with ideas and patches from participants of the git-list <git@vger.kernel.org>. - -Documentation --------------- -Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@laptop.org>, and Matthias Urlichs <smurf@smurf.noris.de>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index d15cb6a845..ebd13be72e 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -279,17 +279,6 @@ that connected to it, if the IP address is available. REMOTE_ADDR will be available in the environment of hooks called when services are performed. - - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki -<yoshfuji@linux-ipv6.org> and the git-list <git@vger.kernel.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 02e015ad9c..039cce2e98 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -156,17 +156,6 @@ selected and output. Here fewest commits different is defined as the number of commits which would be shown by `git log tag..input` will be the smallest number of commits possible. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org>, but somewhat -butchered by Junio C Hamano <gitster@pobox.com>. Later significantly -updated by Shawn Pearce <spearce@spearce.org>. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt index 9cd8ccef37..8d481948bd 100644 --- a/Documentation/git-diff-files.txt +++ b/Documentation/git-diff-files.txt @@ -46,15 +46,6 @@ omit diff output for unmerged entries and just show "Unmerged". include::diff-format.txt[] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index 162cb741b3..2ea22abca2 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -96,8 +96,8 @@ show that. So let's say that you have edited `kernel/sched.c`, but have not actually done a 'git update-index' on it yet - there is no "object" associated with the new state, and you get: - torvalds@ppc970:~/v2.6/linux> git diff-index HEAD - *100644->100664 blob 7476bb......->000000...... kernel/sched.c + torvalds@ppc970:~/v2.6/linux> git diff-index --abbrev HEAD + :100644 100664 7476bb... 000000... kernel/sched.c i.e., it shows that the tree has changed, and that `kernel/sched.c` has is not up-to-date and may contain new stuff. The all-zero sha1 means that to @@ -116,15 +116,6 @@ tell which file is in which state, since the "has been updated" ones show a valid sha1, and the "not in sync with the index" ones will always have the special all-zero sha1. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt index a7e37b875f..1439486e40 100644 --- a/Documentation/git-diff-tree.txt +++ b/Documentation/git-diff-tree.txt @@ -138,8 +138,8 @@ so it can be used to name subdirectories. An example of normal usage is: - torvalds@ppc970:~/git> git diff-tree 5319e4...... - *100664->100664 blob ac348b.......->a01513....... git-fsck-objects.c + torvalds@ppc970:~/git> git diff-tree --abbrev 5319e4 + :100664 100664 ac348b... a01513... git-fsck-objects.c which tells you that the last commit changed just one file (it's from this one: @@ -162,15 +162,6 @@ in case you care). include::diff-format.txt[] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index f6ac847507..f8d0819113 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -38,6 +38,8 @@ directories. This behavior can be forced by --no-index. commit relative to the named <commit>. Typically you would want comparison with the latest commit, so if you do not give <commit>, it defaults to HEAD. + If HEAD does not exist (e.g. unborned branches) and + <commit> is not given, it shows all staged changes. --staged is a synonym of --cached. 'git diff' [--options] <commit> [--] [<path>...]:: @@ -172,14 +174,6 @@ linkgit:gitdiffcore[7], linkgit:git-format-patch[1], linkgit:git-apply[1] -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index db87f1d423..590f410abf 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -31,8 +31,8 @@ OPTIONS --tool=<tool>:: Use the diff tool specified by <tool>. Valid merge tools are: - kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, - ecmerge, diffuse, opendiff, p4merge and araxis. + araxis, bc3, diffuse, emerge, ecmerge, gvimdiff, kdiff3, + kompare, meld, opendiff, p4merge, tkdiff, vimdiff and xxdiff. + If a diff tool is not specified, 'git difftool' will use the configuration variable `diff.tool`. If the @@ -109,15 +109,6 @@ linkgit:git-mergetool[1]:: linkgit:git-config[1]:: Get and set repository or global options - -AUTHOR ------- -Written by David Aguilar <davvid@gmail.com>. - -Documentation --------------- -Documentation by David Aguilar and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index e05b686b1e..781bd6edc3 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -135,15 +135,6 @@ Since 'git fast-import' cannot tag trees, you will not be able to export the linux-2.6.git repository completely, as it contains a tag referencing a tree instead of a commit. - -Author ------- -Written by Johannes E. Schindelin <johannes.schindelin@gmx.de>. - -Documentation --------------- -Documentation by Johannes E. Schindelin <johannes.schindelin@gmx.de>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 4415e63635..249249aac7 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -78,8 +78,12 @@ OPTIONS set of marks. If a mark is defined to different values, the last file wins. +--import-marks-if-exists=<file>:: + Like --import-marks but instead of erroring out, silently + skips the file if it does not exist. + --relative-marks:: - After specifying --relative-marks= the paths specified + After specifying --relative-marks the paths specified with --import-marks= and --export-marks= are relative to an internal directory in the current repository. In git-fast-import this means that the paths are relative @@ -89,7 +93,7 @@ OPTIONS --no-relative-marks:: Negates a previous --relative-marks. Allows for combining relative and non-relative marks by interweaving - --(no-)-relative-marks= with the --(import|export)-marks= + --(no-)-relative-marks with the --(import|export)-marks= options. --cat-blob-fd=<fd>:: @@ -192,7 +196,8 @@ especially when a higher level language such as Perl, Python or Ruby is being used. fast-import is very strict about its input. Where we say SP below we mean -*exactly* one space. Likewise LF means one (and only one) linefeed. +*exactly* one space. Likewise LF means one (and only one) linefeed +and HT one (and only one) horizontal tab. Supplying additional whitespace characters will cause unexpected results, such as branch names or file names with leading or trailing spaces in their name, or early termination of fast-import when it encounters @@ -330,6 +335,11 @@ and control the current import process. More detailed discussion format to the file descriptor set with `--cat-blob-fd` or `stdout` if unspecified. +`ls`:: + Causes fast-import to print a line describing a directory + entry in 'ls-tree' format to the file descriptor set with + `--cat-blob-fd` or `stdout` if unspecified. + `feature`:: Require that fast-import supports the specified feature, or abort if it does not. @@ -915,6 +925,55 @@ This command can be used anywhere in the stream that comments are accepted. In particular, the `cat-blob` command can be used in the middle of a commit but not in the middle of a `data` command. +`ls` +~~~~ +Prints information about the object at a path to a file descriptor +previously arranged with the `--cat-blob-fd` argument. This allows +printing a blob from the active commit (with `cat-blob`) or copying a +blob or tree from a previous commit for use in the current one (with +`filemodify`). + +The `ls` command can be used anywhere in the stream that comments are +accepted, including the middle of a commit. + +Reading from the active commit:: + This form can only be used in the middle of a `commit`. + The path names a directory entry within fast-import's + active commit. The path must be quoted in this case. ++ +.... + 'ls' SP <path> LF +.... + +Reading from a named tree:: + The `<dataref>` can be a mark reference (`:<idnum>`) or the + full 40-byte SHA-1 of a Git tag, commit, or tree object, + preexisting or waiting to be written. + The path is relative to the top level of the tree + named by `<dataref>`. ++ +.... + 'ls' SP <dataref> SP <path> LF +.... + +See `filemodify` above for a detailed description of `<path>`. + +Output uses the same format as `git ls-tree <tree> {litdd} <path>`: + +==== + <mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF +==== + +The <dataref> represents the blob, tree, or commit object at <path> +and can be used in later 'cat-blob', 'filemodify', or 'ls' commands. + +If there is no file or subtree at that path, 'git fast-import' will +instead report + +==== + missing SP <path> LF +==== + `feature` ~~~~~~~~~ Require that fast-import supports the specified feature, or abort if @@ -942,12 +1001,21 @@ import-marks:: any "feature import-marks" command in the stream. cat-blob:: - Ignored. Versions of fast-import not supporting the - "cat-blob" command will exit with a message indicating so. +ls:: + Require that the backend support the 'cat-blob' or 'ls' command. + Versions of fast-import not supporting the specified command + will exit with a message indicating so. This lets the import error out early with a clear message, rather than wasting time on the early part of an import before the unsupported command is detected. +notes:: + Require that the backend support the 'notemodify' (N) + subcommand to the 'commit' command. + Versions of fast-import not supporting notes will exit + with a message indicating so. + + `option` ~~~~~~~~ Processes the specified option so that git fast-import behaves in a @@ -1282,14 +1350,6 @@ operator can use this facility to peek at the objects and refs from an import in progress, at the cost of some added running time and worse compression. -Author ------- -Written by Shawn O. Pearce <spearce@spearce.org>. - -Documentation --------------- -Documentation by Shawn O. Pearce <spearce@spearce.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index 4a8487c154..48d4bf6d68 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -90,15 +90,6 @@ OPTIONS $GIT_DIR (e.g. "HEAD", "refs/heads/master"). When unspecified, update from all heads the remote side has. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index c76e313923..60ac8d26eb 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -34,7 +34,7 @@ pointed by remote tags that it does not yet have, then fetch those missing tags. If the other end has tags that point at branches you are not interested in, you will not get them. -'git fetch' can fetch from either a single named repository, or +'git fetch' can fetch from either a single named repository, or from several repositories at once if <group> is given and there is a remotes.<group> entry in the configuration file. (See linkgit:git-config[1]). @@ -76,20 +76,19 @@ The `pu` branch will be updated even if it is does not fast-forward, because it is prefixed with a plus sign; `tmp` will not be. +BUGS +---- +Using --recurse-submodules can only fetch new commits in already checked +out submodules right now. When e.g. upstream added a new submodule in the +just fetched commits of the superproject the submodule itself can not be +fetched, making it impossible to check out that submodule later without +having to do a fetch again. This is expected to be fixed in a future git +version. + SEE ALSO -------- linkgit:git-pull[1] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> and -Junio C Hamano <gitster@pobox.com> - -Documentation -------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 796e7489ff..9dc1f2a947 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -361,7 +361,7 @@ git filter-branch --index-filter \ 'git ls-files -s | sed "s-\t\"*-&newsubdir/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new \ git update-index --index-info && - mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD + mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD --------------------------------------------------------------- @@ -405,16 +405,6 @@ warned. (or if your git-gc is not new enough to support arguments to `\--prune`, use `git repack -ad; git prune` instead). - -Author ------- -Written by Petr "Pasky" Baudis <pasky@suse.cz>, -and the git list <git@vger.kernel.org> - -Documentation --------------- -Documentation by Petr Baudis and the git list. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index 75adf7a502..32aff954a2 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -67,15 +67,6 @@ SEE ALSO -------- linkgit:git-merge[1] - -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index fac1cf55e5..152e695c81 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -123,7 +123,7 @@ EXAMPLES -------- An example directly producing formatted text. Show the most recent -3 tagged commits:: +3 tagged commits: ------------ #!/bin/sh @@ -140,7 +140,7 @@ Ref: %(*refname) A simple example showing the use of shell eval on the output, -demonstrating the use of --shell. List the prefixes of all heads:: +demonstrating the use of --shell. List the prefixes of all heads: ------------ #!/bin/sh @@ -154,7 +154,7 @@ done A bit more elaborate report on tags, demonstrating that the format -may be an entire script:: +may be an entire script: ------------ #!/bin/sh @@ -204,3 +204,15 @@ eval=`git for-each-ref --shell --format="$fmt" \ refs/tags` eval "$eval" ------------ + +Author +------ +Written by Junio C Hamano <gitster@pobox.com>. + +Documentation +------------- +Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 9dcafc6d44..d13c9b23f7 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -20,7 +20,7 @@ SYNOPSIS [--ignore-if-in-upstream] [--subject-prefix=Subject-Prefix] [--to=<email>] [--cc=<email>] - [--cover-letter] + [--cover-letter] [--quiet] [<common diff options>] [ <since> | <revision range> ] @@ -196,6 +196,9 @@ will want to ensure that threading is disabled for `git send-email`. Note that the leading character does not have to be a dot; for example, you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`. +--quiet:: + Do not print the names of the generated files to standard output. + --no-binary:: Do not output contents of changes in binary files, instead display a notice that those files changed. Patches generated @@ -229,6 +232,233 @@ attachments, and sign off patches with configuration variables. ------------ +DISCUSSION +---------- + +The patch produced by 'git format-patch' is in UNIX mailbox format, +with a fixed "magic" time stamp to indicate that the file is output +from format-patch rather than a real mailbox, like so: + +------------ +From 8f72bad1baf19a53459661343e21d6491c3908d3 Mon Sep 17 00:00:00 2001 +From: Tony Luck <tony.luck@intel.com> +Date: Tue, 13 Jul 2010 11:42:54 -0700 +Subject: [PATCH] =?UTF-8?q?[IA64]=20Put=20ia64=20config=20files=20on=20the=20?= + =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20diet?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +arch/arm config files were slimmed down using a python script +(See commit c2330e286f68f1c408b4aa6515ba49d57f05beae comment) + +Do the same for ia64 so we can have sleek & trim looking +... +------------ + +Typically it will be placed in a MUA's drafts folder, edited to add +timely commentary that should not go in the changelog after the three +dashes, and then sent as a message whose body, in our example, starts +with "arch/arm config files were...". On the receiving end, readers +can save interesting patches in a UNIX mailbox and apply them with +linkgit:git-am[1]. + +When a patch is part of an ongoing discussion, the patch generated by +'git format-patch' can be tweaked to take advantage of the 'git am +--scissors' feature. After your response to the discussion comes a +line that consists solely of "`-- >8 --`" (scissors and perforation), +followed by the patch with unnecessary header fields removed: + +------------ +... +> So we should do such-and-such. + +Makes sense to me. How about this patch? + +-- >8 -- +Subject: [IA64] Put ia64 config files on the Uwe Kleine-König diet + +arch/arm config files were slimmed down using a python script +... +------------ + +When sending a patch this way, most often you are sending your own +patch, so in addition to the "`From $SHA1 $magic_timestamp`" marker you +should omit `From:` and `Date:` lines from the patch file. The patch +title is likely to be different from the subject of the discussion the +patch is in response to, so it is likely that you would want to keep +the Subject: line, like the example above. + +Checking for patch corruption +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Many mailers if not set up properly will corrupt whitespace. Here are +two common types of corruption: + +* Empty context lines that do not have _any_ whitespace. + +* Non-empty context lines that have one extra whitespace at the + beginning. + +One way to test if your MUA is set up correctly is: + +* Send the patch to yourself, exactly the way you would, except + with To: and Cc: lines that do not contain the list and + maintainer address. + +* Save that patch to a file in UNIX mailbox format. Call it a.patch, + say. + +* Apply it: + + $ git fetch <project> master:test-apply + $ git checkout test-apply + $ git reset --hard + $ git am a.patch + +If it does not apply correctly, there can be various reasons. + +* The patch itself does not apply cleanly. That is _bad_ but + does not have much to do with your MUA. You might want to rebase + the patch with linkgit:git-rebase[1] before regenerating it in + this case. + +* The MUA corrupted your patch; "am" would complain that + the patch does not apply. Look in the .git/rebase-apply/ subdirectory and + see what 'patch' file contains and check for the common + corruption patterns mentioned above. + +* While at it, check the 'info' and 'final-commit' files as well. + If what is in 'final-commit' is not exactly what you would want to + see in the commit log message, it is very likely that the + receiver would end up hand editing the log message when applying + your patch. Things like "Hi, this is my first patch.\n" in the + patch e-mail should come after the three-dash line that signals + the end of the commit message. + +MUA-SPECIFIC HINTS +------------------ +Here are some hints on how to successfully submit patches inline using +various mailers. + +GMail +~~~~~ +GMail does not have any way to turn off line wrapping in the web +interface, so it will mangle any emails that you send. You can however +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. + +For hints on using 'git send-email' to send your patches through the +GMail SMTP server, see the EXAMPLE section of linkgit:git-send-email[1]. + +For hints on submission using the IMAP interface, see the EXAMPLE +section of linkgit:git-imap-send[1]. + +Thunderbird +~~~~~~~~~~~ +By default, Thunderbird will both wrap emails as well as flag +them as being 'format=flowed', both of which will make the +resulting email unusable by git. + +There are three different approaches: use an add-on to turn off line wraps, +configure Thunderbird to not mangle patches, or use +an external editor to keep Thunderbird from mangling the patches. + +Approach #1 (add-on) +^^^^^^^^^^^^^^^^^^^^ + +Install the Toggle Word Wrap add-on that is available from +https://addons.mozilla.org/thunderbird/addon/toggle-word-wrap/ +It adds a menu entry "Enable Word Wrap" in the composer's "Options" menu +that you can tick off. Now you can compose the message as you otherwise do +(cut + paste, 'git format-patch' | 'git imap-send', etc), but you have to +insert line breaks manually in any text that you type. + +Approach #2 (configuration) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Three steps: + +1. Configure your mail server composition as plain text: + Edit...Account Settings...Composition & Addressing, + uncheck "Compose Messages in HTML". + +2. Configure your general composition window to not wrap. ++ +In Thunderbird 2: +Edit..Preferences..Composition, wrap plain text messages at 0 ++ +In Thunderbird 3: +Edit..Preferences..Advanced..Config Editor. Search for +"mail.wrap_long_lines". +Toggle it to make sure it is set to `false`. + +3. Disable the use of format=flowed: +Edit..Preferences..Advanced..Config Editor. Search for +"mailnews.send_plaintext_flowed". +Toggle it to make sure it is set to `false`. + +After that is done, you should be able to compose email as you +otherwise would (cut + paste, 'git format-patch' | 'git imap-send', etc), +and the patches will not be mangled. + +Approach #3 (external editor) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following Thunderbird extensions are needed: +AboutConfig from http://aboutconfig.mozdev.org/ and +External Editor from http://globs.org/articles.php?lng=en&pg=8 + +1. Prepare the patch as a text file using your method of choice. + +2. Before opening a compose window, use Edit->Account Settings to + uncheck the "Compose messages in HTML format" setting in the + "Composition & Addressing" panel of the account to be used to + send the patch. + +3. In the main Thunderbird window, 'before' you open the compose + window for the patch, use Tools->about:config to set the + following to the indicated values: ++ +---------- + mailnews.send_plaintext_flowed => false + mailnews.wraplength => 0 +---------- + +4. Open a compose window and click the external editor icon. + +5. In the external editor window, read in the patch file and exit + the editor normally. + +Side note: it may be possible to do step 2 with +about:config and the following settings but no one's tried yet. + +---------- + mail.html_compose => false + mail.identity.default.compose_html => false + mail.identity.id?.compose_html => false +---------- + +There is a script in contrib/thunderbird-patch-inline which can help +you include patches with Thunderbird in an easy way. To use it, do the +steps above and then use the script as the external editor. + +KMail +~~~~~ +This should help you to submit patches inline using KMail. + +1. Prepare the patch as a text file. + +2. Click on New Mail. + +3. Go under "Options" in the Composer window and be sure that + "Word wrap" is not set. + +4. Use Message -> Insert file... and insert the patch. + +5. Back in the compose window: add whatever other text you wish to the + message, complete the addressing and subject fields, and press send. + + EXAMPLES -------- @@ -278,15 +508,6 @@ SEE ALSO -------- linkgit:git-am[1], linkgit:git-send-email[1] - -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-fsck-objects.txt b/Documentation/git-fsck-objects.txt index 965a8279c1..90ebb8a594 100644 --- a/Documentation/git-fsck-objects.txt +++ b/Documentation/git-fsck-objects.txt @@ -15,3 +15,7 @@ DESCRIPTION This is a synonym for linkgit:git-fsck[1]. Please refer to the documentation of that command. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 86f9b2bf91..a2a508dc28 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -26,7 +26,7 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless --no-reflogs is given) as heads. --unreachable:: - Print out objects that exist but that aren't readable from any + Print out objects that exist but that aren't reachable from any of the reference nodes. --root:: @@ -76,7 +76,7 @@ It tests SHA1 and general object sanity, and it does full tracking of the resulting reachability and everything else. It prints out any corruption it finds (missing or bad objects), and if you use the '--unreachable' flag it will also print out objects that exist but -that aren't readable from any of the specified head nodes. +that aren't reachable from any of the specified head nodes. So for example @@ -140,14 +140,6 @@ GIT_INDEX_FILE:: GIT_ALTERNATE_OBJECT_DIRECTORIES:: used to specify additional object database roots (usually unset) -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 26632414b2..4966cb5784 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -151,10 +151,6 @@ linkgit:git-reflog[1] linkgit:git-repack[1] linkgit:git-rerere[1] -Author ------- -Written by Shawn O. Pearce <spearce@spearce.org> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-get-tar-commit-id.txt b/Documentation/git-get-tar-commit-id.txt index 790af9573b..8035736c96 100644 --- a/Documentation/git-get-tar-commit-id.txt +++ b/Documentation/git-get-tar-commit-id.txt @@ -22,15 +22,6 @@ return code of 1. This can happen if <tarfile> had not been created using 'git archive' or if the first parameter of 'git archive' had been a tree ID instead of a commit ID or tag. - -Author ------- -Written by Rene Scharfe <rene.scharfe@lsrfire.ath.cx> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index dab0a78fa8..e150c77cff 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -12,7 +12,8 @@ SYNOPSIS 'git grep' [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp] [-v | --invert-match] [-h|-H] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] - [-F | --fixed-strings] [-n] + [-P | --perl-regexp] + [-F | --fixed-strings] [-n | --line-number] [-l | --files-with-matches] [-L | --files-without-match] [(-O | --open-files-in-pager) [<pager>]] [-z | --null] @@ -31,6 +32,16 @@ Look for specified patterns in the tracked files in the work tree, blobs registered in the index file, or blobs in given tree objects. +CONFIGURATION +------------- + +grep.lineNumber:: + If set to true, enable '-n' option by default. + +grep.extendedRegexp:: + If set to true, enable '--extended-regexp' option by default. + + OPTIONS ------- --cached:: @@ -87,12 +98,18 @@ OPTIONS Use POSIX extended/basic regexp for patterns. Default is to use basic regexp. +-P:: +--perl-regexp:: + Use Perl-compatible regexp for patterns. Requires libpcre to be + compiled in. + -F:: --fixed-strings:: Use fixed strings for patterns (don't interpret pattern as a regex). -n:: +--line-number:: Prefix the line number to matching lines. -l:: @@ -203,16 +220,6 @@ git grep --all-match -e NODE -e Unexpected:: Looks for a line that has `NODE` or `Unexpected` in files that have lines that match both. -Author ------- -Originally written by Linus Torvalds <torvalds@osdl.org>, later -revamped by Junio C Hamano. - - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt index 2563710b56..32a833e0ae 100644 --- a/Documentation/git-gui.txt +++ b/Documentation/git-gui.txt @@ -121,14 +121,6 @@ or or browsed online at http://repo.or.cz/w/git-gui.git/[]. -Author ------- -Written by Shawn O. Pearce <spearce@spearce.org>. - -Documentation --------------- -Documentation by Shawn O. Pearce <spearce@spearce.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt index 51edeecbe5..4b0a502e8e 100644 --- a/Documentation/git-hash-object.txt +++ b/Documentation/git-hash-object.txt @@ -53,14 +53,6 @@ OPTIONS conversion. If the file is read from standard input then this is always implied, unless the --path option is given. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index eccd0ffd38..42aa2b0c01 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -171,17 +171,6 @@ $ git config --global web.browser firefox as they are probably more user specific than repository specific. See linkgit:git-config[1] for more information about this. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> and the git-list -<git@vger.kernel.org>. - -Documentation -------------- -Initial documentation was part of the linkgit:git[1] man page. -Christian Couder <chriscool@tuxfamily.org> extracted and rewrote it a -little. Maintenance is done by the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt index d91cb7ff85..fefa752198 100644 --- a/Documentation/git-http-fetch.txt +++ b/Documentation/git-http-fetch.txt @@ -43,14 +43,6 @@ commit-id:: Verify that everything reachable from target is fetched. Used after an earlier fetch is interrupted. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index ddf7a18dc4..82ae34b9b8 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -91,15 +91,6 @@ With '--force', the fast-forward check is disabled for all refs. Optionally, a <ref> parameter can be prefixed with a plus '+' sign to disable the fast-forward check only on that ref. - -Author ------- -Written by Nick Hengeveld <nickh@reactrix.com> - -Documentation --------------- -Documentation by Nick Hengeveld - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index 57aba42e66..4e09708cc9 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -111,6 +111,31 @@ Using direct mode with SSL: .......................... +EXAMPLE +------- +To submit patches using GMail's IMAP interface, first, edit your ~/.gitconfig +to specify your account settings: + +--------- +[imap] + folder = "[Gmail]/Drafts" + host = imaps://imap.gmail.com + user = user@gmail.com + port = 993 + sslverify = false +--------- + +You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error +that the "Folder doesn't exist". + +Once the commits are ready to be sent, run the following command: + + $ git format-patch --cover-letter -M --stdout origin/master | git imap-send + +Just make sure to disable line wrapping in the email client (GMail's web +interface will wrap lines no matter what, so you need to use a real +IMAP client). + CAUTION ------- It is still your responsibility to make sure that the email message @@ -124,13 +149,9 @@ Thunderbird in particular is known to be problematic. Thunderbird users may wish to visit this web page for more information: http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email -Author ------- -Derived from isync 1.0.1 by Mike McCormack. - -Documentation --------------- -Documentation by Mike McCormack +SEE ALSO +-------- +linkgit:git-format-patch[1], linkgit:git-send-email[1], mbox(5) GIT --- diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index c2bb81042c..909687fed4 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -85,15 +85,6 @@ new .keep file was successfully created. This is useful to remove a .keep file used as a lock to prevent the race with 'git repack' mentioned above. - -Author ------- -Written by Sergey Vlasov <vsu@altlinux.ru> - -Documentation -------------- -Documentation by Sergey Vlasov - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index eba3cb4998..9f97f5a915 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -8,7 +8,7 @@ git-init-db - Creates an empty git repository SYNOPSIS -------- -'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] +'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]] DESCRIPTION @@ -16,3 +16,7 @@ DESCRIPTION This is a synonym for linkgit:git-init[1]. Please refer to the documentation of that command. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 00d4a124c9..f2777a7786 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -8,9 +8,32 @@ git-init - Create an empty git repository or reinitialize an existing one SYNOPSIS -------- -'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] [directory] +'git init' [-q | --quiet] [--bare] [--template=<template_directory>] + [--separate-git-dir <git dir>] + [--shared[=<permissions>]] [directory] +DESCRIPTION +----------- + +This command creates an empty git repository - basically a `.git` +directory with subdirectories for `objects`, `refs/heads`, +`refs/tags`, and template files. An initial `HEAD` file that +references the HEAD of the master branch is also created. + +If the `$GIT_DIR` environment variable is set then it specifies a path +to use instead of `./.git` for the base of the repository. + +If the object storage directory is specified via the +`$GIT_OBJECT_DIRECTORY` environment variable then the sha1 directories +are created underneath - otherwise the default `$GIT_DIR/objects` +directory is used. + +Running 'git init' in an existing repository is safe. It will not +overwrite things that are already there. The primary reason for +rerunning 'git init' is to pick up newly added templates (or to move +the repository to another place if --separate-git-dir is given). + OPTIONS ------- @@ -31,6 +54,15 @@ current working directory. Specify the directory from which templates will be used. (See the "TEMPLATE DIRECTORY" section below.) +--separate-git-dir=<git dir>:: + +Instead of initializing the repository where it is supposed to be, +place a filesytem-agnostic git symbolic link there, pointing to the +specified git path, and initialize a git repository at the path. The +result is git repository can be separated from working tree. If this +is reinitialization, the repository will be moved to the specified +path. + --shared[=(false|true|umask|group|all|world|everybody|0xxx)]:: Specify that the git repository is to be shared amongst several users. This @@ -74,32 +106,6 @@ line, the command is run inside the directory (possibly after creating it). -- -DESCRIPTION ------------ -This command creates an empty git repository - basically a `.git` directory -with subdirectories for `objects`, `refs/heads`, `refs/tags`, and -template files. -An initial `HEAD` file that references the HEAD of the master branch -is also created. - -If the `$GIT_DIR` environment variable is set then it specifies a path -to use instead of `./.git` for the base of the repository. - -If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY` -environment variable then the sha1 directories are created underneath - -otherwise the default `$GIT_DIR/objects` directory is used. - -Running 'git init' in an existing repository is safe. It will not overwrite -things that are already there. The primary reason for rerunning 'git init' -is to pick up newly added templates. - -Note that 'git init' is the same as 'git init-db'. The command -was primarily meant to initialize the object database, but over -time it has become responsible for setting up the other aspects -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 ------------------ @@ -134,15 +140,6 @@ $ git add . <2> <1> prepare /path/to/my/codebase/.git directory <2> add all existing file to the index - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index 7477ce8fa8..08f85ba046 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -84,14 +84,6 @@ If the configuration variable 'instaweb.browser' is not set, 'web.browser' will be used instead if it is defined. See linkgit:git-web{litdd}browse[1] for more information about this. -Author ------- -Written by Eric Wong <normalperson@yhbt.net> - -Documentation --------------- -Documentation by Eric Wong <normalperson@yhbt.net>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index ff41784c60..de5c0d37a5 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -25,6 +25,7 @@ OPTIONS -<n>:: Limits the number of commits to show. + Note that this is a commit limiting option, see below. <since>..<until>:: Show only commits between the named two commits. When @@ -72,16 +73,16 @@ produced by --stat etc. to be prefixed with "\-- " to separate them from options or refnames. +include::rev-list-options.txt[] + +include::pretty-formats.txt[] + Common diff options -~~~~~~~~~~~~~~~~~~~ +------------------- :git-log: 1 include::diff-options.txt[] -include::rev-list-options.txt[] - -include::pretty-formats.txt[] - include::diff-generate-patch.txt[] Examples @@ -177,17 +178,9 @@ 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, +This setting can be disabled by the `--no-notes` option, overridden by the 'GIT_NOTES_DISPLAY_REF' environment variable, -and supplemented by the `--show-notes` option. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. +and overridden by the `--notes=<ref>` option. GIT --- diff --git a/Documentation/git-lost-found.txt b/Documentation/git-lost-found.txt index 602b8d5d4d..adf7e1c055 100644 --- a/Documentation/git-lost-found.txt +++ b/Documentation/git-lost-found.txt @@ -67,15 +67,6 @@ $ git rev-parse not-lost-anymore 1ef2b196d909eed523d4f3c9bf54b78cdd6843c6 ------------ -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 86abd13451..4b28292811 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -209,15 +209,6 @@ SEE ALSO -------- linkgit:git-read-tree[1], linkgit:gitignore[5] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano, Josh Triplett, and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index abe7bf9ff9..7a9b86a58a 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git ls-remote' [--heads] [--tags] [-u <exec> | --upload-pack <exec>] - <repository> <refs>... + [--exit-code] <repository> [<refs>...] DESCRIPTION ----------- @@ -36,6 +36,12 @@ OPTIONS SSH and where the SSH daemon does not use the PATH configured by the user. +--exit-code:: + Exit with status "2" when no matching refs are found in the remote + repository. Usually the command exits with status "0" to indicate + it successfully talked with the remote repository, whether it + found any matching refs. + <repository>:: Location of the repository. The shorthand defined in $GIT_DIR/branches/ can be used. Use "." (dot) to list references in @@ -67,10 +73,6 @@ EXAMPLES c5db5456ae3b0873fc659c19fafdde22313cc441 refs/tags/v0.99.2 7ceca275d047c90c0c7d5afb13ab97efdf51bd6e refs/tags/v0.99.3 -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index 76ed625e7b..16e87fd6dd 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -95,18 +95,6 @@ Object size identified by <object> is given in bytes, and right-justified with minimum width of 7 characters. Object size is given only for blobs (file) entries; for other entries `-` character is used in place of size. - -Author ------- -Written by Petr Baudis <pasky@suse.cz> -Completely rewritten from scratch by Junio C Hamano <gitster@pobox.com>, -another major rewrite by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list -<git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 3ea5aad56c..ed45662cc9 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -80,17 +80,6 @@ This can enabled by default with the configuration option mailinfo.scissors. <patch>:: The patch extracted from e-mail. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> and -Junio C Hamano <gitster@pobox.com> - - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt index 71912a19a4..9b2049d674 100644 --- a/Documentation/git-mailsplit.txt +++ b/Documentation/git-mailsplit.txt @@ -46,16 +46,6 @@ OPTIONS --keep-cr:: Do not remove `\r` from lines ending with `\r\n`. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> -and Junio C Hamano <gitster@pobox.com> - - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt index eedef1bb1a..b295bf8330 100644 --- a/Documentation/git-merge-base.txt +++ b/Documentation/git-merge-base.txt @@ -9,7 +9,8 @@ git-merge-base - Find as good common ancestors as possible for a merge SYNOPSIS -------- [verse] -'git merge-base' [-a|--all] [--octopus] <commit> <commit>... +'git merge-base' [-a|--all] <commit> <commit>... +'git merge-base' [-a|--all] --octopus <commit>... 'git merge-base' --independent <commit>... DESCRIPTION @@ -22,23 +23,21 @@ that does not have any better common ancestor is a 'best common ancestor', i.e. a 'merge base'. Note that there can be more than one merge base for a pair of commits. -Unless `--octopus` is given, among the two commits to compute the merge -base from, one is specified by the first commit argument on the command -line; the other commit is a (possibly hypothetical) commit that is a merge -across all the remaining commits on the command line. As the most common -special case, specifying only two commits on the command line means -computing the merge base between the given two commits. +OPERATION MODE +-------------- + +As the most common special case, specifying only two commits on the +command line means computing the merge base between the given two commits. + +More generally, among the two commits to compute the merge base from, +one is specified by the first commit argument on the command line; +the other commit is a (possibly hypothetical) commit that is a merge +across all the remaining commits on the command line. As a consequence, the 'merge base' is not necessarily contained in each of the commit arguments if more than two commits are specified. This is different from linkgit:git-show-branch[1] when used with the `--merge-base` option. -OPTIONS -------- --a:: ---all:: - Output all merge bases for the commits, instead of just one. - --octopus:: Compute the best common ancestors of all supplied commits, in preparation for an n-way merge. This mimics the behavior @@ -51,6 +50,12 @@ OPTIONS from any other. This mimics the behavior of 'git show-branch --independent'. +OPTIONS +------- +-a:: +--all:: + Output all merge bases for the commits, instead of just one. + DISCUSSION ---------- @@ -89,6 +94,9 @@ and the result of `git merge-base A M` is '1'. Commit '2' is also a common ancestor between 'A' and 'M', but '1' is a better common ancestor, because '2' is an ancestor of '1'. Hence, '2' is not a merge base. +The result of `git merge-base --octopus A B C` is '2', because '2' is +the best common ancestor of all commits. + When the history involves criss-cross merges, there can be more than one 'best' common ancestor for two commits. For example, with this topology: @@ -102,14 +110,6 @@ both '1' and '2' are merge-bases of A and B. Neither one is better than the other (both are 'best' merge bases). When the `--all` option is not given, it is unspecified which best one is output. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - See also -------- linkgit:git-rev-list[1], diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt index f334d694e0..635c66956e 100644 --- a/Documentation/git-merge-file.txt +++ b/Documentation/git-merge-file.txt @@ -86,17 +86,6 @@ git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345:: merges tmp/a123 and tmp/c345 with the base tmp/b234, but uses labels `a` and `c` instead of `tmp/a123` and `tmp/c345`. - -Author ------- -Written by Johannes Schindelin <johannes.schindelin@gmx.de> - - -Documentation --------------- -Documentation by Johannes Schindelin and the git-list <git@vger.kernel.org>, -with parts copied from the original documentation of RCS 'merge'. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 921b38f183..6ce54673b0 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -73,15 +73,6 @@ merge once anything has returned an error (i.e., `cat` returned an error for the AA file, because it didn't exist in the original, and thus 'git merge-index' didn't even try to merge the MM thing). -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> -One-shot merge by Petr Baudis <pasky@ucw.cz> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-merge-one-file.txt b/Documentation/git-merge-one-file.txt index a163cfca69..ee059def79 100644 --- a/Documentation/git-merge-one-file.txt +++ b/Documentation/git-merge-one-file.txt @@ -15,15 +15,6 @@ DESCRIPTION This is the standard helper program to use with 'git merge-index' to resolve a merge after the trivial merge done with 'git read-tree -m'. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org>, -Junio C Hamano <gitster@pobox.com> and Petr Baudis <pasky@suse.cz>. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt index f869a7f00f..3bfa7b4220 100644 --- a/Documentation/git-merge-tree.txt +++ b/Documentation/git-merge-tree.txt @@ -23,14 +23,6 @@ merge results outside of the index, and stuff the results back into the index. For this reason, the output from the command omits entries that match the <branch1> tree. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index c1efaaa5c5..e2e6aba17e 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>] [-X <strategy-option>] - [--[no-]rerere-autoupdate] [-m <msg>] <commit>... + [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...] 'git merge' <msg> HEAD <commit>... 'git merge' --abort @@ -95,8 +95,13 @@ commit or stash your changes before running 'git merge'. <commit>...:: Commits, usually other branch heads, to merge into our branch. - You need at least one <commit>. Specifying more than one - <commit> obviously means you are trying an Octopus. + Specifying more than one commit will create a merge with + more than two parents (affectionately called an Octopus merge). ++ +If no commit is given from the command line, and if `merge.defaultToUpstream` +configuration variable is set, merge the remote tracking branches +that the current branch is configured to use as its upstream. +See also the configuration section of this manual page. PRE-MERGE CHECKS @@ -312,15 +317,6 @@ linkgit:git-diff[1], linkgit:git-ls-files[1], linkgit:git-add[1], linkgit:git-rm[1], linkgit:git-mergetool[1] -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-mergetool--lib.txt b/Documentation/git-mergetool--lib.txt index d8df55362c..63ededec1d 100644 --- a/Documentation/git-mergetool--lib.txt +++ b/Documentation/git-mergetool--lib.txt @@ -41,14 +41,6 @@ run_merge_tool:: '$MERGED', '$LOCAL', '$REMOTE', and '$BASE' must be defined for use by the merge tool. -Author ------- -Written by David Aguilar <davvid@gmail.com> - -Documentation --------------- -Documentation by David Aguilar and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 1f75a848ba..8c79ae8d2a 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -26,8 +26,8 @@ OPTIONS --tool=<tool>:: Use the merge resolution program specified by <tool>. Valid merge tools are: - kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, - diffuse, tortoisemerge, opendiff, p4merge and araxis. + araxis, bc3, diffuse, ecmerge, emerge, gvimdiff, kdiff3, + meld, opendiff, p4merge, tkdiff, tortoisemerge, vimdiff and xxdiff. + If a merge resolution program is not specified, 'git mergetool' will use the configuration variable `merge.tool`. If the @@ -82,14 +82,6 @@ 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> - -Documentation --------------- -Documentation by Theodore Y Ts'o. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-mktag.txt b/Documentation/git-mktag.txt index 8bcc11443d..037ab1045d 100644 --- a/Documentation/git-mktag.txt +++ b/Documentation/git-mktag.txt @@ -32,15 +32,6 @@ exists, is separated by a blank line from the header. The message part may contain a signature that git itself doesn't care about, but that can be verified with gpg. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-mktree.txt b/Documentation/git-mktree.txt index 81e3326772..afe21be64d 100644 --- a/Documentation/git-mktree.txt +++ b/Documentation/git-mktree.txt @@ -34,14 +34,6 @@ OPTIONS optional. Note - if the '-z' option is used, lines are terminated with NUL. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt index bdcb58526e..db0e030d69 100644 --- a/Documentation/git-mv.txt +++ b/Documentation/git-mv.txt @@ -39,17 +39,6 @@ OPTIONS --dry-run:: Do nothing; only show what would happen - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> -Rewritten by Ryan Anderson <ryan@michonline.com> -Move functionality added by Josef Weidendorfer <Josef.Weidendorfer@gmx.de> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt index 2108237c36..ad1d1468c9 100644 --- a/Documentation/git-name-rev.txt +++ b/Documentation/git-name-rev.txt @@ -70,15 +70,6 @@ Another nice thing you can do is: % git log | git name-rev --stdin ------------ - -Author ------- -Written by Johannes Schindelin <Johannes.Schindelin@gmx.de> - -Documentation --------------- -Documentation by Johannes Schindelin. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 296f314eae..6a187f2e23 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -17,7 +17,7 @@ SYNOPSIS 'git notes' merge [-v | -q] [-s <strategy> ] <notes_ref> 'git notes' merge --commit [-v | -q] 'git notes' merge --abort [-v | -q] -'git notes' remove [<object>] +'git notes' remove [--ignore-missing] [--stdin] [<object>...] 'git notes' prune [-n | -v] 'git notes' get-ref @@ -57,8 +57,11 @@ list:: add:: Add notes for a given object (defaults to HEAD). Abort if the - object already has notes (use `-f` to overwrite an - existing note). + object already has notes (use `-f` to overwrite existing notes). + However, if you're using `add` interactively (using an editor + to supply the notes contents), then - instead of aborting - + the existing notes will be opened in the editor (like the `edit` + subcommand). copy:: Copy the notes for the first object onto the second object. @@ -103,8 +106,9 @@ When done, the user can either finalize the merge with 'git notes merge --abort'. remove:: - Remove the notes for a given object (defaults to HEAD). - This is equivalent to specifying an empty note message to + Remove the notes for given objects (defaults to HEAD). When + giving zero or one object from the command line, this is + equivalent to specifying an empty note message to the `edit` subcommand. prune:: @@ -151,6 +155,15 @@ OPTIONS 'GIT_NOTES_REF' and the "core.notesRef" configuration. The ref is taken to be in `refs/notes/` if it is not qualified. +--ignore-missing:: + Do not consider it an error to request removing notes from an + object that does not have notes attached to it. + +--stdin:: + Also read the object names to remove notes from from the standard + input (there is no reason you cannot combine this with object + names from the command line). + -n:: --dry-run:: Do not remove anything; just report the object names whose notes diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 65eff66afe..20c8551d6a 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -115,7 +115,7 @@ base-name:: --honor-pack-keep:: This flag causes an object already in a local pack that - has a .keep file to be ignored, even if it it would have + has a .keep file to be ignored, even if it would have otherwise been packed. --incremental:: @@ -190,15 +190,20 @@ self-contained. Use `git index-pack --fix-thin` (see linkgit:git-index-pack[1]) to restore the self-contained property. --delta-base-offset:: - A packed archive can express base object of a delta as - either 20-byte object name or as an offset in the - stream, but older version of git does not understand the + A packed archive can express the base object of a delta as + either a 20-byte object name or as an offset in the + stream, but ancient versions of git don't understand the latter. By default, 'git pack-objects' only uses the former format for better compatibility. This option allows the command to use the latter format for compactness. Depending on the average delta chain length, this option typically shrinks the resulting packfile by 3-5 per-cent. ++ +Note: Porcelain commands such as `git gc` (see linkgit:git-gc[1]), +`git repack` (see linkgit:git-repack[1]) pass this option by default +in modern git when they put objects in your repository into pack files. +So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle. --threads=<n>:: Specifies the number of threads to spawn when searching for best @@ -219,15 +224,6 @@ self-contained. Use `git index-pack --fix-thin` With this option, parents that are hidden by grafts are packed nevertheless. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation -------------- -Documentation by Junio C Hamano - SEE ALSO -------- linkgit:git-rev-list[1] diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt index d0607879db..db9f0f7055 100644 --- a/Documentation/git-pack-redundant.txt +++ b/Documentation/git-pack-redundant.txt @@ -38,14 +38,6 @@ OPTIONS --verbose:: Outputs some statistics to stderr. Has a small performance penalty. -Author ------- -Written by Lukas Sandström <lukass@etek.chalmers.se> - -Documentation --------------- -Documentation by Lukas Sandström <lukass@etek.chalmers.se> - SEE ALSO -------- linkgit:git-pack-objects[1] diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt index 1ee99c208c..54b92534ce 100644 --- a/Documentation/git-pack-refs.txt +++ b/Documentation/git-pack-refs.txt @@ -56,11 +56,6 @@ a repository with many branches of historical interests. The command usually removes loose refs under `$GIT_DIR/refs` hierarchy after packing them. This option tells it not to. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-parse-remote.txt b/Documentation/git-parse-remote.txt index 39d9daa7e0..02217f6ba2 100644 --- a/Documentation/git-parse-remote.txt +++ b/Documentation/git-parse-remote.txt @@ -17,14 +17,6 @@ routines to parse files under $GIT_DIR/remotes/ and $GIT_DIR/branches/ and configuration variables that are related to fetching, pulling and pushing. -Author ------- -Written by Junio C Hamano. - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt index 4dae1390a5..50e26f43c1 100644 --- a/Documentation/git-patch-id.txt +++ b/Documentation/git-patch-id.txt @@ -29,14 +29,6 @@ OPTIONS <patch>:: The diff to create the ID of. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt index 87dacd797f..a34d62f0da 100644 --- a/Documentation/git-peek-remote.txt +++ b/Documentation/git-peek-remote.txt @@ -37,14 +37,6 @@ OPTIONS The repository to sync from. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt index abfc6b6ead..9e6202cdff 100644 --- a/Documentation/git-prune-packed.txt +++ b/Documentation/git-prune-packed.txt @@ -36,14 +36,6 @@ OPTIONS --quiet:: Squelch the progress indicator. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Ryan Anderson <ryan@michonline.com> - SEE ALSO -------- linkgit:git-pack-objects[1] diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index 4d673a5686..f616a739ef 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -78,14 +78,6 @@ linkgit:git-fsck[1], linkgit:git-gc[1], linkgit:git-reflog[1] -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 30466917da..14609cbd4d 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -84,6 +84,15 @@ must be given before the options meant for 'git fetch'. --verbose:: Pass --verbose to git-fetch and git-merge. +--[no-]recurse-submodules[=yes|on-demand|no]:: + This option controls if new commits of all populated submodules should + be fetched too (see linkgit:git-config[1] and linkgit:gitmodules[5]). + That might be necessary to get the data needed for merging submodule + commits, a feature git learned in 1.7.3. Notice that the result of a + merge will not be checked out in the submodule, "git submodule update" + has to be called afterwards to bring the work tree up to date with the + merge result. + Options related to merging ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -211,22 +220,19 @@ If you tried a pull which resulted in a complex conflicts and would want to start over, you can recover with 'git reset'. +BUGS +---- +Using --recurse-submodules can only fetch new commits in already checked +out submodules right now. When e.g. upstream added a new submodule in the +just fetched commits of the superproject the submodule itself can not be +fetched, making it impossible to check out that submodule later without +having to do a fetch again. This is expected to be fixed in a future git +version. + SEE ALSO -------- linkgit:git-fetch[1], linkgit:git-merge[1], linkgit:git-config[1] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> -and Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Jon Loeliger, -David Greaves, -Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index e11660a2e6..88acfcd4cc 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -406,16 +406,6 @@ Commits A and B would no longer belong to a branch with a symbolic name, and so would be unreachable. As such, these commits would be removed by a `git gc` command on the origin repository. - -Author ------- -Written by Junio C Hamano <gitster@pobox.com>, later rewritten in C -by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt index 579e8d2f3b..7f112f3dcd 100644 --- a/Documentation/git-quiltimport.txt +++ b/Documentation/git-quiltimport.txt @@ -49,14 +49,6 @@ The default for the patch directory is patches or the value of the $QUILT_PATCHES environment variable. -Author ------- -Written by Eric Biederman <ebiederm@lnxi.com> - -Documentation --------------- -Documentation by Eric Biederman <ebiederm@lnxi.com> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 634423a69e..46a96f2313 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -53,6 +53,11 @@ OPTIONS trees that are not directly related to the current working tree status into a temporary index file. +-n:: +--dry-run:: + Check if the command would error out, without updating the index + nor the files in the working tree for real. + -v:: Show the progress of checking files out. @@ -421,15 +426,6 @@ SEE ALSO linkgit:git-write-tree[1]; linkgit:git-ls-files[1]; linkgit:gitignore[5] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 96680c8456..9a075bc4d2 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git rebase' [-i | --interactive] [options] [--onto <newbase>] - <upstream> [<branch>] + [<upstream>] [<branch>] 'git rebase' [-i | --interactive] [options] --onto <newbase> --root [<branch>] @@ -21,6 +21,12 @@ If <branch> is specified, 'git rebase' will perform an automatic `git checkout <branch>` before doing anything else. Otherwise it remains on the current branch. +If <upstream> is not specified, the upstream configured in +branch.<name>.remote and branch.<name>.merge options will be used; see +linkgit:git-config[1] for details. If you are currently not on any +branch or if the current branch does not have a configured upstream, +the rebase will abort. + All changes made by commits in the current branch but that are not in <upstream> are saved to a temporary area. This is the same set of commits that would be shown by `git log <upstream>..HEAD` (or @@ -66,8 +72,9 @@ would be: D---E---F---G master ------------ -The latter form is just a short-hand of `git checkout topic` -followed by `git rebase master`. +*NOTE:* The latter form is just a short-hand of `git checkout topic` +followed by `git rebase master`. When rebase exits `topic` will +remain the checked-out branch. If the upstream branch already contains a change you have made (e.g., because you mailed a patch which was applied upstream), then that commit @@ -216,7 +223,8 @@ 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, - not just an existing branch name. + not just an existing branch name. Defaults to the configured + upstream for the current branch. <branch>:: Working branch; defaults to HEAD. @@ -658,7 +666,6 @@ The ripple effect of a "hard case" recovery is especially bad: 'everyone' downstream from 'topic' will now have to perform a "hard case" recovery too! - BUGS ---- The todo list presented by `--preserve-merges --interactive` does not @@ -681,15 +688,6 @@ by moving the "pick 4" line will result in the following history: 1 --- 2 --- 4 --- 5 ------------ -Authors ------- -Written by Junio C Hamano <gitster@pobox.com> and -Johannes E. Schindelin <johannes.schindelin@gmx.de> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 2790eebaff..f34e0ae1bd 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -151,15 +151,6 @@ SEE ALSO -------- linkgit:git-send-pack[1] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index e50bd9b68d..09057bf90c 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -90,14 +90,6 @@ them. --verbose:: Print extra information on screen. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt index 8fc809f82b..9893376487 100644 --- a/Documentation/git-relink.txt +++ b/Documentation/git-relink.txt @@ -24,14 +24,6 @@ OPTIONS <dir>:: Directories containing a .git/objects/ subdirectory. -Author ------- -Written by Ryan Anderson <ryan@michonline.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-remote-ext.txt b/Documentation/git-remote-ext.txt index 2d65cfefd5..68263a6a53 100644 --- a/Documentation/git-remote-ext.txt +++ b/Documentation/git-remote-ext.txt @@ -7,17 +7,17 @@ git-remote-ext - Bridge smart transport to external command. SYNOPSIS -------- -git remote add nick "ext::<command>[ <arguments>...]" +git remote add <nick> "ext::<command>[ <arguments>...]" DESCRIPTION ----------- -This remote helper uses the specified 'program' to connect +This remote helper uses the specified '<command>' to connect to a remote git server. -Data written to stdin of this specified 'program' is assumed +Data written to stdin of the specified '<command>' is assumed to be sent to a git:// server, git-upload-pack, git-receive-pack or git-upload-archive (depending on situation), and data read -from stdout of this program is assumed to be received from +from stdout of <command> is assumed to be received from the same service. Command and arguments are separated by an unescaped space. @@ -40,7 +40,7 @@ The following sequences have a special meaning: git wants to invoke. '%G' (must be the first characters in an argument):: - This argument will not be passed to 'program'. Instead, it + This argument will not be passed to '<command>'. Instead, it will cause the helper to start by sending git:// service requests to the remote side with the service field set to an appropriate value and the repository field set to rest of the argument. Default is not to send @@ -50,7 +50,7 @@ This is useful if remote side is git:// server accessed over some tunnel. '%V' (must be first characters in argument):: - This argument will not be passed to 'program'. Instead it sets + This argument will not be passed to '<command>'. Instead it sets the vhost field in the git:// service request (to rest of the argument). Default is not to send vhost in such request (if sent). @@ -76,7 +76,7 @@ EXAMPLES: --------- This remote helper is transparently used by git when you use commands such as "git fetch <URL>", "git clone <URL>", -, "git push <URL>" or "git remote add nick <URL>", where <URL> +, "git push <URL>" or "git remote add <nick> <URL>", where <URL> begins with `ext::`. Examples: "ext::ssh -i /home/foo/.ssh/somekey user@host.example %S 'foo/repo'":: diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 3a23477ce7..58f6ad4994 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -181,11 +181,11 @@ CAPABILITIES When using the import command, expect the source ref to have been written to the destination ref. The earliest applicable refspec takes precedence. For example - "refs/heads/*:refs/svn/origin/branches/*" means that, after an - "import refs/heads/name", the script has written to + "refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}" means + that, after an "import refs/heads/name", the script has written to refs/svn/origin/branches/name. If this capability is used at all, it must cover all refs reported by the list command; if - it is not used, it is effectively "*:*" + it is not used, it is effectively "{asterisk}:{asterisk}" REF LIST ATTRIBUTES ------------------- @@ -201,12 +201,12 @@ REF LIST ATTRIBUTES OPTIONS ------- -'option verbosity' <N>:: +'option verbosity' <n>:: Changes the verbosity of messages displayed by the helper. - A value of 0 for N means that processes operate + 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 + of <n> correspond to the number of -v flags passed on the command line. 'option progress' \{'true'|'false'\}:: @@ -239,10 +239,6 @@ SEE ALSO -------- linkgit:git-remote[1] -Documentation -------------- -Documentation by Daniel Barkalow and Ilari Liusvaara - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index c258ea48db..528f34a131 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git remote' [-v | --verbose] -'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror] <name> <url> +'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url> 'git remote rename' <old> <new> 'git remote rm' <name> 'git remote set-head' <name> (-a | -d | <branch>) @@ -67,11 +67,14 @@ multiple branches without grabbing all branches. With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set up to point at remote's `<master>` branch. See also the set-head command. + -In mirror mode, enabled with `\--mirror`, the refs will not be stored -in the 'refs/remotes/' namespace, but in 'refs/heads/'. This option -only makes sense in bare repositories. If a remote uses mirror -mode, furthermore, `git push` will always behave as if `\--mirror` -was passed. +When a fetch mirror is created with `\--mirror=fetch`, the refs will not +be stored in the 'refs/remotes/' namespace, but rather everything in +'refs/' on the remote will be directly mirrored into 'refs/' in the +local repository. This option only makes sense in bare repositories, +because a fetch would overwrite any local commits. ++ +When a push mirror is created with `\--mirror=push`, then `git push` +will always behave as if `\--mirror` was passed. 'rename':: @@ -214,16 +217,6 @@ linkgit:git-fetch[1] linkgit:git-branch[1] linkgit:git-config[1] -Author ------- -Written by Junio Hamano - - -Documentation --------------- -Documentation by J. Bruce Fields and the git-list <git@vger.kernel.org>. - - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 27f7865b06..0decee240b 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -123,15 +123,6 @@ need to set the configuration variable `repack.UseDeltaBaseOffset` to is unaffected by this option as the conversion is performed on the fly as needed in that case. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Ryan Anderson <ryan@michonline.com> - SEE ALSO -------- linkgit:git-pack-objects[1] diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index fde2092582..17df525275 100644 --- a/Documentation/git-replace.txt +++ b/Documentation/git-replace.txt @@ -80,17 +80,6 @@ linkgit:git-tag[1] linkgit:git-branch[1] linkgit:git[1] -Author ------- -Written by Christian Couder <chriscool@tuxfamily.org> and Junio C -Hamano <gitster@pobox.com>, based on 'git tag' by Kristian Hogsberg -<krh@redhat.com> and Carlos Rica <jasampler@gmail.com>. - -Documentation --------------- -Documentation by Christian Couder <chriscool@tuxfamily.org> and the -git-list <git@vger.kernel.org>, based on 'git tag' documentation. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index e5bdb5533e..a0d1fa6594 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -16,3 +16,7 @@ DESCRIPTION This is a synonym for linkgit:git-config[1]. Please refer to the documentation of that command. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt index 400f61f6e2..3521d8e3c8 100644 --- a/Documentation/git-request-pull.txt +++ b/Documentation/git-request-pull.txt @@ -29,14 +29,6 @@ OPTIONS <end>:: Commit to end at; defaults to HEAD. -Author ------- -Written by Ryan Anderson <ryan@michonline.com> and Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index db99d4786e..52db1d80cf 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'|'forget' [<pathspec>]|'diff'|'status'|'gc'] +'git rerere' ['clear'|'forget' <pathspec>|'diff'|'status'|'gc'] DESCRIPTION ----------- @@ -43,7 +43,7 @@ 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. +conflict in <pathspec>. 'diff':: @@ -205,11 +205,6 @@ would conflict the same way as the test merge you resolved earlier. 'git rerere' will be run by 'git rebase' to help you resolve this conflict. - -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 927ecee2f2..b2832fc7eb 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git reset' [-q] [<commit>] [--] <paths>... -'git reset' --patch [<commit>] [--] [<paths>...] +'git reset' [--patch|-p] [<commit>] [--] [<paths>...] 'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>] DESCRIPTION @@ -39,8 +39,9 @@ working tree in one go. 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]). +This means that `git reset -p` is the opposite of `git add -p`, i.e. +you can use it to selectively reset hunks. See the ``Interactive Mode'' +section of linkgit:git-add[1] to learn how to operate the `\--patch` mode. 'git reset' [--<mode>] [<commit>]:: This form resets the current branch head to <commit> and @@ -397,15 +398,6 @@ entries: 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> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 8e1e32908c..38fafcaa6b 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -16,6 +16,10 @@ SYNOPSIS [ \--sparse ] [ \--merges ] [ \--no-merges ] + [ \--min-parents=<number> ] + [ \--no-min-parents ] + [ \--max-parents=<number> ] + [ \--no-max-parents ] [ \--first-parent ] [ \--remove-empty ] [ \--full-history ] @@ -25,12 +29,16 @@ SYNOPSIS [ \--tags[=<pattern>] ] [ \--remotes[=<pattern>] ] [ \--glob=<glob-pattern> ] + [ \--ignore-missing ] [ \--stdin ] [ \--quiet ] [ \--topo-order ] [ \--parents ] [ \--timestamp ] [ \--left-right ] + [ \--left-only ] + [ \--right-only ] + [ \--cherry-mark ] [ \--cherry-pick ] [ \--encoding[=<encoding>] ] [ \--(author|committer|grep)=<pattern> ] @@ -105,16 +113,6 @@ include::rev-list-options.txt[] include::pretty-formats.txt[] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano, Jonas Fonseca -and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index ff23cb0219..02c44c999f 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -308,16 +308,6 @@ $ git rev-parse --default master --verify $REV + but if $REV is empty, the commit object name from master will be printed. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> . -Junio C Hamano <gitster@pobox.com> and Pierre Habouzit <madcoder@debian.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 752fc88e76..ac10cfbb14 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -80,6 +80,16 @@ effect to your index in a row. --signoff:: Add Signed-off-by line at the end of the commit message. +--strategy=<strategy>:: + Use the given merge strategy. Should only be used once. + See the MERGE STRATEGIES section in linkgit:git-merge[1] + for details. + +-X<option>:: +--strategy-option=<option>:: + Pass the merge strategy-specific option through to the + merge strategy. See linkgit:git-merge[1] for details. + EXAMPLES -------- git revert HEAD~3:: @@ -95,14 +105,6 @@ git revert -n master{tilde}5..master{tilde}2:: changes. The revert only modifies the working tree and the index. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - SEE ALSO -------- linkgit:git-cherry-pick[1] diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 0adbe8b1f8..8c0554f971 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -153,14 +153,6 @@ SEE ALSO -------- linkgit:git-add[1] -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 7ec9dabe68..5a168cfab2 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -348,11 +348,12 @@ sendemail.confirm:: one of 'always', 'never', 'cc', 'compose', or 'auto'. See '--confirm' in the previous section for the meaning of these values. - +EXAMPLE +------- Use gmail as the smtp server ----------------------------- - -Add the following section to the config file: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +To use 'git send-email' to send your patches through the GMail SMTP server, +edit ~/.gitconfig to specify your account settings: [sendemail] smtpencryption = tls @@ -360,22 +361,19 @@ Add the following section to the config file: smtpuser = yourname@gmail.com smtpserverport = 587 -Note: the following perl modules are required - Net::SMTP::SSL, MIME::Base64 and Authen::SASL - +Once your commits are ready to be sent to the mailing list, run the +following commands: -Author ------- -Written by Ryan Anderson <ryan@michonline.com> + $ git format-patch --cover-letter -M origin/master -o outgoing/ + $ edit outgoing/0000-* + $ git send-email outgoing/* -git-send-email is originally based upon -send_lots_of_email.pl by Greg Kroah-Hartman. - - -Documentation --------------- -Documentation by Ryan Anderson +Note: the following perl modules are required + Net::SMTP::SSL, MIME::Base64 and Authen::SASL +SEE ALSO +-------- +linkgit:git-format-patch[1], linkgit:git-imap-send[1], mbox(5) GIT --- diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index deaa7d9654..17f8f55526 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -114,15 +114,6 @@ With '--force', the fast-forward check is disabled for all refs. Optionally, a <ref> parameter can be prefixed with a plus '+' sign to disable the fast-forward check only on that ref. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-sh-i18n--envsubst.txt b/Documentation/git-sh-i18n--envsubst.txt new file mode 100644 index 0000000000..61e4c08dac --- /dev/null +++ b/Documentation/git-sh-i18n--envsubst.txt @@ -0,0 +1,36 @@ +git-sh-i18n--envsubst(1) +======================== + +NAME +---- +git-sh-i18n--envsubst - Git's own envsubst(1) for i18n fallbacks + +SYNOPSIS +-------- +[verse] +eval_gettext () { + printf "%s" "$1" | ( + export PATH $('git sh-i18n--envsubst' --variables "$1"); + 'git sh-i18n--envsubst' "$1" + ) +} + +DESCRIPTION +----------- + +This is not a command the end user would want to run. Ever. +This documentation is meant for people who are studying the +plumbing scripts and/or are writing new ones. + +git-sh-i18n--envsubst is Git's stripped-down copy of the GNU +`envsubst(1)` program that comes with the GNU gettext package. It's +used internally by linkgit:git-sh-i18n[1] to interpolate the variables +passed to the the `eval_gettext` function. + +No promises are made about the interface, or that this +program won't disappear without warning in the next version +of Git. Don't use it. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-sh-i18n.txt b/Documentation/git-sh-i18n.txt new file mode 100644 index 0000000000..3b1f7ac7b5 --- /dev/null +++ b/Documentation/git-sh-i18n.txt @@ -0,0 +1,42 @@ +git-sh-i18n(1) +============== + +NAME +---- +git-sh-i18n - Git's i18n setup code for shell scripts + +SYNOPSIS +-------- +'. "$(git --exec-path)/git-sh-i18n"' + +DESCRIPTION +----------- + +This is not a command the end user would want to run. Ever. +This documentation is meant for people who are studying the +Porcelain-ish scripts and/or are writing new ones. + +The 'git sh-i18n scriptlet is designed to be sourced (using +`.`) by Git's porcelain programs implemented in shell +script. It provides wrappers for the GNU `gettext` and +`eval_gettext` functions accessible through the `gettext.sh` +script, and provides pass-through fallbacks on systems +without GNU gettext. + +FUNCTIONS +--------- + +gettext:: + Currently a dummy fall-through function implemented as a wrapper + around `printf(1)`. Will be replaced by a real gettext + implementation in a later version. + +eval_gettext:: + Currently a dummy fall-through function implemented as a wrapper + around `printf(1)` with variables expanded by the + linkgit:git-sh-i18n--envsubst[1] helper. Will be replaced by a + real gettext implementation in a later version. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt index 3da241304b..27fd8ba854 100644 --- a/Documentation/git-sh-setup.txt +++ b/Documentation/git-sh-setup.txt @@ -58,23 +58,19 @@ cd_to_toplevel:: runs chdir to the toplevel of the working tree. require_work_tree:: - checks if the repository is a bare repository, and dies - if so. Used by scripts that require working tree - (e.g. `checkout`). + checks if the current directory is within the working tree + of the repository, and otherwise dies. + +require_work_tree_exists:: + checks if the working tree associated with the repository + exists, and otherwise dies. Often done before calling + cd_to_toplevel, which is impossible to do if there is no + working tree. get_author_ident_from_commit:: outputs code for use with eval to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL and GIT_AUTHOR_DATE variables for a given commit. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt index 6403126a02..d7d4b92894 100644 --- a/Documentation/git-shell.txt +++ b/Documentation/git-shell.txt @@ -28,14 +28,6 @@ read and execute permissions to the directory in order to execute the programs in it. The programs are executed with a cwd of $HOME, and <argument> is parsed as a command-line string. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Petr Baudis and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 5cc3baf48d..ff3755b4c7 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -68,15 +68,6 @@ spelled differently. include::mailmap.txt[] - -Author ------- -Written by Jeff Garzik <jgarzik@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 3b0c88271a..ee4559b6f2 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -200,17 +200,6 @@ shows 10 reflog entries going back from the tip as of 1 hour ago. Without `--list`, the output also shows how these tips are topologically related with each other. - -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - - -Documentation --------------- -Documentation by Junio C Hamano. - - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt index 8382fbe0ec..c4d99f1028 100644 --- a/Documentation/git-show-index.txt +++ b/Documentation/git-show-index.txt @@ -20,15 +20,6 @@ The information it outputs is subset of what you can get from 'git verify-pack -v'; this command only shows the packfile offset and SHA1 of each object. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index be0ec189af..3c45895299 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -177,11 +177,6 @@ linkgit:git-ls-remote[1], linkgit:git-update-ref[1], linkgit:gitrepository-layout[5] -AUTHORS -------- -Written by Linus Torvalds <torvalds@osdl.org>. -Man page by Jonas Fonseca <fonseca@diku.dk>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index f0a8a1aff3..7f075e84f5 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -72,17 +72,6 @@ Discussion include::i18n.txt[] -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> and -Junio C Hamano <gitster@pobox.com>. Significantly enhanced by -Johannes Schindelin <Johannes.Schindelin@gmx.de>. - - -Documentation -------------- -Documentation by David Greaves, Petr Baudis and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-stage.txt b/Documentation/git-stage.txt index 7f251a5865..ba3fe0d7f5 100644 --- a/Documentation/git-stage.txt +++ b/Documentation/git-stage.txt @@ -17,3 +17,7 @@ DESCRIPTION This is a synonym for linkgit:git-add[1]. Please refer to the documentation of that command. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 8728f7a514..15f051fa44 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,7 +13,7 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] -'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] +'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] 'git stash' clear 'git stash' create @@ -42,7 +42,7 @@ is also possible). OPTIONS ------- -save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: +save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: Save your local modifications to a new 'stash', and run `git reset --hard` to revert them. The <message> part is optional and gives @@ -54,12 +54,13 @@ save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: If the `--keep-index` option is used, all changes already added to the index are left intact. + -With `--patch`, you can interactively select hunks from in the diff +With `--patch`, you can interactively select hunks from the diff between HEAD and the working tree to be stashed. The stash entry is constructed such that its index state is the same as the index state of your repository, and its worktree contains only the changes you selected interactively. The selected changes are then rolled back -from your worktree. +from your worktree. See the ``Interactive Mode'' section of +linkgit:git-add[1] to learn how to operate the `\--patch` mode. + The `--patch` option implies `--keep-index`. You can use `--no-keep-index` to override this. @@ -257,10 +258,6 @@ linkgit:git-commit[1], linkgit:git-reflog[1], linkgit:git-reset[1] -AUTHOR ------- -Written by Nanako Shiraishi <nanako3@bluebottle.com> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index dae190a5f2..38cb741f18 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -32,26 +32,27 @@ OPTIONS 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 - not to change in the future, making it safe for scripts. + Give the output in an easy-to-parse format for scripts. + This is similar to the short output, but will remain stable + across git versions and regardless of user configuration. See + below for details. -u[<mode>]:: --untracked-files[=<mode>]:: - Show untracked files (Default: 'all'). + Show untracked files. + -The mode parameter is optional, and is used to specify -the handling of untracked files. The possible options are: +The mode parameter is optional (defaults to 'all'), and is used to +specify the handling of untracked files; when -u is not used, the +default is 'normal', i.e. show untracked files and directories. ++ +The possible options are: + --- - 'no' - Show no untracked files - 'normal' - Shows untracked files and directories - 'all' - Also shows individual files in untracked directories. --- + -See linkgit:git-config[1] for configuration variable -used to change the default for when the option is not -specified. +The default can be changed using the status.showUntrackedFiles +configuration variable documented in linkgit:git-config[1]. --ignore-submodules[=<when>]:: Ignore changes to submodules when looking for changes. <when> can be @@ -78,23 +79,27 @@ OUTPUT The output from this command is designed to be used as a commit template comment, and all the output lines are prefixed with '#'. The default, long format, is designed to be human readable, -verbose and descriptive. They are subject to change in any time. +verbose and descriptive. Its contents and format are subject to change +at any time. The paths mentioned in the output, unlike many other git commands, are made relative to the current directory if you are working in a subdirectory (this is on purpose, to help cutting and pasting). See the status.relativePaths config option below. -In short-format, the status of each path is shown as +Short Format +~~~~~~~~~~~~ + +In the short-format, the status of each path is shown as XY PATH1 -> PATH2 -where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is +where `PATH1` is the path in the `HEAD`, and the ` \-> PATH2` part is shown only when `PATH1` corresponds to a different path in the index/worktree (i.e. the file is renamed). The 'XY' is a two-letter status code. -The fields (including the `->`) are separated from each other by a +The fields (including the `\->`) are separated from each other by a single space. If a filename contains whitespace or other nonprintable characters, that field will be quoted in the manner of a C string literal: surrounded by ASCII double quote (34) characters, and with @@ -143,10 +148,25 @@ 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 +Porcelain Format +~~~~~~~~~~~~~~~~ + +The porcelain format is similar to the short format, but is guaranteed +not to change in a backwards-incompatible way between git versions or +based on user configuration. This makes it ideal for parsing by scripts. +The description of the short format above also describes the porcelain +format, with a few exceptions: + +1. The user's color.status configuration is not respected; color will + always be off. + +2. The user's status.relativePaths configuration is not respected; paths + shown will always be relative to the repository root. + +There is also 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 -order is reversed (e.g 'from -> to' becomes 'to from'). Second, a NUL +change. First, the '\->' is omitted from rename entries and the field +order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL (ASCII 0) follows each filename, replacing space as a field separator and the terminating newline (but a space still separates the status field from the first filename). Third, filenames containing special @@ -174,14 +194,6 @@ SEE ALSO -------- linkgit:gitignore[5] -Author ------- -Written by Junio C Hamano <gitster@pobox.com>. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt index 7508c0e42d..10509cc450 100644 --- a/Documentation/git-stripspace.txt +++ b/Documentation/git-stripspace.txt @@ -23,14 +23,6 @@ OPTIONS <stream>:: Byte stream to act on. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 1ed331c599..5e7a4130ee 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -101,9 +101,10 @@ status:: currently checked out commit for each submodule, along with the submodule path and the output of 'git describe' for the SHA-1. Each SHA-1 will be prefixed with `-` if the submodule is not - initialized and `+` if the currently checked out submodule commit + initialized, `+` if the currently checked out submodule commit does not match the SHA-1 found in the index of the containing - repository. This command is the default command for 'git submodule'. + repository and `U` if the submodule has merge conflicts. + This command is the default command for 'git submodule'. + If '--recursive' is specified, this command will recurse into nested submodules, and show their status as well. @@ -185,8 +186,10 @@ OPTIONS -f:: --force:: - This option is only valid for the add command. - Allow adding an otherwise ignored submodule path. + This option is only valid for add and update commands. + When running add, allow adding an otherwise ignored submodule path. + When running update, throw away local changes in submodules when + switching to a different commit. --cached:: This option is only valid for status and summary commands. These @@ -257,11 +260,6 @@ This file should be formatted in the same way as `$GIT_DIR/config`. The key to each submodule url is "submodule.$name.url". See linkgit:gitmodules[5] for details. - -AUTHOR ------- -Written by Lars Hjemli <hjemli@gmail.com> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 0ade2ce54e..713e523034 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -66,7 +66,7 @@ COMMANDS Set the 'rewriteRoot' option in the [svn-remote] config. --rewrite-uuid=<UUID>;; Set the 'rewriteUUID' option in the [svn-remote] config. ---username=<USER>;; +--username=<user>;; For transports that SVN handles authentication for (http, https, and plain svn), specify the username. For other transports (eg svn+ssh://), you must include the username in @@ -145,17 +145,6 @@ Skip "branches" and "tags" of first level directories;; ------------------------------------------------------------------------ -- ---use-log-author;; - When retrieving svn commits into git (as part of fetch, rebase, or - dcommit operations), look for the first From: or Signed-off-by: line - in the log message and use that as the author string. ---add-author-from;; - When committing to svn from git (as part of commit or dcommit - operations), if the existing log message doesn't already have a - From: or Signed-off-by: line, append a From: line based on the - git commit's author string. If you use this, then --use-log-author - will retrieve a valid author string for all commits. - 'clone':: Runs 'init' and 'fetch'. It will automatically create a directory based on the basename of the URL passed to it; @@ -217,6 +206,13 @@ config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options) Using this option for any other purpose (don't ask) is very strongly discouraged. +--mergeinfo=<mergeinfo>;; + Add the given merge information during the dcommit + (e.g. `--mergeinfo="/branches/foo:1-10"`). All svn server versions can + store this information (as a property), and svn clients starting from + version 1.5 can make use of it. 'git svn' currently does not use it + and does not set it automatically. + 'branch':: Create a branch in the SVN repository. @@ -343,6 +339,8 @@ Any other arguments are passed directly to 'git log' Empty directories are automatically recreated when using "git svn clone" and "git svn rebase", so "mkdirs" is intended for use after commands like "git checkout" or "git reset". + (See the svn-remote.<name>.automkdirs config file option for + more information.) 'commit-diff':: Commits the diff of two tree-ish arguments from the @@ -443,8 +441,8 @@ OPTIONS Only used with the 'init' command. These are passed directly to 'git init'. --r <ARG>:: ---revision <ARG>:: +-r <arg>:: +--revision <arg>:: Used with the 'fetch' command. + This allows revision ranges for partial/cauterized history @@ -565,6 +563,17 @@ repository that will be fetched from. For 'branch' and 'tag', display the urls that will be used for copying when creating the branch or tag. +--use-log-author:: + When retrieving svn commits into git (as part of 'fetch', 'rebase', or + 'dcommit' operations), look for the first `From:` or `Signed-off-by:` line + in the log message and use that as the author string. +--add-author-from:: + When committing to svn from git (as part of 'commit-diff', 'set-tree' or 'dcommit' + operations), if the existing log message doesn't already have a + `From:` or `Signed-off-by:` line, append a `From:` line based on the + git commit's author string. If you use this, then `--use-log-author` + will retrieve a valid author string for all commits. + ADVANCED OPTIONS ---------------- @@ -648,6 +657,16 @@ svn-remote.<name>.rewriteUUID:: where the original UUID is not available via either useSvmProps or useSvnsyncProps. +svn-remote.<name>.pushurl:: + + Similar to git's 'remote.<name>.pushurl', this key is designed + to be used in cases where 'url' points to an SVN repository + via a read-only transport, to provide an alternate read/write + transport. It is assumed that both keys point to the same + repository. Unlike 'commiturl', 'pushurl' is a base path. If + either 'commiturl' or 'pushurl' could be used, 'commiturl' + takes precedence. + svn.brokenSymlinkWorkaround:: This disables potentially expensive checks to workaround broken symlinks checked into SVN by broken clients. Set this @@ -663,6 +682,14 @@ svn.pathnameencoding:: locales to avoid corrupted file names with non-ASCII characters. Valid encodings are the ones supported by Perl's Encode module. +svn-remote.<name>.automkdirs:: + Normally, the "git svn clone" and "git svn rebase" commands + attempt to recreate empty directories that are in the + Subversion repository. If this option is set to "false", then + empty directories will only be created if the "git svn mkdirs" + command is run explicitly. If unset, 'git svn' assumes this + option to be "true". + 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 @@ -757,10 +784,9 @@ use `git svn rebase` to update your work branch instead of `git pull` or when committing into SVN, which can lead to merge commits reversing previous commits in SVN. -DESIGN PHILOSOPHY ------------------ -Merge tracking in Subversion is lacking and doing branched development -with Subversion can be cumbersome as a result. While 'git svn' can track +MERGE TRACKING +-------------- +While 'git svn' can track copy history (including branches and tags) for repositories adopting a standard layout, it cannot yet represent merge history that happened inside git back upstream to SVN users. Therefore it is advised that @@ -770,16 +796,15 @@ compatibility with SVN (see the CAVEATS section below). CAVEATS ------- -For the sake of simplicity and interoperating with a less-capable system -(SVN), it is recommended that all 'git svn' users clone, fetch and dcommit +For the sake of simplicity and interoperating with Subversion, +it is recommended that all 'git svn' users clone, fetch and dcommit directly from the SVN server, and avoid all 'git clone'/'pull'/'merge'/'push' operations between git repositories and branches. The recommended method of exchanging code between git branches and users is 'git format-patch' and 'git am', or just 'dcommit'ing to the SVN repository. Running 'git merge' or 'git pull' is NOT recommended on a branch you -plan to 'dcommit' from. Subversion does not represent merges in any -reasonable or useful fashion; so users using Subversion cannot see any +plan to 'dcommit' from because Subversion users cannot see any merges you've made. Furthermore, if you merge or pull from a git branch that is a mirror of an SVN branch, 'dcommit' may commit to the wrong branch. @@ -829,7 +854,7 @@ Renamed and copied directories are not detected by git and hence not tracked when committing to SVN. I do not plan on adding support for this as it's quite difficult and time-consuming to get working for all the possible corner cases (git doesn't do it, either). Committing -renamed and copied files are fully supported if they're similar enough +renamed and copied files is fully supported if they're similar enough for git to detect them. CONFIGURATION @@ -878,10 +903,6 @@ SEE ALSO -------- linkgit:git-rebase[1] -Author ------- -Written by Eric Wong <normalperson@yhbt.net>. - -Documentation -------------- -Written by Eric Wong <normalperson@yhbt.net>. +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index 33a1536294..d7795ed657 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -53,10 +53,6 @@ and symbolic refs are used by default. symbolic ref were printed correctly, with status 1 if the requested name is not a symbolic ref, or 128 if another error occurs. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 8b169e364a..d82f62120a 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -18,21 +18,22 @@ SYNOPSIS DESCRIPTION ----------- -Adds a tag reference in `.git/refs/tags/`. +Add a tag reference in `.git/refs/tags/`, unless `-d/-l/-v` is given +to delete, list or verify tags. -Unless `-f` is given, the tag must not yet exist in +Unless `-f` is given, the tag to be created must not yet exist in the `.git/refs/tags/` directory. If one of `-a`, `-s`, or `-u <key-id>` is passed, the command -creates a 'tag' object, and requires the tag message. Unless +creates a 'tag' object, and requires a tag message. Unless `-m <msg>` or `-F <file>` is given, an editor is started for the user to type in the tag message. If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied. -Otherwise just the SHA1 object name of the commit object is -written (i.e. a lightweight tag). +Otherwise just a tag reference for the SHA1 object name of the commit object is +created (i.e. a lightweight tag). A GnuPG signed tag object will be created when `-s` or `-u <key-id>` is used. When `-u <key-id>` is not used, the @@ -164,13 +165,12 @@ You can test which tag you have by doing which should return 0123456789abcdef.. if you have the new version. -Sorry for inconvenience. +Sorry for the inconvenience. ------------ Does this seem a bit complicated? It *should* be. There is no -way that it would be correct to just "fix" it behind peoples -backs. People need to know that their tags might have been -changed. +way that it would be correct to just "fix" it automatically. +People need to know that their tags might have been changed. On Automatic following @@ -188,9 +188,10 @@ the toplevel but not limited to them. Mere mortals when pulling from each other do not necessarily want to automatically get private anchor point tags from the other person. -You would notice "please pull" messages on the mailing list says -repo URL and branch name alone. This is designed to be easily -cut&pasted to a 'git fetch' command line: +Often, "please pull" messages on the mailing list just provide +two pieces of information: a repo URL and a branch name; this +is designed to be easily cut&pasted at the end of a 'git fetch' +command line: ------------ Linus, please pull from @@ -206,14 +207,14 @@ becomes: $ git pull git://git..../proj.git master ------------ -In such a case, you do not want to automatically follow other's -tags. +In such a case, you do not want to automatically follow the other +person's tags. -One important aspect of git is it is distributed, and being -distributed largely means there is no inherent "upstream" or +One important aspect of git is its distributed nature, which +largely means there is no inherent "upstream" or "downstream" in the system. On the face of it, the above example might seem to indicate that the tag namespace is owned -by upper echelon of people and tags only flow downwards, but +by the upper echelon of people and that tags only flow downwards, but that is not the case. It only shows that the usage pattern determines who are interested in whose tags. @@ -231,7 +232,7 @@ this case. It may well be that among networking people, they may want to exchange the tags internal to their group, but in that workflow -they are most likely tracking with each other's progress by +they are most likely tracking each other's progress by having remote-tracking branches. Again, the heuristic to automatically follow such tags is a good thing. @@ -241,35 +242,26 @@ On Backdating Tags If you have imported some changes from another VCS and would like to add tags for major releases of your work, it is useful to be able -to specify the date to embed inside of the tag object. The data in +to specify the date to embed inside of the tag object; such data in the tag object affects, for example, the ordering of tags in the gitweb interface. To set the date used in future tag objects, set the environment -variable GIT_COMMITTER_DATE to one or more of the date and time. The -date and time can be specified in a number of ways; the most common -is "YYYY-MM-DD HH:MM". +variable GIT_COMMITTER_DATE (see the later discussion of possible +values; the most common form is "YYYY-MM-DD HH:MM"). -An example follows. +For example: ------------ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 ------------ +include::date-formats.txt[] SEE ALSO -------- linkgit:git-check-ref-format[1]. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org>, -Junio C Hamano <gitster@pobox.com> and Chris Wright <chrisw@osdl.org>. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt index 3c786bd283..5f15754257 100644 --- a/Documentation/git-tar-tree.txt +++ b/Documentation/git-tar-tree.txt @@ -76,14 +76,6 @@ git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar:: Put everything in the current head's Documentation/ directory into 'git-1.4.0-docs.tar', with the prefix 'git-docs/'. -Author ------- -Written by Rene Scharfe. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-unpack-file.txt b/Documentation/git-unpack-file.txt index 995db9fead..c49d727f74 100644 --- a/Documentation/git-unpack-file.txt +++ b/Documentation/git-unpack-file.txt @@ -22,14 +22,6 @@ OPTIONS <blob>:: Must be a blob id -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index 36d1038056..dd7799095b 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -43,15 +43,6 @@ OPTIONS --strict:: Don't write objects with broken content or links. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation -------------- -Documentation by Junio C Hamano - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 1ca56c85aa..d3931294d1 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -365,15 +365,6 @@ SEE ALSO linkgit:git-config[1], linkgit:git-add[1] - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index 9639f705af..e25a65a80f 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -84,10 +84,6 @@ An update will fail (without changing <ref>) if the current user is unable to create a new log file, append to the existing log file or does not have committer information available. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-update-server-info.txt b/Documentation/git-update-server-info.txt index 035cc3018f..775024da3e 100644 --- a/Documentation/git-update-server-info.txt +++ b/Documentation/git-update-server-info.txt @@ -38,15 +38,6 @@ what they are for: * info/refs - -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt index f5f2b3908b..acbf634f85 100644 --- a/Documentation/git-upload-archive.txt +++ b/Documentation/git-upload-archive.txt @@ -24,14 +24,6 @@ OPTIONS <directory>:: The repository to get a tar archive from. -Author ------- -Written by Franck Bui-Huu. - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt index 71ca4ef442..4c0ca9ded2 100644 --- a/Documentation/git-upload-pack.txt +++ b/Documentation/git-upload-pack.txt @@ -33,14 +33,6 @@ OPTIONS <directory>:: The repository to sync from. -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by Junio C Hamano. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt index 458f3e2755..6498f7cb69 100644 --- a/Documentation/git-var.txt +++ b/Documentation/git-var.txt @@ -65,14 +65,6 @@ linkgit:git-commit-tree[1] linkgit:git-tag[1] linkgit:git-config[1] -Author ------- -Written by Eric Biederman <ebiederm@xmission.com> - -Documentation --------------- -Documentation by Eric Biederman and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt index 916a38aa99..7c2428d569 100644 --- a/Documentation/git-verify-pack.txt +++ b/Documentation/git-verify-pack.txt @@ -8,7 +8,7 @@ git-verify-pack - Validate packed git archive files SYNOPSIS -------- -'git verify-pack' [-v|--verbose] [--] <pack>.idx ... +'git verify-pack' [-v|--verbose] [-s|--stat-only] [--] <pack>.idx ... DESCRIPTION @@ -47,14 +47,6 @@ for objects that are not deltified in the pack, and for objects that are deltified. -Author ------- -Written by Junio C Hamano <gitster@pobox.com> - -Documentation --------------- -Documentation by Junio C Hamano - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index 711219749c..8c9a71865b 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -22,14 +22,6 @@ OPTIONS <tag>...:: SHA1 identifiers of git tag objects. -Author ------- -Written by Jan Harkes <jaharkes@cs.cmu.edu> and Eric W. Biederman <ebiederm@xmission.com> - -Documentation --------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index c0416e5e1a..69d92fa00e 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -116,16 +116,6 @@ $ git config --global web.browser firefox as they are probably more user specific than repository specific. See linkgit:git-config[1] for more information about this. -Author ------- -Written by Christian Couder <chriscool@tuxfamily.org> and the git-list -<git@vger.kernel.org>, based on 'git mergetool' by Theodore Y. Ts'o. - -Documentation -------------- -Documentation by Christian Couder <chriscool@tuxfamily.org> and the -git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt index ea753cdafc..31f3663ae7 100644 --- a/Documentation/git-whatchanged.txt +++ b/Documentation/git-whatchanged.txt @@ -63,17 +63,6 @@ git whatchanged --since="2 weeks ago" \-- gitk:: The "--" is necessary to avoid confusion with the *branch* named 'gitk' - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> and -Junio C Hamano <gitster@pobox.com> - - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt index bfceacacb3..e8c94c1352 100644 --- a/Documentation/git-write-tree.txt +++ b/Documentation/git-write-tree.txt @@ -36,15 +36,6 @@ OPTIONS `<prefix>`. This can be used to write the tree object for a subproject that is in the named subdirectory. - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index e968ed4aa0..65317cce18 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -9,7 +9,7 @@ git - the stupid content tracker SYNOPSIS -------- [verse] -'git' [--version] [--exec-path[=<path>]] [--html-path] +'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=<path>] [--work-tree=<path>] [-c <name>=<value>] @@ -44,9 +44,22 @@ 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.4/git.html[documentation for release 1.7.4] +* link:v1.7.5.3/git.html[documentation for release 1.7.5.3] * release notes for + link:RelNotes/1.7.5.3.txt[1.7.5.3], + link:RelNotes/1.7.5.2.txt[1.7.5.2], + link:RelNotes/1.7.5.1.txt[1.7.5.1], + link:RelNotes/1.7.5.txt[1.7.5]. + +* link:v1.7.4.5/git.html[documentation for release 1.7.4.5] + +* release notes for + link:RelNotes/1.7.4.5.txt[1.7.4.5], + link:RelNotes/1.7.4.4.txt[1.7.4.4], + link:RelNotes/1.7.4.3.txt[1.7.4.3], + link:RelNotes/1.7.4.2.txt[1.7.4.2], + link:RelNotes/1.7.4.1.txt[1.7.4.1], link:RelNotes/1.7.4.txt[1.7.4]. * link:v1.7.3.5/git.html[documentation for release 1.7.3.5] @@ -277,8 +290,16 @@ help ...`. the current setting and then exit. --html-path:: - Print the path to wherever your git HTML documentation is installed - and exit. + Print the path, without trailing slash, where git's HTML + documentation is installed and exit. + +--man-path:: + Print the manpath (see `man(1)`) for the man pages for + this version of git and exit. + +--info-path:: + Print the path where the Info files documenting this + version of git are installed and exit. -p:: --paginate:: @@ -618,7 +639,6 @@ where: contents of <old|new>, <old|new>-hex:: are the 40-hexdigit SHA1 hashes, <old|new>-mode:: are the octal representation of the file modes. - + The file parameters can point at the user's working file (e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file` @@ -744,16 +764,12 @@ unmerged version of a file when a merge is in progress. Authors ------- -* git's founding father is Linus Torvalds <torvalds@osdl.org>. -* The current git nurse is Junio C Hamano <gitster@pobox.com>. -* The git potty was written by Andreas Ericsson <ae@op5.se>. -* General upbringing is handled by the git-list <git@vger.kernel.org>. - -Documentation --------------- -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>. +Git was started by Linus Torvalds, and is currently maintained by Junio +C Hamano. Numerous contributions have come from the git mailing list +<git@vger.kernel.org>. For a more complete list of contributors, see +http://git-scm.com/about. If you have a clone of git.git itself, the +output of linkgit:git-shortlog[1] and linkgit:git-blame[1] can show you +the authors for specific parts of the project. Reporting Bugs -------------- diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 7e7e12168e..412c55b549 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -593,6 +593,37 @@ 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). +Choosing textconv versus external diff +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you want to show differences between binary or specially-formatted +blobs in your repository, you can choose to use either an external diff +command, or to use textconv to convert them to a diff-able text format. +Which method you choose depends on your exact situation. + +The advantage of using an external diff command is flexibility. You are +not bound to find line-oriented changes, nor is it necessary for the +output to resemble unified diff. You are free to locate and report +changes in the most appropriate way for your data format. + +A textconv, by comparison, is much more limiting. You provide a +transformation of the data into a line-oriented text format, and git +uses its regular diff tools to generate the output. There are several +advantages to choosing this method: + +1. Ease of use. It is often much simpler to write a binary to text + transformation than it is to perform your own diff. In many cases, + existing programs can be used as textconv filters (e.g., exif, + odt2txt). + +2. Git diff features. By performing only the transformation step + yourself, you can still utilize many of git's diff features, + including colorization, word-diff, and combined diffs for merges. + +3. Caching. Textconv caching can speed up repeated diffs, such as those + you might trigger by running `git log -p`. + + Marking files as binary ^^^^^^^^^^^^^^^^^^^^^^^ @@ -632,7 +663,7 @@ Performing a three-way merge `merge` ^^^^^^^ -The attribute `merge` affects how three versions of a file is +The attribute `merge` affects how three versions of a file are merged when a file-level merge is necessary during `git merge`, and other commands such as `git revert` and `git cherry-pick`. @@ -646,15 +677,15 @@ Unset:: Take the version from the current branch as the tentative merge result, and declare that the merge has - conflicts. This is suitable for binary files that does + conflicts. This is suitable for binary files that do not have a well-defined merge semantics. Unspecified:: By default, this uses the same built-in 3-way merge - driver as is the case the `merge` attribute is set. - However, `merge.default` configuration variable can name - different merge driver to be used for paths to which the + driver as is the case when the `merge` attribute is set. + However, the `merge.default` configuration variable can name + different merge driver to be used with paths for which the `merge` attribute is unspecified. String:: diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index 6928724a05..f734f97b8e 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -169,10 +169,6 @@ See also http://marc.info/?l=git&m=116563135620359 and http://marc.info/?l=git&m=119150393620273 for further information. -Documentation -------------- -Documentation by Pierre Habouzit and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 8416f3445a..2e7328b830 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -156,11 +156,6 @@ SEE ALSO linkgit:git-rm[1], linkgit:git-update-index[1], linkgit:gitrepository-layout[5] -Documentation -------------- -Documentation by David Greaves, Junio C Hamano, Josh Triplett, -Frank Lichtenheld, and the git-list <git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index e21bac4f3f..e10ac58cae 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -113,15 +113,6 @@ SEE ALSO A minimal repository browser and git tool output highlighter written in C using Ncurses. -Author ------- -Written by Paul Mackerras <paulus@samba.org>. - -Documentation --------------- -Documentation by Junio C Hamano, Jonas Fonseca, and the git-list -<git@vger.kernel.org>. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 68977943e7..4040941e55 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -45,12 +45,12 @@ submodule.<name>.update:: the '--merge' or '--rebase' options. submodule.<name>.fetchRecurseSubmodules:: - This option can be used to enable/disable recursive fetching of this + This option can be used to control recursive fetching of this submodule. If this option is also present in the submodules entry in .git/config of the superproject, the setting there will override the one found in .gitmodules. Both settings can be overridden on the command line by using the - "--[no-]recurse-submodules" option to "git fetch" and "git pull".. + "--[no-]recurse-submodules" option to "git fetch" and "git pull". submodule.<name>.ignore:: Defines under what circumstances "git status" and the diff family show @@ -90,10 +90,6 @@ SEE ALSO -------- linkgit:git-submodule[1] linkgit:git-config[1] -DOCUMENTATION -------------- -Documentation by Lars Hjemli <hjemli@gmail.com> - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index f04b48ef0d..8f62d1abee 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -273,6 +273,61 @@ This commit is referred to as a "merge commit", or sometimes just a <<def_pack,pack>>, to assist in efficiently accessing the contents of a pack. +[[def_pathspec]]pathspec:: + Pattern used to specify paths. ++ +Pathspecs are used on the command line of "git ls-files", "git +ls-tree", "git add", "git grep", "git diff", "git checkout", +and many other commands to +limit the scope of operations to some subset of the tree or +worktree. See the documentation of each command for whether +paths are relative to the current directory or toplevel. The +pathspec syntax is as follows: + +* any path matches itself +* the pathspec up to the last slash represents a + directory prefix. The scope of that pathspec is + limited to that subtree. +* the rest of the pathspec is a pattern for the remainder + of the pathname. Paths relative to the directory + prefix will be matched against that pattern using fnmatch(3); + in particular, '*' and '?' _can_ match directory separators. ++ +For example, Documentation/*.jpg will match all .jpg files +in the Documentation subtree, +including Documentation/chapter_1/figure_1.jpg. + ++ +A pathspec that begins with a colon `:` has special meaning. In the +short form, the leading colon `:` is followed by zero or more "magic +signature" letters (which optionally is terminated by another colon `:`), +and the remainder is the pattern to match against the path. The optional +colon that terminates the "magic signature" can be omitted if the pattern +begins with a character that cannot be a "magic signature" and is not a +colon. ++ +In the long form, the leading colon `:` is followed by a open +parenthesis `(`, a comma-separated list of zero or more "magic words", +and a close parentheses `)`, and the remainder is the pattern to match +against the path. ++ +The "magic signature" consists of an ASCII symbol that is not +alphanumeric. ++ +-- +top `/`;; + The magic word `top` (mnemonic: `/`) makes the pattern match + from the root of the working tree, even when you are running + the command from inside a subdirectory. +-- ++ +Currently only the slash `/` is recognized as the "magic signature", +but it is envisioned that we will support more types of magic in later +versions of git. ++ +A pathspec with only a colon means "there is no pathspec". This form +should not be combined with other pathspec. + [[def_parent]]parent:: A <<def_commit_object,commit object>> contains a (possibly empty) list of the logical predecessor(s) in the line of development, i.e. its diff --git a/Documentation/howto/using-merge-subtree.txt b/Documentation/howto/using-merge-subtree.txt index 0953a50b69..2933056120 100644 --- a/Documentation/howto/using-merge-subtree.txt +++ b/Documentation/howto/using-merge-subtree.txt @@ -71,5 +71,5 @@ Additional tips relevant parts of your tree. - Please note that if the other project merges from you, then it will - connects its history to yours, which can be something they don't want + connect its history to yours, which can be something they don't want to. diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index 1e5c22c5e5..861bd6f553 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -6,6 +6,26 @@ merge.conflictstyle:: a `>>>>>>>` marker. An alternate style, "diff3", adds a `|||||||` marker and the original text before the `=======` marker. +merge.defaultToUpstream:: + If merge is called without any commit argument, merge the upstream + branches configured for the current branch by using their last + observed values stored in their remote tracking branches. + The values of the `branch.<current branch>.merge` that name the + branches at the remote named by `branch.<current branch>.remote` + are consulted, and then they are mapped via `remote.<remote>.fetch` + to their corresponding remote tracking branches, and the tips of + these tracking branches are merged. + +merge.ff:: + By default, git does not create an extra merge commit when merging + a commit that is a descendant of the current commit. Instead, the + tip of the current branch is fast-forwarded. When set to `false`, + this variable tells git to create an extra merge commit in such + a case (equivalent to giving the `--no-ff` option from the command + line). When set to `only`, only such fast-forward merges are + allowed (equivalent to giving the `--ff-only` option from the + command line). + merge.log:: In addition to branch names, populate the log message with at most the specified number of one-line descriptions from the @@ -33,10 +53,10 @@ merge.stat:: merge.tool:: Controls which merge resolution program is used by - linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3", - "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", - "diffuse", "ecmerge", "tortoisemerge", "p4merge", "araxis" and - "opendiff". Any other value is treated is custom merge tool + linkgit:git-mergetool[1]. Valid built-in values are: "araxis", + "bc3", "diffuse", "ecmerge", "emerge", "gvimdiff", "kdiff3", "meld", + "opendiff", "p4merge", "tkdiff", "tortoisemerge", "vimdiff" + and "xxdiff". Any other value is treated is custom merge tool and there must be a corresponding mergetool.<tool>.cmd option. merge.verbosity:: diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index e33e0f8e11..b613d4ed08 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -75,9 +75,17 @@ option can be used to override --squash. ifndef::git-pull[] -q:: --quiet:: - Operate quietly. + Operate quietly. Implies --no-progress. -v:: --verbose:: Be verbose. + +--progress:: +--no-progress:: + Turn progress on/off explicitly. If neither is specified, + progress is shown if standard error is connected to a terminal. + Note that not all merge strategies may support progress + reporting. + endif::git-pull[] diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 50923e2ce9..2a3dc8664f 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -19,6 +19,11 @@ configuration (see linkgit:git-config[1]). This should make "--pretty=oneline" a whole lot more readable for people using 80-column terminals. +--no-abbrev-commit:: + Show the full 40-byte hexadecimal commit object name. This negates + `--abbrev-commit` and those options which imply it such as + "--oneline". It also overrides the 'log.abbrevCommit' variable. + --oneline:: This is a shorthand for "--pretty=oneline --abbrev-commit" used together. @@ -30,19 +35,34 @@ people using 80-column terminals. preferred by the user. For non plumbing commands this defaults to UTF-8. ---no-notes:: ---show-notes[=<ref>]:: +--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. + there is no `--pretty`, `--format` nor `--oneline` option given + on the command line. ++ +By default, the notes shown are from the notes refs listed in the +'core.notesRef' and 'notes.displayRef' variables (or corresponding +environment overrides). See linkgit:git-config[1] for more details. + -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. +With an optional '<ref>' argument, show this notes ref instead of the +default notes ref(s). The ref is taken to be in `refs/notes/` if it +is not qualified. ++ +Multiple --notes options can be combined to control which notes are +being displayed. Examples: "--notes=foo" will show only notes from +"refs/notes/foo"; "--notes=foo --notes" will show both notes from +"refs/notes/foo" and from the default notes ref(s). + +--no-notes:: + Do not show notes. This negates the above `--notes` option, by + resetting the list of notes refs from which notes are shown. + Options are parsed in the order given on the command line, so e.g. + "--notes --notes=foo --no-notes --notes=bar" will only show notes + from "refs/notes/bar". +--show-notes[=<ref>]:: --[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]. + These options are deprecated. Use the above --notes/--no-notes + options instead. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 44a2ef1de1..7e7ba68781 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -1,171 +1,17 @@ -Commit Formatting -~~~~~~~~~~~~~~~~~ - -ifdef::git-rev-list[] -Using these options, linkgit:git-rev-list[1] will act similar to the -more specialized family of commit log tools: linkgit:git-log[1], -linkgit:git-show[1], and linkgit:git-whatchanged[1] -endif::git-rev-list[] - -include::pretty-options.txt[] - ---relative-date:: - - Synonym for `--date=relative`. - ---date=(relative|local|default|iso|rfc|short|raw):: - - Only takes effect for dates shown in human-readable format, such - as when using "--pretty". `log.date` config variable sets a default - value for log command's --date option. -+ -`--date=relative` shows dates relative to the current time, -e.g. "2 hours ago". -+ -`--date=local` shows timestamps in user's local timezone. -+ -`--date=iso` (or `--date=iso8601`) shows timestamps in ISO 8601 format. -+ -`--date=rfc` (or `--date=rfc2822`) shows timestamps in RFC 2822 -format, often found in E-mail messages. -+ -`--date=short` shows only date but not time, in `YYYY-MM-DD` format. -+ -`--date=raw` shows the date in the internal raw git format `%s %z` format. -+ -`--date=default` shows timestamps in the original timezone -(either committer's or author's). - -ifdef::git-rev-list[] ---header:: - - Print the contents of the commit in raw-format; each record is - separated with a NUL character. -endif::git-rev-list[] - ---parents:: - - Print also the parents of the commit (in the form "commit parent..."). - Also enables parent rewriting, see 'History Simplification' below. - ---children:: - - Print also the children of the commit (in the form "commit child..."). - Also enables parent rewriting, see 'History Simplification' below. - -ifdef::git-rev-list[] ---timestamp:: - Print the raw commit timestamp. -endif::git-rev-list[] - ---left-right:: - - Mark which side of a symmetric diff a commit is reachable from. - Commits from the left side are prefixed with `<` and those from - the right with `>`. If combined with `--boundary`, those - commits are prefixed with `-`. -+ -For example, if you have this topology: -+ ------------------------------------------------------------------------ - y---b---b branch B - / \ / - / . - / / \ - o---x---a---a branch A ------------------------------------------------------------------------ -+ -you would get an output like this: -+ ------------------------------------------------------------------------ - $ git rev-list --left-right --boundary --pretty=oneline A...B - - >bbbbbbb... 3rd on b - >bbbbbbb... 2nd on b - <aaaaaaa... 3rd on a - <aaaaaaa... 2nd on a - -yyyyyyy... 1st on b - -xxxxxxx... 1st on a ------------------------------------------------------------------------ - ---graph:: - - Draw a text-based graphical representation of the commit history - on the left hand side of the output. This may cause extra lines - to be printed in between commits, in order for the graph history - to be drawn properly. -+ -This enables parent rewriting, see 'History Simplification' below. -+ -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 -~~~~~~~~~~~~~~~ - -Below are listed options that control the formatting of diff output. -Some of them are specific to linkgit:git-rev-list[1], however other diff -options may be given. See linkgit:git-diff-files[1] for more options. - --c:: - - 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. - ---cc:: - - This flag implies the '-c' options and further compresses the - patch output by omitting uninteresting hunks whose contents in - 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. - --t:: - - Show the tree objects in the diff output. This implies '-r'. - --s:: - Suppress diff output. -endif::git-rev-list[] - Commit Limiting ~~~~~~~~~~~~~~~ Besides specifying a range of commits that should be listed using the special notations explained in the description, additional commit -limiting may be applied. +limiting may be applied. Note that they are applied before commit +ordering and formatting options, such as '--reverse'. -- -n 'number':: --max-count=<number>:: - Limit the number of commits output. + Limit the number of commits to output. --skip=<number>:: @@ -226,11 +72,26 @@ endif::git-rev-list[] --merges:: - Print only merge commits. + Print only merge commits. This is exactly the same as `--min-parents=2`. --no-merges:: - Do not print commits with more than one parent. + Do not print commits with more than one parent. This is + exactly the same as `--max-parents=1`. + +--min-parents=<number>:: +--max-parents=<number>:: +--no-min-parents:: +--no-max-parents:: + + Show only commits which have at least (or at most) that many + commits. In particular, `--max-parents=1` is the same as `--no-merges`, + `--min-parents=2` is the same as `--merges`. `--max-parents=0` + gives all root commits and `--min-parents=3` all octopus merges. ++ +`--no-min-parents` and `--no-max-parents` reset these limits (to no limit) +again. Equivalent forms are `--min-parents=0` (any commit has 0 or more +parents) and `--max-parents=-1` (negative numbers denote no upper limit). --first-parent:: Follow only the first parent commit upon seeing a merge @@ -278,6 +139,10 @@ endif::git-rev-list[] is automatically prepended if missing. If pattern lacks '?', '*', or '[', '/*' at the end is implied. +--ignore-missing:: + + Upon seeing an invalid object name in the input, pretend as if + the bad input was not given. ifndef::git-rev-list[] --bisect:: @@ -305,6 +170,11 @@ ifdef::git-rev-list[] to /dev/null as the output does not have to be formatted. endif::git-rev-list[] +--cherry-mark:: + + Like `--cherry-pick` (see below) but mark equivalent commits + with `=` rather than omitting them, and inequivalent ones with `+`. + --cherry-pick:: Omit any commit that introduces the same change as @@ -313,12 +183,33 @@ endif::git-rev-list[] + For example, if you have two branches, `A` and `B`, a usual way to list all commits on only one side of them is with -`--left-right`, like the example above in the description of -that option. It however shows the commits that were cherry-picked +`--left-right` (see the example below in the description of +the `--left-right` option). It however shows the commits that were cherry-picked from the other branch (for example, "3rd on b" may be cherry-picked from branch A). With this option, such pairs of commits are excluded from the output. +--left-only:: +--right-only:: + + List only commits on the respective side of a symmetric range, + i.e. only those which would be marked `<` resp. `>` by + `--left-right`. ++ +For example, `--cherry-pick --right-only A...B` omits those +commits from `B` which are in `A` or are patch-equivalent to a commit in +`A`. In other words, this lists the `{plus}` commits from `git cherry A B`. +More precisely, `--cherry-pick --right-only --no-merges` gives the exact +list. + +--cherry:: + + A synonym for `--right-only --cherry-mark --no-merges`; useful to + limit the output to the commits on our side and mark those that + have been applied to the other side of a forked history with + `git log --cherry upstream...mybranch`, similar to + `git cherry upstream mybranch`. + -g:: --walk-reflogs:: @@ -735,3 +626,161 @@ These options are mostly targeted for packing of git repositories. --do-walk:: Overrides a previous --no-walk. + +Commit Formatting +~~~~~~~~~~~~~~~~~ + +ifdef::git-rev-list[] +Using these options, linkgit:git-rev-list[1] will act similar to the +more specialized family of commit log tools: linkgit:git-log[1], +linkgit:git-show[1], and linkgit:git-whatchanged[1] +endif::git-rev-list[] + +include::pretty-options.txt[] + +--relative-date:: + + Synonym for `--date=relative`. + +--date=(relative|local|default|iso|rfc|short|raw):: + + Only takes effect for dates shown in human-readable format, such + as when using "--pretty". `log.date` config variable sets a default + value for log command's --date option. ++ +`--date=relative` shows dates relative to the current time, +e.g. "2 hours ago". ++ +`--date=local` shows timestamps in user's local timezone. ++ +`--date=iso` (or `--date=iso8601`) shows timestamps in ISO 8601 format. ++ +`--date=rfc` (or `--date=rfc2822`) shows timestamps in RFC 2822 +format, often found in E-mail messages. ++ +`--date=short` shows only date but not time, in `YYYY-MM-DD` format. ++ +`--date=raw` shows the date in the internal raw git format `%s %z` format. ++ +`--date=default` shows timestamps in the original timezone +(either committer's or author's). + +ifdef::git-rev-list[] +--header:: + + Print the contents of the commit in raw-format; each record is + separated with a NUL character. +endif::git-rev-list[] + +--parents:: + + Print also the parents of the commit (in the form "commit parent..."). + Also enables parent rewriting, see 'History Simplification' below. + +--children:: + + Print also the children of the commit (in the form "commit child..."). + Also enables parent rewriting, see 'History Simplification' below. + +ifdef::git-rev-list[] +--timestamp:: + Print the raw commit timestamp. +endif::git-rev-list[] + +--left-right:: + + Mark which side of a symmetric diff a commit is reachable from. + Commits from the left side are prefixed with `<` and those from + the right with `>`. If combined with `--boundary`, those + commits are prefixed with `-`. ++ +For example, if you have this topology: ++ +----------------------------------------------------------------------- + y---b---b branch B + / \ / + / . + / / \ + o---x---a---a branch A +----------------------------------------------------------------------- ++ +you would get an output like this: ++ +----------------------------------------------------------------------- + $ git rev-list --left-right --boundary --pretty=oneline A...B + + >bbbbbbb... 3rd on b + >bbbbbbb... 2nd on b + <aaaaaaa... 3rd on a + <aaaaaaa... 2nd on a + -yyyyyyy... 1st on b + -xxxxxxx... 1st on a +----------------------------------------------------------------------- + +--graph:: + + Draw a text-based graphical representation of the commit history + on the left hand side of the output. This may cause extra lines + to be printed in between commits, in order for the graph history + to be drawn properly. ++ +This enables parent rewriting, see 'History Simplification' below. ++ +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. When used together with + '--cherry-mark', omit patch equivalent commits from these + counts and print the count for equivalent commits separated + by a tab. +endif::git-rev-list[] + + +ifndef::git-rev-list[] +Diff Formatting +~~~~~~~~~~~~~~~ + +Below are listed options that control the formatting of diff output. +Some of them are specific to linkgit:git-rev-list[1], however other diff +options may be given. See linkgit:git-diff-files[1] for more options. + +-c:: + + 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. + +--cc:: + + This flag implies the '-c' options and further compresses the + patch output by omitting uninteresting hunks whose contents in + 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. + +-t:: + + Show the tree objects in the diff output. This implies '-r'. + +-s:: + Suppress diff output. +endif::git-rev-list[] diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index 9e92734bc1..b290b617d4 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -1,144 +1,163 @@ SPECIFYING REVISIONS -------------------- -A revision parameter typically, but not necessarily, names a -commit object. They use what is called an 'extended SHA1' +A revision parameter '<rev>' typically, but not necessarily, names a +commit object. It uses 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 +ones listed near the end of this list 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. +'<sha1>', e.g. 'dae86e1950b1277e545cee180551750029cfe735', 'dae86e':: + The full SHA1 object name (40-byte hexadecimal string), or + a leading substring that is unique within the repository. E.g. dae86e1950b1277e545cee180551750029cfe735 and dae86e both - name the same commit object if there are no other object in + name the same commit object if there is no other object in your repository whose object name starts with dae86e. -* An output from 'git describe'; i.e. a closest tag, optionally +'<describeOutput>', e.g. 'v1.7.4.2-679-g3bee7fb':: + 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. + '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 +'<refname>', e.g. 'master', 'heads/master', 'refs/heads/master':: + 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 + 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`); + . If '$GIT_DIR/<name>' exists, that is what you mean (this is usually + useful only for 'HEAD', 'FETCH_HEAD', 'ORIG_HEAD', 'MERGE_HEAD' + and 'CHERRY_PICK_HEAD'); - . otherwise, `refs/<name>` if exists; + . otherwise, 'refs/<name>' if it exists; - . otherwise, `refs/tags/<name>` if exists; + . otherwise, 'refs/tags/<refname>' if it exists; - . otherwise, `refs/heads/<name>` if exists; + . otherwise, 'refs/heads/<name>' if it exists; - . otherwise, `refs/remotes/<name>` if exists; + . otherwise, 'refs/remotes/<name>' if it exists; - . otherwise, `refs/remotes/<name>/HEAD` if exists. + . otherwise, 'refs/remotes/<name>/HEAD' if it 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'. +'HEAD' names the commit on which you based the changes in the working tree. +'FETCH_HEAD' records the branch which you fetched from a remote repository +with your last `git fetch` invocation. +'ORIG_HEAD' is created by commands that move your 'HEAD' in a drastic +way, to record the position of the 'HEAD' before their operation, so that +you can easily change the tip of the branch back to the state before you ran +them. +'MERGE_HEAD' records the commit(s) which you are merging into your branch +when you run `git merge`. +'CHERRY_PICK_HEAD' records the commit which you are cherry-picking +when you run `git cherry-pick`. + -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. +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 +'<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}':: + 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 + second ago\}' or '\{1979-02-26 18:30:00\}') specifies 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 + 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`. + '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 +'<refname>@\{<n>\}', e.g. 'master@\{1\}':: + A ref followed by the suffix '@' with an ordinal specification + enclosed in a brace pair (e.g. '\{1\}', '\{15\}') specifies 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>). + log ('$GIT_DIR/logs/<refname>'). -* 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\}'. +'@\{<n>\}', e.g. '@\{1\}':: + You can use the '@' construct with an empty ref part to get at a + reflog entry of the current branch. For example, if you are on + branch 'blabla' then '@\{1\}' means the same as 'blabla@\{1\}'. -* The special construct '@\{-<n>\}' means the <n>th branch checked out +'@\{-<n>\}', e.g. '@\{-1\}':: + The 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 +'<refname>@\{upstream\}', e.g. 'master@\{upstream\}', '@\{u\}':: + The suffix '@\{upstream\}' to a ref (short form '<refname>@\{u\}') refers to + the branch the ref is set to build on top of. A missing ref defaults to the current branch. -* A suffix '{caret}' to a revision parameter (e.g. 'HEAD{caret}') means the first parent of +'<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0':: + 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 + '<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 +'<rev>{tilde}<n>', e.g. 'master{tilde}3':: + 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 + commit object, following only the first parents. I.e. '<rev>{tilde}3' is + equivalent to '<rev>{caret}{caret}{caret}' which is equivalent to + '<rev>{caret}1{caret}1{caret}1'. See below for an 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 +'<rev>{caret}\{<type>\}', e.g. 'v0.99.8{caret}\{commit\}':: + A suffix '{caret}' followed by an object type name enclosed in + brace pair 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\}`. + dereferenced anymore (in which case, barf). '<rev>{caret}0' + 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, +'<rev>{caret}\{\}', e.g. 'v0.99.8{caret}\{\}':: + A suffix '{caret}' followed by an empty brace pair + means the object could be a tag, and dereference the tag recursively until a non-tag object is found. -* A suffix '{caret}' to a revision parameter followed by a brace - pair that contains a text led by a slash (e.g. `HEAD^{/fix nasty bug}`): - this is the same as `:/fix nasty bug` syntax below except that +'<rev>{caret}\{/<text>\}', e.g. 'HEAD^{/fix nasty bug}':: + A suffix '{caret}' to a revision parameter, followed by a brace + pair that contains a text led by a slash, + is the same as the ':/fix nasty bug' syntax below except that it returns the youngest matching commit which is reachable from - the ref before '{caret}'. + the '<rev>' before '{caret}'. -* A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names +':/<text>', e.g. ':/fix nasty bug':: + A colon, followed by a slash, followed by a text, names a commit whose commit message matches the specified regular expression. 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. + '!' you have to repeat that; the special sequence ':/!', + followed by something else than '!', is reserved for now. The regular expression can match any part of the commit message. To - match messages starting with a string, one can use e.g. `:/^foo`. + match messages starting with a string, one can use e.g. ':/^foo'. -* A suffix ':' followed by a path (e.g. `HEAD:README`); this names the blob or tree +'<rev>:<path>', e.g. 'HEAD:README', ':README', 'master:./README':: + A suffix ':' followed by a path 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`) + ':path' (with an empty part before the colon) is a special case of the syntax described next: content recorded in the index at the given path. - A path starting with './' or '../' is relative to current working directory. - The given path will be converted to be relative to working tree's root directory. + A path starting with './' or '../' is relative to the current working directory. + The given path will be converted to be relative to the working tree's root directory. This is most useful to address a blob or tree from a commit or tree that has - the same tree structure with the working tree. + the same tree structure as the working tree. -* 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 +':<n>:<path>', e.g. ':0:README', ':README':: + A colon, optionally followed by a stage number (0 to 3) and a + colon, followed by a path, names a blob object in the + index at the given path. A 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. + the branch which is 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 @@ -172,31 +191,31 @@ G H I J SPECIFYING RANGES ----------------- -History traversing commands such as 'git log' operate on a set +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`. +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 +for it. When you have two commits 'r1' and 'r2' (named according to the syntax explained in SPECIFYING REVISIONS above), you can ask for commits that are reachable from r2 excluding those that are reachable -from r1 by `{caret}r1 r2` and it can be written as `r1..r2`. +from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'. -A similar notation `r1\...r2` is called symmetric difference -of `r1` and `r2` and is defined as -`r1 r2 --not $(git merge-base --all 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. +'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 +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: diff --git a/Documentation/technical/api-diff.txt b/Documentation/technical/api-diff.txt index 20b0241d30..2d2ebc04b7 100644 --- a/Documentation/technical/api-diff.txt +++ b/Documentation/technical/api-diff.txt @@ -32,7 +32,7 @@ Calling sequence * As you find different pairs of files, call `diff_change()` to feed modified files, `diff_addremove()` to feed created or deleted files, - or `diff_unmerged()` to feed a file whose state is 'unmerged' to the + or `diff_unmerge()` to feed a file whose state is 'unmerged' to the API. These are thin wrappers to a lower-level `diff_queue()` function that is flexible enough to record any of these kinds of changes. @@ -50,7 +50,7 @@ Data structures This is the internal representation for a single file (blob). It records the blob object name (if known -- for a work tree file it typically is a NUL SHA-1), filemode and pathname. This is what the -`diff_addremove()`, `diff_change()` and `diff_unmerged()` synthesize and +`diff_addremove()`, `diff_change()` and `diff_unmerge()` synthesize and feed `diff_queue()` function with. * `struct diff_filepair` diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt new file mode 100644 index 0000000000..7b233ca196 --- /dev/null +++ b/Documentation/technical/index-format.txt @@ -0,0 +1,185 @@ +GIT index format +================ + += The git index file has the following format + + All binary numbers are in network byte order. Version 2 is described + here unless stated otherwise. + + - A 12-byte header consisting of + + 4-byte signature: + The signature is { 'D', 'I', 'R', 'C' } (stands for "dircache") + + 4-byte version number: + The current supported versions are 2 and 3. + + 32-bit number of index entries. + + - A number of sorted index entries (see below). + + - Extensions + + Extensions are identified by signature. Optional extensions can + be ignored if GIT does not understand them. + + GIT currently supports cached tree and resolve undo extensions. + + 4-byte extension signature. If the first byte is 'A'..'Z' the + extension is optional and can be ignored. + + 32-bit size of the extension + + Extension data + + - 160-bit SHA-1 over the content of the index file before this + checksum. + +== Index entry + + Index entries are sorted in ascending order on the name field, + interpreted as a string of unsigned bytes (i.e. memcmp() order, no + localization, no special casing of directory separator '/'). Entries + with the same name are sorted by their stage field. + + 32-bit ctime seconds, the last time a file's metadata changed + this is stat(2) data + + 32-bit ctime nanosecond fractions + this is stat(2) data + + 32-bit mtime seconds, the last time a file's data changed + this is stat(2) data + + 32-bit mtime nanosecond fractions + this is stat(2) data + + 32-bit dev + this is stat(2) data + + 32-bit ino + this is stat(2) data + + 32-bit mode, split into (high to low bits) + + 4-bit object type + valid values in binary are 1000 (regular file), 1010 (symbolic link) + and 1110 (gitlink) + + 3-bit unused + + 9-bit unix permission. Only 0755 and 0644 are valid for regular files. + Symbolic links and gitlinks have value 0 in this field. + + 32-bit uid + this is stat(2) data + + 32-bit gid + this is stat(2) data + + 32-bit file size + This is the on-disk size from stat(2), truncated to 32-bit. + + 160-bit SHA-1 for the represented object + + A 16-bit 'flags' field split into (high to low bits) + + 1-bit assume-valid flag + + 1-bit extended flag (must be zero in version 2) + + 2-bit stage (during merge) + + 12-bit name length if the length is less than 0xFFF; otherwise 0xFFF + is stored in this field. + + (Version 3) A 16-bit field, only applicable if the "extended flag" + above is 1, split into (high to low bits). + + 1-bit reserved for future + + 1-bit skip-worktree flag (used by sparse checkout) + + 1-bit intent-to-add flag (used by "git add -N") + + 13-bit unused, must be zero + + Entry path name (variable length) relative to top level directory + (without leading slash). '/' is used as path separator. The special + path components ".", ".." and ".git" (without quotes) are disallowed. + Trailing slash is also disallowed. + + The exact encoding is undefined, but the '.' and '/' characters + are encoded in 7-bit ASCII and the encoding cannot contain a NUL + byte (iow, this is a UNIX pathname). + + 1-8 nul bytes as necessary to pad the entry to a multiple of eight bytes + while keeping the name NUL-terminated. + +== Extensions + +=== Cached tree + + Cached tree extension contains pre-computed hashes for trees that can + be derived from the index. It helps speed up tree object generation + from index for a new commit. + + When a path is updated in index, the path must be invalidated and + removed from tree cache. + + The signature for this extension is { 'T', 'R', 'E', 'E' }. + + A series of entries fill the entire extension; each of which + consists of: + + - NUL-terminated path component (relative to its parent directory); + + - ASCII decimal number of entries in the index that is covered by the + tree this entry represents (entry_count); + + - A space (ASCII 32); + + - ASCII decimal number that represents the number of subtrees this + tree has; + + - A newline (ASCII 10); and + + - 160-bit object name for the object that would result from writing + this span of index as a tree. + + An entry can be in an invalidated state and is represented by having -1 + in the entry_count field. + + The entries are written out in the top-down, depth-first order. The + first entry represents the root level of the repository, followed by the + first subtree---let's call this A---of the root level (with its name + relative to the root level), followed by the first subtree of A (with + its name relative to A), ... + +=== Resolve undo + + A conflict is represented in the index as a set of higher stage entries. + When a conflict is resolved (e.g. with "git add path"), these higher + stage entries will be removed and a stage-0 entry with proper resoluton + is added. + + When these higher stage entries are removed, they are saved in the + resolve undo extension, so that conflicts can be recreated (e.g. with + "git checkout -m"), in case users want to redo a conflict resolution + from scratch. + + The signature for this extension is { 'R', 'E', 'U', 'C' }. + + A series of entries fill the entire extension; each of which + consists of: + + - NUL-terminated pathname the entry describes (relative to the root of + the repository, i.e. full pathname); + + - Three NUL-terminated ASCII octal numbers, entry mode of entries in + stage 1 to 3 (a missing stage is represented by "0" in this field); + and + + - At most three 160-bit object names of the entry in stages from 1 to 3 + (nothing is written for a missing stage). + diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 92fe7a59db..7f48227494 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.4 +DEF_VER=v1.7.5.GIT LF=' ' diff --git a/LGPL-2.1 b/LGPL-2.1 new file mode 100644 index 0000000000..d38b1b92bd --- /dev/null +++ b/LGPL-2.1 @@ -0,0 +1,511 @@ + + While most of this project is under the GPL (see COPYING), the xdiff/ + library and some libc code from compat/ are licensed under the + GNU LGPL, version 2.1 or (at your option) any later version and some + other files are under other licenses. Check the individual files to + be sure. + +---------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! @@ -24,6 +24,12 @@ all:: # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies BLK_SHA1. # +# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be +# able to use Perl-compatible regular expressions. +# +# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +# # Define NO_CURL if you do not have libcurl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. @@ -45,11 +51,6 @@ all:: # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks # 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, -# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). -# some C compilers supported these specifiers prior to C99 as an extension. -# # Define NO_STRCASESTR if you don't have strcasestr. # # Define NO_MEMMEM if you don't have memmem. @@ -75,6 +76,9 @@ all:: # Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the # FNM_CASEFOLD GNU extension. # +# Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd +# in the C library. +# # Define NO_LIBGEN_H if you don't have libgen.h. # # Define NEEDS_LIBGEN if your libgen needs -lgen when linking @@ -216,6 +220,11 @@ all:: # # Define NO_REGEX if you have no or inferior regex support in your C library. # +# Define GETTEXT_POISON if you are debugging the choice of strings marked +# for translation. In a GETTEXT_POISON build, you can turn all strings marked +# for translation into gibberish by setting the GIT_GETTEXT_POISON variable +# (to any value) in your environment. +# # Define JSMIN to point to JavaScript minifier that functions as # a filter to have gitweb.js minified. # @@ -274,8 +283,7 @@ STRIP ?= strip # mandir # infodir # htmldir -# ETC_GITCONFIG (but not sysconfdir) -# ETC_GITATTRIBUTES +# sysconfdir # can be specified as a relative path some/where/else; # this is interpreted as relative to $(prefix) and "git" at # runtime figures out where they are based on the path to the executable. @@ -291,15 +299,8 @@ sharedir = $(prefix)/share gitwebdir = $(sharedir)/gitweb template_dir = share/git-core/templates htmldir = share/doc/git-doc -ifeq ($(prefix),/usr) -sysconfdir = /etc ETC_GITCONFIG = $(sysconfdir)/gitconfig ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes -else -sysconfdir = $(prefix)/etc -ETC_GITCONFIG = etc/gitconfig -ETC_GITATTRIBUTES = etc/gitattributes -endif lib = lib # DESTDIR= pathsep = : @@ -316,15 +317,14 @@ INSTALL = install RPMBUILD = rpmbuild TCL_PATH = tclsh TCLTK_PATH = wish +XGETTEXT = xgettext PTHREAD_LIBS = -lpthread PTHREAD_CFLAGS = GCOV = gcov export TCL_PATH TCLTK_PATH -# sparse is architecture-neutral, which means that we need to tell it -# explicitly what architecture to check for. Fix this up for yours.. -SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ +SPARSE_FLAGS = @@ -369,7 +369,6 @@ SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.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 @@ -379,7 +378,11 @@ SCRIPT_SH += git-web--browse.sh SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote +SCRIPT_LIB += git-rebase--am +SCRIPT_LIB += git-rebase--interactive +SCRIPT_LIB += git-rebase--merge SCRIPT_LIB += git-sh-setup +SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl SCRIPT_PERL += git-difftool.perl @@ -413,6 +416,7 @@ PROGRAM_OBJS += shell.o PROGRAM_OBJS += show-index.o PROGRAM_OBJS += upload-pack.o PROGRAM_OBJS += http-backend.o +PROGRAM_OBJS += sh-i18n--envsubst.o PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS)) @@ -422,8 +426,10 @@ 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-index-version TEST_PROGRAMS_NEED_X += test-line-buffer TEST_PROGRAMS_NEED_X += test-match-trees +TEST_PROGRAMS_NEED_X += test-mktemp TEST_PROGRAMS_NEED_X += test-obj-pool TEST_PROGRAMS_NEED_X += test-parse-options TEST_PROGRAMS_NEED_X += test-path-utils @@ -434,7 +440,6 @@ TEST_PROGRAMS_NEED_X += test-string-pool TEST_PROGRAMS_NEED_X += test-subprocess TEST_PROGRAMS_NEED_X += test-svn-fe TEST_PROGRAMS_NEED_X += test-treap -TEST_PROGRAMS_NEED_X += test-index-version TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) @@ -514,6 +519,7 @@ LIB_H += diff.h LIB_H += dir.h LIB_H += exec_cmd.h LIB_H += fsck.h +LIB_H += gettext.h LIB_H += git-compat-util.h LIB_H += graph.h LIB_H += grep.h @@ -524,6 +530,7 @@ LIB_H += list-objects.h LIB_H += ll-merge.h LIB_H += log-tree.h LIB_H += mailmap.h +LIB_H += merge-file.h LIB_H += merge-recursive.h LIB_H += notes.h LIB_H += notes-cache.h @@ -544,6 +551,7 @@ LIB_H += rerere.h LIB_H += resolve-undo.h LIB_H += revision.h LIB_H += run-command.h +LIB_H += sha1-array.h LIB_H += sha1-lookup.h LIB_H += sideband.h LIB_H += sigchain.h @@ -646,6 +654,7 @@ LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o LIB_OBJS += setup.o +LIB_OBJS += sha1-array.o LIB_OBJS += sha1-lookup.o LIB_OBJS += sha1_file.o LIB_OBJS += sha1_name.o @@ -870,7 +879,6 @@ ifeq ($(uname_S),SunOS) NO_UNSETENV = YesPlease NO_SETENV = YesPlease NO_STRLCPY = YesPlease - NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease GIT_TEST_CMP = cmp endif @@ -881,21 +889,18 @@ ifeq ($(uname_S),SunOS) 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.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 @@ -925,6 +930,7 @@ ifeq ($(uname_O),Cygwin) X = .exe COMPAT_OBJS += compat/cygwin.o UNRELIABLE_FSTAT = UnfortunatelyYes + SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease @@ -1041,6 +1047,7 @@ ifeq ($(uname_S),HP-UX) NO_UNSETENV = YesPlease NO_HSTRERROR = YesPlease NO_SYS_SELECT_H = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease NO_NSEC = YesPlease ifeq ($(uname_R),B.11.00) @@ -1074,7 +1081,6 @@ ifeq ($(uname_S),Windows) NO_MEMMEM = YesPlease # NEEDS_LIBICONV = YesPlease NO_ICONV = YesPlease - NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease NO_STRTOULL = YesPlease NO_MKDTEMP = YesPlease @@ -1151,7 +1157,6 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_MEMMEM = YesPlease NEEDS_LIBICONV = YesPlease OLD_ICONV = YesPlease - NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease @@ -1179,6 +1184,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) EXTLIBS += -lws2_32 PTHREAD_LIBS = X = .exe + SPARSE_FLAGS = -Wno-one-bit-signed-bitfield ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) htmldir=doc/git/html/ prefix = @@ -1194,6 +1200,14 @@ endif -include config.mak.autogen -include config.mak +ifndef sysconfdir +ifeq ($(prefix),/usr) +sysconfdir = /etc +else +sysconfdir = etc +endif +endif + ifdef CHECK_HEADER_DEPENDENCIES COMPUTE_HEADER_DEPENDENCIES = USE_COMPUTED_HEADER_DEPENDENCIES = @@ -1250,6 +1264,15 @@ ifdef NO_LIBGEN_H COMPAT_OBJS += compat/basename.o endif +ifdef USE_LIBPCRE + BASIC_CFLAGS += -DUSE_LIBPCRE + ifdef LIBPCREDIR + BASIC_CFLAGS += -I$(LIBPCREDIR)/include + EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib) + endif + EXTLIBS += -lpcre +endif + ifdef NO_CURL BASIC_CFLAGS += -DNO_CURL REMOTE_CURL_PRIMARY = @@ -1354,9 +1377,6 @@ endif ifdef NO_NSEC BASIC_CFLAGS += -DNO_NSEC endif -ifdef NO_C99_FORMAT - BASIC_CFLAGS += -DNO_C99_FORMAT -endif ifdef SNPRINTF_RETURNS_BOGUS COMPAT_CFLAGS += -DSNPRINTF_RETURNS_BOGUS COMPAT_OBJS += compat/snprintf.o @@ -1368,6 +1388,10 @@ endif ifdef NO_SYMLINK_HEAD BASIC_CFLAGS += -DNO_SYMLINK_HEAD endif +ifdef GETTEXT_POISON + LIB_OBJS += gettext.o + BASIC_CFLAGS += -DGETTEXT_POISON +endif ifdef NO_STRCASESTR COMPAT_CFLAGS += -DNO_STRCASESTR COMPAT_OBJS += compat/strcasestr.o @@ -1579,7 +1603,9 @@ ifndef V QUIET_BUILT_IN = @echo ' ' BUILTIN $@; QUIET_GEN = @echo ' ' GEN $@; QUIET_LNCP = @echo ' ' LN/CP $@; + QUIET_XGETTEXT = @echo ' ' XGETTEXT $@; QUIET_GCOV = @echo ' ' GCOV $@; + QUIET_SP = @echo ' ' SP $<; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir @@ -1675,17 +1701,19 @@ strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X git.o: common-cmds.h -git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \ - '-DGIT_HTML_PATH="$(htmldir_SQ)"' +git.sp git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \ + '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ + '-DGIT_MAN_PATH="$(mandir_SQ)"' \ + '-DGIT_INFO_PATH="$(infodir_SQ)"' git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -help.o: common-cmds.h +help.sp help.o: common-cmds.h -builtin/help.o: common-cmds.h -builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ +builtin/help.sp builtin/help.o: common-cmds.h +builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' @@ -1747,33 +1775,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl gitweb: $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all -ifdef JSMIN -GITWEB_PROGRAMS += gitweb/static/gitweb.min.js -GITWEB_JS = gitweb/static/gitweb.min.js -else -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/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/static/gitweb.css gitweb/static/gitweb.js +git-instaweb: git-instaweb.sh gitweb $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ @@ -1971,30 +1973,34 @@ $(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \ test-svn-fe.o: vcs-svn/svndump.h endif -exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \ +exec_cmd.sp 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: EXTRA_CPPFLAGS = \ +builtin/init-db.sp builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \ -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' -config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' +config.sp config.s config.o: EXTRA_CPPFLAGS = \ + -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' -attr.s attr.o: EXTRA_CPPFLAGS = -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' +attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \ + -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' -http.s http.o: EXTRA_CPPFLAGS = -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' +http.sp http.s http.o: EXTRA_CPPFLAGS = \ + -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' ifdef NO_EXPAT -http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT +http-walker.sp http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT endif ifdef NO_REGEX -compat/regex/regex.o: EXTRA_CPPFLAGS = -DGAWK -DNO_MBSUPPORT +compat/regex/regex.sp compat/regex/regex.o: EXTRA_CPPFLAGS = \ + -DGAWK -DNO_MBSUPPORT endif ifdef USE_NED_ALLOCATOR -compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ +compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR endif @@ -2046,6 +2052,25 @@ info: pdf: $(MAKE) -C Documentation pdf +XGETTEXT_FLAGS = \ + --force-po \ + --add-comments \ + --msgid-bugs-address="Git Mailing List <git@vger.kernel.org>" \ + --from-code=UTF-8 +XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \ + --keyword=_ --keyword=N_ --keyword="Q_:1,2" +XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell +LOCALIZED_C := $(C_OBJ:o=c) +LOCALIZED_SH := $(SCRIPT_SH) + +po/git.pot: $(LOCALIZED_C) + $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C) + $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \ + $(LOCALIZED_SH) + mv $@+ $@ + +pot: po/git.pot + $(ETAGS_TARGET): FORCE $(RM) $(ETAGS_TARGET) $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET) @@ -2079,6 +2104,7 @@ GIT-BUILD-OPTIONS: FORCE @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@ @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@ + @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@ ifdef GIT_TEST_CMP @@ -2087,6 +2113,7 @@ endif ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@ endif + @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@ ### Detect Tck/Tk interpreter path changes ifndef NO_TCLTK @@ -2144,13 +2171,20 @@ test-%$X: test-%.o $(GITLIBS) check-sha1:: test-sha1$X ./test-sha1.sh +SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ)) + +$(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE + $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \ + $(SPARSE_FLAGS) $< + +.PHONY: sparse $(SP_OBJ) +sparse: $(SP_OBJ) + check: common-cmds.h - if sparse; \ + @if sparse; \ then \ - for i in *.c; \ - do \ - sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; \ - done; \ + echo 2>&1 "Use 'make sparse' instead"; \ + $(MAKE) --no-print-directory sparse; \ else \ echo 2>&1 "Did you mean 'make test'?"; \ exit 1; \ @@ -2312,6 +2346,7 @@ dist-doc: distclean: clean $(RM) configure + $(RM) po/git.pot clean: $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \ @@ -1 +1 @@ -Documentation/RelNotes/1.7.4.txt
\ No newline at end of file +Documentation/RelNotes/1.7.6.txt
\ No newline at end of file @@ -14,7 +14,14 @@ int is_directory(const char *path) /* We allow "recursive" symbolic links. Only within reason, though. */ #define MAXDEPTH 5 -const char *make_absolute_path(const char *path) +/* + * Use this to get the real path, i.e. resolve links. If you want an + * absolute path but don't mind links, use absolute_path. + * + * If path is our buffer, then return path, as it's already what the + * user wants. + */ +const char *real_path(const char *path) { static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1]; char cwd[1024] = ""; @@ -24,6 +31,10 @@ const char *make_absolute_path(const char *path) char *last_elem = NULL; struct stat st; + /* We've already done it */ + if (path == buf || path == next_buf) + return path; + if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) die ("Too long path: %.*s", 60, path); @@ -100,7 +111,14 @@ static const char *get_pwd_cwd(void) return cwd; } -const char *make_nonrelative_path(const char *path) +/* + * Use this to get an absolute path from a relative one. If you want + * to resolve links, you should use real_path. + * + * If the path is already absolute, then return path. As the user is + * never meant to free the return value, we're safe. + */ +const char *absolute_path(const char *path) { static char buf[PATH_MAX + 1]; diff --git a/aclocal.m4 b/aclocal.m4 index d399de2671..f11bc7ed7d 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -13,7 +13,7 @@ AC_DEFUN([TYPE_SOCKLEN_T], 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([ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ #include <sys/types.h> #include <sys/socket.h> @@ -21,7 +21,7 @@ AC_DEFUN([TYPE_SOCKLEN_T], ],[ $t len; getpeername(0,0,&len); - ],[ + ])],[ git_cv_socklen_t_equiv="$t" break 2 ]) @@ -51,19 +51,12 @@ DEFINE_ALLOCATOR(commit, struct commit) DEFINE_ALLOCATOR(tag, struct tag) DEFINE_ALLOCATOR(object, union any_object) -#ifdef NO_C99_FORMAT -#define SZ_FMT "%u" -#else -#define SZ_FMT "%zu" -#endif - static void report(const char *name, unsigned int count, size_t size) { - fprintf(stderr, "%10s: %8u (" SZ_FMT " kB)\n", name, count, size); + fprintf(stderr, "%10s: %8u (%"PRIuMAX" kB)\n", + name, count, (uintmax_t) size); } -#undef SZ_FMT - #define REPORT(name) \ report(#name, name##_allocs, name##_allocs*sizeof(struct name) >> 10) @@ -157,6 +157,7 @@ int write_archive_entries(struct archiver_args *args, struct archiver_context context; struct unpack_trees_options opts; struct tree_desc t; + struct pathspec pathspec; int err; if (args->baselen > 0 && args->base[args->baselen - 1] == '/') { @@ -191,8 +192,10 @@ int write_archive_entries(struct archiver_args *args, git_attr_set_direction(GIT_ATTR_INDEX, &the_index); } - err = read_tree_recursive(args->tree, "", 0, 0, args->pathspec, + init_pathspec(&pathspec, args->pathspec); + err = read_tree_recursive(args->tree, "", 0, 0, &pathspec, write_archive_entry, &context); + free_pathspec(&pathspec); if (err == READ_TREE_RECURSIVE) err = 0; return err; @@ -221,11 +224,14 @@ static int reject_entry(const unsigned char *sha1, const char *base, static int path_exists(struct tree *tree, const char *path) { - const char *pathspec[] = { path, NULL }; - - if (read_tree_recursive(tree, "", 0, 0, pathspec, reject_entry, NULL)) - return 1; - return 0; + const char *paths[] = { path, NULL }; + struct pathspec pathspec; + int ret; + + init_pathspec(&pathspec, paths); + ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL); + free_pathspec(&pathspec); + return ret != 0; } static void parse_pathspec_arg(const char **pathspec, @@ -465,7 +465,7 @@ static void drop_attr_stack(void) } } -const char *git_etc_gitattributes(void) +static const char *git_etc_gitattributes(void) { static const char *system_wide; if (!system_wide) @@ -473,16 +473,11 @@ const char *git_etc_gitattributes(void) return system_wide; } -int git_attr_system(void) +static int git_attr_system(void) { return !git_env_bool("GIT_ATTR_NOSYSTEM", 0); } -int git_attr_global(void) -{ - return !git_env_bool("GIT_ATTR_NOGLOBAL", 0); -} - static int git_attr_config(const char *var, const char *value, void *dummy) { if (!strcmp(var, "core.attributesfile")) @@ -511,7 +506,7 @@ static void bootstrap_attr_stack(void) } git_config(git_attr_config, NULL); - if (git_attr_global() && attributes_file) { + if (attributes_file) { elem = read_attr_from_file(attributes_file, 1); if (elem) { elem->origin = NULL; @@ -9,13 +9,7 @@ #include "run-command.h" #include "log-tree.h" #include "bisect.h" - -struct sha1_array { - unsigned char (*sha1)[20]; - int sha1_nr; - int sha1_alloc; - int sorted; -}; +#include "sha1-array.h" static struct sha1_array good_revs; static struct sha1_array skipped_revs; @@ -425,22 +419,15 @@ static void argv_array_push_sha1(struct argv_array *array, argv_array_push(array, strbuf_detach(&buf, NULL)); } -static void sha1_array_push(struct sha1_array *array, - const unsigned char *sha1) -{ - ALLOC_GROW(array->sha1, array->sha1_nr + 1, array->sha1_alloc); - hashcpy(array->sha1[array->sha1_nr++], sha1); -} - static int register_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { if (!strcmp(refname, "bad")) { current_bad_sha1 = sha1; } else if (!prefixcmp(refname, "good-")) { - sha1_array_push(&good_revs, sha1); + sha1_array_append(&good_revs, sha1); } else if (!prefixcmp(refname, "skip-")) { - sha1_array_push(&skipped_revs, sha1); + sha1_array_append(&skipped_revs, sha1); } return 0; @@ -477,41 +464,14 @@ static void read_bisect_paths(struct argv_array *array) fclose(fp); } -static int array_cmp(const void *a, const void *b) -{ - return hashcmp(a, b); -} - -static void sort_sha1_array(struct sha1_array *array) -{ - qsort(array->sha1, array->sha1_nr, sizeof(*array->sha1), array_cmp); - - array->sorted = 1; -} - -static const unsigned char *sha1_access(size_t index, void *table) -{ - unsigned char (*array)[20] = table; - return array[index]; -} - -static int lookup_sha1_array(struct sha1_array *array, - const unsigned char *sha1) -{ - if (!array->sorted) - sort_sha1_array(array); - - return sha1_pos(sha1, array->sha1, array->sha1_nr, sha1_access); -} - static char *join_sha1_array_hex(struct sha1_array *array, char delim) { struct strbuf joined_hexs = STRBUF_INIT; int i; - for (i = 0; i < array->sha1_nr; i++) { + for (i = 0; i < array->nr; i++) { strbuf_addstr(&joined_hexs, sha1_to_hex(array->sha1[i])); - if (i + 1 < array->sha1_nr) + if (i + 1 < array->nr) strbuf_addch(&joined_hexs, delim); } @@ -546,13 +506,13 @@ struct commit_list *filter_skipped(struct commit_list *list, if (count) *count = 0; - if (!skipped_revs.sha1_nr) + if (!skipped_revs.nr) return list; while (list) { struct commit_list *next = list->next; list->next = NULL; - if (0 <= lookup_sha1_array(&skipped_revs, + if (0 <= sha1_array_lookup(&skipped_revs, list->item->object.sha1)) { if (skipped_first && !*skipped_first) *skipped_first = 1; @@ -647,7 +607,7 @@ static struct commit_list *managed_skipped(struct commit_list *list, *tried = NULL; - if (!skipped_revs.sha1_nr) + if (!skipped_revs.nr) return list; list = filter_skipped(list, tried, 0, &count, &skipped_first); @@ -672,7 +632,7 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix, /* rev_argv.argv[0] will be ignored by setup_revisions */ argv_array_push(&rev_argv, xstrdup("bisect_rev_setup")); argv_array_push_sha1(&rev_argv, current_bad_sha1, bad_format); - for (i = 0; i < good_revs.sha1_nr; i++) + for (i = 0; i < good_revs.nr; i++) argv_array_push_sha1(&rev_argv, good_revs.sha1[i], good_format); argv_array_push(&rev_argv, xstrdup("--")); @@ -772,12 +732,12 @@ static struct commit *get_commit_reference(const unsigned char *sha1) static struct commit **get_bad_and_good_commits(int *rev_nr) { - int len = 1 + good_revs.sha1_nr; + int len = 1 + good_revs.nr; struct commit **rev = xmalloc(len * sizeof(*rev)); int i, n = 0; rev[n++] = get_commit_reference(current_bad_sha1); - for (i = 0; i < good_revs.sha1_nr; i++) + for (i = 0; i < good_revs.nr; i++) rev[n++] = get_commit_reference(good_revs.sha1[i]); *rev_nr = n; @@ -840,9 +800,9 @@ static void check_merge_bases(void) const unsigned char *mb = result->item->object.sha1; if (!hashcmp(mb, current_bad_sha1)) { handle_bad_merge_base(); - } else if (0 <= lookup_sha1_array(&good_revs, mb)) { + } else if (0 <= sha1_array_lookup(&good_revs, mb)) { continue; - } else if (0 <= lookup_sha1_array(&skipped_revs, mb)) { + } else if (0 <= sha1_array_lookup(&skipped_revs, mb)) { handle_skipped_merge_base(mb); } else { printf("Bisecting: a merge base must be tested\n"); @@ -903,7 +863,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix) return; /* Bisecting with no good rev is ok. */ - if (good_revs.sha1_nr == 0) + if (good_revs.nr == 0) return; /* Check if all good revs are ancestor of the bad rev. */ @@ -968,7 +928,7 @@ int bisect_next_all(const char *prefix) bisect_common(&revs); revs.commits = find_bisection(revs.commits, &reaches, &all, - !!skipped_revs.sha1_nr); + !!skipped_revs.nr); revs.commits = managed_skipped(revs.commits, &tried); if (!revs.commits) { @@ -175,9 +175,14 @@ void create_branch(const char *head, die("Cannot setup tracking information; starting point is not a branch."); break; case 1: - /* Unique completion -- good, only if it is a real ref */ - if (explicit_tracking && !strcmp(real_ref, "HEAD")) - die("Cannot setup tracking information; starting point is not a branch."); + /* Unique completion -- good, only if it is a real branch */ + if (prefixcmp(real_ref, "refs/heads/") && + prefixcmp(real_ref, "refs/remotes/")) { + if (explicit_tracking) + die("Cannot setup tracking information; starting point is not a branch."); + else + real_ref = NULL; + } break; default: die("Ambiguous object name: '%s'.", start_name); @@ -217,6 +222,7 @@ void create_branch(const char *head, void remove_branch_state(void) { + unlink(git_path("CHERRY_PICK_HEAD")); unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_RR")); unlink(git_path("MERGE_MSG")); @@ -57,6 +57,7 @@ extern int cmd_clone(int argc, const char **argv, const char *prefix); extern int cmd_clean(int argc, const char **argv, const char *prefix); extern int cmd_commit(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); +extern int cmd_config(int argc, const char **argv, const char *prefix); extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_describe(int argc, const char **argv, const char *prefix); extern int cmd_diff_files(int argc, const char **argv, const char *prefix); @@ -110,7 +111,7 @@ extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_remote(int argc, const char **argv, const char *prefix); extern int cmd_remote_ext(int argc, const char **argv, const char *prefix); extern int cmd_remote_fd(int argc, const char **argv, const char *prefix); -extern int cmd_config(int argc, const char **argv, const char *prefix); +extern int cmd_repo_config(int argc, const char **argv, const char *prefix); extern int cmd_rerere(int argc, const char **argv, const char *prefix); extern int cmd_reset(int argc, const char **argv, const char *prefix); extern int cmd_rev_list(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index 12b964e642..c59b0c98fe 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -21,12 +21,32 @@ static const char * const builtin_add_usage[] = { static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; -struct update_callback_data -{ +struct update_callback_data { int flags; int add_errors; }; +static int fix_unmerged_status(struct diff_filepair *p, + struct update_callback_data *data) +{ + if (p->status != DIFF_STATUS_UNMERGED) + return p->status; + if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL) && !p->two->mode) + /* + * This is not an explicit add request, and the + * path is missing from the working tree (deleted) + */ + return DIFF_STATUS_DELETED; + else + /* + * Either an explicit add request, or path exists + * in the working tree. An attempt to explicitly + * add a path that does not exist in the working tree + * will be caught as an error by the caller immediately. + */ + return DIFF_STATUS_MODIFIED; +} + static void update_callback(struct diff_queue_struct *q, struct diff_options *opt, void *cbdata) { @@ -36,35 +56,14 @@ static void update_callback(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; - switch (p->status) { + switch (fix_unmerged_status(p, data)) { default: - die("unexpected diff status %c", p->status); - case DIFF_STATUS_UNMERGED: - /* - * ADD_CACHE_IGNORE_REMOVAL is unset if "git - * add -u" is calling us, In such a case, a - * missing work tree file needs to be removed - * if there is an unmerged entry at stage #2, - * but such a diff record is followed by - * another with DIFF_STATUS_DELETED (and if - * there is no stage #2, we won't see DELETED - * nor MODIFIED). We can simply continue - * either way. - */ - if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL)) - continue; - /* - * Otherwise, it is "git add path" is asking - * to explicitly add it; we fall through. A - * missing work tree file is an error and is - * caught by add_file_to_index() in such a - * case. - */ + die(_("unexpected diff status %c"), p->status); case DIFF_STATUS_MODIFIED: case DIFF_STATUS_TYPE_CHANGED: if (add_file_to_index(&the_index, path, data->flags)) { if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) - die("updating files failed"); + die(_("updating files failed")); data->add_errors++; } break; @@ -74,7 +73,7 @@ static void update_callback(struct diff_queue_struct *q, if (!(data->flags & ADD_CACHE_PRETEND)) remove_file_from_index(&the_index, path); if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE)) - printf("remove '%s'\n", path); + printf(_("remove '%s'\n"), path); break; } } @@ -86,12 +85,13 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags) struct rev_info rev; init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = pathspec; + init_pathspec(&rev.prune_data, pathspec); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; data.flags = flags; data.add_errors = 0; rev.diffopt.format_callback_data = &data; + rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); return !!data.add_errors; } @@ -172,7 +172,7 @@ static void treat_gitlinks(const char **pathspec) /* strip trailing slash */ pathspec[j] = xstrndup(ce->name, len); else - die ("Path '%s' is in submodule '%.*s'", + die (_("Path '%s' is in submodule '%.*s'"), pathspec[j], len, ce->name); } } @@ -188,10 +188,10 @@ static void refresh(int verbose, const char **pathspec) /* nothing */; seen = xcalloc(specs, 1); refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET, - pathspec, seen, "Unstaged changes after refreshing the index:"); + pathspec, seen, _("Unstaged changes after refreshing the index:")); for (i = 0; i < specs; i++) { if (!seen[i]) - die("pathspec '%s' did not match any files", pathspec[i]); + die(_("pathspec '%s' did not match any files"), pathspec[i]); } free(seen); } @@ -205,7 +205,7 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p for (p = pathspec; *p; p++) { if (has_symlink_leading_path(*p, strlen(*p))) { int len = prefix ? strlen(prefix) : 0; - die("'%s' is beyond a symbolic link", *p + len); + die(_("'%s' is beyond a symbolic link"), *p + len); } } } @@ -242,7 +242,7 @@ int run_add_interactive(const char *revision, const char *patch_mode, return status; } -int interactive_add(int argc, const char **argv, const char *prefix) +int interactive_add(int argc, const char **argv, const char *prefix, int patch) { const char **pathspec = NULL; @@ -253,7 +253,7 @@ int interactive_add(int argc, const char **argv, const char *prefix) } return run_add_interactive(NULL, - patch_interactive ? "--patch" : NULL, + patch ? "--patch" : NULL, pathspec); } @@ -272,7 +272,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ if (read_cache() < 0) - die ("Could not read the index"); + die (_("Could not read the index")); init_revisions(&rev, prefix); rev.diffopt.context = 7; @@ -281,24 +281,24 @@ static int edit_patch(int argc, const char **argv, const char *prefix) rev.diffopt.output_format = DIFF_FORMAT_PATCH; out = open(file, O_CREAT | O_WRONLY, 0644); if (out < 0) - die ("Could not open '%s' for writing.", file); + die (_("Could not open '%s' for writing."), file); rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; if (run_diff_files(&rev, 0)) - die ("Could not write patch"); + die (_("Could not write patch")); launch_editor(file, NULL, NULL); if (stat(file, &st)) - die_errno("Could not stat '%s'", file); + die_errno(_("Could not stat '%s'"), file); if (!st.st_size) - die("Empty patch. Aborted."); + die(_("Empty patch. Aborted.")); memset(&child, 0, sizeof(child)); child.git_cmd = 1; child.argv = apply_argv; if (run_command(&child)) - die ("Could not apply '%s'", file); + die (_("Could not apply '%s'"), file); unlink(file); return 0; @@ -307,7 +307,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) static struct lock_file lock_file; static const char ignore_error[] = -"The following paths are ignored by one of your .gitignore files:\n"; +N_("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, ignore_missing = 0; @@ -317,12 +317,12 @@ static struct option builtin_add_options[] = { OPT__VERBOSE(&verbose, "be verbose"), OPT_GROUP(""), OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), - OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), + OPT_BOOLEAN('p', "patch", &patch_interactive, "select hunks interactively"), OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), OPT__FORCE(&ignored_too, "allow adding otherwise ignored files"), OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"), - OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"), + OPT_BOOLEAN('A', "all", &addremove, "add changes from all tracked and untracked 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"), @@ -331,8 +331,8 @@ static struct option builtin_add_options[] = { static int add_config(const char *var, const char *value, void *cb) { - if (!strcasecmp(var, "add.ignoreerrors") || - !strcasecmp(var, "add.ignore-errors")) { + if (!strcmp(var, "add.ignoreerrors") || + !strcmp(var, "add.ignore-errors")) { ignore_add_errors = git_config_bool(var, value); return 0; } @@ -344,17 +344,17 @@ static int add_files(struct dir_struct *dir, int flags) int i, exit_status = 0; if (dir->ignored_nr) { - fprintf(stderr, ignore_error); + fprintf(stderr, _(ignore_error)); for (i = 0; i < dir->ignored_nr; i++) fprintf(stderr, "%s\n", dir->ignored[i]->name); - fprintf(stderr, "Use -f if you really want to add them.\n"); - die("no files added"); + fprintf(stderr, _("Use -f if you really want to add them.\n")); + die(_("no files added")); } for (i = 0; i < dir->nr; i++) if (add_file_to_cache(dir->entries[i]->name, flags)) { if (!ignore_add_errors) - die("adding files failed"); + die(_("adding files failed")); exit_status = 1; } return exit_status; @@ -378,7 +378,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (patch_interactive) add_interactive = 1; if (add_interactive) - exit(interactive_add(argc - 1, argv + 1, prefix)); + exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive)); if (edit_interactive) return(edit_patch(argc, argv, prefix)); @@ -386,9 +386,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) argv++; if (addremove && take_worktree_changes) - die("-A and -u are mutually incompatible"); + die(_("-A and -u are mutually incompatible")); if (!show_only && ignore_missing) - die("Option --ignore-missing can only be used together with --dry-run"); + 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; @@ -408,14 +408,14 @@ int cmd_add(int argc, const char **argv, const char *prefix) ? ADD_CACHE_IGNORE_REMOVAL : 0)); if (require_pathspec && argc == 0) { - fprintf(stderr, "Nothing specified, nothing added.\n"); - fprintf(stderr, "Maybe you wanted to say 'git add .'?\n"); + fprintf(stderr, _("Nothing specified, nothing added.\n")); + fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); return 0; } pathspec = validate_pathspec(argc, argv, prefix); if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); treat_gitlinks(pathspec); if (add_new_files) { @@ -451,7 +451,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (excluded(&dir, pathspec[i], &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else - die("pathspec '%s' did not match any files", + die(_("pathspec '%s' did not match any files"), pathspec[i]); } } @@ -467,7 +467,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return exit_status; diff --git a/builtin/apply.c b/builtin/apply.c index 14951daedf..530d4bb7e7 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -43,6 +43,7 @@ static int apply = 1; static int apply_in_reverse; static int apply_with_reject; static int apply_verbosely; +static int allow_overlap; static int no_add; static const char *fake_ancestor; static int line_termination = '\n'; @@ -204,6 +205,7 @@ struct line { unsigned hash : 24; unsigned flag : 8; #define LINE_COMMON 1 +#define LINE_PATCHED 2 }; /* @@ -2085,7 +2087,8 @@ static int match_fragment(struct image *img, /* Quick hash check */ for (i = 0; i < preimage_limit; i++) - if (preimage->line[i].hash != img->line[try_lno + i].hash) + if ((img->line[try_lno + i].flag & LINE_PATCHED) || + (preimage->line[i].hash != img->line[try_lno + i].hash)) return 0; if (preimage_limit == preimage->nr) { @@ -2428,11 +2431,15 @@ static void update_image(struct image *img, memcpy(img->line + applied_pos, postimage->line, postimage->nr * sizeof(*img->line)); + if (!allow_overlap) + for (i = 0; i < postimage->nr; i++) + img->line[applied_pos + i].flag |= LINE_PATCHED; img->nr = nr; } static int apply_one_fragment(struct image *img, struct fragment *frag, - int inaccurate_eof, unsigned ws_rule) + int inaccurate_eof, unsigned ws_rule, + int nth_fragment) { int match_beginning, match_end; const char *patch = frag->patch; @@ -2638,6 +2645,15 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, apply = 0; } + if (apply_verbosely && applied_pos != pos) { + int offset = applied_pos - pos; + if (apply_in_reverse) + offset = 0 - offset; + fprintf(stderr, + "Hunk #%d succeeded at %d (offset %d lines).\n", + nth_fragment, applied_pos + 1, offset); + } + /* * Warn if it was necessary to reduce the number * of context lines. @@ -2785,12 +2801,14 @@ static int apply_fragments(struct image *img, struct patch *patch) const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned ws_rule = patch->ws_rule; unsigned inaccurate_eof = patch->inaccurate_eof; + int nth = 0; if (patch->is_binary) return apply_binary(img, patch); while (frag) { - if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) { + nth++; + if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) { error("patch failed: %s:%ld", name, frag->oldpos); if (!apply_with_reject) return -1; @@ -3872,6 +3890,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) "don't expect at least one line of context"), OPT_BOOLEAN(0, "reject", &apply_with_reject, "leave the rejected hunks in corresponding *.rej files"), + OPT_BOOLEAN(0, "allow-overlap", &allow_overlap, + "allow overlapping hunks"), OPT__VERBOSE(&apply_verbosely, "be verbose"), OPT_BIT(0, "inaccurate-eof", &options, "tolerate incorrectly detected missing new-line at the end of file", diff --git a/builtin/archive.c b/builtin/archive.c index 6a887f5a9d..b14eaba159 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -14,10 +14,10 @@ static void create_output_file(const char *output_file) { int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (output_fd < 0) - die_errno("could not create archive file '%s'", output_file); + die_errno(_("could not create archive file '%s'"), output_file); if (output_fd != 1) { if (dup2(output_fd, 1) < 0) - die_errno("could not redirect output"); + die_errno(_("could not redirect output")); else close(output_fd); } @@ -33,7 +33,7 @@ static int run_remote_archiver(int argc, const char **argv, _remote = remote_get(remote); if (!_remote->url[0]) - die("git archive: Remote with no URL"); + die(_("git archive: Remote with no URL")); transport = transport_get(_remote, _remote->url[0]); transport_connect(transport, "git-upload-archive", exec, fd); @@ -43,18 +43,18 @@ static int run_remote_archiver(int argc, const char **argv, len = packet_read_line(fd[0], buf, sizeof(buf)); if (!len) - die("git archive: expected ACK/NAK, got EOF"); + die(_("git archive: expected ACK/NAK, got EOF")); if (buf[len-1] == '\n') buf[--len] = 0; if (strcmp(buf, "ACK")) { if (len > 5 && !prefixcmp(buf, "NACK ")) - die("git archive: NACK %s", buf + 5); - die("git archive: protocol error"); + die(_("git archive: NACK %s"), buf + 5); + die(_("git archive: protocol error")); } len = packet_read_line(fd[0], buf, sizeof(buf)); if (len) - die("git archive: expected a flush"); + die(_("git archive: expected a flush")); /* Now, start reading from fd[0] and spit it out to stdout */ rv = recv_sideband("archive", fd[0], 1); diff --git a/builtin/blame.c b/builtin/blame.c index aa30ec5269..26a5d424b8 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -41,6 +41,7 @@ static int reverse; static int blank_boundary; static int incremental; static int xdl_opts; +static int abbrev = -1; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; @@ -1312,8 +1313,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) /* * Information on commits, used for output. */ -struct commit_info -{ +struct commit_info { const char *author; const char *author_mail; unsigned long author_time; @@ -1378,7 +1378,7 @@ static void get_ac_line(const char *inbuf, const char *what, timepos = tmp; *tmp = 0; - while (person < tmp && *tmp != ' ') + while (person < tmp && !(*tmp == ' ' && tmp[1] == '<')) tmp--; if (tmp <= person) return; @@ -1484,13 +1484,14 @@ static void write_filename_info(const char *path) /* * Porcelain/Incremental format wants to show a lot of details per * commit. Instead of repeating this every line, emit it only once, - * the first time each commit appears in the output. + * the first time each commit appears in the output (unless the + * user has specifically asked for us to repeat). */ -static int emit_one_suspect_detail(struct origin *suspect) +static int emit_one_suspect_detail(struct origin *suspect, int repeat) { struct commit_info ci; - if (suspect->commit->object.flags & METAINFO_SHOWN) + if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN)) return 0; suspect->commit->object.flags |= METAINFO_SHOWN; @@ -1529,7 +1530,7 @@ static void found_guilty_entry(struct blame_entry *ent) printf("%s %d %d %d\n", sha1_to_hex(suspect->commit->object.sha1), ent->s_lno + 1, ent->lno + 1, ent->num_lines); - emit_one_suspect_detail(suspect); + emit_one_suspect_detail(suspect, 0); write_filename_info(suspect->path); maybe_flush_or_die(stdout, "stdout"); } @@ -1618,9 +1619,19 @@ static const char *format_time(unsigned long time, const char *tz_str, #define OUTPUT_SHOW_SCORE 0100 #define OUTPUT_NO_AUTHOR 0200 #define OUTPUT_SHOW_EMAIL 0400 +#define OUTPUT_LINE_PORCELAIN 01000 -static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) +static void emit_porcelain_details(struct origin *suspect, int repeat) { + if (emit_one_suspect_detail(suspect, repeat) || + (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) + write_filename_info(suspect->path); +} + +static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, + int opt) +{ + int repeat = opt & OUTPUT_LINE_PORCELAIN; int cnt; const char *cp; struct origin *suspect = ent->suspect; @@ -1633,17 +1644,18 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) ent->s_lno + 1, ent->lno + 1, ent->num_lines); - if (emit_one_suspect_detail(suspect) || - (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) - write_filename_info(suspect->path); + emit_porcelain_details(suspect, repeat); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - if (cnt) + if (cnt) { printf("%s %d %d\n", hex, ent->s_lno + 1 + cnt, ent->lno + 1 + cnt); + if (repeat) + emit_porcelain_details(suspect, 1); + } putchar('\t'); do { ch = *cp++; @@ -1671,7 +1683,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev; if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) @@ -1756,7 +1768,7 @@ static void output(struct scoreboard *sb, int option) for (ent = sb->ent; ent; ent = ent->next) { if (option & OUTPUT_PORCELAIN) - emit_porcelain(sb, ent); + emit_porcelain(sb, ent, option); else { emit_other(sb, ent, option); } @@ -2300,6 +2312,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME), OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER), OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN), + OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT), OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP), OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME), @@ -2311,6 +2324,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback }, { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback }, OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback), + OPT__ABBREV(&abbrev), OPT_END() }; @@ -2346,6 +2360,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_done: argc = parse_options_end(&ctx); + if (abbrev == -1) + abbrev = default_abbrev; + /* one more abbrev length is needed for the boundary commit */ + abbrev++; + if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); diff --git a/builtin/branch.c b/builtin/branch.c index d8f15221ed..d6ab93bfbb 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -133,12 +133,12 @@ static int branch_merged(int kind, const char *name, if ((head_rev != reference_rev) && in_merge_bases(rev, &head_rev, 1) != merged) { if (merged) - warning("deleting branch '%s' that has been merged to\n" - " '%s', but it is not yet merged to HEAD.", + warning(_("deleting branch '%s' that has been merged to\n" + " '%s', but not yet merged to HEAD."), name, reference_name); else - warning("not deleting branch '%s' that is not yet merged to\n" - " '%s', even though it is merged to HEAD.", + warning(_("not deleting branch '%s' that is not yet merged to\n" + " '%s', even though it is merged to HEAD."), name, reference_name); } return merged; @@ -157,7 +157,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) switch (kinds) { case REF_REMOTE_BRANCH: fmt = "refs/remotes/%s"; - remote = "remote "; + /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */ + remote = _("remote "); force = 1; break; case REF_LOCAL_BRANCH: @@ -165,19 +166,19 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) remote = ""; break; default: - die("cannot use -a with -d"); + die(_("cannot use -a with -d")); } if (!force) { head_rev = lookup_commit_reference(head_sha1); if (!head_rev) - die("Couldn't look up commit object for HEAD"); + die(_("Couldn't look up commit object for HEAD")); } for (i = 0; i < argc; i++, strbuf_release(&bname)) { strbuf_branchname(&bname, argv[i]); if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { - error("Cannot delete the branch '%s' " - "which you are currently on.", bname.buf); + error(_("Cannot delete the branch '%s' " + "which you are currently on."), bname.buf); ret = 1; continue; } @@ -186,7 +187,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) name = xstrdup(mkpath(fmt, bname.buf)); if (!resolve_ref(name, sha1, 1, NULL)) { - error("%sbranch '%s' not found.", + error(_("%sbranch '%s' not found."), remote, bname.buf); ret = 1; continue; @@ -194,31 +195,31 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) rev = lookup_commit_reference(sha1); if (!rev) { - error("Couldn't look up commit object for '%s'", name); + error(_("Couldn't look up commit object for '%s'"), name); ret = 1; continue; } if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) { - error("The branch '%s' is not fully merged.\n" + error(_("The branch '%s' is not fully merged.\n" "If you are sure you want to delete it, " - "run 'git branch -D %s'.", bname.buf, bname.buf); + "run 'git branch -D %s'."), bname.buf, bname.buf); ret = 1; continue; } if (delete_ref(name, sha1, 0)) { - error("Error deleting %sbranch '%s'", remote, + error(_("Error deleting %sbranch '%s'"), remote, bname.buf); ret = 1; } else { struct strbuf buf = STRBUF_INIT; - printf("Deleted %sbranch %s (was %s).\n", remote, + printf(_("Deleted %sbranch %s (was %s).\n"), remote, bname.buf, find_unique_abbrev(sha1, DEFAULT_ABBREV)); strbuf_addf(&buf, "branch.%s", bname.buf); if (git_config_rename_section(buf.buf, NULL) < 0) - warning("Update of config-file failed"); + warning(_("Update of config-file failed")); strbuf_release(&buf); } } @@ -300,7 +301,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { commit = lookup_commit_reference_gently(sha1, 1); if (!commit) { - cb->ret = error("branch '%s' does not point at a commit", refname); + cb->ret = error(_("branch '%s' does not point at a commit"), refname); return 0; } @@ -372,11 +373,11 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, strbuf_addf(stat, "%s: ", shorten_unambiguous_ref(branch->merge[0]->dst, 0)); if (!ours) - strbuf_addf(stat, "behind %d] ", theirs); + strbuf_addf(stat, _("behind %d] "), theirs); else if (!theirs) - strbuf_addf(stat, "ahead %d] ", ours); + strbuf_addf(stat, _("ahead %d] "), ours); else - strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs); + strbuf_addf(stat, _("ahead %d, behind %d] "), ours, theirs); } static int matches_merge_filter(struct commit *commit) @@ -390,6 +391,28 @@ static int matches_merge_filter(struct commit *commit) return (is_merged == (merge_filter == SHOW_MERGED)); } +static void add_verbose_info(struct strbuf *out, struct ref_item *item, + int verbose, int abbrev) +{ + struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; + const char *sub = " **** invalid ref ****"; + struct commit *commit = item->commit; + + if (commit && !parse_commit(commit)) { + pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject); + sub = subject.buf; + } + + if (item->kind == REF_LOCAL_BRANCH) + fill_tracking_info(&stat, item->name, verbose > 1); + + strbuf_addf(out, " %s %s%s", + find_unique_abbrev(item->commit->object.sha1, abbrev), + stat.buf, sub); + strbuf_release(&stat); + strbuf_release(&subject); +} + static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, int abbrev, int current, char *prefix) { @@ -430,25 +453,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, if (item->dest) strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) { - struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; - const char *sub = " **** invalid ref ****"; - - commit = item->commit; - if (commit && !parse_commit(commit)) { - pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject); - sub = subject.buf; - } - - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); - - strbuf_addf(&out, " %s %s%s", - find_unique_abbrev(item->commit->object.sha1, abbrev), - stat.buf, sub); - strbuf_release(&stat); - strbuf_release(&subject); - } + else if (verbose) + /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ + add_verbose_info(&out, item, verbose, abbrev); printf("%s\n", out.buf); strbuf_release(&name); strbuf_release(&out); @@ -473,7 +480,7 @@ static void show_detached(struct ref_list *ref_list) if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { struct ref_item item; - item.name = xstrdup("(no branch)"); + item.name = xstrdup(_("(no branch)")); item.len = strlen(item.name); item.kind = REF_LOCAL_BRANCH; item.dest = NULL; @@ -533,7 +540,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru free_ref_list(&ref_list); if (cb.ret) - error("some refs could not be read"); + error(_("some refs could not be read")); return cb.ret; } @@ -546,7 +553,7 @@ static void rename_branch(const char *oldname, const char *newname, int force) int recovery = 0; if (!oldname) - die("cannot rename the current branch while not on any."); + die(_("cannot rename the current branch while not on any.")); if (strbuf_check_branch_ref(&oldref, oldname)) { /* @@ -556,35 +563,35 @@ static void rename_branch(const char *oldname, const char *newname, int force) if (resolve_ref(oldref.buf, sha1, 1, NULL)) recovery = 1; else - die("Invalid branch name: '%s'", oldname); + die(_("Invalid branch name: '%s'"), oldname); } if (strbuf_check_branch_ref(&newref, newname)) - die("Invalid branch name: '%s'", newname); + die(_("Invalid branch name: '%s'"), newname); if (resolve_ref(newref.buf, sha1, 1, NULL) && !force) - die("A branch named '%s' already exists.", newref.buf + 11); + die(_("A branch named '%s' already exists."), newref.buf + 11); strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) - die("Branch rename failed"); + die(_("Branch rename failed")); strbuf_release(&logmsg); if (recovery) - warning("Renamed a misnamed branch '%s' away", oldref.buf + 11); + warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11); /* no need to pass logmsg here as HEAD didn't really move */ if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL)) - die("Branch renamed to %s, but HEAD is not updated!", newname); + die(_("Branch renamed to %s, but HEAD is not updated!"), newname); strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); strbuf_release(&oldref); strbuf_addf(&newsection, "branch.%s", newref.buf + 11); strbuf_release(&newref); if (git_config_rename_section(oldsection.buf, newsection.buf) < 0) - die("Branch is renamed, but update of config-file failed"); + die(_("Branch is renamed, but update of config-file failed")); strbuf_release(&oldsection); strbuf_release(&newsection); } @@ -599,7 +606,7 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int if (!arg) arg = "HEAD"; if (get_sha1(arg, merge_filter_ref)) - die("malformed object name %s", arg); + die(_("malformed object name %s"), arg); return 0; } @@ -673,13 +680,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) head = resolve_ref("HEAD", head_sha1, 0, NULL); if (!head) - die("Failed to resolve HEAD as a valid ref."); + die(_("Failed to resolve HEAD as a valid ref.")); head = xstrdup(head); if (!strcmp(head, "HEAD")) { detached = 1; } else { if (prefixcmp(head, "refs/heads/")) - die("HEAD not found below refs/heads!"); + die(_("HEAD not found below refs/heads!")); head += 11; } hashcpy(merge_filter_ref, head_sha1); @@ -699,7 +706,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) rename_branch(argv[0], argv[1], rename > 1); else if (argc <= 2) { if (kinds != REF_LOCAL_BRANCH) - die("-a and -r options to 'git branch' do not make sense with a branch name"); + die(_("-a and -r options to 'git branch' do not make sense with a branch name")); create_branch(head, argv[0], (argc == 2) ? argv[1] : head, force_create, reflog, track); } else diff --git a/builtin/bundle.c b/builtin/bundle.c index 9b87fb9ac2..81046a9cb8 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -44,7 +44,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) close(bundle_fd); if (verify_bundle(&header, 1)) return 1; - fprintf(stderr, "%s is okay\n", bundle_file); + fprintf(stderr, _("%s is okay\n"), bundle_file); return 0; } if (!strcmp(cmd, "list-heads")) { @@ -53,11 +53,11 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } if (!strcmp(cmd, "create")) { if (!startup_info->have_repository) - die("Need a repository to create a bundle."); + die(_("Need a repository to create a bundle.")); return !!create_bundle(&header, bundle_file, argc, argv); } else if (!strcmp(cmd, "unbundle")) { if (!startup_info->have_repository) - die("Need a repository to unbundle."); + die(_("Need a repository to unbundle.")); return !!unbundle(&header, bundle_fd) || list_bundle_refs(&header, argc, argv); } else diff --git a/builtin/checkout.c b/builtin/checkout.c index c1759dc3a4..28cdc51b85 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -30,6 +30,7 @@ struct checkout_opts { int quiet; int merge; int force; + int force_detach; int writeout_stage; int writeout_error; @@ -78,7 +79,10 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen, static int read_tree_some(struct tree *tree, const char **pathspec) { - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); + struct pathspec ps; + init_pathspec(&ps, pathspec); + read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL); + free_pathspec(&ps); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -103,9 +107,10 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) return 0; pos++; } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); + if (stage == 2) + return error(_("path '%s' does not have our version"), ce->name); + else + return error(_("path '%s' does not have their version"), ce->name); } static int check_all_stages(struct cache_entry *ce, int pos) @@ -116,7 +121,7 @@ static int check_all_stages(struct cache_entry *ce, int pos) ce_stage(active_cache[pos+1]) != 2 || strcmp(active_cache[pos+2]->name, ce->name) || ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all three versions", + return error(_("path '%s' does not have all three versions"), ce->name); return 0; } @@ -130,9 +135,10 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, return checkout_entry(active_cache[pos], state, NULL); pos++; } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); + if (stage == 2) + return error(_("path '%s' does not have our version"), ce->name); + else + return error(_("path '%s' does not have their version"), ce->name); } static int checkout_merged(int pos, struct checkout *state) @@ -150,7 +156,7 @@ static int checkout_merged(int pos, struct checkout *state) ce_stage(active_cache[pos+1]) != 2 || strcmp(active_cache[pos+2]->name, path) || ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all 3 versions", path); + return error(_("path '%s' does not have all 3 versions"), path); read_mmblob(&ancestor, active_cache[pos]->sha1); read_mmblob(&ours, active_cache[pos+1]->sha1); @@ -167,7 +173,7 @@ static int checkout_merged(int pos, struct checkout *state) free(theirs.ptr); if (status < 0 || !result_buf.ptr) { free(result_buf.ptr); - return error("path '%s': cannot merge", path); + return error(_("path '%s': cannot merge"), path); } /* @@ -184,12 +190,12 @@ static int checkout_merged(int pos, struct checkout *state) */ if (write_sha1_file(result_buf.ptr, result_buf.size, blob_type, sha1)) - die("Unable to add merge result for '%s'", path); + die(_("Unable to add merge result for '%s'"), path); ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode), sha1, path, 2, 0); if (!ce) - die("make_cache_entry failed for path '%s'", path); + die(_("make_cache_entry failed for path '%s'"), path); status = checkout_entry(ce, state, NULL); return status; } @@ -211,7 +217,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, newfd = hold_locked_index(lock_file, 1); if (read_cache_preload(pathspec) < 0) - return error("corrupt index file"); + return error(_("corrupt index file")); if (source_tree) read_tree_some(source_tree, pathspec); @@ -239,14 +245,14 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, if (!ce_stage(ce)) continue; if (opts->force) { - warning("path '%s' is unmerged", ce->name); + warning(_("path '%s' is unmerged"), ce->name); } else if (stage) { errs |= check_stage(stage, ce, pos); } else if (opts->merge) { errs |= check_all_stages(ce, pos); } else { errs = 1; - error("path '%s' is unmerged", ce->name); + error(_("path '%s' is unmerged"), ce->name); } pos = skip_same_name(ce, pos) - 1; } @@ -275,7 +281,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); resolve_ref("HEAD", rev, 0, &flag); head = lookup_commit_reference_gently(rev, 1); @@ -292,12 +298,12 @@ static void show_local_changes(struct object *head, struct diff_options *opts) rev.diffopt.flags = opts->flags; rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); + die(_("diff_setup_done failed")); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); } -static void describe_detached_head(char *msg, struct commit *commit) +static void describe_detached_head(const char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; parse_commit(commit); @@ -365,7 +371,7 @@ static int merge_working_tree(struct checkout_opts *opts, int newfd = hold_locked_index(lock_file, 1); if (read_cache_preload(NULL) < 0) - return error("corrupt index file"); + return error(_("corrupt index file")); resolve_undo_clear(); if (opts->force) { @@ -387,7 +393,7 @@ static int merge_working_tree(struct checkout_opts *opts, refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { - error("you need to resolve your current index first"); + error(_("you need to resolve your current index first")); return 1; } @@ -403,7 +409,7 @@ static int merge_working_tree(struct checkout_opts *opts, topts.dir->exclude_per_dir = ".gitignore"; tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : - (unsigned char *)EMPTY_TREE_SHA1_BIN); + EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); tree = parse_tree_indirect(new->commit->object.sha1); init_tree_desc(&trees[1], tree->buffer, tree->size); @@ -469,7 +475,7 @@ static int merge_working_tree(struct checkout_opts *opts, if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); if (!opts->force && !opts->quiet) show_local_changes(&new->commit->object, &opts->diff_options); @@ -518,7 +524,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, 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", + fprintf(stderr, _("Can not do reflog for '%s'\n"), opts->new_orphan_branch); log_all_ref_updates = temp; return; @@ -540,19 +546,31 @@ static void update_refs_for_switch(struct checkout_opts *opts, strbuf_addf(&msg, "checkout: moving from %s to %s", old_desc ? old_desc : "(invalid)", new->name); - if (new->path) { + if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) { + /* Nothing to do. */ + } else if (opts->force_detach || !new->path) { /* No longer on any branch. */ + update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, + REF_NODEREF, DIE_ON_ERR); + if (!opts->quiet) { + if (old->path && advice_detached_head) + detach_advice(old->path, new->name); + describe_detached_head(_("HEAD is now at"), new->commit); + } + } else if (new->path) { /* Switch branches. */ create_symref("HEAD", new->path, msg.buf); if (!opts->quiet) { - if (old->path && !strcmp(new->path, old->path)) - fprintf(stderr, "Already on '%s'\n", - new->name); - else if (opts->new_branch) - fprintf(stderr, "Switched to%s branch '%s'\n", - opts->branch_exists ? " and reset" : " a new", + if (old->path && !strcmp(new->path, old->path)) { + fprintf(stderr, _("Already on '%s'\n"), new->name); - else - fprintf(stderr, "Switched to branch '%s'\n", + } else if (opts->new_branch) { + if (opts->branch_exists) + fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name); + else + fprintf(stderr, _("Switched to a new branch '%s'\n"), new->name); + } else { + fprintf(stderr, _("Switched to branch '%s'\n"), new->name); + } } if (old->path && old->name) { char log_file[PATH_MAX], ref_file[PATH_MAX]; @@ -562,21 +580,134 @@ static void update_refs_for_switch(struct checkout_opts *opts, 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); - if (!opts->quiet) { - if (old->path && advice_detached_head) - detach_advice(old->path, new->name); - describe_detached_head("HEAD is now at", new->commit); - } } remove_branch_state(); strbuf_release(&msg); - if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) + if (!opts->quiet && + (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD")))) report_tracking(new); } +struct rev_list_args { + int argc; + int alloc; + const char **argv; +}; + +static void add_one_rev_list_arg(struct rev_list_args *args, const char *s) +{ + ALLOC_GROW(args->argv, args->argc + 1, args->alloc); + args->argv[args->argc++] = s; +} + +static int add_one_ref_to_rev_list_arg(const char *refname, + const unsigned char *sha1, + int flags, + void *cb_data) +{ + add_one_rev_list_arg(cb_data, refname); + return 0; +} + +static int clear_commit_marks_from_one_ref(const char *refname, + const unsigned char *sha1, + int flags, + void *cb_data) +{ + struct commit *commit = lookup_commit_reference_gently(sha1, 1); + if (commit) + clear_commit_marks(commit, -1); + return 0; +} + +static void describe_one_orphan(struct strbuf *sb, struct commit *commit) +{ + parse_commit(commit); + strbuf_addstr(sb, " "); + strbuf_addstr(sb, + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + strbuf_addch(sb, ' '); + pp_commit_easy(CMIT_FMT_ONELINE, commit, sb); + strbuf_addch(sb, '\n'); +} + +#define ORPHAN_CUTOFF 4 +static void suggest_reattach(struct commit *commit, struct rev_info *revs) +{ + struct commit *c, *last = NULL; + struct strbuf sb = STRBUF_INIT; + int lost = 0; + while ((c = get_revision(revs)) != NULL) { + if (lost < ORPHAN_CUTOFF) + describe_one_orphan(&sb, c); + last = c; + lost++; + } + if (ORPHAN_CUTOFF < lost) { + int more = lost - ORPHAN_CUTOFF; + if (more == 1) + describe_one_orphan(&sb, last); + else + strbuf_addf(&sb, _(" ... and %d more.\n"), more); + } + + fprintf(stderr, + Q_( + /* The singular version */ + "Warning: you are leaving %d commit behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n" + "If you want to keep it by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch new_branch_name %s\n\n", + /* The plural version */ + "Warning: you are leaving %d commits behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n" + "If you want to keep them by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch new_branch_name %s\n\n", + /* Give ngettext() the count */ + lost), + lost, + sb.buf, + sha1_to_hex(commit->object.sha1)); + strbuf_release(&sb); +} + +/* + * We are about to leave commit that was at the tip of a detached + * HEAD. If it is not reachable from any ref, this is the last chance + * for the user to do so without resorting to reflog. + */ +static void orphaned_commit_warning(struct commit *commit) +{ + struct rev_list_args args = { 0, 0, NULL }; + struct rev_info revs; + + add_one_rev_list_arg(&args, "(internal)"); + add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1)); + add_one_rev_list_arg(&args, "--not"); + for_each_ref(add_one_ref_to_rev_list_arg, &args); + add_one_rev_list_arg(&args, "--"); + add_one_rev_list_arg(&args, NULL); + + init_revisions(&revs, NULL); + if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1) + die(_("internal error: only -- alone should have been left")); + if (prepare_revision_walk(&revs)) + die(_("internal error in revision walk")); + if (!(commit->object.flags & UNINTERESTING)) + suggest_reattach(commit, &revs); + else + describe_detached_head(_("Previous HEAD position was"), commit); + + clear_commit_marks(commit, -1); + for_each_ref(clear_commit_marks_from_one_ref, NULL); +} + static int switch_branches(struct checkout_opts *opts, struct branch_info *new) { int ret = 0; @@ -596,7 +727,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) new->name = "HEAD"; new->commit = old.commit; if (!new->commit) - die("You are on a branch yet to be born"); + die(_("You are on a branch yet to be born")); parse_commit(new->commit); } @@ -604,13 +735,8 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) if (ret) return ret; - /* - * If we were on a detached HEAD, but have now moved to - * a new commit, we want to mention the old commit once more - * to remind the user that it might be lost. - */ if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) - describe_detached_head("Previous HEAD position was", old.commit); + orphaned_commit_warning(old.commit); update_refs_for_switch(opts, &old, new); @@ -674,11 +800,123 @@ static const char *unique_tracking_name(const char *name) return NULL; } +static int parse_branchname_arg(int argc, const char **argv, + int dwim_new_local_branch_ok, + struct branch_info *new, + struct tree **source_tree, + unsigned char rev[20], + const char **new_branch) +{ + int argcount = 0; + unsigned char branch_rev[20]; + const char *arg; + int has_dash_dash; + + /* + * case 1: git checkout <ref> -- [<paths>] + * + * <ref> must be a valid tree, everything after the '--' must be + * a path. + * + * case 2: git checkout -- [<paths>] + * + * everything after the '--' must be paths. + * + * case 3: git checkout <something> [<paths>] + * + * With no paths, if <something> is a commit, that is to + * switch to the branch or detach HEAD at it. As a special case, + * if <something> is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. + * + * With no paths, if <something> is _not_ a commit, no -t nor -b + * was given, and there is a tracking branch whose name is + * <something> in one and only one remote, then this is a short-hand + * to fork local <something> from that remote-tracking branch. + * + * Otherwise <something> shall not be ambiguous. + * - If it's *only* a reference, treat it like case (1). + * - If it's only a path, treat it like case (2). + * - else: fail. + * + */ + if (!argc) + return 0; + + if (!strcmp(argv[0], "--")) /* case (2) */ + return 1; + + arg = argv[0]; + has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); + + if (!strcmp(arg, "-")) + arg = "@{-1}"; + + if (get_sha1_mb(arg, rev)) { + if (has_dash_dash) /* case (1) */ + die(_("invalid reference: %s"), arg); + if (dwim_new_local_branch_ok && + !check_filename(NULL, arg) && + argc == 1) { + const char *remote = unique_tracking_name(arg); + if (!remote || get_sha1(remote, rev)) + return argcount; + *new_branch = arg; + arg = remote; + /* DWIMmed to create local branch */ + } else { + return argcount; + } + } + + /* we can't end up being in (2) anymore, eat the argument */ + argcount++; + argv++; + argc--; + + new->name = arg; + setup_branch_path(new); + + if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK && + resolve_ref(new->path, branch_rev, 1, NULL)) + hashcpy(rev, branch_rev); + else + new->path = NULL; /* not an existing branch */ + + new->commit = lookup_commit_reference_gently(rev, 1); + if (!new->commit) { + /* not a commit */ + *source_tree = parse_tree_indirect(rev); + } else { + parse_commit(new->commit); + *source_tree = new->commit->tree; + } + + if (!*source_tree) /* case (1): want a tree */ + die(_("reference is not a tree: %s"), arg); + if (!has_dash_dash) {/* case (3 -> 1) */ + /* + * Do not complain the most common case + * git checkout branch + * even if there happen to be a file called 'branch'; + * it would be extremely annoying. + */ + if (argc) + verify_non_filename(NULL, arg); + } else { + argcount++; + argv++; + argc--; + } + + return argcount; +} + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; unsigned char rev[20]; - const char *arg; struct branch_info new; struct tree *source_tree = NULL; char *conflict_style = NULL; @@ -691,6 +929,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_STRING('B', NULL, &opts.new_branch_force, "branch", "create/reset and checkout a branch"), OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"), + OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"), OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch", BRANCH_TRACK_EXPLICIT), OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"), @@ -708,7 +947,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END(), }; - int has_dash_dash; memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); @@ -723,36 +961,42 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) /* we can assume from now on new_branch = !new_branch_force */ if (opts.new_branch && opts.new_branch_force) - die("-B cannot be used with -b"); + die(_("-B cannot be used with -b")); /* copy -B over to -b, so that we can just check the latter */ if (opts.new_branch_force) opts.new_branch = opts.new_branch_force; if (patch_mode && (opts.track > 0 || opts.new_branch - || opts.new_branch_log || opts.merge || opts.force)) - die ("--patch is incompatible with all other options"); + || opts.new_branch_log || opts.merge || opts.force + || opts.force_detach)) + die (_("--patch is incompatible with all other options")); + + if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch)) + die(_("--detach cannot be used with -b/-B/--orphan")); + if (opts.force_detach && 0 < opts.track) + die(_("--detach cannot be used with -t")); /* --track without -b should DWIM */ if (0 < opts.track && !opts.new_branch) { const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) - die ("--track needs a branch name"); + die (_("--track needs a branch name")); if (!prefixcmp(argv0, "refs/")) argv0 += 5; if (!prefixcmp(argv0, "remotes/")) argv0 += 8; argv0 = strchr(argv0, '/'); if (!argv0 || !argv0[1]) - die ("Missing branch name; try -b"); + die (_("Missing branch name; try -b")); opts.new_branch = argv0 + 1; } if (opts.new_orphan_branch) { if (opts.new_branch) - die("--orphan and -b|-B are mutually exclusive"); + die(_("--orphan and -b|-B are mutually exclusive")); if (opts.track > 0) - die("--orphan cannot be used with -t"); + die(_("--orphan cannot be used with -t")); opts.new_branch = opts.new_orphan_branch; } @@ -762,108 +1006,33 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) } if (opts.force && opts.merge) - die("git checkout: -f and -m are incompatible"); + die(_("git checkout: -f and -m are incompatible")); /* - * case 1: git checkout <ref> -- [<paths>] - * - * <ref> must be a valid tree, everything after the '--' must be - * a path. + * Extract branch name from command line arguments, so + * all that is left is pathspecs. * - * case 2: git checkout -- [<paths>] - * - * everything after the '--' must be paths. - * - * case 3: git checkout <something> [<paths>] - * - * With no paths, if <something> is a commit, that is to - * switch to the branch or detach HEAD at it. As a special case, - * if <something> is A...B (missing A or B means HEAD but you can - * omit at most one side), and if there is a unique merge base - * between A and B, A...B names that merge base. + * Handle * - * With no paths, if <something> is _not_ a commit, no -t nor -b - * was given, and there is a remote-tracking branch whose name is - * <something> in one and only one remote, then this is a short-hand - * to fork local <something> from that remote-tracking branch. - * - * Otherwise <something> shall not be ambiguous. - * - If it's *only* a reference, treat it like case (1). - * - If it's only a path, treat it like case (2). - * - else: fail. + * 1) git checkout <tree> -- [<paths>] + * 2) git checkout -- [<paths>] + * 3) git checkout <something> [<paths>] * + * including "last branch" syntax and DWIM-ery for names of + * remote branches, erroring out for invalid or ambiguous cases. */ if (argc) { - if (!strcmp(argv[0], "--")) { /* case (2) */ - argv++; - argc--; - goto no_reference; - } - - arg = argv[0]; - has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); - - if (!strcmp(arg, "-")) - arg = "@{-1}"; - - if (get_sha1_mb(arg, rev)) { - if (has_dash_dash) /* case (1) */ - die("invalid reference: %s", arg); - if (!patch_mode && - dwim_new_local_branch && - opts.track == BRANCH_TRACK_UNSPECIFIED && - !opts.new_branch && - !check_filename(NULL, arg) && - argc == 1) { - const char *remote = unique_tracking_name(arg); - if (!remote || get_sha1(remote, rev)) - goto no_reference; - opts.new_branch = arg; - arg = remote; - /* DWIMmed to create local branch */ - } - else - goto no_reference; - } - - /* we can't end up being in (2) anymore, eat the argument */ - argv++; - argc--; - - new.name = arg; - if ((new.commit = lookup_commit_reference_gently(rev, 1))) { - setup_branch_path(&new); - - if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) && - resolve_ref(new.path, rev, 1, NULL)) - ; - else - new.path = NULL; - parse_commit(new.commit); - source_tree = new.commit->tree; - } else - source_tree = parse_tree_indirect(rev); - - if (!source_tree) /* case (1): want a tree */ - die("reference is not a tree: %s", arg); - if (!has_dash_dash) {/* case (3 -> 1) */ - /* - * Do not complain the most common case - * git checkout branch - * even if there happen to be a file called 'branch'; - * it would be extremely annoying. - */ - if (argc) - verify_non_filename(NULL, arg); - } - else { - argv++; - argc--; - } + int dwim_ok = + !patch_mode && + dwim_new_local_branch && + opts.track == BRANCH_TRACK_UNSPECIFIED && + !opts.new_branch; + int n = parse_branchname_arg(argc, argv, dwim_ok, + &new, &source_tree, rev, &opts.new_branch); + argv += n; + argc -= n; } -no_reference: - if (opts.track == BRANCH_TRACK_UNSPECIFIED) opts.track = git_branch_track; @@ -871,7 +1040,7 @@ no_reference: const char **pathspec = get_pathspec(prefix, argv); if (!pathspec) - die("invalid path specification"); + die(_("invalid path specification")); if (patch_mode) return interactive_checkout(new.name, pathspec, &opts); @@ -879,14 +1048,17 @@ no_reference: /* Checkout paths */ if (opts.new_branch) { if (argc == 1) { - die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); + die(_("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?"), argv[0]); } else { - die("git checkout: updating paths is incompatible with switching branches."); + die(_("git checkout: updating paths is incompatible with switching branches.")); } } + if (opts.force_detach) + die(_("git checkout: --detach does not take a path argument")); + if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) - die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."); + die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.")); return checkout_paths(source_tree, pathspec, &opts); } @@ -897,22 +1069,22 @@ no_reference: if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; if (strbuf_check_branch_ref(&buf, opts.new_branch)) - die("git checkout: we do not like '%s' as a branch name.", + die(_("git checkout: we do not like '%s' as a branch name."), opts.new_branch); if (!get_sha1(buf.buf, rev)) { opts.branch_exists = 1; if (!opts.new_branch_force) - die("git checkout: branch %s already exists", + die(_("git checkout: branch %s already exists"), opts.new_branch); } strbuf_release(&buf); } if (new.name && !new.commit) { - die("Cannot switch branch to a non-commit."); + die(_("Cannot switch branch to a non-commit.")); } if (opts.writeout_stage) - die("--ours/--theirs is incompatible with switching branches."); + die(_("--ours/--theirs is incompatible with switching branches.")); return switch_branches(&opts, &new); } diff --git a/builtin/clean.c b/builtin/clean.c index 4a312abc6b..75697f7111 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -75,11 +75,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_IGNORED; if (ignored && ignored_only) - die("-x and -X cannot be used together"); - - if (!show_only && !force) - die("clean.requireForce %s to true and neither -n nor -f given; " - "refusing to clean", config_set ? "set" : "defaults"); + die(_("-x and -X cannot be used together")); + + if (!show_only && !force) { + if (config_set) + die(_("clean.requireForce set to true and neither -n nor -f given; " + "refusing to clean")); + else + die(_("clean.requireForce defaults to true and neither -n nor -f given; " + "refusing to clean")); + } if (force > 1) rm_flags = 0; @@ -87,7 +92,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); if (!ignored) setup_standard_excludes(&dir); @@ -146,20 +151,20 @@ int cmd_clean(int argc, const char **argv, const char *prefix) qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); if (show_only && (remove_directories || (matches == MATCHED_EXACTLY))) { - printf("Would remove %s\n", qname); + printf(_("Would remove %s\n"), qname); } else if (remove_directories || (matches == MATCHED_EXACTLY)) { if (!quiet) - printf("Removing %s\n", qname); + printf(_("Removing %s\n"), qname); if (remove_dir_recursively(&directory, rm_flags) != 0) { - warning("failed to remove %s", qname); + warning(_("failed to remove %s"), qname); errors++; } } else if (show_only) { - printf("Would not remove %s\n", qname); + printf(_("Would not remove %s\n"), qname); } else { - printf("Not removing %s\n", qname); + printf(_("Not removing %s\n"), qname); } strbuf_reset(&directory); } else { @@ -167,13 +172,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) continue; qname = quote_path_relative(ent->name, -1, &buf, prefix); if (show_only) { - printf("Would remove %s\n", qname); + printf(_("Would remove %s\n"), qname); continue; } else if (!quiet) { - printf("Removing %s\n", qname); + printf(_("Removing %s\n"), qname); } if (unlink(ent->name) != 0) { - warning("failed to remove %s", qname); + warning(_("failed to remove %s"), qname); errors++; } } diff --git a/builtin/clone.c b/builtin/clone.c index 61e0989b5a..f579794d9a 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -8,7 +8,7 @@ * Clone a repository into a different directory that does not yet exist. */ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "fetch-pack.h" #include "refs.h" @@ -42,6 +42,7 @@ 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 const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress; @@ -66,10 +67,10 @@ static struct option builtin_clone_options[] = { "setup as shared repository"), OPT_BOOLEAN(0, "recursive", &option_recursive, "initialize submodules in the clone"), - OPT_BOOLEAN(0, "recurse_submodules", &option_recursive, + OPT_BOOLEAN(0, "recurse-submodules", &option_recursive, "initialize submodules in the clone"), - OPT_STRING(0, "template", &option_template, "path", - "path the template repository"), + OPT_STRING(0, "template", &option_template, "template-directory", + "directory from which templates will be used"), OPT_STRING(0, "reference", &option_reference, "repo", "reference repository"), OPT_STRING('o', "origin", &option_origin, "branch", @@ -80,6 +81,8 @@ static struct option builtin_clone_options[] = { "path to git-upload-pack on the remote"), OPT_STRING(0, "depth", &option_depth, "depth", "create a shallow clone of that depth"), + OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", + "separate git dir from working tree"), OPT_END() }; @@ -100,7 +103,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) path = mkpath("%s%s", repo, suffix[i]); if (is_directory(path)) { *is_bundle = 0; - return xstrdup(make_nonrelative_path(path)); + return xstrdup(absolute_path(path)); } } @@ -109,7 +112,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) path = mkpath("%s%s", repo, bundle_suffix[i]); if (!stat(path, &st) && S_ISREG(st.st_mode)) { *is_bundle = 1; - return xstrdup(make_nonrelative_path(path)); + return xstrdup(absolute_path(path)); } } @@ -203,12 +206,12 @@ static void setup_reference(const char *repo) struct transport *transport; const struct ref *extra; - ref_git = make_absolute_path(option_reference); + ref_git = real_path(option_reference); if (is_directory(mkpath("%s/.git/objects", ref_git))) ref_git = mkpath("%s/.git", ref_git); else if (!is_directory(mkpath("%s/objects", ref_git))) - die("reference repository '%s' is not a local directory.", + die(_("reference repository '%s' is not a local directory."), option_reference); ref_git_copy = xstrdup(ref_git); @@ -235,15 +238,15 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) dir = opendir(src->buf); if (!dir) - die_errno("failed to open '%s'", src->buf); + die_errno(_("failed to open '%s'"), src->buf); if (mkdir(dest->buf, 0777)) { if (errno != EEXIST) - die_errno("failed to create directory '%s'", dest->buf); + die_errno(_("failed to create directory '%s'"), dest->buf); else if (stat(dest->buf, &buf)) - die_errno("failed to stat '%s'", dest->buf); + die_errno(_("failed to stat '%s'"), dest->buf); else if (!S_ISDIR(buf.st_mode)) - die("%s exists and is not a directory", dest->buf); + die(_("%s exists and is not a directory"), dest->buf); } strbuf_addch(src, '/'); @@ -257,7 +260,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) strbuf_setlen(dest, dest_len); strbuf_addstr(dest, de->d_name); if (stat(src->buf, &buf)) { - warning ("failed to stat %s\n", src->buf); + warning (_("failed to stat %s\n"), src->buf); continue; } if (S_ISDIR(buf.st_mode)) { @@ -267,16 +270,16 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) } if (unlink(dest->buf) && errno != ENOENT) - die_errno("failed to unlink '%s'", dest->buf); + die_errno(_("failed to unlink '%s'"), dest->buf); if (!option_no_hardlinks) { if (!link(src->buf, dest->buf)) continue; if (option_local) - die_errno("failed to create link '%s'", dest->buf); + die_errno(_("failed to create link '%s'"), dest->buf); option_no_hardlinks = 1; } if (copy_file_with_time(dest->buf, src->buf, 0666)) - die_errno("failed to copy file to '%s'", dest->buf); + die_errno(_("failed to copy file to '%s'"), dest->buf); } closedir(dir); } @@ -305,7 +308,7 @@ static const struct ref *clone_local(const char *src_repo, ret = transport_get_remote_refs(transport); transport_disconnect(transport); if (0 <= option_verbosity) - printf("done.\n"); + printf(_("done.\n")); return ret; } @@ -383,15 +386,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix) junk_pid = getpid(); + packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); if (argc > 2) - usage_msg_opt("Too many arguments.", + usage_msg_opt(_("Too many arguments."), builtin_clone_usage, builtin_clone_options); if (argc == 0) - usage_msg_opt("You must specify a repository to clone.", + usage_msg_opt(_("You must specify a repository to clone."), builtin_clone_usage, builtin_clone_options); if (option_mirror) @@ -399,7 +403,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_bare) { if (option_origin) - die("--bare and --origin %s options are incompatible.", + die(_("--bare and --origin %s options are incompatible."), option_origin); option_no_checkout = 1; } @@ -411,14 +415,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (path) - repo = xstrdup(make_nonrelative_path(repo_name)); + repo = xstrdup(absolute_path(repo_name)); else if (!strchr(repo_name, ':')) - repo = xstrdup(make_absolute_path(repo_name)); + die(_("repository '%s' does not exist"), repo_name); else repo = repo_name; is_local = path && !is_bundle; if (is_local && option_depth) - warning("--depth is ignored in local clones; use file:// instead."); + warning(_("--depth is ignored in local clones; use file:// instead.")); if (argc == 2) dir = xstrdup(argv[1]); @@ -428,8 +432,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dest_exists = !stat(dir, &buf); if (dest_exists && !is_empty_dir(dir)) - die("destination path '%s' already exists and is not " - "an empty directory.", dir); + die(_("destination path '%s' already exists and is not " + "an empty directory."), dir); strbuf_addf(&reflog_msg, "clone: from %s", repo); @@ -438,7 +442,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) else { work_tree = getenv("GIT_WORK_TREE"); if (work_tree && !stat(work_tree, &buf)) - die("working tree '%s' already exists.", work_tree); + die(_("working tree '%s' already exists."), work_tree); } if (option_bare || work_tree) @@ -451,10 +455,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (!option_bare) { junk_work_tree = work_tree; if (safe_create_leading_directories_const(work_tree) < 0) - die_errno("could not create leading directories of '%s'", + die_errno(_("could not create leading directories of '%s'"), work_tree); if (!dest_exists && mkdir(work_tree, 0755)) - die_errno("could not create work tree dir '%s'.", + die_errno(_("could not create work tree dir '%s'."), work_tree); set_git_work_tree(work_tree); } @@ -465,12 +469,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix) setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1); if (safe_create_leading_directories_const(git_dir) < 0) - die("could not create leading directories of '%s'", git_dir); - set_git_dir(make_absolute_path(git_dir)); + die(_("could not create leading directories of '%s'"), git_dir); - if (0 <= option_verbosity) - printf("Cloning into %s%s...\n", - option_bare ? "bare repository " : "", dir); + set_git_dir_init(git_dir, real_git_dir, 0); + if (real_git_dir) + git_dir = real_git_dir; + + if (0 <= option_verbosity) { + if (option_bare) + printf(_("Cloning into bare repository %s...\n"), dir); + else + printf(_("Cloning into %s...\n"), dir); + } init_db(option_template, INIT_DB_QUIET); /* @@ -527,7 +537,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport = transport_get(remote, remote->url[0]); if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to clone %s", transport->url); + die(_("Don't know how to clone %s"), transport->url); transport_set_option(transport, TRANS_OPT_KEEP, "yes"); @@ -566,8 +576,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_release(&head); if (!our_head_points_at) { - warning("Remote branch %s not found in " - "upstream %s, using HEAD instead", + warning(_("Remote branch %s not found in " + "upstream %s, using HEAD instead"), option_branch, option_origin); our_head_points_at = remote_head_points_at; } @@ -576,7 +586,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) our_head_points_at = remote_head_points_at; } else { - warning("You appear to have cloned an empty repository."); + warning(_("You appear to have cloned an empty repository.")); our_head_points_at = NULL; remote_head_points_at = NULL; remote_head = NULL; @@ -618,8 +628,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } else { /* Nothing to checkout out */ if (!option_no_checkout) - warning("remote HEAD refers to nonexistent ref, " - "unable to checkout.\n"); + warning(_("remote HEAD refers to nonexistent ref, " + "unable to checkout.\n")); option_no_checkout = 1; } @@ -655,7 +665,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (write_cache(fd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), sha1_to_hex(our_head_points_at->old_sha1), "1", diff --git a/builtin/commit.c b/builtin/commit.c index 03cff5af63..5286432f39 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -38,7 +38,7 @@ static const char * const builtin_status_usage[] = { }; static const char implicit_ident_advice[] = -"Your name and email address were configured automatically based\n" +N_("Your name and email address were configured automatically based\n" "on your username and hostname. Please check that they are accurate.\n" "You can suppress this message by setting them explicitly:\n" "\n" @@ -47,16 +47,24 @@ static const char implicit_ident_advice[] = "\n" "After doing this, you may fix the identity used for this commit with:\n" "\n" -" git commit --amend --reset-author\n"; +" git commit --amend --reset-author\n"); static const char empty_amend_advice[] = -"You asked to amend the most recent commit, but doing so would make\n" +N_("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"; +"remove the commit entirely with \"git reset HEAD^\".\n"); + +static const char empty_cherry_pick_advice[] = +N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\n" +"If you wish to commit it anyway, use:\n" +"\n" +" git commit --allow-empty\n" +"\n" +"Otherwise, please use 'git reset'\n"); static unsigned char head_sha1[20]; -static char *use_message_buffer; +static const char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; static struct lock_file index_lock; /* real index */ static struct lock_file false_lock; /* used only for partial commits */ @@ -68,9 +76,14 @@ static enum { static const char *logfile, *force_author; static const char *template_file; +/* + * The _message variables are commit names from which to take + * the commit message and/or authorship. + */ +static const char *author_message, *author_message_buffer; static char *edit_message, *use_message; static char *fixup_message, *squash_message; -static int all, edit_flag, also, interactive, only, amend, signoff; +static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff; static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; @@ -88,7 +101,8 @@ static enum { } cleanup_mode; static char *cleanup_arg; -static int use_editor = 1, initial_commit, in_merge, include_status = 1; +static enum commit_whence whence; +static int use_editor = 1, initial_commit, include_status = 1; static int show_ignored_in_status; static const char *only_include_assumed; static struct strbuf message; @@ -118,14 +132,14 @@ static struct option builtin_commit_options[] = { OPT__VERBOSE(&verbose, "show diff in commit message template"), OPT_GROUP("Commit message options"), - OPT_FILENAME('F', "file", &logfile, "read log from file"), - OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), - OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"), - OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), - OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), - OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), - OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"), - OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"), + OPT_FILENAME('F', "file", &logfile, "read message from file"), + OPT_STRING(0, "author", &force_author, "author", "override author for commit"), + OPT_STRING(0, "date", &force_date, "date", "override date for commit"), + OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m), + OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"), + OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"), + OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"), + OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"), OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_FILENAME('t', "template", &template_file, "use specified template file"), @@ -138,6 +152,7 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN('a', "all", &all, "commit all changed files"), OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"), OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), + OPT_BOOLEAN('p', "patch", &patch_interactive, "interactively add changes"), OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), @@ -145,12 +160,12 @@ static struct option builtin_commit_options[] = { 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), + "machine-readable output", STATUS_FORMAT_PORCELAIN), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), 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" }, + { 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, @@ -163,6 +178,36 @@ static struct option builtin_commit_options[] = { OPT_END() }; +static void determine_whence(struct wt_status *s) +{ + if (file_exists(git_path("MERGE_HEAD"))) + whence = FROM_MERGE; + else if (file_exists(git_path("CHERRY_PICK_HEAD"))) + whence = FROM_CHERRY_PICK; + else + whence = FROM_COMMIT; + if (s) + s->whence = whence; +} + +static const char *whence_s(void) +{ + char *s = ""; + + switch (whence) { + case FROM_COMMIT: + break; + case FROM_MERGE: + s = "merge"; + break; + case FROM_CHERRY_PICK: + s = "cherry-pick"; + break; + } + + return s; +} + static void rollback_index_files(void) { switch (commit_style) { @@ -243,7 +288,7 @@ static void add_remove_files(struct string_list *list) if (!lstat(p->string, &st)) { if (add_to_cache(p->string, &st, 0)) - die("updating files failed"); + die(_("updating files failed")); } else remove_file_from_cache(p->string); } @@ -270,7 +315,7 @@ static void create_base_index(void) opts.fn = oneway_merge; tree = parse_tree_indirect(head_sha1); if (!tree) - die("failed to unpack HEAD tree object"); + die(_("failed to unpack HEAD tree object")); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -292,24 +337,44 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int int fd; struct string_list partial; const char **pathspec = NULL; + char *old_index_env = NULL; int refresh_flags = REFRESH_QUIET; if (is_status) refresh_flags |= REFRESH_UNMERGED; - if (interactive) { - if (interactive_add(argc, argv, prefix) != 0) - die("interactive add failed"); - if (read_cache_preload(NULL) < 0) - die("index file corrupt"); - commit_style = COMMIT_AS_IS; - return get_index_file(); - } if (*argv) pathspec = get_pathspec(prefix, argv); if (read_cache_preload(pathspec) < 0) - die("index file corrupt"); + die(_("index file corrupt")); + + if (interactive) { + fd = hold_locked_index(&index_lock, 1); + + refresh_cache_or_die(refresh_flags); + + if (write_cache(fd, active_cache, active_nr) || + close_lock_file(&index_lock)) + die(_("unable to create temporary index")); + + old_index_env = getenv(INDEX_ENVIRONMENT); + setenv(INDEX_ENVIRONMENT, index_lock.filename, 1); + + if (interactive_add(argc, argv, prefix, patch_interactive) != 0) + die(_("interactive add failed")); + + if (old_index_env && *old_index_env) + setenv(INDEX_ENVIRONMENT, old_index_env, 1); + else + unsetenv(INDEX_ENVIRONMENT); + + discard_cache(); + read_cache_from(index_lock.filename); + + commit_style = COMMIT_NORMAL; + return index_lock.filename; + } /* * Non partial, non as-is commit. @@ -329,7 +394,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int refresh_cache_or_die(refresh_flags); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) - die("unable to write new_index file"); + die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; return index_lock.filename; } @@ -349,7 +414,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int if (active_cache_changed) { if (write_cache(fd, active_cache, active_nr) || commit_locked_index(&index_lock)) - die("unable to write new_index file"); + die(_("unable to write new_index file")); } else { rollback_lock_file(&index_lock); } @@ -378,8 +443,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int */ commit_style = COMMIT_PARTIAL; - if (in_merge) - die("cannot do a partial commit during a merge."); + if (whence != FROM_COMMIT) + die(_("cannot do a partial commit during a %s."), whence_s()); memset(&partial, 0, sizeof(partial)); partial.strdup_strings = 1; @@ -388,14 +453,14 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int discard_cache(); if (read_cache() < 0) - die("cannot read the index"); + die(_("cannot read the index")); fd = hold_locked_index(&index_lock, 1); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) - die("unable to write new_index file"); + die(_("unable to write new_index file")); fd = hold_lock_file_for_update(&false_lock, git_path("next-index-%"PRIuMAX, @@ -408,7 +473,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int if (write_cache(fd, active_cache, active_nr) || close_lock_file(&false_lock)) - die("unable to write temporary index file"); + die(_("unable to write temporary index file")); discard_cache(); read_cache_from(false_lock.filename); @@ -455,7 +520,7 @@ static int is_a_merge(const unsigned char *sha1) { struct commit *commit = lookup_commit(sha1); if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); + die(_("could not parse HEAD commit")); return !!(commit->parents && commit->parents->next); } @@ -469,18 +534,18 @@ static void determine_author_info(struct strbuf *author_ident) email = getenv("GIT_AUTHOR_EMAIL"); date = getenv("GIT_AUTHOR_DATE"); - if (use_message && !renew_authorship) { + if (author_message) { const char *a, *lb, *rb, *eol; - a = strstr(use_message_buffer, "\nauthor "); + a = strstr(author_message_buffer, "\nauthor "); if (!a) - die("invalid commit: %s", use_message); + die(_("invalid commit: %s"), author_message); lb = strchrnul(a + strlen("\nauthor "), '<'); rb = strchrnul(lb, '>'); eol = strchrnul(rb, '\n'); if (!*lb || !*rb || !*eol) - die("invalid commit: %s", use_message); + die(_("invalid commit: %s"), author_message); if (lb == a + strlen("\nauthor ")) /* \nauthor <foo@example.com> */ @@ -498,7 +563,7 @@ static void determine_author_info(struct strbuf *author_ident) const char *rb = strchr(force_author, '>'); if (!lb || !rb) - die("malformed --author parameter"); + die(_("malformed --author parameter")); name = xstrndup(force_author, lb - force_author); email = xstrndup(lb + 2, rb - (lb + 2)); } @@ -554,7 +619,7 @@ static char *cut_ident_timestamp_part(char *string) { char *ket = strrchr(string, '>'); if (!ket || ket[1] != ' ') - die("Malformed ident string: '%s'", string); + die(_("Malformed ident string: '%s'"), string); *++ket = '\0'; return ket; } @@ -568,10 +633,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int commitable, saved_color_setting; struct strbuf sb = STRBUF_INIT; char *buffer; - FILE *fp; const char *hook_arg1 = NULL; const char *hook_arg2 = NULL; int ident_shown = 0; + int clean_message_contents = (cleanup_mode != CLEANUP_NONE); if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; @@ -588,7 +653,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct commit *c; c = lookup_commit_reference_by_name(squash_message); if (!c) - die("could not lookup commit %s", squash_message); + die(_("could not lookup commit %s"), squash_message); ctx.output_encoding = get_commit_output_encoding(); format_commit_message(c, "squash! %s\n\n", &sb, &ctx); @@ -600,19 +665,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix, hook_arg1 = "message"; } else if (logfile && !strcmp(logfile, "-")) { if (isatty(0)) - fprintf(stderr, "(reading log message from standard input)\n"); + fprintf(stderr, _("(reading log message from standard input)\n")); if (strbuf_read(&sb, 0, 0) < 0) - die_errno("could not read log from standard input"); + die_errno(_("could not read log from standard input")); hook_arg1 = "message"; } else if (logfile) { if (strbuf_read_file(&sb, logfile, 0) < 0) - die_errno("could not read log file '%s'", + die_errno(_("could not read log file '%s'"), logfile); hook_arg1 = "message"; } else if (use_message) { buffer = strstr(use_message_buffer, "\n\n"); if (!buffer || buffer[2] == '\0') - die("commit has empty message"); + die(_("commit has empty message")); strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); hook_arg1 = "commit"; hook_arg2 = use_message; @@ -621,31 +686,36 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct commit *commit; commit = lookup_commit_reference_by_name(fixup_message); if (!commit) - die("could not lookup commit %s", fixup_message); + die(_("could not lookup commit %s"), fixup_message); ctx.output_encoding = get_commit_output_encoding(); format_commit_message(commit, "fixup! %s\n\n", &sb, &ctx); hook_arg1 = "message"; } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) - die_errno("could not read MERGE_MSG"); + die_errno(_("could not read MERGE_MSG")); hook_arg1 = "merge"; } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) - die_errno("could not read SQUASH_MSG"); + die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; - } else if (template_file && !stat(template_file, &statbuf)) { + } else if (template_file) { if (strbuf_read_file(&sb, template_file, 0) < 0) - die_errno("could not read '%s'", template_file); + die_errno(_("could not read '%s'"), template_file); hook_arg1 = "template"; + clean_message_contents = 0; } /* - * This final case does not modify the template message, - * it just sets the argument to the prepare-commit-msg hook. + * The remaining cases don't modify the template message, but + * just set the argument(s) to the prepare-commit-msg hook. */ - else if (in_merge) + else if (whence == FROM_MERGE) hook_arg1 = "merge"; + else if (whence == FROM_CHERRY_PICK) { + hook_arg1 = "commit"; + hook_arg2 = "CHERRY_PICK_HEAD"; + } if (squash_message) { /* @@ -657,11 +727,11 @@ static int prepare_to_commit(const char *index_file, const char *prefix, hook_arg2 = ""; } - fp = fopen(git_path(commit_editmsg), "w"); - if (fp == NULL) - die_errno("could not open '%s'", git_path(commit_editmsg)); + s->fp = fopen(git_path(commit_editmsg), "w"); + if (s->fp == NULL) + die_errno(_("could not open '%s'"), git_path(commit_editmsg)); - if (cleanup_mode != CLEANUP_NONE) + if (clean_message_contents) stripspace(&sb, 0); if (signoff) { @@ -682,8 +752,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_release(&sob); } - if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) - die_errno("could not write commit template"); + if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) + die_errno(_("could not write commit template")); strbuf_release(&sb); @@ -694,55 +764,59 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_addstr(&committer_ident, git_committer_info(0)); if (use_editor && include_status) { char *ai_tmp, *ci_tmp; - if (in_merge) - fprintf(fp, - "#\n" - "# It looks like you may be committing a MERGE.\n" - "# If this is not correct, please remove the file\n" - "# %s\n" - "# and try again.\n" - "#\n", - git_path("MERGE_HEAD")); - - fprintf(fp, - "\n" - "# Please enter the commit message for your changes."); + if (whence != FROM_COMMIT) + status_printf_ln(s, GIT_COLOR_NORMAL, + _("\n" + "It looks like you may be committing a %s.\n" + "If this is not correct, please remove the file\n" + " %s\n" + "and try again.\n" + ""), + whence_s(), + git_path(whence == FROM_MERGE + ? "MERGE_HEAD" + : "CHERRY_PICK_HEAD")); + + fprintf(s->fp, "\n"); + status_printf(s, GIT_COLOR_NORMAL, + _("Please enter the commit message for your changes.")); if (cleanup_mode == CLEANUP_ALL) - fprintf(fp, - " Lines starting\n" - "# with '#' will be ignored, and an empty" - " message aborts the commit.\n"); + status_printf_more(s, GIT_COLOR_NORMAL, + _(" Lines starting\n" + "with '#' will be ignored, and an empty" + " message aborts the commit.\n")); else /* CLEANUP_SPACE, that is. */ - fprintf(fp, - " Lines starting\n" - "# with '#' will be kept; you may remove them" + status_printf_more(s, GIT_COLOR_NORMAL, + _(" Lines starting\n" + "with '#' will be kept; you may remove them" " yourself if you want to.\n" - "# An empty message aborts the commit.\n"); + "An empty message aborts the commit.\n")); if (only_include_assumed) - fprintf(fp, "# %s\n", only_include_assumed); + status_printf_ln(s, GIT_COLOR_NORMAL, + "%s", only_include_assumed); ai_tmp = cut_ident_timestamp_part(author_ident->buf); ci_tmp = cut_ident_timestamp_part(committer_ident.buf); if (strcmp(author_ident->buf, committer_ident.buf)) - fprintf(fp, - "%s" - "# Author: %s\n", - ident_shown++ ? "" : "#\n", + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Author: %s"), + ident_shown++ ? "" : "\n", author_ident->buf); if (!user_ident_sufficiently_given()) - fprintf(fp, - "%s" - "# Committer: %s\n", - ident_shown++ ? "" : "#\n", + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Committer: %s"), + ident_shown++ ? "" : "\n", committer_ident.buf); if (ident_shown) - fprintf(fp, "#\n"); + status_printf_ln(s, GIT_COLOR_NORMAL, ""); saved_color_setting = s->use_color; s->use_color = 0; - commitable = run_status(fp, index_file, prefix, 1, s); + commitable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; *ai_tmp = ' '; @@ -752,7 +826,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, const char *parent = "HEAD"; if (!active_nr && read_cache() < 0) - die("Cannot read index"); + die(_("Cannot read index")); if (amend) parent = "HEAD^1"; @@ -764,13 +838,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } strbuf_release(&committer_ident); - fclose(fp); + fclose(s->fp); - if (!commitable && !in_merge && !allow_empty && + /* + * Reject an attempt to record a non-merge empty commit without + * explicit --allow-empty. In the cherry-pick case, it may be + * empty due to conflict resolution, which the user should okay. + */ + if (!commitable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(head_sha1))) { run_status(stdout, index_file, prefix, 0, s); if (amend) - fputs(empty_amend_advice, stderr); + fputs(_(empty_amend_advice), stderr); + else if (whence == FROM_CHERRY_PICK) + fputs(_(empty_cherry_pick_advice), stderr); return 0; } @@ -785,7 +866,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, active_cache_tree = cache_tree(); if (cache_tree_update(active_cache_tree, active_cache, active_nr, 0, 0) < 0) { - error("Error building trees"); + error(_("Error building trees")); return 0; } @@ -800,7 +881,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); if (launch_editor(git_path(commit_editmsg), NULL, env)) { fprintf(stderr, - "Please supply the message using either -m or -F option.\n"); + _("Please supply the message using either -m or -F option.\n")); exit(1); } } @@ -880,7 +961,7 @@ static const char *find_author_by_nickname(const char *name) format_commit_message(commit, "%an <%ae>", &buf, &ctx); return strbuf_detach(&buf, NULL); } - die("No existing author found with '%s'", name); + die(_("No existing author found with '%s'"), name); } @@ -895,7 +976,29 @@ static void handle_untracked_files_arg(struct wt_status *s) else if (!strcmp(untracked_files_arg, "all")) s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; else - die("Invalid untracked files mode '%s'", untracked_files_arg); + die(_("Invalid untracked files mode '%s'"), untracked_files_arg); +} + +static const char *read_commit_message(const char *name) +{ + const char *out_enc, *out; + struct commit *commit; + + commit = lookup_commit_reference_by_name(name); + if (!commit) + die(_("could not lookup commit %s"), name); + out_enc = get_commit_output_encoding(); + out = logmsg_reencode(commit, out_enc); + + /* + * If we failed to reencode the buffer, just copy it + * byte for byte so the user can try to fix it up. + * This also handles the case where input and output + * encodings are identical. + */ + if (out == NULL) + out = xstrdup(commit->buffer); + return out; } static int parse_and_validate_options(int argc, const char *argv[], @@ -912,7 +1015,7 @@ static int parse_and_validate_options(int argc, const char *argv[], force_author = find_author_by_nickname(force_author); if (force_author && renew_authorship) - die("Using both --reset-author and --author does not make sense"); + die(_("Using both --reset-author and --author does not make sense")); if (logfile || message.len || use_message || fixup_message) use_editor = 0; @@ -926,11 +1029,11 @@ static int parse_and_validate_options(int argc, const char *argv[], /* Sanity check options */ if (amend && initial_commit) - die("You have nothing to amend."); - if (amend && in_merge) - die("You are in the middle of a merge -- cannot amend."); + die(_("You have nothing to amend.")); + if (amend && whence != FROM_COMMIT) + die(_("You are in the middle of a %s -- cannot amend."), whence_s()); if (fixup_message && squash_message) - die("Options --squash and --fixup cannot be used together"); + die(_("Options --squash and --fixup cannot be used together")); if (use_message) f++; if (edit_message) @@ -940,43 +1043,38 @@ static int parse_and_validate_options(int argc, const char *argv[], if (logfile) f++; if (f > 1) - die("Only one of -c/-C/-F/--fixup can be used."); + die(_("Only one of -c/-C/-F/--fixup can be used.")); if (message.len && f > 0) - die("Option -m cannot be combined with -c/-C/-F/--fixup."); + die((_("Option -m cannot be combined with -c/-C/-F/--fixup."))); if (edit_message) use_message = edit_message; if (amend && !use_message && !fixup_message) use_message = "HEAD"; - if (!use_message && renew_authorship) - die("--reset-author can be used only with -C, -c or --amend."); + if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship) + die(_("--reset-author can be used only with -C, -c or --amend.")); if (use_message) { - const char *out_enc; - struct commit *commit; - - commit = lookup_commit_reference_by_name(use_message); - if (!commit) - die("could not lookup commit %s", use_message); - out_enc = get_commit_output_encoding(); - use_message_buffer = logmsg_reencode(commit, out_enc); - - /* - * If we failed to reencode the buffer, just copy it - * byte for byte so the user can try to fix it up. - * This also handles the case where input and output - * encodings are identical. - */ - if (use_message_buffer == NULL) - use_message_buffer = xstrdup(commit->buffer); + use_message_buffer = read_commit_message(use_message); + if (!renew_authorship) { + author_message = use_message; + author_message_buffer = use_message_buffer; + } + } + if (whence == FROM_CHERRY_PICK && !renew_authorship) { + author_message = "CHERRY_PICK_HEAD"; + author_message_buffer = read_commit_message(author_message); } + if (patch_interactive) + interactive = 1; + if (!!also + !!only + !!all + !!interactive > 1) - die("Only one of --include/--only/--all/--interactive can be used."); + die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); if (argc == 0 && (also || (only && !amend))) - die("No paths with --include/--only does not make sense."); + die(_("No paths with --include/--only does not make sense.")); if (argc == 0 && only && amend) - only_include_assumed = "Clever... amending the last one with dirty index."; + only_include_assumed = _("Clever... amending the last one with dirty index."); if (argc > 0 && !also && !only) - only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths..."; + only_include_assumed = _("Explicit paths specified without -i nor -o; assuming --only paths..."); if (!cleanup_arg || !strcmp(cleanup_arg, "default")) cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE; else if (!strcmp(cleanup_arg, "verbatim")) @@ -986,14 +1084,12 @@ static int parse_and_validate_options(int argc, const char *argv[], else if (!strcmp(cleanup_arg, "strip")) cleanup_mode = CLEANUP_ALL; else - die("Invalid cleanup mode %s", cleanup_arg); + die(_("Invalid cleanup mode %s"), cleanup_arg); handle_untracked_files_arg(s); if (all && argc > 0) - die("Paths with -a does not make sense."); - else if (interactive && argc > 0) - die("Paths with --interactive does not make sense."); + die(_("Paths with -a does not make sense.")); if (null_termination && status_format == STATUS_FORMAT_LONG) status_format = STATUS_FORMAT_PORCELAIN; @@ -1074,7 +1170,7 @@ static int git_status_config(const char *k, const char *v, void *cb) else if (!strcmp(v, "all")) s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; else - return error("Invalid untracked files mode '%s'", v); + return error(_("Invalid untracked files mode '%s'"), v); return 0; } return git_diff_ui_config(k, v, NULL); @@ -1092,7 +1188,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('b', "branch", &status_show_branch, "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, - "show porcelain output format", + "machine-readable output", STATUS_FORMAT_PORCELAIN), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), @@ -1117,7 +1213,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) wt_status_prepare(&s); gitmodules_config(); git_config(git_status_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); + determine_whence(&s); argc = parse_options(argc, argv, prefix, builtin_status_options, builtin_status_usage, 0); @@ -1131,16 +1227,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) 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); - } + if (0 <= fd) + update_index_if_able(&the_index, &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); @@ -1180,9 +1270,9 @@ static void print_summary(const char *prefix, const unsigned char *sha1) commit = lookup_commit(sha1); if (!commit) - die("couldn't look up newly created commit"); + die(_("couldn't look up newly created commit")); if (!commit || parse_commit(commit)) - die("could not parse newly created commit"); + die(_("could not parse newly created commit")); strbuf_addstr(&format, "format:%h] %s"); @@ -1197,7 +1287,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { strbuf_addch(&format, '\n'); - strbuf_addstr(&format, implicit_ident_advice); + strbuf_addstr(&format, _(implicit_ident_advice)); } } strbuf_release(&author_ident); @@ -1215,7 +1305,6 @@ static void print_summary(const char *prefix, const unsigned char *sha1) get_commit_format(format.buf, &rev); rev.always_show_header = 0; rev.diffopt.detect_rename = 1; - rev.diffopt.rename_limit = 100; rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); @@ -1223,9 +1312,9 @@ static void print_summary(const char *prefix, const unsigned char *sha1) !prefixcmp(head, "refs/heads/") ? head + 11 : !strcmp(head, "HEAD") ? - "detached HEAD" : + _("detached HEAD") : head, - initial_commit ? " (root-commit)" : ""); + initial_commit ? _(" (root-commit)") : ""); if (!log_tree_commit(&rev, commit)) { rev.always_show_header = 1; @@ -1302,8 +1391,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) wt_status_prepare(&s); git_config(git_commit_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); - s.in_merge = in_merge; + determine_whence(&s); if (s.use_color == -1) s.use_color = git_use_color_default; @@ -1336,11 +1424,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix) reflog_msg = "commit (amend)"; commit = lookup_commit(head_sha1); if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); + die(_("could not parse HEAD commit")); for (c = commit->parents; c; c = c->next) pptr = &commit_list_insert(c->item, pptr)->next; - } else if (in_merge) { + } else if (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; FILE *fp; @@ -1349,19 +1437,19 @@ int cmd_commit(int argc, const char **argv, const char *prefix) pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; fp = fopen(git_path("MERGE_HEAD"), "r"); if (fp == NULL) - die_errno("could not open '%s' for reading", + die_errno(_("could not open '%s' for reading"), git_path("MERGE_HEAD")); while (strbuf_getline(&m, fp, '\n') != EOF) { unsigned char sha1[20]; if (get_sha1_hex(m.buf, sha1) < 0) - die("Corrupt MERGE_HEAD file (%s)", m.buf); + die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next; } fclose(fp); strbuf_release(&m); if (!stat(git_path("MERGE_MODE"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0) - die_errno("could not read MERGE_MODE"); + die_errno(_("could not read MERGE_MODE")); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; } @@ -1369,7 +1457,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) parents = reduce_heads(parents); } else { if (!reflog_msg) - reflog_msg = "commit"; + reflog_msg = (whence == FROM_CHERRY_PICK) + ? "commit (cherry-pick)" + : "commit"; pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; } @@ -1378,7 +1468,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { int saved_errno = errno; rollback_index_files(); - die("could not read commit message: %s", strerror(saved_errno)); + die(_("could not read commit message: %s"), strerror(saved_errno)); } /* Truncate the message just before the diff, if any. */ @@ -1392,14 +1482,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix) stripspace(&sb, cleanup_mode == CLEANUP_ALL); if (message_is_empty(&sb) && !allow_empty_message) { rollback_index_files(); - fprintf(stderr, "Aborting commit due to empty commit message.\n"); + fprintf(stderr, _("Aborting commit due to empty commit message.\n")); exit(1); } if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1, author_ident.buf)) { rollback_index_files(); - die("failed to write commit object"); + die(_("failed to write commit object")); } strbuf_release(&author_ident); @@ -1417,22 +1507,23 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!ref_lock) { rollback_index_files(); - die("cannot lock HEAD ref"); + die(_("cannot lock HEAD ref")); } if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) { rollback_index_files(); - die("cannot update HEAD ref"); + die(_("cannot update HEAD ref")); } + unlink(git_path("CHERRY_PICK_HEAD")); unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_MSG")); unlink(git_path("MERGE_MODE")); unlink(git_path("SQUASH_MSG")); if (commit_index_files()) - die ("Repository has been updated, but unable to write\n" + die (_("Repository has been updated, but unable to write\n" "new_index file. Check that disk is not full or quota is\n" - "not exceeded, and then \"git reset HEAD\" to recover."); + "not exceeded, and then \"git reset HEAD\" to recover.")); rerere(0); run_hook(get_index_file(), "post-commit", NULL); diff --git a/builtin/config.c b/builtin/config.c index ca4a0db4a7..211e118d57 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -52,7 +52,7 @@ static struct option builtin_config_options[] = { OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"), - OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"), + OPT_STRING('f', "file", &given_config_file, "file", "use given config file"), OPT_GROUP("Action"), OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET), OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL), @@ -153,7 +153,6 @@ static int show_config(const char *key_, const char *value_, void *cb) static int get_value(const char *key_, const char *regex_) { int ret = -1; - char *tl; char *global = NULL, *repo_config = NULL; const char *system_wide = NULL, *local; @@ -161,24 +160,38 @@ static int get_value(const char *key_, const char *regex_) if (!local) { const char *home = getenv("HOME"); local = repo_config = git_pathdup("config"); - if (git_config_global() && home) + if (home) global = xstrdup(mkpath("%s/.gitconfig", home)); if (git_config_system()) system_wide = git_etc_gitconfig(); } - key = xstrdup(key_); - for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl) - *tl = tolower(*tl); - for (tl=key; *tl && *tl != '.'; ++tl) - *tl = tolower(*tl); - if (use_key_regexp) { + char *tl; + + /* + * NEEDSWORK: this naive pattern lowercasing obviously does not + * work for more complex patterns like "^[^.]*Foo.*bar". + * Perhaps we should deprecate this altogether someday. + */ + + key = xstrdup(key_); + for (tl = key + strlen(key) - 1; + tl >= key && *tl != '.'; + tl--) + *tl = tolower(*tl); + for (tl = key; *tl && *tl != '.'; tl++) + *tl = tolower(*tl); + key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { fprintf(stderr, "Invalid key pattern: %s\n", key_); + free(key); goto free_strings; } + } else { + if (git_config_parse_key(key_, &key, NULL)) + goto free_strings; } if (regex_) { @@ -423,9 +436,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) NULL, NULL); } else if (actions == ACTION_SET) { + int ret; check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); - return git_config_set(argv[0], value); + ret = git_config_set(argv[0], value); + if (ret == CONFIG_NOTHING_SET) + error("cannot overwrite multiple values with a single value\n" + " Use a regexp, --add or --set-all to change %s.", argv[0]); + return ret; } else if (actions == ACTION_SET_ALL) { check_argc(argc, 2, 3); @@ -500,3 +518,9 @@ int cmd_config(int argc, const char **argv, const char *prefix) return 0; } + +int cmd_repo_config(int argc, const char **argv, const char *prefix) +{ + fprintf(stderr, "WARNING: git repo-config is deprecated in favor of git config.\n"); + return cmd_config(argc, argv, prefix); +} diff --git a/builtin/describe.c b/builtin/describe.c index 342129fdbd..66fc291c8a 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -21,7 +21,7 @@ static int debug; /* Display lots of verbose info */ static int all; /* Any valid ref can be used */ static int tags; /* Allow lightweight tags */ static int longformat; -static int abbrev = DEFAULT_ABBREV; +static int abbrev = -1; /* unspecified */ static int max_candidates = 10; static struct hash_table names; static int have_util; @@ -63,7 +63,7 @@ static inline struct commit_name *find_commit_name(const unsigned char *peeled) return n; } -static int set_util(void *chain) +static int set_util(void *chain, void *data) { struct commit_name *n; for (n = chain; n; n = n->next) { @@ -231,13 +231,13 @@ 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)) - die("annotated tag %s not available", n->path); + 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); + 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); + warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); n->name_checked = 1; } @@ -264,10 +264,10 @@ static void describe(const char *arg, int last_one) unsigned int unannotated_cnt = 0; if (get_sha1(arg, sha1)) - die("Not a valid object name %s", arg); + die(_("Not a valid object name %s"), arg); cmit = lookup_commit_reference(sha1); if (!cmit) - die("%s is not a valid '%s' object", arg, commit_type); + die(_("%s is not a valid '%s' object"), arg, commit_type); n = find_commit_name(cmit->object.sha1); if (n && (tags || all || n->prio == 2)) { @@ -284,12 +284,12 @@ static void describe(const char *arg, int last_one) } if (!max_candidates) - die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1)); + die(_("no tag exactly matches '%s'"), sha1_to_hex(cmit->object.sha1)); if (debug) - fprintf(stderr, "searching to describe %s\n", arg); + fprintf(stderr, _("searching to describe %s\n"), arg); if (!have_util) { - for_each_hash(&names, set_util); + for_each_hash(&names, set_util, NULL); have_util = 1; } @@ -326,7 +326,7 @@ static void describe(const char *arg, int last_one) } if (annotated_cnt && !list) { if (debug) - fprintf(stderr, "finished search at %s\n", + fprintf(stderr, _("finished search at %s\n"), sha1_to_hex(c->object.sha1)); break; } @@ -350,12 +350,12 @@ static void describe(const char *arg, int last_one) return; } if (unannotated_cnt) - die("No annotated tags can describe '%s'.\n" - "However, there were unannotated tags: try --tags.", + die(_("No annotated tags can describe '%s'.\n" + "However, there were unannotated tags: try --tags."), sha1_to_hex(sha1)); else - die("No tags can describe '%s'.\n" - "Try --always, or create some tags.", + die(_("No tags can describe '%s'.\n" + "Try --always, or create some tags."), sha1_to_hex(sha1)); } @@ -375,11 +375,11 @@ static void describe(const char *arg, int last_one) prio_names[t->name->prio], t->depth, t->name->path); } - fprintf(stderr, "traversed %lu commits\n", seen_commits); + fprintf(stderr, _("traversed %lu commits\n"), seen_commits); if (gave_up_on) { fprintf(stderr, - "more than %i tags found; listed %i most recent\n" - "gave up search at %s\n", + _("more than %i tags found; listed %i most recent\n" + "gave up search at %s\n"), max_candidates, max_candidates, sha1_to_hex(gave_up_on->object.sha1)); } @@ -420,7 +420,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_END(), }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, describe_usage, 0); + if (abbrev < 0) + abbrev = DEFAULT_ABBREV; + if (max_candidates < 0) max_candidates = 0; else if (max_candidates > MAX_TAGS) @@ -429,7 +433,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; if (longformat && abbrev == 0) - die("--long is incompatible with --abbrev=0"); + die(_("--long is incompatible with --abbrev=0")); if (contains) { const char **args = xmalloc((7 + argc) * sizeof(char *)); @@ -455,14 +459,14 @@ int cmd_describe(int argc, const char **argv, const char *prefix) init_hash(&names); for_each_rawref(get_name, NULL); if (!names.nr && !always) - die("No names found, cannot describe anything."); + die(_("No names found, cannot describe anything.")); if (argc == 0) { if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix)) dirty = NULL; describe("HEAD", 1); } else if (dirty) { - die("--dirty is incompatible with committishes"); + die(_("--dirty is incompatible with committishes")); } else { while (argc-- > 0) { describe(*argv++, argc == 0); diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 951c7c8994..46085f862f 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) rev.combine_merges = rev.dense_combined_merges = 1; - if (read_cache_preload(rev.diffopt.paths) < 0) { + if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 0d2a3e9fa2..be6417d166 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -163,6 +163,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } if (read_stdin) { + int saved_nrl = 0; + int saved_dcctc = 0; + if (opt->diffopt.detect_rename) opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | DIFF_SETUP_USE_CACHE); @@ -173,9 +176,16 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) fputs(line, stdout); fflush(stdout); } - else + else { diff_tree_stdin(line); + if (saved_nrl < opt->diffopt.needed_rename_limit) + saved_nrl = opt->diffopt.needed_rename_limit; + if (opt->diffopt.degraded_cc_to_c) + saved_dcctc = 1; + } } + opt->diffopt.degraded_cc_to_c = saved_dcctc; + opt->diffopt.needed_rename_limit = saved_nrl; } return diff_result_code(&opt->diffopt, 0); diff --git a/builtin/diff.c b/builtin/diff.c index 945e7583a8..14bd14fce0 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -71,9 +71,9 @@ static int builtin_diff_b_f(struct rev_info *revs, usage(builtin_diff_usage); if (lstat(path, &st)) - die_errno("failed to stat '%s'", path); + die_errno(_("failed to stat '%s'"), path); if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) - die("'%s': not a regular file or symlink", path); + die(_("'%s': not a regular file or symlink"), path); diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/"); @@ -135,7 +135,7 @@ static int builtin_diff_index(struct rev_info *revs, revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); - if (read_cache_preload(revs->diffopt.paths) < 0) { + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } @@ -197,17 +197,11 @@ static void refresh_index_quietly(void) discard_cache(); read_cache(); refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); - - if (active_cache_changed && - !write_cache(fd, active_cache, active_nr)) - commit_locked_index(lock_file); - - rollback_lock_file(lock_file); + update_index_if_able(&the_index, lock_file); } static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) { - int result; unsigned int options = 0; while (1 < argc && argv[1][0] == '-') { @@ -222,7 +216,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv else if (!strcmp(argv[1], "-h")) usage(builtin_diff_usage); else - return error("invalid option: %s", argv[1]); + return error(_("invalid option: %s"), argv[1]); argv++; argc--; } @@ -237,12 +231,11 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv revs->combine_merges = revs->dense_combined_merges = 1; setup_work_tree(); - if (read_cache_preload(revs->diffopt.paths) < 0) { + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } - result = run_diff_files(revs, options); - return diff_result_code(&revs->diffopt, result); + return run_diff_files(revs, options); } int cmd_diff(int argc, const char **argv, const char *prefix) @@ -299,12 +292,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); if (nongit) - die("Not a git repository"); + die(_("Not a git repository")); argc = setup_revisions(argc, argv, &rev, NULL); if (!rev.diffopt.output_format) { rev.diffopt.output_format = DIFF_FORMAT_PATCH; if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); + die(_("diff_setup_done failed")); } DIFF_OPT_SET(&rev.diffopt, RECURSIVE); @@ -330,8 +323,11 @@ int cmd_diff(int argc, const char **argv, const char *prefix) else if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) { add_head_to_pending(&rev); - if (!rev.pending.nr) - die("No HEAD commit to compare with (yet)"); + if (!rev.pending.nr) { + struct tree *tree; + tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN); + add_pending_object(&rev, &tree->object, "HEAD"); + } break; } } @@ -346,12 +342,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) obj = parse_object(obj->sha1); obj = deref_tag(obj, NULL, 0); if (!obj) - die("invalid object '%s' given.", name); + die(_("invalid object '%s' given."), name); if (obj->type == OBJ_COMMIT) obj = &((struct commit *)obj)->tree->object; if (obj->type == OBJ_TREE) { if (ARRAY_SIZE(ent) <= ents) - die("more than %d trees given: '%s'", + die(_("more than %d trees given: '%s'"), (int) ARRAY_SIZE(ent), name); obj->flags |= flags; ent[ents].item = obj; @@ -361,7 +357,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } if (obj->type == OBJ_BLOB) { if (2 <= blobs) - die("more than two blobs given: '%s'", name); + die(_("more than two blobs given: '%s'"), name); hashcpy(blob[blobs].sha1, obj->sha1); blob[blobs].name = name; blob[blobs].mode = list->mode; @@ -369,16 +365,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) continue; } - die("unhandled object '%s' given.", name); + die(_("unhandled object '%s' given."), name); } - if (rev.prune_data) { - const char **pathspec = rev.prune_data; - while (*pathspec) { - if (!path) - path = *pathspec; - paths++; - pathspec++; - } + if (rev.prune_data.nr) { + if (!path) + path = rev.prune_data.items[0].match; + paths += rev.prune_data.nr; } /* diff --git a/builtin/fast-export.c b/builtin/fast-export.c index c8fd46b872..daf19451ba 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -619,9 +619,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode", "select handling of tags that tag filtered objects", parse_opt_tag_of_filtered_mode), - OPT_STRING(0, "export-marks", &export_filename, "FILE", + OPT_STRING(0, "export-marks", &export_filename, "file", "Dump marks to this file"), - OPT_STRING(0, "import-marks", &import_filename, "FILE", + OPT_STRING(0, "import-marks", &import_filename, "file", "Import marks from this file"), OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger, "Fake a tagger when tags lack one"), @@ -651,7 +651,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (import_filename) import_marks(import_filename); - if (import_filename && revs.prune_data) + if (import_filename && revs.prune_data.nr) full_tree = 1; get_tags_and_duplicates(&revs.pending, &extra_refs); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index b999413934..4367984102 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "refs.h" #include "pkt-line.h" #include "commit.h" @@ -9,11 +9,13 @@ #include "fetch-pack.h" #include "remote.h" #include "run-command.h" +#include "transport.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; static int unpack_limit = 100; static int prefer_ofs_delta = 1; +static int no_done = 0; static struct fetch_pack_args args = { /* .uploadpack = */ "git-upload-pack", }; @@ -217,14 +219,40 @@ static void send_request(int fd, struct strbuf *buf) safe_write(fd, buf->buf, buf->len); } +static void insert_one_alternate_ref(const struct ref *ref, void *unused) +{ + rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL); +} + +static void insert_alternate_refs(void) +{ + for_each_alternate_ref(insert_one_alternate_ref, NULL); +} + +#define INITIAL_FLUSH 16 +#define PIPESAFE_FLUSH 32 +#define LARGE_FLUSH 1024 + +static int next_flush(int count) +{ + int flush_limit = args.stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH; + + if (count < flush_limit) + count <<= 1; + else + count += flush_limit; + return count; +} + static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { int fetching; - int count = 0, flushes = 0, retval; + int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval; const unsigned char *sha1; unsigned in_vain = 0; int got_continue = 0; + int got_ready = 0; struct strbuf req_buf = STRBUF_INIT; size_t state_len = 0; @@ -235,6 +263,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, marked = 1; for_each_ref(rev_list_insert_ref, NULL); + insert_alternate_refs(); fetching = 0; for ( ; refs ; refs = refs->next) { @@ -262,6 +291,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, struct strbuf c = STRBUF_INIT; if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); + if (no_done) strbuf_addstr(&c, " no-done"); if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); if (use_sideband == 1) strbuf_addstr(&c, " side-band"); if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); @@ -332,19 +362,20 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.verbose) fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); in_vain++; - if (!(31 & ++count)) { + if (flush_at <= ++count) { int ack; packet_buf_flush(&req_buf); send_request(fd[1], &req_buf); strbuf_setlen(&req_buf, state_len); flushes++; + flush_at = next_flush(count); /* * We keep one window "ahead" of the other side, and * will wait for an ACK only on the next one */ - if (!args.stateless_rpc && count == 32) + if (!args.stateless_rpc && count == INITIAL_FLUSH) continue; consume_shallow_list(fd[0]); @@ -379,6 +410,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, retval = 0; in_vain = 0; got_continue = 1; + if (ack == ACK_ready) { + rev_list = NULL; + got_ready = 1; + } break; } } @@ -392,8 +427,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, } } done: - packet_buf_write(&req_buf, "done\n"); - send_request(fd[1], &req_buf); + if (!got_ready || !no_done) { + packet_buf_write(&req_buf, "done\n"); + send_request(fd[1], &req_buf); + } if (args.verbose) fprintf(stderr, "done\n"); if (retval != 0) { @@ -435,8 +472,10 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag, } if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - commit->object.flags |= COMPLETE; - commit_list_insert_by_date(commit, &complete); + if (!(commit->object.flags & COMPLETE)) { + commit->object.flags |= COMPLETE; + commit_list_insert_by_date(commit, &complete); + } } return 0; } @@ -696,6 +735,12 @@ static struct ref *do_fetch_pack(int fd[2], if (args.verbose) fprintf(stderr, "Server supports multi_ack_detailed\n"); multi_ack = 2; + if (server_supports("no-done")) { + if (args.verbose) + fprintf(stderr, "Server supports no-done\n"); + if (args.stateless_rpc) + no_done = 1; + } } else if (server_supports("multi_ack")) { if (args.verbose) @@ -804,6 +849,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) char **pack_lockfile_ptr = NULL; struct child_process *conn; + packet_trace_identity("fetch-pack"); + nr_heads = 0; heads = NULL; for (i = 1; i < argc; i++) { diff --git a/builtin/fetch.c b/builtin/fetch.c index 357f3cdbbf..f9c41da475 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -28,12 +28,6 @@ enum { TAGS_SET = 2 }; -enum { - RECURSE_SUBMODULES_OFF = 0, - RECURSE_SUBMODULES_DEFAULT = 1, - RECURSE_SUBMODULES_ON = 2 -}; - static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int tags = TAGS_DEFAULT; @@ -42,6 +36,21 @@ static const char *upload_pack; static struct strbuf default_rla = STRBUF_INIT; static struct transport *transport; static const char *submodule_prefix = ""; +static const char *recurse_submodules_default; + +static int option_parse_recurse_submodules(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + recurse_submodules = RECURSE_SUBMODULES_OFF; + } else { + if (arg) + recurse_submodules = parse_fetch_recurse_submodules_arg(opt->long_name, arg); + else + recurse_submodules = RECURSE_SUBMODULES_ON; + } + return 0; +} static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), @@ -49,7 +58,7 @@ static struct option builtin_fetch_options[] = { "fetch from all remotes"), OPT_BOOLEAN('a', "append", &append, "append to .git/FETCH_HEAD instead of overwriting"), - OPT_STRING(0, "upload-pack", &upload_pack, "PATH", + OPT_STRING(0, "upload-pack", &upload_pack, "path", "path to upload pack on remote end"), OPT__FORCE(&force, "force overwrite of local branch"), OPT_BOOLEAN('m', "multiple", &multiple, @@ -60,19 +69,22 @@ static struct option builtin_fetch_options[] = { "do not fetch all tags (--no-tags)", TAGS_UNSET), OPT_BOOLEAN('p', "prune", &prune, "prune remote-tracking branches no longer on remote"), - OPT_SET_INT(0, "recurse-submodules", &recurse_submodules, + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "on-demand", "control recursive fetching of submodules", - RECURSE_SUBMODULES_ON), + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_BOOLEAN(0, "dry-run", &dry_run, "dry run"), 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", + OPT_STRING(0, "depth", &depth, "depth", "deepen history of shallow clone"), - { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "DIR", + { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir", "prepend this to submodule path output", PARSE_OPT_HIDDEN }, + { OPTION_STRING, 0, "recurse-submodules-default", + &recurse_submodules_default, NULL, + "default mode for recursion", PARSE_OPT_HIDDEN }, OPT_END() }; @@ -184,7 +196,7 @@ static struct ref *get_ref_map(struct transport *transport, } else { ref_map = get_remote_ref(remote_refs, "HEAD"); if (!ref_map) - die("Couldn't find remote ref HEAD"); + die(_("Couldn't find remote ref HEAD")); ref_map->merge = 1; tail = &ref_map->next; } @@ -237,12 +249,12 @@ static int update_local_ref(struct ref *ref, *display = 0; type = sha1_object_info(ref->new_sha1, NULL); if (type < 0) - die("object %s not found", sha1_to_hex(ref->new_sha1)); + die(_("object %s not found"), sha1_to_hex(ref->new_sha1)); if (!hashcmp(ref->old_sha1, ref->new_sha1)) { if (verbosity > 0) sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH, - "[up to date]", REFCOL_WIDTH, remote, + _("[up to date]"), REFCOL_WIDTH, remote, pretty_ref); return 0; } @@ -255,8 +267,8 @@ static int update_local_ref(struct ref *ref, * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ - sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)", - TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, + sprintf(display, _("! %-*s %-*s -> %s (can't fetch in current branch)"), + TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -266,8 +278,8 @@ 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 ? '!' : '-', - TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, - pretty_ref, r ? " (unable to update local ref)" : ""); + TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), REFCOL_WIDTH, remote, + pretty_ref, r ? _(" (unable to update local ref)") : ""); return r; } @@ -279,17 +291,20 @@ static int update_local_ref(struct ref *ref, int r; if (!strncmp(ref->name, "refs/tags/", 10)) { msg = "storing tag"; - what = "[new tag]"; + what = _("[new tag]"); } else { msg = "storing head"; - what = "[new branch]"; + what = _("[new branch]"); + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); } r = s_update_ref(msg, ref, 0); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, - r ? " (unable to update local ref)" : ""); + r ? _(" (unable to update local ref)") : ""); return r; } @@ -299,10 +314,13 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, ".."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("fast-forward", ref, 1); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, - pretty_ref, r ? " (unable to update local ref)" : ""); + pretty_ref, r ? _(" (unable to update local ref)") : ""); return r; } else if (force || ref->force) { char quickref[84]; @@ -310,16 +328,19 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, "..."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("forced-update", ref, 1); sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref, - r ? "unable to update local ref" : "forced update"); + r ? _("unable to update local ref") : _("forced update")); return r; } else { - sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)", - TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, - pretty_ref); + sprintf(display, "! %-*s %-*s -> %s %s", + TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote, + pretty_ref, _("(non-fast-forward)")); return 1; } } @@ -337,7 +358,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, fp = fopen(filename, "a"); if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); + return error(_("cannot open %s: %s\n"), filename, strerror(errno)); if (raw_url) url = transport_anonymize_url(raw_url); @@ -415,7 +436,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, REFCOL_WIDTH, *what ? what : "HEAD"); if (*note) { if (verbosity >= 0 && !shown_url) { - fprintf(stderr, "From %.*s\n", + fprintf(stderr, _("From %.*s\n"), url_len, url); shown_url = 1; } @@ -426,9 +447,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, free(url); fclose(fp); if (rc & STORE_REF_ERROR_DF_CONFLICT) - error("some local refs could not be updated; try running\n" + error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " - "branches", remote_name); + "branches"), remote_name); return rc; } @@ -476,7 +497,7 @@ static int quickfetch(struct ref *ref_map) err = start_command(&revlist); if (err) { - error("could not run rev-list"); + error(_("could not run rev-list")); return err; } @@ -490,14 +511,14 @@ static int quickfetch(struct ref *ref_map) if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || write_str_in_full(revlist.in, "\n") < 0) { if (errno != EPIPE && errno != EINVAL) - error("failed write to rev-list: %s", strerror(errno)); + error(_("failed write to rev-list: %s"), strerror(errno)); err = -1; break; } } if (close(revlist.in)) { - error("failed to close rev-list's stdin: %s", strerror(errno)); + error(_("failed to close rev-list's stdin: %s"), strerror(errno)); err = -1; } @@ -524,16 +545,16 @@ static int prune_refs(struct transport *transport, struct ref *ref_map) int result = 0; struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); const char *dangling_msg = dry_run - ? " (%s will become dangling)\n" - : " (%s has become dangling)\n"; + ? _(" (%s will become dangling)\n") + : _(" (%s has become dangling)\n"); for (ref = stale_refs; ref; ref = ref->next) { if (!dry_run) result |= delete_ref(ref->name, NULL, 0); if (verbosity >= 0) { fprintf(stderr, " x %-*s %-*s -> %s\n", - TRANSPORT_SUMMARY_WIDTH, "[deleted]", - REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); + TRANSPORT_SUMMARY_WIDTH, _("[deleted]"), + REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name)); warn_dangling_symref(stderr, dangling_msg, ref->name); } } @@ -650,8 +671,8 @@ static void check_not_current_branch(struct ref *ref_map) for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && !strcmp(current_branch->refname, ref_map->peer_ref->name)) - die("Refusing to fetch into current branch %s " - "of non-bare repository", current_branch->refname); + die(_("Refusing to fetch into current branch %s " + "of non-bare repository"), current_branch->refname); } static int truncate_fetch_head(void) @@ -660,7 +681,7 @@ static int truncate_fetch_head(void) FILE *fp = fopen(filename, "w"); if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); + return error(_("cannot open %s: %s\n"), filename, strerror(errno)); fclose(fp); return 0; } @@ -684,7 +705,7 @@ static int do_fetch(struct transport *transport, } if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to fetch from %s", transport->url); + die(_("Don't know how to fetch from %s"), transport->url); /* if not appending, truncate FETCH_HEAD */ if (!append && !dry_run) { @@ -738,10 +759,10 @@ static void set_option(const char *name, const char *value) { int r = transport_set_option(transport, name, value); if (r < 0) - die("Option \"%s\" value \"%s\" is not valid for %s", + die(_("Option \"%s\" value \"%s\" is not valid for %s"), name, value, transport->url); if (r > 0) - warning("Option \"%s\" is ignored for %s\n", + warning(_("Option \"%s\" is ignored for %s\n"), name, transport->url); } @@ -810,6 +831,8 @@ static void add_options_to_argv(int *argc, const char **argv) argv[(*argc)++] = "--keep"; if (recurse_submodules == RECURSE_SUBMODULES_ON) argv[(*argc)++] = "--recurse-submodules"; + else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) + argv[(*argc)++] = "--recurse-submodules=on-demand"; if (verbosity >= 2) argv[(*argc)++] = "-v"; if (verbosity >= 1) @@ -838,9 +861,9 @@ static int fetch_multiple(struct string_list *list) argv[argc] = name; argv[argc + 1] = NULL; if (verbosity >= 0) - printf("Fetching %s\n", name); + printf(_("Fetching %s\n"), name); if (run_command_v_opt(argv, RUN_GIT_CMD)) { - error("Could not fetch %s", name); + error(_("Could not fetch %s"), name); result = 1; } } @@ -856,8 +879,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) int exit_code; if (!remote) - die("No remote repository specified. Please, specify either a URL or a\n" - "remote name from which new revisions should be fetched."); + 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); transport_set_verbosity(transport, verbosity, progress); @@ -876,7 +899,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) char *ref; i++; if (i >= argc) - die("You need to specify a tag name."); + die(_("You need to specify a tag name.")); ref = xmalloc(strlen(argv[i]) * 2 + 22); strcpy(ref, "refs/tags/"); strcat(ref, argv[i]); @@ -906,6 +929,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) struct remote *remote; int result = 0; + packet_trace_identity("fetch"); + /* Record the command line for the reflog */ strbuf_addstr(&default_rla, "fetch"); for (i = 1; i < argc; i++) @@ -916,9 +941,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (all) { if (argc == 1) - die("fetch --all does not take a repository argument"); + die(_("fetch --all does not take a repository argument")); else if (argc > 1) - die("fetch --all does not make sense with refspecs"); + die(_("fetch --all does not make sense with refspecs")); (void) for_each_remote(get_one_remote_for_fetch, &list); result = fetch_multiple(&list); } else if (argc == 0) { @@ -929,7 +954,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) /* All arguments are assumed to be remotes or groups */ for (i = 0; i < argc; i++) if (!add_remote_or_group(argv[i], &list)) - die("No such remote or remote group: %s", argv[i]); + die(_("No such remote or remote group: %s"), argv[i]); result = fetch_multiple(&list); } else { /* Single remote or group */ @@ -937,7 +962,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (list.nr > 1) { /* More than one remote */ if (argc > 1) - die("Fetching a group and specifying refspecs does not make sense"); + die(_("Fetching a group and specifying refspecs does not make sense")); result = fetch_multiple(&list); } else { /* Zero or one remotes */ @@ -949,15 +974,16 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { const char *options[10]; int num_options = 0; - /* Set recursion as default when we already are recursing */ - if (submodule_prefix[0]) - set_config_fetch_recurse_submodules(1); + if (recurse_submodules_default) { + int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default); + set_config_fetch_recurse_submodules(arg); + } gitmodules_config(); git_config(submodule_config, NULL); add_options_to_argv(&num_options, options); result = fetch_populated_submodules(num_options, options, submodule_prefix, - recurse_submodules == RECURSE_SUBMODULES_ON, + recurse_submodules, verbosity < 0); } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 5189b16c9e..75816329d6 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -31,7 +31,7 @@ struct src_data { int head_status; }; -void init_src_data(struct src_data *data) +static void init_src_data(struct src_data *data) { data->branch.strdup_strings = 1; data->tag.strdup_strings = 1; diff --git a/builtin/fsck.c b/builtin/fsck.c index 6d5ebca7a9..5ae0366bc8 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -74,7 +74,13 @@ static int mark_object(struct object *obj, int type, void *data) { struct object *parent = data; + /* + * The only case data is NULL or type is OBJ_ANY is when + * mark_object_reachable() calls us. All the callers of + * that function has non-NULL obj hence ... + */ if (!obj) { + /* ... these references to parent->fld are safe here */ printf("broken link from %7s %s\n", typename(parent->type), sha1_to_hex(parent->sha1)); printf("broken link from %7s %s\n", @@ -84,6 +90,7 @@ static int mark_object(struct object *obj, int type, void *data) } if (type != OBJ_ANY && obj->type != type) + /* ... and the reference to parent is safe here */ objerror(parent, "wrong object type in link"); if (obj->flags & REACHABLE) @@ -109,7 +116,7 @@ static void mark_object_reachable(struct object *obj) mark_object(obj, OBJ_ANY, NULL); } -static int traverse_one_object(struct object *obj, struct object *parent) +static int traverse_one_object(struct object *obj) { int result; struct tree *tree = NULL; @@ -133,12 +140,11 @@ static int traverse_reachable(void) int result = 0; while (pending.nr) { struct object_array_entry *entry; - struct object *obj, *parent; + struct object *obj; entry = pending.objects + --pending.nr; obj = entry->item; - parent = (struct object *) entry->name; - result |= traverse_one_object(obj, parent); + result |= traverse_one_object(obj); } return !!result; } @@ -385,10 +391,20 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) sha1_list.nr = ++nr; } +static inline int is_loose_object_file(struct dirent *de, + char *name, unsigned char *sha1) +{ + if (strlen(de->d_name) != 38) + return 0; + memcpy(name + 2, de->d_name, 39); + return !get_sha1_hex(name, sha1); +} + static void fsck_dir(int i, char *path) { DIR *dir = opendir(path); struct dirent *de; + char name[100]; if (!dir) return; @@ -396,17 +412,13 @@ static void fsck_dir(int i, char *path) if (verbose) fprintf(stderr, "Checking directory %s\n", path); + sprintf(name, "%02x", i); while ((de = readdir(dir)) != NULL) { - char name[100]; unsigned char sha1[20]; if (is_dot_or_dotdot(de->d_name)) continue; - if (strlen(de->d_name) == 38) { - sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, 39); - if (get_sha1_hex(name, sha1) < 0) - break; + if (is_loose_object_file(de, name, sha1)) { add_sha1_list(sha1, DIRENT_SORT_HINT(de)); continue; } @@ -556,8 +568,8 @@ static int fsck_cache_tree(struct cache_tree *it) sha1_to_hex(it->sha1)); return 1; } - mark_object_reachable(obj); obj->used = 1; + mark_object_reachable(obj); if (obj->type != OBJ_TREE) err |= objerror(obj, "non-tree in cache-tree"); } diff --git a/builtin/gc.c b/builtin/gc.c index 1a80702b3d..ff5f73ba87 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -60,7 +60,7 @@ static int gc_config(const char *var, const char *value, void *cb) if (value && strcmp(value, "now")) { unsigned long now = approxidate("now"); if (approxidate(value) >= now) - return error("Invalid %s: '%s'", var, value); + return error(_("Invalid %s: '%s'"), var, value); } return git_config_string(&prune_expire, var, value); } @@ -75,7 +75,7 @@ static void append_option(const char **cmd, const char *opt, int max_length) ; if (i + 2 >= max_length) - die("Too many options specified"); + die(_("Too many options specified")); cmd[i++] = opt; cmd[i] = NULL; } @@ -100,7 +100,7 @@ static int too_many_loose_objects(void) return 0; if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) { - warning("insanely long object directory %.*s", 50, objdir); + warning(_("insanely long object directory %.*s"), 50, objdir); return 0; } dir = opendir(path); @@ -219,13 +219,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix) */ if (!need_to_gc()) return 0; - fprintf(stderr, - "Auto packing the repository for optimum performance.%s\n", - quiet - ? "" - : (" You may also\n" - "run \"git gc\" manually. See " - "\"git help gc\" for more information.")); + if (quiet) + fprintf(stderr, _("Auto packing the repository for optimum performance.\n")); + else + fprintf(stderr, + _("Auto packing the repository for optimum performance. You may also\n" + "run \"git gc\" manually. See " + "\"git help gc\" for more information.")); } else append_option(argv_repack, prune_expire && !strcmp(prune_expire, "now") @@ -251,8 +251,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return error(FAILED_RUN, argv_rerere[0]); if (auto_gc && too_many_loose_objects()) - warning("There are too many unreachable loose objects; " - "run 'git prune' to remove them."); + warning(_("There are too many unreachable loose objects; " + "run 'git prune' to remove them.")); return 0; } diff --git a/builtin/grep.c b/builtin/grep.c index fdf7131efd..871afaa3c7 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -40,8 +40,7 @@ enum work_type {WORK_SHA1, WORK_FILE}; * threads. The producer adds struct work_items to 'todo' and the * consumers pick work items from the same array. */ -struct work_item -{ +struct work_item { enum work_type type; char *name; @@ -245,7 +244,7 @@ static void start_threads(struct grep_opt *opt) err = pthread_create(&threads[i], NULL, run, o); if (err) - die("grep: failed to create thread: %s", + die(_("grep: failed to create thread: %s"), strerror(err)); } } @@ -303,6 +302,19 @@ static int grep_config(const char *var, const char *value, void *cb) default: return 0; } + if (!strcmp(var, "grep.extendedregexp")) { + if (git_config_bool(var, value)) + opt->regflags |= REG_EXTENDED; + else + opt->regflags &= ~REG_EXTENDED; + return 0; + } + + if (!strcmp(var, "grep.linenumber")) { + opt->linenum = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "color.grep")) opt->color = git_config_colorbool(var, value, -1); else if (!strcmp(var, "color.grep.context")) @@ -329,106 +341,6 @@ static int grep_config(const char *var, const char *value, void *cb) return 0; } -/* - * Return non-zero if max_depth is negative or path has no more then max_depth - * slashes. - */ -static int accept_subdir(const char *path, int max_depth) -{ - if (max_depth < 0) - return 1; - - while ((path = strchr(path, '/')) != NULL) { - max_depth--; - if (max_depth < 0) - return 0; - path++; - } - return 1; -} - -/* - * Return non-zero if name is a subdirectory of match and is not too deep. - */ -static int is_subdir(const char *name, int namelen, - const char *match, int matchlen, int max_depth) -{ - if (matchlen > namelen || strncmp(name, match, matchlen)) - return 0; - - if (name[matchlen] == '\0') /* exact match */ - return 1; - - if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/') - return accept_subdir(name + matchlen + 1, max_depth); - - return 0; -} - -/* - * git grep pathspecs are somewhat different from diff-tree pathspecs; - * pathname wildcards are allowed. - */ -static int pathspec_matches(const char **paths, const char *name, int max_depth) -{ - int namelen, i; - if (!paths || !*paths) - return accept_subdir(name, max_depth); - namelen = strlen(name); - for (i = 0; paths[i]; i++) { - const char *match = paths[i]; - int matchlen = strlen(match); - const char *cp, *meta; - - if (is_subdir(name, namelen, match, matchlen, max_depth)) - return 1; - if (!fnmatch(match, name, 0)) - return 1; - if (name[namelen-1] != '/') - continue; - - /* We are being asked if the directory ("name") is worth - * descending into. - * - * Find the longest leading directory name that does - * not have metacharacter in the pathspec; the name - * we are looking at must overlap with that directory. - */ - for (cp = match, meta = NULL; cp - match < matchlen; cp++) { - char ch = *cp; - if (ch == '*' || ch == '[' || ch == '?') { - meta = cp; - break; - } - } - if (!meta) - meta = cp; /* fully literal */ - - if (namelen <= meta - match) { - /* Looking at "Documentation/" and - * the pattern says "Documentation/howto/", or - * "Documentation/diff*.txt". The name we - * have should match prefix. - */ - if (!memcmp(match, name, namelen)) - return 1; - continue; - } - - if (meta - match < namelen) { - /* Looking at "Documentation/howto/" and - * the pattern says "Documentation/h*"; - * match up to "Do.../h"; this avoids descending - * into "Documentation/technical/". - */ - if (!memcmp(match, name, meta - match)) - return 1; - continue; - } - } - return 0; -} - static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) { void *data; @@ -450,7 +362,7 @@ static void *load_sha1(const unsigned char *sha1, unsigned long *size, void *data = lock_and_read_sha1_file(sha1, &type, size); if (!data) - error("'%s': unable to read %s", name, sha1_to_hex(sha1)); + error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1)); return data; } @@ -501,21 +413,21 @@ static void *load_file(const char *filename, size_t *sz) if (lstat(filename, &st) < 0) { err_ret: if (errno != ENOENT) - error("'%s': %s", filename, strerror(errno)); - return 0; + error(_("'%s': %s"), filename, strerror(errno)); + return NULL; } if (!S_ISREG(st.st_mode)) - return 0; + return NULL; *sz = xsize_t(st.st_size); i = open(filename, O_RDONLY); if (i < 0) goto err_ret; data = xmalloc(*sz + 1); if (st.st_size != read_in_full(i, data, *sz)) { - error("'%s': short read %s", filename, strerror(errno)); + error(_("'%s': short read %s"), filename, strerror(errno)); close(i); free(data); - return 0; + return NULL; } close(i); data[*sz] = 0; @@ -574,14 +486,14 @@ static void run_pager(struct grep_opt *opt, const char *prefix) argv[path_list->nr] = NULL; if (prefix && chdir(prefix)) - die("Failed to chdir: %s", 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) +static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) { int hit = 0; int nr; @@ -591,7 +503,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) + if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) continue; /* * If CE_VALID is on, we assume worktree file and its cache entry @@ -618,44 +530,29 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) return hit; } -static int grep_tree(struct grep_opt *opt, const char **paths, - struct tree_desc *tree, - const char *tree_name, const char *base) +static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, + struct tree_desc *tree, struct strbuf *base, int tn_len) { - int len; - int hit = 0; + int hit = 0, match = 0; struct name_entry entry; - char *down; - int tn_len = strlen(tree_name); - struct strbuf pathbuf; - - strbuf_init(&pathbuf, PATH_MAX + tn_len); - - if (tn_len) { - strbuf_add(&pathbuf, tree_name, tn_len); - strbuf_addch(&pathbuf, ':'); - tn_len = pathbuf.len; - } - strbuf_addstr(&pathbuf, base); - len = pathbuf.len; + int old_baselen = base->len; while (tree_entry(tree, &entry)) { int te_len = tree_entry_len(entry.path, entry.sha1); - pathbuf.len = len; - strbuf_add(&pathbuf, entry.path, te_len); - - if (S_ISDIR(entry.mode)) - /* Match "abc/" against pathspec to - * decide if we want to descend into "abc" - * directory. - */ - strbuf_addch(&pathbuf, '/'); - - down = pathbuf.buf + tn_len; - if (!pathspec_matches(paths, down, opt->max_depth)) - ; - else if (S_ISREG(entry.mode)) - hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len); + + if (match != 2) { + match = tree_entry_interesting(&entry, base, tn_len, pathspec); + if (match < 0) + break; + if (match == 0) + continue; + } + + strbuf_add(base, entry.path, te_len); + + if (S_ISREG(entry.mode)) { + hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len); + } else if (S_ISDIR(entry.mode)) { enum object_type type; struct tree_desc sub; @@ -664,20 +561,23 @@ static int grep_tree(struct grep_opt *opt, const char **paths, data = lock_and_read_sha1_file(entry.sha1, &type, &size); if (!data) - die("unable to read tree (%s)", + die(_("unable to read tree (%s)"), sha1_to_hex(entry.sha1)); + + strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); - hit |= grep_tree(opt, paths, &sub, tree_name, down); + hit |= grep_tree(opt, pathspec, &sub, base, tn_len); free(data); } + strbuf_setlen(base, old_baselen); + if (hit && opt->status_only) break; } - strbuf_release(&pathbuf); return hit; } -static int grep_object(struct grep_opt *opt, const char **paths, +static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) @@ -686,20 +586,30 @@ static int grep_object(struct grep_opt *opt, const char **paths, struct tree_desc tree; void *data; unsigned long size; - int hit; + struct strbuf base; + int hit, len; + data = read_object_with_reference(obj->sha1, tree_type, &size, NULL); if (!data) - die("unable to read tree (%s)", sha1_to_hex(obj->sha1)); + die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1)); + + len = name ? strlen(name) : 0; + strbuf_init(&base, PATH_MAX + len + 1); + if (len) { + strbuf_add(&base, name, len); + strbuf_addch(&base, ':'); + } init_tree_desc(&tree, data, size); - hit = grep_tree(opt, paths, &tree, name, ""); + hit = grep_tree(opt, pathspec, &tree, &base, base.len); + strbuf_release(&base); free(data); return hit; } - die("unable to grep from object of type %s", typename(obj->type)); + die(_("unable to grep from object of type %s"), typename(obj->type)); } -static int grep_objects(struct grep_opt *opt, const char **paths, +static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, const struct object_array *list) { unsigned int i; @@ -709,7 +619,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths, 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)) { + if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) { hit = 1; if (opt->status_only) break; @@ -718,7 +628,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths, return hit; } -static int grep_directory(struct grep_opt *opt, const char **paths) +static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec) { struct dir_struct dir; int i, hit = 0; @@ -726,8 +636,12 @@ static int grep_directory(struct grep_opt *opt, const char **paths) memset(&dir, 0, sizeof(dir)); setup_standard_excludes(&dir); - fill_directory(&dir, paths); + fill_directory(&dir, pathspec->raw); for (i = 0; i < dir.nr; i++) { + const char *name = dir.entries[i]->name; + int namelen = strlen(name); + if (!match_pathspec_depth(pathspec, name, namelen, 0, NULL)) + continue; hit |= grep_file(opt, dir.entries[i]->name); if (hit && opt->status_only) break; @@ -748,7 +662,7 @@ static int context_callback(const struct option *opt, const char *arg, } value = strtol(arg, (char **)&endp, 10); if (*endp) { - return error("switch `%c' expects a numerical value", + return error(_("switch `%c' expects a numerical value"), opt->short_name); } grep_opt->pre_context = grep_opt->post_context = value; @@ -758,13 +672,14 @@ static int context_callback(const struct option *opt, const char *arg, static int file_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; + int from_stdin = !strcmp(arg, "-"); FILE *patterns; int lno = 0; struct strbuf sb = STRBUF_INIT; - patterns = fopen(arg, "r"); + patterns = from_stdin ? stdin : fopen(arg, "r"); if (!patterns) - die_errno("cannot open '%s'", arg); + die_errno(_("cannot open '%s'"), arg); while (strbuf_getline(&sb, patterns, '\n') == 0) { char *s; size_t len; @@ -776,7 +691,8 @@ static int file_callback(const struct option *opt, const char *arg, int unset) s = strbuf_detach(&sb, &len); append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN); } - fclose(patterns); + if (!from_stdin) + fclose(patterns); strbuf_release(&sb); return 0; } @@ -832,10 +748,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix) struct grep_opt opt; struct object_array list = OBJECT_ARRAY_INIT; const char **paths = NULL; + struct pathspec pathspec; struct string_list path_list = STRING_LIST_INIT_NODUP; int i; int dummy; int use_index = 1; + enum { + pattern_type_unspecified = 0, + pattern_type_bre, + pattern_type_ere, + pattern_type_fixed, + pattern_type_pcre, + }; + int pattern_type = pattern_type_unspecified; + struct option options[] = { OPT_BOOLEAN(0, "cached", &cached, "search in index instead of in the work tree"), @@ -857,15 +783,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix) "descend at most <depth> levels", PARSE_OPT_NONEG, NULL, 1 }, OPT_GROUP(""), - OPT_BIT('E', "extended-regexp", &opt.regflags, - "use extended POSIX regular expressions", REG_EXTENDED), - OPT_NEGBIT('G', "basic-regexp", &opt.regflags, - "use basic POSIX regular expressions (default)", - REG_EXTENDED), - OPT_BOOLEAN('F', "fixed-strings", &opt.fixed, - "interpret patterns as fixed strings"), + OPT_SET_INT('E', "extended-regexp", &pattern_type, + "use extended POSIX regular expressions", + pattern_type_ere), + OPT_SET_INT('G', "basic-regexp", &pattern_type, + "use basic POSIX regular expressions (default)", + pattern_type_bre), + OPT_SET_INT('F', "fixed-strings", &pattern_type, + "interpret patterns as fixed strings", + pattern_type_fixed), + OPT_SET_INT('P', "perl-regexp", &pattern_type, + "use Perl-compatible regular expressions", + pattern_type_pcre), OPT_GROUP(""), - OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"), + OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"), OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1), OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1), OPT_NEGBIT(0, "full-name", &opt.relative, @@ -969,6 +900,28 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + switch (pattern_type) { + case pattern_type_fixed: + opt.fixed = 1; + opt.pcre = 0; + break; + case pattern_type_bre: + opt.fixed = 0; + opt.pcre = 0; + opt.regflags &= ~REG_EXTENDED; + break; + case pattern_type_ere: + opt.fixed = 0; + opt.pcre = 0; + opt.regflags |= REG_EXTENDED; + break; + case pattern_type_pcre: + opt.fixed = 0; + opt.pcre = 1; + break; + default: + break; /* nothing */ + } if (use_index && !startup_info->have_repository) /* die the same way as if we did it at the beginning */ @@ -1005,11 +958,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } if (!opt.pattern_list) - die("no pattern given."); + die(_("no pattern given.")); if (!opt.fixed && opt.ignore_case) opt.regflags |= REG_ICASE; - if ((opt.regflags != REG_NEWLINE) && opt.fixed) - die("cannot mix --fixed-strings and regexp"); #ifndef NO_PTHREADS if (online_cpus() == 1 || !grep_threads_ok(&opt)) @@ -1034,7 +985,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!get_sha1(arg, sha1)) { struct object *object = parse_object(sha1); if (!object) - die("bad object %s", arg); + die(_("bad object %s"), arg); add_object_array(object, arg, &list); continue; } @@ -1052,16 +1003,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j]); } - if (i < argc) - paths = get_pathspec(prefix, argv + i); - else if (prefix) { - paths = xcalloc(2, sizeof(const char *)); - paths[0] = prefix; - paths[1] = NULL; - } + paths = get_pathspec(prefix, argv + i); + init_pathspec(&pathspec, paths); + pathspec.max_depth = opt.max_depth; + pathspec.recursive = 1; if (show_in_pager && (cached || list.nr)) - die("--open-files-in-pager only works on the worktree"); + die(_("--open-files-in-pager only works on the worktree")); if (show_in_pager && opt.pattern_list && !opt.pattern_list->next) { const char *pager = path_list.items[0].string; @@ -1086,19 +1034,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!use_index) { if (cached) - die("--cached cannot be used with --no-index."); + die(_("--cached cannot be used with --no-index.")); if (list.nr) - die("--no-index cannot be used with revs."); - hit = grep_directory(&opt, paths); + die(_("--no-index cannot be used with revs.")); + hit = grep_directory(&opt, &pathspec); } else if (!list.nr) { if (!cached) setup_work_tree(); - hit = grep_cache(&opt, paths, cached); + hit = grep_cache(&opt, &pathspec, cached); } else { if (cached) - die("both --cached and trees are given."); - hit = grep_objects(&opt, paths, &list); + die(_("both --cached and trees are given.")); + hit = grep_objects(&opt, &pathspec, &list); } if (use_threads) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 080af1a01b..33911fd5e9 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -4,7 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 * Copyright (C) Junio C Hamano, 2005 */ -#include "cache.h" +#include "builtin.h" #include "blob.h" #include "quote.h" #include "parse-options.h" @@ -14,8 +14,11 @@ static void hash_fd(int fd, const char *type, int write_object, const char *path { struct stat st; unsigned char sha1[20]; + unsigned flags = (HASH_FORMAT_CHECK | + (write_object ? HASH_WRITE_OBJECT : 0)); + if (fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, write_object, type_from_string(type), path)) + index_fd(sha1, fd, &st, type_from_string(type), path, flags)) die(write_object ? "Unable to add %s to database" : "Unable to hash %s", path); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 8dc5c0b541..e40451ffb4 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "delta.h" #include "pack.h" #include "csum-file.h" @@ -13,8 +13,7 @@ static const char index_pack_usage[] = "git index-pack [-v] [-o <index-file>] [ --keep | --keep=<msg> ] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; -struct object_entry -{ +struct object_entry { struct pack_idx_entry idx; unsigned long size; unsigned int hdr_size; @@ -44,8 +43,7 @@ struct base_data { #define FLAG_LINK (1u<<20) #define FLAG_CHECKED (1u<<21) -struct delta_entry -{ +struct delta_entry { union delta_base base; int obj_no; }; @@ -209,7 +207,7 @@ static void parse_pack_header(void) static NORETURN void bad_object(unsigned long offset, const char *format, ...) __attribute__((format (printf, 2, 3))); -static void bad_object(unsigned long offset, const char *format, ...) +static NORETURN void bad_object(unsigned long offset, const char *format, ...) { va_list params; char buf[1024]; @@ -296,7 +294,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ void *data; obj->idx.offset = consumed_bytes; - input_crc32 = crc32(0, Z_NULL, 0); + input_crc32 = crc32(0, NULL, 0); p = fill(1); c = *p; diff --git a/builtin/init-db.c b/builtin/init-db.c index e3af9eaa87..025aa47c80 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -21,6 +21,7 @@ static int init_is_bare_repository = 0; static int init_shared_repository = -1; static const char *init_db_template_dir; +static const char *git_link; static void safe_create_dir(const char *dir, int share) { @@ -31,7 +32,7 @@ static void safe_create_dir(const char *dir, int share) } } else if (share && adjust_shared_perm(dir)) - die("Could not make %s writable by group", dir); + die(_("Could not make %s writable by group"), dir); } static void copy_templates_1(char *path, int baselen, @@ -58,25 +59,25 @@ static void copy_templates_1(char *path, int baselen, namelen = strlen(de->d_name); if ((PATH_MAX <= baselen + namelen) || (PATH_MAX <= template_baselen + namelen)) - die("insanely long template name %s", de->d_name); + die(_("insanely long template name %s"), de->d_name); memcpy(path + baselen, de->d_name, namelen+1); memcpy(template + template_baselen, de->d_name, namelen+1); if (lstat(path, &st_git)) { if (errno != ENOENT) - die_errno("cannot stat '%s'", path); + die_errno(_("cannot stat '%s'"), path); } else exists = 1; if (lstat(template, &st_template)) - die_errno("cannot stat template '%s'", template); + die_errno(_("cannot stat template '%s'"), template); if (S_ISDIR(st_template.st_mode)) { DIR *subdir = opendir(template); int baselen_sub = baselen + namelen; int template_baselen_sub = template_baselen + namelen; if (!subdir) - die_errno("cannot opendir '%s'", template); + die_errno(_("cannot opendir '%s'"), template); path[baselen_sub++] = template[template_baselen_sub++] = '/'; path[baselen_sub] = @@ -93,20 +94,20 @@ static void copy_templates_1(char *path, int baselen, int len; len = readlink(template, lnk, sizeof(lnk)); if (len < 0) - die_errno("cannot readlink '%s'", template); + die_errno(_("cannot readlink '%s'"), template); if (sizeof(lnk) <= len) - die("insanely long symlink %s", template); + die(_("insanely long symlink %s"), template); lnk[len] = 0; if (symlink(lnk, path)) - die_errno("cannot symlink '%s' '%s'", lnk, path); + die_errno(_("cannot symlink '%s' '%s'"), lnk, path); } else if (S_ISREG(st_template.st_mode)) { if (copy_file(path, template, st_template.st_mode)) - die_errno("cannot copy '%s' to '%s'", template, + die_errno(_("cannot copy '%s' to '%s'"), template, path); } else - error("ignoring template %s", template); + error(_("ignoring template %s"), template); } } @@ -129,7 +130,7 @@ static void copy_templates(const char *template_dir) return; template_len = strlen(template_dir); if (PATH_MAX <= (template_len+strlen("/config"))) - die("insanely long template path %s", template_dir); + die(_("insanely long template path %s"), template_dir); strcpy(template_path, template_dir); if (template_path[template_len-1] != '/') { template_path[template_len++] = '/'; @@ -137,7 +138,7 @@ static void copy_templates(const char *template_dir) } dir = opendir(template_path); if (!dir) { - warning("templates not found %s", template_dir); + warning(_("templates not found %s"), template_dir); return; } @@ -150,8 +151,8 @@ static void copy_templates(const char *template_dir) if (repository_format_version && repository_format_version != GIT_REPO_VERSION) { - warning("not copying templates of " - "a wrong format version %d from '%s'", + warning(_("not copying templates of " + "a wrong format version %d from '%s'"), repository_format_version, template_dir); closedir(dir); @@ -188,7 +189,7 @@ static int create_default_files(const char *template_path) int filemode; if (len > sizeof(path)-50) - die("insane git directory %s", git_dir); + die(_("insane git directory %s"), git_dir); memcpy(path, git_dir, len); if (len && path[len-1] != '/') @@ -311,11 +312,67 @@ static void create_object_directory(void) free(path); } +int set_git_dir_init(const char *git_dir, const char *real_git_dir, + int exist_ok) +{ + if (real_git_dir) { + struct stat st; + + if (!exist_ok && !stat(git_dir, &st)) + die(_("%s already exists"), git_dir); + + if (!exist_ok && !stat(real_git_dir, &st)) + die(_("%s already exists"), real_git_dir); + + /* + * make sure symlinks are resolved because we'll be + * moving the target repo later on in separate_git_dir() + */ + git_link = xstrdup(real_path(git_dir)); + } + else { + real_git_dir = real_path(git_dir); + git_link = NULL; + } + set_git_dir(real_path(real_git_dir)); + return 0; +} + +static void separate_git_dir(const char *git_dir) +{ + struct stat st; + FILE *fp; + + if (!stat(git_link, &st)) { + const char *src; + + if (S_ISREG(st.st_mode)) + src = read_gitfile_gently(git_link); + else if (S_ISDIR(st.st_mode)) + src = git_link; + else + die(_("unable to handle file type %d"), st.st_mode); + + if (rename(src, git_dir)) + die_errno(_("unable to move %s to %s"), src, git_dir); + } + + fp = fopen(git_link, "w"); + if (!fp) + die(_("Could not create git link %s"), git_link); + fprintf(fp, "gitdir: %s\n", git_dir); + fclose(fp); +} + int init_db(const char *template_dir, unsigned int flags) { int reinit; + const char *git_dir = get_git_dir(); + + if (git_link) + separate_git_dir(git_dir); - safe_create_dir(get_git_dir(), 0); + safe_create_dir(git_dir, 0); init_is_bare_repository = is_bare_repository(); @@ -352,11 +409,16 @@ int init_db(const char *template_dir, unsigned int flags) } 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" : "", + + /* + * TRANSLATORS: The first '%s' is either "Reinitialized + * existing" or "Initialized empty", the second " shared" or + * "", and the last '%s%s' is the verbatim directory name. + */ + printf(_("%s%s Git repository in %s%s\n"), + reinit ? _("Reinitialized existing") : _("Initialized empty"), + shared_repository ? _(" shared") : "", git_dir, len && git_dir[len-1] != '/' ? "/" : ""); } @@ -375,7 +437,7 @@ static int guess_repository_type(const char *git_dir) if (!strcmp(".", git_dir)) return 1; if (!getcwd(cwd, sizeof(cwd))) - die_errno("cannot tell cwd"); + die_errno(_("cannot tell cwd")); if (!strcmp(git_dir, cwd)) return 1; /* @@ -414,12 +476,13 @@ static const char *const init_db_usage[] = { int cmd_init_db(int argc, const char **argv, const char *prefix) { const char *git_dir; + const char *real_git_dir = NULL; const char *work_tree; const char *template_dir = NULL; unsigned int flags = 0; const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, "template-directory", - "provide the directory from which templates will be used"), + "directory from which templates will be used"), OPT_SET_INT(0, "bare", &is_bare_repository_cfg, "create a bare repository", 1), { OPTION_CALLBACK, 0, "shared", &init_shared_repository, @@ -427,11 +490,16 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) "specify that the git repository is to be shared amongst several users", PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET), + OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", + "separate git dir from working tree"), OPT_END() }; argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); + if (real_git_dir && !is_absolute_path(real_git_dir)) + real_git_dir = xstrdup(real_path(real_git_dir)); + if (argc == 1) { int mkdir_tried = 0; retry: @@ -450,18 +518,18 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) errno = EEXIST; /* fallthru */ case -1: - die_errno("cannot mkdir %s", argv[0]); + die_errno(_("cannot mkdir %s"), argv[0]); break; default: break; } shared_repository = saved; if (mkdir(argv[0], 0777) < 0) - die_errno("cannot mkdir %s", argv[0]); + die_errno(_("cannot mkdir %s"), argv[0]); mkdir_tried = 1; goto retry; } - die_errno("cannot chdir to %s", argv[0]); + die_errno(_("cannot chdir to %s"), argv[0]); } } else if (0 < argc) { usage(init_db_usage[0]); @@ -483,8 +551,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) git_dir = getenv(GIT_DIR_ENVIRONMENT); work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT); if ((!git_dir || is_bare_repository_cfg == 1) && work_tree) - die("%s (or --work-tree=<directory>) not allowed without " - "specifying %s (or --git-dir=<directory>)", + die(_("%s (or --work-tree=<directory>) not allowed without " + "specifying %s (or --git-dir=<directory>)"), GIT_WORK_TREE_ENVIRONMENT, GIT_DIR_ENVIRONMENT); @@ -498,33 +566,31 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) is_bare_repository_cfg = guess_repository_type(git_dir); if (!is_bare_repository_cfg) { - if (git_dir) { - const char *git_dir_parent = strrchr(git_dir, '/'); - if (git_dir_parent) { - char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(make_absolute_path(rel)); - free(rel); - } + const char *git_dir_parent = strrchr(git_dir, '/'); + if (git_dir_parent) { + char *rel = xstrndup(git_dir, git_dir_parent - git_dir); + git_work_tree_cfg = xstrdup(real_path(rel)); + free(rel); } if (!git_work_tree_cfg) { git_work_tree_cfg = xcalloc(PATH_MAX, 1); if (!getcwd(git_work_tree_cfg, PATH_MAX)) - die_errno ("Cannot access current working directory"); + die_errno (_("Cannot access current working directory")); } if (work_tree) - set_git_work_tree(make_absolute_path(work_tree)); + set_git_work_tree(real_path(work_tree)); else set_git_work_tree(git_work_tree_cfg); if (access(get_git_work_tree(), X_OK)) - die_errno ("Cannot access work tree '%s'", + die_errno (_("Cannot access work tree '%s'"), get_git_work_tree()); } else { if (work_tree) - set_git_work_tree(make_absolute_path(work_tree)); + set_git_work_tree(real_path(work_tree)); } - set_git_dir(make_absolute_path(git_dir)); + set_git_dir_init(git_dir, real_git_dir, 1); return init_db(template_dir, flags); } diff --git a/builtin/log.c b/builtin/log.c index 0e46e5ae95..5c2af59004 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -23,14 +23,18 @@ /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; +static int default_abbrev_commit; static int default_show_root = 1; static int decoration_style; +static int decoration_given; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; -static const char * const builtin_log_usage = +static const char * const builtin_log_usage[] = { "git log [<options>] [<since>..<until>] [[--] <path>...]\n" - " or: git show [options] <object>..."; + " or: git show [options] <object>...", + NULL +}; static int parse_decoration_style(const char *var, const char *value) { @@ -49,33 +53,66 @@ static int parse_decoration_style(const char *var, const char *value) return -1; } -static void cmd_log_init(int argc, const char **argv, const char *prefix, - struct rev_info *rev, struct setup_revision_opt *opt) +static int decorate_callback(const struct option *opt, const char *arg, int unset) { - int i; - int decoration_given = 0; - struct userformat_want w; + if (unset) + decoration_style = 0; + else if (arg) + decoration_style = parse_decoration_style("command line", arg); + else + decoration_style = DECORATE_SHORT_REFS; + + if (decoration_style < 0) + die("invalid --decorate option: %s", arg); + + decoration_given = 1; + return 0; +} + +static void cmd_log_init_defaults(struct rev_info *rev) +{ rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; if (fmt_pretty) get_commit_format(fmt_pretty, rev); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); + rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); if (default_date_mode) rev->date_mode = parse_date_format(default_date_mode); +} + +static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, + struct rev_info *rev, struct setup_revision_opt *opt) +{ + struct userformat_want w; + int quiet = 0, source = 0; + + const struct option builtin_log_options[] = { + OPT_BOOLEAN(0, "quiet", &quiet, "suppress diff output"), + OPT_BOOLEAN(0, "source", &source, "show source"), + { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options", + PARSE_OPT_OPTARG, decorate_callback}, + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, + builtin_log_options, builtin_log_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); - /* - * Check for -h before setup_revisions(), or "git log -h" will - * fail when run without a git directory. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_log_usage); argc = setup_revisions(argc, argv, rev, opt); + if (quiet) + rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT; + + /* Any arguments at this point are not recognized */ + if (argc > 1) + die("unrecognized argument: %s", argv[1]); memset(&w, 0, sizeof(w)); userformat_find_requirements(NULL, &w); @@ -89,38 +126,24 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->always_show_header = 0; if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { rev->always_show_header = 0; - if (rev->diffopt.nr_paths != 1) + if (rev->diffopt.pathspec.nr != 1) usage("git logs can only follow renames on one pathname at a time"); } - for (i = 1; i < argc; i++) { - 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="); - 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")) { + + if (source) + rev->show_source = 1; + + if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { + /* + * "log --pretty=raw" is special; ignore UI oriented + * configuration variables such as decoration. + */ + if (!decoration_given) decoration_style = 0; - } else if (!strcmp(arg, "--source")) { - rev->show_source = 1; - } else if (!strcmp(arg, "-h")) { - usage(builtin_log_usage); - } else - die("unrecognized argument: %s", arg); + if (!rev->abbrev_commit_given) + rev->abbrev_commit = 0; } - /* - * 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); @@ -128,6 +151,13 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, setup_pager(); } +static void cmd_log_init(int argc, const char **argv, const char *prefix, + struct rev_info *rev, struct setup_revision_opt *opt) +{ + cmd_log_init_defaults(rev); + cmd_log_init_finish(argc, argv, prefix, rev, opt); +} + /* * This gives a rough estimate for how many commits we * will print out in the list. @@ -153,7 +183,7 @@ static void show_early_header(struct rev_info *rev, const char *stage, int nr) if (rev->commit_format != CMIT_FMT_ONELINE) putchar(rev->diffopt.line_termination); } - printf("Final output: %d %s\n", nr, stage); + printf(_("Final output: %d %s\n"), nr, stage); } static struct itimerval early_output_timer; @@ -247,12 +277,14 @@ static void finish_early_output(struct rev_info *rev) static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; + int saved_nrl = 0; + int saved_dcctc = 0; if (rev->early_output) setup_early_output(rev); if (prepare_revision_walk(rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); if (rev->early_output) finish_early_output(rev); @@ -263,7 +295,13 @@ static int cmd_log_walk(struct rev_info *rev) * retain that state information if replacing rev->diffopt in this loop */ while ((commit = get_revision(rev)) != NULL) { - log_tree_commit(rev, commit); + if (!log_tree_commit(rev, commit) && + rev->max_count >= 0) + /* + * We decremented max_count in get_revision, + * but we didn't actually show the commit. + */ + rev->max_count++; if (!rev->reflog_info) { /* we allow cycles in reflog ancestry */ free(commit->buffer); @@ -271,7 +309,14 @@ static int cmd_log_walk(struct rev_info *rev) } free_commit_list(commit->parents); commit->parents = NULL; + if (saved_nrl < rev->diffopt.needed_rename_limit) + saved_nrl = rev->diffopt.needed_rename_limit; + if (rev->diffopt.degraded_cc_to_c) + saved_dcctc = 1; } + rev->diffopt.degraded_cc_to_c = saved_dcctc; + rev->diffopt.needed_rename_limit = saved_nrl; + if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { return 02; @@ -285,6 +330,10 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "log.abbrevcommit")) { + default_abbrev_commit = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); if (!strcmp(var, "log.decorate")) { @@ -345,7 +394,7 @@ static int show_object(const unsigned char *sha1, int show_tag_object, int offset = 0; if (!buf) - return error("Could not read object %s", sha1_to_hex(sha1)); + return error(_("Could not read object %s"), sha1_to_hex(sha1)); if (show_tag_object) while (offset < size && buf[offset] != '\n') { @@ -392,6 +441,7 @@ 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; + struct pathspec match_all; int i, count, ret = 0; git_config(git_log_config, NULL); @@ -399,6 +449,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; + init_pathspec(&match_all, NULL); init_revisions(&rev, prefix); rev.diff = 1; rev.always_show_header = 1; @@ -432,7 +483,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) break; o = parse_object(t->tagged->sha1); if (!o) - ret = error("Could not read object %s", + ret = error(_("Could not read object %s"), sha1_to_hex(t->tagged->sha1)); objects[i].item = o; i--; @@ -445,7 +496,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, NULL, + read_tree_recursive((struct tree *)o, "", 0, 0, &match_all, show_tree_object, NULL); rev.shown_one = 1; break; @@ -456,7 +507,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) ret = cmd_log_walk(&rev); break; default: - ret = error("Unknown type: %d", o->type); + ret = error(_("Unknown type: %d"), o->type); } } free(objects); @@ -478,20 +529,15 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); init_reflog_walk(&rev.reflog_info); - rev.abbrev_commit = 1; rev.verbose_header = 1; 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 - * on the cmd line. Sad, but cmd_log_init() currently doesn't - * allow us to set a different default. - */ + cmd_log_init_defaults(&rev); + rev.abbrev_commit = 1; rev.commit_format = CMIT_FMT_ONELINE; rev.use_terminator = 1; rev.always_show_header = 1; + cmd_log_init_finish(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -556,7 +602,7 @@ static int git_format_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "format.headers")) { if (!value) - die("format.headers without value"); + die(_("format.headers without value")); add_header(value); return 0; } @@ -619,7 +665,7 @@ static FILE *realstdout = NULL; static const char *output_directory = NULL; static int outdir_offset; -static int reopen_stdout(struct commit *commit, struct rev_info *rev) +static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; int suffix_len = strlen(fmt_patch_suffix) + 1; @@ -628,18 +674,18 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev) strbuf_addstr(&filename, output_directory); if (filename.len >= PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) - return error("name of output directory is too long"); + return error(_("name of output directory is too long")); if (filename.buf[filename.len - 1] != '/') strbuf_addch(&filename, '/'); } get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); - if (!DIFF_OPT_TST(&rev->diffopt, QUICK)) + if (!quiet) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); if (freopen(filename.buf, "w", stdout) == NULL) - return error("Cannot open patch file %s", filename.buf); + return error(_("Cannot open patch file %s"), filename.buf); strbuf_release(&filename); return 0; @@ -653,7 +699,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha unsigned flags1, flags2; if (rev->pending.nr != 2) - die("Need exactly one range."); + die(_("Need exactly one range.")); o1 = rev->pending.objects[0].item; flags1 = o1->flags; @@ -661,7 +707,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha flags2 = o2->flags; if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) - die("Not a range."); + die(_("Not a range.")); init_patch_ids(ids); @@ -672,7 +718,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha add_pending_object(&check_rev, o1, "o1"); add_pending_object(&check_rev, o2, "o2"); if (prepare_revision_walk(&check_rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(&check_rev)) != NULL) { /* ignore merges */ @@ -698,7 +744,7 @@ static void gen_message_id(struct rev_info *info, char *base) const char *email_end = strrchr(committer, '>'); struct strbuf buf = STRBUF_INIT; if (!email_start || !email_end || email_start > email_end - 1) - die("Could not extract email from committer identity."); + die(_("Could not extract email from committer identity.")); strbuf_addf(&buf, "%s.%lu.git.%.*s", base, (unsigned long) time(NULL), (int)(email_end - email_start - 1), email_start + 1); @@ -714,7 +760,8 @@ static void print_signature(void) static void make_cover_letter(struct rev_info *rev, int use_stdout, int numbered, int numbered_files, struct commit *origin, - int nr, struct commit **list, struct commit *head) + int nr, struct commit **list, struct commit *head, + int quiet) { const char *committer; const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; @@ -729,7 +776,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, struct pretty_print_context pp = {0}; if (rev->commit_format != CMIT_FMT_EMAIL) - die("Cover letter needs email format"); + die(_("Cover letter needs email format")); committer = git_committer_info(0); @@ -749,7 +796,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, sha1_to_hex(head->object.sha1), committer, committer); } - if (!use_stdout && reopen_stdout(commit, rev)) + if (!use_stdout && reopen_stdout(commit, rev, quiet)) return; if (commit) { @@ -822,7 +869,7 @@ static const char *clean_message_id(const char *msg_id) m++; } if (!z) - die("insane in-reply-to: %s", msg_id); + die(_("insane in-reply-to: %s"), msg_id); if (++z == m) return a; return xmemdupz(a, z - a); @@ -895,7 +942,7 @@ static int output_directory_callback(const struct option *opt, const char *arg, { const char **dir = (const char **)opt->value; if (*dir) - die("Two output directories?"); + die(_("Two output directories?")); *dir = arg; return 0; } @@ -990,6 +1037,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; + int quiet = 0; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, "use [PATCH n/m] even with a single patch", @@ -1045,6 +1093,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback }, OPT_STRING(0, "signature", &signature, "signature", "add a signature"), + OPT_BOOLEAN(0, "quiet", &quiet, + "don't print the patch filenames"), OPT_END() }; @@ -1056,7 +1106,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.commit_format = CMIT_FMT_EMAIL; rev.verbose_header = 1; rev.diff = 1; - rev.no_merges = 1; + rev.max_parents = 1; DIFF_OPT_SET(&rev.diffopt, RECURSIVE); rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); @@ -1083,7 +1133,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); endpos = strchr(committer, '>'); if (!endpos) - die("bogus committer info %s", committer); + die(_("bogus committer info %s"), committer); add_signoff = xmemdupz(committer, endpos - committer + 1); } @@ -1128,21 +1178,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) numbered = 0; if (numbered && keep_subject) - die ("-n and -k are mutually exclusive."); + die (_("-n and -k are mutually exclusive.")); if (keep_subject && subject_prefix) - die ("--subject-prefix and -k are mutually exclusive."); + die (_("--subject-prefix and -k are mutually exclusive.")); rev.preserve_subject = keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); if (argc > 1) - die ("unrecognized argument: %s", argv[1]); + die (_("unrecognized argument: %s"), argv[1]); if (rev.diffopt.output_format & DIFF_FORMAT_NAME) - die("--name-only does not make sense"); + die(_("--name-only does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) - die("--name-status does not make sense"); + die(_("--name-status does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) - die("--check does not make sense"); + die(_("--check does not make sense")); if (!use_patch_format && (!rev.diffopt.output_format || @@ -1165,9 +1215,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (output_directory) { if (use_stdout) - die("standard output, or directory, which one?"); + die(_("standard output, or directory, which one?")); if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) - die_errno("Could not create directory '%s'", + die_errno(_("Could not create directory '%s'"), output_directory); } @@ -1221,7 +1271,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) realstdout = xfdopen(xdup(1), "w"); if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); rev.boundary = 1; while ((commit = get_revision(&rev)) != NULL) { if (commit->object.flags & BOUNDARY) { @@ -1255,7 +1305,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, numbered, numbered_files, - origin, nr, list, head); + origin, nr, list, head, quiet); total++; start_number--; } @@ -1301,8 +1351,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit, - &rev)) - die("Failed to create output files"); + &rev, quiet)) + die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); free(commit->buffer); commit->buffer = NULL; @@ -1354,6 +1404,22 @@ static const char * const cherry_usage[] = { NULL }; +static void print_commit(char sign, struct commit *commit, int verbose, + int abbrev) +{ + if (!verbose) { + printf("%c %s\n", sign, + find_unique_abbrev(commit->object.sha1, abbrev)); + } else { + struct strbuf buf = STRBUF_INIT; + pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf); + printf("%c %s %s\n", sign, + find_unique_abbrev(commit->object.sha1, abbrev), + buf.buf); + strbuf_release(&buf); + } +} + int cmd_cherry(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1389,9 +1455,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) if (!current_branch || !current_branch->merge || !current_branch->merge[0] || !current_branch->merge[0]->dst) { - fprintf(stderr, "Could not find a tracked" + fprintf(stderr, _("Could not find a tracked" " remote branch, please" - " specify <upstream> manually.\n"); + " specify <upstream> manually.\n")); usage_with_options(cherry_usage, options); } @@ -1405,9 +1471,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) DIFF_OPT_SET(&revs.diffopt, RECURSIVE); if (add_pending_commit(head, &revs, 0)) - die("Unknown commit %s", head); + die(_("Unknown commit %s"), head); if (add_pending_commit(upstream, &revs, UNINTERESTING)) - die("Unknown commit %s", upstream); + die(_("Unknown commit %s"), upstream); /* Don't say anything if head and upstream are the same. */ if (revs.pending.nr == 2) { @@ -1419,11 +1485,11 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) get_patch_ids(&revs, &ids, prefix); if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) - die("Unknown commit %s", limit); + die(_("Unknown commit %s"), limit); /* reverse the list of commits */ if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(&revs)) != NULL) { /* ignore merges */ if (commit->parents && commit->parents->next) @@ -1438,20 +1504,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) commit = list->item; if (has_commit_patch_id(commit, &ids)) sign = '-'; - - if (verbose) { - struct strbuf buf = STRBUF_INIT; - pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf); - printf("%c %s %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev), - buf.buf); - strbuf_release(&buf); - } - else { - printf("%c %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev)); - } - + print_commit(sign, commit, verbose, abbrev); list = list->next; } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index fb2d5f4b1f..15701233e2 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -338,7 +338,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) { struct tree *tree; unsigned char sha1[20]; - const char **match; + struct pathspec pathspec; struct cache_entry *last_stage0 = NULL; int i; @@ -360,10 +360,11 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) static const char *(matchbuf[2]); matchbuf[0] = prefix; matchbuf[1] = NULL; - match = matchbuf; + init_pathspec(&pathspec, matchbuf); + pathspec.items[0].use_wildcard = 0; } else - match = NULL; - if (read_tree(tree, 1, match)) + init_pathspec(&pathspec, NULL); + if (read_tree(tree, 1, &pathspec)) die("unable to read tree entries %s", tree_name); for (i = 0; i < active_nr; i++) { diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 97eed4012b..10223092a9 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -5,7 +5,7 @@ static const char ls_remote_usage[] = "git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" -" [-q|--quiet] [<repository> [<refs>...]]"; +" [-q|--quiet] [--exit-code] [<repository> [<refs>...]]"; /* * Is there one among the list of patterns that match the tail part @@ -33,7 +33,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) int i; const char *dest = NULL; unsigned flags = 0; + int get_url = 0; int quiet = 0; + int status = 0; const char *uploadpack = NULL; const char **pattern = NULL; @@ -69,6 +71,15 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) quiet = 1; continue; } + if (!strcmp("--get-url", arg)) { + get_url = 1; + continue; + } + if (!strcmp("--exit-code", arg)) { + /* return this code if no refs are reported */ + status = 2; + continue; + } usage(ls_remote_usage); } dest = arg; @@ -94,6 +105,12 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } if (!remote->url_nr) die("remote %s has no configured URL", dest); + + if (get_url) { + printf("%s\n", *remote->url); + return 0; + } + transport = transport_get(remote, NULL); if (uploadpack != NULL) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); @@ -110,6 +127,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (!tail_match(pattern, ref->name)) continue; printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); + status = 0; /* we found something */ } - return 0; + return status; } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index f73e6bd962..f08c5b0c94 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -19,7 +19,7 @@ static int line_termination = '\n'; #define LS_SHOW_SIZE 16 static int abbrev; static int ls_options; -static const char **pathspec; +static struct pathspec pathspec; static int chomp_prefix; static const char *ls_tree_prefix; @@ -35,7 +35,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) if (ls_options & LS_RECURSIVE) return 1; - s = pathspec; + s = pathspec.raw; if (!s) return 0; @@ -120,7 +120,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) { unsigned char sha1[20]; struct tree *tree; - int full_tree = 0; + int i, full_tree = 0; const struct option ls_tree_options[] = { OPT_BIT('d', NULL, &ls_options, "only show trees", LS_TREE_ONLY), @@ -166,11 +166,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) if (get_sha1(argv[0], sha1)) die("Not a valid object name %s", argv[0]); - pathspec = get_pathspec(prefix, argv + 1); + init_pathspec(&pathspec, get_pathspec(prefix, argv + 1)); + for (i = 0; i < pathspec.nr; i++) + pathspec.items[i].use_wildcard = 0; + pathspec.has_wildcard = 0; tree = parse_tree_indirect(sha1); if (!tree) die("not a tree object"); - read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL); + read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); return 0; } diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 96dd160731..4f30f1b0c8 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -23,7 +23,8 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) } static const char * const merge_base_usage[] = { - "git merge-base [-a|--all] [--octopus] <commit> <commit>...", + "git merge-base [-a|--all] <commit> <commit>...", + "git merge-base [-a|--all] --octopus <commit>...", "git merge-base --independent <commit>...", NULL }; diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 2c4cf5e559..2338832587 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -1,6 +1,5 @@ -#include "cache.h" +#include "builtin.h" #include "run-command.h" -#include "exec_cmd.h" static const char *pgm; static int one_shot, quiet; diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index c33091b3ed..3a64f5d0bd 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "commit.h" #include "tag.h" #include "merge-recursive.h" diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 9b25ddc979..897a563bc6 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -1,8 +1,9 @@ -#include "cache.h" +#include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" #include "blob.h" #include "exec_cmd.h" +#include "merge-file.h" static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; static int resolve_directories = 1; @@ -54,8 +55,6 @@ static const char *explanation(struct merge_list *entry) return "removed in remote"; } -extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); - static void *result(struct merge_list *entry, unsigned long *size) { enum object_type type; diff --git a/builtin/merge.c b/builtin/merge.c index c902e81cf1..325891edb6 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -25,6 +25,7 @@ #include "help.h" #include "merge-recursive.h" #include "resolve-undo.h" +#include "remote.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -37,8 +38,9 @@ struct strategy { }; static const char * const builtin_merge_usage[] = { - "git merge [options] <remote>...", - "git merge [options] <msg> HEAD <remote>", + "git merge [options] [<commit>...]", + "git merge [options] <msg> HEAD <commit>", + "git merge --abort", NULL }; @@ -54,10 +56,13 @@ static size_t use_strategies_nr, use_strategies_alloc; static const char **xopts; static size_t xopts_nr, xopts_alloc; static const char *branch; +static char *branch_mergeoptions; static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int show_progress = -1; +static int default_to_upstream; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -80,7 +85,7 @@ static int option_parse_message(const struct option *opt, strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg); have_message = 1; } else - return error("switch `m' requires a value"); + return error(_("switch `m' requires a value")); return 0; } @@ -117,13 +122,13 @@ static struct strategy *get_strategy(const char *name) exclude_cmds(&main_cmds, ¬_strategies); } if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) { - fprintf(stderr, "Could not find merge strategy '%s'.\n", name); - fprintf(stderr, "Available strategies are:"); + fprintf(stderr, _("Could not find merge strategy '%s'.\n"), name); + fprintf(stderr, _("Available strategies are:")); for (i = 0; i < main_cmds.cnt; i++) fprintf(stderr, " %s", main_cmds.names[i]->name); fprintf(stderr, ".\n"); if (other_cmds.cnt) { - fprintf(stderr, "Available custom strategies are:"); + fprintf(stderr, _("Available custom strategies are:")); for (i = 0; i < other_cmds.cnt; i++) fprintf(stderr, " %s", other_cmds.names[i]->name); fprintf(stderr, ".\n"); @@ -195,11 +200,12 @@ static struct option builtin_merge_options[] = { OPT_CALLBACK('X', "strategy-option", &xopts, "option=value", "option for selected merge strategy", option_parse_x), OPT_CALLBACK('m', "message", &merge_msg, "message", - "message to be used for the merge commit (if any)", + "merge commit message (for a non-fast-forward merge)", option_parse_message), OPT__VERBOSITY(&verbosity), OPT_BOOLEAN(0, "abort", &abort_current_merge, "abort the current in-progress merge"), + OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1), OPT_END() }; @@ -224,17 +230,17 @@ static void save_state(void) cp.git_cmd = 1; if (start_command(&cp)) - die("could not run stash."); + die(_("could not run stash.")); len = strbuf_read(&buffer, cp.out, 1024); close(cp.out); if (finish_command(&cp) || len < 0) - die("stash failed"); + die(_("stash failed")); else if (!len) return; strbuf_setlen(&buffer, buffer.len-1); if (get_sha1(buffer.buf, stash)) - die("not a valid object: %s", buffer.buf); + die(_("not a valid object: %s"), buffer.buf); } static void read_empty(unsigned const char *sha1, int verbose) @@ -252,7 +258,7 @@ static void read_empty(unsigned const char *sha1, int verbose) args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) - die("read-tree failed"); + die(_("read-tree failed")); } static void reset_hard(unsigned const char *sha1, int verbose) @@ -269,7 +275,7 @@ static void reset_hard(unsigned const char *sha1, int verbose) args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) - die("read-tree failed"); + die(_("read-tree failed")); } static void restore_state(void) @@ -298,7 +304,7 @@ static void restore_state(void) static void finish_up_to_date(const char *msg) { if (verbosity >= 0) - printf("%s%s\n", squash ? " (nothing to squash)" : "", msg); + printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); drop_save(); } @@ -311,10 +317,10 @@ static void squash_message(void) int fd; struct pretty_print_context ctx = {0}; - printf("Squash commit -- not updating HEAD\n"); + printf(_("Squash commit -- not updating HEAD\n")); fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); if (fd < 0) - die_errno("Could not write to '%s'", git_path("SQUASH_MSG")); + die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG")); init_revisions(&rev, NULL); rev.ignore_merges = 1; @@ -329,7 +335,7 @@ static void squash_message(void) setup_revisions(0, NULL, &rev, NULL); if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); ctx.abbrev = rev.abbrev; ctx.date_mode = rev.date_mode; @@ -343,9 +349,9 @@ static void squash_message(void) pretty_print_commit(&ctx, commit, &out); } if (write(fd, out.buf, out.len) < 0) - die_errno("Writing SQUASH_MSG"); + die_errno(_("Writing SQUASH_MSG")); if (close(fd)) - die_errno("Finishing SQUASH_MSG"); + die_errno(_("Finishing SQUASH_MSG")); strbuf_release(&out); } @@ -365,7 +371,7 @@ static void finish(const unsigned char *new_head, const char *msg) squash_message(); } else { if (verbosity >= 0 && !merge_msg.len) - printf("No merge message -- not updating HEAD\n"); + printf(_("No merge message -- not updating HEAD\n")); else { const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", @@ -387,7 +393,7 @@ static void finish(const unsigned char *new_head, const char *msg) if (diff_use_color_default > 0) DIFF_OPT_SET(&opts, COLOR_DIFF); if (diff_setup_done(&opts) < 0) - die("diff_setup_done failed"); + die(_("diff_setup_done failed")); diff_tree_sha1(head, new_head, "", &opts); diffcore_std(&opts); diff_flush(&opts); @@ -416,7 +422,7 @@ static void merge_name(const char *remote, struct strbuf *msg) memset(branch_head, 0, sizeof(branch_head)); remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); if (!remote_head) - die("'%s' does not point to a commit", remote); + die(_("'%s' does not point to a commit"), remote); if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) { if (!prefixcmp(found_ref, "refs/heads/")) { @@ -481,7 +487,7 @@ static void merge_name(const char *remote, struct strbuf *msg) fp = fopen(git_path("FETCH_HEAD"), "r"); if (!fp) - die_errno("could not open '%s' for reading", + die_errno(_("could not open '%s' for reading"), git_path("FETCH_HEAD")); strbuf_getline(&line, fp, '\n'); fclose(fp); @@ -499,26 +505,34 @@ cleanup: strbuf_release(&bname); } +static void parse_branch_merge_options(char *bmo) +{ + const char **argv; + int argc; + + if (!bmo) + return; + argc = split_cmdline(bmo, &argv); + if (argc < 0) + die(_("Bad branch.%s.mergeoptions string: %s"), branch, + split_cmdline_strerror(argc)); + argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); + memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); + argc++; + argv[0] = "branch.*.mergeoptions"; + parse_options(argc, argv, NULL, builtin_merge_options, + builtin_merge_usage, 0); + free(argv); +} + static int git_merge_config(const char *k, const char *v, void *cb) { if (branch && !prefixcmp(k, "branch.") && !prefixcmp(k + 7, branch) && !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { - const char **argv; - int argc; - char *buf; - - buf = xstrdup(v); - argc = split_cmdline(buf, &argv); - if (argc < 0) - die("Bad branch.%s.mergeoptions string: %s", branch, - split_cmdline_strerror(argc)); - argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); - memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); - argc++; - parse_options(argc, argv, NULL, builtin_merge_options, - builtin_merge_usage, 0); - free(buf); + free(branch_mergeoptions); + branch_mergeoptions = xstrdup(v); + return 0; } if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) @@ -533,10 +547,22 @@ static int git_merge_config(const char *k, const char *v, void *cb) int is_bool; shortlog_len = git_config_bool_or_int(k, v, &is_bool); if (!is_bool && shortlog_len < 0) - return error("%s: negative length %s", k, v); + return error(_("%s: negative length %s"), k, v); if (is_bool && shortlog_len) shortlog_len = DEFAULT_MERGE_LOG_LEN; return 0; + } else if (!strcmp(k, "merge.ff")) { + int boolval = git_config_maybe_bool(k, v); + if (0 <= boolval) { + allow_fast_forward = boolval; + } else if (v && !strcmp(v, "only")) { + allow_fast_forward = 1; + fast_forward_only = 1; + } /* do not barf on values from future versions of git */ + return 0; + } else if (!strcmp(k, "merge.defaulttoupstream")) { + default_to_upstream = git_config_bool(k, v); + return 0; } return git_diff_ui_config(k, v, cb); } @@ -580,10 +606,19 @@ static int read_tree_trivial(unsigned char *common, unsigned char *head, static void write_tree_trivial(unsigned char *sha1) { if (write_cache_as_tree(sha1, 0, NULL)) - die("git write-tree failed to write a tree"); + die(_("git write-tree failed to write a tree")); +} + +static const char *merge_argument(struct commit *commit) +{ + if (commit) + return sha1_to_hex(commit->object.sha1); + else + return EMPTY_TREE_SHA1_HEX; } -int try_merge_command(const char *strategy, struct commit_list *common, +int try_merge_command(const char *strategy, size_t xopts_nr, + const char **xopts, struct commit_list *common, const char *head_arg, struct commit_list *remotes) { const char **args; @@ -602,11 +637,11 @@ int try_merge_command(const char *strategy, struct commit_list *common, args[i++] = s; } for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = xstrdup(merge_argument(j->item)); 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++] = xstrdup(merge_argument(j->item)); args[i] = NULL; ret = run_command_v_opt(args, RUN_GIT_CMD); strbuf_release(&buf); @@ -621,7 +656,7 @@ int try_merge_command(const char *strategy, struct commit_list *common, free(args); discard_cache(); if (read_cache() < 0) - die("failed to read the cache"); + die(_("failed to read the cache")); resolve_undo_clear(); return ret; @@ -638,7 +673,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) - return error("Unable to write index."); + return error(_("Unable to write index.")); rollback_lock_file(lock); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { @@ -651,7 +686,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *j; if (remoteheads->next) { - error("Not handling anything other than two heads merge."); + error(_("Not handling anything other than two heads merge.")); return 2; } @@ -660,10 +695,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, o.subtree_shift = ""; o.renormalize = option_renormalize; + o.show_rename_progress = + show_progress == -1 ? isatty(2) : show_progress; for (x = 0; x < xopts_nr; x++) if (parse_merge_opt(&o, xopts[x])) - die("Unknown option for merge-recursive: -X%s", xopts[x]); + die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); o.branch1 = head_arg; o.branch2 = remoteheads->item->util; @@ -677,11 +714,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) - die ("unable to write %s", get_index_file()); + die (_("unable to write %s"), get_index_file()); rollback_lock_file(lock); return clean ? 0 : 1; } else { - return try_merge_command(strategy, common, head_arg, remoteheads); + return try_merge_command(strategy, xopts_nr, xopts, + common, head_arg, remoteheads); } } @@ -748,7 +786,7 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote return -1; if (write_cache(fd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); return 0; } @@ -796,17 +834,44 @@ static void add_strategies(const char *string, unsigned attr) } +static void write_merge_msg(void) +{ + int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), + git_path("MERGE_MSG")); + if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len) + die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG")); + close(fd); +} + +static void read_merge_msg(void) +{ + strbuf_reset(&merge_msg); + if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0) + die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG")); +} + +static void run_prepare_commit_msg(void) +{ + write_merge_msg(); + run_hook(get_index_file(), "prepare-commit-msg", + git_path("MERGE_MSG"), "merge", NULL, NULL); + read_merge_msg(); +} + static int merge_trivial(void) { unsigned char result_tree[20], result_commit[20]; struct commit_list *parent = xmalloc(sizeof(*parent)); write_tree_trivial(result_tree); - printf("Wonderful.\n"); + printf(_("Wonderful.\n")); parent->item = lookup_commit(head); parent->next = xmalloc(sizeof(*parent->next)); parent->next->item = remoteheads->item; parent->next->next = NULL; + run_prepare_commit_msg(); commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL); finish(result_commit, "In-index merge"); drop_save(); @@ -836,6 +901,7 @@ static int finish_automerge(struct commit_list *common, } free_commit_list(remoteheads); strbuf_addch(&merge_msg, '\n'); + run_prepare_commit_msg(); commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); strbuf_addf(&buf, "Merge made by %s.", wt_strategy); finish(result_commit, buf.buf); @@ -851,7 +917,7 @@ static int suggest_conflicts(int renormalizing) fp = fopen(git_path("MERGE_MSG"), "a"); if (!fp) - die_errno("Could not open '%s' for writing", + die_errno(_("Could not open '%s' for writing"), git_path("MERGE_MSG")); fprintf(fp, "\nConflicts:\n"); for (pos = 0; pos < active_nr; pos++) { @@ -867,8 +933,8 @@ static int suggest_conflicts(int renormalizing) } fclose(fp); rerere(allow_rerere_auto); - printf("Automatic merge failed; " - "fix conflicts and then commit the result.\n"); + printf(_("Automatic merge failed; " + "fix conflicts and then commit the result.\n")); return 1; } @@ -882,7 +948,7 @@ static struct commit *is_old_style_invocation(int argc, const char **argv) return NULL; second_token = lookup_commit_reference_gently(second_sha1, 0); if (!second_token) - die("'%s' is not a commit", argv[1]); + die(_("'%s' is not a commit"), argv[1]); if (hashcmp(second_token->object.sha1, head)) return NULL; } @@ -912,6 +978,35 @@ static int evaluate_result(void) return cnt; } +/* + * Pretend as if the user told us to merge with the tracking + * branch we have for the upstream of the current branch + */ +static int setup_with_upstream(const char ***argv) +{ + struct branch *branch = branch_get(NULL); + int i; + const char **args; + + if (!branch) + die(_("No current branch.")); + if (!branch->remote) + die(_("No remote for the current branch.")); + if (!branch->merge_nr) + die(_("No default upstream defined for the current branch.")); + + args = xcalloc(branch->merge_nr + 1, sizeof(char *)); + for (i = 0; i < branch->merge_nr; i++) { + if (!branch->merge[i]->dst) + die(_("No remote tracking branch for %s from %s"), + branch->merge[i]->src, branch->remote_name); + args[i] = branch->merge[i]->dst; + } + args[i] = NULL; + *argv = args; + return i; +} + int cmd_merge(int argc, const char **argv, const char *prefix) { unsigned char result_tree[20]; @@ -942,15 +1037,20 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; + if (branch_mergeoptions) + parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); + if (verbosity < 0 && show_progress == -1) + show_progress = 0; + if (abort_current_merge) { int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; if (!file_exists(git_path("MERGE_HEAD"))) - die("There is no merge to abort (MERGE_HEAD missing)."); + die(_("There is no merge to abort (MERGE_HEAD missing).")); /* Invoke 'git reset --merge' */ return cmd_reset(nargc, nargv, prefix); @@ -965,10 +1065,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * add/rm <file>', just 'git commit'. */ if (advice_resolve_conflict) - die("You have not concluded your merge (MERGE_HEAD exists).\n" - "Please, commit your changes before you can merge."); + die(_("You have not concluded your merge (MERGE_HEAD exists).\n" + "Please, commit your changes before you can merge.")); + else + die(_("You have not concluded your merge (MERGE_HEAD exists).")); + } + if (file_exists(git_path("CHERRY_PICK_HEAD"))) { + if (advice_resolve_conflict) + die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" + "Please, commit your changes before you can merge.")); else - die("You have not concluded your merge (MERGE_HEAD exists)."); + die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).")); } resolve_undo_clear(); @@ -977,13 +1084,19 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (squash) { if (!allow_fast_forward) - die("You cannot combine --squash with --no-ff."); + die(_("You cannot combine --squash with --no-ff.")); option_commit = 0; } if (!allow_fast_forward && fast_forward_only) - die("You cannot combine --no-ff with --ff-only."); + die(_("You cannot combine --no-ff with --ff-only.")); + if (!abort_current_merge) { + if (!argc && default_to_upstream) + argc = setup_with_upstream(&argv); + else if (argc == 1 && !strcmp(argv[0], "-")) + argv[0] = "@{-1}"; + } if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1010,19 +1123,19 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * We do the same for "git pull". */ if (argc != 1) - die("Can merge only exactly one commit into " - "empty head"); + die(_("Can merge only exactly one commit into " + "empty head")); if (squash) - die("Squash commit into empty head not supported yet"); + die(_("Squash commit into empty head not supported yet")); if (!allow_fast_forward) - die("Non-fast-forward commit does not make sense into " - "an empty head"); + die(_("Non-fast-forward commit does not make sense into " + "an empty head")); remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); if (!remote_head) - die("%s - not something we can merge", argv[0]); + die(_("%s - not something we can merge"), argv[0]); + read_empty(remote_head->sha1, 0); update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, DIE_ON_ERR); - read_empty(remote_head->sha1, 0); return 0; } else { struct strbuf merge_names = STRBUF_INIT; @@ -1065,7 +1178,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); if (!o) - die("%s - not something we can merge", argv[i]); + die(_("%s - not something we can merge"), argv[i]); commit = lookup_commit(o->sha1); commit->util = (void *)argv[i]; remotes = &commit_list_insert(commit, remotes)->next; @@ -1123,7 +1236,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV)); if (verbosity >= 0) - printf("Updating %s..%s\n", + printf(_("Updating %s..%s\n"), hex, find_unique_abbrev(remoteheads->item->object.sha1, DEFAULT_ABBREV)); @@ -1157,11 +1270,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (allow_trivial && !fast_forward_only) { /* See if it is really trivial. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); - printf("Trying really trivial in-index merge...\n"); + printf(_("Trying really trivial in-index merge...\n")); if (!read_tree_trivial(common->item->object.sha1, head, remoteheads->item->object.sha1)) return merge_trivial(); - printf("Nope.\n"); + printf(_("Nope.\n")); } } else { /* @@ -1194,7 +1307,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (fast_forward_only) - die("Not possible to fast-forward, aborting."); + die(_("Not possible to fast-forward, aborting.")); /* We are going to make a new commit. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); @@ -1220,11 +1333,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < use_strategies_nr; i++) { int ret; if (i) { - printf("Rewinding the tree to pristine...\n"); + printf(_("Rewinding the tree to pristine...\n")); restore_state(); } if (use_strategies_nr != 1) - printf("Trying merge strategy %s...\n", + printf(_("Trying merge strategy %s...\n"), use_strategies[i]->name); /* * Remember which strategy left the state in the working @@ -1285,17 +1398,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) restore_state(); if (use_strategies_nr > 1) fprintf(stderr, - "No merge strategy handled the merge.\n"); + _("No merge strategy handled the merge.\n")); else - fprintf(stderr, "Merge with strategy %s failed.\n", + fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); return 2; } else if (best_strategy == wt_strategy) ; /* We already have its result in the working tree. */ else { - printf("Rewinding the tree to pristine...\n"); + printf(_("Rewinding the tree to pristine...\n")); restore_state(); - printf("Using the %s to prepare resolving by hand.\n", + printf(_("Using the %s to prepare resolving by hand.\n"), best_strategy); try_merge_strategy(best_strategy, common, head_arg); } @@ -1311,35 +1424,28 @@ int cmd_merge(int argc, const char **argv, const char *prefix) sha1_to_hex(j->item->object.sha1)); fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666); if (fd < 0) - die_errno("Could not open '%s' for writing", + die_errno(_("Could not open '%s' for writing"), git_path("MERGE_HEAD")); if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_HEAD")); + die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD")); close(fd); strbuf_addch(&merge_msg, '\n'); - fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MSG")); - if (write_in_full(fd, merge_msg.buf, merge_msg.len) != - merge_msg.len) - die_errno("Could not write to '%s'", git_path("MERGE_MSG")); - close(fd); + write_merge_msg(); fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) - die_errno("Could not open '%s' for writing", + die_errno(_("Could not open '%s' for writing"), git_path("MERGE_MODE")); strbuf_reset(&buf); if (!allow_fast_forward) strbuf_addf(&buf, "no-ff"); if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_MODE")); + die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE")); close(fd); } if (merge_was_ok) { - fprintf(stderr, "Automatic merge went well; " - "stopped before committing as requested\n"); + fprintf(stderr, _("Automatic merge went well; " + "stopped before committing as requested\n")); return 0; } else return suggest_conflicts(option_renormalize); diff --git a/builtin/mktag.c b/builtin/mktag.c index 1cb0f3f2a7..640ab64f41 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -1,6 +1,5 @@ -#include "cache.h" +#include "builtin.h" #include "tag.h" -#include "exec_cmd.h" /* * A signature file has a very simple fixed format: four lines @@ -24,8 +23,8 @@ static int verify_object(const unsigned char *sha1, const char *expected_type) int ret = -1; enum object_type type; unsigned long size; - const unsigned char *repl; - void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); + void *buffer = read_sha1_file(sha1, &type, &size); + const unsigned char *repl = lookup_replace_object(sha1); if (buffer) { if (type == type_from_string(expected_type)) @@ -35,12 +34,6 @@ static int verify_object(const unsigned char *sha1, const char *expected_type) return ret; } -#ifdef NO_C99_FORMAT -#define PD_FMT "%d" -#else -#define PD_FMT "%td" -#endif - static int verify_tag(char *buffer, unsigned long size) { int typelen; @@ -70,15 +63,18 @@ static int verify_tag(char *buffer, unsigned long size) /* Verify tag-line */ tag_line = strchr(type_line, '\n'); if (!tag_line) - return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer); + return error("char%"PRIuMAX": could not find next \"\\n\"", + (uintmax_t) (type_line - buffer)); tag_line++; if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer); + return error("char%"PRIuMAX": no \"tag \" found", + (uintmax_t) (tag_line - buffer)); /* Get the actual type */ typelen = tag_line - type_line - strlen("type \n"); if (typelen >= sizeof(type)) - return error("char" PD_FMT ": type too long", type_line+5 - buffer); + return error("char%"PRIuMAX": type too long", + (uintmax_t) (type_line+5 - buffer)); memcpy(type, type_line+5, typelen); type[typelen] = 0; @@ -95,15 +91,16 @@ static int verify_tag(char *buffer, unsigned long size) break; if (c > ' ') continue; - return error("char" PD_FMT ": could not verify tag name", tag_line - buffer); + return error("char%"PRIuMAX": could not verify tag name", + (uintmax_t) (tag_line - buffer)); } /* Verify the tagger line */ tagger_line = tag_line; if (memcmp(tagger_line, "tagger ", 7)) - return error("char" PD_FMT ": could not find \"tagger \"", - tagger_line - buffer); + return error("char%"PRIuMAX": could not find \"tagger \"", + (uintmax_t) (tagger_line - buffer)); /* * Check for correct form for name and email @@ -115,44 +112,42 @@ static int verify_tag(char *buffer, unsigned long size) if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) || strpbrk(tagger_line, "<>\n") != lb+1 || strpbrk(lb+2, "><\n ") != rb) - return error("char" PD_FMT ": malformed tagger field", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tagger field", + (uintmax_t) (tagger_line - buffer)); /* Check for author name, at least one character, space is acceptable */ if (lb == tagger_line) - return error("char" PD_FMT ": missing tagger name", - tagger_line - buffer); + return error("char%"PRIuMAX": missing tagger name", + (uintmax_t) (tagger_line - buffer)); /* timestamp, 1 or more digits followed by space */ tagger_line = rb + 2; if (!(len = strspn(tagger_line, "0123456789"))) - return error("char" PD_FMT ": missing tag timestamp", - tagger_line - buffer); + return error("char%"PRIuMAX": missing tag timestamp", + (uintmax_t) (tagger_line - buffer)); tagger_line += len; if (*tagger_line != ' ') - return error("char" PD_FMT ": malformed tag timestamp", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tag timestamp", + (uintmax_t) (tagger_line - buffer)); tagger_line++; /* timezone, 5 digits [+-]hhmm, max. 1400 */ if (!((tagger_line[0] == '+' || tagger_line[0] == '-') && strspn(tagger_line+1, "0123456789") == 4 && tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400)) - return error("char" PD_FMT ": malformed tag timezone", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tag timezone", + (uintmax_t) (tagger_line - buffer)); tagger_line += 6; /* Verify the blank line separating the header from the body */ if (*tagger_line != '\n') - return error("char" PD_FMT ": trailing garbage in tag header", - tagger_line - buffer); + return error("char%"PRIuMAX": trailing garbage in tag header", + (uintmax_t) (tagger_line - buffer)); /* The actual stuff afterwards we don't care about.. */ return 0; } -#undef PD_FMT - int cmd_mktag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; diff --git a/builtin/mv.c b/builtin/mv.c index 93e8995d9e..40f33ca4d0 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -74,7 +74,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) newfd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); source = copy_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); @@ -100,17 +100,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) const char *bad = NULL; if (show_only) - printf("Checking rename of '%s' to '%s'\n", src, dst); + printf(_("Checking rename of '%s' to '%s'\n"), src, dst); length = strlen(src); if (lstat(src, &st) < 0) - bad = "bad source"; + bad = _("bad source"); else if (!strncmp(src, dst, length) && (dst[length] == 0 || dst[length] == '/')) { - bad = "can not move directory into itself"; + bad = _("can not move directory into itself"); } else if ((src_is_dir = S_ISDIR(st.st_mode)) && lstat(dst, &st) == 0) - bad = "cannot move directory over file"; + bad = _("cannot move directory over file"); else if (src_is_dir) { const char *src_w_slash = add_slash(src); int len_w_slash = length + 1; @@ -120,7 +120,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) first = cache_name_pos(src_w_slash, len_w_slash); if (first >= 0) - die ("Huh? %.*s is in index?", + die (_("Huh? %.*s is in index?"), len_w_slash, src_w_slash); first = -1 - first; @@ -132,7 +132,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) free((char *)src_w_slash); if (last - first < 1) - bad = "source directory is empty"; + bad = _("source directory is empty"); else { int j, dst_len; @@ -163,22 +163,22 @@ int cmd_mv(int argc, const char **argv, const char *prefix) argc += last - first; } } else if (cache_name_pos(src, length) < 0) - bad = "not under version control"; + bad = _("not under version control"); else if (lstat(dst, &st) == 0) { - bad = "destination exists"; + bad = _("destination exists"); if (force) { /* * only files can overwrite each other: * check both source and destination */ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - warning("%s; will overwrite!", bad); + warning(_("%s; will overwrite!"), bad); bad = NULL; } else - bad = "Cannot overwrite"; + bad = _("Cannot overwrite"); } } else if (string_list_has_string(&src_for_dst, dst)) - bad = "multiple sources for the same target"; + bad = _("multiple sources for the same target"); else string_list_insert(&src_for_dst, dst); @@ -193,7 +193,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) i--; } } else - die ("%s, source=%s, destination=%s", + die (_("%s, source=%s, destination=%s"), bad, src, dst); } } @@ -203,10 +203,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) enum update_mode mode = modes[i]; int pos; if (show_only || verbose) - printf("Renaming %s to %s\n", src, dst); + printf(_("Renaming %s to %s\n"), src, dst); if (!show_only && mode != INDEX && rename(src, dst) < 0 && !ignore_errors) - die_errno ("renaming '%s' failed", src); + die_errno (_("renaming '%s' failed"), src); if (mode == WORKING_DIRECTORY) continue; @@ -220,7 +220,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return 0; diff --git a/builtin/notes.c b/builtin/notes.c index 4d5556e2cb..f8e437db01 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -29,7 +29,7 @@ static const char * const git_notes_usage[] = { "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>", "git notes merge --commit [-v | -q]", "git notes merge --abort [-v | -q]", - "git notes [--ref <notes_ref>] remove [<object>]", + "git notes [--ref <notes_ref>] remove [<object>...]", "git notes [--ref <notes_ref>] prune [-n | -v]", "git notes [--ref <notes_ref>] get-ref", NULL @@ -100,16 +100,6 @@ struct msg_arg { struct strbuf buf; }; -static void expand_notes_ref(struct strbuf *sb) -{ - if (!prefixcmp(sb->buf, "refs/notes/")) - return; /* we're happy */ - else if (!prefixcmp(sb->buf, "notes/")) - strbuf_insert(sb, 0, "refs/", 5); - else - strbuf_insert(sb, 0, "refs/notes/", 11); -} - static int list_each_note(const unsigned char *object_sha1, const unsigned char *note_sha1, char *note_path, void *cb_data) @@ -146,13 +136,13 @@ static void write_commented_object(int fd, const unsigned char *object) show.err = 0; show.git_cmd = 1; if (start_command(&show)) - die("unable to start 'show' for object '%s'", + 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"); + 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) { @@ -162,10 +152,10 @@ static void write_commented_object(int fd, const unsigned char *object) } strbuf_release(&buf); if (fclose(show_out)) - die_errno("failed to close pipe to 'show' for object '%s'", + 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'", + die(_("failed to finish 'show' for object '%s'"), sha1_to_hex(object)); } @@ -182,7 +172,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, 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); + die_errno(_("could not create file '%s'"), path); if (msg->given) write_or_die(fd, msg->buf.buf, msg->buf.len); @@ -196,8 +186,8 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, strbuf_reset(&(msg->buf)); if (launch_editor(path, &(msg->buf), NULL)) { - die("Please supply the note contents using either -m" \ - " or -F option"); + die(_("Please supply the note contents using either -m" \ + " or -F option")); } stripspace(&(msg->buf), 1); } @@ -217,14 +207,14 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, } if (!msg->buf.len) { - fprintf(stderr, "Removing note for object %s\n", + 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"); + error(_("unable to write note object")); if (path) - error("The note contents has been left in %s", + error(_("The note contents has been left in %s"), path); exit(128); } @@ -258,9 +248,9 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset) strbuf_addch(&(msg->buf), '\n'); if (!strcmp(arg, "-")) { if (strbuf_read(&(msg->buf), 0, 1024) < 0) - die_errno("cannot read '%s'", arg); + 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); + die_errno(_("could not open or read '%s'"), arg); stripspace(&(msg->buf), 0); msg->given = 1; @@ -279,10 +269,10 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) strbuf_addch(&(msg->buf), '\n'); if (get_sha1(arg, object)) - die("Failed to resolve '%s' as a valid ref.", arg); + 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);; + die(_("Failed to read object '%s'."), arg);; } strbuf_add(&(msg->buf), buf, len); free(buf); @@ -306,7 +296,7 @@ void commit_notes(struct notes_tree *t, const char *msg) if (!t) t = &default_notes_tree; if (!t->initialized || !t->ref || !*t->ref) - die("Cannot commit uninitialized/unreferenced notes tree"); + die(_("Cannot commit uninitialized/unreferenced notes tree")); if (!t->dirty) return; /* don't have to commit an unchanged tree */ @@ -347,7 +337,7 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb) config_error_nonbool(k); c->combine = parse_combine_notes_fn(v); if (!c->combine) { - error("Bad notes.rewriteMode value: '%s'", v); + error(_("Bad notes.rewriteMode value: '%s'"), v); return 1; } return 0; @@ -357,8 +347,8 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb) 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); + warning(_("Refusing to rewrite notes in %s" + " (outside of refs/notes/)"), v); return 0; } @@ -382,8 +372,10 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) 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); + /* TRANSLATORS: The first %s is the name of the + environment variable, the second %s is its value */ + error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT, + rewrite_mode_env); } if (rewrite_refs_env) { c->refs_from_env = 1; @@ -423,7 +415,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c) free(c); } -int notes_copy_from_stdin(int force, const char *rewrite_cmd) +static int notes_copy_from_stdin(int force, const char *rewrite_cmd) { struct strbuf buf = STRBUF_INIT; struct notes_rewrite_cfg *c = NULL; @@ -446,13 +438,13 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd) split = strbuf_split(&buf, ' '); if (!split[0] || !split[1]) - die("Malformed input line: '%s'.", buf.buf); + 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); + 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); + 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); @@ -461,7 +453,7 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd) combine_notes_overwrite); if (err) { - error("Failed to copy notes from '%s' to '%s'", + error(_("Failed to copy notes from '%s' to '%s'"), split[0]->buf, split[1]->buf); ret = 1; } @@ -505,20 +497,20 @@ static int list(int argc, const char **argv, const char *prefix) git_notes_list_usage, 0); if (1 < argc) { - error("too many parameters"); + 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]); + 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.", + retval = error(_("No note found for object %s."), sha1_to_hex(object)); } else retval = for_each_note(t, 0, list_each_note, NULL); @@ -527,6 +519,8 @@ static int list(int argc, const char **argv, const char *prefix) return retval; } +static int append_edit(int argc, const char **argv, const char *prefix); + static int add(int argc, const char **argv, const char *prefix) { int retval = 0, force = 0; @@ -537,16 +531,16 @@ static int add(int argc, const char **argv, const char *prefix) const unsigned char *note; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &msg, "MSG", + { OPTION_CALLBACK, 'm', "message", &msg, "msg", "note contents as a string", PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, "FILE", + { OPTION_CALLBACK, 'F', "file", &msg, "file", "note contents in a file", PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT", + { 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", + { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object", "reuse specified note object", PARSE_OPT_NONEG, parse_reuse_arg}, OPT__FORCE(&force, "replace existing notes"), @@ -554,29 +548,41 @@ static int add(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, git_notes_add_usage, - 0); + PARSE_OPT_KEEP_ARGV0); - if (1 < argc) { - error("too many parameters"); + if (2 < argc) { + error(_("too many parameters")); usage_with_options(git_notes_add_usage, options); } - object_ref = argc ? argv[0] : "HEAD"; + object_ref = argc > 1 ? argv[1] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + 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 " + if (!msg.given) { + /* + * Redirect to "edit" subcommand. + * + * We only end up here if none of -m/-F/-c/-C + * or -f are given. The original args are + * therefore still in argv[0-1]. + */ + argv[0] = "edit"; + free_notes(t); + return append_edit(argc, argv, prefix); + } + retval = error(_("Cannot add notes. Found existing notes " "for object %s. Use '-f' to overwrite " - "existing notes", sha1_to_hex(object)); + "existing notes"), sha1_to_hex(object)); goto out; } - fprintf(stderr, "Overwriting existing notes for object %s\n", + fprintf(stderr, _("Overwriting existing notes for object %s\n"), sha1_to_hex(object)); } @@ -618,7 +624,7 @@ static int copy(int argc, const char **argv, const char *prefix) if (from_stdin || rewrite_cmd) { if (argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_copy_usage, options); } else { return notes_copy_from_stdin(force, rewrite_cmd); @@ -626,41 +632,41 @@ static int copy(int argc, const char **argv, const char *prefix) } if (argc < 2) { - error("too few parameters"); + error(_("too few parameters")); usage_with_options(git_notes_copy_usage, options); } if (2 < argc) { - error("too many parameters"); + 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]); + 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); + 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 " + retval = error(_("Cannot copy notes. Found existing " "notes for object %s. Use '-f' to " - "overwrite existing notes", + "overwrite existing notes"), sha1_to_hex(object)); goto out; } - fprintf(stderr, "Overwriting existing notes for object %s\n", + 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)); + retval = error(_("Missing notes on source object %s. Cannot " + "copy."), sha1_to_hex(from_obj)); goto out; } @@ -682,16 +688,16 @@ static int append_edit(int argc, const char **argv, const char *prefix) const char * const *usage; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &msg, "MSG", + { OPTION_CALLBACK, 'm', "message", &msg, "msg", "note contents as a string", PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, "FILE", + { OPTION_CALLBACK, 'F', "file", &msg, "file", "note contents in a file", PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT", + { 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", + { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object", "reuse specified note object", PARSE_OPT_NONEG, parse_reuse_arg}, OPT_END() @@ -703,19 +709,19 @@ static int append_edit(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(usage, options); } if (msg.given && edit) - fprintf(stderr, "The -m/-F/-c/-C options have been deprecated " + 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"); + "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); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check(argv[0]); note = get_note(t, object); @@ -750,20 +756,20 @@ static int show(int argc, const char **argv, const char *prefix) 0); if (1 < argc) { - error("too many parameters"); + 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); + 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.", + retval = error(_("No note found for object %s."), sha1_to_hex(object)); else { const char *show_args[3] = {"show", sha1_to_hex(note), NULL}; @@ -819,7 +825,7 @@ static int merge_commit(struct notes_merge_options *o) t = xcalloc(1, sizeof(struct notes_tree)); init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); - o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, 0); + o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL); if (!o->local_ref) die("Failed to resolve NOTES_MERGE_REF"); @@ -947,40 +953,60 @@ static int merge(int argc, const char **argv, const char *prefix) return result < 0; /* return non-zero on conflicts */ } +#define IGNORE_MISSING 1 + +static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag) +{ + int status; + unsigned char sha1[20]; + if (get_sha1(name, sha1)) + return error(_("Failed to resolve '%s' as a valid ref."), name); + status = remove_note(t, sha1); + if (status) + fprintf(stderr, _("Object %s has no note\n"), name); + else + fprintf(stderr, _("Removing note for object %s\n"), name); + return (flag & IGNORE_MISSING) ? 0 : status; +} + static int remove_cmd(int argc, const char **argv, const char *prefix) { + unsigned flag = 0; + int from_stdin = 0; struct option options[] = { + OPT_BIT(0, "ignore-missing", &flag, + "attempt to remove non-existent note is not an error", + IGNORE_MISSING), + OPT_BOOLEAN(0, "stdin", &from_stdin, + "read object names from the standard input"), OPT_END() }; - const char *object_ref; struct notes_tree *t; - unsigned char object[20]; - int retval; + int retval = 0; 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"); - retval = remove_note(t, object); - if (retval) - fprintf(stderr, "Object %s has no note\n", sha1_to_hex(object)); - else { - fprintf(stderr, "Removing note for object %s\n", - sha1_to_hex(object)); - - commit_notes(t, "Notes removed by 'git notes remove'"); + if (!argc && !from_stdin) { + retval = remove_one_note(t, "HEAD", flag); + } else { + while (*argv) { + retval |= remove_one_note(t, *argv, flag); + argv++; + } + } + if (from_stdin) { + struct strbuf sb = STRBUF_INIT; + while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) { + strbuf_rtrim(&sb); + retval |= remove_one_note(t, sb.buf, flag); + } + strbuf_release(&sb); } + if (!retval) + commit_notes(t, "Notes removed by 'git notes remove'"); free_notes(t); return retval; } @@ -999,7 +1025,7 @@ static int prune(int argc, const char **argv, const char *prefix) 0); if (argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_prune_usage, options); } @@ -1069,7 +1095,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[0], "get-ref")) result = get_ref(argc, argv, prefix); else { - result = error("Unknown subcommand: %s", argv[0]); + result = error(_("Unknown subcommand: %s"), argv[0]); usage_with_options(git_notes_usage, options); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index b0503b202a..f402a843bb 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1142,8 +1142,12 @@ static void get_object_details(void) sorted_by_offset[i] = objects + i; qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort); - for (i = 0; i < nr_objects; i++) - check_object(sorted_by_offset[i]); + for (i = 0; i < nr_objects; i++) { + struct object_entry *entry = sorted_by_offset[i]; + check_object(entry); + if (big_file_threshold <= entry->size) + entry->no_try_delta = 1; + } free(sorted_by_offset); } diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 41e1615a28..f5c6afc5dd 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -6,8 +6,7 @@ * */ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" #define BLKSIZE 512 diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index 091860b2e3..39a9d89fbd 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "pack-refs.h" diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 512530022e..f821eb3f0b 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,5 +1,4 @@ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) { @@ -57,7 +56,7 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) +static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) { static char line[1000]; int patchlen = 0, found_next = 0; @@ -73,6 +72,8 @@ int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) p += 7; else if (!memcmp(line, "From ", 5)) p += 5; + else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line)) + continue; if (!get_sha1_hex(p, next_sha1)) { found_next = 1; diff --git a/builtin/push.c b/builtin/push.c index e655eb7695..9cebf9ea23 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -40,7 +40,7 @@ static void set_refspecs(const char **refs, int nr) char *tag; int len; if (nr <= ++i) - die("tag shorthand without <tag>"); + die(_("tag shorthand without <tag>")); len = strlen(refs[i]) + 11; if (deleterefs) { tag = xmalloc(len+1); @@ -59,28 +59,38 @@ static void set_refspecs(const char **refs, int nr) strcat(delref, ref); ref = delref; } else if (deleterefs) - die("--delete only accepts plain target ref names"); + die(_("--delete only accepts plain target ref names")); add_refspec(ref); } } -static void setup_push_tracking(void) +static void setup_push_upstream(struct remote *remote) { struct strbuf refspec = STRBUF_INIT; struct branch *branch = branch_get(NULL); if (!branch) - die("You are not currently on a branch."); + die(_("You are not currently on a branch.\n" + "To push the history leading to the current (detached HEAD)\n" + "state now, use\n" + "\n" + " git push %s HEAD:<name-of-remote-branch>\n"), + remote->name); if (!branch->merge_nr || !branch->merge) - die("The current branch %s is not tracking anything.", + die(_("The current branch %s has no upstream branch.\n" + "To push the current branch and set the remote as upstream, use\n" + "\n" + " git push --set-upstream %s %s\n"), + branch->name, + remote->name, branch->name); if (branch->merge_nr != 1) - die("The current branch %s is tracking multiple branches, " - "refusing to push.", branch->name); + die(_("The current branch %s has multiple upstream branches, " + "refusing to push."), branch->name); strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); } -static void setup_default_push_refspecs(void) +static void setup_default_push_refspecs(struct remote *remote) { switch (push_default) { default: @@ -88,8 +98,8 @@ static void setup_default_push_refspecs(void) add_refspec(":"); break; - case PUSH_DEFAULT_TRACKING: - setup_push_tracking(); + case PUSH_DEFAULT_UPSTREAM: + setup_push_upstream(remote); break; case PUSH_DEFAULT_CURRENT: @@ -97,8 +107,8 @@ static void setup_default_push_refspecs(void) break; case PUSH_DEFAULT_NOTHING: - die("You didn't specify any refspecs to push, and " - "push.default is \"nothing\"."); + die(_("You didn't specify any refspecs to push, and " + "push.default is \"nothing\".")); break; } } @@ -117,11 +127,11 @@ static int push_with_options(struct transport *transport, int flags) transport_set_option(transport, TRANS_OPT_THIN, "yes"); if (verbosity > 0) - fprintf(stderr, "Pushing to %s\n", transport->url); + fprintf(stderr, _("Pushing to %s\n"), transport->url); err = transport_push(transport, refspec_nr, refspec, flags, &nonfastforward); if (err != 0) - error("failed to push some refs to '%s'", transport->url); + error(_("failed to push some refs to '%s'"), transport->url); err |= transport_disconnect(transport); @@ -129,9 +139,9 @@ static int push_with_options(struct transport *transport, int flags) return 0; if (nonfastforward && advice_push_nonfastforward) { - fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\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"); + "'Note about fast-forwards' section of 'git push --help' for details.\n")); } return 1; @@ -146,8 +156,15 @@ static int do_push(const char *repo, int flags) if (!remote) { if (repo) - die("bad repository '%s'", repo); - die("No destination configured to push to."); + die(_("bad repository '%s'"), repo); + die(_("No configured push destination.\n" + "Either specify the URL from the command-line or configure a remote repository using\n" + "\n" + " git remote add <name> <url>\n" + "\n" + "and then push using the remote name\n" + "\n" + " git push <name>\n")); } if (remote->mirror) @@ -155,19 +172,19 @@ static int do_push(const char *repo, int flags) if ((flags & TRANSPORT_PUSH_ALL) && refspec) { if (!strcmp(*refspec, "refs/tags/*")) - return error("--all and --tags are incompatible"); - return error("--all can't be combined with refspecs"); + return error(_("--all and --tags are incompatible")); + return error(_("--all can't be combined with refspecs")); } if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) { if (!strcmp(*refspec, "refs/tags/*")) - return error("--mirror and --tags are incompatible"); - return error("--mirror can't be combined with refspecs"); + return error(_("--mirror and --tags are incompatible")); + return error(_("--mirror can't be combined with refspecs")); } if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { - return error("--all and --mirror are incompatible"); + return error(_("--all and --mirror are incompatible")); } if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) { @@ -175,7 +192,7 @@ static int do_push(const char *repo, int flags) refspec = remote->push_refspec; refspec_nr = remote->push_refspec_nr; } else if (!(flags & TRANSPORT_PUSH_MIRROR)) - setup_default_push_refspecs(); + setup_default_push_refspecs(remote); } errs = 0; if (remote->pushurl_nr) { @@ -228,13 +245,14 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_END() }; + packet_trace_identity("push"); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) - die("--delete is incompatible with --all, --mirror and --tags"); + die(_("--delete is incompatible with --all, --mirror and --tags")); if (deleterefs && argc < 2) - die("--delete doesn't make sense without any refs"); + die(_("--delete doesn't make sense without any refs")); if (tags) add_refspec("refs/tags/*"); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 73c89ed15b..df6c4c8819 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -104,8 +104,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) struct unpack_trees_options opts; int prefix_set = 0; const struct option read_tree_options[] = { - { OPTION_CALLBACK, 0, "index-output", NULL, "FILE", - "write resulting index to <FILE>", + { OPTION_CALLBACK, 0, "index-output", NULL, "file", + "write resulting index to <file>", PARSE_OPT_NONEG, index_output_cb }, OPT_SET_INT(0, "empty", &read_empty, "only empty the index", 1), @@ -130,6 +130,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) PARSE_OPT_NONEG, exclude_per_directory_cb }, OPT_SET_INT('i', NULL, &opts.index_only, "don't check the working tree after merging", 1), + OPT__DRY_RUN(&opts.dry_run, "don't update the index or the work tree"), OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, "skip applying sparse checkout filter", 1), OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack, @@ -219,7 +220,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if (unpack_trees(nr_trees, t, &opts)) return 128; - if (opts.debug_unpack) + if (opts.debug_unpack || opts.dry_run) return 0; /* do not write the index out */ /* diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 760817dbd7..e1a687ad07 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "pack.h" #include "refs.h" #include "pkt-line.h" @@ -10,6 +10,7 @@ #include "remote.h" #include "transport.h" #include "string-list.h" +#include "sha1-array.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -731,43 +732,23 @@ static int delete_only(struct command *commands) return 1; } -static int add_refs_from_alternate(struct alternate_object_database *e, void *unused) +static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused) { - char *other; - size_t len; - struct remote *remote; - struct transport *transport; - const struct ref *extra; - - e->name[-1] = '\0'; - other = xstrdup(make_absolute_path(e->base)); - e->name[-1] = '/'; - len = strlen(other); - - while (other[len-1] == '/') - other[--len] = '\0'; - if (len < 8 || memcmp(other + len - 8, "/objects", 8)) - return 0; - /* Is this a git repository with refs? */ - memcpy(other + len - 8, "/refs", 6); - if (!is_directory(other)) - return 0; - other[len - 8] = '\0'; - remote = remote_get(other); - transport = transport_get(remote, other); - for (extra = transport_get_remote_refs(transport); - extra; - extra = extra->next) { - add_extra_ref(".have", extra->old_sha1, 0); - } - transport_disconnect(transport); - free(other); - return 0; + add_extra_ref(".have", sha1, 0); +} + +static void collect_one_alternate_ref(const struct ref *ref, void *data) +{ + struct sha1_array *sa = data; + sha1_array_append(sa, ref->old_sha1); } static void add_alternate_refs(void) { - foreach_alt_odb(add_refs_from_alternate, NULL); + struct sha1_array sa = SHA1_ARRAY_INIT; + for_each_alternate_ref(collect_one_alternate_ref, &sa); + sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL); + sha1_array_clear(&sa); } int cmd_receive_pack(int argc, const char **argv, const char *prefix) @@ -778,6 +759,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) char *dir = NULL; struct command *commands; + packet_trace_identity("receive-pack"); + argv++; for (i = 1; i < argc; i++) { const char *arg = *argv++; diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index ea71977c83..692c834d9d 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -1,4 +1,4 @@ -#include "git-compat-util.h" +#include "builtin.h" #include "transport.h" #include "run-command.h" @@ -30,16 +30,12 @@ static char *strip_escapes(const char *str, const char *service, size_t rpos = 0; int escape = 0; char special = 0; - size_t pslen = 0; - size_t pSlen = 0; size_t psoff = 0; struct strbuf ret = STRBUF_INIT; /* Calculate prefix length for \s and lengths for \s and \S */ if (!strncmp(service, "git-", 4)) psoff = 4; - pSlen = strlen(service); - pslen = pSlen - psoff; /* Pass the service to command. */ setenv("GIT_EXT_SERVICE", service, 1); diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c index 1f2467bdb7..08d7121b6d 100644 --- a/builtin/remote-fd.c +++ b/builtin/remote-fd.c @@ -1,4 +1,4 @@ -#include "git-compat-util.h" +#include "builtin.h" #include "transport.h" /* diff --git a/builtin/remote.c b/builtin/remote.c index cb26080956..9ff1cac69b 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "transport.h" #include "remote.h" @@ -9,7 +9,7 @@ static const char * const builtin_remote_usage[] = { "git remote [-v | --verbose]", - "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>", + "git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>", "git remote rename <old> <new>", "git remote rm <name>", "git remote set-head <name> (-a | -d | <branch>)", @@ -117,6 +117,11 @@ enum { TAGS_SET = 2 }; +#define MIRROR_NONE 0 +#define MIRROR_FETCH 1 +#define MIRROR_PUSH 2 +#define MIRROR_BOTH (MIRROR_FETCH|MIRROR_PUSH) + static int add_branch(const char *key, const char *branchname, const char *remotename, int mirror, struct strbuf *tmp) { @@ -131,9 +136,32 @@ static int add_branch(const char *key, const char *branchname, return git_config_set_multivar(key, tmp->buf, "^$", 0); } +static const char mirror_advice[] = +"--mirror is dangerous and deprecated; please\n" +"\t use --mirror=fetch or --mirror=push instead"; + +static int parse_mirror_opt(const struct option *opt, const char *arg, int not) +{ + unsigned *mirror = opt->value; + if (not) + *mirror = MIRROR_NONE; + else if (!arg) { + warning("%s", mirror_advice); + *mirror = MIRROR_BOTH; + } + else if (!strcmp(arg, "fetch")) + *mirror = MIRROR_FETCH; + else if (!strcmp(arg, "push")) + *mirror = MIRROR_PUSH; + else + return error("unknown mirror argument: %s", arg); + return 0; +} + static int add(int argc, const char **argv) { - int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT; + int fetch = 0, fetch_tags = TAGS_DEFAULT; + unsigned mirror = MIRROR_NONE; struct string_list track = STRING_LIST_INIT_NODUP; const char *master = NULL; struct remote *remote; @@ -151,7 +179,9 @@ static int add(int argc, const char **argv) OPT_CALLBACK('t', "track", &track, "branch", "branch(es) to track", opt_parse_track), OPT_STRING('m', "master", &master, "branch", "master branch"), - OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"), + { OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch", + "set up remote as a mirror to push to or fetch from", + PARSE_OPT_OPTARG, parse_mirror_opt }, OPT_END() }; @@ -161,6 +191,11 @@ static int add(int argc, const char **argv) if (argc < 2) usage_with_options(builtin_remote_add_usage, options); + if (mirror && master) + die("specifying a master branch makes no sense with --mirror"); + if (mirror && !(mirror & MIRROR_FETCH) && track.nr) + die("specifying branches to track makes sense only with fetch mirrors"); + name = argv[0]; url = argv[1]; @@ -177,18 +212,19 @@ static int add(int argc, const char **argv) if (git_config_set(buf.buf, url)) return 1; - strbuf_reset(&buf); - strbuf_addf(&buf, "remote.%s.fetch", name); - - if (track.nr == 0) - string_list_append(&track, "*"); - for (i = 0; i < track.nr; i++) { - if (add_branch(buf.buf, track.items[i].string, - name, mirror, &buf2)) - return 1; + if (!mirror || mirror & MIRROR_FETCH) { + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", name); + if (track.nr == 0) + string_list_append(&track, "*"); + for (i = 0; i < track.nr; i++) { + if (add_branch(buf.buf, track.items[i].string, + name, mirror, &buf2)) + return 1; + } } - if (mirror) { + if (mirror & MIRROR_PUSH) { strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.mirror", name); if (git_config_set(buf.buf, "true")) diff --git a/builtin/rerere.c b/builtin/rerere.c index 642bf35587..08213c7c0b 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -8,78 +8,10 @@ #include "xdiff-interface.h" static const char * const rerere_usage[] = { - "git rerere [clear | status | diff | gc]", + "git rerere [clear | forget path... | status | remaining | diff | gc]", NULL, }; -/* these values are days */ -static int cutoff_noresolve = 15; -static int cutoff_resolve = 60; - -static time_t rerere_created_at(const char *name) -{ - struct stat st; - return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; -} - -static time_t rerere_last_used_at(const char *name) -{ - struct stat st; - return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime; -} - -static void unlink_rr_item(const char *name) -{ - unlink(rerere_path(name, "thisimage")); - unlink(rerere_path(name, "preimage")); - unlink(rerere_path(name, "postimage")); - rmdir(git_path("rr-cache/%s", name)); -} - -static int git_rerere_gc_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "gc.rerereresolved")) - cutoff_resolve = git_config_int(var, value); - else if (!strcmp(var, "gc.rerereunresolved")) - cutoff_noresolve = git_config_int(var, value); - else - return git_default_config(var, value, cb); - return 0; -} - -static void garbage_collect(struct string_list *rr) -{ - struct string_list to_remove = STRING_LIST_INIT_DUP; - DIR *dir; - struct dirent *e; - int i, cutoff; - time_t now = time(NULL), then; - - git_config(git_rerere_gc_config, NULL); - dir = opendir(git_path("rr-cache")); - if (!dir) - die_errno("unable to open rr-cache directory"); - while ((e = readdir(dir))) { - if (is_dot_or_dotdot(e->d_name)) - continue; - - then = rerere_last_used_at(e->d_name); - if (then) { - cutoff = cutoff_resolve; - } else { - then = rerere_created_at(e->d_name); - if (!then) - continue; - cutoff = cutoff_noresolve; - } - if (then < now - cutoff * 86400) - string_list_append(&to_remove, e->d_name); - } - for (i = 0; i < to_remove.nr; i++) - unlink_rr_item(to_remove.items[i].string); - string_list_clear(&to_remove, 0); -} - static int outf(void *dummy, mmbuffer_t *ptr, int nbuf) { int i; @@ -136,7 +68,10 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) return rerere(flags); if (!strcmp(argv[0], "forget")) { - const char **pathspec = get_pathspec(prefix, argv + 1); + const char **pathspec; + if (argc < 2) + warning("'git rerere forget' without paths is deprecated"); + pathspec = get_pathspec(prefix, argv + 1); return rerere_forget(pathspec); } @@ -145,18 +80,23 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) return 0; if (!strcmp(argv[0], "clear")) { - for (i = 0; i < merge_rr.nr; i++) { - const char *name = (const char *)merge_rr.items[i].util; - if (!has_rerere_resolution(name)) - unlink_rr_item(name); - } - unlink_or_warn(git_path("MERGE_RR")); + rerere_clear(&merge_rr); } else if (!strcmp(argv[0], "gc")) - garbage_collect(&merge_rr); + rerere_gc(&merge_rr); else if (!strcmp(argv[0], "status")) for (i = 0; i < merge_rr.nr; i++) printf("%s\n", merge_rr.items[i].string); - else if (!strcmp(argv[0], "diff")) + else if (!strcmp(argv[0], "remaining")) { + rerere_remaining(&merge_rr); + for (i = 0; i < merge_rr.nr; i++) { + if (merge_rr.items[i].util != RERERE_RESOLVED) + printf("%s\n", merge_rr.items[i].string); + else + /* prepare for later call to + * string_list_clear() */ + merge_rr.items[i].util = NULL; + } + } else if (!strcmp(argv[0], "diff")) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; const char *name = (const char *)merge_rr.items[i].util; diff --git a/builtin/reset.c b/builtin/reset.c index 5de2bceeec..98bca044c1 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -7,7 +7,7 @@ * * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ -#include "cache.h" +#include "builtin.h" #include "tag.h" #include "object.h" #include "commit.h" @@ -30,7 +30,7 @@ static const char * const git_reset_usage[] = { enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE }; static const char *reset_type_names[] = { - "mixed", "soft", "hard", "merge", "keep", NULL + N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL }; static char *args_to_str(const char **argv) @@ -92,20 +92,20 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet if (reset_type == KEEP) { unsigned char head_sha1[20]; if (get_sha1("HEAD", head_sha1)) - return error("You do not have a valid HEAD."); + return error(_("You do not have a valid HEAD.")); if (!fill_tree_descriptor(desc, head_sha1)) - return error("Failed to find tree of HEAD."); + 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)); + return error(_("Failed to find tree of %s."), sha1_to_hex(sha1)); if (unpack_trees(nr, desc, &opts)) return -1; if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock)) - return error("Could not write new index file."); + return error(_("Could not write new index file.")); return 0; } @@ -115,7 +115,7 @@ static void print_new_head_line(struct commit *commit) const char *hex, *body; hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); - printf("HEAD is now at %s", hex); + printf(_("HEAD is now at %s"), hex); body = strstr(commit->buffer, "\n\n"); if (body) { const char *eol; @@ -139,10 +139,10 @@ static int update_index_refresh(int fd, struct lock_file *index_lock, int flags) } if (read_cache() < 0) - return error("Could not read index"); + return error(_("Could not read index")); result = refresh_index(&the_index, (flags), NULL, NULL, - "Unstaged changes after reset:") ? 1 : 0; + _("Unstaged changes after reset:")) ? 1 : 0; if (write_cache(fd, active_cache, active_nr) || commit_locked_index(index_lock)) return error ("Could not refresh index"); @@ -167,7 +167,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, ce = make_cache_entry(one->mode, one->sha1, one->path, 0, 0); if (!ce) - die("make_cache_entry failed for path '%s'", + die(_("make_cache_entry failed for path '%s'"), one->path); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); @@ -222,14 +222,14 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size) if (!rla) rla = sep = ""; if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size) - warning("Reflog action message too long: %.*s...", 50, buf); + 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]); + die(_("Cannot do a %s reset in the middle of a merge."), + _(reset_type_names[reset_type])); } @@ -300,16 +300,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix) } if (get_sha1(rev, sha1)) - die("Failed to resolve '%s' as a valid ref.", rev); + die(_("Failed to resolve '%s' as a valid ref."), rev); commit = lookup_commit_reference(sha1); if (!commit) - die("Could not parse object '%s'.", rev); + die(_("Could not parse object '%s'."), rev); hashcpy(sha1, commit->object.sha1); if (patch_mode) { if (reset_type != NONE) - die("--patch is incompatible with --{hard,mixed,soft}"); + die(_("--patch is incompatible with --{hard,mixed,soft}")); return interactive_reset(rev, argv + i, prefix); } @@ -318,10 +318,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix) * affecting the working tree nor HEAD. */ if (i < argc) { if (reset_type == MIXED) - warning("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."); + warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead.")); else if (reset_type != NONE) - die("Cannot do %s reset with paths.", - reset_type_names[reset_type]); + die(_("Cannot do %s reset with paths."), + _(reset_type_names[reset_type])); return read_from_tree(prefix, argv + i, sha1, quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); } @@ -332,8 +332,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) setup_work_tree(); if (reset_type == MIXED && is_bare_repository()) - die("%s reset is not allowed in a bare repository", - reset_type_names[reset_type]); + die(_("%s reset is not allowed in a bare repository"), + _(reset_type_names[reset_type])); /* Soft reset does not touch the index file nor the working tree * at all, but requires them in a good order. Other resets reset @@ -348,7 +348,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type == KEEP) err = err || reset_index_file(sha1, MIXED, quiet); if (err) - die("Could not reset index file to revision '%s'.", rev); + die(_("Could not reset index file to revision '%s'."), rev); } /* Any resets update HEAD to the head being switched to, diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 0ec42fc75c..56727e8c1d 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -16,6 +16,10 @@ static const char rev_list_usage[] = " --min-age=<epoch>\n" " --sparse\n" " --no-merges\n" +" --min-parents=<n>\n" +" --no-min-parents\n" +" --max-parents=<n>\n" +" --no-max-parents\n" " --remove-empty\n" " --all\n" " --branches\n" @@ -51,7 +55,9 @@ static void show_commit(struct commit *commit, void *data) graph_show_commit(revs->graph); if (revs->count) { - if (commit->object.flags & SYMMETRIC_LEFT) + if (commit->object.flags & PATCHSAME) + revs->count_same++; + else if (commit->object.flags & SYMMETRIC_LEFT) revs->count_left++; else revs->count_right++; @@ -64,18 +70,8 @@ static void show_commit(struct commit *commit, void *data) if (info->header_prefix) fputs(info->header_prefix, stdout); - if (!revs->graph) { - if (commit->object.flags & BOUNDARY) - putchar('-'); - else if (commit->object.flags & UNINTERESTING) - putchar('^'); - else if (revs->left_right) { - if (commit->object.flags & SYMMETRIC_LEFT) - putchar('<'); - else - putchar('>'); - } - } + if (!revs->graph) + fputs(get_revision_mark(revs, commit), stdout); if (revs->abbrev_commit && revs->abbrev) fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev), stdout); @@ -413,8 +409,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) &info); if (revs.count) { - if (revs.left_right) + if (revs.left_right && revs.cherry_mark) + printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same); + else if (revs.left_right) printf("%d\t%d\n", revs.count_left, revs.count_right); + else if (revs.cherry_mark) + printf("%d\t%d\n", revs.count_left + revs.count_right, revs.count_same); else printf("%d\n", revs.count_left + revs.count_right); } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index a5a1c86e92..4c19f844a9 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -44,10 +44,15 @@ static int is_rev_argument(const char *arg) "--branches=", "--branches", "--header", + "--ignore-missing", "--max-age=", "--max-count=", "--min-age=", "--no-merges", + "--min-parents=", + "--no-min-parents", + "--max-parents=", + "--no-max-parents", "--objects", "--objects-edge", "--parents", diff --git a/builtin/revert.c b/builtin/revert.c index bb6e9e83b7..1f27c63343 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -3,7 +3,6 @@ #include "object.h" #include "commit.h" #include "tag.h" -#include "wt-status.h" #include "run-command.h" #include "exec_cmd.h" #include "utf8.h" @@ -44,7 +43,11 @@ static const char **commit_argv; static int allow_rerere_auto; static const char *me; + +/* Merge strategy. */ static const char *strategy; +static const char **xopts; +static size_t xopts_nr, xopts_alloc; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -55,6 +58,17 @@ static const char * const *revert_or_cherry_pick_usage(void) return action == REVERT ? revert_usage : cherry_pick_usage; } +static int option_parse_x(const struct option *opt, + const char *arg, int unset) +{ + if (unset) + return 0; + + ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); + xopts[xopts_nr++] = xstrdup(arg); + return 0; +} + static void parse_args(int argc, const char **argv) { const char * const * usage_str = revert_or_cherry_pick_usage(); @@ -62,11 +76,14 @@ static void parse_args(int argc, const char **argv) 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('r', NULL, &noop, "no-op (backward compatibility)"), + { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 }, OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"), + OPT_CALLBACK('X', "strategy-option", &xopts, "option", + "option for merge strategy", option_parse_x), OPT_END(), OPT_END(), OPT_END(), @@ -79,7 +96,7 @@ static void parse_args(int argc, const char **argv) OPT_END(), }; if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) - die("program error"); + die(_("program error")); } commit_argc = parse_options(argc, argv, NULL, options, usage_str, @@ -151,7 +168,7 @@ static char *get_encoding(const char *message) const char *p = message, *eol; if (!p) - die ("Could not read commit message of %s", + die (_("Could not read commit message of %s"), sha1_to_hex(commit->object.sha1)); while (*p && *p != '\n') { for (eol = p + 1; *eol && *eol != '\n'; eol++) @@ -181,54 +198,20 @@ static void add_message_to_msg(struct strbuf *msgbuf, const char *message) strbuf_addstr(msgbuf, p); } -static void set_author_ident_env(const char *message) +static void write_cherry_pick_head(void) { - const char *p = message; - if (!p) - die ("Could not read commit message of %s", - sha1_to_hex(commit->object.sha1)); - while (*p && *p != '\n') { - const char *eol; - - for (eol = p; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - if (!prefixcmp(p, "author ")) { - char *line, *pend, *email, *timestamp; - - p += 7; - line = xmemdupz(p, eol - p); - email = strchr(line, '<'); - if (!email) - die ("Could not extract author email from %s", - sha1_to_hex(commit->object.sha1)); - if (email == line) - pend = line; - else - for (pend = email; pend != line + 1 && - isspace(pend[-1]); pend--); - ; /* do nothing */ - *pend = '\0'; - email++; - timestamp = strchr(email, '>'); - if (!timestamp) - die ("Could not extract author time from %s", - sha1_to_hex(commit->object.sha1)); - *timestamp = '\0'; - for (timestamp++; *timestamp && isspace(*timestamp); - timestamp++) - ; /* do nothing */ - setenv("GIT_AUTHOR_NAME", line, 1); - setenv("GIT_AUTHOR_EMAIL", email, 1); - setenv("GIT_AUTHOR_DATE", timestamp, 1); - free(line); - return; - } - p = eol; - if (*p == '\n') - p++; - } - die ("No author information found in %s", - sha1_to_hex(commit->object.sha1)); + int fd; + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); + + fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), + git_path("CHERRY_PICK_HEAD")); + if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) + die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD")); + strbuf_release(&buf); } static void advise(const char *advice, ...) @@ -246,15 +229,18 @@ static void print_advice(void) if (msg) { fprintf(stderr, "%s\n", msg); + /* + * A conflict has occured but the porcelain + * (typically rebase --interactive) wants to take care + * of the commit itself so remove CHERRY_PICK_HEAD + */ + unlink(git_path("CHERRY_PICK_HEAD")); return; } advise("after resolving the conflicts, mark the corrected paths"); advise("with 'git add <paths>' or 'git rm <paths>'"); - - if (action == CHERRY_PICK) - advise("and commit the result with 'git commit -c %s'", - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + advise("and commit the result with 'git commit'"); } static void write_message(struct strbuf *msgbuf, const char *filename) @@ -264,10 +250,10 @@ static void write_message(struct strbuf *msgbuf, const char *filename) int msg_fd = hold_lock_file_for_update(&msg_file, filename, LOCK_DIE_ON_ERROR); if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - die_errno("Could not write to %s.", filename); + die_errno(_("Could not write to %s."), filename); strbuf_release(msgbuf); if (commit_lock_file(&msg_file) < 0) - die("Error wrapping up %s", filename); + die(_("Error wrapping up %s"), filename); } static struct tree *empty_tree(void) @@ -285,11 +271,19 @@ static NORETURN void die_dirty_index(const char *me) if (read_cache_unmerged()) { die_resolve_conflict(me); } else { - if (advice_commit_before_merge) - die("Your local changes would be overwritten by %s.\n" - "Please, commit your changes or stash them to proceed.", me); - else - die("Your local changes would be overwritten by %s.\n", me); + if (advice_commit_before_merge) { + if (action == REVERT) + die(_("Your local changes would be overwritten by revert.\n" + "Please, commit your changes or stash them to proceed.")); + else + die(_("Your local changes would be overwritten by cherry-pick.\n" + "Please, commit your changes or stash them to proceed.")); + } else { + if (action == REVERT) + die(_("Your local changes would be overwritten by revert.\n")); + else + die(_("Your local changes would be overwritten by cherry-pick.\n")); + } } } @@ -311,18 +305,13 @@ static int do_recursive_merge(struct commit *base, struct commit *next, struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; int clean, index_fd; + const char **xopt; static struct lock_file index_lock; index_fd = hold_locked_index(&index_lock, 1); read_cache(); - /* - * NEEDSWORK: cherry-picking between branches with - * different end-of-line normalization is a pain; - * plumb in an option to set o.renormalize? - * (or better: arbitrary -X options) - */ init_merge_options(&o); o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; @@ -332,6 +321,9 @@ static int do_recursive_merge(struct commit *base, struct commit *next, next_tree = next ? next->tree : empty_tree(); base_tree = base ? base->tree : empty_tree(); + for (xopt = xopts; xopt != xopts + xopts_nr; xopt++) + parse_merge_opt(&o, *xopt); + clean = merge_trees(&o, head_tree, next_tree, base_tree, &result); @@ -339,7 +331,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, 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); + /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + die(_("%s: Unable to write new index file"), me); rollback_lock_file(&index_lock); if (!clean) { @@ -405,18 +398,16 @@ static int do_pick_commit(void) * to work on. */ if (write_cache_as_tree(head, 0, NULL)) - die ("Your index file is unmerged."); + die (_("Your index file is unmerged.")); } else { if (get_sha1("HEAD", head)) - die ("You do not have a valid HEAD"); + die (_("You do not have a valid HEAD")); if (index_differs_from("HEAD", 0)) die_dirty_index(me); } discard_cache(); if (!commit->parents) { - if (action == REVERT) - die ("Cannot revert a root commit"); parent = NULL; } else if (commit->parents->next) { @@ -425,7 +416,7 @@ static int do_pick_commit(void) struct commit_list *p; if (!mainline) - die("Commit %s is a merge but no -m option was given.", + die(_("Commit %s is a merge but no -m option was given."), sha1_to_hex(commit->object.sha1)); for (cnt = 1, p = commit->parents; @@ -433,11 +424,11 @@ static int do_pick_commit(void) cnt++) p = p->next; if (cnt != mainline || !p) - die("Commit %s does not have parent %d", + die(_("Commit %s does not have parent %d"), sha1_to_hex(commit->object.sha1), mainline); parent = p->item; } else if (0 < mainline) - die("Mainline was specified but commit %s is not a merge.", + die(_("Mainline was specified but commit %s is not a merge."), sha1_to_hex(commit->object.sha1)); else parent = commit->parents->item; @@ -446,11 +437,13 @@ static int do_pick_commit(void) return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) - die("%s: cannot parse parent commit %s", + /* TRANSLATORS: The first %s will be "revert" or + "cherry-pick", the second %s a SHA1 */ + die(_("%s: cannot parse parent commit %s"), me, sha1_to_hex(parent->object.sha1)); if (get_message(commit->buffer, &msg) != 0) - die("Cannot get commit message for %s", + die(_("Cannot get commit message for %s"), sha1_to_hex(commit->object.sha1)); /* @@ -472,7 +465,7 @@ static int do_pick_commit(void) strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); - if (commit->parents->next) { + if (commit->parents && commit->parents->next) { strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); } @@ -482,13 +475,14 @@ static int do_pick_commit(void) base_label = msg.parent_label; next = commit; next_label = msg.label; - set_author_ident_env(msg.message); add_message_to_msg(&msgbuf, msg.message); if (no_replay) { strbuf_addstr(&msgbuf, "(cherry picked from commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } + if (!no_commit) + write_cherry_pick_head(); } if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) { @@ -503,15 +497,16 @@ static int do_pick_commit(void) commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res = try_merge_command(strategy, common, + res = try_merge_command(strategy, xopts_nr, xopts, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); } if (res) { - error("could not %s %s... %s", - action == REVERT ? "revert" : "apply", + error(action == REVERT + ? _("could not revert %s... %s") + : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), msg.subject); print_advice(); @@ -541,10 +536,10 @@ static void prepare_revs(struct rev_info *revs) usage(*revert_or_cherry_pick_usage()); if (prepare_revision_walk(revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); if (!revs->commits) - die("empty commit set passed"); + die(_("empty commit set passed")); } static void read_and_refresh_cache(const char *me) @@ -552,12 +547,12 @@ static void read_and_refresh_cache(const char *me) static struct lock_file index_lock; int index_fd = hold_locked_index(&index_lock, 0); if (read_index_preload(&the_index, NULL) < 0) - die("git %s: failed to read the index", me); + die(_("git %s: failed to read the index"), me); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed) { if (write_index(&the_index, index_fd) || commit_locked_index(&index_lock)) - die("git %s: failed to refresh the index", me); + die(_("git %s: failed to refresh the index"), me); } rollback_lock_file(&index_lock); } @@ -573,13 +568,13 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (allow_ff) { if (signoff) - die("cherry-pick --ff cannot be used with --signoff"); + die(_("cherry-pick --ff cannot be used with --signoff")); if (no_commit) - die("cherry-pick --ff cannot be used with --no-commit"); + die(_("cherry-pick --ff cannot be used with --no-commit")); if (no_replay) - die("cherry-pick --ff cannot be used with -x"); + die(_("cherry-pick --ff cannot be used with -x")); if (edit) - die("cherry-pick --ff cannot be used with --edit"); + die(_("cherry-pick --ff cannot be used with --edit")); } read_and_refresh_cache(me); diff --git a/builtin/rm.c b/builtin/rm.c index ff491d7761..90c8a5047c 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -106,19 +106,19 @@ static int check_local_mod(unsigned char *head, int index_only) */ if (local_changes && staged_changes) { if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) - errs = error("'%s' has staged content different " + errs = error(_("'%s' has staged content different " "from both the file and the HEAD\n" - "(use -f to force removal)", name); + "(use -f to force removal)"), name); } else if (!index_only) { if (staged_changes) - errs = error("'%s' has changes staged in the index\n" + errs = error(_("'%s' has changes staged in the index\n" "(use --cached to keep the file, " - "or -f to force removal)", name); + "or -f to force removal)"), name); if (local_changes) - errs = error("'%s' has local modifications\n" + errs = error(_("'%s' has local modifications\n" "(use --cached to keep the file, " - "or -f to force removal)", name); + "or -f to force removal)"), name); } } return errs; @@ -159,7 +159,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) newfd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); pathspec = get_pathspec(prefix, argv); refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); @@ -183,7 +183,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) for (i = 0; (match = pathspec[i]) != NULL ; i++) { if (!seen[i]) { if (!ignore_unmatch) { - die("pathspec '%s' did not match any files", + die(_("pathspec '%s' did not match any files"), match); } } @@ -191,7 +191,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen_any = 1; } if (!recursive && seen[i] == MATCHED_RECURSIVELY) - die("not removing '%s' recursively without -r", + die(_("not removing '%s' recursively without -r"), *match ? match : "."); } @@ -227,7 +227,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) printf("rm '%s'\n", path); if (remove_file_from_cache(path)) - die("git rm: unable to remove %s", path); + die(_("git rm: unable to remove %s"), path); } if (show_only) @@ -257,7 +257,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return 0; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 2cd1c40b70..c1f6ddd927 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "commit.h" #include "refs.h" #include "pkt-line.h" @@ -228,8 +228,11 @@ static void print_helper_status(struct ref *ref) static int sideband_demux(int in, int out, void *data) { - int *fd = data; - int ret = recv_sideband("send-pack", fd[0], out); + int *fd = data, ret; +#ifdef NO_PTHREADS + close(fd[1]); +#endif + ret = recv_sideband("send-pack", fd[0], out); close(out); return ret; } @@ -339,6 +342,10 @@ int send_pack(struct send_pack_args *args, if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; + if (args->stateless_rpc) + close(out); + if (git_connection_is_socket(conn)) + shutdown(fd[0], SHUT_WR); if (use_sideband) finish_async(&demux); return -1; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 074fd26048..37f3193a33 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -29,9 +29,6 @@ static int compare_by_number(const void *a1, const void *a2) return -1; } -const char *format_subject(struct strbuf *sb, const char *msg, - const char *line_separator); - static void insert_one_record(struct shortlog *log, const char *author, const char *oneline) @@ -157,7 +154,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) buffer = eol; } if (!author) - die("Missing author: %s", + die(_("Missing author: %s"), sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct pretty_print_context ctx = {0}; @@ -181,7 +178,7 @@ static void get_from_rev(struct rev_info *rev, struct shortlog *log) struct commit *commit; if (prepare_revision_walk(rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(rev)) != NULL) shortlog_add_commit(log, commit); } @@ -284,7 +281,7 @@ parse_done: argc = parse_options_end(&ctx); if (setup_revisions(argc, argv, &rev, NULL) != 1) { - error("unrecognized argument: %s", argv[1]); + error(_("unrecognized argument: %s"), argv[1]); usage_with_options(shortlog_usage, options); } @@ -296,7 +293,7 @@ parse_done: add_head_to_pending(&rev); if (rev.pending.nr == 0) { if (isatty(0)) - fprintf(stderr, "(reading log message from standard input)\n"); + 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 a5fc2aa884..facc63a79e 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -12,16 +12,6 @@ static const char* show_branch_usage[] = { }; static int showbranch_use_color = -1; -static char column_colors[][COLOR_MAXLEN] = { - GIT_COLOR_RED, - GIT_COLOR_GREEN, - GIT_COLOR_YELLOW, - GIT_COLOR_BLUE, - GIT_COLOR_MAGENTA, - GIT_COLOR_CYAN, -}; - -#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) static int default_num; static int default_alloc; @@ -37,7 +27,7 @@ static const char **default_arg; static const char *get_color_code(int idx) { if (showbranch_use_color) - return column_colors[idx]; + return column_colors_ansi[idx % column_colors_ansi_max]; return ""; } @@ -891,7 +881,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) for (j = 0; j < i; j++) putchar(' '); printf("%s%c%s [%s] ", - get_color_code(i % COLUMN_COLORS_MAX), + get_color_code(i), is_head ? '*' : '!', get_color_reset_code(), ref_name[i]); } @@ -953,7 +943,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) else mark = '+'; printf("%s%c%s", - get_color_code(i % COLUMN_COLORS_MAX), + get_color_code(i), mark, get_color_reset_code()); } putchar(' '); diff --git a/builtin/tag.c b/builtin/tag.c index aa1f87d47a..ec926fc8ee 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -118,12 +118,12 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) for (p = argv; *p; p++) { if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p) >= sizeof(ref)) { - error("tag name too long: %.*s...", 50, *p); + error(_("tag name too long: %.*s..."), 50, *p); had_error = 1; continue; } if (!resolve_ref(ref, sha1, 1, NULL)) { - error("tag '%s' not found.", *p); + error(_("tag '%s' not found."), *p); had_error = 1; continue; } @@ -138,7 +138,7 @@ static int delete_tag(const char *name, const char *ref, { if (delete_ref(ref, sha1, 0)) return 1; - printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); + printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 0; } @@ -150,7 +150,7 @@ static int verify_tag(const char *name, const char *ref, argv_verify_tag[2] = sha1_to_hex(sha1); if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD)) - return error("could not verify the tag '%s'", name); + return error(_("could not verify the tag '%s'"), name); return 0; } @@ -165,7 +165,7 @@ static int do_sign(struct strbuf *buffer) if (!*signingkey) { if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME), sizeof(signingkey)) > sizeof(signingkey) - 1) - return error("committer info too long."); + return error(_("committer info too long.")); bracket = strchr(signingkey, '>'); if (bracket) bracket[1] = '\0'; @@ -185,20 +185,20 @@ static int do_sign(struct strbuf *buffer) args[3] = NULL; if (start_command(&gpg)) - return error("could not run gpg."); + return error(_("could not run gpg.")); if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) { close(gpg.in); close(gpg.out); finish_command(&gpg); - return error("gpg did not accept the tag data"); + return error(_("gpg did not accept the tag data")); } close(gpg.in); len = strbuf_read(buffer, gpg.out, 1024); close(gpg.out); if (finish_command(&gpg) || !len || len < 0) - return error("gpg failed to sign the tag"); + return error(_("gpg failed to sign the tag")); /* Strip CR from the line endings, in case we are on Windows. */ for (i = j = 0; i < buffer->len; i++) @@ -213,15 +213,15 @@ static int do_sign(struct strbuf *buffer) } static const char tag_template[] = - "\n" + N_("\n" "#\n" "# Write a tag message\n" - "#\n"; + "#\n"); static void set_signingkey(const char *value) { if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey)) - die("signing key value too long (%.10s...)", value); + die(_("signing key value too long (%.10s...)"), value); } static int git_tag_config(const char *var, const char *value, void *cb) @@ -261,9 +261,9 @@ static void write_tag_body(int fd, const unsigned char *sha1) static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result) { if (sign && do_sign(buf) < 0) - return error("unable to sign the tag"); + return error(_("unable to sign the tag")); if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0) - return error("unable to write tag file"); + return error(_("unable to write tag file")); return 0; } @@ -278,7 +278,7 @@ static void create_tag(const unsigned char *object, const char *tag, type = sha1_object_info(object, NULL); if (type <= OBJ_NONE) - die("bad object type."); + die(_("bad object type.")); header_len = snprintf(header_buf, sizeof(header_buf), "object %s\n" @@ -291,7 +291,7 @@ static void create_tag(const unsigned char *object, const char *tag, git_committer_info(IDENT_ERROR_ON_NO_NAME)); if (header_len > sizeof(header_buf) - 1) - die("tag header too big."); + die(_("tag header too big.")); if (!message) { int fd; @@ -300,17 +300,17 @@ static void create_tag(const unsigned char *object, const char *tag, path = git_pathdup("TAG_EDITMSG"); fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) - die_errno("could not create file '%s'", path); + die_errno(_("could not create file '%s'"), path); if (!is_null_sha1(prev)) write_tag_body(fd, prev); else - write_or_die(fd, tag_template, strlen(tag_template)); + write_or_die(fd, _(tag_template), strlen(_(tag_template))); close(fd); if (launch_editor(path, buf, NULL)) { fprintf(stderr, - "Please supply the message using either -m or -F option.\n"); + _("Please supply the message using either -m or -F option.\n")); exit(1); } } @@ -318,13 +318,13 @@ static void create_tag(const unsigned char *object, const char *tag, stripspace(buf, 1); if (!message && !buf->len) - die("no tag message?"); + die(_("no tag message?")); strbuf_insert(buf, 0, header_buf, header_len); if (build_tag_object(buf, sign, result) < 0) { if (path) - fprintf(stderr, "The tag message has been left in %s\n", + fprintf(stderr, _("The tag message has been left in %s\n"), path); exit(128); } @@ -352,11 +352,22 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) return 0; } +static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) +{ + if (name[0] == '-') + return CHECK_REF_FORMAT_ERROR; + + strbuf_reset(sb); + strbuf_addf(sb, "refs/tags/%s", name); + + return check_ref_format(sb->buf); +} + int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; + struct strbuf ref = STRBUF_INIT; unsigned char object[20], prev[20]; - char ref[PATH_MAX]; const char *object_ref, *tag; struct ref_lock *lock; @@ -376,9 +387,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_GROUP("Tag creation options"), OPT_BOOLEAN('a', NULL, &annotate, "annotated tag, needs a message"), - OPT_CALLBACK('m', NULL, &msg, "msg", - "message for the tag", parse_msg_arg), - OPT_FILENAME('F', NULL, &msgfile, "message in a file"), + OPT_CALLBACK('m', NULL, &msg, "message", + "tag message", parse_msg_arg), + OPT_FILENAME('F', NULL, &msgfile, "read message from file"), OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), OPT_STRING('u', NULL, &keyid, "key-id", "use another key to sign the tag"), @@ -417,9 +428,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) return list_tags(argv[0], lines == -1 ? 0 : lines, with_commit); if (lines != -1) - die("-n option is only allowed with -l."); + die(_("-n option is only allowed with -l.")); if (with_commit) - die("--contains option is only allowed with -l."); + die(_("--contains option is only allowed with -l.")); if (delete) return for_each_tag_name(argv, delete_tag); if (verify) @@ -427,17 +438,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (msg.given || msgfile) { if (msg.given && msgfile) - die("only one -F or -m option is allowed."); + die(_("only one -F or -m option is allowed.")); annotate = 1; if (msg.given) strbuf_addbuf(&buf, &(msg.buf)); else { if (!strcmp(msgfile, "-")) { if (strbuf_read(&buf, 0, 1024) < 0) - die_errno("cannot read '%s'", msgfile); + die_errno(_("cannot read '%s'"), msgfile); } else { if (strbuf_read_file(&buf, msgfile, 1024) < 0) - die_errno("could not open or read '%s'", + die_errno(_("could not open or read '%s'"), msgfile); } } @@ -447,33 +458,32 @@ int cmd_tag(int argc, const char **argv, const char *prefix) object_ref = argc == 2 ? argv[1] : "HEAD"; if (argc > 2) - die("too many params"); + die(_("too many params")); if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); - if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1) - die("tag name too long: %.*s...", 50, tag); - if (check_ref_format(ref)) - die("'%s' is not a valid tag name.", tag); + if (strbuf_check_tag_ref(&ref, tag)) + die(_("'%s' is not a valid tag name."), tag); - if (!resolve_ref(ref, prev, 1, NULL)) + if (!resolve_ref(ref.buf, prev, 1, NULL)) hashclr(prev); else if (!force) - die("tag '%s' already exists", tag); + die(_("tag '%s' already exists"), tag); if (annotate) create_tag(object, tag, &buf, msg.given || msgfile, sign, prev, object); - lock = lock_any_ref_for_update(ref, prev, 0); + lock = lock_any_ref_for_update(ref.buf, prev, 0); if (!lock) - die("%s: cannot lock the ref", ref); + die(_("%s: cannot lock the ref"), ref.buf); if (write_ref_sha1(lock, object, NULL) < 0) - die("%s: cannot update the ref", ref); + die(_("%s: cannot update the ref"), ref.buf); if (force && hashcmp(prev, object)) - printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); + printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); strbuf_release(&buf); + strbuf_release(&ref); return 0; } diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 608590ada8..19200291a2 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -1,6 +1,4 @@ -#include "cache.h" -#include "blob.h" -#include "exec_cmd.h" +#include "builtin.h" static char *create_temp_file(unsigned char *sha1) { diff --git a/builtin/update-index.c b/builtin/update-index.c index 56baf27fb7..f14bc90830 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -99,7 +99,8 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(ce->sha1, path, st, !info_only)) + if (index_path(ce->sha1, path, st, + info_only ? 0 : HASH_WRITE_OBJECT)) return -1; option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; @@ -546,7 +547,10 @@ static int do_reupdate(int ac, const char **av, */ int pos; int has_head = 1; - const char **pathspec = get_pathspec(prefix, av + 1); + const char **paths = get_pathspec(prefix, av + 1); + struct pathspec pathspec; + + init_pathspec(&pathspec, paths); if (read_ref("HEAD", head_sha1)) /* If there is no HEAD, that means it is an initial @@ -559,7 +563,7 @@ static int do_reupdate(int ac, const char **av, struct cache_entry *old = NULL; int save_nr; - if (ce_stage(ce) || !ce_path_match(ce, pathspec)) + if (ce_stage(ce) || !ce_path_match(ce, &pathspec)) continue; if (has_head) old = read_one_ent(NULL, head_sha1, @@ -578,6 +582,7 @@ static int do_reupdate(int ac, const char **av, if (save_nr != active_nr) goto redo; } + free_pathspec(&pathspec); return 0; } diff --git a/builtin/var.c b/builtin/var.c index 0744bb8318..99d068a532 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -3,8 +3,7 @@ * * Copyright (C) Eric Biederman, 2005 */ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" static const char var_usage[] = "git var (-l | <variable>)"; @@ -200,7 +200,7 @@ int create_bundle(struct bundle_header *header, const char *path, int bundle_fd = -1; int bundle_to_stdout; const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *)); - const char **argv_pack = xmalloc(5 * sizeof(const char *)); + const char **argv_pack = xmalloc(6 * sizeof(const char *)); int i, ref_count = 0; char buffer[1024]; struct rev_info revs; @@ -346,7 +346,8 @@ int create_bundle(struct bundle_header *header, const char *path, argv_pack[1] = "--all-progress-implied"; argv_pack[2] = "--stdout"; argv_pack[3] = "--thin"; - argv_pack[4] = NULL; + argv_pack[4] = "--delta-base-offset"; + argv_pack[5] = NULL; memset(&rls, 0, sizeof(rls)); rls.argv = argv_pack; rls.in = -1; @@ -5,6 +5,7 @@ #include "strbuf.h" #include "hash.h" #include "advice.h" +#include "gettext.h" #include SHA1_HEADER #ifndef git_SHA_CTX @@ -436,6 +437,7 @@ extern void verify_non_filename(const char *prefix, const char *name); #define INIT_DB_QUIET 0x0001 +extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int); extern int init_db(const char *template_dir, unsigned int flags); #define alloc_nr(x) (((x)+16)*3/2) @@ -500,9 +502,27 @@ extern int index_name_is_other(const struct index_state *, const char *, int); extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); -extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); -extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); -extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); +struct pathspec { + const char **raw; /* get_pathspec() result, not freed by free_pathspec() */ + int nr; + unsigned int has_wildcard:1; + unsigned int recursive:1; + int max_depth; + struct pathspec_item { + const char *match; + int len; + unsigned int use_wildcard:1; + } *items; +}; + +extern int init_pathspec(struct pathspec *, const char **); +extern void free_pathspec(struct pathspec *); +extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec); + +#define HASH_WRITE_OBJECT 1 +#define HASH_FORMAT_CHECK 2 +extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); +extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); #define REFRESH_REALLY 0x0001 /* ignore_valid */ @@ -511,7 +531,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); #define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */ #define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */ -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, char *header_msg); +extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg); struct lock_file { struct lock_file *next; @@ -527,6 +547,7 @@ extern NORETURN void unable_to_lock_index_die(const char *path, int err); extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); +extern void update_index_if_able(struct index_state *, struct lock_file *); extern int hold_locked_index(struct lock_file *, int); extern int commit_locked_index(struct lock_file *); @@ -540,12 +561,12 @@ extern int trust_executable_bit; extern int trust_ctime; extern int quote_path_fully; extern int has_symlinks; +extern int minimum_abbrev, default_abbrev; extern int ignore_case; extern int assume_unchanged; extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; -extern int unique_abbrev_extra_length; extern int shared_repository; extern const char *apply_default_whitespace; extern const char *apply_default_ignorewhitespace; @@ -555,6 +576,7 @@ 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 unsigned long big_file_threshold; extern int read_replace_refs; extern int fsync_object_files; extern int core_preload_index; @@ -571,7 +593,7 @@ extern enum safe_crlf safe_crlf; enum auto_crlf { AUTO_CRLF_FALSE = 0, AUTO_CRLF_TRUE = 1, - AUTO_CRLF_INPUT = -1, + AUTO_CRLF_INPUT = -1 }; extern enum auto_crlf auto_crlf; @@ -587,7 +609,7 @@ enum eol { #endif }; -extern enum eol eol; +extern enum eol core_eol; enum branch_track { BRANCH_TRACK_UNSPECIFIED = -1, @@ -608,7 +630,7 @@ enum rebase_setup_type { enum push_default_type { PUSH_DEFAULT_NOTHING = 0, PUSH_DEFAULT_MATCHING, - PUSH_DEFAULT_TRACKING, + PUSH_DEFAULT_UPSTREAM, PUSH_DEFAULT_CURRENT }; @@ -657,14 +679,24 @@ extern char *sha1_pack_name(const unsigned char *sha1); extern char *sha1_pack_index_name(const unsigned char *sha1); extern const char *find_unique_abbrev(const unsigned char *sha1, int); extern const unsigned char null_sha1[20]; -static inline int is_null_sha1(const unsigned char *sha1) + +static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) { - return !memcmp(sha1, null_sha1, 20); + int i; + + for (i = 0; i < 20; i++, sha1++, sha2++) { + if (*sha1 != *sha2) + return *sha1 - *sha2; + } + + return 0; } -static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) + +static inline int is_null_sha1(const unsigned char *sha1) { - return memcmp(sha1, sha2, 20); + return !hashcmp(sha1, null_sha1); } + static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src) { memcpy(sha_dst, sha_src, 20); @@ -676,9 +708,11 @@ static inline void hashclr(unsigned char *hash) #define EMPTY_TREE_SHA1_HEX \ "4b825dc642cb6eb9a060e54bf8d69288fbee4904" -#define EMPTY_TREE_SHA1_BIN \ +#define EMPTY_TREE_SHA1_BIN_LITERAL \ "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \ "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04" +#define EMPTY_TREE_SHA1_BIN \ + ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL) int git_mkstemp(char *path, size_t n, const char *template); @@ -708,6 +742,7 @@ int set_shared_perm(const char *path, int mode); #define adjust_shared_perm(path) set_shared_perm((path), 0) int safe_create_leading_directories(char *path); int safe_create_leading_directories_const(const char *path); +int mkdir_in_gitdir(const char *path); extern char *expand_user_path(const char *path); char *enter_repo(char *path, int strict); static inline int is_absolute_path(const char *path) @@ -715,22 +750,32 @@ static inline int is_absolute_path(const char *path) return path[0] == '/' || has_dos_drive_prefix(path); } int is_directory(const char *); -const char *make_absolute_path(const char *path); -const char *make_nonrelative_path(const char *path); -const char *make_relative_path(const char *abs, const char *base); +const char *real_path(const char *path); +const char *absolute_path(const char *path); +const char *relative_path(const char *abs, const char *base); int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); char *strip_path_suffix(const char *path, const char *suffix); 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 *); -extern void *read_sha1_file_repl(const unsigned char *sha1, enum object_type *type, unsigned long *size, const unsigned char **replacement); +/* object replacement */ +#define READ_SHA1_FILE_REPLACE 1 +extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag); static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) { - return read_sha1_file_repl(sha1, type, size, NULL); + return read_sha1_file_extended(sha1, type, size, READ_SHA1_FILE_REPLACE); +} +extern const unsigned char *do_lookup_replace_object(const unsigned char *sha1); +static inline const unsigned char *lookup_replace_object(const unsigned char *sha1) +{ + if (!read_replace_refs) + return sha1; + return do_lookup_replace_object(sha1); } + +/* Read and unpack a sha1 file into memory, write memory to a sha1 file */ +extern int sha1_object_info(const unsigned char *, unsigned long *); extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *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 *); @@ -758,8 +803,8 @@ static inline unsigned int hexval(unsigned char c) } /* Convert to/from hex/sha1 representation */ -#define MINIMUM_ABBREV 4 -#define DEFAULT_ABBREV 7 +#define MINIMUM_ABBREV minimum_abbrev +#define DEFAULT_ABBREV default_abbrev struct object_context { unsigned char tree[20]; @@ -768,15 +813,15 @@ struct object_context { }; 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); +extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int only_to_die, 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); + return get_sha1_with_mode_1(str, sha1, mode, 0, NULL); } -extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int gently, const char *prefix); +extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int only_to_die, 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); + return get_sha1_with_context_1(str, sha1, orc, 0, NULL); } extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ @@ -897,7 +942,8 @@ extern struct packed_git { time_t mtime; int pack_fd; unsigned pack_local:1, - pack_keep:1; + pack_keep:1, + do_not_close:1; unsigned char sha1[20]; /* something like ".git/objects/pack/xxxxx.pack" */ char pack_name[FLEX_ARRAY]; /* more */ @@ -942,6 +988,7 @@ extern struct ref *find_ref_by_name(const struct ref *list, const char *name); extern char *git_getpass(const char *prompt); extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); extern int finish_connect(struct child_process *conn); +extern int git_connection_is_socket(struct child_process *conn); extern int path_match(const char *path, int nr, char **match); struct extra_have_objects { int nr, alloc; @@ -979,12 +1026,20 @@ extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigne /* Dumb servers support */ extern int update_server_info(int); +/* git_config_parse_key() returns these negated: */ +#define CONFIG_INVALID_KEY 1 +#define CONFIG_NO_SECTION_OR_NAME 2 +/* git_config_set(), git_config_set_multivar() return the above or these: */ +#define CONFIG_NO_LOCK -1 +#define CONFIG_INVALID_FILE 3 +#define CONFIG_NO_WRITE 4 +#define CONFIG_NOTHING_SET 5 +#define CONFIG_INVALID_PATTERN 6 + 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 void git_config_push_parameter(const char *text); -extern int git_config_parse_parameter(const char *text); -extern int git_config_parse_environment(void); extern int git_config_from_parameters(config_fn_t fn, void *data); extern int git_config(config_fn_t fn, void *); extern int git_config_early(config_fn_t fn, void *, const char *repo_config); @@ -997,13 +1052,13 @@ 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 *); +extern int git_config_parse_key(const char *, char **, int *); extern int git_config_set_multivar(const char *, const char *, const char *, int); extern int git_config_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 *); extern const char *get_log_output_encoding(void); extern const char *get_commit_output_encoding(void); @@ -1065,9 +1120,14 @@ extern void alloc_report(void); /* trace.c */ __attribute__((format (printf, 1, 2))) extern void trace_printf(const char *format, ...); +extern void trace_vprintf(const char *key, const char *format, va_list ap); __attribute__((format (printf, 2, 3))) extern void trace_argv_printf(const char **argv, const char *format, ...); extern void trace_repo_setup(const char *prefix); +extern int trace_want(const char *key); +extern void trace_strbuf(const char *key, const struct strbuf *buf); + +void packet_trace_identity(const char *prog); /* convert.c */ /* returns 1 if *dst was used */ @@ -3,6 +3,28 @@ int git_use_color_default = 0; +/* + * The list of available column colors. + */ +const char *column_colors_ansi[] = { + GIT_COLOR_RED, + GIT_COLOR_GREEN, + GIT_COLOR_YELLOW, + GIT_COLOR_BLUE, + GIT_COLOR_MAGENTA, + 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, + GIT_COLOR_RESET, +}; + +/* Ignore the RESET at the end when giving the size */ +const int column_colors_ansi_max = ARRAY_SIZE(column_colors_ansi) - 1; + static int parse_color(const char *name, int len) { static const char * const color_names[] = { @@ -175,6 +197,15 @@ int git_color_default_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb) +{ + if (*color) + fprintf(fp, "%s", color); + fprintf(fp, "%s", sb->buf); + if (*color) + fprintf(fp, "%s", GIT_COLOR_RESET); +} + static int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args, const char *trail) { @@ -1,6 +1,8 @@ #ifndef COLOR_H #define COLOR_H +struct strbuf; + /* 2 + (2 * num_attrs) + 8 + 1 + 8 + 'm' + NUL */ /* "\033[1;2;4;5;7;38;5;2xx;48;5;2xxm\0" */ /* @@ -51,6 +53,9 @@ */ extern int git_use_color_default; +/* A default list of colors to use for commit graphs and show-branch output */ +extern const char *column_colors_ansi[]; +extern const int column_colors_ansi_max; /* * Use this instead of git_default_config if you need the value of color.ui. @@ -64,6 +69,7 @@ __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, ...); +void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb); int color_is_nil(const char *color); @@ -245,10 +245,10 @@ int unregister_shallow(const unsigned char *sha1) return 0; } -int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) +int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size) { - char *tail = buffer; - char *bufptr = buffer; + const char *tail = buffer; + const char *bufptr = buffer; unsigned char parent[20]; struct commit_list **pptr; struct commit_graft *graft; @@ -38,7 +38,7 @@ struct commit *lookup_commit_reference_gently(const unsigned char *sha1, int quiet); struct commit *lookup_commit_reference_by_name(const char *name); -int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); +int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size); int parse_commit(struct commit *item); /* Find beginning and length of commit subject. */ @@ -68,8 +68,7 @@ enum cmit_fmt { CMIT_FMT_UNSPECIFIED }; -struct pretty_print_context -{ +struct pretty_print_context { enum cmit_fmt fmt; int abbrev; const char *subject; @@ -93,6 +92,8 @@ extern char *logmsg_reencode(const struct commit *commit, 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 const char *format_subject(struct strbuf *sb, const char *msg, + const char *line_separator); 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, @@ -146,8 +147,6 @@ struct commit_graft *read_graft_line(char *buf, int len); int register_commit_graft(struct commit_graft *, int); struct commit_graft *lookup_commit_graft(const unsigned char *sha1); -const unsigned char *lookup_replace_object(const unsigned char *sha1); - extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup); extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); @@ -162,7 +161,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int is_descendant_of(struct commit *, struct commit_list *); int in_merge_bases(struct commit *, struct commit **, int); -extern int interactive_add(int argc, const char **argv, const char *prefix); +extern int interactive_add(int argc, const char **argv, const char *prefix, int patch); extern int run_add_interactive(const char *revision, const char *patch_mode, const char **pathspec); diff --git a/compat/bswap.h b/compat/bswap.h index 54756dbb05..5061214f73 100644 --- a/compat/bswap.h +++ b/compat/bswap.h @@ -21,14 +21,16 @@ static inline uint32_t default_swab32(uint32_t val) #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) -#define bswap32(x) ({ \ - uint32_t __res; \ - if (__builtin_constant_p(x)) { \ - __res = default_swab32(x); \ - } else { \ - __asm__("bswap %0" : "=r" (__res) : "0" ((uint32_t)(x))); \ - } \ - __res; }) +#define bswap32 git_bswap32 +static inline uint32_t git_bswap32(uint32_t x) +{ + uint32_t result; + if (__builtin_constant_p(x)) + result = default_swab32(x); + else + __asm__("bswap %0" : "=r" (result) : "0" (x)); + return result; +} #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c index 14feac7fe1..9473aed2bb 100644 --- a/compat/fnmatch/fnmatch.c +++ b/compat/fnmatch/fnmatch.c @@ -127,6 +127,10 @@ extern char *getenv (); extern int errno; # endif +# ifndef NULL +# define NULL 0 +# endif + /* This function doesn't exist on most systems. */ # if !defined HAVE___STRCHRNUL && !defined _LIBC diff --git a/compat/mingw.c b/compat/mingw.c index bee6054419..f6e9ff7762 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2,6 +2,9 @@ #include "win32.h" #include <conio.h> #include "../strbuf.h" +#include "../run-command.h" + +static const int delay[] = { 0, 1, 10, 20, 40 }; int err_win_to_posix(DWORD winerr) { @@ -116,6 +119,165 @@ int err_win_to_posix(DWORD winerr) return error; } +static inline int is_file_in_use_error(DWORD errcode) +{ + switch (errcode) { + case ERROR_SHARING_VIOLATION: + case ERROR_ACCESS_DENIED: + return 1; + } + + return 0; +} + +static int read_yes_no_answer(void) +{ + char answer[1024]; + + if (fgets(answer, sizeof(answer), stdin)) { + size_t answer_len = strlen(answer); + int got_full_line = 0, c; + + /* remove the newline */ + if (answer_len >= 2 && answer[answer_len-2] == '\r') { + answer[answer_len-2] = '\0'; + got_full_line = 1; + } else if (answer_len >= 1 && answer[answer_len-1] == '\n') { + answer[answer_len-1] = '\0'; + got_full_line = 1; + } + /* flush the buffer in case we did not get the full line */ + if (!got_full_line) + while ((c = getchar()) != EOF && c != '\n') + ; + } else + /* we could not read, return the + * default answer which is no */ + return 0; + + if (tolower(answer[0]) == 'y' && !answer[1]) + return 1; + if (!strncasecmp(answer, "yes", sizeof(answer))) + return 1; + if (tolower(answer[0]) == 'n' && !answer[1]) + return 0; + if (!strncasecmp(answer, "no", sizeof(answer))) + return 0; + + /* did not find an answer we understand */ + return -1; +} + +static int ask_yes_no_if_possible(const char *format, ...) +{ + char question[4096]; + const char *retry_hook[] = { NULL, NULL, NULL }; + va_list args; + + va_start(args, format); + vsnprintf(question, sizeof(question), format, args); + va_end(args); + + if ((retry_hook[0] = getenv("GIT_ASK_YESNO"))) { + retry_hook[1] = question; + return !run_command_v_opt(retry_hook, 0); + } + + if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr))) + return 0; + + while (1) { + int answer; + fprintf(stderr, "%s (y/n) ", question); + + if ((answer = read_yes_no_answer()) >= 0) + return answer; + + fprintf(stderr, "Sorry, I did not understand your answer. " + "Please type 'y' or 'n'\n"); + } +} + +#undef unlink +int mingw_unlink(const char *pathname) +{ + int ret, tries = 0; + + /* read-only files cannot be removed */ + chmod(pathname, 0666); + while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) { + if (!is_file_in_use_error(GetLastError())) + break; + /* + * We assume that some other process had the source or + * destination file open at the wrong moment and retry. + * In order to give the other process a higher chance to + * complete its operation, we give up our time slice now. + * If we have to retry again, we do sleep a bit. + */ + Sleep(delay[tries]); + tries++; + } + while (ret == -1 && is_file_in_use_error(GetLastError()) && + ask_yes_no_if_possible("Unlink of file '%s' failed. " + "Should I try again?", pathname)) + ret = unlink(pathname); + return ret; +} + +static int is_dir_empty(const char *path) +{ + struct strbuf buf = STRBUF_INIT; + WIN32_FIND_DATAA findbuf; + HANDLE handle; + + strbuf_addf(&buf, "%s\\*", path); + handle = FindFirstFileA(buf.buf, &findbuf); + if (handle == INVALID_HANDLE_VALUE) { + strbuf_release(&buf); + return GetLastError() == ERROR_NO_MORE_FILES; + } + + while (!strcmp(findbuf.cFileName, ".") || + !strcmp(findbuf.cFileName, "..")) + if (!FindNextFile(handle, &findbuf)) { + strbuf_release(&buf); + return GetLastError() == ERROR_NO_MORE_FILES; + } + FindClose(handle); + strbuf_release(&buf); + return 0; +} + +#undef rmdir +int mingw_rmdir(const char *pathname) +{ + int ret, tries = 0; + + while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) { + if (!is_file_in_use_error(GetLastError())) + break; + if (!is_dir_empty(pathname)) { + errno = ENOTEMPTY; + break; + } + /* + * We assume that some other process had the source or + * destination file open at the wrong moment and retry. + * In order to give the other process a higher chance to + * complete its operation, we give up our time slice now. + * If we have to retry again, we do sleep a bit. + */ + Sleep(delay[tries]); + tries++; + } + while (ret == -1 && is_file_in_use_error(GetLastError()) && + ask_yes_no_if_possible("Deletion of directory '%s' failed. " + "Should I try again?", pathname)) + ret = rmdir(pathname); + return ret; +} + #undef open int mingw_open (const char *filename, int oflags, ...) { @@ -968,7 +1130,7 @@ char **make_augmented_environ(const char *const *vars) /* * Note, this isn't a complete replacement for getaddrinfo. It assumes - * that service contains a numerical port, or that it it is null. It + * that service contains a numerical port, or that it is null. It * does a simple search using gethostbyname, and returns one IPv4 host * if one was found. */ @@ -1219,6 +1381,13 @@ int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen) return setsockopt(s, lvl, optname, (const char*)optval, optlen); } +#undef shutdown +int mingw_shutdown(int sockfd, int how) +{ + SOCKET s = (SOCKET)_get_osfhandle(sockfd); + return shutdown(s, how); +} + #undef listen int mingw_listen(int sockfd, int backlog) { @@ -1249,7 +1418,6 @@ int mingw_rename(const char *pold, const char *pnew) { DWORD attrs, gle; int tries = 0; - static const int delay[] = { 0, 1, 10, 20, 40 }; /* * Try native rename() first to get errno right. @@ -1291,6 +1459,11 @@ repeat: tries++; goto repeat; } + if (gle == ERROR_ACCESS_DENIED && + ask_yes_no_if_possible("Rename from '%s' to '%s' failed. " + "Should I try again?", pold, pnew)) + goto repeat; + errno = EACCES; return -1; } diff --git a/compat/mingw.h b/compat/mingw.h index cafc1eb08a..547568b918 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -119,14 +119,6 @@ static inline int mingw_mkdir(const char *path, int mode) } #define mkdir mingw_mkdir -static inline int mingw_unlink(const char *pathname) -{ - /* read-only files cannot be removed */ - chmod(pathname, 0666); - return unlink(pathname); -} -#define unlink mingw_unlink - #define WNOHANG 1 pid_t waitpid(pid_t pid, int *status, unsigned options); @@ -174,6 +166,12 @@ int link(const char *oldpath, const char *newpath); * replacements of existing functions */ +int mingw_unlink(const char *pathname); +#define unlink mingw_unlink + +int mingw_rmdir(const char *path); +#define rmdir mingw_rmdir + int mingw_open (const char *filename, int oflags, ...); #define open mingw_open @@ -219,6 +217,9 @@ int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz); int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen); #define setsockopt mingw_setsockopt +int mingw_shutdown(int sockfd, int how); +#define shutdown mingw_shutdown + int mingw_listen(int sockfd, int backlog); #define listen mingw_listen @@ -233,6 +234,22 @@ int mingw_getpagesize(void); #define getpagesize mingw_getpagesize #endif +struct rlimit { + unsigned int rlim_cur; +}; +#define RLIMIT_NOFILE 0 + +static inline int getrlimit(int resource, struct rlimit *rlp) +{ + if (resource != RLIMIT_NOFILE) { + errno = EINVAL; + return -1; + } + + rlp->rlim_cur = 2048; + return 0; +} + /* Use mingw_lstat() instead of lstat()/stat() and * mingw_fstat() instead of fstat() on Windows. */ diff --git a/compat/msvc.h b/compat/msvc.h index 023aba0238..a33b01c032 100644 --- a/compat/msvc.h +++ b/compat/msvc.h @@ -9,7 +9,6 @@ #define inline __inline #define __inline__ __inline #define __attribute__(x) -#define va_copy(dst, src) ((dst) = (src)) #define strncasecmp _strnicmp #define ftruncate _chsize diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h index 87260d2642..ff7c2c4fd8 100644 --- a/compat/nedmalloc/malloc.c.h +++ b/compat/nedmalloc/malloc.c.h @@ -100,7 +100,7 @@ If you don't like either of these options, you can define CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything - else. And if if you are sure that your program using malloc has + else. And if you are sure that your program using malloc has no errors or vulnerabilities, you can define INSECURE to 1, which might (or might not) provide a small performance improvement. @@ -2279,12 +2279,12 @@ nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ of the same size are arranged in a circularly-linked list, with only the oldest chunk (the next to be used, in our FIFO ordering) actually in the tree. (Tree members are distinguished by a non-null - parent pointer.) If a chunk with the same size an an existing node + parent pointer.) If a chunk with the same size as an existing node is inserted, it is linked off the existing node using pointers that work in the same way as fd/bk pointers of small chunks. Each tree contains a power of 2 sized range of chunk sizes (the - smallest is 0x100 <= x < 0x180), which is is divided in half at each + smallest is 0x100 <= x < 0x180), which is divided in half at each tree level, with the chunks in the smaller half of the range (0x100 <= x < 0x140 for the top nose) in the left subtree and the larger half (0x140 <= x < 0x180) in the right subtree. This is, of course, @@ -3943,7 +3943,7 @@ static void* sys_alloc(mstate m, size_t nb) { least-preferred order): 1. A call to MORECORE that can normally contiguously extend memory. (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or - or main space is mmapped or a previous contiguous call failed) + main space is mmapped or a previous contiguous call failed) 2. A call to MMAP new space (disabled if not HAVE_MMAP). Note that under the default settings, if MORECORE is unable to fulfill a request, and HAVE_MMAP is true, then mmap is @@ -5748,5 +5748,3 @@ History: structure of old version, but most details differ.) */ - - @@ -20,15 +20,6 @@ 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++) @@ -48,9 +39,9 @@ void git_config_push_parameter(const char *text) strbuf_release(&env); } -int git_config_parse_parameter(const char *text) +static int git_config_parse_parameter(const char *text, + config_fn_t fn, void *data) { - struct config_item *ct; struct strbuf tmp = STRBUF_INIT; struct strbuf **pair; strbuf_addstr(&tmp, text); @@ -60,22 +51,19 @@ int git_config_parse_parameter(const char *text) strbuf_trim(pair[0]); if (!pair[0]->len) { strbuf_list_free(pair); - return -1; + return error("bogus config parameter: %s", text); } - 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); + lowercase(pair[0]->buf); + if (fn(pair[0]->buf, pair[1] ? pair[1]->buf : NULL, data) < 0) { + strbuf_list_free(pair); + return -1; } strbuf_list_free(pair); - lowercase(ct->name); - *config_parameters_tail = ct; - config_parameters_tail = &ct->next; return 0; } -int git_config_parse_environment(void) { +int git_config_from_parameters(config_fn_t fn, void *data) +{ const char *env = getenv(CONFIG_DATA_ENVIRONMENT); char *envw; const char **argv = NULL; @@ -93,8 +81,7 @@ int git_config_parse_environment(void) { } for (i = 0; i < nr; i++) { - if (git_config_parse_parameter(argv[i]) < 0) { - error("bogus config parameter: %s", argv[i]); + if (git_config_parse_parameter(argv[i], fn, data) < 0) { free(argv); free(envw); return -1; @@ -103,7 +90,7 @@ int git_config_parse_environment(void) { free(argv); free(envw); - return 0; + return nr > 0; } static int get_next_char(void) @@ -134,23 +121,21 @@ static int get_next_char(void) static char *parse_value(void) { - static char value[1024]; - int quote = 0, comment = 0, len = 0, space = 0; + static struct strbuf value = STRBUF_INIT; + int quote = 0, comment = 0, space = 0; + strbuf_reset(&value); for (;;) { int c = get_next_char(); - if (len >= sizeof(value) - 1) - return NULL; if (c == '\n') { if (quote) return NULL; - value[len] = 0; - return value; + return value.buf; } if (comment) continue; if (isspace(c) && !quote) { - if (len) + if (value.len) space++; continue; } @@ -161,7 +146,7 @@ static char *parse_value(void) } } for (; space; space--) - value[len++] = ' '; + strbuf_addch(&value, ' '); if (c == '\\') { c = get_next_char(); switch (c) { @@ -183,14 +168,14 @@ static char *parse_value(void) default: return NULL; } - value[len++] = c; + strbuf_addch(&value, c); continue; } if (c == '"') { quote = 1-quote; continue; } - value[len++] = c; + strbuf_addch(&value, c); } } @@ -499,13 +484,6 @@ static int git_default_core_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "core.abbrevguard")) { - unique_abbrev_extra_length = git_config_int(var, value); - if (unique_abbrev_extra_length < 0) - unique_abbrev_extra_length = 0; - return 0; - } - if (!strcmp(var, "core.bare")) { is_bare_repository_cfg = git_config_bool(var, value); return 0; @@ -531,6 +509,14 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.abbrev")) { + int abbrev = git_config_int(var, value); + if (abbrev < minimum_abbrev || abbrev > 40) + return -1; + default_abbrev = abbrev; + return 0; + } + if (!strcmp(var, "core.loosecompression")) { int level = git_config_int(var, value); if (level == -1) @@ -567,6 +553,12 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.bigfilethreshold")) { + long n = git_config_int(var, value); + big_file_threshold = 0 < n ? n : 0; + return 0; + } + if (!strcmp(var, "core.packedgitlimit")) { packed_git_limit = git_config_int(var, value); return 0; @@ -579,7 +571,7 @@ static int git_default_core_config(const char *var, const char *value) if (!strcmp(var, "core.autocrlf")) { if (value && !strcasecmp(value, "input")) { - if (eol == EOL_CRLF) + if (core_eol == EOL_CRLF) return error("core.autocrlf=input conflicts with core.eol=crlf"); auto_crlf = AUTO_CRLF_INPUT; return 0; @@ -599,14 +591,14 @@ static int git_default_core_config(const char *var, const char *value) if (!strcmp(var, "core.eol")) { if (value && !strcasecmp(value, "lf")) - eol = EOL_LF; + core_eol = EOL_LF; else if (value && !strcasecmp(value, "crlf")) - eol = EOL_CRLF; + core_eol = EOL_CRLF; else if (value && !strcasecmp(value, "native")) - eol = EOL_NATIVE; + core_eol = EOL_NATIVE; else - eol = EOL_UNSET; - if (eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT) + core_eol = EOL_UNSET; + if (core_eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT) return error("core.autocrlf=input conflicts with core.eol=crlf"); return 0; } @@ -737,8 +729,10 @@ static int git_default_push_config(const char *var, const char *value) push_default = PUSH_DEFAULT_NOTHING; else if (!strcmp(value, "matching")) push_default = PUSH_DEFAULT_MATCHING; - else if (!strcmp(value, "tracking")) - push_default = PUSH_DEFAULT_TRACKING; + else if (!strcmp(value, "upstream")) + push_default = PUSH_DEFAULT_UPSTREAM; + else if (!strcmp(value, "tracking")) /* deprecated */ + push_default = PUSH_DEFAULT_UPSTREAM; else if (!strcmp(value, "current")) push_default = PUSH_DEFAULT_CURRENT; else { @@ -831,27 +825,6 @@ int git_config_system(void) return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); } -int git_config_global(void) -{ - return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0); -} - -int git_config_from_parameters(config_fn_t fn, void *data) -{ - static int loaded_environment; - const struct config_item *ct; - - if (!loaded_environment) { - if (git_config_parse_environment() < 0) - return -1; - loaded_environment = 1; - } - for (ct = config_parameters; ct; ct = ct->next) - if (fn(ct->name, ct->value, data) < 0) - return -1; - return 0; -} - int git_config_early(config_fn_t fn, void *data, const char *repo_config) { int ret = 0, found = 0; @@ -867,7 +840,7 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) } home = getenv("HOME"); - if (git_config_global() && home) { + if (home) { char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); if (!access(user_config, R_OK)) { ret += git_config_from_file(fn, user_config, data); @@ -881,9 +854,16 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) found += 1; } - ret += git_config_from_parameters(fn, data); - if (config_parameters) - found += 1; + switch (git_config_from_parameters(fn, data)) { + case -1: /* error */ + ret--; + break; + case 0: /* found nothing */ + break; + default: /* found at least one item */ + found++; + break; + } return ret == 0 ? found : ret; } @@ -1099,6 +1079,75 @@ int git_config_set(const char *key, const char *value) } /* + * Auxiliary function to sanity-check and split the key into the section + * identifier and variable name. + * + * Returns 0 on success, -1 when there is an invalid character in the key and + * -2 if there is no section name in the key. + * + * store_key - pointer to char* which will hold a copy of the key with + * lowercase section and variable name + * baselen - pointer to int which will hold the length of the + * section + subsection part, can be NULL + */ +int git_config_parse_key(const char *key, char **store_key, int *baselen_) +{ + int i, dot, baselen; + const char *last_dot = strrchr(key, '.'); + + /* + * Since "key" actually contains the section name and the real + * key name separated by a dot, we have to know where the dot is. + */ + + if (last_dot == NULL || last_dot == key) { + error("key does not contain a section: %s", key); + return -CONFIG_NO_SECTION_OR_NAME; + } + + if (!last_dot[1]) { + error("key does not contain variable name: %s", key); + return -CONFIG_NO_SECTION_OR_NAME; + } + + baselen = last_dot - key; + if (baselen_) + *baselen_ = baselen; + + /* + * Validate the key and while at it, lower case it for matching. + */ + *store_key = xmalloc(strlen(key) + 1); + + dot = 0; + for (i = 0; key[i]; i++) { + unsigned char c = key[i]; + if (c == '.') + dot = 1; + /* Leave the extended basename untouched.. */ + if (!dot || i > baselen) { + if (!iskeychar(c) || + (i == baselen + 1 && !isalpha(c))) { + error("invalid key: %s", key); + goto out_free_ret_1; + } + c = tolower(c); + } else if (c == '\n') { + error("invalid key (newline): %s", key); + goto out_free_ret_1; + } + (*store_key)[i] = c; + } + (*store_key)[i] = 0; + + return 0; + +out_free_ret_1: + free(*store_key); + return -CONFIG_INVALID_KEY; +} + +/* * If value==NULL, unset in (remove from) config, * if value_regex!=NULL, disregard key/value pairs where value does not match. * if multi_replace==0, nothing, or only one matching key/value is replaced, @@ -1124,59 +1173,23 @@ int git_config_set(const char *key, const char *value) int git_config_set_multivar(const char *key, const char *value, const char *value_regex, int multi_replace) { - int i, dot; int fd = -1, in_fd; int ret; char *config_filename; struct lock_file *lock = NULL; - const char *last_dot = strrchr(key, '.'); if (config_exclusive_filename) config_filename = xstrdup(config_exclusive_filename); else config_filename = git_pathdup("config"); - /* - * Since "key" actually contains the section name and the real - * key name separated by a dot, we have to know where the dot is. - */ - - if (last_dot == NULL) { - error("key does not contain a section: %s", key); - ret = 2; + /* parse-key returns negative; flip the sign to feed exit(3) */ + ret = 0 - git_config_parse_key(key, &store.key, &store.baselen); + if (ret) goto out_free; - } - store.baselen = last_dot - key; store.multi_replace = multi_replace; - /* - * Validate the key and while at it, lower case it for matching. - */ - store.key = xmalloc(strlen(key) + 1); - dot = 0; - for (i = 0; key[i]; i++) { - unsigned char c = key[i]; - if (c == '.') - dot = 1; - /* Leave the extended basename untouched.. */ - if (!dot || i > store.baselen) { - if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) { - error("invalid key: %s", key); - free(store.key); - ret = 1; - goto out_free; - } - c = tolower(c); - } else if (c == '\n') { - error("invalid key (newline): %s", key); - free(store.key); - ret = 1; - goto out_free; - } - store.key[i] = c; - } - store.key[i] = 0; /* * The lock serves a purpose in addition to locking: the new @@ -1187,7 +1200,7 @@ int git_config_set_multivar(const char *key, const char *value, if (fd < 0) { error("could not lock config file %s: %s", config_filename, strerror(errno)); free(store.key); - ret = -1; + ret = CONFIG_NO_LOCK; goto out_free; } @@ -1201,12 +1214,12 @@ int git_config_set_multivar(const char *key, const char *value, if ( ENOENT != errno ) { error("opening %s: %s", config_filename, strerror(errno)); - ret = 3; /* same as "invalid config file" */ + ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */ goto out_free; } /* if nothing to unset, error out */ if (value == NULL) { - ret = 5; + ret = CONFIG_NOTHING_SET; goto out_free; } @@ -1234,7 +1247,7 @@ int git_config_set_multivar(const char *key, const char *value, REG_EXTENDED)) { error("invalid pattern: %s", value_regex); free(store.value_regex); - ret = 6; + ret = CONFIG_INVALID_PATTERN; goto out_free; } } @@ -1256,7 +1269,7 @@ int git_config_set_multivar(const char *key, const char *value, regfree(store.value_regex); free(store.value_regex); } - ret = 3; + ret = CONFIG_INVALID_FILE; goto out_free; } @@ -1269,7 +1282,7 @@ int git_config_set_multivar(const char *key, const char *value, /* if nothing to unset, or too many matches, error out */ if ((store.seen == 0 && value == NULL) || (store.seen > 1 && multi_replace == 0)) { - ret = 5; + ret = CONFIG_NOTHING_SET; goto out_free; } @@ -1330,7 +1343,7 @@ int git_config_set_multivar(const char *key, const char *value, if (commit_lock_file(lock) < 0) { error("could not commit config file %s", config_filename); - ret = 4; + ret = CONFIG_NO_WRITE; goto out_free; } diff --git a/config.mak.in b/config.mak.in index 9614973057..ab371012a2 100644 --- a/config.mak.in +++ b/config.mak.in @@ -18,6 +18,7 @@ bindir = @bindir@ gitexecdir = @libexecdir@/git-core datarootdir = @datarootdir@ template_dir = @datadir@/git-core/templates +sysconfdir = @sysconfdir@ mandir=@mandir@ @@ -43,7 +44,6 @@ NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@ 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_STRTOK_R=@NO_STRTOK_R@ @@ -62,6 +62,7 @@ NO_INET_PTON=@NO_INET_PTON@ NO_ICONV=@NO_ICONV@ OLD_ICONV=@OLD_ICONV@ NO_REGEX=@NO_REGEX@ +USE_LIBPCRE=@USE_LIBPCRE@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@ INLINE=@INLINE@ SOCKLEN_T=@SOCKLEN_T@ diff --git a/configure.ac b/configure.ac index 5792425a49..048a1d4972 100644 --- a/configure.ac +++ b/configure.ac @@ -220,6 +220,27 @@ AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\ GIT_PARSE_WITH(openssl)) # +# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be +# able to use Perl-compatible regular expressions. +# +# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +# +AC_ARG_WITH(libpcre, +AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)]) +AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]), +if test "$withval" = "no"; then \ + USE_LIBPCRE=; \ +elif test "$withval" = "yes"; then \ + USE_LIBPCRE=YesPlease; \ +else + USE_LIBPCRE=YesPlease; \ + LIBPCREDIR=$withval; \ + AC_MSG_NOTICE([Setting LIBPCREDIR to $withval]); \ + GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval); \ +fi \ +) +# # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. @@ -345,7 +366,7 @@ esac AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [ SAVE_LDFLAGS="${LDFLAGS}" LDFLAGS="${SAVE_LDFLAGS} -R /" - AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no]) LDFLAGS="${SAVE_LDFLAGS}" ]) if test "$git_cv_ld_dashr" = "yes"; then @@ -354,7 +375,7 @@ else AC_CACHE_CHECK([if linker supports -Wl,-rpath,], git_cv_ld_wl_rpath, [ SAVE_LDFLAGS="${LDFLAGS}" LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/" - AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no]) LDFLAGS="${SAVE_LDFLAGS}" ]) if test "$git_cv_ld_wl_rpath" = "yes"; then @@ -363,7 +384,7 @@ else AC_CACHE_CHECK([if linker supports -rpath], git_cv_ld_rpath, [ SAVE_LDFLAGS="${LDFLAGS}" LDFLAGS="${SAVE_LDFLAGS} -rpath /" - AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no]) LDFLAGS="${SAVE_LDFLAGS}" ]) if test "$git_cv_ld_rpath" = "yes"; then @@ -435,6 +456,25 @@ AC_SUBST(NEEDS_SSL_WITH_CRYPTO) AC_SUBST(NO_OPENSSL) # +# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be +# able to use Perl-compatible regular expressions. +# + +if test -n "$USE_LIBPCRE"; then + +GIT_STASH_FLAGS($LIBPCREDIR) + +AC_CHECK_LIB([pcre], [pcre_version], +[USE_LIBPCRE=YesPlease], +[USE_LIBPCRE=]) + +GIT_UNSTASH_FLAGS($LIBPCREDIR) + +AC_SUBST(USE_LIBPCRE) + +fi + +# # Define NO_CURL if you do not have libcurl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. @@ -472,15 +512,9 @@ if test -z "$NO_ICONV"; then GIT_STASH_FLAGS($ICONVDIR) -AC_DEFUN([ICONVTEST_SRC], [ -#include <iconv.h> - -int main(void) -{ - iconv_open("", ""); - return 0; -} -]) +AC_DEFUN([ICONVTEST_SRC], +[AC_LANG_PROGRAM([#include <iconv.h>], + [iconv_open("", "");])]) if test -n "$ICONVDIR"; then lib_order="-liconv -lc" @@ -500,7 +534,7 @@ for l in $lib_order; do old_LIBS="$LIBS" LIBS="$LIBS $l" AC_MSG_CHECKING([for iconv in $l]) - AC_LINK_IFELSE(ICONVTEST_SRC, + AC_LINK_IFELSE([ICONVTEST_SRC], [AC_MSG_RESULT([yes]) NO_ICONV= break], @@ -528,18 +562,12 @@ fi GIT_STASH_FLAGS($ZLIB_PATH) AC_DEFUN([ZLIBTEST_SRC], [ -#include <zlib.h> - -int main(void) -{ - deflateBound(0, 0); - return 0; -} -]) +AC_LANG_PROGRAM([#include <zlib.h>], + [deflateBound(0, 0);])]) AC_MSG_CHECKING([for deflateBound in -lz]) old_LIBS="$LIBS" LIBS="$LIBS -lz" -AC_LINK_IFELSE(ZLIBTEST_SRC, +AC_LINK_IFELSE([ZLIBTEST_SRC], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) NO_DEFLATE_BOUND=yes]) @@ -631,23 +659,19 @@ AC_SUBST(NO_INTTYPES_H) # # Define OLD_ICONV if your library has an old iconv(), where the second # (input buffer pointer) parameter is declared with type (const char **). -AC_DEFUN([OLDICONVTEST_SRC], [[ +AC_DEFUN([OLDICONVTEST_SRC], [ +AC_LANG_PROGRAM([[ #include <iconv.h> extern size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft); - -int main(void) -{ - return 0; -} -]]) +]], [])]) GIT_STASH_FLAGS($ICONVDIR) AC_MSG_CHECKING([for old iconv()]) -AC_COMPILE_IFELSE(OLDICONVTEST_SRC, +AC_COMPILE_IFELSE([OLDICONVTEST_SRC], [AC_MSG_RESULT([no])], [AC_MSG_RESULT([yes]) OLD_ICONV=UnfortunatelyYes]) @@ -702,30 +726,6 @@ AC_CHECK_TYPE([struct addrinfo],[ ]) AC_SUBST(NO_IPV6) # -# 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, -# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). -# some C compilers supported these specifiers prior to C99 as an extension. -AC_CACHE_CHECK([whether formatted IO functions support C99 size specifiers], - [ac_cv_c_c99_format], -[# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c -AC_RUN_IFELSE( - [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], - [[char buf[64]; - if (sprintf(buf, "%lld%hhd%jd%zd%td", (long long int)1, (char)2, (intmax_t)3, (size_t)4, (ptrdiff_t)5) != 5) - return 1; - else if (strcmp(buf, "12345")) - return 2;]])], - [ac_cv_c_c99_format=yes], - [ac_cv_c_c99_format=no]) -]) -if test $ac_cv_c_c99_format = no; then - NO_C99_FORMAT=YesPlease -else - NO_C99_FORMAT= -fi -AC_SUBST(NO_C99_FORMAT) -# # Define NO_REGEX if you have no or inferior regex support in your C library. AC_CACHE_CHECK([whether the platform regex can handle null bytes], [ac_cv_c_excellent_regex], [ @@ -931,18 +931,18 @@ AC_SUBST(NO_INITGROUPS) # # Define PTHREAD_LIBS to the linker flag used for Pthread support. AC_DEFUN([PTHREADTEST_SRC], [ +AC_LANG_PROGRAM([[ #include <pthread.h> - -int main(void) -{ +]], [[ pthread_mutex_t test_mutex; + pthread_key_t test_key; int retcode = 0; + retcode |= pthread_key_create(&test_key, (void *)0); retcode |= pthread_mutex_init(&test_mutex,(void *)0); retcode |= pthread_mutex_lock(&test_mutex); retcode |= pthread_mutex_unlock(&test_mutex); return retcode; -} -]) +]])]) dnl AC_LANG_CONFTEST([AC_LANG_PROGRAM( dnl [[#include <pthread.h>]], @@ -962,7 +962,7 @@ elif test -z "$PTHREAD_CFLAGS"; then old_CFLAGS="$CFLAGS" CFLAGS="$opt $CFLAGS" AC_MSG_CHECKING([Checking for POSIX Threads with '$opt']) - AC_LINK_IFELSE(PTHREADTEST_SRC, + AC_LINK_IFELSE([PTHREADTEST_SRC], [AC_MSG_RESULT([yes]) NO_PTHREADS= PTHREAD_LIBS="$opt" @@ -982,7 +982,7 @@ else old_CFLAGS="$CFLAGS" CFLAGS="$PTHREAD_CFLAGS $CFLAGS" AC_MSG_CHECKING([Checking for POSIX Threads with '$PTHREAD_CFLAGS']) - AC_LINK_IFELSE(PTHREADTEST_SRC, + AC_LINK_IFELSE([PTHREADTEST_SRC], [AC_MSG_RESULT([yes]) NO_PTHREADS= PTHREAD_LIBS="$PTHREAD_CFLAGS" @@ -395,26 +395,28 @@ static int git_use_proxy(const char *host) return (git_proxy_command && *git_proxy_command); } -static void git_proxy_connect(int fd[2], char *host) +static struct child_process *git_proxy_connect(int fd[2], char *host) { const char *port = STR(DEFAULT_GIT_PORT); - const char *argv[4]; - struct child_process proxy; + const char **argv; + struct child_process *proxy; get_host_and_port(&host, &port); + argv = xmalloc(sizeof(*argv) * 4); argv[0] = git_proxy_command; argv[1] = host; argv[2] = port; argv[3] = NULL; - memset(&proxy, 0, sizeof(proxy)); - proxy.argv = argv; - proxy.in = -1; - proxy.out = -1; - if (start_command(&proxy)) + proxy = xcalloc(1, sizeof(*proxy)); + proxy->argv = argv; + proxy->in = -1; + proxy->out = -1; + if (start_command(proxy)) die("cannot start proxy %s", argv[0]); - fd[0] = proxy.out; /* read from proxy stdout */ - fd[1] = proxy.in; /* write to proxy stdin */ + fd[0] = proxy->out; /* read from proxy stdout */ + fd[1] = proxy->in; /* write to proxy stdin */ + return proxy; } #define MAX_CMD_LEN 1024 @@ -455,7 +457,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, char *host, *path; char *end; int c; - struct child_process *conn; + struct child_process *conn = &no_fork; enum protocol protocol = PROTO_LOCAL; int free_path = 0; char *port = NULL; @@ -540,7 +542,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, */ char *target_host = xstrdup(host); if (git_use_proxy(host)) - git_proxy_connect(fd, host); + conn = git_proxy_connect(fd, host); else git_tcp_connect(fd, host, flags); /* @@ -558,7 +560,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, free(url); if (free_path) free(path); - return &no_fork; + return conn; } conn = xcalloc(1, sizeof(*conn)); @@ -607,10 +609,15 @@ struct child_process *git_connect(int fd[2], const char *url_orig, return conn; } +int git_connection_is_socket(struct child_process *conn) +{ + return conn == &no_fork; +} + int finish_connect(struct child_process *conn) { int code; - if (!conn || conn == &no_fork) + if (!conn || git_connection_is_socket(conn)) return 0; code = finish_command(conn); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 893b7716ca..b36290fa60 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1,6 +1,6 @@ #!bash # -# bash completion support for core Git. +# bash/zsh completion support for core Git. # # Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org> # Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/). @@ -18,16 +18,12 @@ # To use these routines: # # 1) Copy this file to somewhere (e.g. ~/.git-completion.sh). -# 2) Added the following line to your .bashrc: -# source ~/.git-completion.sh -# -# Or, add the following lines to your .zshrc: -# autoload bashcompinit -# bashcompinit +# 2) Add the following line to your .bashrc/.zshrc: # source ~/.git-completion.sh # # 3) Consider changing your PS1 to also show the current branch: -# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' +# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' +# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' # # The argument to __git_ps1 will be displayed only if you # are currently in a git repository. The %s token will be @@ -77,6 +73,10 @@ # git@vger.kernel.org # +if [[ -n ${ZSH_VERSION-} ]]; then + autoload -U +X bashcompinit && bashcompinit +fi + case "$COMP_WORDBREAKS" in *:*) : great ;; *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" @@ -246,6 +246,8 @@ __git_ps1 () fi elif [ -f "$g/MERGE_HEAD" ]; then r="|MERGING" + elif [ -f "$g/CHERRY_PICK_HEAD" ]; then + r="|CHERRY-PICKING" elif [ -f "$g/BISECT_LOG" ]; then r="|BISECTING" fi @@ -487,12 +489,12 @@ fi # generates completion reply with compgen __gitcomp () { - local cur - _get_comp_words_by_ref -n =: cur + local cur_="$cur" + if [ $# -gt 2 ]; then - cur="$3" + cur_="$3" fi - case "$cur" in + case "$cur_" in --*=) COMPREPLY=() ;; @@ -500,7 +502,7 @@ __gitcomp () local IFS=$'\n' COMPREPLY=($(compgen -P "${2-}" \ -W "$(__gitcomp_1 "${1-}" "${4-}")" \ - -- "$cur")) + -- "$cur_")) ;; esac } @@ -549,8 +551,7 @@ __git_tags () __git_refs () { local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}" - local cur format refs - _get_comp_words_by_ref -n =: cur + local format refs if [ -d "$dir" ]; then case "$cur" in refs|refs/*) @@ -627,12 +628,12 @@ __git_refs_remotes () __git_remotes () { local i ngoff IFS=$'\n' d="$(__gitdir)" - shopt -q nullglob || ngoff=1 - shopt -s nullglob + __git_shopt -q nullglob || ngoff=1 + __git_shopt -s nullglob for i in "$d/remotes"/*; do echo ${i#$d/remotes/} done - [ "$ngoff" ] && shopt -u nullglob + [ "$ngoff" ] && __git_shopt -u nullglob for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do i="${i#remote.}" echo "${i/.url*/}" @@ -662,25 +663,27 @@ __git_compute_merge_strategies () : ${__git_merge_strategies:=$(__git_list_merge_strategies)} } -__git_complete_file () +__git_complete_revlist_file () { - local pfx ls ref cur - _get_comp_words_by_ref -n =: cur - case "$cur" in + local pfx ls ref cur_="$cur" + case "$cur_" in + *..?*:*) + return + ;; ?*:*) - ref="${cur%%:*}" - cur="${cur#*:}" - case "$cur" in + ref="${cur_%%:*}" + cur_="${cur_#*:}" + case "$cur_" in ?*/*) - pfx="${cur%/*}" - cur="${cur##*/}" + pfx="${cur_%/*}" + cur_="${cur_##*/}" ls="$ref:$pfx" pfx="$pfx/" ;; *) ls="$ref" ;; - esac + esac case "$COMP_WORDBREAKS" in *:*) : great ;; @@ -703,28 +706,17 @@ __git_complete_file () s,$,/, } s/^.* //')" \ - -- "$cur")) - ;; - *) - __gitcomp "$(__git_refs)" + -- "$cur_")) ;; - esac -} - -__git_complete_revlist () -{ - local pfx cur - _get_comp_words_by_ref -n =: cur - case "$cur" in *...*) - pfx="${cur%...*}..." - cur="${cur#*...}" - __gitcomp "$(__git_refs)" "$pfx" "$cur" + pfx="${cur_%...*}..." + cur_="${cur_#*...}" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" ;; *..*) - pfx="${cur%..*}.." - cur="${cur#*..}" - __gitcomp "$(__git_refs)" "$pfx" "$cur" + pfx="${cur_%..*}.." + cur_="${cur_#*..}" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" ;; *) __gitcomp "$(__git_refs)" @@ -732,11 +724,20 @@ __git_complete_revlist () esac } + +__git_complete_file () +{ + __git_complete_revlist_file +} + +__git_complete_revlist () +{ + __git_complete_revlist_file +} + __git_complete_remote_or_refspec () { - local cur words cword - _get_comp_words_by_ref -n =: cur words cword - local cmd="${words[1]}" + local cur_="$cur" cmd="${words[1]}" local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0 while [ $c -lt $cword ]; do i="${words[c]}" @@ -766,40 +767,40 @@ __git_complete_remote_or_refspec () return fi [ "$remote" = "." ] && remote= - case "$cur" in + case "$cur_" in *:*) case "$COMP_WORDBREAKS" in *:*) : great ;; - *) pfx="${cur%%:*}:" ;; + *) pfx="${cur_%%:*}:" ;; esac - cur="${cur#*:}" + cur_="${cur_#*:}" lhs=0 ;; +*) pfx="+" - cur="${cur#+}" + cur_="${cur_#+}" ;; esac case "$cmd" in fetch) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur" + __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_" else - __gitcomp "$(__git_refs)" "$pfx" "$cur" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" fi ;; pull) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur" + __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_" else - __gitcomp "$(__git_refs)" "$pfx" "$cur" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" fi ;; push) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs)" "$pfx" "$cur" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" else - __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur" + __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_" fi ;; esac @@ -807,8 +808,6 @@ __git_complete_remote_or_refspec () __git_complete_strategy () { - local cur prev - _get_comp_words_by_ref -n =: cur prev __git_compute_merge_strategies case "$prev" in -s|--strategy) @@ -986,8 +985,7 @@ __git_aliased_command () # __git_find_on_cmdline requires 1 argument __git_find_on_cmdline () { - local word subcommand c=1 words cword - _get_comp_words_by_ref -n =: words cword + local word subcommand c=1 while [ $c -lt $cword ]; do word="${words[c]}" for subcommand in $1; do @@ -1002,8 +1000,7 @@ __git_find_on_cmdline () __git_has_doubledash () { - local c=1 words cword - _get_comp_words_by_ref -n =: words cword + local c=1 while [ $c -lt $cword ]; do if [ "--" = "${words[c]}" ]; then return 0 @@ -1017,8 +1014,7 @@ __git_whitespacelist="nowarn warn error error-all fix" _git_am () { - local cur dir="$(__gitdir)" - _get_comp_words_by_ref -n =: cur + local dir="$(__gitdir)" if [ -d "$dir"/rebase-apply ]; then __gitcomp "--skip --continue --resolved --abort" return @@ -1042,8 +1038,6 @@ _git_am () _git_apply () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --whitespace=*) __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" @@ -1066,8 +1060,6 @@ _git_add () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1081,8 +1073,6 @@ _git_add () _git_archive () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --format=*) __gitcomp "$(git archive --list)" "" "${cur##--format=}" @@ -1130,9 +1120,8 @@ _git_bisect () _git_branch () { - local i c=1 only_local_ref="n" has_r="n" cur words cword + local i c=1 only_local_ref="n" has_r="n" - _get_comp_words_by_ref -n =: cur words cword while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -1162,8 +1151,6 @@ _git_branch () _git_bundle () { - local words cword - _get_comp_words_by_ref -n =: words cword local cmd="${words[2]}" case "$cword" in 2) @@ -1186,8 +1173,6 @@ _git_checkout () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --conflict=*) __gitcomp "diff3 merge" "" "${cur##--conflict=}" @@ -1217,8 +1202,6 @@ _git_cherry () _git_cherry_pick () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--edit --no-commit" @@ -1233,8 +1216,6 @@ _git_clean () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--dry-run --quiet" @@ -1246,8 +1227,6 @@ _git_clean () _git_clone () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1274,8 +1253,6 @@ _git_commit () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --cleanup=*) __gitcomp "default strip verbatim whitespace @@ -1310,8 +1287,6 @@ _git_commit () _git_describe () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1343,8 +1318,6 @@ _git_diff () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex @@ -1354,19 +1327,17 @@ _git_diff () return ;; esac - __git_complete_file + __git_complete_revlist_file } __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff - tkdiff vimdiff gvimdiff xxdiff araxis p4merge + tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc3 " _git_difftool () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --tool=*) __gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}" @@ -1391,8 +1362,6 @@ __git_fetch_options=" _git_fetch () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "$__git_fetch_options" @@ -1404,8 +1373,6 @@ _git_fetch () _git_format_patch () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --thread=*) __gitcomp " @@ -1437,8 +1404,6 @@ _git_format_patch () _git_fsck () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1453,8 +1418,6 @@ _git_fsck () _git_gc () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--prune --aggressive" @@ -1473,15 +1436,14 @@ _git_grep () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " --cached --text --ignore-case --word-regexp --invert-match - --full-name + --full-name --line-number --extended-regexp --basic-regexp --fixed-strings + --perl-regexp --files-with-matches --name-only --files-without-match --max-depth @@ -1497,8 +1459,6 @@ _git_grep () _git_help () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--all --info --man --web" @@ -1506,7 +1466,7 @@ _git_help () ;; esac __git_compute_all_commands - __gitcomp "$__git_all_commands + __gitcomp "$__git_all_commands $(__git_aliases) attributes cli core-tutorial cvs-migration diffcore gitk glossary hooks ignore modules repository-layout tutorial tutorial-2 @@ -1516,8 +1476,6 @@ _git_help () _git_init () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --shared=*) __gitcomp " @@ -1537,8 +1495,6 @@ _git_ls_files () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--cached --deleted --modified --others --ignored @@ -1572,12 +1528,14 @@ __git_log_common_options=" --max-count= --max-age= --since= --after= --min-age= --until= --before= + --min-parents= --max-parents= + --no-min-parents --no-max-parents " # Options that go well for log and gitk (not shortlog) __git_log_gitk_options=" --dense --sparse --full-history --simplify-merges --simplify-by-decoration - --left-right + --left-right --notes --no-notes " # Options that go well for log and shortlog (not gitk) __git_log_shortlog_options=" @@ -1597,8 +1555,6 @@ _git_log () if [ -f "$g/MERGE_HEAD" ]; then merge="--merge" fi - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --pretty=*) __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) @@ -1652,8 +1608,6 @@ _git_merge () { __git_complete_strategy && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "$__git_merge_options" @@ -1664,8 +1618,6 @@ _git_merge () _git_mergetool () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --tool=*) __gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}" @@ -1686,8 +1638,6 @@ _git_merge_base () _git_mv () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--dry-run" @@ -1706,8 +1656,6 @@ _git_notes () { local subcommands='add append copy edit list prune remove show' local subcommand="$(__git_find_on_cmdline "$subcommands")" - local cur words cword - _get_comp_words_by_ref -n =: cur words cword case "$subcommand,$cur" in ,--*) @@ -1757,8 +1705,6 @@ _git_pull () { __git_complete_strategy && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1774,8 +1720,6 @@ _git_pull () _git_push () { - local cur prev - _get_comp_words_by_ref -n =: cur prev case "$prev" in --repo) __gitcomp "$(__git_remotes)" @@ -1800,8 +1744,6 @@ _git_push () _git_rebase () { local dir="$(__gitdir)" - local cur - _get_comp_words_by_ref -n =: cur if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then __gitcomp "--continue --skip --abort" return @@ -1843,8 +1785,6 @@ __git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all" _git_send_email () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --confirm=*) __gitcomp " @@ -1886,8 +1826,6 @@ _git_stage () __git_config_get_set_variables () { - local words cword - _get_comp_words_by_ref -n =: words cword local prevword word config_file= c=$cword while [ $c -gt 1 ]; do word="${words[c]}" @@ -1918,8 +1856,6 @@ __git_config_get_set_variables () _git_config () { - local cur prev - _get_comp_words_by_ref -n =: cur prev case "$prev" in branch.*.remote) __gitcomp "$(__git_remotes)" @@ -2005,70 +1941,60 @@ _git_config () return ;; branch.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur_" return ;; branch.*) - local pfx="${cur%.*}." - cur="${cur#*.}" - __gitcomp "$(__git_heads)" "$pfx" "$cur" "." + local pfx="${cur%.*}." cur_="${cur#*.}" + __gitcomp "$(__git_heads)" "$pfx" "$cur_" "." return ;; guitool.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" + local pfx="${cur%.*}." cur_="${cur##*.}" __gitcomp " argprompt cmd confirm needsfile noconsole norescan prompt revprompt revunmerged title - " "$pfx" "$cur" + " "$pfx" "$cur_" return ;; difftool.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "cmd path" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "cmd path" "$pfx" "$cur_" return ;; man.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "cmd path" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "cmd path" "$pfx" "$cur_" return ;; mergetool.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "cmd path trustExitCode" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "cmd path trustExitCode" "$pfx" "$cur_" return ;; pager.*) - local pfx="${cur%.*}." - cur="${cur#*.}" + local pfx="${cur%.*}." cur_="${cur#*.}" __git_compute_all_commands - __gitcomp "$__git_all_commands" "$pfx" "$cur" + __gitcomp "$__git_all_commands" "$pfx" "$cur_" return ;; remote.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" + local pfx="${cur%.*}." cur_="${cur##*.}" __gitcomp " url proxy fetch push mirror skipDefaultUpdate receivepack uploadpack tagopt pushurl - " "$pfx" "$cur" + " "$pfx" "$cur_" return ;; remote.*) - local pfx="${cur%.*}." - cur="${cur#*.}" - __gitcomp "$(__git_remotes)" "$pfx" "$cur" "." + local pfx="${cur%.*}." cur_="${cur#*.}" + __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "." return ;; url.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" return ;; esac @@ -2389,8 +2315,6 @@ _git_reset () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--merge --mixed --hard --soft --patch" @@ -2402,8 +2326,6 @@ _git_reset () _git_revert () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--edit --mainline --no-edit --no-commit --signoff" @@ -2417,8 +2339,6 @@ _git_rm () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--cached --dry-run --ignore-unmatch --quiet" @@ -2432,8 +2352,6 @@ _git_shortlog () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -2451,8 +2369,6 @@ _git_show () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --pretty=*) __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) @@ -2476,8 +2392,6 @@ _git_show () _git_show_branch () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -2494,8 +2408,6 @@ _git_show_branch () _git_stash () { - local cur - _get_comp_words_by_ref -n =: cur local save_opts='--keep-index --no-keep-index --quiet --patch' local subcommands='save list show apply clear drop pop create branch' local subcommand="$(__git_find_on_cmdline "$subcommands")" @@ -2540,8 +2452,6 @@ _git_submodule () local subcommands="add status init update summary foreach sync" if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--quiet --cached" @@ -2585,8 +2495,6 @@ _git_svn () --edit --rmdir --find-copies-harder --copy-similarity= " - local cur - _get_comp_words_by_ref -n =: cur case "$subcommand,$cur" in fetch,--*) __gitcomp "--revision= --fetch-all $fc_opts" @@ -2658,8 +2566,6 @@ _git_svn () _git_tag () { local i c=1 f=0 - local words cword prev - _get_comp_words_by_ref -n =: words cword prev while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -2703,10 +2609,14 @@ _git () if [[ -n ${ZSH_VERSION-} ]]; then emulate -L bash setopt KSH_TYPESET + + # workaround zsh's bug that leaves 'words' as a special + # variable in versions < 4.3.12 + typeset -h words fi - local cur words cword - _get_comp_words_by_ref -n =: cur words cword + local cur words cword prev + _get_comp_words_by_ref -n =: cur words cword prev while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -2754,17 +2664,22 @@ _gitk () if [[ -n ${ZSH_VERSION-} ]]; then emulate -L bash setopt KSH_TYPESET + + # workaround zsh's bug that leaves 'words' as a special + # variable in versions < 4.3.12 + typeset -h words fi + local cur words cword prev + _get_comp_words_by_ref -n =: cur words cword prev + __git_has_doubledash && return - local cur local g="$(__gitdir)" local merge="" if [ -f "$g/MERGE_HEAD" ]; then merge="--merge" fi - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -2793,7 +2708,7 @@ complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \ fi if [[ -n ${ZSH_VERSION-} ]]; then - shopt () { + __git_shopt () { local option if [ $# -ne 2 ]; then echo "USAGE: $0 (-q|-s|-u) <option>" >&2 @@ -2816,4 +2731,8 @@ if [[ -n ${ZSH_VERSION-} ]]; then return 1 esac } +else + __git_shopt () { + shopt "$@" + } fi diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 214930a021..65c95d9d5a 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1310,6 +1310,13 @@ The FILES list must be sorted." (when sign-off (git-append-sign-off committer-name committer-email))) buffer)) +(define-derived-mode git-log-edit-mode log-edit-mode "Git-Log-Edit" + "Major mode for editing git log messages. + +Set up git-specific `font-lock-keywords' for `log-edit-mode'." + (set (make-local-variable 'font-lock-defaults) + '(git-log-edit-font-lock-keywords t t))) + (defun git-commit-file () "Commit the marked file(s), asking for a commit message." (interactive) @@ -1335,9 +1342,9 @@ The FILES list must be sorted." (git-setup-log-buffer buffer (git-get-merge-heads) author-name author-email subject date)) (if (boundp 'log-edit-diff-function) (log-edit 'git-do-commit nil '((log-edit-listfun . git-log-edit-files) - (log-edit-diff-function . git-log-edit-diff)) buffer) - (log-edit 'git-do-commit nil 'git-log-edit-files buffer)) - (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords)) + (log-edit-diff-function . git-log-edit-diff)) buffer 'git-log-edit-mode) + (log-edit 'git-do-commit nil 'git-log-edit-files buffer + 'git-log-edit-mode)) (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[ ]*$")) (setq buffer-file-coding-system coding-system) (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))) diff --git a/contrib/examples/git-revert.sh b/contrib/examples/git-revert.sh index 60a05a8b97..6bf155cbdb 100755 --- a/contrib/examples/git-revert.sh +++ b/contrib/examples/git-revert.sh @@ -26,6 +26,7 @@ require_work_tree cd_to_toplevel no_commit= +xopt= while case "$#" in 0) break ;; esac do case "$1" in @@ -44,6 +45,16 @@ do -x|--i-really-want-to-expose-my-private-commit-object-name) replay= ;; + -X?*) + xopt="$xopt$(git rev-parse --sq-quote "--${1#-X}")" + ;; + --strategy-option=*) + xopt="$xopt$(git rev-parse --sq-quote "--${1#--strategy-option=}")" + ;; + -X|--strategy-option) + shift + xopt="$xopt$(git rev-parse --sq-quote "--$1")" + ;; -*) usage ;; @@ -159,7 +170,7 @@ export GITHEAD_$head GITHEAD_$next # and $prev on top of us (when reverting), or the change between # $prev and $commit on top of us (when cherry-picking or replaying). -git-merge-recursive $base -- $head $next && +eval "git merge-recursive $xopt $base -- $head $next" && result=$(git-write-tree 2>/dev/null) || { mv -f .msg "$GIT_DIR/MERGE_MSG" { diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 04ce7e3b02..98d2aee67f 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -222,10 +222,10 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None): try: while True: entry = marshal.load(p4.stdout) - if cb is not None: - cb(entry) - else: - result.append(entry) + if cb is not None: + cb(entry) + else: + result.append(entry) except EOFError: pass exitCode = p4.wait() @@ -333,9 +333,13 @@ def gitBranchExists(branch): return proc.wait() == 0; _gitConfig = {} -def gitConfig(key): +def gitConfig(key, args = None): # set args to "--bool", for instance if not _gitConfig.has_key(key): - _gitConfig[key] = read_pipe("git config %s" % key, ignore_error=True).strip() + argsFilter = "" + if args != None: + argsFilter = "%s " % args + cmd = "git config %s%s" % (argsFilter, key) + _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip() return _gitConfig[key] def p4BranchesInGit(branchesAreInRemotes = True): @@ -445,18 +449,72 @@ def p4ChangesForPaths(depotPaths, changeRange): changes = {} for line in output: - changeNum = int(line.split(" ")[1]) - changes[changeNum] = True + changeNum = int(line.split(" ")[1]) + changes[changeNum] = True changelist = changes.keys() changelist.sort() return changelist +def p4PathStartsWith(path, prefix): + # This method tries to remedy a potential mixed-case issue: + # + # If UserA adds //depot/DirA/file1 + # and UserB adds //depot/dira/file2 + # + # we may or may not have a problem. If you have core.ignorecase=true, + # we treat DirA and dira as the same directory + ignorecase = gitConfig("core.ignorecase", "--bool") == "true" + if ignorecase: + return path.lower().startswith(prefix.lower()) + return path.startswith(prefix) + class Command: def __init__(self): self.usage = "usage: %prog [options]" self.needsGit = True +class P4UserMap: + def __init__(self): + self.userMapFromPerforceServer = False + + def getUserCacheFilename(self): + home = os.environ.get("HOME", os.environ.get("USERPROFILE")) + return home + "/.gitp4-usercache.txt" + + def getUserMapFromPerforceServer(self): + if self.userMapFromPerforceServer: + return + self.users = {} + self.emails = {} + + for output in p4CmdList("users"): + if not output.has_key("User"): + continue + self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">" + self.emails[output["Email"]] = output["User"] + + + s = '' + for (key, val) in self.users.items(): + s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1)) + + open(self.getUserCacheFilename(), "wb").write(s) + self.userMapFromPerforceServer = True + + def loadUserMapFromCache(self): + self.users = {} + self.userMapFromPerforceServer = False + try: + cache = open(self.getUserCacheFilename(), "rb") + lines = cache.readlines() + cache.close() + for line in lines: + entry = line.strip().split("\t") + self.users[entry[0]] = entry[1] + except IOError: + self.getUserMapFromPerforceServer() + class P4Debug(Command): def __init__(self): Command.__init__(self) @@ -537,21 +595,26 @@ class P4RollBack(Command): return True -class P4Submit(Command): +class P4Submit(Command, P4UserMap): def __init__(self): Command.__init__(self) + P4UserMap.__init__(self) self.options = [ optparse.make_option("--verbose", dest="verbose", action="store_true"), optparse.make_option("--origin", dest="origin"), - optparse.make_option("-M", dest="detectRename", action="store_true"), + optparse.make_option("-M", dest="detectRenames", action="store_true"), + # preserve the user, requires relevant p4 permissions + optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"), ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" self.interactive = True self.origin = "" - self.detectRename = False + self.detectRenames = False self.verbose = False + self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true" self.isWindows = (platform.system() == "Windows") + self.myP4UserId = None def check(self): if len(p4CmdList("opened ...")) > 0: @@ -570,7 +633,7 @@ class P4Submit(Command): continue if inDescriptionSection: - if line.startswith("Files:"): + if line.startswith("Files:") or line.startswith("Jobs:"): inDescriptionSection = False else: continue @@ -585,6 +648,99 @@ class P4Submit(Command): return result + def p4UserForCommit(self,id): + # Return the tuple (perforce user,git email) for a given git commit id + self.getUserMapFromPerforceServer() + gitEmail = read_pipe("git log --max-count=1 --format='%%ae' %s" % id) + gitEmail = gitEmail.strip() + if not self.emails.has_key(gitEmail): + return (None,gitEmail) + else: + return (self.emails[gitEmail],gitEmail) + + def checkValidP4Users(self,commits): + # check if any git authors cannot be mapped to p4 users + for id in commits: + (user,email) = self.p4UserForCommit(id) + if not user: + msg = "Cannot find p4 user for email %s in commit %s." % (email, id) + if gitConfig('git-p4.allowMissingP4Users').lower() == "true": + print "%s" % msg + else: + die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg) + + def lastP4Changelist(self): + # Get back the last changelist number submitted in this client spec. This + # then gets used to patch up the username in the change. If the same + # client spec is being used by multiple processes then this might go + # wrong. + results = p4CmdList("client -o") # find the current client + client = None + for r in results: + if r.has_key('Client'): + client = r['Client'] + break + if not client: + die("could not get client spec") + results = p4CmdList("changes -c %s -m 1" % client) + for r in results: + if r.has_key('change'): + return r['change'] + die("Could not get changelist number for last submit - cannot patch up user details") + + def modifyChangelistUser(self, changelist, newUser): + # fixup the user field of a changelist after it has been submitted. + changes = p4CmdList("change -o %s" % changelist) + if len(changes) != 1: + die("Bad output from p4 change modifying %s to user %s" % + (changelist, newUser)) + + c = changes[0] + if c['User'] == newUser: return # nothing to do + c['User'] = newUser + input = marshal.dumps(c) + + result = p4CmdList("change -f -i", stdin=input) + for r in result: + if r.has_key('code'): + if r['code'] == 'error': + die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data'])) + if r.has_key('data'): + print("Updated user field for changelist %s to %s" % (changelist, newUser)) + return + die("Could not modify user field of changelist %s to %s" % (changelist, newUser)) + + def canChangeChangelists(self): + # check to see if we have p4 admin or super-user permissions, either of + # which are required to modify changelists. + results = p4CmdList("protects %s" % self.depotPath) + for r in results: + if r.has_key('perm'): + if r['perm'] == 'admin': + return 1 + if r['perm'] == 'super': + return 1 + return 0 + + def p4UserId(self): + if self.myP4UserId: + return self.myP4UserId + + results = p4CmdList("user -o") + for r in results: + if r.has_key('User'): + self.myP4UserId = r['User'] + return r['User'] + die("Could not find your p4 user id") + + def p4UserIsMe(self, p4User): + # return True if the given p4 user is actually me + me = self.p4UserId() + if not p4User or p4User != me: + return False + else: + return True + def prepareSubmitTemplate(self): # remove lines in the Files section that show changes to files outside the depot path we're committing into template = "" @@ -599,7 +755,7 @@ class P4Submit(Command): lastTab = path.rfind("\t") if lastTab != -1: path = path[:lastTab] - if not path.startswith(self.depotPath): + if not p4PathStartsWith(path, self.depotPath): continue else: inFilesSection = False @@ -613,7 +769,24 @@ class P4Submit(Command): def applyCommit(self, id): print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id)) - diffOpts = ("", "-M")[self.detectRename] + + (p4User, gitEmail) = self.p4UserForCommit(id) + + if not self.detectRenames: + # If not explicitly set check the config variable + self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true" + + if self.detectRenames: + diffOpts = "-M" + else: + diffOpts = "" + + if gitConfig("git-p4.detectCopies").lower() == "true": + diffOpts += " -C" + + if gitConfig("git-p4.detectCopiesHarder").lower() == "true": + diffOpts += " --find-copies-harder" + diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id)) filesToAdd = set() filesToDelete = set() @@ -637,11 +810,23 @@ class P4Submit(Command): filesToDelete.add(path) if path in filesToAdd: filesToAdd.remove(path) + elif modifier == "C": + src, dest = diff['src'], diff['dst'] + p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest)) + if diff['src_sha1'] != diff['dst_sha1']: + p4_system("edit \"%s\"" % (dest)) + if isModeExecChanged(diff['src_mode'], diff['dst_mode']): + p4_system("edit \"%s\"" % (dest)) + filesToChangeExecBit[dest] = diff['dst_mode'] + os.unlink(dest) + editedFiles.add(dest) elif modifier == "R": src, dest = diff['src'], diff['dst'] p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest)) - p4_system("edit \"%s\"" % (dest)) + if diff['src_sha1'] != diff['dst_sha1']: + p4_system("edit \"%s\"" % (dest)) if isModeExecChanged(diff['src_mode'], diff['dst_mode']): + p4_system("edit \"%s\"" % (dest)) filesToChangeExecBit[dest] = diff['dst_mode'] os.unlink(dest) editedFiles.add(dest) @@ -704,6 +889,10 @@ class P4Submit(Command): if self.interactive: submitTemplate = self.prepareLogMessage(template, logMessage) + + if self.preserveUser: + submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User) + if os.environ.has_key("P4DIFF"): del(os.environ["P4DIFF"]) diff = "" @@ -720,6 +909,11 @@ class P4Submit(Command): newdiff += "+" + line f.close() + if self.checkAuthorship and not self.p4UserIsMe(p4User): + submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail + submitTemplate += "######## Use git-p4 option --preserve-user to modify authorship\n" + submitTemplate += "######## Use git-p4 config git-p4.skipUserNameCheck hides this message.\n" + separatorLine = "######## everything below this line is just the diff #######\n" [handle, fileName] = tempfile.mkstemp() @@ -737,8 +931,13 @@ class P4Submit(Command): editor = read_pipe("git var GIT_EDITOR").strip() system(editor + " " + fileName) + if gitConfig("git-p4.skipSubmitEditCheck") == "true": + checkModTime = False + else: + checkModTime = True + response = "y" - if os.stat(fileName).st_mtime <= mtime: + if checkModTime and (os.stat(fileName).st_mtime <= mtime): response = "x" while response != "y" and response != "n": response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ") @@ -751,6 +950,14 @@ class P4Submit(Command): if self.isWindows: submitTemplate = submitTemplate.replace("\r\n", "\n") p4_write_pipe("submit -i", submitTemplate) + + if self.preserveUser: + if p4User: + # Get last changelist number. Cannot easily get it from + # the submit command output as the output is unmarshalled. + changelist = self.lastP4Changelist() + self.modifyChangelistUser(changelist, p4User) + else: for f in editedFiles: p4_system("revert \"%s\"" % f); @@ -787,6 +994,10 @@ class P4Submit(Command): if len(self.origin) == 0: self.origin = upstream + if self.preserveUser: + if not self.canChangeChangelists(): + die("Cannot preserve user names without p4 super-user or admin permissions") + if self.verbose: print "Origin branch is " + self.origin @@ -814,6 +1025,14 @@ class P4Submit(Command): commits.append(line.strip()) commits.reverse() + if self.preserveUser or (gitConfig("git-p4.skipUserNameCheck") == "true"): + self.checkAuthorship = False + else: + self.checkAuthorship = True + + if self.preserveUser: + self.checkValidP4Users(commits) + while len(commits) > 0: commit = commits[0] commits = commits[1:] @@ -833,9 +1052,12 @@ class P4Submit(Command): return True -class P4Sync(Command): +class P4Sync(Command, P4UserMap): + delete_actions = ( "delete", "move/delete", "purge" ) + def __init__(self): Command.__init__(self) + P4UserMap.__init__(self) self.options = [ optparse.make_option("--branch", dest="branch"), optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"), @@ -882,6 +1104,23 @@ class P4Sync(Command): if gitConfig("git-p4.syncFromOrigin") == "false": self.syncWithOrigin = False + # + # P4 wildcards are not allowed in filenames. P4 complains + # if you simply add them, but you can force it with "-f", in + # which case it translates them into %xx encoding internally. + # Search for and fix just these four characters. Do % last so + # that fixing it does not inadvertently create new %-escapes. + # + def wildcard_decode(self, path): + # Cannot have * in a filename in windows; untested as to + # what p4 would do in such a case. + if not self.isWindows: + path = path.replace("%2A", "*") + path = path.replace("%23", "#") \ + .replace("%40", "@") \ + .replace("%25", "%") + return path + def extractFilesFromCommit(self, commit): self.cloneExclude = [re.sub(r"\.\.\.$", "", path) for path in self.cloneExclude] @@ -891,11 +1130,11 @@ class P4Sync(Command): path = commit["depotFile%s" % fnum] if [p for p in self.cloneExclude - if path.startswith (p)]: + if p4PathStartsWith(path, p)]: found = False else: found = [p for p in self.depotPaths - if path.startswith (p)] + if p4PathStartsWith(path, p)] if not found: fnum = fnum + 1 continue @@ -910,11 +1149,27 @@ class P4Sync(Command): return files def stripRepoPath(self, path, prefixes): + if self.useClientSpec: + + # if using the client spec, we use the output directory + # specified in the client. For example, a view + # //depot/foo/branch/... //client/branch/foo/... + # will end up putting all foo/branch files into + # branch/foo/ + for val in self.clientSpecDirs: + if path.startswith(val[0]): + # replace the depot path with the client path + path = path.replace(val[0], val[1][1]) + # now strip out the client (//client/...) + path = re.sub("^(//[^/]+/)", '', path) + # the rest is all path + return path + if self.keepRepoPath: prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])] for p in prefixes: - if path.startswith(p): + if p4PathStartsWith(path, p): path = path[len(p):] return path @@ -925,7 +1180,7 @@ class P4Sync(Command): while commit.has_key("depotFile%s" % fnum): path = commit["depotFile%s" % fnum] found = [p for p in self.depotPaths - if path.startswith (p)] + if p4PathStartsWith(path, p)] if not found: fnum = fnum + 1 continue @@ -954,12 +1209,13 @@ class P4Sync(Command): # - helper for streamP4Files def streamOneP4File(self, file, contents): - if file["type"] == "apple": - print "\nfile %s is a strange apple file that forks. Ignoring" % \ - file['depotFile'] - return + if file["type"] == "apple": + print "\nfile %s is a strange apple file that forks. Ignoring" % \ + file['depotFile'] + return relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes) + relPath = self.wildcard_decode(relPath) if verbose: sys.stderr.write("%s\n" % relPath) @@ -1005,22 +1261,22 @@ class P4Sync(Command): # handle another chunk of streaming data def streamP4FilesCb(self, marshalled): - if marshalled.has_key('depotFile') and self.stream_have_file_info: - # start of a new file - output the old one first - self.streamOneP4File(self.stream_file, self.stream_contents) - self.stream_file = {} - self.stream_contents = [] - self.stream_have_file_info = False + if marshalled.has_key('depotFile') and self.stream_have_file_info: + # start of a new file - output the old one first + self.streamOneP4File(self.stream_file, self.stream_contents) + self.stream_file = {} + self.stream_contents = [] + self.stream_have_file_info = False - # pick up the new file information... for the - # 'data' field we need to append to our array - for k in marshalled.keys(): - if k == 'data': - self.stream_contents.append(marshalled['data']) - else: - self.stream_file[k] = marshalled[k] + # pick up the new file information... for the + # 'data' field we need to append to our array + for k in marshalled.keys(): + if k == 'data': + self.stream_contents.append(marshalled['data']) + else: + self.stream_file[k] = marshalled[k] - self.stream_have_file_info = True + self.stream_have_file_info = True # Stream directly from "p4 files" into "git fast-import" def streamP4Files(self, files): @@ -1032,16 +1288,16 @@ class P4Sync(Command): includeFile = True for val in self.clientSpecDirs: if f['path'].startswith(val[0]): - if val[1] <= 0: + if val[1][0] <= 0: includeFile = False break if includeFile: filesForCommit.append(f) - if f['action'] not in ('delete', 'move/delete', 'purge'): - filesToRead.append(f) - else: + if f['action'] in self.delete_actions: filesToDelete.append(f) + else: + filesToRead.append(f) # deleted files... for f in filesToDelete: @@ -1052,14 +1308,14 @@ class P4Sync(Command): self.stream_contents = [] self.stream_have_file_info = False - # curry self argument - def streamP4FilesCbSelf(entry): - self.streamP4FilesCb(entry) + # curry self argument + def streamP4FilesCbSelf(entry): + self.streamP4FilesCb(entry) - p4CmdList("-x - print", - '\n'.join(['%s#%s' % (f['path'], f['rev']) + p4CmdList("-x - print", + '\n'.join(['%s#%s' % (f['path'], f['rev']) for f in filesToRead]), - cb=streamP4FilesCbSelf) + cb=streamP4FilesCbSelf) # do the last chunk if self.stream_file.has_key('depotFile'): @@ -1068,7 +1324,7 @@ class P4Sync(Command): def commit(self, details, files, branch, branchPrefixes, parent = ""): epoch = details["time"] author = details["user"] - self.branchPrefixes = branchPrefixes + self.branchPrefixes = branchPrefixes if self.verbose: print "commit into %s" % branch @@ -1077,10 +1333,10 @@ class P4Sync(Command): # create a commit. new_files = [] for f in files: - if [p for p in branchPrefixes if f['path'].startswith(p)]: + if [p for p in branchPrefixes if p4PathStartsWith(f['path'], p)]: new_files.append (f) else: - sys.stderr.write("Ignoring file outside of prefix: %s\n" % path) + sys.stderr.write("Ignoring file outside of prefix: %s\n" % f['path']) self.gitStream.write("commit %s\n" % branch) # gitStream.write("mark :%s\n" % details["change"]) @@ -1127,7 +1383,7 @@ class P4Sync(Command): cleanedFiles = {} for info in files: - if info["action"] in ("delete", "purge"): + if info["action"] in self.delete_actions: continue cleanedFiles[info["depotFile"]] = info["rev"] @@ -1156,41 +1412,6 @@ class P4Sync(Command): print ("Tag %s does not match with change %s: file count is different." % (labelDetails["label"], change)) - def getUserCacheFilename(self): - home = os.environ.get("HOME", os.environ.get("USERPROFILE")) - return home + "/.gitp4-usercache.txt" - - def getUserMapFromPerforceServer(self): - if self.userMapFromPerforceServer: - return - self.users = {} - - for output in p4CmdList("users"): - if not output.has_key("User"): - continue - self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">" - - - s = '' - for (key, val) in self.users.items(): - s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1)) - - open(self.getUserCacheFilename(), "wb").write(s) - self.userMapFromPerforceServer = True - - def loadUserMapFromCache(self): - self.users = {} - self.userMapFromPerforceServer = False - try: - cache = open(self.getUserCacheFilename(), "rb") - lines = cache.readlines() - cache.close() - for line in lines: - entry = line.strip().split("\t") - self.users[entry[0]] = entry[1] - except IOError: - self.getUserMapFromPerforceServer() - def getLabels(self): self.labels = {} @@ -1241,7 +1462,7 @@ class P4Sync(Command): source = paths[0] destination = paths[1] ## HACK - if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]): + if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]): source = source[len(self.depotPaths[0]):-4] destination = destination[len(self.depotPaths[0]):-4] @@ -1429,7 +1650,7 @@ class P4Sync(Command): print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch) details = { "user" : "git perforce import user", "time" : int(time.time()) } - details["desc"] = ("Initial import of %s from the state at revision %s" + details["desc"] = ("Initial import of %s from the state at revision %s\n" % (' '.join(self.depotPaths), revision)) details["change"] = revision newestRevision = 0 @@ -1440,9 +1661,16 @@ class P4Sync(Command): % (p, revision) for p in self.depotPaths])): - if info['code'] == 'error': + if 'code' in info and info['code'] == 'error': sys.stderr.write("p4 returned an error: %s\n" % info['data']) + if info['data'].find("must refer to client") >= 0: + sys.stderr.write("This particular p4 error is misleading.\n") + sys.stderr.write("Perhaps the depot path was misspelled.\n"); + sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths)) + sys.exit(1) + if 'p4ExitCode' in info: + sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode']) sys.exit(1) @@ -1450,7 +1678,7 @@ class P4Sync(Command): if change > newestRevision: newestRevision = change - if info["action"] in ("delete", "purge"): + if info["action"] in self.delete_actions: # don't increase the file cnt, otherwise details["depotFile123"] will have gaps! #fileCnt = fileCnt + 1 continue @@ -1475,19 +1703,45 @@ class P4Sync(Command): for entry in specList: for k,v in entry.iteritems(): if k.startswith("View"): + + # p4 has these %%1 to %%9 arguments in specs to + # reorder paths; which we can't handle (yet :) + if re.match('%%\d', v) != None: + print "Sorry, can't handle %%n arguments in client specs" + sys.exit(1) + if v.startswith('"'): start = 1 else: start = 0 index = v.find("...") + + # save the "client view"; i.e the RHS of the view + # line that tells the client where to put the + # files for this view. + cv = v[index+3:].strip() # +3 to remove previous '...' + + # if the client view doesn't end with a + # ... wildcard, then we're going to mess up the + # output directory, so fail gracefully. + if not cv.endswith('...'): + print 'Sorry, client view in "%s" needs to end with wildcard' % (k) + sys.exit(1) + cv=cv[:-3] + + # now save the view; +index means included, -index + # means it should be filtered out. v = v[start:index] if v.startswith("-"): v = v[1:] - temp[v] = -len(v) + include = -len(v) else: - temp[v] = len(v) + include = len(v) + + temp[v] = (include, cv) + self.clientSpecDirs = temp.items() - self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) ) + self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) ) def run(self, args): self.depotPaths = [] @@ -1667,6 +1921,10 @@ class P4Sync(Command): changes.sort() else: + # catch "git-p4 sync" with no new branches, in a repo that + # does not have any existing git-p4 branches + if len(args) == 0 and not self.p4BranchesInGit: + die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here."); if self.verbose: print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths), self.changeRange) @@ -1747,10 +2005,13 @@ class P4Clone(P4Sync): help="where to leave result of the clone"), optparse.make_option("-/", dest="cloneExclude", action="append", type="string", - help="exclude depot path") + help="exclude depot path"), + optparse.make_option("--bare", dest="cloneBare", + action="store_true", default=False), ] self.cloneDestination = None self.needsGit = False + self.cloneBare = False # This is required for the "append" cloneExclude action def ensure_value(self, attr, value): @@ -1790,11 +2051,16 @@ class P4Clone(P4Sync): self.cloneDestination = self.defaultDestination(args) print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination) + if not os.path.exists(self.cloneDestination): os.makedirs(self.cloneDestination) chdir(self.cloneDestination) - system("git init") - self.gitdir = os.getcwd() + "/.git" + + init_cmd = [ "git", "init" ] + if self.cloneBare: + init_cmd.append("--bare") + subprocess.check_call(init_cmd) + if not P4Sync.run(self, depotPaths): return False if self.branch != "master": @@ -1804,7 +2070,8 @@ class P4Clone(P4Sync): masterbranch = "refs/heads/p4/master" if gitBranchExists(masterbranch): system("git branch master %s" % masterbranch) - system("git checkout -f") + if not self.cloneBare: + system("git checkout -f") else: print "Could not detect main branch. No checkout/master branch created." diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt index 49b335921a..caa4bb3e30 100644 --- a/contrib/fast-import/git-p4.txt +++ b/contrib/fast-import/git-p4.txt @@ -110,6 +110,12 @@ is not your current git branch you can also pass that as an argument: You can override the reference branch with the --origin=mysourcebranch option. +The Perforce changelists will be created with the user who ran git-p4. If you +use --preserve-user then git-p4 will attempt to create Perforce changelists +with the Perforce user corresponding to the git commit author. You need to +have sufficient permissions within Perforce, and the git users need to have +Perforce accounts. Permissions can be granted using 'p4 protect'. + If a submit fails you may have to "p4 resolve" and submit manually. You can continue importing the remaining changes with @@ -191,6 +197,41 @@ git-p4.useclientspec git config [--global] git-p4.useclientspec false +The P4CLIENT environment variable should be correctly set for p4 to be +able to find the relevant client. This client spec will be used to +both filter the files cloned by git and set the directory layout as +specified in the client (this implies --keep-path style semantics). + +git-p4.skipSubmitModTimeCheck + + git config [--global] git-p4.skipSubmitModTimeCheck false + +If true, submit will not check if the p4 change template has been modified. + +git-p4.preserveUser + + git config [--global] git-p4.preserveUser false + +If true, attempt to preserve user names by modifying the p4 changelists. See +the "--preserve-user" submit option. + +git-p4.allowMissingPerforceUsers + + git config [--global] git-p4.allowMissingP4Users false + +If git-p4 is setting the perforce user for a commit (--preserve-user) then +if there is no perforce user corresponding to the git author, git-p4 will +stop. With allowMissingPerforceUsers set to true, git-p4 will use the +current user (i.e. the behavior without --preserve-user) and carry on with +the perforce commit. + +git-p4.skipUserNameCheck + + git config [--global] git-p4.skipUserNameCheck false + +When submitting, git-p4 checks that the git commits are authored by the current +p4 user, and warns if they are not. This disables the check. + Implementation Details... ========================= diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index f99ea95850..21989fc6ab 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -709,7 +709,7 @@ if [ -z "$GIT_DIR" ]; then exit 1 fi -projectdesc=$(sed -ne '1p' "$GIT_DIR/description") +projectdesc=$(sed -ne '1p' "$GIT_DIR/description" 2>/dev/null) # Check if the description is unchanged from it's default, and shorten it to # a more manageable length if it is if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c index a2677b03e0..35db24f5ea 100644 --- a/contrib/svn-fe/svn-fe.c +++ b/contrib/svn-fe/svn-fe.c @@ -8,7 +8,8 @@ int main(int argc, char **argv) { - svndump_init(NULL); + if (svndump_init(NULL)) + return 1; svndump_read((argc > 1) ? argv[1] : NULL); svndump_deinit(); svndump_reset(); diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt index 35f84bd9e7..cd075b96c5 100644 --- a/contrib/svn-fe/svn-fe.txt +++ b/contrib/svn-fe/svn-fe.txt @@ -18,6 +18,9 @@ Subversion repository mirrored on the local disk. Remote Subversion repositories can be mirrored on local disk using the `svnsync` command. +Note: this tool is very young. The details of its commandline +interface may change in backward incompatible ways. + INPUT FORMAT ------------ Subversion's repository dump format is documented in full in diff --git a/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh index cc518f3c89..5eb4a51643 100755 --- a/contrib/thunderbird-patch-inline/appp.sh +++ b/contrib/thunderbird-patch-inline/appp.sh @@ -1,8 +1,8 @@ -#!/bin/bash +#!/bin/sh # Copyright 2008 Lukas Sandström <luksan@gmail.com> # # AppendPatch - A script to be used together with ExternalEditor -# for Mozilla Thunderbird to properly include pathes inline i e-mails. +# for Mozilla Thunderbird to properly include patches inline in e-mails. # ExternalEditor can be downloaded at http://globs.org/articles.php?lng=en&pg=2 @@ -12,13 +12,13 @@ * translation when the "text" attribute or "auto_crlf" option is set. */ -enum action { +enum crlf_action { CRLF_GUESS = -1, CRLF_BINARY = 0, CRLF_TEXT, CRLF_INPUT, CRLF_CRLF, - CRLF_AUTO, + CRLF_AUTO }; struct text_stat { @@ -94,9 +94,9 @@ static int is_binary(unsigned long size, struct text_stat *stats) return 0; } -static enum eol determine_output_conversion(enum action action) +static enum eol output_eol(enum crlf_action crlf_action) { - switch (action) { + switch (crlf_action) { case CRLF_BINARY: return EOL_UNSET; case CRLF_CRLF: @@ -113,19 +113,19 @@ static enum eol determine_output_conversion(enum action action) return EOL_CRLF; else if (auto_crlf == AUTO_CRLF_INPUT) return EOL_LF; - else if (eol == EOL_UNSET) + else if (core_eol == EOL_UNSET) return EOL_NATIVE; } - return eol; + return core_eol; } -static void check_safe_crlf(const char *path, enum action action, +static void check_safe_crlf(const char *path, enum crlf_action crlf_action, struct text_stat *stats, enum safe_crlf checksafe) { if (!checksafe) return; - if (determine_output_conversion(action) == EOL_LF) { + if (output_eol(crlf_action) == EOL_LF) { /* * CRLFs would not be restored by checkout: * check if we'd remove CRLFs @@ -136,7 +136,7 @@ static void check_safe_crlf(const char *path, enum action action, else /* i.e. SAFE_CRLF_FAIL */ die("CRLF would be replaced by LF in %s.", path); } - } else if (determine_output_conversion(action) == EOL_CRLF) { + } else if (output_eol(crlf_action) == EOL_CRLF) { /* * CRLFs would be added by checkout: * check if we have "naked" LFs @@ -188,18 +188,19 @@ static int has_cr_in_index(const char *path) } static int crlf_to_git(const char *path, const char *src, size_t len, - struct strbuf *buf, enum action action, enum safe_crlf checksafe) + struct strbuf *buf, + enum crlf_action crlf_action, enum safe_crlf checksafe) { struct text_stat stats; char *dst; - if (action == CRLF_BINARY || - (action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len) + if (crlf_action == CRLF_BINARY || + (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len) return 0; gather_stats(src, len, &stats); - if (action == CRLF_AUTO || action == CRLF_GUESS) { + if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) { /* * We're currently not going to even try to convert stuff * that has bare CR characters. Does anybody do that crazy @@ -214,7 +215,7 @@ 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 (crlf_action == CRLF_GUESS) { /* * If the file in the index has any CR in it, do not convert. * This is the new safer autocrlf handling. @@ -224,7 +225,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len, } } - check_safe_crlf(path, action, &stats, checksafe); + check_safe_crlf(path, crlf_action, &stats, checksafe); /* Optimization: No CR? Nothing to convert, regardless. */ if (!stats.cr) @@ -234,7 +235,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_AUTO || action == CRLF_GUESS) { + if (crlf_action == CRLF_AUTO || crlf_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 @@ -257,12 +258,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, enum action action) + struct strbuf *buf, enum crlf_action crlf_action) { char *to_free = NULL; struct text_stat stats; - if (!len || determine_output_conversion(action) != EOL_CRLF) + if (!len || output_eol(crlf_action) != EOL_CRLF) return 0; gather_stats(src, len, &stats); @@ -275,8 +276,8 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len, if (stats.lf == stats.crlf) return 0; - if (action == CRLF_AUTO || action == CRLF_GUESS) { - if (action == CRLF_GUESS) { + if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) { + if (crlf_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) @@ -474,30 +475,6 @@ static int read_convert_config(const char *var, const char *value, void *cb) return 0; } -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_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; - git_config(read_convert_config, NULL); - } - 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) { /* @@ -715,7 +692,7 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check) return !!ATTR_TRUE(value); } -static enum action determine_action(enum action text_attr, enum eol eol_attr) +static enum crlf_action input_crlf_action(enum crlf_action text_attr, enum eol eol_attr) { if (text_attr == CRLF_BINARY) return CRLF_BINARY; @@ -726,66 +703,83 @@ static enum action determine_action(enum action text_attr, enum eol eol_attr) return text_attr; } +struct conv_attrs { + struct convert_driver *drv; + enum crlf_action crlf_action; + enum eol eol_attr; + int ident; +}; + +static const char *conv_attr_name[] = { + "crlf", "ident", "filter", "eol", "text", +}; +#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name) + +static void convert_attrs(struct conv_attrs *ca, const char *path) +{ + int i; + static struct git_attr_check ccheck[NUM_CONV_ATTRS]; + + if (!ccheck[0].attr) { + for (i = 0; i < NUM_CONV_ATTRS; i++) + ccheck[i].attr = git_attr(conv_attr_name[i]); + user_convert_tail = &user_convert; + git_config(read_convert_config, NULL); + } + + if (!git_checkattr(path, NUM_CONV_ATTRS, ccheck)) { + ca->crlf_action = git_path_check_crlf(path, ccheck + 4); + if (ca->crlf_action == CRLF_GUESS) + ca->crlf_action = git_path_check_crlf(path, ccheck + 0); + ca->ident = git_path_check_ident(path, ccheck + 1); + ca->drv = git_path_check_convert(path, ccheck + 2); + ca->eol_attr = git_path_check_eol(path, ccheck + 3); + } else { + ca->drv = NULL; + ca->crlf_action = CRLF_GUESS; + ca->eol_attr = EOL_UNSET; + ca->ident = 0; + } +} + 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[5]; - enum action action = CRLF_GUESS; - enum eol eol_attr = EOL_UNSET; - int ident = 0, ret = 0; + int ret = 0; const char *filter = NULL; + struct conv_attrs ca; - setup_convert_check(check); - if (!git_checkattr(path, ARRAY_SIZE(check), check)) { - struct convert_driver *drv; - 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; - } + convert_attrs(&ca, path); + if (ca.drv) + filter = ca.drv->clean; ret |= apply_filter(path, src, len, dst, filter); if (ret) { src = dst->buf; len = dst->len; } - action = determine_action(action, eol_attr); - ret |= crlf_to_git(path, src, len, dst, action, checksafe); + ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr); + ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe); if (ret) { src = dst->buf; len = dst->len; } - return ret | ident_to_git(path, src, len, dst, ident); + return ret | ident_to_git(path, src, len, dst, ca.ident); } static int convert_to_working_tree_internal(const char *path, const char *src, size_t len, struct strbuf *dst, int normalizing) { - struct git_attr_check check[5]; - enum action action = CRLF_GUESS; - enum eol eol_attr = EOL_UNSET; - int ident = 0, ret = 0; + int ret = 0; const char *filter = NULL; + struct conv_attrs ca; - setup_convert_check(check); - if (!git_checkattr(path, ARRAY_SIZE(check), check)) { - struct convert_driver *drv; - 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; - } + convert_attrs(&ca, path); + if (ca.drv) + filter = ca.drv->smudge; - ret |= ident_to_worktree(path, src, len, dst, ident); + ret |= ident_to_worktree(path, src, len, dst, ca.ident); if (ret) { src = dst->buf; len = dst->len; @@ -795,8 +789,8 @@ static int convert_to_working_tree_internal(const char *path, const char *src, * is a smudge filter. The filter might expect CRLFs. */ if (filter || !normalizing) { - action = determine_action(action, eol_attr); - ret |= crlf_to_worktree(path, src, len, dst, action); + ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr); + ret |= crlf_to_worktree(path, src, len, dst, ca.crlf_action); if (ret) { src = dst->buf; len = dst->len; diff --git a/csum-file.c b/csum-file.c index 4d50cc5ce1..be49d5fcf9 100644 --- a/csum-file.c +++ b/csum-file.c @@ -116,7 +116,7 @@ struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp void crc32_begin(struct sha1file *f) { - f->crc32 = crc32(0, Z_NULL, 0); + f->crc32 = crc32(0, NULL, 0); f->do_crc = 1; } @@ -10,17 +10,18 @@ enum { A = GIT_ALPHA, D = GIT_DIGIT, G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ - R = GIT_REGEX_SPECIAL /* $, (, ), +, ., ^, {, | */ + R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ + P = GIT_PATHSPEC_MAGIC /* other non-alnum, except for ] and } */ }; unsigned char sane_ctype[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0, /* 0.. 15 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16.. 31 */ - S, 0, 0, 0, R, 0, 0, 0, R, R, G, R, 0, 0, R, 0, /* 32.. 47 */ - D, D, D, D, D, D, D, D, D, D, 0, 0, 0, 0, 0, G, /* 48.. 63 */ - 0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ - A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, 0, /* 80.. 95 */ - 0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ - A, A, A, A, A, A, A, A, A, A, A, R, R, 0, 0, 0, /* 112..127 */ + S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ + D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ + A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, P, /* 80.. 95 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ + A, A, A, A, A, A, A, A, A, A, A, R, R, 0, P, 0, /* 112..127 */ /* Nothing in the 128.. range */ }; @@ -660,7 +660,7 @@ static void check_dead_children(void) static char **cld_argv; static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen) { - struct child_process cld = { 0 }; + struct child_process cld = { NULL }; char addrbuf[300] = "REMOTE_ADDR=", portbuf[300]; char *env[] = { addrbuf, portbuf, NULL }; @@ -129,8 +129,9 @@ const char *show_date_relative(unsigned long time, int tz, } /* Give years and months for 5 years or so */ if (diff < 1825) { - unsigned long years = diff / 365; - unsigned long months = (diff % 365 + 15) / 30; + unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2); + unsigned long years = totalmonths / 12; + unsigned long months = totalmonths % 12; int n; n = snprintf(timebuf, timebuf_size, "%lu year%s", years, (years > 1 ? "s" : "")); diff --git a/diff-lib.c b/diff-lib.c index 392ce2bef0..3b5f2242a5 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -103,14 +103,17 @@ int run_diff_files(struct rev_info *revs, unsigned int option) unsigned dirty_submodule = 0; if (DIFF_OPT_TST(&revs->diffopt, QUICK) && - DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) + !revs->diffopt.filter && + DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; - if (!ce_path_match(ce, revs->prune_data)) + if (!ce_path_match(ce, &revs->prune_data)) continue; if (ce_stage(ce)) { struct combine_diff_path *dpath; + struct diff_filepair *pair; + unsigned int wt_mode = 0; int num_compare_stages = 0; size_t path_len; @@ -129,7 +132,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) changed = check_removed(ce, &st); if (!changed) - dpath->mode = ce_mode_from_stat(ce, st.st_mode); + wt_mode = ce_mode_from_stat(ce, st.st_mode); else { if (changed < 0) { perror(ce->name); @@ -137,7 +140,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } if (silent_on_removed) continue; + wt_mode = 0; } + dpath->mode = wt_mode; while (i < entries) { struct cache_entry *nce = active_cache[i]; @@ -183,7 +188,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option) * Show the diff for the 'ce' if we found the one * from the desired stage. */ - diff_unmerge(&revs->diffopt, ce->name, 0, null_sha1); + pair = diff_unmerge(&revs->diffopt, ce->name); + if (wt_mode) + pair->two->mode = wt_mode; if (ce_stage(ce) != diff_unmerged_stage) continue; } @@ -372,8 +379,9 @@ static void do_oneway_diff(struct unpack_trees_options *o, match_missing = !revs->ignore_merges; if (cached && idx && ce_stage(idx)) { - diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, - idx->sha1); + struct diff_filepair *pair; + pair = diff_unmerge(&revs->diffopt, idx->name); + fill_filespec(pair->one, idx->sha1, idx->ce_mode); return; } @@ -427,7 +435,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) if (tree == o->df_conflict_entry) tree = NULL; - if (ce_path_match(idx ? idx : tree, revs->prune_data)) + if (ce_path_match(idx ? idx : tree, &revs->prune_data)) do_oneway_diff(o, idx, tree); return 0; @@ -501,7 +509,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) active_nr = dst - active_cache; init_revisions(&revs, NULL); - revs.prune_data = opt->paths; + init_pathspec(&revs.prune_data, opt->pathspec.raw); tree = parse_tree_indirect(tree_sha1); if (!tree) die("bad tree object %s", sha1_to_hex(tree_sha1)); diff --git a/diff-no-index.c b/diff-no-index.c index ce9e783407..3a36144687 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -231,8 +231,9 @@ void diff_no_index(struct rev_info *revs, if (prefix) { int len = strlen(prefix); + const char *paths[3]; + memset(paths, 0, sizeof(paths)); - revs->diffopt.paths = xcalloc(2, sizeof(char *)); for (i = 0; i < 2; i++) { const char *p = argv[argc - 2 + i]; /* @@ -242,12 +243,12 @@ void diff_no_index(struct rev_info *revs, p = (strcmp(p, "-") ? xstrdup(prefix_filename(prefix, len, p)) : p); - revs->diffopt.paths[i] = p; + paths[i] = p; } + diff_tree_setup_paths(paths, &revs->diffopt); } else - revs->diffopt.paths = argv + argc - 2; - revs->diffopt.nr_paths = 2; + diff_tree_setup_paths(argv + argc - 2, &revs->diffopt); revs->diffopt.skip_stat_unmatch = 1; if (!revs->diffopt.output_format) revs->diffopt.output_format = DIFF_FORMAT_PATCH; @@ -259,8 +260,8 @@ void diff_no_index(struct rev_info *revs, if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); - if (queue_diff(&revs->diffopt, revs->diffopt.paths[0], - revs->diffopt.paths[1])) + if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0], + revs->diffopt.pathspec.raw[1])) exit(1); diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/"); diffcore_std(&revs->diffopt); @@ -23,7 +23,7 @@ #endif static int diff_detect_rename_default; -static int diff_rename_limit_default = 200; +static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; int diff_use_color_default = -1; static const char *diff_word_regex_cfg; @@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static int diff_no_prefix; +static int diff_dirstat_permille_default = 30; static struct diff_options default_diff_options; static char diff_colors[][COLOR_MAXLEN] = { @@ -66,6 +67,58 @@ static int parse_diff_color_slot(const char *var, int ofs) return -1; } +static int parse_dirstat_params(struct diff_options *options, const char *params, + struct strbuf *errmsg) +{ + const char *p = params; + int p_len, ret = 0; + + while (*p) { + p_len = strchrnul(p, ',') - p; + if (!memcmp(p, "changes", p_len)) { + DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); + DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); + } else if (!memcmp(p, "lines", p_len)) { + DIFF_OPT_SET(options, DIRSTAT_BY_LINE); + DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); + } else if (!memcmp(p, "files", p_len)) { + DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); + DIFF_OPT_SET(options, DIRSTAT_BY_FILE); + } else if (!memcmp(p, "noncumulative", p_len)) { + DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE); + } else if (!memcmp(p, "cumulative", p_len)) { + DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); + } else if (isdigit(*p)) { + char *end; + int permille = strtoul(p, &end, 10) * 10; + if (*end == '.' && isdigit(*++end)) { + /* only use first digit */ + permille += *end - '0'; + /* .. and ignore any further digits */ + while (isdigit(*++end)) + ; /* nothing */ + } + if (end - p == p_len) + options->dirstat_permille = permille; + else { + strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%.*s'\n"), + p_len, p); + ret++; + } + } else { + strbuf_addf(errmsg, _(" Unknown dirstat parameter '%.*s'\n"), + p_len, p); + ret++; + } + + p += p_len; + + if (*p) + p++; /* more parameters, swallow separator */ + } + return ret; +} + static int git_config_rename(const char *var, const char *value) { if (!value) @@ -145,6 +198,17 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "diff.dirstat")) { + struct strbuf errmsg = STRBUF_INIT; + default_diff_options.dirstat_permille = diff_dirstat_permille_default; + if (parse_dirstat_params(&default_diff_options, value, &errmsg)) + warning(_("Found errors in 'diff.dirstat' config variable:\n%s"), + errmsg.buf); + strbuf_release(&errmsg); + diff_dirstat_permille_default = default_diff_options.dirstat_permille; + return 0; + } + if (!prefixcmp(var, "submodule.")) return parse_submodule_config_option(var, value); @@ -245,6 +309,15 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) return 0; } +/* like fill_mmfile, but only for size, so we can avoid retrieving blob */ +static unsigned long diff_filespec_size(struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) + return 0; + diff_populate_filespec(one, 1); + return one->size; +} + static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) { char *ptr = mf->ptr; @@ -572,11 +645,14 @@ static void emit_rewrite_diff(const char *name_a, 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); + if (!o->irreversible_delete) + print_line_count(o->file, lc_a); + else + fprintf(o->file, "?,?"); fprintf(o->file, " +"); print_line_count(o->file, lc_b); fprintf(o->file, " @@%s\n", reset); - if (lc_a) + if (lc_a && !o->irreversible_delete) emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) emit_rewrite_lines(&ecbdata, '+', data_two, size_two); @@ -606,22 +682,20 @@ static void diff_words_append(char *line, unsigned long len, buffer->text.ptr[buffer->text.size] = '\0'; } -struct diff_words_style_elem -{ +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 -{ +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[] = { +static struct diff_words_style diff_words_styles[] = { { DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" }, { DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" }, { DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" } @@ -1043,8 +1117,16 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) 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); + /* + * Skip the prefix character, if any. With + * diff_suppress_blank_empty, there may be + * none. + */ + if (line[0] != '\n') { + line++; + len--; + } + emit_line(ecbdata->opt, plain, reset, line, len); } return; } @@ -1235,7 +1317,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) uintmax_t max_change = 0, max_len = 0; int total_files = data->nr; int width, name_width; - const char *reset, *set, *add_c, *del_c; + const char *reset, *add_c, *del_c; const char *line_prefix = ""; struct strbuf *msg = NULL; @@ -1262,7 +1344,6 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) /* Find the longest filename and max number of changes */ reset = diff_get_color_opt(options, DIFF_RESET); - set = diff_get_color_opt(options, DIFF_PLAIN); add_c = diff_get_color_opt(options, DIFF_FILE_NEW); del_c = diff_get_color_opt(options, DIFF_FILE_OLD); @@ -1446,7 +1527,7 @@ struct dirstat_file { struct dirstat_dir { struct dirstat_file *files; - int alloc, nr, percent, cumulative; + int alloc, nr, permille, cumulative; }; static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, @@ -1493,12 +1574,11 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, * under this directory (sources == 1). */ if (baselen && sources != 1) { - int permille = this_dir * 1000 / changed; - if (permille) { - int percent = permille / 10; - if (percent >= dir->percent) { + if (this_dir) { + int permille = this_dir * 1000 / changed; + if (permille >= dir->permille) { fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix, - percent, permille % 10, baselen, base); + permille / 10, permille % 10, baselen, base); if (!dir->cumulative) return 0; } @@ -1524,7 +1604,7 @@ static void show_dirstat(struct diff_options *options) dir.files = NULL; dir.alloc = 0; dir.nr = 0; - dir.percent = options->dirstat_percent; + dir.permille = options->dirstat_permille; dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE); changed = 0; @@ -1532,8 +1612,36 @@ static void show_dirstat(struct diff_options *options) struct diff_filepair *p = q->queue[i]; const char *name; unsigned long copied, added, damage; + int content_changed; + + name = p->two->path ? p->two->path : p->one->path; + + if (p->one->sha1_valid && p->two->sha1_valid) + content_changed = hashcmp(p->one->sha1, p->two->sha1); + else + content_changed = 1; - name = p->one->path ? p->one->path : p->two->path; + if (!content_changed) { + /* + * The SHA1 has not changed, so pre-/post-content is + * identical. We can therefore skip looking at the + * file contents altogether. + */ + damage = 0; + goto found_damage; + } + + if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE)) { + /* + * In --dirstat-by-file mode, we don't really need to + * look at the actual file contents at all. + * The fact that the SHA1 changed is enough for us to + * add this file to the list of results + * (with each file contributing equal damage). + */ + damage = 1; + goto found_damage; + } if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) { diff_populate_filespec(p->one, 0); @@ -1557,14 +1665,18 @@ static void show_dirstat(struct diff_options *options) /* * Original minus copied is the removed material, * added is the new material. They are both damages - * made to the preimage. In --dirstat-by-file mode, count - * damaged files, not damaged lines. This is done by - * counting only a single damaged line per file. + * made to the preimage. + * If the resulting damage is zero, we know that + * diffcore_count_changes() considers the two entries to + * be identical, but since content_changed is true, we + * know that there must have been _some_ kind of change, + * so we force all entries to have damage > 0. */ damage = (p->one->size - copied) + added; - if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE) && damage > 0) + if (!damage) damage = 1; +found_damage: ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc); dir.files[dir.nr].name = name; dir.files[dir.nr].changed = damage; @@ -1581,6 +1693,50 @@ static void show_dirstat(struct diff_options *options) gather_dirstat(options, &dir, changed, "", 0); } +static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *options) +{ + int i; + unsigned long changed; + struct dirstat_dir dir; + + if (data->nr == 0) + return; + + dir.files = NULL; + dir.alloc = 0; + dir.nr = 0; + dir.permille = options->dirstat_permille; + dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE); + + changed = 0; + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + unsigned long damage = file->added + file->deleted; + if (file->is_binary) + /* + * binary files counts bytes, not lines. Must find some + * way to normalize binary bytes vs. textual lines. + * The following heuristic assumes that there are 64 + * bytes per "line". + * This is stupid and ugly, but very cheap... + */ + damage = (damage + 63) / 64; + ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc); + dir.files[dir.nr].name = file->name; + dir.files[dir.nr].changed = damage; + changed += damage; + dir.nr++; + } + + /* This can happen even with many files, if everything was renames */ + if (!changed) + return; + + /* Show all directories with more than x% of the changes */ + qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare); + gather_dirstat(options, &dir, changed, "", 0); +} + static void free_diffstat_info(struct diffstat_t *diffstat) { int i; @@ -1943,7 +2099,11 @@ static void builtin_diff(const char *name_a, } } - if (!DIFF_OPT_TST(o, TEXT) && + if (o->irreversible_delete && lbl[1][0] == '/') { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); + goto free_ab_and_return; + } else if (!DIFF_OPT_TST(o, TEXT) && ( (!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) @@ -1963,8 +2123,7 @@ static void builtin_diff(const char *name_a, fprintf(o->file, "%sBinary files %s and %s differ\n", line_prefix, lbl[0], lbl[1]); o->found_changes = 1; - } - else { + } else { /* Crazy xdl interfaces.. */ const char *diffopts = getenv("GIT_DIFF_OPTS"); xpparam_t xpp; @@ -2079,25 +2238,28 @@ static void builtin_diffstat(const char *name_a, const char *name_b, data->is_unmerged = 1; return; } - if (complete_rewrite) { + + if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) { + data->is_binary = 1; + data->added = diff_filespec_size(two); + data->deleted = diff_filespec_size(one); + } + + else if (complete_rewrite) { diff_populate_filespec(one, 0); diff_populate_filespec(two, 0); data->deleted = count_lines(one->data, one->size); data->added = count_lines(two->data, two->size); - goto free_and_return; } - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) - die("unable to read files to diff"); - if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) { - data->is_binary = 1; - data->added = mf2.size; - data->deleted = mf1.size; - } else { + else { /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; + if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + die("unable to read files to diff"); + memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = o->xdl_opts; @@ -2105,7 +2267,6 @@ static void builtin_diffstat(const char *name_a, const char *name_b, &xpp, &xecfg); } - free_and_return: diff_free_filespec_data(one); diff_free_filespec_data(two); } @@ -2845,7 +3006,7 @@ void diff_setup(struct diff_options *options) options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; - options->dirstat_percent = 3; + options->dirstat_permille = diff_dirstat_permille_default; options->context = 3; options->change = diff_change; @@ -3103,6 +3264,21 @@ static int stat_opt(struct diff_options *options, const char **av) return argcount; } +static int parse_dirstat_opt(struct diff_options *options, const char *params) +{ + struct strbuf errmsg = STRBUF_INIT; + if (parse_dirstat_params(options, params, &errmsg)) + die(_("Failed to parse --dirstat/-X option parameter:\n%s"), + errmsg.buf); + strbuf_release(&errmsg); + /* + * The caller knows a dirstat-related option is given from the command + * line; allow it to say "return this_function();" + */ + options->output_format |= DIFF_FORMAT_DIRSTAT; + return 1; +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac) { const char *arg = av[0]; @@ -3122,15 +3298,19 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; - else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent)) - options->output_format |= DIFF_FORMAT_DIRSTAT; - else if (!strcmp(arg, "--cumulative")) { - options->output_format |= DIFF_FORMAT_DIRSTAT; - DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); - } else if (opt_arg(arg, 0, "dirstat-by-file", - &options->dirstat_percent)) { - options->output_format |= DIFF_FORMAT_DIRSTAT; - DIFF_OPT_SET(options, DIRSTAT_BY_FILE); + else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat")) + return parse_dirstat_opt(options, ""); + else if (!prefixcmp(arg, "-X")) + return parse_dirstat_opt(options, arg + 2); + else if (!prefixcmp(arg, "--dirstat=")) + return parse_dirstat_opt(options, arg + 10); + else if (!strcmp(arg, "--cumulative")) + return parse_dirstat_opt(options, "cumulative"); + else if (!strcmp(arg, "--dirstat-by-file")) + return parse_dirstat_opt(options, "files"); + else if (!prefixcmp(arg, "--dirstat-by-file=")) { + parse_dirstat_opt(options, "files"); + return parse_dirstat_opt(options, arg + 18); } else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; @@ -3160,6 +3340,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) return error("invalid argument to -M: %s", arg+2); options->detect_rename = DIFF_DETECT_RENAME; } + else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) { + options->irreversible_delete = 1; + } else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") || !strcmp(arg, "--find-copies")) { if (options->detect_rename == DIFF_DETECT_COPY) @@ -3947,11 +4130,34 @@ static int is_summary_empty(const struct diff_queue_struct *q) return 1; } +static const char rename_limit_warning[] = +"inexact rename detection was skipped due to too many files."; + +static const char degrade_cc_to_c_warning[] = +"only found copies from modified paths due to too many files."; + +static const char rename_limit_advice[] = +"you may want to set your %s variable to at least " +"%d and retry the command."; + +void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) +{ + if (degraded_cc) + warning(degrade_cc_to_c_warning); + else if (needed) + warning(rename_limit_warning); + else + return; + if (0 < needed && needed < 32767) + warning(rename_limit_advice, varname, needed); +} + void diff_flush(struct diff_options *options) { struct diff_queue_struct *q = &diff_queued_diff; int i, output_format = options->output_format; int separator = 0; + int dirstat_by_line = 0; /* * Order: raw, stat, summary, patch @@ -3972,7 +4178,11 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { + if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE)) + dirstat_by_line = 1; + + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) || + dirstat_by_line) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -3987,10 +4197,12 @@ void diff_flush(struct diff_options *options) show_stats(&diffstat, options); if (output_format & DIFF_FORMAT_SHORTSTAT) show_shortstats(&diffstat, options); + if (output_format & DIFF_FORMAT_DIRSTAT) + show_dirstat_by_line(&diffstat, options); free_diffstat_info(&diffstat); separator++; } - if (output_format & DIFF_FORMAT_DIRSTAT) + if ((output_format & DIFF_FORMAT_DIRSTAT) && !dirstat_by_line) show_dirstat(options); if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) { @@ -4228,6 +4440,10 @@ void diffcore_std(struct diff_options *options) int diff_result_code(struct diff_options *opt, int status) { int result = 0; + + diff_warn_rename_limit("diff.renamelimit", + opt->needed_rename_limit, + opt->degraded_cc_to_c); if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && !(opt->output_format & DIFF_FORMAT_CHECKDIFF)) return status; @@ -4341,20 +4557,20 @@ void diff_change(struct diff_options *options, DIFF_OPT_SET(options, HAS_CHANGES); } -void diff_unmerge(struct diff_options *options, - const char *path, - unsigned mode, const unsigned char *sha1) +struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path) { + struct diff_filepair *pair; struct diff_filespec *one, *two; if (options->prefix && strncmp(path, options->prefix, options->prefix_length)) - return; + return NULL; one = alloc_filespec(path); two = alloc_filespec(path); - fill_filespec(one, sha1, mode); - diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1; + pair = diff_queue(&diff_queued_diff, one, two); + pair->is_unmerged = 1; + return pair; } static char *run_textconv(const char *pgm, struct diff_filespec *spec, @@ -78,6 +78,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) #define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27) +#define DIFF_OPT_DIRSTAT_BY_LINE (1 << 28) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) @@ -104,14 +105,17 @@ struct diff_options { int interhunkcontext; int break_opt; int detect_rename; + int irreversible_delete; int skip_stat_unmatch; int line_termination; int output_format; int pickaxe_opts; int rename_score; int rename_limit; - int warn_on_too_large_rename; - int dirstat_percent; + int needed_rename_limit; + int degraded_cc_to_c; + int show_rename_progress; + int dirstat_permille; int setup; int abbrev; const char *prefix; @@ -133,9 +137,7 @@ struct diff_options { FILE *file; int close_file; - int nr_paths; - const char **paths; - int *pathlens; + struct pathspec pathspec; change_fn_t change; add_remove_fn_t add_remove; diff_format_fn_t format_callback; @@ -209,10 +211,7 @@ extern void diff_change(struct diff_options *, const char *fullpath, unsigned dirty_submodule1, unsigned dirty_submodule2); -extern void diff_unmerge(struct diff_options *, - const char *path, - unsigned mode, - const unsigned char *sha1); +extern struct diff_filepair *diff_unmerge(struct diff_options *, const char *path); #define DIFF_SETUP_REVERSE 1 #define DIFF_SETUP_USE_CACHE 2 @@ -274,6 +273,7 @@ extern void diffcore_fix_diff_index(struct diff_options *); extern int diff_queue_is_empty(void); extern void diff_flush(struct diff_options*); +extern void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc); /* diff-raw status letters */ #define DIFF_STATUS_ADDED 'A' diff --git a/diffcore-rename.c b/diffcore-rename.c index df41be56de..f639601c76 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -5,6 +5,7 @@ #include "diff.h" #include "diffcore.h" #include "hash.h" +#include "progress.h" /* Table of rename/copy destinations */ @@ -54,22 +55,23 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two, /* Table of rename/copy src files */ static struct diff_rename_src { - struct diff_filespec *one; + struct diff_filepair *p; unsigned short score; /* to remember the break score */ } *rename_src; static int rename_src_nr, rename_src_alloc; -static struct diff_rename_src *register_rename_src(struct diff_filespec *one, - unsigned short score) +static struct diff_rename_src *register_rename_src(struct diff_filepair *p) { int first, last; + struct diff_filespec *one = p->one; + unsigned short score = p->score; first = 0; last = rename_src_nr; while (last > first) { int next = (last + first) >> 1; struct diff_rename_src *src = &(rename_src[next]); - int cmp = strcmp(one->path, src->one->path); + int cmp = strcmp(one->path, src->p->one->path); if (!cmp) return src; if (cmp < 0) { @@ -89,7 +91,7 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one, if (first < rename_src_nr) memmove(rename_src + first + 1, rename_src + first, (rename_src_nr - first - 1) * sizeof(*rename_src)); - rename_src[first].one = one; + rename_src[first].p = p; rename_src[first].score = score; return &(rename_src[first]); } @@ -170,7 +172,7 @@ static int estimate_similarity(struct diff_filespec *src, * and the final score computation below would not have a * divide-by-zero issue. */ - if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) + if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) return 0; if (!src->cnt_data && diff_populate_filespec(src, 0)) @@ -204,7 +206,7 @@ static void record_rename_pair(int dst_index, int src_index, int score) if (rename_dst[dst_index].pair) die("internal error: dst already matched."); - src = rename_src[src_index].one; + src = rename_src[src_index].p->one; src->rename_used++; src->count++; @@ -247,7 +249,8 @@ struct file_similarity { }; static int find_identical_files(struct file_similarity *src, - struct file_similarity *dst) + struct file_similarity *dst, + struct diff_options *options) { int renames = 0; @@ -277,6 +280,8 @@ static int find_identical_files(struct file_similarity *src, } /* Give higher scores to sources that haven't been used already */ score = !source->rename_used; + if (source->rename_used && options->detect_rename != DIFF_DETECT_COPY) + continue; score += basename_same(source, target); if (score > best_score) { best = p; @@ -306,11 +311,12 @@ static void free_similarity_list(struct file_similarity *p) } } -static int find_same_files(void *ptr) +static int find_same_files(void *ptr, void *data) { int ret; struct file_similarity *p = ptr; struct file_similarity *src = NULL, *dst = NULL; + struct diff_options *options = data; /* Split the hash list up into sources and destinations */ do { @@ -329,7 +335,7 @@ static int find_same_files(void *ptr) * If we have both sources *and* destinations, see if * we can match them up */ - ret = (src && dst) ? find_identical_files(src, dst) : 0; + ret = (src && dst) ? find_identical_files(src, dst, options) : 0; /* Free the hashes and return the number of renames found */ free_similarity_list(src); @@ -377,20 +383,20 @@ static void insert_file_table(struct hash_table *table, int src_dst, int index, * and then during the second round we try to match * cache-dirty entries as well. */ -static int find_exact_renames(void) +static int find_exact_renames(struct diff_options *options) { int i; struct hash_table file_table; init_hash(&file_table); for (i = 0; i < rename_src_nr; i++) - insert_file_table(&file_table, -1, i, rename_src[i].one); + insert_file_table(&file_table, -1, i, rename_src[i].p->one); for (i = 0; i < rename_dst_nr; i++) insert_file_table(&file_table, 1, i, rename_dst[i].two); /* Find the renames */ - i = for_each_hash(&file_table, find_same_files); + i = for_each_hash(&file_table, find_same_files, options); /* .. and free the hash data structure */ free_hash(&file_table); @@ -414,16 +420,86 @@ static void record_if_better(struct diff_score m[], struct diff_score *o) m[worst] = *o; } +/* + * Returns: + * 0 if we are under the limit; + * 1 if we need to disable inexact rename detection; + * 2 if we would be under the limit if we were given -C instead of -C -C. + */ +static int too_many_rename_candidates(int num_create, + struct diff_options *options) +{ + int rename_limit = options->rename_limit; + int num_src = rename_src_nr; + int i; + + options->needed_rename_limit = 0; + + /* + * This basically does a test for the rename matrix not + * growing larger than a "rename_limit" square matrix, ie: + * + * num_create * num_src > rename_limit * rename_limit + * + * but handles the potential overflow case specially (and we + * assume at least 32-bit integers) + */ + if (rename_limit <= 0 || rename_limit > 32767) + rename_limit = 32767; + if ((num_create <= rename_limit || num_src <= rename_limit) && + (num_create * num_src <= rename_limit * rename_limit)) + return 0; + + options->needed_rename_limit = + num_src > num_create ? num_src : num_create; + + /* Are we running under -C -C? */ + if (!DIFF_OPT_TST(options, FIND_COPIES_HARDER)) + return 1; + + /* Would we bust the limit if we were running under -C? */ + for (num_src = i = 0; i < rename_src_nr; i++) { + if (diff_unmodified_pair(rename_src[i].p)) + continue; + num_src++; + } + if ((num_create <= rename_limit || num_src <= rename_limit) && + (num_create * num_src <= rename_limit * rename_limit)) + return 2; + return 1; +} + +static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, int copies) +{ + int count = 0, i; + + for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) { + struct diff_rename_dst *dst; + + if ((mx[i].dst < 0) || + (mx[i].score < minimum_score)) + break; /* there is no more usable pair. */ + dst = &rename_dst[mx[i].dst]; + if (dst->pair) + continue; /* already done, either exact or fuzzy. */ + if (!copies && rename_src[mx[i].src].p->one->rename_used) + continue; + record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); + count++; + } + return count; +} + void diffcore_rename(struct diff_options *options) { int detect_rename = options->detect_rename; int minimum_score = options->rename_score; - int rename_limit = options->rename_limit; struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; struct diff_score *mx; - int i, j, rename_count; - int num_create, num_src, dst_cnt; + int i, j, rename_count, skip_unmodified = 0; + int num_create, dst_cnt; + struct progress *progress = NULL; if (!minimum_score) minimum_score = DEFAULT_RENAME_SCORE; @@ -439,7 +515,7 @@ void diffcore_rename(struct diff_options *options) else locate_rename_dst(p->two, 1); } - else if (!DIFF_FILE_VALID(p->two)) { + else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) { /* * If the source is a broken "delete", and * they did not really want to get broken, @@ -449,7 +525,7 @@ void diffcore_rename(struct diff_options *options) */ if (p->broken_pair && !p->score) p->one->rename_used++; - register_rename_src(p->one, p->score); + register_rename_src(p); } else if (detect_rename == DIFF_DETECT_COPY) { /* @@ -457,7 +533,7 @@ void diffcore_rename(struct diff_options *options) * one, to indicate ourselves as a user. */ p->one->rename_used++; - register_rename_src(p->one, p->score); + register_rename_src(p); } } if (rename_dst_nr == 0 || rename_src_nr == 0) @@ -467,7 +543,7 @@ void diffcore_rename(struct diff_options *options) * We really want to cull the candidates list early * with cheap tests in order to avoid doing deltas. */ - rename_count = find_exact_renames(); + rename_count = find_exact_renames(options); /* Did we only want exact renames? */ if (minimum_score == MAX_SCORE) @@ -478,28 +554,26 @@ void diffcore_rename(struct diff_options *options) * files still remain as options for rename/copies!) */ num_create = (rename_dst_nr - rename_count); - num_src = rename_src_nr; /* All done? */ if (!num_create) goto cleanup; - /* - * This basically does a test for the rename matrix not - * growing larger than a "rename_limit" square matrix, ie: - * - * num_create * num_src > rename_limit * rename_limit - * - * but handles the potential overflow case specially (and we - * assume at least 32-bit integers) - */ - if (rename_limit <= 0 || rename_limit > 32767) - rename_limit = 32767; - if ((num_create > rename_limit && num_src > rename_limit) || - (num_create * num_src > rename_limit * rename_limit)) { - if (options->warn_on_too_large_rename) - warning("too many files (created: %d deleted: %d), skipping inexact rename detection", num_create, num_src); + switch (too_many_rename_candidates(num_create, options)) { + case 1: goto cleanup; + case 2: + options->degraded_cc_to_c = 1; + skip_unmodified = 1; + break; + default: + break; + } + + if (options->show_rename_progress) { + progress = start_progress_delay( + "Performing inexact rename detection", + rename_dst_nr * rename_src_nr, 50, 1); } mx = xcalloc(num_create * NUM_CANDIDATE_PER_DST, sizeof(*mx)); @@ -515,8 +589,13 @@ void diffcore_rename(struct diff_options *options) m[j].dst = -1; for (j = 0; j < rename_src_nr; j++) { - struct diff_filespec *one = rename_src[j].one; + struct diff_filespec *one = rename_src[j].p->one; struct diff_score this_src; + + if (skip_unmodified && + diff_unmodified_pair(rename_src[j].p)) + continue; + this_src.score = estimate_similarity(one, two, minimum_score); this_src.name_score = basename_same(one, two); @@ -531,38 +610,16 @@ void diffcore_rename(struct diff_options *options) diff_free_filespec_blob(two); } dst_cnt++; + display_progress(progress, (i+1)*rename_src_nr); } + stop_progress(&progress); /* cost matrix sorted by most to least similar pair */ qsort(mx, dst_cnt * NUM_CANDIDATE_PER_DST, sizeof(*mx), score_compare); - for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) { - struct diff_rename_dst *dst; - - if ((mx[i].dst < 0) || - (mx[i].score < minimum_score)) - break; /* there is no more usable pair. */ - dst = &rename_dst[mx[i].dst]; - if (dst->pair) - continue; /* already done, either exact or fuzzy. */ - if (rename_src[mx[i].src].one->rename_used) - continue; - record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); - rename_count++; - } - - for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) { - struct diff_rename_dst *dst; - - if ((mx[i].dst < 0) || - (mx[i].score < minimum_score)) - break; /* there is no more usable pair. */ - dst = &rename_dst[mx[i].dst]; - if (dst->pair) - continue; /* already done, either exact or fuzzy. */ - record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); - rename_count++; - } + rename_count += find_renames(mx, dst_cnt, minimum_score, 0); + if (detect_rename == DIFF_DETECT_COPY) + rename_count += find_renames(mx, dst_cnt, minimum_score, 1); free(mx); cleanup: @@ -574,7 +631,10 @@ void diffcore_rename(struct diff_options *options) struct diff_filepair *p = q->queue[i]; struct diff_filepair *pair_to_free = NULL; - if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) { + if (DIFF_PAIR_UNMERGED(p)) { + diff_q(&outq, p); + } + else if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) { /* * Creation * @@ -87,6 +87,21 @@ int fill_directory(struct dir_struct *dir, const char **pathspec) return len; } +int within_depth(const char *name, int namelen, + int depth, int max_depth) +{ + const char *cp = name, *cpe = name + namelen; + + while (cp < cpe) { + if (*cp++ != '/') + continue; + depth++; + if (depth > max_depth) + return 0; + } + return 1; +} + /* * Does 'match' match the given name? * A match is found if @@ -184,6 +199,95 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, return retval; } +/* + * Does 'match' match the given name? + * A match is found if + * + * (1) the 'match' string is leading directory of 'name', or + * (2) the 'match' string is a wildcard and matches 'name', or + * (3) the 'match' string is exactly the same as 'name'. + * + * and the return value tells which case it was. + * + * It returns 0 when there is no match. + */ +static int match_pathspec_item(const struct pathspec_item *item, int prefix, + const char *name, int namelen) +{ + /* name/namelen has prefix cut off by caller */ + const char *match = item->match + prefix; + int matchlen = item->len - prefix; + + /* If the match was just the prefix, we matched */ + if (!*match) + return MATCHED_RECURSIVELY; + + if (matchlen <= namelen && !strncmp(match, name, matchlen)) { + if (matchlen == namelen) + return MATCHED_EXACTLY; + + if (match[matchlen-1] == '/' || name[matchlen] == '/') + return MATCHED_RECURSIVELY; + } + + if (item->use_wildcard && !fnmatch(match, name, 0)) + return MATCHED_FNMATCH; + + return 0; +} + +/* + * Given a name and a list of pathspecs, see if the name matches + * any of the pathspecs. The caller is also interested in seeing + * all pathspec matches some names it calls this function with + * (otherwise the user could have mistyped the unmatched pathspec), + * and a mark is left in seen[] array for pathspec element that + * actually matched anything. + */ +int match_pathspec_depth(const struct pathspec *ps, + const char *name, int namelen, + int prefix, char *seen) +{ + int i, retval = 0; + + if (!ps->nr) { + if (!ps->recursive || ps->max_depth == -1) + return MATCHED_RECURSIVELY; + + if (within_depth(name, namelen, 0, ps->max_depth)) + return MATCHED_EXACTLY; + else + return 0; + } + + name += prefix; + namelen -= prefix; + + for (i = ps->nr - 1; i >= 0; i--) { + int how; + if (seen && seen[i] == MATCHED_EXACTLY) + continue; + how = match_pathspec_item(ps->items+i, prefix, name, namelen); + if (ps->recursive && ps->max_depth != -1 && + how && how != MATCHED_FNMATCH) { + int len = ps->items[i].len; + if (name[len] == '/') + len++; + if (within_depth(name+len, namelen-len, 0, ps->max_depth)) + how = MATCHED_EXACTLY; + else + how = 0; + } + if (how) { + if (retval < how) + retval = how; + if (seen && seen[i] < how) + seen[i] = how; + } + } + return retval; +} + static int no_wildcard(const char *string) { return string[strcspn(string, "*?[{\\")] == '\0'; @@ -1001,57 +1105,45 @@ int file_exists(const char *f) } /* - * get_relative_cwd() gets the prefix of the current working directory - * relative to 'dir'. If we are not inside 'dir', it returns NULL. - * - * As a convenience, it also returns NULL if 'dir' is already NULL. The - * reason for this behaviour is that it is natural for functions returning - * directory names to return NULL to say "this directory does not exist" - * or "this directory is invalid". These cases are usually handled the - * same as if the cwd is not inside 'dir' at all, so get_relative_cwd() - * returns NULL for both of them. - * - * Most notably, get_relative_cwd(buffer, size, get_git_work_tree()) - * unifies the handling of "outside work tree" with "no work tree at all". + * Given two normalized paths (a trailing slash is ok), if subdir is + * outside dir, return -1. Otherwise return the offset in subdir that + * can be used as relative path to dir. */ -char *get_relative_cwd(char *buffer, int size, const char *dir) +int dir_inside_of(const char *subdir, const char *dir) { - char *cwd = buffer; + int offset = 0; - if (!dir) - return NULL; - if (!getcwd(buffer, size)) - die_errno("can't find the current directory"); + assert(dir && subdir && *dir && *subdir); - if (!is_absolute_path(dir)) - dir = make_absolute_path(dir); - - while (*dir && *dir == *cwd) { + while (*dir && *subdir && *dir == *subdir) { dir++; - cwd++; - } - if (*dir) - return NULL; - switch (*cwd) { - case '\0': - return cwd; - case '/': - return cwd + 1; - default: - /* - * dir can end with a path separator when it's root - * directory. Return proper prefix in that case. - */ - if (dir[-1] == '/') - return cwd; - return NULL; + subdir++; + offset++; } + + /* hel[p]/me vs hel[l]/yeah */ + if (*dir && *subdir) + return -1; + + if (!*subdir) + return !*dir ? offset : -1; /* same dir */ + + /* foo/[b]ar vs foo/[] */ + if (is_dir_sep(dir[-1])) + return is_dir_sep(subdir[-1]) ? offset : -1; + + /* foo[/]bar vs foo[] */ + return is_dir_sep(*subdir) ? offset + 1 : -1; } int is_inside_dir(const char *dir) { - char buffer[PATH_MAX]; - return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL; + char cwd[PATH_MAX]; + if (!dir) + return 0; + if (!getcwd(cwd, sizeof(cwd))) + die_errno("can't find the current directory"); + return dir_inside_of(cwd, dir) >= 0; } int is_empty_dir(const char *path) @@ -1088,7 +1180,7 @@ int remove_dir_recursively(struct strbuf *path, int flag) dir = opendir(path->buf); if (!dir) - return -1; + return rmdir(path->buf); if (path->buf[original_len - 1] != '/') strbuf_addch(path, '/'); @@ -1151,3 +1243,50 @@ int remove_path(const char *name) return 0; } +static int pathspec_item_cmp(const void *a_, const void *b_) +{ + struct pathspec_item *a, *b; + + a = (struct pathspec_item *)a_; + b = (struct pathspec_item *)b_; + return strcmp(a->match, b->match); +} + +int init_pathspec(struct pathspec *pathspec, const char **paths) +{ + const char **p = paths; + int i; + + memset(pathspec, 0, sizeof(*pathspec)); + if (!p) + return 0; + while (*p) + p++; + pathspec->raw = paths; + pathspec->nr = p - paths; + if (!pathspec->nr) + return 0; + + pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr); + for (i = 0; i < pathspec->nr; i++) { + struct pathspec_item *item = pathspec->items+i; + const char *path = paths[i]; + + item->match = path; + item->len = strlen(path); + item->use_wildcard = !no_wildcard(path); + if (item->use_wildcard) + pathspec->has_wildcard = 1; + } + + qsort(pathspec->items, pathspec->nr, + sizeof(struct pathspec_item), pathspec_item_cmp); + + return 0; +} + +void free_pathspec(struct pathspec *pathspec) +{ + free(pathspec->items); + pathspec->items = NULL; +} @@ -65,6 +65,10 @@ struct dir_struct { #define MATCHED_FNMATCH 2 #define MATCHED_EXACTLY 3 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); +extern int match_pathspec_depth(const struct pathspec *pathspec, + const char *name, int namelen, + int prefix, char *seen); +extern int within_depth(const char *name, int namelen, int depth, int max_depth); extern int fill_directory(struct dir_struct *dir, const char **pathspec); extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec); @@ -81,8 +85,8 @@ extern void add_exclude(const char *string, const char *base, extern void free_excludes(struct exclude_list *el); extern int file_exists(const char *); -extern char *get_relative_cwd(char *buffer, int size, const char *dir); extern int is_inside_dir(const char *dir); +extern int dir_inside_of(const char *subdir, const char *dir); static inline int is_dot_or_dotdot(const char *name) { diff --git a/environment.c b/environment.c index 9564475f42..94d58fd244 100644 --- a/environment.c +++ b/environment.c @@ -15,13 +15,13 @@ int user_ident_explicitly_given; int trust_executable_bit = 1; int trust_ctime = 1; int has_symlinks = 1; +int minimum_abbrev = 4, default_abbrev = 7; int ignore_case; int assume_unchanged; int prefer_symlink_refs; int is_bare_repository_cfg = -1; /* unspecified */ int log_all_ref_updates = -1; /* unspecified */ int warn_ambiguous_refs = 1; -int unique_abbrev_extra_length; int repository_format_version; const char *git_commit_encoding; const char *git_log_output_encoding; @@ -35,14 +35,15 @@ int fsync_object_files; size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 16 * 1024 * 1024; +unsigned long big_file_threshold = 512 * 1024 * 1024; const char *pager_program; int pager_use_color = 1; const char *editor_program; const char *askpass_program; const char *excludes_file; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; -int read_replace_refs = 1; -enum eol eol = EOL_UNSET; +int read_replace_refs = 1; /* NEEDSWORK: rename to use_replace_refs */ +enum eol core_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; @@ -140,7 +141,7 @@ static int git_work_tree_initialized; void set_git_work_tree(const char *new_work_tree) { if (git_work_tree_initialized) { - new_work_tree = make_absolute_path(new_work_tree); + new_work_tree = real_path(new_work_tree); if (strcmp(new_work_tree, work_tree)) die("internal error: work tree has already been set\n" "Current worktree: %s\nNew worktree: %s", @@ -148,7 +149,7 @@ void set_git_work_tree(const char *new_work_tree) return; } git_work_tree_initialized = 1; - work_tree = xstrdup(make_absolute_path(new_work_tree)); + work_tree = xstrdup(real_path(new_work_tree)); } const char *get_git_work_tree(void) diff --git a/exec_cmd.c b/exec_cmd.c index 38545e8bfd..171e841531 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -89,7 +89,7 @@ static void add_path(struct strbuf *out, const char *path) if (is_absolute_path(path)) strbuf_addstr(out, path); else - strbuf_addstr(out, make_nonrelative_path(path)); + strbuf_addstr(out, absolute_path(path)); strbuf_addch(out, PATH_SEP); } diff --git a/fast-import.c b/fast-import.c index 60f26fe473..78d978684d 100644 --- a/fast-import.c +++ b/fast-import.c @@ -24,10 +24,12 @@ Format of STDIN stream: commit_msg ('from' sp committish lf)? ('merge' sp committish lf)* - file_change* + (file_change | ls)* lf?; commit_msg ::= data; + ls ::= 'ls' sp '"' quoted(path) '"' lf; + file_change ::= file_clr | file_del | file_rnm @@ -132,7 +134,7 @@ Format of STDIN stream: ts ::= # time since the epoch in seconds, ascii base10 notation; tz ::= # GIT style timezone; - # note: comments and cat requests may appear anywhere + # note: comments, ls and cat requests may appear anywhere # in the input, except within a data command. Any form # of the data command always escapes the related input # from comment processing. @@ -141,7 +143,9 @@ Format of STDIN stream: # must be the first character on that line (an lf # preceded it). # + cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf; + ls_tree ::= 'ls' sp (hexsha1 | idnum) sp path_str lf; comment ::= '#' not_lf* lf; not_lf ::= # Any byte that is not ASCII newline (LF); @@ -166,8 +170,7 @@ Format of STDIN stream: #define DEPTH_BITS 13 #define MAX_DEPTH ((1<<DEPTH_BITS)-1) -struct object_entry -{ +struct object_entry { struct pack_idx_entry idx; struct object_entry *next; uint32_t type : TYPE_BITS, @@ -175,16 +178,14 @@ struct object_entry depth : DEPTH_BITS; }; -struct object_entry_pool -{ +struct object_entry_pool { struct object_entry_pool *next_pool; struct object_entry *next_free; struct object_entry *end; struct object_entry entries[FLEX_ARRAY]; /* more */ }; -struct mark_set -{ +struct mark_set { union { struct object_entry *marked[1024]; struct mark_set *sets[1024]; @@ -192,57 +193,49 @@ struct mark_set unsigned int shift; }; -struct last_object -{ +struct last_object { struct strbuf data; off_t offset; unsigned int depth; unsigned no_swap : 1; }; -struct mem_pool -{ +struct mem_pool { struct mem_pool *next_pool; char *next_free; char *end; uintmax_t space[FLEX_ARRAY]; /* more */ }; -struct atom_str -{ +struct atom_str { struct atom_str *next_atom; unsigned short str_len; char str_dat[FLEX_ARRAY]; /* more */ }; struct tree_content; -struct tree_entry -{ +struct tree_entry { struct tree_content *tree; struct atom_str *name; - struct tree_entry_ms - { + struct tree_entry_ms { uint16_t mode; unsigned char sha1[20]; } versions[2]; }; -struct tree_content -{ +struct tree_content { unsigned int entry_capacity; /* must match avail_tree_content */ unsigned int entry_count; unsigned int delta_depth; struct tree_entry *entries[FLEX_ARRAY]; /* more */ }; -struct avail_tree_content -{ +struct avail_tree_content { unsigned int entry_capacity; /* must match tree_content */ struct avail_tree_content *next_avail; }; -struct branch -{ +struct branch { struct branch *table_next_branch; struct branch *active_next_branch; const char *name; @@ -254,16 +247,14 @@ struct branch unsigned char sha1[20]; }; -struct tag -{ +struct tag { struct tag *next_tag; const char *name; unsigned int pack_id; unsigned char sha1[20]; }; -struct hash_list -{ +struct hash_list { struct hash_list *next; unsigned char sha1[20]; }; @@ -274,8 +265,7 @@ typedef enum { WHENSPEC_NOW } whenspec_type; -struct recent_command -{ +struct recent_command { struct recent_command *prev; struct recent_command *next; char *buf; @@ -284,7 +274,6 @@ struct recent_command /* Configured limits on output */ static unsigned long max_depth = 10; static off_t max_packsize; -static uintmax_t big_file_threshold = 512 * 1024 * 1024; static int force_update; static int pack_compression_level = Z_DEFAULT_COMPRESSION; static int pack_compression_seen; @@ -329,6 +318,7 @@ static struct mark_set *marks; static const char *export_marks_file; static const char *import_marks_file; static int import_marks_file_from_stream; +static int import_marks_file_ignore_missing; static int relative_marks_paths; /* Our last blob */ @@ -373,6 +363,7 @@ static int cat_blob_fd = STDOUT_FILENO; static void parse_argv(void); static void parse_cat_blob(void); +static void parse_ls(struct branch *b); static void write_branch_report(FILE *rpt, struct branch *b) { @@ -871,6 +862,7 @@ static void start_packfile(void) p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2); strcpy(p->pack_name, tmpfile); p->pack_fd = pack_fd; + p->do_not_close = 1; pack_file = sha1fd(pack_fd, p->pack_name); hdr.hdr_signature = htonl(PACK_SIGNATURE); @@ -1795,7 +1787,11 @@ static void read_marks(void) { char line[512]; FILE *f = fopen(import_marks_file, "r"); - if (!f) + if (f) + ; + else if (import_marks_file_ignore_missing && errno == ENOENT) + return; /* Marks file does not exist */ + else die_errno("cannot read '%s'", import_marks_file); while (fgets(line, sizeof(line), f)) { uintmax_t mark; @@ -2609,6 +2605,8 @@ static void parse_new_commit(void) note_change_n(b, prev_fanout); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); + else if (!prefixcmp(command_buf.buf, "ls ")) + parse_ls(b); else { unread_command_buf = 1; break; @@ -2832,6 +2830,153 @@ static void parse_cat_blob(void) cat_blob(oe, sha1); } +static struct object_entry *dereference(struct object_entry *oe, + unsigned char sha1[20]) +{ + unsigned long size; + char *buf = NULL; + if (!oe) { + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("object not found: %s", sha1_to_hex(sha1)); + /* cache it! */ + oe = insert_object(sha1); + oe->type = type; + oe->pack_id = MAX_PACK_ID; + oe->idx.offset = 1; + } + switch (oe->type) { + case OBJ_TREE: /* easy case. */ + return oe; + case OBJ_COMMIT: + case OBJ_TAG: + break; + default: + die("Not a treeish: %s", command_buf.buf); + } + + if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */ + buf = gfi_unpack_entry(oe, &size); + } else { + enum object_type unused; + buf = read_sha1_file(sha1, &unused, &size); + } + if (!buf) + die("Can't load object %s", sha1_to_hex(sha1)); + + /* Peel one layer. */ + switch (oe->type) { + case OBJ_TAG: + if (size < 40 + strlen("object ") || + get_sha1_hex(buf + strlen("object "), sha1)) + die("Invalid SHA1 in tag: %s", command_buf.buf); + break; + case OBJ_COMMIT: + if (size < 40 + strlen("tree ") || + get_sha1_hex(buf + strlen("tree "), sha1)) + die("Invalid SHA1 in commit: %s", command_buf.buf); + } + + free(buf); + return find_object(sha1); +} + +static struct object_entry *parse_treeish_dataref(const char **p) +{ + unsigned char sha1[20]; + struct object_entry *e; + + if (**p == ':') { /* <mark> */ + char *endptr; + e = find_mark(strtoumax(*p + 1, &endptr, 10)); + if (endptr == *p + 1) + die("Invalid mark: %s", command_buf.buf); + if (!e) + die("Unknown mark: %s", command_buf.buf); + *p = endptr; + hashcpy(sha1, e->idx.sha1); + } else { /* <sha1> */ + if (get_sha1_hex(*p, sha1)) + die("Invalid SHA1: %s", command_buf.buf); + e = find_object(sha1); + *p += 40; + } + + while (!e || e->type != OBJ_TREE) + e = dereference(e, sha1); + return e; +} + +static void print_ls(int mode, const unsigned char *sha1, const char *path) +{ + static struct strbuf line = STRBUF_INIT; + + /* See show_tree(). */ + const char *type = + S_ISGITLINK(mode) ? commit_type : + S_ISDIR(mode) ? tree_type : + blob_type; + + if (!mode) { + /* missing SP path LF */ + strbuf_reset(&line); + strbuf_addstr(&line, "missing "); + quote_c_style(path, &line, NULL, 0); + strbuf_addch(&line, '\n'); + } else { + /* mode SP type SP object_name TAB path LF */ + strbuf_reset(&line); + strbuf_addf(&line, "%06o %s %s\t", + mode, type, sha1_to_hex(sha1)); + quote_c_style(path, &line, NULL, 0); + strbuf_addch(&line, '\n'); + } + cat_blob_write(line.buf, line.len); +} + +static void parse_ls(struct branch *b) +{ + const char *p; + struct tree_entry *root = NULL; + struct tree_entry leaf = {NULL}; + + /* ls SP (<treeish> SP)? <path> */ + p = command_buf.buf + strlen("ls "); + if (*p == '"') { + if (!b) + die("Not in a commit: %s", command_buf.buf); + root = &b->branch_tree; + } else { + struct object_entry *e = parse_treeish_dataref(&p); + root = new_tree_entry(); + hashcpy(root->versions[1].sha1, e->idx.sha1); + load_tree(root); + if (*p++ != ' ') + die("Missing space after tree-ish: %s", command_buf.buf); + } + if (*p == '"') { + static struct strbuf uq = STRBUF_INIT; + const char *endp; + strbuf_reset(&uq); + if (unquote_c_style(&uq, p, &endp)) + die("Invalid path: %s", command_buf.buf); + if (*endp) + die("Garbage after path in: %s", command_buf.buf); + p = uq.buf; + } + tree_content_get(root, p, &leaf); + /* + * A directory in preparation would have a sha1 of zero + * until it is saved. Save, for simplicity. + */ + if (S_ISDIR(leaf.versions[1].mode)) + store_tree(&leaf); + + print_ls(leaf.versions[1].mode, leaf.versions[1].sha1, p); + if (!b || root != &b->branch_tree) + release_tree_entry(root); +} + static void checkpoint(void) { checkpoint_requested = 0; @@ -2867,7 +3012,8 @@ static char* make_fast_import_path(const char *path) return strbuf_detach(&abs_path, NULL); } -static void option_import_marks(const char *marks, int from_stream) +static void option_import_marks(const char *marks, + int from_stream, int ignore_missing) { if (import_marks_file) { if (from_stream) @@ -2881,6 +3027,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; + import_marks_file_ignore_missing = ignore_missing; } static void option_date_format(const char *fmt) @@ -2980,17 +3127,22 @@ static int parse_one_feature(const char *feature, int from_stream) if (!prefixcmp(feature, "date-format=")) { option_date_format(feature + 12); } else if (!prefixcmp(feature, "import-marks=")) { - option_import_marks(feature + 13, from_stream); + option_import_marks(feature + 13, from_stream, 0); + } else if (!prefixcmp(feature, "import-marks-if-exists=")) { + option_import_marks(feature + strlen("import-marks-if-exists="), + from_stream, 1); } else if (!prefixcmp(feature, "export-marks=")) { option_export_marks(feature + 13); } else if (!strcmp(feature, "cat-blob")) { ; /* Don't die - this feature is supported */ - } else if (!prefixcmp(feature, "relative-marks")) { + } else if (!strcmp(feature, "relative-marks")) { relative_marks_paths = 1; - } else if (!prefixcmp(feature, "no-relative-marks")) { + } else if (!strcmp(feature, "no-relative-marks")) { relative_marks_paths = 0; - } else if (!prefixcmp(feature, "force")) { + } else if (!strcmp(feature, "force")) { force_update = 1; + } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) { + ; /* do nothing; we have the feature */ } else { return 0; } @@ -3053,10 +3205,6 @@ static int git_pack_config(const char *k, const char *v, void *cb) max_packsize = git_config_ulong(k, v); return 0; } - if (!strcmp(k, "core.bigfilethreshold")) { - long n = git_config_int(k, v); - big_file_threshold = 0 < n ? n : 0; - } return git_default_config(k, v, cb); } @@ -3130,6 +3278,8 @@ int main(int argc, const char **argv) while (read_next_command() != EOF) { if (!strcmp("blob", command_buf.buf)) parse_new_blob(); + else if (!prefixcmp(command_buf.buf, "ls ")) + parse_ls(NULL); else if (!prefixcmp(command_buf.buf, "commit ")) parse_new_commit(); else if (!prefixcmp(command_buf.buf, "tag ")) diff --git a/fetch-pack.h b/fetch-pack.h index fbe85ac05f..0608edae3f 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -1,8 +1,7 @@ #ifndef FETCH_PACK_H #define FETCH_PACK_H -struct fetch_pack_args -{ +struct fetch_pack_args { const char *uploadpack; int unpacklimit; int depth; @@ -347,26 +347,14 @@ int fsck_object(struct object *obj, int strict, fsck_error error_func) int fsck_error_function(struct object *obj, int type, const char *fmt, ...) { va_list ap; - int len; struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "object %s:", obj->sha1?sha1_to_hex(obj->sha1):"(null)"); + strbuf_addf(&sb, "object %s:", sha1_to_hex(obj->sha1)); va_start(ap, fmt); - len = vsnprintf(sb.buf + sb.len, strbuf_avail(&sb), fmt, ap); + strbuf_vaddf(&sb, fmt, ap); va_end(ap); - if (len < 0) - len = 0; - if (len >= strbuf_avail(&sb)) { - strbuf_grow(&sb, len + 2); - va_start(ap, fmt); - len = vsnprintf(sb.buf + sb.len, strbuf_avail(&sb), fmt, ap); - va_end(ap); - if (len >= strbuf_avail(&sb)) - die("this should not happen, your snprintf is broken"); - } - error("%s", sb.buf); strbuf_release(&sb); return 1; diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index 75c68d948f..3ef4861d04 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -1,8 +1,7 @@ #!/bin/sh echo "/* Automatically generated by $0 */ -struct cmdname_help -{ +struct cmdname_help { char name[16]; char help[80]; }; diff --git a/gettext.c b/gettext.c new file mode 100644 index 0000000000..ae5394a496 --- /dev/null +++ b/gettext.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Ævar Arnfjörð Bjarmason + */ + +#include "git-compat-util.h" +#include "gettext.h" + +int use_gettext_poison(void) +{ + static int poison_requested = -1; + if (poison_requested == -1) + poison_requested = getenv("GIT_GETTEXT_POISON") ? 1 : 0; + return poison_requested; +} diff --git a/gettext.h b/gettext.h new file mode 100644 index 0000000000..24d91824e5 --- /dev/null +++ b/gettext.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2011 Ævar Arnfjörð Bjarmason + * + * This is a skeleton no-op implementation of gettext for Git. + * You can replace it with something that uses libintl.h and wraps + * gettext() to try out the translations. + */ + +#ifndef GETTEXT_H +#define GETTEXT_H + +#if defined(_) || defined(Q_) +#error "namespace conflict: '_' or 'Q_' is pre-defined?" +#endif + +#define FORMAT_PRESERVING(n) __attribute__((format_arg(n))) + +#ifdef GETTEXT_POISON +extern int use_gettext_poison(void); +#else +#define use_gettext_poison() 0 +#endif + +static inline FORMAT_PRESERVING(1) const char *_(const char *msgid) +{ + return use_gettext_poison() ? "# GETTEXT POISON #" : msgid; +} + +static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2) +const char *Q_(const char *msgid, const char *plu, unsigned long n) +{ + if (use_gettext_poison()) + return "# GETTEXT POISON #"; + return n == 1 ? msgid : plu; +} + +/* Mark msgid for translation but do not translate it. */ +#define N_(msgid) msgid + +#endif diff --git a/git-add--interactive.perl b/git-add--interactive.perl index a329c5a1f8..8f0839d205 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -45,6 +45,9 @@ my ($diff_new_color) = my $normal_color = $repo->get_color("", "reset"); my $use_readkey = 0; +my $use_termcap = 0; +my %term_escapes; + sub ReadMode; sub ReadKey; if ($repo->config_bool("interactive.singlekey")) { @@ -53,6 +56,14 @@ if ($repo->config_bool("interactive.singlekey")) { Term::ReadKey->import; $use_readkey = 1; }; + eval { + require Term::Cap; + my $termcap = Term::Cap->Tgetent; + foreach (values %$termcap) { + $term_escapes{$_} = 1 if /^\e/; + } + $use_termcap = 1; + }; } sub colored { @@ -705,7 +716,7 @@ sub add_untracked_cmd { sub run_git_apply { my $cmd = shift; my $fh; - open $fh, '| git ' . $cmd; + open $fh, '| git ' . $cmd . " --recount --allow-overlap"; print $fh @_; return close $fh; } @@ -1050,7 +1061,7 @@ EOF sub diff_applies { my $fh; - return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check', + return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check', map { @{$_->{TEXT}} } @_); } @@ -1067,6 +1078,14 @@ sub prompt_single_character { ReadMode 'cbreak'; my $key = ReadKey 0; ReadMode 'restore'; + if ($use_termcap and $key eq "\e") { + while (!defined $term_escapes{$key}) { + my $next = ReadKey 0.5; + last if (!defined $next); + $key .= $next; + } + $key =~ s/\e/^[/; + } print "$key" if defined $key; print "\n"; return $key; @@ -1139,7 +1158,7 @@ EOF sub apply_patch { my $cmd = shift; - my $ret = run_git_apply $cmd . ' --recount', @_; + my $ret = run_git_apply $cmd, @_; if (!$ret) { print STDERR @_; } @@ -1148,17 +1167,17 @@ sub apply_patch { sub apply_patch_for_checkout_commit { my $reverse = shift; - my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_; - my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_; + my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_; + my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_; if ($applies_worktree && $applies_index) { - run_git_apply 'apply '.$reverse.' --cached --recount', @_; - run_git_apply 'apply '.$reverse.' --recount', @_; + run_git_apply 'apply '.$reverse.' --cached', @_; + run_git_apply 'apply '.$reverse, @_; return 1; } elsif (!$applies_index) { print colored $error_color, "The selected hunks do not apply to the index!\n"; if (prompt_yesno "Apply them to the worktree anyway? ") { - return run_git_apply 'apply '.$reverse.' --recount', @_; + return run_git_apply 'apply '.$reverse, @_; } else { print colored $error_color, "Nothing was applied.\n"; return 0; @@ -1366,14 +1385,13 @@ sub patch_update_file { next; } elsif ($line =~ /^q/i) { - while ($ix < $num) { - if (!defined $hunk[$ix]{USE}) { - $hunk[$ix]{USE} = 0; + for ($i = 0; $i < $num; $i++) { + if (!defined $hunk[$i]{USE}) { + $hunk[$i]{USE} = 0; } - $ix++; } $quit = 1; - next; + last; } elsif ($line =~ m|^/(.*)|) { my $regex = $1; diff --git a/git-bisect.sh b/git-bisect.sh index c21e33c8d1..415a8d04cc 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -288,10 +288,12 @@ bisect_visualize() { if test $# = 0 then - case "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in - '') set git log ;; - set*) set gitk ;; - esac + if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" && + type gitk >/dev/null 2>&1; then + set gitk + else + set git log + fi else case "$1" in git*|tig) ;; diff --git a/git-compat-util.h b/git-compat-util.h index d6d269f138..e0bb81ed8d 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -31,6 +31,9 @@ #define maximum_signed_value_of_type(a) \ (INTMAX_MAX >> (bitsizeof(intmax_t) - bitsizeof(a))) +#define maximum_unsigned_value_of_type(a) \ + (UINTMAX_MAX >> (bitsizeof(uintmax_t) - bitsizeof(a))) + /* * Signed integer overflow is undefined in C, so here's a helper macro * to detect if the sum of two integers will overflow. @@ -40,6 +43,9 @@ #define signed_add_overflows(a, b) \ ((b) > maximum_signed_value_of_type(a) - (a)) +#define unsigned_add_overflows(a, b) \ + ((b) > maximum_unsigned_value_of_type(a) - (a)) + #ifdef __GNUC__ #define TYPEOF(x) (__typeof__(x)) #else @@ -112,6 +118,7 @@ #endif #ifndef __MINGW32__ #include <sys/wait.h> +#include <sys/resource.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <termios.h> @@ -208,7 +215,10 @@ extern char *gitbasename(char *); #define is_dir_sep(c) ((c) == '/') #endif -#ifdef __GNUC__ +#if __HP_cc >= 61000 +#define NORETURN __attribute__((noreturn)) +#define NORETURN_PTR +#elif defined(__GNUC__) #define NORETURN __attribute__((__noreturn__)) #define NORETURN_PTR __attribute__((__noreturn__)) #elif defined(_MSC_VER) @@ -453,6 +463,7 @@ extern unsigned char sane_ctype[256]; #define GIT_ALPHA 0x04 #define GIT_GLOB_SPECIAL 0x08 #define GIT_REGEX_SPECIAL 0x10 +#define GIT_PATHSPEC_MAGIC 0x20 #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) #define isascii(x) (((x) & ~0x7f) == 0) #define isspace(x) sane_istest(x,GIT_SPACE) @@ -463,6 +474,7 @@ extern unsigned char sane_ctype[256]; #define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL) #define tolower(x) sane_case((unsigned char)(x), 0x20) #define toupper(x) sane_case((unsigned char)(x), 0) +#define is_pathspec_magic(x) sane_istest(x,GIT_PATHSPEC_MAGIC) static inline int sane_case(int x, int high) { @@ -529,6 +541,19 @@ void git_qsort(void *base, size_t nmemb, size_t size, #define fstat_is_reliable() 1 #endif +#ifndef va_copy +/* + * Since an obvious implementation of va_list would be to make it a + * pointer into the stack frame, a simple assignment will work on + * many systems. But let's try to be more portable. + */ +#ifdef __va_copy +#define va_copy(dst, src) __va_copy(dst, src) +#else +#define va_copy(dst, src) ((dst) = (src)) +#endif +#endif + /* * Preserves errno, prints a message, but gives no warning for ENOENT. * Always returns the return value of unlink(2). diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 8e683e5478..8d41610bcf 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -227,6 +227,31 @@ sub new { return $self; } +sub find_password_entry { + my ($cvspass, @cvsroot) = @_; + my ($file, $delim) = @$cvspass; + my $pass; + local ($_); + + if (open(my $fh, $file)) { + # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z + CVSPASSFILE: + while (<$fh>) { + chomp; + s/^\/\d+\s+//; + my ($w, $p) = split($delim,$_,2); + for my $cvsroot (@cvsroot) { + if ($w eq $cvsroot) { + $pass = $p; + last CVSPASSFILE; + } + } + } + close($fh); + } + return $pass; +} + sub conn { my $self = shift; my $repo = $self->{'fullrep'}; @@ -259,19 +284,23 @@ sub conn { if ($pass) { $pass = $self->_scramble($pass); } else { - open(H,$ENV{'HOME'}."/.cvspass") and do { - # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z - while (<H>) { - chomp; - s/^\/\d+\s+//; - my ($w,$p) = split(/\s/,$_,2); - if ($w eq $rr or $w eq $rr2) { - $pass = $p; - last; - } + my @cvspass = ([$ENV{'HOME'}."/.cvspass", qr/\s/], + [$ENV{'HOME'}."/.cvs/cvspass", qr/=/]); + my @loc = (); + foreach my $cvspass (@cvspass) { + my $p = find_password_entry($cvspass, $rr, $rr2); + if ($p) { + push @loc, $cvspass->[0]; + $pass = $p; } - }; - $pass = "A" unless $pass; + } + + if (1 < @loc) { + die("Multiple cvs password files have ". + "entries for CVSROOT $opt_d: @loc"); + } elsif (!$pass) { + $pass = "A"; + } } my ($s, $rep); @@ -366,7 +395,9 @@ sub conn { $self->{'socketo'}->write("valid-requests\n"); $self->{'socketo'}->flush(); - chomp(my $rep=$self->readline()); + my $rep=$self->readline(); + die "Failed to read from server" unless defined $rep; + chomp($rep); if ($rep !~ s/^Valid-requests\s*//) { $rep="<unknown>" unless $rep; die "Expected Valid-requests from server, but got: $rep\n"; diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index d3acf0d213..fd6a43d0a2 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -93,6 +93,25 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { package require msgcat +# Check for Windows 7 MUI language pack (missed by msgcat < 1.4.4) +if {[tk windowingsystem] eq "win32" + && [package vcompare [package provide msgcat] 1.4.4] < 0 +} then { + proc _mc_update_locale {} { + set key {HKEY_CURRENT_USER\Control Panel\Desktop} + if {![catch { + package require registry + set uilocale [registry get $key "PreferredUILanguages"] + msgcat::ConvertLocale [string map {- _} [lindex $uilocale 0]] + } uilocale]} { + if {[string length $uilocale] > 0} { + msgcat::mclocale $uilocale + } + } + } + _mc_update_locale +} + proc _mc_trim {fmt} { set cmk [string first @@ $fmt] if {$cmk > 0} { @@ -139,6 +158,10 @@ if {$_trace >= 0} { set _trace 0 } +# variable for the last merged branch (useful for a default when deleting +# branches). +set _last_merged_branch {} + proc shellpath {} { global _shellpath env if {[string match @@* $_shellpath]} { @@ -1448,13 +1471,17 @@ proc rescan_stage2 {fd after} { close $fd } - set ls_others [list --exclude-per-directory=.gitignore] - if {[have_info_exclude]} { - lappend ls_others "--exclude-from=[gitdir info exclude]" - } - set user_exclude [get_config core.excludesfile] - if {$user_exclude ne {} && [file readable $user_exclude]} { - lappend ls_others "--exclude-from=$user_exclude" + if {[package vsatisfies $::_git_version 1.6.3]} { + set ls_others [list --exclude-standard] + } else { + set ls_others [list --exclude-per-directory=.gitignore] + if {[have_info_exclude]} { + lappend ls_others "--exclude-from=[gitdir info exclude]" + } + set user_exclude [get_config core.excludesfile] + if {$user_exclude ne {} && [file readable $user_exclude]} { + lappend ls_others "--exclude-from=[file normalize $user_exclude]" + } } set buf_rdi {} @@ -1958,8 +1985,8 @@ static unsigned char file_merge_bits[] = { } -maskdata $filemask image create bitmap file_statechange -background white -foreground green -data { -#define file_merge_width 14 -#define file_merge_height 15 +#define file_statechange_width 14 +#define file_statechange_height 15 static unsigned char file_statechange_bits[] = { 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10, 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, @@ -1993,7 +2020,11 @@ foreach i { {MD {mc "Staged for commit, missing"}} {_T {mc "File type changed, not staged"}} + {MT {mc "File type changed, old type staged for commit"}} + {AT {mc "File type changed, old type staged for commit"}} {T_ {mc "File type changed, staged"}} + {TM {mc "File type change staged, modification not staged"}} + {TD {mc "File type change staged, file missing"}} {_O {mc "Untracked, not staged"}} {A_ {mc "Staged for commit"}} @@ -3331,6 +3362,8 @@ foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 gr } $ui_diff tag configure clr1 -font font_diffbold +$ui_diff tag conf d_info -foreground blue -font font_diffbold + $ui_diff tag conf d_cr -elide true $ui_diff tag conf d_@ -font font_diffbold $ui_diff tag conf d_+ -foreground {#00a000} @@ -3351,13 +3384,13 @@ $ui_diff tag conf d_s- \ -foreground red \ -background ivory1 -$ui_diff tag conf d<<<<<<< \ +$ui_diff tag conf d< \ -foreground orange \ -font font_diffbold -$ui_diff tag conf d======= \ +$ui_diff tag conf d= \ -foreground orange \ -font font_diffbold -$ui_diff tag conf d>>>>>>> \ +$ui_diff tag conf d> \ -foreground orange \ -font font_diffbold @@ -3533,8 +3566,8 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} { || $current_diff_path eq {} || {__} eq $state || {_O} eq $state - || {_T} eq $state - || {T_} eq $state + || [string match {?T} $state] + || [string match {T?} $state] || [has_textconv $current_diff_path]} { set s disabled } else { diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index c2415729e0..a8c6223511 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@ -121,7 +121,7 @@ method _parent {} { if {$browser_stack eq {}} { regsub {:.*$} $browser_path {:} browser_path } else { - regsub {/[^/]+$} $browser_path {} browser_path + regsub {/[^/]+/$} $browser_path {/} browser_path } set browser_status [mc "Loading %s..." $browser_path] _ls $this [lindex $parent 0] [lindex $parent 1] diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index fae119286d..657f7d5dc1 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -214,14 +214,6 @@ constructor pick {} { } } -proc _home {} { - if {[catch {set h $::env(HOME)}] - || ![file isdirectory $h]} { - set h . - } - return $h -} - method _center {} { set nx [winfo reqwidth $top] set ny [winfo reqheight $top] @@ -420,7 +412,7 @@ method _new_local_path {} { if {$local_path ne {}} { set p [file dirname $local_path] } else { - set p [_home] + set p [pwd] } set p [tk_chooseDirectory \ @@ -541,7 +533,7 @@ method _open_origin {} { if {$origin_url ne {} && [file isdirectory $origin_url]} { set p $origin_url } else { - set p [_home] + set p [pwd] } set p [tk_chooseDirectory \ @@ -1042,7 +1034,7 @@ method _open_local_path {} { if {$local_path ne {}} { set p $local_path } else { - set p [_home] + set p [pwd] } set p [tk_chooseDirectory \ diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 7f459cd564..5ce46877bf 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -161,11 +161,12 @@ The rescan will be automatically started now. # set files_ready 0 foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { + set s $file_states($path) + switch -glob -- [lindex $s 0] { _? {continue} A? - D? - - T_ - + T? - M? {set files_ready 1} _U - U? { @@ -452,7 +453,11 @@ A rescan will be automatically started now. } AM - AD - + AT - + TM - + TD - MM - + MT - MD { set file_states($path) [list \ _[string index $m 1] \ diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index dcf0711be0..cf8a95ec34 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -122,22 +122,22 @@ proc show_unmerged_diff {cont_info} { if {$merge_stages(2) eq {}} { set is_conflict_diff 1 lappend current_diff_queue \ - [list [mc "LOCAL: deleted\nREMOTE:\n"] d======= \ + [list [mc "LOCAL: deleted\nREMOTE:\n"] d= \ [list ":1:$current_diff_path" ":3:$current_diff_path"]] } elseif {$merge_stages(3) eq {}} { set is_conflict_diff 1 lappend current_diff_queue \ - [list [mc "REMOTE: deleted\nLOCAL:\n"] d======= \ + [list [mc "REMOTE: deleted\nLOCAL:\n"] d= \ [list ":1:$current_diff_path" ":2:$current_diff_path"]] } elseif {[lindex $merge_stages(1) 0] eq {120000} || [lindex $merge_stages(2) 0] eq {120000} || [lindex $merge_stages(3) 0] eq {120000}} { set is_conflict_diff 1 lappend current_diff_queue \ - [list [mc "LOCAL:\n"] d======= \ + [list [mc "LOCAL:\n"] d= \ [list ":1:$current_diff_path" ":2:$current_diff_path"]] lappend current_diff_queue \ - [list [mc "REMOTE:\n"] d======= \ + [list [mc "REMOTE:\n"] d= \ [list ":1:$current_diff_path" ":3:$current_diff_path"]] } else { start_show_diff $cont_info @@ -208,32 +208,32 @@ proc show_other_diff {path w m cont_info} { $ui_diff insert end [append \ "* " \ [mc "Git Repository (subproject)"] \ - "\n"] d_@ + "\n"] d_info } elseif {![catch {set type [exec file $path]}]} { set n [string length $path] if {[string equal -length $n $path $type]} { set type [string range $type $n end] regsub {^:?\s*} $type {} type } - $ui_diff insert end "* $type\n" d_@ + $ui_diff insert end "* $type\n" d_info } if {[string first "\0" $content] != -1} { $ui_diff insert end \ [mc "* Binary file (not showing content)."] \ - d_@ + d_info } else { if {$sz > $max_sz} { $ui_diff insert end [mc \ "* Untracked file is %d bytes. * Showing only first %d bytes. -" $sz $max_sz] d_@ +" $sz $max_sz] d_info } $ui_diff insert end $content if {$sz > $max_sz} { $ui_diff insert end [mc " * Untracked file clipped here by %s. * To see the entire file, use an external editor. -" [appname]] d_@ +" [appname]] d_info } } $ui_diff conf -state disabled @@ -253,6 +253,19 @@ proc show_other_diff {path w m cont_info} { } } +proc get_conflict_marker_size {path} { + set size 7 + catch { + set fd_rc [eval [list git_read check-attr "conflict-marker-size" -- $path]] + set ret [gets $fd_rc line] + close $fd_rc + if {$ret > 0} { + regexp {.*: conflict-marker-size: (\d+)$} $line line size + } + } + return $size +} + proc start_show_diff {cont_info {add_opts {}}} { global file_states file_lists global is_3way_diff is_submodule_diff diff_active repo_config @@ -268,6 +281,7 @@ proc start_show_diff {cont_info {add_opts {}}} { set is_submodule_diff 0 set diff_active 1 set current_diff_header {} + set conflict_size [get_conflict_marker_size $path] set cmd [list] if {$w eq $ui_index} { @@ -329,7 +343,7 @@ proc start_show_diff {cont_info {add_opts {}}} { -blocking 0 \ -encoding [get_path_encoding $path] \ -translation lf - fileevent $fd readable [list read_diff $fd $cont_info] + fileevent $fd readable [list read_diff $fd $conflict_size $cont_info] } proc parse_color_line {line} { @@ -337,19 +351,27 @@ proc parse_color_line {line} { set result "" set markup [list] set regexp {\033\[((?:\d+;)*\d+)?m} + set need_reset 0 while {[regexp -indices -start $start $regexp $line match code]} { foreach {begin end} $match break append result [string range $line $start [expr {$begin - 1}]] - lappend markup [string length $result] \ - [eval [linsert $code 0 string range $line]] + set pos [string length $result] + set col [eval [linsert $code 0 string range $line]] set start [incr end] + if {$col eq "0" || $col eq ""} { + if {!$need_reset} continue + set need_reset 0 + } else { + set need_reset 1 + } + lappend markup $pos $col } append result [string range $line $start end] if {[llength $markup] < 4} {set markup {}} return [list $result $markup] } -proc read_diff {fd cont_info} { +proc read_diff {fd conflict_size cont_info} { global ui_diff diff_active is_submodule_diff global is_3way_diff is_conflict_diff current_diff_header global current_diff_queue @@ -360,37 +382,50 @@ proc read_diff {fd cont_info} { foreach {line markup} [parse_color_line $line] break set line [string map {\033 ^} $line] - # -- Cleanup uninteresting diff header lines. + set tags {} + + # -- Check for start of diff header. + if { [string match {diff --git *} $line] + || [string match {diff --cc *} $line] + || [string match {diff --combined *} $line]} { + set ::current_diff_inheader 1 + } + + # -- Check for end of diff header (any hunk line will do this). + # + if {[regexp {^@@+ } $line]} {set ::current_diff_inheader 0} + + # -- Automatically detect if this is a 3 way diff. # + if {[string match {@@@ *} $line]} {set is_3way_diff 1} + if {$::current_diff_inheader} { + + # -- These two lines stop a diff header and shouldn't be in there + if { [string match {Binary files * and * differ} $line] + || [regexp {^\* Unmerged path } $line]} { + set ::current_diff_inheader 0 + } else { + append current_diff_header $line "\n" + } + + # -- Cleanup uninteresting diff header lines. + # if { [string match {diff --git *} $line] || [string match {diff --cc *} $line] || [string match {diff --combined *} $line] || [string match {--- *} $line] - || [string match {+++ *} $line]} { - append current_diff_header $line "\n" + || [string match {+++ *} $line] + || [string match {index *} $line]} { continue } - } - if {[string match {index *} $line]} continue - if {$line eq {deleted file mode 120000}} { - set line "deleted symlink" - } - set ::current_diff_inheader 0 - # -- Automatically detect if this is a 3 way diff. - # - if {[string match {@@@ *} $line]} {set is_3way_diff 1} + # -- Name it symlink, not 120000 + # Note, that the original line is in $current_diff_header + regsub {^(deleted|new) file mode 120000} $line {\1 symlink} line - if {[string match {mode *} $line] - || [string match {new file *} $line] - || [regexp {^(old|new) mode *} $line] - || [string match {deleted file *} $line] - || [string match {deleted symlink} $line] - || [string match {Binary files * and * differ} $line] - || $line eq {\ No newline at end of file} - || [regexp {^\* Unmerged path } $line]} { - set tags {} + } elseif { $line eq {\ No newline at end of file}} { + # -- Handle some special lines } elseif {$is_3way_diff} { set op [string range $line 0 1] switch -- $op { @@ -402,7 +437,9 @@ proc read_diff {fd cont_info} { {- } {set tags d_-s} {--} {set tags d_--} {++} { - if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { + set regexp [string map [list %conflict_size $conflict_size]\ + {^\+\+([<>=]){%conflict_size}(?: |$)}] + if {[regexp $regexp $line _g op]} { set is_conflict_diff 1 set line [string replace $line 0 1 { }] set tags d$op @@ -418,10 +455,10 @@ proc read_diff {fd cont_info} { } elseif {$is_submodule_diff} { if {$line == ""} continue if {[regexp {^Submodule } $line]} { - set tags d_@ + set tags d_info } elseif {[regexp {^\* } $line]} { set line [string replace $line 0 1 {Submodule }] - set tags d_@ + set tags d_info } else { set op [string range $line 0 2] switch -- $op { @@ -441,7 +478,9 @@ proc read_diff {fd cont_info} { {@} {set tags d_@} {-} {set tags d_-} {+} { - if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { + set regexp [string map [list %conflict_size $conflict_size]\ + {^\+([<>=]){%conflict_size}(?: |$)}] + if {[regexp $regexp $line _g op]} { set is_conflict_diff 1 set tags d$op } else { diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index e9db0c4989..5d7bbf23ed 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -103,8 +103,11 @@ proc write_update_indexinfo {fd pathList totalCnt batch after} { set s $file_states($path) switch -glob -- [lindex $s 0] { A? {set new _O} - M? {set new _M} + MT - + TM - T_ {set new _T} + M? {set new _M} + TD - D_ {set new _D} D? {set new _?} ?? {continue} @@ -167,7 +170,10 @@ proc write_update_index {fd pathList totalCnt batch after} { AD {set new __} ?D {set new D_} _O - + AT - AM {set new A_} + TM - + MT - _T {set new T_} _U - U? { @@ -261,7 +267,7 @@ proc unstage_helper {txt paths} { switch -glob -- [lindex $file_states($path) 0] { A? - M? - - T_ - + T? - D? { lappend pathList $path if {$path eq $current_diff_path} { diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl index 5cded2341c..460d32fa22 100644 --- a/git-gui/lib/merge.tcl +++ b/git-gui/lib/merge.tcl @@ -83,6 +83,7 @@ method _visualize {} { method _start {} { global HEAD current_branch remote_url + global _last_merged_branch set name [_rev $this] if {$name eq {}} { @@ -109,6 +110,7 @@ method _start {} { regsub ^refs/heads/ $branch {} branch puts $fh "$cmit\t\tbranch '$branch' of $remote" close $fh + set _last_merged_branch $branch set cmd [list git] lappend cmd merge diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl index 3fe90e6970..3c8e73bceb 100644 --- a/git-gui/lib/mergetool.tcl +++ b/git-gui/lib/mergetool.tcl @@ -175,43 +175,56 @@ proc merge_resolve_tool2 {} { # Build the command line switch -- $tool { - kdiff3 { + araxis { if {$base_stage ne {}} { - set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Base)" \ - --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE"] + set cmdline [list "$merge_tool_path" -wait -merge -3 -a1 \ + -title1:"'$MERGED (Base)'" -title2:"'$MERGED (Local)'" \ + -title3:"'$MERGED (Remote)'" \ + "$BASE" "$LOCAL" "$REMOTE" "$MERGED"] } else { - set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Local)" \ - --L2 "$MERGED (Remote)" -o "$MERGED" "$LOCAL" "$REMOTE"] + set cmdline [list "$merge_tool_path" -wait -2 \ + -title1:"'$MERGED (Local)'" -title2:"'$MERGED (Remote)'" \ + "$LOCAL" "$REMOTE" "$MERGED"] } } - tkdiff { + bc3 { if {$base_stage ne {}} { - set cmdline [list "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"] + set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" -mergeoutput="$MERGED"] } else { - set cmdline [list "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"] + set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -mergeoutput="$MERGED"] } } - meld { - set cmdline [list "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"] + ecmerge { + if {$base_stage ne {}} { + set cmdline [list "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"] + } else { + set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"] + } + } + emerge { + if {$base_stage ne {}} { + set cmdline [list "$merge_tool_path" -f emerge-files-with-ancestor-command \ + "$LOCAL" "$REMOTE" "$BASE" "$basename"] + } else { + set cmdline [list "$merge_tool_path" -f emerge-files-command \ + "$LOCAL" "$REMOTE" "$basename"] + } } gvimdiff { set cmdline [list "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"] } - xxdiff { + kdiff3 { if {$base_stage ne {}} { - set cmdline [list "$merge_tool_path" -X --show-merged-pane \ - -R {Accel.SaveAsMerged: "Ctrl-S"} \ - -R {Accel.Search: "Ctrl+F"} \ - -R {Accel.SearchForward: "Ctrl-G"} \ - --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"] + set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Base)" \ + --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE"] } else { - set cmdline [list "$merge_tool_path" -X --show-merged-pane \ - -R {Accel.SaveAsMerged: "Ctrl-S"} \ - -R {Accel.Search: "Ctrl+F"} \ - -R {Accel.SearchForward: "Ctrl-G"} \ - --merged-file "$MERGED" "$LOCAL" "$REMOTE"] + set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Local)" \ + --L2 "$MERGED (Remote)" -o "$MERGED" "$LOCAL" "$REMOTE"] } } + meld { + set cmdline [list "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"] + } opendiff { if {$base_stage ne {}} { set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"] @@ -219,22 +232,20 @@ proc merge_resolve_tool2 {} { set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED"] } } - ecmerge { - if {$base_stage ne {}} { - set cmdline [list "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"] - } else { - set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"] - } + p4merge { + set cmdline [list "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"] } - emerge { + tkdiff { if {$base_stage ne {}} { - set cmdline [list "$merge_tool_path" -f emerge-files-with-ancestor-command \ - "$LOCAL" "$REMOTE" "$BASE" "$basename"] + set cmdline [list "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"] } else { - set cmdline [list "$merge_tool_path" -f emerge-files-command \ - "$LOCAL" "$REMOTE" "$basename"] + set cmdline [list "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"] } } + vimdiff { + error_popup [mc "Not a GUI merge tool: '%s'" $tool] + return + } winmerge { if {$base_stage ne {}} { # This tool does not support 3-way merges. @@ -245,25 +256,21 @@ proc merge_resolve_tool2 {} { -dl "Theirs File" -dr "Mine File" "$REMOTE" "$LOCAL" "$MERGED"] } } - araxis { + xxdiff { if {$base_stage ne {}} { - set cmdline [list "$merge_tool_path" -wait -merge -3 -a1 \ - -title1:"'$MERGED (Base)'" -title2:"'$MERGED (Local)'" \ - -title3:"'$MERGED (Remote)'" \ - "$BASE" "$LOCAL" "$REMOTE" "$MERGED"] + set cmdline [list "$merge_tool_path" -X --show-merged-pane \ + -R {Accel.SaveAsMerged: "Ctrl-S"} \ + -R {Accel.Search: "Ctrl+F"} \ + -R {Accel.SearchForward: "Ctrl-G"} \ + --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"] } else { - set cmdline [list "$merge_tool_path" -wait -2 \ - -title1:"'$MERGED (Local)'" -title2:"'$MERGED (Remote)'" \ - "$LOCAL" "$REMOTE" "$MERGED"] + set cmdline [list "$merge_tool_path" -X --show-merged-pane \ + -R {Accel.SaveAsMerged: "Ctrl-S"} \ + -R {Accel.Search: "Ctrl+F"} \ + -R {Accel.SearchForward: "Ctrl-G"} \ + --merged-file "$MERGED" "$LOCAL" "$REMOTE"] } } - p4merge { - set cmdline [list "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"] - } - vimdiff { - error_popup [mc "Not a GUI merge tool: '%s'" $tool] - return - } default { error_popup [mc "Unsupported merge tool '%s'" $tool] return diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl index b92b429cf7..5e4e7f4c83 100644 --- a/git-gui/lib/remote.tcl +++ b/git-gui/lib/remote.tcl @@ -157,22 +157,7 @@ proc add_fetch_entry {r} { } if {$enable} { - if {![winfo exists $fetch_m]} { - menu $remove_m - $remote_m insert 0 cascade \ - -label [mc "Remove Remote"] \ - -menu $remove_m - - menu $prune_m - $remote_m insert 0 cascade \ - -label [mc "Prune from"] \ - -menu $prune_m - - menu $fetch_m - $remote_m insert 0 cascade \ - -label [mc "Fetch from"] \ - -menu $fetch_m - } + make_sure_remote_submenues_exist $remote_m $fetch_m add command \ -label $r \ @@ -222,6 +207,70 @@ proc add_push_entry {r} { } } +proc make_sure_remote_submenues_exist {remote_m} { + set fetch_m $remote_m.fetch + set prune_m $remote_m.prune + set remove_m $remote_m.remove + + if {![winfo exists $fetch_m]} { + menu $remove_m + $remote_m insert 0 cascade \ + -label [mc "Remove Remote"] \ + -menu $remove_m + + menu $prune_m + $remote_m insert 0 cascade \ + -label [mc "Prune from"] \ + -menu $prune_m + + menu $fetch_m + $remote_m insert 0 cascade \ + -label [mc "Fetch from"] \ + -menu $fetch_m + } +} + +proc update_all_remotes_menu_entry {} { + global all_remotes + + if {[git-version < 1.6.6]} { return } + + set have_remote 0 + foreach r $all_remotes { + incr have_remote + } + + set remote_m .mbar.remote + set fetch_m $remote_m.fetch + set prune_m $remote_m.prune + if {$have_remote > 1} { + make_sure_remote_submenues_exist $remote_m + if {[$fetch_m entrycget end -label] ne "All"} { + + $fetch_m insert end separator + $fetch_m insert end command \ + -label "All" \ + -command fetch_from_all + + $prune_m insert end separator + $prune_m insert end command \ + -label "All" \ + -command prune_from_all + } + } else { + if {[winfo exists $fetch_m]} { + if {[$fetch_m entrycget end -label] eq "All"} { + + delete_from_menu $fetch_m end + delete_from_menu $fetch_m end + + delete_from_menu $prune_m end + delete_from_menu $prune_m end + } + } + } +} + proc populate_remotes_menu {} { global all_remotes @@ -229,6 +278,8 @@ proc populate_remotes_menu {} { add_fetch_entry $r add_push_entry $r } + + update_all_remotes_menu_entry } proc add_single_remote {name location} { @@ -244,6 +295,8 @@ proc add_single_remote {name location} { add_fetch_entry $name add_push_entry $name + + update_all_remotes_menu_entry } proc delete_from_menu {menu name} { @@ -264,8 +317,8 @@ proc remove_remote {name} { unset repo_config(remote.$name.push) } - set i [lsearch -exact all_remotes $name] - lreplace all_remotes $i $i + set i [lsearch -exact $all_remotes $name] + set all_remotes [lreplace $all_remotes $i $i] set remote_m .mbar.remote delete_from_menu $remote_m.fetch $name @@ -273,4 +326,6 @@ proc remove_remote {name} { delete_from_menu $remote_m.remove $name # Not all remotes are in the push menu catch { delete_from_menu $remote_m.push $name } + + update_all_remotes_menu_entry } diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl index f872a3d89d..fcc06d03a1 100644 --- a/git-gui/lib/remote_branch_delete.tcl +++ b/git-gui/lib/remote_branch_delete.tcl @@ -251,7 +251,7 @@ method _write_url {args} { set urltype url } method _write_check_head {args} { set checktype head } method _write_head_list {args} { - global current_branch + global current_branch _last_merged_branch $head_m delete 0 end foreach abr $head_list { @@ -267,6 +267,13 @@ method _write_head_list {args} { set check_head $current_branch } } + set lmb [lsearch -exact -sorted $head_list $_last_merged_branch] + if {$lmb >= 0} { + $w.heads.l conf -state normal + $w.heads.l select set $lmb + $w.heads.l yview $lmb + $w.heads.l conf -state disabled + } } method _write_urltype {args} { diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl index 60e3a642c5..7fad9b7d91 100644 --- a/git-gui/lib/transport.tcl +++ b/git-gui/lib/transport.tcl @@ -20,6 +20,35 @@ proc prune_from {remote} { console::exec $w [list git remote prune $remote] } +proc fetch_from_all {} { + set w [console::new \ + [mc "fetch all remotes"] \ + [mc "Fetching new changes from all remotes"]] + + set cmd [list git fetch --all] + if {[is_config_true gui.pruneduringfetch]} { + lappend cmd --prune + } + + console::exec $w $cmd +} + +proc prune_from_all {} { + global all_remotes + + set w [console::new \ + [mc "remote prune all remotes"] \ + [mc "Pruning tracking branches deleted from all remotes"]] + + set cmd [list git remote prune] + + foreach r $all_remotes { + lappend cmd $r + } + + console::exec $w $cmd +} + proc push_to {remote} { set w [console::new \ [mc "push %s" $remote] \ @@ -123,6 +152,7 @@ proc do_push_anywhere {} { $w.source.l insert end $h if {$h eq $current_branch} { $w.source.l select set end + $w.source.l yview end } } pack $w.source.l -side left -fill both -expand 1 @@ -135,7 +165,9 @@ proc do_push_anywhere {} { -value remote \ -variable push_urltype if {$use_ttk} { - ttk::combobox $w.dest.remote_m -textvariable push_remote \ + ttk::combobox $w.dest.remote_m -state readonly \ + -exportselection false \ + -textvariable push_remote \ -values $all_remotes } else { eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes diff --git a/git-gui/po/glossary/pt_br.po b/git-gui/po/glossary/pt_br.po new file mode 100644 index 0000000000..eb039b2b49 --- /dev/null +++ b/git-gui/po/glossary/pt_br.po @@ -0,0 +1,169 @@ +# Translation of git-gui to Brazilian Portuguese +# Copyright (C) 2007 Shawn Pearce, et al. +# This file is distributed under the same license as the git-gui package. +# +# Alexandre Erwin Ittner <alexandre@ittner.com.br>, 2010. +msgid "" +msgstr "" +"Project-Id-Version: git-gui\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" +"PO-Revision-Date: 2010-09-18 11:09-0300\n" +"Last-Translator: Alexandre Erwin Ittner <alexandre@ittner.com.br>\n" +"Language-Team: Brazilian Portuguese <>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" +msgid "" +"English Term (Dear translator: This file will never be visible to the user!)" +msgstr "" + +#. "" +msgid "amend" +msgstr "corrigir" + +#. "" +msgid "annotate" +msgstr "anotar" + +#. "A 'branch' is an active line of development." +msgid "branch [noun]" +msgstr "ramo" + +#. "" +msgid "branch [verb]" +msgstr "ramificar" + +#. "" +msgid "checkout [noun]" +msgstr "checkout" + +#. "The action of updating the working tree to a revision which was stored in the object database." +msgid "checkout [verb]" +msgstr "efetuar checkout" + +#. "" +msgid "clone [verb]" +msgstr "clonar" + +#. "A single point in the git history." +msgid "commit [noun]" +msgstr "revisão" + +#. "The action of storing a new snapshot of the project's state in the git history." +msgid "commit [verb]" +msgstr "salvar revisão" + +#. "" +msgid "diff [noun]" +msgstr "diff" + +#. "" +msgid "diff [verb]" +msgstr "comparar" + +#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +msgid "fast forward merge" +msgstr "mesclagem rápida" + +#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." +msgid "fetch" +msgstr "receber" + +#. "One context of consecutive lines in a whole patch, which consists of many such hunks" +msgid "hunk" +msgstr "trecho" + +#. "A collection of files. The index is a stored version of your working tree." +msgid "index (in git-gui: staging area)" +msgstr "índice" + +#. "A successful merge results in the creation of a new commit representing the result of the merge." +msgid "merge [noun]" +msgstr "mesclagem" + +#. "To bring the contents of another branch into the current branch." +msgid "merge [verb]" +msgstr "mesclar" + +#. "" +msgid "message" +msgstr "descrição da revisão" + +#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'." +msgid "prune" +msgstr "limpar" + +#. "Pulling a branch means to fetch it and merge it." +msgid "pull" +msgstr "receber e mesclar" + +#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +msgid "push" +msgstr "enviar" + +#. "" +msgid "redo" +msgstr "refazer" + +#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks." +msgid "remote" +msgstr "repositório remoto" + +#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +msgid "repository" +msgstr "repositório" + +#. "" +msgid "reset" +msgstr "descartar, redefinir" + +#. "" +msgid "revert" +msgstr "reverter" + +#. "A particular state of files and directories which was stored in the object database." +msgid "revision" +msgstr "revisão" + +#. "" +msgid "sign off" +msgstr "assinar embaixo" + +#. "" +msgid "staging area" +msgstr "???" + +#. "" +msgid "status" +msgstr "status" + +#. "A ref pointing to a tag or commit object" +msgid "tag [noun]" +msgstr "etiqueta" + +#. "" +msgid "tag [verb]" +msgstr "marcar etiqueta" + +#. "A regular git branch that is used to follow changes from another repository." +msgid "tracking branch" +msgstr "ramo de rastreamento" + +#. "" +msgid "undo" +msgstr "desfazer" + +#. "" +msgid "update" +msgstr "atualizar" + +#. "" +msgid "verify" +msgstr "verificar" + +#. "The tree of actual checked out files." +msgid "working copy, working tree" +msgstr "cópia de trabalho, árvore de trabalho" diff --git a/git-gui/po/pt_br.po b/git-gui/po/pt_br.po new file mode 100644 index 0000000000..b175b9791e --- /dev/null +++ b/git-gui/po/pt_br.po @@ -0,0 +1,2568 @@ +# Translation of git-gui to Brazilian Portuguese +# Copyright (C) 2007 Shawn Pearce, et al. +# This file is distributed under the same license as the git-gui package. +# +# Alexandre Erwin Ittner <alexandre@ittner.com.br>, 2010. +msgid "" +msgstr "" +"Project-Id-Version: git-gui\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" +"PO-Revision-Date: 2010-09-18 11:09-0300\n" +"Last-Translator: Alexandre Erwin Ittner <alexandre@ittner.com.br>\n" +"Language-Team: Brazilian Portuguese <>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 +#: git-gui.sh:922 +msgid "git-gui: fatal error" +msgstr "git-gui: erro fatal" + +#: git-gui.sh:743 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "Fonte inválida indicada em %s:" + +#: git-gui.sh:779 +msgid "Main Font" +msgstr "Fonte principal" + +#: git-gui.sh:780 +msgid "Diff/Console Font" +msgstr "Fonte para o diff/console" + +#: git-gui.sh:794 +msgid "Cannot find git in PATH." +msgstr "Impossível encontrar o git no \"PATH\"" + +#: git-gui.sh:821 +msgid "Cannot parse Git version string:" +msgstr "Impossível interpretar a versão do git:" + +#: git-gui.sh:839 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" +"Não foi possível determinar a versão do git:\n" +"\n" +"%s afirmar que sua versão é \"%s\".\n" +"\n" +"%s exige o Git 1.5.0 ou posterior.\n" +"\n" +"Assumir que '%s' é a versão 1.5.0?\n" + +#: git-gui.sh:1128 +msgid "Git directory not found:" +msgstr "Diretório do Git não encontrado:" + +#: git-gui.sh:1146 +msgid "Cannot move to top of working directory:" +msgstr "Impossível mover para o início do diretório de trabalho:" + +#: git-gui.sh:1154 +msgid "Cannot use bare repository:" +msgstr "Impossível usar repositório puro:" + +#: git-gui.sh:1162 +msgid "No working directory" +msgstr "Sem diretório de trabalho" + +#: git-gui.sh:1334 lib/checkout_op.tcl:306 +msgid "Refreshing file status..." +msgstr "Atualizando estado dos arquivos..." + +#: git-gui.sh:1390 +msgid "Scanning for modified files ..." +msgstr "Procurando por arquivos modificados ..." + +#: git-gui.sh:1454 +msgid "Calling prepare-commit-msg hook..." +msgstr "Executando hook \"prepare-commit-msg\"..." + +#: git-gui.sh:1471 +msgid "Commit declined by prepare-commit-msg hook." +msgstr "O script \"prepare-commit-msg\" negou a criação de uma nova revisão" + +#: git-gui.sh:1629 lib/browser.tcl:246 +msgid "Ready." +msgstr "Pronto." + +#: git-gui.sh:1787 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "Exibindo apenas %s de %s arquivos." + +#: git-gui.sh:1913 +msgid "Unmodified" +msgstr "Não modificado" + +#: git-gui.sh:1915 +msgid "Modified, not staged" +msgstr "Modificado, não marcado" + +#: git-gui.sh:1916 git-gui.sh:1924 +msgid "Staged for commit" +msgstr "Marcado para uma nova revisão" + +#: git-gui.sh:1917 git-gui.sh:1925 +msgid "Portions staged for commit" +msgstr "Trechos marcados para revisão" + +#: git-gui.sh:1918 git-gui.sh:1926 +msgid "Staged for commit, missing" +msgstr "Marcado para revisão, faltando" + +#: git-gui.sh:1920 +msgid "File type changed, not staged" +msgstr "Tipo do arquivo modificado, não marcado" + +#: git-gui.sh:1921 +msgid "File type changed, staged" +msgstr "Tipo do arquivo modificado, marcado" + +#: git-gui.sh:1923 +msgid "Untracked, not staged" +msgstr "Não monitorado, não marcado" + +#: git-gui.sh:1928 +msgid "Missing" +msgstr "Faltando" + +#: git-gui.sh:1929 +msgid "Staged for removal" +msgstr "Marcado para remoção" + +#: git-gui.sh:1930 +msgid "Staged for removal, still present" +msgstr "Marcado para remoção, ainda presente" + +#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 +#: git-gui.sh:1936 git-gui.sh:1937 +msgid "Requires merge resolution" +msgstr "Requer resolução de conflitos" + +#: git-gui.sh:1972 +msgid "Starting gitk... please wait..." +msgstr "Iniciando gitk... Aguarde..." + +#: git-gui.sh:1984 +msgid "Couldn't find gitk in PATH" +msgstr "Impossível encontrar o gitk no PATH" + +#: git-gui.sh:2043 +msgid "Couldn't find git gui in PATH" +msgstr "Impossível encontrar o \"git gui\" no PATH" + +#: git-gui.sh:2455 lib/choose_repository.tcl:36 +msgid "Repository" +msgstr "Repositório" + +#: git-gui.sh:2456 +msgid "Edit" +msgstr "Editar" + +#: git-gui.sh:2458 lib/choose_rev.tcl:561 +msgid "Branch" +msgstr "Ramo" + +#: git-gui.sh:2461 lib/choose_rev.tcl:548 +msgid "Commit@@noun" +msgstr "Revisão" + +#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +msgid "Merge" +msgstr "Mesclar" + +#: git-gui.sh:2465 lib/choose_rev.tcl:557 +msgid "Remote" +msgstr "Remoto" + +#: git-gui.sh:2468 +msgid "Tools" +msgstr "Ferramentas" + +#: git-gui.sh:2477 +msgid "Explore Working Copy" +msgstr "Explorar cópia de trabalho" + +#: git-gui.sh:2483 +msgid "Browse Current Branch's Files" +msgstr "Explorar arquivos do ramo atual" + +#: git-gui.sh:2487 +msgid "Browse Branch Files..." +msgstr "Explorar arquivos do ramo..." + +#: git-gui.sh:2492 +msgid "Visualize Current Branch's History" +msgstr "Visualizar histórico do ramo atual" + +#: git-gui.sh:2496 +msgid "Visualize All Branch History" +msgstr "Visualizar histórico de todos os ramos" + +#: git-gui.sh:2503 +#, tcl-format +msgid "Browse %s's Files" +msgstr "Explorar arquivos de %s" + +#: git-gui.sh:2505 +#, tcl-format +msgid "Visualize %s's History" +msgstr "Visualizar histórico de %s" + +#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "Estatísticas do banco de dados" + +#: git-gui.sh:2513 lib/database.tcl:34 +msgid "Compress Database" +msgstr "Compactar banco de dados" + +#: git-gui.sh:2516 +msgid "Verify Database" +msgstr "Verificar banco de dados" + +#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 +msgid "Create Desktop Icon" +msgstr "Criar ícone na área de trabalho" + +#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 +msgid "Quit" +msgstr "Sair" + +#: git-gui.sh:2547 +msgid "Undo" +msgstr "Desfazer" + +#: git-gui.sh:2550 +msgid "Redo" +msgstr "Refazer" + +#: git-gui.sh:2554 git-gui.sh:3109 +msgid "Cut" +msgstr "Recortar" + +#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 +#: lib/console.tcl:69 +msgid "Copy" +msgstr "Copiar" + +#: git-gui.sh:2560 git-gui.sh:3115 +msgid "Paste" +msgstr "Colar" + +#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "Apagar" + +#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 +msgid "Select All" +msgstr "Selecionar tudo" + +#: git-gui.sh:2576 +msgid "Create..." +msgstr "Criar..." + +#: git-gui.sh:2582 +msgid "Checkout..." +msgstr "Checkout..." + +#: git-gui.sh:2588 +msgid "Rename..." +msgstr "Renomear..." + +#: git-gui.sh:2593 +msgid "Delete..." +msgstr "Apagar..." + +#: git-gui.sh:2598 +msgid "Reset..." +msgstr "Redefinir..." + +#: git-gui.sh:2608 +msgid "Done" +msgstr "Pronto" + +#: git-gui.sh:2610 +msgid "Commit@@verb" +msgstr "Salvar revisão" + +#: git-gui.sh:2619 git-gui.sh:3050 +msgid "New Commit" +msgstr "Nova revisão" + +#: git-gui.sh:2627 git-gui.sh:3057 +msgid "Amend Last Commit" +msgstr "Corrigir última revisão" + +#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "Atualizar" + +#: git-gui.sh:2643 +msgid "Stage To Commit" +msgstr "Marcar para revisão" + +#: git-gui.sh:2649 +msgid "Stage Changed Files To Commit" +msgstr "Marcar arquivos modificados" + +#: git-gui.sh:2655 +msgid "Unstage From Commit" +msgstr "Desmarcar" + +#: git-gui.sh:2661 lib/index.tcl:412 +msgid "Revert Changes" +msgstr "Reverter mudanças" + +#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 +msgid "Show Less Context" +msgstr "Mostrar menos contexto" + +#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 +msgid "Show More Context" +msgstr "Mostrar mais contexto" + +#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 +msgid "Sign Off" +msgstr "Assinar embaixo" + +#: git-gui.sh:2696 +msgid "Local Merge..." +msgstr "Mesclar localmente..." + +#: git-gui.sh:2701 +msgid "Abort Merge..." +msgstr "Abortar mesclagem..." + +#: git-gui.sh:2713 git-gui.sh:2741 +msgid "Add..." +msgstr "Adicionar..." + +#: git-gui.sh:2717 +msgid "Push..." +msgstr "Enviar..." + +#: git-gui.sh:2721 +msgid "Delete Branch..." +msgstr "Apagar ramo..." + +#: git-gui.sh:2731 git-gui.sh:3292 +msgid "Options..." +msgstr "Opções..." + +#: git-gui.sh:2742 +msgid "Remove..." +msgstr "Remover..." + +#: git-gui.sh:2751 lib/choose_repository.tcl:50 +msgid "Help" +msgstr "Ajuda" + +#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#, tcl-format +msgid "About %s" +msgstr "Sobre o %s" + +#: git-gui.sh:2783 +msgid "Online Documentation" +msgstr "Ajuda online" + +#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +msgid "Show SSH Key" +msgstr "Mostrar chave SSH" + +#: git-gui.sh:2893 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "" +"erro fatal: impossível executar \"stat\" em %s: Arquivo ou diretório não " +"encontrado" + +#: git-gui.sh:2926 +msgid "Current Branch:" +msgstr "Ramo atual:" + +#: git-gui.sh:2947 +msgid "Staged Changes (Will Commit)" +msgstr "Mudanças marcadas" + +#: git-gui.sh:2967 +msgid "Unstaged Changes" +msgstr "Mudanças não marcadas" + +#: git-gui.sh:3017 +msgid "Stage Changed" +msgstr "Marcar alterados" + +#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 +msgid "Push" +msgstr "Enviar" + +#: git-gui.sh:3071 +msgid "Initial Commit Message:" +msgstr "Descrição da revisão inicial:" + +#: git-gui.sh:3072 +msgid "Amended Commit Message:" +msgstr "Descrição da revisão corrigida:" + +#: git-gui.sh:3073 +msgid "Amended Initial Commit Message:" +msgstr "Descrição da revisão inicial corrigida:" + +#: git-gui.sh:3074 +msgid "Amended Merge Commit Message:" +msgstr "Descrição da revisão de mescla corrigida:" + +#: git-gui.sh:3075 +msgid "Merge Commit Message:" +msgstr "Descrição da revisão de mescla:" + +#: git-gui.sh:3076 +msgid "Commit Message:" +msgstr "Descrição da revisão:" + +#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 +msgid "Copy All" +msgstr "Copiar todos" + +#: git-gui.sh:3149 lib/blame.tcl:104 +msgid "File:" +msgstr "Arquivo:" + +#: git-gui.sh:3255 +msgid "Refresh" +msgstr "Atualizar" + +#: git-gui.sh:3276 +msgid "Decrease Font Size" +msgstr "Reduzir tamanho da fonte" + +#: git-gui.sh:3280 +msgid "Increase Font Size" +msgstr "Aumentar tamanho da fonte" + +#: git-gui.sh:3288 lib/blame.tcl:281 +msgid "Encoding" +msgstr "Codificação" + +#: git-gui.sh:3299 +msgid "Apply/Reverse Hunk" +msgstr "Aplicar/reverter trecho" + +#: git-gui.sh:3304 +msgid "Apply/Reverse Line" +msgstr "Aplicar/reverter linha" + +#: git-gui.sh:3323 +msgid "Run Merge Tool" +msgstr "Executar ferramenta de mescla" + +#: git-gui.sh:3328 +msgid "Use Remote Version" +msgstr "Usar versão remota" + +#: git-gui.sh:3332 +msgid "Use Local Version" +msgstr "Usar versão local" + +#: git-gui.sh:3336 +msgid "Revert To Base" +msgstr "Reverter para a versão-base" + +#: git-gui.sh:3354 +msgid "Visualize These Changes In The Submodule" +msgstr "Visualizar estas mudanças no sub-módulo" + +#: git-gui.sh:3358 +msgid "Visualize Current Branch History In The Submodule" +msgstr "Visualizar histórico do ramo atual no sub-módulo" + +#: git-gui.sh:3362 +msgid "Visualize All Branch History In The Submodule" +msgstr "Visualizar histórico de todos os camos no sub-módulo" + +#: git-gui.sh:3367 +msgid "Start git gui In The Submodule" +msgstr "Iniciar \"git gui\" no sub-módulo" + +#: git-gui.sh:3389 +msgid "Unstage Hunk From Commit" +msgstr "Desmarcar trecho para revisão" + +#: git-gui.sh:3391 +msgid "Unstage Lines From Commit" +msgstr "Desmarcar linhas para revisão" + +#: git-gui.sh:3393 +msgid "Unstage Line From Commit" +msgstr "Desmarcar linha para revisão" + +#: git-gui.sh:3396 +msgid "Stage Hunk For Commit" +msgstr "Marcar trecho para revisão" + +#: git-gui.sh:3398 +msgid "Stage Lines For Commit" +msgstr "Marcar linhas para revisão" + +#: git-gui.sh:3400 +msgid "Stage Line For Commit" +msgstr "Marcar linha para revisão" + +#: git-gui.sh:3424 +msgid "Initializing..." +msgstr "Iniciando..." + +#: git-gui.sh:3541 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" +"Possíveis problemas com as variáveis de ambiente.\n" +"\n" +"As seguintes variáveis de ambiente provavelmente serão\n" +"ignoradas por qualquer sub-processo do Git executado por\n" +"%s:\n" + +#: git-gui.sh:3570 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" +"\n" +"Isto se deve a um problema conhecido com os binários da Tcl \n" +"distribuídos com o Cygwin" + +#: git-gui.sh:3575 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" +"\n" +"\n" +"Uma boa alternativa para %s\n" +"é colocar os valores para o nome de usuário e e-mail\n" +"no seu arquivo \"~/.gitconfig\"\n" + +#: lib/about.tcl:26 +msgid "git-gui - a graphical user interface for Git." +msgstr "git-gui - uma interface gráfica para o Git" + +#: lib/blame.tcl:72 +msgid "File Viewer" +msgstr "Visualizador de arquivos" + +#: lib/blame.tcl:78 +msgid "Commit:" +msgstr "Revisão:" + +#: lib/blame.tcl:271 +msgid "Copy Commit" +msgstr "Copiar revisão" + +#: lib/blame.tcl:275 +msgid "Find Text..." +msgstr "Procurar texto..." + +#: lib/blame.tcl:284 +msgid "Do Full Copy Detection" +msgstr "Executar detecção completa de cópias" + +#: lib/blame.tcl:288 +msgid "Show History Context" +msgstr "Mostrar contexto do histórico" + +#: lib/blame.tcl:291 +msgid "Blame Parent Commit" +msgstr "Anotar revisão anterior" + +#: lib/blame.tcl:450 +#, tcl-format +msgid "Reading %s..." +msgstr "Lendo %s..." + +#: lib/blame.tcl:557 +msgid "Loading copy/move tracking annotations..." +msgstr "Carregando anotações de cópia/movimentação..." + +#: lib/blame.tcl:577 +msgid "lines annotated" +msgstr "linhas anotadas" + +#: lib/blame.tcl:769 +msgid "Loading original location annotations..." +msgstr "Carregando anotações originais..." + +#: lib/blame.tcl:772 +msgid "Annotation complete." +msgstr "Anotação completa." + +#: lib/blame.tcl:802 +msgid "Busy" +msgstr "Ocupado" + +#: lib/blame.tcl:803 +msgid "Annotation process is already running." +msgstr "O processo de anotação já está em execução" + +#: lib/blame.tcl:842 +msgid "Running thorough copy detection..." +msgstr "Executando detecção de cópia..." + +#: lib/blame.tcl:910 +msgid "Loading annotation..." +msgstr "Carregando anotações..." + +#: lib/blame.tcl:963 +msgid "Author:" +msgstr "Autor:" + +#: lib/blame.tcl:967 +msgid "Committer:" +msgstr "Revisor:" + +#: lib/blame.tcl:972 +msgid "Original File:" +msgstr "Arquivo original:" + +#: lib/blame.tcl:1020 +msgid "Cannot find HEAD commit:" +msgstr "Impossível encontrar revisão HEAD:" + +#: lib/blame.tcl:1075 +msgid "Cannot find parent commit:" +msgstr "Impossível encontrar revisão anterior:" + +#: lib/blame.tcl:1090 +msgid "Unable to display parent" +msgstr "Impossível exibir revisão anterior" + +#: lib/blame.tcl:1091 lib/diff.tcl:320 +msgid "Error loading diff:" +msgstr "Erro ao carregar as diferenças:" + +#: lib/blame.tcl:1231 +msgid "Originally By:" +msgstr "Originalmente por:" + +#: lib/blame.tcl:1237 +msgid "In File:" +msgstr "No arquivo:" + +#: lib/blame.tcl:1242 +msgid "Copied Or Moved Here By:" +msgstr "Copiado ou movido para cá por:" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "Efetuar checkout do ramo" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "Checkout" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282 +#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 +#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 +#: lib/transport.tcl:108 +msgid "Cancel" +msgstr "Cancelar" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328 +msgid "Revision" +msgstr "Revisão" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280 +msgid "Options" +msgstr "Opções" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "Obter ramo de rastreamento" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "Separar do ramo local" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "Criar ramo" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "Criar novo ramo" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 +msgid "Create" +msgstr "Criar" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "Nome do ramo" + +#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50 +msgid "Name:" +msgstr "Nome:" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "Coincidir nome do ramo de rastreamento" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "Revisão inicial" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "Atualizar ramo existente:" + +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "Não" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "Somente se for um avanço rápido" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 +msgid "Reset" +msgstr "Redefinir" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "Efetuar checkout após a criação" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "Selecione um ramo de rastreamento." + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "O ramo de rastreamento %s não é um ramo do repositório remoto." + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "Indique um nome para o ramo." + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "\"%s\" não é um nome de ramo válido" + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "Apagar ramo" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "Apagar ramo local" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "Ramos locais" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "Apagar somente se mesclado em" + +#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Forçar exclusão (não verificar se o ramo foi mesclado)" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "Os ramos seguintes não foram completamente mesclados em %s:" + +#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Recuperar ramos apagados é difícil.\n" +"\n" +"Apagar os ramos selecionados?" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" +"Erro ao apagar ramos:\n" +"%s" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "Renomear ramo" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "Renomear" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "Ramo:" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "Novo nome:" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "Selecione um ramo para renomear." + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "O ramo \"%s\" já existe." + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "Erro ao renomear \"%s\"." + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "Inciando..." + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "Navegador de arquivos" + +#: lib/browser.tcl:126 lib/browser.tcl:143 +#, tcl-format +msgid "Loading %s..." +msgstr "Carregando %s..." + +#: lib/browser.tcl:187 +msgid "[Up To Parent]" +msgstr "[Subir]" + +#: lib/browser.tcl:267 lib/browser.tcl:273 +msgid "Browse Branch Files" +msgstr "Explorar arquivos do ramo" + +#: lib/browser.tcl:278 lib/choose_repository.tcl:398 +#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 +#: lib/choose_repository.tcl:1028 +msgid "Browse" +msgstr "Explorar" + +#: lib/checkout_op.tcl:85 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "Obtendo %s de %s" + +#: lib/checkout_op.tcl:133 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "Erro fatal: impossível resolver %s" + +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 +#: lib/sshkey.tcl:53 +msgid "Close" +msgstr "Fechar" + +#: lib/checkout_op.tcl:175 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "O ramo \"%s\" não existe." + +#: lib/checkout_op.tcl:194 +#, tcl-format +msgid "Failed to configure simplified git-pull for '%s'." +msgstr "Erro ao configurar git-pull simplificado para \"%s\"." + +#: lib/checkout_op.tcl:229 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" +"O ramo \"%s\" já existe.\n" +"\n" +"Não é possível avançá-lo para %s.\n" +"É preciso mesclar." + +#: lib/checkout_op.tcl:243 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "Estratégia de mesclagem \"%s\" não suportada." + +#: lib/checkout_op.tcl:262 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "Erro ao atualizar \"%s\"." + +#: lib/checkout_op.tcl:274 +msgid "Staging area (index) is already locked." +msgstr "A área de marcação (staging area, index) já está bloqueada." + +#: lib/checkout_op.tcl:289 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"O último estado lido não confere com o estado atual.\n" +"\n" +"Outro programa do Git modificou o repositório desde a última leitura. Uma " +"atualização deve ser executada antes de alterar o ramo atual.\n" +"\n" +"A atualização começará automaticamente agora.\n" + +#: lib/checkout_op.tcl:345 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "Atualizando diretório de trabalho para \"%s\"..." + +#: lib/checkout_op.tcl:346 +msgid "files checked out" +msgstr "arquivos retirados" + +#: lib/checkout_op.tcl:376 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "Checkout de \"%s\" abortado (é preciso mesclar alguns arquivos)" + +#: lib/checkout_op.tcl:377 +msgid "File level merge required." +msgstr "Mesclagem de arquivos necessária." + +#: lib/checkout_op.tcl:381 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "Permanecendo no ramo \"%s\"." + +#: lib/checkout_op.tcl:452 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" +"Você não está mais em um ramo local\n" +"\n" +"Se você deseja um ramo, crie um agora a partir deste checkout." + +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 +#, tcl-format +msgid "Checked out '%s'." +msgstr "Checkout de \"%s\" concluído." + +#: lib/checkout_op.tcl:535 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "Redefinir \"%s\" para \"%s\" provocará a perda das seguintes revisões:" + +#: lib/checkout_op.tcl:557 +msgid "Recovering lost commits may not be easy." +msgstr "Recuperar revisões perdidas pode não ser fácil." + +#: lib/checkout_op.tcl:562 +#, tcl-format +msgid "Reset '%s'?" +msgstr "Redefinir \"%s\"?" + +#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 +msgid "Visualize" +msgstr "Visualizar" + +#: lib/checkout_op.tcl:635 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" +"Erro ao definir o ramo atual.\n" +"\n" +"Este diretório de trabalho está incompleto. Foi possível atualizar seus " +"arquivos, mas houve uma falha ao atualizar os arquivos internos do Git.\n" +"\n" +"Isto não deveria ter acontecido, %s terminará agora." + +#: lib/choose_font.tcl:39 +msgid "Select" +msgstr "Selecionar" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "Tipo da fonte" + +#: lib/choose_font.tcl:74 +msgid "Font Size" +msgstr "Tamanho da fonte" + +#: lib/choose_font.tcl:91 +msgid "Font Example" +msgstr "Exemplo" + +#: lib/choose_font.tcl:103 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" +"Este é um texto de exemplo.\n" +"Se você gostar deste texto, esta pode ser sua fonte." + +#: lib/choose_repository.tcl:28 +msgid "Git Gui" +msgstr "Git Gui" + +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 +msgid "Create New Repository" +msgstr "Criar novo repositório" + +#: lib/choose_repository.tcl:93 +msgid "New..." +msgstr "Novo..." + +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 +msgid "Clone Existing Repository" +msgstr "Clonar repositório existente" + +#: lib/choose_repository.tcl:106 +msgid "Clone..." +msgstr "Clonar..." + +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 +msgid "Open Existing Repository" +msgstr "Abrir repositório existente" + +#: lib/choose_repository.tcl:119 +msgid "Open..." +msgstr "Abrir..." + +#: lib/choose_repository.tcl:132 +msgid "Recent Repositories" +msgstr "Repositórios recentes" + +#: lib/choose_repository.tcl:138 +msgid "Open Recent Repository:" +msgstr "Abrir repositório recente:" + +#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 +#: lib/choose_repository.tcl:320 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "Erro ao criar repositório %s:" + +#: lib/choose_repository.tcl:391 +msgid "Directory:" +msgstr "Diretório:" + +#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 +#: lib/choose_repository.tcl:1052 +msgid "Git Repository" +msgstr "Repositório Git" + +#: lib/choose_repository.tcl:448 +#, tcl-format +msgid "Directory %s already exists." +msgstr "O diretório %s já existe." + +#: lib/choose_repository.tcl:452 +#, tcl-format +msgid "File %s already exists." +msgstr "O arquivo %s já existe." + +#: lib/choose_repository.tcl:466 +msgid "Clone" +msgstr "Clonar" + +#: lib/choose_repository.tcl:479 +msgid "Source Location:" +msgstr "Origem:" + +#: lib/choose_repository.tcl:490 +msgid "Target Directory:" +msgstr "Diretório de destino:" + +#: lib/choose_repository.tcl:502 +msgid "Clone Type:" +msgstr "Tipo de clonagem:" + +#: lib/choose_repository.tcl:508 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "Padrão (rápida, semi-redundante, com hardlinks)" + +#: lib/choose_repository.tcl:514 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "Cópia completa (mais lenta, backup redundante)" + +#: lib/choose_repository.tcl:520 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "Compartilhada (A mais rápida, não recomendada, sem backup)" + +#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 +#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 +#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "Este não é um repositório do Git: %s" + +#: lib/choose_repository.tcl:592 +msgid "Standard only available for local repository." +msgstr "Clonagens padrões só são possíveis em repositórios locais." + +#: lib/choose_repository.tcl:596 +msgid "Shared only available for local repository." +msgstr "Clonagens parciais só são possíveis em repositórios locais." + +#: lib/choose_repository.tcl:617 +#, tcl-format +msgid "Location %s already exists." +msgstr "O local %s já existe." + +#: lib/choose_repository.tcl:628 +msgid "Failed to configure origin" +msgstr "Erro ao configurar origem" + +#: lib/choose_repository.tcl:640 +msgid "Counting objects" +msgstr "Contando objetos" + +#: lib/choose_repository.tcl:641 +msgid "buckets" +msgstr "buckets" + +#: lib/choose_repository.tcl:665 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "Erro ao copiar objetos ou informações adicionais: %s" + +#: lib/choose_repository.tcl:701 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "Não há nada para clonar em %s." + +#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 +#: lib/choose_repository.tcl:929 +msgid "The 'master' branch has not been initialized." +msgstr "O ramo \"master\" não foi inicializado." + +#: lib/choose_repository.tcl:716 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "Não foi possível criar hardlinks, usando cópias convencionais." + +#: lib/choose_repository.tcl:728 +#, tcl-format +msgid "Cloning from %s" +msgstr "Clonando de %s" + +#: lib/choose_repository.tcl:759 +msgid "Copying objects" +msgstr "Copiando objetos" + +#: lib/choose_repository.tcl:760 +msgid "KiB" +msgstr "KiB" + +#: lib/choose_repository.tcl:784 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "Não foi possível copiar o objeto: %s" + +#: lib/choose_repository.tcl:794 +msgid "Linking objects" +msgstr "Ligando objetos" + +#: lib/choose_repository.tcl:795 +msgid "objects" +msgstr "objetos" + +#: lib/choose_repository.tcl:803 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "Não foi possível ligar o objeto: %s" + +#: lib/choose_repository.tcl:858 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" +"Não foi possível receber ramos ou objetos. Veja a saída do console para " +"detalhes." + +#: lib/choose_repository.tcl:869 +msgid "Cannot fetch tags. See console output for details." +msgstr "" +"Não foi possível receber as etiquetas. Veja a saída do console para detalhes." + +#: lib/choose_repository.tcl:893 +msgid "Cannot determine HEAD. See console output for details." +msgstr "" +"Não foi possível determinar a etiqueta HEAD. Veja a saída do console para " +"detalhes." + +#: lib/choose_repository.tcl:902 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "Não foi possível limpar %s" + +#: lib/choose_repository.tcl:908 +msgid "Clone failed." +msgstr "A clonagem falhou." + +#: lib/choose_repository.tcl:915 +msgid "No default branch obtained." +msgstr "O ramo padrão não foi recebido." + +#: lib/choose_repository.tcl:926 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "Não foi possível resolver %s como uma revisão." + +#: lib/choose_repository.tcl:938 +msgid "Creating working directory" +msgstr "Criando diretório de trabalho." + +#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 +#: lib/index.tcl:198 +msgid "files" +msgstr "arquivos" + +#: lib/choose_repository.tcl:968 +msgid "Initial file checkout failed." +msgstr "Erro ao efetuar checkout inicial." + +#: lib/choose_repository.tcl:1011 +msgid "Open" +msgstr "Abrir" + +#: lib/choose_repository.tcl:1021 +msgid "Repository:" +msgstr "Repositório:" + +#: lib/choose_repository.tcl:1072 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "Erro ao abrir o repositório %s:" + +#: lib/choose_rev.tcl:53 +msgid "This Detached Checkout" +msgstr "Este checkout" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "Expressão de revisão:" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "Ramo local" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "Ramo de rastreamento" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538 +msgid "Tag" +msgstr "Etiqueta" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "Revisão inválida: %s" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "Nenhuma revisão selecionada." + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "A expressão de revisão está vazia." + +#: lib/choose_rev.tcl:531 +msgid "Updated" +msgstr "Atualizado" + +#: lib/choose_rev.tcl:559 +msgid "URL" +msgstr "URL" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" +"Não há nada para corrigir.\n" +"\n" +"Você está prestes a criar uma revisão inicial. Não há revisão anterior para " +"corrigir.\n" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" +"Não é possível corrigir durante uma mesclagem.\n" +"\n" +"Você está em meio a uma operação de mesclagem que não foi completada. Não é " +"possível corrigir a revisão anterior a menos que você aborte a mescla atual " +"antes.\n" + +#: lib/commit.tcl:48 +msgid "Error loading commit data for amend:" +msgstr "Erro ao carregar dados da revisão para corrigir:" + +#: lib/commit.tcl:75 +msgid "Unable to obtain your identity:" +msgstr "Não foi possível obter a sua identidade:" + +#: lib/commit.tcl:80 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "Variável \"GIT_COMMITTER_IDENT\" inválida:" + +#: lib/commit.tcl:129 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "aviso: O Tcl não suporta a codificação \"%s\"." + +#: lib/commit.tcl:149 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"O último estado lido não confere com o estado atual.\n" +"\n" +"Outro programa do Git modificou o repositório desde a última leitura. Uma " +"atualização deve ser executada antes de criar outra revisão.\n" +"\n" +"A atualização começará automaticamente agora.\n" + +#: lib/commit.tcl:172 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"Não é possível salvar revisões para arquivos não mesclados.\n" +"\n" +"O arquivo %s possui conflitos de mesclagem. Você deve resolvê-los e marcar o " +"arquivo antes de salvar a revisão.\n" + +#: lib/commit.tcl:180 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" +"Estado desconhecido detectado para o arquivo %s.\n" +"\n" +"Este programa não pode salvar uma revisão para o arquivo %s.\n" + +#: lib/commit.tcl:188 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" +"Não há mudanças para salvar.\n" +"\n" +"Você deve marcar ao menos um arquivo antes de salvar a revisão.\n" + +#: lib/commit.tcl:203 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentence what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" +"Por favor, indique uma descrição para a revisão.\n" +"\n" +"Uma boa descrição tem o seguinte formato:\n" +"\n" +"- Primeira linha: descreve, em uma única frase, o que você fez.\n" +"- Segunda linha: em branco.\n" +"- Demais linhas: Descreve detalhadamente a revisão.\n" + +#: lib/commit.tcl:234 +msgid "Calling pre-commit hook..." +msgstr "Executando script \"pre-commit\"..." + +#: lib/commit.tcl:249 +msgid "Commit declined by pre-commit hook." +msgstr "A revisão foi bloqueada pelo script \"pre-commit\"." + +#: lib/commit.tcl:272 +msgid "Calling commit-msg hook..." +msgstr "Executando script \"commit-msg\"..." + +#: lib/commit.tcl:287 +msgid "Commit declined by commit-msg hook." +msgstr "Revisão bloqueada pelo script \"commit-msg\"." + +#: lib/commit.tcl:300 +msgid "Committing changes..." +msgstr "Salvando revisão..." + +#: lib/commit.tcl:316 +msgid "write-tree failed:" +msgstr "write-tree falhou:" + +#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 +msgid "Commit failed." +msgstr "A revisão falhou." + +#: lib/commit.tcl:334 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "A revisão %s parece estar corrompida." + +#: lib/commit.tcl:339 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" +"Não há alterações para salvar.\n" +"\n" +"Nenhum arquivo foi modificado e esta não é uma revisão de mesclagem.\n" +"\n" +"Uma atualização será executada automaticamente agora.\n" + +#: lib/commit.tcl:346 +msgid "No changes to commit." +msgstr "Não há alterações para salvar." + +#: lib/commit.tcl:360 +msgid "commit-tree failed:" +msgstr "commit-tree falhou:" + +#: lib/commit.tcl:381 +msgid "update-ref failed:" +msgstr "update-ref falhou:" + +#: lib/commit.tcl:469 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "Revisão %s criada: %s" + +#: lib/console.tcl:59 +msgid "Working... please wait..." +msgstr "Trabalhando... aguarde..." + +#: lib/console.tcl:186 +msgid "Success" +msgstr "Sucesso" + +#: lib/console.tcl:200 +msgid "Error: Command Failed" +msgstr "Erro: o comando falhou" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "Número de objetos soltos" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "Espaço ocupado pelos objetos soltos" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "Número de objetos compactados" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "Número de pacotes" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "Espaço ocupado pelos objetos compactados" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "Objetos compactados aguardando eliminação" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "Arquivos de lixo" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "Compactando banco de dados de objetos" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "Verificando banco de dados de objetos com fsck-objects" + +#: lib/database.tcl:107 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database.\n" +"\n" +"Compress the database now?" +msgstr "" +"Este repositório possui aproximadamente %i objetos soltos.\n" +"\n" +"Para manter o desempenho ótimo é altamente recomendado que você compacte o " +"banco de dados.\n" +"\n" +"Compactar o banco de dados agora?" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Data inválida recebida do Git: %s" + +#: lib/diff.tcl:64 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"Nenhuma diferença foi detectada.\n" +"\n" +"%s não possui mudanças.\n" +"\n" +"A data de modificação deste arquivo foi atualizada por outro aplicativo, mas " +"o conteúdo do arquivo não foi alterado.\n" +"\n" +"Uma atualização ser executada para encontrar outros arquivos que possam ter " +"o mesmo estado." + +#: lib/diff.tcl:104 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "Carregando diferenças de %s..." + +#: lib/diff.tcl:125 +msgid "" +"LOCAL: deleted\n" +"REMOTE:\n" +msgstr "" +"Local: apagado\n" +"Remoto:\n" + +#: lib/diff.tcl:130 +msgid "" +"REMOTE: deleted\n" +"LOCAL:\n" +msgstr "" +"Remoto: apagado\n" +"Local:\n" + +#: lib/diff.tcl:137 +msgid "LOCAL:\n" +msgstr "Local:\n" + +#: lib/diff.tcl:140 +msgid "REMOTE:\n" +msgstr "Remoto:\n" + +#: lib/diff.tcl:202 lib/diff.tcl:319 +#, tcl-format +msgid "Unable to display %s" +msgstr "Impossível exibir %s" + +#: lib/diff.tcl:203 +msgid "Error loading file:" +msgstr "Erro ao carregar o arquivo:" + +#: lib/diff.tcl:210 +msgid "Git Repository (subproject)" +msgstr "Repositório Git (sub-projeto)" + +#: lib/diff.tcl:222 +msgid "* Binary file (not showing content)." +msgstr "* Arquivo binário (conteúdo não exibido)." + +#: lib/diff.tcl:227 +#, tcl-format +msgid "" +"* Untracked file is %d bytes.\n" +"* Showing only first %d bytes.\n" +msgstr "" +"* O arquivo não rastreado possui %d bytes.\n" +"* Exibindo apenas os primeiros %d bytes.\n" + +#: lib/diff.tcl:233 +#, tcl-format +msgid "" +"\n" +"* Untracked file clipped here by %s.\n" +"* To see the entire file, use an external editor.\n" +msgstr "" +"\n" +"* O arquivo não rastreado foi cortado aqui por %s.\n" +"* Para ver o arquivo completo, use um editor externo.\n" + +#: lib/diff.tcl:482 +msgid "Failed to unstage selected hunk." +msgstr "Erro ao desmarcar o trecho selecionado." + +#: lib/diff.tcl:489 +msgid "Failed to stage selected hunk." +msgstr "Erro ao marcar o trecho selecionado." + +#: lib/diff.tcl:568 +msgid "Failed to unstage selected line." +msgstr "Erro ao desmarcar a linha selecionada." + +#: lib/diff.tcl:576 +msgid "Failed to stage selected line." +msgstr "Erro ao marcar a linha selecionada." + +#: lib/encoding.tcl:443 +msgid "Default" +msgstr "Padrão" + +#: lib/encoding.tcl:448 +#, tcl-format +msgid "System (%s)" +msgstr "Sistema (%s)" + +#: lib/encoding.tcl:459 lib/encoding.tcl:465 +msgid "Other" +msgstr "Outro" + +#: lib/error.tcl:20 lib/error.tcl:114 +msgid "error" +msgstr "Erro" + +#: lib/error.tcl:36 +msgid "warning" +msgstr "aviso" + +#: lib/error.tcl:94 +msgid "You must correct the above errors before committing." +msgstr "Você precisa corrigir os erros acima antes de salvar a revisão." + +#: lib/index.tcl:6 +msgid "Unable to unlock the index." +msgstr "Impossível desbloquear o índice." + +#: lib/index.tcl:15 +msgid "Index Error" +msgstr "Erro no índice" + +#: lib/index.tcl:17 +msgid "" +"Updating the Git index failed. A rescan will be automatically started to " +"resynchronize git-gui." +msgstr "" +"A atualização do índice do Git falhou. Uma atualização será executada " +"automaticamente para ressincronizar o Git GUI" + +#: lib/index.tcl:28 +msgid "Continue" +msgstr "Continuar" + +#: lib/index.tcl:31 +msgid "Unlock Index" +msgstr "Desbloquear índice" + +#: lib/index.tcl:289 +#, tcl-format +msgid "Unstaging %s from commit" +msgstr "Desmarcando %s para revisão" + +#: lib/index.tcl:328 +msgid "Ready to commit." +msgstr "Pronto para salvar a revisão." + +#: lib/index.tcl:341 +#, tcl-format +msgid "Adding %s" +msgstr "Adicionando %s" + +#: lib/index.tcl:398 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "Reverter as alterações no arquivo %s?" + +#: lib/index.tcl:400 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "Reverter as alterações nestes %i arquivos?" + +#: lib/index.tcl:408 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" +"Qualquer alteração não marcada será permanentemente perdida na reversão." + +#: lib/index.tcl:411 +msgid "Do Nothing" +msgstr "Não fazer nada" + +#: lib/index.tcl:429 +msgid "Reverting selected files" +msgstr "Revertendo os arquivos selecionados" + +#: lib/index.tcl:433 +#, tcl-format +msgid "Reverting %s" +msgstr "Revertendo %s" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"Não é possível mesclar durante uma correção.\n" +"\n" +"Você deve concluir a correção antes de começar qualquer mesclagem.\n" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"O último estado lido não confere com o estado atual.\n" +"\n" +"Outro programa do Git modificou o repositório desde a última leitura. Uma " +"atualização deve ser executada antes de efetuar uma mesclagem.\n" +"\n" +"A atualização começará automaticamente agora.\n" + +#: lib/merge.tcl:45 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"Há uma mesclagem com conflitos em progresso.\n" +"\n" +"O arquivo %s possui conflitos de mesclagem.\n" +"\n" +"Você deve resolvê-los, marcar o arquivo e salvar a revisão para completar a " +"mesclagem atual. Só então você poderá começar outra.\n" + +#: lib/merge.tcl:55 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"Você está em meio a uma mudança.\n" +"\n" +"O arquivo %s foi modificado.\n" +"\n" +"Você deve completar e salvar a revisão atual antes de começar uma mesclagem. " +"Ao fazê-lo, você poderá abortar a mesclagem caso haja algum erro.\n" + +#: lib/merge.tcl:107 +#, tcl-format +msgid "%s of %s" +msgstr "%s de %s" + +#: lib/merge.tcl:120 +#, tcl-format +msgid "Merging %s and %s..." +msgstr "Mesclando %s e %s..." + +#: lib/merge.tcl:131 +msgid "Merge completed successfully." +msgstr "Mesclagem completada com sucesso." + +#: lib/merge.tcl:133 +msgid "Merge failed. Conflict resolution is required." +msgstr "A mesclagem falhou. É necessário resolver conflitos." + +#: lib/merge.tcl:158 +#, tcl-format +msgid "Merge Into %s" +msgstr "Mesclar em %s" + +#: lib/merge.tcl:177 +msgid "Revision To Merge" +msgstr "Revisão para mesclar" + +#: lib/merge.tcl:212 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"Não é possível abortar durante uma correção.\n" +"\n" +"Você precisa finalizar a correção desta revisão.\n" + +#: lib/merge.tcl:222 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"Abortar mesclagem?\n" +"\n" +"Abortar a mesclagem atual implicará na perda de *TODAS* as mudanças não " +"salvas.\n" +"\n" +"Abortar a mesclagem atual?" + +#: lib/merge.tcl:228 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"Descartar as mudanças?\n" +"\n" +"Ao fazê-lo, *TODAS* as alterações não salvas serão perdidas.\n" +"\n" +"Continuar e descartar as mudanças atuais?" + +#: lib/merge.tcl:239 +msgid "Aborting" +msgstr "Abortando" + +#: lib/merge.tcl:239 +msgid "files reset" +msgstr "arquivos redefindos" + +#: lib/merge.tcl:267 +msgid "Abort failed." +msgstr "A tentativa de abortar a operação falhou" + +#: lib/merge.tcl:269 +msgid "Abort completed. Ready." +msgstr "Operação abortada com sucesso. Pronto." + +#: lib/mergetool.tcl:8 +msgid "Force resolution to the base version?" +msgstr "Forçar a resolução para a versão base?" + +#: lib/mergetool.tcl:9 +msgid "Force resolution to this branch?" +msgstr "Forçar resolução para este ramo?" + +#: lib/mergetool.tcl:10 +msgid "Force resolution to the other branch?" +msgstr "Forçar resolução para o outro ramo?" + +#: lib/mergetool.tcl:14 +#, tcl-format +msgid "" +"Note that the diff shows only conflicting changes.\n" +"\n" +"%s will be overwritten.\n" +"\n" +"This operation can be undone only by restarting the merge." +msgstr "" +"Note que o diff mostra apenas as mudanças conflitantes.\n" +"\n" +"%s será sobrescrito.\n" +"\n" +"Caso necessário, será preciso reiniciar a mesclagem para desfazer esta " +"operação." + +#: lib/mergetool.tcl:45 +#, tcl-format +msgid "File %s seems to have unresolved conflicts, still stage?" +msgstr "O arquivo %s parece ter conflitos não resolvidos. Marcar mesmo assim?" + +#: lib/mergetool.tcl:60 +#, tcl-format +msgid "Adding resolution for %s" +msgstr "Adicionando resolução para %s" + +#: lib/mergetool.tcl:141 +msgid "Cannot resolve deletion or link conflicts using a tool" +msgstr "" +"Impossível resolver conflitos envolvendo exclusão ou links de arquivos com " +"esta ferramenta." + +#: lib/mergetool.tcl:146 +msgid "Conflict file does not exist" +msgstr "O arquivo conflitante não existe" + +#: lib/mergetool.tcl:264 +#, tcl-format +msgid "Not a GUI merge tool: '%s'" +msgstr "Não é uma ferramenta de mesclagem gráfica: \"%s\"" + +#: lib/mergetool.tcl:268 +#, tcl-format +msgid "Unsupported merge tool '%s'" +msgstr "Ferramenta de mesclagem não suportada \"%s\"" + +#: lib/mergetool.tcl:303 +msgid "Merge tool is already running, terminate it?" +msgstr "A ferramenta de mesclagem já está em execução. Finalizar?" + +#: lib/mergetool.tcl:323 +#, tcl-format +msgid "" +"Error retrieving versions:\n" +"%s" +msgstr "" +"Erro ao obter as versões:\n" +"%s" + +#: lib/mergetool.tcl:343 +#, tcl-format +msgid "" +"Could not start the merge tool:\n" +"\n" +"%s" +msgstr "" +"Não foi possível iniciar a ferramenta de mesclagem:\n" +"\n" +"%s" + +#: lib/mergetool.tcl:347 +msgid "Running merge tool..." +msgstr "Executando ferramenta de mesclagem..." + +#: lib/mergetool.tcl:375 lib/mergetool.tcl:383 +msgid "Merge tool failed." +msgstr "Ferramenta de mesclagem falhou." + +#: lib/option.tcl:11 +#, tcl-format +msgid "Invalid global encoding '%s'" +msgstr "Codificação global inválida \"%s\"" + +#: lib/option.tcl:19 +#, tcl-format +msgid "Invalid repo encoding '%s'" +msgstr "Codificação do repositório inválida \"%s\"" + +#: lib/option.tcl:117 +msgid "Restore Defaults" +msgstr "Restaurar padrões" + +#: lib/option.tcl:121 +msgid "Save" +msgstr "Salvar" + +#: lib/option.tcl:131 +#, tcl-format +msgid "%s Repository" +msgstr "Repositório %s" + +#: lib/option.tcl:132 +msgid "Global (All Repositories)" +msgstr "Global (todos os repositórios)" + +#: lib/option.tcl:138 +msgid "User Name" +msgstr "Nome do usuário" + +#: lib/option.tcl:139 +msgid "Email Address" +msgstr "Endereço de e-mail" + +#: lib/option.tcl:141 +msgid "Summarize Merge Commits" +msgstr "Exibir sumário das revisões de mesclagem" + +#: lib/option.tcl:142 +msgid "Merge Verbosity" +msgstr "Nível de detalhamento da mesclagem" + +#: lib/option.tcl:143 +msgid "Show Diffstat After Merge" +msgstr "Exibir estatísticas após mesclagens" + +#: lib/option.tcl:144 +msgid "Use Merge Tool" +msgstr "Usar ferramenta de mesclagem" + +#: lib/option.tcl:146 +msgid "Trust File Modification Timestamps" +msgstr "Confiar nas datas de modificação dos arquivos" + +#: lib/option.tcl:147 +msgid "Prune Tracking Branches During Fetch" +msgstr "Eliminar ramos de rastreamento ao receber" + +#: lib/option.tcl:148 +msgid "Match Tracking Branches" +msgstr "Coincidir ramos de rastreamento" + +#: lib/option.tcl:149 +msgid "Blame Copy Only On Changed Files" +msgstr "Detectar cópias somente em arquivos modificados" + +#: lib/option.tcl:150 +msgid "Minimum Letters To Blame Copy On" +msgstr "Número mínimo de letras para detectar cópias" + +#: lib/option.tcl:151 +msgid "Blame History Context Radius (days)" +msgstr "Extensão do contexto de detecção (em dias)" + +#: lib/option.tcl:152 +msgid "Number of Diff Context Lines" +msgstr "Número de linhas para o diff contextual" + +#: lib/option.tcl:153 +msgid "Commit Message Text Width" +msgstr "Largura do texto da descrição da revisão" + +#: lib/option.tcl:154 +msgid "New Branch Name Template" +msgstr "Modelo de nome para novos ramos" + +#: lib/option.tcl:155 +msgid "Default File Contents Encoding" +msgstr "Codificação padrão dos arquivos" + +#: lib/option.tcl:203 +msgid "Change" +msgstr "Alterar" + +#: lib/option.tcl:230 +msgid "Spelling Dictionary:" +msgstr "Dicionário para o verificador ortográfico:" + +#: lib/option.tcl:254 +msgid "Change Font" +msgstr "Mudar fonte" + +#: lib/option.tcl:258 +#, tcl-format +msgid "Choose %s" +msgstr "Escolher %s" + +#: lib/option.tcl:264 +msgid "pt." +msgstr "pt." + +#: lib/option.tcl:278 +msgid "Preferences" +msgstr "Preferências" + +#: lib/option.tcl:314 +msgid "Failed to completely save options:" +msgstr "Houve um erro ao salvar as opções:" + +#: lib/remote.tcl:163 +msgid "Remove Remote" +msgstr "Excluir" + +#: lib/remote.tcl:168 +msgid "Prune from" +msgstr "Limpar de" + +#: lib/remote.tcl:173 +msgid "Fetch from" +msgstr "Receber de" + +#: lib/remote.tcl:215 +msgid "Push to" +msgstr "Enviar para" + +#: lib/remote_add.tcl:19 +msgid "Add Remote" +msgstr "Adicionar repositório remoto" + +#: lib/remote_add.tcl:24 +msgid "Add New Remote" +msgstr "Adicionar novo repositório remoto" + +#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36 +msgid "Add" +msgstr "Adicionar" + +#: lib/remote_add.tcl:37 +msgid "Remote Details" +msgstr "Detalhes do repositório remoto" + +#: lib/remote_add.tcl:50 +msgid "Location:" +msgstr "Local:" + +#: lib/remote_add.tcl:62 +msgid "Further Action" +msgstr "Ações adicionais" + +#: lib/remote_add.tcl:65 +msgid "Fetch Immediately" +msgstr "Receber imediatamente" + +#: lib/remote_add.tcl:71 +msgid "Initialize Remote Repository and Push" +msgstr "Inicializar repositório remoto e enviar" + +#: lib/remote_add.tcl:77 +msgid "Do Nothing Else Now" +msgstr "Não fazer nada agora" + +#: lib/remote_add.tcl:101 +msgid "Please supply a remote name." +msgstr "Por favor, indique um nome para o repositório remoto." + +#: lib/remote_add.tcl:114 +#, tcl-format +msgid "'%s' is not an acceptable remote name." +msgstr "\"%s\" não é um nome válido para um repositório remoto." + +#: lib/remote_add.tcl:125 +#, tcl-format +msgid "Failed to add remote '%s' of location '%s'." +msgstr "Erro ao adicionar repositório remoto \"%s\" do local \"%s\":" + +#: lib/remote_add.tcl:133 lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "receber %s" + +#: lib/remote_add.tcl:134 +#, tcl-format +msgid "Fetching the %s" +msgstr "Recebendo o %s" + +#: lib/remote_add.tcl:157 +#, tcl-format +msgid "Do not know how to initialize repository at location '%s'." +msgstr "Não sabe como inicializar o repositório remoto em \"%s\"." + +#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63 +#: lib/transport.tcl:81 +#, tcl-format +msgid "push %s" +msgstr "enviar %s" + +#: lib/remote_add.tcl:164 +#, tcl-format +msgid "Setting up the %s (at %s)" +msgstr "Configurando %s (em %s)" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Branch Remotely" +msgstr "Apagar ramo remoto" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "Do repositório" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134 +msgid "Remote:" +msgstr "Remoto:" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149 +msgid "Arbitrary Location:" +msgstr "Outro local:" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "Ramos" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "Apagar somente se" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "Mesclado em:" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "É preciso indicar um ramo para \"Mesclado em\"." + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" +"Os seguintes ramos não estão inteiramente mesclados em %s:\n" +"\n" +" - %s" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"Um ou mais testes de mesclagem falharam porque você não possui as revisões " +"necessárias. Tente receber revisões de %s primeiro." + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "Por favor selecione um ou mais ramos para apagar." + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "Apagar ramos de %s" + +#: lib/remote_branch_delete.tcl:292 +msgid "No repository selected." +msgstr "Nenhum repositório foi selecionado." + +#: lib/remote_branch_delete.tcl:297 +#, tcl-format +msgid "Scanning %s..." +msgstr "Atualizando %s..." + +#: lib/search.tcl:21 +msgid "Find:" +msgstr "Encontrar:" + +#: lib/search.tcl:23 +msgid "Next" +msgstr "Próximo" + +#: lib/search.tcl:24 +msgid "Prev" +msgstr "Anterior" + +#: lib/search.tcl:25 +msgid "Case-Sensitive" +msgstr "Sensível a maiúsculas/minúsculas" + +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 +msgid "Cannot write shortcut:" +msgstr "Não foi possível gravar o atalho:" + +#: lib/shortcut.tcl:137 +msgid "Cannot write icon:" +msgstr "Não foi possível gravar o ícone:" + +#: lib/spellcheck.tcl:57 +msgid "Unsupported spell checker" +msgstr "Verificador ortográfico não suportado" + +#: lib/spellcheck.tcl:65 +msgid "Spell checking is unavailable" +msgstr "Verificação ortográfica indisponível" + +#: lib/spellcheck.tcl:68 +msgid "Invalid spell checking configuration" +msgstr "Configuração do verificador ortográfico inválida" + +#: lib/spellcheck.tcl:70 +#, tcl-format +msgid "Reverting dictionary to %s." +msgstr "Revertendo dicionário para %s." + +#: lib/spellcheck.tcl:73 +msgid "Spell checker silently failed on startup" +msgstr "O verificador ortográfico falhou sem relatar nenhum erro" + +#: lib/spellcheck.tcl:80 +msgid "Unrecognized spell checker" +msgstr "Verificador ortográfico não reconhecido" + +#: lib/spellcheck.tcl:186 +msgid "No Suggestions" +msgstr "Sem sugestões" + +#: lib/spellcheck.tcl:388 +msgid "Unexpected EOF from spell checker" +msgstr "Final de arquivo inesperado recebido do verificador ortográfico" + +#: lib/spellcheck.tcl:392 +msgid "Spell Checker Failed" +msgstr "A verificação ortográfica falhou" + +#: lib/sshkey.tcl:31 +msgid "No keys found." +msgstr "Nenhuma chave encontrada" + +#: lib/sshkey.tcl:34 +#, tcl-format +msgid "Found a public key in: %s" +msgstr "Chave pública encontrada em: %s" + +#: lib/sshkey.tcl:40 +msgid "Generate Key" +msgstr "Gerar chave" + +#: lib/sshkey.tcl:56 +msgid "Copy To Clipboard" +msgstr "Copiar para a área de transferência" + +#: lib/sshkey.tcl:70 +msgid "Your OpenSSH Public Key" +msgstr "Sua chave pública OpenSSH" + +#: lib/sshkey.tcl:78 +msgid "Generating..." +msgstr "Gerando..." + +#: lib/sshkey.tcl:84 +#, tcl-format +msgid "" +"Could not start ssh-keygen:\n" +"\n" +"%s" +msgstr "" +"Impossível iniciar ssh-keygen:\n" +"\n" +"%s" + +#: lib/sshkey.tcl:111 +msgid "Generation failed." +msgstr "A geração da chave falhou." + +#: lib/sshkey.tcl:118 +msgid "Generation succeded, but no keys found." +msgstr "A geração da chave foi bem-sucedida, mas nenhuma chave foi encontrada." + +#: lib/sshkey.tcl:121 +#, tcl-format +msgid "Your key is in: %s" +msgstr "Sua chave em: %s" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%s ... %*i de %*i %s (%3i%%)" + +#: lib/tools.tcl:75 +#, tcl-format +msgid "Running %s requires a selected file." +msgstr "É preciso selecionar um arquivo para executar %s." + +#: lib/tools.tcl:90 +#, tcl-format +msgid "Are you sure you want to run %s?" +msgstr "Você tem certeza que deseja executar %s?" + +#: lib/tools.tcl:110 +#, tcl-format +msgid "Tool: %s" +msgstr "Ferramenta: %s" + +#: lib/tools.tcl:111 +#, tcl-format +msgid "Running: %s" +msgstr "Executando: %s" + +#: lib/tools.tcl:149 +#, tcl-format +msgid "Tool completed successfully: %s" +msgstr "Execução completada com sucesso: %s" + +#: lib/tools.tcl:151 +#, tcl-format +msgid "Tool failed: %s" +msgstr "Ferramenta falhou: %s" + +#: lib/tools_dlg.tcl:22 +msgid "Add Tool" +msgstr "Adicionar ferramenta" + +#: lib/tools_dlg.tcl:28 +msgid "Add New Tool Command" +msgstr "Adicionar novo comando de ferramenta" + +#: lib/tools_dlg.tcl:33 +msgid "Add globally" +msgstr "Adicionar globalmente" + +#: lib/tools_dlg.tcl:45 +msgid "Tool Details" +msgstr "Detalhes da ferramenta" + +#: lib/tools_dlg.tcl:48 +msgid "Use '/' separators to create a submenu tree:" +msgstr "Use o separador \"/\" para criar uma árvore de sub-menus:" + +#: lib/tools_dlg.tcl:61 +msgid "Command:" +msgstr "Comando:" + +#: lib/tools_dlg.tcl:74 +msgid "Show a dialog before running" +msgstr "Exibir uma caixa de diálogo antes de executar" + +#: lib/tools_dlg.tcl:80 +msgid "Ask the user to select a revision (sets $REVISION)" +msgstr "Solicitar a seleção de uma revisão (a variável $REVISION)" + +#: lib/tools_dlg.tcl:85 +msgid "Ask the user for additional arguments (sets $ARGS)" +msgstr "Solicitar argumentos adicionais (define a variável $ARGS)" + +#: lib/tools_dlg.tcl:92 +msgid "Don't show the command output window" +msgstr "Não exibir a janela de saída do comando" + +#: lib/tools_dlg.tcl:97 +msgid "Run only if a diff is selected ($FILENAME not empty)" +msgstr "Executar apenas se houver um diff selecionado ($FILENAME não-vazio)" + +#: lib/tools_dlg.tcl:121 +msgid "Please supply a name for the tool." +msgstr "Por favor, indique um nome para a ferramenta." + +#: lib/tools_dlg.tcl:129 +#, tcl-format +msgid "Tool '%s' already exists." +msgstr "A ferramenta \"%s\" já existe." + +#: lib/tools_dlg.tcl:151 +#, tcl-format +msgid "" +"Could not add tool:\n" +"%s" +msgstr "" +"Não foi possível adicionar a ferramenta:\n" +"%s" + +#: lib/tools_dlg.tcl:190 +msgid "Remove Tool" +msgstr "Excluir ferramenta" + +#: lib/tools_dlg.tcl:196 +msgid "Remove Tool Commands" +msgstr "Excluir comando de ferramenta" + +#: lib/tools_dlg.tcl:200 +msgid "Remove" +msgstr "Excluir" + +#: lib/tools_dlg.tcl:236 +msgid "(Blue denotes repository-local tools)" +msgstr "(Azul indica ferramentas do repositório local)" + +#: lib/tools_dlg.tcl:297 +#, tcl-format +msgid "Run Command: %s" +msgstr "Executar comando: %s" + +#: lib/tools_dlg.tcl:311 +msgid "Arguments" +msgstr "Argumentos" + +#: lib/tools_dlg.tcl:348 +msgid "OK" +msgstr "OK" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "Recebendo novas mudanças de %s" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "Limpar %s" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "Limpando ramos excluídos de %s" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "Enviando mudanças para %s" + +#: lib/transport.tcl:64 +#, tcl-format +msgid "Mirroring to %s" +msgstr "Duplicando para %s" + +#: lib/transport.tcl:82 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "Enviando %s %s para %s" + +#: lib/transport.tcl:100 +msgid "Push Branches" +msgstr "Enviar ramos" + +#: lib/transport.tcl:114 +msgid "Source Branches" +msgstr "Ramos de origem" + +#: lib/transport.tcl:131 +msgid "Destination Repository" +msgstr "Repositório de destino" + +#: lib/transport.tcl:169 +msgid "Transfer Options" +msgstr "Opções de transferência" + +#: lib/transport.tcl:171 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "Sobrescrever ramos existentes (pode descartar mudanças)" + +#: lib/transport.tcl:175 +msgid "Use thin pack (for slow network connections)" +msgstr "Usar compactação minimalista (para redes lentas)" + +#: lib/transport.tcl:179 +msgid "Include tags" +msgstr "Incluir etiquetas" diff --git a/git-gui/po/ru.po b/git-gui/po/ru.po index 364c074c50..30f4b77dac 100644 --- a/git-gui/po/ru.po +++ b/git-gui/po/ru.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-08 08:31-0800\n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" "PO-Revision-Date: 2007-10-22 22:30-0200\n" "Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n" "Language-Team: Russian Translation <git@vger.kernel.org>\n" @@ -15,33 +15,33 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847 -#: git-gui.sh:866 +#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 +#: git-gui.sh:922 msgid "git-gui: fatal error" msgstr "git-gui: критическая ошибка" -#: git-gui.sh:689 +#: git-gui.sh:743 #, tcl-format msgid "Invalid font specified in %s:" msgstr "В %s установлен неверный шрифт:" -#: git-gui.sh:723 +#: git-gui.sh:779 msgid "Main Font" msgstr "Шрифт интерфейса" -#: git-gui.sh:724 +#: git-gui.sh:780 msgid "Diff/Console Font" msgstr "Шрифт консоли и изменений (diff)" -#: git-gui.sh:738 +#: git-gui.sh:794 msgid "Cannot find git in PATH." msgstr "git не найден в PATH." -#: git-gui.sh:765 +#: git-gui.sh:821 msgid "Cannot parse Git version string:" msgstr "Невозможно распознать строку версии Git: " -#: git-gui.sh:783 +#: git-gui.sh:839 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -60,450 +60,474 @@ msgstr "" "\n" "Принять '%s' как версию 1.5.0?\n" -#: git-gui.sh:1062 +#: git-gui.sh:1128 msgid "Git directory not found:" msgstr "Каталог Git не найден:" -#: git-gui.sh:1069 +#: git-gui.sh:1146 msgid "Cannot move to top of working directory:" msgstr "Невозможно перейти к корню рабочего каталога репозитория: " -#: git-gui.sh:1076 -msgid "Cannot use funny .git directory:" -msgstr "Каталог .git испорчен: " +#: git-gui.sh:1154 +msgid "Cannot use bare repository:" +msgstr "Невозможно использование репозитория без рабочего каталога:" -#: git-gui.sh:1081 +#: git-gui.sh:1162 msgid "No working directory" msgstr "Отсутствует рабочий каталог" -#: git-gui.sh:1247 lib/checkout_op.tcl:305 +#: git-gui.sh:1334 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "Обновление информации о состоянии файлов..." -#: git-gui.sh:1303 +#: git-gui.sh:1390 msgid "Scanning for modified files ..." msgstr "Поиск измененных файлов..." -#: git-gui.sh:1367 +#: git-gui.sh:1454 msgid "Calling prepare-commit-msg hook..." msgstr "Вызов программы поддержки репозитория prepare-commit-msg..." -#: git-gui.sh:1384 +#: git-gui.sh:1471 msgid "Commit declined by prepare-commit-msg hook." msgstr "" "Сохранение прервано программой поддержки репозитория prepare-commit-msg" -#: git-gui.sh:1542 lib/browser.tcl:246 +#: git-gui.sh:1629 lib/browser.tcl:246 msgid "Ready." msgstr "Готово." -#: git-gui.sh:1726 +#: git-gui.sh:1787 #, tcl-format msgid "Displaying only %s of %s files." msgstr "Показано %s из %s файлов." -#: git-gui.sh:1819 +#: git-gui.sh:1913 msgid "Unmodified" msgstr "Не изменено" -#: git-gui.sh:1821 +#: git-gui.sh:1915 msgid "Modified, not staged" msgstr "Изменено, не подготовлено" -#: git-gui.sh:1822 git-gui.sh:1830 +#: git-gui.sh:1916 git-gui.sh:1924 msgid "Staged for commit" msgstr "Подготовлено для сохранения" -#: git-gui.sh:1823 git-gui.sh:1831 +#: git-gui.sh:1917 git-gui.sh:1925 msgid "Portions staged for commit" msgstr "Части, подготовленные для сохранения" -#: git-gui.sh:1824 git-gui.sh:1832 +#: git-gui.sh:1918 git-gui.sh:1926 msgid "Staged for commit, missing" msgstr "Подготовлено для сохранения, отсутствует" -#: git-gui.sh:1826 +#: git-gui.sh:1920 msgid "File type changed, not staged" msgstr "Тип файла изменён, не подготовлено" -#: git-gui.sh:1827 +#: git-gui.sh:1921 msgid "File type changed, staged" msgstr "Тип файла изменён, подготовлено" -#: git-gui.sh:1829 +#: git-gui.sh:1923 msgid "Untracked, not staged" msgstr "Не отслеживается, не подготовлено" -#: git-gui.sh:1834 +#: git-gui.sh:1928 msgid "Missing" msgstr "Отсутствует" -#: git-gui.sh:1835 +#: git-gui.sh:1929 msgid "Staged for removal" msgstr "Подготовлено для удаления" -#: git-gui.sh:1836 +#: git-gui.sh:1930 msgid "Staged for removal, still present" msgstr "Подготовлено для удаления, еще не удалено" -#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841 -#: git-gui.sh:1842 git-gui.sh:1843 +#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 +#: git-gui.sh:1936 git-gui.sh:1937 msgid "Requires merge resolution" msgstr "Требуется разрешение конфликта при слиянии" -#: git-gui.sh:1878 +#: git-gui.sh:1972 msgid "Starting gitk... please wait..." msgstr "Запускается gitk... Подождите, пожалуйста..." -#: git-gui.sh:1887 +#: git-gui.sh:1984 msgid "Couldn't find gitk in PATH" msgstr "gitk не найден в PATH." -#: git-gui.sh:2280 lib/choose_repository.tcl:36 +#: git-gui.sh:2043 +msgid "Couldn't find git gui in PATH" +msgstr "git gui не найден в PATH." + +#: git-gui.sh:2455 lib/choose_repository.tcl:36 msgid "Repository" msgstr "Репозиторий" -#: git-gui.sh:2281 +#: git-gui.sh:2456 msgid "Edit" msgstr "Редактировать" -#: git-gui.sh:2283 lib/choose_rev.tcl:561 +#: git-gui.sh:2458 lib/choose_rev.tcl:561 msgid "Branch" msgstr "Ветвь" -#: git-gui.sh:2286 lib/choose_rev.tcl:548 +#: git-gui.sh:2461 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "Состояние" -#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" msgstr "Слияние" -#: git-gui.sh:2290 lib/choose_rev.tcl:557 +#: git-gui.sh:2465 lib/choose_rev.tcl:557 msgid "Remote" msgstr "Внешние репозитории" -#: git-gui.sh:2293 +#: git-gui.sh:2468 msgid "Tools" msgstr "Вспомогательные операции" -#: git-gui.sh:2302 +#: git-gui.sh:2477 msgid "Explore Working Copy" msgstr "Просмотр рабочего каталога" -#: git-gui.sh:2307 +#: git-gui.sh:2483 msgid "Browse Current Branch's Files" msgstr "Просмотреть файлы текущей ветви" -#: git-gui.sh:2311 +#: git-gui.sh:2487 msgid "Browse Branch Files..." msgstr "Показать файлы ветви..." -#: git-gui.sh:2316 +#: git-gui.sh:2492 msgid "Visualize Current Branch's History" msgstr "Показать историю текущей ветви" -#: git-gui.sh:2320 +#: git-gui.sh:2496 msgid "Visualize All Branch History" msgstr "Показать историю всех ветвей" -#: git-gui.sh:2327 +#: git-gui.sh:2503 #, tcl-format msgid "Browse %s's Files" msgstr "Показать файлы ветви %s" -#: git-gui.sh:2329 +#: git-gui.sh:2505 #, tcl-format msgid "Visualize %s's History" msgstr "Показать историю ветви %s" -#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "Статистика базы данных" -#: git-gui.sh:2337 lib/database.tcl:34 +#: git-gui.sh:2513 lib/database.tcl:34 msgid "Compress Database" msgstr "Сжать базу данных" -#: git-gui.sh:2340 +#: git-gui.sh:2516 msgid "Verify Database" msgstr "Проверить базу данных" -#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7 -#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 msgid "Create Desktop Icon" msgstr "Создать ярлык на рабочем столе" -#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 +#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" msgstr "Выход" -#: git-gui.sh:2371 +#: git-gui.sh:2547 msgid "Undo" msgstr "Отменить" -#: git-gui.sh:2374 +#: git-gui.sh:2550 msgid "Redo" msgstr "Повторить" -#: git-gui.sh:2378 git-gui.sh:2937 +#: git-gui.sh:2554 git-gui.sh:3109 msgid "Cut" msgstr "Вырезать" -#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096 +#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 #: lib/console.tcl:69 msgid "Copy" msgstr "Копировать" -#: git-gui.sh:2384 git-gui.sh:2943 +#: git-gui.sh:2560 git-gui.sh:3115 msgid "Paste" msgstr "Вставить" -#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26 +#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "Удалить" -#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71 +#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 msgid "Select All" msgstr "Выделить все" -#: git-gui.sh:2400 +#: git-gui.sh:2576 msgid "Create..." msgstr "Создать..." -#: git-gui.sh:2406 +#: git-gui.sh:2582 msgid "Checkout..." msgstr "Перейти..." -#: git-gui.sh:2412 +#: git-gui.sh:2588 msgid "Rename..." msgstr "Переименовать..." -#: git-gui.sh:2417 +#: git-gui.sh:2593 msgid "Delete..." msgstr "Удалить..." -#: git-gui.sh:2422 +#: git-gui.sh:2598 msgid "Reset..." msgstr "Сбросить..." -#: git-gui.sh:2432 +#: git-gui.sh:2608 msgid "Done" msgstr "Завершено" -#: git-gui.sh:2434 +#: git-gui.sh:2610 msgid "Commit@@verb" msgstr "Сохранить" -#: git-gui.sh:2443 git-gui.sh:2878 +#: git-gui.sh:2619 git-gui.sh:3050 msgid "New Commit" msgstr "Новое состояние" -#: git-gui.sh:2451 git-gui.sh:2885 +#: git-gui.sh:2627 git-gui.sh:3057 msgid "Amend Last Commit" msgstr "Исправить последнее состояние" -#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "Перечитать" -#: git-gui.sh:2467 +#: git-gui.sh:2643 msgid "Stage To Commit" msgstr "Подготовить для сохранения" -#: git-gui.sh:2473 +#: git-gui.sh:2649 msgid "Stage Changed Files To Commit" msgstr "Подготовить измененные файлы для сохранения" -#: git-gui.sh:2479 +#: git-gui.sh:2655 msgid "Unstage From Commit" msgstr "Убрать из подготовленного" -#: git-gui.sh:2484 lib/index.tcl:410 +#: git-gui.sh:2661 lib/index.tcl:412 msgid "Revert Changes" msgstr "Отменить изменения" -#: git-gui.sh:2491 git-gui.sh:3083 +#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 msgid "Show Less Context" msgstr "Меньше контекста" -#: git-gui.sh:2495 git-gui.sh:3087 +#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 msgid "Show More Context" msgstr "Больше контекста" -#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961 +#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 msgid "Sign Off" msgstr "Вставить Signed-off-by" -#: git-gui.sh:2518 +#: git-gui.sh:2696 msgid "Local Merge..." msgstr "Локальное слияние..." -#: git-gui.sh:2523 +#: git-gui.sh:2701 msgid "Abort Merge..." msgstr "Прервать слияние..." -#: git-gui.sh:2535 git-gui.sh:2575 +#: git-gui.sh:2713 git-gui.sh:2741 msgid "Add..." msgstr "Добавить..." -#: git-gui.sh:2539 +#: git-gui.sh:2717 msgid "Push..." msgstr "Отправить..." -#: git-gui.sh:2543 +#: git-gui.sh:2721 msgid "Delete Branch..." msgstr "Удалить ветвь..." -#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 -#, tcl-format -msgid "About %s" -msgstr "О %s" - -#: git-gui.sh:2557 -msgid "Preferences..." -msgstr "Настройки..." - -#: git-gui.sh:2565 git-gui.sh:3129 +#: git-gui.sh:2731 git-gui.sh:3292 msgid "Options..." msgstr "Настройки..." -#: git-gui.sh:2576 +#: git-gui.sh:2742 msgid "Remove..." msgstr "Удалить..." -#: git-gui.sh:2585 lib/choose_repository.tcl:50 +#: git-gui.sh:2751 lib/choose_repository.tcl:50 msgid "Help" msgstr "Помощь" -#: git-gui.sh:2611 +#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#, tcl-format +msgid "About %s" +msgstr "О %s" + +#: git-gui.sh:2783 msgid "Online Documentation" msgstr "Документация в интернете" -#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 msgid "Show SSH Key" msgstr "Показать ключ SSH" -#: git-gui.sh:2721 +#: git-gui.sh:2893 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "критическая ошибка: %s: нет такого файла или каталога" -#: git-gui.sh:2754 +#: git-gui.sh:2926 msgid "Current Branch:" msgstr "Текущая ветвь:" -#: git-gui.sh:2775 +#: git-gui.sh:2947 msgid "Staged Changes (Will Commit)" msgstr "Подготовлено (будет сохранено)" -#: git-gui.sh:2795 +#: git-gui.sh:2967 msgid "Unstaged Changes" msgstr "Изменено (не будет сохранено)" -#: git-gui.sh:2845 +#: git-gui.sh:3017 msgid "Stage Changed" msgstr "Подготовить все" -#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193 +#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" msgstr "Отправить" -#: git-gui.sh:2899 +#: git-gui.sh:3071 msgid "Initial Commit Message:" msgstr "Комментарий к первому состоянию:" -#: git-gui.sh:2900 +#: git-gui.sh:3072 msgid "Amended Commit Message:" msgstr "Комментарий к исправленному состоянию:" -#: git-gui.sh:2901 +#: git-gui.sh:3073 msgid "Amended Initial Commit Message:" msgstr "Комментарий к исправленному первоначальному состоянию:" -#: git-gui.sh:2902 +#: git-gui.sh:3074 msgid "Amended Merge Commit Message:" msgstr "Комментарий к исправленному слиянию:" -#: git-gui.sh:2903 +#: git-gui.sh:3075 msgid "Merge Commit Message:" msgstr "Комментарий к слиянию:" -#: git-gui.sh:2904 +#: git-gui.sh:3076 msgid "Commit Message:" msgstr "Комментарий к состоянию:" -#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73 +#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 msgid "Copy All" msgstr "Копировать все" -#: git-gui.sh:2977 lib/blame.tcl:104 +#: git-gui.sh:3149 lib/blame.tcl:104 msgid "File:" msgstr "Файл:" -#: git-gui.sh:3092 +#: git-gui.sh:3255 msgid "Refresh" msgstr "Обновить" -#: git-gui.sh:3113 +#: git-gui.sh:3276 msgid "Decrease Font Size" msgstr "Уменьшить размер шрифта" -#: git-gui.sh:3117 +#: git-gui.sh:3280 msgid "Increase Font Size" msgstr "Увеличить размер шрифта" -#: git-gui.sh:3125 lib/blame.tcl:281 +#: git-gui.sh:3288 lib/blame.tcl:281 msgid "Encoding" msgstr "Кодировка" -#: git-gui.sh:3136 +#: git-gui.sh:3299 msgid "Apply/Reverse Hunk" msgstr "Применить/Убрать изменение" -#: git-gui.sh:3141 +#: git-gui.sh:3304 msgid "Apply/Reverse Line" msgstr "Применить/Убрать строку" -#: git-gui.sh:3151 +#: git-gui.sh:3323 msgid "Run Merge Tool" msgstr "Запустить программу слияния" -#: git-gui.sh:3156 +#: git-gui.sh:3328 msgid "Use Remote Version" msgstr "Взять внешнюю версию" -#: git-gui.sh:3160 +#: git-gui.sh:3332 msgid "Use Local Version" msgstr "Взять локальную версию" -#: git-gui.sh:3164 +#: git-gui.sh:3336 msgid "Revert To Base" msgstr "Отменить изменения" -#: git-gui.sh:3183 +#: git-gui.sh:3354 +msgid "Visualize These Changes In The Submodule" +msgstr "" + +#: git-gui.sh:3358 +msgid "Visualize Current Branch History In The Submodule" +msgstr "Показать историю текущей ветви подмодуля" + +#: git-gui.sh:3362 +msgid "Visualize All Branch History In The Submodule" +msgstr "Показать историю всех ветвей подмодуля" + +#: git-gui.sh:3367 +msgid "Start git gui In The Submodule" +msgstr "" + +#: git-gui.sh:3389 msgid "Unstage Hunk From Commit" msgstr "Не сохранять часть" -#: git-gui.sh:3184 +#: git-gui.sh:3391 +msgid "Unstage Lines From Commit" +msgstr "Убрать строки из подготовленного" + +#: git-gui.sh:3393 msgid "Unstage Line From Commit" msgstr "Убрать строку из подготовленного" -#: git-gui.sh:3186 +#: git-gui.sh:3396 msgid "Stage Hunk For Commit" msgstr "Подготовить часть для сохранения" -#: git-gui.sh:3187 +#: git-gui.sh:3398 +msgid "Stage Lines For Commit" +msgstr "Подготовить строки для сохранения" + +#: git-gui.sh:3400 msgid "Stage Line For Commit" msgstr "Подготовить строку для сохранения" -#: git-gui.sh:3210 +#: git-gui.sh:3424 msgid "Initializing..." msgstr "Инициализация..." -#: git-gui.sh:3315 +#: git-gui.sh:3541 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -520,7 +544,7 @@ msgstr "" "запущенными из %s\n" "\n" -#: git-gui.sh:3345 +#: git-gui.sh:3570 msgid "" "\n" "This is due to a known issue with the\n" @@ -530,7 +554,7 @@ msgstr "" "Это известная проблема с Tcl,\n" "распространяемым Cygwin." -#: git-gui.sh:3350 +#: git-gui.sh:3575 #, tcl-format msgid "" "\n" @@ -640,7 +664,7 @@ msgstr "Невозможно найти состояние предка:" msgid "Unable to display parent" msgstr "Не могу показать предка" -#: lib/blame.tcl:1091 lib/diff.tcl:297 +#: lib/blame.tcl:1091 lib/diff.tcl:320 msgid "Error loading diff:" msgstr "Ошибка загрузки изменений:" @@ -666,7 +690,7 @@ msgstr "Перейти" #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282 -#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 #: lib/transport.tcl:108 @@ -697,7 +721,7 @@ msgstr "Создание ветви" msgid "Create New Branch" msgstr "Создать новую ветвь" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377 +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 msgid "Create" msgstr "Создать" @@ -729,7 +753,7 @@ msgstr "Нет" msgid "Fast Forward Only" msgstr "Только Fast Forward" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 msgid "Reset" msgstr "Сброс" @@ -771,8 +795,8 @@ msgstr "Локальные ветви" msgid "Delete Only If Merged Into" msgstr "Удалить только в случае, если было слияние с" -#: lib/branch_delete.tcl:54 -msgid "Always (Do not perform merge test.)" +#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" msgstr "Всегда (не выполнять проверку на слияние)" #: lib/branch_delete.tcl:103 @@ -780,6 +804,16 @@ msgstr "Всегда (не выполнять проверку на слияни msgid "The following branches are not completely merged into %s:" msgstr "Ветви, которые не полностью сливаются с %s:" +#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Восстановить удаленные ветви сложно.\n" +"\n" +"Продолжить?" + #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -809,7 +843,7 @@ msgstr "Новое название:" msgid "Please select a branch to rename." msgstr "Укажите ветвь для переименования." -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." msgstr "Ветвь '%s' уже существует." @@ -840,38 +874,38 @@ msgstr "[На уровень выше]" msgid "Browse Branch Files" msgstr "Показать файлы ветви" -#: lib/browser.tcl:278 lib/choose_repository.tcl:394 -#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491 -#: lib/choose_repository.tcl:995 +#: lib/browser.tcl:278 lib/choose_repository.tcl:398 +#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 +#: lib/choose_repository.tcl:1028 msgid "Browse" msgstr "Показать" -#: lib/checkout_op.tcl:84 +#: lib/checkout_op.tcl:85 #, tcl-format msgid "Fetching %s from %s" msgstr "Получение %s из %s " -#: lib/checkout_op.tcl:132 +#: lib/checkout_op.tcl:133 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "критическая ошибка: невозможно разрешить %s" -#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 #: lib/sshkey.tcl:53 msgid "Close" msgstr "Закрыть" -#: lib/checkout_op.tcl:174 +#: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." msgstr "Ветвь '%s' не существует " -#: lib/checkout_op.tcl:193 +#: lib/checkout_op.tcl:194 #, tcl-format msgid "Failed to configure simplified git-pull for '%s'." msgstr "Ошибка создания упрощённой конфигурации git pull для '%s'." -#: lib/checkout_op.tcl:228 +#: lib/checkout_op.tcl:229 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -884,21 +918,21 @@ msgstr "" "Она не может быть прокручена(fast-forward) к %s.\n" "Требуется слияние." -#: lib/checkout_op.tcl:242 +#: lib/checkout_op.tcl:243 #, tcl-format msgid "Merge strategy '%s' not supported." msgstr "Неизвестная стратегия слияния: '%s'." -#: lib/checkout_op.tcl:261 +#: lib/checkout_op.tcl:262 #, tcl-format msgid "Failed to update '%s'." msgstr "Не удалось обновить '%s'." -#: lib/checkout_op.tcl:273 +#: lib/checkout_op.tcl:274 msgid "Staging area (index) is already locked." msgstr "Рабочая область заблокирована другим процессом." -#: lib/checkout_op.tcl:288 +#: lib/checkout_op.tcl:289 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -914,30 +948,30 @@ msgstr "" "\n" "Это будет сделано сейчас автоматически.\n" -#: lib/checkout_op.tcl:344 +#: lib/checkout_op.tcl:345 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "Обновление рабочего каталога из '%s'..." -#: lib/checkout_op.tcl:345 +#: lib/checkout_op.tcl:346 msgid "files checked out" msgstr "файлы извлечены" -#: lib/checkout_op.tcl:375 +#: lib/checkout_op.tcl:376 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." msgstr "Прерван переход на '%s' (требуется слияние содержания файлов)" -#: lib/checkout_op.tcl:376 +#: lib/checkout_op.tcl:377 msgid "File level merge required." msgstr "Требуется слияние содержания файлов." -#: lib/checkout_op.tcl:380 +#: lib/checkout_op.tcl:381 #, tcl-format msgid "Staying on branch '%s'." msgstr "Ветвь '%s' остается текущей." -#: lib/checkout_op.tcl:451 +#: lib/checkout_op.tcl:452 msgid "" "You are no longer on a local branch.\n" "\n" @@ -949,30 +983,30 @@ msgstr "" "Если вы хотите снова вернуться к какой-нибудь ветви, создайте ее сейчас, " "начиная с 'Текущего отсоединенного состояния'." -#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format msgid "Checked out '%s'." msgstr "Ветвь '%s' сделана текущей." -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:535 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "Сброс '%s' в '%s' приведет к потере следующих сохраненных состояний: " -#: lib/checkout_op.tcl:522 +#: lib/checkout_op.tcl:557 msgid "Recovering lost commits may not be easy." msgstr "Восстановить потерянные сохраненные состояния будет сложно." -#: lib/checkout_op.tcl:527 +#: lib/checkout_op.tcl:562 #, tcl-format msgid "Reset '%s'?" msgstr "Сбросить '%s'?" -#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343 +#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" msgstr "Наглядно" -#: lib/checkout_op.tcl:600 +#: lib/checkout_op.tcl:635 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -1017,7 +1051,7 @@ msgstr "" msgid "Git Gui" msgstr "Git Gui" -#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382 +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 msgid "Create New Repository" msgstr "Создать новый репозиторий" @@ -1025,7 +1059,7 @@ msgstr "Создать новый репозиторий" msgid "New..." msgstr "Новый..." -#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465 +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 msgid "Clone Existing Repository" msgstr "Склонировать существующий репозиторий" @@ -1033,7 +1067,7 @@ msgstr "Склонировать существующий репозиторий msgid "Clone..." msgstr "Склонировать..." -#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983 +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 msgid "Open Existing Repository" msgstr "Выбрать существующий репозиторий" @@ -1049,194 +1083,194 @@ msgstr "Недавние репозитории" msgid "Open Recent Repository:" msgstr "Открыть последний репозиторий" -#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309 -#: lib/choose_repository.tcl:316 +#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 +#: lib/choose_repository.tcl:320 #, tcl-format msgid "Failed to create repository %s:" msgstr "Не удалось создать репозиторий %s:" -#: lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:391 msgid "Directory:" msgstr "Каталог:" -#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544 -#: lib/choose_repository.tcl:1017 +#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 +#: lib/choose_repository.tcl:1052 msgid "Git Repository" msgstr "Репозиторий" -#: lib/choose_repository.tcl:442 +#: lib/choose_repository.tcl:448 #, tcl-format msgid "Directory %s already exists." msgstr "Каталог '%s' уже существует." -#: lib/choose_repository.tcl:446 +#: lib/choose_repository.tcl:452 #, tcl-format msgid "File %s already exists." msgstr "Файл '%s' уже существует." -#: lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:466 msgid "Clone" msgstr "Склонировать" -#: lib/choose_repository.tcl:473 +#: lib/choose_repository.tcl:479 msgid "Source Location:" msgstr "Исходное положение:" -#: lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:490 msgid "Target Directory:" msgstr "Каталог назначения:" -#: lib/choose_repository.tcl:496 +#: lib/choose_repository.tcl:502 msgid "Clone Type:" msgstr "Тип клона:" -#: lib/choose_repository.tcl:502 +#: lib/choose_repository.tcl:508 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "Стандартный (Быстрый, полуизбыточный, \"жесткие\" ссылки)" -#: lib/choose_repository.tcl:508 +#: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" msgstr "Полная копия (Медленный, создает резервную копию)" -#: lib/choose_repository.tcl:514 +#: lib/choose_repository.tcl:520 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "Общий (Самый быстрый, не рекомендуется, без резервной копии)" -#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 -#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813 -#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 +#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 +#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 #, tcl-format msgid "Not a Git repository: %s" msgstr "Каталог не является репозиторием: %s" -#: lib/choose_repository.tcl:586 +#: lib/choose_repository.tcl:592 msgid "Standard only available for local repository." msgstr "Стандартный клон возможен только для локального репозитория." -#: lib/choose_repository.tcl:590 +#: lib/choose_repository.tcl:596 msgid "Shared only available for local repository." msgstr "Общий клон возможен только для локального репозитория." -#: lib/choose_repository.tcl:611 +#: lib/choose_repository.tcl:617 #, tcl-format msgid "Location %s already exists." msgstr "Путь '%s' уже существует." -#: lib/choose_repository.tcl:622 +#: lib/choose_repository.tcl:628 msgid "Failed to configure origin" msgstr "Не могу сконфигурировать исходный репозиторий." -#: lib/choose_repository.tcl:634 +#: lib/choose_repository.tcl:640 msgid "Counting objects" msgstr "Считаю объекты" -#: lib/choose_repository.tcl:635 +#: lib/choose_repository.tcl:641 msgid "buckets" msgstr "" -#: lib/choose_repository.tcl:659 +#: lib/choose_repository.tcl:665 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "Не могу скопировать objects/info/alternates: %s" -#: lib/choose_repository.tcl:695 +#: lib/choose_repository.tcl:701 #, tcl-format msgid "Nothing to clone from %s." msgstr "Нечего клонировать с %s." -#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911 -#: lib/choose_repository.tcl:923 +#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 +#: lib/choose_repository.tcl:929 msgid "The 'master' branch has not been initialized." msgstr "Не инициализирована ветвь 'master'." -#: lib/choose_repository.tcl:710 +#: lib/choose_repository.tcl:716 msgid "Hardlinks are unavailable. Falling back to copying." msgstr "\"Жесткие ссылки\" недоступны. Будет использовано копирование." -#: lib/choose_repository.tcl:722 +#: lib/choose_repository.tcl:728 #, tcl-format msgid "Cloning from %s" msgstr "Клонирование %s" -#: lib/choose_repository.tcl:753 +#: lib/choose_repository.tcl:759 msgid "Copying objects" msgstr "Копирование objects" -#: lib/choose_repository.tcl:754 +#: lib/choose_repository.tcl:760 msgid "KiB" msgstr "КБ" -#: lib/choose_repository.tcl:778 +#: lib/choose_repository.tcl:784 #, tcl-format msgid "Unable to copy object: %s" msgstr "Не могу скопировать объект: %s" -#: lib/choose_repository.tcl:788 +#: lib/choose_repository.tcl:794 msgid "Linking objects" msgstr "Создание ссылок на objects" -#: lib/choose_repository.tcl:789 +#: lib/choose_repository.tcl:795 msgid "objects" msgstr "объекты" -#: lib/choose_repository.tcl:797 +#: lib/choose_repository.tcl:803 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "Не могу \"жестко связать\" объект: %s" -#: lib/choose_repository.tcl:852 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch branches and objects. See console output for details." msgstr "" "Не могу получить ветви и объекты. Дополнительная информация на консоли." -#: lib/choose_repository.tcl:863 +#: lib/choose_repository.tcl:869 msgid "Cannot fetch tags. See console output for details." msgstr "Не могу получить метки. Дополнительная информация на консоли." -#: lib/choose_repository.tcl:887 +#: lib/choose_repository.tcl:893 msgid "Cannot determine HEAD. See console output for details." msgstr "Не могу определить HEAD. Дополнительная информация на консоли." -#: lib/choose_repository.tcl:896 +#: lib/choose_repository.tcl:902 #, tcl-format msgid "Unable to cleanup %s" msgstr "Не могу очистить %s" -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:908 msgid "Clone failed." msgstr "Клонирование не удалось." -#: lib/choose_repository.tcl:909 +#: lib/choose_repository.tcl:915 msgid "No default branch obtained." msgstr "Не было получено ветви по умолчанию." -#: lib/choose_repository.tcl:920 +#: lib/choose_repository.tcl:926 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "Не могу распознать %s как состояние." -#: lib/choose_repository.tcl:932 +#: lib/choose_repository.tcl:938 msgid "Creating working directory" msgstr "Создаю рабочий каталог" -#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128 -#: lib/index.tcl:196 +#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 +#: lib/index.tcl:198 msgid "files" msgstr "файлов" -#: lib/choose_repository.tcl:962 +#: lib/choose_repository.tcl:968 msgid "Initial file checkout failed." msgstr "Не удалось получить начальное состояние файлов репозитория." -#: lib/choose_repository.tcl:978 +#: lib/choose_repository.tcl:1011 msgid "Open" msgstr "Открыть" -#: lib/choose_repository.tcl:988 +#: lib/choose_repository.tcl:1021 msgid "Repository:" msgstr "Репозиторий:" -#: lib/choose_repository.tcl:1037 +#: lib/choose_repository.tcl:1072 #, tcl-format msgid "Failed to open repository %s:" msgstr "Не удалось открыть репозиторий %s:" @@ -1318,7 +1352,12 @@ msgstr "Невозможно получить информацию об авто msgid "Invalid GIT_COMMITTER_IDENT:" msgstr "Неверный GIT_COMMITTER_IDENT:" -#: lib/commit.tcl:132 +#: lib/commit.tcl:129 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "предупреждение: Tcl не поддерживает кодировку '%s'." + +#: lib/commit.tcl:149 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -1334,7 +1373,7 @@ msgstr "" "\n" "Это будет сделано сейчас автоматически.\n" -#: lib/commit.tcl:155 +#: lib/commit.tcl:172 #, tcl-format msgid "" "Unmerged files cannot be committed.\n" @@ -1342,12 +1381,12 @@ msgid "" "File %s has merge conflicts. You must resolve them and stage the file " "before committing.\n" msgstr "" -"Нельзя сохранить файлы с незавершённой операцей слияния.\n" +"Нельзя сохранить файлы с незавершённой операцией слияния.\n" "\n" "Для файла %s возник конфликт слияния. Разрешите конфликт и добавьте к " "подготовленным файлам перед сохранением.\n" -#: lib/commit.tcl:163 +#: lib/commit.tcl:180 #, tcl-format msgid "" "Unknown file state %s detected.\n" @@ -1358,7 +1397,7 @@ msgstr "" "\n" "Файл %s не может быть сохранен данной программой.\n" -#: lib/commit.tcl:171 +#: lib/commit.tcl:188 msgid "" "No changes to commit.\n" "\n" @@ -1368,7 +1407,7 @@ msgstr "" "\n" "Подготовьте хотя бы один файл до создания сохраненного состояния.\n" -#: lib/commit.tcl:186 +#: lib/commit.tcl:203 msgid "" "Please supply a commit message.\n" "\n" @@ -1386,45 +1425,40 @@ msgstr "" "- вторая строка пустая\n" "- оставшиеся строки: опишите, что дают ваши изменения.\n" -#: lib/commit.tcl:210 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "предупреждение: Tcl не поддерживает кодировку '%s'." - -#: lib/commit.tcl:226 +#: lib/commit.tcl:234 msgid "Calling pre-commit hook..." msgstr "Вызов программы поддержки репозитория pre-commit..." -#: lib/commit.tcl:241 +#: lib/commit.tcl:249 msgid "Commit declined by pre-commit hook." msgstr "Сохранение прервано программой поддержки репозитория pre-commit" -#: lib/commit.tcl:264 +#: lib/commit.tcl:272 msgid "Calling commit-msg hook..." msgstr "Вызов программы поддержки репозитория commit-msg..." -#: lib/commit.tcl:279 +#: lib/commit.tcl:287 msgid "Commit declined by commit-msg hook." msgstr "Сохранение прервано программой поддержки репозитория commit-msg" -#: lib/commit.tcl:292 +#: lib/commit.tcl:300 msgid "Committing changes..." msgstr "Сохранение изменений..." -#: lib/commit.tcl:308 +#: lib/commit.tcl:316 msgid "write-tree failed:" msgstr "Программа write-tree завершилась с ошибкой:" -#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373 +#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 msgid "Commit failed." msgstr "Сохранить состояние не удалось." -#: lib/commit.tcl:326 +#: lib/commit.tcl:334 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "Состояние %s выглядит поврежденным" -#: lib/commit.tcl:331 +#: lib/commit.tcl:339 msgid "" "No changes to commit.\n" "\n" @@ -1438,19 +1472,19 @@ msgstr "" "\n" "Сейчас автоматически запустится перечитывание репозитория.\n" -#: lib/commit.tcl:338 +#: lib/commit.tcl:346 msgid "No changes to commit." -msgstr "Отуствуют измения для сохранения." +msgstr "Отсутствуют изменения для сохранения." -#: lib/commit.tcl:352 +#: lib/commit.tcl:360 msgid "commit-tree failed:" msgstr "Программа commit-tree завершилась с ошибкой:" -#: lib/commit.tcl:372 +#: lib/commit.tcl:381 msgid "update-ref failed:" msgstr "Программа update-ref завершилась с ошибкой:" -#: lib/commit.tcl:460 +#: lib/commit.tcl:469 #, tcl-format msgid "Created commit %s: %s" msgstr "Создано состояние %s: %s " @@ -1503,20 +1537,19 @@ msgstr "Сжатие базы объектов" msgid "Verifying the object database with fsck-objects" msgstr "Проверка базы объектов при помощи fsck" -#: lib/database.tcl:108 +#: lib/database.tcl:107 #, tcl-format msgid "" "This repository currently has approximately %i loose objects.\n" "\n" "To maintain optimal performance it is strongly recommended that you compress " -"the database when more than %i loose objects exist.\n" +"the database.\n" "\n" "Compress the database now?" msgstr "" "Этот репозиторий сейчас содержит примерно %i свободных объектов\n" "\n" -"Для лучшей производительности рекомендуется сжать базу данных, когда есть " -"более %i несвязанных объектов.\n" +"Для лучшей производительности рекомендуется сжать базу данных.\n" "\n" "Сжать базу данных сейчас?" @@ -1525,7 +1558,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "Неправильная дата в репозитории: %s" -#: lib/diff.tcl:59 +#: lib/diff.tcl:64 #, tcl-format msgid "" "No differences detected.\n" @@ -1540,19 +1573,19 @@ msgid "" msgstr "" "Изменений не обнаружено.\n" "\n" -"в %s отутствуют изменения.\n" +"в %s отсутствуют изменения.\n" "\n" "Дата изменения файла была обновлена другой программой, но содержимое файла " "осталось прежним.\n" "\n" "Сейчас будет запущено перечитывание репозитория, чтобы найти подобные файлы." -#: lib/diff.tcl:99 +#: lib/diff.tcl:104 #, tcl-format msgid "Loading diff of %s..." msgstr "Загрузка изменений в %s..." -#: lib/diff.tcl:120 +#: lib/diff.tcl:125 msgid "" "LOCAL: deleted\n" "REMOTE:\n" @@ -1560,7 +1593,7 @@ msgstr "" "ЛОКАЛЬНО: удалён\n" "ВНЕШНИЙ:\n" -#: lib/diff.tcl:125 +#: lib/diff.tcl:130 msgid "" "REMOTE: deleted\n" "LOCAL:\n" @@ -1568,41 +1601,41 @@ msgstr "" "ВНЕШНИЙ: удалён\n" "ЛОКАЛЬНО:\n" -#: lib/diff.tcl:132 +#: lib/diff.tcl:137 msgid "LOCAL:\n" msgstr "ЛОКАЛЬНО:\n" -#: lib/diff.tcl:135 +#: lib/diff.tcl:140 msgid "REMOTE:\n" msgstr "ВНЕШНИЙ:\n" -#: lib/diff.tcl:197 lib/diff.tcl:296 +#: lib/diff.tcl:202 lib/diff.tcl:319 #, tcl-format msgid "Unable to display %s" msgstr "Не могу показать %s" -#: lib/diff.tcl:198 +#: lib/diff.tcl:203 msgid "Error loading file:" msgstr "Ошибка загрузки файла:" -#: lib/diff.tcl:205 +#: lib/diff.tcl:210 msgid "Git Repository (subproject)" msgstr "Репозиторий Git (подпроект)" -#: lib/diff.tcl:217 +#: lib/diff.tcl:222 msgid "* Binary file (not showing content)." msgstr "* Двоичный файл (содержимое не показано)" -#: lib/diff.tcl:222 +#: lib/diff.tcl:227 #, tcl-format msgid "" "* Untracked file is %d bytes.\n" "* Showing only first %d bytes.\n" msgstr "" -"* Размер неподготовленого файла %d байт.\n" +"* Размер неподготовленного файла %d байт.\n" "* Показано первых %d байт.\n" -#: lib/diff.tcl:228 +#: lib/diff.tcl:233 #, tcl-format msgid "" "\n" @@ -1610,22 +1643,22 @@ msgid "" "* To see the entire file, use an external editor.\n" msgstr "" "\n" -"* Неподготовленый файл обрезан: %s.\n" +"* Неподготовленный файл обрезан: %s.\n" "* Чтобы увидеть весь файл, используйте программу-редактор.\n" -#: lib/diff.tcl:436 +#: lib/diff.tcl:482 msgid "Failed to unstage selected hunk." msgstr "Не удалось исключить выбранную часть." -#: lib/diff.tcl:443 +#: lib/diff.tcl:489 msgid "Failed to stage selected hunk." msgstr "Не удалось подготовить к сохранению выбранную часть." -#: lib/diff.tcl:509 +#: lib/diff.tcl:568 msgid "Failed to unstage selected line." msgstr "Не удалось исключить выбранную строку." -#: lib/diff.tcl:517 +#: lib/diff.tcl:576 msgid "Failed to stage selected line." msgstr "Не удалось подготовить к сохранению выбранную строку." @@ -1662,15 +1695,15 @@ msgstr "Не удалось разблокировать индекс" msgid "Index Error" msgstr "Ошибка в индексе" -#: lib/index.tcl:21 +#: lib/index.tcl:17 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." msgstr "" -"Не удалось обновить индекс Git. Состояние репозитория будетперечитано " +"Не удалось обновить индекс Git. Состояние репозитория будет перечитано " "автоматически." -#: lib/index.tcl:27 +#: lib/index.tcl:28 msgid "Continue" msgstr "Продолжить" @@ -1678,45 +1711,45 @@ msgstr "Продолжить" msgid "Unlock Index" msgstr "Разблокировать индекс" -#: lib/index.tcl:287 +#: lib/index.tcl:289 #, tcl-format msgid "Unstaging %s from commit" msgstr "Удаление %s из подготовленного" -#: lib/index.tcl:326 +#: lib/index.tcl:328 msgid "Ready to commit." msgstr "Подготовлено для сохранения" -#: lib/index.tcl:339 +#: lib/index.tcl:341 #, tcl-format msgid "Adding %s" msgstr "Добавление %s..." -#: lib/index.tcl:396 +#: lib/index.tcl:398 #, tcl-format msgid "Revert changes in file %s?" msgstr "Отменить изменения в файле %s?" -#: lib/index.tcl:398 +#: lib/index.tcl:400 #, tcl-format msgid "Revert changes in these %i files?" msgstr "Отменить изменения в %i файле(-ах)?" -#: lib/index.tcl:406 +#: lib/index.tcl:408 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" "Любые изменения, не подготовленные к сохранению, будут потеряны при данной " "операции." -#: lib/index.tcl:409 +#: lib/index.tcl:411 msgid "Do Nothing" msgstr "Ничего не делать" -#: lib/index.tcl:427 +#: lib/index.tcl:429 msgid "Reverting selected files" -msgstr "Удаление изменений в выбраных файлах" +msgstr "Удаление изменений в выбранных файлах" -#: lib/index.tcl:431 +#: lib/index.tcl:433 #, tcl-format msgid "Reverting %s" msgstr "Отмена изменений в %s" @@ -1778,7 +1811,7 @@ msgstr "" "\n" "Файл %s изменен.\n" "\n" -"Подготовьте и сохраните измения перед началом слияния. В случае " +"Подготовьте и сохраните изменения перед началом слияния. В случае " "необходимости это позволит прервать операцию слияния.\n" #: lib/merge.tcl:107 @@ -1893,7 +1926,7 @@ msgstr "" #, tcl-format msgid "File %s seems to have unresolved conflicts, still stage?" msgstr "" -"Файл %s кажется содержит необработаные конфликты. Продолжить подготовку к " +"Файл %s, похоже, содержит необработанные конфликты. Продолжить подготовку к " "сохранению?" #: lib/mergetool.tcl:60 @@ -2152,7 +2185,7 @@ msgstr "Получение %s" #: lib/remote_add.tcl:157 #, tcl-format msgid "Do not know how to initialize repository at location '%s'." -msgstr "Невозможно инициалировать репозиторий в '%s'." +msgstr "Невозможно инициализировать репозиторий в '%s'." #: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63 #: lib/transport.tcl:81 @@ -2179,7 +2212,7 @@ msgstr "внешний:" #: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149 msgid "Arbitrary Location:" -msgstr "Указаное положение:" +msgstr "Указанное положение:" #: lib/remote_branch_delete.tcl:84 msgid "Branches" @@ -2193,10 +2226,6 @@ msgstr "Удалить только в случае, если" msgid "Merged Into:" msgstr "Слияние с:" -#: lib/remote_branch_delete.tcl:119 -msgid "Always (Do not perform merge checks)" -msgstr "Всегда (не выполнять проверку на слияние)" - #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." msgstr "Для опции 'Слияние с' требуется указать ветвь." @@ -2225,26 +2254,16 @@ msgstr "" msgid "Please select one or more branches to delete." msgstr "Укажите одну или несколько ветвей для удаления." -#: lib/remote_branch_delete.tcl:216 -msgid "" -"Recovering deleted branches is difficult.\n" -"\n" -"Delete the selected branches?" -msgstr "" -"Восстановить удаленные ветви сложно.\n" -"\n" -"Продолжить?" - #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" msgstr "Удаление ветвей из %s" -#: lib/remote_branch_delete.tcl:286 +#: lib/remote_branch_delete.tcl:292 msgid "No repository selected." msgstr "Не указан репозиторий." -#: lib/remote_branch_delete.tcl:291 +#: lib/remote_branch_delete.tcl:297 #, tcl-format msgid "Scanning %s..." msgstr "Перечитывание %s... " @@ -2265,11 +2284,11 @@ msgstr "Обратно" msgid "Case-Sensitive" msgstr "Игн. большие/маленькие" -#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 msgid "Cannot write shortcut:" msgstr "Невозможно записать ссылку:" -#: lib/shortcut.tcl:136 +#: lib/shortcut.tcl:137 msgid "Cannot write icon:" msgstr "Невозможно записать значок:" @@ -2292,11 +2311,11 @@ msgstr "Словарь вернут к %s." #: lib/spellcheck.tcl:73 msgid "Spell checker silently failed on startup" -msgstr "Программа проверки правописания не смогла запустится" +msgstr "Программа проверки правописания не смогла запуститься" #: lib/spellcheck.tcl:80 msgid "Unrecognized spell checker" -msgstr "Нераспознаная программа проверки правописания" +msgstr "Нераспознанная программа проверки правописания" #: lib/spellcheck.tcl:186 msgid "No Suggestions" @@ -2412,7 +2431,7 @@ msgstr "Описание вспомогательной операции" #: lib/tools_dlg.tcl:48 msgid "Use '/' separators to create a submenu tree:" -msgstr "Испольуйте '/' для создания подменю" +msgstr "Используйте '/' для создания подменю" #: lib/tools_dlg.tcl:61 msgid "Command:" diff --git a/git-instaweb.sh b/git-instaweb.sh index 10fcebb119..8bfa8a055c 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -558,12 +558,14 @@ my \$app = builder { # make it runnable as standalone app, # like it would be run via 'plackup' utility -if (__FILE__ eq \$0) { +if (caller) { + return \$app; +} else { require Plack::Runner; my \$runner = Plack::Runner->new(); \$runner->parse_options(qw(--env deployment --port $port), - "$local" ? qw(--host 127.0.0.1) : ()); + "$local" ? qw(--host 127.0.0.1) : ()); \$runner->run(\$app); } __END__ diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index b86402afa5..7aeb96952f 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -22,6 +22,11 @@ LONG_USAGE="Usage: git merge-one-file $USAGE Blob ids and modes should be empty for missing files." +SUBDIRECTORY_OK=Yes +. git-sh-setup +cd_to_toplevel +require_work_tree + if ! test "$#" -eq 7 then echo "$LONG_USAGE" @@ -132,7 +137,7 @@ case "${1:-.}${2:-.}${3:-.}" in # Create the working tree file, using "our tree" version from the # index, and then store the result of the merge. - git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4" + git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4" || exit 1 rm -f -- "$orig" "$src1" "$src2" if [ "$6" != "$7" ]; then diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 77d4aee20e..4db9212331 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -10,17 +10,20 @@ merge_mode() { translate_merge_tool_path () { case "$1" in - vimdiff|vimdiff2) - echo vim + araxis) + echo compare ;; - gvimdiff|gvimdiff2) - echo gvim + bc3) + echo bcompare ;; emerge) echo emacs ;; - araxis) - echo compare + gvimdiff|gvimdiff2) + echo gvim + ;; + vimdiff|vimdiff2) + echo vim ;; *) echo "$1" @@ -46,17 +49,16 @@ check_unchanged () { valid_tool () { case "$1" in - kdiff3 | tkdiff | xxdiff | meld | opendiff | \ - vimdiff | gvimdiff | vimdiff2 | gvimdiff2 | \ - emerge | ecmerge | diffuse | araxis | p4merge) + araxis | bc3 | diffuse | ecmerge | emerge | gvimdiff | gvimdiff2 | \ + kdiff3 | meld | opendiff | p4merge | tkdiff | vimdiff | vimdiff2 | xxdiff) ;; # happy - tortoisemerge) - if ! merge_mode; then + kompare) + if ! diff_mode; then return 1 fi ;; - kompare) - if ! diff_mode; then + tortoisemerge) + if ! merge_mode; then return 1 fi ;; @@ -89,88 +91,91 @@ run_merge_tool () { status=0 case "$1" in - kdiff3) + araxis) if merge_mode; then + touch "$BACKUP" if $base_present; then - ("$merge_tool_path" --auto \ - --L1 "$MERGED (Base)" \ - --L2 "$MERGED (Local)" \ - --L3 "$MERGED (Remote)" \ - -o "$MERGED" \ - "$BASE" "$LOCAL" "$REMOTE" \ - > /dev/null 2>&1) + "$merge_tool_path" -wait -merge -3 -a1 \ + "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \ + >/dev/null 2>&1 else - ("$merge_tool_path" --auto \ - --L1 "$MERGED (Local)" \ - --L2 "$MERGED (Remote)" \ - -o "$MERGED" \ - "$LOCAL" "$REMOTE" \ - > /dev/null 2>&1) + "$merge_tool_path" -wait -2 \ + "$LOCAL" "$REMOTE" "$MERGED" \ + >/dev/null 2>&1 fi - status=$? + check_unchanged else - ("$merge_tool_path" --auto \ - --L1 "$MERGED (A)" \ - --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \ - > /dev/null 2>&1) + "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \ + >/dev/null 2>&1 fi ;; - kompare) - "$merge_tool_path" "$LOCAL" "$REMOTE" - ;; - tkdiff) + bc3) if merge_mode; then + touch "$BACKUP" if $base_present; then - "$merge_tool_path" -a "$BASE" \ - -o "$MERGED" "$LOCAL" "$REMOTE" + "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \ + -mergeoutput="$MERGED" else - "$merge_tool_path" \ - -o "$MERGED" "$LOCAL" "$REMOTE" + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + -mergeoutput="$MERGED" fi - status=$? + check_unchanged else "$merge_tool_path" "$LOCAL" "$REMOTE" fi ;; - p4merge) + diffuse) if merge_mode; then - touch "$BACKUP" + touch "$BACKUP" if $base_present; then - "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED" + "$merge_tool_path" \ + "$LOCAL" "$MERGED" "$REMOTE" \ + "$BASE" | cat else - "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED" + "$merge_tool_path" \ + "$LOCAL" "$MERGED" "$REMOTE" | cat fi check_unchanged else - "$merge_tool_path" "$LOCAL" "$REMOTE" + "$merge_tool_path" "$LOCAL" "$REMOTE" | cat fi ;; - meld) + ecmerge) if merge_mode; then touch "$BACKUP" - "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE" + if $base_present; then + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \ + --default --mode=merge3 --to="$MERGED" + else + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + --default --mode=merge2 --to="$MERGED" + fi check_unchanged else - "$merge_tool_path" "$LOCAL" "$REMOTE" + "$merge_tool_path" --default --mode=diff2 \ + "$LOCAL" "$REMOTE" fi ;; - diffuse) + emerge) if merge_mode; then - touch "$BACKUP" if $base_present; then "$merge_tool_path" \ - "$LOCAL" "$MERGED" "$REMOTE" \ - "$BASE" | cat + -f emerge-files-with-ancestor-command \ + "$LOCAL" "$REMOTE" "$BASE" \ + "$(basename "$MERGED")" else "$merge_tool_path" \ - "$LOCAL" "$MERGED" "$REMOTE" | cat + -f emerge-files-command \ + "$LOCAL" "$REMOTE" \ + "$(basename "$MERGED")" fi - check_unchanged + status=$? else - "$merge_tool_path" "$LOCAL" "$REMOTE" | cat + "$merge_tool_path" -f emerge-files-command \ + "$LOCAL" "$REMOTE" fi ;; - vimdiff|gvimdiff) + gvimdiff|vimdiff) if merge_mode; then touch "$BACKUP" if $base_present; then @@ -182,45 +187,57 @@ run_merge_tool () { fi check_unchanged else - "$merge_tool_path" -f -d -c "wincmd l" \ + "$merge_tool_path" -R -f -d -c "wincmd l" \ "$LOCAL" "$REMOTE" fi ;; - vimdiff2|gvimdiff2) + gvimdiff2|vimdiff2) if merge_mode; then touch "$BACKUP" "$merge_tool_path" -f -d -c "wincmd l" \ "$LOCAL" "$MERGED" "$REMOTE" check_unchanged else - "$merge_tool_path" -f -d -c "wincmd l" \ + "$merge_tool_path" -R -f -d -c "wincmd l" \ "$LOCAL" "$REMOTE" fi ;; - xxdiff) + kdiff3) if merge_mode; then - touch "$BACKUP" if $base_present; then - "$merge_tool_path" -X --show-merged-pane \ - -R 'Accel.SaveAsMerged: "Ctrl-S"' \ - -R 'Accel.Search: "Ctrl+F"' \ - -R 'Accel.SearchForward: "Ctrl-G"' \ - --merged-file "$MERGED" \ - "$LOCAL" "$BASE" "$REMOTE" + ("$merge_tool_path" --auto \ + --L1 "$MERGED (Base)" \ + --L2 "$MERGED (Local)" \ + --L3 "$MERGED (Remote)" \ + -o "$MERGED" \ + "$BASE" "$LOCAL" "$REMOTE" \ + > /dev/null 2>&1) else - "$merge_tool_path" -X $extra \ - -R 'Accel.SaveAsMerged: "Ctrl-S"' \ - -R 'Accel.Search: "Ctrl+F"' \ - -R 'Accel.SearchForward: "Ctrl-G"' \ - --merged-file "$MERGED" \ - "$LOCAL" "$REMOTE" + ("$merge_tool_path" --auto \ + --L1 "$MERGED (Local)" \ + --L2 "$MERGED (Remote)" \ + -o "$MERGED" \ + "$LOCAL" "$REMOTE" \ + > /dev/null 2>&1) fi + status=$? + else + ("$merge_tool_path" --auto \ + --L1 "$MERGED (A)" \ + --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \ + > /dev/null 2>&1) + fi + ;; + kompare) + "$merge_tool_path" "$LOCAL" "$REMOTE" + ;; + meld) + if merge_mode; then + touch "$BACKUP" + "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE" check_unchanged else - "$merge_tool_path" \ - -R 'Accel.Search: "Ctrl+F"' \ - -R 'Accel.SearchForward: "Ctrl-G"' \ - "$LOCAL" "$REMOTE" + "$merge_tool_path" "$LOCAL" "$REMOTE" fi ;; opendiff) @@ -239,39 +256,28 @@ run_merge_tool () { "$merge_tool_path" "$LOCAL" "$REMOTE" | cat fi ;; - ecmerge) + p4merge) if merge_mode; then touch "$BACKUP" - if $base_present; then - "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \ - --default --mode=merge3 --to="$MERGED" - else - "$merge_tool_path" "$LOCAL" "$REMOTE" \ - --default --mode=merge2 --to="$MERGED" - fi + $base_present || >"$BASE" + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED" check_unchanged else - "$merge_tool_path" --default --mode=diff2 \ - "$LOCAL" "$REMOTE" + "$merge_tool_path" "$LOCAL" "$REMOTE" fi ;; - emerge) + tkdiff) if merge_mode; then if $base_present; then - "$merge_tool_path" \ - -f emerge-files-with-ancestor-command \ - "$LOCAL" "$REMOTE" "$BASE" \ - "$(basename "$MERGED")" + "$merge_tool_path" -a "$BASE" \ + -o "$MERGED" "$LOCAL" "$REMOTE" else "$merge_tool_path" \ - -f emerge-files-command \ - "$LOCAL" "$REMOTE" \ - "$(basename "$MERGED")" + -o "$MERGED" "$LOCAL" "$REMOTE" fi status=$? else - "$merge_tool_path" -f emerge-files-command \ - "$LOCAL" "$REMOTE" + "$merge_tool_path" "$LOCAL" "$REMOTE" fi ;; tortoisemerge) @@ -286,22 +292,30 @@ run_merge_tool () { status=1 fi ;; - araxis) + xxdiff) if merge_mode; then touch "$BACKUP" if $base_present; then - "$merge_tool_path" -wait -merge -3 -a1 \ - "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \ - >/dev/null 2>&1 + "$merge_tool_path" -X --show-merged-pane \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$MERGED" \ + "$LOCAL" "$BASE" "$REMOTE" else - "$merge_tool_path" -wait -2 \ - "$LOCAL" "$REMOTE" "$MERGED" \ - >/dev/null 2>&1 + "$merge_tool_path" -X $extra \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$MERGED" \ + "$LOCAL" "$REMOTE" fi check_unchanged else - "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \ - >/dev/null 2>&1 + "$merge_tool_path" \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + "$LOCAL" "$REMOTE" fi ;; *) @@ -343,7 +357,7 @@ guess_merge_tool () { else tools="opendiff kdiff3 tkdiff xxdiff meld $tools" fi - tools="$tools gvimdiff diffuse ecmerge p4merge araxis" + tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3" fi case "${VISUAL:-$EDITOR}" in *vim*) diff --git a/git-mergetool.sh b/git-mergetool.sh index 2f8dc441c6..3aab5aae84 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -21,6 +21,10 @@ is_symlink () { test "$1" = 120000 } +is_submodule () { + test "$1" = 160000 +} + local_present () { test -n "$local_mode" } @@ -35,7 +39,8 @@ base_present () { cleanup_temp_files () { if test "$1" = --save-backup ; then - mv -- "$BACKUP" "$MERGED.orig" + rm -rf -- "$MERGED.orig" + test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig" rm -f -- "$LOCAL" "$REMOTE" "$BASE" else rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP" @@ -52,11 +57,13 @@ describe_file () { echo "deleted" elif is_symlink "$mode" ; then echo "a symbolic link -> '$(cat "$file")'" + elif is_submodule "$mode" ; then + echo "submodule commit $file" else if base_present; then - echo "modified" + echo "modified file" else - echo "created" + echo "created file" fi fi } @@ -112,6 +119,67 @@ resolve_deleted_merge () { done } +resolve_submodule_merge () { + while true; do + printf "Use (l)ocal or (r)emote, or (a)bort? " + read ans + case "$ans" in + [lL]*) + if ! local_present; then + if test -n "$(git ls-tree HEAD -- "$MERGED")"; then + # Local isn't present, but it's a subdirectory + git ls-tree --full-name -r HEAD -- "$MERGED" | git update-index --index-info || exit $? + else + test -e "$MERGED" && mv -- "$MERGED" "$BACKUP" + git update-index --force-remove "$MERGED" + cleanup_temp_files --save-backup + fi + elif is_submodule "$local_mode"; then + stage_submodule "$MERGED" "$local_sha1" + else + git checkout-index -f --stage=2 -- "$MERGED" + git add -- "$MERGED" + fi + return 0 + ;; + [rR]*) + if ! remote_present; then + if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"; then + # Remote isn't present, but it's a subdirectory + git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" | git update-index --index-info || exit $? + else + test -e "$MERGED" && mv -- "$MERGED" "$BACKUP" + git update-index --force-remove "$MERGED" + fi + elif is_submodule "$remote_mode"; then + ! is_submodule "$local_mode" && test -e "$MERGED" && mv -- "$MERGED" "$BACKUP" + stage_submodule "$MERGED" "$remote_sha1" + else + test -e "$MERGED" && mv -- "$MERGED" "$BACKUP" + git checkout-index -f --stage=3 -- "$MERGED" + git add -- "$MERGED" + fi + cleanup_temp_files --save-backup + return 0 + ;; + [aA]*) + return 1 + ;; + esac + done +} + +stage_submodule () { + path="$1" + submodule_sha1="$2" + mkdir -p "$path" || die "fatal: unable to create directory for module at $path" + # Find $path relative to work tree + work_tree_root=$(cd_to_toplevel && pwd) + work_rel_path=$(cd "$path" && GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix) + test -n "$work_rel_path" || die "fatal: unable to get path of module $path relative to work tree" + git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die +} + checkout_staged_file () { tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ') @@ -139,13 +207,23 @@ merge_file () { REMOTE="./$MERGED.REMOTE.$ext" BASE="./$MERGED.BASE.$ext" - mv -- "$MERGED" "$BACKUP" - cp -- "$BACKUP" "$MERGED" - base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}') local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}') remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}') + if is_submodule "$local_mode" || is_submodule "$remote_mode"; then + echo "Submodule merge conflict for '$MERGED':" + local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}') + remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}') + describe_file "$local_mode" "local" "$local_sha1" + describe_file "$remote_mode" "remote" "$remote_sha1" + resolve_submodule_merge + return + fi + + mv -- "$MERGED" "$BACKUP" + cp -- "$BACKUP" "$MERGED" + base_present && checkout_staged_file 1 "$MERGED" "$BASE" local_present && checkout_staged_file 2 "$MERGED" "$LOCAL" remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE" @@ -269,7 +347,7 @@ rerere=false files_to_merge() { if test "$rerere" = true then - git rerere status + git rerere remaining else git ls-files -u | sed -e 's/^[^ ]* //' | sort -u fi diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 1cc2ba6e09..b24119d69c 100644 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -4,58 +4,9 @@ # this would fail in that case and would issue an error message. GIT_DIR=$(git rev-parse -q --git-dir) || :; -get_data_source () { - case "$1" in - */*) - echo '' - ;; - .) - echo self - ;; - *) - if test "$(git config --get "remote.$1.url")" - then - echo config - elif test -f "$GIT_DIR/remotes/$1" - then - echo remotes - elif test -f "$GIT_DIR/branches/$1" - then - echo branches - else - echo '' - fi ;; - esac -} - -get_remote_url () { - data_source=$(get_data_source "$1") - case "$data_source" in - '') - echo "$1" - ;; - self) - echo "$1" - ;; - config) - git config --get "remote.$1.url" - ;; - remotes) - sed -ne '/^URL: */{ - s///p - q - }' "$GIT_DIR/remotes/$1" - ;; - branches) - sed -e 's/#.*//' "$GIT_DIR/branches/$1" - ;; - *) - die "internal error: get-remote-url $1" ;; - esac -} - get_default_remote () { - curr_branch=$(git symbolic-ref -q HEAD | sed -e 's|^refs/heads/||') + curr_branch=$(git symbolic-ref -q HEAD) + curr_branch="${curr_branch#refs/heads/}" origin=$(git config --get "branch.$curr_branch.remote") echo ${origin:-origin} } @@ -99,3 +50,41 @@ get_remote_merge_branch () { esac esac } + +error_on_missing_default_upstream () { + cmd="$1" + op_type="$2" + op_prep="$3" + example="$4" + branch_name=$(git symbolic-ref -q HEAD) + if test -z "$branch_name" + then + echo "You are not currently on a branch, so I cannot use any +'branch.<branchname>.merge' in your configuration file. +Please specify which branch you want to $op_type $op_prep on the command +line and try again (e.g. '$example'). +See git-${cmd}(1) for details." + else + echo "You asked me to $cmd without telling me which branch you +want to $op_type $op_prep, and 'branch.${branch_name#refs/heads/}.merge' in +your configuration file does not tell me, either. Please +specify which branch you want to use on the command line and +try again (e.g. '$example'). +See git-${cmd}(1) for details. + +If you often $op_type $op_prep the same branch, you may want to +use something like the following in your configuration file: + [branch \"${branch_name#refs/heads/}\"] + remote = <nickname> + merge = <remote-ref>" + test rebase = "$op_type" && + echo " rebase = true" + echo " + [remote \"<nickname>\"] + url = <url> + fetch = <refspec> + +See git-config(1) for details." + fi + exit 1 +} diff --git a/git-pull.sh b/git-pull.sh index eb87f49062..fb9e2df931 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -53,6 +53,8 @@ do verbosity="$verbosity -v" ;; --progress) progress=--progress ;; + --no-progress) + progress=--no-progress ;; -n|--no-stat|--no-summary) diffstat=--no-stat ;; --stat|--summary) @@ -108,13 +110,16 @@ do --recurse-submodules) recurse_submodules=--recurse-submodules ;; + --recurse-submodules=*) + recurse_submodules="$1" + ;; --no-recurse-submodules) recurse_submodules=--no-recurse-submodules ;; --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run) dry_run=--dry-run ;; - -h|--h|--he|--hel|--help) + -h|--h|--he|--hel|--help|--help-|--help-a|--help-al|--help-all) usage ;; *) @@ -163,34 +168,10 @@ error_on_no_merge_candidates () { echo "You asked to pull from the remote '$1', but did not specify" echo "a branch. Because this is not the default configured remote" echo "for your current branch, you must specify a branch on the command line." - elif [ -z "$curr_branch" ]; then - echo "You are not currently on a branch, so I cannot use any" - echo "'branch.<branchname>.merge' in your configuration file." - echo "Please specify which remote branch you want to use on the command" - echo "line and try again (e.g. 'git pull <repository> <refspec>')." - echo "See git-pull(1) for details." - elif [ -z "$upstream" ]; then - echo "You asked me to pull without telling me which branch you" - echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in" - echo "your configuration file does not tell me, either. Please" - echo "specify which branch you want to use on the command line and" - echo "try again (e.g. 'git pull <repository> <refspec>')." - echo "See git-pull(1) for details." - echo - echo "If you often $op_type $op_prep the same branch, you may want to" - echo "use something like the following in your configuration file:" - echo - echo " [branch \"${curr_branch}\"]" - echo " remote = <nickname>" - echo " merge = <remote-ref>" - test rebase = "$op_type" && - echo " rebase = true" - echo - echo " [remote \"<nickname>\"]" - echo " url = <url>" - echo " fetch = <refspec>" - echo - echo "See git-config(1) for details." + elif [ -z "$curr_branch" -o -z "$upstream" ]; then + . git-parse-remote + error_on_missing_default_upstream "pull" $op_type $op_prep \ + "git pull <repository> <refspec>" else echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'" echo "from the remote, but no such ref was fetched." @@ -272,7 +253,7 @@ esac if test -z "$orig_head" then git update-ref -m "initial pull" HEAD $merge_head "$curr_head" && - git read-tree --reset -u HEAD || exit 1 + git read-tree -m -u HEAD || exit 1 exit fi @@ -293,8 +274,8 @@ true) ;; *) eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only" - eval="$eval $log_arg $strategy_args $merge_args" - eval="$eval \"\$merge_name\" HEAD $merge_head $verbosity" + eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress" + eval="$eval \"\$merge_name\" HEAD $merge_head" ;; esac eval "exec $eval" diff --git a/git-rebase--am.sh b/git-rebase--am.sh new file mode 100644 index 0000000000..c815a2412c --- /dev/null +++ b/git-rebase--am.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Copyright (c) 2010 Junio C Hamano. +# + +. git-sh-setup + +case "$action" in +continue) + git am --resolved --resolvemsg="$resolvemsg" && + move_to_original_branch + exit + ;; +skip) + git am --skip --resolvemsg="$resolvemsg" && + move_to_original_branch + exit + ;; +esac + +test -n "$rebase_root" && root_flag=--root + +git format-patch -k --stdout --full-index --ignore-if-in-upstream \ + --src-prefix=a/ --dst-prefix=b/ \ + --no-renames $root_flag "$revisions" | +git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" && +move_to_original_branch +ret=$? +test 0 != $ret -a -d "$state_dir" && write_basic_state +exit $ret diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 5873ba4bc3..65690af893 100755..100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -10,48 +10,22 @@ # The original idea comes from Eric W. Biederman, in # http://article.gmane.org/gmane.comp.version-control.git/22407 -OPTIONS_KEEPDASHDASH= -OPTIONS_SPEC="\ -git-rebase [-i] [options] [--] <upstream> [<branch>] -git-rebase [-i] (--continue | --abort | --skip) --- - Available options are -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: -continue continue rebasing process -abort abort rebasing process and restore original branch -skip skip current patch and continue rebasing process -no-verify override pre-rebase hook from stopping the operation -verify allow pre-rebase hook to run -root rebase all reachable commmits up to the root(s) -autosquash move commits that begin with squash!/fixup! under -i -" - . git-sh-setup -require_work_tree - -DOTEST="$GIT_DIR/rebase-merge" # The file containing rebase commands, comments, and empty lines. # This file is created by "git rebase -i" then edited by the user. As # the lines are processed, they are removed from the front of this -# file and written to the tail of $DONE. -TODO="$DOTEST"/git-rebase-todo +# file and written to the tail of $done. +todo="$state_dir"/git-rebase-todo # The rebase command lines that have already been processed. A line # is moved here when it is first handled, before any associated user # actions. -DONE="$DOTEST"/done +done="$state_dir"/done # The commit message that is planned to be used for any changes that # need to be committed following a user interaction. -MSG="$DOTEST"/message +msg="$state_dir"/message # The file into which is accumulated the suggested commit message for # squash/fixup commands. When the first of a series of squash/fixups @@ -61,34 +35,34 @@ MSG="$DOTEST"/message # is appended to the file as it is processed. # # The first line of the file is of the form -# # This is a combination of $COUNT commits. -# where $COUNT is the number of commits whose messages have been +# # This is a combination of $count commits. +# where $count is the number of commits whose messages have been # written to the file so far (including the initial "pick" commit). # Each time that a commit message is processed, this line is read and # updated. It is deleted just before the combined commit is made. -SQUASH_MSG="$DOTEST"/message-squash +squash_msg="$state_dir"/message-squash # If the current series of squash/fixups has not yet included a squash # command, then this file exists and holds the commit message of the # original "pick" commit. (If the series ends without a "squash" # command, then this can be used as the commit message of the combined # commit without opening the editor.) -FIXUP_MSG="$DOTEST"/message-fixup +fixup_msg="$state_dir"/message-fixup -# $REWRITTEN is the name of a directory containing files for each -# commit that is reachable by at least one merge base of $HEAD and -# $UPSTREAM. They are not necessarily rewritten, but their children +# $rewritten is the name of a directory containing files for each +# commit that is reachable by at least one merge base of $head and +# $upstream. They are not necessarily rewritten, but their children # might be. This ensures that commits on merged, but otherwise # unrelated side branches are left alone. (Think "X" in the man page's # example.) -REWRITTEN="$DOTEST"/rewritten +rewritten="$state_dir"/rewritten -DROPPED="$DOTEST"/dropped +dropped="$state_dir"/dropped # A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and # GIT_AUTHOR_DATE that will be used for the commit that is currently # being rebased. -AUTHOR_SCRIPT="$DOTEST"/author-script +author_script="$state_dir"/author-script # When an "edit" rebase command is being processed, the SHA1 of the # commit to be edited is recorded in this file. When "git rebase @@ -96,69 +70,31 @@ AUTHOR_SCRIPT="$DOTEST"/author-script # will be amended to the HEAD commit, but only provided the HEAD # commit is still the commit to be edited. When any other rebase # command is processed, this file is deleted. -AMEND="$DOTEST"/amend +amend="$state_dir"/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= -VERBOSE= -OK_TO_SKIP_PRE_REBASE= -REBASE_ROOT= -AUTOSQUASH= -test "$(git config --bool rebase.autosquash)" = "true" && AUTOSQUASH=t -NEVER_FF= - -GIT_CHERRY_PICK_HELP="\ -hint: after resolving the conflicts, mark the corrected paths -hint: with 'git add <paths>' and run 'git rebase --continue'" +rewritten_list="$state_dir"/rewritten-list +rewritten_pending="$state_dir"/rewritten-pending + +GIT_CHERRY_PICK_HELP="$resolvemsg" export GIT_CHERRY_PICK_HELP warn () { printf '%s\n' "$*" >&2 } -output () { - case "$VERBOSE" in - '') - output=$("$@" 2>&1 ) - status=$? - test $status != 0 && printf "%s\n" "$output" - return $status - ;; - *) - "$@" - ;; - esac -} - # Output the commit message for the specified commit. commit_message () { git cat-file commit "$1" | sed "1,/^$/d" } -run_pre_rebase_hook () { - if test -z "$OK_TO_SKIP_PRE_REBASE" && - test -x "$GIT_DIR/hooks/pre-rebase" - then - "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || { - echo >&2 "The pre-rebase hook refused to rebase." - exit 1 - } - fi -} - - -ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION" +orig_reflog_action="$GIT_REFLOG_ACTION" comment_for_reflog () { - case "$ORIG_REFLOG_ACTION" in + case "$orig_reflog_action" in ''|rebase*) GIT_REFLOG_ACTION="rebase -i ($1)" export GIT_REFLOG_ACTION @@ -168,16 +104,16 @@ comment_for_reflog () { last_count= mark_action_done () { - sed -e 1q < "$TODO" >> "$DONE" - sed -e 1d < "$TODO" >> "$TODO".new - mv -f "$TODO".new "$TODO" - count=$(sane_grep -c '^[^#]' < "$DONE") - total=$(($count+$(sane_grep -c '^[^#]' < "$TODO"))) - if test "$last_count" != "$count" + sed -e 1q < "$todo" >> "$done" + sed -e 1d < "$todo" >> "$todo".new + mv -f "$todo".new "$todo" + new_count=$(sane_grep -c '^[^#]' < "$done") + total=$(($new_count+$(sane_grep -c '^[^#]' < "$todo"))) + if test "$last_count" != "$new_count" then - last_count=$count - printf "Rebasing (%d/%d)\r" $count $total - test -z "$VERBOSE" || echo + last_count=$new_count + printf "Rebasing (%d/%d)\r" $new_count $total + test -z "$verbose" || echo fi } @@ -193,22 +129,22 @@ make_patch () { *) echo "Root commit" ;; - esac > "$DOTEST"/patch - test -f "$MSG" || - commit_message "$1" > "$MSG" - test -f "$AUTHOR_SCRIPT" || - get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT" + esac > "$state_dir"/patch + test -f "$msg" || + commit_message "$1" > "$msg" + test -f "$author_script" || + get_author_ident_from_commit "$1" > "$author_script" } die_with_patch () { - echo "$1" > "$DOTEST"/stopped-sha + echo "$1" > "$state_dir"/stopped-sha make_patch "$1" git rerere die "$2" } die_abort () { - rm -rf "$DOTEST" + rm -rf "$state_dir" die "$1" } @@ -228,15 +164,10 @@ do_with_author () { pick_one () { ff=--ff case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac - case "$NEVER_FF" in '') ;; ?*) ff= ;; esac + case "$force_rebase" in '') ;; ?*) ff= ;; esac output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" - test -d "$REWRITTEN" && + test -d "$rewritten" && pick_one_preserving_merges "$@" && return - if test -n "$REBASE_ROOT" - then - output git cherry-pick "$@" - return - fi output git cherry-pick $ff "$@" } @@ -253,20 +184,20 @@ pick_one_preserving_merges () { esac sha1=$(git rev-parse $sha1) - if test -f "$DOTEST"/current-commit + if test -f "$state_dir"/current-commit then if test "$fast_forward" = t then while read current_commit do - git rev-parse HEAD > "$REWRITTEN"/$current_commit - done <"$DOTEST"/current-commit - rm "$DOTEST"/current-commit || + git rev-parse HEAD > "$rewritten"/$current_commit + done <"$state_dir"/current-commit + rm "$state_dir"/current-commit || die "Cannot write current commit's replacement sha1" fi fi - echo $sha1 >> "$DOTEST"/current-commit + echo $sha1 >> "$state_dir"/current-commit # rewrite parents; if none were rewritten, we can fast-forward. new_parents= @@ -280,9 +211,9 @@ pick_one_preserving_merges () { p=$(expr "$pend" : ' \([^ ]*\)') pend="${pend# $p}" - if test -f "$REWRITTEN"/$p + if test -f "$rewritten"/$p then - new_p=$(cat "$REWRITTEN"/$p) + new_p=$(cat "$rewritten"/$p) # If the todo reordered commits, and our parent is marked for # rewriting, but hasn't been gotten to yet, assume the user meant to @@ -301,10 +232,10 @@ pick_one_preserving_merges () { ;; esac else - if test -f "$DROPPED"/$p + if test -f "$dropped"/$p then fast_forward=f - replacement="$(cat "$DROPPED"/$p)" + replacement="$(cat "$dropped"/$p)" test -z "$replacement" && replacement=root pend=" $replacement$pend" else @@ -333,18 +264,19 @@ pick_one_preserving_merges () { test "a$1" = a-n && die "Refusing to squash a merge: $sha1" # redo merge - author_script=$(get_author_ident_from_commit $sha1) - eval "$author_script" - msg="$(commit_message $sha1)" + author_script_content=$(get_author_ident_from_commit $sha1) + eval "$author_script_content" + msg_content="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} if ! do_with_author output \ - git merge $STRATEGY -m "$msg" $new_parents + git merge --no-ff ${strategy:+-s $strategy} -m \ + "$msg_content" $new_parents then - printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG + printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "Error redoing merge $sha1" fi - echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST" + echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" ;; *) output git cherry-pick "$@" || @@ -365,46 +297,46 @@ nth_string () { } update_squash_messages () { - if test -f "$SQUASH_MSG"; then - mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit - COUNT=$(($(sed -n \ + if test -f "$squash_msg"; then + mv "$squash_msg" "$squash_msg".bak || exit + count=$(($(sed -n \ -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ - -e "q" < "$SQUASH_MSG".bak)+1)) + -e "q" < "$squash_msg".bak)+1)) { - echo "# This is a combination of $COUNT commits." + echo "# This is a combination of $count commits." sed -e 1d -e '2,/^./{ /^$/d - }' <"$SQUASH_MSG".bak - } >"$SQUASH_MSG" + }' <"$squash_msg".bak + } >"$squash_msg" else - commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG" - COUNT=2 + commit_message HEAD > "$fixup_msg" || die "Cannot write $fixup_msg" + count=2 { echo "# This is a combination of 2 commits." echo "# The first commit's message is:" echo - cat "$FIXUP_MSG" - } >"$SQUASH_MSG" + cat "$fixup_msg" + } >"$squash_msg" fi case $1 in squash) - rm -f "$FIXUP_MSG" + rm -f "$fixup_msg" echo - echo "# This is the $(nth_string $COUNT) commit message:" + echo "# This is the $(nth_string $count) commit message:" echo commit_message $2 ;; fixup) echo - echo "# The $(nth_string $COUNT) commit message will be skipped:" + echo "# The $(nth_string $count) commit message will be skipped:" echo commit_message $2 | sed -e 's/^/# /' ;; - esac >>"$SQUASH_MSG" + esac >>"$squash_msg" } peek_next_command () { - sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$TODO" + sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$todo" } # A squash/fixup has failed. Prepare the long version of the squash @@ -414,24 +346,24 @@ peek_next_command () { # messages, effectively causing the combined commit to be used as the # new basis for any further squash/fixups. Args: sha1 rest die_failed_squash() { - mv "$SQUASH_MSG" "$MSG" || exit - rm -f "$FIXUP_MSG" - cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + mv "$squash_msg" "$msg" || exit + rm -f "$fixup_msg" + cp "$msg" "$GIT_DIR"/MERGE_MSG || exit warn warn "Could not apply $1... $2" die_with_patch $1 "" } flush_rewritten_pending() { - test -s "$REWRITTEN_PENDING" || return + test -s "$rewritten_pending" || return newsha1="$(git rev-parse HEAD^0)" - sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST" - rm -f "$REWRITTEN_PENDING" + sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list" + rm -f "$rewritten_pending" } record_in_rewritten() { oldsha1="$(git rev-parse $1)" - echo "$oldsha1" >> "$REWRITTEN_PENDING" + echo "$oldsha1" >> "$rewritten_pending" case "$(peek_next_command)" in squash|s|fixup|f) @@ -443,8 +375,8 @@ record_in_rewritten() { } do_next () { - rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit - read -r command sha1 rest < "$TODO" + rm -f "$msg" "$author_script" "$amend" || exit + read -r command sha1 rest < "$todo" case "$command" in '#'*|''|noop) mark_action_done @@ -472,9 +404,9 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" - echo "$sha1" > "$DOTEST"/stopped-sha + echo "$sha1" > "$state_dir"/stopped-sha make_patch $sha1 - git rev-parse --verify HEAD > "$AMEND" + git rev-parse --verify HEAD > "$amend" warn "Stopped at $sha1... $rest" warn "You can amend the commit now, with" warn @@ -497,47 +429,47 @@ do_next () { esac comment_for_reflog $squash_style - test -f "$DONE" && has_action "$DONE" || + test -f "$done" && has_action "$done" || die "Cannot '$squash_style' without a previous commit" mark_action_done update_squash_messages $squash_style $sha1 - author_script=$(get_author_ident_from_commit HEAD) - echo "$author_script" > "$AUTHOR_SCRIPT" - eval "$author_script" + author_script_content=$(get_author_ident_from_commit HEAD) + echo "$author_script_content" > "$author_script" + eval "$author_script_content" output git reset --soft HEAD^ pick_one -n $sha1 || die_failed_squash $sha1 "$rest" case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --no-verify -F "$SQUASH_MSG" || + do_with_author output git commit --no-verify -F "$squash_msg" || die_failed_squash $sha1 "$rest" ;; *) # This is the final command of this squash/fixup group - if test -f "$FIXUP_MSG" + if test -f "$fixup_msg" then - do_with_author git commit --no-verify -F "$FIXUP_MSG" || + do_with_author git commit --no-verify -F "$fixup_msg" || die_failed_squash $sha1 "$rest" else - cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit + cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG do_with_author git commit --no-verify -e || die_failed_squash $sha1 "$rest" fi - rm -f "$SQUASH_MSG" "$FIXUP_MSG" + rm -f "$squash_msg" "$fixup_msg" ;; esac record_in_rewritten $sha1 ;; x|"exec") - read -r command rest < "$TODO" + read -r command rest < "$todo" mark_action_done printf 'Executing: %s\n' "$rest" # "exec" command doesn't take a sha1 in the todo-list. # => can't just use $sha1 here. - git rev-parse --verify HEAD > "$DOTEST"/stopped-sha + git rev-parse --verify HEAD > "$state_dir"/stopped-sha ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution status=$? if test "$status" -ne 0 @@ -563,42 +495,42 @@ do_next () { warn "Unknown command: $command $sha1 $rest" if git rev-parse --verify -q "$sha1" >/dev/null then - die_with_patch $sha1 "Please fix this in the file $TODO." + die_with_patch $sha1 "Please fix this in the file $todo." else - die "Please fix this in the file $TODO." + die "Please fix this in the file $todo." fi ;; esac - test -s "$TODO" && return + test -s "$todo" && return comment_for_reflog finish && - HEADNAME=$(cat "$DOTEST"/head-name) && - OLDHEAD=$(cat "$DOTEST"/head) && - SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) && - NEWHEAD=$(git rev-parse HEAD) && - case $HEADNAME in + shortonto=$(git rev-parse --short $onto) && + newhead=$(git rev-parse HEAD) && + case $head_name in refs/*) - message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO" && - git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD && - git symbolic-ref HEAD $HEADNAME + message="$GIT_REFLOG_ACTION: $head_name onto $shortonto" && + git update-ref -m "$message" $head_name $newhead $orig_head && + git symbolic-ref \ + -m "$GIT_REFLOG_ACTION: returning to $head_name" \ + HEAD $head_name ;; esac && { - test ! -f "$DOTEST"/verbose || - git diff-tree --stat $(cat "$DOTEST"/head)..HEAD + test ! -f "$state_dir"/verbose || + git diff-tree --stat $orig_head..HEAD } && { - test -s "$REWRITTEN_LIST" && - git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" || + 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" + 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" && + rm -rf "$state_dir" && git gc --auto && - warn "Successfully rebased and updated $HEADNAME." + warn "Successfully rebased and updated $head_name." exit } @@ -618,11 +550,11 @@ skip_unnecessary_picks () { # fd=3 means we skip the command case "$fd,$command" in 3,pick|3,p) - # pick a commit whose parent is current $ONTO -> skip + # pick a commit whose parent is current $onto -> skip sha1=${rest%% *} case "$(git rev-parse --verify --quiet "$sha1"^)" in - "$ONTO"*) - ONTO=$sha1 + "$onto"*) + onto=$sha1 ;; *) fd=1 @@ -637,32 +569,16 @@ skip_unnecessary_picks () { ;; esac printf '%s\n' "$command${rest:+ }$rest" >&$fd - done <"$TODO" >"$TODO.new" 3>>"$DONE" && - mv -f "$TODO".new "$TODO" && + done <"$todo" >"$todo.new" 3>>"$done" && + mv -f "$todo".new "$todo" && case "$(peek_next_command)" in squash|s|fixup|f) - record_in_rewritten "$ONTO" + record_in_rewritten "$onto" ;; esac || die "Could not skip unnecessary pick commands" } -# check if no other options are set -is_standalone () { - test $# -eq 2 -a "$2" = '--' && - test -z "$ONTO" && - test -z "$PRESERVE_MERGES" && - test -z "$STRATEGY" && - test -z "$VERBOSE" -} - -get_saved_options () { - test -d "$REWRITTEN" && PRESERVE_MERGES=t - test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" - test -f "$DOTEST"/verbose && VERBOSE=t - test -f "$DOTEST"/rebase-root && REBASE_ROOT=t -} - # Rearrange the todo list that has both "pick sha1 msg" and # "pick sha1 fixup!/squash! msg" appears in it so that the latter # comes immediately after the former, and change "pick" to @@ -699,7 +615,7 @@ rearrange_squash () { esac printf '%s\n' "$pick $sha1 $message" used="$used$sha1 " - while read -r squash action msg + while read -r squash action msg_content do case " $used" in *" $squash "*) continue ;; @@ -709,13 +625,13 @@ rearrange_squash () { +*) action="${action#+}" # full sha1 prefix test - case "$msg" in "$sha1"*) emit=1;; esac ;; + case "$msg_content" in "$sha1"*) emit=1;; esac ;; *) # message prefix test - case "$message" in "$msg"*) emit=1;; esac ;; + case "$message" in "$msg_content"*) emit=1;; esac ;; esac if test $emit = 1; then - printf '%s\n' "$action $squash $action! $msg" + printf '%s\n' "$action $squash $action! $msg_content" used="$used$squash " fi done <"$1.sq" @@ -724,296 +640,159 @@ rearrange_squash () { rm -f "$1.sq" "$1.rearranged" } -LF=' -' -parse_onto () { - case "$1" in - *...*) - if left=${1%...*} right=${1#*...} && - onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) - then - case "$onto" in - ?*"$LF"?* | '') - exit 1 ;; - esac - echo "$onto" - exit 0 - fi - esac - git rev-parse --verify "$1^0" -} - -while test $# != 0 -do - case "$1" in - --no-verify) - OK_TO_SKIP_PRE_REBASE=yes - ;; - --verify) - OK_TO_SKIP_PRE_REBASE= - ;; - --continue) - is_standalone "$@" || usage - get_saved_options - comment_for_reflog continue - - test -d "$DOTEST" || die "No interactive rebase running" - - # Sanity check - git rev-parse --verify HEAD >/dev/null || - die "Cannot read HEAD" - git update-index --ignore-submodules --refresh && - git diff-files --quiet --ignore-submodules || - die "Working tree is dirty" - - # do we have anything to commit? - if git diff-index --cached --quiet --ignore-submodules HEAD -- +case "$action" in +continue) + # do we have anything to commit? + if git diff-index --cached --quiet --ignore-submodules HEAD -- + then + : Nothing to commit -- skip this + else + . "$author_script" || + die "Cannot find the author identity" + current_head= + if test -f "$amend" then - : Nothing to commit -- skip this - else - . "$AUTHOR_SCRIPT" || - die "Cannot find the author identity" - amend= - if test -f "$AMEND" - then - amend=$(git rev-parse --verify HEAD) - test "$amend" = $(cat "$AMEND") || - die "\ + current_head=$(git rev-parse --verify HEAD) + test "$current_head" = $(cat "$amend") || + die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." - git reset --soft HEAD^ || - die "Cannot rewind the HEAD" - fi - do_with_author git commit --no-verify -F "$MSG" -e || { - test -n "$amend" && git reset --soft $amend - die "Could not commit staged changes." - } + git reset --soft HEAD^ || + die "Cannot rewind the HEAD" fi + do_with_author git commit --no-verify -F "$msg" -e || { + test -n "$current_head" && git reset --soft $current_head + die "Could not commit staged changes." + } + fi - record_in_rewritten "$(cat "$DOTEST"/stopped-sha)" - - require_clean_work_tree "rebase" - do_rest - ;; - --abort) - is_standalone "$@" || usage - get_saved_options - comment_for_reflog abort - - git rerere clear - test -d "$DOTEST" || die "No interactive rebase running" - - HEADNAME=$(cat "$DOTEST"/head-name) - HEAD=$(cat "$DOTEST"/head) - case $HEADNAME in - refs/*) - git symbolic-ref HEAD $HEADNAME - ;; - esac && - output git reset --hard $HEAD && - rm -rf "$DOTEST" - exit - ;; - --skip) - is_standalone "$@" || usage - get_saved_options - comment_for_reflog skip - - git rerere clear - test -d "$DOTEST" || die "No interactive rebase running" - - output git reset --hard && do_rest - ;; - -s) - case "$#,$1" in - *,*=*) - STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;; - 1,*) - usage ;; - *) - STRATEGY="-s $2" - shift ;; - esac - ;; - -m) - # we use merge anyway - ;; - -v) - VERBOSE=t - ;; - -p) - PRESERVE_MERGES=t - ;; - -i) - # yeah, we know - ;; - --no-ff) - NEVER_FF=t - ;; - --root) - REBASE_ROOT=t - ;; - --autosquash) - AUTOSQUASH=t - ;; - --no-autosquash) - AUTOSQUASH= - ;; - --onto) - shift - ONTO=$(parse_onto "$1") || - die "Does not point to a valid commit: $1" - ;; - --) - shift - test -z "$REBASE_ROOT" -a $# -ge 1 -a $# -le 2 || - test ! -z "$REBASE_ROOT" -a $# -le 1 || usage - test -d "$DOTEST" && - die "Interactive rebase already started" - - git var GIT_COMMITTER_IDENT >/dev/null || - die "You need to set your committer info first" + record_in_rewritten "$(cat "$state_dir"/stopped-sha)" - if test -z "$REBASE_ROOT" - then - UPSTREAM_ARG="$1" - UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" - test -z "$ONTO" && ONTO=$UPSTREAM - shift - else - UPSTREAM= - UPSTREAM_ARG=--root - test -z "$ONTO" && - die "You must specify --onto when using --root" - fi - run_pre_rebase_hook "$UPSTREAM_ARG" "$@" + require_clean_work_tree "rebase" + do_rest + ;; +skip) + git rerere clear - comment_for_reflog start + do_rest + ;; +esac - require_clean_work_tree "rebase" "Please commit or stash them." +git var GIT_COMMITTER_IDENT >/dev/null || + die "You need to set your committer info first" - if test ! -z "$1" - then - output git checkout "$1" -- || - die "Could not checkout $1" - fi +comment_for_reflog start - HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" - mkdir "$DOTEST" || die "Could not create temporary $DOTEST" +if test ! -z "$switch_to" +then + output git checkout "$switch_to" -- || + die "Could not checkout $switch_to" +fi - : > "$DOTEST"/interactive || die "Could not mark as interactive" - git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null || - echo "detached HEAD" > "$DOTEST"/head-name +orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?" +mkdir "$state_dir" || die "Could not create temporary $state_dir" - echo $HEAD > "$DOTEST"/head - case "$REBASE_ROOT" in - '') - rm -f "$DOTEST"/rebase-root ;; - *) - : >"$DOTEST"/rebase-root ;; - esac - echo $ONTO > "$DOTEST"/onto - test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy - test t = "$VERBOSE" && : > "$DOTEST"/verbose - if test t = "$PRESERVE_MERGES" - then - if test -z "$REBASE_ROOT" - then - mkdir "$REWRITTEN" && - for c in $(git merge-base --all $HEAD $UPSTREAM) - do - echo $ONTO > "$REWRITTEN"/$c || - die "Could not init rewritten commits" - done - else - mkdir "$REWRITTEN" && - echo $ONTO > "$REWRITTEN"/root || - die "Could not init rewritten commits" - fi - # No cherry-pick because our first pass is to determine - # parents to rewrite and skipping dropped commits would - # prematurely end our probe - MERGES_OPTION= - first_after_upstream="$(git rev-list --reverse --first-parent $UPSTREAM..$HEAD | head -n 1)" - else - MERGES_OPTION="--no-merges --cherry-pick" - fi - - SHORTHEAD=$(git rev-parse --short $HEAD) - SHORTONTO=$(git rev-parse --short $ONTO) - if test -z "$REBASE_ROOT" - # this is now equivalent to ! -z "$UPSTREAM" - then - SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) - REVISIONS=$UPSTREAM...$HEAD - SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD - else - REVISIONS=$ONTO...$HEAD - SHORTREVISIONS=$SHORTHEAD - fi - git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ - --abbrev=7 --reverse --left-right --topo-order \ - $REVISIONS | \ - sed -n "s/^>//p" | - while read -r shortsha1 rest +: > "$state_dir"/interactive || die "Could not mark as interactive" +write_basic_state +if test t = "$preserve_merges" +then + if test -z "$rebase_root" + then + mkdir "$rewritten" && + for c in $(git merge-base --all $orig_head $upstream) do - if test t != "$PRESERVE_MERGES" - then - printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" - else - sha1=$(git rev-parse $shortsha1) - if test -z "$REBASE_ROOT" - then - preserve=t - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) - do - if test -f "$REWRITTEN"/$p -a \( $p != $ONTO -o $sha1 = $first_after_upstream \) - then - preserve=f - fi - done - else - preserve=f - fi - if test f = "$preserve" - then - touch "$REWRITTEN"/$sha1 - printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" - fi - fi + echo $onto > "$rewritten"/$c || + die "Could not init rewritten commits" done - - # Watch for commits that been dropped by --cherry-pick - if test t = "$PRESERVE_MERGES" + else + mkdir "$rewritten" && + echo $onto > "$rewritten"/root || + die "Could not init rewritten commits" + fi + # No cherry-pick because our first pass is to determine + # parents to rewrite and skipping dropped commits would + # prematurely end our probe + merges_option= + first_after_upstream="$(git rev-list --reverse --first-parent $upstream..$orig_head | head -n 1)" +else + merges_option="--no-merges --cherry-pick" +fi + +shorthead=$(git rev-parse --short $orig_head) +shortonto=$(git rev-parse --short $onto) +if test -z "$rebase_root" + # this is now equivalent to ! -z "$upstream" +then + shortupstream=$(git rev-parse --short $upstream) + revisions=$upstream...$orig_head + shortrevisions=$shortupstream..$shorthead +else + revisions=$onto...$orig_head + shortrevisions=$shorthead +fi +git rev-list $merges_option --pretty=oneline --abbrev-commit \ + --abbrev=7 --reverse --left-right --topo-order \ + $revisions | \ + sed -n "s/^>//p" | +while read -r shortsha1 rest +do + if test t != "$preserve_merges" + then + printf '%s\n' "pick $shortsha1 $rest" >> "$todo" + else + sha1=$(git rev-parse $shortsha1) + if test -z "$rebase_root" then - mkdir "$DROPPED" - # Save all non-cherry-picked changes - git rev-list $REVISIONS --left-right --cherry-pick | \ - sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks - # Now all commits and note which ones are missing in - # not-cherry-picks and hence being dropped - git rev-list $REVISIONS | - while read rev + preserve=t + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) do - if test -f "$REWRITTEN"/$rev -a "$(sane_grep "$rev" "$DOTEST"/not-cherry-picks)" = "" + if test -f "$rewritten"/$p -a \( $p != $onto -o $sha1 = $first_after_upstream \) then - # Use -f2 because if rev-list is telling us this commit is - # not worthwhile, we don't want to track its multiple heads, - # just the history of its first-parent for others that will - # be rebasing on top of it - git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev - short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev) - sane_grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO" - rm "$REWRITTEN"/$rev + preserve=f fi done + else + preserve=f + fi + if test f = "$preserve" + then + touch "$rewritten"/$sha1 + printf '%s\n' "pick $shortsha1 $rest" >> "$todo" fi + fi +done - test -s "$TODO" || echo noop >> "$TODO" - test -n "$AUTOSQUASH" && rearrange_squash "$TODO" - cat >> "$TODO" << EOF +# Watch for commits that been dropped by --cherry-pick +if test t = "$preserve_merges" +then + mkdir "$dropped" + # Save all non-cherry-picked changes + git rev-list $revisions --left-right --cherry-pick | \ + sed -n "s/^>//p" > "$state_dir"/not-cherry-picks + # Now all commits and note which ones are missing in + # not-cherry-picks and hence being dropped + git rev-list $revisions | + while read rev + do + if test -f "$rewritten"/$rev -a "$(sane_grep "$rev" "$state_dir"/not-cherry-picks)" = "" + then + # Use -f2 because if rev-list is telling us this commit is + # not worthwhile, we don't want to track its multiple heads, + # just the history of its first-parent for others that will + # be rebasing on top of it + git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev + short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev) + sane_grep -v "^[a-z][a-z]* $short" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo" + rm "$rewritten"/$rev + fi + done +fi + +test -s "$todo" || echo noop >> "$todo" +test -n "$autosquash" && rearrange_squash "$todo" +cat >> "$todo" << EOF -# Rebase $SHORTREVISIONS onto $SHORTONTO +# Rebase $shortrevisions onto $shortonto # # Commands: # p, pick = use commit @@ -1028,22 +807,18 @@ first and then run 'git rebase --continue' again." # EOF - has_action "$TODO" || - die_abort "Nothing to do" +has_action "$todo" || + die_abort "Nothing to do" - cp "$TODO" "$TODO".backup - git_editor "$TODO" || - die_abort "Could not execute editor" +cp "$todo" "$todo".backup +git_editor "$todo" || + die_abort "Could not execute editor" - has_action "$TODO" || - die_abort "Nothing to do" +has_action "$todo" || + die_abort "Nothing to do" - test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks +test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks - output git checkout $ONTO || die_abort "could not detach HEAD" - git update-ref ORIG_HEAD $HEAD - do_rest - ;; - esac - shift -done +output git checkout $onto || die_abort "could not detach HEAD" +git update-ref ORIG_HEAD $orig_head +do_rest diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh new file mode 100644 index 0000000000..26afc75cc7 --- /dev/null +++ b/git-rebase--merge.sh @@ -0,0 +1,151 @@ +#!/bin/sh +# +# Copyright (c) 2010 Junio C Hamano. +# + +. git-sh-setup + +prec=4 + +read_state () { + onto_name=$(cat "$state_dir"/onto_name) && + end=$(cat "$state_dir"/end) && + msgnum=$(cat "$state_dir"/msgnum) +} + +continue_merge () { + test -d "$state_dir" || die "$state_dir directory does not exist" + + unmerged=$(git ls-files -u) + if test -n "$unmerged" + then + echo "You still have unmerged paths in your index" + echo "did you forget to use git add?" + die "$resolvemsg" + fi + + cmt=`cat "$state_dir/current"` + if ! git diff-index --quiet --ignore-submodules HEAD -- + then + if ! git commit --no-verify -C "$cmt" + then + echo "Commit failed, please do not call \"git commit\"" + echo "directly, but instead do one of the following: " + die "$resolvemsg" + fi + if test -z "$GIT_QUIET" + then + printf "Committed: %0${prec}d " $msgnum + fi + echo "$cmt $(git rev-parse HEAD^0)" >> "$state_dir/rewritten" + else + if test -z "$GIT_QUIET" + then + printf "Already applied: %0${prec}d " $msgnum + fi + fi + test -z "$GIT_QUIET" && + GIT_PAGER='' git log --format=%s -1 "$cmt" + + # onto the next patch: + msgnum=$(($msgnum + 1)) + echo "$msgnum" >"$state_dir/msgnum" +} + +call_merge () { + cmt="$(cat "$state_dir/cmt.$1")" + echo "$cmt" > "$state_dir/current" + hd=$(git rev-parse --verify HEAD) + cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD) + msgnum=$(cat "$state_dir/msgnum") + eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' + eval GITHEAD_$hd='$onto_name' + export GITHEAD_$cmt GITHEAD_$hd + if test -n "$GIT_QUIET" + then + GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY + fi + test -z "$strategy" && strategy=recursive + eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"' + rv=$? + case "$rv" in + 0) + unset GITHEAD_$cmt GITHEAD_$hd + return + ;; + 1) + git rerere $allow_rerere_autoupdate + die "$resolvemsg" + ;; + 2) + echo "Strategy: $strategy failed, try another" 1>&2 + die "$resolvemsg" + ;; + *) + die "Unknown exit code ($rv) from command:" \ + "git-merge-$strategy $cmt^ -- HEAD $cmt" + ;; + esac +} + +finish_rb_merge () { + move_to_original_branch + git notes copy --for-rewrite=rebase < "$state_dir"/rewritten + if test -x "$GIT_DIR"/hooks/post-rewrite && + test -s "$state_dir"/rewritten; then + "$GIT_DIR"/hooks/post-rewrite rebase < "$state_dir"/rewritten + fi + rm -r "$state_dir" + say All done. +} + +case "$action" in +continue) + read_state + continue_merge + while test "$msgnum" -le "$end" + do + call_merge "$msgnum" + continue_merge + done + finish_rb_merge + exit + ;; +skip) + read_state + git rerere clear + msgnum=$(($msgnum + 1)) + while test "$msgnum" -le "$end" + do + call_merge "$msgnum" + continue_merge + done + finish_rb_merge + exit + ;; +esac + +mkdir -p "$state_dir" +echo "$onto_name" > "$state_dir/onto_name" +write_basic_state + +msgnum=0 +for cmt in `git rev-list --reverse --no-merges "$revisions"` +do + msgnum=$(($msgnum + 1)) + echo "$cmt" > "$state_dir/cmt.$msgnum" +done + +echo 1 >"$state_dir/msgnum" +echo $msgnum >"$state_dir/end" + +end=$msgnum +msgnum=1 + +while test "$msgnum" -le "$end" +do + call_merge "$msgnum" + continue_merge +done + +finish_rb_merge diff --git a/git-rebase.sh b/git-rebase.sh index cbb0ea90ed..d7855ea1c6 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] [--no-ff] [--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> @@ -28,7 +28,39 @@ Example: git-rebase master~1 topic ' SUBDIRECTORY_OK=Yes -OPTIONS_SPEC= +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>] +git rebase [-i] [options] --onto <newbase> --root [<branch>] +git-rebase [-i] --continue | --abort | --skip +-- + Available options are +v,verbose! display a diffstat of what changed upstream +q,quiet! be quiet. implies --no-stat +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! use merging strategies to rebase +i,interactive! let the user edit the list of commits to rebase +f,force-rebase! force rebase even if branch is up to date +X,strategy-option=! pass the argument through to the merge strategy +stat! display a diffstat of what changed upstream +n,no-stat! do not show diffstat of what changed upstream +verify allow pre-rebase hook to run +rerere-autoupdate allow rerere to update index with resolved conflicts +root! rebase all reachable commits up to the root(s) +autosquash move commits that begin with squash!/fixup! under -i +committer-date-is-author-date! passed to 'git am' +ignore-date! passed to 'git am' +whitespace=! passed to 'git apply' +ignore-whitespace! passed to 'git apply' +C=! passed to 'git apply' + Actions: +continue! continue rebasing process +abort! abort rebasing process and restore original branch +skip! skip current patch and continue rebasing process +" . git-sh-setup set_reflog_action rebase require_work_tree @@ -36,18 +68,18 @@ cd_to_toplevel LF=' ' -OK_TO_SKIP_PRE_REBASE= -RESOLVEMSG=" +ok_to_skip_pre_rebase= +resolvemsg=" When you have resolved this problem run \"git rebase --continue\". If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " -unset newbase -strategy=recursive +unset onto +strategy= strategy_opts= do_merge= -dotest="$GIT_DIR"/rebase-merge -prec=4 +merge_dir="$GIT_DIR"/rebase-merge +apply_dir="$GIT_DIR"/rebase-apply verbose= diffstat= test "$(git config --bool rebase.stat)" = true && diffstat=t @@ -55,139 +87,90 @@ git_am_opt= rebase_root= force_rebase= allow_rerere_autoupdate= - -continue_merge () { - test -n "$prev_head" || die "prev_head must be defined" - test -d "$dotest" || die "$dotest directory does not exist" - - unmerged=$(git ls-files -u) - if test -n "$unmerged" - then - echo "You still have unmerged paths in your index" - echo "did you forget to use git add?" - die "$RESOLVEMSG" - fi - - cmt=`cat "$dotest/current"` - if ! git diff-index --quiet --ignore-submodules HEAD -- +# Non-empty if a rebase was in progress when 'git rebase' was invoked +in_progress= +# One of {am, merge, interactive} +type= +# One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge} +state_dir= +# One of {'', continue, skip, abort}, as parsed from command line +action= +preserve_merges= +autosquash= +test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t + +read_basic_state () { + head_name=$(cat "$state_dir"/head-name) && + onto=$(cat "$state_dir"/onto) && + # We always write to orig-head, but interactive rebase used to write to + # head. Fall back to reading from head to cover for the case that the + # user upgraded git with an ongoing interactive rebase. + if test -f "$state_dir"/orig-head then - if ! git commit --no-verify -C "$cmt" - then - echo "Commit failed, please do not call \"git commit\"" - echo "directly, but instead do one of the following: " - die "$RESOLVEMSG" - fi - if test -z "$GIT_QUIET" - then - printf "Committed: %0${prec}d " $msgnum - fi - echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten" + orig_head=$(cat "$state_dir"/orig-head) else - if test -z "$GIT_QUIET" - then - printf "Already applied: %0${prec}d " $msgnum - fi - fi - test -z "$GIT_QUIET" && - GIT_PAGER='' git log --format=%s -1 "$cmt" - - prev_head=`git rev-parse HEAD^0` - # save the resulting commit so we can read-tree on it later - echo "$prev_head" > "$dotest/prev_head" + orig_head=$(cat "$state_dir"/head) + fi && + GIT_QUIET=$(cat "$state_dir"/quiet) && + test -f "$state_dir"/verbose && verbose=t + test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)" + test -f "$state_dir"/strategy_opts && + strategy_opts="$(cat "$state_dir"/strategy_opts)" + test -f "$state_dir"/allow_rerere_autoupdate && + allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)" +} - # onto the next patch: - msgnum=$(($msgnum + 1)) - echo "$msgnum" >"$dotest/msgnum" +write_basic_state () { + echo "$head_name" > "$state_dir"/head-name && + echo "$onto" > "$state_dir"/onto && + echo "$orig_head" > "$state_dir"/orig-head && + echo "$GIT_QUIET" > "$state_dir"/quiet && + test t = "$verbose" && : > "$state_dir"/verbose + test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy + test -n "$strategy_opts" && echo "$strategy_opts" > \ + "$state_dir"/strategy_opts + test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ + "$state_dir"/allow_rerere_autoupdate } -call_merge () { - cmt="$(cat "$dotest/cmt.$1")" - echo "$cmt" > "$dotest/current" - hd=$(git rev-parse --verify HEAD) - cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD) - msgnum=$(cat "$dotest/msgnum") - end=$(cat "$dotest/end") - eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' - eval GITHEAD_$hd='$(cat "$dotest/onto_name")' - export GITHEAD_$cmt GITHEAD_$hd - if test -n "$GIT_QUIET" - then - GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY - fi - eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"' - rv=$? - case "$rv" in - 0) - unset GITHEAD_$cmt GITHEAD_$hd - return - ;; - 1) - git rerere $allow_rerere_autoupdate - die "$RESOLVEMSG" - ;; - 2) - echo "Strategy: $rv $strategy failed, try another" 1>&2 - die "$RESOLVEMSG" +output () { + case "$verbose" in + '') + output=$("$@" 2>&1 ) + status=$? + test $status != 0 && printf "%s\n" "$output" + return $status ;; *) - die "Unknown exit code ($rv) from command:" \ - "git-merge-$strategy $cmt^ -- HEAD $cmt" + "$@" ;; esac } move_to_original_branch () { - test -z "$head_name" && - head_name="$(cat "$dotest"/head-name)" && - onto="$(cat "$dotest"/onto)" && - orig_head="$(cat "$dotest"/orig-head)" case "$head_name" in refs/*) message="rebase finished: $head_name onto $onto" git update-ref -m "$message" \ $head_name $(git rev-parse HEAD) $orig_head && - git symbolic-ref HEAD $head_name || + git symbolic-ref \ + -m "rebase finished: returning to $head_name" \ + HEAD $head_name || die "Could not move back to $head_name" ;; esac } -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. -} - -is_interactive () { - while test $# != 0 - do - case "$1" in - -i|--interactive) - interactive_rebase=explicit - break - ;; - -p|--preserve-merges) - interactive_rebase=implied - ;; - esac - shift - done - +run_specific_rebase () { if [ "$interactive_rebase" = implied ]; then GIT_EDITOR=: export GIT_EDITOR fi - - test -n "$interactive_rebase" || test -f "$dotest"/interactive + . git-rebase--$type } run_pre_rebase_hook () { - if test -z "$OK_TO_SKIP_PRE_REBASE" && + if test -z "$ok_to_skip_pre_rebase" && test -x "$GIT_DIR/hooks/pre-rebase" then "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || @@ -195,163 +178,94 @@ run_pre_rebase_hook () { fi } -test -f "$GIT_DIR"/rebase-apply/applying && +test -f "$apply_dir"/applying && die 'It looks like git-am is in progress. Cannot rebase.' -is_interactive "$@" && exec git-rebase--interactive "$@" +if test -d "$apply_dir" +then + type=am + state_dir="$apply_dir" +elif test -d "$merge_dir" +then + if test -f "$merge_dir"/interactive + then + type=interactive + interactive_rebase=explicit + else + type=merge + fi + state_dir="$merge_dir" +fi +test -n "$type" && in_progress=t +total_argc=$# while test $# != 0 do case "$1" in --no-verify) - OK_TO_SKIP_PRE_REBASE=yes + ok_to_skip_pre_rebase=yes ;; --verify) - OK_TO_SKIP_PRE_REBASE= - ;; - --continue) - 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" - exit 1 - } - if test -d "$dotest" - then - prev_head=$(cat "$dotest/prev_head") - end=$(cat "$dotest/end") - msgnum=$(cat "$dotest/msgnum") - onto=$(cat "$dotest/onto") - GIT_QUIET=$(cat "$dotest/quiet") - continue_merge - while test "$msgnum" -le "$end" - do - call_merge "$msgnum" - continue_merge - done - finish_rb_merge - exit - fi - head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) && - onto=$(cat "$GIT_DIR"/rebase-apply/onto) && - orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) && - GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet) - git am --resolved --3way --resolvemsg="$RESOLVEMSG" && - move_to_original_branch - exit + ok_to_skip_pre_rebase= ;; - --skip) - test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || - die "No rebase in progress?" - - git reset --hard HEAD || exit $? - if test -d "$dotest" - then - git rerere clear - prev_head=$(cat "$dotest/prev_head") - end=$(cat "$dotest/end") - msgnum=$(cat "$dotest/msgnum") - msgnum=$(($msgnum + 1)) - onto=$(cat "$dotest/onto") - GIT_QUIET=$(cat "$dotest/quiet") - while test "$msgnum" -le "$end" - do - call_merge "$msgnum" - continue_merge - done - finish_rb_merge - exit - fi - head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) && - onto=$(cat "$GIT_DIR"/rebase-apply/onto) && - orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) && - GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet) - git am -3 --skip --resolvemsg="$RESOLVEMSG" && - move_to_original_branch - exit - ;; - --abort) - test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || - die "No rebase in progress?" - - git rerere clear - - test -d "$dotest" || dotest="$GIT_DIR"/rebase-apply - - head_name="$(cat "$dotest"/head-name)" && - case "$head_name" in - refs/*) - git symbolic-ref HEAD $head_name || - die "Could not move back to $head_name" - ;; - esac - git reset --hard $(cat "$dotest/orig-head") - rm -r "$dotest" - exit + --continue|--skip|--abort) + test $total_argc -eq 2 || usage + action=${1##--} ;; --onto) test 2 -le "$#" || usage - newbase="$2" + onto="$2" shift ;; - -M|-m|--m|--me|--mer|--merg|--merge) + -i) + interactive_rebase=explicit + ;; + -p) + preserve_merges=t + test -z "$interactive_rebase" && interactive_rebase=implied + ;; + --autosquash) + autosquash=t + ;; + --no-autosquash) + autosquash= + ;; + -M|-m) do_merge=t ;; - -X*|--strategy-option*) - case "$#,$1" in - 1,-X|1,--strategy-option) - usage ;; - *,-X|*,--strategy-option) - newopt="$2" - shift ;; - *,--strategy-option=*) - newopt="$(expr " $1" : ' --strategy-option=\(.*\)')" ;; - *,-X*) - newopt="$(expr " $1" : ' -X\(.*\)')" ;; - 1,*) - usage ;; - esac - strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$newopt")" + -X) + shift + strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$1")" do_merge=t + test -z "$strategy" && strategy=recursive ;; - -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ - --strateg=*|--strategy=*|\ - -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) - case "$#,$1" in - *,*=*) - strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; - 1,*) - usage ;; - *) - strategy="$2" - shift ;; - esac + -s) + shift + strategy="$1" do_merge=t ;; - -n|--no-stat) + -n) diffstat= ;; --stat) diffstat=t ;; - -v|--verbose) + -v) verbose=t diffstat=t GIT_QUIET= ;; - -q|--quiet) + -q) GIT_QUIET=t git_am_opt="$git_am_opt -q" verbose= diffstat= ;; - --whitespace=*) - git_am_opt="$git_am_opt $1" + --whitespace) + shift + git_am_opt="$git_am_opt --whitespace=$1" case "$1" in - --whitespace=fix|--whitespace=strip) + fix|strip) force_rebase=t ;; esac @@ -363,22 +277,21 @@ do git_am_opt="$git_am_opt $1" force_rebase=t ;; - -C*) - git_am_opt="$git_am_opt $1" + -C) + shift + git_am_opt="$git_am_opt -C$1" ;; --root) rebase_root=t ;; - -f|--f|--fo|--for|--forc|--force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff) + -f|--no-ff) force_rebase=t ;; --rerere-autoupdate|--no-rerere-autoupdate) allow_rerere_autoupdate="$1" ;; - -*) - usage - ;; - *) + --) + shift break ;; esac @@ -386,58 +299,106 @@ do done test $# -gt 2 && usage -if test $# -eq 0 && test -z "$rebase_root" +if test -n "$action" 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.' + test -z "$in_progress" && die "No rebase in progress?" + # Only interactive rebase uses detailed reflog messages + if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase + then + GIT_REFLOG_ACTION="rebase -i ($action)" + export GIT_REFLOG_ACTION + fi fi -# Make sure we do not have $GIT_DIR/rebase-apply -if test -z "$do_merge" +case "$action" in +continue) + # Sanity check + git rev-parse --verify HEAD >/dev/null || + die "Cannot read HEAD" + 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" + exit 1 + } + read_basic_state + run_specific_rebase + ;; +skip) + output git reset --hard HEAD || exit $? + read_basic_state + run_specific_rebase + ;; +abort) + git rerere clear + read_basic_state + case "$head_name" in + refs/*) + git symbolic-ref -m "rebase: aborting" HEAD $head_name || + die "Could not move back to $head_name" + ;; + esac + output git reset --hard $orig_head + rm -r "$state_dir" + exit + ;; +esac + +# Make sure no rebase is in progress +if test -n "$in_progress" then - if mkdir "$GIT_DIR"/rebase-apply 2>/dev/null - then - rmdir "$GIT_DIR"/rebase-apply - else - echo >&2 ' -It seems that I cannot create a rebase-apply directory, and -I wonder if you are in the middle of patch application or another -rebase. If that is not the case, please - rm -fr '"$GIT_DIR"'/rebase-apply + die ' +It seems that there is already a '"${state_dir##*/}"' directory, and +I wonder if you are in the middle of another rebase. If that is the +case, please try + git rebase (--continue | --abort | --skip) +If that is not the case, please + rm -fr '"$state_dir"' and run me again. I am stopping in case you still have something valuable there.' - exit 1 - fi -else - if test -d "$dotest" - then - die "previous rebase directory $dotest still exists." \ - 'Try git rebase (--continue | --abort | --skip)' - fi fi -require_clean_work_tree "rebase" "Please commit or stash them." +if test -n "$interactive_rebase" +then + type=interactive + state_dir="$merge_dir" +elif test -n "$do_merge" +then + type=merge + state_dir="$merge_dir" +else + type=am + state_dir="$apply_dir" +fi if test -z "$rebase_root" then - # The upstream head must be given. Make sure it is valid. - upstream_name="$1" - shift + case "$#" in + 0) + if ! upstream_name=$(git rev-parse --symbolic-full-name \ + --verify -q @{upstream} 2>/dev/null) + then + . git-parse-remote + error_on_missing_default_upstream "rebase" "rebase" \ + "against" "git rebase <upstream branch>" + fi + ;; + *) upstream_name="$1" + shift + ;; + esac upstream=`git rev-parse --verify "${upstream_name}^0"` || die "invalid upstream $upstream_name" - unset root_flag upstream_arg="$upstream_name" else - test -z "$newbase" && die "--root must be used with --onto" + test -z "$onto" && die "You must specify --onto when using --root" unset upstream_name unset upstream - root_flag="--root" - upstream_arg="$root_flag" + upstream_arg=--root fi # Make sure the branch to rebase onto is valid. -onto_name=${newbase-"$upstream_name"} +onto_name=${onto-"$upstream_name"} case "$onto_name" in *...*) if left=${onto_name%...*} right=${onto_name#*...} && @@ -456,13 +417,11 @@ case "$onto_name" in fi ;; *) - onto=$(git rev-parse --verify "${onto_name}^0") || exit + onto=$(git rev-parse --verify "${onto_name}^0") || + die "Does not point to a valid commit: $1" ;; esac -# If a hook exists, give it a chance to interrupt -run_pre_rebase_hook "$upstream_arg" "$@" - # If the branch to rebase is given, that is the branch we will rebase # $branch_name -- branch being rebased, or HEAD (already detached) # $orig_head -- commit object name of tip of the branch before rebasing @@ -475,10 +434,10 @@ case "$#" in switch_to="$1" if git show-ref --verify --quiet -- "refs/heads/$1" && - branch=$(git rev-parse -q --verify "refs/heads/$1") + orig_head=$(git rev-parse -q --verify "refs/heads/$1") then head_name="refs/heads/$1" - elif branch=$(git rev-parse -q --verify "$1") + elif orig_head=$(git rev-parse -q --verify "$1") then head_name="detached HEAD" else @@ -496,20 +455,23 @@ case "$#" in head_name="detached HEAD" branch_name=HEAD ;# detached fi - branch=$(git rev-parse --verify "${branch_name}^0") || exit + orig_head=$(git rev-parse --verify "${branch_name}^0") || exit ;; esac -orig_head=$branch -# Now we are rebasing commits $upstream..$branch (or with --root, -# everything leading up to $branch) on top of $onto +require_clean_work_tree "rebase" "Please commit or stash them." + +# Now we are rebasing commits $upstream..$orig_head (or with --root, +# everything leading up to $orig_head) on top of $onto # Check if we are already based on $onto with linear history, -# but this should be done only when upstream and onto are the same. -mb=$(git merge-base "$onto" "$branch") -if test "$upstream" = "$onto" && test "$mb" = "$onto" && +# but this should be done only when upstream and onto are the same +# and if this is not an interactive rebase. +mb=$(git merge-base "$onto" "$orig_head") +if test "$type" != interactive && test "$upstream" = "$onto" && + test "$mb" = "$onto" && # linear history? - ! (git rev-list --parents "$onto".."$branch" | sane_grep " .* ") > /dev/null + ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null then if test -z "$force_rebase" then @@ -522,10 +484,8 @@ then fi fi -# Detach HEAD and reset the tree -say "First, rewinding head to replay your work on top of it..." -git checkout -q "$onto^0" || die "could not detach HEAD" -git update-ref ORIG_HEAD $branch +# If a hook exists, give it a chance to interrupt +run_pre_rebase_hook "$upstream_arg" "$@" if test -n "$diffstat" then @@ -537,9 +497,16 @@ then GIT_PAGER='' git diff --stat --summary "$mb" "$onto" fi +test "$type" = interactive && run_specific_rebase + +# Detach HEAD and reset the tree +say "First, rewinding head to replay your work on top of it..." +git checkout -q "$onto^0" || die "could not detach HEAD" +git update-ref ORIG_HEAD $orig_head + # If the $onto is a proper descendant of the tip of the branch, then # we just fast-forwarded. -if test "$mb" = "$branch" +if test "$mb" = "$orig_head" then say "Fast-forwarded $branch_name to $onto_name." move_to_original_branch @@ -553,51 +520,4 @@ else revisions="$upstream..$orig_head" fi -if test -z "$do_merge" -then - git format-patch -k --stdout --full-index --ignore-if-in-upstream \ - --src-prefix=a/ --dst-prefix=b/ \ - --no-renames $root_flag "$revisions" | - git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" && - move_to_original_branch - ret=$? - test 0 != $ret -a -d "$GIT_DIR"/rebase-apply && - echo $head_name > "$GIT_DIR"/rebase-apply/head-name && - echo $onto > "$GIT_DIR"/rebase-apply/onto && - echo $orig_head > "$GIT_DIR"/rebase-apply/orig-head && - echo "$GIT_QUIET" > "$GIT_DIR"/rebase-apply/quiet - exit $ret -fi - -# start doing a rebase with git-merge -# this is rename-aware if the recursive (default) strategy is used - -mkdir -p "$dotest" -echo "$onto" > "$dotest/onto" -echo "$onto_name" > "$dotest/onto_name" -prev_head=$orig_head -echo "$prev_head" > "$dotest/prev_head" -echo "$orig_head" > "$dotest/orig-head" -echo "$head_name" > "$dotest/head-name" -echo "$GIT_QUIET" > "$dotest/quiet" - -msgnum=0 -for cmt in `git rev-list --reverse --no-merges "$revisions"` -do - msgnum=$(($msgnum + 1)) - echo "$cmt" > "$dotest/cmt.$msgnum" -done - -echo 1 >"$dotest/msgnum" -echo $msgnum >"$dotest/end" - -end=$msgnum -msgnum=1 - -while test "$msgnum" -le "$end" -do - call_merge "$msgnum" - continue_merge -done - -finish_rb_merge +run_specific_rebase diff --git a/git-request-pull.sh b/git-request-pull.sh index 6fdea397dd..fc080cc5e4 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -15,7 +15,6 @@ p show patch text as well ' . git-sh-setup -. git-parse-remote GIT_PAGER= export GIT_PAGER @@ -55,7 +54,7 @@ branch=$(git ls-remote "$url" \ p q }") -url=$(get_remote_url "$url") +url=$(git ls-remote --get-url "$url") if [ -z "$branch" ]; then echo "warn: No branch of $url is at:" >&2 git log --max-count=1 --pretty='tformat:warn: %h: %s' $headrev >&2 diff --git a/git-send-email.perl b/git-send-email.perl index 76565de2ee..98ab33aae7 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1091,7 +1091,7 @@ X-Mailer: git-send-email $gitversion "VALUES: server=$smtp_server ", "encryption=$smtp_encryption ", "hello=$smtp_domain", - defined $smtp_server_port ? "port=$smtp_server_port" : ""; + defined $smtp_server_port ? " port=$smtp_server_port" : ""; } if (defined $smtp_authuser) { diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh new file mode 100644 index 0000000000..32ca59de82 --- /dev/null +++ b/git-sh-i18n.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# +# This is a skeleton no-op implementation of gettext for Git. It'll be +# replaced by something that uses gettext.sh in a future patch series. + +if test -z "$GIT_GETTEXT_POISON" +then + gettext () { + printf "%s" "$1" + } + + eval_gettext () { + printf "%s" "$1" | ( + export PATH $(git sh-i18n--envsubst --variables "$1"); + git sh-i18n--envsubst "$1" + ) + } +else + gettext () { + printf "%s" "# GETTEXT POISON #" + } + + eval_gettext () { + printf "%s" "# GETTEXT POISON #" + } +fi + diff --git a/git-sh-setup.sh b/git-sh-setup.sh index aa16b83565..94e26ed5e8 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -140,6 +140,13 @@ cd_to_toplevel () { } } +require_work_tree_exists () { + if test "z$(git rev-parse --is-bare-repository)" != zfalse + then + die "fatal: $0 cannot be used without a working tree." + fi +} + require_work_tree () { test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true || die "fatal: $0 cannot be used without a working tree." diff --git a/git-stash.sh b/git-stash.sh index 7561b374d2..0a9403653d 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -12,12 +12,14 @@ USAGE="list [<options>] SUBDIRECTORY_OK=Yes OPTIONS_SPEC= +START_DIR=`pwd` . git-sh-setup require_work_tree cd_to_toplevel TMP="$GIT_DIR/.git-stash.$$" -trap 'rm -f "$TMP-*"' 0 +TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$ +trap 'rm -f "$TMP-"* "$TMPindex"' 0 ref_stash=refs/stash @@ -81,14 +83,12 @@ create_stash () { # state of the working tree w_tree=$( ( - rm -f "$TMP-index" && - cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && - GIT_INDEX_FILE="$TMP-index" && + git read-tree --index-output="$TMPindex" -m $i_tree && + GIT_INDEX_FILE="$TMPindex" && export GIT_INDEX_FILE && - git read-tree -m $i_tree && git diff --name-only -z HEAD | git update-index -z --add --remove --stdin && git write-tree && - rm -f "$TMP-index" + rm -f "$TMPindex" ) ) || die "Cannot save the current worktree state" @@ -136,11 +136,12 @@ save_stash () { keep_index=t ;; --no-keep-index) - keep_index= + keep_index=n ;; -p|--patch) patch_mode=t - keep_index=t + # only default to keep if we don't already have an override + test -z "$keep_index" && keep_index=t ;; -q|--quiet) GIT_QUIET=t @@ -185,7 +186,7 @@ save_stash () { then git reset --hard ${GIT_QUIET:+-q} - if test -n "$keep_index" && test -n $i_tree + if test "$keep_index" = "t" && test -n $i_tree then git read-tree --reset -u $i_tree fi @@ -193,7 +194,7 @@ save_stash () { git apply -R < "$TMP-patch" || die "Cannot remove worktree changes" - if test -z "$keep_index" + if test "$keep_index" != "t" then git reset fi @@ -264,7 +265,7 @@ parse_flags_and_rev() b_tree= i_tree= - REV=$(git rev-parse --no-flags --symbolic "$@" 2>/dev/null) + REV=$(git rev-parse --no-flags --symbolic "$@") || exit 1 FLAGS= for opt @@ -310,16 +311,6 @@ parse_flags_and_rev() IS_STASH_LIKE=t && test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" && IS_STASH_REF=t - - if test "${REV}" != "${REV%{*\}}" - then - # maintainers: it would be better if git rev-parse indicated - # this condition with a non-zero status code but as of 1.7.2.1 it - # it did not. So, we use non-empty stderr output as a proxy for the - # condition of interest. - test -z "$(git rev-parse "$REV" 2>&1 >/dev/null)" || die "$REV does not exist in the stash log" - fi - } is_stash_like() @@ -344,9 +335,7 @@ apply_stash () { assert_stash_like "$@" - git update-index -q --refresh && - git diff-files --quiet --ignore-submodules || - die 'Cannot apply to a dirty working tree, please stage your changes' + git update-index -q --refresh || die 'unable to refresh index' # current index state c_tree=$(git write-tree) || @@ -394,7 +383,7 @@ apply_stash () { then squelch='>/dev/null 2>&1' fi - eval "git status $squelch" || : + (cd "$START_DIR" && eval "git status $squelch") || : else # Merge conflict; keep the exit status from merge-recursive status=$? diff --git a/git-submodule.sh b/git-submodule.sh index 8b90589717..d189a24c71 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -8,7 +8,7 @@ dashless=$(basename "$0" | sed -e 's/-/ /') 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>...] + or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] or: $dashless [--quiet] foreach [--recursive] <command> or: $dashless [--quiet] sync [--] [<path>...]" @@ -72,7 +72,24 @@ resolve_relative_url () # module_list() { - git ls-files --error-unmatch --stage -- "$@" | sane_grep '^160000 ' + git ls-files --error-unmatch --stage -- "$@" | + perl -e ' + my %unmerged = (); + my ($null_sha1) = ("0" x 40); + while (<STDIN>) { + chomp; + my ($mode, $sha1, $stage, $path) = + /^([0-7]+) ([0-9a-f]{40}) ([0-3])\t(.*)$/; + next unless $mode eq "160000"; + if ($stage ne "0") { + if (!$unmerged{$path}++) { + print "$mode $null_sha1 U\t$path\n"; + } + next; + } + print "$_\n"; + } + ' } # @@ -385,6 +402,9 @@ cmd_update() -N|--no-fetch) nofetch=1 ;; + -f|--force) + force=$1 + ;; -r|--rebase) update="rebase" ;; @@ -423,9 +443,15 @@ cmd_update() cmd_init "--" "$@" || return fi + cloned_modules= module_list "$@" | while read mode sha1 stage path do + if test "$stage" = U + then + echo >&2 "Skipping unmerged submodule $path" + continue + fi name=$(module_name "$path") || exit url=$(git config submodule."$name".url) update_module=$(git config submodule."$name".update) @@ -442,6 +468,7 @@ cmd_update() if ! test -d "$path"/.git -o -f "$path"/.git then module_clone "$path" "$url" "$reference"|| exit + cloned_modules="$cloned_modules;$name" subsha1= else subsha1=$(clear_local_git_env; cd "$path" && @@ -456,19 +483,30 @@ cmd_update() if test "$subsha1" != "$sha1" then - force= - if test -z "$subsha1" + subforce=$force + # If we don't already have a -f flag and the submodule has never been checked out + if test -z "$subsha1" -a -z "$force" then - force="-f" + subforce="-f" fi if test -z "$nofetch" then + # Run fetch only if $sha1 isn't present or it + # is not reachable from a ref. (clear_local_git_env; cd "$path" && - git-fetch) || + ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) && + test -z "$rev") || git-fetch)) || die "Unable to fetch in submodule path '$path'" fi + # Is this something we just cloned? + case ";$cloned_modules;" in + *";$name;"*) + # then there is no local change to integrate + update_module= ;; + esac + case "$update_module" in rebase) command="git rebase" @@ -481,7 +519,7 @@ cmd_update() msg="merged in" ;; *) - command="git checkout $force -q" + command="git checkout $subforce -q" action="checkout" msg="checked out" ;; @@ -761,6 +799,11 @@ cmd_status() name=$(module_name "$path") || exit url=$(git config submodule."$name".url) displaypath="$prefix$path" + if test "$stage" = U + then + say "U$sha1 $displaypath" + continue + fi if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git then say "-$sha1 $displaypath" diff --git a/git-svn.perl b/git-svn.perl index 177dd259cd..7849cfc141 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -59,6 +59,7 @@ use File::Find; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/; use IPC::Open3; use Git; +use Memoize; # core since 5.8.0, Jul 2002 BEGIN { # import functions from Git into our packages, en masse @@ -72,6 +73,8 @@ BEGIN { *{"${package}::$_"} = \&{"Git::$_"}; } } + Memoize::memoize 'Git::config'; + Memoize::memoize 'Git::config_bool'; } my ($SVN); @@ -528,7 +531,7 @@ sub cmd_dcommit { $url = eval { command_oneline('config', '--get', "svn-remote.$gs->{repo_id}.commiturl") }; if (!$url) { - $url = $gs->full_url + $url = $gs->full_pushurl } } @@ -676,7 +679,7 @@ sub cmd_branch { $head ||= 'HEAD'; my (undef, $rev, undef, $gs) = working_head_info($head); - my $src = $gs->full_url; + my $src = $gs->full_pushurl; my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}}; my $allglobs = $remote->{ $_tag ? 'tags' : 'branches' }; @@ -727,7 +730,7 @@ sub cmd_branch { $url = eval { command_oneline('config', '--get', "svn-remote.$gs->{repo_id}.commiturl") }; if (!$url) { - $url = $remote->{url}; + $url = $remote->{pushurl} || $remote->{url}; } } my $dst = join '/', $url, $lft, $branch_name, ($rgt || ()); @@ -781,6 +784,15 @@ sub cmd_find_rev { print "$result\n" if $result; } +sub auto_create_empty_directories { + my ($gs) = @_; + my $var = eval { command_oneline('config', '--get', '--bool', + "svn-remote.$gs->{repo_id}.automkdirs") }; + # By default, create empty directories by consulting the unhandled log, + # but allow setting it to 'false' to skip it. + return !($var && $var eq 'false'); +} + sub cmd_rebase { command_noisy(qw/update-index --refresh/); my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); @@ -804,7 +816,9 @@ sub cmd_rebase { $_fetch_all ? $gs->fetch_all : $gs->fetch; } command_noisy(rebase_cmd(), $gs->refname); - $gs->mkemptydirs; + if (auto_create_empty_directories($gs)) { + $gs->mkemptydirs; + } } sub cmd_show_ignore { @@ -1242,7 +1256,9 @@ sub post_fetch_checkout { command_noisy(qw/read-tree -m -u -v HEAD HEAD/); print STDERR "Checked out HEAD:\n ", $gs->full_url, " r", $gs->last_rev, "\n"; - $gs->mkemptydirs($gs->last_rev); + if (auto_create_empty_directories($gs)) { + $gs->mkemptydirs($gs->last_rev); + } } sub complete_svn_url { @@ -1831,6 +1847,8 @@ sub read_all_remotes { $r->{$1}->{svm} = {}; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { $r->{$1}->{url} = $2; + } elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) { + $r->{$1}->{pushurl} = $2; } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) { my ($remote, $t, $local_ref, $remote_ref) = ($1, $2, $3, $4); @@ -2068,6 +2086,8 @@ sub new { $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; + $self->{pushurl} = eval { command_oneline('config', '--get', + "svn-remote.$repo_id.pushurl") }; $self->rebuild; $self; } @@ -2545,6 +2565,15 @@ sub full_url { $self->{url} . (length $self->{path} ? '/' . $self->{path} : ''); } +sub full_pushurl { + my ($self) = @_; + if ($self->{pushurl}) { + return $self->{pushurl} . (length $self->{path} ? '/' . + $self->{path} : ''); + } else { + return $self->full_url; + } +} sub set_commit_header_env { my ($log_entry) = @_; @@ -3197,6 +3226,8 @@ sub has_no_changes { Memoize::unmemoize 'check_cherry_pick'; Memoize::unmemoize 'has_no_changes'; } + + Memoize::memoize 'Git::SVN::repos_root'; } END { @@ -5734,7 +5765,7 @@ sub cmd_show_log { my (@k, $c, $d, $stat); my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; while (<$log>) { - if (/^${esc_color}commit -?($::sha1_short)/o) { + if (/^${esc_color}commit (?:- )?($::sha1_short)/o) { my $cmt = $1; if ($c && cmt_showable($c) && $c->{r} != $r_last) { $r_last = $c->{r}; @@ -6,7 +6,7 @@ #include "run-command.h" const char git_usage_string[] = - "git [--version] [--exec-path[=<path>]] [--html-path]\n" + "git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n" " [-p|--paginate|--no-pager] [--no-replace-objects]\n" " [--bare] [--git-dir=<path>] [--work-tree=<path>]\n" " [-c name=value] [--help]\n" @@ -66,7 +66,7 @@ static void commit_pager_choice(void) { static int handle_options(const char ***argv, int *argc, int *envchanged) { - int handled = 0; + const char **orig_argv = *argv; while (*argc > 0) { const char *cmd = (*argv)[0]; @@ -95,6 +95,12 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) } else if (!strcmp(cmd, "--html-path")) { puts(system_path(GIT_HTML_PATH)); exit(0); + } else if (!strcmp(cmd, "--man-path")) { + puts(system_path(GIT_MAN_PATH)); + exit(0); + } else if (!strcmp(cmd, "--info-path")) { + puts(system_path(GIT_INFO_PATH)); + exit(0); } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { use_pager = 1; } else if (!strcmp(cmd, "--no-pager")) { @@ -116,7 +122,6 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) *envchanged = 1; (*argv)++; (*argc)--; - handled++; } else if (!prefixcmp(cmd, "--git-dir=")) { setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1); if (envchanged) @@ -156,9 +161,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) (*argv)++; (*argc)--; - handled++; } - return handled; + return (*argv) - orig_argv; } static int handle_alias(int *argcp, const char ***argv) @@ -177,24 +181,32 @@ static int handle_alias(int *argcp, const char ***argv) alias_string = alias_lookup(alias_command); if (alias_string) { if (alias_string[0] == '!') { + const char **alias_argv; + int argc = *argcp, i; + struct strbuf sb = STRBUF_INIT; + const char *env[2]; + commit_pager_choice(); - if (*argcp > 1) { - struct strbuf buf; - - strbuf_init(&buf, PATH_MAX); - strbuf_addstr(&buf, alias_string); - sq_quote_argv(&buf, (*argv) + 1, PATH_MAX); - free(alias_string); - alias_string = buf.buf; - } - trace_printf("trace: alias to shell cmd: %s => %s\n", - alias_command, alias_string + 1); - ret = system(alias_string + 1); - if (ret >= 0 && WIFEXITED(ret) && - WEXITSTATUS(ret) != 127) - exit(WEXITSTATUS(ret)); - die("Failed to run '%s' when expanding alias '%s'", - alias_string + 1, alias_command); + + /* build alias_argv */ + alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1)); + alias_argv[0] = alias_string + 1; + for (i = 1; i < argc; ++i) + alias_argv[i] = (*argv)[i]; + alias_argv[argc] = NULL; + + strbuf_addstr(&sb, "GIT_PREFIX="); + if (subdir) + strbuf_addstr(&sb, subdir); + env[0] = sb.buf; + env[1] = NULL; + ret = run_command_v_opt_cd_env(alias_argv, RUN_USING_SHELL, NULL, env); + strbuf_release(&sb); + if (ret >= 0) /* normal exit */ + exit(ret); + + die_errno("While expanding alias '%s': '%s'", + alias_command, alias_string + 1); } count = split_cmdline(alias_string, &new_argv); if (count < 0) @@ -313,7 +325,6 @@ static void handle_internal_command(int argc, const char **argv) const char *cmd = argv[0]; static struct cmd_struct commands[] = { { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, - { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "annotate", cmd_annotate, RUN_SETUP }, { "apply", cmd_apply, RUN_SETUP_GENTLY }, { "archive", cmd_archive }, @@ -322,15 +333,15 @@ static void handle_internal_command(int argc, const char **argv) { "branch", cmd_branch, RUN_SETUP }, { "bundle", cmd_bundle, RUN_SETUP_GENTLY }, { "cat-file", cmd_cat_file, RUN_SETUP }, + { "check-attr", cmd_check_attr, RUN_SETUP }, + { "check-ref-format", cmd_check_ref_format }, { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE }, { "checkout-index", cmd_checkout_index, RUN_SETUP | NEED_WORK_TREE}, - { "check-ref-format", cmd_check_ref_format }, - { "check-attr", cmd_check_attr, RUN_SETUP }, { "cherry", cmd_cherry, RUN_SETUP }, { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, - { "clone", cmd_clone }, { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, + { "clone", cmd_clone }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config, RUN_SETUP_GENTLY }, @@ -358,8 +369,8 @@ static void handle_internal_command(int argc, const char **argv) { "init-db", cmd_init_db }, { "log", cmd_log, RUN_SETUP }, { "ls-files", cmd_ls_files, RUN_SETUP }, - { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, + { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, @@ -379,6 +390,7 @@ static void handle_internal_command(int argc, const char **argv) { "notes", cmd_notes, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, { "pack-redundant", cmd_pack_redundant, RUN_SETUP }, + { "pack-refs", cmd_pack_refs, RUN_SETUP }, { "patch-id", cmd_patch_id }, { "peek-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, { "pickaxe", cmd_blame, RUN_SETUP }, @@ -392,7 +404,7 @@ static void handle_internal_command(int argc, const char **argv) { "remote-ext", cmd_remote_ext }, { "remote-fd", cmd_remote_fd }, { "replace", cmd_replace, RUN_SETUP }, - { "repo-config", cmd_config, RUN_SETUP_GENTLY }, + { "repo-config", cmd_repo_config, RUN_SETUP_GENTLY }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, { "rev-list", cmd_rev_list, RUN_SETUP }, @@ -401,8 +413,10 @@ static void handle_internal_command(int argc, const char **argv) { "rm", cmd_rm, RUN_SETUP }, { "send-pack", cmd_send_pack, RUN_SETUP }, { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER }, - { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP }, + { "show-branch", cmd_show_branch, RUN_SETUP }, + { "show-ref", cmd_show_ref, RUN_SETUP }, + { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, @@ -415,13 +429,11 @@ static void handle_internal_command(int argc, const char **argv) { "update-server-info", cmd_update_server_info, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, { "var", cmd_var, RUN_SETUP_GENTLY }, + { "verify-pack", cmd_verify_pack }, { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP }, { "write-tree", cmd_write_tree, RUN_SETUP }, - { "verify-pack", cmd_verify_pack }, - { "show-ref", cmd_show_ref, RUN_SETUP }, - { "pack-refs", cmd_pack_refs, RUN_SETUP }, }; int i; static const char ext[] = STRIP_EXTENSION; diff --git a/gitk-git/gitk b/gitk-git/gitk index e82c6bfede..4cde0c493b 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -2652,7 +2652,7 @@ proc savestuff {w} { global viewname viewfiles viewargs viewargscmd viewperm nextviewnum global cmitmode wrapcomment datetimeformat limitdiffs global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor - global autoselect extdifftool perfile_attrs markbgcolor use_ttk + global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk global hideremotes want_ttk if {$stuffsaved} return @@ -2673,6 +2673,7 @@ proc savestuff {w} { puts $f [list set cmitmode $cmitmode] puts $f [list set wrapcomment $wrapcomment] puts $f [list set autoselect $autoselect] + puts $f [list set autosellen $autosellen] puts $f [list set showneartags $showneartags] puts $f [list set hideremotes $hideremotes] puts $f [list set showlocalchanges $showlocalchanges] @@ -6300,6 +6301,7 @@ proc drawtags {id x xt y1} { -width $lthickness -fill black -tags tag.$id] $canv lower $t foreach tag $marks x $xvals wid $wvals { + set tag_quoted [string map {% %%} $tag] set xl [expr {$x + $delta}] set xr [expr {$x + $delta + $wid + $lthickness}] set font mainfont @@ -6308,7 +6310,7 @@ proc drawtags {id x xt y1} { set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \ $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \ -width 1 -outline black -fill yellow -tags tag.$id] - $canv bind $t <1> [list showtag $tag 1] + $canv bind $t <1> [list showtag $tag_quoted 1] set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}] } else { # draw a head or other ref @@ -6335,9 +6337,9 @@ proc drawtags {id x xt y1} { set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \ -font $font -tags [list tag.$id text]] if {$ntags >= 0} { - $canv bind $t <1> [list showtag $tag 1] + $canv bind $t <1> [list showtag $tag_quoted 1] } elseif {$nheads >= 0} { - $canv bind $t $ctxbut [list headmenu %X %Y $id $tag] + $canv bind $t $ctxbut [list headmenu %X %Y $id $tag_quoted] } } return $xt @@ -6896,7 +6898,7 @@ proc selectline {l isnew {desired_loc {}}} { global mergemax numcommits pending_select global cmitmode showneartags allcommits global targetrow targetid lastscrollrows - global autoselect jump_to_here + global autoselect autosellen jump_to_here catch {unset pending_select} $canv delete hover @@ -6958,7 +6960,7 @@ proc selectline {l isnew {desired_loc {}}} { $sha1entry delete 0 end $sha1entry insert 0 $id if {$autoselect} { - $sha1entry selection range 0 end + $sha1entry selection range 0 $autosellen } rhighlight_sel $id @@ -9063,7 +9065,7 @@ proc cherrypick {} { to file '%s'.\nPlease commit, reset or stash\ your changes and try again." $fname] } elseif {[regexp -line \ - {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \ + {^(CONFLICT \(.*\):|Automatic cherry-pick failed|error: could not apply)} \ $err]} { if {[confirm_popup [mc "Cherry-pick failed because of merge\ conflict.\nDo you wish to run git citool to\ @@ -10756,7 +10758,7 @@ proc doprefs {} { global maxwidth maxgraphpct use_ttk NS global oldprefs prefstop showneartags showlocalchanges global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor - global tabstop limitdiffs autoselect extdifftool perfile_attrs + global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs global hideremotes want_ttk have_ttk set top .gitkprefs @@ -10784,9 +10786,10 @@ proc doprefs {} { ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \ -variable showlocalchanges grid x $top.showlocal -sticky w - ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \ + ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1 (length)"] \ -variable autoselect - grid x $top.autoselect -sticky w + spinbox $top.autosellen -from 1 -to 40 -width 4 -textvariable autosellen + grid x $top.autoselect $top.autosellen -sticky w ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \ -variable hideremotes grid x $top.hideremotes -sticky w @@ -11428,6 +11431,7 @@ set showlocalchanges 1 set limitdiffs 1 set datetimeformat "%Y-%m-%d %H:%M:%S" set autoselect 1 +set autosellen 40 set perfile_attrs 0 set want_ttk 1 @@ -11581,7 +11585,7 @@ if {![info exists have_ttk]} { set use_ttk [expr {$have_ttk && $want_ttk}] set NS [expr {$use_ttk ? "ttk" : ""}] -set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .] +regexp {^git version ([\d.]*\d)} [exec git version] _ git_version set show_notes {} if {[package vcompare $git_version "1.6.6.2"] >= 0} { diff --git a/gitk-git/po/ru.po b/gitk-git/po/ru.po index c3d0285b24..59873033af 100644 --- a/gitk-git/po/ru.po +++ b/gitk-git/po/ru.po @@ -24,7 +24,7 @@ msgstr "Ошибка в идентификаторе версии:" #: gitk:323 msgid "Error executing --argscmd command:" -msgstr "Ошибка выполнения команды заданой --argscmd:" +msgstr "Ошибка выполнения команды заданной --argscmd:" #: gitk:336 msgid "No files selected: --merge specified but no files are unmerged." @@ -37,7 +37,7 @@ msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." msgstr "" -"Файлы не выбраны: указан --merge, но в рамках указаного " +"Файлы не выбраны: указан --merge, но в рамках указанного " "ограничения на имена файлов нет ни одного " "где эта операция должна быть завершена." @@ -246,11 +246,11 @@ msgstr "Файлы" #: gitk:2326 gitk:2339 msgid "Diff this -> selected" -msgstr "Сравнить это состояние с выделеным" +msgstr "Сравнить это состояние с выделенным" #: gitk:2327 gitk:2340 msgid "Diff selected -> this" -msgstr "Сравнить выделеное с этим состоянием" +msgstr "Сравнить выделенное с этим состоянием" #: gitk:2328 gitk:2341 msgid "Make patch" @@ -440,11 +440,11 @@ msgstr "<%s-F>\t\tПоиск" #: gitk:2666 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" -msgstr "<%s-G>\t\tПерейти к следующему найденому состоянию" +msgstr "<%s-G>\t\tПерейти к следующему найденному состоянию" #: gitk:2667 msgid "<Return>\tMove to next find hit" -msgstr "<Return>\tПерейти к следующему найденому состоянию" +msgstr "<Return>\tПерейти к следующему найденному состоянию" #: gitk:2668 msgid "/\t\tFocus the search box" @@ -452,7 +452,7 @@ msgstr "/\t\tПерейти к полю поиска" #: gitk:2669 msgid "?\t\tMove to previous find hit" -msgstr "?\t\tПерейти к предыдущему найденому состоянию" +msgstr "?\t\tПерейти к предыдущему найденному состоянию" #: gitk:2670 msgid "f\t\tScroll diff view to next file" @@ -466,7 +466,7 @@ msgstr "<%s-S>\t\tПродолжить поиск в списке изменен #: gitk:2672 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" -msgstr "<%s-R>\t\tПерейти к предыдущему найденому тексту в списке изменений" +msgstr "<%s-R>\t\tПерейти к предыдущему найденному тексту в списке изменений" #: gitk:2673 #, tcl-format @@ -855,7 +855,7 @@ msgstr "Лёгкий: оставить рабочий каталог и инде #: gitk:8472 msgid "Mixed: Leave working tree untouched, reset index" msgstr "" -"Смешаный: оставить рабочий каталог неизменным, установить индекс" +"Смешанный: оставить рабочий каталог неизменным, установить индекс" #: gitk:8475 msgid "" @@ -962,7 +962,7 @@ msgstr "Показывать близкие метки" #: gitk:10126 msgid "Limit diffs to listed paths" -msgstr "Ограничить показ изменений выбраными файлами" +msgstr "Ограничить показ изменений выбранными файлами" #: gitk:10129 msgid "Support per-file encodings" @@ -1022,11 +1022,11 @@ msgstr "заголовок блока изменений" #: gitk:10169 msgid "Marked line bg" -msgstr "Фон выбраной строки" +msgstr "Фон выбранной строки" #: gitk:10171 msgid "marked line background" -msgstr "фон выбраной строки" +msgstr "фон выбранной строки" #: gitk:10175 msgid "Select bg" diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 823053173c..4964a679b3 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -237,6 +237,12 @@ Requirements - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename. - web server +The following optional Perl modules are required for extra features + - Digest::MD5 - for gravatar support + - CGI::Fast and FCGI - for running gitweb as FastCGI script + - HTML::TagCloud - for fancy tag cloud in project list view + - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds + Example web server configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/gitweb/Makefile b/gitweb/Makefile index 0a6ac00631..5d20515fba 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -86,7 +86,7 @@ ifndef V endif endif -all:: gitweb.cgi +all:: gitweb.cgi static/gitweb.js GITWEB_PROGRAMS = gitweb.cgi @@ -112,6 +112,18 @@ endif GITWEB_FILES += static/git-logo.png static/git-favicon.png +# JavaScript files that are composed (concatenated) to form gitweb.js +# +# js/lib/common-lib.js should be always first, then js/lib/*.js, +# then the rest of files; js/gitweb.js should be last (if it exists) +GITWEB_JSLIB_FILES += static/js/lib/common-lib.js +GITWEB_JSLIB_FILES += static/js/lib/datetime.js +GITWEB_JSLIB_FILES += static/js/lib/cookies.js +GITWEB_JSLIB_FILES += static/js/javascript-detection.js +GITWEB_JSLIB_FILES += static/js/adjust-timezone.js +GITWEB_JSLIB_FILES += static/js/blame_incremental.js + + GITWEB_REPLACE = \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ -e 's|++GIT_BINDIR++|$(bindir)|g' \ @@ -146,6 +158,11 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS chmod +x $@+ && \ mv $@+ $@ +static/gitweb.js: $(GITWEB_JSLIB_FILES) + $(QUIET_GEN)$(RM) $@ $@+ && \ + cat $^ >$@+ && \ + mv $@+ $@ + ### Testing rules test: diff --git a/gitweb/README b/gitweb/README index 4a673933ac..a3a697bf55 100644 --- a/gitweb/README +++ b/gitweb/README @@ -29,7 +29,7 @@ You can specify the following configuration variables when building GIT: The filesystem traversing limit for getting the project list; the number is taken as depth relative to the projectroot. It is used when GITWEB_LIST is a directory (or is not set; then project root is used). - Is is meant to speed up project listing on large work trees by limiting + This is meant to speed up project listing on large work trees by limiting search depth. [Default: 2007] * GITWEB_LIST Points to a directory to scan for projects (defaults to project root @@ -207,6 +207,15 @@ not include variables usually directly set during build): full description is available as 'title' attribute (usually shown on mouseover). By default set to 25, which might be too small if you use long project descriptions. + * $projects_list_group_categories + Enables the grouping of projects by category on the project list page. + The category of a project is determined by the $GIT_DIR/category + file or the 'gitweb.category' variable in its repository configuration. + Disabled by default. + * $project_list_default_category + Default category for projects for which none is specified. If set + to the empty string, such projects will remain uncategorized and + listed at the top, above categorized projects. * @git_base_url_list List of git base URLs used for URL to where fetch project from, shown in project summary page. Full URL is "$git_base_url/$project". @@ -314,6 +323,13 @@ You can use the following files in repository: from the template during repository creation. You can use the gitweb.description repo configuration variable, but the file takes precedence. + * category (or gitweb.category) + Singe line category of a project, used to group projects if + $projects_list_group_categories is enabled. By default (file and + configuration variable absent), uncategorized projects are put in + the $project_list_default_category category. You can use the + gitweb.category repo configuration variable, but the file takes + precedence. * cloneurl (or multiple-valued gitweb.url) File with repository URL (used for clone and fetch), one per line. Displayed in the project summary page. You can use multiple-valued diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0779f12d61..240dd4701c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -115,6 +115,14 @@ our $projects_list = "++GITWEB_LIST++"; # the width (in characters) of the projects list "Description" column our $projects_list_description_width = 25; +# group projects by category on the projects list +# (enabled if this variable evaluates to true) +our $projects_list_group_categories = 0; + +# default category if none specified +# (leave the empty string for no category) +our $project_list_default_category = ""; + # default order of projects list # valid values are none, project, descr, owner, and age our $default_projects_order = "project"; @@ -186,7 +194,7 @@ our %known_snapshot_formats = ( 'type' => 'application/x-gzip', 'suffix' => '.tar.gz', 'format' => 'tar', - 'compressor' => ['gzip']}, + 'compressor' => ['gzip', '-n']}, 'tbz2' => { 'display' => 'tar.bz2', @@ -412,20 +420,23 @@ our %feature = ( 'override' => 0, 'default' => []}, - # Allow gitweb scan project content tags described in ctags/ - # of project repository, and display the popular Web 2.0-ish - # "tag cloud" near the project list. Note that this is something - # COMPLETELY different from the normal Git tags. + # Allow gitweb scan project content tags of project repository, + # and display the popular Web 2.0-ish "tag cloud" near the projects + # list. Note that this is something COMPLETELY different from the + # normal Git tags. # gitweb by itself can show existing tags, but it does not handle - # tagging itself; you need an external application for that. - # For an example script, check Girocco's cgi/tagproj.cgi. + # tagging itself; you need to do it externally, outside gitweb. + # The format is described in git_get_project_ctags() subroutine. # You may want to install the HTML::TagCloud Perl module to get # a pretty tag cloud instead of just a list of tags. # To enable system wide have in $GITWEB_CONFIG - # $feature{'ctags'}{'default'} = ['path_to_tag_script']; + # $feature{'ctags'}{'default'} = [1]; # Project specific override is not supported. + + # In the future whether ctags editing is enabled might depend + # on the value, but using 1 should always mean no editing of ctags. 'ctags' => { 'override' => 0, 'default' => [0]}, @@ -480,6 +491,18 @@ our %feature = ( 'override' => 0, 'default' => [0]}, + # Enable and configure ability to change common timezone for dates + # in gitweb output via JavaScript. Enabled by default. + # Project specific override is not supported. + 'javascript-timezone' => { + 'override' => 0, + 'default' => [ + 'local', # default timezone: 'utc', 'local', or '(-|+)HHMM' format, + # or undef to turn off this feature + 'gitweb_tz', # name of cookie where to store selected timezone + 'datetime', # CSS class used to mark up dates for manipulation + ]}, + # 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, @@ -620,18 +643,30 @@ sub filter_snapshot_fmts { # if it is true then gitweb config would be run for each request. our $per_request_config = 1; +# read and parse gitweb config file given by its parameter. +# returns true on success, false on recoverable error, allowing +# to chain this subroutine, using first file that exists. +# dies on errors during parsing config file, as it is unrecoverable. +sub read_config_file { + my $filename = shift; + return unless defined $filename; + # die if there are errors parsing config file + if (-e $filename) { + do $filename; + die $@ if $@; + return 1; + } + return; +} + 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 $@; - } + + # use first config file that exists + read_config_file($GITWEB_CONFIG) or + read_config_file($GITWEB_CONFIG_SYSTEM); } # Get loadavg of system, to compare against $maxload. @@ -703,6 +738,7 @@ our @cgi_param_mapping = ( snapshot_format => "sf", extra_options => "opt", search_use_regexp => "sr", + ctag => "by_tag", # this must be last entry (for manipulation from JavaScript) javascript => "js" ); @@ -1199,11 +1235,15 @@ if (defined caller) { # -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) +# -anchor => ANCHOR - add #ANCHOR to end of URL, implies -replay if used alone sub href { my %params = @_; # default is to use -absolute url() i.e. $my_uri my $href = $params{-full} ? $my_url : $my_uri; + # implicit -replay, must be first of implicit params + $params{-replay} = 1 if (keys %params == 1 && $params{-anchor}); + $params{'project'} = $project unless exists $params{'project'}; if ($params{-replay}) { @@ -1314,6 +1354,10 @@ sub href { # final transformation: trailing spaces must be escaped (URI-encoded) $href =~ s/(\s+)$/CGI::escape($1)/e; + if ($params{-anchor}) { + $href .= "#".esc_param($params{-anchor}); + } + return $href; } @@ -2550,37 +2594,94 @@ sub git_get_path_by_hash { ## ...................................................................... ## git utility functions, directly accessing git repository -sub git_get_project_description { - my $path = shift; +# get the value of config variable either from file named as the variable +# itself in the repository ($GIT_DIR/$name file), or from gitweb.$name +# configuration variable in the repository config file. +sub git_get_file_or_project_config { + my ($path, $name) = @_; $git_dir = "$projectroot/$path"; - open my $fd, '<', "$git_dir/description" - or return git_get_project_config('description'); - my $descr = <$fd>; + open my $fd, '<', "$git_dir/$name" + or return git_get_project_config($name); + my $conf = <$fd>; close $fd; - if (defined $descr) { - chomp $descr; + if (defined $conf) { + chomp $conf; } - return $descr; + return $conf; } -sub git_get_project_ctags { +sub git_get_project_description { + my $path = shift; + return git_get_file_or_project_config($path, 'description'); +} + +sub git_get_project_category { my $path = shift; + return git_get_file_or_project_config($path, 'category'); +} + + +# supported formats: +# * $GIT_DIR/ctags/<tagname> file (in 'ctags' subdirectory) +# - if its contents is a number, use it as tag weight, +# - otherwise add a tag with weight 1 +# * $GIT_DIR/ctags file, each line is a tag (with weight 1) +# the same value multiple times increases tag weight +# * `gitweb.ctag' multi-valued repo config variable +sub git_get_project_ctags { + my $project = shift; my $ctags = {}; - $git_dir = "$projectroot/$path"; - opendir my $dh, "$git_dir/ctags" - or return $ctags; - foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) { - open my $ct, '<', $_ or next; - my $val = <$ct>; - chomp $val; - close $ct; - my $ctag = $_; $ctag =~ s#.*/##; - $ctags->{$ctag} = $val; + $git_dir = "$projectroot/$project"; + if (opendir my $dh, "$git_dir/ctags") { + my @files = grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh); + foreach my $tagfile (@files) { + open my $ct, '<', $tagfile + or next; + my $val = <$ct>; + chomp $val if $val; + close $ct; + + (my $ctag = $tagfile) =~ s#.*/##; + if ($val =~ /\d+/) { + $ctags->{$ctag} = $val; + } else { + $ctags->{$ctag} = 1; + } + } + closedir $dh; + + } elsif (open my $fh, '<', "$git_dir/ctags") { + while (my $line = <$fh>) { + chomp $line; + $ctags->{$line}++ if $line; + } + close $fh; + + } else { + my $taglist = config_to_multi(git_get_project_config('ctag')); + foreach my $tag (@$taglist) { + $ctags->{$tag}++; + } + } + + return $ctags; +} + +# return hash, where keys are content tags ('ctags'), +# and values are sum of weights of given tag in every project +sub git_gather_all_ctags { + my $projects = shift; + my $ctags = {}; + + foreach my $p (@$projects) { + foreach my $ct (keys %{$p->{'ctags'}}) { + $ctags->{$ct} += $p->{'ctags'}->{$ct}; + } } - closedir $dh; - $ctags; + + return $ctags; } sub git_populate_project_tagcloud { @@ -2598,33 +2699,49 @@ sub git_populate_project_tagcloud { } my $cloud; + my $matched = $cgi->param('by_tag'); if (eval { require HTML::TagCloud; 1; }) { $cloud = HTML::TagCloud->new; - foreach (sort keys %ctags_lc) { + foreach my $ctag (sort keys %ctags_lc) { # Pad the title with spaces so that the cloud looks # less crammed. - my $title = $ctags_lc{$_}->{topname}; + my $title = esc_html($ctags_lc{$ctag}->{topname}); $title =~ s/ / /g; $title =~ s/^/ /g; $title =~ s/$/ /g; - $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count}); + if (defined $matched && $matched eq $ctag) { + $title = qq(<span class="match">$title</span>); + } + $cloud->add($title, href(project=>undef, ctag=>$ctag), + $ctags_lc{$ctag}->{count}); } } else { - $cloud = \%ctags_lc; + $cloud = {}; + foreach my $ctag (keys %ctags_lc) { + my $title = esc_html($ctags_lc{$ctag}->{topname}, -nbsp=>1); + if (defined $matched && $matched eq $ctag) { + $title = qq(<span class="match">$title</span>); + } + $cloud->{$ctag}{count} = $ctags_lc{$ctag}->{count}; + $cloud->{$ctag}{ctag} = + $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, $title); + } } - $cloud; + return $cloud; } sub git_show_project_tagcloud { my ($cloud, $count) = @_; - print STDERR ref($cloud)."..\n"; if (ref $cloud eq 'HTML::TagCloud') { return $cloud->html_and_css($count); } else { - my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud; - return '<p align="center">' . join (', ', map { - $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname}) - } splice(@tags, 0, $count)) . '</p>'; + my @tags = sort { $cloud->{$a}->{'count'} <=> $cloud->{$b}->{'count'} } keys %$cloud; + return + '<div id="htmltagcloud"'.($project ? '' : ' align="center"').'>' . + join (', ', map { + $cloud->{$_}->{'ctag'} + } splice(@tags, 0, $count)) . + '</div>'; } } @@ -2643,21 +2760,23 @@ sub git_get_project_url_list { } sub git_get_projects_list { - my ($filter) = @_; + my $filter = shift || ''; my @list; - $filter ||= ''; $filter =~ s/\.git$//; - my $check_forks = gitweb_check_feature('forks'); - if (-d $projects_list) { # search in directory - my $dir = $projects_list . ($filter ? "/$filter" : ''); + my $dir = $projects_list; # remove the trailing "/" $dir =~ s!/+$!!; - my $pfxlen = length("$dir"); - my $pfxdepth = ($dir =~ tr!/!!); + my $pfxlen = length("$projects_list"); + my $pfxdepth = ($projects_list =~ tr!/!!); + # when filtering, search only given subdirectory + if ($filter) { + $dir .= "/$filter"; + $dir =~ s!/+$!!; + } File::Find::find({ follow_fast => 1, # follow symbolic links @@ -2672,14 +2791,14 @@ sub git_get_projects_list { # only directories can be git repositories return unless (-d $_); # don't traverse too deep (Find is super slow on os x) + # $project_maxdepth excludes depth of $projectroot if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) { $File::Find::prune = 1; return; } - my $subdir = substr($File::Find::name, $pfxlen + 1); + my $path = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot - my $path = ($filter ? "$filter/" : '') . $subdir; if (check_export_ok("$projectroot/$path")) { push @list, { path => $path }; $File::Find::prune = 1; @@ -2692,7 +2811,6 @@ sub git_get_projects_list { # 'git%2Fgit.git Linus+Torvalds' # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' - my %paths; open my $fd, '<', $projects_list or return; PROJECT: while (my $line = <$fd>) { @@ -2703,32 +2821,9 @@ sub git_get_projects_list { if (!defined $path) { next; } - if ($filter ne '') { - # looking for forks; - my $pfx = substr($path, 0, length($filter)); - if ($pfx ne $filter) { - next PROJECT; - } - my $sfx = substr($path, length($filter)); - if ($sfx !~ /^\/.*\.git$/) { - next PROJECT; - } - } elsif ($check_forks) { - PATH: - foreach my $filter (keys %paths) { - # looking for forks; - my $pfx = substr($path, 0, length($filter)); - if ($pfx ne $filter) { - next PATH; - } - my $sfx = substr($path, length($filter)); - if ($sfx !~ /^\/.*\.git$/) { - next PATH; - } - # is a fork, don't include it in - # the list - next PROJECT; - } + # if $filter is rpovided, check if $path begins with $filter + if ($filter && $path !~ m!^\Q$filter\E/!) { + next; } if (check_export_ok("$projectroot/$path")) { my $pr = { @@ -2736,8 +2831,6 @@ sub git_get_projects_list { owner => to_utf8($owner), }; push @list, $pr; - (my $forks_path = $path) =~ s/\.git$//; - $paths{$forks_path}++; } } close $fd; @@ -2745,6 +2838,98 @@ sub git_get_projects_list { return @list; } +# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile) +# as side effects it sets 'forks' field to list of forks for forked projects +sub filter_forks_from_projects_list { + my $projects = shift; + + my %trie; # prefix tree of directories (path components) + # generate trie out of those directories that might contain forks + foreach my $pr (@$projects) { + my $path = $pr->{'path'}; + $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory + next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git' + next unless ($path); # skip '.git' repository: tests, git-instaweb + next unless (-d $path); # containing directory exists + $pr->{'forks'} = []; # there can be 0 or more forks of project + + # add to trie + my @dirs = split('/', $path); + # walk the trie, until either runs out of components or out of trie + my $ref = \%trie; + while (scalar @dirs && + exists($ref->{$dirs[0]})) { + $ref = $ref->{shift @dirs}; + } + # create rest of trie structure from rest of components + foreach my $dir (@dirs) { + $ref = $ref->{$dir} = {}; + } + # create end marker, store $pr as a data + $ref->{''} = $pr if (!exists $ref->{''}); + } + + # filter out forks, by finding shortest prefix match for paths + my @filtered; + PROJECT: + foreach my $pr (@$projects) { + # trie lookup + my $ref = \%trie; + DIR: + foreach my $dir (split('/', $pr->{'path'})) { + if (exists $ref->{''}) { + # found [shortest] prefix, is a fork - skip it + push @{$ref->{''}{'forks'}}, $pr; + next PROJECT; + } + if (!exists $ref->{$dir}) { + # not in trie, cannot have prefix, not a fork + push @filtered, $pr; + next PROJECT; + } + # If the dir is there, we just walk one step down the trie. + $ref = $ref->{$dir}; + } + # we ran out of trie + # (shouldn't happen: it's either no match, or end marker) + push @filtered, $pr; + } + + return @filtered; +} + +# note: fill_project_list_info must be run first, +# for 'descr_long' and 'ctags' to be filled +sub search_projects_list { + my ($projlist, %opts) = @_; + my $tagfilter = $opts{'tagfilter'}; + my $searchtext = $opts{'searchtext'}; + + return @$projlist + unless ($tagfilter || $searchtext); + + my @projects; + PROJECT: + foreach my $pr (@$projlist) { + + if ($tagfilter) { + next unless ref($pr->{'ctags'}) eq 'HASH'; + next unless + grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}}; + } + + if ($searchtext) { + next unless + $pr->{'path'} =~ /$searchtext/ || + $pr->{'descr_long'} =~ /$searchtext/; + } + + push @projects, $pr; + } + + return @projects; +} + our $gitweb_project_owner = undef; sub git_get_project_list_from_file { @@ -2913,8 +3098,10 @@ sub parse_date { $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ", 1900+$year, 1+$mon, $mday, $hour ,$min, $sec; - $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; - my $local = $epoch + ((int $1 + ($2/60)) * 3600); + my ($tz_sign, $tz_hour, $tz_min) = + ($tz =~ m/^([-+])(\d\d)(\d\d)$/); + $tz_sign = ($tz_sign eq '-' ? -1 : +1); + my $local = $epoch + $tz_sign*((($tz_hour*60) + $tz_min)*60); ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); $date{'hour_local'} = $hour; $date{'minute_local'} = $min; @@ -3468,7 +3655,7 @@ sub run_highlighter { close $fd; open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". quote_command($highlight_bin). - " --fragment --syntax $syntax |" + " --replace-tabs=8 --fragment --syntax $syntax |" or die_error(500, "Couldn't open file or run syntax highlighter"); return $fd; } @@ -3501,7 +3688,7 @@ sub print_feed_meta { $href_params{'-title'} = 'log'; } - foreach my $format qw(RSS Atom) { + foreach my $format (qw(RSS Atom)) { my $type = lc($format); my %link_attr = ( '-rel' => 'alternate', @@ -3682,7 +3869,7 @@ sub git_footer_html { } $href_params{'-title'} ||= 'log'; - foreach my $format qw(RSS Atom) { + foreach my $format (qw(RSS Atom)) { $href_params{'action'} = lc($format); print $cgi->a({-href => href(%href_params), -title => "$href_params{'-title'} $format feed", @@ -3722,9 +3909,20 @@ sub git_footer_html { qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!. qq! "!. href() .qq!");\n!. qq!</script>\n!; - } elsif (gitweb_check_feature('javascript-actions')) { + } else { + my ($jstimezone, $tz_cookie, $datetime_class) = + gitweb_get_feature('javascript-timezone'); + print qq!<script type="text/javascript">\n!. - qq!window.onload = fixLinks;\n!. + qq!window.onload = function () {\n!; + if (gitweb_check_feature('javascript-actions')) { + print qq! fixLinks();\n!; + } + if ($jstimezone && $tz_cookie && $datetime_class) { + print qq! var tz_cookie = { name: '$tz_cookie', expires: 14, path: '/' };\n!. # in days + qq! onloadTZSetup('$jstimezone', tz_cookie, '$datetime_class');\n!; + } + print qq!};\n!. qq!</script>\n!; } @@ -3928,22 +4126,25 @@ sub git_print_section { print $cgi->end_div; } -sub print_local_time { - print format_local_time(@_); -} +sub format_timestamp_html { + my $date = shift; + my $strtime = $date->{'rfc2822'}; -sub format_local_time { - my $localtime = ''; - my %date = @_; - if ($date{'hour_local'} < 6) { - $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)", - $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'}); - } else { - $localtime .= sprintf(" (%02d:%02d %s)", - $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'}); + my (undef, undef, $datetime_class) = + gitweb_get_feature('javascript-timezone'); + if ($datetime_class) { + $strtime = qq!<span class="$datetime_class">$strtime</span>!; } - return $localtime; + my $localtime_format = '(%02d:%02d %s)'; + if ($date->{'hour_local'} < 6) { + $localtime_format = '(<span class="atnight">%02d:%02d</span> %s)'; + } + $strtime .= ' ' . + sprintf($localtime_format, + $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'}); + + return $strtime; } # Outputs the author name and date in long form @@ -3956,10 +4157,9 @@ sub git_print_authorship { my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . format_search_author($author, "author", esc_html($author)) . - " [$ad{'rfc2822'}"; - print_local_time(%ad) if ($opts{-localtime}); - print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1) - . "</$tag>\n"; + " [".format_timestamp_html(\%ad)."]". + git_get_avatar($co->{'author_email'}, -pad_before => 1) . + "</$tag>\n"; } # Outputs table rows containing the full author or committer information, @@ -3976,16 +4176,16 @@ sub git_print_authorship_rows { my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"}); print "<tr><td>$who</td><td>" . format_search_author($co->{"${who}_name"}, $who, - esc_html($co->{"${who}_name"})) . " " . + esc_html($co->{"${who}_name"})) . " " . format_search_author($co->{"${who}_email"}, $who, - esc_html("<" . $co->{"${who}_email"} . ">")) . + esc_html("<" . $co->{"${who}_email"} . ">")) . "</td><td rowspan=\"2\">" . git_get_avatar($co->{"${who}_email"}, -size => 'double') . "</td></tr>\n" . "<tr>" . - "<td></td><td> $wd{'rfc2822'}"; - print_local_time(%wd); - print "</td>" . + "<td></td><td>" . + format_timestamp_html(\%wd) . + "</td>" . "</tr>\n"; } } @@ -4335,7 +4535,8 @@ sub git_difftree_body { # link to patch $patchno++; print "<td class=\"link\">" . - $cgi->a({-href => "#patch$patchno"}, "patch") . + $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . " | " . "</td>\n"; } @@ -4412,7 +4613,7 @@ sub git_difftree_body { } if ($diff->{'from_mode'} ne ('0' x 6)) { $from_mode_oct = oct $diff->{'from_mode'}; - if (S_ISREG($to_mode_oct)) { # only for regular file + if (S_ISREG($from_mode_oct)) { # only for regular file $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits } $from_file_type = file_type($diff->{'from_mode'}); @@ -4432,8 +4633,9 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - print " | "; + print $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'}, hash_base=>$hash, file_name=>$diff->{'file'})}, @@ -4452,8 +4654,9 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - print " | "; + print $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'}, hash_base=>$parent, file_name=>$diff->{'file'})}, @@ -4494,7 +4697,8 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch") . + print $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . " | "; } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) { # "commit" view and modified file (not onlu mode changed) @@ -4539,7 +4743,8 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch") . + print $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . " | "; } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) { # "commit" view and modified file (not only pure rename or copy) @@ -4723,11 +4928,12 @@ sub git_patchset_body { # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -# fills project list info (age, description, owner, forks) for each -# project in the list, removing invalid projects from returned list +# fills project list info (age, description, owner, category, forks) +# for each project in the list, removing invalid projects from +# returned list # NOTE: modifies $projlist, but does not remove entries from it sub fill_project_list_info { - my ($projlist, $check_forks) = @_; + my $projlist = shift; my @projects; my $show_ctags = gitweb_check_feature('ctags'); @@ -4747,23 +4953,59 @@ sub fill_project_list_info { if (!defined $pr->{'owner'}) { $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || ""; } - if ($check_forks) { - my $pname = $pr->{'path'}; - if (($pname =~ s/\.git$//) && - ($pname !~ /\/$/) && - (-d "$projectroot/$pname")) { - $pr->{'forks'} = "-d $projectroot/$pname"; - } else { - $pr->{'forks'} = 0; - } + if ($show_ctags) { + $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); } - $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); + if ($projects_list_group_categories && !defined $pr->{'category'}) { + my $cat = git_get_project_category($pr->{'path'}) || + $project_list_default_category; + $pr->{'category'} = to_utf8($cat); + } + push @projects, $pr; } return @projects; } +sub sort_projects_list { + my ($projlist, $order) = @_; + my @projects; + + my %order_info = ( + project => { key => 'path', type => 'str' }, + descr => { key => 'descr_long', type => 'str' }, + owner => { key => 'owner', type => 'str' }, + age => { key => 'age', type => 'num' } + ); + my $oi = $order_info{$order}; + return @$projlist unless defined $oi; + if ($oi->{'type'} eq 'str') { + @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @$projlist; + } else { + @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @$projlist; + } + + return @projects; +} + +# returns a hash of categories, containing the list of project +# belonging to each category +sub build_projlist_by_category { + my ($projlist, $from, $to) = @_; + my %categories; + + $from = 0 unless defined $from; + $to = $#$projlist if (!defined $to || $#$projlist < $to); + + for (my $i = $from; $i <= $to; $i++) { + my $pr = $projlist->[$i]; + push @{$categories{ $pr->{'category'} }}, $pr; + } + + return wantarray ? %categories : \%categories; +} + # print 'sort by' <th> element, generating 'sort by $name' replay link # if that order is not selected sub print_sort_th { @@ -4787,70 +5029,15 @@ sub format_sort_th { return $sort_th; } -sub git_project_list_body { - # actually uses global variable $project - my ($projlist, $order, $from, $to, $extra, $no_header) = @_; - - my $check_forks = gitweb_check_feature('forks'); - my @projects = fill_project_list_info($projlist, $check_forks); +sub git_project_list_rows { + my ($projlist, $from, $to, $check_forks) = @_; - $order ||= $default_projects_order; $from = 0 unless defined $from; - $to = $#projects if (!defined $to || $#projects < $to); - - my %order_info = ( - project => { key => 'path', type => 'str' }, - descr => { key => 'descr_long', type => 'str' }, - owner => { key => 'owner', type => 'str' }, - age => { key => 'age', type => 'num' } - ); - my $oi = $order_info{$order}; - if ($oi->{'type'} eq 'str') { - @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects; - } else { - @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects; - } + $to = $#$projlist if (!defined $to || $#$projlist < $to); - my $show_ctags = gitweb_check_feature('ctags'); - if ($show_ctags) { - my %ctags; - foreach my $p (@projects) { - foreach my $ct (keys %{$p->{'ctags'}}) { - $ctags{$ct} += $p->{'ctags'}->{$ct}; - } - } - my $cloud = git_populate_project_tagcloud(\%ctags); - print git_show_project_tagcloud($cloud, 64); - } - - print "<table class=\"project_list\">\n"; - unless ($no_header) { - print "<tr>\n"; - if ($check_forks) { - print "<th></th>\n"; - } - print_sort_th('project', $order, 'Project'); - print_sort_th('descr', $order, 'Description'); - print_sort_th('owner', $order, 'Owner'); - print_sort_th('age', $order, 'Last Change'); - print "<th></th>\n" . # for links - "</tr>\n"; - } my $alternate = 1; - my $tagfilter = $cgi->param('by_tag'); for (my $i = $from; $i <= $to; $i++) { - my $pr = $projects[$i]; - - next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}}; - next if $searchtext and not $pr->{'path'} =~ /$searchtext/ - and not $pr->{'descr_long'} =~ /$searchtext/; - # Weed out forks or non-matching entries of search - if ($check_forks) { - my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#; - $forkbase="^$forkbase" if $forkbase; - next if not $searchtext and not $tagfilter and $show_ctags - and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe - } + my $pr = $projlist->[$i]; if ($alternate) { print "<tr class=\"dark\">\n"; @@ -4858,11 +5045,17 @@ sub git_project_list_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + if ($check_forks) { print "<td>"; if ($pr->{'forks'}) { - print "<!-- $pr->{'forks'} -->\n"; - print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+"); + my $nforks = scalar @{$pr->{'forks'}}; + if ($nforks > 0) { + print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"), + -title => "$nforks forks"}, "+"); + } else { + print $cgi->span({-title => "$nforks forks"}, "+"); + } } print "</td>\n"; } @@ -4883,6 +5076,84 @@ sub git_project_list_body { "</td>\n" . "</tr>\n"; } +} + +sub git_project_list_body { + # actually uses global variable $project + my ($projlist, $order, $from, $to, $extra, $no_header) = @_; + my @projects = @$projlist; + + my $check_forks = gitweb_check_feature('forks'); + my $show_ctags = gitweb_check_feature('ctags'); + my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef; + $check_forks = undef + if ($tagfilter || $searchtext); + + # filtering out forks before filling info allows to do less work + @projects = filter_forks_from_projects_list(\@projects) + if ($check_forks); + @projects = fill_project_list_info(\@projects); + # searching projects require filling to be run before it + @projects = search_projects_list(\@projects, + 'searchtext' => $searchtext, + 'tagfilter' => $tagfilter) + if ($tagfilter || $searchtext); + + $order ||= $default_projects_order; + $from = 0 unless defined $from; + $to = $#projects if (!defined $to || $#projects < $to); + + # short circuit + if ($from > $to) { + print "<center>\n". + "<b>No such projects found</b><br />\n". + "Click ".$cgi->a({-href=>href(project=>undef)},"here")." to view all projects<br />\n". + "</center>\n<br />\n"; + return; + } + + @projects = sort_projects_list(\@projects, $order); + + if ($show_ctags) { + my $ctags = git_gather_all_ctags(\@projects); + my $cloud = git_populate_project_tagcloud($ctags); + print git_show_project_tagcloud($cloud, 64); + } + + print "<table class=\"project_list\">\n"; + unless ($no_header) { + print "<tr>\n"; + if ($check_forks) { + print "<th></th>\n"; + } + print_sort_th('project', $order, 'Project'); + print_sort_th('descr', $order, 'Description'); + print_sort_th('owner', $order, 'Owner'); + print_sort_th('age', $order, 'Last Change'); + print "<th></th>\n" . # for links + "</tr>\n"; + } + + if ($projects_list_group_categories) { + # only display categories with projects in the $from-$to window + @projects = sort {$a->{'category'} cmp $b->{'category'}} @projects[$from..$to]; + my %categories = build_projlist_by_category(\@projects, $from, $to); + foreach my $cat (sort keys %categories) { + unless ($cat eq "") { + print "<tr>\n"; + if ($check_forks) { + print "<td></td>\n"; + } + print "<td class=\"category\" colspan=\"5\">".esc_html($cat)."</td>\n"; + print "</tr>\n"; + } + + git_project_list_rows($categories{$cat}, undef, undef, $check_forks); + } + } else { + git_project_list_rows(\@projects, $from, $to, $check_forks); + } + if (defined $extra) { print "<tr>\n"; if ($check_forks) { @@ -4906,7 +5177,6 @@ sub git_log_body { next if !%co; my $commit = $co{'id'}; my $ref = format_ref_marker($refs, $commit); - my %ad = parse_date($co{'author_epoch'}); git_print_header_div('commit', "<span class=\"age\">$co{'age_string'}</span>" . esc_html($co{'title'}) . $ref, @@ -5343,7 +5613,10 @@ sub git_forks { } sub git_project_index { - my @projects = git_get_projects_list($project); + my @projects = git_get_projects_list(); + if (!@projects) { + die_error(404, "No projects found"); + } print $cgi->header( -type => 'text/plain', @@ -5385,7 +5658,11 @@ sub git_summary { my $check_forks = gitweb_check_feature('forks'); if ($check_forks) { + # find forks of a project @forklist = git_get_projects_list($project); + # filter out forks of forks + @forklist = filter_forks_from_projects_list(\@forklist) + if (@forklist); } git_header_html(); @@ -5396,7 +5673,8 @@ sub git_summary { "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" . "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n"; if (defined $cd{'rfc2822'}) { - print "<tr id=\"metadata_lchange\"><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n"; + print "<tr id=\"metadata_lchange\"><td>last change</td>" . + "<td>".format_timestamp_html(\%cd)."</td></tr>\n"; } # use per project git URL list in $projectroot/$project/cloneurl @@ -5414,13 +5692,14 @@ sub git_summary { my $show_ctags = gitweb_check_feature('ctags'); if ($show_ctags) { my $ctags = git_get_project_ctags($project); - my $cloud = git_populate_project_tagcloud($ctags); - print "<tr id=\"metadata_ctags\"><td>Content tags:<br />"; - print "</td>\n<td>" unless %$ctags; - print "<form action=\"$show_ctags\" method=\"post\"><input type=\"hidden\" name=\"p\" value=\"$project\" />Add: <input type=\"text\" name=\"t\" size=\"8\" /></form>"; - print "</td>\n<td>" if %$ctags; - print git_show_project_tagcloud($cloud, 48); - print "</td></tr>"; + if (%$ctags) { + # without ability to add tags, don't show if there are none + my $cloud = git_populate_project_tagcloud($ctags); + print "<tr id=\"metadata_ctags\">" . + "<td>content tags</td>" . + "<td>".git_show_project_tagcloud($cloud, 48)."</td>" . + "</tr>\n"; + } } print "</table>\n"; @@ -7064,7 +7343,7 @@ sub git_feed { if (defined($commitlist[0])) { %latest_commit = %{$commitlist[0]}; my $latest_epoch = $latest_commit{'committer_epoch'}; - %latest_date = parse_date($latest_epoch); + %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'}); my $if_modified = $cgi->http('IF_MODIFIED_SINCE'); if (defined $if_modified) { my $since; @@ -7195,7 +7474,7 @@ XML if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) { last; } - my %cd = parse_date($co{'author_epoch'}); + my %cd = parse_date($co{'author_epoch'}, $co{'author_tz'}); # get list of changed files open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, @@ -7305,6 +7584,9 @@ sub git_atom { sub git_opml { my @list = git_get_projects_list(); + if (!@list) { + die_error(404, "No projects found"); + } print $cgi->header( -type => 'text/xml', diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 79d7eebba7..7d88509208 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -295,6 +295,13 @@ td.current_head { text-decoration: underline; } +td.category { + background-color: #d9d8d1; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + font-weight: bold; +} + table.diff_tree span.file_status.new { color: #008000; } @@ -579,6 +586,39 @@ div.remote { display: inline-block; } +/* JavaScript-based timezone manipulation */ + +.popup { /* timezone selection UI */ + position: absolute; + /* "top: 0; right: 0;" would be better, if not for bugs in browsers */ + top: 0; left: 0; + border: 1px solid; + padding: 2px; + background-color: #f0f0f0; + font-style: normal; + color: #000000; + cursor: auto; +} + +.close-button { /* close timezone selection UI without selecting */ + /* float doesn't work within absolutely positioned container, + * if width of container is not set explicitly */ + /* float: right; */ + position: absolute; + top: 0px; right: 0px; + border: 1px solid green; + margin: 1px 1px 1px 1px; + padding-bottom: 2px; + width: 12px; + height: 10px; + font-size: 9px; + font-weight: bold; + text-align: center; + background-color: #fff0f0; + cursor: pointer; +} + + /* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */ /* Highlighting theme definition: */ diff --git a/gitweb/static/js/README b/gitweb/static/js/README new file mode 100644 index 0000000000..f8460ed32f --- /dev/null +++ b/gitweb/static/js/README @@ -0,0 +1,20 @@ +GIT web interface (gitweb) - JavaScript +======================================= + +This directory holds JavaScript code used by gitweb (GIT web interface). +Scripts from there would be concatenated together in the order specified +by gitweb/Makefile into gitweb/static/gitweb.js, during building of +gitweb/gitweb.cgi (during gitweb building). The resulting file (or its +minification) would then be installed / deployed together with gitweb. + +Scripts in 'lib/' subdirectory compose generic JavaScript library, +providing features required by gitweb but in no way limited to gitweb +only. In the future those scripts could be replaced by some JavaScript +library / framework, like e.g. jQuery, YUI, Prototype, MooTools, Dojo, +ExtJS, Script.aculo.us or SproutCore. + +All scripts that manipulate gitweb output should be put outside 'lib/', +directly in this directory ('gitweb/static/js/'). Those scripts would +have to be rewritten if gitweb moves to using some JavaScript library. + +See also comments in gitweb/Makefile. diff --git a/gitweb/static/js/adjust-timezone.js b/gitweb/static/js/adjust-timezone.js new file mode 100644 index 0000000000..0c67779500 --- /dev/null +++ b/gitweb/static/js/adjust-timezone.js @@ -0,0 +1,330 @@ +// Copyright (C) 2011, John 'Warthog9' Hawley <warthog9@eaglescrag.net> +// 2011, Jakub Narebski <jnareb@gmail.com> + +/** + * @fileOverview Manipulate dates in gitweb output, adjusting timezone + * @license GPLv2 or later + */ + +/** + * Get common timezone, add UI for changing timezones, and adjust + * dates to use requested common timezone. + * + * This function is called during onload event (added to window.onload). + * + * @param {String} tzDefault: default timezone, if there is no cookie + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzCookieInfo.name: name of cookie to store timezone + * @param {String} tzClassName: denotes elements with date to be adjusted + */ +function onloadTZSetup(tzDefault, tzCookieInfo, tzClassName) { + var tzCookieTZ = getCookie(tzCookieInfo.name, tzCookieInfo); + var tz = tzDefault; + + if (tzCookieTZ) { + // set timezone to value saved in a cookie + tz = tzCookieTZ; + // refresh cookie, so its expiration counts from last use of gitweb + setCookie(tzCookieInfo.name, tzCookieTZ, tzCookieInfo); + } + + // add UI for changing timezone + addChangeTZ(tz, tzCookieInfo, tzClassName); + + // server-side of gitweb produces datetime in UTC, + // so if tz is 'utc' there is no need for changes + var nochange = tz === 'utc'; + + // adjust dates to use specified common timezone + fixDatetimeTZ(tz, tzClassName, nochange); +} + + +/* ...................................................................... */ +/* Changing dates to use requested timezone */ + +/** + * Replace RFC-2822 dates contained in SPAN elements with tzClassName + * CSS class with equivalent dates in given timezone. + * + * @param {String} tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local' + * @param {String} tzClassName: specifies elements to be changed + * @param {Boolean} nochange: markup for timezone change, but don't change it + */ +function fixDatetimeTZ(tz, tzClassName, nochange) { + // sanity check, method should be ensured by common-lib.js + if (!document.getElementsByClassName) { + return; + } + + // translate to timezone in '(-|+)HHMM' format + tz = normalizeTimezoneInfo(tz); + + // NOTE: result of getElementsByClassName should probably be cached + var classesFound = document.getElementsByClassName(tzClassName, "span"); + for (var i = 0, len = classesFound.length; i < len; i++) { + var curElement = classesFound[i]; + + curElement.title = 'Click to change timezone'; + if (!nochange) { + // we use *.firstChild.data (W3C DOM) instead of *.innerHTML + // as the latter doesn't always work everywhere in every browser + var epoch = parseRFC2822Date(curElement.firstChild.data); + var adjusted = formatDateRFC2882(epoch, tz); + + curElement.firstChild.data = adjusted; + } + } +} + + +/* ...................................................................... */ +/* Adding triggers, generating timezone menu, displaying and hiding */ + +/** + * Adds triggers for UI to change common timezone used for dates in + * gitweb output: it marks up and/or creates item to click to invoke + * timezone change UI, creates timezone UI fragment to be attached, + * and installs appropriate onclick trigger (via event delegation). + * + * @param {String} tzSelected: pre-selected timezone, + * 'utc' or 'local' or '(-|+)HHMM' + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzClassName: specifies elements to install trigger + */ +function addChangeTZ(tzSelected, tzCookieInfo, tzClassName) { + // make link to timezone UI discoverable + addCssRule('.'+tzClassName + ':hover', + 'text-decoration: underline; cursor: help;'); + + // create form for selecting timezone (to be saved in a cookie) + var tzSelectFragment = document.createDocumentFragment(); + tzSelectFragment = createChangeTZForm(tzSelectFragment, + tzSelected, tzCookieInfo, tzClassName); + + // event delegation handler for timezone selection UI (clicking on entry) + // see http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/ + // assumes that there is no existing document.onclick handler + document.onclick = function onclickHandler(event) { + //IE doesn't pass in the event object + event = event || window.event; + + //IE uses srcElement as the target + var target = event.target || event.srcElement; + + switch (target.className) { + case tzClassName: + // don't display timezone menu if it is already displayed + if (tzSelectFragment.childNodes.length > 0) { + displayChangeTZForm(target, tzSelectFragment); + } + break; + } // end switch + }; +} + +/** + * Create DocumentFragment with UI for changing common timezone in + * which dates are shown in. + * + * @param {DocumentFragment} documentFragment: where attach UI + * @param {String} tzSelected: default (pre-selected) timezone + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @returns {DocumentFragment} + */ +function createChangeTZForm(documentFragment, tzSelected, tzCookieInfo, tzClassName) { + var div = document.createElement("div"); + div.className = 'popup'; + + /* '<div class="close-button" title="(click on this box to close)">X</div>' */ + var closeButton = document.createElement('div'); + closeButton.className = 'close-button'; + closeButton.title = '(click on this box to close)'; + closeButton.appendChild(document.createTextNode('X')); + closeButton.onclick = closeTZFormHandler(documentFragment, tzClassName); + div.appendChild(closeButton); + + /* 'Select timezone: <br clear="all">' */ + div.appendChild(document.createTextNode('Select timezone: ')); + var br = document.createElement('br'); + br.clear = 'all'; + div.appendChild(br); + + /* '<select name="tzoffset"> + * ... + * <option value="-0700">UTC-07:00</option> + * <option value="-0600">UTC-06:00</option> + * ... + * </select>' */ + var select = document.createElement("select"); + select.name = "tzoffset"; + //select.style.clear = 'all'; + select.appendChild(generateTZOptions(tzSelected)); + select.onchange = selectTZHandler(documentFragment, tzCookieInfo, tzClassName); + div.appendChild(select); + + documentFragment.appendChild(div); + + return documentFragment; +} + + +/** + * Hide (remove from DOM) timezone change UI, ensuring that it is not + * garbage collected and that it can be re-enabled later. + * + * @param {DocumentFragment} documentFragment: contains detached UI + * @param {HTMLSelectElement} target: select element inside of UI + * @param {String} tzClassName: specifies element where UI was installed + * @returns {DocumentFragment} documentFragment + */ +function removeChangeTZForm(documentFragment, target, tzClassName) { + // find containing element, where we appended timezone selection UI + // `target' is somewhere inside timezone menu + var container = target.parentNode, popup = target; + while (container && + container.className !== tzClassName) { + popup = container; + container = container.parentNode; + } + // safety check if we found correct container, + // and if it isn't deleted already + if (!container || !popup || + container.className !== tzClassName || + popup.className !== 'popup') { + return documentFragment; + } + + // timezone selection UI was appended as last child + // see also displayChangeTZForm function + var removed = popup.parentNode.removeChild(popup); + if (documentFragment.firstChild !== removed) { // the only child + // re-append it so it would be available for next time + documentFragment.appendChild(removed); + } + // all of inline style was added by this script + // it is not really needed to remove it, but it is a good practice + container.removeAttribute('style'); + + return documentFragment; +} + + +/** + * Display UI for changing common timezone for dates in gitweb output. + * To be used from 'onclick' event handler. + * + * @param {HTMLElement} target: where to install/display UI + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + */ +function displayChangeTZForm(target, tzSelectFragment) { + // for absolute positioning to be related to target element + target.style.position = 'relative'; + target.style.display = 'inline-block'; + + // show/display UI for changing timezone + target.appendChild(tzSelectFragment); +} + + +/* ...................................................................... */ +/* List of timezones for timezone selection menu */ + +/** + * Generate list of timezones for creating timezone select UI + * + * @returns {Object[]} list of e.g. { value: '+0100', descr: 'GMT+01:00' } + */ +function generateTZList() { + var timezones = [ + { value: "utc", descr: "UTC/GMT"}, + { value: "local", descr: "Local (per browser)"} + ]; + + // generate all full hour timezones (no fractional timezones) + for (var x = -12, idx = timezones.length; x <= +14; x++, idx++) { + var hours = (x >= 0 ? '+' : '-') + padLeft(x >=0 ? x : -x, 2); + timezones[idx] = { value: hours + '00', descr: 'UTC' + hours + ':00'}; + if (x === 0) { + timezones[idx].descr = 'UTC\u00B100:00'; // 'UTC±00:00' + } + } + + return timezones; +} + +/** + * Generate <options> elements for timezone select UI + * + * @param {String} tzSelected: default timezone + * @returns {DocumentFragment} list of options elements to appendChild + */ +function generateTZOptions(tzSelected) { + var elems = document.createDocumentFragment(); + var timezones = generateTZList(); + + for (var i = 0, len = timezones.length; i < len; i++) { + var tzone = timezones[i]; + var option = document.createElement("option"); + if (tzone.value === tzSelected) { + option.defaultSelected = true; + } + option.value = tzone.value; + option.appendChild(document.createTextNode(tzone.descr)); + + elems.appendChild(option); + } + + return elems; +} + + +/* ...................................................................... */ +/* Event handlers and/or their generators */ + +/** + * Create event handler that select timezone and closes timezone select UI. + * To be used as $('select[name="tzselect"]').onchange handler. + * + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzCookieInfo.name: name of cookie to save result of selection + * @param {String} tzClassName: specifies element where UI was installed + * @returns {Function} event handler + */ +function selectTZHandler(tzSelectFragment, tzCookieInfo, tzClassName) { + //return function selectTZ(event) { + return function (event) { + event = event || window.event; + var target = event.target || event.srcElement; + + var selected = target.options.item(target.selectedIndex); + removeChangeTZForm(tzSelectFragment, target, tzClassName); + + if (selected) { + selected.defaultSelected = true; + setCookie(tzCookieInfo.name, selected.value, tzCookieInfo); + fixDatetimeTZ(selected.value, tzClassName); + } + }; +} + +/** + * Create event handler that closes timezone select UI. + * To be used e.g. as $('.closebutton').onclick handler. + * + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + * @param {String} tzClassName: specifies element where UI was installed + * @returns {Function} event handler + */ +function closeTZFormHandler(tzSelectFragment, tzClassName) { + //return function closeTZForm(event) { + return function (event) { + event = event || window.event; + var target = event.target || event.srcElement; + + removeChangeTZForm(tzSelectFragment, target, tzClassName); + }; +} + +/* end of adjust-timezone.js */ diff --git a/gitweb/static/gitweb.js b/gitweb/static/js/blame_incremental.js index 9c66928c4a..676da6b590 100644 --- a/gitweb/static/gitweb.js +++ b/gitweb/static/js/blame_incremental.js @@ -1,45 +1,13 @@ // Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> // 2007, Petr Baudis <pasky@suse.cz> -// 2008-2009, Jakub Narebski <jnareb@gmail.com> +// 2008-2011, Jakub Narebski <jnareb@gmail.com> /** - * @fileOverview JavaScript code for gitweb (git web interface). + * @fileOverview JavaScript side of Ajax-y 'blame_incremental' view in gitweb * @license GPLv2 or later */ /* ============================================================ */ -/* functions for generic gitweb actions and views */ - -/** - * used to check if link has 'js' query parameter already (at end), - * and other reasons to not add 'js=1' param at the end of link - * @constant - */ -var jsExceptionsRe = /[;?]js=[01]$/; - -/** - * Add '?js=1' or ';js=1' to the end of every link in the document - * that doesn't have 'js' query parameter set already. - * - * Links with 'js=1' lead to JavaScript version of given action, if it - * exists (currently there is only 'blame_incremental' for 'blame') - * - * @globals jsExceptionsRe - */ -function fixLinks() { - var allLinks = document.getElementsByTagName("a") || document.links; - for (var i = 0, len = allLinks.length; i < len; i++) { - var link = allLinks[i]; - if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/; - link.href += - (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1'; - } - } -} - - -/* ============================================================ */ - /* * This code uses DOM methods instead of (nonstandard) innerHTML * to modify page. @@ -58,72 +26,7 @@ function fixLinks() { */ -/* ============================================================ */ -/* generic utility functions */ - - -/** - * pad number N with nonbreakable spaces on the left, to WIDTH characters - * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' - * ('\u00A0' is nonbreakable space) - * - * @param {Number|String} input: number to pad - * @param {Number} width: visible width of output - * @param {String} str: string to prefix to string, e.g. '\u00A0' - * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR - */ -function padLeftStr(input, width, str) { - var prefix = ''; - - width -= input.toString().length; - while (width > 0) { - prefix += str; - width--; - } - return prefix + input; -} - -/** - * Pad INPUT on the left to SIZE width, using given padding character CH, - * for example padLeft('a', 3, '_') is '__a'. - * - * @param {String} input: input value converted to string. - * @param {Number} width: desired length of output. - * @param {String} ch: single character to prefix to string. - * - * @returns {String} Modified string, at least SIZE length. - */ -function padLeft(input, width, ch) { - var s = input + ""; - while (s.length < width) { - s = ch + s; - } - return s; -} - -/** - * Create XMLHttpRequest object in cross-browser way - * @returns XMLHttpRequest object, or null - */ -function createRequestObject() { - try { - return new XMLHttpRequest(); - } catch (e) {} - try { - return window.createRequest(); - } catch (e) {} - try { - return new ActiveXObject("Msxml2.XMLHTTP"); - } catch (e) {} - try { - return new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) {} - - return null; -} - - -/* ============================================================ */ +/* ............................................................ */ /* utility/helper functions (and variables) */ var xhr; // XMLHttpRequest object @@ -229,7 +132,7 @@ function writeTimeInterval() { } /** - * show an error message alert to user within page (in prohress info area) + * show an error message alert to user within page (in progress info area) * @param {String} str: plain text error message (no HTML) * * @globals div_progress_info @@ -279,7 +182,7 @@ function getColorNo(tr) { var colorsFreq = [0, 0, 0]; /** - * return one of given possible colors (curently least used one) + * return one of given possible colors (currently least used one) * example: chooseColorNoFrom(2, 3) returns 2 or 3 * * @param {Number[]} arguments: one or more numbers @@ -300,8 +203,8 @@ function chooseColorNoFrom() { } /** - * given two neigbour <tr> elements, find color which would be different - * from color of both of neighbours; used to 3-color blame table + * given two neighbor <tr> elements, find color which would be different + * from color of both of neighbors; used to 3-color blame table * * @param {HTMLElement} tr_prev * @param {HTMLElement} tr_next @@ -313,14 +216,14 @@ function findColorNo(tr_prev, tr_next) { var color_next = getColorNo(tr_next); - // neither of neighbours has color set + // neither of neighbors has color set // THEN we can use any of 3 possible colors if (!color_prev && !color_next) { return chooseColorNoFrom(1,2,3); } - // either both neighbours have the same color, - // or only one of neighbours have color set + // either both neighbors have the same color, + // or only one of neighbors have color set // THEN we can use any color except given var color; if (color_prev === color_next) { @@ -334,7 +237,7 @@ function findColorNo(tr_prev, tr_next) { return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); } - // neighbours have different colors + // neighbors have different colors // THEN there is only one color left return (3 - ((color_prev + color_next) % 3)); } @@ -355,7 +258,7 @@ function isStartOfGroup(tr) { /** * change colors to use zebra coloring (2 colors) instead of 3 colors - * concatenate neighbour commit groups belonging to the same commit + * concatenate neighbor commit groups belonging to the same commit * * @globals colorRe */ @@ -392,97 +295,6 @@ function fixColorsAndGroups() { } } -/* ............................................................ */ -/* time and data */ - -/** - * used to extract hours and minutes from timezone info, e.g '-0900' - * @constant - */ -var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/; - -/** - * return date in local time formatted in iso-8601 like format - * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' - * - * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {String} date in local time in iso-8601 like format - * - * @globals tzRe - */ -function formatDateISOLocal(epoch, timezoneInfo) { - var match = tzRe.exec(timezoneInfo); - // date corrected by timezone - var localDate = new Date(1000 * (epoch + - (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60))); - var localDateStr = // e.g. '2005-08-07' - localDate.getUTCFullYear() + '-' + - padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + - padLeft(localDate.getUTCDate(), 2, '0'); - var localTimeStr = // e.g. '21:49:46' - padLeft(localDate.getUTCHours(), 2, '0') + ':' + - padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + - padLeft(localDate.getUTCSeconds(), 2, '0'); - - return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; -} - -/* ............................................................ */ -/* unquoting/unescaping filenames */ - -/**#@+ - * @constant - */ -var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; -var octEscRe = /^[0-7]{1,3}$/; -var maybeQuotedRe = /^\"(.*)\"$/; -/**#@-*/ - -/** - * unquote maybe git-quoted filename - * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' - * - * @param {String} str: git-quoted string - * @returns {String} Unquoted and unescaped string - * - * @globals escCodeRe, octEscRe, maybeQuotedRe - */ -function unquote(str) { - function unq(seq) { - var es = { - // character escape codes, aka escape sequences (from C) - // replacements are to some extent JavaScript specific - t: "\t", // tab (HT, TAB) - n: "\n", // newline (NL) - r: "\r", // return (CR) - f: "\f", // form feed (FF) - b: "\b", // backspace (BS) - a: "\x07", // alarm (bell) (BEL) - e: "\x1B", // escape (ESC) - v: "\v" // vertical tab (VT) - }; - - if (seq.search(octEscRe) !== -1) { - // octal char sequence - return String.fromCharCode(parseInt(seq, 8)); - } else if (seq in es) { - // C escape sequence, aka character escape code - return es[seq]; - } - // quoted ordinary character - return seq; - } - - var match = str.match(maybeQuotedRe); - if (match) { - str = match[1]; - // perhaps str = eval('"'+str+'"'); would be enough? - str = str.replace(escCodeRe, - function (substr, p1, offset, s) { return unq(p1); }); - } - return str; -} /* ============================================================ */ /* main part: parsing response */ @@ -872,4 +684,4 @@ function startBlame(blamedataUrl, bUrl) { pollTimer = setInterval(xhr.onreadystatechange, 1000); } -// end of gitweb.js +/* end of blame_incremental.js */ diff --git a/gitweb/static/js/javascript-detection.js b/gitweb/static/js/javascript-detection.js new file mode 100644 index 0000000000..93dd2bdd91 --- /dev/null +++ b/gitweb/static/js/javascript-detection.js @@ -0,0 +1,43 @@ +// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> +// 2007, Petr Baudis <pasky@suse.cz> +// 2008-2011, Jakub Narebski <jnareb@gmail.com> + +/** + * @fileOverview Detect if JavaScript is enabled, and pass it to server-side + * @license GPLv2 or later + */ + + +/* ============================================================ */ +/* Manipulating links */ + +/** + * used to check if link has 'js' query parameter already (at end), + * and other reasons to not add 'js=1' param at the end of link + * @constant + */ +var jsExceptionsRe = /[;?]js=[01]$/; + +/** + * Add '?js=1' or ';js=1' to the end of every link in the document + * that doesn't have 'js' query parameter set already. + * + * Links with 'js=1' lead to JavaScript version of given action, if it + * exists (currently there is only 'blame_incremental' for 'blame') + * + * To be used as `window.onload` handler + * + * @globals jsExceptionsRe + */ +function fixLinks() { + var allLinks = document.getElementsByTagName("a") || document.links; + for (var i = 0, len = allLinks.length; i < len; i++) { + var link = allLinks[i]; + if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/; + link.href += + (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1'; + } + } +} + +/* end of javascript-detection.js */ diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js new file mode 100644 index 0000000000..018bbb7d4c --- /dev/null +++ b/gitweb/static/js/lib/common-lib.js @@ -0,0 +1,224 @@ +// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> +// 2007, Petr Baudis <pasky@suse.cz> +// 2008-2011, Jakub Narebski <jnareb@gmail.com> + +/** + * @fileOverview Generic JavaScript code (helper functions) + * @license GPLv2 or later + */ + + +/* ============================================================ */ +/* ............................................................ */ +/* Padding */ + +/** + * pad INPUT on the left with STR that is assumed to have visible + * width of single character (for example nonbreakable spaces), + * to WIDTH characters + * + * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' + * ('\u00A0' is nonbreakable space) + * + * @param {Number|String} input: number to pad + * @param {Number} width: visible width of output + * @param {String} str: string to prefix to string, defaults to '\u00A0' + * @returns {String} INPUT prefixed with STR x (WIDTH - INPUT.length) + */ +function padLeftStr(input, width, str) { + var prefix = ''; + if (typeof str === 'undefined') { + ch = '\u00A0'; // using ' ' doesn't work in all browsers + } + + width -= input.toString().length; + while (width > 0) { + prefix += str; + width--; + } + return prefix + input; +} + +/** + * Pad INPUT on the left to WIDTH, using given padding character CH, + * for example padLeft('a', 3, '_') is '__a' + * padLeft(4, 2) is '04' (same as padLeft(4, 2, '0')) + * + * @param {String} input: input value converted to string. + * @param {Number} width: desired length of output. + * @param {String} ch: single character to prefix to string, defaults to '0'. + * + * @returns {String} Modified string, at least SIZE length. + */ +function padLeft(input, width, ch) { + var s = input + ""; + if (typeof ch === 'undefined') { + ch = '0'; + } + + while (s.length < width) { + s = ch + s; + } + return s; +} + + +/* ............................................................ */ +/* Handling browser incompatibilities */ + +/** + * Create XMLHttpRequest object in cross-browser way + * @returns XMLHttpRequest object, or null + */ +function createRequestObject() { + try { + return new XMLHttpRequest(); + } catch (e) {} + try { + return window.createRequest(); + } catch (e) {} + try { + return new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) {} + try { + return new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + + return null; +} + + +/** + * Insert rule giving specified STYLE to given SELECTOR at the end of + * first CSS stylesheet. + * + * @param {String} selector: CSS selector, e.g. '.class' + * @param {String} style: rule contents, e.g. 'background-color: red;' + */ +function addCssRule(selector, style) { + var stylesheet = document.styleSheets[0]; + + var theRules = []; + if (stylesheet.cssRules) { // W3C way + theRules = stylesheet.cssRules; + } else if (stylesheet.rules) { // IE way + theRules = stylesheet.rules; + } + + if (stylesheet.insertRule) { // W3C way + stylesheet.insertRule(selector + ' { ' + style + ' }', theRules.length); + } else if (stylesheet.addRule) { // IE way + stylesheet.addRule(selector, style); + } +} + + +/* ............................................................ */ +/* Support for legacy browsers */ + +/** + * Provides getElementsByClassName method, if there is no native + * implementation of this method. + * + * NOTE that there are limits and differences compared to native + * getElementsByClassName as defined by e.g.: + * https://developer.mozilla.org/en/DOM/document.getElementsByClassName + * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-getelementsbyclassname + * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-document-getelementsbyclassname + * + * Namely, this implementation supports only single class name as + * argument and not set of space-separated tokens representing classes, + * it returns Array of nodes rather than live NodeList, and has + * additional optional argument where you can limit search to given tags + * (via getElementsByTagName). + * + * Based on + * http://code.google.com/p/getelementsbyclassname/ + * http://www.dustindiaz.com/getelementsbyclass/ + * http://stackoverflow.com/questions/1818865/do-we-have-getelementsbyclassname-in-javascript + * + * See also http://ejohn.org/blog/getelementsbyclassname-speed-comparison/ + * + * @param {String} class: name of _single_ class to find + * @param {String} [taghint] limit search to given tags + * @returns {Node[]} array of matching elements + */ +if (!('getElementsByClassName' in document)) { + document.getElementsByClassName = function (classname, taghint) { + taghint = taghint || "*"; + var elements = (taghint === "*" && document.all) ? + document.all : + document.getElementsByTagName(taghint); + var pattern = new RegExp("(^|\\s)" + classname + "(\\s|$)"); + var matches= []; + for (var i = 0, j = 0, n = elements.length; i < n; i++) { + var el= elements[i]; + if (el.className && pattern.test(el.className)) { + // matches.push(el); + matches[j] = el; + j++; + } + } + return matches; + }; +} // end if + + +/* ............................................................ */ +/* unquoting/unescaping filenames */ + +/**#@+ + * @constant + */ +var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; +var octEscRe = /^[0-7]{1,3}$/; +var maybeQuotedRe = /^\"(.*)\"$/; +/**#@-*/ + +/** + * unquote maybe C-quoted filename (as used by git, i.e. it is + * in double quotes '"' if there is any escape character used) + * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' + * + * @param {String} str: git-quoted string + * @returns {String} Unquoted and unescaped string + * + * @globals escCodeRe, octEscRe, maybeQuotedRe + */ +function unquote(str) { + function unq(seq) { + var es = { + // character escape codes, aka escape sequences (from C) + // replacements are to some extent JavaScript specific + t: "\t", // tab (HT, TAB) + n: "\n", // newline (NL) + r: "\r", // return (CR) + f: "\f", // form feed (FF) + b: "\b", // backspace (BS) + a: "\x07", // alarm (bell) (BEL) + e: "\x1B", // escape (ESC) + v: "\v" // vertical tab (VT) + }; + + if (seq.search(octEscRe) !== -1) { + // octal char sequence + return String.fromCharCode(parseInt(seq, 8)); + } else if (seq in es) { + // C escape sequence, aka character escape code + return es[seq]; + } + // quoted ordinary character + return seq; + } + + var match = str.match(maybeQuotedRe); + if (match) { + str = match[1]; + // perhaps str = eval('"'+str+'"'); would be enough? + str = str.replace(escCodeRe, + function (substr, p1, offset, s) { return unq(p1); }); + } + return str; +} + +/* end of common-lib.js */ diff --git a/gitweb/static/js/lib/cookies.js b/gitweb/static/js/lib/cookies.js new file mode 100644 index 0000000000..72b51cd1b4 --- /dev/null +++ b/gitweb/static/js/lib/cookies.js @@ -0,0 +1,114 @@ +/** + * @fileOverview Accessing cookies from JavaScript + * @license GPLv2 or later + */ + +/* + * Based on subsection "Cookies in JavaScript" of "Professional + * JavaScript for Web Developers" by Nicholas C. Zakas and cookie + * plugin from jQuery (dual licensed under the MIT and GPL licenses) + */ + + +/** + * Create a cookie with the given name and value, + * and other optional parameters. + * + * @example + * setCookie('foo', 'bar'); // will be deleted when browser exits + * setCookie('foo', 'bar', { expires: new Date(Date.parse('Jan 1, 2012')) }); + * setCookie('foo', 'bar', { expires: 7 }); // 7 days = 1 week + * setCookie('foo', 'bar', { expires: 14, path: '/' }); + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores). + * @param {String} sValue: The string value stored in a cookie. + * @param {Object} [options] An object literal containing key/value pairs + * to provide optional cookie attributes. + * @param {String|Number|Date} [options.expires] Either literal string to be used as cookie expires, + * or an integer specifying the expiration date from now on in days, + * or a Date object to be used as cookie expiration date. + * If a negative value is specified or a date in the past), + * the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie + * and will not be retained when the the browser exits. + * @param {String} [options.path] Restrict access of a cookie to particular directory + * (default: path of page that created the cookie). + * @param {String} [options.domain] Override what web sites are allowed to access cookie + * (default: domain of page that created the cookie). + * @param {Boolean} [options.secure] If true, the secure attribute of the cookie will be set + * and the cookie would be accessible only from secure sites + * (cookie transmission will require secure protocol like HTTPS). + */ +function setCookie(sName, sValue, options) { + options = options || {}; + if (sValue === null) { + sValue = ''; + option.expires = 'delete'; + } + + var sCookie = sName + '=' + encodeURIComponent(sValue); + + if (options.expires) { + var oExpires = options.expires, sDate; + if (oExpires === 'delete') { + sDate = 'Thu, 01 Jan 1970 00:00:00 GMT'; + } else if (typeof oExpires === 'string') { + sDate = oExpires; + } else { + var oDate; + if (typeof oExpires === 'number') { + oDate = new Date(); + oDate.setTime(oDate.getTime() + (oExpires * 24 * 60 * 60 * 1000)); // days to ms + } else { + oDate = oExpires; + } + sDate = oDate.toGMTString(); + } + sCookie += '; expires=' + sDate; + } + + if (options.path) { + sCookie += '; path=' + (options.path); + } + if (options.domain) { + sCookie += '; domain=' + (options.domain); + } + if (options.secure) { + sCookie += '; secure'; + } + document.cookie = sCookie; +} + +/** + * Get the value of a cookie with the given name. + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) + * @returns {String|null} The string value stored in a cookie + */ +function getCookie(sName) { + var sRE = '(?:; )?' + sName + '=([^;]*);?'; + var oRE = new RegExp(sRE); + if (oRE.test(document.cookie)) { + return decodeURIComponent(RegExp['$1']); + } else { + return null; + } +} + +/** + * Delete cookie with given name + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) + * @param {Object} [options] An object literal containing key/value pairs + * to provide optional cookie attributes. + * @param {String} [options.path] Must be the same as when setting a cookie + * @param {String} [options.domain] Must be the same as when setting a cookie + */ +function deleteCookie(sName, options) { + options = options || {}; + options.expires = 'delete'; + + setCookie(sName, '', options); +} + +/* end of cookies.js */ diff --git a/gitweb/static/js/lib/datetime.js b/gitweb/static/js/lib/datetime.js new file mode 100644 index 0000000000..f78c60a912 --- /dev/null +++ b/gitweb/static/js/lib/datetime.js @@ -0,0 +1,176 @@ +// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> +// 2007, Petr Baudis <pasky@suse.cz> +// 2008-2011, Jakub Narebski <jnareb@gmail.com> + +/** + * @fileOverview Datetime manipulation: parsing and formatting + * @license GPLv2 or later + */ + + +/* ............................................................ */ +/* parsing and retrieving datetime related information */ + +/** + * used to extract hours and minutes from timezone info, e.g '-0900' + * @constant + */ +var tzRe = /^([+\-])([0-9][0-9])([0-9][0-9])$/; + +/** + * convert numeric timezone +/-ZZZZ to offset from UTC in seconds + * + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {Number} offset from UTC in seconds for timezone + * + * @globals tzRe + */ +function timezoneOffset(timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + var tz_sign = (match[1] === '-' ? -1 : +1); + var tz_hour = parseInt(match[2],10); + var tz_min = parseInt(match[3],10); + + return tz_sign*(((tz_hour*60) + tz_min)*60); +} + +/** + * return local (browser) timezone as offset from UTC in seconds + * + * @returns {Number} offset from UTC in seconds for local timezone + */ +function localTimezoneOffset() { + // getTimezoneOffset returns the time-zone offset from UTC, + // in _minutes_, for the current locale + return ((new Date()).getTimezoneOffset() * -60); +} + +/** + * return local (browser) timezone as numeric timezone '(+|-)HHMM' + * + * @returns {String} locat timezone as -/+ZZZZ + */ +function localTimezoneInfo() { + var tzOffsetMinutes = (new Date()).getTimezoneOffset() * -1; + + return formatTimezoneInfo(0, tzOffsetMinutes); +} + + +/** + * Parse RFC-2822 date into a Unix timestamp (into epoch) + * + * @param {String} date: date in RFC-2822 format, e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' + * @returns {Number} epoch i.e. seconds since '00:00:00 1970-01-01 UTC' + */ +function parseRFC2822Date(date) { + // Date.parse accepts the IETF standard (RFC 1123 Section 5.2.14 and elsewhere) + // date syntax, which is defined in RFC 2822 (obsoletes RFC 822) + // and returns number of _milli_seconds since January 1, 1970, 00:00:00 UTC + return Date.parse(date) / 1000; +} + + +/* ............................................................ */ +/* formatting date */ + +/** + * format timezone offset as numerical timezone '(+|-)HHMM' or '(+|-)HH:MM' + * + * @param {Number} hours: offset in hours, e.g. 2 for '+0200' + * @param {Number} [minutes] offset in minutes, e.g. 30 for '-4030'; + * it is split into hours if not 0 <= minutes < 60, + * for example 1200 would give '+0100'; + * defaults to 0 + * @param {String} [sep] separator between hours and minutes part, + * default is '', might be ':' for W3CDTF (rfc-3339) + * @returns {String} timezone in '(+|-)HHMM' or '(+|-)HH:MM' format + */ +function formatTimezoneInfo(hours, minutes, sep) { + minutes = minutes || 0; // to be able to use formatTimezoneInfo(hh) + sep = sep || ''; // default format is +/-ZZZZ + + if (minutes < 0 || minutes > 59) { + hours = minutes > 0 ? Math.floor(minutes / 60) : Math.ceil(minutes / 60); + minutes = Math.abs(minutes - 60*hours); // sign of minutes is sign of hours + // NOTE: this works correctly because there is no UTC-00:30 timezone + } + + var tzSign = hours >= 0 ? '+' : '-'; + if (hours < 0) { + hours = -hours; // sign is stored in tzSign + } + + return tzSign + padLeft(hours, 2, '0') + sep + padLeft(minutes, 2, '0'); +} + +/** + * translate 'utc' and 'local' to numerical timezone + * @param {String} timezoneInfo: might be 'utc' or 'local' (browser) + */ +function normalizeTimezoneInfo(timezoneInfo) { + switch (timezoneInfo) { + case 'utc': + return '+0000'; + case 'local': // 'local' is browser timezone + return localTimezoneInfo(); + } + return timezoneInfo; +} + + +/** + * return date in local time formatted in iso-8601 like format + * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {String} date in local time in iso-8601 like format + */ +function formatDateISOLocal(epoch, timezoneInfo) { + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + timezoneOffset(timezoneInfo))); + var localDateStr = // e.g. '2005-08-07' + localDate.getUTCFullYear() + '-' + + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + + padLeft(localDate.getUTCDate(), 2, '0'); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/** + * return date in local time formatted in rfc-2822 format + * e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @param {Boolean} [padDay] e.g. 'Sun, 07 Aug' if true, 'Sun, 7 Aug' otherwise + * @returns {String} date in local time in rfc-2822 format + */ +function formatDateRFC2882(epoch, timezoneInfo, padDay) { + // A short textual representation of a month, three letters + var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + // A textual representation of a day, three letters + var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + timezoneOffset(timezoneInfo))); + var localDateStr = // e.g. 'Sun, 7 Aug 2005' or 'Sun, 07 Aug 2005' + days[localDate.getUTCDay()] + ', ' + + (padDay ? padLeft(localDate.getUTCDate(),2,'0') : localDate.getUTCDate()) + ' ' + + months[localDate.getUTCMonth()] + ' ' + + localDate.getUTCFullYear(); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/* end of datetime.js */ @@ -59,27 +59,6 @@ enum graph_state { GRAPH_COLLAPSING }; -/* - * The list of available column colors. - */ -static const char *column_colors_ansi[] = { - GIT_COLOR_RED, - GIT_COLOR_GREEN, - GIT_COLOR_YELLOW, - GIT_COLOR_BLUE, - GIT_COLOR_MAGENTA, - 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, - GIT_COLOR_RESET, -}; - -#define COLUMN_COLORS_ANSI_MAX (ARRAY_SIZE(column_colors_ansi) - 1) - static const char **column_colors; static unsigned short column_colors_max; @@ -228,7 +207,7 @@ struct git_graph *graph_init(struct rev_info *opt) if (!column_colors) graph_set_column_colors(column_colors_ansi, - COLUMN_COLORS_ANSI_MAX); + column_colors_ansi_max); graph->commit = NULL; graph->revs = opt; @@ -798,22 +777,9 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb) } /* - * If revs->left_right is set, print '<' for commits that - * come from the left side, and '>' for commits from the right - * side. - */ - if (graph->revs && graph->revs->left_right) { - if (graph->commit->object.flags & SYMMETRIC_LEFT) - strbuf_addch(sb, '<'); - else - strbuf_addch(sb, '>'); - return; - } - - /* - * Print '*' in all other cases + * get_revision_mark() handles all other cases without assert() */ - strbuf_addch(sb, '*'); + strbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit)); } /* @@ -59,6 +59,84 @@ struct grep_opt *grep_opt_dup(const struct grep_opt *opt) return ret; } +static NORETURN void compile_regexp_failed(const struct grep_pat *p, + const char *error) +{ + char where[1024]; + + if (p->no) + sprintf(where, "In '%s' at %d, ", p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + + die("%s'%s': %s", where, p->pattern, error); +} + +#ifdef USE_LIBPCRE +static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) +{ + const char *error; + int erroffset; + int options = 0; + + if (opt->ignore_case) + options |= PCRE_CASELESS; + + p->pcre_regexp = pcre_compile(p->pattern, options, &error, &erroffset, + NULL); + if (!p->pcre_regexp) + compile_regexp_failed(p, error); + + p->pcre_extra_info = pcre_study(p->pcre_regexp, 0, &error); + if (!p->pcre_extra_info && error) + die("%s", error); +} + +static int pcrematch(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + int ovector[30], ret, flags = 0; + + if (eflags & REG_NOTBOL) + flags |= PCRE_NOTBOL; + + ret = pcre_exec(p->pcre_regexp, p->pcre_extra_info, line, eol - line, + 0, flags, ovector, ARRAY_SIZE(ovector)); + if (ret < 0 && ret != PCRE_ERROR_NOMATCH) + die("pcre_exec failed with error code %d", ret); + if (ret > 0) { + ret = 0; + match->rm_so = ovector[0]; + match->rm_eo = ovector[1]; + } + + return ret; +} + +static void free_pcre_regexp(struct grep_pat *p) +{ + pcre_free(p->pcre_regexp); + pcre_free(p->pcre_extra_info); +} +#else /* !USE_LIBPCRE */ +static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) +{ + die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); +} + +static int pcrematch(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + return 1; +} + +static void free_pcre_regexp(struct grep_pat *p) +{ +} +#endif /* !USE_LIBPCRE */ + static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { int err; @@ -70,20 +148,17 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) if (p->fixed) return; + if (opt->pcre) { + compile_pcre_regexp(p, opt); + return; + } + err = regcomp(&p->regexp, p->pattern, opt->regflags); if (err) { char errbuf[1024]; - char where[1024]; - if (p->no) - sprintf(where, "In '%s' at %d, ", - p->origin, p->no); - else if (p->origin) - sprintf(where, "%s, ", p->origin); - else - where[0] = 0; regerror(err, &p->regexp, errbuf, 1024); regfree(&p->regexp); - die("%s'%s': %s", where, p->pattern, errbuf); + compile_regexp_failed(p, errbuf); } } @@ -320,7 +395,10 @@ void free_grep_patterns(struct grep_opt *opt) case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - regfree(&p->regexp); + if (p->pcre_regexp) + free_pcre_regexp(p); + else + regfree(&p->regexp); break; default: break; @@ -412,6 +490,21 @@ static int regmatch(const regex_t *preg, char *line, char *eol, return regexec(preg, line, 1, match, eflags); } +static int patmatch(struct grep_pat *p, char *line, char *eol, + regmatch_t *match, int eflags) +{ + int hit; + + if (p->fixed) + hit = !fixmatch(p, line, eol, match); + else if (p->pcre_regexp) + hit = !pcrematch(p, line, eol, match, eflags); + else + hit = !regmatch(&p->regexp, line, eol, match, eflags); + + return hit; +} + static int strip_timestamp(char *bol, char **eol_p) { char *eol = *eol_p; @@ -461,10 +554,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, } again: - if (p->fixed) - hit = !fixmatch(p, bol, eol, pmatch); - else - hit = !regmatch(&p->regexp, bol, eol, pmatch, eflags); + hit = patmatch(p, bol, eol, pmatch, eflags); if (hit && p->word_regexp) { if ((pmatch[0].rm_so < 0) || @@ -791,10 +881,7 @@ static int look_ahead(struct grep_opt *opt, int hit; regmatch_t m; - if (p->fixed) - hit = !fixmatch(p, bol, bol + *left_p, &m); - else - hit = !regmatch(&p->regexp, bol, bol + *left_p, &m, 0); + hit = patmatch(p, bol, bol + *left_p, &m, 0); if (!hit || m.rm_so < 0 || m.rm_eo < 0) continue; if (earliest < 0 || m.rm_so < earliest) @@ -891,7 +978,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, int hit; /* - * look_ahead() skips quicly to the line that possibly + * look_ahead() skips quickly to the line that possibly * has the next hit; don't call it if we need to do * something more than just skipping the current line * in response to an unmatch for the current line. E.g. @@ -1,6 +1,12 @@ #ifndef GREP_H #define GREP_H #include "color.h" +#ifdef USE_LIBPCRE +#include <pcre.h> +#else +typedef int pcre; +typedef int pcre_extra; +#endif enum grep_pat_token { GREP_PATTERN, @@ -33,6 +39,8 @@ struct grep_pat { size_t patternlen; enum grep_header_field field; regex_t regexp; + pcre *pcre_regexp; + pcre_extra *pcre_extra_info; unsigned fixed:1; unsigned ignore_case:1; unsigned word_regexp:1; @@ -83,6 +91,7 @@ struct grep_opt { #define GREP_BINARY_TEXT 2 int binary; int extended; + int pcre; int relative; int pathname; int null_following_name; @@ -81,7 +81,7 @@ void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table) return insert_hash_entry(hash, ptr, table); } -int for_each_hash(const struct hash_table *table, int (*fn)(void *)) +int for_each_hash(const struct hash_table *table, int (*fn)(void *, void *), void *data) { int sum = 0; unsigned int i; @@ -92,7 +92,7 @@ int for_each_hash(const struct hash_table *table, int (*fn)(void *)) void *ptr = array->ptr; array++; if (ptr) { - int val = fn(ptr); + int val = fn(ptr, data); if (val < 0) return val; sum += val; @@ -30,7 +30,7 @@ struct hash_table { extern void *lookup_hash(unsigned int hash, const struct hash_table *table); extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table); -extern int for_each_hash(const struct hash_table *table, int (*fn)(void *)); +extern int for_each_hash(const struct hash_table *table, int (*fn)(void *, void *), void *data); extern void free_hash(struct hash_table *table); static inline void init_hash(struct hash_table *table) diff --git a/http-fetch.c b/http-fetch.c index 923904f97f..3af4c71bd0 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -8,7 +8,6 @@ static const char http_fetch_usage[] = "git http-fetch " int main(int argc, const char **argv) { - const char *prefix; struct walker *walker; int commits_on_stdin = 0; int commits; @@ -60,7 +59,7 @@ int main(int argc, const char **argv) if (argv[arg]) str_end_url_with_slash(argv[arg], &url); - prefix = setup_git_directory(); + setup_git_directory(); git_config(git_default_config, NULL); diff --git a/http-push.c b/http-push.c index ff41a0e183..28bfe768f7 100644 --- a/http-push.c +++ b/http-push.c @@ -82,8 +82,7 @@ static int helper_status; static struct object_list *objects; -struct repo -{ +struct repo { char *url; char *path; int path_len; @@ -108,8 +107,7 @@ enum transfer_state { COMPLETE }; -struct transfer_request -{ +struct transfer_request { struct object *obj; char *url; char *dest; @@ -127,8 +125,7 @@ struct transfer_request static struct transfer_request *request_queue_head; -struct xml_ctx -{ +struct xml_ctx { char *name; int len; char *cdata; @@ -136,8 +133,7 @@ struct xml_ctx void *userData; }; -struct remote_lock -{ +struct remote_lock { char *url; char *owner; char *token; @@ -156,8 +152,7 @@ struct remote_lock /* Flags that remote_ls passes to callback functions */ #define IS_DIR (1u << 0) -struct remote_ls_ctx -{ +struct remote_ls_ctx { char *path; void (*userFunc)(struct remote_ls_ctx *ls); void *userData; @@ -174,7 +169,7 @@ enum dav_header_flag { DAV_HEADER_TIMEOUT = (1u << 2) }; -static char *xml_entities(char *s) +static char *xml_entities(const char *s) { struct strbuf buf = STRBUF_INIT; while (*s) { @@ -202,6 +197,34 @@ static char *xml_entities(char *s) return strbuf_detach(&buf, NULL); } +static void curl_setup_http_get(CURL *curl, const char *url, + const char *custom_req) +{ + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_null); +} + +static void curl_setup_http(CURL *curl, const char *url, + const char *custom_req, struct buffer *buffer, + curl_write_callback write_fn) +{ + curl_easy_setopt(curl, CURLOPT_PUT, 1); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_INFILE, buffer); + curl_easy_setopt(curl, CURLOPT_INFILESIZE, buffer->buf.len); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); + curl_easy_setopt(curl, CURLOPT_IOCTLDATA, &buffer); +#endif + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_fn); + curl_easy_setopt(curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); +} + static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options) { struct strbuf buf = STRBUF_INIT; @@ -277,11 +300,8 @@ static void start_mkcol(struct transfer_request *request) slot = get_active_slot(); slot->callback_func = process_response; slot->callback_data = request; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */ - curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); + curl_setup_http_get(slot->curl, request->url, DAV_MKCOL); curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); if (start_active_slot(slot)) { request->slot = slot; @@ -400,19 +420,8 @@ static void start_put(struct transfer_request *request) slot = get_active_slot(); slot->callback_func = process_response; slot->callback_data = request; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); - curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); + curl_setup_http(slot->curl, request->url, DAV_PUT, + &request->buffer, fwrite_null); if (start_active_slot(slot)) { request->slot = slot; @@ -432,13 +441,10 @@ static void start_move(struct transfer_request *request) slot = get_active_slot(); slot->callback_func = process_response; slot->callback_data = request; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */ - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE); + curl_setup_http_get(slot->curl, request->url, DAV_MOVE); dav_headers = curl_slist_append(dav_headers, request->dest); dav_headers = curl_slist_append(dav_headers, "Overwrite: T"); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); if (start_active_slot(slot)) { request->slot = slot; @@ -463,10 +469,7 @@ static int refresh_lock(struct remote_lock *lock) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK); + curl_setup_http_get(slot->curl, lock->url, DAV_LOCK); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); if (start_active_slot(slot)) { @@ -802,7 +805,7 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) } } -static void one_remote_ref(char *refname); +static void one_remote_ref(const char *refname); static void xml_start_tag(void *userData, const char *name, const char **atts) @@ -881,10 +884,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) ep[1] = '\0'; slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_setup_http_get(slot->curl, url, DAV_MKCOL); if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result != CURLE_OK && @@ -914,19 +914,9 @@ static struct remote_lock *lock_remote(const char *path, long timeout) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK); + curl_setup_http(slot->curl, url, DAV_LOCK, &out_buffer, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); lock = xcalloc(1, sizeof(*lock)); lock->timeout = -1; @@ -992,9 +982,7 @@ static int unlock_remote(struct remote_lock *lock) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK); + curl_setup_http_get(slot->curl, lock->url, DAV_UNLOCK); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); if (start_active_slot(slot)) { @@ -1172,19 +1160,10 @@ static void remote_ls(const char *path, int flags, slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); + curl_setup_http(slot->curl, url, DAV_PROPFIND, + &out_buffer, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); if (start_active_slot(slot)) { run_active_slot(slot); @@ -1255,19 +1234,10 @@ static int locking_available(void) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, repo->url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); + curl_setup_http(slot->curl, repo->url, DAV_PROPFIND, + &out_buffer, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); if (start_active_slot(slot)) { run_active_slot(slot); @@ -1441,19 +1411,9 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); + curl_setup_http(slot->curl, lock->url, DAV_PUT, + &out_buffer, fwrite_null); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); if (start_active_slot(slot)) { run_active_slot(slot); @@ -1476,7 +1436,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) static struct ref *remote_refs; -static void one_remote_ref(char *refname) +static void one_remote_ref(const char *refname) { struct ref *ref; struct object *obj; @@ -1577,19 +1537,9 @@ static void update_remote_info_refs(struct remote_lock *lock) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); + curl_setup_http(slot->curl, lock->url, DAV_PUT, + &buffer, fwrite_null); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); if (start_active_slot(slot)) { run_active_slot(slot); @@ -1665,7 +1615,7 @@ static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha return (merge_bases && !merge_bases->next && merge_bases->item == branch); } -static int delete_remote_branch(char *pattern, int force) +static int delete_remote_branch(const char *pattern, int force) { struct ref *refs = remote_refs; struct ref *remote_ref = NULL; @@ -1747,10 +1697,7 @@ static int delete_remote_branch(char *pattern, int force) sprintf(url, "%s%s", repo->url, remote_ref->name); slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE); + curl_setup_http_get(slot->curl, url, DAV_DELETE); if (start_active_slot(slot)) { run_active_slot(slot); free(url); diff --git a/http-walker.c b/http-walker.c index 18bd6504be..51a906e9e3 100644 --- a/http-walker.c +++ b/http-walker.c @@ -3,8 +3,7 @@ #include "walker.h" #include "http.h" -struct alt_base -{ +struct alt_base { char *base; int got_indices; struct packed_git *packs; @@ -18,8 +17,7 @@ enum object_request_state { COMPLETE }; -struct object_request -{ +struct object_request { struct walker *walker; unsigned char sha1[20]; struct alt_base *repo; @@ -187,7 +185,7 @@ static void process_alternates_response(void *callback_data) struct active_request_slot *slot = alt_req->slot; struct alt_base *tail = cdata->alt; const char *base = alt_req->base; - static const char null_byte = '\0'; + const char null_byte = '\0'; char *data; int i = 0; @@ -220,7 +218,7 @@ static void process_alternates_response(void *callback_data) } } - fwrite_buffer(&null_byte, 1, 1, alt_req->buffer); + fwrite_buffer((char *)&null_byte, 1, 1, alt_req->buffer); alt_req->buffer->len--; data = alt_req->buffer->buf; @@ -60,7 +60,7 @@ static struct curl_slist *no_pragma_header; static struct active_request_slot *active_queue_head; -size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_) +size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; struct buffer *buffer = buffer_; @@ -92,7 +92,7 @@ curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp) } #endif -size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_) +size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; struct strbuf *buffer = buffer_; @@ -102,7 +102,7 @@ size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer return size; } -size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf) +size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf) { data_received++; return eltsize * nmemb; @@ -536,6 +536,7 @@ struct active_request_slot *get_active_slot(void) curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); @@ -1166,7 +1167,7 @@ abort: } /* Helpers for fetching objects (loose) */ -static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, +static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb, void *data) { unsigned char expn[4096]; @@ -1183,7 +1184,7 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, } while (posn < size); freq->stream.avail_in = size; - freq->stream.next_in = ptr; + freq->stream.next_in = (void *)ptr; do { freq->stream.next_out = expn; freq->stream.avail_out = sizeof(expn); @@ -1202,7 +1203,7 @@ struct http_object_request *new_http_object_request(const char *base_url, char *filename; char prevfile[PATH_MAX]; int prevlocal; - unsigned char prev_buf[PREV_BUF_SIZE]; + char prev_buf[PREV_BUF_SIZE]; ssize_t prev_read = 0; long prev_posn = 0; char range[RANGE_HEADER_SIZE]; @@ -42,14 +42,12 @@ #define NO_CURL_IOCTL #endif -struct slot_results -{ +struct slot_results { CURLcode curl_result; long http_code; }; -struct active_request_slot -{ +struct active_request_slot { CURL *curl; FILE *local; int in_use; @@ -62,16 +60,15 @@ struct active_request_slot struct active_request_slot *next; }; -struct buffer -{ +struct buffer { struct strbuf buf; size_t posn; }; /* Curl request read/write callbacks */ -extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *strbuf); -extern size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf); -extern size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf); +extern size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf); +extern size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf); +extern size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf); #ifndef NO_CURL_IOCTL extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp); #endif @@ -149,8 +146,7 @@ extern int http_fetch_ref(const char *base, struct ref *ref); extern int http_get_info_packs(const char *base_url, struct packed_git **packs_head); -struct http_pack_request -{ +struct http_pack_request { char *url; struct packed_git *target; struct packed_git **lst; @@ -166,8 +162,7 @@ extern int finish_http_pack_request(struct http_pack_request *preq); extern void release_http_pack_request(struct http_pack_request *preq); /* Helpers for fetching object */ -struct http_object_request -{ +struct http_object_request { char *url; char tmpfile[PATH_MAX]; int localfile; @@ -9,6 +9,12 @@ static char git_default_date[50]; +#ifdef NO_GECOS_IN_PWENT +#define get_gecos(ignored) "&" +#else +#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos) +#endif + static void copy_gecos(const struct passwd *w, char *name, size_t sz) { char *src, *dst; @@ -20,7 +26,7 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz) * with commas. Also & stands for capitalized form of the login name. */ - for (len = 0, dst = name, src = w->pw_gecos; len < sz; src++) { + for (len = 0, dst = name, src = get_gecos(w); len < sz; src++) { int ch = *src; if (ch != '&') { *dst++ = ch; @@ -34,6 +40,7 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz) *dst++ = toupper(*w->pw_name); memcpy(dst, w->pw_name + 1, nlen - 1); dst += nlen - 1; + len += nlen; } } if (len < sz) diff --git a/imap-send.c b/imap-send.c index 71506a8dd3..e1ad1a48ce 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1069,7 +1069,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) if (srvc->tunnel) { const char *argv[] = { srvc->tunnel, NULL }; - struct child_process tunnel = {0}; + struct child_process tunnel = {NULL}; imap_info("Starting tunnel '%s'... ", srvc->tunnel); @@ -1193,13 +1193,13 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) if (!preauth) { #ifndef NO_OPENSSL if (!srvc->use_ssl && CAP(STARTTLS)) { - if (imap_exec(ctx, 0, "STARTTLS") != RESP_OK) + if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK) goto bail; if (ssl_socket_connect(&imap->buf.sock, 1, srvc->ssl_verify)) goto bail; /* capabilities may have changed, so get the new capabilities */ - if (imap_exec(ctx, 0, "CAPABILITY") != RESP_OK) + if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK) goto bail; } #endif diff --git a/list-objects.c b/list-objects.c index 8953548c07..0fb44e7ed7 100644 --- a/list-objects.c +++ b/list-objects.c @@ -61,12 +61,15 @@ static void process_tree(struct rev_info *revs, struct tree *tree, show_object_fn show, struct name_path *path, + struct strbuf *base, const char *name) { struct object *obj = &tree->object; struct tree_desc desc; struct name_entry entry; struct name_path me; + int match = revs->diffopt.pathspec.nr == 0 ? 2 : 0; + int baselen = base->len; if (!revs->tree_objects) return; @@ -82,13 +85,28 @@ static void process_tree(struct rev_info *revs, me.elem = name; me.elem_len = strlen(name); + if (!match) { + strbuf_addstr(base, name); + if (base->len) + strbuf_addch(base, '/'); + } + init_tree_desc(&desc, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { + if (match != 2) { + match = tree_entry_interesting(&entry, base, 0, + &revs->diffopt.pathspec); + if (match < 0) + break; + if (match == 0) + continue; + } + if (S_ISDIR(entry.mode)) process_tree(revs, lookup_tree(entry.sha1), - show, &me, entry.path); + show, &me, base, entry.path); else if (S_ISGITLINK(entry.mode)) process_gitlink(revs, entry.sha1, show, &me, entry.path); @@ -97,6 +115,7 @@ static void process_tree(struct rev_info *revs, lookup_blob(entry.sha1), show, &me, entry.path); } + strbuf_setlen(base, baselen); free(tree->buffer); tree->buffer = NULL; } @@ -146,9 +165,16 @@ void traverse_commit_list(struct rev_info *revs, { int i; struct commit *commit; + struct strbuf base; + strbuf_init(&base, PATH_MAX); while ((commit = get_revision(revs)) != NULL) { - add_pending_tree(revs, commit->tree); + /* + * an uninteresting boundary commit may not have its tree + * parsed yet, but we are not going to show them anyway + */ + if (commit->tree) + add_pending_tree(revs, commit->tree); show_commit(commit, data); } for (i = 0; i < revs->pending.nr; i++) { @@ -164,7 +190,7 @@ void traverse_commit_list(struct rev_info *revs, } if (obj->type == OBJ_TREE) { process_tree(revs, (struct tree *)obj, show_object, - NULL, name); + NULL, &base, name); continue; } if (obj->type == OBJ_BLOB) { @@ -181,4 +207,5 @@ void traverse_commit_list(struct rev_info *revs, revs->pending.alloc = 0; revs->pending.objects = NULL; } + strbuf_release(&base); } diff --git a/lockfile.c b/lockfile.c index b0d74cddde..c6fb77b26f 100644 --- a/lockfile.c +++ b/lockfile.c @@ -164,10 +164,10 @@ static char *unable_to_lock_message(const char *path, int err) "If no other git process is currently running, this probably means a\n" "git process crashed in this repository earlier. Make sure no other git\n" "process is running and remove the file manually to continue.", - make_nonrelative_path(path), strerror(err)); + absolute_path(path), strerror(err)); } else strbuf_addf(&buf, "Unable to create '%s.lock': %s", - make_nonrelative_path(path), strerror(err)); + absolute_path(path), strerror(err)); return strbuf_detach(&buf, NULL); } diff --git a/log-tree.c b/log-tree.c index 0c41789356..e9457019d5 100644 --- a/log-tree.c +++ b/log-tree.c @@ -294,8 +294,9 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, if (opt->total > 0) { static char buffer[64]; snprintf(buffer, sizeof(buffer), - "Subject: [%s %0*d/%d] ", + "Subject: [%s%s%0*d/%d] ", opt->subject_prefix, + *opt->subject_prefix ? " " : "", digits_in_number(opt->total), opt->nr, opt->total); subject = buffer; @@ -380,18 +381,8 @@ void show_log(struct rev_info *opt) if (!opt->verbose_header) { graph_show_commit(opt->graph); - if (!opt->graph) { - if (commit->object.flags & BOUNDARY) - putchar('-'); - else if (commit->object.flags & UNINTERESTING) - putchar('^'); - else if (opt->left_right) { - if (commit->object.flags & SYMMETRIC_LEFT) - putchar('<'); - else - putchar('>'); - } - } + if (!opt->graph) + put_revision_mark(opt, commit); fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->print_parents) show_parents(commit, abbrev_commit); @@ -448,18 +439,8 @@ void show_log(struct rev_info *opt) if (opt->commit_format != CMIT_FMT_ONELINE) fputs("commit ", stdout); - if (!opt->graph) { - if (commit->object.flags & BOUNDARY) - putchar('-'); - else if (commit->object.flags & UNINTERESTING) - putchar('^'); - else if (opt->left_right) { - if (commit->object.flags & SYMMETRIC_LEFT) - putchar('<'); - else - putchar('>'); - } - } + if (!opt->graph) + put_revision_mark(opt, commit); fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->print_parents) diff --git a/merge-file.c b/merge-file.c index f7f4533926..7845528e88 100644 --- a/merge-file.c +++ b/merge-file.c @@ -3,6 +3,7 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "blob.h" +#include "merge-file.h" static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { diff --git a/merge-file.h b/merge-file.h new file mode 100644 index 0000000000..9b3b83ac6c --- /dev/null +++ b/merge-file.h @@ -0,0 +1,7 @@ +#ifndef MERGE_FILE_H +#define MERGE_FILE_H + +extern void *merge_file(const char *path, struct blob *base, struct blob *our, + struct blob *their, unsigned long *size); + +#endif diff --git a/merge-recursive.c b/merge-recursive.c index 16c2dbeab9..db9ba19ddf 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -83,10 +83,8 @@ struct rename_df_conflict_info { * Since we want to write the index eventually, we cannot reuse the index * for these (temporary) data. */ -struct stage_data -{ - struct - { +struct stage_data { + struct { unsigned mode; unsigned char sha[20]; } stages[4]; @@ -137,7 +135,6 @@ static void flush_output(struct merge_options *o) __attribute__((format (printf, 3, 4))) static void output(struct merge_options *o, int v, const char *fmt, ...) { - int len; va_list ap; if (!show(o, v)) @@ -148,21 +145,9 @@ static void output(struct merge_options *o, int v, const char *fmt, ...) strbuf_setlen(&o->obuf, o->obuf.len + o->call_depth * 2); va_start(ap, fmt); - len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap); + strbuf_vaddf(&o->obuf, fmt, ap); va_end(ap); - if (len < 0) - len = 0; - if (len >= strbuf_avail(&o->obuf)) { - strbuf_grow(&o->obuf, len + 2); - va_start(ap, fmt); - len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap); - va_end(ap); - if (len >= strbuf_avail(&o->obuf)) { - die("this should not happen, your snprintf is broken"); - } - } - strbuf_setlen(&o->obuf, o->obuf.len + len); strbuf_add(&o->obuf, "\n", 1); if (!o->buffer_output) flush_output(o); @@ -288,7 +273,9 @@ static int save_files_dirs(const unsigned char *sha1, static int get_files_dirs(struct merge_options *o, struct tree *tree) { int n; - if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, o)) + struct pathspec match_all; + init_pathspec(&match_all, NULL); + if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o)) return 0; n = o->current_file_set.nr + o->current_directory_set.nr; return n; @@ -366,7 +353,6 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o, */ const char *last_file = NULL; int last_len = 0; - struct stage_data *last_e; int i; for (i = 0; i < entries->nr; i++) { @@ -396,15 +382,13 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o, if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) { last_file = path; last_len = len; - last_e = e; } else { last_file = NULL; } } } -struct rename -{ +struct rename { struct diff_filepair *pair; struct stage_data *src_entry; struct stage_data *dst_entry; @@ -434,14 +418,16 @@ static struct string_list *get_renames(struct merge_options *o, opts.detect_rename = DIFF_DETECT_RENAME; opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit : o->diff_rename_limit >= 0 ? o->diff_rename_limit : - 500; + 1000; opts.rename_score = o->rename_score; - opts.warn_on_too_large_rename = 1; + opts.show_rename_progress = o->show_rename_progress; opts.output_format = DIFF_FORMAT_NO_OUTPUT; if (diff_setup_done(&opts) < 0) die("diff setup failed"); diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); diffcore_std(&opts); + if (opts.needed_rename_limit > o->needed_rename_limit) + o->needed_rename_limit = opts.needed_rename_limit; for (i = 0; i < diff_queued_diff.nr; ++i) { struct string_list_item *item; struct rename *re; @@ -717,8 +703,7 @@ static void update_file(struct merge_options *o, /* Low level file merging, update and removal */ -struct merge_file_info -{ +struct merge_file_info { unsigned char sha[20]; unsigned mode; unsigned clean:1, @@ -971,7 +956,6 @@ static int process_renames(struct merge_options *o, } for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { - char *src; struct string_list *renames1, *renames2Dst; struct rename *ren1 = NULL, *ren2 = NULL; const char *branch1, *branch2; @@ -1006,7 +990,6 @@ static int process_renames(struct merge_options *o, ren2 = ren1; ren1 = tmp; } - src = ren1->pair->one->path; ren1->dst_entry->processed = 1; ren1->src_entry->processed = 1; @@ -1666,6 +1649,9 @@ int merge_recursive(struct merge_options *o, commit_list_insert(h2, &(*result)->parents->next); } flush_output(o); + if (show(o, 2)) + diff_warn_rename_limit("merge.renamelimit", + o->needed_rename_limit, 0); return clean; } @@ -1723,15 +1709,15 @@ int merge_recursive_generic(struct merge_options *o, static int merge_recursive_config(const char *var, const char *value, void *cb) { struct merge_options *o = cb; - if (!strcasecmp(var, "merge.verbosity")) { + if (!strcmp(var, "merge.verbosity")) { o->verbosity = git_config_int(var, value); return 0; } - if (!strcasecmp(var, "diff.renamelimit")) { + if (!strcmp(var, "diff.renamelimit")) { o->diff_rename_limit = git_config_int(var, value); return 0; } - if (!strcasecmp(var, "merge.renamelimit")) { + if (!strcmp(var, "merge.renamelimit")) { o->merge_rename_limit = git_config_int(var, value); return 0; } diff --git a/merge-recursive.h b/merge-recursive.h index c8135b0ec7..7e1e972b13 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -20,6 +20,8 @@ struct merge_options { int diff_rename_limit; int merge_rename_limit; int rename_score; + int needed_rename_limit; + int show_rename_progress; int call_depth; struct strbuf obuf; struct string_list current_file_set; @@ -57,6 +59,8 @@ struct tree *write_tree_from_memory(struct merge_options *o); int parse_merge_opt(struct merge_options *out, const char *s); /* builtin/merge.c */ -int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes); +int try_merge_command(const char *strategy, size_t xopts_nr, + const char **xopts, struct commit_list *common, + const char *head_arg, struct commit_list *remotes); #endif diff --git a/notes-merge.c b/notes-merge.c index 71c4d45fcd..e1aaf43b43 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -359,7 +359,7 @@ static int ll_merge_in_worktree(struct notes_merge_options *o, read_mmblob(&remote, p->remote); status = ll_merge(&result_buf, sha1_to_hex(p->obj), &base, NULL, - &local, o->local_ref, &remote, o->remote_ref, 0); + &local, o->local_ref, &remote, o->remote_ref, NULL); free(base.ptr); free(local.ptr); @@ -615,7 +615,7 @@ int notes_merge(struct notes_merge_options *o, bases = get_merge_bases(local, remote, 1); if (!bases) { base_sha1 = null_sha1; - base_tree_sha1 = (unsigned char *)EMPTY_TREE_SHA1_BIN; + base_tree_sha1 = EMPTY_TREE_SHA1_BIN; OUTPUT(o, 4, "No merge base found; doing history-less merge"); } else if (!bases->next) { base_sha1 = bases->item->object.sha1; @@ -707,7 +707,7 @@ int notes_merge_commit(struct notes_merge_options *o, /* write file as blob, and add to partial_tree */ if (stat(ent->name, &st)) die_errno("Failed to stat '%s'", ent->name); - if (index_path(blob_sha1, ent->name, &st, 1)) + if (index_path(blob_sha1, ent->name, &st, HASH_WRITE_OBJECT)) die("Failed to write blob object from '%s'", ent->name); if (add_note(partial_tree, obj_sha1, blob_sha1, NULL)) die("Failed to add resolved note '%s' to notes tree", @@ -1053,7 +1053,8 @@ void init_display_notes(struct display_notes_opt *opt) assert(!display_notes_trees); - if (!opt || !opt->suppress_default_notes) { + if (!opt || opt->use_default_notes > 0 || + (opt->use_default_notes == -1 && !opt->extra_notes_refs.nr)) { string_list_append(&display_notes_refs, default_notes_ref()); display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT); if (display_ref_env) { @@ -1066,9 +1067,9 @@ void init_display_notes(struct display_notes_opt *opt) git_config(notes_display_config, &load_config_refs); - if (opt && opt->extra_notes_refs) { + if (opt) { struct string_list_item *item; - for_each_string_list_item(item, opt->extra_notes_refs) + for_each_string_list_item(item, &opt->extra_notes_refs) string_list_add_refs_by_glob(&display_notes_refs, item->string); } @@ -1285,3 +1286,13 @@ int copy_note(struct notes_tree *t, return 0; } + +void expand_notes_ref(struct strbuf *sb) +{ + if (!prefixcmp(sb->buf, "refs/notes/")) + return; /* we're happy */ + else if (!prefixcmp(sb->buf, "notes/")) + strbuf_insert(sb, 0, "refs/", 5); + else + strbuf_insert(sb, 0, "refs/notes/", 11); +} @@ -1,6 +1,8 @@ #ifndef NOTES_H #define NOTES_H +#include "string-list.h" + /* * Function type for combining two notes annotating the same object. * @@ -256,8 +258,8 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1, struct string_list; struct display_notes_opt { - unsigned int suppress_default_notes:1; - struct string_list *extra_notes_refs; + int use_default_notes; + struct string_list extra_notes_refs; }; /* @@ -307,4 +309,7 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob); void string_list_add_refs_from_colon_sep(struct string_list *list, const char *globs); +/* Expand inplace a note ref like "foo" or "notes/foo" into "refs/notes/foo" */ +void expand_notes_ref(struct strbuf *sb); + #endif @@ -188,8 +188,8 @@ struct object *parse_object(const unsigned char *sha1) unsigned long size; enum object_type type; int eaten; - const unsigned char *repl; - void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); + const unsigned char *repl = lookup_replace_object(sha1); + void *buffer = read_sha1_file(sha1, &type, &size); if (buffer) { struct object *obj; @@ -6,11 +6,6 @@ struct object_list { struct object_list *next; }; -struct object_refs { - unsigned count; - struct object *ref[FLEX_ARRAY]; /* more */ -}; - struct object_array { unsigned int nr; unsigned int alloc; diff --git a/pack-check.c b/pack-check.c index 9d0cb9a114..a1a521648d 100644 --- a/pack-check.c +++ b/pack-check.c @@ -2,8 +2,7 @@ #include "pack.h" #include "pack-revindex.h" -struct idx_entry -{ +struct idx_entry { off_t offset; const unsigned char *sha1; unsigned int nr; @@ -24,7 +23,7 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr) { const uint32_t *index_crc; - uint32_t data_crc = crc32(0, Z_NULL, 0); + uint32_t data_crc = crc32(0, NULL, 0); do { unsigned int avail; diff --git a/parse-options.c b/parse-options.c index 42b51ef145..73bd28ad90 100644 --- a/parse-options.c +++ b/parse-options.c @@ -561,14 +561,14 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, return PARSE_OPT_HELP; } -void usage_with_options(const char * const *usagestr, +void NORETURN usage_with_options(const char * const *usagestr, const struct option *opts) { usage_with_options_internal(NULL, usagestr, opts, 0, 1); exit(129); } -void usage_msg_opt(const char *msg, +void NORETURN usage_msg_opt(const char *msg, const char * const *usagestr, const struct option *options) { diff --git a/parse-options.h b/parse-options.h index 31ec5d2476..d1b12fe979 100644 --- a/parse-options.h +++ b/parse-options.h @@ -141,7 +141,7 @@ struct option { { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \ PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } #define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \ - "FILE", (h) } + "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" } diff --git a/patch-delta.c b/patch-delta.c index d218faa02b..56e0a5ede2 100644 --- a/patch-delta.c +++ b/patch-delta.c @@ -48,7 +48,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size, if (cmd & 0x20) cp_size |= (*data++ << 8); if (cmd & 0x40) cp_size |= (*data++ << 16); if (cp_size == 0) cp_size = 0x10000; - if (cp_off + cp_size < cp_size || + if (unsigned_add_overflows(cp_off, cp_size) || cp_off + cp_size > src_size || cp_size > size) break; @@ -397,7 +397,7 @@ int set_shared_perm(const char *path, int mode) return 0; } -const char *make_relative_path(const char *abs, const char *base) +const char *relative_path(const char *abs, const char *base) { static char buf[PATH_MAX + 1]; int i = 0, j = 0; diff --git a/perl/Git.pm b/perl/Git.pm index 205e48aa3a..a86ab709c2 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -99,7 +99,7 @@ increase notwithstanding). use Carp qw(carp croak); # but croak is bad - throw instead use Error qw(:try); -use Cwd qw(abs_path); +use Cwd qw(abs_path cwd); use IPC::Open2 qw(open2); use Fcntl qw(SEEK_SET SEEK_CUR); } @@ -396,7 +396,16 @@ See C<command_close_bidi_pipe()> for details. sub command_bidi_pipe { my ($pid, $in, $out); + my ($self) = _maybe_self(@_); + local %ENV = %ENV; + my $cwd_save = undef; + if ($self) { + shift; + $cwd_save = cwd(); + _setup_git_cmd_env($self); + } $pid = open2($in, $out, 'git', @_); + chdir($cwd_save) if $cwd_save; return ($pid, $in, $out, join(' ', @_)); } @@ -843,7 +852,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 --no-filters)); + $self->command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters)); } sub _close_hash_and_insert_object { @@ -932,7 +941,7 @@ sub _open_cat_blob_if_needed { ($self->{cat_blob_pid}, $self->{cat_blob_in}, $self->{cat_blob_out}, $self->{cat_blob_ctx}) = - command_bidi_pipe(qw(cat-file --batch)); + $self->command_bidi_pipe(qw(cat-file --batch)); } sub _close_cat_blob { @@ -1279,6 +1288,14 @@ sub _command_common_pipe { # for the given repository and execute the git command. sub _cmd_exec { my ($self, @args) = @_; + _setup_git_cmd_env($self); + _execv_git_cmd(@args); + die qq[exec "@args" failed: $!]; +} + +# set up the appropriate state for git command +sub _setup_git_cmd_env { + my $self = shift; if ($self) { $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); $self->repo_path() and $self->wc_path() @@ -1286,8 +1303,6 @@ sub _cmd_exec { $self->wc_path() and chdir($self->wc_path()); $self->wc_subdir() and chdir($self->wc_subdir()); } - _execv_git_cmd(@args); - die qq[exec "@args" failed: $!]; } # Execute the given Git command ($_[0]) with arguments ($_[1..]) diff --git a/pkt-line.c b/pkt-line.c index 295ba2b16c..5a04984ea3 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -1,6 +1,51 @@ #include "cache.h" #include "pkt-line.h" +static const char *packet_trace_prefix = "git"; +static const char trace_key[] = "GIT_TRACE_PACKET"; + +void packet_trace_identity(const char *prog) +{ + packet_trace_prefix = xstrdup(prog); +} + +static void packet_trace(const char *buf, unsigned int len, int write) +{ + int i; + struct strbuf out; + + if (!trace_want(trace_key)) + return; + + /* +32 is just a guess for header + quoting */ + strbuf_init(&out, len+32); + + strbuf_addf(&out, "packet: %12s%c ", + packet_trace_prefix, write ? '>' : '<'); + + if ((len >= 4 && !prefixcmp(buf, "PACK")) || + (len >= 5 && !prefixcmp(buf+1, "PACK"))) { + strbuf_addstr(&out, "PACK ..."); + unsetenv(trace_key); + } + else { + /* XXX we should really handle printable utf8 */ + for (i = 0; i < len; i++) { + /* suppress newlines */ + if (buf[i] == '\n') + continue; + if (buf[i] >= 0x20 && buf[i] <= 0x7e) + strbuf_addch(&out, buf[i]); + else + strbuf_addf(&out, "\\%o", buf[i]); + } + } + + strbuf_addch(&out, '\n'); + trace_strbuf(trace_key, &out); + strbuf_release(&out); +} + /* * Write a packetized stream, where each line is preceded by * its length (including the header) as a 4-byte hex number. @@ -39,11 +84,13 @@ ssize_t safe_write(int fd, const void *buf, ssize_t n) */ void packet_flush(int fd) { + packet_trace("0000", 4, 1); safe_write(fd, "0000", 4); } void packet_buf_flush(struct strbuf *buf) { + packet_trace("0000", 4, 1); strbuf_add(buf, "0000", 4); } @@ -62,6 +109,7 @@ static unsigned format_packet(const char *fmt, va_list args) buffer[1] = hex(n >> 8); buffer[2] = hex(n >> 4); buffer[3] = hex(n); + packet_trace(buffer+4, n-4, 1); return n; } @@ -130,13 +178,16 @@ int packet_read_line(int fd, char *buffer, unsigned size) len = packet_length(linelen); if (len < 0) die("protocol error: bad line length character: %.4s", linelen); - if (!len) + if (!len) { + packet_trace("0000", 4, 0); return 0; + } len -= 4; if (len >= size) die("protocol error: bad line length %d", len); safe_read(fd, buffer, len); buffer[len] = 0; + packet_trace(buffer, len, 0); return len; } @@ -153,6 +204,7 @@ int packet_get_line(struct strbuf *out, if (!len) { *src_buf += 4; *src_len -= 4; + packet_trace("0000", 4, 0); return 0; } if (*src_len < len) @@ -165,5 +217,6 @@ int packet_get_line(struct strbuf *out, strbuf_add(out, *src_buf, len); *src_buf += len; *src_len -= len; + packet_trace(out->buf, out->len, 0); return len; } diff --git a/po/.gitignore b/po/.gitignore new file mode 100644 index 0000000000..a242a86e93 --- /dev/null +++ b/po/.gitignore @@ -0,0 +1 @@ +/git.pot diff --git a/preload-index.c b/preload-index.c index e3d0bda31a..49cb08df96 100644 --- a/preload-index.c +++ b/preload-index.c @@ -35,7 +35,9 @@ static void *preload_thread(void *_data) struct index_state *index = p->index; struct cache_entry **cep = index->cache + p->offset; struct cache_def cache; + struct pathspec pathspec; + init_pathspec(&pathspec, p->pathspec); memset(&cache, 0, sizeof(cache)); nr = p->nr; if (nr + p->offset > index->cache_nr) @@ -51,7 +53,7 @@ static void *preload_thread(void *_data) continue; if (ce_uptodate(ce)) continue; - if (!ce_path_match(ce, p->pathspec)) + if (!ce_path_match(ce, &pathspec)) continue; if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce))) continue; @@ -61,6 +63,7 @@ static void *preload_thread(void *_data) continue; ce_mark_uptodate(ce); } while (--nr > 0); + free_pathspec(&pathspec); return NULL; } @@ -208,6 +208,58 @@ int has_non_ascii(const char *s) return 0; } +static int is_rfc822_special(char ch) +{ + switch (ch) { + case '(': + case ')': + case '<': + case '>': + case '[': + case ']': + case ':': + case ';': + case '@': + case ',': + case '.': + case '"': + case '\\': + return 1; + default: + return 0; + } +} + +static int has_rfc822_specials(const char *s, int len) +{ + int i; + for (i = 0; i < len; i++) + if (is_rfc822_special(s[i])) + return 1; + return 0; +} + +static void add_rfc822_quoted(struct strbuf *out, const char *s, int len) +{ + int i; + + /* just a guess, we may have to also backslash-quote */ + strbuf_grow(out, len + 2); + + strbuf_addch(out, '"'); + for (i = 0; i < len; i++) { + switch (s[i]) { + case '"': + case '\\': + strbuf_addch(out, '\\'); + /* fall through */ + default: + strbuf_addch(out, s[i]); + } + } + strbuf_addch(out, '"'); +} + static int is_rfc2047_special(char ch) { return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_')); @@ -294,7 +346,14 @@ void pp_user_info(const struct pretty_print_context *pp, name_tail--; display_name_length = name_tail - line; strbuf_addstr(sb, "From: "); - add_rfc2047(sb, line, display_name_length, encoding); + if (!has_rfc822_specials(line, display_name_length)) { + add_rfc2047(sb, line, display_name_length, encoding); + } else { + struct strbuf quoted = STRBUF_INIT; + add_rfc822_quoted("ed, line, display_name_length); + add_rfc2047(sb, quoted.buf, quoted.len, encoding); + strbuf_release("ed); + } for (final_line = 0; final_line < sb->len; final_line++) if (sb->buf[sb->len - final_line - 1] == '\n') break; @@ -885,11 +944,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, c->abbrev_parent_hashes.off; return 1; case 'm': /* left/right/bottom */ - strbuf_addch(sb, (commit->object.flags & BOUNDARY) - ? '-' - : (commit->object.flags & SYMMETRIC_LEFT) - ? '<' - : '>'); + strbuf_addstr(sb, get_revision_mark(NULL, commit)); return 1; case 'd': format_decoration(sb, commit); @@ -1029,7 +1084,7 @@ void userformat_find_requirements(const char *fmt, struct userformat_want *w) return; fmt = user_format; } - strbuf_expand(&dummy, user_format, userformat_want_item, w); + strbuf_expand(&dummy, fmt, userformat_want_item, w); strbuf_release(&dummy); } @@ -1,8 +1,7 @@ #ifndef QUOTE_H #define QUOTE_H -#include <stddef.h> -#include <stdio.h> +struct strbuf; /* Help to copy the thing properly quoted for the shell safety. * any single quote is replaced with '\'', any exclamation point diff --git a/reachable.c b/reachable.c index a03fabf060..3fc6b1d320 100644 --- a/reachable.c +++ b/reachable.c @@ -70,16 +70,11 @@ static void process_tree(struct tree *tree, static void process_tag(struct tag *tag, struct object_array *p, const char *name) { struct object *obj = &tag->object; - struct name_path me; if (obj->flags & SEEN) return; obj->flags |= SEEN; - me.up = NULL; - me.elem = "tag:/"; - me.elem_len = 5; - if (parse_tag(tag) < 0) die("bad tag object %s", sha1_to_hex(obj->sha1)); if (tag->tagged) diff --git a/read-cache.c b/read-cache.c index 4f2e890b01..4ac9a037f4 100644 --- a/read-cache.c +++ b/read-cache.c @@ -92,7 +92,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st) if (fd >= 0) { unsigned char sha1[20]; - if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name)) + if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name, 0)) match = hashcmp(sha1, ce->sha1); /* index_fd() closed the file descriptor already */ } @@ -641,7 +641,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, return 0; } if (!intent_only) { - if (index_path(ce->sha1, path, st, 1)) + if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT)) return error("unable to index file %s", path); } else record_intent_to_add(ce); @@ -706,30 +706,9 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b) return ce_namelen(b) == len && !memcmp(a->name, b->name, len); } -int ce_path_match(const struct cache_entry *ce, const char **pathspec) +int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec) { - const char *match, *name; - int len; - - if (!pathspec) - return 1; - - len = ce_namelen(ce); - name = ce->name; - while ((match = *pathspec++) != NULL) { - int matchlen = strlen(match); - if (matchlen > len) - continue; - if (memcmp(name, match, matchlen)) - continue; - if (matchlen && name[matchlen-1] == '/') - return 1; - if (name[matchlen] == '/' || !name[matchlen]) - return 1; - if (!matchlen) - return 1; - } - return 0; + return match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL); } /* @@ -1104,7 +1083,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, } static void show_file(const char * fmt, const char * name, int in_porcelain, - int * first, char *header_msg) + int * first, const char *header_msg) { if (in_porcelain && *first && header_msg) { printf("%s\n", header_msg); @@ -1114,7 +1093,7 @@ static void show_file(const char * fmt, const char * name, int in_porcelain, } int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec, - char *seen, char *header_msg) + char *seen, const char *header_msg) { int i; int has_errors = 0; @@ -1568,6 +1547,31 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce) return result; } +static int has_racy_timestamp(struct index_state *istate) +{ + int entries = istate->cache_nr; + int i; + + for (i = 0; i < entries; i++) { + struct cache_entry *ce = istate->cache[i]; + if (is_racy_timestamp(istate, ce)) + return 1; + } + return 0; +} + +/* + * Opportunisticly update the index but do not complain if we can't + */ +void update_index_if_able(struct index_state *istate, struct lock_file *lockfile) +{ + if ((istate->cache_changed || has_racy_timestamp(istate)) && + !write_index(istate, lockfile->fd)) + commit_locked_index(lockfile); + else + rollback_lock_file(lockfile); +} + int write_index(struct index_state *istate, int newfd) { git_SHA_CTX c; diff --git a/remote-curl.c b/remote-curl.c index 04d4813e41..17d8a9b377 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -347,7 +347,7 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) } #endif -static size_t rpc_in(const void *ptr, size_t eltsize, +static size_t rpc_in(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -356,14 +356,59 @@ static size_t rpc_in(const void *ptr, size_t eltsize, return size; } +static int run_slot(struct active_request_slot *slot) +{ + int err = 0; + struct slot_results results; + + slot->results = &results; + slot->curl_result = curl_easy_perform(slot->curl); + finish_active_slot(slot); + + if (results.curl_result != CURLE_OK) { + err |= error("RPC failed; result=%d, HTTP code = %ld", + results.curl_result, results.http_code); + } + + return err; +} + +static int probe_rpc(struct rpc_state *rpc) +{ + struct active_request_slot *slot; + struct curl_slist *headers = NULL; + struct strbuf buf = STRBUF_INIT; + int err; + + slot = get_active_slot(); + + headers = curl_slist_append(headers, rpc->hdr_content_type); + headers = curl_slist_append(headers, rpc->hdr_accept); + + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_POST, 1); + curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, ""); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, "0000"); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, 4); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf); + + err = run_slot(slot); + + curl_slist_free_all(headers); + strbuf_release(&buf); + return err; +} + static int post_rpc(struct rpc_state *rpc) { struct active_request_slot *slot; - struct slot_results results; struct curl_slist *headers = NULL; int use_gzip = rpc->gzip_request; char *gzip_body = NULL; - int err = 0, large_request = 0; + int err, large_request = 0; /* Try to load the entire request, if we can fit it into the * allocated buffer space we can use HTTP/1.0 and avoid the @@ -386,8 +431,13 @@ static int post_rpc(struct rpc_state *rpc) rpc->len += n; } + if (large_request) { + err = probe_rpc(rpc); + if (err) + return err; + } + slot = get_active_slot(); - slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); curl_easy_setopt(slot->curl, CURLOPT_POST, 1); @@ -396,12 +446,12 @@ static int post_rpc(struct rpc_state *rpc) headers = curl_slist_append(headers, rpc->hdr_content_type); headers = curl_slist_append(headers, rpc->hdr_accept); + headers = curl_slist_append(headers, "Expect:"); if (large_request) { /* The request body is large and the size cannot be predicted. * We must use chunked encoding to send it. */ - headers = curl_slist_append(headers, "Expect: 100-continue"); headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); rpc->initial_buffer = 1; curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out); @@ -475,13 +525,7 @@ static int post_rpc(struct rpc_state *rpc) curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in); curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc); - slot->curl_result = curl_easy_perform(slot->curl); - finish_active_slot(slot); - - if (results.curl_result != CURLE_OK) { - err |= error("RPC failed; result=%d, HTTP code = %ld", - results.curl_result, results.http_code); - } + err = run_slot(slot); curl_slist_free_all(headers); free(gzip_body); diff --git a/replace_object.c b/replace_object.c index eb59604fd3..d0b1548726 100644 --- a/replace_object.c +++ b/replace_object.c @@ -1,6 +1,7 @@ #include "cache.h" #include "sha1-lookup.h" #include "refs.h" +#include "commit.h" static struct replace_object { unsigned char sha1[2][20]; @@ -84,12 +85,14 @@ static void prepare_replace_object(void) for_each_replace_ref(register_replace_ref, NULL); replace_object_prepared = 1; + if (!replace_object_nr) + read_replace_refs = 0; } /* We allow "recursive" replacement. Only within reason, though */ #define MAXREPLACEDEPTH 5 -const unsigned char *lookup_replace_object(const unsigned char *sha1) +const unsigned char *do_lookup_replace_object(const unsigned char *sha1) { int pos, depth = MAXREPLACEDEPTH; const unsigned char *cur = sha1; @@ -7,6 +7,11 @@ #include "ll-merge.h" #include "attr.h" +#define RESOLVED 0 +#define PUNTED 1 +#define THREE_STAGED 2 +void *RERERE_RESOLVED = &RERERE_RESOLVED; + /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */ static int rerere_enabled = -1; @@ -42,8 +47,14 @@ static void read_rr(struct string_list *rr) name = xstrdup(buf); if (fgetc(in) != '\t') die("corrupt MERGE_RR"); - for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++) - ; /* do nothing */ + for (i = 0; i < sizeof(buf); i++) { + int c = fgetc(in); + if (c < 0) + die("corrupt MERGE_RR"); + buf[i] = c; + if (c == 0) + break; + } if (i == sizeof(buf)) die("filename too long"); string_list_insert(rr, buf)->util = name; @@ -345,21 +356,74 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu return hunk_no; } -static int find_conflict(struct string_list *conflict) +static int check_one_conflict(int i, int *type) { - int i; - if (read_cache() < 0) - return error("Could not read index"); - for (i = 0; i+1 < active_nr; i++) { + struct cache_entry *e = active_cache[i]; + + if (!ce_stage(e)) { + *type = RESOLVED; + return i + 1; + } + + *type = PUNTED; + if (ce_stage(e) == 1) { + if (active_nr <= ++i) + return i + 1; + } + + /* Only handle regular files with both stages #2 and #3 */ + if (i + 1 < active_nr) { struct cache_entry *e2 = active_cache[i]; - struct cache_entry *e3 = active_cache[i+1]; + struct cache_entry *e3 = active_cache[i + 1]; if (ce_stage(e2) == 2 && ce_stage(e3) == 3 && - ce_same_name(e2, e3) && + ce_same_name(e, e3) && S_ISREG(e2->ce_mode) && - S_ISREG(e3->ce_mode)) { - string_list_insert(conflict, (const char *)e2->name); - i++; /* skip over both #2 and #3 */ + S_ISREG(e3->ce_mode)) + *type = THREE_STAGED; + } + + /* Skip the entries with the same name */ + while (i < active_nr && ce_same_name(e, active_cache[i])) + i++; + return i; +} + +static int find_conflict(struct string_list *conflict) +{ + int i; + if (read_cache() < 0) + return error("Could not read index"); + + for (i = 0; i < active_nr;) { + int conflict_type; + struct cache_entry *e = active_cache[i]; + i = check_one_conflict(i, &conflict_type); + if (conflict_type == THREE_STAGED) + string_list_insert(conflict, (const char *)e->name); + } + return 0; +} + +int rerere_remaining(struct string_list *merge_rr) +{ + int i; + if (read_cache() < 0) + return error("Could not read index"); + + for (i = 0; i < active_nr;) { + int conflict_type; + struct cache_entry *e = active_cache[i]; + i = check_one_conflict(i, &conflict_type); + if (conflict_type == PUNTED) + string_list_insert(merge_rr, (const char *)e->name); + else if (conflict_type == RESOLVED) { + struct string_list_item *it; + it = string_list_lookup(merge_rr, (const char *)e->name); + if (it != NULL) { + free(it->util); + it->util = RERERE_RESOLVED; + } } } return 0; @@ -380,7 +444,7 @@ static int merge(const char *name, const char *path) ret = 1; goto out; } - ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", 0); + ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL); if (!ret) { FILE *f; @@ -532,8 +596,7 @@ static int is_rerere_enabled(void) if (rerere_enabled < 0) return rr_cache_exists; - if (!rr_cache_exists && - (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache))) + if (!rr_cache_exists && mkdir_in_gitdir(rr_cache)) die("Could not create directory %s", rr_cache); return 1; } @@ -614,3 +677,88 @@ int rerere_forget(const char **pathspec) } return write_rr(&merge_rr, fd); } + +static time_t rerere_created_at(const char *name) +{ + struct stat st; + return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; +} + +static time_t rerere_last_used_at(const char *name) +{ + struct stat st; + return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime; +} + +static void unlink_rr_item(const char *name) +{ + unlink(rerere_path(name, "thisimage")); + unlink(rerere_path(name, "preimage")); + unlink(rerere_path(name, "postimage")); + rmdir(git_path("rr-cache/%s", name)); +} + +struct rerere_gc_config_cb { + int cutoff_noresolve; + int cutoff_resolve; +}; + +static int git_rerere_gc_config(const char *var, const char *value, void *cb) +{ + struct rerere_gc_config_cb *cf = cb; + + if (!strcmp(var, "gc.rerereresolved")) + cf->cutoff_resolve = git_config_int(var, value); + else if (!strcmp(var, "gc.rerereunresolved")) + cf->cutoff_noresolve = git_config_int(var, value); + else + return git_default_config(var, value, cb); + return 0; +} + +void rerere_gc(struct string_list *rr) +{ + struct string_list to_remove = STRING_LIST_INIT_DUP; + DIR *dir; + struct dirent *e; + int i, cutoff; + time_t now = time(NULL), then; + struct rerere_gc_config_cb cf = { 15, 60 }; + + git_config(git_rerere_gc_config, &cf); + dir = opendir(git_path("rr-cache")); + if (!dir) + die_errno("unable to open rr-cache directory"); + while ((e = readdir(dir))) { + if (is_dot_or_dotdot(e->d_name)) + continue; + + then = rerere_last_used_at(e->d_name); + if (then) { + cutoff = cf.cutoff_resolve; + } else { + then = rerere_created_at(e->d_name); + if (!then) + continue; + cutoff = cf.cutoff_noresolve; + } + if (then < now - cutoff * 86400) + string_list_append(&to_remove, e->d_name); + } + closedir(dir); + for (i = 0; i < to_remove.nr; i++) + unlink_rr_item(to_remove.items[i].string); + string_list_clear(&to_remove, 0); +} + +void rerere_clear(struct string_list *merge_rr) +{ + int i; + + for (i = 0; i < merge_rr->nr; i++) { + const char *name = (const char *)merge_rr->items[i].util; + if (!has_rerere_resolution(name)) + unlink_rr_item(name); + } + unlink_or_warn(git_path("MERGE_RR")); +} @@ -6,11 +6,21 @@ #define RERERE_AUTOUPDATE 01 #define RERERE_NOAUTOUPDATE 02 +/* + * Marks paths that have been hand-resolved and added to the + * index. Set in the util field of such paths after calling + * rerere_remaining. + */ +extern void *RERERE_RESOLVED; + extern int setup_rerere(struct string_list *, int); extern int rerere(int); extern const char *rerere_path(const char *hex, const char *file); extern int has_rerere_resolution(const char *hex); extern int rerere_forget(const char **); +extern int rerere_remaining(struct string_list *); +extern void rerere_clear(struct string_list *); +extern void rerere_gc(struct string_list *); #define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \ "update the index with reused conflict resolution if possible") diff --git a/revision.c b/revision.c index 7b9eaefae4..c46cfaa3e4 100644 --- a/revision.c +++ b/revision.c @@ -133,6 +133,8 @@ void mark_parents_uninteresting(struct commit *commit) static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode) { + if (!obj) + return; if (revs->no_walk && (obj->flags & UNINTERESTING)) revs->no_walk = 0; if (revs->reflog_info && obj->type == OBJ_COMMIT) { @@ -174,8 +176,11 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con struct object *object; object = parse_object(sha1); - if (!object) + if (!object) { + if (revs->ignore_missing) + return object; die("bad object %s", name); + } object->flags |= flags; return object; } @@ -323,7 +328,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct * tagged commit by specifying both --simplify-by-decoration * and pathspec. */ - if (!revs->prune_data) + if (!revs->prune_data.nr) return REV_TREE_SAME; } @@ -535,6 +540,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) int left_count = 0, right_count = 0; int left_first; struct patch_ids ids; + unsigned cherry_flag; /* First count the commits on the left and on the right */ for (p = list; p; p = p->next) { @@ -553,11 +559,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) left_first = left_count < right_count; init_patch_ids(&ids); - if (revs->diffopt.nr_paths) { - ids.diffopts.nr_paths = revs->diffopt.nr_paths; - ids.diffopts.paths = revs->diffopt.paths; - ids.diffopts.pathlens = revs->diffopt.pathlens; - } + ids.diffopts.pathspec = revs->diffopt.pathspec; /* Compute patch-ids for one side */ for (p = list; p; p = p->next) { @@ -576,6 +578,9 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) commit->util = add_commit_patch_id(commit, &ids); } + /* either cherry_mark or cherry_pick are true */ + cherry_flag = revs->cherry_mark ? PATCHSAME : SHOWN; + /* Check the other side */ for (p = list; p; p = p->next) { struct commit *commit = p->item; @@ -598,7 +603,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) if (!id) continue; id->seen = 1; - commit->object.flags |= SHOWN; + commit->object.flags |= cherry_flag; } /* Now check the original side for seen ones */ @@ -610,7 +615,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) if (!ent) continue; if (ent->seen) - commit->object.flags |= SHOWN; + commit->object.flags |= cherry_flag; commit->util = NULL; } @@ -733,6 +738,23 @@ static struct commit_list *collect_bottom_commits(struct commit_list *list) return bottom; } +/* Assumes either left_only or right_only is set */ +static void limit_left_right(struct commit_list *list, struct rev_info *revs) +{ + struct commit_list *p; + + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + + if (revs->right_only) { + if (commit->object.flags & SYMMETRIC_LEFT) + commit->object.flags |= SHOWN; + } else /* revs->left_only is set */ + if (!(commit->object.flags & SYMMETRIC_LEFT)) + commit->object.flags |= SHOWN; + } +} + static int limit_list(struct rev_info *revs) { int slop = SLOP; @@ -785,9 +807,12 @@ static int limit_list(struct rev_info *revs) show(revs, newlist); show_early_output = NULL; } - if (revs->cherry_pick) + if (revs->cherry_pick || revs->cherry_mark) cherry_pick_list(newlist, revs); + if (revs->left_only || revs->right_only) + limit_left_right(newlist, revs); + if (bottom) { limit_to_ancestry(bottom, newlist); free_commit_list(bottom); @@ -886,6 +911,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) return 0; while (1) { it = get_reference(revs, arg, sha1, 0); + if (!it && revs->ignore_missing) + return 0; if (it->type != OBJ_TAG) break; if (!((struct tag*)it)->tagged) @@ -921,6 +948,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->min_age = -1; revs->skip_count = -1; revs->max_count = -1; + revs->max_parents = -1; revs->commit_format = CMIT_FMT_DEFAULT; @@ -934,6 +962,8 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->diffopt.prefix = prefix; revs->diffopt.prefix_length = strlen(prefix); } + + revs->notes_opt.use_default_notes = -1; } static void add_pending_commit_list(struct rev_info *revs, @@ -973,7 +1003,7 @@ static void prepare_show_merge(struct rev_info *revs) struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; - if (ce_path_match(ce, revs->prune_data)) { + if (ce_path_match(ce, &revs->prune_data)) { prune_num++; prune = xrealloc(prune, sizeof(*prune) * prune_num); prune[prune_num-2] = ce->name; @@ -983,7 +1013,8 @@ static void prepare_show_merge(struct rev_info *revs) ce_same_name(ce, active_cache[i+1])) i++; } - revs->prune_data = prune; + free_pathspec(&revs->prune_data); + init_pathspec(&revs->prune_data, prune); revs->limited = 1; } @@ -1020,6 +1051,8 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, a = lookup_commit_reference(from_sha1); b = lookup_commit_reference(sha1); if (!a || !b) { + if (revs->ignore_missing) + return 0; die(symmetric ? "Invalid symmetric difference expression %s...%s" : "Invalid revision range %s..%s", @@ -1066,7 +1099,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, arg++; } if (get_sha1_with_mode(arg, sha1, &mode)) - return -1; + return revs->ignore_missing ? 0 : -1; if (!cant_be_filename) verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, sha1, flags ^ local_flags); @@ -1074,35 +1107,34 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; } -static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb, const char ***prune_data) -{ - const char **prune = *prune_data; - int prune_nr; - int prune_alloc; +struct cmdline_pathspec { + int alloc; + int nr; + const char **path; +}; - /* count existing ones */ - if (!prune) - prune_nr = 0; - else - for (prune_nr = 0; prune[prune_nr]; prune_nr++) - ; - prune_alloc = prune_nr; /* not really, but we do not know */ +static void append_prune_data(struct cmdline_pathspec *prune, const char **av) +{ + while (*av) { + ALLOC_GROW(prune->path, prune->nr+1, prune->alloc); + prune->path[prune->nr++] = *(av++); + } +} +static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb, + struct cmdline_pathspec *prune) +{ while (strbuf_getwholeline(sb, stdin, '\n') != EOF) { int len = sb->len; if (len && sb->buf[len - 1] == '\n') sb->buf[--len] = '\0'; - ALLOC_GROW(prune, prune_nr+1, prune_alloc); - prune[prune_nr++] = xstrdup(sb->buf); + ALLOC_GROW(prune->path, prune->nr+1, prune->alloc); + prune->path[prune->nr++] = xstrdup(sb->buf); } - if (prune) { - ALLOC_GROW(prune, prune_nr+1, prune_alloc); - prune[prune_nr] = NULL; - } - *prune_data = prune; } -static void read_revisions_from_stdin(struct rev_info *revs, const char ***prune) +static void read_revisions_from_stdin(struct rev_info *revs, + struct cmdline_pathspec *prune) { struct strbuf sb; int seen_dashdash = 0; @@ -1156,7 +1188,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") || !strcmp(arg, "--reflog") || !strcmp(arg, "--not") || !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") || - !strcmp(arg, "--bisect")) + !strcmp(arg, "--bisect") || !prefixcmp(arg, "--glob=") || + !prefixcmp(arg, "--branches=") || !prefixcmp(arg, "--tags=") || + !prefixcmp(arg, "--remotes=")) { unkv[(*unkc)++] = arg; return 1; @@ -1256,16 +1290,47 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--remove-empty")) { revs->remove_empty_trees = 1; } else if (!strcmp(arg, "--merges")) { - revs->merges_only = 1; + revs->min_parents = 2; } else if (!strcmp(arg, "--no-merges")) { - revs->no_merges = 1; + revs->max_parents = 1; + } else if (!prefixcmp(arg, "--min-parents=")) { + revs->min_parents = atoi(arg+14); + } else if (!prefixcmp(arg, "--no-min-parents")) { + revs->min_parents = 0; + } else if (!prefixcmp(arg, "--max-parents=")) { + revs->max_parents = atoi(arg+14); + } else if (!prefixcmp(arg, "--no-max-parents")) { + revs->max_parents = -1; } else if (!strcmp(arg, "--boundary")) { revs->boundary = 1; } else if (!strcmp(arg, "--left-right")) { revs->left_right = 1; + } else if (!strcmp(arg, "--left-only")) { + if (revs->right_only) + die("--left-only is incompatible with --right-only" + " or --cherry"); + revs->left_only = 1; + } else if (!strcmp(arg, "--right-only")) { + if (revs->left_only) + die("--right-only is incompatible with --left-only"); + revs->right_only = 1; + } else if (!strcmp(arg, "--cherry")) { + if (revs->left_only) + die("--cherry is incompatible with --left-only"); + revs->cherry_mark = 1; + revs->right_only = 1; + revs->max_parents = 1; + revs->limited = 1; } else if (!strcmp(arg, "--count")) { revs->count = 1; + } else if (!strcmp(arg, "--cherry-mark")) { + if (revs->cherry_pick) + die("--cherry-mark is incompatible with --cherry-pick"); + revs->cherry_mark = 1; + revs->limited = 1; /* needs limit_list() */ } else if (!strcmp(arg, "--cherry-pick")) { + if (revs->cherry_mark) + die("--cherry-pick is incompatible with --cherry-mark"); revs->cherry_pick = 1; revs->limited = 1; } else if (!strcmp(arg, "--objects")) { @@ -1312,32 +1377,39 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->verbose_header = 1; revs->pretty_given = 1; get_commit_format(arg+9, revs); - } else if (!strcmp(arg, "--show-notes")) { + } else if (!strcmp(arg, "--show-notes") || !strcmp(arg, "--notes")) { revs->show_notes = 1; revs->show_notes_given = 1; - } else if (!prefixcmp(arg, "--show-notes=")) { + revs->notes_opt.use_default_notes = 1; + } else if (!prefixcmp(arg, "--show-notes=") || + !prefixcmp(arg, "--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/"); + if (!prefixcmp(arg, "--show-notes")) { + if (revs->notes_opt.use_default_notes < 0) + revs->notes_opt.use_default_notes = 1; + strbuf_addstr(&buf, arg+13); + } else - strbuf_addstr(&buf, "refs/notes/"); - strbuf_addstr(&buf, arg+13); - string_list_append(revs->notes_opt.extra_notes_refs, + strbuf_addstr(&buf, arg+8); + expand_notes_ref(&buf); + 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; + revs->notes_opt.use_default_notes = -1; + /* we have been strdup'ing ourselves, so trick + * string_list into free()ing strings */ + revs->notes_opt.extra_notes_refs.strdup_strings = 1; + string_list_clear(&revs->notes_opt.extra_notes_refs, 0); + revs->notes_opt.extra_notes_refs.strdup_strings = 0; } else if (!strcmp(arg, "--standard-notes")) { revs->show_notes_given = 1; - revs->notes_opt.suppress_default_notes = 0; + revs->notes_opt.use_default_notes = 1; } else if (!strcmp(arg, "--no-standard-notes")) { - revs->notes_opt.suppress_default_notes = 1; + revs->notes_opt.use_default_notes = 0; } else if (!strcmp(arg, "--oneline")) { revs->verbose_header = 1; get_commit_format("oneline", revs); @@ -1365,6 +1437,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->abbrev = 40; } else if (!strcmp(arg, "--abbrev-commit")) { revs->abbrev_commit = 1; + revs->abbrev_commit_given = 1; + } else if (!strcmp(arg, "--no-abbrev-commit")) { + revs->abbrev_commit = 0; } else if (!strcmp(arg, "--full-diff")) { revs->diff = 1; revs->full_diff = 1; @@ -1411,6 +1486,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--children")) { revs->children.name = "children"; revs->limited = 1; + } else if (!strcmp(arg, "--ignore-missing")) { + revs->ignore_missing = 1; } else { int opts = diff_opt_parse(&revs->diffopt, argv, argc); if (!opts) @@ -1445,32 +1522,67 @@ static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void return for_each_ref_in_submodule(submodule, "refs/bisect/good", fn, cb_data); } -static void append_prune_data(const char ***prune_data, const char **av) +static int handle_revision_pseudo_opt(const char *submodule, + struct rev_info *revs, + int argc, const char **argv, int *flags) { - const char **prune = *prune_data; - int prune_nr; - int prune_alloc; + const char *arg = argv[0]; + const char *optarg; + int argcount; - if (!prune) { - *prune_data = av; - return; + /* + * NOTE! + * + * Commands like "git shortlog" will not accept the options below + * unless parse_revision_opt queues them (as opposed to erroring + * out). + * + * When implementing your new pseudo-option, remember to + * register it in the list at the top of handle_revision_opt. + */ + if (!strcmp(arg, "--all")) { + handle_refs(submodule, revs, *flags, for_each_ref_submodule); + handle_refs(submodule, revs, *flags, head_ref_submodule); + } else if (!strcmp(arg, "--branches")) { + handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule); + } else if (!strcmp(arg, "--bisect")) { + handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref); + handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref); + revs->bisect = 1; + } else if (!strcmp(arg, "--tags")) { + handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule); + } else if (!strcmp(arg, "--remotes")) { + handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule); + } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, *flags); + for_each_glob_ref(handle_one_ref, optarg, &cb); + return argcount; + } else if (!prefixcmp(arg, "--branches=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, *flags); + for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); + } else if (!prefixcmp(arg, "--tags=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, *flags); + for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); + } else if (!prefixcmp(arg, "--remotes=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, *flags); + for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); + } else if (!strcmp(arg, "--reflog")) { + handle_reflog(revs, *flags); + } else if (!strcmp(arg, "--not")) { + *flags ^= UNINTERESTING; + } else if (!strcmp(arg, "--no-walk")) { + revs->no_walk = 1; + } else if (!strcmp(arg, "--do-walk")) { + revs->no_walk = 0; + } else { + return 0; } - /* count existing ones */ - for (prune_nr = 0; prune[prune_nr]; prune_nr++) - ; - prune_alloc = prune_nr; /* not really, but we do not know */ - - while (*av) { - ALLOC_GROW(prune, prune_nr+1, prune_alloc); - prune[prune_nr++] = *av; - av++; - } - if (prune) { - ALLOC_GROW(prune, prune_nr+1, prune_alloc); - prune[prune_nr] = NULL; - } - *prune_data = prune; + return 1; } /* @@ -1483,11 +1595,10 @@ static void append_prune_data(const char ***prune_data, const char **av) 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; + struct cmdline_pathspec prune_data; const char *submodule = NULL; - const char *optarg; - int argcount; + memset(&prune_data, 0, sizeof(prune_data)); if (opt) submodule = opt->submodule; @@ -1500,7 +1611,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s argv[i] = NULL; argc = i; if (argv[i + 1]) - prune_data = argv + i + 1; + append_prune_data(&prune_data, argv + i + 1); seen_dashdash = 1; break; } @@ -1513,70 +1624,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (*arg == '-') { int opts; - if (!strcmp(arg, "--all")) { - handle_refs(submodule, revs, flags, for_each_ref_submodule); - handle_refs(submodule, revs, flags, head_ref_submodule); - continue; - } - if (!strcmp(arg, "--branches")) { - handle_refs(submodule, revs, flags, for_each_branch_ref_submodule); - continue; - } - if (!strcmp(arg, "--bisect")) { - handle_refs(submodule, revs, flags, for_each_bad_bisect_ref); - handle_refs(submodule, revs, flags ^ UNINTERESTING, for_each_good_bisect_ref); - revs->bisect = 1; - continue; - } - if (!strcmp(arg, "--tags")) { - handle_refs(submodule, revs, flags, for_each_tag_ref_submodule); - continue; - } - if (!strcmp(arg, "--remotes")) { - handle_refs(submodule, revs, flags, for_each_remote_ref_submodule); - continue; - } - if ((argcount = parse_long_opt("glob", argv + i, &optarg))) { - struct all_refs_cb cb; - i += argcount - 1; - init_all_refs_cb(&cb, revs, flags); - for_each_glob_ref(handle_one_ref, optarg, &cb); - continue; - } - if (!prefixcmp(arg, "--branches=")) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, flags); - for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); - continue; - } - if (!prefixcmp(arg, "--tags=")) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, flags); - for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); - continue; - } - if (!prefixcmp(arg, "--remotes=")) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, flags); - for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); - continue; - } - if (!strcmp(arg, "--reflog")) { - handle_reflog(revs, flags); - continue; - } - if (!strcmp(arg, "--not")) { - flags ^= UNINTERESTING; - continue; - } - if (!strcmp(arg, "--no-walk")) { - revs->no_walk = 1; - continue; - } - if (!strcmp(arg, "--do-walk")) { - revs->no_walk = 0; + opts = handle_revision_pseudo_opt(submodule, + revs, argc - i, argv + i, + &flags); + if (opts > 0) { + i += opts - 1; continue; } + if (!strcmp(arg, "--stdin")) { if (revs->disable_stdin) { argv[left++] = arg; @@ -1619,8 +1674,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s got_rev_arg = 1; } - if (prune_data) - revs->prune_data = get_pathspec(revs->prefix, prune_data); + if (prune_data.nr) { + /* + * If we need to introduce the magic "a lone ':' means no + * pathspec whatsoever", here is the place to do so. + * + * if (prune_data.nr == 1 && !strcmp(prune_data[0], ":")) { + * prune_data.nr = 0; + * prune_data.alloc = 0; + * free(prune_data.path); + * prune_data.path = NULL; + * } else { + * terminate prune_data.alloc with NULL and + * call init_pathspec() to set revs->prune_data here. + * } + */ + ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc); + prune_data.path[prune_data.nr++] = NULL; + init_pathspec(&revs->prune_data, + get_pathspec(revs->prefix, prune_data.path)); + } if (revs->def == NULL) revs->def = opt ? opt->def : NULL; @@ -1651,13 +1724,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->topo_order) revs->limited = 1; - if (revs->prune_data) { - diff_tree_setup_paths(revs->prune_data, &revs->pruning); + if (revs->prune_data.nr) { + diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning); /* Can't prune commits with rename following: the paths change.. */ if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) revs->prune = 1; if (!revs->full_diff) - diff_tree_setup_paths(revs->prune_data, &revs->diffopt); + diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt); } if (revs->combine_merges) revs->ignore_merges = 0; @@ -1985,10 +2058,15 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi return commit_ignore; if (revs->min_age != -1 && (commit->date > revs->min_age)) return commit_ignore; - if (revs->no_merges && commit->parents && commit->parents->next) - return commit_ignore; - if (revs->merges_only && !(commit->parents && commit->parents->next)) - return commit_ignore; + if (revs->min_parents || (revs->max_parents >= 0)) { + int n = 0; + struct commit_list *p; + for (p = commit->parents; p; p = p->next) + n++; + if ((n < revs->min_parents) || + ((revs->max_parents >= 0) && (n > revs->max_parents))) + return commit_ignore; + } if (!commit_match(commit, revs)) return commit_ignore; if (revs->prune && revs->dense) { @@ -2235,3 +2313,32 @@ struct commit *get_revision(struct rev_info *revs) graph_update(revs->graph, c); return c; } + +char *get_revision_mark(const struct rev_info *revs, const struct commit *commit) +{ + if (commit->object.flags & BOUNDARY) + return "-"; + else if (commit->object.flags & UNINTERESTING) + return "^"; + else if (commit->object.flags & PATCHSAME) + return "="; + else if (!revs || revs->left_right) { + if (commit->object.flags & SYMMETRIC_LEFT) + return "<"; + else + return ">"; + } else if (revs->graph) + return "*"; + else if (revs->cherry_mark) + return "+"; + return ""; +} + +void put_revision_mark(const struct rev_info *revs, const struct commit *commit) +{ + char *mark = get_revision_mark(revs, commit); + if (!strlen(mark)) + return; + fputs(mark, stdout); + putchar(' '); +} diff --git a/revision.h b/revision.h index f8ddd83e79..3d64adad18 100644 --- a/revision.h +++ b/revision.h @@ -14,7 +14,8 @@ #define CHILD_SHOWN (1u<<6) #define ADDED (1u<<7) /* Parents already parsed and added? */ #define SYMMETRIC_LEFT (1u<<8) -#define ALL_REV_FLAGS ((1u<<9)-1) +#define PATCHSAME (1u<<9) +#define ALL_REV_FLAGS ((1u<<10)-1) #define DECORATE_SHORT_REFS 1 #define DECORATE_FULL_REFS 2 @@ -34,14 +35,13 @@ struct rev_info { /* Basic information */ const char *prefix; const char *def; - void *prune_data; - unsigned int early_output; + struct pathspec prune_data; + unsigned int early_output:1, + ignore_missing:1; /* Traversal flags */ unsigned int dense:1, prune:1, - no_merges:1, - merges_only:1, no_walk:1, show_all:1, remove_empty_trees:1, @@ -59,6 +59,8 @@ struct rev_info { boundary:2, count:1, left_right:1, + left_only:1, + right_only:1, rewrite_parents:1, print_parents:1, show_source:1, @@ -66,6 +68,7 @@ struct rev_info { reverse:1, reverse_output_stage:1, cherry_pick:1, + cherry_mark:1, bisect:1, ancestry_path:1, first_parent_only:1; @@ -88,6 +91,7 @@ struct rev_info { show_notes_given:1, pretty_given:1, abbrev_commit:1, + abbrev_commit_given:1, use_terminator:1, missing_newline:1, date_mode_explicit:1, @@ -123,6 +127,8 @@ struct rev_info { int max_count; unsigned long max_age; unsigned long min_age; + int min_parents; + int max_parents; /* diff info for patches and for paths limiting */ struct diff_options diffopt; @@ -138,6 +144,7 @@ struct rev_info { /* commit counts */ int count_left; int count_right; + int count_same; }; #define REV_TREE_SAME 0 @@ -164,6 +171,8 @@ extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags, extern int prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); +extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit); +extern void put_revision_mark(const struct rev_info *revs, const struct commit *commit); extern void mark_parents_uninteresting(struct commit *commit); extern void mark_tree_uninteresting(struct tree *tree); diff --git a/run-command.c b/run-command.c index 2a1041ef65..70e8a249d0 100644 --- a/run-command.c +++ b/run-command.c @@ -67,21 +67,24 @@ static int child_notifier = -1; static void notify_parent(void) { - ssize_t unused; - unused = write(child_notifier, "", 1); + /* + * execvp failed. If possible, we'd like to let start_command + * know, so failures like ENOENT can be handled right away; but + * otherwise, finish_command will still report the error. + */ + xwrite(child_notifier, "", 1); } static NORETURN void die_child(const char *err, va_list params) { char msg[4096]; - ssize_t unused; int len = vsnprintf(msg, sizeof(msg), err, params); if (len > sizeof(msg)) len = sizeof(msg); - unused = write(child_err, "fatal: ", 7); - unused = write(child_err, msg, len); - unused = write(child_err, "\n", 1); + write_in_full(child_err, "fatal: ", 7); + write_in_full(child_err, msg, len); + write_in_full(child_err, "\n", 1); exit(128); } #endif @@ -194,6 +197,7 @@ fail_pipe: } trace_argv_printf(cmd->argv, "trace: run_command:"); + fflush(NULL); #ifndef WIN32 { @@ -201,7 +205,6 @@ fail_pipe: if (pipe(notify_pipe)) notify_pipe[0] = notify_pipe[1] = -1; - fflush(NULL); cmd->pid = fork(); if (!cmd->pid) { /* @@ -7,10 +7,13 @@ static int inside_work_tree = -1; char *prefix_path(const char *prefix, int len, const char *path) { const char *orig = path; - char *sanitized = xmalloc(len + strlen(path) + 1); - if (is_absolute_path(orig)) - strcpy(sanitized, path); - else { + char *sanitized; + if (is_absolute_path(orig)) { + const char *temp = real_path(path); + sanitized = xmalloc(len + strlen(temp) + 1); + strcpy(sanitized, temp); + } else { + sanitized = xmalloc(len + strlen(path) + 1); if (len) memcpy(sanitized, prefix, len); strcpy(sanitized + len, path); @@ -82,8 +85,17 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg) { unsigned char sha1[20]; unsigned mode; - /* try a detailed diagnostic ... */ - get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix); + + /* + * Saying "'(icase)foo' does not exist in the index" when the + * user gave us ":(icase)foo" is just stupid. A magic pathspec + * begins with a colon and is followed by a non-alnum; do not + * let get_sha1_with_mode_1(only_to_die=1) to even trigger. + */ + if (!(arg[0] == ':' && !isalnum(arg[1]))) + /* try a detailed diagnostic ... */ + get_sha1_with_mode_1(arg, sha1, &mode, 1, prefix); + /* ... or fall back the most general message. */ die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" "Use '--' to separate paths from revisions", arg); @@ -123,6 +135,105 @@ void verify_non_filename(const char *prefix, const char *arg) "Use '--' to separate filenames from revisions", arg); } +/* + * Magic pathspec + * + * NEEDSWORK: These need to be moved to dir.h or even to a new + * pathspec.h when we restructure get_pathspec() users to use the + * "struct pathspec" interface. + * + * Possible future magic semantics include stuff like: + * + * { PATHSPEC_NOGLOB, '!', "noglob" }, + * { PATHSPEC_ICASE, '\0', "icase" }, + * { PATHSPEC_RECURSIVE, '*', "recursive" }, + * { PATHSPEC_REGEXP, '\0', "regexp" }, + * + */ +#define PATHSPEC_FROMTOP (1<<0) + +static struct pathspec_magic { + unsigned bit; + char mnemonic; /* this cannot be ':'! */ + const char *name; +} pathspec_magic[] = { + { PATHSPEC_FROMTOP, '/', "top" }, +}; + +/* + * Take an element of a pathspec and check for magic signatures. + * Append the result to the prefix. + * + * For now, we only parse the syntax and throw out anything other than + * "top" magic. + * + * NEEDSWORK: This needs to be rewritten when we start migrating + * get_pathspec() users to use the "struct pathspec" interface. For + * example, a pathspec element may be marked as case-insensitive, but + * the prefix part must always match literally, and a single stupid + * string cannot express such a case. + */ +static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt) +{ + unsigned magic = 0; + const char *copyfrom = elt; + int i; + + if (elt[0] != ':') { + ; /* nothing to do */ + } else if (elt[1] == '(') { + /* longhand */ + const char *nextat; + for (copyfrom = elt + 2; + *copyfrom && *copyfrom != ')'; + copyfrom = nextat) { + size_t len = strcspn(copyfrom, ",)"); + if (copyfrom[len] == ')') + nextat = copyfrom + len; + else + nextat = copyfrom + len + 1; + if (!len) + continue; + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) + if (strlen(pathspec_magic[i].name) == len && + !strncmp(pathspec_magic[i].name, copyfrom, len)) { + magic |= pathspec_magic[i].bit; + break; + } + if (ARRAY_SIZE(pathspec_magic) <= i) + die("Invalid pathspec magic '%.*s' in '%s'", + (int) len, copyfrom, elt); + } + if (*copyfrom == ')') + copyfrom++; + } else { + /* shorthand */ + for (copyfrom = elt + 1; + *copyfrom && *copyfrom != ':'; + copyfrom++) { + char ch = *copyfrom; + + if (!is_pathspec_magic(ch)) + break; + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) + if (pathspec_magic[i].mnemonic == ch) { + magic |= pathspec_magic[i].bit; + break; + } + if (ARRAY_SIZE(pathspec_magic) <= i) + die("Unimplemented pathspec magic '%c' in '%s'", + ch, elt); + } + if (*copyfrom == ':') + copyfrom++; + } + + if (magic & PATHSPEC_FROMTOP) + return xstrdup(copyfrom); + else + return prefix_path(prefix, prefixlen, copyfrom); +} + const char **get_pathspec(const char *prefix, const char **pathspec) { const char *entry = *pathspec; @@ -144,8 +255,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec) dst = pathspec; prefixlen = prefix ? strlen(prefix) : 0; while (*src) { - const char *p = prefix_path(prefix, prefixlen, *src); - *(dst++) = p; + *(dst++) = prefix_pathspec(prefix, prefixlen, *src); src++; } *dst = NULL; @@ -218,7 +328,7 @@ void setup_work_tree(void) work_tree = get_git_work_tree(); git_dir = get_git_dir(); if (!is_absolute_path(git_dir)) - git_dir = make_absolute_path(git_dir); + git_dir = real_path(get_git_dir()); if (!work_tree || chdir(work_tree)) die("This operation must be run in a work tree"); @@ -229,7 +339,7 @@ void setup_work_tree(void) if (getenv(GIT_WORK_TREE_ENVIRONMENT)) setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1); - set_git_dir(make_relative_path(git_dir, work_tree)); + set_git_dir(relative_path(git_dir, work_tree)); initialized = 1; } @@ -272,7 +382,7 @@ const char *read_gitfile_gently(const char *path) const char *slash; struct stat st; int fd; - size_t len; + ssize_t len; if (stat(path, &st)) return NULL; @@ -309,7 +419,7 @@ const char *read_gitfile_gently(const char *path) if (!is_git_directory(dir)) die("Not a git repository: %s", dir); - path = make_absolute_path(dir); + path = real_path(dir); free(buf); return path; @@ -322,6 +432,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT); const char *worktree; char *gitfile; + int offset; if (PATH_MAX - 40 < strlen(gitdirenv)) die("'$%s' too big", GIT_DIR_ENVIRONMENT); @@ -387,15 +498,15 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, return NULL; } - if (!prefixcmp(cwd, worktree) && - cwd[strlen(worktree)] == '/') { /* cwd inside worktree */ - set_git_dir(make_absolute_path(gitdirenv)); + offset = dir_inside_of(cwd, worktree); + if (offset >= 0) { /* cwd inside worktree? */ + set_git_dir(real_path(gitdirenv)); if (chdir(worktree)) die_errno("Could not chdir to '%s'", worktree); cwd[len++] = '/'; cwd[len] = '\0'; free(gitfile); - return cwd + strlen(worktree) + 1; + return cwd + offset; } /* cwd outside worktree */ @@ -414,7 +525,7 @@ static const char *setup_discovered_git_dir(const char *gitdir, /* --work-tree is set without --git-dir; use discovered one */ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { if (offset != len && !is_absolute_path(gitdir)) - gitdir = xstrdup(make_absolute_path(gitdir)); + gitdir = xstrdup(real_path(gitdir)); if (chdir(cwd)) die_errno("Could not come back to cwd"); return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok); @@ -422,7 +533,7 @@ static const char *setup_discovered_git_dir(const char *gitdir, /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */ if (is_bare_repository_cfg > 0) { - set_git_dir(offset == len ? gitdir : make_absolute_path(gitdir)); + set_git_dir(offset == len ? gitdir : real_path(gitdir)); if (chdir(cwd)) die_errno("Could not come back to cwd"); return NULL; diff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c new file mode 100644 index 0000000000..2eb0ee44b8 --- /dev/null +++ b/sh-i18n--envsubst.c @@ -0,0 +1,444 @@ +/* + * sh-i18n--envsubst.c - a stripped-down version of gettext's envsubst(1) + * + * Copyright (C) 2010 Ævar Arnfjörð Bjarmason + * + * This is a modified version of + * 67d0871a8c:gettext-runtime/src/envsubst.c from the gettext.git + * repository. It has been stripped down to only implement the + * envsubst(1) features that we need in the git-sh-i18n fallbacks. + * + * The "Close standard error" part in main() is from + * 8dac033df0:gnulib-local/lib/closeout.c. The copyright notices for + * both files are reproduced immediately below. + */ + +#include "git-compat-util.h" + +/* Substitution of environment variables in shell format strings. + Copyright (C) 2003-2007 Free Software Foundation, Inc. + Written by Bruno Haible <bruno@clisp.org>, 2003. + + 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, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* closeout.c - close standard output and standard error + Copyright (C) 1998-2007 Free Software Foundation, Inc. + + 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, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* If true, substitution shall be performed on all variables. */ +static unsigned short int all_variables; + +/* Forward declaration of local functions. */ +static void print_variables (const char *string); +static void note_variables (const char *string); +static void subst_from_stdin (void); + +int +main (int argc, char *argv[]) +{ + /* Default values for command line options. */ + /* unsigned short int show_variables = 0; */ + + switch (argc) + { + case 1: + error ("we won't substitute all variables on stdin for you"); + /* + all_variables = 1; + subst_from_stdin (); + */ + case 2: + /* echo '$foo and $bar' | git sh-i18n--envsubst --variables '$foo and $bar' */ + all_variables = 0; + note_variables (argv[1]); + subst_from_stdin (); + break; + case 3: + /* git sh-i18n--envsubst --variables '$foo and $bar' */ + if (strcmp(argv[1], "--variables")) + error ("first argument must be --variables when two are given"); + /* show_variables = 1; */ + print_variables (argv[2]); + break; + default: + error ("too many arguments"); + break; + } + + /* Close standard error. This is simpler than fwriteerror_no_ebadf, because + upon failure we don't need an errno - all we can do at this point is to + set an exit status. */ + errno = 0; + if (ferror (stderr) || fflush (stderr)) + { + fclose (stderr); + exit (EXIT_FAILURE); + } + if (fclose (stderr) && errno != EBADF) + exit (EXIT_FAILURE); + + exit (EXIT_SUCCESS); +} + +/* Parse the string and invoke the callback each time a $VARIABLE or + ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence + of ASCII alphanumeric/underscore characters, starting with an ASCII + alphabetic/underscore character. + We allow only ASCII characters, to avoid dependencies w.r.t. the current + encoding: While "${\xe0}" looks like a variable access in ISO-8859-1 + encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030, + SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these + encodings. */ +static void +find_variables (const char *string, + void (*callback) (const char *var_ptr, size_t var_len)) +{ + for (; *string != '\0';) + if (*string++ == '$') + { + const char *variable_start; + const char *variable_end; + unsigned short int valid; + char c; + + if (*string == '{') + string++; + + variable_start = string; + c = *string; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') + { + do + c = *++string; + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') || c == '_'); + variable_end = string; + + if (variable_start[-1] == '{') + { + if (*string == '}') + { + string++; + valid = 1; + } + else + valid = 0; + } + else + valid = 1; + + if (valid) + callback (variable_start, variable_end - variable_start); + } + } +} + + +/* Print a variable to stdout, followed by a newline. */ +static void +print_variable (const char *var_ptr, size_t var_len) +{ + fwrite (var_ptr, var_len, 1, stdout); + putchar ('\n'); +} + +/* Print the variables contained in STRING to stdout, each one followed by a + newline. */ +static void +print_variables (const char *string) +{ + find_variables (string, &print_variable); +} + + +/* Type describing list of immutable strings, + implemented using a dynamic array. */ +typedef struct string_list_ty string_list_ty; +struct string_list_ty +{ + const char **item; + size_t nitems; + size_t nitems_max; +}; + +/* Initialize an empty list of strings. */ +static inline void +string_list_init (string_list_ty *slp) +{ + slp->item = NULL; + slp->nitems = 0; + slp->nitems_max = 0; +} + +/* Append a single string to the end of a list of strings. */ +static inline void +string_list_append (string_list_ty *slp, const char *s) +{ + /* Grow the list. */ + if (slp->nitems >= slp->nitems_max) + { + size_t nbytes; + + slp->nitems_max = slp->nitems_max * 2 + 4; + nbytes = slp->nitems_max * sizeof (slp->item[0]); + slp->item = (const char **) xrealloc (slp->item, nbytes); + } + + /* Add the string to the end of the list. */ + slp->item[slp->nitems++] = s; +} + +/* Compare two strings given by reference. */ +static int +cmp_string (const void *pstr1, const void *pstr2) +{ + const char *str1 = *(const char **)pstr1; + const char *str2 = *(const char **)pstr2; + + return strcmp (str1, str2); +} + +/* Sort a list of strings. */ +static inline void +string_list_sort (string_list_ty *slp) +{ + if (slp->nitems > 0) + qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string); +} + +/* Test whether a string list contains a given string. */ +static inline int +string_list_member (const string_list_ty *slp, const char *s) +{ + size_t j; + + for (j = 0; j < slp->nitems; ++j) + if (strcmp (slp->item[j], s) == 0) + return 1; + return 0; +} + +/* Test whether a sorted string list contains a given string. */ +static int +sorted_string_list_member (const string_list_ty *slp, const char *s) +{ + size_t j1, j2; + + j1 = 0; + j2 = slp->nitems; + if (j2 > 0) + { + /* Binary search. */ + while (j2 - j1 > 1) + { + /* Here we know that if s is in the list, it is at an index j + with j1 <= j < j2. */ + size_t j = (j1 + j2) >> 1; + int result = strcmp (slp->item[j], s); + + if (result > 0) + j2 = j; + else if (result == 0) + return 1; + else + j1 = j + 1; + } + if (j2 > j1) + if (strcmp (slp->item[j1], s) == 0) + return 1; + } + return 0; +} + + +/* Set of variables on which to perform substitution. + Used only if !all_variables. */ +static string_list_ty variables_set; + +/* Adds a variable to variables_set. */ +static void +note_variable (const char *var_ptr, size_t var_len) +{ + char *string = xmalloc (var_len + 1); + memcpy (string, var_ptr, var_len); + string[var_len] = '\0'; + + string_list_append (&variables_set, string); +} + +/* Stores the variables occurring in the string in variables_set. */ +static void +note_variables (const char *string) +{ + string_list_init (&variables_set); + find_variables (string, ¬e_variable); + string_list_sort (&variables_set); +} + + +static int +do_getc (void) +{ + int c = getc (stdin); + + if (c == EOF) + { + if (ferror (stdin)) + error ("error while reading standard input"); + } + + return c; +} + +static inline void +do_ungetc (int c) +{ + if (c != EOF) + ungetc (c, stdin); +} + +/* Copies stdin to stdout, performing substitutions. */ +static void +subst_from_stdin (void) +{ + static char *buffer; + static size_t bufmax; + static size_t buflen; + int c; + + for (;;) + { + c = do_getc (); + if (c == EOF) + break; + /* Look for $VARIABLE or ${VARIABLE}. */ + if (c == '$') + { + unsigned short int opening_brace = 0; + unsigned short int closing_brace = 0; + + c = do_getc (); + if (c == '{') + { + opening_brace = 1; + c = do_getc (); + } + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') + { + unsigned short int valid; + + /* Accumulate the VARIABLE in buffer. */ + buflen = 0; + do + { + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; + + c = do_getc (); + } + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') || c == '_'); + + if (opening_brace) + { + if (c == '}') + { + closing_brace = 1; + valid = 1; + } + else + { + valid = 0; + do_ungetc (c); + } + } + else + { + valid = 1; + do_ungetc (c); + } + + if (valid) + { + /* Terminate the variable in the buffer. */ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + + /* Test whether the variable shall be substituted. */ + if (!all_variables + && !sorted_string_list_member (&variables_set, buffer)) + valid = 0; + } + + if (valid) + { + /* Substitute the variable's value from the environment. */ + const char *env_value = getenv (buffer); + + if (env_value != NULL) + fputs (env_value, stdout); + } + else + { + /* Perform no substitution at all. Since the buffered input + contains no other '$' than at the start, we can just + output all the buffered contents. */ + putchar ('$'); + if (opening_brace) + putchar ('{'); + fwrite (buffer, buflen, 1, stdout); + if (closing_brace) + putchar ('}'); + } + } + else + { + do_ungetc (c); + putchar ('$'); + if (opening_brace) + putchar ('{'); + } + } + else + putchar (c); + } +} diff --git a/sha1-array.c b/sha1-array.c new file mode 100644 index 0000000000..b2f47f98fb --- /dev/null +++ b/sha1-array.c @@ -0,0 +1,59 @@ +#include "cache.h" +#include "sha1-array.h" +#include "sha1-lookup.h" + +void sha1_array_append(struct sha1_array *array, const unsigned char *sha1) +{ + ALLOC_GROW(array->sha1, array->nr + 1, array->alloc); + hashcpy(array->sha1[array->nr++], sha1); + array->sorted = 0; +} + +static int void_hashcmp(const void *a, const void *b) +{ + return hashcmp(a, b); +} + +void sha1_array_sort(struct sha1_array *array) +{ + qsort(array->sha1, array->nr, sizeof(*array->sha1), void_hashcmp); + array->sorted = 1; +} + +static const unsigned char *sha1_access(size_t index, void *table) +{ + unsigned char (*array)[20] = table; + return array[index]; +} + +int sha1_array_lookup(struct sha1_array *array, const unsigned char *sha1) +{ + if (!array->sorted) + sha1_array_sort(array); + return sha1_pos(sha1, array->sha1, array->nr, sha1_access); +} + +void sha1_array_clear(struct sha1_array *array) +{ + free(array->sha1); + array->sha1 = NULL; + array->nr = 0; + array->alloc = 0; + array->sorted = 0; +} + +void sha1_array_for_each_unique(struct sha1_array *array, + for_each_sha1_fn fn, + void *data) +{ + int i; + + if (!array->sorted) + sha1_array_sort(array); + + for (i = 0; i < array->nr; i++) { + if (i > 0 && !hashcmp(array->sha1[i], array->sha1[i-1])) + continue; + fn(array->sha1[i], data); + } +} diff --git a/sha1-array.h b/sha1-array.h new file mode 100644 index 0000000000..4499b5dad4 --- /dev/null +++ b/sha1-array.h @@ -0,0 +1,24 @@ +#ifndef SHA1_ARRAY_H +#define SHA1_ARRAY_H + +struct sha1_array { + unsigned char (*sha1)[20]; + int nr; + int alloc; + int sorted; +}; + +#define SHA1_ARRAY_INIT { NULL, 0, 0, 0 } + +void sha1_array_append(struct sha1_array *array, const unsigned char *sha1); +void sha1_array_sort(struct sha1_array *array); +int sha1_array_lookup(struct sha1_array *array, const unsigned char *sha1); +void sha1_array_clear(struct sha1_array *array); + +typedef void (*for_each_sha1_fn)(const unsigned char sha1[20], + void *data); +void sha1_array_for_each_unique(struct sha1_array *array, + for_each_sha1_fn fn, + void *data); + +#endif /* SHA1_ARRAY_H */ diff --git a/sha1_file.c b/sha1_file.c index d86a8db69a..064a330408 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -11,8 +11,10 @@ #include "pack.h" #include "blob.h" #include "commit.h" +#include "run-command.h" #include "tag.h" #include "tree.h" +#include "tree-walk.h" #include "refs.h" #include "pack-revindex.h" #include "sha1-lookup.h" @@ -25,17 +27,74 @@ #endif #endif -#ifdef NO_C99_FORMAT -#define SZ_FMT "lu" -static unsigned long sz_fmt(size_t s) { return (unsigned long)s; } -#else -#define SZ_FMT "zu" -static size_t sz_fmt(size_t s) { return s; } -#endif +#define SZ_FMT PRIuMAX +static inline uintmax_t sz_fmt(size_t s) { return s; } const unsigned char null_sha1[20]; -static int git_open_noatime(const char *name, struct packed_git *p); +/* + * This is meant to hold a *small* number of objects that you would + * want read_sha1_file() to be able to return, but yet you do not want + * to write them into the object store (e.g. a browse-only + * application). + */ +static struct cached_object { + unsigned char sha1[20]; + enum object_type type; + void *buf; + unsigned long size; +} *cached_objects; +static int cached_object_nr, cached_object_alloc; + +static struct cached_object empty_tree = { + EMPTY_TREE_SHA1_BIN_LITERAL, + OBJ_TREE, + "", + 0 +}; + +static struct cached_object *find_cached_object(const unsigned char *sha1) +{ + int i; + struct cached_object *co = cached_objects; + + for (i = 0; i < cached_object_nr; i++, co++) { + if (!hashcmp(co->sha1, sha1)) + return co; + } + if (!hashcmp(sha1, empty_tree.sha1)) + return &empty_tree; + return NULL; +} + +int mkdir_in_gitdir(const char *path) +{ + if (mkdir(path, 0777)) { + int saved_errno = errno; + struct stat st; + struct strbuf sb = STRBUF_INIT; + + if (errno != EEXIST) + return -1; + /* + * Are we looking at a path in a symlinked worktree + * whose original repository does not yet have it? + * e.g. .git/rr-cache pointing at its original + * repository in which the user hasn't performed any + * conflict resolution yet? + */ + if (lstat(path, &st) || !S_ISLNK(st.st_mode) || + strbuf_readlink(&sb, path, st.st_size) || + !is_absolute_path(sb.buf) || + mkdir(sb.buf, 0777)) { + strbuf_release(&sb); + errno = saved_errno; + return -1; + } + strbuf_release(&sb); + } + return adjust_shared_perm(path); +} int safe_create_leading_directories(char *path) { @@ -167,6 +226,7 @@ struct alternate_object_database *alt_odb_list; static struct alternate_object_database **alt_odb_tail; static void read_info_alternates(const char * alternates, int depth); +static int git_open_noatime(const char *name); /* * Prepare alternate object database registry. @@ -300,7 +360,7 @@ static void read_info_alternates(const char * relative_base, int depth) int fd; sprintf(path, "%s/%s", relative_base, alt_file_name); - fd = git_open_noatime(path, NULL); + fd = git_open_noatime(path); if (fd < 0) return; if (fstat(fd, &st) || (st.st_size == 0)) { @@ -382,6 +442,8 @@ static unsigned int pack_used_ctr; static unsigned int pack_mmap_calls; static unsigned int peak_pack_open_windows; static unsigned int pack_open_windows; +static unsigned int pack_open_fds; +static unsigned int pack_max_fds; static size_t peak_pack_mapped; static size_t pack_mapped; struct packed_git *packed_git; @@ -413,7 +475,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) struct pack_idx_header *hdr; size_t idx_size; uint32_t version, nr, i, *index; - int fd = git_open_noatime(path, p); + int fd = git_open_noatime(path); struct stat st; if (fd < 0) @@ -559,8 +621,10 @@ static int unuse_one_window(struct packed_git *current, int keep_fd) lru_l->next = lru_w->next; else { lru_p->windows = lru_w->next; - if (!lru_p->windows && lru_p->pack_fd != keep_fd) { + if (!lru_p->windows && lru_p->pack_fd != -1 + && lru_p->pack_fd != keep_fd) { close(lru_p->pack_fd); + pack_open_fds--; lru_p->pack_fd = -1; } } @@ -645,8 +709,10 @@ void free_pack_by_name(const char *pack_name) if (strcmp(pack_name, p->pack_name) == 0) { clear_delta_base_cache(); close_pack_windows(p); - if (p->pack_fd != -1) + if (p->pack_fd != -1) { close(p->pack_fd); + pack_open_fds--; + } close_pack_index(p); free(p->bad_object_sha1); *pp = p->next; @@ -672,9 +738,29 @@ static int open_packed_git_1(struct packed_git *p) if (!p->index_data && open_pack_index(p)) return error("packfile %s index unavailable", p->pack_name); - p->pack_fd = git_open_noatime(p->pack_name, p); + if (!pack_max_fds) { + struct rlimit lim; + unsigned int max_fds; + + if (getrlimit(RLIMIT_NOFILE, &lim)) + die_errno("cannot get RLIMIT_NOFILE"); + + max_fds = lim.rlim_cur; + + /* Save 3 for stdin/stdout/stderr, 22 for work */ + if (25 < max_fds) + pack_max_fds = max_fds - 25; + else + pack_max_fds = 1; + } + + while (pack_max_fds <= pack_open_fds && unuse_one_window(NULL, -1)) + ; /* nothing */ + + p->pack_fd = git_open_noatime(p->pack_name); if (p->pack_fd < 0 || fstat(p->pack_fd, &st)) return -1; + pack_open_fds++; /* If we created the struct before we had the pack we lack size. */ if (!p->pack_size) { @@ -726,6 +812,7 @@ static int open_packed_git(struct packed_git *p) return 0; if (p->pack_fd != -1) { close(p->pack_fd); + pack_open_fds--; p->pack_fd = -1; } return -1; @@ -751,14 +838,13 @@ unsigned char *use_pack(struct packed_git *p, { struct pack_window *win = *w_cursor; - if (p->pack_fd == -1 && open_packed_git(p)) - die("packfile %s cannot be accessed", p->pack_name); - /* Since packfiles end in a hash of their content and it's * pointless to ask for an offset into the middle of that * hash, and the in_window function above wouldn't match * don't allow an offset too close to the end of the file. */ + if (!p->pack_size && p->pack_fd == -1 && open_packed_git(p)) + die("packfile %s cannot be accessed", p->pack_name); if (offset > (p->pack_size - 20)) die("offset beyond end of packfile (truncated pack?)"); @@ -772,6 +858,10 @@ unsigned char *use_pack(struct packed_git *p, if (!win) { size_t window_align = packed_git_window_size / 2; off_t len; + + if (p->pack_fd == -1 && open_packed_git(p)) + die("packfile %s cannot be accessed", p->pack_name); + win = xcalloc(1, sizeof(*win)); win->offset = (offset / window_align) * window_align; len = p->pack_size - win->offset; @@ -789,6 +879,12 @@ unsigned char *use_pack(struct packed_git *p, die("packfile %s cannot be mapped: %s", p->pack_name, strerror(errno)); + if (!win->offset && win->len == p->pack_size + && !p->do_not_close) { + close(p->pack_fd); + pack_open_fds--; + p->pack_fd = -1; + } pack_mmap_calls++; pack_open_windows++; if (pack_mapped > peak_pack_mapped) @@ -883,6 +979,9 @@ struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path) void install_packed_git(struct packed_git *pack) { + if (pack->pack_fd != -1) + pack_open_fds++; + pack->next = packed_git; packed_git = pack; } @@ -900,8 +999,6 @@ static void prepare_packed_git_one(char *objdir, int local) sprintf(path, "%s/pack", objdir); len = strlen(path); dir = opendir(path); - while (!dir && errno == EMFILE && unuse_one_window(NULL, -1)) - dir = opendir(path); if (!dir) { if (errno != ENOENT) error("unable to open object pack directory: %s: %s", @@ -1048,7 +1145,7 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz return hashcmp(sha1, real_sha1) ? -1 : 0; } -static int git_open_noatime(const char *name, struct packed_git *p) +static int git_open_noatime(const char *name) { static int sha1_file_open_flag = O_NOATIME; @@ -1057,14 +1154,6 @@ static int git_open_noatime(const char *name, struct packed_git *p) if (fd >= 0) return fd; - /* Might the failure be insufficient file descriptors? */ - if (errno == EMFILE) { - if (unuse_one_window(p, -1)) - continue; - else - return -1; - } - /* Might the failure be due to O_NOATIME? */ if (errno != ENOENT && sha1_file_open_flag) { sha1_file_open_flag = 0; @@ -1081,7 +1170,7 @@ static int open_sha1_file(const unsigned char *sha1) char *name = sha1_file_name(sha1); struct alternate_object_database *alt; - fd = git_open_noatime(name, NULL); + fd = git_open_noatime(name); if (fd >= 0) return fd; @@ -1090,7 +1179,7 @@ static int open_sha1_file(const unsigned char *sha1) for (alt = alt_odb_list; alt; alt = alt->next) { name = alt->name; fill_sha1_path(name, sha1); - fd = git_open_noatime(alt->base, NULL); + fd = git_open_noatime(alt->base); if (fd >= 0) return fd; } @@ -1219,7 +1308,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size /* * The above condition must be (bytes <= size), not * (bytes < size). In other words, even though we - * expect no more output and set avail_out to zer0, + * expect no more output and set avail_out to zero, * the input zlib stream may have bytes that express * "this concludes the stream", and we *do* want to * eat that input. @@ -1445,7 +1534,7 @@ static int unpack_object_header(struct packed_git *p, enum object_type type; /* use_pack() assures us we have [base, base + 20) available - * as a range that we can look at at. (Its actually the hash + * as a range that we can look at. (Its actually the hash * size that is assured.) With our object header encoding * the maximum deflated object size is 2^137, which is just * insane, so we know won't exceed what we have been given. @@ -1896,6 +1985,27 @@ off_t find_pack_entry_one(const unsigned char *sha1, return 0; } +static int is_pack_valid(struct packed_git *p) +{ + /* An already open pack is known to be valid. */ + if (p->pack_fd != -1) + return 1; + + /* If the pack has one window completely covering the + * file size, the pack is known to be valid even if + * the descriptor is not currently open. + */ + if (p->windows) { + struct pack_window *w = p->windows; + + if (!w->offset && w->len == p->pack_size) + return 1; + } + + /* Force the pack to open to prove its valid. */ + return !open_packed_git(p); +} + static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) { static struct packed_git *last_found = (void *)1; @@ -1925,7 +2035,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) * it may have been deleted since the index * was loaded! */ - if (p->pack_fd == -1 && open_packed_git(p)) { + if (!is_pack_valid(p)) { error("packfile %s cannot be accessed", p->pack_name); goto next; } @@ -1985,9 +2095,17 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size int sha1_object_info(const unsigned char *sha1, unsigned long *sizep) { + struct cached_object *co; struct pack_entry e; int status; + co = find_cached_object(sha1); + if (co) { + if (sizep) + *sizep = co->size; + return co->type; + } + if (!find_pack_entry(sha1, &e)) { /* Most likely it's a loose object. */ status = sha1_loose_object_info(sha1, sizep); @@ -2033,41 +2151,6 @@ static void *read_packed_sha1(const unsigned char *sha1, return data; } -/* - * This is meant to hold a *small* number of objects that you would - * want read_sha1_file() to be able to return, but yet you do not want - * to write them into the object store (e.g. a browse-only - * application). - */ -static struct cached_object { - unsigned char sha1[20]; - enum object_type type; - void *buf; - unsigned long size; -} *cached_objects; -static int cached_object_nr, cached_object_alloc; - -static struct cached_object empty_tree = { - EMPTY_TREE_SHA1_BIN, - OBJ_TREE, - "", - 0 -}; - -static struct cached_object *find_cached_object(const unsigned char *sha1) -{ - int i; - struct cached_object *co = cached_objects; - - for (i = 0; i < cached_object_nr; i++, co++) { - if (!hashcmp(co->sha1, sha1)) - return co; - } - if (!hashcmp(sha1, empty_tree.sha1)) - return &empty_tree; - return NULL; -} - int pretend_sha1_file(void *buf, unsigned long len, enum object_type type, unsigned char *sha1) { @@ -2123,23 +2206,21 @@ static void *read_object(const unsigned char *sha1, enum object_type *type, * deal with them should arrange to call read_object() and give error * messages themselves. */ -void *read_sha1_file_repl(const unsigned char *sha1, - enum object_type *type, - unsigned long *size, - const unsigned char **replacement) +void *read_sha1_file_extended(const unsigned char *sha1, + enum object_type *type, + unsigned long *size, + unsigned flag) { - const unsigned char *repl = lookup_replace_object(sha1); void *data; char *path; const struct packed_git *p; + const unsigned char *repl = (flag & READ_SHA1_FILE_REPLACE) + ? lookup_replace_object(sha1) : sha1; errno = 0; data = read_object(repl, type, size); - if (data) { - if (replacement) - *replacement = repl; + if (data) return data; - } if (errno && errno != ENOENT) die_errno("failed to read object %s", sha1_to_hex(sha1)); @@ -2351,8 +2432,6 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, filename = sha1_file_name(sha1); fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename); - while (fd < 0 && errno == EMFILE && unuse_one_window(NULL, -1)) - fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename); if (fd < 0) { if (errno == EACCES) return error("insufficient permission for adding an object to repository database %s\n", get_object_directory()); @@ -2471,10 +2550,40 @@ int has_sha1_file(const unsigned char *sha1) return has_loose_object(sha1); } +static void check_tree(const void *buf, size_t size) +{ + struct tree_desc desc; + struct name_entry entry; + + init_tree_desc(&desc, buf, size); + while (tree_entry(&desc, &entry)) + /* do nothing + * tree_entry() will die() on malformed entries */ + ; +} + +static void check_commit(const void *buf, size_t size) +{ + struct commit c; + memset(&c, 0, sizeof(c)); + if (parse_commit_buffer(&c, buf, size)) + die("corrupt commit"); +} + +static void check_tag(const void *buf, size_t size) +{ + struct tag t; + memset(&t, 0, sizeof(t)); + if (parse_tag_buffer(&t, buf, size)) + die("corrupt tag"); +} + static int index_mem(unsigned char *sha1, void *buf, size_t size, - int write_object, enum object_type type, const char *path) + enum object_type type, + const char *path, unsigned flags) { int ret, re_allocated = 0; + int write_object = flags & HASH_WRITE_OBJECT; if (!type) type = OBJ_BLOB; @@ -2490,6 +2599,14 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size, re_allocated = 1; } } + if (flags & HASH_FORMAT_CHECK) { + if (type == OBJ_TREE) + check_tree(buf, size); + if (type == OBJ_COMMIT) + check_commit(buf, size); + if (type == OBJ_TAG) + check_tag(buf, size); + } if (write_object) ret = write_sha1_file(buf, size, typename(type), sha1); @@ -2500,42 +2617,141 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size, return ret; } +static int index_pipe(unsigned char *sha1, int fd, enum object_type type, + const char *path, unsigned flags) +{ + struct strbuf sbuf = STRBUF_INIT; + int ret; + + if (strbuf_read(&sbuf, fd, 4096) >= 0) + ret = index_mem(sha1, sbuf.buf, sbuf.len, type, path, flags); + else + ret = -1; + strbuf_release(&sbuf); + return ret; +} + #define SMALL_FILE_SIZE (32*1024) -int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, - enum object_type type, const char *path) +static int index_core(unsigned char *sha1, int fd, size_t size, + enum object_type type, const char *path, + unsigned flags) { int ret; - size_t size = xsize_t(st->st_size); - if (!S_ISREG(st->st_mode)) { - struct strbuf sbuf = STRBUF_INIT; - if (strbuf_read(&sbuf, fd, 4096) >= 0) - ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object, - type, path); - else - ret = -1; - strbuf_release(&sbuf); - } else if (!size) { - ret = index_mem(sha1, NULL, size, write_object, type, path); + if (!size) { + ret = index_mem(sha1, NULL, size, type, path, flags); } else if (size <= SMALL_FILE_SIZE) { char *buf = xmalloc(size); if (size == read_in_full(fd, buf, size)) - ret = index_mem(sha1, buf, size, write_object, type, - path); + ret = index_mem(sha1, buf, size, type, path, flags); else ret = error("short read %s", strerror(errno)); free(buf); } else { void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - ret = index_mem(sha1, buf, size, write_object, type, path); + ret = index_mem(sha1, buf, size, type, path, flags); munmap(buf, size); } + return ret; +} + +/* + * This creates one packfile per large blob, because the caller + * immediately wants the result sha1, and fast-import can report the + * object name via marks mechanism only by closing the created + * packfile. + * + * This also bypasses the usual "convert-to-git" dance, and that is on + * purpose. We could write a streaming version of the converting + * functions and insert that before feeding the data to fast-import + * (or equivalent in-core API described above), but the primary + * motivation for trying to stream from the working tree file and to + * avoid mmaping it in core is to deal with large binary blobs, and + * by definition they do _not_ want to get any conversion. + */ +static int index_stream(unsigned char *sha1, int fd, size_t size, + enum object_type type, const char *path, + unsigned flags) +{ + struct child_process fast_import; + char export_marks[512]; + const char *argv[] = { "fast-import", "--quiet", export_marks, NULL }; + char tmpfile[512]; + char fast_import_cmd[512]; + char buf[512]; + int len, tmpfd; + + strcpy(tmpfile, git_path("hashstream_XXXXXX")); + tmpfd = git_mkstemp_mode(tmpfile, 0600); + if (tmpfd < 0) + die_errno("cannot create tempfile: %s", tmpfile); + if (close(tmpfd)) + die_errno("cannot close tempfile: %s", tmpfile); + sprintf(export_marks, "--export-marks=%s", tmpfile); + + memset(&fast_import, 0, sizeof(fast_import)); + fast_import.in = -1; + fast_import.argv = argv; + fast_import.git_cmd = 1; + if (start_command(&fast_import)) + die_errno("index-stream: git fast-import failed"); + + len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n", + (unsigned long) size); + write_or_whine(fast_import.in, fast_import_cmd, len, + "index-stream: feeding fast-import"); + while (size) { + char buf[10240]; + size_t sz = size < sizeof(buf) ? size : sizeof(buf); + size_t actual; + + actual = read_in_full(fd, buf, sz); + if (actual < 0) + die_errno("index-stream: reading input"); + if (write_in_full(fast_import.in, buf, actual) != actual) + die_errno("index-stream: feeding fast-import"); + size -= actual; + } + if (close(fast_import.in)) + die_errno("index-stream: closing fast-import"); + if (finish_command(&fast_import)) + die_errno("index-stream: finishing fast-import"); + + tmpfd = open(tmpfile, O_RDONLY); + if (tmpfd < 0) + die_errno("index-stream: cannot open fast-import mark"); + len = read(tmpfd, buf, sizeof(buf)); + if (len < 0) + die_errno("index-stream: reading fast-import mark"); + if (close(tmpfd) < 0) + die_errno("index-stream: closing fast-import mark"); + if (unlink(tmpfile)) + die_errno("index-stream: unlinking fast-import mark"); + if (len != 44 || + memcmp(":1 ", buf, 3) || + get_sha1_hex(buf + 3, sha1)) + die_errno("index-stream: unexpected fast-import mark: <%s>", buf); + return 0; +} + +int index_fd(unsigned char *sha1, int fd, struct stat *st, + enum object_type type, const char *path, unsigned flags) +{ + int ret; + size_t size = xsize_t(st->st_size); + + if (!S_ISREG(st->st_mode)) + ret = index_pipe(sha1, fd, type, path, flags); + else if (size <= big_file_threshold || type != OBJ_BLOB) + ret = index_core(sha1, fd, size, type, path, flags); + else + ret = index_stream(sha1, fd, size, type, path, flags); close(fd); return ret; } -int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object) +int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags) { int fd; struct strbuf sb = STRBUF_INIT; @@ -2546,7 +2762,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write if (fd < 0) return error("open(\"%s\"): %s", path, strerror(errno)); - if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path) < 0) + if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0) return error("%s: failed to insert into database", path); break; @@ -2556,7 +2772,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write return error("readlink(\"%s\"): %s", path, errstr); } - if (!write_object) + if (!(flags & HASH_WRITE_OBJECT)) hash_sha1_file(sb.buf, sb.len, blob_type, sha1); else if (write_sha1_file(sb.buf, sb.len, blob_type, sha1)) return error("%s: failed to insert into database", diff --git a/sha1_name.c b/sha1_name.c index 709ff2eee6..ff5992acc9 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -208,9 +208,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) if (exists ? !status : status == SHORT_NAME_NOT_FOUND) { - int cut_at = len + unique_abbrev_extra_length; - cut_at = (cut_at < 40) ? cut_at : 40; - hex[cut_at] = 0; + hex[len] = 0; return hex; } len++; @@ -1014,11 +1012,13 @@ static void diagnose_invalid_sha1_path(const char *prefix, if (!get_tree_entry(tree_sha1, fullname, sha1, &mode)) { die("Path '%s' exists, but not '%s'.\n" - "Did you mean '%s:%s'?", + "Did you mean '%s:%s' aka '%s:./%s'?", fullname, filename, object_name, - fullname); + fullname, + object_name, + filename); } die("Path '%s' does not exist in '%s'", filename, object_name); @@ -1067,9 +1067,10 @@ static void diagnose_invalid_index_path(int stage, if (ce_namelen(ce) == fullnamelen && !memcmp(ce->name, fullname, fullnamelen)) die("Path '%s' is in the index, but not '%s'.\n" - "Did you mean ':%d:%s'?", + "Did you mean ':%d:%s' aka ':%d:./%s'?", fullname, filename, - ce_stage(ce), fullname); + ce_stage(ce), fullname, + ce_stage(ce), filename); } if (!lstat(filename, &st)) @@ -1082,11 +1083,12 @@ 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) +int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, + int only_to_die, const char *prefix) { struct object_context oc; int ret; - ret = get_sha1_with_context_1(name, sha1, &oc, gently, prefix); + ret = get_sha1_with_context_1(name, sha1, &oc, only_to_die, prefix); *mode = oc.mode; return ret; } @@ -1110,7 +1112,7 @@ static char *resolve_relative_path(const char *rel) int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *oc, - int gently, const char *prefix) + int only_to_die, const char *prefix) { int ret, bracket_depth; int namelen = strlen(name); @@ -1132,7 +1134,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct cache_entry *ce; char *new_path = NULL; int pos; - if (namelen > 2 && name[1] == '/') { + if (!only_to_die && namelen > 2 && name[1] == '/') { struct commit_list *list = NULL; for_each_ref(handle_one_ref, &list); return get_sha1_oneline(name + 2, sha1, list); @@ -1175,7 +1177,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, } pos++; } - if (!gently) + if (only_to_die && name[1] && name[1] != '/') diagnose_invalid_index_path(stage, prefix, cp); free(new_path); return -1; @@ -1191,7 +1193,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, if (*cp == ':') { unsigned char tree_sha1[20]; char *object_name = NULL; - if (!gently) { + if (only_to_die) { object_name = xmalloc(cp-name+1); strncpy(object_name, name, cp-name); object_name[cp-name] = '\0'; @@ -1204,7 +1206,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, if (new_filename) filename = new_filename; ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode); - if (!gently) { + if (only_to_die) { diagnose_invalid_sha1_path(prefix, filename, tree_sha1, object_name); free(object_name); @@ -1217,7 +1219,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, free(new_filename); return ret; } else { - if (!gently) + if (only_to_die) die("Invalid object name '%s'.", object_name); } } @@ -137,6 +137,8 @@ int main(int argc, char **argv) int devnull_fd; int count; + git_extract_argv0_path(argv[0]); + /* * Always open file descriptors 0/1/2 to avoid clobbering files * in die(). It also avoids not messing up when the pipes are @@ -30,8 +30,10 @@ void strbuf_init(struct strbuf *sb, size_t hint) { sb->alloc = sb->len = 0; sb->buf = strbuf_slopbuf; - if (hint) + if (hint) { strbuf_grow(sb, hint); + sb->buf[0] = '\0'; + } } void strbuf_release(struct strbuf *sb) @@ -63,7 +65,8 @@ void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc) void strbuf_grow(struct strbuf *sb, size_t extra) { - if (sb->len + extra + 1 <= sb->len) + if (unsigned_add_overflows(extra, 1) || + unsigned_add_overflows(sb->len, extra + 1)) die("you want to use way too much memory"); if (!sb->alloc) sb->buf = NULL; @@ -152,7 +155,7 @@ int strbuf_cmp(const struct strbuf *a, const struct strbuf *b) void strbuf_splice(struct strbuf *sb, size_t pos, size_t len, const void *data, size_t dlen) { - if (pos + len < pos) + if (unsigned_add_overflows(pos, len)) die("you want to use way too much memory"); if (pos > sb->len) die("`pos' is too far after the end of the buffer"); @@ -194,24 +197,29 @@ void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len) void strbuf_addf(struct strbuf *sb, const char *fmt, ...) { - int len; va_list ap; + va_start(ap, fmt); + strbuf_vaddf(sb, fmt, ap); + va_end(ap); +} + +void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap) +{ + int len; + va_list cp; if (!strbuf_avail(sb)) strbuf_grow(sb, 64); - va_start(ap, fmt); - len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); - va_end(ap); + va_copy(cp, ap); + len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, cp); + va_end(cp); if (len < 0) - die("your vsnprintf is broken"); + die("BUG: your vsnprintf is broken (returned %d)", len); if (len > strbuf_avail(sb)) { strbuf_grow(sb, len); - va_start(ap, fmt); len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); - va_end(ap); - if (len > strbuf_avail(sb)) { - die("this should not happen, your snprintf is broken"); - } + if (len > strbuf_avail(sb)) + die("BUG: your vsnprintf is broken (insatiable)"); } strbuf_setlen(sb, sb->len + len); } @@ -1,44 +1,7 @@ #ifndef STRBUF_H #define STRBUF_H -/* - * Strbuf's can be use in many ways: as a byte array, or to store arbitrary - * long, overflow safe strings. - * - * Strbufs has some invariants that are very important to keep in mind: - * - * 1. the ->buf member is always malloc-ed, hence strbuf's can be used to - * build complex strings/buffers whose final size isn't easily known. - * - * It is NOT legal to copy the ->buf pointer away. - * `strbuf_detach' is the operation that detaches a buffer from its shell - * while keeping the shell valid wrt its invariants. - * - * 2. the ->buf member is a byte array that has at least ->len + 1 bytes - * allocated. The extra byte is used to store a '\0', allowing the ->buf - * member to be a valid C-string. Every strbuf function ensures this - * invariant is preserved. - * - * Note that it is OK to "play" with the buffer directly if you work it - * that way: - * - * strbuf_grow(sb, SOME_SIZE); - * ... Here, the memory array starting at sb->buf, and of length - * ... strbuf_avail(sb) is all yours, and you are sure that - * ... strbuf_avail(sb) is at least SOME_SIZE. - * strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE); - * - * Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb). - * - * Doing so is safe, though if it has to be done in many places, adding the - * missing API to the strbuf module is the way to go. - * - * XXX: do _not_ assume that the area that is yours is of size ->alloc - 1 - * even if it's true in the current implementation. Alloc is somehow a - * "private" member that should not be messed with. - */ - -#include <assert.h> +/* See Documentation/technical/api-strbuf.txt */ extern char strbuf_slopbuf[]; struct strbuf { @@ -68,9 +31,8 @@ static inline size_t strbuf_avail(const struct strbuf *sb) { extern void strbuf_grow(struct strbuf *, size_t); static inline void strbuf_setlen(struct strbuf *sb, size_t len) { - if (!sb->alloc) - strbuf_grow(sb, 0); - assert(len < sb->alloc); + if (len > (sb->alloc ? sb->alloc - 1 : 0)) + die("BUG: strbuf_setlen() beyond buffer"); sb->len = len; sb->buf[len] = '\0'; } @@ -120,6 +82,8 @@ extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf * __attribute__((format (printf,2,3))) extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); +__attribute__((format (printf,2,0))) +extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap); extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); /* XXX: if read fails, any partial read is undone */ diff --git a/string-list.c b/string-list.c index 9b023a2584..51681189e8 100644 --- a/string-list.c +++ b/string-list.c @@ -153,6 +153,7 @@ struct string_list_item *string_list_append(struct string_list *list, const char ALLOC_GROW(list->items, list->nr + 1, list->alloc); list->items[list->nr].string = list->strdup_strings ? xstrdup(string) : (char *)string; + list->items[list->nr].util = NULL; return list->items + list->nr++; } diff --git a/string-list.h b/string-list.h index 494693898b..bda6983983 100644 --- a/string-list.h +++ b/string-list.h @@ -5,8 +5,7 @@ struct string_list_item { char *string; void *util; }; -struct string_list -{ +struct string_list { struct string_list_item *items; unsigned int nr, alloc; unsigned int strdup_strings:1; diff --git a/submodule.c b/submodule.c index 6f1c10722f..b6dec70bd1 100644 --- a/submodule.c +++ b/submodule.c @@ -9,10 +9,20 @@ #include "refs.h" #include "string-list.h" -struct string_list config_name_for_path; -struct string_list config_fetch_recurse_submodules_for_name; -struct string_list config_ignore_for_name; -static int config_fetch_recurse_submodules; +static struct string_list config_name_for_path; +static struct string_list config_fetch_recurse_submodules_for_name; +static struct string_list config_ignore_for_name; +static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; +static struct string_list changed_submodule_paths; +/* + * The following flag is set if the .gitmodules file is unmerged. We then + * disable recursion for all submodules where .git/config doesn't have a + * matching config entry because we can't guess what might be configured in + * .gitmodules unless the user resolves the conflict. When a command line + * option is given (which always overrides configuration) this flag will be + * ignored. + */ +static int gitmodules_is_unmerged; static int add_submodule_odb(const char *path) { @@ -62,6 +72,8 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util); if (ignore_option) handle_ignore_submodules_arg(diffopt, ignore_option->util); + else if (gitmodules_is_unmerged) + DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); } } @@ -70,7 +82,7 @@ int submodule_config(const char *var, const char *value, void *cb) if (!prefixcmp(var, "submodule.")) return parse_submodule_config_option(var, value); else if (!strcmp(var, "fetch.recursesubmodules")) { - config_fetch_recurse_submodules = git_config_bool(var, value); + config_fetch_recurse_submodules = parse_fetch_recurse_submodules_arg(var, value); return 0; } return 0; @@ -81,9 +93,24 @@ void gitmodules_config(void) const char *work_tree = get_git_work_tree(); if (work_tree) { struct strbuf gitmodules_path = STRBUF_INIT; + int pos; strbuf_addstr(&gitmodules_path, work_tree); strbuf_addstr(&gitmodules_path, "/.gitmodules"); - git_config_from_file(submodule_config, gitmodules_path.buf, NULL); + if (read_cache() < 0) + die("index file corrupt"); + pos = cache_name_pos(".gitmodules", 11); + if (pos < 0) { /* .gitmodules not found or isn't merged */ + pos = -1 - pos; + if (active_nr > pos) { /* there is a .gitmodules */ + const struct cache_entry *ce = active_cache[pos]; + if (ce_namelen(ce) == 11 && + !memcmp(ce->name, ".gitmodules", 11)) + gitmodules_is_unmerged = 1; + } + } + + if (!gitmodules_is_unmerged) + git_config_from_file(submodule_config, gitmodules_path.buf, NULL); strbuf_release(&gitmodules_path); } } @@ -112,7 +139,7 @@ int parse_submodule_config_option(const char *var, const char *value) if (!config) config = string_list_append(&config_fetch_recurse_submodules_for_name, strbuf_detach(&submodname, NULL)); - config->util = git_config_bool(var, value) ? (void *)1 : NULL; + config->util = (void *)(intptr_t)parse_fetch_recurse_submodules_arg(var, value); strbuf_release(&submodname); } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) { if (strcmp(value, "untracked") && strcmp(value, "dirty") && @@ -152,17 +179,83 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, die("bad --ignore-submodules argument: %s", arg); } +static int prepare_submodule_summary(struct rev_info *rev, const char *path, + struct commit *left, struct commit *right, + int *fast_forward, int *fast_backward) +{ + struct commit_list *merge_bases, *list; + + init_revisions(rev, NULL); + setup_revisions(0, NULL, rev, NULL); + rev->left_right = 1; + rev->first_parent_only = 1; + left->object.flags |= SYMMETRIC_LEFT; + add_pending_object(rev, &left->object, path); + add_pending_object(rev, &right->object, path); + merge_bases = get_merge_bases(left, right, 1); + if (merge_bases) { + if (merge_bases->item == left) + *fast_forward = 1; + else if (merge_bases->item == right) + *fast_backward = 1; + } + for (list = merge_bases; list; list = list->next) { + list->item->object.flags |= UNINTERESTING; + add_pending_object(rev, &list->item->object, + sha1_to_hex(list->item->object.sha1)); + } + return prepare_revision_walk(rev); +} + +static void print_submodule_summary(struct rev_info *rev, FILE *f, + const char *del, const char *add, const char *reset) +{ + static const char format[] = " %m %s"; + struct strbuf sb = STRBUF_INIT; + struct commit *commit; + + while ((commit = get_revision(rev))) { + struct pretty_print_context ctx = {0}; + ctx.date_mode = rev->date_mode; + strbuf_setlen(&sb, 0); + if (commit->object.flags & SYMMETRIC_LEFT) { + if (del) + strbuf_addstr(&sb, del); + } + else if (add) + strbuf_addstr(&sb, add); + format_commit_message(commit, format, &sb, &ctx); + if (reset) + strbuf_addstr(&sb, reset); + strbuf_addch(&sb, '\n'); + fprintf(f, "%s", sb.buf); + } + strbuf_release(&sb); +} + +int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) +{ + switch (git_config_maybe_bool(opt, arg)) { + case 1: + return RECURSE_SUBMODULES_ON; + case 0: + return RECURSE_SUBMODULES_OFF; + default: + if (!strcmp(arg, "on-demand")) + return RECURSE_SUBMODULES_ON_DEMAND; + die("bad %s argument: %s", opt, arg); + } +} + 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) { struct rev_info rev; - struct commit *commit, *left = left, *right = right; - struct commit_list *merge_bases, *list; + struct commit *left = left, *right = right; const char *message = NULL; struct strbuf sb = STRBUF_INIT; - static const char *format = " %m %s"; int fast_forward = 0, fast_backward = 0; if (is_null_sha1(two)) @@ -175,29 +268,10 @@ void show_submodule_summary(FILE *f, const char *path, !(right = lookup_commit_reference(two))) message = "(commits not present)"; - if (!message) { - init_revisions(&rev, NULL); - setup_revisions(0, NULL, &rev, NULL); - rev.left_right = 1; - rev.first_parent_only = 1; - left->object.flags |= SYMMETRIC_LEFT; - add_pending_object(&rev, &left->object, path); - add_pending_object(&rev, &right->object, path); - merge_bases = get_merge_bases(left, right, 1); - if (merge_bases) { - if (merge_bases->item == left) - fast_forward = 1; - else if (merge_bases->item == right) - fast_backward = 1; - } - for (list = merge_bases; list; list = list->next) { - list->item->object.flags |= UNINTERESTING; - add_pending_object(&rev, &list->item->object, - sha1_to_hex(list->item->object.sha1)); - } - if (prepare_revision_walk(&rev)) - message = "(revision walker failed)"; - } + if (!message && + prepare_submodule_summary(&rev, path, left, right, + &fast_forward, &fast_backward)) + message = "(revision walker failed)"; if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) fprintf(f, "Submodule %s contains untracked content\n", path); @@ -221,25 +295,11 @@ void show_submodule_summary(FILE *f, const char *path, fwrite(sb.buf, sb.len, 1, f); if (!message) { - while ((commit = get_revision(&rev))) { - struct pretty_print_context ctx = {0}; - ctx.date_mode = rev.date_mode; - strbuf_setlen(&sb, 0); - if (commit->object.flags & SYMMETRIC_LEFT) { - if (del) - strbuf_addstr(&sb, del); - } - else if (add) - strbuf_addstr(&sb, add); - format_commit_message(commit, format, &sb, &ctx); - if (reset) - strbuf_addstr(&sb, reset); - strbuf_addch(&sb, '\n'); - fprintf(f, "%s", sb.buf); - } + print_submodule_summary(&rev, f, del, add, reset); clear_commit_marks(left, ~0); clear_commit_marks(right, ~0); } + strbuf_release(&sb); } @@ -248,27 +308,122 @@ void set_config_fetch_recurse_submodules(int value) config_fetch_recurse_submodules = value; } +static int is_submodule_commit_present(const char *path, unsigned char sha1[20]) +{ + int is_present = 0; + if (!add_submodule_odb(path) && lookup_commit_reference(sha1)) { + /* Even if the submodule is checked out and the commit is + * present, make sure it is reachable from a ref. */ + struct child_process cp; + const char *argv[] = {"rev-list", "-n", "1", NULL, "--not", "--all", NULL}; + struct strbuf buf = STRBUF_INIT; + + argv[3] = sha1_to_hex(sha1); + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = local_repo_env; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.out = -1; + cp.dir = path; + if (!run_command(&cp) && !strbuf_read(&buf, cp.out, 1024)) + is_present = 1; + + close(cp.out); + strbuf_release(&buf); + } + return is_present; +} + +static void submodule_collect_changed_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (!S_ISGITLINK(p->two->mode)) + continue; + + if (S_ISGITLINK(p->one->mode)) { + /* NEEDSWORK: We should honor the name configured in + * the .gitmodules file of the commit we are examining + * here to be able to correctly follow submodules + * being moved around. */ + struct string_list_item *path; + path = unsorted_string_list_lookup(&changed_submodule_paths, p->two->path); + if (!path && !is_submodule_commit_present(p->two->path, p->two->sha1)) + string_list_append(&changed_submodule_paths, xstrdup(p->two->path)); + } else { + /* Submodule is new or was moved here */ + /* NEEDSWORK: When the .git directories of submodules + * live inside the superprojects .git directory some + * day we should fetch new submodules directly into + * that location too when config or options request + * that so they can be checked out from there. */ + continue; + } + } +} + +void check_for_new_submodule_commits(unsigned char new_sha1[20]) +{ + struct rev_info rev; + struct commit *commit; + const char *argv[] = {NULL, NULL, "--not", "--all", NULL}; + int argc = ARRAY_SIZE(argv) - 1; + + init_revisions(&rev, NULL); + argv[1] = xstrdup(sha1_to_hex(new_sha1)); + setup_revisions(argc, argv, &rev, NULL); + if (prepare_revision_walk(&rev)) + die("revision walk setup failed"); + + /* + * Collect all submodules (whether checked out or not) for which new + * commits have been recorded upstream in "changed_submodule_paths". + */ + while ((commit = get_revision(&rev))) { + struct commit_list *parent = commit->parents; + while (parent) { + struct diff_options diff_opts; + diff_setup(&diff_opts); + diff_opts.output_format |= DIFF_FORMAT_CALLBACK; + diff_opts.format_callback = submodule_collect_changed_cb; + if (diff_setup_done(&diff_opts) < 0) + die("diff_setup_done failed"); + diff_tree_sha1(parent->item->object.sha1, commit->object.sha1, "", &diff_opts); + diffcore_std(&diff_opts); + diff_flush(&diff_opts); + parent = parent->next; + } + } + free((char *)argv[1]); +} + int fetch_populated_submodules(int num_options, const char **options, - const char *prefix, int ignore_config, + const char *prefix, int command_line_option, int quiet) { - int i, result = 0, argc = 0; + int i, result = 0, argc = 0, default_argc; struct child_process cp; const char **argv; struct string_list_item *name_for_path; const char *work_tree = get_git_work_tree(); if (!work_tree) - return 0; + goto out; if (!the_index.initialized) if (read_cache() < 0) die("index file corrupt"); - /* 4: "fetch" (options) "--submodule-prefix" prefix NULL */ - argv = xcalloc(num_options + 4, sizeof(const char *)); + /* 6: "fetch" (options) --recurse-submodules-default default "--submodule-prefix" prefix NULL */ + argv = xcalloc(num_options + 6, sizeof(const char *)); argv[argc++] = "fetch"; for (i = 0; i < num_options; i++) argv[argc++] = options[i]; + argv[argc++] = "--recurse-submodules-default"; + default_argc = argc++; argv[argc++] = "--submodule-prefix"; memset(&cp, 0, sizeof(cp)); @@ -282,7 +437,7 @@ int fetch_populated_submodules(int num_options, const char **options, struct strbuf submodule_git_dir = STRBUF_INIT; struct strbuf submodule_prefix = STRBUF_INIT; struct cache_entry *ce = active_cache[i]; - const char *git_dir, *name; + const char *git_dir, *name, *default_argv; if (!S_ISGITLINK(ce->ce_mode)) continue; @@ -292,16 +447,32 @@ int fetch_populated_submodules(int num_options, const char **options, if (name_for_path) name = name_for_path->util; - if (!ignore_config) { + default_argv = "yes"; + if (command_line_option == RECURSE_SUBMODULES_DEFAULT) { struct string_list_item *fetch_recurse_submodules_option; fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name); if (fetch_recurse_submodules_option) { - if (!fetch_recurse_submodules_option->util) + if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_OFF) continue; + if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_ON_DEMAND) { + if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name)) + continue; + default_argv = "on-demand"; + } } else { - if (!config_fetch_recurse_submodules) + if ((config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF) || + gitmodules_is_unmerged) continue; + if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) { + if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name)) + continue; + default_argv = "on-demand"; + } } + } else if (command_line_option == RECURSE_SUBMODULES_ON_DEMAND) { + if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name)) + continue; + default_argv = "on-demand"; } strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name); @@ -314,6 +485,7 @@ int fetch_populated_submodules(int num_options, const char **options, if (!quiet) printf("Fetching submodule %s%s\n", prefix, ce->name); cp.dir = submodule_path.buf; + argv[default_argc] = default_argv; argv[argc] = submodule_prefix.buf; if (run_command(&cp)) result = 1; @@ -323,6 +495,8 @@ int fetch_populated_submodules(int num_options, const char **options, strbuf_release(&submodule_prefix); } free(argv); +out: + string_list_clear(&changed_submodule_paths, 1); return result; } diff --git a/submodule.h b/submodule.h index 4729023aa5..5350b0d5a0 100644 --- a/submodule.h +++ b/submodule.h @@ -3,19 +3,28 @@ struct diff_options; +enum { + RECURSE_SUBMODULES_ON_DEMAND = -1, + RECURSE_SUBMODULES_OFF = 0, + RECURSE_SUBMODULES_DEFAULT = 1, + RECURSE_SUBMODULES_ON = 2 +}; + void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(); int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); +int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); 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); void set_config_fetch_recurse_submodules(int value); +void check_for_new_submodule_commits(unsigned char new_sha1[20]); int fetch_populated_submodules(int num_options, const char **options, - const char *prefix, int ignore_config, + const char *prefix, int command_line_option, int quiet); unsigned is_submodule_modified(const char *path, int ignore_untracked); int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], diff --git a/symlinks.c b/symlinks.c index 3cacebd91a..034943bda0 100644 --- a/symlinks.c +++ b/symlinks.c @@ -223,7 +223,7 @@ int check_leading_path(const char *name, int len) int flags; int match_len = lstat_cache_matchlen(cache, name, len, &flags, FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT); - if (flags & (FL_SYMLINK|FL_NOENT)) + if (flags & FL_NOENT) return 0; else if (flags & FL_DIR) return -1; @@ -79,6 +79,10 @@ appropriately before running "make". --debug:: This may help the person who is developing a new test. It causes the command defined with test_debug to run. + The "trash" directory (used to store all temporary data + during testing) is not deleted even if there are no + failed tests so that you can inspect its contents after + the test finished. --immediate:: This causes the test to immediately exit upon the first @@ -98,6 +102,13 @@ appropriately before running "make". not see any output, this option implies --verbose. For convenience, it also implies --tee. + Note that valgrind is run with the option --leak-check=no, + as the git process is short-lived and some errors are not + interesting. In order to run a single command under the same + conditions manually, you should set GIT_VALGRIND to point to + the 't/valgrind/' directory and use the commands under + 't/valgrind/bin/'. + --tee:: In addition to printing the test output to the terminal, write it to files named 't/test-results/$TEST_NAME.out'. @@ -190,7 +201,7 @@ we are testing. If you create files under t/ directory (i.e. here) that is not the top-level test script, never name the file to match the above pattern. The Makefile here considers all such files as the -top-level test script and tries to run all of them. A care is +top-level test script and tries to run all of them. Care is especially needed if you are creating a common test library file, similar to test-lib.sh, because such a library file may not be suitable for standalone execution. @@ -274,9 +285,8 @@ Do: - Check the test coverage for your tests. See the "Test coverage" below. - Don't blindly follow test coverage metrics, they're a good way to - spot if you've missed something. If a new function you added - doesn't have any coverage you're probably doing something wrong, + Don't blindly follow test coverage metrics; if a new function you added + doesn't have any coverage, then you're probably doing something wrong, but having 100% coverage doesn't necessarily mean that you tested everything. @@ -328,7 +338,7 @@ Keep in mind: Skipping tests -------------- -If you need to skip tests you should do so be using the three-arg form +If you need to skip tests you should do so by using the three-arg form of the test_* functions (see the "Test harness library" section below), e.g.: @@ -369,7 +379,7 @@ library for your script to use. - test_expect_success [<prereq>] <message> <script> - Usually takes two strings as parameter, and evaluates the + Usually takes two strings as parameters, and evaluates the <script>. If it yields success, test is considered successful. <message> should state what it is testing. @@ -380,7 +390,7 @@ library for your script to use. 'tree=$(git-write-tree)' If you supply three parameters the first will be taken to be a - prerequisite, see the test_set_prereq and test_have_prereq + prerequisite; see the test_set_prereq and test_have_prereq documentation below: test_expect_success TTY 'git --paginate rev-list uses a pager' \ @@ -420,7 +430,7 @@ library for your script to use. - test_tick Make commit and tag names consistent by setting the author and - committer times to defined stated. Subsequent calls will + committer times to defined state. Subsequent calls will advance the times by a fixed amount. - test_commit <message> [<filename> [<contents>]] @@ -436,7 +446,7 @@ 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 + - test_set_prereq <prereq> Set a test prerequisite to be used later with test_have_prereq. The test-lib will set some prerequisites for you, see the @@ -446,7 +456,7 @@ library for your script to use. test_have_prereq directly, or the three argument invocation of test_expect_success and test_expect_failure. - - test_have_prereq SOME PREREQ + - test_have_prereq <prereq> Check if we have a prerequisite previously set with test_set_prereq. The most common use of this directly is to skip @@ -516,12 +526,13 @@ library for your script to use. Check whether a file has the length it is expected to. - - test_path_is_file <file> [<diagnosis>] - test_path_is_dir <dir> [<diagnosis>] + - test_path_is_file <path> [<diagnosis>] + test_path_is_dir <path> [<diagnosis>] test_path_is_missing <path> [<diagnosis>] - Check whether a file/directory exists or doesn't. <diagnosis> will - be displayed if the test fails. + Check if the named path is a file, if the named path is a + directory, or if the named path does not exist, respectively, + and fail otherwise, showing the <diagnosis> text. - test_when_finished <script> @@ -577,6 +588,11 @@ use these, and "test_set_prereq" for how to define your own. Test is not run by root user, and an attempt to write to an unwritable file is expected to fail correctly. + - LIBPCRE + + Git was compiled with USE_LIBPCRE=YesPlease. Wrap any tests + that use git-grep --perl-regexp or git-grep -P in these. + Tips for Writing Tests ---------------------- diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index d34208cc27..c56a77d237 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -1,5 +1,5 @@ # This file isn't used as a test script directly, instead it is -# sourced from t8001-annotate.sh and t8001-blame.sh. +# sourced from t8001-annotate.sh and t8002-blame.sh. check_count () { head= @@ -124,3 +124,14 @@ test_expect_success \ test_expect_success \ 'some edit' \ 'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1' + +test_expect_success \ + 'an obfuscated email added' \ + 'echo "No robots allowed" > file.new && + cat file >> file.new && + mv file.new file && + GIT_AUTHOR_NAME="E" GIT_AUTHOR_EMAIL="E at test dot git" git commit -a -m "norobots"' + +test_expect_success \ + 'obfuscated email parsed' \ + 'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1' diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index b9bb95feaa..143eb1f240 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -82,7 +82,12 @@ gitweb_run () { } close O; ' gitweb.output && - if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi + if grep '^[[]' gitweb.log >/dev/null 2>&1; then + test_debug 'cat gitweb.log >&2' && + false + else + true + fi # gitweb.log is left for debugging # gitweb.output is used to parse HTTP output diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 3f24384371..b8996a373a 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -158,7 +158,6 @@ test_http_push_nonff() { ' 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_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" output ' } diff --git a/t/lib-read-tree.sh b/t/lib-read-tree.sh new file mode 100644 index 0000000000..abc2c6f57f --- /dev/null +++ b/t/lib-read-tree.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# +# Helper functions to check if read-tree would succeed/fail as expected with +# and without the dry-run option. They also test that the dry-run does not +# write the index and that together with -u it doesn't touch the work tree. +# +read_tree_must_succeed () { + git ls-files -s >pre-dry-run && + git read-tree -n "$@" && + git ls-files -s >post-dry-run && + test_cmp pre-dry-run post-dry-run && + git read-tree "$@" +} + +read_tree_must_fail () { + git ls-files -s >pre-dry-run && + test_must_fail git read-tree -n "$@" && + git ls-files -s >post-dry-run && + test_cmp pre-dry-run post-dry-run && + test_must_fail git read-tree "$@" +} + +read_tree_u_must_succeed () { + git ls-files -s >pre-dry-run && + git diff-files -p >pre-dry-run-wt && + git read-tree -n "$@" && + git ls-files -s >post-dry-run && + git diff-files -p >post-dry-run-wt && + test_cmp pre-dry-run post-dry-run && + test_cmp pre-dry-run-wt post-dry-run-wt && + git read-tree "$@" +} + +read_tree_u_must_fail () { + git ls-files -s >pre-dry-run && + git diff-files -p >pre-dry-run-wt && + test_must_fail git read-tree -n "$@" && + git ls-files -s >post-dry-run && + git diff-files -p >post-dry-run-wt && + test_cmp pre-dry-run post-dry-run && + test_cmp pre-dry-run-wt post-dry-run-wt && + test_must_fail git read-tree "$@" +} diff --git a/t/lib-terminal.sh b/t/lib-terminal.sh index c383b57ed9..58d911d21b 100644 --- a/t/lib-terminal.sh +++ b/t/lib-terminal.sh @@ -1,8 +1,24 @@ #!/bin/sh -test_expect_success 'set up terminal for tests' ' - if - test_have_prereq PERL && +test_expect_success PERL 'set up terminal for tests' ' + # Reading from the pty master seems to get stuck _sometimes_ + # on Mac OS X 10.5.0, using Perl 5.10.0 or 5.8.9. + # + # Reproduction recipe: run + # + # i=0 + # while ./test-terminal.perl echo hi $i + # do + # : $((i = $i + 1)) + # done + # + # After 2000 iterations or so it hangs. + # https://rt.cpan.org/Ticket/Display.html?id=65692 + # + if test "$(uname -s)" = Darwin + then + : + elif "$PERL_PATH" "$TEST_DIRECTORY"/test-terminal.perl \ sh -c "test -t 1 && test -t 2" then diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 8deec75c3a..f4e8f43bae 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -435,7 +435,7 @@ test_expect_success 'update-index D/F conflict' ' test $numpath0 = 1 ' -test_expect_success SYMLINKS 'absolute path works as expected' ' +test_expect_success SYMLINKS 'real path works as expected' ' mkdir first && ln -s ../.git first/.git && mkdir second && @@ -443,14 +443,14 @@ test_expect_success SYMLINKS 'absolute path works as expected' ' mkdir third && dir="$(cd .git; pwd -P)" && dir2=third/../second/other/.git && - test "$dir" = "$(test-path-utils make_absolute_path $dir2)" && + test "$dir" = "$(test-path-utils real_path $dir2)" && file="$dir"/index && - test "$file" = "$(test-path-utils make_absolute_path $dir2/index)" && + test "$file" = "$(test-path-utils real_path $dir2/index)" && basename=blub && - test "$dir/$basename" = "$(cd .git && test-path-utils make_absolute_path "$basename")" && + test "$dir/$basename" = "$(cd .git && test-path-utils real_path "$basename")" && ln -s ../first/file .git/syml && sym="$(cd first; pwd -P)"/file && - test "$sym" = "$(test-path-utils make_absolute_path "$dir2/syml")" + test "$sym" = "$(test-path-utils real_path "$dir2/syml")" ' test_expect_success 'very long name in the index handled sanely' ' diff --git a/t/t0001-init.sh b/t/t0001-init.sh index f684993211..ad66410564 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -47,7 +47,7 @@ test_expect_success 'plain nested in bare' ' test_expect_success 'plain through aliased command, outside any git repo' ' ( - sane_unset GIT_DIR GIT_WORK_TREE GIT_CONFIG_NOGLOBAL && + sane_unset GIT_DIR GIT_WORK_TREE && HOME=$(pwd)/alias-config && export HOME && mkdir alias-config && @@ -190,11 +190,11 @@ test_expect_success 'reinit' ' git init >out1 2>err1 && git init >out2 2>err2 ) && - grep "Initialized empty" again/out1 && - grep "Reinitialized existing" again/out2 && + test_i18ngrep "Initialized empty" again/out1 && + test_i18ngrep "Reinitialized existing" again/out2 && >again/empty && - test_cmp again/empty again/err1 && - test_cmp again/empty again/err2 + test_i18ncmp again/empty again/err1 && + test_i18ncmp again/empty again/err2 ' test_expect_success 'init with --template' ' @@ -231,7 +231,6 @@ test_expect_success 'init with init.templatedir set' ' git config -f "$test_config" init.templatedir "${HOME}/templatedir-source" && mkdir templatedir-set && cd templatedir-set && - sane_unset GIT_CONFIG_NOGLOBAL && sane_unset GIT_TEMPLATE_DIR && NO_SET_GIT_TEMPLATE_DIR=t && export NO_SET_GIT_TEMPLATE_DIR && @@ -243,7 +242,6 @@ test_expect_success 'init with init.templatedir set' ' test_expect_success 'init --bare/--shared overrides system/global config' ' ( test_config="$HOME"/.gitconfig && - sane_unset GIT_CONFIG_NOGLOBAL && git config -f "$test_config" core.bare false && git config -f "$test_config" core.sharedRepository 0640 && mkdir init-bare-shared-override && @@ -258,7 +256,6 @@ test_expect_success 'init --bare/--shared overrides system/global config' ' test_expect_success 'init honors global core.sharedRepository' ' ( test_config="$HOME"/.gitconfig && - sane_unset GIT_CONFIG_NOGLOBAL && git config -f "$test_config" core.sharedRepository 0666 && mkdir shared-honor-global && cd shared-honor-global && @@ -374,4 +371,50 @@ test_expect_success 'init prefers command line to GIT_DIR' ' ! test -d otherdir/refs ' +test_expect_success 'init with separate gitdir' ' + rm -rf newdir && + git init --separate-git-dir realgitdir newdir && + echo "gitdir: `pwd`/realgitdir" >expected && + test_cmp expected newdir/.git && + test -d realgitdir/refs +' + +test_expect_success 're-init to update git link' ' + ( + cd newdir && + git init --separate-git-dir ../surrealgitdir + ) && + echo "gitdir: `pwd`/surrealgitdir" >expected && + test_cmp expected newdir/.git && + test -d surrealgitdir/refs && + ! test -d realgitdir/refs +' + +test_expect_success 're-init to move gitdir' ' + rm -rf newdir realgitdir surrealgitdir && + git init newdir && + ( + cd newdir && + git init --separate-git-dir ../realgitdir + ) && + echo "gitdir: `pwd`/realgitdir" >expected && + test_cmp expected newdir/.git && + test -d realgitdir/refs +' + +test_expect_success SYMLINKS 're-init to move gitdir symlink' ' + rm -rf newdir realgitdir && + git init newdir && + ( + cd newdir && + mv .git here && + ln -s here .git && + git init --separate-git-dir ../realgitdir + ) && + echo "gitdir: `pwd`/realgitdir" >expected && + test_cmp expected newdir/.git && + test -d realgitdir/refs && + ! test -d newdir/here +' + test_done diff --git a/t/t0006-date.sh b/t/t0006-date.sh index 1d4d0a5c7d..f87abb5a06 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -25,6 +25,7 @@ check_show 37500000 '1 year, 2 months ago' check_show 55188000 '1 year, 9 months ago' check_show 630000000 '20 years ago' check_show 31449600 '12 months ago' +check_show 62985600 '2 years ago' check_parse() { echo "$1 -> $2" >expect diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 20924506af..ae266147b6 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -19,7 +19,7 @@ usage: test-parse-options <options> --set23 set integer to 23 -t <time> get timestamp of <time> -L, --length <str> get length of <str> - -F, --file <FILE> set file to <FILE> + -F, --file <file> set file to <file> String options -s, --string <string> diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 10b26e4d8e..8d4938f019 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -7,8 +7,31 @@ test_description='Test run command' . ./test-lib.sh +cat >hello-script <<-EOF + #!$SHELL_PATH + cat hello-script +EOF +>empty + test_expect_success 'start_command reports ENOENT' ' test-run-command start-command-ENOENT ./does-not-exist ' +test_expect_success 'run_command can run a command' ' + cat hello-script >hello.sh && + chmod +x hello.sh && + test-run-command run-command ./hello.sh >actual 2>err && + + test_cmp hello-script actual && + test_cmp empty err +' + +test_expect_success POSIXPERM 'run_command reports EACCES' ' + cat hello-script >hello.sh && + chmod -x hello.sh && + test_must_fail test-run-command run-command ./hello.sh 2>err && + + grep "fatal: cannot exec.*hello.sh" err +' + test_done diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh index 680d7d6861..9bee8bfd2e 100755 --- a/t/t0070-fundamental.sh +++ b/t/t0070-fundamental.sh @@ -12,4 +12,17 @@ test_expect_success 'character classes (isspace, isalpha etc.)' ' test-ctype ' +test_expect_success 'mktemp to nonexistent directory prints filename' ' + test_must_fail test-mktemp doesnotexist/testXXXXXX 2>err && + grep "doesnotexist/test" err +' + +test_expect_success POSIXPERM 'mktemp to unwritable directory prints filename' ' + mkdir cannotwrite && + chmod -w cannotwrite && + test_when_finished "chmod +w cannotwrite" && + test_must_fail test-mktemp cannotwrite/testXXXXXX 2>err && + grep "cannotwrite/test" err +' + test_done diff --git a/t/t0080-vcs-svn.sh b/t/t0080-vcs-svn.sh index d3225ada68..99a314b080 100755 --- a/t/t0080-vcs-svn.sh +++ b/t/t0080-vcs-svn.sh @@ -76,60 +76,6 @@ test_expect_success 'obj pool: high-water mark' ' test_cmp expected actual ' -test_expect_success 'line buffer' ' - echo HELLO >expected1 && - printf "%s\n" "" HELLO >expected2 && - echo >expected3 && - printf "%s\n" "" Q | q_to_nul >expected4 && - printf "%s\n" foo "" >expected5 && - printf "%s\n" "" foo >expected6 && - - test-line-buffer <<-\EOF >actual1 && - 5 - HELLO - EOF - - test-line-buffer <<-\EOF >actual2 && - 0 - - 5 - HELLO - EOF - - q_to_nul <<-\EOF | - 1 - Q - EOF - test-line-buffer >actual3 && - - q_to_nul <<-\EOF | - 0 - - 1 - Q - EOF - test-line-buffer >actual4 && - - test-line-buffer <<-\EOF >actual5 && - 5 - foo - EOF - - test-line-buffer <<-\EOF >actual6 && - 0 - - 5 - foo - EOF - - test_cmp expected1 actual1 && - test_cmp expected2 actual2 && - test_cmp expected3 actual3 && - test_cmp expected4 actual4 && - test_cmp expected5 actual5 && - test_cmp expected6 actual6 -' - test_expect_success 'string pool' ' echo a does not equal b >expected.differ && echo a equals a >expected.match && diff --git a/t/t0081-line-buffer.sh b/t/t0081-line-buffer.sh new file mode 100755 index 0000000000..bd83ed371a --- /dev/null +++ b/t/t0081-line-buffer.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +test_description="Test the svn importer's input handling routines. + +These tests provide some simple checks that the line_buffer API +behaves as advertised. + +While at it, check that input of newlines and null bytes are handled +correctly. +" +. ./test-lib.sh + +test_expect_success 'hello world' ' + echo ">HELLO" >expect && + test-line-buffer <<-\EOF >actual && + binary 6 + HELLO + EOF + test_cmp expect actual +' + +test_expect_success '0-length read, send along greeting' ' + echo ">HELLO" >expect && + test-line-buffer <<-\EOF >actual && + binary 0 + copy 6 + HELLO + EOF + test_cmp expect actual +' + +test_expect_success 'read from file descriptor' ' + rm -f input && + echo hello >expect && + echo hello >input && + echo copy 6 | + test-line-buffer "&4" 4<input >actual && + test_cmp expect actual +' + +test_expect_success 'skip, copy null byte' ' + echo Q | q_to_nul >expect && + q_to_nul <<-\EOF | test-line-buffer >actual && + skip 2 + Q + copy 2 + Q + EOF + test_cmp expect actual +' + +test_expect_success 'read null byte' ' + echo ">QhelloQ" | q_to_nul >expect && + q_to_nul <<-\EOF | test-line-buffer >actual && + binary 8 + QhelloQ + EOF + test_cmp expect actual +' + +test_expect_success 'long reads are truncated' ' + echo ">foo" >expect && + test-line-buffer <<-\EOF >actual && + binary 5 + foo + EOF + test_cmp expect actual +' + +test_expect_success 'long copies are truncated' ' + printf "%s\n" ">" foo >expect && + test-line-buffer <<-\EOF >actual && + binary 1 + + copy 5 + foo + EOF + test_cmp expect actual +' + +test_expect_success 'long binary reads are truncated' ' + echo ">foo" >expect && + test-line-buffer <<-\EOF >actual && + binary 5 + foo + EOF + test_cmp expect actual +' + +test_done diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh new file mode 100755 index 0000000000..54d98b9b10 --- /dev/null +++ b/t/t0201-gettext-fallbacks.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description='Gettext Shell fallbacks' + +. ./test-lib.sh +. "$GIT_BUILD_DIR"/git-sh-i18n + +test_expect_success 'gettext: our gettext() fallback has pass-through semantics' ' + printf "test" >expect && + gettext "test" >actual && + test_i18ncmp expect actual && + printf "test more words" >expect && + gettext "test more words" >actual && + test_i18ncmp expect actual +' + +test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' ' + printf "test" >expect && + eval_gettext "test" >actual && + test_i18ncmp expect actual && + printf "test more words" >expect && + eval_gettext "test more words" >actual && + test_i18ncmp expect actual +' + +test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' ' + printf "test YesPlease" >expect && + GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease eval_gettext "test \$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" >actual && + test_i18ncmp expect actual +' + +test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables with spaces' ' + cmdline="git am" && + export cmdline; + printf "When you have resolved this problem run git am --resolved." >expect && + eval_gettext "When you have resolved this problem run \$cmdline --resolved." >actual + test_i18ncmp expect actual +' + +test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables with spaces and quotes' ' + cmdline="git am" && + export cmdline; + printf "When you have resolved this problem run \"git am --resolved\"." >expect && + eval_gettext "When you have resolved this problem run \"\$cmdline --resolved\"." >actual + test_i18ncmp expect actual +' + +test_done diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index ca8a4098fa..babcdd2343 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -72,6 +72,7 @@ In addition: ' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh . "$TEST_DIRECTORY"/lib-read-tree-m-3way.sh ################################################################ @@ -137,7 +138,7 @@ test_expect_success \ '3-way merge with git read-tree -m, empty cache' \ "rm -fr [NDMALTS][NDMALTSF] Z && rm .git/index && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" # This starts out with the first head, which is the normal @@ -146,9 +147,9 @@ test_expect_success \ '3-way merge with git read-tree -m, match H' \ "rm -fr [NDMALTS][NDMALTSF] Z && rm .git/index && - git read-tree $tree_A && + read_tree_must_succeed $tree_A && git checkout-index -f -u -a && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" : <<\END_OF_CASE_TABLE @@ -211,7 +212,7 @@ test_expect_success '1 - must not have an entry not in A.' " rm -f .git/index XX && echo XX >XX && git update-index --add XX && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -219,7 +220,7 @@ test_expect_success \ "rm -f .git/index NA && cp .orig-B/NA NA && git update-index --add NA && - git read-tree -m $tree_O $tree_A $tree_B" + read_tree_must_succeed -m $tree_O $tree_A $tree_B" test_expect_success \ '2 - matching B alone is OK in !O && !A && B case.' \ @@ -227,14 +228,14 @@ test_expect_success \ cp .orig-B/NA NA && git update-index --add NA && echo extra >>NA && - git read-tree -m $tree_O $tree_A $tree_B" + read_tree_must_succeed -m $tree_O $tree_A $tree_B" test_expect_success \ '3 - must match A in !O && A && !B case.' \ "rm -f .git/index AN && cp .orig-A/AN AN && git update-index --add AN && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -243,7 +244,7 @@ test_expect_success \ cp .orig-A/AN AN && git update-index --add AN && echo extra >>AN && - git read-tree -m $tree_O $tree_A $tree_B" + read_tree_must_succeed -m $tree_O $tree_A $tree_B" test_expect_success \ '3 (fail) - must match A in !O && A && !B case.' " @@ -251,7 +252,7 @@ test_expect_success \ cp .orig-A/AN AN && echo extra >>AN && git update-index --add AN && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -259,7 +260,7 @@ test_expect_success \ "rm -f .git/index AA && cp .orig-A/AA AA && git update-index --add AA && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -268,7 +269,7 @@ test_expect_success \ cp .orig-A/AA AA && git update-index --add AA && echo extra >>AA && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -277,7 +278,7 @@ test_expect_success \ cp .orig-A/AA AA && echo extra >>AA && git update-index --add AA && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -285,7 +286,7 @@ test_expect_success \ "rm -f .git/index LL && cp .orig-A/LL LL && git update-index --add LL && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -294,7 +295,7 @@ test_expect_success \ cp .orig-A/LL LL && git update-index --add LL && echo extra >>LL && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -303,7 +304,7 @@ test_expect_success \ cp .orig-A/LL LL && echo extra >>LL && git update-index --add LL && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -311,7 +312,7 @@ test_expect_success \ rm -f .git/index DD && echo DD >DD && git update-index --add DD && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -319,7 +320,7 @@ test_expect_success \ rm -f .git/index DM && cp .orig-B/DM DM && git update-index --add DM && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -327,7 +328,7 @@ test_expect_success \ rm -f .git/index DN && cp .orig-B/DN DN && git update-index --add DN && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -335,7 +336,7 @@ test_expect_success \ "rm -f .git/index MD && cp .orig-A/MD MD && git update-index --add MD && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -344,7 +345,7 @@ test_expect_success \ cp .orig-A/MD MD && git update-index --add MD && echo extra >>MD && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -353,7 +354,7 @@ test_expect_success \ cp .orig-A/MD MD && echo extra >>MD && git update-index --add MD && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -361,7 +362,7 @@ test_expect_success \ "rm -f .git/index ND && cp .orig-A/ND ND && git update-index --add ND && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -370,7 +371,7 @@ test_expect_success \ cp .orig-A/ND ND && git update-index --add ND && echo extra >>ND && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -379,7 +380,7 @@ test_expect_success \ cp .orig-A/ND ND && echo extra >>ND && git update-index --add ND && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -387,7 +388,7 @@ test_expect_success \ "rm -f .git/index MM && cp .orig-A/MM MM && git update-index --add MM && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -396,7 +397,7 @@ test_expect_success \ cp .orig-A/MM MM && git update-index --add MM && echo extra >>MM && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -405,7 +406,7 @@ test_expect_success \ cp .orig-A/MM MM && echo extra >>MM && git update-index --add MM && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -413,7 +414,7 @@ test_expect_success \ "rm -f .git/index SS && cp .orig-A/SS SS && git update-index --add SS && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -422,7 +423,7 @@ test_expect_success \ cp .orig-A/SS SS && git update-index --add SS && echo extra >>SS && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -431,7 +432,7 @@ test_expect_success \ cp .orig-A/SS SS && echo extra >>SS && git update-index --add SS && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -439,7 +440,7 @@ test_expect_success \ "rm -f .git/index MN && cp .orig-A/MN MN && git update-index --add MN && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -448,7 +449,7 @@ test_expect_success \ cp .orig-A/MN MN && git update-index --add MN && echo extra >>MN && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -456,7 +457,7 @@ test_expect_success \ "rm -f .git/index NM && cp .orig-A/NM NM && git update-index --add NM && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -465,7 +466,7 @@ test_expect_success \ cp .orig-B/NM NM && git update-index --add NM && echo extra >>NM && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -474,7 +475,7 @@ test_expect_success \ cp .orig-A/NM NM && git update-index --add NM && echo extra >>NM && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -483,7 +484,7 @@ test_expect_success \ cp .orig-A/NM NM && echo extra >>NM && git update-index --add NM && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " test_expect_success \ @@ -491,7 +492,7 @@ test_expect_success \ "rm -f .git/index NN && cp .orig-A/NN NN && git update-index --add NN && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -500,7 +501,7 @@ test_expect_success \ cp .orig-A/NN NN && git update-index --add NN && echo extra >>NN && - git read-tree -m $tree_O $tree_A $tree_B && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && check_result" test_expect_success \ @@ -509,7 +510,7 @@ test_expect_success \ cp .orig-A/NN NN && echo extra >>NN && git update-index --add NN && - test_must_fail git read-tree -m $tree_O $tree_A $tree_B + read_tree_must_fail -m $tree_O $tree_A $tree_B " # #16 @@ -522,7 +523,7 @@ test_expect_success \ echo E16 >F16 && git update-index F16 && tree1=`git write-tree` && - git read-tree -m $tree0 $tree1 $tree1 $tree0 && + read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 && git ls-files --stage' test_done diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 680d992f22..acaab07fac 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -21,6 +21,7 @@ In the test, these paths are used: yomin - not in H nor M ' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh read_tree_twoway () { git read-tree -m "$1" "$2" && git ls-files --stage @@ -94,7 +95,7 @@ echo '+100644 X 0 yomin' >expected test_expect_success \ '4 - carry forward local addition.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && git update-index --add yomin && read_tree_twoway $treeH $treeM && @@ -106,7 +107,7 @@ test_expect_success \ test_expect_success \ '5 - carry forward local addition.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo yomin >yomin && git update-index --add yomin && @@ -120,7 +121,7 @@ test_expect_success \ test_expect_success \ '6 - local addition already has the same.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && git update-index --add frotz && read_tree_twoway $treeH $treeM && @@ -131,7 +132,7 @@ test_expect_success \ test_expect_success \ '7 - local addition already has the same.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo frotz >frotz && git update-index --add frotz && @@ -144,7 +145,7 @@ test_expect_success \ test_expect_success \ '8 - conflicting addition.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo frotz frotz >frotz && git update-index --add frotz && @@ -153,7 +154,7 @@ test_expect_success \ test_expect_success \ '9 - conflicting addition.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo frotz frotz >frotz && git update-index --add frotz && @@ -163,7 +164,7 @@ test_expect_success \ test_expect_success \ '10 - path removed.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo rezrov >rezrov && git update-index --add rezrov && @@ -174,7 +175,7 @@ test_expect_success \ test_expect_success \ '11 - dirty path removed.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo rezrov >rezrov && git update-index --add rezrov && @@ -184,7 +185,7 @@ test_expect_success \ test_expect_success \ '12 - unmatching local changes being removed.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo rezrov rezrov >rezrov && git update-index --add rezrov && @@ -193,7 +194,7 @@ test_expect_success \ test_expect_success \ '13 - unmatching local changes being removed.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo rezrov rezrov >rezrov && git update-index --add rezrov && @@ -208,7 +209,7 @@ EOF test_expect_success \ '14 - unchanged in two heads.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo nitfol nitfol >nitfol && git update-index --add nitfol && @@ -221,7 +222,7 @@ test_expect_success \ test_expect_success \ '15 - unchanged in two heads.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo nitfol nitfol >nitfol && git update-index --add nitfol && @@ -235,7 +236,7 @@ test_expect_success \ test_expect_success \ '16 - conflicting local change.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo bozbar bozbar >bozbar && git update-index --add bozbar && @@ -244,7 +245,7 @@ test_expect_success \ test_expect_success \ '17 - conflicting local change.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && echo bozbar bozbar >bozbar && git update-index --add bozbar && @@ -254,7 +255,7 @@ test_expect_success \ test_expect_success \ '18 - local change already having a good result.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && cat bozbar-new >bozbar && git update-index --add bozbar && @@ -266,7 +267,7 @@ test_expect_success \ test_expect_success \ '19 - local change already having a good result, further modified.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && cat bozbar-new >bozbar && git update-index --add bozbar && @@ -279,7 +280,7 @@ test_expect_success \ test_expect_success \ '20 - no local change, use new tree.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && cat bozbar-old >bozbar && git update-index --add bozbar && @@ -291,7 +292,7 @@ test_expect_success \ test_expect_success \ '21 - no local change, dirty cache.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && cat bozbar-old >bozbar && git update-index --add bozbar && @@ -302,7 +303,7 @@ test_expect_success \ test_expect_success \ '22 - local change cache updated.' \ 'rm -f .git/index && - git read-tree $treeH && + read_tree_must_succeed $treeH && git checkout-index -u -f -q -a && sed -e "s/such as/SUCH AS/" bozbar-old >bozbar && git update-index --add bozbar && @@ -359,7 +360,7 @@ test_expect_success \ test_expect_success \ 'a/b (untracked) vs a, plus c/d case test.' \ - 'test_must_fail git read-tree -u -m "$treeH" "$treeM" && + 'read_tree_u_must_fail -u -m "$treeH" "$treeM" && git ls-files --stage && test -f a/b' @@ -386,7 +387,7 @@ test_expect_success \ test_expect_success \ 'a/b vs a, plus c/d case test.' \ - 'git read-tree -u -m "$treeH" "$treeM" && + 'read_tree_u_must_succeed -u -m "$treeH" "$treeM" && git ls-files --stage | tee >treeMcheck.out && test_cmp treeM.out treeMcheck.out' @@ -401,7 +402,7 @@ test_expect_success '-m references the correct modified tree' ' echo a >file-a && git add file-a && git ls-tree $(git write-tree) file-a >expect && - git read-tree -m HEAD initial-mod && + read_tree_must_succeed -m HEAD initial-mod && git ls-tree $(git write-tree) file-a >actual && test_cmp expect actual ' diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index a4a17e0017..a847709a13 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -9,6 +9,7 @@ This is identical to t1001, but uses -u to update the work tree as well. ' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh compare_change () { sed >current \ @@ -56,8 +57,8 @@ test_expect_success \ test_expect_success \ '1, 2, 3 - no carry forward' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed --reset -u $treeH && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >1-3.out && cmp M.out 1-3.out && sum bozbar frotz nitfol >actual3.sum && @@ -69,11 +70,11 @@ test_expect_success \ test_expect_success \ '4 - carry forward local addition.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo "+100644 X 0 yomin" >expected && echo yomin >yomin && git update-index --add yomin && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >4.out || return 1 git diff -U0 --no-index M.out 4.out >4diff.out compare_change 4diff.out expected && @@ -87,12 +88,12 @@ test_expect_success \ test_expect_success \ '5 - carry forward local addition.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && - git read-tree -m -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && + read_tree_u_must_succeed -m -u $treeH && echo yomin >yomin && git update-index --add yomin && echo yomin yomin >yomin && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >5.out || return 1 git diff -U0 --no-index M.out 5.out >5diff.out compare_change 5diff.out expected && @@ -107,10 +108,10 @@ test_expect_success \ test_expect_success \ '6 - local addition already has the same.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo frotz >frotz && git update-index --add frotz && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >6.out && test_cmp M.out 6.out && check_cache_at frotz clean && @@ -123,11 +124,11 @@ test_expect_success \ test_expect_success \ '7 - local addition already has the same.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo frotz >frotz && git update-index --add frotz && echo frotz frotz >frotz && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >7.out && test_cmp M.out 7.out && check_cache_at frotz dirty && @@ -141,27 +142,27 @@ test_expect_success \ test_expect_success \ '8 - conflicting addition.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo frotz frotz >frotz && git update-index --add frotz && - if git read-tree -m -u $treeH $treeM; then false; else :; fi' + if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '9 - conflicting addition.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo frotz frotz >frotz && git update-index --add frotz && echo frotz >frotz && - if git read-tree -m -u $treeH $treeM; then false; else :; fi' + if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '10 - path removed.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo rezrov >rezrov && git update-index --add rezrov && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >10.out && cmp M.out 10.out && sum bozbar frotz nitfol >actual10.sum && @@ -170,28 +171,28 @@ test_expect_success \ test_expect_success \ '11 - dirty path removed.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo rezrov >rezrov && git update-index --add rezrov && echo rezrov rezrov >rezrov && - if git read-tree -m -u $treeH $treeM; then false; else :; fi' + if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '12 - unmatching local changes being removed.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo rezrov rezrov >rezrov && git update-index --add rezrov && - if git read-tree -m -u $treeH $treeM; then false; else :; fi' + if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '13 - unmatching local changes being removed.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo rezrov rezrov >rezrov && git update-index --add rezrov && echo rezrov >rezrov && - if git read-tree -m -u $treeH $treeM; then false; else :; fi' + if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' cat >expected <<EOF -100644 X 0 nitfol @@ -201,10 +202,10 @@ EOF test_expect_success \ '14 - unchanged in two heads.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo nitfol nitfol >nitfol && git update-index --add nitfol && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >14.out && test_must_fail git diff -U0 --no-index M.out 14.out >14diff.out && compare_change 14diff.out expected && @@ -221,11 +222,11 @@ test_expect_success \ test_expect_success \ '15 - unchanged in two heads.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo nitfol nitfol >nitfol && git update-index --add nitfol && echo nitfol nitfol nitfol >nitfol && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >15.out && test_must_fail git diff -U0 --no-index M.out 15.out >15diff.out && compare_change 15diff.out expected && @@ -242,27 +243,27 @@ test_expect_success \ test_expect_success \ '16 - conflicting local change.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo bozbar bozbar >bozbar && git update-index --add bozbar && - if git read-tree -m -u $treeH $treeM; then false; else :; fi' + if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '17 - conflicting local change.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo bozbar bozbar >bozbar && git update-index --add bozbar && echo bozbar bozbar bozbar >bozbar && - if git read-tree -m -u $treeH $treeM; then false; else :; fi' + if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '18 - local change already having a good result.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo gnusto >bozbar && git update-index --add bozbar && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >18.out && test_cmp M.out 18.out && check_cache_at bozbar clean && @@ -272,11 +273,11 @@ test_expect_success \ test_expect_success \ '19 - local change already having a good result, further modified.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo gnusto >bozbar && git update-index --add bozbar && echo gnusto gnusto >bozbar && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >19.out && test_cmp M.out 19.out && check_cache_at bozbar dirty && @@ -292,10 +293,10 @@ test_expect_success \ test_expect_success \ '20 - no local change, use new tree.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo bozbar >bozbar && git update-index --add bozbar && - git read-tree -m -u $treeH $treeM && + read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >20.out && test_cmp M.out 20.out && check_cache_at bozbar clean && @@ -305,11 +306,11 @@ test_expect_success \ test_expect_success \ '21 - no local change, dirty cache.' \ 'rm -f .git/index nitfol bozbar rezrov frotz && - git read-tree --reset -u $treeH && + read_tree_u_must_succeed --reset -u $treeH && echo bozbar >bozbar && git update-index --add bozbar && echo gnusto gnusto >bozbar && - if git read-tree -m -u $treeH $treeM; then false; else :; fi' + if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' # Also make sure we did not break DF vs DF/DF case. test_expect_success \ @@ -336,7 +337,7 @@ test_expect_success \ rm -fr DF && echo DF >DF && git update-index --add DF && - git read-tree -m -u $treeDF $treeDFDF && + read_tree_u_must_succeed -m -u $treeDF $treeDFDF && git ls-files --stage >DFDFcheck.out && test_cmp DFDF.out DFDFcheck.out && check_cache_at DF/DF clean' diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index eb8e3d4476..b3ae7d52c6 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -3,6 +3,7 @@ test_description='read-tree -m -u checks working tree files' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh # two-tree test @@ -29,7 +30,7 @@ test_expect_success 'two-way not clobbering' ' echo >file2 master creates untracked file2 && echo >subdir/file2 master creates untracked subdir/file2 && - if err=`git read-tree -m -u master side 2>&1` + if err=`read_tree_u_must_succeed -m -u master side 2>&1` then echo should have complained false @@ -42,7 +43,7 @@ echo file2 >.gitignore test_expect_success 'two-way with incorrect --exclude-per-directory (1)' ' - if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1` + if err=`read_tree_u_must_succeed -m --exclude-per-directory=.gitignore master side 2>&1` then echo should have complained false @@ -53,7 +54,7 @@ test_expect_success 'two-way with incorrect --exclude-per-directory (1)' ' test_expect_success 'two-way with incorrect --exclude-per-directory (2)' ' - if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1` + if err=`read_tree_u_must_succeed -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1` then echo should have complained false @@ -64,7 +65,7 @@ test_expect_success 'two-way with incorrect --exclude-per-directory (2)' ' test_expect_success 'two-way clobbering a ignored file' ' - git read-tree -m -u --exclude-per-directory=.gitignore master side + read_tree_u_must_succeed -m -u --exclude-per-directory=.gitignore master side ' rm -f .gitignore @@ -84,7 +85,7 @@ test_expect_success 'three-way not complaining on an untracked path in both' ' echo >file2 file two is untracked on the master side && echo >subdir/file2 file two is untracked on the master side && - git read-tree -m -u branch-point master side + read_tree_u_must_succeed -m -u branch-point master side ' test_expect_success 'three-way not clobbering a working tree file' ' @@ -94,7 +95,7 @@ test_expect_success 'three-way not clobbering a working tree file' ' git checkout master && echo >file3 file three created in master, untracked && echo >subdir/file3 file three created in master, untracked && - if err=`git read-tree -m -u branch-point master side 2>&1` + if err=`read_tree_u_must_succeed -m -u branch-point master side 2>&1` then echo should have complained false @@ -113,7 +114,7 @@ test_expect_success 'three-way not complaining on an untracked file' ' echo >file3 file three created in master, untracked && echo >subdir/file3 file three created in master, untracked && - git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side + read_tree_u_must_succeed -m -u --exclude-per-directory=.gitignore branch-point master side ' test_expect_success '3-way not overwriting local changes (setup)' ' @@ -137,7 +138,7 @@ test_expect_success '3-way not overwriting local changes (our side)' ' git reset --hard && echo >>file1 "local changes" && - git read-tree -m -u branch-point side-a side-b && + read_tree_u_must_succeed -m -u branch-point side-a side-b && grep "new line to be kept" file1 && grep "local changes" file1 @@ -151,7 +152,7 @@ test_expect_success '3-way not overwriting local changes (their side)' ' git reset --hard && echo >>file2 "local changes" && - test_must_fail git read-tree -m -u branch-point side-a side-b && + read_tree_u_must_fail -m -u branch-point side-a side-b && ! grep "new line to be kept" file2 && grep "local changes" file2 @@ -173,7 +174,7 @@ test_expect_success SYMLINKS 'funny symlink in work tree' ' git add a/b && git commit -m "we add a/b" && - git read-tree -m -u sym-a sym-a sym-b + read_tree_u_must_succeed -m -u sym-a sym-a sym-b ' @@ -209,7 +210,7 @@ test_expect_success 'D/F setup' ' test_expect_success 'D/F' ' git checkout side-b && - git read-tree -m -u branch-point side-b side-a && + read_tree_u_must_succeed -m -u branch-point side-b side-a && git ls-files -u >actual && ( a=$(git rev-parse branch-point:subdir/file2) diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh index 849911683a..f53de79e56 100755 --- a/t/t1005-read-tree-reset.sh +++ b/t/t1005-read-tree-reset.sh @@ -3,6 +3,7 @@ test_description='read-tree -u --reset' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh # two-tree test @@ -22,13 +23,13 @@ test_expect_success 'setup' ' ' test_expect_success 'reset should work' ' - git read-tree -u --reset HEAD^ && + read_tree_u_must_succeed -u --reset HEAD^ && git ls-files >actual && test_cmp expect actual ' test_expect_success 'reset should remove remnants from a failed merge' ' - git read-tree --reset -u HEAD && + read_tree_u_must_succeed --reset -u HEAD && git ls-files -s >expect && sha1=$(git rev-parse :new) && ( @@ -37,13 +38,13 @@ test_expect_success 'reset should remove remnants from a failed merge' ' ) | git update-index --index-info && >old && git ls-files -s && - git read-tree --reset -u HEAD && + read_tree_u_must_succeed --reset -u HEAD && git ls-files -s >actual && ! test -f old ' test_expect_success 'Porcelain reset should remove remnants too' ' - git read-tree --reset -u HEAD && + read_tree_u_must_succeed --reset -u HEAD && git ls-files -s >expect && sha1=$(git rev-parse :new) && ( @@ -58,7 +59,7 @@ test_expect_success 'Porcelain reset should remove remnants too' ' ' test_expect_success 'Porcelain checkout -f should remove remnants too' ' - git read-tree --reset -u HEAD && + read_tree_u_must_succeed --reset -u HEAD && git ls-files -s >expect && sha1=$(git rev-parse :new) && ( @@ -73,7 +74,7 @@ test_expect_success 'Porcelain checkout -f should remove remnants too' ' ' test_expect_success 'Porcelain checkout -f HEAD should remove remnants too' ' - git read-tree --reset -u HEAD && + read_tree_u_must_succeed --reset -u HEAD && git ls-files -s >expect && sha1=$(git rev-parse :new) && ( diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index dd32432d62..6d52b824b1 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -188,4 +188,17 @@ for args in "-w --stdin-paths" "--stdin-paths -w"; do pop_repo done +test_expect_success 'corrupt tree' ' + echo abc >malformed-tree + test_must_fail git hash-object -t tree malformed-tree +' + +test_expect_success 'corrupt commit' ' + test_must_fail git hash-object -t commit --stdin </dev/null +' + +test_expect_success 'corrupt tag' ' + test_must_fail git hash-object -t tag --stdin </dev/null +' + test_done diff --git a/t/t1008-read-tree-overlay.sh b/t/t1008-read-tree-overlay.sh index f9e00285db..4c50ed955e 100755 --- a/t/t1008-read-tree-overlay.sh +++ b/t/t1008-read-tree-overlay.sh @@ -3,6 +3,7 @@ test_description='test multi-tree read-tree without merging' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh test_expect_success setup ' echo one >a && @@ -21,7 +22,7 @@ test_expect_success setup ' ' test_expect_success 'multi-read' ' - git read-tree initial master side && + read_tree_must_succeed initial master side && (echo a; echo b/c) >expect && git ls-files >actual && test_cmp expect actual diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index de84e35c43..018c3546b6 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -12,24 +12,27 @@ test_description='sparse checkout tests ' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh test_expect_success 'setup' ' cat >expected <<-\EOF && 100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/addedtoo 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 subsub/added EOF cat >expected.swt <<-\EOF && H init.t H sub/added + H sub/addedtoo H subsub/added EOF test_commit init && echo modified >>init.t && mkdir sub subsub && - touch sub/added subsub/added && - git add init.t sub/added subsub/added && + touch sub/added sub/addedtoo subsub/added && + git add init.t sub/added sub/addedtoo subsub/added && git commit -m "modified and added" && git tag top && git rm sub/added && @@ -41,7 +44,7 @@ test_expect_success 'setup' ' ' test_expect_success 'read-tree without .git/info/sparse-checkout' ' - git read-tree -m -u HEAD && + read_tree_u_must_succeed -m -u HEAD && git ls-files --stage >result && test_cmp expected result && git ls-files -t >result && @@ -50,7 +53,7 @@ test_expect_success 'read-tree without .git/info/sparse-checkout' ' test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' echo >.git/info/sparse-checkout && - git read-tree -m -u HEAD && + read_tree_u_must_succeed -m -u HEAD && git ls-files -t >result && test_cmp expected.swt result && test -f init.t && @@ -60,7 +63,7 @@ test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' ' git config core.sparsecheckout true && echo >.git/info/sparse-checkout && - git read-tree --no-sparse-checkout -m -u HEAD && + read_tree_u_must_succeed --no-sparse-checkout -m -u HEAD && git ls-files -t >result && test_cmp expected.swt result && test -f init.t && @@ -70,7 +73,7 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse- test_expect_success 'read-tree with empty .git/info/sparse-checkout' ' git config core.sparsecheckout true && echo >.git/info/sparse-checkout && - test_must_fail git read-tree -m -u HEAD && + read_tree_u_must_fail -m -u HEAD && git ls-files --stage >result && test_cmp expected result && git ls-files -t >result && @@ -83,11 +86,12 @@ test_expect_success 'match directories with trailing slash' ' cat >expected.swt-noinit <<-\EOF && S init.t H sub/added + H sub/addedtoo S subsub/added EOF echo sub/ > .git/info/sparse-checkout && - git read-tree -m -u HEAD && + read_tree_u_must_succeed -m -u HEAD && git ls-files -t > result && test_cmp expected.swt-noinit result && test ! -f init.t && @@ -95,18 +99,59 @@ test_expect_success 'match directories with trailing slash' ' ' test_expect_success 'match directories without trailing slash' ' - echo sub >>.git/info/sparse-checkout && - git read-tree -m -u HEAD && + echo sub >.git/info/sparse-checkout && + read_tree_u_must_succeed -m -u HEAD && git ls-files -t >result && test_cmp expected.swt-noinit result && test ! -f init.t && test -f sub/added ' -test_expect_success 'match directory pattern' ' - echo "s?b" >>.git/info/sparse-checkout && +test_expect_success 'match directories with negated patterns' ' + cat >expected.swt-negation <<\EOF && +S init.t +S sub/added +H sub/addedtoo +S subsub/added +EOF + + cat >.git/info/sparse-checkout <<\EOF && +sub +!sub/added +EOF + git read-tree -m -u HEAD && + git ls-files -t >result && + test_cmp expected.swt-negation result && + test ! -f init.t && + test ! -f sub/added && + test -f sub/addedtoo +' + +test_expect_success 'match directories with negated patterns (2)' ' + cat >expected.swt-negation2 <<\EOF && +H init.t +H sub/added +S sub/addedtoo +H subsub/added +EOF + + cat >.git/info/sparse-checkout <<\EOF && +/* +!sub +sub/added +EOF git read-tree -m -u HEAD && git ls-files -t >result && + test_cmp expected.swt-negation2 result && + test -f init.t && + test -f sub/added && + test ! -f sub/addedtoo +' + +test_expect_success 'match directory pattern' ' + echo "s?b" >.git/info/sparse-checkout && + read_tree_u_must_succeed -m -u HEAD && + git ls-files -t >result && test_cmp expected.swt-noinit result && test ! -f init.t && test -f sub/added @@ -116,11 +161,12 @@ test_expect_success 'checkout area changes' ' cat >expected.swt-nosub <<-\EOF && H init.t S sub/added + S sub/addedtoo S subsub/added EOF echo init.t >.git/info/sparse-checkout && - git read-tree -m -u HEAD && + read_tree_u_must_succeed -m -u HEAD && git ls-files -t >result && test_cmp expected.swt-nosub result && test -f init.t && @@ -130,7 +176,7 @@ test_expect_success 'checkout area changes' ' test_expect_success 'read-tree updates worktree, absent case' ' echo sub/added >.git/info/sparse-checkout && git checkout -f top && - git read-tree -m -u HEAD^ && + read_tree_u_must_succeed -m -u HEAD^ && test ! -f init.t ' @@ -138,7 +184,7 @@ test_expect_success 'read-tree updates worktree, dirty case' ' echo sub/added >.git/info/sparse-checkout && git checkout -f top && echo dirty >init.t && - git read-tree -m -u HEAD^ && + read_tree_u_must_succeed -m -u HEAD^ && grep -q dirty init.t && rm init.t ' @@ -147,14 +193,14 @@ test_expect_success 'read-tree removes worktree, dirty case' ' echo init.t >.git/info/sparse-checkout && git checkout -f top && echo dirty >added && - git read-tree -m -u HEAD^ && + read_tree_u_must_succeed -m -u HEAD^ && grep -q dirty added ' test_expect_success 'read-tree adds to worktree, absent case' ' echo init.t >.git/info/sparse-checkout && git checkout -f removed && - git read-tree -u -m HEAD^ && + read_tree_u_must_succeed -u -m HEAD^ && test ! -f sub/added ' @@ -163,7 +209,7 @@ test_expect_success 'read-tree adds to worktree, dirty case' ' git checkout -f removed && mkdir sub && echo dirty >sub/added && - git read-tree -u -m HEAD^ && + read_tree_u_must_succeed -u -m HEAD^ && grep -q dirty sub/added ' diff --git a/t/t1012-read-tree-df.sh b/t/t1012-read-tree-df.sh index 9811d467da..a6a04b6b90 100755 --- a/t/t1012-read-tree-df.sh +++ b/t/t1012-read-tree-df.sh @@ -3,6 +3,7 @@ test_description='read-tree D/F conflict corner cases' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh maketree () { ( @@ -53,7 +54,7 @@ test_expect_success setup ' test_expect_success '3-way (1)' ' settree A-000 && - git read-tree -m -u O-000 A-000 B-000 && + read_tree_u_must_succeed -m -u O-000 A-000 B-000 && checkindex <<-EOF 3 a/b 0 a/b-2/c/d @@ -65,7 +66,7 @@ test_expect_success '3-way (1)' ' test_expect_success '3-way (2)' ' settree A-001 && - git read-tree -m -u O-000 A-001 B-000 && + read_tree_u_must_succeed -m -u O-000 A-001 B-000 && checkindex <<-EOF 3 a/b 0 a/b-2/c/d @@ -78,7 +79,7 @@ test_expect_success '3-way (2)' ' test_expect_success '3-way (3)' ' settree A-010 && - git read-tree -m -u O-010 A-010 B-010 && + read_tree_u_must_succeed -m -u O-010 A-010 B-010 && checkindex <<-EOF 2 t 1 t-0 @@ -92,7 +93,7 @@ test_expect_success '3-way (3)' ' test_expect_success '2-way (1)' ' settree O-020 && - git read-tree -m -u O-020 A-020 && + read_tree_u_must_succeed -m -u O-020 A-020 && checkindex <<-EOF 0 ds/dma/ioat/Makefile 0 ds/dma/ioat/registers.h diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index 1fd187c5eb..f6a44c9ee0 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -7,6 +7,7 @@ test_description='Try various core-level commands in subdirectory. ' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-read-tree.sh test_expect_success setup ' long="a b c d e f g h i j k l m n o p q r s t u v w x y z" && @@ -98,13 +99,13 @@ test_expect_success 'checkout-index' ' test_expect_success 'read-tree' ' rm -f one dir/two && tree=`git write-tree` && - git read-tree --reset -u "$tree" && + read_tree_u_must_succeed --reset -u "$tree" && cmp one original.one && cmp dir/two original.two && ( cd dir && rm -f two && - git read-tree --reset -u "$tree" && + read_tree_u_must_succeed --reset -u "$tree" && cmp two ../original.two && cmp ../one ../original.one ) @@ -118,6 +119,27 @@ test_expect_success 'alias expansion' ' git ss ) ' + +test_expect_success '!alias expansion' ' + pwd >expect && + ( + git config alias.test !pwd && + cd dir && + git test >../actual + ) && + test_cmp expect actual +' + +test_expect_success 'GIT_PREFIX for !alias' ' + printf "dir/" >expect && + ( + git config alias.test "!sh -c \"printf \$GIT_PREFIX\"" && + cd dir && + git test >../actual + ) && + test_cmp expect actual +' + test_expect_success 'no file/rev ambiguity check inside .git' ' git commit -a -m 1 && ( diff --git a/t/t1021-rerere-in-workdir.sh b/t/t1021-rerere-in-workdir.sh new file mode 100755 index 0000000000..301e071ff7 --- /dev/null +++ b/t/t1021-rerere-in-workdir.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='rerere run in a workdir' +. ./test-lib.sh + +test_expect_success SYMLINKS setup ' + git config rerere.enabled true && + >world && + git add world && + test_tick && + git commit -m initial && + + echo hello >world && + test_tick && + git commit -a -m hello && + + git checkout -b side HEAD^ && + echo goodbye >world && + test_tick && + git commit -a -m goodbye && + + git checkout master +' + +test_expect_success SYMLINKS 'rerere in workdir' ' + rm -rf .git/rr-cache && + "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" . work && + ( + cd work && + test_must_fail git merge side && + git rerere status >actual && + echo world >expect && + test_cmp expect actual + ) +' + +# This fails because we don't resolve relative symlink in mkdir_in_gitdir() +# For the purpose of helping contrib/workdir/git-new-workdir users, we do not +# have to support relative symlinks, but it might be nicer to make this work +# with a relative symbolic link someday. +test_expect_failure SYMLINKS 'rerere in workdir (relative)' ' + rm -rf .git/rr-cache && + "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" . krow && + ( + cd krow && + rm -f .git/rr-cache && + ln -s ../.git/rr-cache .git/rr-cache && + test_must_fail git merge side && + git rerere status >actual && + echo world >expect && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t1050-large.sh b/t/t1050-large.sh new file mode 100755 index 0000000000..deba111bd7 --- /dev/null +++ b/t/t1050-large.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Copyright (c) 2011, Google Inc. + +test_description='adding and checking out large blobs' + +. ./test-lib.sh + +test_expect_success setup ' + git config core.bigfilethreshold 200k && + echo X | dd of=large bs=1k seek=2000 +' + +test_expect_success 'add a large file' ' + git add large && + # make sure we got a packfile and no loose objects + test -f .git/objects/pack/pack-*.pack && + test ! -f .git/objects/??/?????????????????????????????????????? +' + +test_expect_success 'checkout a large file' ' + large=$(git rev-parse :large) && + git update-index --add --cacheinfo 100644 $large another && + git checkout another && + cmp large another ;# this must not be test_cmp +' + +test_done diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index bfa2c2190d..5e29e13782 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -163,8 +163,11 @@ test_expect_success 'git resolve' ' git checkout mybranch && git merge -m "Merge upstream changes." master | sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" \ - -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output && - test_cmp resolve.expect resolve.output + -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output +' + +test_expect_success 'git resolve output' ' + test_i18ncmp resolve.expect resolve.output ' cat > show-branch2.expect << EOF diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index d0e55465ff..3db56267ee 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -876,11 +876,32 @@ test_expect_success 'check split_cmdline return' " " 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 "z$(git -c foo.CamelCase=value config foo.camelcase)" = zvalue && + test "z$(git -c foo.flag config --bool foo.flag)" = ztrue && + test_must_fail git -c name=value config core.name +' + +test_expect_success 'key sanity-checking' ' + test_must_fail git config foo=bar && + test_must_fail git config foo=.bar && + test_must_fail git config foo.ba=r && + test_must_fail git config foo.1bar && + test_must_fail git config foo."ba + z".bar && + test_must_fail git config . false && + test_must_fail git config .foo false && + test_must_fail git config foo. false && + test_must_fail git config .foo. false && + git config foo.bar true && + git config foo."ba =z".bar false +' + +test_expect_success 'git -c works with aliases of builtins' ' + git config alias.checkconfig "-c foo.check=bar config foo.check" && + echo bar >expect && + git checkconfig >actual && + test_cmp expect actual ' test_done diff --git a/t/t1303-wacky-config.sh b/t/t1303-wacky-config.sh index 080117c6bc..46103a1591 100755 --- a/t/t1303-wacky-config.sh +++ b/t/t1303-wacky-config.sh @@ -44,7 +44,7 @@ LONG_VALUE=$(printf "x%01021dx a" 7) test_expect_success 'do not crash on special long config line' ' setup && git config section.key "$LONG_VALUE" && - check section.key "fatal: bad config file line 2 in .git/config" + check section.key "$LONG_VALUE" ' test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index ff747f8229..4fd83a667a 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -6,7 +6,7 @@ test_description='Test git update-ref and basic ref logging' . ./test-lib.sh -Z=0000000000000000000000000000000000000000 +Z=$_z40 test_expect_success setup ' diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index ba25ff354d..caa687b5b4 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -28,6 +28,24 @@ test_expect_success 'oneline reflog format' ' test_cmp expect actual ' +test_expect_success 'reflog default format' ' + git reflog -1 >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +commit e46513e +Reflog: HEAD@{0} (C O Mitter <committer@example.com>) +Reflog message: commit (initial): one +Author: A U Thor <author@example.com> + + one +EOF +test_expect_success 'override reflog default format' ' + git reflog --format=short -1 >actual && + test_cmp expect actual +' + cat >expect <<'EOF' Reflog: HEAD@{Thu Apr 7 15:13:13 2005 -0700} (C O Mitter <committer@example.com>) Reflog message: commit (initial): one diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index da6252b117..63849836c8 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -7,7 +7,6 @@ test_expect_success 'setup' ' EMPTY_TREE=$(git write-tree) && EMPTY_BLOB=$(git hash-object -t blob --stdin </dev/null) && CHANGED_BLOB=$(echo changed | git hash-object -t blob --stdin) && - ZEROES=0000000000000000000000000000000000000000 && EMPTY_BLOB7=$(echo $EMPTY_BLOB | sed "s/\(.......\).*/\1/") && CHANGED_BLOB7=$(echo $CHANGED_BLOB | sed "s/\(.......\).*/\1/") && @@ -239,10 +238,10 @@ test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' ' test_expect_success 'diff-index respects work tree under .git dir' ' cat >diff-index-cached.expected <<-EOF && - :000000 100644 $ZEROES $EMPTY_BLOB A sub/dir/tracked + :000000 100644 $_z40 $EMPTY_BLOB A sub/dir/tracked EOF cat >diff-index.expected <<-EOF && - :000000 100644 $ZEROES $ZEROES A sub/dir/tracked + :000000 100644 $_z40 $_z40 A sub/dir/tracked EOF ( @@ -258,7 +257,7 @@ test_expect_success 'diff-index respects work tree under .git dir' ' test_expect_success 'diff-files respects work tree under .git dir' ' cat >diff-files.expected <<-EOF && - :100644 100644 $EMPTY_BLOB $ZEROES M sub/dir/tracked + :100644 100644 $EMPTY_BLOB $_z40 M sub/dir/tracked EOF ( diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh index 9f8adb1f82..0843a1c13b 100755 --- a/t/t1506-rev-parse-diagnosis.sh +++ b/t/t1506-rev-parse-diagnosis.sh @@ -6,6 +6,16 @@ exec </dev/null . ./test-lib.sh +test_did_you_mean () +{ + sq="'" && + cat >expected <<-EOF && + fatal: Path '$2$3' $4, but not ${5:-$sq$3$sq}. + Did you mean '$1:$2$3'${2:+ aka $sq$1:./$3$sq}? + EOF + test_cmp expected error +} + HASH_file= test_expect_success 'set up basic repo' ' @@ -106,7 +116,7 @@ test_expect_success 'incorrect file in sha1:path' ' grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error && (cd subdir && test_must_fail git rev-parse HEAD:file2.txt 2> error && - grep "Did you mean '"'"'HEAD:subdir/file2.txt'"'"'?" error ) + test_did_you_mean HEAD subdir/ file2.txt exists ) ' test_expect_success 'incorrect file in :path and :N:path' ' @@ -115,14 +125,14 @@ test_expect_success 'incorrect file in :path and :N:path' ' test_must_fail git rev-parse :1:nothing.txt 2> error && grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error && test_must_fail git rev-parse :1:file.txt 2> error && - grep "Did you mean '"'"':0:file.txt'"'"'?" error && + test_did_you_mean ":0" "" file.txt "is in the index" "at stage 1" && (cd subdir && test_must_fail git rev-parse :1:file.txt 2> error && - grep "Did you mean '"'"':0:file.txt'"'"'?" error && + test_did_you_mean ":0" "" file.txt "is in the index" "at stage 1" && test_must_fail git rev-parse :file2.txt 2> error && - grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error && + test_did_you_mean ":0" subdir/ file2.txt "is in the index" && test_must_fail git rev-parse :2:file2.txt 2> error && - grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error) && + test_did_you_mean :0 subdir/ file2.txt "is in the index") && test_must_fail git rev-parse :disk-only.txt 2> error && grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error ' diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh index 15101d5e03..ec50a9ad70 100755 --- a/t/t1510-repo-setup.sh +++ b/t/t1510-repo-setup.sh @@ -57,7 +57,7 @@ test_repo () { export GIT_WORK_TREE fi && rm -f trace && - GIT_TRACE="$(pwd)/trace" git symbolic-ref HEAD >/dev/null && + GIT_TRACE_SETUP="$(pwd)/trace" git symbolic-ref HEAD >/dev/null && grep '^setup: ' trace >result && test_cmp expected result ) diff --git a/t/t2011-checkout-invalid-head.sh b/t/t2011-checkout-invalid-head.sh index 15ebdc26eb..300f8bf25c 100755 --- a/t/t2011-checkout-invalid-head.sh +++ b/t/t2011-checkout-invalid-head.sh @@ -15,7 +15,7 @@ test_expect_success 'checkout should not start branch from a tree' ' ' test_expect_success 'checkout master from invalid HEAD' ' - echo 0000000000000000000000000000000000000000 >.git/HEAD && + echo $_z40 >.git/HEAD && git checkout master -- ' diff --git a/t/t2019-checkout-ambiguous-ref.sh b/t/t2019-checkout-ambiguous-ref.sh new file mode 100755 index 0000000000..b99d5192a9 --- /dev/null +++ b/t/t2019-checkout-ambiguous-ref.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +test_description='checkout handling of ambiguous (branch/tag) refs' +. ./test-lib.sh + +test_expect_success 'setup ambiguous refs' ' + test_commit branch file && + git branch ambiguity && + git branch vagueness && + test_commit tag file && + git tag ambiguity && + git tag vagueness HEAD:file && + test_commit other file +' + +test_expect_success 'checkout ambiguous ref succeeds' ' + git checkout ambiguity >stdout 2>stderr +' + +test_expect_success 'checkout produces ambiguity warning' ' + grep "warning.*ambiguous" stderr +' + +test_expect_success 'checkout chooses branch over tag' ' + echo refs/heads/ambiguity >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual && + echo branch >expect && + test_cmp expect file +' + +test_expect_success 'checkout reports switch to branch' ' + test_i18ngrep "Switched to branch" stderr && + test_i18ngrep ! "^HEAD is now at" stderr +' + +test_expect_success 'checkout vague ref succeeds' ' + git checkout vagueness >stdout 2>stderr && + test_set_prereq VAGUENESS_SUCCESS +' + +test_expect_success VAGUENESS_SUCCESS 'checkout produces ambiguity warning' ' + grep "warning.*ambiguous" stderr +' + +test_expect_success VAGUENESS_SUCCESS 'checkout chooses branch over tag' ' + echo refs/heads/vagueness >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual && + echo branch >expect && + test_cmp expect file +' + +test_expect_success VAGUENESS_SUCCESS 'checkout reports switch to branch' ' + test_i18ngrep "Switched to branch" stderr && + test_i18ngrep ! "^HEAD is now at" stderr +' + +test_done diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh new file mode 100755 index 0000000000..2366f0f414 --- /dev/null +++ b/t/t2020-checkout-detach.sh @@ -0,0 +1,151 @@ +#!/bin/sh + +test_description='checkout into detached HEAD state' +. ./test-lib.sh + +check_detached () { + test_must_fail git symbolic-ref -q HEAD >/dev/null +} + +check_not_detached () { + git symbolic-ref -q HEAD >/dev/null +} + +ORPHAN_WARNING='you are leaving .* commit.*behind' +check_orphan_warning() { + test_i18ngrep "$ORPHAN_WARNING" "$1" +} +check_no_orphan_warning() { + test_i18ngrep ! "$ORPHAN_WARNING" "$1" +} + +reset () { + git checkout master && + check_not_detached +} + +test_expect_success 'setup' ' + test_commit one && + test_commit two && + test_commit three && git tag -d three && + test_commit four && git tag -d four && + git branch branch && + git tag tag +' + +test_expect_success 'checkout branch does not detach' ' + reset && + git checkout branch && + check_not_detached +' + +test_expect_success 'checkout tag detaches' ' + reset && + git checkout tag && + check_detached +' + +test_expect_success 'checkout branch by full name detaches' ' + reset && + git checkout refs/heads/branch && + check_detached +' + +test_expect_success 'checkout non-ref detaches' ' + reset && + git checkout branch^ && + check_detached +' + +test_expect_success 'checkout ref^0 detaches' ' + reset && + git checkout branch^0 && + check_detached +' + +test_expect_success 'checkout --detach detaches' ' + reset && + git checkout --detach branch && + check_detached +' + +test_expect_success 'checkout --detach without branch name' ' + reset && + git checkout --detach && + check_detached +' + +test_expect_success 'checkout --detach errors out for non-commit' ' + reset && + test_must_fail git checkout --detach one^{tree} && + check_not_detached +' + +test_expect_success 'checkout --detach errors out for extra argument' ' + reset && + git checkout master && + test_must_fail git checkout --detach tag one.t && + check_not_detached +' + +test_expect_success 'checkout --detached and -b are incompatible' ' + reset && + test_must_fail git checkout --detach -b newbranch tag && + check_not_detached +' + +test_expect_success 'checkout --detach moves HEAD' ' + reset && + git checkout one && + git checkout --detach two && + git diff --exit-code HEAD && + git diff --exit-code two +' + +test_expect_success 'checkout warns on orphan commits' ' + reset && + git checkout --detach two && + echo content >orphan && + git add orphan && + git commit -a -m orphan && + git checkout master 2>stderr +' + +test_expect_success 'checkout warns on orphan commits: output' ' + check_orphan_warning stderr +' + +test_expect_success 'checkout does not warn leaving ref tip' ' + reset && + git checkout --detach two && + git checkout master 2>stderr +' + +test_expect_success 'checkout does not warn leaving ref tip' ' + check_no_orphan_warning stderr +' + +test_expect_success 'checkout does not warn leaving reachable commit' ' + reset && + git checkout --detach HEAD^ && + git checkout master 2>stderr +' + +test_expect_success 'checkout does not warn leaving reachable commit' ' + check_no_orphan_warning stderr +' + +cat >expect <<'EOF' +Your branch is behind 'master' by 1 commit, and can be fast-forwarded. +EOF +test_expect_success 'tracking count is accurate after orphan check' ' + reset && + git branch child master^ && + git config branch.child.remote . && + git config branch.child.merge refs/heads/master && + git checkout child^ && + git checkout child >stdout && + test_cmp expect stdout +' + +test_done diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh new file mode 100755 index 0000000000..5da63e9fa2 --- /dev/null +++ b/t/t2021-checkout-overwrite.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='checkout must not overwrite an untracked objects' +. ./test-lib.sh + +test_expect_success 'setup' ' + + mkdir -p a/b/c && + >a/b/c/d && + git add -A && + git commit -m base && + git tag start +' + +test_expect_success 'create a commit where dir a/b changed to file' ' + + git checkout -b file && + rm -rf a/b && + >a/b && + git add -A && + git commit -m "dir to file" +' + +test_expect_success 'checkout commit with dir must not remove untracked a/b' ' + + git rm --cached a/b && + git commit -m "un-track the file" && + test_must_fail git checkout start && + test -f a/b +' + +test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' ' + + rm -rf a/b && # cleanup if previous test failed + git checkout -f -b symlink start && + rm -rf a/b && + ln -s foo a/b && + git add -A && + git commit -m "dir to symlink" +' + +test_expect_success SYMLINKS 'checkout commit with dir must not remove untracked a/b' ' + + git rm --cached a/b && + git commit -m "un-track the symlink" && + test_must_fail git checkout start && + test -h a/b +' + +test_done diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index 0692427cb6..4cdebda6a5 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -124,7 +124,7 @@ test_expect_success 'add -n -u should not add but just report' ' after=$(git ls-files -s check top) && test "$before" = "$after" && - test_cmp expect actual + test_i18ncmp expect actual ' @@ -149,31 +149,21 @@ test_expect_success 'add -u resolves unmerged paths' ' echo 3 >path1 && echo 2 >path3 && echo 2 >path5 && - git add -u && - git ls-files -s path1 path2 path3 path4 path5 path6 >actual && - { - echo "100644 $three 0 path1" - echo "100644 $one 1 path3" - echo "100644 $one 1 path4" - echo "100644 $one 3 path5" - echo "100644 $one 3 path6" - } >expect && - test_cmp expect actual && - # Bonus tests. Explicit resolving - git add path3 path5 && + # Explicit resolving by adding removed paths should fail test_must_fail git add path4 && test_must_fail git add path6 && - git rm path4 && - git rm path6 && - git ls-files -s "path?" >actual && + # "add -u" should notice removals no matter what stages + # the index entries are in. + git add -u && + git ls-files -s path1 path2 path3 path4 path5 path6 >actual && { echo "100644 $three 0 path1" echo "100644 $two 0 path3" echo "100644 $two 0 path5" - } >expect - + } >expect && + test_cmp expect actual ' test_expect_success '"add -u non-existent" should fail' ' diff --git a/t/t2201-add-update-typechange.sh b/t/t2201-add-update-typechange.sh index 2e8f702452..954fc51e5b 100755 --- a/t/t2201-add-update-typechange.sh +++ b/t/t2201-add-update-typechange.sh @@ -4,8 +4,6 @@ test_description='more git add -u' . ./test-lib.sh -_z40=0000000000000000000000000000000000000000 - test_expect_success setup ' >xyzzy && _empty=$(git hash-object --stdin <xyzzy) && diff --git a/t/t2204-add-ignored.sh b/t/t2204-add-ignored.sh index 24afdabab7..8340ac2f07 100755 --- a/t/t2204-add-ignored.sh +++ b/t/t2204-add-ignored.sh @@ -31,18 +31,21 @@ do rm -f .git/index && test_must_fail git add "$i" 2>err && git ls-files "$i" >out && - ! test -s out && - grep -e "Use -f if" err && - cat err + ! test -s out + ' + + test_expect_success "complaints for ignored $i output" ' + test_i18ngrep -e "Use -f if" err ' test_expect_success "complaints for ignored $i with unignored file" ' rm -f .git/index && test_must_fail git add "$i" file 2>err && git ls-files "$i" >out && - ! test -s out && - grep -e "Use -f if" err && - cat err + ! test -s out + ' + test_expect_success "complaints for ignored $i with unignored file output" ' + test_i18ngrep -e "Use -f if" err ' done @@ -54,9 +57,14 @@ do cd dir && test_must_fail git add "$i" 2>err && git ls-files "$i" >out && - ! test -s out && - grep -e "Use -f if" err && - cat err + ! test -s out + ) + ' + + test_expect_success "complaints for ignored $i in dir output" ' + ( + cd dir && + test_i18ngrep -e "Use -f if" err ) ' done @@ -69,9 +77,14 @@ do cd sub && test_must_fail git add "$i" 2>err && git ls-files "$i" >out && - ! test -s out && - grep -e "Use -f if" err && - cat err + ! test -s out + ) + ' + + test_expect_success "complaints for ignored $i in sub output" ' + ( + cd sub && + test_i18ngrep -e "Use -f if" err ) ' done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 34794f8a70..0c02d56952 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -319,13 +319,13 @@ test_expect_success 'fail if the index has unresolved entries' ' test_must_fail git merge "$c5" && test_must_fail git merge "$c5" 2> out && - grep "not possible because you have unmerged files" out && + test_i18ngrep "not possible because you have unmerged files" out && git add -u && test_must_fail git merge "$c5" 2> out && - grep "You have not concluded your merge" out && + test_i18ngrep "You have not concluded your merge" out && rm -f .git/MERGE_HEAD && test_must_fail git merge "$c5" 2> out && - grep "Your local changes to the following files would be overwritten by merge:" out + test_i18ngrep "Your local changes to the following files would be overwritten by merge:" out ' test_expect_success 'merge-recursive remove conflict' ' diff --git a/t/t3032-merge-recursive-options.sh b/t/t3032-merge-recursive-options.sh index 44f5421be4..2b17311cb0 100755 --- a/t/t3032-merge-recursive-options.sh +++ b/t/t3032-merge-recursive-options.sh @@ -110,6 +110,20 @@ test_expect_success '--ignore-space-change makes merge succeed' ' git merge-recursive --ignore-space-change HEAD^ -- HEAD remote ' +test_expect_success 'naive cherry-pick fails' ' + git read-tree --reset -u HEAD && + test_must_fail git cherry-pick --no-commit remote && + git read-tree --reset -u HEAD && + test_must_fail git cherry-pick remote && + test_must_fail git update-index --refresh && + grep "<<<<<<" text.txt +' + +test_expect_success '-Xignore-space-change makes cherry-pick succeed' ' + git read-tree --reset -u HEAD && + git cherry-pick --no-commit -Xignore-space-change remote +' + test_expect_success '--ignore-space-change: our w/s-only change wins' ' q_to_cr <<-\EOF >expected && justice and holiness and is the nurse of his age and theQ diff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh new file mode 100755 index 0000000000..c286854485 --- /dev/null +++ b/t/t3102-ls-tree-wildcards.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='ls-tree with(out) globs' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir a aa "a[a]" && + touch a/one aa/two "a[a]/three" && + git add a/one aa/two "a[a]/three" && + git commit -m test +' + +test_expect_success 'ls-tree a[a] matches literally' ' + cat >expected <<EOF && +100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a[a]/three +EOF + git ls-tree -r HEAD "a[a]" >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index f308235f5d..9e69c8c926 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -46,7 +46,7 @@ test_expect_success \ 'git branch a/b/c && test -f .git/refs/heads/a/b/c' cat >expect <<EOF -0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master +$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF test_expect_success \ 'git branch -l d/e/f should create a branch and a log' \ @@ -206,7 +206,9 @@ test_expect_success 'test deleting branch deletes branch config' \ test_expect_success 'test deleting branch without config' \ 'git branch my7 s && sha1=$(git rev-parse my7 | cut -c 1-7) && - test "$(git branch -d my7 2>&1)" = "Deleted branch my7 (was $sha1)."' + echo "Deleted branch my7 (was $sha1)." >expect && + git branch -d my7 >actual 2>&1 && + test_i18ncmp expect actual' test_expect_success 'test --track without .fetch entries' \ 'git branch --track my8 && @@ -223,9 +225,14 @@ test_expect_success \ 'branch from non-branch HEAD w/--track causes failure' \ 'test_must_fail git branch --track my10 HEAD^' +test_expect_success \ + 'branch from tag w/--track causes failure' \ + 'git tag foobar && + test_must_fail git branch --track my11 foobar' + # Keep this test last, as it changes the current branch cat >expect <<EOF -0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master +$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF test_expect_success \ 'git checkout -b g/h/i -l should create a branch and a log' \ @@ -488,6 +495,15 @@ test_expect_success 'autosetuprebase always on an untracked remote branch' ' test "z$(git config branch.myr20.rebase)" = z ' +test_expect_success 'autosetuprebase always on detached HEAD' ' + git config branch.autosetupmerge always && + test_when_finished git checkout master && + git checkout HEAD^0 && + git branch my11 && + test -z "$(git config branch.my11.remote)" && + test -z "$(git config branch.my11.merge)" +' + test_expect_success 'detect misconfigured autosetuprebase (bad value)' ' git config branch.autosetuprebase garbage && test_must_fail git branch diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 6028748c6c..6b7c118e4f 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -75,7 +75,7 @@ EOF test_expect_success 'git branch shows detached HEAD properly' ' git checkout HEAD^0 && git branch >actual && - test_cmp expect actual + test_i18ncmp expect actual ' test_done diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 1921ca3a73..16de05aff9 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -101,8 +101,8 @@ test_expect_success 'edit existing notes' ' test_must_fail git notes show HEAD^ ' -test_expect_success 'cannot add note where one exists' ' - ! MSG=b2 git notes add && +test_expect_success 'cannot "git notes add -m" where notes already exists' ' + test_must_fail git notes add -m "b2" && test ! -f .git/NOTES_EDITMSG && test 1 = $(git ls-tree refs/notes/commits | wc -l) && test b3 = $(git notes show) && @@ -110,6 +110,24 @@ test_expect_success 'cannot add note where one exists' ' test_must_fail git notes show HEAD^ ' +test_expect_success 'can overwrite existing note with "git notes add -f -m"' ' + git notes add -f -m "b1" && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b1 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +test_expect_success 'add w/no options on existing note morphs into edit' ' + MSG=b2 git notes add && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b2 = $(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 && @@ -194,6 +212,13 @@ test_expect_success 'show -F notes' ' test_cmp expect-F output ' +test_expect_success 'Re-adding -F notes without -f fails' ' + echo "zyxxy" > note5 && + test_must_fail git notes add -F note5 && + git log -3 > output && + test_cmp expect-F output +' + cat >expect << EOF commit 15023535574ded8b1a89052b32673f84cf9582b8 tree e070e3af51011e47b183c33adf9736736a525709 @@ -247,6 +272,44 @@ do ' done +test_expect_success 'setup alternate notes ref' ' + git notes --ref=alternate add -m alternate +' + +test_expect_success 'git log --notes shows default notes' ' + git log -1 --notes >output && + grep xyzzy output && + ! grep alternate output +' + +test_expect_success 'git log --notes=X shows only X' ' + git log -1 --notes=alternate >output && + ! grep xyzzy output && + grep alternate output +' + +test_expect_success 'git log --notes --notes=X shows both' ' + git log -1 --notes --notes=alternate >output && + grep xyzzy output && + grep alternate output +' + +test_expect_success 'git log --no-notes resets default state' ' + git log -1 --notes --notes=alternate \ + --no-notes --notes=alternate \ + >output && + ! grep xyzzy output && + grep alternate output +' + +test_expect_success 'git log --no-notes resets ref list' ' + git log -1 --notes --notes=alternate \ + --no-notes --notes \ + >output && + grep xyzzy output && + ! grep alternate output +' + test_expect_success 'create -m notes (setup)' ' : > a5 && git add a5 && @@ -372,6 +435,81 @@ test_expect_success 'removing non-existing note should not create new commit' ' test_cmp before_commit after_commit ' +test_expect_success 'removing more than one' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + + # We have only two -- add another and make sure it stays + git notes add -m "extra" && + git notes list HEAD >after-removal-expect && + git notes remove HEAD^^ HEAD^^^ && + git notes list | sed -e "s/ .*//" >actual && + test_cmp after-removal-expect actual +' + +test_expect_success 'removing is atomic' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + test_must_fail git notes remove HEAD^^ HEAD^^^ HEAD^ && + after=$(git rev-parse --verify refs/notes/commits) && + test "$before" = "$after" +' + +test_expect_success 'removing with --ignore-missing' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + + # We have only two -- add another and make sure it stays + git notes add -m "extra" && + git notes list HEAD >after-removal-expect && + git notes remove --ignore-missing HEAD^^ HEAD^^^ HEAD^ && + git notes list | sed -e "s/ .*//" >actual && + test_cmp after-removal-expect actual +' + +test_expect_success 'removing with --ignore-missing but bogus ref' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + test_must_fail git notes remove --ignore-missing HEAD^^ HEAD^^^ NO-SUCH-COMMIT && + after=$(git rev-parse --verify refs/notes/commits) && + test "$before" = "$after" +' + +test_expect_success 'remove reads from --stdin' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + + # We have only two -- add another and make sure it stays + git notes add -m "extra" && + git notes list HEAD >after-removal-expect && + git rev-parse HEAD^^ HEAD^^^ >input && + git notes remove --stdin <input && + git notes list | sed -e "s/ .*//" >actual && + test_cmp after-removal-expect actual +' + +test_expect_success 'remove --stdin is also atomic' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + git rev-parse HEAD^^ HEAD^^^ HEAD^ >input && + test_must_fail git notes remove --stdin <input && + after=$(git rev-parse --verify refs/notes/commits) && + test "$before" = "$after" +' + +test_expect_success 'removing with --stdin --ignore-missing' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + + # We have only two -- add another and make sure it stays + git notes add -m "extra" && + git notes list HEAD >after-removal-expect && + git rev-parse HEAD^^ HEAD^^^ HEAD^ >input && + git notes remove --ignore-missing --stdin <input && + git notes list | sed -e "s/ .*//" >actual && + test_cmp after-removal-expect actual +' + test_expect_success 'list notes with "git notes list"' ' git notes list > output && test_cmp expect output diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh index c4282179b3..86bf909ee3 100755 --- a/t/t3306-notes-prune.sh +++ b/t/t3306-notes-prune.sh @@ -20,6 +20,9 @@ test_expect_success 'setup: create a few commits with notes' ' git add file3 && test_tick && git commit -m 3rd && + COMMIT_FILE=.git/objects/5e/e1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + test -f $COMMIT_FILE && + test-chmtime =+0 $COMMIT_FILE && git notes add -m "Note #3" ' diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 349eebd542..6eaecec906 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -158,15 +158,24 @@ test_expect_success 'Show verbose error when HEAD could not be detached' ' ' 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 'fail when upstream arg is missing and not on branch' ' + git checkout topic && + test_must_fail git rebase >output.out && + grep "You are not currently on a branch" output.out +' + +test_expect_success 'fail when upstream arg is missing and not configured' ' + git checkout -b no-config topic && + test_must_fail git rebase >output.out && + grep "branch.no-config.merge" output.out +' + +test_expect_success 'default to @{upstream} when upstream arg is missing' ' + git checkout -b default topic && + git config branch.default.remote . + git config branch.default.merge refs/heads/master + git rebase && + test "$(git rev-parse default~1)" = "$(git rev-parse master)" ' test_expect_success 'rebase -q is quiet' ' diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 64446e3db3..826500bd18 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -35,6 +35,11 @@ test_expect_success 'rebase with git am -3 (default)' ' test_must_fail git rebase master ' +test_expect_success 'rebase --skip can not be used with other options' ' + test_must_fail git rebase -v --skip && + test_must_fail git rebase --skip -v +' + test_expect_success 'rebase --skip with am -3' ' git rebase --skip ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 7d8147bb93..47c8371c7e 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -317,7 +317,7 @@ test_expect_success '--continue tries to commit' ' ' test_expect_success 'verbose flag is heeded, even after --continue' ' - git reset --hard HEAD@{1} && + git reset --hard master@{1} && test_tick && test_must_fail git rebase -v -i --onto new-branch1 HEAD^ && echo resolved > file1 && diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index e573dc845b..a6a6c40a98 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -84,6 +84,16 @@ testrebase() { test_cmp reflog_before reflog_after && rm reflog_before reflog_after ' + + test_expect_success 'rebase --abort can not be used with other options' ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase$type master && + test_must_fail git rebase -v --abort && + test_must_fail git rebase --abort -v && + git rebase --abort + ' } testrebase "" .git/rebase-apply diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh index 19341e5ca1..08201e2331 100755 --- a/t/t3409-rebase-preserve-merges.sh +++ b/t/t3409-rebase-preserve-merges.sh @@ -27,7 +27,17 @@ export GIT_AUTHOR_EMAIL # \ # B2 <-- origin/topic # -# In both cases, 'topic' is rebased onto 'origin/topic'. +# Clone 3 (no-ff merge): +# +# A1--A2--B3 <-- origin/master +# \ +# B1------M <-- topic +# \ / +# \--A3 <-- topic2 +# \ +# B2 <-- origin/topic +# +# In all cases, 'topic' is rebased onto 'origin/topic'. test_expect_success 'setup for merge-preserving rebase' \ 'echo First > A && @@ -61,6 +71,16 @@ test_expect_success 'setup for merge-preserving rebase' \ git commit -m "Merge origin/master into topic" ) && + git clone ./. clone3 && + ( + cd clone3 && + git checkout -b topic2 origin/topic && + echo Sixth > A && + git commit -a -m "Modify A3" && + git checkout -b topic origin/topic && + git merge --no-ff topic2 + ) && + git checkout topic && echo Fourth >> B && git commit -a -m "Modify B2" @@ -93,4 +113,14 @@ test_expect_success '--continue works after a conflict' ' ) ' +test_expect_success 'rebase -p preserves no-ff merges' ' + ( + cd clone3 && + git fetch && + git rebase -p origin/topic && + test 3 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) && + test 1 = $(git rev-list --all --pretty=oneline | grep "Merge branch" | wc -l) + ) +' + test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 3b0d27350e..1e855cdae5 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -40,4 +40,59 @@ test_expect_success 'non-interactive rebase --continue works with touched file' git rebase --continue ' +test_expect_success 'rebase --continue can not be used with other options' ' + test_must_fail git rebase -v --continue && + test_must_fail git rebase --continue -v +' + +test_expect_success 'rebase --continue remembers merge strategy and options' ' + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F2-on-topic-branch && + test_commit "commit-new-file-F3-on-topic-branch" F3 32 && + test_when_finished "rm -fr test-bin funny.was.run" && + mkdir test-bin && + cat >test-bin/git-merge-funny <<-EOF + #!$SHELL_PATH + case "\$1" in --opt) ;; *) exit 2 ;; esac + shift && + >funny.was.run && + exec git merge-recursive "\$@" + EOF + chmod +x test-bin/git-merge-funny && + ( + PATH=./test-bin:$PATH + test_must_fail git rebase -s funny -Xopt master topic + ) && + test -f funny.was.run && + rm funny.was.run && + echo "Resolved" >F2 && + git add F2 && + ( + PATH=./test-bin:$PATH + git rebase --continue + ) && + test -f funny.was.run +' + +test_expect_success 'rebase --continue remembers --rerere-autoupdate' ' + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F3-on-topic-branch && + git checkout master + test_commit "commit-new-file-F3" F3 3 && + git config rerere.enabled true && + test_must_fail git rebase -m master topic && + echo "Resolved" >F2 && + git add F2 && + test_must_fail git rebase --continue && + echo "Resolved" >F3 && + git add F3 && + git rebase --continue && + git reset --hard topic@{1} && + test_must_fail git rebase -m --rerere-autoupdate master && + test "$(cat F2)" = "Resolved" && + test_must_fail git rebase --continue && + test "$(cat F3)" = "Resolved" && + git rebase --continue +' + test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 043954422c..595d2ff990 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -96,7 +96,7 @@ test_expect_success 'revert forbidden on dirty working tree' ' echo content >extra_file && git add extra_file && test_must_fail git revert HEAD 2>errors && - grep "Your local changes would be overwritten by " errors + test_i18ngrep "Your local changes would be overwritten by " errors ' diff --git a/t/t3503-cherry-pick-root.sh b/t/t3503-cherry-pick-root.sh index b0faa29918..9aefe3a1be 100755 --- a/t/t3503-cherry-pick-root.sh +++ b/t/t3503-cherry-pick-root.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='test cherry-picking a root commit' +test_description='test cherry-picking (and reverting) a root commit' . ./test-lib.sh @@ -23,7 +23,30 @@ test_expect_success setup ' test_expect_success 'cherry-pick a root commit' ' git cherry-pick master && - test first = $(cat file1) + echo first >expect && + test_cmp expect file1 + +' + +test_expect_success 'revert a root commit' ' + + git revert master && + test_path_is_missing file1 + +' + +test_expect_success 'cherry-pick a root commit with an external strategy' ' + + git cherry-pick --strategy=resolve master && + echo first >expect && + test_cmp expect file1 + +' + +test_expect_success 'revert a root commit with an external strategy' ' + + git revert --strategy=resolve master && + test_path_is_missing file1 ' diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index 607bf25d8f..212ec54aaf 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -11,6 +11,18 @@ test_description='test cherry-pick and revert with conflicts . ./test-lib.sh +test_cmp_rev () { + git rev-parse --verify "$1" >expect.rev && + git rev-parse --verify "$2" >actual.rev && + test_cmp expect.rev actual.rev +} + +pristine_detach () { + git checkout -f "$1^0" && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x +} + test_expect_success setup ' echo unrelated >unrelated && @@ -23,13 +35,7 @@ test_expect_success setup ' ' 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 && + pristine_detach initial && head=$(git rev-parse HEAD) && test_must_fail git cherry-pick picked && @@ -39,33 +45,96 @@ test_expect_success 'failed cherry-pick does not advance HEAD' ' ' test_expect_success 'advice from failed cherry-pick' " - 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 && + pristine_detach initial && picked=\$(git rev-parse --short picked) && cat <<-EOF >expected && error: could not apply \$picked... picked hint: after resolving the conflicts, mark the corrected paths hint: with 'git add <paths>' or 'git rm <paths>' - hint: and commit the result with 'git commit -c \$picked' + hint: and commit the result with 'git commit' EOF test_must_fail git cherry-pick picked 2>actual && - test_cmp expected actual + test_i18ncmp expected actual " -test_expect_success 'failed cherry-pick produces dirty index' ' +test_expect_success 'failed cherry-pick sets CHERRY_PICK_HEAD' ' + pristine_detach initial && + test_must_fail git cherry-pick picked && + test_cmp_rev picked CHERRY_PICK_HEAD +' - git checkout -f initial^0 && - git read-tree -u --reset HEAD && - git clean -d -f -f -q -x && +test_expect_success 'successful cherry-pick does not set CHERRY_PICK_HEAD' ' + pristine_detach initial && + git cherry-pick base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD +' + +test_expect_success 'cherry-pick --no-commit does not set CHERRY_PICK_HEAD' ' + pristine_detach initial && + git cherry-pick --no-commit base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD +' + +test_expect_success 'GIT_CHERRY_PICK_HELP suppresses CHERRY_PICK_HEAD' ' + pristine_detach initial && + ( + GIT_CHERRY_PICK_HELP="and then do something else" && + export GIT_CHERRY_PICK_HELP && + test_must_fail git cherry-pick picked + ) && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD +' + +test_expect_success 'git reset clears CHERRY_PICK_HEAD' ' + pristine_detach initial && + + test_must_fail git cherry-pick picked && + git reset && + + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD +' - git update-index --refresh && - git diff-index --exit-code HEAD && +test_expect_success 'failed commit does not clear CHERRY_PICK_HEAD' ' + pristine_detach initial && + + test_must_fail git cherry-pick picked && + test_must_fail git commit && + + test_cmp_rev picked CHERRY_PICK_HEAD +' + +test_expect_success 'cancelled commit does not clear CHERRY_PICK_HEAD' ' + pristine_detach initial && + + test_must_fail git cherry-pick picked && + echo resolved >foo && + git add foo && + git update-index --refresh -q && + test_must_fail git diff-index --exit-code HEAD && + ( + GIT_EDITOR=false && + export GIT_EDITOR && + test_must_fail git commit + ) && + + test_cmp_rev picked CHERRY_PICK_HEAD +' + +test_expect_success 'successful commit clears CHERRY_PICK_HEAD' ' + pristine_detach initial && + + test_must_fail git cherry-pick picked && + echo resolved >foo && + git add foo && + git commit && + + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD +' + +test_expect_success 'failed cherry-pick produces dirty index' ' + pristine_detach initial && test_must_fail git cherry-pick picked && @@ -74,9 +143,7 @@ test_expect_success 'failed cherry-pick produces dirty index' ' ' test_expect_success 'failed cherry-pick registers participants in index' ' - - git read-tree -u --reset HEAD && - git clean -d -f -f -q -x && + pristine_detach initial && { git checkout base -- foo && git ls-files --stage foo && @@ -90,10 +157,7 @@ test_expect_success 'failed cherry-pick registers participants in index' ' 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 && + git read-tree -u --reset HEAD && test_must_fail git cherry-pick picked && git ls-files --stage --unmerged > actual && @@ -102,10 +166,7 @@ test_expect_success 'failed cherry-pick registers participants in index' ' ' 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 && + pristine_detach initial && cat <<-EOF > expected && <<<<<<< HEAD a @@ -114,9 +175,6 @@ test_expect_success 'failed cherry-pick describes conflict in work tree' ' >>>>>>> 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 && @@ -124,11 +182,8 @@ test_expect_success 'failed cherry-pick describes conflict in work tree' ' ' test_expect_success 'diff3 -m style' ' - + pristine_detach initial && 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 @@ -139,9 +194,6 @@ test_expect_success 'diff3 -m style' ' >>>>>>> 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 && @@ -149,10 +201,8 @@ test_expect_success 'diff3 -m style' ' ' 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 && + pristine_detach initial && cat <<-EOF > expected && <<<<<<< HEAD a @@ -173,10 +223,7 @@ test_expect_success 'revert also handles conflicts sanely' ' 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 && + git read-tree -u --reset HEAD && head=$(git rev-parse HEAD) && test_must_fail git revert picked && @@ -192,10 +239,8 @@ test_expect_success 'revert also handles conflicts sanely' ' ' test_expect_success 'revert conflict, diff3 -m style' ' + pristine_detach initial && 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 @@ -206,9 +251,6 @@ test_expect_success 'revert conflict, diff3 -m style' ' >>>>>>> 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 && diff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh index 948ca1bce6..df921d1f33 100755 --- a/t/t3509-cherry-pick-merge-df.sh +++ b/t/t3509-cherry-pick-merge-df.sh @@ -3,12 +3,14 @@ test_description='Test cherry-pick with directory/file conflicts' . ./test-lib.sh -test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' ' +test_expect_success 'Initialize repository' ' mkdir a && >a/f && git add a && - git commit -m a && + git commit -m a +' +test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' ' mkdir b && ln -s ../a b/a && git add b && diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index cd093bd347..9fd28bcf34 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -240,11 +240,10 @@ test_expect_success 'refresh index before checking if it is up-to-date' ' test_expect_success 'choking "git rm" should not let it die with cruft' ' git reset -q --hard && - H=0000000000000000000000000000000000000000 && i=0 && while test $i -lt 12000 do - echo "100644 $H 0 some-file-$i" + echo "100644 $_z40 0 some-file-$i" i=$(( $i + 1 )) done | git update-index --index-info && git rm -n "some-file-*" | :; diff --git a/t/t3700-add.sh b/t/t3700-add.sh index ec7108358e..575d9508a0 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -268,8 +268,12 @@ test_expect_success 'git add --dry-run of existing changed file' " 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 + test_must_fail git add --dry-run track-this ignored-file >actual 2>&1 +" + +test_expect_success 'git add --dry-run of an existing file output' " + echo \"fatal: pathspec 'ignored-file' did not match any files\" >expect && + test_i18ncmp expect actual " cat >expect.err <<\EOF @@ -283,9 +287,12 @@ 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_must_fail git add --dry-run --ignore-missing track-this ignored-file >actual.out 2>actual.err +' + +test_expect_success 'git add --dry-run --ignore-missing of non-existing file output' ' + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d6327e7c74..9e236f9cc0 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -82,10 +82,9 @@ EOF ' test_expect_success PERL 'setup fake editor' ' - cat >fake_editor.sh <<EOF - EOF + >fake_editor.sh && chmod a+x fake_editor.sh && - test_set_editor "$(pwd)/fake_editor.sh" && + test_set_editor "$(pwd)/fake_editor.sh" ' test_expect_success PERL 'dummy edit works' ' @@ -295,4 +294,40 @@ test_expect_success PERL 'deleting an empty file' ' test_cmp expected diff ' +test_expect_success PERL 'split hunk setup' ' + git reset --hard && + for i in 10 20 30 40 50 60 + do + echo $i + done >test && + git add test && + test_tick && + git commit -m test && + + for i in 10 15 20 21 22 23 24 30 40 50 60 + do + echo $i + done >test +' + +test_expect_success PERL 'split hunk "add -p (edit)"' ' + # Split, say Edit and do nothing. Then: + # + # 1. Broken version results in a patch that does not apply and + # only takes [y/n] (edit again) so the first q is discarded + # and then n attempts to discard the edit. Repeat q enough + # times to get out. + # + # 2. Correct version applies the (not)edited version, and asks + # about the next hunk, against wich we say q and program + # exits. + for a in s e q n q q + do + echo $a + done | + EDITOR=: git add -p && + git diff >actual && + ! grep "^+15" actual +' + test_done diff --git a/t/t3703-add-magic-pathspec.sh b/t/t3703-add-magic-pathspec.sh new file mode 100755 index 0000000000..e508246c0c --- /dev/null +++ b/t/t3703-add-magic-pathspec.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +test_description='magic pathspec tests using git-add' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir sub anothersub && + : >sub/foo && + : >anothersub/foo +' + +test_expect_success 'add :/' " + cat >expected <<-EOF && + add 'anothersub/foo' + add 'expected' + add 'sub/actual' + add 'sub/foo' + EOF + (cd sub && git add -n :/ >actual) && + test_cmp expected sub/actual +" + +cat >expected <<EOF +add 'anothersub/foo' +EOF + +test_expect_success 'add :/anothersub' ' + (cd sub && git add -n :/anothersub >actual) && + test_cmp expected sub/actual +' + +test_expect_success 'add :/non-existent' ' + (cd sub && test_must_fail git add -n :/non-existent) +' + +cat >expected <<EOF +add 'sub/foo' +EOF + +test_expect_success 'a file with the same (long) magic name exists' ' + : >":(icase)ha" && + test_must_fail git add -n ":(icase)ha" && + git add -n "./:(icase)ha" +' + +if mkdir ":" 2>/dev/null +then + test_set_prereq COLON_DIR +fi + +test_expect_success COLON_DIR 'a file with the same (short) magic name exists' ' + : >":/bar" && + test_must_fail git add -n :/bar && + git add -n "./:/bar" +' + +test_done diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 6fd560ccf1..5c72540642 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -37,14 +37,32 @@ test_expect_success 'parents of stash' ' test_cmp output expect ' -test_expect_success 'apply needs clean working directory' ' - echo 4 > other-file && +test_expect_success 'applying bogus stash does nothing' ' + test_must_fail git stash apply stash@{1} && + echo 1 >expect && + test_cmp expect file +' + +test_expect_success 'apply does not need clean working directory' ' + echo 4 >other-file && git add other-file && - echo 5 > other-file && - test_must_fail git stash apply + echo 5 >other-file && + git stash apply && + echo 3 >expect && + test_cmp expect file +' + +test_expect_success 'apply does not clobber working directory changes' ' + git reset --hard && + echo 4 >file && + test_must_fail git stash apply && + echo 4 >expect && + test_cmp expect file ' test_expect_success 'apply stashed changes' ' + git reset --hard && + echo 5 >other-file && git add other-file && test_tick && git commit -m other-file && @@ -218,6 +236,14 @@ test_expect_success 'stash -k' ' test bar,bar4 = $(cat file),$(cat file2) ' +test_expect_success 'stash --no-keep-index' ' + echo bar33 > file && + echo bar44 > file2 && + git add file2 && + git stash --no-keep-index && + test bar,bar2 = $(cat file),$(cat file2) +' + test_expect_success 'stash --invalid-option' ' echo bar5 > file && echo bar6 > file2 && @@ -537,11 +563,11 @@ test_expect_success 'invalid ref of the form stash@{n}, n >= N' ' echo bar6 > file2 && git add file2 && git stash && - test_must_fail git drop stash@{1} && - test_must_fail git pop stash@{1} && - test_must_fail git apply stash@{1} && - test_must_fail git show stash@{1} && - test_must_fail git branch tmp stash@{1} && + test_must_fail git stash drop stash@{1} && + test_must_fail git stash pop stash@{1} && + test_must_fail git stash apply stash@{1} && + test_must_fail git stash show stash@{1} && + test_must_fail git stash branch tmp stash@{1} && git stash drop ' @@ -556,4 +582,23 @@ test_expect_success 'stash branch should not drop the stash if the branch exists git rev-parse stash@{0} -- ' +test_expect_success 'stash apply shows status same as git status (relative to current directory)' ' + git stash clear && + echo 1 >subdir/subfile1 && + echo 2 >subdir/subfile2 && + git add subdir/subfile1 && + git commit -m subdir && + ( + cd subdir && + echo x >subfile1 && + echo x >../file && + git status >../expect && + git stash && + sane_unset GIT_MERGE_VERBOSITY && + git stash apply + ) | + sed -e 1,2d >actual && # drop "Saved..." and "HEAD is now..." + test_cmp expect actual +' + test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh index 1e7193ac0b..781fd71681 100755 --- a/t/t3904-stash-patch.sh +++ b/t/t3904-stash-patch.sh @@ -48,6 +48,18 @@ test_expect_success PERL 'git stash -p --no-keep-index' ' verify_state bar dummy bar_index ' +test_expect_success PERL 'git stash --no-keep-index -p' ' + set_state dir/foo work index && + set_state bar bar_work bar_index && + (echo n; echo y) | git stash save --no-keep-index -p && + verify_state dir/foo head head && + verify_state bar bar_work dummy && + git reset --hard && + git stash apply --index && + verify_state dir/foo work index && + verify_state bar dummy bar_index +' + test_expect_success PERL 'none of this moved HEAD' ' verify_saved_head ' diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 71bac83dd5..844277cfa6 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -71,10 +71,35 @@ test_expect_success 'favour same basenames over different ones' ' git rm path1 && mkdir subdir && git mv another-path subdir/path1 && - git status | grep "renamed: .*path1 -> subdir/path1"' + git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"' -test_expect_success 'favour same basenames even with minor differences' ' +test_expect_success 'favour same basenames even with minor differences' ' git show HEAD:path1 | sed "s/15/16/" > subdir/path1 && - git status | grep "renamed: .*path1 -> subdir/path1"' + git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"' + +test_expect_success 'setup for many rename source candidates' ' + git reset --hard && + for i in 0 1 2 3 4 5 6 7 8 9; + do + for j in 0 1 2 3 4 5 6 7 8 9; + do + echo "$i$j" >"path$i$j" + done + done && + git add "path??" && + test_tick && + git commit -m "hundred" && + (cat path1; echo new) >new-path && + echo old >>path1 && + git add new-path path1 && + git diff -l 4 -C -C --cached --name-status >actual 2>actual.err && + sed -e "s/^\([CM]\)[0-9]* /\1 /" actual >actual.munged && + cat >expect <<-EOF && + C path1 new-path + M path1 + EOF + test_cmp expect actual.munged && + grep warning actual.err +' test_done diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index 9fb8ca06a8..a5e8b83083 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -126,15 +126,12 @@ cat >.test-recursive-AB <<\EOF :100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M Z/NM EOF -x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -x40="$x40$x40$x40$x40$x40$x40$x40$x40" -z40='0000000000000000000000000000000000000000' cmp_diff_files_output () { # diff-files never reports additions. Also it does not fill in the # object ID for the changed files because it wants you to look at the # filesystem. sed <"$2" >.test-tmp \ - -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\) /'$z40'\1 /' && + -e '/^:000000 /d;s/'$_x40'\( [MCRNDU][0-9]*\) /'$_z40'\1 /' && test_cmp "$1" .test-tmp } diff --git a/t/t4003-diff-rename-1.sh b/t/t4003-diff-rename-1.sh index c6130c4019..bfa8835638 100755 --- a/t/t4003-diff-rename-1.sh +++ b/t/t4003-diff-rename-1.sh @@ -29,7 +29,7 @@ test_expect_success \ # copy-and-edit one, and rename-and-edit the other. We do not say # anything about rezrov. -GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current +GIT_DIFF_OPTS=--unified=0 git diff-index -C -p $tree >current cat >expected <<\EOF diff --git a/COPYING b/COPYING.1 copy from COPYING diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh index 92a65f4852..6e562c80d1 100755 --- a/t/t4004-diff-rename-symlink.sh +++ b/t/t4004-diff-rename-symlink.sh @@ -35,7 +35,7 @@ test_expect_success SYMLINKS \ # a new creation. test_expect_success SYMLINKS 'setup diff output' " - GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current && + GIT_DIFF_OPTS=--unified=0 git diff-index -C -p $tree >current && cat >expected <<\EOF diff --git a/bozbar b/bozbar new file mode 120000 diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh index 1ba359d478..77d7f4946f 100755 --- a/t/t4005-diff-rename-2.sh +++ b/t/t4005-diff-rename-2.sh @@ -29,7 +29,7 @@ test_expect_success \ # and COPYING.2 are based on COPYING, and do not say anything about # rezrov. -git diff-index -M $tree >current +git diff-index -C $tree >current cat >expected <<\EOF :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1 diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index d79d9e1e71..73b4a24f5e 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -173,8 +173,8 @@ test_expect_success \ 'compare_diff_raw expected current' test_expect_success \ - 'run diff with -B -M' \ - 'git diff-index -B -M "$tree" >current' + 'run diff with -B -C' \ + 'git diff-index -B -C "$tree" >current' cat >expected <<\EOF :100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095 file0 file1 diff --git a/t/t4009-diff-rename-4.sh b/t/t4009-diff-rename-4.sh index de3f17478e..f22c8e3dba 100755 --- a/t/t4009-diff-rename-4.sh +++ b/t/t4009-diff-rename-4.sh @@ -29,7 +29,7 @@ test_expect_success \ # and COPYING.2 are based on COPYING, and do not say anything about # rezrov. -git diff-index -z -M $tree >current +git diff-index -z -C $tree >current cat >expected <<\EOF :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 94df7ae53a..fbc8cd8f05 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -70,4 +70,36 @@ test_expect_success 'diff-tree pathspec' ' test_cmp expected current ' +EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 + +test_expect_success 'diff-tree with wildcard shows dir also matches' ' + git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result && + echo file0 >expected && + test_cmp expected result +' + +test_expect_success 'diff-tree -r with wildcard' ' + git diff-tree -r --name-only $EMPTY_TREE $tree -- "*file1" >result && + echo path1/file1 >expected && + test_cmp expected result +' + +test_expect_success 'diff-tree with wildcard shows dir also matches' ' + git diff-tree --name-only $tree $tree2 -- "path1/f*" >result && + echo path1 >expected && + test_cmp expected result +' + +test_expect_success 'diff-tree -r with wildcard from beginning' ' + git diff-tree -r --name-only $tree $tree2 -- "path1/*file1" >result && + echo path1/file1 >expected && + test_cmp expected result +' + +test_expect_success 'diff-tree -r with wildcard' ' + git diff-tree -r --name-only $tree $tree2 -- "path1/f*" >result && + echo path1/file1 >expected && + test_cmp expected result +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 9a66520588..93a6f20871 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -80,18 +80,31 @@ test_expect_success setup ' git config log.showroot false && git commit --amend && + + GIT_AUTHOR_DATE="2006-06-26 00:06:00 +0000" && + GIT_COMMITTER_DATE="2006-06-26 00:06:00 +0000" && + export GIT_AUTHOR_DATE GIT_COMMITTER_DATE && + git checkout -b rearrange initial && + for i in B A; do echo $i; done >dir/sub && + git add dir/sub && + git commit -m "Rearranged lines in dir/sub" && + git checkout master && + git show-branch ' : <<\EOF ! [initial] Initial * [master] Merge branch 'side' - ! [side] Side ---- - - [master] Merge branch 'side' - *+ [side] Side - * [master^] Second -+*+ [initial] Initial + ! [rearrange] Rearranged lines in dir/sub + ! [side] Side +---- + + [rearrange] Rearranged lines in dir/sub + - [master] Merge branch 'side' + * + [side] Side + * [master^] Third + * [master~2] Second ++*++ [initial] Initial EOF V=`git version | sed -e 's/^git version //' -e 's/\./\\./g'` @@ -210,6 +223,9 @@ log -m -p master log -SF master log -S F master log -SF -p master +log -SF master --max-count=0 +log -SF master --max-count=1 +log -SF master --max-count=2 log -GF master log -GF -p master log -GF -p --pickaxe-all master @@ -284,10 +300,23 @@ diff --no-index --name-status -- dir2 dir diff --no-index dir dir3 diff master master^ side diff --dirstat master~1 master~2 +diff --dirstat initial rearrange +diff --dirstat-by-file initial rearrange EOF test_expect_success 'log -S requires an argument' ' test_must_fail git log -S ' +test_expect_success 'diff --cached on unborn branch' ' + echo ref: refs/heads/unborn >.git/HEAD && + git diff --cached >result && + test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached" result +' + +test_expect_success 'diff --cached -- file on unborn branch' ' + git diff --cached -- file0 >result && + test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" result +' + test_done diff --git a/t/t4013/diff.diff_--cached b/t/t4013/diff.diff_--cached new file mode 100644 index 0000000000..ff16e83e7c --- /dev/null +++ b/t/t4013/diff.diff_--cached @@ -0,0 +1,38 @@ +diff --git a/dir/sub b/dir/sub +new file mode 100644 +index 0000000..992913c +--- /dev/null ++++ b/dir/sub +@@ -0,0 +1,8 @@ ++A ++B ++C ++D ++E ++F ++1 ++2 +diff --git a/file0 b/file0 +new file mode 100644 +index 0000000..10a8a9f +--- /dev/null ++++ b/file0 +@@ -0,0 +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/t/t4013/diff.diff_--cached_--_file0 b/t/t4013/diff.diff_--cached_--_file0 new file mode 100644 index 0000000000..b9bb858a03 --- /dev/null +++ b/t/t4013/diff.diff_--cached_--_file0 @@ -0,0 +1,15 @@ +diff --git a/file0 b/file0 +new file mode 100644 +index 0000000..10a8a9f +--- /dev/null ++++ b/file0 +@@ -0,0 +1,9 @@ ++1 ++2 ++3 ++4 ++5 ++6 ++A ++B ++C diff --git a/t/t4013/diff.diff_--dirstat-by-file_initial_rearrange b/t/t4013/diff.diff_--dirstat-by-file_initial_rearrange new file mode 100644 index 0000000000..e48e33f678 --- /dev/null +++ b/t/t4013/diff.diff_--dirstat-by-file_initial_rearrange @@ -0,0 +1,3 @@ +$ git diff --dirstat-by-file initial rearrange + 100.0% dir/ +$ diff --git a/t/t4013/diff.diff_--dirstat_initial_rearrange b/t/t4013/diff.diff_--dirstat_initial_rearrange new file mode 100644 index 0000000000..5fb02c13bc --- /dev/null +++ b/t/t4013/diff.diff_--dirstat_initial_rearrange @@ -0,0 +1,3 @@ +$ git diff --dirstat initial rearrange + 100.0% dir/ +$ 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 1f0f9ad44b..3b4e113012 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^ @@ -1,7 +1,7 @@ $ git format-patch --stdout --cover-letter -n initial..master^ From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001 From: C O Mitter <committer@example.com> -Date: Mon, 26 Jun 2006 00:05:00 +0000 +Date: Mon, 26 Jun 2006 00:06:00 +0000 Subject: [DIFFERENT_PREFIX 0/2] *** SUBJECT HERE *** *** BLURB HERE *** diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all index d155e0bab2..44d45257da 100644 --- a/t/t4013/diff.log_--decorate=full_--all +++ b/t/t4013/diff.log_--decorate=full_--all @@ -1,4 +1,10 @@ $ git log --decorate=full --all +commit cd4e72fd96faed3f0ba949dc42967430374e2290 (refs/heads/rearrange) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Rearranged lines in dir/sub + commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, refs/heads/master) Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all index fd7c3e6439..27d3eabc26 100644 --- a/t/t4013/diff.log_--decorate_--all +++ b/t/t4013/diff.log_--decorate_--all @@ -1,4 +1,10 @@ $ git log --decorate --all +commit cd4e72fd96faed3f0ba949dc42967430374e2290 (rearrange) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Rearranged lines in dir/sub + commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, master) Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> diff --git a/t/t4013/diff.log_-SF_master_--max-count=0 b/t/t4013/diff.log_-SF_master_--max-count=0 new file mode 100644 index 0000000000..c1fc6c8731 --- /dev/null +++ b/t/t4013/diff.log_-SF_master_--max-count=0 @@ -0,0 +1,2 @@ +$ git log -SF master --max-count=0 +$ diff --git a/t/t4013/diff.log_-SF_master_--max-count=1 b/t/t4013/diff.log_-SF_master_--max-count=1 new file mode 100644 index 0000000000..c981a03814 --- /dev/null +++ b/t/t4013/diff.log_-SF_master_--max-count=1 @@ -0,0 +1,7 @@ +$ git log -SF master --max-count=1 +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third +$ diff --git a/t/t4013/diff.log_-SF_master_--max-count=2 b/t/t4013/diff.log_-SF_master_--max-count=2 new file mode 100644 index 0000000000..a6c55fd482 --- /dev/null +++ b/t/t4013/diff.log_-SF_master_--max-count=2 @@ -0,0 +1,7 @@ +$ git log -SF master --max-count=2 +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 9f64a4e2a1..92248d24c4 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -616,11 +616,11 @@ echo "fatal: --check does not make sense" > expect.check test_expect_success 'options no longer allowed for format-patch' ' test_must_fail git format-patch --name-only 2> output && - test_cmp expect.name-only output && + test_i18ncmp expect.name-only output && test_must_fail git format-patch --name-status 2> output && - test_cmp expect.name-status output && + test_i18ncmp expect.name-status output && test_must_fail git format-patch --check 2> output && - test_cmp expect.check output' + test_i18ncmp expect.check output' test_expect_success 'format-patch --numstat should produce a patch' ' git format-patch --numstat --stdout master..side > output && @@ -808,4 +808,65 @@ test_expect_success 'format-patch wraps non-quotable headers' ' sed -n "/^From: /p; /^ /p; /^$/q" <patch >from && test_cmp expect from ' + +check_author() { + echo content >>file && + git add file && + GIT_AUTHOR_NAME=$1 git commit -m author-check && + git format-patch --stdout -1 >patch && + grep ^From: patch >actual && + test_cmp expect actual +} + +cat >expect <<'EOF' +From: "Foo B. Bar" <author@example.com> +EOF +test_expect_success 'format-patch quotes dot in headers' ' + check_author "Foo B. Bar" +' + +cat >expect <<'EOF' +From: "Foo \"The Baz\" Bar" <author@example.com> +EOF +test_expect_success 'format-patch quotes double-quote in headers' ' + check_author "Foo \"The Baz\" Bar" +' + +cat >expect <<'EOF' +From: =?UTF-8?q?"F=C3=B6o=20B.=20Bar"?= <author@example.com> +EOF +test_expect_success 'rfc2047-encoded headers also double-quote 822 specials' ' + check_author "Föo B. Bar" +' + +cat >expect <<'EOF' +Subject: header with . in it +EOF +test_expect_success 'subject lines do not have 822 atom-quoting' ' + echo content >>file && + git add file && + git commit -m "header with . in it" && + git format-patch -k -1 --stdout >patch && + grep ^Subject: patch >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +Subject: [PREFIX 1/1] header with . in it +EOF +test_expect_success 'subject prefixes have space prepended' ' + git format-patch -n -1 --stdout --subject-prefix=PREFIX >patch && + grep ^Subject: patch >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +Subject: [1/1] header with . in it +EOF +test_expect_success 'empty subject prefix does not have extra space' ' + git format-patch -n -1 --stdout --subject-prefix= >patch && + grep ^Subject: patch >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 3646930623..b68c56b68c 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -9,8 +9,7 @@ test_description='Test custom diff function name patterns' LF=' ' - -cat > Beer.java << EOF +cat >Beer.java <<\EOF public class Beer { int special; @@ -29,61 +28,163 @@ public class Beer } } EOF +sed 's/beer\\/beer,\\/' <Beer.java >Beer-correct.java +cat >Beer.perl <<\EOT +package Beer; + +use strict; +use warnings; +use parent qw(Exporter); +our @EXPORT_OK = qw(round finalround); + +sub other; # forward declaration + +# hello + +sub round { + my ($n) = @_; + print "$n bottles of beer on the wall "; + print "$n bottles of beer\n"; + print "Take one down, pass it around, "; + $n = $n - 1; + print "$n bottles of beer on the wall.\n"; +} + +sub finalround +{ + print "Go to the store, buy some more\n"; + print "99 bottles of beer on the wall.\n"); +} + +sub withheredocument { + print <<"EOF" +decoy here-doc +EOF + # some lines of context + # to pad it out + print "hello\n"; +} + +__END__ + +=head1 NAME + +Beer - subroutine to output fragment of a drinking song + +=head1 SYNOPSIS + + use Beer qw(round finalround); + + sub song { + for (my $i = 99; $i > 0; $i--) { + round $i; + } + finalround; + } -sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java + song; -builtin_patterns="bibtex cpp csharp fortran html java objc pascal perl php python ruby tex" -for p in $builtin_patterns +=cut +EOT +sed -e ' + s/hello/goodbye/ + s/beer\\/beer,\\/ + s/more\\/more,\\/ + s/song;/song();/ +' <Beer.perl >Beer-correct.perl + +test_config () { + git config "$1" "$2" && + test_when_finished "git config --unset $1" +} + +test_expect_funcname () { + lang=${2-java} + test_expect_code 1 git diff --no-index -U1 \ + "Beer.$lang" "Beer-correct.$lang" >diff && + grep "^@@.*@@ $1" diff +} + +for p in bibtex cpp csharp fortran html java objc pascal perl php python ruby tex do test_expect_success "builtin $p pattern compiles" ' - echo "*.java diff=$p" > .gitattributes && - ! { git diff --no-index Beer.java Beer-correct.java 2>&1 | - grep "fatal" > /dev/null; } + echo "*.java diff=$p" >.gitattributes && + test_expect_code 1 git diff --no-index \ + Beer.java Beer-correct.java 2>msg && + ! grep fatal msg && + ! grep error msg ' test_expect_success "builtin $p wordRegex pattern compiles" ' - ! { git diff --no-index --word-diff \ - Beer.java Beer-correct.java 2>&1 | - grep "fatal" > /dev/null; } + echo "*.java diff=$p" >.gitattributes && + test_expect_code 1 git diff --no-index --word-diff \ + Beer.java Beer-correct.java 2>msg && + ! grep fatal msg && + ! grep error msg ' done test_expect_success 'default behaviour' ' rm -f .gitattributes && - git diff --no-index Beer.java Beer-correct.java | - grep "^@@.*@@ public class Beer" + test_expect_funcname "public class Beer\$" +' + +test_expect_success 'set up .gitattributes declaring drivers to test' ' + cat >.gitattributes <<-\EOF + *.java diff=java + *.perl diff=perl + EOF ' test_expect_success 'preset java pattern' ' - echo "*.java diff=java" >.gitattributes && - git diff --no-index Beer.java Beer-correct.java | - grep "^@@.*@@ public static void main(" + test_expect_funcname "public static void main(" ' -git config diff.java.funcname '!static -!String -[^ ].*s.*' +test_expect_success 'preset perl pattern' ' + test_expect_funcname "sub round {\$" perl +' + +test_expect_success 'perl pattern accepts K&R style brace placement, too' ' + test_expect_funcname "sub finalround\$" perl +' + +test_expect_success 'but is not distracted by end of <<here document' ' + test_expect_funcname "sub withheredocument {\$" perl +' + +test_expect_success 'perl pattern is not distracted by sub within POD' ' + test_expect_funcname "=head" perl +' + +test_expect_success 'perl pattern gets full line of POD header' ' + test_expect_funcname "=head1 SYNOPSIS\$" perl +' + +test_expect_success 'perl pattern is not distracted by forward declaration' ' + test_expect_funcname "package Beer;\$" perl +' test_expect_success 'custom pattern' ' - git diff --no-index Beer.java Beer-correct.java | - grep "^@@.*@@ int special;$" + test_config diff.java.funcname "!static +!String +[^ ].*s.*" && + test_expect_funcname "int special;\$" ' test_expect_success 'last regexp must not be negated' ' - git config diff.java.funcname "!static" && - git diff --no-index Beer.java Beer-correct.java 2>&1 | - grep "fatal: Last expression must not be negated:" + test_config diff.java.funcname "!static" && + test_expect_code 128 git diff --no-index Beer.java Beer-correct.java 2>msg && + grep ": Last expression must not be negated:" msg ' test_expect_success 'pattern which matches to end of line' ' - git config diff.java.funcname "Beer$" && - git diff --no-index Beer.java Beer-correct.java | - grep "^@@.*@@ Beer" + test_config diff.java.funcname "Beer\$" && + test_expect_funcname "Beer\$" ' test_expect_success 'alternation in pattern' ' - git config diff.java.xfuncname "^[ ]*((public|static).*)$" && - git diff --no-index Beer.java Beer-correct.java | - grep "^@@.*@@ public static void main(" + test_config diff.java.funcname "Beer$" && + test_config diff.java.xfuncname "^[ ]*((public|static).*)$" && + test_expect_funcname "public static void main(" ' test_done diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index a7602cf923..083f62d1d6 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -4,8 +4,6 @@ test_description='external diff interface test' . ./test-lib.sh -_z40=0000000000000000000000000000000000000000 - test_expect_success setup ' test_tick && diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh index 2a537a21e8..c00a94b9ba 100755 --- a/t/t4022-diff-rewrite.sh +++ b/t/t4022-diff-rewrite.sh @@ -11,7 +11,9 @@ test_expect_success setup ' tr \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" \ - <"$TEST_DIRECTORY"/../COPYING >test + <"$TEST_DIRECTORY"/../COPYING >test && + echo "to be deleted" >test2 && + git add test2 ' @@ -25,5 +27,44 @@ test_expect_success 'detect rewrite' ' ' +cat >expect <<EOF +diff --git a/test2 b/test2 +deleted file mode 100644 +index 4202011..0000000 +--- a/test2 ++++ /dev/null +@@ -1 +0,0 @@ +-to be deleted +EOF +test_expect_success 'show deletion diff without -D' ' + + rm test2 && + git diff -- test2 >actual && + test_cmp expect actual +' + +cat >expect <<EOF +diff --git a/test2 b/test2 +deleted file mode 100644 +index 4202011..0000000 +EOF +test_expect_success 'suppress deletion diff with -D' ' + + git diff -D -- test2 >actual && + test_cmp expect actual +' + +test_expect_success 'show deletion diff with -B' ' + + git diff -B -- test >actual && + grep "Linus Torvalds" actual +' + +test_expect_success 'suppress deletion diff with -B -D' ' + + git diff -B -D -- test >actual && + grep -v "Linus Torvalds" actual +' + test_done diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 241a74d2a2..518bf9524e 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -5,7 +5,6 @@ test_description='difference in submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh -_z40=0000000000000000000000000000000000000000 test_expect_success setup ' test_tick && test_create_repo sub && diff --git a/t/t4031-diff-rewrite-binary.sh b/t/t4031-diff-rewrite-binary.sh index 7e7b307a24..7d7470f21b 100755 --- a/t/t4031-diff-rewrite-binary.sh +++ b/t/t4031-diff-rewrite-binary.sh @@ -44,6 +44,13 @@ test_expect_success 'rewrite diff can show binary patch' ' grep "GIT binary patch" diff ' +test_expect_success 'rewrite diff --stat shows binary changes' ' + git diff -B --stat --summary >diff && + grep "Bin" diff && + grep "0 insertions.*0 deletions" diff && + grep " rewrite file" diff +' + { echo "#!$SHELL_PATH" cat <<'EOF' diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 8096d8a337..c374aa4c1c 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -4,331 +4,333 @@ test_description='word diff colors' . ./test-lib.sh -test_expect_success setup ' +cat >pre.simple <<-\EOF + h(4) - git config diff.color.old red && - git config diff.color.new green && - git config diff.color.func magenta + a = b + c +EOF +cat >post.simple <<-\EOF + h(4),hh[44] -' + a = b + c -word_diff () { - test_must_fail git diff --no-index "$@" pre post > output && - test_decode_color <output >output.decrypted && - test_cmp expect output.decrypted -} + aa = a -cat > pre <<\EOF -h(4) - -a = b + c + aeff = aeff * ( aaa ) EOF +cat >expect.letter-runs-are-words <<-\EOF + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ b/post<RESET> + <CYAN>@@ -1,3 +1,7 @@<RESET> + h(4),<GREEN>hh<RESET>[44] -cat > post <<\EOF -h(4),hh[44] - -a = b + c + a = b + c<RESET> -aa = a + <GREEN>aa = a<RESET> -aeff = aeff * ( aaa ) + <GREEN>aeff = aeff * ( aaa<RESET> ) EOF +cat >expect.non-whitespace-is-word <<-\EOF + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ b/post<RESET> + <CYAN>@@ -1,3 +1,7 @@<RESET> + h(4)<GREEN>,hh[44]<RESET> -cat > expect <<\EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index 330b04f..5ed8eff 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ b/post<RESET> -<CYAN>@@ -1,3 +1,7 @@<RESET> -<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET> - -a = b + c<RESET> + a = b + c<RESET> -<GREEN>aa = a<RESET> + <GREEN>aa = a<RESET> -<GREEN>aeff = aeff * ( aaa )<RESET> + <GREEN>aeff = aeff * ( aaa )<RESET> EOF -test_expect_success 'word diff with runs of whitespace' ' +word_diff () { + test_must_fail git diff --no-index "$@" pre post >output && + test_decode_color <output >output.decrypted && + test_cmp expect output.decrypted +} - word_diff --color-words +test_language_driver () { + lang=$1 + test_expect_success "diff driver '$lang'" ' + cp "$TEST_DIRECTORY/t4034/'"$lang"'/pre" \ + "$TEST_DIRECTORY/t4034/'"$lang"'/post" \ + "$TEST_DIRECTORY/t4034/'"$lang"'/expect" . && + echo "* diff='"$lang"'" >.gitattributes && + word_diff --color-words + ' +} +test_expect_success setup ' + git config diff.color.old red && + git config diff.color.new green && + git config diff.color.func magenta ' -test_expect_success '--word-diff=color' ' - - word_diff --word-diff=color - +test_expect_success 'set up pre and post with runs of whitespace' ' + cp pre.simple pre && + cp post.simple post ' -test_expect_success '--color --word-diff=color' ' - +test_expect_success 'word diff with runs of whitespace' ' + cat >expect <<-\EOF && + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ 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 + word_diff --color-words && + word_diff --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' ' - + 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 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' ' + 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]+} - word_diff --word-diff=plain - -' + a = b + c -test_expect_success '--word-diff=plain --no-color' ' + {+aa = a+} + {+aeff = aeff * ( aaa )+} + EOF + word_diff --word-diff=plain && word_diff --word-diff=plain --no-color - ' -cat > expect <<EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index 330b04f..5ed8eff 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ 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> +test_expect_success '--word-diff=plain --color' ' + cat >expect <<-\EOF && + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ b/post<RESET> + <CYAN>@@ -1,3 +1,7 @@<RESET> + <RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET> -<GREEN>{+aeff = aeff * ( aaa )+}<RESET> -EOF + a = b + c<RESET> -test_expect_success '--word-diff=plain --color' ' + <GREEN>{+aa = a+}<RESET> + <GREEN>{+aeff = aeff * ( aaa )+}<RESET> + EOF word_diff --word-diff=plain --color - ' -cat > expect <<\EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index 330b04f..5ed8eff 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ b/post<RESET> -<CYAN>@@ -1 +1 @@<RESET> -<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET> -<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET> - -<GREEN>aa = a<RESET> - -<GREEN>aeff = aeff * ( aaa )<RESET> -EOF - test_expect_success 'word diff without context' ' - + cat >expect <<-\EOF && + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ b/post<RESET> + <CYAN>@@ -1 +1 @@<RESET> + <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET> + <CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET> + + <GREEN>aa = a<RESET> + + <GREEN>aeff = aeff * ( aaa )<RESET> + EOF word_diff --color-words --unified=0 - ' -cat > expect <<\EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index 330b04f..5ed8eff 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ 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 -cp expect expect.letter-runs-are-words - test_expect_success 'word diff with a regular expression' ' - + cp expect.letter-runs-are-words expect && word_diff --color-words="[a-z]+" - ' -test_expect_success 'set a diff driver' ' +test_expect_success 'set up a diff driver' ' git config diff.testdriver.wordRegex "[^[:space:]]" && - cat <<EOF > .gitattributes -pre diff=testdriver -post diff=testdriver -EOF + cat <<-\EOF >.gitattributes + pre diff=testdriver + post diff=testdriver + EOF ' test_expect_success 'option overrides .gitattributes' ' - + cp expect.letter-runs-are-words expect && word_diff --color-words="[a-z]+" - ' -cat > expect <<\EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index 330b04f..5ed8eff 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ b/post<RESET> -<CYAN>@@ -1,3 +1,7 @@<RESET> -h(4)<GREEN>,hh[44]<RESET> - -a = b + c<RESET> - -<GREEN>aa = a<RESET> - -<GREEN>aeff = aeff * ( aaa )<RESET> -EOF -cp expect expect.non-whitespace-is-word - test_expect_success 'use regex supplied by driver' ' - + cp expect.non-whitespace-is-word expect && word_diff --color-words - ' -test_expect_success 'set diff.wordRegex option' ' +test_expect_success 'set up diff.wordRegex option' ' git config diff.wordRegex "[[:alnum:]]+" ' -cp expect.letter-runs-are-words expect - test_expect_success 'command-line overrides config' ' + cp expect.letter-runs-are-words expect && word_diff --color-words="[a-z]+" ' -cat > expect <<\EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index 330b04f..5ed8eff 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ b/post<RESET> -<CYAN>@@ -1,3 +1,7 @@<RESET> -h(4),<GREEN>{+hh+}<RESET>[44] - -a = b + c<RESET> +test_expect_success 'command-line overrides config: --word-diff-regex' ' + cat >expect <<-\EOF && + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ b/post<RESET> + <CYAN>@@ -1,3 +1,7 @@<RESET> + h(4),<GREEN>{+hh+}<RESET>[44] -<GREEN>{+aa = a+}<RESET> + a = b + c<RESET> -<GREEN>{+aeff = aeff * ( aaa+}<RESET> ) -EOF + <GREEN>{+aa = a+}<RESET> -test_expect_success 'command-line overrides config: --word-diff-regex' ' + <GREEN>{+aeff = aeff * ( aaa+}<RESET> ) + EOF word_diff --color --word-diff-regex="[a-z]+" ' -cp expect.non-whitespace-is-word expect - test_expect_success '.gitattributes override config' ' + cp expect.non-whitespace-is-word expect && word_diff --color-words ' -test_expect_success 'remove diff driver regex' ' - git config --unset diff.testdriver.wordRegex +test_expect_success 'setup: remove diff driver regex' ' + test_might_fail git config --unset diff.testdriver.wordRegex ' -cat > expect <<\EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index 330b04f..5ed8eff 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ b/post<RESET> -<CYAN>@@ -1,3 +1,7 @@<RESET> -h(4),<GREEN>hh[44<RESET>] - -a = b + c<RESET> +test_expect_success 'use configured regex' ' + cat >expect <<-\EOF && + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ b/post<RESET> + <CYAN>@@ -1,3 +1,7 @@<RESET> + h(4),<GREEN>hh[44<RESET>] -<GREEN>aa = a<RESET> + a = b + c<RESET> -<GREEN>aeff = aeff * ( aaa<RESET> ) -EOF + <GREEN>aa = a<RESET> -test_expect_success 'use configured regex' ' + <GREEN>aeff = aeff * ( aaa<RESET> ) + EOF word_diff --color-words ' -echo 'aaa (aaa)' > pre -echo 'aaa (aaa) aaa' > post - -cat > expect <<\EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index c29453b..be22f37 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ b/post<RESET> -<CYAN>@@ -1 +1 @@<RESET> -aaa (aaa) <GREEN>aaa<RESET> -EOF - test_expect_success 'test parsing words for newline' ' - + echo "aaa (aaa)" >pre && + echo "aaa (aaa) aaa" >post && + cat >expect <<-\EOF && + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index c29453b..be22f37 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ b/post<RESET> + <CYAN>@@ -1 +1 @@<RESET> + aaa (aaa) <GREEN>aaa<RESET> + EOF word_diff --color-words="a+" - - ' -echo '(:' > pre -echo '(' > post - -cat > expect <<\EOF -<BOLD>diff --git a/pre b/post<RESET> -<BOLD>index 289cb9d..2d06f37 100644<RESET> -<BOLD>--- a/pre<RESET> -<BOLD>+++ b/post<RESET> -<CYAN>@@ -1 +1 @@<RESET> -(<RED>:<RESET> -EOF - test_expect_success 'test when words are only removed at the end' ' - + echo "(:" >pre && + echo "(" >post && + cat >expect <<-\EOF && + <BOLD>diff --git a/pre b/post<RESET> + <BOLD>index 289cb9d..2d06f37 100644<RESET> + <BOLD>--- a/pre<RESET> + <BOLD>+++ b/post<RESET> + <CYAN>@@ -1 +1 @@<RESET> + (<RED>:<RESET> + EOF word_diff --color-words=. - ' -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' ' - + echo "(:" >pre && + echo "(" >post && + cat >expect <<-\EOF && + diff --git a/pre b/post + index 289cb9d..2d06f37 100644 + --- a/pre + +++ b/post + @@ -1 +1 @@ + -(: + +( + EOF word_diff --word-diff=plain --word-diff=none +' +test_language_driver bibtex +test_language_driver cpp +test_language_driver csharp +test_language_driver fortran +test_language_driver html +test_language_driver java +test_language_driver objc +test_language_driver pascal +test_language_driver perl +test_language_driver php +test_language_driver python +test_language_driver ruby +test_language_driver tex + +test_expect_success 'word-diff with diff.sbe' ' + cat >expect <<-\EOF && + diff --git a/pre b/post + index a1a53b5..bc8fe6d 100644 + --- a/pre + +++ b/post + @@ -1,3 +1,3 @@ + a + + [-b-]{+c+} + EOF + cat >pre <<-\EOF && + a + + b + EOF + cat >post <<-\EOF && + a + + c + EOF + test_when_finished "git config --unset diff.suppress-blank-empty" && + git config diff.suppress-blank-empty true && + word_diff --word-diff=plain ' test_done diff --git a/t/t4034/bibtex/expect b/t/t4034/bibtex/expect new file mode 100644 index 0000000000..a157774f9d --- /dev/null +++ b/t/t4034/bibtex/expect @@ -0,0 +1,15 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 95cd55b..ddcba9b 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,9 +1,10 @@<RESET> +@article{aldous1987uie,<RESET> + title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}},<RESET> + author={Aldous, <RED>D.<RESET><GREEN>David<RESET>}, + journal={Information Theory, IEEE Transactions on},<RESET> + volume={<RED>33<RESET><GREEN>Bogus.<RESET>}, + number={<RED>2<RESET><GREEN>4<RESET>}, + pages={219--223},<RESET> + year=<GREEN>1987,<RESET> +<GREEN> note={This is in fact a rather funny read since ethernet works well in practice. The<RESET> {<RED>1987<RESET><GREEN>\em pre} reference is the right one, however.<RESET>}<RED>,<RESET> +}<RESET> diff --git a/t/t4034/bibtex/post b/t/t4034/bibtex/post new file mode 100644 index 0000000000..ddcba9b2fc --- /dev/null +++ b/t/t4034/bibtex/post @@ -0,0 +1,10 @@ +@article{aldous1987uie, + title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}}, + author={Aldous, David}, + journal={Information Theory, IEEE Transactions on}, + volume={Bogus.}, + number={4}, + pages={219--223}, + year=1987, + note={This is in fact a rather funny read since ethernet works well in practice. The {\em pre} reference is the right one, however.} +} diff --git a/t/t4034/bibtex/pre b/t/t4034/bibtex/pre new file mode 100644 index 0000000000..95cd55bd7b --- /dev/null +++ b/t/t4034/bibtex/pre @@ -0,0 +1,9 @@ +@article{aldous1987uie, + title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}}, + author={Aldous, D.}, + journal={Information Theory, IEEE Transactions on}, + volume={33}, + number={2}, + pages={219--223}, + year={1987}, +} diff --git a/t/t4034/cpp/expect b/t/t4034/cpp/expect new file mode 100644 index 0000000000..37d1ea2587 --- /dev/null +++ b/t/t4034/cpp/expect @@ -0,0 +1,36 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 23d5c8a..7e8c026 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,19 +1,19 @@<RESET> +Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> } +cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl; +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>|<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>||<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z +<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>,y +<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET> diff --git a/t/t4034/cpp/post b/t/t4034/cpp/post new file mode 100644 index 0000000000..7e8c026cef --- /dev/null +++ b/t/t4034/cpp/post @@ -0,0 +1,19 @@ +Foo() : x(0&42) { bar(x); } +cout<<"Hello World?\n"<<endl; +(1) (-1e10) (0xabcdef) 'y' +[x] x->y x.y +!x ~x x++ x-- x*y x&y +x*y x/y x%y +x+y x-y +x<<y x>>y +x<y x<=y x>y x>=y +x==y x!=y +x&y +x^y +x|y +x&&y +x||y +x?y:z +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +x,y +x::y diff --git a/t/t4034/cpp/pre b/t/t4034/cpp/pre new file mode 100644 index 0000000000..23d5c8adf5 --- /dev/null +++ b/t/t4034/cpp/pre @@ -0,0 +1,19 @@ +Foo():x(0&&1){} +cout<<"Hello World!\n"<<endl; +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a ~a a++ a-- a*b a&b +a*b a/b a%b +a+b a-b +a<<b a>>b +a<b a<=b a>b a>=b +a==b a!=b +a&b +a^b +a|b +a&&b +a||b +a?b:z +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a,y +a::b diff --git a/t/t4034/csharp/expect b/t/t4034/csharp/expect new file mode 100644 index 0000000000..e5d1dd2b3d --- /dev/null +++ b/t/t4034/csharp/expect @@ -0,0 +1,35 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 9106d63..dd5f421 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,18 +1,18 @@<RESET> +Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> } +cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl; +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>|<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>||<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z +<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>,y diff --git a/t/t4034/csharp/post b/t/t4034/csharp/post new file mode 100644 index 0000000000..dd5f4218a6 --- /dev/null +++ b/t/t4034/csharp/post @@ -0,0 +1,18 @@ +Foo() : x(0&42) { bar(x); } +cout<<"Hello World?\n"<<endl; +(1) (-1e10) (0xabcdef) 'y' +[x] x->y x.y +!x ~x x++ x-- x*y x&y +x*y x/y x%y +x+y x-y +x<<y x>>y +x<y x<=y x>y x>=y +x==y x!=y +x&y +x^y +x|y +x&&y +x||y +x?y:z +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +x,y diff --git a/t/t4034/csharp/pre b/t/t4034/csharp/pre new file mode 100644 index 0000000000..9106d63e87 --- /dev/null +++ b/t/t4034/csharp/pre @@ -0,0 +1,18 @@ +Foo():x(0&&1){} +cout<<"Hello World!\n"<<endl; +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a ~a a++ a-- a*b a&b +a*b a/b a%b +a+b a-b +a<<b a>>b +a<b a<=b a>b a>=b +a==b a!=b +a&b +a^b +a|b +a&&b +a||b +a?b:z +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a,y diff --git a/t/t4034/fortran/expect b/t/t4034/fortran/expect new file mode 100644 index 0000000000..b233dbd621 --- /dev/null +++ b/t/t4034/fortran/expect @@ -0,0 +1,10 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 87f0d0b..d308da2 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,5 +1,5 @@<RESET> +print *, "Hello World<RED>!<RESET><GREEN>?<RESET>" + +DO10I = 1,10<RESET> +<RED>DO10I<RESET><GREEN>DO 10 I<RESET> = 1,10 +<RED>DO10I<RESET><GREEN>DO 1 0 I<RESET> = 1,10 diff --git a/t/t4034/fortran/post b/t/t4034/fortran/post new file mode 100644 index 0000000000..d308da2ad2 --- /dev/null +++ b/t/t4034/fortran/post @@ -0,0 +1,5 @@ +print *, "Hello World?" + +DO10I = 1,10 +DO 10 I = 1,10 +DO 1 0 I = 1,10 diff --git a/t/t4034/fortran/pre b/t/t4034/fortran/pre new file mode 100644 index 0000000000..87f0d0b031 --- /dev/null +++ b/t/t4034/fortran/pre @@ -0,0 +1,5 @@ +print *, "Hello World!" + +DO10I = 1,10 +DO10I = 1,10 +DO10I = 1,10 diff --git a/t/t4034/html/expect b/t/t4034/html/expect new file mode 100644 index 0000000000..447b49ab6d --- /dev/null +++ b/t/t4034/html/expect @@ -0,0 +1,8 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 8ca4aea..46921e5 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,3 +1,3 @@<RESET> +<tag <GREEN>newattr="newvalue"<RESET>><GREEN>added<RESET> content</tag> +<tag attr=<RED>"value"<RESET><GREEN>"newvalue"<RESET>><RED>content<RESET><GREEN>changed<RESET></tag> +<<RED>tag<RESET><GREEN>newtag<RESET>>content <RED>&entity;<RESET><GREEN>&newentity;<RESET><<RED>/tag<RESET><GREEN>/newtag<RESET>> diff --git a/t/t4034/html/post b/t/t4034/html/post new file mode 100644 index 0000000000..46921e586c --- /dev/null +++ b/t/t4034/html/post @@ -0,0 +1,3 @@ +<tag newattr="newvalue">added content</tag> +<tag attr="newvalue">changed</tag> +<newtag>content &newentity;</newtag> diff --git a/t/t4034/html/pre b/t/t4034/html/pre new file mode 100644 index 0000000000..8ca4aeae83 --- /dev/null +++ b/t/t4034/html/pre @@ -0,0 +1,3 @@ +<tag>content</tag> +<tag attr="value">content</tag> +<tag>content &entity;</tag> diff --git a/t/t4034/java/expect b/t/t4034/java/expect new file mode 100644 index 0000000000..37d1ea2587 --- /dev/null +++ b/t/t4034/java/expect @@ -0,0 +1,36 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 23d5c8a..7e8c026 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,19 +1,19 @@<RESET> +Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> } +cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl; +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>|<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>||<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z +<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>,y +<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET> diff --git a/t/t4034/java/post b/t/t4034/java/post new file mode 100644 index 0000000000..7e8c026cef --- /dev/null +++ b/t/t4034/java/post @@ -0,0 +1,19 @@ +Foo() : x(0&42) { bar(x); } +cout<<"Hello World?\n"<<endl; +(1) (-1e10) (0xabcdef) 'y' +[x] x->y x.y +!x ~x x++ x-- x*y x&y +x*y x/y x%y +x+y x-y +x<<y x>>y +x<y x<=y x>y x>=y +x==y x!=y +x&y +x^y +x|y +x&&y +x||y +x?y:z +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +x,y +x::y diff --git a/t/t4034/java/pre b/t/t4034/java/pre new file mode 100644 index 0000000000..23d5c8adf5 --- /dev/null +++ b/t/t4034/java/pre @@ -0,0 +1,19 @@ +Foo():x(0&&1){} +cout<<"Hello World!\n"<<endl; +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a ~a a++ a-- a*b a&b +a*b a/b a%b +a+b a-b +a<<b a>>b +a<b a<=b a>b a>=b +a==b a!=b +a&b +a^b +a|b +a&&b +a||b +a?b:z +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a,y +a::b diff --git a/t/t4034/objc/expect b/t/t4034/objc/expect new file mode 100644 index 0000000000..e5d1dd2b3d --- /dev/null +++ b/t/t4034/objc/expect @@ -0,0 +1,35 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 9106d63..dd5f421 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,18 +1,18 @@<RESET> +Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> } +cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl; +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>|<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>||<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z +<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>,y diff --git a/t/t4034/objc/post b/t/t4034/objc/post new file mode 100644 index 0000000000..dd5f4218a6 --- /dev/null +++ b/t/t4034/objc/post @@ -0,0 +1,18 @@ +Foo() : x(0&42) { bar(x); } +cout<<"Hello World?\n"<<endl; +(1) (-1e10) (0xabcdef) 'y' +[x] x->y x.y +!x ~x x++ x-- x*y x&y +x*y x/y x%y +x+y x-y +x<<y x>>y +x<y x<=y x>y x>=y +x==y x!=y +x&y +x^y +x|y +x&&y +x||y +x?y:z +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +x,y diff --git a/t/t4034/objc/pre b/t/t4034/objc/pre new file mode 100644 index 0000000000..9106d63e87 --- /dev/null +++ b/t/t4034/objc/pre @@ -0,0 +1,18 @@ +Foo():x(0&&1){} +cout<<"Hello World!\n"<<endl; +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a ~a a++ a-- a*b a&b +a*b a/b a%b +a+b a-b +a<<b a>>b +a<b a<=b a>b a>=b +a==b a!=b +a&b +a^b +a|b +a&&b +a||b +a?b:z +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a,y diff --git a/t/t4034/pascal/expect b/t/t4034/pascal/expect new file mode 100644 index 0000000000..2ce4230954 --- /dev/null +++ b/t/t4034/pascal/expect @@ -0,0 +1,35 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 077046c..8865e6b 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,18 +1,18 @@<RESET> +writeln("Hello World<RED>!<RESET><GREEN>?<RESET>"); +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>|<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>||<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z +<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>,y +<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET> diff --git a/t/t4034/pascal/post b/t/t4034/pascal/post new file mode 100644 index 0000000000..8865e6bddd --- /dev/null +++ b/t/t4034/pascal/post @@ -0,0 +1,18 @@ +writeln("Hello World?"); +(1) (-1e10) (0xabcdef) 'y' +[x] x->y x.y +!x ~x x++ x-- x*y x&y +x*y x/y x%y +x+y x-y +x<<y x>>y +x<y x<=y x>y x>=y +x==y x!=y +x&y +x^y +x|y +x&&y +x||y +x?y:z +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +x,y +x::y diff --git a/t/t4034/pascal/pre b/t/t4034/pascal/pre new file mode 100644 index 0000000000..077046c832 --- /dev/null +++ b/t/t4034/pascal/pre @@ -0,0 +1,18 @@ +writeln("Hello World!"); +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a ~a a++ a-- a*b a&b +a*b a/b a%b +a+b a-b +a<<b a>>b +a<b a<=b a>b a>=b +a==b a!=b +a&b +a^b +a|b +a&&b +a||b +a?b:z +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a,y +a::b diff --git a/t/t4034/perl/expect b/t/t4034/perl/expect new file mode 100644 index 0000000000..a1deb6b6ad --- /dev/null +++ b/t/t4034/perl/expect @@ -0,0 +1,13 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index f6610d3..e8b72ef 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -4,8 +4,8 @@<RESET> + +package Frotz;<RESET> +sub new {<RESET> + my <GREEN>(<RESET>$class<GREEN>, %opts)<RESET> = <RED>shift<RESET><GREEN>@_<RESET>; + return bless { <GREEN>xyzzy => "nitfol", %opts<RESET> }, $class; +}<RESET> + +__END__<RESET> diff --git a/t/t4034/perl/post b/t/t4034/perl/post new file mode 100644 index 0000000000..e8b72ef5dc --- /dev/null +++ b/t/t4034/perl/post @@ -0,0 +1,22 @@ +#!/usr/bin/perl + +use strict; + +package Frotz; +sub new { + my ($class, %opts) = @_; + return bless { xyzzy => "nitfol", %opts }, $class; +} + +__END__ +=head1 NAME + +frotz - Frotz + +=head1 SYNOPSIS + + use frotz; + + $nitfol = new Frotz(); + +=cut diff --git a/t/t4034/perl/pre b/t/t4034/perl/pre new file mode 100644 index 0000000000..f6610d37b8 --- /dev/null +++ b/t/t4034/perl/pre @@ -0,0 +1,22 @@ +#!/usr/bin/perl + +use strict; + +package Frotz; +sub new { + my $class = shift; + return bless {}, $class; +} + +__END__ +=head1 NAME + +frotz - Frotz + +=head1 SYNOPSIS + + use frotz; + + $nitfol = new Frotz(); + +=cut diff --git a/t/t4034/php/expect b/t/t4034/php/expect new file mode 100644 index 0000000000..040440860a --- /dev/null +++ b/t/t4034/php/expect @@ -0,0 +1,35 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index cf6e06b..4420a49 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,18 +1,18 @@<RESET> +<GREEN>(<RESET>$var<GREEN>)<RESET> $ var +<?="Hello World<RED>!<RESET><GREEN>?<RESET>"?> +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>|<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>||<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z +<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>,y diff --git a/t/t4034/php/post b/t/t4034/php/post new file mode 100644 index 0000000000..4420a49192 --- /dev/null +++ b/t/t4034/php/post @@ -0,0 +1,18 @@ +($var) $ var +<?="Hello World?"?> +(1) (-1e10) (0xabcdef) 'y' +[x] x->y x.y +!x ~x x++ x-- x*y x&y +x*y x/y x%y +x+y x-y +x<<y x>>y +x<y x<=y x>y x>=y +x==y x!=y +x&y +x^y +x|y +x&&y +x||y +x?y:z +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +x,y diff --git a/t/t4034/php/pre b/t/t4034/php/pre new file mode 100644 index 0000000000..cf6e06bc22 --- /dev/null +++ b/t/t4034/php/pre @@ -0,0 +1,18 @@ +$var $var +<?= "Hello World!" ?> +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a ~a a++ a-- a*b a&b +a*b a/b a%b +a+b a-b +a<<b a>>b +a<b a<=b a>b a>=b +a==b a!=b +a&b +a^b +a|b +a&&b +a||b +a?b:z +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a,y diff --git a/t/t4034/python/expect b/t/t4034/python/expect new file mode 100644 index 0000000000..8abb8a48b4 --- /dev/null +++ b/t/t4034/python/expect @@ -0,0 +1,34 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 438f776..68baf34 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,17 +1,17 @@<RESET> +print<RED>u<RESET> "Hello World<RED>!<RESET><GREEN>?<RESET>\n"<GREEN>; print<RESET> +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>) u<RESET>'<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>|<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>||<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z +<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>,y diff --git a/t/t4034/python/post b/t/t4034/python/post new file mode 100644 index 0000000000..68baf34f0e --- /dev/null +++ b/t/t4034/python/post @@ -0,0 +1,17 @@ +print "Hello World?\n"; print +(1) (-1e10) (0xabcdef) u'y' +[x] x->y x.y +!x ~x x++ x-- x*y x&y +x*y x/y x%y +x+y x-y +x<<y x>>y +x<y x<=y x>y x>=y +x==y x!=y +x&y +x^y +x|y +x&&y +x||y +x?y:z +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +x,y diff --git a/t/t4034/python/pre b/t/t4034/python/pre new file mode 100644 index 0000000000..438f776875 --- /dev/null +++ b/t/t4034/python/pre @@ -0,0 +1,17 @@ +print u"Hello World!\n" +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a ~a a++ a-- a*b a&b +a*b a/b a%b +a+b a-b +a<<b a>>b +a<b a<=b a>b a>=b +a==b a!=b +a&b +a^b +a|b +a&&b +a||b +a?b:z +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a,y diff --git a/t/t4034/ruby/expect b/t/t4034/ruby/expect new file mode 100644 index 0000000000..16e1dd5674 --- /dev/null +++ b/t/t4034/ruby/expect @@ -0,0 +1,34 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 30ed9a1..7678f14 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,17 +1,17 @@<RESET> +10.downto(1) {|<RED>x<RESET><GREEN>y<RESET>| puts <RED>x<RESET><GREEN>y<RESET>} +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>|<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>||<RED>b<RESET> +<RED>a?b<RESET><GREEN>y<RESET> +<GREEN>x?y<RESET>:z +<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>,y diff --git a/t/t4034/ruby/post b/t/t4034/ruby/post new file mode 100644 index 0000000000..7678f14e14 --- /dev/null +++ b/t/t4034/ruby/post @@ -0,0 +1,17 @@ +10.downto(1) {|y| puts y} +(1) (-1e10) (0xabcdef) 'y' +[x] x->y x.y +!x ~x x++ x-- x*y x&y +x*y x/y x%y +x+y x-y +x<<y x>>y +x<y x<=y x>y x>=y +x==y x!=y +x&y +x^y +x|y +x&&y +x||y +x?y:z +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +x,y diff --git a/t/t4034/ruby/pre b/t/t4034/ruby/pre new file mode 100644 index 0000000000..30ed9a1595 --- /dev/null +++ b/t/t4034/ruby/pre @@ -0,0 +1,17 @@ +10.downto(1) {|x| puts x} +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a ~a a++ a-- a*b a&b +a*b a/b a%b +a+b a-b +a<<b a>>b +a<b a<=b a>b a>=b +a==b a!=b +a&b +a^b +a|b +a&&b +a||b +a?b:z +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a,y diff --git a/t/t4034/tex/expect b/t/t4034/tex/expect new file mode 100644 index 0000000000..604969bcde --- /dev/null +++ b/t/t4034/tex/expect @@ -0,0 +1,9 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 2b2dfcb..65cab61 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,4 +1,4 @@<RESET> +\section{Something <GREEN>new<RESET>} +<RED>\emph<RESET><GREEN>\textbf<RESET>{Macro style} +{<RED>\em<RESET><GREEN>\bfseries<RESET> State toggle style} +\\[<RED>1em<RESET><GREEN>1cm<RESET>] diff --git a/t/t4034/tex/post b/t/t4034/tex/post new file mode 100644 index 0000000000..65cab61a10 --- /dev/null +++ b/t/t4034/tex/post @@ -0,0 +1,4 @@ +\section{Something new} +\textbf{Macro style} +{\bfseries State toggle style} +\\[1cm] diff --git a/t/t4034/tex/pre b/t/t4034/tex/pre new file mode 100644 index 0000000000..2b2dfcb65c --- /dev/null +++ b/t/t4034/tex/pre @@ -0,0 +1,4 @@ +\section{Something} +\emph{Macro style} +{\em State toggle style} +\\[1em] diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh index a30b03bcf2..abc49348b1 100755 --- a/t/t4040-whitespace-status.sh +++ b/t/t4040-whitespace-status.sh @@ -60,4 +60,11 @@ test_expect_success 'diff-files -b -p --exit-code' ' git diff-files -b -p --exit-code ' +test_expect_success 'diff-files --diff-filter --quiet' ' + git reset --hard && + rm a/d && + echo x >>b/e && + test_must_fail git diff-files --diff-filter=M --quiet +' + test_done diff --git a/t/t4046-diff-unmerged.sh b/t/t4046-diff-unmerged.sh new file mode 100755 index 0000000000..25d50a654a --- /dev/null +++ b/t/t4046-diff-unmerged.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +test_description='diff with unmerged index entries' +. ./test-lib.sh + +test_expect_success setup ' + for i in 0 1 2 3 + do + blob=$(echo $i | git hash-object --stdin) && + eval "blob$i=$blob" && + eval "m$i=\"100644 \$blob$i $i\"" || break + done && + paths= && + for b in o x + do + for o in o x + do + for t in o x + do + path="$b$o$t" && + case "$path" in ooo) continue ;; esac + paths="$paths$path " && + p=" $path" && + case "$b" in x) echo "$m1$p" ;; esac && + case "$o" in x) echo "$m2$p" ;; esac && + case "$t" in x) echo "$m3$p" ;; esac || + break + done || break + done || break + done >ls-files-s.expect && + git update-index --index-info <ls-files-s.expect && + git ls-files -s >ls-files-s.actual && + test_cmp ls-files-s.expect ls-files-s.actual +' + +test_expect_success 'diff-files -0' ' + for path in $paths + do + >"$path" && + echo ":000000 100644 $_z40 $_z40 U $path" + done >diff-files-0.expect && + git diff-files -0 >diff-files-0.actual && + test_cmp diff-files-0.expect diff-files-0.actual +' + +test_expect_success 'diff-files -1' ' + for path in $paths + do + >"$path" && + echo ":000000 100644 $_z40 $_z40 U $path" && + case "$path" in + x??) echo ":100644 100644 $blob1 $_z40 M $path" + esac + done >diff-files-1.expect && + git diff-files -1 >diff-files-1.actual && + test_cmp diff-files-1.expect diff-files-1.actual +' + +test_expect_success 'diff-files -2' ' + for path in $paths + do + >"$path" && + echo ":000000 100644 $_z40 $_z40 U $path" && + case "$path" in + ?x?) echo ":100644 100644 $blob2 $_z40 M $path" + esac + done >diff-files-2.expect && + git diff-files -2 >diff-files-2.actual && + test_cmp diff-files-2.expect diff-files-2.actual && + git diff-files >diff-files-default-2.actual && + test_cmp diff-files-2.expect diff-files-default-2.actual +' + +test_expect_success 'diff-files -3' ' + for path in $paths + do + >"$path" && + echo ":000000 100644 $_z40 $_z40 U $path" && + case "$path" in + ??x) echo ":100644 100644 $blob3 $_z40 M $path" + esac + done >diff-files-3.expect && + git diff-files -3 >diff-files-3.actual && + test_cmp diff-files-3.expect diff-files-3.actual +' + +test_done diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh new file mode 100755 index 0000000000..29e80a58cd --- /dev/null +++ b/t/t4047-diff-dirstat.sh @@ -0,0 +1,979 @@ +#!/bin/sh + +test_description='diff --dirstat tests' +. ./test-lib.sh + +# set up two commits where the second commit has these files +# (10 lines in each file): +# +# unchanged/text (unchanged from 1st commit) +# changed/text (changed 1st line) +# rearranged/text (swapped 1st and 2nd line) +# dst/copy/unchanged/text (copied from src/copy/unchanged/text, unchanged) +# dst/copy/changed/text (copied from src/copy/changed/text, changed) +# dst/copy/rearranged/text (copied from src/copy/rearranged/text, rearranged) +# dst/move/unchanged/text (moved from src/move/unchanged/text, unchanged) +# dst/move/changed/text (moved from src/move/changed/text, changed) +# dst/move/rearranged/text (moved from src/move/rearranged/text, rearranged) + +test_expect_success 'setup' ' + mkdir unchanged && + mkdir changed && + mkdir rearranged && + mkdir src && + mkdir src/copy && + mkdir src/copy/unchanged && + mkdir src/copy/changed && + mkdir src/copy/rearranged && + mkdir src/move && + mkdir src/move/unchanged && + mkdir src/move/changed && + mkdir src/move/rearranged && + cat <<EOF >unchanged/text && +unchanged line #0 +unchanged line #1 +unchanged line #2 +unchanged line #3 +unchanged line #4 +unchanged line #5 +unchanged line #6 +unchanged line #7 +unchanged line #8 +unchanged line #9 +EOF + cat <<EOF >changed/text && +changed line #0 +changed line #1 +changed line #2 +changed line #3 +changed line #4 +changed line #5 +changed line #6 +changed line #7 +changed line #8 +changed line #9 +EOF + cat <<EOF >rearranged/text && +rearranged line #0 +rearranged line #1 +rearranged line #2 +rearranged line #3 +rearranged line #4 +rearranged line #5 +rearranged line #6 +rearranged line #7 +rearranged line #8 +rearranged line #9 +EOF + cat <<EOF >src/copy/unchanged/text && +copy unchanged line #0 +copy unchanged line #1 +copy unchanged line #2 +copy unchanged line #3 +copy unchanged line #4 +copy unchanged line #5 +copy unchanged line #6 +copy unchanged line #7 +copy unchanged line #8 +copy unchanged line #9 +EOF + cat <<EOF >src/copy/changed/text && +copy changed line #0 +copy changed line #1 +copy changed line #2 +copy changed line #3 +copy changed line #4 +copy changed line #5 +copy changed line #6 +copy changed line #7 +copy changed line #8 +copy changed line #9 +EOF + cat <<EOF >src/copy/rearranged/text && +copy rearranged line #0 +copy rearranged line #1 +copy rearranged line #2 +copy rearranged line #3 +copy rearranged line #4 +copy rearranged line #5 +copy rearranged line #6 +copy rearranged line #7 +copy rearranged line #8 +copy rearranged line #9 +EOF + cat <<EOF >src/move/unchanged/text && +move unchanged line #0 +move unchanged line #1 +move unchanged line #2 +move unchanged line #3 +move unchanged line #4 +move unchanged line #5 +move unchanged line #6 +move unchanged line #7 +move unchanged line #8 +move unchanged line #9 +EOF + cat <<EOF >src/move/changed/text && +move changed line #0 +move changed line #1 +move changed line #2 +move changed line #3 +move changed line #4 +move changed line #5 +move changed line #6 +move changed line #7 +move changed line #8 +move changed line #9 +EOF + cat <<EOF >src/move/rearranged/text && +move rearranged line #0 +move rearranged line #1 +move rearranged line #2 +move rearranged line #3 +move rearranged line #4 +move rearranged line #5 +move rearranged line #6 +move rearranged line #7 +move rearranged line #8 +move rearranged line #9 +EOF + git add . && + git commit -m "initial" && + mkdir dst && + mkdir dst/copy && + mkdir dst/copy/unchanged && + mkdir dst/copy/changed && + mkdir dst/copy/rearranged && + mkdir dst/move && + mkdir dst/move/unchanged && + mkdir dst/move/changed && + mkdir dst/move/rearranged && + cat <<EOF >changed/text && +CHANGED XXXXXXX line #0 +changed line #1 +changed line #2 +changed line #3 +changed line #4 +changed line #5 +changed line #6 +changed line #7 +changed line #8 +changed line #9 +EOF + cat <<EOF >rearranged/text && +rearranged line #1 +rearranged line #0 +rearranged line #2 +rearranged line #3 +rearranged line #4 +rearranged line #5 +rearranged line #6 +rearranged line #7 +rearranged line #8 +rearranged line #9 +EOF + cat <<EOF >dst/copy/unchanged/text && +copy unchanged line #0 +copy unchanged line #1 +copy unchanged line #2 +copy unchanged line #3 +copy unchanged line #4 +copy unchanged line #5 +copy unchanged line #6 +copy unchanged line #7 +copy unchanged line #8 +copy unchanged line #9 +EOF + cat <<EOF >dst/copy/changed/text && +copy XXXCHANGED line #0 +copy changed line #1 +copy changed line #2 +copy changed line #3 +copy changed line #4 +copy changed line #5 +copy changed line #6 +copy changed line #7 +copy changed line #8 +copy changed line #9 +EOF + cat <<EOF >dst/copy/rearranged/text && +copy rearranged line #1 +copy rearranged line #0 +copy rearranged line #2 +copy rearranged line #3 +copy rearranged line #4 +copy rearranged line #5 +copy rearranged line #6 +copy rearranged line #7 +copy rearranged line #8 +copy rearranged line #9 +EOF + cat <<EOF >dst/move/unchanged/text && +move unchanged line #0 +move unchanged line #1 +move unchanged line #2 +move unchanged line #3 +move unchanged line #4 +move unchanged line #5 +move unchanged line #6 +move unchanged line #7 +move unchanged line #8 +move unchanged line #9 +EOF + cat <<EOF >dst/move/changed/text && +move XXXCHANGED line #0 +move changed line #1 +move changed line #2 +move changed line #3 +move changed line #4 +move changed line #5 +move changed line #6 +move changed line #7 +move changed line #8 +move changed line #9 +EOF + cat <<EOF >dst/move/rearranged/text && +move rearranged line #1 +move rearranged line #0 +move rearranged line #2 +move rearranged line #3 +move rearranged line #4 +move rearranged line #5 +move rearranged line #6 +move rearranged line #7 +move rearranged line #8 +move rearranged line #9 +EOF + git add . && + git rm -r src/move/unchanged && + git rm -r src/move/changed && + git rm -r src/move/rearranged && + git commit -m "changes" +' + +cat <<EOF >expect_diff_stat + changed/text | 2 +- + dst/copy/changed/text | 10 ++++++++++ + dst/copy/rearranged/text | 10 ++++++++++ + dst/copy/unchanged/text | 10 ++++++++++ + dst/move/changed/text | 10 ++++++++++ + dst/move/rearranged/text | 10 ++++++++++ + dst/move/unchanged/text | 10 ++++++++++ + rearranged/text | 2 +- + src/move/changed/text | 10 ---------- + src/move/rearranged/text | 10 ---------- + src/move/unchanged/text | 10 ---------- + 11 files changed, 62 insertions(+), 32 deletions(-) +EOF + +cat <<EOF >expect_diff_stat_M + changed/text | 2 +- + dst/copy/changed/text | 10 ++++++++++ + dst/copy/rearranged/text | 10 ++++++++++ + dst/copy/unchanged/text | 10 ++++++++++ + {src => dst}/move/changed/text | 2 +- + {src => dst}/move/rearranged/text | 2 +- + {src => dst}/move/unchanged/text | 0 + rearranged/text | 2 +- + 8 files changed, 34 insertions(+), 4 deletions(-) +EOF + +cat <<EOF >expect_diff_stat_CC + changed/text | 2 +- + {src => dst}/copy/changed/text | 2 +- + {src => dst}/copy/rearranged/text | 2 +- + {src => dst}/copy/unchanged/text | 0 + {src => dst}/move/changed/text | 2 +- + {src => dst}/move/rearranged/text | 2 +- + {src => dst}/move/unchanged/text | 0 + rearranged/text | 2 +- + 8 files changed, 6 insertions(+), 6 deletions(-) +EOF + +test_expect_success 'sanity check setup (--stat)' ' + git diff --stat HEAD^..HEAD >actual_diff_stat && + test_cmp expect_diff_stat actual_diff_stat && + git diff --stat -M HEAD^..HEAD >actual_diff_stat_M && + test_cmp expect_diff_stat_M actual_diff_stat_M && + git diff --stat -C -C HEAD^..HEAD >actual_diff_stat_CC && + test_cmp expect_diff_stat_CC actual_diff_stat_CC +' + +# changed/text and rearranged/text falls below default 3% threshold +cat <<EOF >expect_diff_dirstat + 10.8% dst/copy/changed/ + 10.8% dst/copy/rearranged/ + 10.8% dst/copy/unchanged/ + 10.8% dst/move/changed/ + 10.8% dst/move/rearranged/ + 10.8% dst/move/unchanged/ + 10.8% src/move/changed/ + 10.8% src/move/rearranged/ + 10.8% src/move/unchanged/ +EOF + +# rearranged/text falls below default 3% threshold +cat <<EOF >expect_diff_dirstat_M + 5.8% changed/ + 29.3% dst/copy/changed/ + 29.3% dst/copy/rearranged/ + 29.3% dst/copy/unchanged/ + 5.8% dst/move/changed/ +EOF + +# rearranged/text falls below default 3% threshold +cat <<EOF >expect_diff_dirstat_CC + 32.6% changed/ + 32.6% dst/copy/changed/ + 32.6% dst/move/changed/ +EOF + +test_expect_success 'various ways to misspell --dirstat' ' + test_must_fail git show --dirstat10 && + test_must_fail git show --dirstat10,files && + test_must_fail git show -X=20 && + test_must_fail git show -X=20,cumulative +' + +test_expect_success 'vanilla --dirstat' ' + git diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'vanilla -X' ' + git diff -X HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff -X -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff -X -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'explicit defaults: --dirstat=changes,noncumulative,3' ' + git diff --dirstat=changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'explicit defaults: -Xchanges,noncumulative,3' ' + git diff -Xchanges,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff -Xchanges,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff -Xchanges,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'later options override earlier options:' ' + git diff --dirstat=files,10,cumulative,changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,10,cumulative,changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,10,cumulative,changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC + git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'non-defaults in config overridden by explicit defaults on command line' ' + git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 2.1% changed/ + 10.8% dst/copy/changed/ + 10.8% dst/copy/rearranged/ + 10.8% dst/copy/unchanged/ + 10.8% dst/move/changed/ + 10.8% dst/move/rearranged/ + 10.8% dst/move/unchanged/ + 0.0% rearranged/ + 10.8% src/move/changed/ + 10.8% src/move/rearranged/ + 10.8% src/move/unchanged/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 5.8% changed/ + 29.3% dst/copy/changed/ + 29.3% dst/copy/rearranged/ + 29.3% dst/copy/unchanged/ + 5.8% dst/move/changed/ + 0.1% dst/move/rearranged/ + 0.1% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 32.6% changed/ + 32.6% dst/copy/changed/ + 0.6% dst/copy/rearranged/ + 32.6% dst/move/changed/ + 0.6% dst/move/rearranged/ + 0.6% rearranged/ +EOF + +test_expect_success '--dirstat=0' ' + git diff --dirstat=0 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=0 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '-X0' ' + git diff -X0 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff -X0 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff -X0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=0' ' + git -c diff.dirstat=0 diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=0 diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=0 diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 2.1% changed/ + 10.8% dst/copy/changed/ + 10.8% dst/copy/rearranged/ + 10.8% dst/copy/unchanged/ + 32.5% dst/copy/ + 10.8% dst/move/changed/ + 10.8% dst/move/rearranged/ + 10.8% dst/move/unchanged/ + 32.5% dst/move/ + 65.1% dst/ + 0.0% rearranged/ + 10.8% src/move/changed/ + 10.8% src/move/rearranged/ + 10.8% src/move/unchanged/ + 32.5% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 5.8% changed/ + 29.3% dst/copy/changed/ + 29.3% dst/copy/rearranged/ + 29.3% dst/copy/unchanged/ + 88.0% dst/copy/ + 5.8% dst/move/changed/ + 0.1% dst/move/rearranged/ + 5.9% dst/move/ + 94.0% dst/ + 0.1% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 32.6% changed/ + 32.6% dst/copy/changed/ + 0.6% dst/copy/rearranged/ + 33.3% dst/copy/ + 32.6% dst/move/changed/ + 0.6% dst/move/rearranged/ + 33.3% dst/move/ + 66.6% dst/ + 0.6% rearranged/ +EOF + +test_expect_success '--dirstat=0 --cumulative' ' + git diff --dirstat=0 --cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=0 --cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=0 --cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=0,cumulative' ' + git diff --dirstat=0,cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=0,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=0,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '-X0,cumulative' ' + git diff -X0,cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff -X0,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff -X0,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=0,cumulative' ' + git -c diff.dirstat=0,cumulative diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=0,cumulative diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=0,cumulative diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=0 & --dirstat=cumulative' ' + git -c diff.dirstat=0 diff --dirstat=cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=0 diff --dirstat=cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=0 diff --dirstat=cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 9.0% changed/ + 9.0% dst/copy/changed/ + 9.0% dst/copy/rearranged/ + 9.0% dst/copy/unchanged/ + 9.0% dst/move/changed/ + 9.0% dst/move/rearranged/ + 9.0% dst/move/unchanged/ + 9.0% rearranged/ + 9.0% src/move/changed/ + 9.0% src/move/rearranged/ + 9.0% src/move/unchanged/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 14.2% changed/ + 14.2% dst/copy/changed/ + 14.2% dst/copy/rearranged/ + 14.2% dst/copy/unchanged/ + 14.2% dst/move/changed/ + 14.2% dst/move/rearranged/ + 14.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat-by-file' ' + git diff --dirstat-by-file HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat-by-file -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat-by-file -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files' ' + git diff --dirstat=files HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=files' ' + git -c diff.dirstat=files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 27.2% dst/copy/ + 27.2% dst/move/ + 27.2% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 14.2% changed/ + 14.2% dst/copy/changed/ + 14.2% dst/copy/rearranged/ + 14.2% dst/copy/unchanged/ + 14.2% dst/move/changed/ + 14.2% dst/move/rearranged/ + 14.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat-by-file=10' ' + git diff --dirstat-by-file=10 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat-by-file=10 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat-by-file=10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files,10' ' + git diff --dirstat=files,10 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,10 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=10,files' ' + git -c diff.dirstat=10,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=10,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=10,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 9.0% changed/ + 9.0% dst/copy/changed/ + 9.0% dst/copy/rearranged/ + 9.0% dst/copy/unchanged/ + 27.2% dst/copy/ + 9.0% dst/move/changed/ + 9.0% dst/move/rearranged/ + 9.0% dst/move/unchanged/ + 27.2% dst/move/ + 54.5% dst/ + 9.0% rearranged/ + 9.0% src/move/changed/ + 9.0% src/move/rearranged/ + 9.0% src/move/unchanged/ + 27.2% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 14.2% changed/ + 14.2% dst/copy/changed/ + 14.2% dst/copy/rearranged/ + 14.2% dst/copy/unchanged/ + 42.8% dst/copy/ + 14.2% dst/move/changed/ + 14.2% dst/move/rearranged/ + 28.5% dst/move/ + 71.4% dst/ + 14.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 33.3% dst/copy/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 33.3% dst/move/ + 66.6% dst/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat-by-file --cumulative' ' + git diff --dirstat-by-file --cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat-by-file --cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat-by-file --cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files,cumulative' ' + git diff --dirstat=files,cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=cumulative,files' ' + git -c diff.dirstat=cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 27.2% dst/copy/ + 27.2% dst/move/ + 54.5% dst/ + 27.2% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 14.2% changed/ + 14.2% dst/copy/changed/ + 14.2% dst/copy/rearranged/ + 14.2% dst/copy/unchanged/ + 42.8% dst/copy/ + 14.2% dst/move/changed/ + 14.2% dst/move/rearranged/ + 28.5% dst/move/ + 71.4% dst/ + 14.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 33.3% dst/copy/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 33.3% dst/move/ + 66.6% dst/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat=files,cumulative,10' ' + git diff --dirstat=files,cumulative,10 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative,10 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative,10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=10,cumulative,files' ' + git -c diff.dirstat=10,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=10,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=10,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 27.2% dst/copy/ + 27.2% dst/move/ + 54.5% dst/ + 27.2% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 42.8% dst/copy/ + 28.5% dst/move/ + 71.4% dst/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 33.3% dst/copy/ + 33.3% dst/move/ + 66.6% dst/ +EOF + +test_expect_success '--dirstat=files,cumulative,16.7' ' + git diff --dirstat=files,cumulative,16.7 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative,16.7 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative,16.7 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=16.7,cumulative,files' ' + git -c diff.dirstat=16.7,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=16.7,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=16.7,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=16.70,cumulative,files' ' + git -c diff.dirstat=16.70,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=16.70,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=16.70,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files,cumulative,27.2' ' + git diff --dirstat=files,cumulative,27.2 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative,27.2 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative,27.2 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files,cumulative,27.09' ' + git diff --dirstat=files,cumulative,27.09 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative,27.09 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative,27.09 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 10.6% dst/copy/changed/ + 10.6% dst/copy/rearranged/ + 10.6% dst/copy/unchanged/ + 10.6% dst/move/changed/ + 10.6% dst/move/rearranged/ + 10.6% dst/move/unchanged/ + 10.6% src/move/changed/ + 10.6% src/move/rearranged/ + 10.6% src/move/unchanged/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 5.2% changed/ + 26.3% dst/copy/changed/ + 26.3% dst/copy/rearranged/ + 26.3% dst/copy/unchanged/ + 5.2% dst/move/changed/ + 5.2% dst/move/rearranged/ + 5.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat=lines' ' + git diff --dirstat=lines HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=lines -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=lines -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=lines' ' + git -c diff.dirstat=lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 2.1% changed/ + 10.6% dst/copy/changed/ + 10.6% dst/copy/rearranged/ + 10.6% dst/copy/unchanged/ + 10.6% dst/move/changed/ + 10.6% dst/move/rearranged/ + 10.6% dst/move/unchanged/ + 2.1% rearranged/ + 10.6% src/move/changed/ + 10.6% src/move/rearranged/ + 10.6% src/move/unchanged/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 5.2% changed/ + 26.3% dst/copy/changed/ + 26.3% dst/copy/rearranged/ + 26.3% dst/copy/unchanged/ + 5.2% dst/move/changed/ + 5.2% dst/move/rearranged/ + 5.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat=lines,0' ' + git diff --dirstat=lines,0 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=lines,0 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=lines,0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=0,lines' ' + git -c diff.dirstat=0,lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=0,lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=0,lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=future_param,lines,0 should fail loudly' ' + test_must_fail git diff --dirstat=future_param,lines,0 HEAD^..HEAD >actual_diff_dirstat 2>actual_error && + test_debug "cat actual_error" && + test_cmp /dev/null actual_diff_dirstat && + test_i18ngrep -q "future_param" actual_error && + test_i18ngrep -q "\--dirstat" actual_error +' + +test_expect_success '--dirstat=dummy1,cumulative,2dummy should report both unrecognized parameters' ' + test_must_fail git diff --dirstat=dummy1,cumulative,2dummy HEAD^..HEAD >actual_diff_dirstat 2>actual_error && + test_debug "cat actual_error" && + test_cmp /dev/null actual_diff_dirstat && + test_i18ngrep -q "dummy1" actual_error && + test_i18ngrep -q "2dummy" actual_error && + test_i18ngrep -q "\--dirstat" actual_error +' + +test_expect_success 'diff.dirstat=future_param,0,lines should warn, but still work' ' + git -c diff.dirstat=future_param,0,lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat 2>actual_error && + test_debug "cat actual_error" && + test_cmp expect_diff_dirstat actual_diff_dirstat && + test_i18ngrep -q "future_param" actual_error && + test_i18ngrep -q "diff\\.dirstat" actual_error && + + git -c diff.dirstat=future_param,0,lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M 2>actual_error && + test_debug "cat actual_error" && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + test_i18ngrep -q "future_param" actual_error && + test_i18ngrep -q "diff\\.dirstat" actual_error && + + git -c diff.dirstat=future_param,0,lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC 2>actual_error && + test_debug "cat actual_error" && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC && + test_i18ngrep -q "future_param" actual_error && + test_i18ngrep -q "diff\\.dirstat" actual_error +' + +test_done diff --git a/t/t4120-apply-popt.sh b/t/t4120-apply-popt.sh index 579c9e6105..a33d510bf6 100755 --- a/t/t4120-apply-popt.sh +++ b/t/t4120-apply-popt.sh @@ -6,6 +6,7 @@ test_description='git apply -p handling.' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh test_expect_success setup ' mkdir sub && @@ -62,8 +63,12 @@ test_expect_success 'apply (-p2) diff, mode change only' ' old mode 100644 new mode 100755 EOF - chmod 644 file1 && - git apply -p2 patch.chmod && + test_chmod -x file1 && + git apply --index -p2 patch.chmod && + case $(git ls-files -s file1) in 100755*) : good;; *) false;; esac +' + +test_expect_success FILEMODE 'file mode was changed' ' test -x file1 ' diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 2fcc31a6f3..983e34bec6 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -448,6 +448,59 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate >actual && test_cmp expect.short actual + git config --unset-all log.decorate && + git log --pretty=raw >expect.raw && + git config log.decorate full && + git log --pretty=raw >actual && + test_cmp expect.raw actual + +' + +test_expect_success 'reflog is expected format' ' + test_might_fail git config --remove-section log && + git log -g --abbrev-commit --pretty=oneline >expect && + git reflog >actual && + test_cmp expect actual +' + +test_expect_success 'whatchanged is expected format' ' + git log --no-merges --raw >expect && + git whatchanged >actual && + test_cmp expect actual +' + +test_expect_success 'log.abbrevCommit configuration' ' + test_when_finished "git config --unset log.abbrevCommit" && + + test_might_fail git config --unset log.abbrevCommit && + + git log --abbrev-commit >expect.log.abbrev && + git log --no-abbrev-commit >expect.log.full && + git log --pretty=raw >expect.log.raw && + git reflog --abbrev-commit >expect.reflog.abbrev && + git reflog --no-abbrev-commit >expect.reflog.full && + git whatchanged --abbrev-commit >expect.whatchanged.abbrev && + git whatchanged --no-abbrev-commit >expect.whatchanged.full && + + git config log.abbrevCommit true && + + git log >actual && + test_cmp expect.log.abbrev actual && + git log --no-abbrev-commit >actual && + test_cmp expect.log.full actual && + + git log --pretty=raw >actual && + test_cmp expect.log.raw actual && + + git reflog >actual && + test_cmp expect.reflog.abbrev actual && + git reflog --no-abbrev-commit >actual && + test_cmp expect.reflog.full actual && + + git whatchanged >actual && + test_cmp expect.whatchanged.abbrev actual && + git whatchanged --no-abbrev-commit >actual && + test_cmp expect.whatchanged.full actual ' test_expect_success 'show added path under "--follow -M"' ' diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index 68e2652814..d2c930de87 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -63,4 +63,40 @@ test_expect_success 'patch-id supports git-format-patch MIME output' ' test_cmp patch-id_master patch-id_same ' +cat >nonl <<\EOF +diff --git i/a w/a +index e69de29..2e65efe 100644 +--- i/a ++++ w/a +@@ -0,0 +1 @@ ++a +\ No newline at end of file +diff --git i/b w/b +index e69de29..6178079 100644 +--- i/b ++++ w/b +@@ -0,0 +1 @@ ++b +EOF + +cat >withnl <<\EOF +diff --git i/a w/a +index e69de29..7898192 100644 +--- i/a ++++ w/a +@@ -0,0 +1 @@ ++a +diff --git i/b w/b +index e69de29..6178079 100644 +--- i/b ++++ w/b +@@ -0,0 +1 @@ ++b +EOF + +test_expect_success 'patch-id handles no-nl-at-eof markers' ' + cat nonl | calc_patch_id nonl && + cat withnl | calc_patch_id withnl && + test_cmp patch-id_nonl patch-id_withnl +' test_done diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh new file mode 100755 index 0000000000..2c482b622b --- /dev/null +++ b/t/t4208-log-magic-pathspec.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='magic pathspec tests using git-log' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit initial && + test_tick && + git commit --allow-empty -m empty && + mkdir sub +' + +test_expect_success '"git log :/" should be ambiguous' ' + test_must_fail git log :/ 2>error && + grep ambiguous error +' + +test_expect_success '"git log :" should be ambiguous' ' + test_must_fail git log : 2>error && + grep ambiguous error +' + +test_expect_success 'git log -- :' ' + git log -- : +' + +test_expect_success 'git log HEAD -- :/' ' + cat >expected <<-EOF && + 24b24cf initial + EOF + (cd sub && git log --oneline HEAD -- :/ >../actual) && + test_cmp expected actual +' + +test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index e2ed13dba2..d645328609 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -14,7 +14,8 @@ add_blob() { BLOB=$(echo aleph_0 | git hash-object -w --stdin) && BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE + test -f $BLOB_FILE && + test-chmtime =+0 $BLOB_FILE } test_expect_success setup ' diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index b0b2684a1f..0eace37a03 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -190,7 +190,7 @@ test_expect_success 'pushing explicit refspecs respects forcing' ' +refs/heads/master:refs/heads/master ) && parent_head=$(cd parent && git rev-parse --verify master) && - child_head=$(cd parent && git rev-parse --verify master) && + child_head=$(cd child && git rev-parse --verify master) && test "$parent_head" = "$child_head" ' @@ -210,7 +210,7 @@ test_expect_success 'pushing wildcard refspecs respects forcing' ' "+refs/heads/*:refs/heads/*" ) && parent_head=$(cd parent && git rev-parse --verify master) && - child_head=$(cd parent && git rev-parse --verify master) && + child_head=$(cd child && git rev-parse --verify master) && test "$parent_head" = "$child_head" ' diff --git a/t/t5501-fetch-push-alternates.sh b/t/t5501-fetch-push-alternates.sh new file mode 100755 index 0000000000..b5ced8483a --- /dev/null +++ b/t/t5501-fetch-push-alternates.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +test_description='fetch/push involving alternates' +. ./test-lib.sh + +count_objects () { + loose=0 inpack=0 + eval "$( + git count-objects -v | + sed -n -e 's/^count: \(.*\)/loose=\1/p' \ + -e 's/^in-pack: \(.*\)/inpack=\1/p' + )" && + echo $(( $loose + $inpack )) +} + + +test_expect_success setup ' + ( + git init original && + cd original && + i=0 && + while test $i -le 100 + do + echo "$i" >count && + git add count && + git commit -m "$i" || exit + i=$(($i + 1)) + done + ) && + ( + git clone --reference=original "file:///$(pwd)/original" one && + cd one && + echo Z >count && + git add count && + git commit -m Z && + count_objects >../one.count + ) && + A=$(pwd)/original/.git/objects && + git init receiver && + echo "$A" >receiver/.git/objects/info/alternates && + git init fetcher && + echo "$A" >fetcher/.git/objects/info/alternates +' + +test_expect_success 'pushing into a repository with the same alternate' ' + ( + cd one && + git push ../receiver master:refs/heads/it + ) && + ( + cd receiver && + count_objects >../receiver.count + ) && + test_cmp one.count receiver.count +' + +test_expect_success 'fetching from a repository with the same alternate' ' + ( + cd fetcher && + git fetch ../one master:refs/heads/it && + count_objects >../fetcher.count + ) && + test_cmp one.count fetcher.count +' + +test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index d189add2d0..0d0222ea2a 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -304,6 +304,106 @@ test_expect_success 'add --mirror && prune' ' git rev-parse --verify refs/heads/side) ' +test_expect_success 'add --mirror=fetch' ' + mkdir mirror-fetch && + git init mirror-fetch/parent && + (cd mirror-fetch/parent && + test_commit one) && + git init --bare mirror-fetch/child && + (cd mirror-fetch/child && + git remote add --mirror=fetch -f parent ../parent) +' + +test_expect_success 'fetch mirrors act as mirrors during fetch' ' + (cd mirror-fetch/parent && + git branch new && + git branch -m master renamed + ) && + (cd mirror-fetch/child && + git fetch parent && + git rev-parse --verify refs/heads/new && + git rev-parse --verify refs/heads/renamed + ) +' + +test_expect_success 'fetch mirrors can prune' ' + (cd mirror-fetch/child && + git remote prune parent && + test_must_fail git rev-parse --verify refs/heads/master + ) +' + +test_expect_success 'fetch mirrors do not act as mirrors during push' ' + (cd mirror-fetch/parent && + git checkout HEAD^0 + ) && + (cd mirror-fetch/child && + git branch -m renamed renamed2 && + git push parent + ) && + (cd mirror-fetch/parent && + git rev-parse --verify renamed && + test_must_fail git rev-parse --verify refs/heads/renamed2 + ) +' + +test_expect_success 'add fetch mirror with specific branches' ' + git init --bare mirror-fetch/track && + (cd mirror-fetch/track && + git remote add --mirror=fetch -t heads/new parent ../parent + ) +' + +test_expect_success 'fetch mirror respects specific branches' ' + (cd mirror-fetch/track && + git fetch parent && + git rev-parse --verify refs/heads/new && + test_must_fail git rev-parse --verify refs/heads/renamed + ) +' + +test_expect_success 'add --mirror=push' ' + mkdir mirror-push && + git init --bare mirror-push/public && + git init mirror-push/private && + (cd mirror-push/private && + test_commit one && + git remote add --mirror=push public ../public + ) +' + +test_expect_success 'push mirrors act as mirrors during push' ' + (cd mirror-push/private && + git branch new && + git branch -m master renamed && + git push public + ) && + (cd mirror-push/private && + git rev-parse --verify refs/heads/new && + git rev-parse --verify refs/heads/renamed && + test_must_fail git rev-parse --verify refs/heads/master + ) +' + +test_expect_success 'push mirrors do not act as mirrors during fetch' ' + (cd mirror-push/public && + git branch -m renamed renamed2 && + git symbolic-ref HEAD refs/heads/renamed2 + ) && + (cd mirror-push/private && + git fetch public && + git rev-parse --verify refs/heads/renamed && + test_must_fail git rev-parse --verify refs/heads/renamed2 + ) +' + +test_expect_success 'push mirrors do not allow you to specify refs' ' + git init mirror-push/track && + (cd mirror-push/track && + test_must_fail git remote add --mirror=push -t new public ../public + ) +' + test_expect_success 'add alt && prune' ' (mkdir alttst && cd alttst && diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index d1912351db..5c546c99a5 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -123,4 +123,28 @@ test_expect_success 'confuses pattern as remote when no remote specified' ' ' +test_expect_success 'die with non-2 for wrong repository even with --exit-code' ' + git ls-remote --exit-code ./no-such-repository ;# not && + status=$? && + test $status != 2 && test $status != 0 +' + +test_expect_success 'Report success even when nothing matches' ' + git ls-remote other.git "refs/nsn/*" >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'Report no-match with --exit-code' ' + test_expect_code 2 git ls-remote --exit-code other.git "refs/nsn/*" >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'Report match with --exit-code' ' + git ls-remote --exit-code other.git "refs/tags/*" >actual && + git ls-remote . tags/mark >expect && + test_cmp expect actual +' + test_done diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 0470a81be0..0e5eb678ce 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -46,6 +46,17 @@ test_expect_success 'pulling into void using master:master' ' test_cmp file cloned-uho/file ' +test_expect_success 'pulling into void does not overwrite untracked files' ' + git init cloned-untracked && + ( + cd cloned-untracked && + echo untracked >file && + test_must_fail git pull .. master && + echo untracked >expect && + test_cmp expect file + ) +' + test_expect_success 'test . as a remote' ' git branch copy master && diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 884a5e567c..a1fddd4d15 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -67,8 +67,8 @@ test_expect_success "fetch --recurse-submodules recurses into submodules" ' cd downstream && git fetch --recurse-submodules >../actual.out 2>../actual.err ) && - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "fetch alone only fetches superproject" ' @@ -96,8 +96,8 @@ test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses i git config -f .gitmodules submodule.submodule.fetchRecurseSubmodules true && git fetch >../actual.out 2>../actual.err ) && - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "--no-recurse-submodules overrides .gitmodules config" ' @@ -124,11 +124,11 @@ test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setti ( cd downstream && git fetch --recurse-submodules >../actual.out 2>../actual.err && - git config -f --unset .gitmodules submodule.submodule.fetchRecurseSubmodules true && + git config --unset -f .gitmodules submodule.submodule.fetchRecurseSubmodules && git config --unset submodule.submodule.fetchRecurseSubmodules ) && - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "--quiet propagates to submodules" ' @@ -146,14 +146,17 @@ test_expect_success "--dry-run propagates to submodules" ' cd downstream && git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err ) && - test_cmp expect.out actual.out && - test_cmp expect.err actual.err && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err +' + +test_expect_success "Without --dry-run propagates to submodules" ' ( cd downstream && git fetch --recurse-submodules >../actual.out 2>../actual.err ) && - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "recurseSubmodules=true propagates into submodules" ' @@ -163,8 +166,8 @@ test_expect_success "recurseSubmodules=true propagates into submodules" ' git config fetch.recurseSubmodules true git fetch >../actual.out 2>../actual.err ) && - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "--recurse-submodules overrides config in submodule" ' @@ -177,8 +180,8 @@ test_expect_success "--recurse-submodules overrides config in submodule" ' ) && git fetch --recurse-submodules >../actual.out 2>../actual.err ) && - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "--no-recurse-submodules overrides config setting" ' @@ -192,4 +195,259 @@ test_expect_success "--no-recurse-submodules overrides config setting" ' ! test -s actual.err ' +test_expect_success "Recursion doesn't happen when no new commits are fetched in the superproject" ' + ( + cd downstream && + ( + cd submodule && + git config --unset fetch.recurseSubmodules + ) && + git config --unset fetch.recurseSubmodules + git fetch >../actual.out 2>../actual.err + ) && + ! test -s actual.out && + ! test -s actual.err +' + +test_expect_success "Recursion stops when no new submodule commits are fetched" ' + head1=$(git rev-parse --short HEAD) && + git add submodule && + git commit -m "new submodule" && + head2=$(git rev-parse --short HEAD) && + echo "Fetching submodule submodule" > expect.out.sub && + echo "From $pwd/." > expect.err.sub && + echo " $head1..$head2 master -> origin/master" >> expect.err.sub + head -2 expect.err >> expect.err.sub && + ( + cd downstream && + git fetch >../actual.out 2>../actual.err + ) && + test_i18ncmp expect.err.sub actual.err && + test_i18ncmp expect.out.sub actual.out +' + +test_expect_success "Recursion doesn't happen when new superproject commits don't change any submodules" ' + add_upstream_commit && + head1=$(git rev-parse --short HEAD) && + echo a > file && + git add file && + git commit -m "new file" && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/." > expect.err.file && + echo " $head1..$head2 master -> origin/master" >> expect.err.file && + ( + cd downstream && + git fetch >../actual.out 2>../actual.err + ) && + ! test -s actual.out && + test_i18ncmp expect.err.file actual.err +' + +test_expect_success "Recursion picks up config in submodule" ' + ( + cd downstream && + git fetch --recurse-submodules && + ( + cd submodule && + git config fetch.recurseSubmodules true + ) + ) && + add_upstream_commit && + head1=$(git rev-parse --short HEAD) && + git add submodule && + git commit -m "new submodule" && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/." > expect.err.sub && + echo " $head1..$head2 master -> origin/master" >> expect.err.sub && + cat expect.err >> expect.err.sub && + ( + cd downstream && + git fetch >../actual.out 2>../actual.err && + ( + cd submodule && + git config --unset fetch.recurseSubmodules + ) + ) && + test_i18ncmp expect.err.sub actual.err && + test_i18ncmp expect.out actual.out +' + +test_expect_success "Recursion picks up all submodules when necessary" ' + add_upstream_commit && + ( + cd submodule && + ( + cd deepsubmodule && + git fetch && + git checkout -q FETCH_HEAD + ) && + head1=$(git rev-parse --short HEAD^) && + git add deepsubmodule && + git commit -m "new deepsubmodule" + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/submodule" > ../expect.err.sub && + echo " $head1..$head2 master -> origin/master" >> ../expect.err.sub + ) && + head1=$(git rev-parse --short HEAD) && + git add submodule && + git commit -m "new submodule" && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/." > expect.err.2 && + echo " $head1..$head2 master -> origin/master" >> expect.err.2 && + cat expect.err.sub >> expect.err.2 && + tail -2 expect.err >> expect.err.2 && + ( + cd downstream && + git fetch >../actual.out 2>../actual.err + ) && + test_i18ncmp expect.err.2 actual.err && + test_i18ncmp expect.out actual.out +' + +test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no new commits are fetched in the superproject (and ignores config)" ' + add_upstream_commit && + ( + cd submodule && + ( + cd deepsubmodule && + git fetch && + git checkout -q FETCH_HEAD + ) && + head1=$(git rev-parse --short HEAD^) && + git add deepsubmodule && + git commit -m "new deepsubmodule" + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/submodule" > ../expect.err.sub && + echo " $head1..$head2 master -> origin/master" >> ../expect.err.sub + ) && + ( + cd downstream && + git config fetch.recurseSubmodules true && + git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err && + git config --unset fetch.recurseSubmodules + ) && + ! test -s actual.out && + ! test -s actual.err +' + +test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necessary (and ignores config)" ' + head1=$(git rev-parse --short HEAD) && + git add submodule && + git commit -m "new submodule" && + head2=$(git rev-parse --short HEAD) && + tail -2 expect.err > expect.err.deepsub && + echo "From $pwd/." > expect.err && + echo " $head1..$head2 master -> origin/master" >> expect.err + cat expect.err.sub >> expect.err && + cat expect.err.deepsub >> expect.err && + ( + cd downstream && + git config fetch.recurseSubmodules false && + ( + cd submodule && + git config -f .gitmodules submodule.deepsubmodule.fetchRecursive false + ) && + git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err && + git config --unset fetch.recurseSubmodules + ( + cd submodule && + git config --unset -f .gitmodules submodule.deepsubmodule.fetchRecursive + ) + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err +' + +test_expect_success "'--recurse-submodules=on-demand' stops when no new submodule commits are found in the superproject (and ignores config)" ' + add_upstream_commit && + head1=$(git rev-parse --short HEAD) && + echo a >> file && + git add file && + git commit -m "new file" && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/." > expect.err.file && + echo " $head1..$head2 master -> origin/master" >> expect.err.file && + ( + cd downstream && + git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err + ) && + ! test -s actual.out && + test_i18ncmp expect.err.file actual.err +' + +test_expect_success "'fetch.recurseSubmodules=on-demand' overrides global config" ' + ( + cd downstream && + git fetch --recurse-submodules + ) && + add_upstream_commit && + git config --global fetch.recurseSubmodules false && + head1=$(git rev-parse --short HEAD) && + git add submodule && + git commit -m "new submodule" && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/." > expect.err.2 && + echo " $head1..$head2 master -> origin/master" >> expect.err.2 + head -2 expect.err >> expect.err.2 && + ( + cd downstream && + git config fetch.recurseSubmodules on-demand && + git fetch >../actual.out 2>../actual.err + ) && + git config --global --unset fetch.recurseSubmodules && + ( + cd downstream && + git config --unset fetch.recurseSubmodules + ) && + test_i18ncmp expect.out.sub actual.out && + test_i18ncmp expect.err.2 actual.err +' + +test_expect_success "'submodule.<sub>.fetchRecurseSubmodules=on-demand' overrides fetch.recurseSubmodules" ' + ( + cd downstream && + git fetch --recurse-submodules + ) && + add_upstream_commit && + git config fetch.recurseSubmodules false && + head1=$(git rev-parse --short HEAD) && + git add submodule && + git commit -m "new submodule" && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/." > expect.err.2 && + echo " $head1..$head2 master -> origin/master" >> expect.err.2 + head -2 expect.err >> expect.err.2 && + ( + cd downstream && + git config submodule.submodule.fetchRecurseSubmodules on-demand && + git fetch >../actual.out 2>../actual.err + ) && + git config --unset fetch.recurseSubmodules && + ( + cd downstream && + git config --unset submodule.submodule.fetchRecurseSubmodules + ) && + test_i18ncmp expect.out.sub actual.out && + test_i18ncmp expect.err.2 actual.err +' + +test_expect_success "don't fetch submodule when newly recorded commits are already present" ' + ( + cd submodule && + git checkout -q HEAD^^ + ) && + head1=$(git rev-parse --short HEAD) && + git add submodule && + git commit -m "submodule rewound" && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/." > expect.err && + echo " $head1..$head2 master -> origin/master" >> expect.err && + ( + cd downstream && + git fetch >../actual.out 2>../actual.err + ) && + ! test -s actual.out && + test_i18ncmp expect.err actual.err +' + test_done diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh new file mode 100755 index 0000000000..62f2460047 --- /dev/null +++ b/t/t5532-fetch-proxy.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='fetching via git:// using core.gitproxy' +. ./test-lib.sh + +test_expect_success 'setup remote repo' ' + git init remote && + (cd remote && + echo content >file && + git add file && + git commit -m one + ) +' + +cat >proxy <<'EOF' +#!/bin/sh +echo >&2 "proxying for $*" +cmd=`perl -e ' + read(STDIN, $buf, 4); + my $n = hex($buf) - 4; + read(STDIN, $buf, $n); + my ($cmd, $other) = split /\0/, $buf; + # drop absolute-path on repo name + $cmd =~ s{ /}{ }; + print $cmd; +'` +echo >&2 "Running '$cmd'" +exec $cmd +EOF +chmod +x proxy +test_expect_success 'setup local repo' ' + git remote add fake git://example.com/remote && + git config core.gitproxy ./proxy +' + +test_expect_success 'fetch through proxy works' ' + git fetch fake && + echo one >expect && + git log -1 --format=%s FETCH_HEAD >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index b0c2a2c3ae..a73c82635f 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -65,14 +65,16 @@ test_expect_success 'clone remote repository' ' git clone $HTTPD_URL/smart/test_repo.git test_repo_clone ' -test_expect_success 'push to remote repository' ' +test_expect_success 'push to remote repository (standard)' ' cd "$ROOT_PATH"/test_repo_clone && : >path2 && git add path2 && test_tick && git commit -m path2 && HEAD=$(git rev-parse --verify HEAD) && - git push && + GIT_CURL_VERBOSE=1 git push -v -v 2>err && + ! grep "Expect: 100-continue" err && + grep "POST git-receive-pack ([0-9]* bytes)" err && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && test $HEAD = $(git rev-parse --verify HEAD)) ' @@ -128,14 +130,29 @@ 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. - test_must_fail git push -v origin +master master:retsam >output 2>&1 && + test_must_fail git push -v origin +master master:retsam >output 2>&1' +test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: remote output' ' grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output && - grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output && + grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output +' - grep "To prevent you from losing history, non-fast-forward updates were rejected" \ +test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: our output' ' + test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" \ output ' +test_expect_success 'push (chunked)' ' + git checkout master && + test_commit commit path3 && + HEAD=$(git rev-parse --verify HEAD) && + git config http.postbuffer 4 && + test_when_finished "git config --unset http.postbuffer" && + git push -v -v origin $BRANCH 2>err && + grep "POST git-receive-pack (chunked)" err && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD = $(git rev-parse --verify HEAD)) +' + stop_httpd test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 987e0c8463..151ea531bd 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -31,7 +31,7 @@ test_expect_success 'clone with excess parameters (2)' ' ' -test_expect_success 'output from clone' ' +test_expect_success C_LOCALE_OUTPUT 'output from clone' ' rm -fr dst && git clone -n "file://$(pwd)/src" dst >output && test $(grep Clon output | wc -l) = 1 @@ -164,7 +164,6 @@ test_expect_success 'clone a void' ' test_expect_success 'clone respects global branch.autosetuprebase' ' ( test_config="$HOME/.gitconfig" && - unset GIT_CONFIG_NOGLOBAL && git config -f "$test_config" branch.autosetuprebase remote && rm -fr dst && git clone src dst && @@ -192,4 +191,20 @@ test_expect_success 'do not respect url-encoding of non-url path' ' git clone x+y xy-regular ' +test_expect_success 'clone separate gitdir' ' + rm -rf dst && + git clone --separate-git-dir realgitdir src dst && + test -d realgitdir/refs +' + +test_expect_success 'clone separate gitdir: output' ' + echo "gitdir: `pwd`/realgitdir" >expected && + test_cmp expected dst/.git +' + +test_expect_success 'clone separate gitdir where target already exists' ' + rm -rf dst && + test_must_fail git clone --separate-git-dir realgitdir src dst +' + test_done diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh index 0f4d487be3..6972258b27 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -144,4 +144,17 @@ test_expect_success 'clone empty repository, and then push should not segfault.' test_must_fail git push) ' +test_expect_success 'cloning non-existent directory fails' ' + cd "$D" && + rm -rf does-not-exist && + test_must_fail git clone does-not-exist +' + +test_expect_success 'cloning non-git directory fails' ' + cd "$D" && + rm -rf not-a-git-repo not-a-git-repo-clone && + mkdir not-a-git-repo && + test_must_fail git clone not-a-git-repo not-a-git-repo-clone +' + test_done diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh new file mode 100755 index 0000000000..b10685af4e --- /dev/null +++ b/t/t6000-rev-list-misc.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +test_description='miscellaneous rev-list tests' + +. ./test-lib.sh + +test_expect_success setup ' + echo content1 >wanted_file && + echo content2 >unwanted_file && + git add wanted_file unwanted_file && + git commit -m one +' + +test_expect_success 'rev-list --objects heeds pathspecs' ' + git rev-list --objects HEAD -- wanted_file >output && + grep wanted_file output && + ! grep unwanted_file output +' + +test_expect_success 'rev-list --objects with pathspecs and deeper paths' ' + mkdir foo && + >foo/file && + git add foo/file && + git commit -m two && + + git rev-list --objects HEAD -- foo >output && + grep foo/file output && + + git rev-list --objects HEAD -- foo/file >output && + grep foo/file output && + ! grep unwanted_file output +' + +test_expect_success 'rev-list --objects with pathspecs and copied files' ' + git checkout --orphan junio-testcase && + git rm -rf . && + + mkdir two && + echo frotz >one && + cp one two/three && + git add one two/three && + test_tick && + git commit -m that && + + ONE=$(git rev-parse HEAD:one) + git rev-list --objects HEAD two >output && + grep "$ONE two/three" output && + ! grep one output +' + +test_done diff --git a/t/t6004-rev-list-path-optim.sh b/t/t6004-rev-list-path-optim.sh index 5dabf1c5e3..3e8c42ee0b 100755 --- a/t/t6004-rev-list-path-optim.sh +++ b/t/t6004-rev-list-path-optim.sh @@ -1,51 +1,96 @@ #!/bin/sh -test_description='git rev-list trivial path optimization test' +test_description='git rev-list trivial path optimization test + + d/z1 + b0 b1 + o------------------------*----o master + / / + o---------o----o----o----o side + a0 c0 c1 a1 c2 + d/f0 d/f1 + d/z0 + +' . ./test-lib.sh test_expect_success setup ' -echo Hello > a && -git add a && -git commit -m "Initial commit" a && -initial=$(git rev-parse --verify HEAD) + echo Hello >a && + mkdir d && + echo World >d/f && + echo World >d/z && + git add a d && + test_tick && + git commit -m "Initial commit" && + git rev-parse --verify HEAD && + git tag initial ' test_expect_success path-optimization ' - commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) && - test $(git rev-list $commit | wc -l) = 2 && - test $(git rev-list $commit -- . | wc -l) = 1 + test_tick && + commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) && + test $(git rev-list $commit | wc -l) = 2 && + test $(git rev-list $commit -- . | wc -l) = 1 ' test_expect_success 'further setup' ' git checkout -b side && echo Irrelevant >c && - git add c && + echo Irrelevant >d/f && + git add c d/f && + test_tick && git commit -m "Side makes an irrelevant commit" && + git tag side_c0 && echo "More Irrelevancy" >c && git add c && + test_tick && git commit -m "Side makes another irrelevant commit" && echo Bye >a && git add a && + test_tick && git commit -m "Side touches a" && - side=$(git rev-parse --verify HEAD) && + git tag side_a1 && echo "Yet more Irrelevancy" >c && git add c && + test_tick && git commit -m "Side makes yet another irrelevant commit" && git checkout master && echo Another >b && - git add b && + echo Munged >d/z && + git add b d/z && + test_tick && git commit -m "Master touches b" && + git tag master_b0 && git merge side && echo Touched >b && git add b && + test_tick && git commit -m "Master touches b again" ' test_expect_success 'path optimization 2' ' - ( echo "$side"; echo "$initial" ) >expected && + git rev-parse side_a1 initial >expected && git rev-list HEAD -- a >actual && test_cmp expected actual ' +test_expect_success 'pathspec with leading path' ' + git rev-parse master^ master_b0 side_c0 initial >expected && + git rev-list HEAD -- d >actual && + test_cmp expected actual +' + +test_expect_success 'pathspec with glob (1)' ' + git rev-parse master^ master_b0 side_c0 initial >expected && + git rev-list HEAD -- "d/*" >actual && + test_cmp expected actual +' + +test_expect_success 'pathspec with glob (2)' ' + git rev-parse side_c0 initial >expected && + git rev-list HEAD -- "d/[a-m]*" >actual && + test_cmp expected actual +' + test_done diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh index b565638e92..28d4f6b259 100755 --- a/t/t6007-rev-list-cherry-pick-file.sh +++ b/t/t6007-rev-list-cherry-pick-file.sh @@ -4,13 +4,14 @@ test_description='test git rev-list --cherry-pick -- file' . ./test-lib.sh -# A---B +# A---B---D---F # \ # \ -# C +# C---E # # B changes a file foo.c, adding a line of text. C changes foo.c as # well as bar.c, but the change in foo.c was identical to change B. +# D and C change bar in the same way, E and F differently. test_expect_success setup ' echo Hallo > foo && @@ -25,11 +26,26 @@ test_expect_success setup ' test_tick && git commit -m "C" && git tag C && + echo Dello > bar && + git add bar && + test_tick && + git commit -m "E" && + git tag E && git checkout master && git checkout branch foo && test_tick && git commit -m "B" && - git tag B + git tag B && + echo Cello > bar && + git add bar && + test_tick && + git commit -m "D" && + git tag D && + echo Nello > bar && + git add bar && + test_tick && + git commit -m "F" && + git tag F ' cat >expect <<EOF @@ -53,8 +69,119 @@ test_expect_success '--cherry-pick foo comes up empty' ' test -z "$(git rev-list --left-right --cherry-pick B...C -- foo)" ' +cat >expect <<EOF +>tags/C +EOF + test_expect_success '--cherry-pick bar does not come up empty' ' - ! test -z "$(git rev-list --left-right --cherry-pick B...C -- bar)" + git rev-list --left-right --cherry-pick B...C -- bar > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +test_expect_success 'bar does not come up empty' ' + git rev-list --left-right B...C -- bar > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +cat >expect <<EOF +<tags/F +>tags/E +EOF + +test_expect_success '--cherry-pick bar does not come up empty (II)' ' + git rev-list --left-right --cherry-pick F...E -- bar > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +cat >expect <<EOF ++tags/F +=tags/D ++tags/E +=tags/C +EOF + +test_expect_success '--cherry-mark' ' + git rev-list --cherry-mark F...E -- bar > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +cat >expect <<EOF +<tags/F +=tags/D +>tags/E +=tags/C +EOF + +test_expect_success '--cherry-mark --left-right' ' + git rev-list --cherry-mark --left-right F...E -- bar > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +cat >expect <<EOF +tags/E +EOF + +test_expect_success '--cherry-pick --right-only' ' + git rev-list --cherry-pick --right-only F...E -- bar > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +test_expect_success '--cherry-pick --left-only' ' + git rev-list --cherry-pick --left-only E...F -- bar > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +cat >expect <<EOF ++tags/E +=tags/C +EOF + +test_expect_success '--cherry' ' + git rev-list --cherry F...E -- bar > actual && + git name-rev --stdin --name-only --refs="*tags/*" \ + < actual > actual.named && + test_cmp actual.named expect +' + +cat >expect <<EOF +1 1 +EOF + +test_expect_success '--cherry --count' ' + git rev-list --cherry --count F...E -- bar > actual && + test_cmp actual expect +' + +cat >expect <<EOF +2 2 +EOF + +test_expect_success '--cherry-mark --count' ' + git rev-list --cherry-mark --count F...E -- bar > actual && + test_cmp actual expect +' + +cat >expect <<EOF +1 1 2 +EOF + +test_expect_success '--cherry-mark --left-right --count' ' + git rev-list --cherry-mark --left-right --count F...E -- bar > actual && + test_cmp actual expect ' test_expect_success '--cherry-pick with independent, but identical branches' ' @@ -75,11 +202,8 @@ 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 && + git rev-list --count --left-right C...D > actual && test_cmp expect actual ' diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index 52f7b277ce..30507407ff 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -1,14 +1,15 @@ #!/bin/sh -test_description='properly cull all ancestors' +test_description='ancestor culling and limiting by parent number' . ./test-lib.sh -commit () { - test_tick && - echo $1 >file && - git commit -a -m $1 && - git tag $1 +check_revlist () { + rev_list_args="$1" && + shift && + git rev-parse "$@" >expect && + git rev-list $rev_list_args --all >actual && + test_cmp expect actual } test_expect_success setup ' @@ -16,13 +17,13 @@ test_expect_success setup ' touch file && git add file && - commit one && + test_commit one && test_tick=$(($test_tick - 2400)) && - commit two && - commit three && - commit four && + test_commit two && + test_commit three && + test_commit four && git log --pretty=oneline --abbrev-commit ' @@ -35,4 +36,101 @@ test_expect_success 'one is ancestor of others and should not be shown' ' ' +test_expect_success 'setup roots, merges and octopuses' ' + + git checkout --orphan newroot && + test_commit five && + git checkout -b sidebranch two && + test_commit six && + git checkout -b anotherbranch three && + test_commit seven && + git checkout -b yetanotherbranch four && + test_commit eight && + git checkout master && + test_merge normalmerge newroot && + test_tick && + git merge -m tripus sidebranch anotherbranch && + git tag tripus && + git checkout -b tetrabranch normalmerge && + test_tick && + git merge -m tetrapus sidebranch anotherbranch yetanotherbranch && + git tag tetrapus && + git checkout master +' + +test_expect_success 'rev-list roots' ' + + check_revlist "--max-parents=0" one five +' + +test_expect_success 'rev-list no merges' ' + + check_revlist "--max-parents=1" one eight seven six five four three two && + check_revlist "--no-merges" one eight seven six five four three two +' + +test_expect_success 'rev-list no octopuses' ' + + check_revlist "--max-parents=2" one normalmerge eight seven six five four three two +' + +test_expect_success 'rev-list no roots' ' + + check_revlist "--min-parents=1" tetrapus tripus normalmerge eight seven six four three two +' + +test_expect_success 'rev-list merges' ' + + check_revlist "--min-parents=2" tetrapus tripus normalmerge && + check_revlist "--merges" tetrapus tripus normalmerge +' + +test_expect_success 'rev-list octopus' ' + + check_revlist "--min-parents=3" tetrapus tripus +' + +test_expect_success 'rev-list ordinary commits' ' + + check_revlist "--min-parents=1 --max-parents=1" eight seven six four three two +' + +test_expect_success 'rev-list --merges --no-merges yields empty set' ' + + check_revlist "--min-parents=2 --no-merges" && + check_revlist "--merges --no-merges" && + check_revlist "--no-merges --merges" +' + +test_expect_success 'rev-list override and infinities' ' + + check_revlist "--min-parents=2 --max-parents=1 --max-parents=3" tripus normalmerge && + check_revlist "--min-parents=1 --min-parents=2 --max-parents=7" tetrapus tripus normalmerge && + check_revlist "--min-parents=2 --max-parents=8" tetrapus tripus normalmerge && + check_revlist "--min-parents=2 --max-parents=-1" tetrapus tripus normalmerge && + check_revlist "--min-parents=2 --no-max-parents" tetrapus tripus normalmerge && + check_revlist "--max-parents=0 --min-parents=1 --no-min-parents" one five +' + +test_expect_success 'dodecapus' ' + + roots= && + for i in 1 2 3 4 5 6 7 8 9 10 11 + do + git checkout -b root$i five && + test_commit $i && + roots="$roots root$i" || + return + done && + git checkout master && + test_tick && + git merge -m dodecapus $roots && + git tag dodecapus && + + check_revlist "--min-parents=4" dodecapus tetrapus && + check_revlist "--min-parents=8" dodecapus && + check_revlist "--min-parents=12" dodecapus && + check_revlist "--min-parents=13" && + check_revlist "--min-parents=4 --max-parents=11" tetrapus +' test_done diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 082032edc3..f80bba871c 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -8,38 +8,38 @@ test_description='Merge base and parent list computation. . ./test-lib.sh -test_expect_success 'setup' ' - T=$(git write-tree) && +M=1130000000 +Z=+0000 - M=1130000000 && - Z=+0000 && +GIT_COMMITTER_EMAIL=git@comm.iter.xz +GIT_COMMITTER_NAME='C O Mmiter' +GIT_AUTHOR_NAME='A U Thor' +GIT_AUTHOR_EMAIL=git@au.thor.xz +export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL - GIT_COMMITTER_EMAIL=git@comm.iter.xz && - GIT_COMMITTER_NAME="C O Mmiter" && - GIT_AUTHOR_NAME="A U Thor" && - GIT_AUTHOR_EMAIL=git@au.thor.xz && - export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL && +doit () { + OFFSET=$1 && + NAME=$2 && + shift 2 && - doit() { - OFFSET=$1 && - NAME=$2 && - shift 2 && + PARENTS= && + for P + do + PARENTS="${PARENTS}-p $P " + done && - PARENTS= && - for P - do - PARENTS="${PARENTS}-p $P " - done && + GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" && + GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE && + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE && - GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" && - GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE && - export GIT_COMMITTER_DATE GIT_AUTHOR_DATE && + commit=$(echo $NAME | git commit-tree $T $PARENTS) && - commit=$(echo $NAME | git commit-tree $T $PARENTS) && + echo $commit >.git/refs/tags/$NAME && + echo $commit +} - echo $commit >.git/refs/tags/$NAME && - echo $commit - } +test_expect_success 'setup' ' + T=$(git mktree </dev/null) ' test_expect_success 'set up G and H' ' diff --git a/t/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh index f1c32dba42..667b37564e 100755 --- a/t/t6017-rev-list-stdin.sh +++ b/t/t6017-rev-list-stdin.sh @@ -58,4 +58,21 @@ check side-3 ^side-4 -- file-3 check side-3 ^side-2 check side-3 ^side-2 -- file-1 +test_expect_success 'not only --stdin' ' + cat >expect <<-EOF && + 7 + + file-1 + file-2 + EOF + cat >input <<-EOF && + ^master^ + -- + file-2 + EOF + git log --pretty=tformat:%s --name-only --stdin master -- file-1 \ + <input >actual && + test_cmp expect actual +' + test_done diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index fb8291c812..f00cebff3e 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -69,6 +69,18 @@ test_expect_success 'rev-parse --glob=heads/subspace' ' ' +test_expect_failure 'rev-parse accepts --glob as detached option' ' + + compare rev-parse "subspace/one subspace/two" "--glob heads/subspace" + +' + +test_expect_failure 'rev-parse is not confused by option-like glob' ' + + compare rev-parse "master" "--glob --symbolic master" + +' + test_expect_success 'rev-parse --branches=subspace/*' ' compare rev-parse "subspace/one subspace/two" "--branches=subspace/*" @@ -129,6 +141,12 @@ test_expect_success 'rev-list --glob refs/heads/subspace/*' ' ' +test_expect_success 'rev-list not confused by option-like --glob arg' ' + + compare rev-list "master" "--glob -0 master" + +' + test_expect_success 'rev-list --glob=heads/subspace/*' ' compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*" @@ -213,4 +231,36 @@ test_expect_success 'rev-list --remotes=foo' ' ' +test_expect_success 'shortlog accepts --glob/--tags/--remotes' ' + + compare shortlog "subspace/one subspace/two" --branches=subspace && + compare shortlog \ + "master subspace-x someref other/three subspace/one subspace/two" \ + --branches && + compare shortlog master "--glob=heads/someref/* master" && + compare shortlog "subspace/one subspace/two other/three" \ + "--glob=heads/subspace/* --glob=heads/other/*" && + compare shortlog \ + "master other/three someref subspace-x subspace/one subspace/two" \ + "--glob=heads/*" && + compare shortlog foo/bar --tags=foo && + compare shortlog foo/bar --tags && + compare shortlog foo/baz --remotes=foo + +' + +test_expect_failure 'shortlog accepts --glob as detached option' ' + + compare shortlog \ + "master other/three someref subspace-x subspace/one subspace/two" \ + "--glob heads/*" + +' + +test_expect_failure 'shortlog --glob is not confused by option-like argument' ' + + compare shortlog master "--glob -e master" + +' + test_done diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh index 92e02d5d77..2599ae50eb 100755 --- a/t/t6035-merge-dir-to-symlink.sh +++ b/t/t6035-merge-dir-to-symlink.sh @@ -17,13 +17,21 @@ test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' git commit -m "dir to symlink" ' -test_expect_success SYMLINKS 'keep a/b-2/c/d across checkout' ' +test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' ' git checkout HEAD^0 && git reset --hard master && git rm --cached a/b && git commit -m "untracked symlink remains" && - git checkout start^0 && - test -f a/b-2/c/d + test_must_fail git checkout start^0 +' + +test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' ' + git checkout HEAD^0 && + git reset --hard master && + git rm --cached a/b && + git commit -m "untracked symlink remains" && + git checkout -f start^0 && + test -f a/b-2/c/d ' test_expect_success SYMLINKS 'checkout should not have deleted a/b-2/c/d' ' diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 1e0447f615..a9b0ac1efc 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -48,7 +48,7 @@ test_expect_success 'branch -v' ' git branch -v ) | sed -n -e "$script" >actual && - test_cmp expect actual + test_i18ncmp expect actual ' test_expect_success 'checkout' ' @@ -74,20 +74,20 @@ test_expect_success 'status' ' grep "have 1 and 1 different" actual ' -test_expect_success 'status when tracking lightweight tags' ' +test_expect_success 'fail to track lightweight tags' ' git checkout master && git tag light && - git branch --track lighttrack light >actual && - grep "set up to track" actual && - git checkout lighttrack + test_must_fail git branch --track lighttrack light >actual && + test_must_fail grep "set up to track" actual && + test_must_fail git checkout lighttrack ' -test_expect_success 'status when tracking annotated tags' ' +test_expect_success 'fail to track annotated tags' ' git checkout master && git tag -m heavy heavy && - git branch --track heavytrack heavy >actual && - grep "set up to track" actual && - git checkout heavytrack + test_must_fail git branch --track heavytrack heavy >actual && + test_must_fail grep "set up to track" actual && + test_must_fail git checkout heavytrack ' test_expect_success 'setup tracking with branch --set-upstream on existing branch' ' diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index ae2194e07d..5c87f28e4e 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -236,6 +236,20 @@ test_expect_success 'index-pack and replacements' ' git index-pack test-*.pack ' -# -# +test_expect_success 'not just commits' ' + echo replaced >file && + git add file && + REPLACED=$(git rev-parse :file) && + mv file file.replaced && + + echo original >file && + git add file && + ORIGINAL=$(git rev-parse :file) && + git update-ref refs/replace/$ORIGINAL $REPLACED && + mv file file.original && + + git checkout file && + test_cmp file.replaced file +' + test_done diff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh new file mode 100755 index 0000000000..debadbd299 --- /dev/null +++ b/t/t6060-merge-index.sh @@ -0,0 +1,100 @@ +#!/bin/sh + +test_description='basic git merge-index / git-merge-one-file tests' +. ./test-lib.sh + +test_expect_success 'setup diverging branches' ' + for i in 1 2 3 4 5 6 7 8 9 10; do + echo $i + done >file && + git add file && + git commit -m base && + git tag base && + sed s/2/two/ <file >tmp && + mv tmp file && + git commit -a -m two && + git tag two && + git checkout -b other HEAD^ && + sed s/10/ten/ <file >tmp && + mv tmp file && + git commit -a -m ten && + git tag ten +' + +cat >expect-merged <<'EOF' +1 +two +3 +4 +5 +6 +7 +8 +9 +ten +EOF + +test_expect_success 'read-tree does not resolve content merge' ' + git read-tree -i -m base ten two && + echo file >expect && + git diff-files --name-only --diff-filter=U >unmerged && + test_cmp expect unmerged +' + +test_expect_success 'git merge-index git-merge-one-file resolves' ' + git merge-index git-merge-one-file -a && + git diff-files --name-only --diff-filter=U >unmerged && + >expect && + test_cmp expect unmerged && + test_cmp expect-merged file && + git cat-file blob :file >file-index && + test_cmp expect-merged file-index +' + +test_expect_success 'setup bare merge' ' + git clone --bare . bare.git && + (cd bare.git && + GIT_INDEX_FILE=$PWD/merge.index && + export GIT_INDEX_FILE && + git read-tree -i -m base ten two + ) +' + +test_expect_success 'merge-one-file fails without a work tree' ' + (cd bare.git && + GIT_INDEX_FILE=$PWD/merge.index && + export GIT_INDEX_FILE && + test_must_fail git merge-index git-merge-one-file -a + ) +' + +test_expect_success 'merge-one-file respects GIT_WORK_TREE' ' + (cd bare.git && + mkdir work && + GIT_WORK_TREE=$PWD/work && + export GIT_WORK_TREE && + GIT_INDEX_FILE=$PWD/merge.index && + export GIT_INDEX_FILE && + git merge-index git-merge-one-file -a && + git cat-file blob :file >work/file-index + ) && + test_cmp expect-merged bare.git/work/file && + test_cmp expect-merged bare.git/work/file-index +' + +test_expect_success 'merge-one-file respects core.worktree' ' + mkdir subdir && + git clone . subdir/child && + (cd subdir && + GIT_DIR=$PWD/child/.git && + export GIT_DIR && + git config core.worktree "$PWD/child" && + git read-tree -i -m base ten two && + git merge-index git-merge-one-file -a && + git cat-file blob :file >file-index + ) && + test_cmp expect-merged subdir/child/file && + test_cmp expect-merged subdir/file-index +' + +test_done diff --git a/t/t6110-rev-list-sparse.sh b/t/t6110-rev-list-sparse.sh new file mode 100755 index 0000000000..656ac7fe9d --- /dev/null +++ b/t/t6110-rev-list-sparse.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='operations that cull histories in unusual ways' +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git checkout -b side HEAD^ && + test_commit D && + test_commit E && + git merge master +' + +test_expect_success 'rev-list --first-parent --boundary' ' + git rev-list --first-parent --boundary HEAD^.. +' + +test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 876d1ab743..f67aa6ff6a 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -124,7 +124,7 @@ warning: tag 'A' is really 'Q' here EOF check_describe A-* HEAD test_expect_success 'warning was displayed for Q' ' - test_cmp err.expect err.actual + test_i18ncmp err.expect err.actual ' test_expect_success 'rename tag Q back to A' ' mv .git/refs/tags/Q .git/refs/tags/A diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 3e7baaf89f..2ac1c66079 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1120,12 +1120,11 @@ test_expect_success \ ! (GIT_EDITOR=cat git tag -a initial-comment > actual) ' -test_expect_success \ - 'message in editor has initial comment: first line' ' +test_expect_success 'message in editor has initial comment: first line' ' # check the first line --- should be empty echo >first.expect && sed -e 1q <actual >first.actual && - test_cmp first.expect first.actual + test_i18ncmp first.expect first.actual ' test_expect_success \ diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh index bb4066f767..8f3b54d826 100755 --- a/t/t7011-skip-worktree-reading.sh +++ b/t/t7011-skip-worktree-reading.sh @@ -24,7 +24,7 @@ H sub/2 EOF NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 -ZERO_SHA0=0000000000000000000000000000000000000000 + setup_absent() { test -f 1 && rm 1 git update-index --remove 1 && @@ -120,7 +120,7 @@ test_expect_success 'grep with skip-worktree file' ' test "$(git grep --no-ext-grep test)" = "1:test" ' -echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A 1" > expected +echo ":000000 100644 $_z40 $NULL_SHA1 A 1" > expected test_expect_success 'diff-index does not examine skip-worktree absent entries' ' setup_absent && git diff-index HEAD -- 1 > result && diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh index 582d0b54f1..9ceaa4049f 100755 --- a/t/t7012-skip-worktree-writing.sh +++ b/t/t7012-skip-worktree-writing.sh @@ -54,7 +54,7 @@ test_expect_success 'read-tree removes worktree, dirty case' ' ' NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 -ZERO_SHA0=0000000000000000000000000000000000000000 + setup_absent() { test -f 1 && rm 1 git update-index --remove 1 && @@ -127,13 +127,13 @@ EOF test_expect_success 'git-clean, absent case' ' setup_absent && git clean -n > result && - test_cmp expected result + test_i18ncmp expected result ' test_expect_success 'git-clean, dirty case' ' setup_dirty && git clean -n > result && - test_cmp expected result + test_i18ncmp expected result ' #TODO test_expect_failure 'git-apply adds file' false diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index fcac472598..b8cb4906aa 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -50,10 +50,72 @@ test_expect_success 'M/D conflict does not segfault' ' git commit -m delete && test_must_fail git merge master && test_must_fail git commit --dry-run >../actual && - test_cmp ../expect ../actual && + test_i18ncmp ../expect ../actual && git status >../actual && - test_cmp ../expect ../actual + test_i18ncmp ../expect ../actual ) ' +test_expect_success 'rename & unmerged setup' ' + git rm -f -r . && + cat "$TEST_DIRECTORY/README" >ONE && + git add ONE && + test_tick && + git commit -m "One commit with ONE" && + + echo Modified >TWO && + cat ONE >>TWO && + cat ONE >>THREE && + git add TWO THREE && + sha1=$(git rev-parse :ONE) && + git rm --cached ONE && + ( + echo "100644 $sha1 1 ONE" && + echo "100644 $sha1 2 ONE" && + echo "100644 $sha1 3 ONE" + ) | git update-index --index-info && + echo Further >>THREE +' + +test_expect_success 'rename & unmerged status' ' + git status -suno >actual && + cat >expect <<-EOF && + UU ONE + AM THREE + A TWO + EOF + test_cmp expect actual +' + +test_expect_success 'git diff-index --cached shows 2 added + 1 unmerged' ' + cat >expected <<-EOF && + U ONE + A THREE + A TWO + EOF + git diff-index --cached --name-status HEAD >actual && + test_cmp expected actual +' + +test_expect_success 'git diff-index --cached -M shows 2 added + 1 unmerged' ' + cat >expected <<-EOF && + U ONE + A THREE + A TWO + EOF + git diff-index --cached --name-status HEAD >actual && + test_cmp expected actual +' + +test_expect_success 'git diff-index --cached -C shows 2 copies + 1 unmerged' ' + cat >expected <<-EOF && + U ONE + C ONE THREE + C ONE TWO + EOF + git diff-index --cached -C --name-status HEAD | + sed "s/^C[0-9]*/C/g" >actual && + test_cmp expected actual +' + test_done diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index b8cf2603a1..f1cfc9ac95 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -426,7 +426,7 @@ EOF test_expect_success '--mixed refreshes the index' ' echo 123 >> file2 && git reset --mixed HEAD > output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success 'disambiguation (1)' ' diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh index 70cdd8e618..a82a07a04a 100755 --- a/t/t7110-reset-merge.sh +++ b/t/t7110-reset-merge.sh @@ -237,7 +237,7 @@ 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_i18ngrep "middle of a merge" err.log ' # The next test will test the following: @@ -263,7 +263,7 @@ 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_i18ngrep "middle of a merge" err.log ' test_expect_success '--merge is ok with added/deleted merge' ' @@ -289,7 +289,7 @@ test_expect_success '--keep fails with added/deleted merge' ' 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_i18ngrep "middle of a merge" err.log ' test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 1337fa5a22..07fb53adcb 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -228,7 +228,7 @@ test_expect_success 'checkout to detach HEAD (with advice declined)' ' git config advice.detachedHead false && git checkout -f renamer && git clean -f && git checkout renamer^ 2>messages && - grep "HEAD is now at 7329388" messages && + test_i18ngrep "HEAD is now at 7329388" messages && test 1 -eq $(wc -l <messages) && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && @@ -246,7 +246,7 @@ test_expect_success 'checkout to detach HEAD' ' git config advice.detachedHead true && git checkout -f renamer && git clean -f && git checkout renamer^ 2>messages && - grep "HEAD is now at 7329388" messages && + test_i18ngrep "HEAD is now at 7329388" messages && test 1 -lt $(wc -l <messages) && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && @@ -408,6 +408,15 @@ test_expect_success 'checkout w/--track from non-branch HEAD fails' ' test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)" ' +test_expect_success 'checkout w/--track from tag fails' ' + git checkout master^0 && + test_must_fail git symbolic-ref HEAD && + test_must_fail git checkout --track -b track frotz && + test_must_fail git rev-parse --verify track && + test_must_fail git symbolic-ref HEAD && + test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)" +' + test_expect_success 'detach a symbolic link HEAD' ' git checkout master && git config --bool core.prefersymlinkrefs yes && @@ -423,7 +432,6 @@ test_expect_success 'detach a symbolic link HEAD' ' test_expect_success \ 'checkout with --track fakes a sensible -b <name>' ' git update-ref refs/remotes/origin/koala/bear renamer && - git update-ref refs/new/koala/bear renamer && git checkout --track origin/koala/bear && test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && @@ -439,12 +447,6 @@ test_expect_success \ git checkout --track remotes/origin/koala/bear && test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && - test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" && - - git checkout master && git branch -D koala/bear && - - git checkout --track refs/new/koala/bear && - test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" ' diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 02f67b73b7..800b5368a5 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -110,7 +110,7 @@ test_expect_success 'git clean with prefix' ' ' -test_expect_success 'git clean with relative prefix' ' +test_expect_success C_LOCALE_OUTPUT 'git clean with relative prefix' ' mkdir -p build docs && touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && @@ -125,7 +125,7 @@ test_expect_success 'git clean with relative prefix' ' } ' -test_expect_success 'git clean with absolute path' ' +test_expect_success C_LOCALE_OUTPUT 'git clean with absolute path' ' mkdir -p build docs && touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && @@ -377,7 +377,7 @@ test_expect_success 'clean.requireForce and -f' ' ' -test_expect_success 'core.excludesfile' ' +test_expect_success C_LOCALE_OUTPUT 'core.excludesfile' ' echo excludes >excludes && echo included >included && @@ -453,4 +453,11 @@ test_expect_success 'git clean -e' ' ) ' +test_expect_success SANITY 'git clean -d with an unreadable empty directory' ' + mkdir foo && + chmod a= foo && + git clean -dfx foo && + ! test -d foo +' + test_done diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index e5b19538b0..d600583cef 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -52,7 +52,7 @@ test_expect_success 'change submodule url' ' test_expect_success '"git submodule sync" should update submodule URLs' ' (cd super-clone && - git pull && + git pull --no-recurse-submodules && git submodule sync ) && test -d "$(git config -f super-clone/submodule/.git/config \ diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh index 7e2e258950..a8fb30b792 100755 --- a/t/t7405-submodule-merge.sh +++ b/t/t7405-submodule-merge.sh @@ -56,11 +56,11 @@ test_expect_success setup ' # History setup # -# b -# / \ -# a d -# \ / -# c +# b +# / \ +# init -- a d +# \ \ / +# g c # # a in the main repository records to sub-a in the submodule and # analogous b and c. d should be automatically found by merging c into @@ -76,6 +76,8 @@ test_expect_success 'setup for merge search' ' git add file-a && git commit -m "sub-a" && git branch sub-a) && + git commit --allow-empty -m init && + git branch init && git add sub && git commit -m "a" && git branch a && @@ -101,7 +103,13 @@ test_expect_success 'setup for merge search' ' git checkout -b sub-d sub-b && git merge sub-c) && git commit -a -m "d" && - git branch test b) + git branch test b && + + git checkout -b g init && + (cd sub && + git checkout -b sub-g sub-c) && + git add sub && + git commit -a -m "g") ' test_expect_success 'merge with one side as a fast-forward of the other' ' @@ -176,6 +184,44 @@ test_expect_success 'merging should fail for changes that are backwards' ' test_must_fail git merge f) ' + +# Check that the conflicting submodule is detected when it is +# in the common ancestor. status should be 'U00...00" +test_expect_success 'git submodule status should display the merge conflict properly with merge base' ' + (cd merge-search && + cat >.gitmodules <<EOF && +[submodule "sub"] + path = sub + url = $TRASH_DIRECTORY/sub +EOF + cat >expect <<EOF && +U0000000000000000000000000000000000000000 sub +EOF + git submodule status > actual && + test_cmp expect actual && + git reset --hard) +' + +# Check that the conflicting submodule is detected when it is +# not in the common ancestor. status should be 'U00...00" +test_expect_success 'git submodule status should display the merge conflict properly without merge-base' ' + (cd merge-search && + git checkout -b test-no-merge-base g && + test_must_fail git merge b && + cat >.gitmodules <<EOF && +[submodule "sub"] + path = sub + url = $TRASH_DIRECTORY/sub +EOF + cat >expect <<EOF && +U0000000000000000000000000000000000000000 sub +EOF + git submodule status > actual && + test_cmp expect actual && + git reset --hard) +' + + test_expect_success 'merging with a modify/modify conflict between merge bases' ' git reset --hard HEAD && git checkout -b test2 c && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index bfb4975e94..4f16fcce2b 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -74,6 +74,49 @@ test_expect_success 'submodule update detaching the HEAD ' ' ) ' +apos="'"; +test_expect_success 'submodule update does not fetch already present commits' ' + (cd submodule && + echo line3 >> file && + git add file && + test_tick && + git commit -m "upstream line3" + ) && + (cd super/submodule && + head=$(git rev-parse --verify HEAD) && + echo "Submodule path ${apos}submodule$apos: checked out $apos$head$apos" > ../../expected && + git reset --hard HEAD~1 + ) && + (cd super && + git submodule update > ../actual 2> ../actual.err + ) && + test_cmp expected actual && + ! test -s actual.err +' + +test_expect_success 'submodule update should fail due to local changes' ' + (cd super/submodule && + git reset --hard HEAD~1 && + echo "local change" > file + ) && + (cd super && + (cd submodule && + compare_head + ) && + test_must_fail git submodule update submodule + ) +' +test_expect_success 'submodule update should throw away changes with --force ' ' + (cd super && + (cd submodule && + compare_head + ) && + git submodule update --force submodule && + cd submodule && + ! compare_head + ) +' + test_expect_success 'submodule update --rebase staying on master' ' (cd super/submodule && git checkout master @@ -203,4 +246,56 @@ test_expect_success 'submodule init picks up merge' ' ) ' +test_expect_success 'submodule update --merge - ignores --merge for new submodules' ' + (cd super && + rm -rf submodule && + git submodule update submodule && + git status -s submodule >expect && + rm -rf submodule && + git submodule update --merge submodule && + git status -s submodule >actual && + test_cmp expect actual + ) +' + +test_expect_success 'submodule update --rebase - ignores --rebase for new submodules' ' + (cd super && + rm -rf submodule && + git submodule update submodule && + git status -s submodule >expect && + rm -rf submodule && + git submodule update --rebase submodule && + git status -s submodule >actual && + test_cmp expect actual + ) +' + +test_expect_success 'submodule update ignores update=merge config for new submodules' ' + (cd super && + rm -rf submodule && + git submodule update submodule && + git status -s submodule >expect && + rm -rf submodule && + git config submodule.submodule.update merge && + git submodule update submodule && + git status -s submodule >actual && + git config --unset submodule.submodule.update && + test_cmp expect actual + ) +' + +test_expect_success 'submodule update ignores update=rebase config for new submodules' ' + (cd super && + rm -rf submodule && + git submodule update submodule && + git status -s submodule >expect && + rm -rf submodule && + git config submodule.submodule.update rebase && + git submodule update submodule && + git status -s submodule >actual && + git config --unset submodule.submodule.update && + test_cmp expect actual + ) +' + test_done diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index d8ad25036f..e5be13c271 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -238,6 +238,10 @@ test_expect_success 'ensure "status --cached --recursive" preserves the --cached ) && git submodule status --cached --recursive -- nested1 > ../actual ) && + if test_have_prereq MINGW + then + dos2unix actual + fi && test_cmp expect actual ' diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 162527c211..1c908f4d39 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -10,7 +10,12 @@ Tests for selected commit options.' . ./test-lib.sh commit_msg_is () { - test "`git log --pretty=format:%s%b -1`" = "$1" + expect=commit_msg_is.expect + actual=commit_msg_is.actual + + printf "%s" "$(git log --pretty=format:%s%b -1)" >$expect && + printf "%s" "$1" >$actual && + test_i18ncmp $expect $actual } # A sanity check to see if commit is working at all. @@ -23,13 +28,21 @@ test_expect_success 'a basic commit in an empty tree should succeed' ' test_expect_success 'nonexistent template file should return error' ' echo changes >> foo && git add foo && - test_must_fail git commit --template "$PWD"/notexist + ( + GIT_EDITOR="echo hello >\"\$1\"" && + export GIT_EDITOR && + test_must_fail git commit --template "$PWD"/notexist + ) ' test_expect_success 'nonexistent template file in config should return error' ' git config commit.template "$PWD"/notexist && - test_must_fail git commit && - git config --unset commit.template + test_when_finished "git config --unset commit.template" && + ( + GIT_EDITOR="echo hello >\"\$1\"" && + export GIT_EDITOR && + test_must_fail git commit + ) ' # From now on we'll use a template file that exists. @@ -110,6 +123,20 @@ test_expect_success 'commit message from file should override template' ' commit_msg_is "standard input msg" ' +cat >"$TEMPLATE" <<\EOF + + +### template + +EOF +test_expect_success 'commit message from template with whitespace issue' ' + echo "content galore" >>foo && + git add foo && + GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-whitespaced-content git commit \ + --template "$TEMPLATE" && + commit_msg_is "commit message" +' + test_expect_success 'using alternate GIT_INDEX_FILE (1)' ' cp .git/index saved-index && diff --git a/t/t7500/add-whitespaced-content b/t/t7500/add-whitespaced-content new file mode 100755 index 0000000000..ccf07c61a4 --- /dev/null +++ b/t/t7500/add-whitespaced-content @@ -0,0 +1,8 @@ +#!/bin/sh +sed -e 's/|$//' >>"$1" <<\EOF + + | +commit message | + +EOF +exit 0 diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 8980738c75..3ad04363b5 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -14,8 +14,12 @@ test_tick test_expect_success \ "initial status" \ "echo 'bongo bongo' >file && - git add file && \ - git status | grep 'Initial commit'" + git add file" + +test_expect_success "Constructing initial commit" ' + git status >actual && + test_i18ngrep "Initial commit" actual +' test_expect_success \ "fail initial amend" \ @@ -38,10 +42,13 @@ test_expect_success \ "echo King of the bongo >file && test_must_fail git commit -m foo -a file" -test_expect_success PERL \ - "using paths with --interactive" \ - "echo bong-o-bong >file && - ! (echo 7 | git commit -m foo --interactive file)" +test_expect_success PERL 'can use paths with --interactive' ' + echo bong-o-bong >file && + # 2: update, 1:st path, that is all, 7: quit + ( echo 2; echo 1; echo; echo 7 ) | + git commit -m foo --interactive file && + git reset --hard HEAD^ +' test_expect_success \ "using invalid commit with -C" \ @@ -127,6 +134,16 @@ test_expect_success PERL \ "interactive add" \ "echo 7 | git commit --interactive | grep 'What now'" +test_expect_success PERL \ + "commit --interactive doesn't change index if editor aborts" \ + "echo zoo >file && + test_must_fail git diff --exit-code >diff1 && + (echo u ; echo '*' ; echo q) | + (EDITOR=: && export EDITOR && + test_must_fail git commit --interactive) && + git diff >diff2 && + test_cmp diff1 diff2" + test_expect_success \ "showing committed revisions" \ "git rev-list HEAD >current" diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 50da034cd3..3f3adc31b9 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -22,7 +22,7 @@ check_summary_oneline() { SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')" echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp && - test_cmp exp act + test_i18ncmp exp act } test_expect_success 'output summary format' ' @@ -32,7 +32,10 @@ test_expect_success 'output summary format' ' check_summary_oneline "root-commit" "initial" && echo change >>file1 && - git add file1 && + git add file1 +' + +test_expect_success 'output summary format: root-commit' ' check_summary_oneline "" "a change" ' @@ -215,19 +218,21 @@ test_expect_success 'cleanup commit messages (strip,-F)' ' ' -echo "sample - -# Please enter the commit message for your changes. Lines starting -# with '#' will be ignored, and an empty message aborts the commit." >expect - test_expect_success 'cleanup commit messages (strip,-F,-e)' ' echo >>negative && { echo;echo sample;echo; } >text && git commit -e -F text -a && - head -n 4 .git/COMMIT_EDITMSG >actual && - test_cmp expect actual + head -n 4 .git/COMMIT_EDITMSG >actual +' + +echo "sample +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit." >expect + +test_expect_success 'cleanup commit messages (strip,-F,-e): output' ' + test_i18ncmp expect actual ' echo "# @@ -235,11 +240,10 @@ echo "# #" >> expect test_expect_success 'author different from committer' ' - echo >>negative && - git commit -e -m "sample" + test_might_fail git commit -e -m "sample" && head -n 7 .git/COMMIT_EDITMSG >actual && - test_cmp expect actual + test_i18ncmp expect actual ' mv expect expect.tmp @@ -258,8 +262,8 @@ test_expect_success 'committer is automatic' ' test_must_fail git commit -e -m "sample" ) && head -n 8 .git/COMMIT_EDITMSG | \ - sed "s/^# Committer: .*/# Committer:/" >actual && - test_cmp expect actual + sed "s/^# Committer: .*/# Committer:/" >actual + test_i18ncmp expect actual ' pwd=`pwd` @@ -362,9 +366,9 @@ try_commit () { GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template && case "$use_template" in '') - ! grep "^## Custom template" .git/COMMIT_EDITMSG ;; + test_i18ngrep ! "^## Custom template" .git/COMMIT_EDITMSG ;; *) - grep "^## Custom template" .git/COMMIT_EDITMSG ;; + test_i18ngrep "^## Custom template" .git/COMMIT_EDITMSG ;; esac } @@ -373,67 +377,67 @@ try_commit_status_combo () { test_expect_success 'commit' ' clear_config commit.status && try_commit "" && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit' ' clear_config commit.status && try_commit "" && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status' ' clear_config commit.status && try_commit --status && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status' ' clear_config commit.status && try_commit --no-status && - ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit with commit.status = yes' ' clear_config commit.status && git config commit.status yes && try_commit "" && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit with commit.status = no' ' clear_config commit.status && git config commit.status no && try_commit "" && - ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status with commit.status = yes' ' clear_config commit.status && git config commit.status yes && try_commit --status && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status with commit.status = yes' ' clear_config commit.status && git config commit.status yes && try_commit --no-status && - ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status with commit.status = no' ' clear_config commit.status && git config commit.status no && try_commit --status && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status with commit.status = no' ' clear_config commit.status && git config commit.status no && try_commit --no-status && - ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' } diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh index ff189624d4..5b4b694f18 100755 --- a/t/t7505-prepare-commit-msg-hook.sh +++ b/t/t7505-prepare-commit-msg-hook.sh @@ -132,6 +132,18 @@ test_expect_success 'with hook (-c)' ' ' +test_expect_success 'with hook (merge)' ' + + head=`git rev-parse HEAD` && + git checkout -b other HEAD@{1} && + echo "more" >> file && + git add file && + git commit -m other && + git checkout - && + git merge other && + test "`git log -1 --pretty=format:%s`" = merge +' + cat > "$HOOK" <<'EOF' #!/bin/sh exit 1 diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 3d4f85d74f..d31b34da83 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -4,17 +4,21 @@ test_description='git status for submodule' . ./test-lib.sh -test_expect_success 'setup' ' - test_create_repo sub && +test_create_repo_with_commit () { + test_create_repo "$1" && ( - cd sub && + cd "$1" && : >bar && git add bar && git commit -m " Add bar" && : >foo && git add foo && git commit -m " Add foo" - ) && + ) +} + +test_expect_success 'setup' ' + test_create_repo_with_commit sub && echo output > .gitignore && git add sub .gitignore && git commit -m "Add submodule sub" @@ -22,19 +26,19 @@ test_expect_success 'setup' ' test_expect_success 'status clean' ' git status >output && - grep "nothing to commit" output + test_i18ngrep "nothing to commit" output ' test_expect_success 'commit --dry-run -a clean' ' test_must_fail git commit --dry-run -a >output && - grep "nothing to commit" output + test_i18ngrep "nothing to commit" output ' test_expect_success 'status with modified file in submodule' ' (cd sub && git reset --hard) && echo "changed" >sub/foo && git status >output && - grep "modified: sub (modified content)" output + test_i18ngrep "modified: sub (modified content)" output ' test_expect_success 'status with modified file in submodule (porcelain)' ' @@ -49,7 +53,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 (modified content)" output + test_i18ngrep "modified: sub (modified content)" output ' test_expect_success 'status with added file in submodule (porcelain)' ' @@ -64,12 +68,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 (untracked content)" output + test_i18ngrep "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_i18ngrep "^nothing to commit" output ' test_expect_success 'status with untracked file in submodule (porcelain)' ' @@ -83,7 +87,7 @@ 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_i18ngrep "modified: sub (modified content, untracked content)" output ' test_expect_success 'status with added and untracked file in submodule (porcelain)' ' @@ -101,7 +105,7 @@ test_expect_success 'status with modified file in modified submodule' ' (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_i18ngrep "modified: sub (new commits, modified content)" output ' test_expect_success 'status with modified file in modified submodule (porcelain)' ' @@ -116,7 +120,7 @@ test_expect_success 'status with modified file in modified submodule (porcelain) 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_i18ngrep "modified: sub (new commits, modified content)" output ' test_expect_success 'status with added file in modified submodule (porcelain)' ' @@ -131,7 +135,7 @@ 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_i18ngrep "modified: sub (new commits, untracked content)" output ' test_expect_success 'status with untracked file in modified submodule (porcelain)' ' @@ -145,7 +149,7 @@ 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_i18ngrep "modified: sub (new commits, modified content, untracked content)" output ' test_expect_success 'status with added and untracked file in modified submodule (porcelain)' ' @@ -170,7 +174,7 @@ test_expect_success 'setup .git file for sub' ' 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_i18ngrep "modified: sub (new commits, modified content)" output ' test_expect_success 'rm submodule contents' ' @@ -179,12 +183,92 @@ test_expect_success 'rm submodule contents' ' test_expect_success 'status clean (empty submodule dir)' ' git status >output && - grep "nothing to commit" output + test_i18ngrep "nothing to commit" output ' test_expect_success 'status -a clean (empty submodule dir)' ' test_must_fail git commit --dry-run -a >output && - grep "nothing to commit" output + test_i18ngrep "nothing to commit" output +' + +cat >status_expect <<\EOF +AA .gitmodules +A sub1 +EOF + +test_expect_success 'status with merge conflict in .gitmodules' ' + git clone . super && + test_create_repo_with_commit sub1 && + test_tick && + test_create_repo_with_commit sub2 && + ( + cd super && + prev=$(git rev-parse HEAD) && + git checkout -b add_sub1 && + git submodule add ../sub1 && + git commit -m "add sub1" && + git checkout -b add_sub2 $prev && + git submodule add ../sub2 && + git commit -m "add sub2" && + git checkout -b merge_conflict_gitmodules && + test_must_fail git merge add_sub1 && + git status -s >../status_actual 2>&1 + ) && + test_cmp status_actual status_expect +' + +sha1_merge_sub1=$(cd sub1 && git rev-parse HEAD) +sha1_merge_sub2=$(cd sub2 && git rev-parse HEAD) +short_sha1_merge_sub1=$(cd sub1 && git rev-parse --short HEAD) +short_sha1_merge_sub2=$(cd sub2 && git rev-parse --short HEAD) +cat >diff_expect <<\EOF +diff --cc .gitmodules +index badaa4c,44f999a..0000000 +--- a/.gitmodules ++++ b/.gitmodules +@@@ -1,3 -1,3 +1,9 @@@ +++<<<<<<< HEAD + +[submodule "sub2"] + + path = sub2 + + url = ../sub2 +++======= ++ [submodule "sub1"] ++ path = sub1 ++ url = ../sub1 +++>>>>>>> add_sub1 +EOF + +cat >diff_submodule_expect <<\EOF +diff --cc .gitmodules +index badaa4c,44f999a..0000000 +--- a/.gitmodules ++++ b/.gitmodules +@@@ -1,3 -1,3 +1,9 @@@ +++<<<<<<< HEAD + +[submodule "sub2"] + + path = sub2 + + url = ../sub2 +++======= ++ [submodule "sub1"] ++ path = sub1 ++ url = ../sub1 +++>>>>>>> add_sub1 +EOF + +test_expect_success 'diff with merge conflict in .gitmodules' ' + ( + cd super && + git diff >../diff_actual 2>&1 + ) && + test_cmp diff_actual diff_expect +' + +test_expect_success 'diff --submodule with merge conflict in .gitmodules' ' + ( + cd super && + git diff --submodule >../diff_submodule_actual 2>&1 + ) && + test_cmp diff_submodule_actual diff_submodule_expect ' test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index f1dc5c3b6a..cd6e2c5e87 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -16,7 +16,7 @@ test_expect_success 'status -h in broken repository' ' echo "[status] showuntrackedfiles = CORRUPT" >>.git/config && test_expect_code 129 git status -h >usage 2>&1 ) && - grep "[Uu]sage" broken/usage + test_i18ngrep "[Uu]sage" broken/usage ' test_expect_success 'commit -h in broken repository' ' @@ -28,7 +28,7 @@ test_expect_success 'commit -h in broken repository' ' echo "[status] showuntrackedfiles = CORRUPT" >>.git/config && test_expect_code 129 git commit -h >usage 2>&1 ) && - grep "[Uu]sage" broken/usage + test_i18ngrep "[Uu]sage" broken/usage ' test_expect_success 'setup' ' @@ -56,9 +56,7 @@ test_expect_success 'setup' ' ' test_expect_success 'status (1)' ' - - grep "use \"git rm --cached <file>\.\.\.\" to unstage" output - + test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output ' cat >expect <<\EOF @@ -86,10 +84,8 @@ cat >expect <<\EOF EOF test_expect_success 'status (2)' ' - git status >output && - test_cmp expect output - + test_i18ncmp expect output ' cat >expect <<\EOF @@ -109,17 +105,14 @@ cat >expect <<\EOF # untracked EOF -git config advice.statusHints false - test_expect_success 'status (advice.statusHints false)' ' - + test_when_finished "git config --unset advice.statusHints" && + git config advice.statusHints false && git status >output && - test_cmp expect output + test_i18ncmp expect output ' -git config --unset advice.statusHints - cat >expect <<\EOF M dir1/modified A dir2/added @@ -157,6 +150,12 @@ test_expect_success 'status -s -b' ' ' +test_expect_success 'setup dir3' ' + mkdir dir3 && + : >dir3/untracked1 && + : >dir3/untracked2 +' + cat >expect <<EOF # On branch master # Changes to be committed: @@ -173,17 +172,15 @@ cat >expect <<EOF # Untracked files not listed (use -u option to show untracked files) EOF test_expect_success 'status -uno' ' - mkdir dir3 && - : >dir3/untracked1 && - : >dir3/untracked2 && git status -uno >output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success 'status (status.showUntrackedFiles no)' ' git config status.showuntrackedfiles no + test_when_finished "git config --unset status.showuntrackedfiles" && git status >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -199,7 +196,7 @@ EOF git config advice.statusHints false test_expect_success 'status -uno (advice.statusHints false)' ' git status -uno >output && - test_cmp expect output + test_i18ncmp expect output ' git config --unset advice.statusHints @@ -208,7 +205,6 @@ cat >expect << EOF A dir2/added EOF test_expect_success 'status -s -uno' ' - git config --unset status.showuntrackedfiles git status -s -uno >output && test_cmp expect output ' @@ -245,13 +241,14 @@ cat >expect <<EOF EOF test_expect_success 'status -unormal' ' git status -unormal >output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success 'status (status.showUntrackedFiles normal)' ' git config status.showuntrackedfiles normal + test_when_finished "git config --unset status.showuntrackedfiles" && git status >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -266,7 +263,6 @@ A dir2/added ?? untracked EOF test_expect_success 'status -s -unormal' ' - git config --unset status.showuntrackedfiles git status -s -unormal >output && test_cmp expect output ' @@ -304,14 +300,18 @@ cat >expect <<EOF EOF test_expect_success 'status -uall' ' git status -uall >output && - test_cmp expect output + test_i18ncmp expect output ' + test_expect_success 'status (status.showUntrackedFiles all)' ' git config status.showuntrackedfiles all + test_when_finished "git config --unset status.showuntrackedfiles" && git status >output && - rm -rf dir3 && - git config --unset status.showuntrackedfiles && - test_cmp expect output + test_i18ncmp expect output +' + +test_expect_success 'teardown dir3' ' + rm -rf dir3 ' cat >expect <<EOF @@ -362,10 +362,8 @@ cat >expect <<\EOF EOF test_expect_success 'status with relative paths' ' - (cd dir1 && git status) >output && - test_cmp expect output - + test_i18ncmp expect output ' cat >expect <<\EOF @@ -435,20 +433,17 @@ cat >expect <<\EOF EOF test_expect_success 'status with color.ui' ' - git config color.ui always && + test_when_finished "git config --unset color.ui" && git status | test_decode_color >output && - test_cmp expect output - + test_i18ncmp expect output ' test_expect_success 'status with color.status' ' - - git config --unset color.ui && git config color.status always && + test_when_finished "git config --unset color.status" && git status | test_decode_color >output && - test_cmp expect output - + test_i18ncmp expect output ' cat >expect <<\EOF @@ -464,7 +459,6 @@ EOF test_expect_success 'status -s with color.ui' ' - git config --unset color.status && git config color.ui always && git status -s | test_decode_color >output && test_cmp expect output @@ -566,9 +560,10 @@ EOF test_expect_success 'status without relative paths' ' - git config status.relativePaths false + git config status.relativePaths false && + test_when_finished "git config --unset status.relativePaths" && (cd dir1 && git status) >output && - test_cmp expect output + test_i18ncmp expect output ' @@ -585,6 +580,8 @@ EOF test_expect_success 'status -s without relative paths' ' + git config status.relativePaths false && + test_when_finished "git config --unset status.relativePaths" && (cd dir1 && git status -s) >output && test_cmp expect output @@ -608,7 +605,7 @@ cat <<EOF >expect EOF test_expect_success 'dry-run of partial commit excluding new file in index' ' git commit --dry-run dir1/modified >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -657,13 +654,13 @@ cat >expect <<EOF EOF test_expect_success 'status submodule summary is disabled by default' ' git status >output && - test_cmp expect output + test_i18ncmp expect output ' # we expect the same as the previous test test_expect_success 'status --untracked-files=all does not show submodule' ' git status --untracked-files=all >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -722,7 +719,7 @@ EOF test_expect_success 'status submodule summary' ' git config status.submodulesummary 10 && git status >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -760,13 +757,13 @@ cat >expect <<EOF # untracked no changes added to commit (use "git add" and/or "git commit -a") EOF -test_expect_success 'status submodule summary (clean submodule)' ' +test_expect_success 'status submodule summary (clean submodule): commit' ' git commit -m "commit submodule" && git config status.submodulesummary 10 && test_must_fail git commit --dry-run >output && - test_cmp expect output && + test_i18ncmp expect output && git status >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -815,7 +812,7 @@ EOF test_expect_success 'commit --dry-run submodule summary (--amend)' ' git config status.submodulesummary 10 && git commit --dry-run --amend >output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' ' @@ -868,19 +865,19 @@ cat > expect << EOF 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 + echo modified sm/untracked && + git status --ignore-submodules=untracked >output && + test_i18ncmp expect output ' test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' ' git config diff.ignoreSubmodules dirty && git status >output && - test_cmp expect output && + test_i18ncmp expect output && git config --add -f .gitmodules submodule.subname.ignore untracked && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname && git config --unset diff.ignoreSubmodules ' @@ -890,15 +887,15 @@ test_expect_success '.git/config ignore=untracked suppresses submodules with unt git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore untracked && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config --remove-section -f .gitmodules submodule.subname ' test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' ' - git status --ignore-submodules=dirty > output && - test_cmp expect output + git status --ignore-submodules=dirty >output && + test_i18ncmp expect output ' test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' ' @@ -907,8 +904,8 @@ test_expect_success '.gitmodules ignore=dirty suppresses submodules with untrack ! test -s actual && git config --add -f .gitmodules submodule.subname.ignore dirty && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname && git config --unset diff.ignoreSubmodules ' @@ -918,23 +915,23 @@ test_expect_success '.git/config ignore=dirty suppresses submodules with untrack git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore dirty && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' 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 + echo modified >sm/foo && + git status --ignore-submodules=dirty >output && + test_i18ncmp expect output ' test_expect_success '.gitmodules ignore=dirty suppresses submodules with modified content' ' git config --add -f .gitmodules submodule.subname.ignore dirty && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' @@ -943,8 +940,8 @@ test_expect_success '.git/config ignore=dirty suppresses submodules with modifie git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore dirty && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' @@ -983,14 +980,14 @@ EOF test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" ' git status --ignore-submodules=untracked > output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success ".gitmodules ignore=untracked doesn't suppress submodules with modified content" ' git config --add -f .gitmodules submodule.subname.ignore untracked && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' @@ -999,8 +996,8 @@ test_expect_success ".git/config ignore=untracked doesn't suppress submodules wi git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore untracked && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' @@ -1045,14 +1042,14 @@ EOF test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" ' git status --ignore-submodules=untracked > output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success ".gitmodules ignore=untracked doesn't suppress submodule summary" ' git config --add -f .gitmodules submodule.subname.ignore untracked && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' @@ -1061,21 +1058,21 @@ test_expect_success ".git/config ignore=untracked doesn't suppress submodule sum git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore untracked && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" ' git status --ignore-submodules=dirty > output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success ".gitmodules ignore=dirty doesn't suppress submodule summary" ' git config --add -f .gitmodules submodule.subname.ignore dirty && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' @@ -1084,8 +1081,8 @@ test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore dirty && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' @@ -1113,7 +1110,7 @@ EOF test_expect_success "--ignore-submodules=all suppresses submodule summary" ' git status --ignore-submodules=all > output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_failure '.gitmodules ignore=all suppresses submodule summary' ' diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh index 77b6920029..b61fd3c3c4 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit.sh @@ -157,4 +157,33 @@ test_expect_success '--reset-author should be rejected without -c/-C/--amend' ' test_must_fail git commit -a --reset-author -m done ' +test_expect_success 'commit respects CHERRY_PICK_HEAD and MERGE_MSG' ' + echo "cherry-pick 1a" >>foo && + test_tick && + git commit -am "cherry-pick 1" --author="Cherry <cherry@pick.er>" && + git tag cherry-pick-head && + git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD && + echo "This is a MERGE_MSG" >.git/MERGE_MSG && + echo "cherry-pick 1b" >>foo && + test_tick && + git commit -a && + author_header cherry-pick-head >expect && + author_header HEAD >actual && + test_cmp expect actual && + + echo "This is a MERGE_MSG" >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' + git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD && + echo "cherry-pick 2" >>foo && + test_tick && + git commit -am "cherry-pick 2" --reset-author && + echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect && + author_header HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index b147a1bd69..87aac835a1 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -28,80 +28,80 @@ Testing basic merge operations/option parsing. . ./test-lib.sh -test_expect_success 'set up test data and helpers' ' - printf "%s\n" 1 2 3 4 5 6 7 8 9 >file && - printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >file.1 && - printf "%s\n" 1 2 3 4 "5 X" 6 7 8 9 >file.5 && - printf "%s\n" 1 2 3 4 5 6 7 8 "9 X" >file.9 && - printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >result.1 && - printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 9 >result.1-5 && - printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 "9 X" >result.1-5-9 && - - create_merge_msgs() { - echo "Merge commit '\''c2'\''" >msg.1-5 && - echo "Merge commit '\''c2'\''; commit '\''c3'\''" >msg.1-5-9 && - { - echo "Squashed commit of the following:" && - echo && - git log --no-merges ^HEAD c1 - } >squash.1 && - { - echo "Squashed commit of the following:" && - echo && - git log --no-merges ^HEAD c2 - } >squash.1-5 && - { - echo "Squashed commit of the following:" && - echo && - git log --no-merges ^HEAD c2 c3 - } >squash.1-5-9 && - echo >msg.nolog && - { - echo "* commit '\''c3'\'':" && - echo " commit 3" && - echo - } >msg.log - } && - - verify_merge() { - test_cmp "$2" "$1" && - git update-index --refresh && - git diff --exit-code && - if test -n "$3" - then - git show -s --pretty=format:%s HEAD >msg.act && - test_cmp "$3" msg.act - fi - } && - - verify_head() { - echo "$1" >head.expected && - git rev-parse HEAD >head.actual && - test_cmp head.expected head.actual - } && - - verify_parents() { - printf "%s\n" "$@" >parents.expected && - >parents.actual && - i=1 && - while test $i -le $# - do - git rev-parse HEAD^$i >>parents.actual && - i=$(expr $i + 1) || - return 1 - done && - test_cmp parents.expected parents.actual - } && - - verify_mergeheads() { - printf "%s\n" "$@" >mergehead.expected && - test_cmp mergehead.expected .git/MERGE_HEAD - } && - - verify_no_mergehead() { - ! test -e .git/MERGE_HEAD - } -' +printf '%s\n' 1 2 3 4 5 6 7 8 9 >file +printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1 +printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5 +printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9 +printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1 +printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5 +printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9 +>empty + +create_merge_msgs () { + echo "Merge commit 'c2'" >msg.1-5 && + echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c1 + } >squash.1 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c2 + } >squash.1-5 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c2 c3 + } >squash.1-5-9 && + echo >msg.nolog && + { + echo "* commit 'c3':" && + echo " commit 3" && + echo + } >msg.log +} + +verify_merge () { + test_cmp "$2" "$1" && + git update-index --refresh && + git diff --exit-code && + if test -n "$3" + then + git show -s --pretty=format:%s HEAD >msg.act && + test_cmp "$3" msg.act + fi +} + +verify_head () { + echo "$1" >head.expected && + git rev-parse HEAD >head.actual && + test_cmp head.expected head.actual +} + +verify_parents () { + printf '%s\n' "$@" >parents.expected && + >parents.actual && + i=1 && + while test $i -le $# + do + git rev-parse HEAD^$i >>parents.actual && + i=$(expr $i + 1) || + return 1 + done && + test_must_fail git rev-parse --verify "HEAD^$i" && + test_cmp parents.expected parents.actual +} + +verify_mergeheads () { + printf '%s\n' "$@" >mergehead.expected && + test_cmp mergehead.expected .git/MERGE_HEAD +} + +verify_no_mergehead () { + ! test -e .git/MERGE_HEAD +} test_expect_success 'setup' ' git add file && @@ -225,12 +225,28 @@ test_expect_success 'merge c1 with c2 and c3' ' test_debug 'git log --graph --decorate --oneline --all' -test_expect_success 'failing merges with --ff-only' ' +test_expect_success 'merges with --ff-only' ' git reset --hard c1 && test_tick && test_must_fail git merge --ff-only c2 && test_must_fail git merge --ff-only c3 && - test_must_fail git merge --ff-only c2 c3 + test_must_fail git merge --ff-only c2 c3 && + git reset --hard c0 && + git merge c3 && + verify_head $c3 +' + +test_expect_success 'merges with merge.ff=only' ' + git reset --hard c1 && + test_tick && + test_when_finished "git config --unset merge.ff" && + git config merge.ff only && + test_must_fail git merge c2 && + test_must_fail git merge c3 && + test_must_fail git merge c2 c3 && + git reset --hard c0 && + git merge c3 && + verify_head $c3 ' test_expect_success 'merge c0 with c1 (no-commit)' ' @@ -324,6 +340,39 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' ' test_debug 'git log --graph --decorate --oneline --all' +test_expect_success 'merge c1 with c2 (log in config)' ' + git config branch.master.mergeoptions "" && + git reset --hard c1 && + git merge --log c2 && + git show -s --pretty=tformat:%s%n%b >expect && + + git config branch.master.mergeoptions --log && + git reset --hard c1 && + git merge c2 && + git show -s --pretty=tformat:%s%n%b >actual && + + test_cmp expect actual +' + +test_expect_success 'merge c1 with c2 (log in config gets overridden)' ' + test_when_finished "git config --remove-section branch.master" && + test_when_finished "git config --remove-section merge" && + test_might_fail git config --remove-section branch.master && + test_might_fail git config --remove-section merge && + + git reset --hard c1 && + git merge c2 && + git show -s --pretty=tformat:%s%n%b >expect && + + git config branch.master.mergeoptions "--no-log" && + git config merge.log true && + git reset --hard c1 && + git merge c2 && + git show -s --pretty=tformat:%s%n%b >actual && + + test_cmp expect actual +' + test_expect_success 'merge c1 with c2 (squash in config)' ' git reset --hard c1 && git config branch.master.mergeoptions "--squash" && @@ -415,7 +464,41 @@ test_expect_success 'merge c0 with c1 (no-ff)' ' test_debug 'git log --graph --decorate --oneline --all' +test_expect_success 'merge c0 with c1 (merge.ff=false)' ' + git reset --hard c0 && + git config merge.ff false && + test_tick && + git merge c1 && + git config --remove-section merge && + verify_merge file result.1 && + verify_parents $c0 $c1 +' +test_debug 'git log --graph --decorate --oneline --all' + +test_expect_success 'combine branch.master.mergeoptions with merge.ff' ' + git reset --hard c0 && + git config branch.master.mergeoptions --ff && + git config merge.ff false && + test_tick && + git merge c1 && + git config --remove-section "branch.master" && + git config --remove-section "merge" && + verify_merge file result.1 && + verify_parents "$c0" +' + +test_expect_success 'tolerate unknown values for merge.ff' ' + git reset --hard c0 && + git config merge.ff something-new && + test_tick && + git merge c1 2>message && + git config --remove-section "merge" && + verify_head "$c1" && + test_cmp empty message +' + test_expect_success 'combining --squash and --no-ff is refused' ' + git reset --hard c0 && test_must_fail git merge --squash --no-ff c1 && test_must_fail git merge --no-ff --squash c1 ' @@ -498,7 +581,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'in-index merge' ' git reset --hard c0 && git merge --no-ff -s resolve c1 >out && - grep "Wonderful." out && + test_i18ngrep "Wonderful." out && verify_parents $c0 $c1 ' diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 4d5ce4e682..72a8731d5e 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -122,7 +122,7 @@ test_expect_success 'will not overwrite untracked file in leading path' ' rm -f sub sub2 ' -test_expect_failure SYMLINKS 'will not overwrite untracked symlink in leading path' ' +test_expect_success SYMLINKS 'will not overwrite untracked symlink in leading path' ' git reset --hard c0 && rm -rf sub && mkdir sub2 && @@ -151,9 +151,33 @@ test_expect_success 'will not overwrite untracked file on unborn branch' ' git checkout --orphan new && cp important c0.c && test_must_fail git merge c0 2>out && - test_cmp out expect && + test_i18ncmp out expect +' + +test_expect_success 'will not overwrite untracked file on unborn branch .git/MERGE_HEAD sanity etc.' ' + test_when_finished "rm c0.c" && test_path_is_missing .git/MERGE_HEAD && test_cmp important c0.c ' +test_expect_success 'failed merge leaves unborn branch in the womb' ' + test_must_fail git rev-parse --verify HEAD +' + +test_expect_success 'set up unborn branch and content' ' + git symbolic-ref HEAD refs/heads/unborn && + rm -f .git/index && + echo foo > tracked-file && + git add tracked-file && + echo bar > untracked-file +' + +test_expect_success 'will not clobber WT/index when merging into unborn' ' + git merge master && + grep foo tracked-file && + git show :tracked-file >expect && + grep foo expect && + grep bar untracked-file +' + test_done diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index d78bdec330..cbc08e3276 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -16,23 +16,57 @@ Testing basic merge tool invocation' test_expect_success 'setup' ' git config rerere.enabled true && echo master >file1 && + echo master file11 >file11 && + echo master file12 >file12 && + echo master file13 >file13 && + echo master file14 >file14 && mkdir subdir && echo master sub >subdir/file3 && - git add file1 subdir/file3 && - git commit -m "added file1" && + test_create_repo submod && + ( + cd submod && + : >foo && + git add foo && + git commit -m "Add foo" + ) && + git submodule add git://example.com/submod submod && + git add file1 file1[1-4] subdir/file3 .gitmodules submod && + git commit -m "add initial versions" && git checkout -b branch1 master && + git submodule update -N && echo branch1 change >file1 && echo branch1 newfile >file2 && + echo branch1 change file11 >file11 && + echo branch1 change file13 >file13 && echo branch1 sub >subdir/file3 && - git add file1 file2 subdir/file3 && + ( + cd submod && + echo branch1 submodule >bar && + git add bar && + git commit -m "Add bar on branch1" && + git checkout -b submod-branch1 + ) && + git add file1 file11 file13 file2 subdir/file3 submod && + git rm file12 && git commit -m "branch1 changes" && git checkout master && + git submodule update -N && echo master updated >file1 && echo master new >file2 && + echo master updated file12 >file12 && + echo master updated file14 >file14 && echo master new sub >subdir/file3 && - git add file1 file2 subdir/file3 && + ( + cd submod && + echo master submodule >bar && + git add bar && + git commit -m "Add bar on master" && + git checkout -b submod-master + ) && + git add file1 file12 file14 file2 subdir/file3 submod && + git rm file11 && git commit -m "master updates" && git config merge.tool mytool && @@ -42,13 +76,18 @@ test_expect_success 'setup' ' test_expect_success 'custom mergetool' ' git checkout -b test1 branch1 && + git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && test "$(cat file1)" = "master updated" && test "$(cat file2)" = "master new" && test "$(cat subdir/file3)" = "master new sub" && + test "$(cat submod/bar)" = "branch1 submodule" && git commit -m "branch1 resolved with mergetool" ' @@ -59,9 +98,14 @@ test_expect_success 'mergetool crlf' ' ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod >/dev/null 2>&1 ) && test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" && test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" && test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && git commit -m "branch1 resolved with mergetool - autocrlf" && git config core.autocrlf false && git reset --hard @@ -69,6 +113,7 @@ test_expect_success 'mergetool crlf' ' test_expect_success 'mergetool in subdir' ' git checkout -b test3 branch1 && + git submodule update -N && ( cd subdir && test_must_fail git merge master >/dev/null 2>&1 && @@ -82,16 +127,24 @@ test_expect_success 'mergetool on file in parent dir' ' cd subdir && ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) && test "$(cat ../file1)" = "master updated" && test "$(cat ../file2)" = "master new" && + test "$(cat ../submod/bar)" = "branch1 submodule" && git commit -m "branch1 resolved with mergetool - subdir" ) ' test_expect_success 'mergetool skips autoresolved' ' git checkout -b test4 branch1 && + git submodule update -N && test_must_fail git merge master && test -n "$(git ls-files -u)" && + ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && output="$(git mergetool --no-prompt)" && test "$output" = "No files need merging" && git reset --hard @@ -102,13 +155,272 @@ test_expect_success 'mergetool merges all from subdir' ' cd subdir && git config rerere.enabled false && test_must_fail git merge master && - git mergetool --no-prompt && + ( yes "r" | git mergetool ../submod ) && + ( yes "d" "d" | git mergetool --no-prompt ) && test "$(cat ../file1)" = "master updated" && test "$(cat ../file2)" = "master new" && test "$(cat file3)" = "master new sub" && - git add ../file1 ../file2 file3 && + ( cd .. && git submodule update -N ) && + test "$(cat ../submod/bar)" = "master submodule" && git commit -m "branch2 resolved by mergetool from subdir" ) ' +test_expect_success 'mergetool skips resolved paths when rerere is active' ' + git config rerere.enabled true && + rm -rf .git/rr-cache && + git checkout -b test5 branch1 + git submodule update -N && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "l" | git mergetool --no-prompt submod >/dev/null 2>&1 ) && + ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) && + git submodule update -N && + output="$(yes "n" | git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git reset --hard +' + +test_expect_success 'deleted vs modified submodule' ' + git checkout -b test6 branch1 && + git submodule update -N && + mv submod submod-movedaside && + git rm submod && + git commit -m "Submodule deleted from branch" && + git checkout -b test6.a test6 && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod ) && + rmdir submod && mv submod-movedaside submod && + test "$(cat submod/bar)" = "branch1 submodule" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping module" && + + mv submod submod-movedaside && + git checkout -b test6.b test6 && + git submodule update -N && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod ) && + test ! -e submod && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by deleting module" && + + mv submod-movedaside submod && + git checkout -b test6.c master && + git submodule update -N && + test_must_fail git merge test6 && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod ) && + test ! -e submod && + test -d submod.orig && + git submodule update -N && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by deleting module" && + mv submod.orig submod && + + git checkout -b test6.d master && + git submodule update -N && + test_must_fail git merge test6 && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod ) && + test "$(cat submod/bar)" = "master submodule" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping module" && + git reset --hard HEAD +' + +test_expect_success 'file vs modified submodule' ' + git checkout -b test7 branch1 && + git submodule update -N && + mv submod submod-movedaside && + git rm submod && + echo not a submodule >submod && + git add submod && + git commit -m "Submodule path becomes file" && + git checkout -b test7.a branch1 && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod ) && + rmdir submod && mv submod-movedaside submod && + test "$(cat submod/bar)" = "branch1 submodule" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping module" && + + mv submod submod-movedaside && + git checkout -b test7.b test7 && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod ) && + git submodule update -N && + test "$(cat submod)" = "not a submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping file" && + + git checkout -b test7.c master && + rmdir submod && mv submod-movedaside submod && + test ! -e submod.orig && + git submodule update -N && + test_must_fail git merge test7 && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod ) && + test -d submod.orig && + git submodule update -N && + test "$(cat submod)" = "not a submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping file" && + + git checkout -b test7.d master && + rmdir submod && mv submod.orig submod && + git submodule update -N && + test_must_fail git merge test7 && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod ) && + test "$(cat submod/bar)" = "master submodule" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping module" +' + +test_expect_success 'submodule in subdirectory' ' + git checkout -b test10 branch1 && + git submodule update -N && + ( + cd subdir && + test_create_repo subdir_module && + ( + cd subdir_module && + : >file15 && + git add file15 && + git commit -m "add initial versions" + ) + ) && + git submodule add git://example.com/subsubmodule subdir/subdir_module && + git add subdir/subdir_module && + git commit -m "add submodule in subdirectory" && + + git checkout -b test10.a test10 && + git submodule update -N && + ( + cd subdir/subdir_module && + git checkout -b super10.a && + echo test10.a >file15 && + git add file15 && + git commit -m "on branch 10.a" + ) && + git add subdir/subdir_module && + git commit -m "change submodule in subdirectory on test10.a" && + + git checkout -b test10.b test10 && + git submodule update -N && + ( + cd subdir/subdir_module && + git checkout -b super10.b && + echo test10.b >file15 && + git add file15 && + git commit -m "on branch 10.b" + ) && + git add subdir/subdir_module && + git commit -m "change submodule in subdirectory on test10.b" && + + test_must_fail git merge test10.a >/dev/null 2>&1 && + ( + cd subdir && + ( yes "l" | git mergetool subdir_module ) + ) && + test "$(cat subdir/subdir_module/file15)" = "test10.b" && + git submodule update -N && + test "$(cat subdir/subdir_module/file15)" = "test10.b" && + git reset --hard && + git submodule update -N && + + test_must_fail git merge test10.a >/dev/null 2>&1 && + ( yes "r" | git mergetool subdir/subdir_module ) && + test "$(cat subdir/subdir_module/file15)" = "test10.b" && + git submodule update -N && + test "$(cat subdir/subdir_module/file15)" = "test10.a" && + git commit -m "branch1 resolved with mergetool" && + rm -rf subdir/subdir_module +' + +test_expect_success 'directory vs modified submodule' ' + git checkout -b test11 branch1 && + mv submod submod-movedaside && + git rm submod && + mkdir submod && + echo not a submodule >submod/file16 && + git add submod/file16 && + git commit -m "Submodule path becomes directory" && + + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "l" | git mergetool submod ) && + test "$(cat submod/file16)" = "not a submodule" && + rm -rf submod.orig && + + git reset --hard && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + test ! -e submod.orig && + ( yes "r" | git mergetool submod ) && + test -d submod.orig && + test "$(cat submod.orig/file16)" = "not a submodule" && + rm -r submod.orig && + mv submod-movedaside/.git submod && + ( cd submod && git clean -f && git reset --hard ) && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + git reset --hard && rm -rf submod-movedaside && + + git checkout -b test11.c master && + git submodule update -N && + test_must_fail git merge test11 && + test -n "$(git ls-files -u)" && + ( yes "l" | git mergetool submod ) && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + + git reset --hard && + git submodule update -N && + test_must_fail git merge test11 && + test -n "$(git ls-files -u)" && + test ! -e submod.orig && + ( yes "r" | git mergetool submod ) && + test "$(cat submod/file16)" = "not a submodule" && + + git reset --hard master && + ( cd submod && git clean -f && git reset --hard ) && + git submodule update -N +' + test_done diff --git a/t/t7611-merge-abort.sh b/t/t7611-merge-abort.sh index 61890bc892..7b4798e8e4 100755 --- a/t/t7611-merge-abort.sh +++ b/t/t7611-merge-abort.sh @@ -47,7 +47,10 @@ pre_merge_head="$(git rev-parse HEAD)" test_expect_success 'fails without MERGE_HEAD (unstarted merge)' ' test_must_fail git merge --abort 2>output && - grep -q MERGE_HEAD output && + test_i18ngrep MERGE_HEAD output +' + +test_expect_success 'fails without MERGE_HEAD (unstarted merge): .git/MERGE_HEAD sanity' ' test ! -f .git/MERGE_HEAD && test "$pre_merge_head" = "$(git rev-parse HEAD)" ' @@ -58,7 +61,10 @@ test_expect_success 'fails without MERGE_HEAD (completed merge)' ' # Merge successfully completed post_merge_head="$(git rev-parse HEAD)" && test_must_fail git merge --abort 2>output && - grep -q MERGE_HEAD output && + test_i18ngrep MERGE_HEAD output +' + +test_expect_success 'fails without MERGE_HEAD (completed merge): .git/MERGE_HEAD sanity' ' test ! -f .git/MERGE_HEAD && test "$post_merge_head" = "$(git rev-parse HEAD)" ' diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index c8777589ca..69bd576d1c 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -26,6 +26,17 @@ test_expect_success setup ' echo foo mmap bar_mmap echo foo_mmap bar mmap baz } >file && + { + echo Hello world + echo HeLLo world + echo Hello_world + echo HeLLo_world + } >hello_world && + { + echo "a+b*c" + echo "a+bc" + echo "abc" + } >ab && echo vvv >v && echo ww w >w && echo x x xx x >x && @@ -59,7 +70,29 @@ do echo ${HC}file:4:foo mmap bar_mmap echo ${HC}file:5:foo_mmap bar mmap baz } >expected && - git grep -n -w -e mmap $H >actual && + git -c grep.linenumber=false grep -n -w -e mmap $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L" ' + { + echo ${HC}file:1:foo mmap bar + echo ${HC}file:3:foo_mmap bar mmap + echo ${HC}file:4:foo mmap bar_mmap + echo ${HC}file:5:foo_mmap bar mmap baz + } >expected && + git -c grep.linenumber=true grep -w -e mmap $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L" ' + { + echo ${HC}file:foo mmap bar + echo ${HC}file:foo_mmap bar mmap + echo ${HC}file:foo mmap bar_mmap + echo ${HC}file:foo_mmap bar mmap baz + } >expected && + git -c grep.linenumber=true grep --no-line-number -w -e mmap $H >actual && test_cmp expected actual ' @@ -182,6 +215,34 @@ do test_cmp expected actual ' + test_expect_success "grep --max-depth 0 -- . t $L" ' + { + echo ${HC}t/v:1:vvv + echo ${HC}v:1:vvv + } >expected && + git grep --max-depth 0 -n -e vvv $H -- . t >actual && + test_cmp expected actual + ' + + test_expect_success "grep --max-depth 0 -- t . $L" ' + { + echo ${HC}t/v:1:vvv + echo ${HC}v:1:vvv + } >expected && + git grep --max-depth 0 -n -e vvv $H -- t . >actual && + test_cmp expected actual + ' + test_expect_success "grep $L with grep.extendedRegexp=false" ' + echo "ab:a+bc" >expected && + git -c grep.extendedRegexp=false grep "a+b*c" ab >actual && + test_cmp expected actual + ' + + test_expect_success "grep $L with grep.extendedRegexp=true" ' + echo "ab:abc" >expected && + git -c grep.extendedRegexp=true grep "a+b*c" ab >actual && + test_cmp expected actual + ' done cat >expected <<EOF @@ -285,6 +346,11 @@ test_expect_success 'grep -f, ignore empty lines' ' test_cmp expected actual ' +test_expect_success 'grep -f, ignore empty lines, read patterns from stdin' ' + git grep -f - <patterns >actual && + test_cmp expected actual +' + cat >expected <<EOF y:y yy -- @@ -554,4 +620,100 @@ test_expect_success 'grep -e -- -- path' ' test_cmp expected actual ' +cat >expected <<EOF +hello.c:int main(int argc, const char **argv) +hello.c: printf("Hello world.\n"); +EOF + +test_expect_success LIBPCRE 'grep --perl-regexp pattern' ' + git grep --perl-regexp "\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp expected actual +' + +test_expect_success LIBPCRE 'grep -P pattern' ' + git grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp expected actual +' + +test_expect_success 'grep pattern with grep.extendedRegexp=true' ' + >empty && + test_must_fail git -c grep.extendedregexp=true \ + grep "\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp empty actual +' + +test_expect_success LIBPCRE 'grep -P pattern with grep.extendedRegexp=true' ' + git -c grep.extendedregexp=true \ + grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp expected actual +' + +test_expect_success LIBPCRE 'grep -P -v pattern' ' + { + echo "ab:a+b*c" + echo "ab:a+bc" + } >expected && + git grep -P -v "abc" ab >actual && + test_cmp expected actual +' + +test_expect_success LIBPCRE 'grep -P -i pattern' ' + { + echo "hello.c: printf(\"Hello world.\n\");" + } >expected && + git grep -P -i "PRINTF\([^\d]+\)" hello.c >actual && + test_cmp expected actual +' + +test_expect_success LIBPCRE 'grep -P -w pattern' ' + { + echo "hello_world:Hello world" + echo "hello_world:HeLLo world" + } >expected && + git grep -P -w "He((?i)ll)o" hello_world >actual && + test_cmp expected actual +' + +test_expect_success 'grep -G invalidpattern properly dies ' ' + test_must_fail git grep -G "a[" +' + +test_expect_success 'grep -E invalidpattern properly dies ' ' + test_must_fail git grep -E "a[" +' + +test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' ' + test_must_fail git grep -P "a[" +' + +test_expect_success 'grep -G -E -F pattern' ' + echo "ab:a+b*c" >expected && + git grep -G -E -F "a+b*c" ab >actual && + test_cmp expected actual +' + +test_expect_success 'grep -E -F -G pattern' ' + echo "ab:a+bc" >expected && + git grep -E -F -G "a+b*c" ab >actual && + test_cmp expected actual +' + +test_expect_success 'grep -F -G -E pattern' ' + echo "ab:abc" >expected && + git grep -F -G -E "a+b*c" ab >actual && + test_cmp expected actual +' + +test_expect_success 'grep -G -F -P -E pattern' ' + >empty && + test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual && + test_cmp empty actual +' + +test_expect_success LIBPCRE 'grep -G -F -E -P pattern' ' + echo "ab:a+b*c" >expected && + git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual && + test_cmp expected actual +' + test_done diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh index 568a6f2b69..a8957782cf 100755 --- a/t/t7811-grep-open.sh +++ b/t/t7811-grep-open.sh @@ -63,7 +63,7 @@ test_expect_success SIMPLEPAGER 'git grep -O' ' 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_i18ngrep open-files-in-pager msg ' test_expect_success 'git grep -O --no-index' ' diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh index 45cb60ea4b..41962f04a7 100755 --- a/t/t8001-annotate.sh +++ b/t/t8001-annotate.sh @@ -6,10 +6,11 @@ test_description='git annotate' PROG='git annotate' . "$TEST_DIRECTORY"/annotate-tests.sh -test_expect_success \ - 'Annotating an old revision works' \ - '[ $(git annotate file master | awk "{print \$3}" | grep -c "^A$") -eq 2 ] && \ - [ $(git annotate file master | awk "{print \$3}" | grep -c "^B$") -eq 2 ]' - +test_expect_success 'Annotating an old revision works' ' + git annotate file master >result && + awk "{ print \$3; }" <result >authors && + test 2 = $(grep A <authors | wc -l) && + test 2 = $(grep B <authors | wc -l) +' test_done diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index d3a51e1269..e2896cffc1 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -8,7 +8,7 @@ PROG='git blame -c' PROG='git blame -c -e' test_expect_success 'Blame --show-email works' ' - check_count "<A@test.git>" 1 "<B@test.git>" 1 "<B1@test.git>" 1 "<B2@test.git>" 1 "<author@example.com>" 1 "<C@test.git>" 1 "<D@test.git>" 1 + check_count "<A@test.git>" 1 "<B@test.git>" 1 "<B1@test.git>" 1 "<B2@test.git>" 1 "<author@example.com>" 1 "<C@test.git>" 1 "<D@test.git>" 1 "<E at test dot git>" 1 ' test_done diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh index ea64cd8d0f..32ec82ad67 100755 --- a/t/t8006-blame-textconv.sh +++ b/t/t8006-blame-textconv.sh @@ -25,7 +25,8 @@ test_expect_success 'setup ' ' echo "bin: test 1 version 2" >one.bin && echo "bin: test number 2 version 2" >>two.bin && if test_have_prereq SYMLINKS; then - ln -sf two.bin symlink.bin + rm symlink.bin && + ln -s two.bin symlink.bin fi && GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" ' diff --git a/t/t8008-blame-formats.sh b/t/t8008-blame-formats.sh new file mode 100755 index 0000000000..d15f8b3d47 --- /dev/null +++ b/t/t8008-blame-formats.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +test_description='blame output in various formats on a simple case' +. ./test-lib.sh + +test_expect_success 'setup' ' + echo a >file && + git add file + test_tick && + git commit -m one && + echo b >>file && + echo c >>file && + echo d >>file && + test_tick && + git commit -a -m two +' + +cat >expect <<'EOF' +^baf5e0b (A U Thor 2005-04-07 15:13:13 -0700 1) a +8825379d (A U Thor 2005-04-07 15:14:13 -0700 2) b +8825379d (A U Thor 2005-04-07 15:14:13 -0700 3) c +8825379d (A U Thor 2005-04-07 15:14:13 -0700 4) d +EOF +test_expect_success 'normal blame output' ' + git blame file >actual && + test_cmp expect actual +' + +ID1=baf5e0b3869e0b2b2beb395a3720c7b51eac94fc +COMMIT1='author A U Thor +author-mail <author@example.com> +author-time 1112911993 +author-tz -0700 +committer C O Mitter +committer-mail <committer@example.com> +committer-time 1112911993 +committer-tz -0700 +summary one +boundary +filename file' +ID2=8825379dfb8a1267b58e8e5bcf69eec838f685ec +COMMIT2='author A U Thor +author-mail <author@example.com> +author-time 1112912053 +author-tz -0700 +committer C O Mitter +committer-mail <committer@example.com> +committer-time 1112912053 +committer-tz -0700 +summary two +previous baf5e0b3869e0b2b2beb395a3720c7b51eac94fc file +filename file' + +cat >expect <<EOF +$ID1 1 1 1 +$COMMIT1 + a +$ID2 2 2 3 +$COMMIT2 + b +$ID2 3 3 + c +$ID2 4 4 + d +EOF +test_expect_success 'blame --porcelain output' ' + git blame --porcelain file >actual && + test_cmp expect actual +' + +cat >expect <<EOF +$ID1 1 1 1 +$COMMIT1 + a +$ID2 2 2 3 +$COMMIT2 + b +$ID2 3 3 +$COMMIT2 + c +$ID2 4 4 +$COMMIT2 + d +EOF +test_expect_success 'blame --line-porcelain output' ' + git blame --line-porcelain file >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh index 88a9751dd3..6f6175a8f7 100755 --- a/t/t9010-svn-fe.sh +++ b/t/t9010-svn-fe.sh @@ -9,6 +9,30 @@ reinit_git () { git init } +properties () { + while test "$#" -ne 0 + do + property="$1" && + value="$2" && + printf "%s\n" "K ${#property}" && + printf "%s\n" "$property" && + printf "%s\n" "V ${#value}" && + printf "%s\n" "$value" && + shift 2 || + return 1 + done +} + +text_no_props () { + text="$1 +" && + printf "%s\n" "Prop-content-length: 10" && + printf "%s\n" "Text-content-length: ${#text}" && + printf "%s\n" "Content-length: $((${#text} + 10))" && + printf "%s\n" "" "PROPS-END" && + printf "%s\n" "$text" +} + >empty test_expect_success 'empty dump' ' @@ -18,13 +42,794 @@ test_expect_success 'empty dump' ' git fast-import <stream ' -test_expect_success 'v3 dumps not supported' ' +test_expect_success 'v4 dumps not supported' ' reinit_git && - echo "SVN-fs-dump-format-version: 3" >input && - test_must_fail test-svn-fe input >stream && + echo "SVN-fs-dump-format-version: 4" >v4.dump && + test_must_fail test-svn-fe v4.dump >stream && test_cmp empty stream ' +test_expect_failure 'empty revision' ' + reinit_git && + printf "rev <nobody, nobody@local>: %s\n" "" "" >expect && + cat >emptyrev.dump <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 0 + Content-length: 0 + + Revision-number: 2 + Prop-content-length: 0 + Content-length: 0 + + EOF + test-svn-fe emptyrev.dump >stream && + git fast-import <stream && + git log -p --format="rev <%an, %ae>: %s" HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'empty properties' ' + reinit_git && + printf "rev <nobody, nobody@local>: %s\n" "" "" >expect && + cat >emptyprop.dump <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Revision-number: 2 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + EOF + test-svn-fe emptyprop.dump >stream && + git fast-import <stream && + git log -p --format="rev <%an, %ae>: %s" HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'author name and commit message' ' + reinit_git && + echo "<author@example.com, author@example.com@local>" >expect.author && + cat >message <<-\EOF && + A concise summary of the change + + A detailed description of the change, why it is needed, what + was broken and why applying this is the best course of action. + + * file.c + Details pertaining to an individual file. + EOF + { + properties \ + svn:author author@example.com \ + svn:log "$(cat message)" && + echo PROPS-END + } >props && + { + echo "SVN-fs-dump-format-version: 3" && + echo && + echo "Revision-number: 1" && + echo Prop-content-length: $(wc -c <props) && + echo Content-length: $(wc -c <props) && + echo && + cat props + } >log.dump && + test-svn-fe log.dump >stream && + git fast-import <stream && + git log -p --format="%B" HEAD >actual.log && + git log --format="<%an, %ae>" >actual.author && + test_cmp message actual.log && + test_cmp expect.author actual.author +' + +test_expect_success 'unsupported properties are ignored' ' + reinit_git && + echo author >expect && + cat >extraprop.dump <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 56 + Content-length: 56 + + K 8 + nonsense + V 1 + y + K 10 + svn:author + V 6 + author + PROPS-END + EOF + test-svn-fe extraprop.dump >stream && + git fast-import <stream && + git log -p --format=%an HEAD >actual && + test_cmp expect actual +' + +test_expect_failure 'timestamp and empty file' ' + echo author@example.com >expect.author && + echo 1999-01-01 >expect.date && + echo file >expect.files && + reinit_git && + { + properties \ + svn:author author@example.com \ + svn:date "1999-01-01T00:01:002.000000Z" \ + svn:log "add empty file" && + echo PROPS-END + } >props && + { + cat <<-EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + EOF + echo Prop-content-length: $(wc -c <props) && + echo Content-length: $(wc -c <props) && + echo && + cat props && + cat <<-\EOF + + Node-path: empty-file + Node-kind: file + Node-action: add + Content-length: 0 + + EOF + } >emptyfile.dump && + test-svn-fe emptyfile.dump >stream && + git fast-import <stream && + git log --format=%an HEAD >actual.author && + git log --date=short --format=%ad HEAD >actual.date && + git ls-tree -r --name-only HEAD >actual.files && + test_cmp expect.author actual.author && + test_cmp expect.date actual.date && + test_cmp expect.files actual.files && + git checkout HEAD empty-file && + test_cmp empty file +' + +test_expect_success 'directory with files' ' + reinit_git && + printf "%s\n" directory/file1 directory/file2 >expect.files && + echo hi >hi && + echo hello >hello && + { + properties \ + svn:author author@example.com \ + svn:date "1999-02-01T00:01:002.000000Z" \ + svn:log "add directory with some files in it" && + echo PROPS-END + } >props && + { + cat <<-EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + EOF + echo Prop-content-length: $(wc -c <props) && + echo Content-length: $(wc -c <props) && + echo && + cat props && + cat <<-\EOF && + + Node-path: directory + Node-kind: dir + Node-action: add + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: directory/file1 + Node-kind: file + Node-action: add + EOF + text_no_props hello && + cat <<-\EOF && + Node-path: directory/file2 + Node-kind: file + Node-action: add + EOF + text_no_props hi + } >directory.dump && + test-svn-fe directory.dump >stream && + git fast-import <stream && + + git ls-tree -r --name-only HEAD >actual.files && + git checkout HEAD directory && + test_cmp expect.files actual.files && + test_cmp hello directory/file1 && + test_cmp hi directory/file2 +' + +test_expect_success 'node without action' ' + cat >inaction.dump <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: directory + Node-kind: dir + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + EOF + test_must_fail test-svn-fe inaction.dump +' + +test_expect_success 'action: add node without text' ' + cat >textless.dump <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: textless + Node-kind: file + Node-action: add + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + EOF + test_must_fail test-svn-fe textless.dump +' + +test_expect_failure 'change file mode but keep old content' ' + reinit_git && + cat >expect <<-\EOF && + OBJID + :120000 100644 OBJID OBJID T greeting + OBJID + :100644 120000 OBJID OBJID T greeting + OBJID + :000000 100644 OBJID OBJID A greeting + EOF + echo "link hello" >expect.blob && + echo hello >hello && + cat >filemode.dump <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: add + Prop-content-length: 10 + Text-content-length: 11 + Content-length: 21 + + PROPS-END + link hello + + Revision-number: 2 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: change + Prop-content-length: 33 + Content-length: 33 + + K 11 + svn:special + V 1 + * + PROPS-END + + Revision-number: 3 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: change + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + EOF + test-svn-fe filemode.dump >stream && + git fast-import <stream && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + git show HEAD:greeting >actual.blob && + git show HEAD^:greeting >actual.target && + test_cmp expect actual && + test_cmp expect.blob actual.blob && + test_cmp hello actual.target +' + +test_expect_success 'NUL in property value' ' + reinit_git && + echo "commit message" >expect.message && + { + properties \ + unimportant "something with a NUL (Q)" \ + svn:log "commit message"&& + echo PROPS-END + } | + q_to_nul >props && + { + cat <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + EOF + echo Prop-content-length: $(wc -c <props) && + echo Content-length: $(wc -c <props) && + echo && + cat props + } >nulprop.dump && + test-svn-fe nulprop.dump >stream && + git fast-import <stream && + git diff-tree --always -s --format=%s HEAD >actual.message && + test_cmp expect.message actual.message +' + +test_expect_success 'NUL in log message, file content, and property name' ' + # Caveat: svnadmin 1.6.16 (r1073529) truncates at \0 in the + # svn:specialQnotreally example. + reinit_git && + cat >expect <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M greeting + OBJID + :000000 100644 OBJID OBJID A greeting + EOF + printf "\n%s\n" "something with an ASCII NUL (Q)" >expect.message && + printf "%s\n" "helQo" >expect.hello1 && + printf "%s\n" "link hello" >expect.hello2 && + { + properties svn:log "something with an ASCII NUL (Q)" && + echo PROPS-END + } | + q_to_nul >props && + { + q_to_nul <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: add + Prop-content-length: 10 + Text-content-length: 6 + Content-length: 16 + + PROPS-END + helQo + + Revision-number: 2 + EOF + echo Prop-content-length: $(wc -c <props) && + echo Content-length: $(wc -c <props) && + echo && + cat props && + q_to_nul <<-\EOF + + Node-path: greeting + Node-kind: file + Node-action: change + Prop-content-length: 43 + Text-content-length: 11 + Content-length: 54 + + K 21 + svn:specialQnotreally + V 1 + * + PROPS-END + link hello + EOF + } >8bitclean.dump && + test-svn-fe 8bitclean.dump >stream && + git fast-import <stream && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + { + git cat-file commit HEAD | nul_to_q && + echo + } | + sed -ne "/^\$/,\$ p" >actual.message && + git cat-file blob HEAD^:greeting | nul_to_q >actual.hello1 && + git cat-file blob HEAD:greeting | nul_to_q >actual.hello2 && + test_cmp expect actual && + test_cmp expect.message actual.message && + test_cmp expect.hello1 actual.hello1 && + test_cmp expect.hello2 actual.hello2 +' + +test_expect_success 'change file mode and reiterate content' ' + reinit_git && + cat >expect <<-\EOF && + OBJID + :120000 100644 OBJID OBJID T greeting + OBJID + :100644 120000 OBJID OBJID T greeting + OBJID + :000000 100644 OBJID OBJID A greeting + EOF + echo "link hello" >expect.blob && + echo hello >hello && + cat >filemode.dump <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: add + Prop-content-length: 10 + Text-content-length: 11 + Content-length: 21 + + PROPS-END + link hello + + Revision-number: 2 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: change + Prop-content-length: 33 + Text-content-length: 11 + Content-length: 44 + + K 11 + svn:special + V 1 + * + PROPS-END + link hello + + Revision-number: 3 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: change + Prop-content-length: 10 + Text-content-length: 11 + Content-length: 21 + + PROPS-END + link hello + EOF + test-svn-fe filemode.dump >stream && + git fast-import <stream && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + git show HEAD:greeting >actual.blob && + git show HEAD^:greeting >actual.target && + test_cmp expect actual && + test_cmp expect.blob actual.blob && + test_cmp hello actual.target +' + +test_expect_success 'deltas not supported' ' + { + # (old) h + (inline) ello + (old) \n + printf "SVNQ%b%b%s" "Q\003\006\005\004" "\001Q\0204\001\002" "ello" | + q_to_nul + } >delta && + { + properties \ + svn:author author@example.com \ + svn:date "1999-01-05T00:01:002.000000Z" \ + svn:log "add greeting" && + echo PROPS-END + } >props && + { + properties \ + svn:author author@example.com \ + svn:date "1999-01-06T00:01:002.000000Z" \ + svn:log "change it" && + echo PROPS-END + } >props2 && + { + echo SVN-fs-dump-format-version: 3 && + echo && + echo Revision-number: 1 && + echo Prop-content-length: $(wc -c <props) && + echo Content-length: $(wc -c <props) && + echo && + cat props && + cat <<-\EOF && + + Node-path: hello + Node-kind: file + Node-action: add + Prop-content-length: 10 + Text-content-length: 3 + Content-length: 13 + + PROPS-END + hi + + EOF + echo Revision-number: 2 && + echo Prop-content-length: $(wc -c <props2) && + echo Content-length: $(wc -c <props2) && + echo && + cat props2 && + cat <<-\EOF && + + Node-path: hello + Node-kind: file + Node-action: change + Text-delta: true + Prop-content-length: 10 + EOF + echo Text-content-length: $(wc -c <delta) && + echo Content-length: $((10 + $(wc -c <delta))) && + echo && + echo PROPS-END && + cat delta + } >delta.dump && + test_must_fail test-svn-fe delta.dump +' + +test_expect_success 'property deltas supported' ' + reinit_git && + cat >expect <<-\EOF && + OBJID + :100755 100644 OBJID OBJID M script.sh + EOF + { + properties \ + svn:author author@example.com \ + svn:date "1999-03-06T00:01:002.000000Z" \ + svn:log "make an executable, or chmod -x it" && + echo PROPS-END + } >revprops && + { + echo SVN-fs-dump-format-version: 3 && + echo && + echo Revision-number: 1 && + echo Prop-content-length: $(wc -c <revprops) && + echo Content-length: $(wc -c <revprops) && + echo && + cat revprops && + echo && + cat <<-\EOF && + Node-path: script.sh + Node-kind: file + Node-action: add + Text-content-length: 0 + Prop-content-length: 39 + Content-length: 39 + + K 14 + svn:executable + V 4 + true + PROPS-END + + EOF + echo Revision-number: 2 && + echo Prop-content-length: $(wc -c <revprops) && + echo Content-length: $(wc -c <revprops) && + echo && + cat revprops && + echo && + cat <<-\EOF + Node-path: script.sh + Node-kind: file + Node-action: change + Prop-delta: true + Prop-content-length: 30 + Content-length: 30 + + D 14 + svn:executable + PROPS-END + EOF + } >propdelta.dump && + test-svn-fe propdelta.dump >stream && + git fast-import <stream && + { + git rev-list HEAD | + git diff-tree --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + test_cmp expect actual +' + +test_expect_success 'properties on /' ' + reinit_git && + cat <<-\EOF >expect && + OBJID + OBJID + :000000 100644 OBJID OBJID A greeting + EOF + sed -e "s/X$//" <<-\EOF >changeroot.dump && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: add + Text-content-length: 0 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Revision-number: 2 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: X + Node-kind: dir + Node-action: change + Prop-delta: true + Prop-content-length: 43 + Content-length: 43 + + K 10 + svn:ignore + V 11 + build-area + + PROPS-END + EOF + test-svn-fe changeroot.dump >stream && + git fast-import <stream && + { + git rev-list HEAD | + git diff-tree --root --always --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + test_cmp expect actual +' + +test_expect_success 'deltas for typechange' ' + reinit_git && + cat >expect <<-\EOF && + OBJID + :120000 100644 OBJID OBJID T test-file + OBJID + :100755 120000 OBJID OBJID T test-file + OBJID + :000000 100755 OBJID OBJID A test-file + EOF + cat >deleteprop.dump <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: test-file + Node-kind: file + Node-action: add + Prop-delta: true + Prop-content-length: 35 + Text-content-length: 17 + Content-length: 52 + + K 14 + svn:executable + V 0 + + PROPS-END + link testing 123 + + Revision-number: 2 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: test-file + Node-kind: file + Node-action: change + Prop-delta: true + Prop-content-length: 53 + Text-content-length: 17 + Content-length: 70 + + K 11 + svn:special + V 1 + * + D 14 + svn:executable + PROPS-END + link testing 231 + + Revision-number: 3 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: test-file + Node-kind: file + Node-action: change + Prop-delta: true + Prop-content-length: 27 + Text-content-length: 17 + Content-length: 44 + + D 11 + svn:special + PROPS-END + link testing 321 + EOF + test-svn-fe deleteprop.dump >stream && + git fast-import <stream && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + test_cmp expect actual +' + + test_expect_success 'set up svn repo' ' svnconf=$PWD/svnconf && mkdir -p "$svnconf" && diff --git a/t/t9116-git-svn-log.sh b/t/t9116-git-svn-log.sh index 5d477e4bda..cf4c05261b 100755 --- a/t/t9116-git-svn-log.sh +++ b/t/t9116-git-svn-log.sh @@ -60,6 +60,21 @@ test_expect_success 'test ascending revision range' " git svn log -r 1:4 | grep '^r[0-9]' | cut -d'|' -f1 | test_cmp expected-range-r1-r2-r4 - " +test_expect_success 'test ascending revision range with --show-commit' " + git reset --hard trunk && + git svn log --show-commit -r 1:4 | grep '^r[0-9]' | cut -d'|' -f1 | test_cmp expected-range-r1-r2-r4 - + " + +test_expect_success 'test ascending revision range with --show-commit (sha1)' " + git svn find-rev r1 >expected-range-r1-r2-r4-sha1 && + git svn find-rev r2 >>expected-range-r1-r2-r4-sha1 && + git svn find-rev r4 >>expected-range-r1-r2-r4-sha1 && + git reset --hard trunk && + git svn log --show-commit -r 1:4 | grep '^r[0-9]' | cut -d'|' -f2 >out && + git rev-parse \$(cat out) >actual && + test_cmp expected-range-r1-r2-r4-sha1 actual + " + printf 'r4 \nr2 \nr1 \n' > expected-range-r4-r2-r1 test_expect_success 'test descending revision range' " diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh index ec0a106614..b324c491c5 100755 --- a/t/t9130-git-svn-authors-file.sh +++ b/t/t9130-git-svn-authors-file.sh @@ -96,7 +96,6 @@ test_expect_success 'fresh clone with svn.authors-file in config' ' rm -r "$GIT_DIR" && test x = x"$(git config svn.authorsfile)" && test_config="$HOME"/.gitconfig && - unset GIT_CONFIG_NOGLOBAL && unset GIT_DIR && unset GIT_CONFIG && git config --global \ diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh index 158c8e33ef..6d3130e618 100755 --- a/t/t9146-git-svn-empty-dirs.sh +++ b/t/t9146-git-svn-empty-dirs.sh @@ -28,6 +28,23 @@ test_expect_success 'empty directories exist' ' ) ' +test_expect_success 'option automkdirs set to false' ' + ( + git svn init "$svnrepo" cloned-no-mkdirs && + cd cloned-no-mkdirs && + git config svn-remote.svn.automkdirs false && + git svn fetch && + for i in a b c d d/e d/e/f "weird file name" + do + if test -d "$i" + then + echo >&2 "$i exists" + exit 1 + fi + done + ) +' + test_expect_success 'more emptiness' ' svn_cmd mkdir -m "bang bang" "$svnrepo"/"! !" ' diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 986bc14d58..6b1ba6c858 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -42,6 +42,14 @@ echo "$@"' >empty +test_expect_success 'setup: have pipes?' ' + rm -f frob && + if mkfifo frob + then + test_set_prereq PIPE + fi +' + ### ### series A ### @@ -898,6 +906,77 @@ test_expect_success \ git diff-tree -C --find-copies-harder -r N4^ N4 >actual && compare_diff_raw expect actual' +test_expect_success PIPE 'N: read and copy directory' ' + cat >expect <<-\EOF + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf + EOF + git update-ref -d refs/heads/N4 && + rm -f backflow && + mkfifo backflow && + ( + exec <backflow && + cat <<-EOF && + commit refs/heads/N4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + copy by tree hash, part 2 + COMMIT + + from refs/heads/branch^0 + ls "file2" + EOF + read mode type tree filename && + echo "M 040000 $tree file3" + ) | + git fast-import --cat-blob-fd=3 3>backflow && + git diff-tree -C --find-copies-harder -r N4^ N4 >actual && + compare_diff_raw expect actual +' + +test_expect_success PIPE 'N: empty directory reads as missing' ' + cat <<-\EOF >expect && + OBJNAME + :000000 100644 OBJNAME OBJNAME A unrelated + EOF + echo "missing src" >expect.response && + git update-ref -d refs/heads/read-empty && + rm -f backflow && + mkfifo backflow && + ( + exec <backflow && + cat <<-EOF && + commit refs/heads/read-empty + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + read "empty" (missing) directory + COMMIT + + M 100644 inline src/greeting + data <<BLOB + hello + BLOB + C src/greeting dst1/non-greeting + C src/greeting unrelated + # leave behind "empty" src directory + D src/greeting + ls "src" + EOF + read -r line && + printf "%s\n" "$line" >response && + cat <<-\EOF + D dst1 + D dst2 + EOF + ) | + git fast-import --cat-blob-fd=3 3>backflow && + test_cmp expect.response response && + git rev-list read-empty | + git diff-tree -r --root --stdin | + sed "s/$_x40/OBJNAME/g" >actual && + test_cmp expect actual +' + test_expect_success \ 'N: copy root directory by tree hash' \ 'cat >expect <<-\EOF && @@ -1748,6 +1827,61 @@ test_expect_success \ 'cat input | git fast-import --export-marks=other.marks && grep :1 other.marks' +test_expect_success 'R: catch typo in marks file name' ' + test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null && + echo "feature import-marks=nonexistent.marks" | + test_must_fail git fast-import +' + +test_expect_success 'R: import and output marks can be the same file' ' + rm -f io.marks && + blob=$(echo hi | git hash-object --stdin) && + cat >expect <<-EOF && + :1 $blob + :2 $blob + EOF + git fast-import --export-marks=io.marks <<-\EOF && + blob + mark :1 + data 3 + hi + + EOF + git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF && + blob + mark :2 + data 3 + hi + + EOF + test_cmp expect io.marks +' + +test_expect_success 'R: --import-marks=foo --output-marks=foo to create foo fails' ' + rm -f io.marks && + test_must_fail git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF + blob + mark :1 + data 3 + hi + + EOF +' + +test_expect_success 'R: --import-marks-if-exists' ' + rm -f io.marks && + blob=$(echo hi | git hash-object --stdin) && + echo ":1 $blob" >expect && + git fast-import --import-marks-if-exists=io.marks --export-marks=io.marks <<-\EOF && + blob + mark :1 + data 3 + hi + + EOF + test_cmp expect io.marks +' + cat >input << EOF feature import-marks=marks.out feature export-marks=marks.new @@ -1806,6 +1940,11 @@ test_expect_success 'R: feature no-relative-marks should be honoured' ' test_cmp marks.new non-relative.out ' +test_expect_success 'R: feature ls supported' ' + echo "feature ls" | + git fast-import +' + test_expect_success 'R: feature cat-blob supported' ' echo "feature cat-blob" | git fast-import @@ -1931,14 +2070,6 @@ test_expect_success 'R: print two blobs to stdout' ' test_cmp expect actual ' -test_expect_success 'setup: have pipes?' ' - rm -f frob && - if mkfifo frob - then - test_set_prereq PIPE - fi -' - test_expect_success PIPE 'R: copy using cat-file' ' expect_id=$(git hash-object big) && expect_len=$(wc -c <big) && diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh index 7cf8cd8a2f..463254c727 100755 --- a/t/t9301-fast-import-notes.sh +++ b/t/t9301-fast-import-notes.sh @@ -120,6 +120,7 @@ test_expect_success 'add notes with simple M command' ' test_tick cat >input <<INPUT_END +feature notes commit refs/notes/test committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 21cd286bb7..f5648a6694 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -18,42 +18,34 @@ or warnings to log.' test_expect_success \ 'no commits: projects_list (implicit)' \ 'gitweb_run' -test_debug 'cat gitweb.log' test_expect_success \ 'no commits: projects_index' \ 'gitweb_run "a=project_index"' -test_debug 'cat gitweb.log' test_expect_success \ 'no commits: .git summary (implicit)' \ 'gitweb_run "p=.git"' -test_debug 'cat gitweb.log' test_expect_success \ 'no commits: .git commit (implicit HEAD)' \ 'gitweb_run "p=.git;a=commit"' -test_debug 'cat gitweb.log' test_expect_success \ 'no commits: .git commitdiff (implicit HEAD)' \ 'gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'no commits: .git tree (implicit HEAD)' \ 'gitweb_run "p=.git;a=tree"' -test_debug 'cat gitweb.log' test_expect_success \ 'no commits: .git heads' \ 'gitweb_run "p=.git;a=heads"' -test_debug 'cat gitweb.log' test_expect_success \ 'no commits: .git tags' \ 'gitweb_run "p=.git;a=tags"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- @@ -69,52 +61,42 @@ test_expect_success \ test_expect_success \ 'projects_list (implicit)' \ 'gitweb_run' -test_debug 'cat gitweb.log' test_expect_success \ 'projects_index' \ 'gitweb_run "a=project_index"' -test_debug 'cat gitweb.log' test_expect_success \ '.git summary (implicit)' \ 'gitweb_run "p=.git"' -test_debug 'cat gitweb.log' test_expect_success \ '.git commit (implicit HEAD)' \ 'gitweb_run "p=.git;a=commit"' -test_debug 'cat gitweb.log' test_expect_success \ '.git commitdiff (implicit HEAD, root commit)' \ 'gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ '.git commitdiff_plain (implicit HEAD, root commit)' \ 'gitweb_run "p=.git;a=commitdiff_plain"' -test_debug 'cat gitweb.log' test_expect_success \ '.git commit (HEAD)' \ 'gitweb_run "p=.git;a=commit;h=HEAD"' -test_debug 'cat gitweb.log' test_expect_success \ '.git tree (implicit HEAD)' \ 'gitweb_run "p=.git;a=tree"' -test_debug 'cat gitweb.log' test_expect_success \ '.git blob (file)' \ 'gitweb_run "p=.git;a=blob;f=file"' -test_debug 'cat gitweb.log' test_expect_success \ '.git blob_plain (file)' \ 'gitweb_run "p=.git;a=blob_plain;f=file"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # nonexistent objects @@ -122,37 +104,30 @@ test_debug 'cat gitweb.log' test_expect_success \ '.git commit (non-existent)' \ 'gitweb_run "p=.git;a=commit;h=non-existent"' -test_debug 'cat gitweb.log' test_expect_success \ '.git commitdiff (non-existent)' \ 'gitweb_run "p=.git;a=commitdiff;h=non-existent"' -test_debug 'cat gitweb.log' test_expect_success \ '.git commitdiff (non-existent vs HEAD)' \ 'gitweb_run "p=.git;a=commitdiff;hp=non-existent;h=HEAD"' -test_debug 'cat gitweb.log' test_expect_success \ '.git tree (0000000000000000000000000000000000000000)' \ 'gitweb_run "p=.git;a=tree;h=0000000000000000000000000000000000000000"' -test_debug 'cat gitweb.log' test_expect_success \ '.git tag (0000000000000000000000000000000000000000)' \ 'gitweb_run "p=.git;a=tag;h=0000000000000000000000000000000000000000"' -test_debug 'cat gitweb.log' test_expect_success \ '.git blob (non-existent)' \ 'gitweb_run "p=.git;a=blob;f=non-existent"' -test_debug 'cat gitweb.log' test_expect_success \ '.git blob_plain (non-existent)' \ 'gitweb_run "p=.git;a=blob_plain;f=non-existent"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- @@ -161,7 +136,6 @@ test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): root' \ 'gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): file added' \ @@ -169,21 +143,18 @@ test_expect_success \ git add new_file && git commit -a -m "File added." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): mode change' \ 'test_chmod +x new_file && git commit -a -m "Mode changed." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): file renamed' \ 'git mv new_file renamed_file && git commit -a -m "File renamed." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success SYMLINKS \ 'commitdiff(0): file to symlink' \ @@ -191,7 +162,6 @@ test_expect_success SYMLINKS \ ln -s file renamed_file && git commit -a -m "File to symlink." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): file deleted' \ @@ -199,7 +169,6 @@ test_expect_success \ rm -f renamed_file && git commit -a -m "File removed." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): file copied / new file' \ @@ -207,7 +176,6 @@ test_expect_success \ git add file2 && git commit -a -m "File copied." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): mode change and modified' \ @@ -215,7 +183,6 @@ test_expect_success \ test_chmod +x file2 && git commit -a -m "Mode change and modification." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): renamed and modified' \ @@ -233,7 +200,6 @@ EOF echo "Propter nomen suum." >> file3 && git commit -a -m "File rename and modification." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): renamed, mode change and modified' \ @@ -242,7 +208,6 @@ test_expect_success \ test_chmod +x file2 && git commit -a -m "File rename, mode change and modification." && gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # commitdiff testing (taken from t4114-apply-typechange.sh) @@ -279,42 +244,34 @@ test_expect_success SYMLINKS 'setup typechange commits' ' test_expect_success \ 'commitdiff(2): file renamed from foo to foo/baz' \ 'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-baz-renamed-from-foo"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(2): file renamed from foo/baz to foo' \ 'gitweb_run "p=.git;a=commitdiff;hp=foo-baz-renamed-from-foo;h=initial"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(2): directory becomes file' \ 'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=initial"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(2): file becomes directory' \ 'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-becomes-a-directory"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(2): file becomes symlink' \ 'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-symlinked-to-bar"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(2): symlink becomes file' \ 'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-back-to-file"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(2): symlink becomes directory' \ 'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-becomes-a-directory"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(2): directory becomes symlink' \ 'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # commit, commitdiff: merge, large @@ -330,12 +287,10 @@ test_expect_success \ test_expect_success \ 'commit(0): merge commit' \ 'gitweb_run "p=.git;a=commit"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): merge commit' \ 'gitweb_run "p=.git;a=commitdiff"' -test_debug 'cat gitweb.log' test_expect_success \ 'Prepare large commit' \ @@ -371,12 +326,10 @@ test_expect_success \ test_expect_success \ 'commit(1): large commit' \ 'gitweb_run "p=.git;a=commit;h=b"' -test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(1): large commit' \ 'gitweb_run "p=.git;a=commitdiff;h=b"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # tags testing @@ -394,17 +347,14 @@ test_expect_success \ git tag lightweight/tag-tree HEAD^{tree} && git tag lightweight/tag-blob HEAD:file && gitweb_run "p=.git;a=tags"' -test_debug 'cat gitweb.log' test_expect_success \ 'tag: Tag to commit object' \ 'gitweb_run "p=.git;a=tag;h=tag-commit"' -test_debug 'cat gitweb.log' test_expect_success \ 'tag: on lightweight tag (invalid)' \ 'gitweb_run "p=.git;a=tag;h=lightweight/tag-commit"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # logs @@ -412,22 +362,18 @@ test_debug 'cat gitweb.log' test_expect_success \ 'logs: log (implicit HEAD)' \ 'gitweb_run "p=.git;a=log"' -test_debug 'cat gitweb.log' test_expect_success \ 'logs: shortlog (implicit HEAD)' \ 'gitweb_run "p=.git;a=shortlog"' -test_debug 'cat gitweb.log' test_expect_success \ 'logs: history (implicit HEAD, file)' \ 'gitweb_run "p=.git;a=history;f=file"' -test_debug 'cat gitweb.log' test_expect_success \ 'logs: history (implicit HEAD, non-existent file)' \ 'gitweb_run "p=.git;a=history;f=non-existent"' -test_debug 'cat gitweb.log' test_expect_success \ 'logs: history (implicit HEAD, deleted file)' \ @@ -438,55 +384,45 @@ test_expect_success \ git rm deleted_file && git commit -m "Delete file" && gitweb_run "p=.git;a=history;f=deleted_file"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # path_info links test_expect_success \ 'path_info: project' \ 'gitweb_run "" "/.git"' -test_debug 'cat gitweb.log' test_expect_success \ 'path_info: project/branch' \ 'gitweb_run "" "/.git/b"' -test_debug 'cat gitweb.log' test_expect_success \ 'path_info: project/branch:file' \ 'gitweb_run "" "/.git/master:file"' -test_debug 'cat gitweb.log' test_expect_success \ 'path_info: project/branch:dir/' \ 'gitweb_run "" "/.git/master:foo/"' -test_debug 'cat gitweb.log' test_expect_success \ 'path_info: project/branch:file (non-existent)' \ 'gitweb_run "" "/.git/master:non-existent"' -test_debug 'cat gitweb.log' test_expect_success \ 'path_info: project/branch:dir/ (non-existent)' \ 'gitweb_run "" "/.git/master:non-existent/"' -test_debug 'cat gitweb.log' test_expect_success \ 'path_info: project/branch:/file' \ 'gitweb_run "" "/.git/master:/file"' -test_debug 'cat gitweb.log' test_expect_success \ 'path_info: project/:/file (implicit HEAD)' \ 'gitweb_run "" "/.git/:/file"' -test_debug 'cat gitweb.log' test_expect_success \ 'path_info: project/:/ (implicit HEAD, top tree)' \ 'gitweb_run "" "/.git/:/"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- @@ -495,17 +431,14 @@ test_debug 'cat gitweb.log' test_expect_success \ 'feeds: OPML' \ 'gitweb_run "a=opml"' -test_debug 'cat gitweb.log' test_expect_success \ 'feed: RSS' \ 'gitweb_run "p=.git;a=rss"' -test_debug 'cat gitweb.log' test_expect_success \ 'feed: Atom' \ 'gitweb_run "p=.git;a=atom"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # encoding/decoding @@ -513,27 +446,28 @@ test_debug 'cat gitweb.log' test_expect_success \ 'encode(commit): utf8' \ '. "$TEST_DIRECTORY"/t3901-utf8.txt && + test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" && + test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && echo "UTF-8" >> file && git add file && git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt && gitweb_run "p=.git;a=commit"' -test_debug 'cat gitweb.log' test_expect_success \ 'encode(commit): iso-8859-1' \ '. "$TEST_DIRECTORY"/t3901-8859-1.txt && + test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" && + test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && echo "ISO-8859-1" >> file && git add file && git config i18n.commitencoding ISO-8859-1 && + test_when_finished "git config --unset i18n.commitencoding" && git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt && - git config --unset i18n.commitencoding && gitweb_run "p=.git;a=commit"' -test_debug 'cat gitweb.log' test_expect_success \ 'encode(log): utf-8 and iso-8859-1' \ 'gitweb_run "p=.git;a=log"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # extra options @@ -541,27 +475,22 @@ test_debug 'cat gitweb.log' test_expect_success \ 'opt: log --no-merges' \ 'gitweb_run "p=.git;a=log;opt=--no-merges"' -test_debug 'cat gitweb.log' test_expect_success \ 'opt: atom --no-merges' \ 'gitweb_run "p=.git;a=log;opt=--no-merges"' -test_debug 'cat gitweb.log' test_expect_success \ 'opt: "file" history --no-merges' \ 'gitweb_run "p=.git;a=history;f=file;opt=--no-merges"' -test_debug 'cat gitweb.log' test_expect_success \ 'opt: log --no-such-option (invalid option)' \ 'gitweb_run "p=.git;a=log;opt=--no-such-option"' -test_debug 'cat gitweb.log' test_expect_success \ 'opt: tree --no-merges (invalid option for action)' \ 'gitweb_run "p=.git;a=tree;opt=--no-merges"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # testing config_to_multi / cloneurl @@ -569,14 +498,12 @@ test_debug 'cat gitweb.log' test_expect_success \ 'URL: no project URLs, no base URL' \ 'gitweb_run "p=.git;a=summary"' -test_debug 'cat gitweb.log' test_expect_success \ 'URL: project URLs via gitweb.url' \ 'git config --add gitweb.url git://example.com/git/trash.git && git config --add gitweb.url http://example.com/git/trash.git && gitweb_run "p=.git;a=summary"' -test_debug 'cat gitweb.log' cat >.git/cloneurl <<\EOF git://example.com/git/trash.git @@ -586,7 +513,6 @@ EOF test_expect_success \ 'URL: project URLs via cloneurl file' \ 'gitweb_run "p=.git;a=summary"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # gitweb config and repo config @@ -604,12 +530,10 @@ EOF test_expect_success \ 'config override: projects list (implicit)' \ 'gitweb_run' -test_debug 'cat gitweb.log' test_expect_success \ 'config override: tree view, features not overridden in repo config' \ 'gitweb_run "p=.git;a=tree"' -test_debug 'cat gitweb.log' test_expect_success \ 'config override: tree view, features disabled in repo config' \ @@ -617,14 +541,12 @@ test_expect_success \ git config gitweb.snapshot none && git config gitweb.avatar gravatar && gitweb_run "p=.git;a=tree"' -test_debug 'cat gitweb.log' test_expect_success \ 'config override: tree view, features enabled in repo config (1)' \ 'git config gitweb.blame yes && git config gitweb.snapshot "zip,tgz, tbz2" && gitweb_run "p=.git;a=tree"' -test_debug 'cat gitweb.log' cat >.git/config <<\EOF # testing noval and alternate separator @@ -635,7 +557,6 @@ EOF test_expect_success \ 'config override: tree view, features enabled in repo config (2)' \ 'gitweb_run "p=.git;a=tree"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # non-ASCII in README.html @@ -645,7 +566,6 @@ test_expect_success \ 'echo "<b>UTF-8 example:</b><br />" > .git/README.html && cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >> .git/README.html && gitweb_run "p=.git;a=summary"' -test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # syntax highlighting @@ -666,7 +586,6 @@ test_expect_success HIGHLIGHT \ 'syntax highlighting (no highlight, unknown syntax)' \ '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, shell script)' \ @@ -675,6 +594,62 @@ test_expect_success HIGHLIGHT \ git add test.sh && git commit -m "Add test.sh" && gitweb_run "p=.git;a=blob;f=test.sh"' -test_debug 'cat gitweb.log' + +# ---------------------------------------------------------------------- +# forks of projects + +cat >>gitweb_config.perl <<\EOF && +$feature{'forks'}{'default'} = [1]; +EOF + +test_expect_success \ + 'forks: prepare' \ + 'git init --bare foo.git && + git --git-dir=foo.git --work-tree=. add file && + git --git-dir=foo.git --work-tree=. commit -m "Initial commit" && + echo "foo" > foo.git/description && + mkdir -p foo && + (cd foo && + git clone --shared --bare ../foo.git foo-forked.git && + echo "fork of foo" > foo-forked.git/description)' + +test_expect_success \ + 'forks: projects list' \ + 'gitweb_run' + +test_expect_success \ + 'forks: forks action' \ + 'gitweb_run "p=foo.git;a=forks"' + +# ---------------------------------------------------------------------- +# content tags (tag cloud) + +cat >>gitweb_config.perl <<-\EOF && +# we don't test _setting_ content tags, so any true value is good +$feature{'ctags'}{'default'} = ['ctags_script.cgi']; +EOF + +test_expect_success \ + 'ctags: tag cloud in projects list' \ + 'mkdir .git/ctags && + echo "2" > .git/ctags/foo && + echo "1" > .git/ctags/bar && + gitweb_run' + +test_expect_success \ + 'ctags: search projects by existing tag' \ + 'gitweb_run "by_tag=foo"' + +test_expect_success \ + 'ctags: search projects by non existent tag' \ + 'gitweb_run "by_tag=non-existent"' + +# ---------------------------------------------------------------------- +# categories + +test_expect_success \ + 'categories: projects list, only default category' \ + 'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl && + gitweb_run' test_done diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh index 18825aff89..26102ee9b0 100755 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -126,7 +126,6 @@ test_expect_success 'load checking: load too high (default action)' ' grep "Status: 503 Service Unavailable" gitweb.headers && grep "503 - The load average on the server is too high" gitweb.body ' -test_debug 'cat gitweb.log' # just in case test_debug 'cat gitweb.headers' # turn off load checking diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh index dd83890001..731e64c3ad 100755 --- a/t/t9502-gitweb-standalone-parse-output.sh +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -112,4 +112,78 @@ test_expect_success 'snapshot: hierarchical branch name (xx/test)' ' ' test_debug 'cat gitweb.headers' +# ---------------------------------------------------------------------- +# forks of projects + +test_expect_success 'forks: setup' ' + git init --bare foo.git && + echo file > file && + git --git-dir=foo.git --work-tree=. add file && + git --git-dir=foo.git --work-tree=. commit -m "Initial commit" && + echo "foo" > foo.git/description && + git clone --bare foo.git foo.bar.git && + echo "foo.bar" > foo.bar.git/description && + git clone --bare foo.git foo_baz.git && + echo "foo_baz" > foo_baz.git/description && + rm -fr foo && + mkdir -p foo && + ( + cd foo && + git clone --shared --bare ../foo.git foo-forked.git && + echo "fork of foo" > foo-forked.git/description + ) +' + +test_expect_success 'forks: not skipped unless "forks" feature enabled' ' + gitweb_run "a=project_list" && + grep -q ">\\.git<" gitweb.body && + grep -q ">foo\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo\\.bar\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo/foo-forked\\.git<" gitweb.body && + grep -q ">fork of .*<" gitweb.body +' + +cat >>gitweb_config.perl <<\EOF && +$feature{'forks'}{'default'} = [1]; +EOF + +test_expect_success 'forks: forks skipped if "forks" feature enabled' ' + gitweb_run "a=project_list" && + grep -q ">\\.git<" gitweb.body && + grep -q ">foo\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo\\.bar\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -v ">foo/foo-forked\\.git<" gitweb.body && + grep -v ">fork of .*<" gitweb.body +' + +test_expect_success 'forks: "forks" action for forked repository' ' + gitweb_run "p=foo.git;a=forks" && + grep -q ">foo/foo-forked\\.git<" gitweb.body && + grep -q ">fork of foo<" gitweb.body +' + +test_expect_success 'forks: can access forked repository' ' + gitweb_run "p=foo/foo-forked.git;a=summary" && + grep -q "200 OK" gitweb.headers && + grep -q ">fork of foo<" gitweb.body +' + +test_expect_success 'forks: project_index lists all projects (incl. forks)' ' + cat >expected <<-\EOF + .git + foo.bar.git + foo.git + foo/foo-forked.git + foo_baz.git + EOF + gitweb_run "a=project_index" && + sed -e "s/ .*//" <gitweb.body | sort >actual && + test_cmp expected actual +' + + test_done diff --git a/t/t9700/test.pl b/t/t9700/test.pl index c15ca2d647..13ba96e21a 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -113,6 +113,16 @@ 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'); +# commands outside working tree +chdir($abs_repo_dir . '/..'); +my $r3 = Git->repository(Directory => $abs_repo_dir); +my $tmpfile3 = "$abs_repo_dir/file3.tmp"; +open TEMPFILE3, "+>$tmpfile3" or die "Can't open $tmpfile3: $!"; +is($r3->cat_blob($file1hash, \*TEMPFILE3), 15, "cat_blob(outside): size"); +close TEMPFILE3; +unlink $tmpfile3; +chdir($abs_repo_dir); + printf "1..%d\n", Test::More->builder->current_test; my $is_passing = eval { Test::More->is_passing }; diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh new file mode 100755 index 0000000000..33b0127651 --- /dev/null +++ b/t/t9800-git-p4.sh @@ -0,0 +1,264 @@ +#!/bin/sh + +test_description='git-p4 tests' + +. ./test-lib.sh + +( p4 -h && p4d -h ) >/dev/null 2>&1 || { + skip_all='skipping git-p4 tests; no p4 or p4d' + test_done +} + +GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4 +P4DPORT=10669 + +export P4PORT=localhost:$P4DPORT + +db="$TRASH_DIRECTORY/db" +cli="$TRASH_DIRECTORY/cli" +git="$TRASH_DIRECTORY/git" + +test_debug 'echo p4d -q -d -r "$db" -p $P4DPORT' +test_expect_success setup ' + mkdir -p "$db" && + p4d -q -d -r "$db" -p $P4DPORT && + mkdir -p "$cli" && + mkdir -p "$git" && + export P4PORT=localhost:$P4DPORT +' + +test_expect_success 'add p4 files' ' + cd "$cli" && + p4 client -i <<-EOF && + Client: client + Description: client + Root: $cli + View: //depot/... //client/... + EOF + export P4CLIENT=client && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "file1" && + echo file2 >file2 && + p4 add file2 && + p4 submit -d "file2" && + cd "$TRASH_DIRECTORY" +' + +test_expect_success 'basic git-p4 clone' ' + "$GITP4" clone --dest="$git" //depot && + cd "$git" && + git log --oneline >lines && + test_line_count = 1 lines && + cd .. && + rm -rf "$git" && mkdir "$git" +' + +test_expect_success 'git-p4 clone @all' ' + "$GITP4" clone --dest="$git" //depot@all && + cd "$git" && + git log --oneline >lines && + test_line_count = 2 lines && + cd .. && + rm -rf "$git" && mkdir "$git" +' + +test_expect_success 'git-p4 sync uninitialized repo' ' + test_create_repo "$git" && + cd "$git" && + test_must_fail "$GITP4" sync && + rm -rf "$git" && mkdir "$git" +' + +# +# Create a git repo by hand. Add a commit so that HEAD is valid. +# Test imports a new p4 repository into a new git branch. +# +test_expect_success 'git-p4 sync new branch' ' + test_create_repo "$git" && + cd "$git" && + test_commit head && + "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all && + git log --oneline p4/depot >lines && + cat lines && + test_line_count = 2 lines && + cd .. && + rm -rf "$git" && mkdir "$git" +' + +test_expect_success 'exit when p4 fails to produce marshaled output' ' + badp4dir="$TRASH_DIRECTORY/badp4dir" && + mkdir -p "$badp4dir" && + cat >"$badp4dir"/p4 <<-EOF && + #!$SHELL_PATH + exit 1 + EOF + chmod 755 "$badp4dir"/p4 && + PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? && + test $retval -eq 1 && + test_must_fail grep -q Traceback errs +' + +test_expect_success 'add p4 files with wildcards in the names' ' + cd "$cli" && + echo file-wild-hash >file-wild#hash && + echo file-wild-star >file-wild\*star && + echo file-wild-at >file-wild@at && + echo file-wild-percent >file-wild%percent && + p4 add -f file-wild* && + p4 submit -d "file wildcards" && + cd "$TRASH_DIRECTORY" +' + +test_expect_success 'wildcard files git-p4 clone' ' + "$GITP4" clone --dest="$git" //depot && + cd "$git" && + test -f file-wild#hash && + test -f file-wild\*star && + test -f file-wild@at && + test -f file-wild%percent && + cd "$TRASH_DIRECTORY" && + rm -rf "$git" && mkdir "$git" +' + +test_expect_success 'clone bare' ' + "$GITP4" clone --dest="$git" --bare //depot && + cd "$git" && + test ! -d .git && + bare=`git config --get core.bare` && + test "$bare" = true && + cd "$TRASH_DIRECTORY" && + rm -rf "$git" && mkdir "$git" +' + +p4_add_user() { + name=$1 + fullname=$2 + p4 user -f -i <<EOF && +User: $name +Email: $name@localhost +FullName: $fullname +EOF + p4 passwd -P secret $name +} + +p4_grant_admin() { + name=$1 + p4 protect -o |\ + awk "{print}END{print \" admin user $name * //depot/...\"}" |\ + p4 protect -i +} + +p4_check_commit_author() { + file=$1 + user=$2 + if p4 changes -m 1 //depot/$file | grep $user > /dev/null ; then + return 0 + else + echo "file $file not modified by user $user" 1>&2 + return 1 + fi +} + +make_change_by_user() { + file=$1 name=$2 email=$3 && + echo "username: a change by $name" >>"$file" && + git add "$file" && + git commit --author "$name <$email>" -m "a change by $name" +} + +# Test username support, submitting as user 'alice' +test_expect_success 'preserve users' ' + p4_add_user alice Alice && + p4_add_user bob Bob && + p4_grant_admin alice && + "$GITP4" clone --dest="$git" //depot && + cd "$git" && + echo "username: a change by alice" >> file1 && + echo "username: a change by bob" >> file2 && + git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 && + git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 && + git config git-p4.skipSubmitEditCheck true && + P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user && + p4_check_commit_author file1 alice && + p4_check_commit_author file2 bob && + cd "$TRASH_DIRECTORY" && + rm -rf "$git" && mkdir "$git" +' + +# Test username support, submitting as bob, who lacks admin rights. Should +# not submit change to p4 (git diff should show deltas). +test_expect_success 'refuse to preserve users without perms' ' + "$GITP4" clone --dest="$git" //depot && + cd "$git" && + echo "username-noperms: a change by alice" >> file1 && + git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 && + ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user && + ! git diff --exit-code HEAD..p4/master > /dev/null && + cd "$TRASH_DIRECTORY" && + rm -rf "$git" && mkdir "$git" +' + +# What happens with unknown author? Without allowMissingP4Users it should fail. +test_expect_success 'preserve user where author is unknown to p4' ' + "$GITP4" clone --dest="$git" //depot && + cd "$git" && + git config git-p4.skipSubmitEditCheck true + echo "username-bob: a change by bob" >> file1 && + git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 && + echo "username-unknown: a change by charlie" >> file1 && + git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 && + ! P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user && + ! git diff --exit-code HEAD..p4/master > /dev/null && + echo "$0: repeat with allowMissingP4Users enabled" && + git config git-p4.allowMissingP4Users true && + git config git-p4.preserveUser true && + P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit && + git diff --exit-code HEAD..p4/master > /dev/null && + p4_check_commit_author file1 alice && + cd "$TRASH_DIRECTORY" && + rm -rf "$git" && mkdir "$git" +' + +# If we're *not* using --preserve-user, git-p4 should warn if we're submitting +# changes that are not all ours. +# Test: user in p4 and user unknown to p4. +# Test: warning disabled and user is the same. +test_expect_success 'not preserving user with mixed authorship' ' + "$GITP4" clone --dest="$git" //depot && + ( + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + p4_add_user derek Derek && + + make_change_by_user usernamefile3 Derek derek@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && + grep "git author derek@localhost does not match" actual && + + make_change_by_user usernamefile3 Charlie charlie@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && + grep "git author charlie@localhost does not match" actual && + + make_change_by_user usernamefile3 alice alice@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && + ! grep "git author.*does not match" actual && + + git config git-p4.skipUserNameCheck true && + make_change_by_user usernamefile3 Charlie charlie@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && + ! grep "git author.*does not match" actual && + + p4_check_commit_author usernamefile3 alice + ) && + rm -rf "$git" && mkdir "$git" +' + + +test_expect_success 'shutdown' ' + pid=`pgrep -f p4d` && + test -n "$pid" && + test_debug "ps wl `echo $pid`" && + kill $pid +' + +test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 0fdc541a7c..64390d716d 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -43,36 +43,25 @@ TERM=dumb export LANG LC_ALL PAGER TERM TZ EDITOR=: unset VISUAL -unset GIT_EDITOR -unset AUTHOR_DATE -unset AUTHOR_EMAIL -unset AUTHOR_NAME -unset COMMIT_AUTHOR_EMAIL -unset COMMIT_AUTHOR_NAME unset EMAIL -unset GIT_ALTERNATE_OBJECT_DIRECTORIES -unset GIT_AUTHOR_DATE +unset $(perl -e ' + my @env = keys %ENV; + my $ok = join("|", qw( + TRACE + DEBUG + USE_LOOKUP + TEST + .*_TEST + PROVE + VALGRIND + )); + my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); + print join("\n", @vars); +') GIT_AUTHOR_EMAIL=author@example.com GIT_AUTHOR_NAME='A U Thor' -unset GIT_COMMITTER_DATE GIT_COMMITTER_EMAIL=committer@example.com GIT_COMMITTER_NAME='C O Mitter' -unset GIT_DIFF_OPTS -unset GIT_DIR -unset GIT_WORK_TREE -unset GIT_EXTERNAL_DIFF -unset GIT_INDEX_FILE -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 -unset GIT_REFLOG_ACTION -unset GIT_CHERRY_PICK_HELP -unset GIT_QUIET GIT_MERGE_VERBOSITY=5 export GIT_MERGE_VERBOSITY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME @@ -100,6 +89,9 @@ esac _x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05" +# Zero SHA-1 +_z40=0000000000000000000000000000000000000000 + # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... @@ -586,7 +578,7 @@ test_external () { test_external_without_stderr () { # The temporary file has no (and must have no) security # implications. - tmp="$TMPDIR"; if [ -z "$tmp" ]; then tmp=/tmp; fi + tmp=${TMPDIR:-/tmp} stderr="$tmp/git-external-stderr.$$.tmp" test_external "$@" 4> "$stderr" [ -f "$stderr" ] || error "Internal error: $stderr disappeared." @@ -739,12 +731,11 @@ test_expect_code () { exit_code=$? if test $exit_code = $want_code then - echo >&2 "test_expect_code: command exited with $exit_code: $*" return 0 - else - echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" - return 1 fi + + echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" + return 1 } # test_cmp is a helper function to compare actual and expected output. @@ -812,12 +803,14 @@ test_done () { mkdir -p "$test_results_dir" test_results_path="$test_results_dir/${0%.sh}-$$.counts" - echo "total $test_count" >> $test_results_path - echo "success $test_success" >> $test_results_path - echo "fixed $test_fixed" >> $test_results_path - echo "broken $test_broken" >> $test_results_path - echo "failed $test_failure" >> $test_results_path - echo "" >> $test_results_path + cat >>"$test_results_path" <<-EOF + total $test_count + success $test_success + fixed $test_fixed + broken $test_broken + failed $test_failure + + EOF fi if test "$test_fixed" != 0 @@ -954,8 +947,8 @@ fi GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt unset GIT_CONFIG GIT_CONFIG_NOSYSTEM=1 -GIT_CONFIG_NOGLOBAL=1 -export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL +GIT_ATTR_NOSYSTEM=1 +export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM . "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS @@ -1004,14 +997,14 @@ rm -fr "$test" || { exit 1 } +HOME="$TRASH_DIRECTORY" +export HOME + test_create_repo "$test" # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). cd -P "$test" || exit 1 -HOME=$(pwd) -export HOME - this_test=${0##*/} this_test=${this_test%%-*} for skp in $GIT_SKIP_TESTS @@ -1078,6 +1071,42 @@ esac test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PYTHON" && test_set_prereq PYTHON +test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE + +# Can we rely on git's output in the C locale? +if test -n "$GETTEXT_POISON" +then + GIT_GETTEXT_POISON=YesPlease + export GIT_GETTEXT_POISON +else + test_set_prereq C_LOCALE_OUTPUT +fi + +# Use this instead of test_cmp to compare files that contain expected and +# actual output from git commands that can be translated. When running +# under GETTEXT_POISON this pretends that the command produced expected +# results. +test_i18ncmp () { + test -n "$GETTEXT_POISON" || test_cmp "$@" +} + +# Use this instead of "grep expected-string actual" to see if the +# output from a git command that can be translated either contains an +# expected string, or does not contain an unwanted one. When running +# under GETTEXT_POISON this pretends that the command produced expected +# results. +test_i18ngrep () { + if test -n "$GETTEXT_POISON" + then + : # pretend success + elif test "x!" = "x$1" + then + shift + ! grep "$@" + else + grep "$@" + fi +} # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS diff --git a/t/valgrind/default.supp b/t/valgrind/default.supp index 9e013fa3b2..0a6724fcc4 100644 --- a/t/valgrind/default.supp +++ b/t/valgrind/default.supp @@ -43,3 +43,9 @@ fun:write_buffer fun:write_loose_object } + +{ + ignore-sse-strlen-invalid-read-size + Memcheck:Addr4 + fun:copy_ref +} @@ -56,7 +56,7 @@ static unsigned long parse_tag_date(const char *buf, const char *tail) return strtoul(dateptr, NULL, 10); } -int parse_tag_buffer(struct tag *item, void *data, unsigned long size) +int parse_tag_buffer(struct tag *item, const void *data, unsigned long size) { unsigned char sha1[20]; char type[20]; @@ -97,7 +97,9 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) item->tagged = NULL; } - if (prefixcmp(bufptr, "tag ")) + if (bufptr + 4 < tail && !prefixcmp(bufptr, "tag ")) + ; /* good */ + else return -1; bufptr += 4; nl = memchr(bufptr, '\n', tail - bufptr); @@ -106,7 +108,7 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) item->tag = xmemdupz(bufptr, nl - bufptr); bufptr = nl + 1; - if (!prefixcmp(bufptr, "tagger ")) + if (bufptr + 7 < tail && !prefixcmp(bufptr, "tagger ")) item->date = parse_tag_date(bufptr, tail); else item->date = 0; @@ -13,7 +13,7 @@ struct tag { }; extern struct tag *lookup_tag(const unsigned char *sha1); -extern int parse_tag_buffer(struct tag *item, void *data, unsigned long size); +extern int parse_tag_buffer(struct tag *item, const void *data, unsigned long size); extern int parse_tag(struct tag *item); extern struct object *deref_tag(struct object *, const char *, int); extern size_t parse_signature(const char *buf, unsigned long size); diff --git a/test-line-buffer.c b/test-line-buffer.c index c11bf7f967..7ec9b13c9b 100644 --- a/test-line-buffer.c +++ b/test-line-buffer.c @@ -1,14 +1,9 @@ /* * test-line-buffer.c: code to exercise the svn importer's input helper - * - * Input format: - * number NL - * (number bytes) NL - * number NL - * ... */ #include "git-compat-util.h" +#include "strbuf.h" #include "vcs-svn/line_buffer.h" static uint32_t strtouint32(const char *s) @@ -20,27 +15,78 @@ static uint32_t strtouint32(const char *s) return (uint32_t) n; } +static void handle_command(const char *command, const char *arg, struct line_buffer *buf) +{ + switch (*command) { + case 'b': + if (!prefixcmp(command, "binary ")) { + struct strbuf sb = STRBUF_INIT; + strbuf_addch(&sb, '>'); + buffer_read_binary(buf, &sb, strtouint32(arg)); + fwrite(sb.buf, 1, sb.len, stdout); + strbuf_release(&sb); + return; + } + case 'c': + if (!prefixcmp(command, "copy ")) { + buffer_copy_bytes(buf, strtouint32(arg)); + return; + } + case 's': + if (!prefixcmp(command, "skip ")) { + buffer_skip_bytes(buf, strtouint32(arg)); + return; + } + default: + die("unrecognized command: %s", command); + } +} + +static void handle_line(const char *line, struct line_buffer *stdin_buf) +{ + const char *arg = strchr(line, ' '); + if (!arg) + die("no argument in line: %s", line); + handle_command(line, arg + 1, stdin_buf); +} + int main(int argc, char *argv[]) { + struct line_buffer stdin_buf = LINE_BUFFER_INIT; + struct line_buffer file_buf = LINE_BUFFER_INIT; + struct line_buffer *input = &stdin_buf; + const char *filename; char *s; - if (argc != 1) - usage("test-line-buffer < input.txt"); - if (buffer_init(NULL)) + if (argc == 1) + filename = NULL; + else if (argc == 2) + filename = argv[1]; + else + usage("test-line-buffer [file | &fd] < script"); + + if (buffer_init(&stdin_buf, NULL)) die_errno("open error"); - while ((s = buffer_read_line())) { - s = buffer_read_string(strtouint32(s)); - fputs(s, stdout); - fputc('\n', stdout); - buffer_skip_bytes(1); - if (!(s = buffer_read_line())) - break; - buffer_copy_bytes(strtouint32(s) + 1); + if (filename) { + if (*filename == '&') { + if (buffer_fdinit(&file_buf, strtouint32(filename + 1))) + die_errno("error opening fd %s", filename + 1); + } else { + if (buffer_init(&file_buf, filename)) + die_errno("error opening %s", filename); + } + input = &file_buf; } - if (buffer_deinit()) + + while ((s = buffer_read_line(&stdin_buf))) + handle_line(s, input); + + if (filename && buffer_deinit(&file_buf)) + die("error reading from %s", filename); + if (buffer_deinit(&stdin_buf)) die("input error"); if (ferror(stdout)) die("output error"); - buffer_reset(); + buffer_reset(&stdin_buf); return 0; } diff --git a/test-mktemp.c b/test-mktemp.c new file mode 100644 index 0000000000..c8c54213a3 --- /dev/null +++ b/test-mktemp.c @@ -0,0 +1,14 @@ +/* + * test-mktemp.c: code to exercise the creation of temporary files + */ +#include "git-compat-util.h" + +int main(int argc, char *argv[]) +{ + if (argc != 2) + usage("Expected 1 parameter defining the temporary file template"); + + xmkstemp(xstrdup(argv[1])); + + return 0; +} diff --git a/test-parse-options.c b/test-parse-options.c index 0828592162..4e3710b9a8 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -46,7 +46,7 @@ int main(int argc, const char **argv) OPT_DATE('t', NULL, ×tamp, "get timestamp of <time>"), OPT_CALLBACK('L', "length", &integer, "str", "get length of <str>", length_callback), - OPT_FILENAME('F', "file", &file, "set file to <FILE>"), + OPT_FILENAME('F', "file", &file, "set file to <file>"), OPT_GROUP("String options"), OPT_STRING('s', "string", &string, "string", "get a string"), OPT_STRING(0, "string2", &string, "str", "get another string"), diff --git a/test-path-utils.c b/test-path-utils.c index d261398d6c..e7671593df 100644 --- a/test-path-utils.c +++ b/test-path-utils.c @@ -11,9 +11,9 @@ int main(int argc, char **argv) return 0; } - if (argc >= 2 && !strcmp(argv[1], "make_absolute_path")) { + if (argc >= 2 && !strcmp(argv[1], "real_path")) { while (argc > 2) { - puts(make_absolute_path(argv[2])); + puts(real_path(argv[2])); argc--; argv++; } diff --git a/test-run-command.c b/test-run-command.c index 0612bfa7cd..37918e15f5 100644 --- a/test-run-command.c +++ b/test-run-command.c @@ -29,6 +29,8 @@ int main(int argc, char **argv) fprintf(stderr, "FAIL %s\n", argv[1]); return 1; } + if (!strcmp(argv[1], "run-command")) + exit(run_command(&proc)); fprintf(stderr, "check usage\n"); return 1; diff --git a/test-subprocess.c b/test-subprocess.c index 667d3e5079..8926bc52a9 100644 --- a/test-subprocess.c +++ b/test-subprocess.c @@ -3,11 +3,10 @@ int main(int argc, char **argv) { - const char *prefix; struct child_process cp; int nogit = 0; - prefix = setup_git_directory_gently(&nogit); + setup_git_directory_gently(&nogit); if (nogit) die("No git repo found"); if (!strcmp(argv[1], "--setup-work-tree")) { diff --git a/test-svn-fe.c b/test-svn-fe.c index 77cf78abcf..b42ba789b1 100644 --- a/test-svn-fe.c +++ b/test-svn-fe.c @@ -9,7 +9,8 @@ int main(int argc, char *argv[]) { if (argc != 2) usage("test-svn-fe <file>"); - svndump_init(argv[1]); + if (svndump_init(argv[1])) + return 1; svndump_read(NULL); svndump_deinit(); svndump_reset(); diff --git a/thread-utils.c b/thread-utils.c index 589f838f82..7f4b76a958 100644 --- a/thread-utils.c +++ b/thread-utils.c @@ -1,5 +1,5 @@ #include "cache.h" -#include <pthread.h> +#include "thread-utils.h" #if defined(hpux) || defined(__hpux) || defined(_hpux) # include <sys/pstat.h> @@ -25,10 +25,10 @@ #include "cache.h" #include "quote.h" -/* Get a trace file descriptor from GIT_TRACE env variable. */ -static int get_trace_fd(int *need_close) +/* Get a trace file descriptor from "key" env variable. */ +static int get_trace_fd(const char *key, int *need_close) { - char *trace = getenv("GIT_TRACE"); + char *trace = getenv(key); if (!trace || !strcmp(trace, "") || !strcmp(trace, "0") || !strcasecmp(trace, "false")) @@ -50,10 +50,10 @@ static int get_trace_fd(int *need_close) return fd; } - fprintf(stderr, "What does '%s' for GIT_TRACE mean?\n", trace); + fprintf(stderr, "What does '%s' for %s mean?\n", trace, key); fprintf(stderr, "If you want to trace into a file, " - "then please set GIT_TRACE to an absolute pathname " - "(starting with /).\n"); + "then please set %s to an absolute pathname " + "(starting with /).\n", key); fprintf(stderr, "Defaulting to tracing on stderr...\n"); return STDERR_FILENO; @@ -62,33 +62,44 @@ static int get_trace_fd(int *need_close) static const char err_msg[] = "Could not trace into fd given by " "GIT_TRACE environment variable"; -void trace_printf(const char *fmt, ...) +void trace_vprintf(const char *key, const char *fmt, va_list ap) { - struct strbuf buf; - va_list ap; - int fd, len, need_close = 0; + struct strbuf buf = STRBUF_INIT; - fd = get_trace_fd(&need_close); - if (!fd) + if (!trace_want(key)) return; set_try_to_free_routine(NULL); /* is never reset */ - strbuf_init(&buf, 64); + strbuf_vaddf(&buf, fmt, ap); + trace_strbuf(key, &buf); + strbuf_release(&buf); +} + +static void trace_printf_key(const char *key, const char *fmt, ...) +{ + va_list ap; va_start(ap, fmt); - len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); + trace_vprintf(key, fmt, ap); va_end(ap); - if (len >= strbuf_avail(&buf)) { - strbuf_grow(&buf, len - strbuf_avail(&buf) + 128); - va_start(ap, fmt); - len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); - va_end(ap); - if (len >= strbuf_avail(&buf)) - die("broken vsnprintf"); - } - strbuf_setlen(&buf, len); +} - write_or_whine_pipe(fd, buf.buf, buf.len, err_msg); - strbuf_release(&buf); +void trace_printf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + trace_vprintf("GIT_TRACE", fmt, ap); + va_end(ap); +} + +void trace_strbuf(const char *key, const struct strbuf *buf) +{ + int fd, need_close = 0; + + fd = get_trace_fd(key, &need_close); + if (!fd) + return; + + write_or_whine_pipe(fd, buf->buf, buf->len, err_msg); if (need_close) close(fd); @@ -96,28 +107,18 @@ void trace_printf(const char *fmt, ...) void trace_argv_printf(const char **argv, const char *fmt, ...) { - struct strbuf buf; + struct strbuf buf = STRBUF_INIT; va_list ap; - int fd, len, need_close = 0; + int fd, need_close = 0; - fd = get_trace_fd(&need_close); + fd = get_trace_fd("GIT_TRACE", &need_close); if (!fd) return; set_try_to_free_routine(NULL); /* is never reset */ - strbuf_init(&buf, 64); va_start(ap, fmt); - len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); + strbuf_vaddf(&buf, fmt, ap); va_end(ap); - if (len >= strbuf_avail(&buf)) { - strbuf_grow(&buf, len - strbuf_avail(&buf) + 128); - va_start(ap, fmt); - len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap); - va_end(ap); - if (len >= strbuf_avail(&buf)) - die("broken vsnprintf"); - } - strbuf_setlen(&buf, len); sq_quote_argv(&buf, argv, 0); strbuf_addch(&buf, '\n'); @@ -154,12 +155,11 @@ static const char *quote_crnl(const char *path) /* FIXME: move prefix to startup_info struct and get rid of this arg */ void trace_repo_setup(const char *prefix) { + static const char *key = "GIT_TRACE_SETUP"; const char *git_work_tree; char cwd[PATH_MAX]; - char *trace = getenv("GIT_TRACE"); - if (!trace || !strcmp(trace, "") || - !strcmp(trace, "0") || !strcasecmp(trace, "false")) + if (!trace_want(key)) return; if (!getcwd(cwd, PATH_MAX)) @@ -171,8 +171,18 @@ void trace_repo_setup(const char *prefix) if (!prefix) prefix = "(null)"; - trace_printf("setup: git_dir: %s\n", quote_crnl(get_git_dir())); - trace_printf("setup: worktree: %s\n", quote_crnl(git_work_tree)); - trace_printf("setup: cwd: %s\n", quote_crnl(cwd)); - trace_printf("setup: prefix: %s\n", quote_crnl(prefix)); + trace_printf_key(key, "setup: git_dir: %s\n", quote_crnl(get_git_dir())); + trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tree)); + trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd)); + trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix)); +} + +int trace_want(const char *key) +{ + const char *trace = getenv(key); + + if (!trace || !strcmp(trace, "") || + !strcmp(trace, "0") || !strcasecmp(trace, "false")) + return 0; + return 1; } diff --git a/transport-helper.c b/transport-helper.c index 4e4754c32b..660147f9e9 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -12,8 +12,7 @@ static int debug; -struct helper_data -{ +struct helper_data { const char *name; struct child_process *helper; FILE *out; @@ -77,7 +76,7 @@ static void write_constant(int fd, const char *str) die_errno("Full write to remote helper failed"); } -const char *remove_ext_force(const char *url) +static const char *remove_ext_force(const char *url) { if (url) { const char *colon = strchr(url, ':'); @@ -562,10 +561,9 @@ static int push_refs_with_push(struct transport *transport, int mirror = flags & TRANSPORT_PUSH_MIRROR; struct helper_data *data = transport->data; struct strbuf buf = STRBUF_INIT; - struct child_process *helper; struct ref *ref; - helper = get_helper(transport); + get_helper(transport); if (!data->push) return 1; @@ -973,7 +971,7 @@ static int udt_do_read(struct unidirectional_transfer *t) */ static int udt_do_write(struct unidirectional_transfer *t) { - size_t bytes; + ssize_t bytes; if (t->bufuse == 0) return 0; /* Nothing to write. */ diff --git a/transport.c b/transport.c index 0078660611..c9c8056f9d 100644 --- a/transport.c +++ b/transport.c @@ -156,7 +156,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs, continue; if (!ref->peer_ref) continue; - if (!ref->new_sha1 || is_null_sha1(ref->new_sha1)) + if (is_null_sha1(ref->new_sha1)) continue; /* Follow symbolic refs (mainly for HEAD). */ @@ -192,7 +192,7 @@ static const char *rsync_url(const char *url) static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; - struct ref dummy = {0}, *tail = &dummy; + struct ref dummy = {NULL}, *tail = &dummy; struct child_process rsync; const char *args[5]; int temp_dir_len; @@ -1189,3 +1189,51 @@ char *transport_anonymize_url(const char *url) literal_copy: return xstrdup(url); } + +struct alternate_refs_data { + alternate_ref_fn *fn; + void *data; +}; + +static int refs_from_alternate_cb(struct alternate_object_database *e, + void *data) +{ + char *other; + size_t len; + struct remote *remote; + struct transport *transport; + const struct ref *extra; + struct alternate_refs_data *cb = data; + + e->name[-1] = '\0'; + other = xstrdup(real_path(e->base)); + e->name[-1] = '/'; + len = strlen(other); + + while (other[len-1] == '/') + other[--len] = '\0'; + if (len < 8 || memcmp(other + len - 8, "/objects", 8)) + return 0; + /* Is this a git repository with refs? */ + memcpy(other + len - 8, "/refs", 6); + if (!is_directory(other)) + return 0; + other[len - 8] = '\0'; + remote = remote_get(other); + transport = transport_get(remote, other); + for (extra = transport_get_remote_refs(transport); + extra; + extra = extra->next) + cb->fn(extra, cb->data); + transport_disconnect(transport); + free(other); + return 0; +} + +void for_each_alternate_ref(alternate_ref_fn fn, void *data) +{ + struct alternate_refs_data cb; + cb.fn = fn; + cb.data = data; + foreach_alt_odb(refs_from_alternate_cb, &cb); +} diff --git a/transport.h b/transport.h index e803c0e7ba..161d724bba 100644 --- a/transport.h +++ b/transport.h @@ -166,4 +166,7 @@ int transport_refs_pushed(struct ref *ref); void transport_print_push_status(const char *dest, struct ref *refs, int verbose, int porcelain, int *nonfastforward); +typedef void alternate_ref_fn(const struct ref *, void *); +extern void for_each_alternate_ref(alternate_ref_fn, void *); + #endif diff --git a/tree-diff.c b/tree-diff.c index 12c9a88884..3f4072525b 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -6,34 +6,17 @@ #include "diffcore.h" #include "tree.h" -static char *malloc_base(const char *base, int baselen, const char *path, int pathlen) -{ - char *newbase = xmalloc(baselen + pathlen + 2); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, path, pathlen); - memcpy(newbase + baselen + pathlen, "/", 2); - return newbase; -} - -static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen) -{ - char *fullname = xmalloc(baselen + pathlen + 1); - memcpy(fullname, base, baselen); - memcpy(fullname + baselen, path, pathlen); - fullname[baselen + pathlen] = 0; - return fullname; -} +static void show_entry(struct diff_options *opt, const char *prefix, + struct tree_desc *desc, struct strbuf *base); -static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, - const char *base, int baselen); - -static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt) +static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, + struct strbuf *base, struct diff_options *opt) { unsigned mode1, mode2; const char *path1, *path2; const unsigned char *sha1, *sha2; int cmp, pathlen1, pathlen2; - char *fullname; + int old_baselen = base->len; sha1 = tree_entry_extract(t1, &path1, &mode1); sha2 = tree_entry_extract(t2, &path2, &mode2); @@ -42,11 +25,11 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const pathlen2 = tree_entry_len(path2, sha2); cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2); if (cmp < 0) { - show_entry(opt, "-", t1, base, baselen); + show_entry(opt, "-", t1, base); return -1; } if (cmp > 0) { - show_entry(opt, "+", t2, base, baselen); + show_entry(opt, "+", t2, base); return 1; } if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2) @@ -57,182 +40,57 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const * file, we need to consider it a remove and an add. */ if (S_ISDIR(mode1) != S_ISDIR(mode2)) { - show_entry(opt, "-", t1, base, baselen); - show_entry(opt, "+", t2, base, baselen); + show_entry(opt, "-", t1, base); + show_entry(opt, "+", t2, base); return 0; } + strbuf_add(base, path1, pathlen1); if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) { - int retval; - char *newbase = malloc_base(base, baselen, path1, pathlen1); if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { - newbase[baselen + pathlen1] = 0; opt->change(opt, mode1, mode2, - sha1, sha2, newbase, 0, 0); - newbase[baselen + pathlen1] = '/'; + sha1, sha2, base->buf, 0, 0); } - retval = diff_tree_sha1(sha1, sha2, newbase, opt); - free(newbase); - return retval; + strbuf_addch(base, '/'); + diff_tree_sha1(sha1, sha2, base->buf, opt); + } else { + opt->change(opt, mode1, mode2, sha1, sha2, base->buf, 0, 0); } - - fullname = malloc_fullname(base, baselen, path1, pathlen1); - opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0); - free(fullname); + strbuf_setlen(base, old_baselen); return 0; } -/* - * Is a tree entry interesting given the pathspec we have? - * - * Pre-condition: baselen == 0 || base[baselen-1] == '/' - * - * Return: - * - 2 for "yes, and all subsequent entries will be" - * - 1 for yes - * - zero for no - * - negative for "no, and no subsequent entries will be either" - */ -static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt) -{ - const char *path; - const unsigned char *sha1; - unsigned mode; - int i; - int pathlen; - int never_interesting = -1; - - if (!opt->nr_paths) - return 2; - - sha1 = tree_entry_extract(desc, &path, &mode); - - pathlen = tree_entry_len(path, sha1); - - for (i = 0; i < opt->nr_paths; i++) { - const char *match = opt->paths[i]; - int matchlen = opt->pathlens[i]; - int m = -1; /* signals that we haven't called strncmp() */ - - if (baselen >= matchlen) { - /* If it doesn't match, move along... */ - if (strncmp(base, match, matchlen)) - continue; - - /* - * If the base is a subdirectory of a path which - * was specified, all of them are interesting. - */ - if (!matchlen || - base[matchlen] == '/' || - match[matchlen - 1] == '/') - return 2; - - /* Just a random prefix match */ - continue; - } - - /* Does the base match? */ - if (strncmp(base, match, baselen)) - continue; - - match += baselen; - matchlen -= baselen; - - if (never_interesting) { - /* - * We have not seen any match that sorts later - * than the current path. - */ - - /* - * Does match sort strictly earlier than path - * with their common parts? - */ - m = strncmp(match, path, - (matchlen < pathlen) ? matchlen : pathlen); - if (m < 0) - continue; - - /* - * If we come here even once, that means there is at - * least one pathspec that would sort equal to or - * later than the path we are currently looking at. - * In other words, if we have never reached this point - * after iterating all pathspecs, it means all - * pathspecs are either outside of base, or inside the - * base but sorts strictly earlier than the current - * one. In either case, they will never match the - * subsequent entries. In such a case, we initialized - * the variable to -1 and that is what will be - * returned, allowing the caller to terminate early. - */ - never_interesting = 0; - } - - if (pathlen > matchlen) - continue; - - if (matchlen > pathlen) { - if (match[pathlen] != '/') - continue; - if (!S_ISDIR(mode)) - continue; - } - - if (m == -1) - /* - * we cheated and did not do strncmp(), so we do - * that here. - */ - m = strncmp(match, path, pathlen); - - /* - * If common part matched earlier then it is a hit, - * because we rejected the case where path is not a - * leading directory and is shorter than match. - */ - if (!m) - return 1; - } - return never_interesting; /* No matches */ -} - /* A whole sub-tree went away or appeared */ -static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen) +static void show_tree(struct diff_options *opt, const char *prefix, + struct tree_desc *desc, struct strbuf *base) { - int all_interesting = 0; - while (desc->size) { - int show; - - if (all_interesting) - show = 1; - else { - show = tree_entry_interesting(desc, base, baselen, - opt); - if (show == 2) - all_interesting = 1; + int match = 0; + for (; desc->size; update_tree_entry(desc)) { + if (match != 2) { + match = tree_entry_interesting(&desc->entry, base, 0, + &opt->pathspec); + if (match < 0) + break; + if (match == 0) + continue; } - if (show < 0) - break; - if (show) - show_entry(opt, prefix, desc, base, baselen); - update_tree_entry(desc); + show_entry(opt, prefix, desc, base); } } /* A file entry went away or appeared */ -static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, - const char *base, int baselen) +static void show_entry(struct diff_options *opt, const char *prefix, + struct tree_desc *desc, struct strbuf *base) { unsigned mode; const char *path; const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode); int pathlen = tree_entry_len(path, sha1); + int old_baselen = base->len; + strbuf_add(base, path, pathlen); if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) { enum object_type type; - char *newbase = malloc_base(base, baselen, path, pathlen); struct tree_desc inner; void *tree; unsigned long size; @@ -241,72 +99,69 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree if (!tree || type != OBJ_TREE) die("corrupt tree sha %s", sha1_to_hex(sha1)); - if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { - newbase[baselen + pathlen] = 0; - opt->add_remove(opt, *prefix, mode, sha1, newbase, 0); - newbase[baselen + pathlen] = '/'; - } + if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) + opt->add_remove(opt, *prefix, mode, sha1, base->buf, 0); - init_tree_desc(&inner, tree, size); - show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen); + strbuf_addch(base, '/'); + init_tree_desc(&inner, tree, size); + show_tree(opt, prefix, &inner, base); free(tree); - free(newbase); - } else { - char *fullname = malloc_fullname(base, baselen, path, pathlen); - opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0); - free(fullname); - } + } else + opt->add_remove(opt, prefix[0], mode, sha1, base->buf, 0); + + strbuf_setlen(base, old_baselen); } -static void skip_uninteresting(struct tree_desc *t, const char *base, int baselen, struct diff_options *opt, int *all_interesting) +static void skip_uninteresting(struct tree_desc *t, struct strbuf *base, + struct diff_options *opt, int *match) { while (t->size) { - int show = tree_entry_interesting(t, base, baselen, opt); - if (show == 2) - *all_interesting = 1; - if (!show) { - update_tree_entry(t); - continue; + *match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec); + if (*match) { + if (*match < 0) + t->size = 0; + break; } - /* Skip it all? */ - if (show < 0) - t->size = 0; - return; + update_tree_entry(t); } } -int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) +int diff_tree(struct tree_desc *t1, struct tree_desc *t2, + const char *base_str, struct diff_options *opt) { - int baselen = strlen(base); - int all_t1_interesting = 0; - int all_t2_interesting = 0; + struct strbuf base; + int baselen = strlen(base_str); + int t1_match = 0, t2_match = 0; + + /* Enable recursion indefinitely */ + opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE); + opt->pathspec.max_depth = -1; + + strbuf_init(&base, PATH_MAX); + strbuf_add(&base, base_str, baselen); for (;;) { if (DIFF_OPT_TST(opt, QUICK) && DIFF_OPT_TST(opt, HAS_CHANGES)) break; - if (opt->nr_paths) { - if (!all_t1_interesting) - skip_uninteresting(t1, base, baselen, opt, - &all_t1_interesting); - if (!all_t2_interesting) - skip_uninteresting(t2, base, baselen, opt, - &all_t2_interesting); + if (opt->pathspec.nr) { + skip_uninteresting(t1, &base, opt, &t1_match); + skip_uninteresting(t2, &base, opt, &t2_match); } if (!t1->size) { if (!t2->size) break; - show_entry(opt, "+", t2, base, baselen); + show_entry(opt, "+", t2, &base); update_tree_entry(t2); continue; } if (!t2->size) { - show_entry(opt, "-", t1, base, baselen); + show_entry(opt, "-", t1, &base); update_tree_entry(t1); continue; } - switch (compare_tree_entry(t1, t2, base, baselen, opt)) { + switch (compare_tree_entry(t1, t2, &base, opt)) { case -1: update_tree_entry(t1); continue; @@ -319,6 +174,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru } die("git diff-tree: internal error"); } + + strbuf_release(&base); return 0; } @@ -349,7 +206,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co DIFF_OPT_SET(&diff_opts, RECURSIVE); 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.single_follow = opt->pathspec.raw[0]; diff_opts.break_opt = opt->break_opt; paths[0] = NULL; diff_tree_setup_paths(paths, &diff_opts); @@ -369,15 +226,16 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co * diff_queued_diff, we will also use that as the path in * the future! */ - if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) { + if ((p->status == 'R' || p->status == 'C') && + !strcmp(p->two->path, opt->pathspec.raw[0])) { /* Switch the file-pairs around */ q->queue[i] = choice; choice = p; /* Update the path we use from now on.. */ diff_tree_release_paths(opt); - opt->paths[0] = xstrdup(p->one->path); - diff_tree_setup_paths(opt->paths, opt); + opt->pathspec.raw[0] = xstrdup(p->one->path); + diff_tree_setup_paths(opt->pathspec.raw, opt); /* * The caller expects us to return a set of vanilla @@ -452,36 +310,12 @@ int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_ return retval; } -static int count_paths(const char **paths) -{ - int i = 0; - while (*paths++) - i++; - return i; -} - void diff_tree_release_paths(struct diff_options *opt) { - free(opt->pathlens); + free_pathspec(&opt->pathspec); } void diff_tree_setup_paths(const char **p, struct diff_options *opt) { - opt->nr_paths = 0; - opt->pathlens = NULL; - opt->paths = NULL; - - if (p) { - int i; - - opt->paths = p; - opt->nr_paths = count_paths(p); - if (opt->nr_paths == 0) { - opt->pathlens = NULL; - return; - } - opt->pathlens = xmalloc(opt->nr_paths * sizeof(int)); - for (i=0; i < opt->nr_paths; i++) - opt->pathlens[i] = strlen(p[i]); - } + init_pathspec(&opt->pathspec, p); } diff --git a/tree-walk.c b/tree-walk.c index a9bbf4e235..33f749e1e7 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -1,6 +1,7 @@ #include "cache.h" #include "tree-walk.h" #include "unpack-trees.h" +#include "dir.h" #include "tree.h" static const char *get_mode(const char *str, unsigned int *modep) @@ -455,3 +456,186 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch free(tree); return retval; } + +static int match_entry(const struct name_entry *entry, int pathlen, + const char *match, int matchlen, + int *never_interesting) +{ + int m = -1; /* signals that we haven't called strncmp() */ + + if (*never_interesting) { + /* + * We have not seen any match that sorts later + * than the current path. + */ + + /* + * Does match sort strictly earlier than path + * with their common parts? + */ + m = strncmp(match, entry->path, + (matchlen < pathlen) ? matchlen : pathlen); + if (m < 0) + return 0; + + /* + * If we come here even once, that means there is at + * least one pathspec that would sort equal to or + * later than the path we are currently looking at. + * In other words, if we have never reached this point + * after iterating all pathspecs, it means all + * pathspecs are either outside of base, or inside the + * base but sorts strictly earlier than the current + * one. In either case, they will never match the + * subsequent entries. In such a case, we initialized + * the variable to -1 and that is what will be + * returned, allowing the caller to terminate early. + */ + *never_interesting = 0; + } + + if (pathlen > matchlen) + return 0; + + if (matchlen > pathlen) { + if (match[pathlen] != '/') + return 0; + if (!S_ISDIR(entry->mode)) + return 0; + } + + if (m == -1) + /* + * we cheated and did not do strncmp(), so we do + * that here. + */ + m = strncmp(match, entry->path, pathlen); + + /* + * If common part matched earlier then it is a hit, + * because we rejected the case where path is not a + * leading directory and is shorter than match. + */ + if (!m) + return 1; + + return 0; +} + +static int match_dir_prefix(const char *base, int baselen, + const char *match, int matchlen) +{ + if (strncmp(base, match, matchlen)) + return 0; + + /* + * If the base is a subdirectory of a path which + * was specified, all of them are interesting. + */ + if (!matchlen || + base[matchlen] == '/' || + match[matchlen - 1] == '/') + return 1; + + /* Just a random prefix match */ + return 0; +} + +/* + * Is a tree entry interesting given the pathspec we have? + * + * Pre-condition: either baselen == base_offset (i.e. empty path) + * or base[baselen-1] == '/' (i.e. with trailing slash). + * + * Return: + * - 2 for "yes, and all subsequent entries will be" + * - 1 for yes + * - zero for no + * - negative for "no, and no subsequent entries will be either" + */ +int tree_entry_interesting(const struct name_entry *entry, + struct strbuf *base, int base_offset, + const struct pathspec *ps) +{ + int i; + int pathlen, baselen = base->len - base_offset; + int never_interesting = ps->has_wildcard ? 0 : -1; + + if (!ps->nr) { + if (!ps->recursive || ps->max_depth == -1) + return 2; + return !!within_depth(base->buf + base_offset, baselen, + !!S_ISDIR(entry->mode), + ps->max_depth); + } + + pathlen = tree_entry_len(entry->path, entry->sha1); + + for (i = ps->nr - 1; i >= 0; i--) { + const struct pathspec_item *item = ps->items+i; + const char *match = item->match; + const char *base_str = base->buf + base_offset; + int matchlen = item->len; + + if (baselen >= matchlen) { + /* If it doesn't match, move along... */ + if (!match_dir_prefix(base_str, baselen, match, matchlen)) + goto match_wildcards; + + if (!ps->recursive || ps->max_depth == -1) + return 2; + + return !!within_depth(base_str + matchlen + 1, + baselen - matchlen - 1, + !!S_ISDIR(entry->mode), + ps->max_depth); + } + + /* Does the base match? */ + if (!strncmp(base_str, match, baselen)) { + if (match_entry(entry, pathlen, + match + baselen, matchlen - baselen, + &never_interesting)) + return 1; + + if (ps->items[i].use_wildcard) { + if (!fnmatch(match + baselen, entry->path, 0)) + return 1; + + /* + * Match all directories. We'll try to + * match files later on. + */ + if (ps->recursive && S_ISDIR(entry->mode)) + return 1; + } + + continue; + } + +match_wildcards: + if (!ps->items[i].use_wildcard) + continue; + + /* + * Concatenate base and entry->path into one and do + * fnmatch() on it. + */ + + strbuf_add(base, entry->path, pathlen); + + if (!fnmatch(match, base->buf + base_offset, 0)) { + strbuf_setlen(base, base_offset + baselen); + return 1; + } + strbuf_setlen(base, base_offset + baselen); + + /* + * Match all directories. We'll try to match files + * later on. + */ + if (ps->recursive && S_ISDIR(entry->mode)) + return 1; + } + return never_interesting; /* No matches */ +} diff --git a/tree-walk.h b/tree-walk.h index 7e3e0b5ad1..39524b7dba 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -60,4 +60,6 @@ static inline int traverse_path_len(const struct traverse_info *info, const stru return info->pathlen + tree_entry_len(n->path, n->sha1); } +extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps); + #endif @@ -45,62 +45,14 @@ static int read_one_entry_quick(const unsigned char *sha1, const char *base, int ADD_CACHE_JUST_APPEND); } -static int match_tree_entry(const char *base, int baselen, const char *path, unsigned int mode, const char **paths) -{ - const char *match; - int pathlen; - - if (!paths) - return 1; - pathlen = strlen(path); - while ((match = *paths++) != NULL) { - int matchlen = strlen(match); - - if (baselen >= matchlen) { - /* If it doesn't match, move along... */ - if (strncmp(base, match, matchlen)) - continue; - /* pathspecs match only at the directory boundaries */ - if (!matchlen || - baselen == matchlen || - base[matchlen] == '/' || - match[matchlen - 1] == '/') - return 1; - continue; - } - - /* Does the base match? */ - if (strncmp(base, match, baselen)) - continue; - - match += baselen; - matchlen -= baselen; - - if (pathlen > matchlen) - continue; - - if (matchlen > pathlen) { - if (match[pathlen] != '/') - continue; - if (!S_ISDIR(mode)) - continue; - } - - if (strncmp(path, match, pathlen)) - continue; - - return 1; - } - return 0; -} - -int read_tree_recursive(struct tree *tree, - const char *base, int baselen, - int stage, const char **match, - read_tree_fn_t fn, void *context) +static int read_tree_1(struct tree *tree, struct strbuf *base, + int stage, struct pathspec *pathspec, + read_tree_fn_t fn, void *context) { struct tree_desc desc; struct name_entry entry; + unsigned char sha1[20]; + int len, retval = 0, oldlen = base->len; if (parse_tree(tree)) return -1; @@ -108,10 +60,16 @@ int read_tree_recursive(struct tree *tree, init_tree_desc(&desc, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { - if (!match_tree_entry(base, baselen, entry.path, entry.mode, match)) - continue; + if (retval != 2) { + retval = tree_entry_interesting(&entry, base, 0, pathspec); + if (retval < 0) + break; + if (retval == 0) + continue; + } - switch (fn(entry.sha1, base, baselen, entry.path, entry.mode, stage, context)) { + switch (fn(entry.sha1, base->buf, base->len, + entry.path, entry.mode, stage, context)) { case 0: continue; case READ_TREE_RECURSIVE: @@ -119,56 +77,55 @@ int read_tree_recursive(struct tree *tree, default: return -1; } - if (S_ISDIR(entry.mode)) { - int retval; - char *newbase; - unsigned int pathlen = tree_entry_len(entry.path, entry.sha1); - - newbase = xmalloc(baselen + 1 + pathlen); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, entry.path, pathlen); - newbase[baselen + pathlen] = '/'; - retval = read_tree_recursive(lookup_tree(entry.sha1), - newbase, - baselen + pathlen + 1, - stage, match, fn, context); - free(newbase); - if (retval) - return -1; - continue; - } else if (S_ISGITLINK(entry.mode)) { - int retval; - struct strbuf path; - unsigned int entrylen; - struct commit *commit; - entrylen = tree_entry_len(entry.path, entry.sha1); - strbuf_init(&path, baselen + entrylen + 1); - strbuf_add(&path, base, baselen); - strbuf_add(&path, entry.path, entrylen); - strbuf_addch(&path, '/'); + if (S_ISDIR(entry.mode)) + hashcpy(sha1, entry.sha1); + else if (S_ISGITLINK(entry.mode)) { + struct commit *commit; commit = lookup_commit(entry.sha1); if (!commit) - die("Commit %s in submodule path %s not found", - sha1_to_hex(entry.sha1), path.buf); + die("Commit %s in submodule path %s%s not found", + sha1_to_hex(entry.sha1), + base->buf, entry.path); if (parse_commit(commit)) - die("Invalid commit %s in submodule path %s", - sha1_to_hex(entry.sha1), path.buf); - - retval = read_tree_recursive(commit->tree, - path.buf, path.len, - stage, match, fn, context); - strbuf_release(&path); - if (retval) - return -1; - continue; + die("Invalid commit %s in submodule path %s%s", + sha1_to_hex(entry.sha1), + base->buf, entry.path); + + hashcpy(sha1, commit->tree->object.sha1); } + else + continue; + + len = tree_entry_len(entry.path, entry.sha1); + strbuf_add(base, entry.path, len); + strbuf_addch(base, '/'); + retval = read_tree_1(lookup_tree(sha1), + base, stage, pathspec, + fn, context); + strbuf_setlen(base, oldlen); + if (retval) + return -1; } return 0; } +int read_tree_recursive(struct tree *tree, + const char *base, int baselen, + int stage, struct pathspec *pathspec, + read_tree_fn_t fn, void *context) +{ + struct strbuf sb = STRBUF_INIT; + int ret; + + strbuf_add(&sb, base, baselen); + ret = read_tree_1(tree, &sb, stage, pathspec, fn, context); + strbuf_release(&sb); + return ret; +} + static int cmp_cache_name_compare(const void *a_, const void *b_) { const struct cache_entry *ce1, *ce2; @@ -179,7 +136,7 @@ static int cmp_cache_name_compare(const void *a_, const void *b_) ce2->name, ce2->ce_flags); } -int read_tree(struct tree *tree, int stage, const char **match) +int read_tree(struct tree *tree, int stage, struct pathspec *match) { read_tree_fn_t fn = NULL; int i, err; @@ -25,9 +25,9 @@ typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const ch extern int read_tree_recursive(struct tree *tree, const char *base, int baselen, - int stage, const char **match, + int stage, struct pathspec *pathspec, read_tree_fn_t fn, void *context); -extern int read_tree(struct tree *tree, int stage, const char **paths); +extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec); #endif /* TREE_H */ diff --git a/unpack-trees.c b/unpack-trees.c index 1ca41b1a69..07f8364244 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -16,7 +16,7 @@ * situation better. See how "git checkout" and "git merge" replaces * them using setup_unpack_trees_porcelain(), for example. */ -const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = { +static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = { /* ERROR_WOULD_OVERWRITE */ "Entry '%s' would be overwritten by merge. Cannot merge.", @@ -203,7 +203,7 @@ static int check_updates(struct unpack_trees_options *o) if (ce->ce_flags & CE_WT_REMOVE) { display_progress(progress, ++cnt); - if (o->update) + if (o->update && !o->dry_run) unlink_entry(ce); continue; } @@ -217,7 +217,7 @@ static int check_updates(struct unpack_trees_options *o) if (ce->ce_flags & CE_UPDATE) { display_progress(progress, ++cnt); ce->ce_flags &= ~CE_UPDATE; - if (o->update) { + if (o->update && !o->dry_run) { errs |= checkout_entry(ce, &state, NULL); } } @@ -381,7 +381,7 @@ 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] = { NULL }; + struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; int ret; src[0] = ce; @@ -427,7 +427,10 @@ static int switch_cache_bottom(struct traverse_info *info) return ret; } -static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info) +static int traverse_trees_recursive(int n, unsigned long dirmask, + unsigned long df_conflicts, + struct name_entry *names, + struct traverse_info *info) { int i, ret, bottom; struct tree_desc t[MAX_UNPACK_TREES]; @@ -811,43 +814,45 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str return mask; } +static int clear_ce_flags_1(struct cache_entry **cache, int nr, + char *prefix, int prefix_len, + int select_mask, int clear_mask, + struct exclude_list *el, int defval); + /* Whole directory matching */ static int clear_ce_flags_dir(struct cache_entry **cache, int nr, char *prefix, int prefix_len, char *basename, int select_mask, int clear_mask, - struct exclude_list *el) + struct exclude_list *el, int defval) { - struct cache_entry **cache_end = cache + nr; + struct cache_entry **cache_end; int dtype = DT_DIR; int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el); prefix[prefix_len++] = '/'; - /* included, no clearing for any entries under this directory */ - if (!ret) { - for (; cache != cache_end; cache++) { - struct cache_entry *ce = *cache; - if (strncmp(ce->name, prefix, prefix_len)) - break; - } - return nr - (cache_end - cache); - } + /* If undecided, use matching result of parent dir in defval */ + if (ret < 0) + ret = defval; - /* excluded, clear all selected entries under this directory. */ - if (ret == 1) { - for (; cache != cache_end; cache++) { - struct cache_entry *ce = *cache; - if (select_mask && !(ce->ce_flags & select_mask)) - continue; - if (strncmp(ce->name, prefix, prefix_len)) - break; - ce->ce_flags &= ~clear_mask; - } - return nr - (cache_end - cache); + for (cache_end = cache; cache_end != cache + nr; cache_end++) { + struct cache_entry *ce = *cache_end; + if (strncmp(ce->name, prefix, prefix_len)) + break; } - return 0; + /* + * TODO: check el, if there are no patterns that may conflict + * with ret (iow, we know in advance the incl/excl + * decision for the entire directory), clear flag here without + * calling clear_ce_flags_1(). That function will call + * the expensive excluded_from_list() on every entry. + */ + return clear_ce_flags_1(cache, cache_end - cache, + prefix, prefix_len, + select_mask, clear_mask, + el, ret); } /* @@ -868,7 +873,7 @@ static int clear_ce_flags_dir(struct cache_entry **cache, int nr, static int clear_ce_flags_1(struct cache_entry **cache, int nr, char *prefix, int prefix_len, int select_mask, int clear_mask, - struct exclude_list *el) + struct exclude_list *el, int defval) { struct cache_entry **cache_end = cache + nr; @@ -879,7 +884,7 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr, while(cache != cache_end) { struct cache_entry *ce = *cache; const char *name, *slash; - int len, dtype; + int len, dtype, ret; if (select_mask && !(ce->ce_flags & select_mask)) { cache++; @@ -908,7 +913,7 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr, prefix, prefix_len + len, prefix + prefix_len, select_mask, clear_mask, - el); + el, defval); /* clear_c_f_dir eats a whole dir already? */ if (processed) { @@ -919,13 +924,16 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr, prefix[prefix_len + len++] = '/'; cache += clear_ce_flags_1(cache, cache_end - cache, prefix, prefix_len + len, - select_mask, clear_mask, el); + select_mask, clear_mask, el, defval); continue; } /* Non-directory */ dtype = ce_to_dtype(ce); - if (excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el) > 0) + ret = excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el); + if (ret < 0) + ret = defval; + if (ret > 0) ce->ce_flags &= ~clear_mask; cache++; } @@ -940,7 +948,7 @@ static int clear_ce_flags(struct cache_entry **cache, int nr, return clear_ce_flags_1(cache, nr, prefix, 0, select_mask, clear_mask, - el); + el, 0); } /* @@ -1105,6 +1113,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } if (o->result.cache_nr && empty_worktree) { + /* dubious---why should this fail??? */ ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory"); goto done; } @@ -1374,16 +1383,22 @@ static int verify_absent_1(struct cache_entry *ce, char path[PATH_MAX + 1]; memcpy(path, ce->name, len); path[len] = 0; - lstat(path, &st); + if (lstat(path, &st)) + return error("cannot stat '%s': %s", path, + strerror(errno)); return check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st, error_type, o); - } else if (!lstat(ce->name, &st)) + } else if (lstat(ce->name, &st)) { + if (errno != ENOENT) + return error("cannot stat '%s': %s", ce->name, + strerror(errno)); + return 0; + } else { return check_ok_to_remove(ce->name, ce_namelen(ce), - ce_to_dtype(ce), ce, &st, - error_type, o); - - return 0; + ce_to_dtype(ce), ce, &st, + error_type, o); + } } static int verify_absent(struct cache_entry *ce, diff --git a/unpack-trees.h b/unpack-trees.h index cd11a08365..64f02cb03a 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -46,7 +46,8 @@ struct unpack_trees_options { debug_unpack, skip_sparse_checkout, gently, - show_all_errors; + show_all_errors, + dry_run; const char *prefix; int cache_bottom; struct dir_struct *dir; diff --git a/upload-pack.c b/upload-pack.c index b40a43f27d..ce5cbbea6b 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -27,6 +27,7 @@ static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=< static unsigned long oldest_have; static int multi_ack, nr_our_refs; +static int no_done; static int use_thin_pack, use_ofs_delta, use_include_tag; static int no_progress, daemon_mode; static int shallow_nr; @@ -156,15 +157,8 @@ static void create_pack_file(void) const char *argv[10]; int arg = 0; - if (shallow_nr) { - memset(&rev_list, 0, sizeof(rev_list)); - rev_list.proc = do_rev_list; - rev_list.out = -1; - if (start_async(&rev_list)) - die("git upload-pack: unable to fork git-rev-list"); - argv[arg++] = "pack-objects"; - } else { - argv[arg++] = "pack-objects"; + argv[arg++] = "pack-objects"; + if (!shallow_nr) { argv[arg++] = "--revs"; if (create_full_pack) argv[arg++] = "--all"; @@ -182,7 +176,7 @@ static void create_pack_file(void) argv[arg++] = NULL; memset(&pack_objects, 0, sizeof(pack_objects)); - pack_objects.in = shallow_nr ? rev_list.out : -1; + pack_objects.in = -1; pack_objects.out = -1; pack_objects.err = -1; pack_objects.git_cmd = 1; @@ -191,8 +185,14 @@ static void create_pack_file(void) if (start_command(&pack_objects)) die("git upload-pack: unable to fork git-pack-objects"); - /* pass on revisions we (don't) want */ - if (!shallow_nr) { + if (shallow_nr) { + memset(&rev_list, 0, sizeof(rev_list)); + rev_list.proc = do_rev_list; + rev_list.out = pack_objects.in; + if (start_async(&rev_list)) + die("git upload-pack: unable to fork git-rev-list"); + } + else { FILE *pipe_fd = xfdopen(pack_objects.in, "w"); if (!create_full_pack) { int i; @@ -429,6 +429,9 @@ static int get_common_commits(void) static char line[1000]; unsigned char sha1[20]; char last_hex[41]; + int got_common = 0; + int got_other = 0; + int sent_ready = 0; save_commit_buffer = 0; @@ -437,25 +440,40 @@ static int get_common_commits(void) reset_timeout(); if (!len) { + if (multi_ack == 2 && got_common + && !got_other && ok_to_give_up()) { + sent_ready = 1; + packet_write(1, "ACK %s ready\n", last_hex); + } if (have_obj.nr == 0 || multi_ack) packet_write(1, "NAK\n"); + + if (no_done && sent_ready) { + packet_write(1, "ACK %s\n", last_hex); + return 0; + } if (stateless_rpc) exit(0); + got_common = 0; + got_other = 0; continue; } strip(line, len); if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ + got_other = 1; if (multi_ack && ok_to_give_up()) { const char *hex = sha1_to_hex(sha1); - if (multi_ack == 2) + if (multi_ack == 2) { + sent_ready = 1; packet_write(1, "ACK %s ready\n", hex); - else + } else packet_write(1, "ACK %s continue\n", hex); } break; default: + got_common = 1; memcpy(last_hex, sha1_to_hex(sha1), 41); if (multi_ack == 2) packet_write(1, "ACK %s common\n", last_hex); @@ -526,6 +544,8 @@ static void receive_needs(void) multi_ack = 2; else if (strstr(line+45, "multi_ack")) multi_ack = 1; + if (strstr(line+45, "no-done")) + no_done = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; if (strstr(line+45, "ofs-delta")) @@ -626,8 +646,9 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1)); if (capabilities) - packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname, - 0, capabilities); + packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname, + 0, capabilities, + stateless_rpc ? " no-done" : ""); else packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname); capabilities = NULL; @@ -682,6 +703,7 @@ int main(int argc, char **argv) int i; int strict = 0; + packet_trace_identity("upload-pack"); git_extract_argv0_path(argv[0]); read_replace_refs = 0; @@ -1,4 +1,5 @@ #include "cache.h" +#include "url.h" int is_urlschemechar(int first_flag, int ch) { @@ -46,7 +46,7 @@ void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list param die_routine = routine; } -void usagef(const char *err, ...) +void NORETURN usagef(const char *err, ...) { va_list params; @@ -55,12 +55,12 @@ void usagef(const char *err, ...) va_end(params); } -void usage(const char *err) +void NORETURN usage(const char *err) { usagef("%s", err); } -void die(const char *err, ...) +void NORETURN die(const char *err, ...) { va_list params; @@ -69,7 +69,7 @@ void die(const char *err, ...) va_end(params); } -void die_errno(const char *fmt, ...) +void NORETURN die_errno(const char *fmt, ...) { va_list params; char fmt_with_err[1024]; diff --git a/userdiff.c b/userdiff.c index 9ebf231ea5..e55310cd02 100644 --- a/userdiff.c +++ b/userdiff.c @@ -8,9 +8,11 @@ static int ndrivers; static int drivers_alloc; #define PATTERNS(name, pattern, word_regex) \ - { name, NULL, -1, { pattern, REG_EXTENDED }, word_regex } + { name, NULL, -1, { pattern, REG_EXTENDED }, \ + word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" } #define IPATTERN(name, pattern, word_regex) \ - { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, word_regex } + { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, \ + word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" } static struct userdiff_driver builtin_drivers[] = { IPATTERN("fortran", "!^([C*]|[ \t]*!)\n" @@ -24,10 +26,9 @@ IPATTERN("fortran", * Don't worry about format statements without leading digits since * they would have been matched above as a variable anyway. */ "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?" - "|//|\\*\\*|::|[/<>=]=" - "|[^[:space:]]|[\x80-\xff]+"), + "|//|\\*\\*|::|[/<>=]="), PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", - "[^<>= \t]+|[^[:space:]]|[\x80-\xff]+"), + "[^<>= \t]+"), PATTERNS("java", "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$", @@ -35,8 +36,7 @@ PATTERNS("java", "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" "|[-+*/<>%&^|=!]=" - "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|" - "|[^[:space:]]|[\x80-\xff]+"), + "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"), PATTERNS("objc", /* Negate C statements that can look like functions */ "!^[ \t]*(do|for|if|else|return|switch|while)\n" @@ -49,8 +49,7 @@ PATTERNS("objc", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->" - "|[^[:space:]]|[\x80-\xff]+"), + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), PATTERNS("pascal", "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|" "implementation|initialization|finalization)[ \t]*.*)$" @@ -59,13 +58,26 @@ PATTERNS("pascal", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" - "|<>|<=|>=|:=|\\.\\." - "|[^[:space:]]|[\x80-\xff]+"), + "|<>|<=|>=|:=|\\.\\."), PATTERNS("perl", - "^[ \t]*package .*;\n" - "^[ \t]*sub .* \\{\n" - "^[A-Z]+ \\{\n" /* BEGIN, END, ... */ - "^=head[0-9] ", /* POD */ + "^package .*\n" + "^sub [[:alnum:]_':]+[ \t]*" + "(\\([^)]*\\)[ \t]*)?" /* prototype */ + /* + * Attributes. A regex can't count nested parentheses, + * so just slurp up whatever we see, taking care not + * to accept lines like "sub foo; # defined elsewhere". + * + * An attribute could contain a semicolon, but at that + * point it seems reasonable enough to give up. + */ + "(:[^;#]*)?" + "(\\{[ \t]*)?" /* brace can come here or on the next line */ + "(#.*)?$\n" /* comment */ + "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*" + "(\\{[ \t]*)?" /* brace can come here or on the next line */ + "(#.*)?$\n" + "^=head[0-9] .*", /* POD */ /* -- */ "[[:alpha:]_'][[:alnum:]_']*" "|0[xb]?[0-9a-fA-F_]*" @@ -76,33 +88,29 @@ PATTERNS("perl", "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?" "|[-+*/%.^&<>=!|]=" "|=~|!~" - "|<<|<>|<=>|>>" - "|[^[:space:]]"), + "|<<|<>|<=>|>>"), 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]+" - "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->" - "|[^[:space:]]|[\x80-\xff]+"), + "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"), PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?" - "|[^[:space:]]|[\x80-\xff]+"), + "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"), /* -- */ PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$", /* -- */ "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?." - "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~" - "|[^[:space:]]|[\x80-\xff]+"), + "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"), PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", "[={}\"]|[^={}\" \t]+"), PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", - "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+|[^[:space:]]"), + "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"), PATTERNS("cpp", /* Jump targets or access declarations */ "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n" @@ -113,8 +121,7 @@ PATTERNS("cpp", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->" - "|[^[:space:]]|[\x80-\xff]+"), + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), PATTERNS("csharp", /* Keywords */ "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" @@ -129,8 +136,7 @@ PATTERNS("csharp", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->" - "|[^[:space:]]|[\x80-\xff]+"), + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), { "default", NULL, -1, { NULL, 0 } }, }; #undef PATTERNS diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c index 6cfa256a37..99ed70b88a 100644 --- a/vcs-svn/fast_export.c +++ b/vcs-svn/fast_export.c @@ -31,27 +31,30 @@ void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode, } static char gitsvnline[MAX_GITSVN_LINE_LEN]; -void fast_export_commit(uint32_t revision, uint32_t author, char *log, - uint32_t uuid, uint32_t url, +void fast_export_commit(uint32_t revision, const char *author, + const struct strbuf *log, + const char *uuid, const char *url, unsigned long timestamp) { + static const struct strbuf empty = STRBUF_INIT; if (!log) - log = ""; - if (~uuid && ~url) { + log = ∅ + if (*uuid && *url) { snprintf(gitsvnline, MAX_GITSVN_LINE_LEN, "\n\ngit-svn-id: %s@%"PRIu32" %s\n", - pool_fetch(url), revision, pool_fetch(uuid)); + url, revision, uuid); } else { *gitsvnline = '\0'; } printf("commit refs/heads/master\n"); printf("committer %s <%s@%s> %ld +0000\n", - ~author ? pool_fetch(author) : "nobody", - ~author ? pool_fetch(author) : "nobody", - ~uuid ? pool_fetch(uuid) : "local", timestamp); - printf("data %"PRIu32"\n%s%s\n", - (uint32_t) (strlen(log) + strlen(gitsvnline)), - log, gitsvnline); + *author ? author : "nobody", + *author ? author : "nobody", + *uuid ? uuid : "local", timestamp); + printf("data %"PRIuMAX"\n", + (uintmax_t) (log->len + strlen(gitsvnline))); + fwrite(log->buf, log->len, 1, stdout); + printf("%s\n", gitsvnline); if (!first_commit_done) { if (revision > 1) printf("from refs/heads/master^0\n"); @@ -63,14 +66,23 @@ void fast_export_commit(uint32_t revision, uint32_t author, char *log, printf("progress Imported commit %"PRIu32".\n\n", revision); } -void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len) +static void die_short_read(struct line_buffer *input) +{ + if (buffer_ferror(input)) + die_errno("error reading dump file"); + die("invalid dump: unexpected end of file"); +} + +void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, struct line_buffer *input) { if (mode == REPO_MODE_LNK) { /* svn symlink blobs start with "link " */ - buffer_skip_bytes(5); len -= 5; + if (buffer_skip_bytes(input, 5) != 5) + die_short_read(input); } printf("blob\nmark :%"PRIu32"\ndata %"PRIu32"\n", mark, len); - buffer_copy_bytes(len); + if (buffer_copy_bytes(input, len) != len) + die_short_read(input); fputc('\n', stdout); } diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h index 2aaaea53d5..33a8fe996f 100644 --- a/vcs-svn/fast_export.h +++ b/vcs-svn/fast_export.h @@ -1,11 +1,16 @@ #ifndef FAST_EXPORT_H_ #define FAST_EXPORT_H_ +#include "line_buffer.h" +struct strbuf; + void fast_export_delete(uint32_t depth, uint32_t *path); void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode, uint32_t mark); -void fast_export_commit(uint32_t revision, uint32_t author, char *log, - uint32_t uuid, uint32_t url, unsigned long timestamp); -void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len); +void fast_export_commit(uint32_t revision, const char *author, + const struct strbuf *log, const char *uuid, + const char *url, unsigned long timestamp); +void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, + struct line_buffer *input); #endif diff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c index 1543567093..c39038723e 100644 --- a/vcs-svn/line_buffer.c +++ b/vcs-svn/line_buffer.c @@ -5,47 +5,81 @@ #include "git-compat-util.h" #include "line_buffer.h" -#include "obj_pool.h" +#include "strbuf.h" -#define LINE_BUFFER_LEN 10000 #define COPY_BUFFER_LEN 4096 -/* Create memory pool for char sequence of known length */ -obj_pool_gen(blob, char, 4096) +int buffer_init(struct line_buffer *buf, const char *filename) +{ + buf->infile = filename ? fopen(filename, "r") : stdin; + if (!buf->infile) + return -1; + return 0; +} -static char line_buffer[LINE_BUFFER_LEN]; -static char byte_buffer[COPY_BUFFER_LEN]; -static FILE *infile; +int buffer_fdinit(struct line_buffer *buf, int fd) +{ + buf->infile = fdopen(fd, "r"); + if (!buf->infile) + return -1; + return 0; +} -int buffer_init(const char *filename) +int buffer_tmpfile_init(struct line_buffer *buf) { - infile = filename ? fopen(filename, "r") : stdin; - if (!infile) + buf->infile = tmpfile(); + if (!buf->infile) return -1; return 0; } -int buffer_deinit(void) +int buffer_deinit(struct line_buffer *buf) { int err; - if (infile == stdin) - return ferror(infile); - err = ferror(infile); - err |= fclose(infile); + if (buf->infile == stdin) + return ferror(buf->infile); + err = ferror(buf->infile); + err |= fclose(buf->infile); return err; } +FILE *buffer_tmpfile_rewind(struct line_buffer *buf) +{ + rewind(buf->infile); + return buf->infile; +} + +long buffer_tmpfile_prepare_to_read(struct line_buffer *buf) +{ + long pos = ftell(buf->infile); + if (pos < 0) + return error("ftell error: %s", strerror(errno)); + if (fseek(buf->infile, 0, SEEK_SET)) + return error("seek error: %s", strerror(errno)); + return pos; +} + +int buffer_ferror(struct line_buffer *buf) +{ + return ferror(buf->infile); +} + +int buffer_read_char(struct line_buffer *buf) +{ + return fgetc(buf->infile); +} + /* Read a line without trailing newline. */ -char *buffer_read_line(void) +char *buffer_read_line(struct line_buffer *buf) { char *end; - if (!fgets(line_buffer, sizeof(line_buffer), infile)) + if (!fgets(buf->line_buffer, sizeof(buf->line_buffer), buf->infile)) /* Error or data exhausted. */ return NULL; - end = line_buffer + strlen(line_buffer); + end = buf->line_buffer + strlen(buf->line_buffer); if (end[-1] == '\n') end[-1] = '\0'; - else if (feof(infile)) + else if (feof(buf->infile)) ; /* No newline at end of file. That's fine. */ else /* @@ -54,44 +88,43 @@ char *buffer_read_line(void) * but for now let's return an error. */ return NULL; - return line_buffer; + return buf->line_buffer; } -char *buffer_read_string(uint32_t len) +void buffer_read_binary(struct line_buffer *buf, + struct strbuf *sb, uint32_t size) { - char *s; - blob_free(blob_pool.size); - s = blob_pointer(blob_alloc(len + 1)); - s[fread(s, 1, len, infile)] = '\0'; - return ferror(infile) ? NULL : s; + strbuf_fread(sb, size, buf->infile); } -void buffer_copy_bytes(uint32_t len) +off_t buffer_copy_bytes(struct line_buffer *buf, off_t nbytes) { - uint32_t in; - while (len > 0 && !feof(infile) && !ferror(infile)) { - in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN; - in = fread(byte_buffer, 1, in, infile); - len -= in; + char byte_buffer[COPY_BUFFER_LEN]; + off_t done = 0; + while (done < nbytes && !feof(buf->infile) && !ferror(buf->infile)) { + off_t len = nbytes - done; + size_t in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN; + in = fread(byte_buffer, 1, in, buf->infile); + done += in; fwrite(byte_buffer, 1, in, stdout); - if (ferror(stdout)) { - buffer_skip_bytes(len); - return; - } + if (ferror(stdout)) + return done + buffer_skip_bytes(buf, nbytes - done); } + return done; } -void buffer_skip_bytes(uint32_t len) +off_t buffer_skip_bytes(struct line_buffer *buf, off_t nbytes) { - uint32_t in; - while (len > 0 && !feof(infile) && !ferror(infile)) { - in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN; - in = fread(byte_buffer, 1, in, infile); - len -= in; + char byte_buffer[COPY_BUFFER_LEN]; + off_t done = 0; + while (done < nbytes && !feof(buf->infile) && !ferror(buf->infile)) { + off_t len = nbytes - done; + size_t in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN; + done += fread(byte_buffer, 1, in, buf->infile); } + return done; } -void buffer_reset(void) +void buffer_reset(struct line_buffer *buf) { - blob_reset(); } diff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h index 9c78ae11a1..d0b22dda76 100644 --- a/vcs-svn/line_buffer.h +++ b/vcs-svn/line_buffer.h @@ -1,12 +1,31 @@ #ifndef LINE_BUFFER_H_ #define LINE_BUFFER_H_ -int buffer_init(const char *filename); -int buffer_deinit(void); -char *buffer_read_line(void); -char *buffer_read_string(uint32_t len); -void buffer_copy_bytes(uint32_t len); -void buffer_skip_bytes(uint32_t len); -void buffer_reset(void); +#include "strbuf.h" + +#define LINE_BUFFER_LEN 10000 + +struct line_buffer { + char line_buffer[LINE_BUFFER_LEN]; + FILE *infile; +}; +#define LINE_BUFFER_INIT { "", NULL } + +int buffer_init(struct line_buffer *buf, const char *filename); +int buffer_fdinit(struct line_buffer *buf, int fd); +int buffer_deinit(struct line_buffer *buf); +void buffer_reset(struct line_buffer *buf); + +int buffer_tmpfile_init(struct line_buffer *buf); +FILE *buffer_tmpfile_rewind(struct line_buffer *buf); /* prepare to write. */ +long buffer_tmpfile_prepare_to_read(struct line_buffer *buf); + +int buffer_ferror(struct line_buffer *buf); +char *buffer_read_line(struct line_buffer *buf); +int buffer_read_char(struct line_buffer *buf); +void buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, uint32_t len); +/* Returns number of bytes read (not necessarily written). */ +off_t buffer_copy_bytes(struct line_buffer *buf, off_t len); +off_t buffer_skip_bytes(struct line_buffer *buf, off_t len); #endif diff --git a/vcs-svn/line_buffer.txt b/vcs-svn/line_buffer.txt index 8906fb1f50..8e139eb22d 100644 --- a/vcs-svn/line_buffer.txt +++ b/vcs-svn/line_buffer.txt @@ -14,22 +14,46 @@ Calling sequence The calling program: + - initializes a `struct line_buffer` to LINE_BUFFER_INIT - specifies a file to read with `buffer_init` - - processes input with `buffer_read_line`, `buffer_read_string`, - `buffer_skip_bytes`, and `buffer_copy_bytes` + - processes input with `buffer_read_line`, `buffer_skip_bytes`, + and `buffer_copy_bytes` - closes the file with `buffer_deinit`, perhaps to start over and read another file. -Before exiting, the caller can use `buffer_reset` to deallocate -resources for the benefit of profiling tools. +When finished, the caller can use `buffer_reset` to deallocate +resources. + +Using temporary files +--------------------- + +Temporary files provide a place to store data that should not outlive +the calling program. A program + + - initializes a `struct line_buffer` to LINE_BUFFER_INIT + - requests a temporary file with `buffer_tmpfile_init` + - acquires an output handle by calling `buffer_tmpfile_rewind` + - uses standard I/O functions like `fprintf` and `fwrite` to fill + the temporary file + - declares writing is over with `buffer_tmpfile_prepare_to_read` + - can re-read what was written with `buffer_read_line`, + `buffer_copy_bytes`, and so on + - can reuse the temporary file by calling `buffer_tmpfile_rewind` + again + - removes the temporary file with `buffer_deinit`, perhaps to + reuse the line_buffer for some other file. + +When finished, the calling program can use `buffer_reset` to deallocate +resources. Functions --------- -`buffer_init`:: - Open the named file for input. If filename is NULL, - start reading from stdin. On failure, returns -1 (with - errno indicating the nature of the failure). +`buffer_init`, `buffer_fdinit`:: + Open the named file or file descriptor for input. + buffer_init(buf, NULL) prepares to read from stdin. + On failure, returns -1 (with errno indicating the nature + of the failure). `buffer_deinit`:: Stop reading from the current file (closing it unless @@ -40,19 +64,14 @@ Functions Read a line and strip off the trailing newline. On failure or end of file, returns NULL. -`buffer_read_string`:: - Read `len` characters of input or up to the end of the - file, whichever comes first. Returns NULL on error. - Returns whatever characters were read (possibly "") - for end of file. - `buffer_copy_bytes`:: Read `len` bytes of input and dump them to the standard output stream. Returns early for error or end of file. `buffer_skip_bytes`:: Discards `len` bytes from the input stream (stopping early - if necessary because of an error or eof). + if necessary because of an error or eof). Return value is + the number of bytes successfully read. `buffer_reset`:: Deallocates non-static buffers. diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c index e3d1fa3544..a21d89de97 100644 --- a/vcs-svn/repo_tree.c +++ b/vcs-svn/repo_tree.c @@ -38,7 +38,7 @@ static uint32_t mark; static int repo_dirent_name_cmp(const void *a, const void *b); /* Treap for directory entries */ -trp_gen(static, dent_, struct repo_dirent, children, dent, repo_dirent_name_cmp); +trp_gen(static, dent_, struct repo_dirent, children, dent, repo_dirent_name_cmp) uint32_t next_blob_mark(void) { @@ -87,7 +87,8 @@ static struct repo_dir *repo_clone_dir(struct repo_dir *orig_dir) return dir_pointer(new_o); } -static struct repo_dirent *repo_read_dirent(uint32_t revision, uint32_t *path) +static struct repo_dirent *repo_read_dirent(uint32_t revision, + const uint32_t *path) { uint32_t name = 0; struct repo_dirent *key = dent_pointer(dent_alloc(1)); @@ -105,7 +106,7 @@ static struct repo_dirent *repo_read_dirent(uint32_t revision, uint32_t *path) return dent; } -static void repo_write_dirent(uint32_t *path, uint32_t mode, +static void repo_write_dirent(const uint32_t *path, uint32_t mode, uint32_t content_offset, uint32_t del) { uint32_t name, revision, dir_o = ~0, parent_dir_o = ~0; @@ -157,42 +158,37 @@ static void repo_write_dirent(uint32_t *path, uint32_t mode, dent_remove(&dir_pointer(parent_dir_o)->entries, dent); } -uint32_t repo_copy(uint32_t revision, uint32_t *src, uint32_t *dst) +uint32_t repo_read_path(const uint32_t *path) { - uint32_t mode = 0, content_offset = 0; - struct repo_dirent *src_dent; - src_dent = repo_read_dirent(revision, src); - if (src_dent != NULL) { - mode = src_dent->mode; - content_offset = src_dent->content_offset; - repo_write_dirent(dst, mode, content_offset, 0); - } - return mode; + uint32_t content_offset = 0; + struct repo_dirent *dent = repo_read_dirent(active_commit, path); + if (dent != NULL) + content_offset = dent->content_offset; + return content_offset; } -void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark) +uint32_t repo_read_mode(const uint32_t *path) { - repo_write_dirent(path, mode, blob_mark, 0); + struct repo_dirent *dent = repo_read_dirent(active_commit, path); + if (dent == NULL) + die("invalid dump: path to be modified is missing"); + return dent->mode; } -uint32_t repo_replace(uint32_t *path, uint32_t blob_mark) +void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst) { - uint32_t mode = 0; + uint32_t mode = 0, content_offset = 0; struct repo_dirent *src_dent; - src_dent = repo_read_dirent(active_commit, path); + src_dent = repo_read_dirent(revision, src); if (src_dent != NULL) { mode = src_dent->mode; - repo_write_dirent(path, mode, blob_mark, 0); + content_offset = src_dent->content_offset; + repo_write_dirent(dst, mode, content_offset, 0); } - return mode; } -void repo_modify(uint32_t *path, uint32_t mode, uint32_t blob_mark) +void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark) { - struct repo_dirent *src_dent; - src_dent = repo_read_dirent(active_commit, path); - if (src_dent != NULL && blob_mark == 0) - blob_mark = src_dent->content_offset; repo_write_dirent(path, mode, blob_mark, 0); } @@ -282,8 +278,9 @@ void repo_diff(uint32_t r1, uint32_t r2) repo_commit_root_dir(commit_pointer(r2))); } -void repo_commit(uint32_t revision, uint32_t author, char *log, uint32_t uuid, - uint32_t url, unsigned long timestamp) +void repo_commit(uint32_t revision, const char *author, + const struct strbuf *log, const char *uuid, const char *url, + unsigned long timestamp) { fast_export_commit(revision, author, log, uuid, url, timestamp); dent_commit(); diff --git a/vcs-svn/repo_tree.h b/vcs-svn/repo_tree.h index 5476175922..37bde2e374 100644 --- a/vcs-svn/repo_tree.h +++ b/vcs-svn/repo_tree.h @@ -1,7 +1,7 @@ #ifndef REPO_TREE_H_ #define REPO_TREE_H_ -#include "git-compat-util.h" +struct strbuf; #define REPO_MODE_DIR 0040000 #define REPO_MODE_BLB 0100644 @@ -12,13 +12,14 @@ #define REPO_MAX_PATH_DEPTH 1000 uint32_t next_blob_mark(void); -uint32_t repo_copy(uint32_t revision, uint32_t *src, uint32_t *dst); +void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst); void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark); -uint32_t repo_replace(uint32_t *path, uint32_t blob_mark); -void repo_modify(uint32_t *path, uint32_t mode, uint32_t blob_mark); +uint32_t repo_read_path(const uint32_t *path); +uint32_t repo_read_mode(const uint32_t *path); void repo_delete(uint32_t *path); -void repo_commit(uint32_t revision, uint32_t author, char *log, uint32_t uuid, - uint32_t url, long unsigned timestamp); +void repo_commit(uint32_t revision, const char *author, + const struct strbuf *log, const char *uuid, const char *url, + long unsigned timestamp); void repo_diff(uint32_t r1, uint32_t r2); void repo_init(void); void repo_reset(void); diff --git a/vcs-svn/string_pool.c b/vcs-svn/string_pool.c index f5b1da836e..8af8d54d6e 100644 --- a/vcs-svn/string_pool.c +++ b/vcs-svn/string_pool.c @@ -30,7 +30,7 @@ static int node_cmp(struct node *a, struct node *b) } /* Build a Treap from the node structure (a trp_node w/ offset) */ -trp_gen(static, tree_, struct node, children, node, node_cmp); +trp_gen(static, tree_, struct node, children, node, node_cmp) const char *pool_fetch(uint32_t entry) { diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c index 2ad2c307dd..bc792223b2 100644 --- a/vcs-svn/svndump.c +++ b/vcs-svn/svndump.c @@ -11,8 +11,15 @@ #include "repo_tree.h" #include "fast_export.h" #include "line_buffer.h" -#include "obj_pool.h" #include "string_pool.h" +#include "strbuf.h" +#include "svndump.h" + +/* + * Compare start of string to literal of equal length; + * must be guarded by length test. + */ +#define constcmp(s, ref) memcmp(s, ref, sizeof(ref) - 1) #define NODEACT_REPLACE 4 #define NODEACT_DELETE 3 @@ -27,40 +34,25 @@ #define LENGTH_UNKNOWN (~0) #define DATE_RFC2822_LEN 31 -/* Create memory pool for log messages */ -obj_pool_gen(log, char, 4096) - -static char* log_copy(uint32_t length, char *log) -{ - char *buffer; - log_free(log_pool.size); - buffer = log_pointer(log_alloc(length)); - strncpy(buffer, log, length); - return buffer; -} +static struct line_buffer input = LINE_BUFFER_INIT; static struct { - uint32_t action, propLength, textLength, srcRev, srcMode, mark, type; + uint32_t action, propLength, textLength, srcRev, type; uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH]; + uint32_t text_delta, prop_delta; } node_ctx; static struct { - uint32_t revision, author; + uint32_t revision; unsigned long timestamp; - char *log; + struct strbuf log, author; } rev_ctx; static struct { - uint32_t version, uuid, url; + uint32_t version; + struct strbuf uuid, url; } dump_ctx; -static struct { - uint32_t svn_log, svn_author, svn_date, svn_executable, svn_special, uuid, - revision_number, node_path, node_kind, node_action, - node_copyfrom_path, node_copyfrom_rev, text_content_length, - prop_content_length, content_length, svn_fs_dump_format_version; -} keys; - static void reset_node_ctx(char *fname) { node_ctx.type = 0; @@ -69,126 +61,223 @@ static void reset_node_ctx(char *fname) node_ctx.textLength = LENGTH_UNKNOWN; node_ctx.src[0] = ~0; node_ctx.srcRev = 0; - node_ctx.srcMode = 0; pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname); - node_ctx.mark = 0; + node_ctx.text_delta = 0; + node_ctx.prop_delta = 0; } static void reset_rev_ctx(uint32_t revision) { rev_ctx.revision = revision; rev_ctx.timestamp = 0; - rev_ctx.log = NULL; - rev_ctx.author = ~0; + strbuf_reset(&rev_ctx.log); + strbuf_reset(&rev_ctx.author); } -static void reset_dump_ctx(uint32_t url) +static void reset_dump_ctx(const char *url) { - dump_ctx.url = url; + strbuf_reset(&dump_ctx.url); + if (url) + strbuf_addstr(&dump_ctx.url, url); dump_ctx.version = 1; - dump_ctx.uuid = ~0; + strbuf_reset(&dump_ctx.uuid); +} + +static void handle_property(const struct strbuf *key_buf, + struct strbuf *val, + uint32_t *type_set) +{ + const char *key = key_buf->buf; + size_t keylen = key_buf->len; + + switch (keylen + 1) { + case sizeof("svn:log"): + if (constcmp(key, "svn:log")) + break; + if (!val) + die("invalid dump: unsets svn:log"); + strbuf_swap(&rev_ctx.log, val); + break; + case sizeof("svn:author"): + if (constcmp(key, "svn:author")) + break; + if (!val) + strbuf_reset(&rev_ctx.author); + else + strbuf_swap(&rev_ctx.author, val); + break; + case sizeof("svn:date"): + if (constcmp(key, "svn:date")) + break; + if (!val) + die("invalid dump: unsets svn:date"); + if (parse_date_basic(val->buf, &rev_ctx.timestamp, NULL)) + warning("invalid timestamp: %s", val->buf); + break; + case sizeof("svn:executable"): + case sizeof("svn:special"): + if (keylen == strlen("svn:executable") && + constcmp(key, "svn:executable")) + break; + if (keylen == strlen("svn:special") && + constcmp(key, "svn:special")) + break; + if (*type_set) { + if (!val) + return; + die("invalid dump: sets type twice"); + } + if (!val) { + node_ctx.type = REPO_MODE_BLB; + return; + } + *type_set = 1; + node_ctx.type = keylen == strlen("svn:executable") ? + REPO_MODE_EXE : + REPO_MODE_LNK; + } } -static void init_keys(void) +static void die_short_read(void) { - keys.svn_log = pool_intern("svn:log"); - keys.svn_author = pool_intern("svn:author"); - keys.svn_date = pool_intern("svn:date"); - keys.svn_executable = pool_intern("svn:executable"); - keys.svn_special = pool_intern("svn:special"); - keys.uuid = pool_intern("UUID"); - keys.revision_number = pool_intern("Revision-number"); - keys.node_path = pool_intern("Node-path"); - keys.node_kind = pool_intern("Node-kind"); - keys.node_action = pool_intern("Node-action"); - keys.node_copyfrom_path = pool_intern("Node-copyfrom-path"); - keys.node_copyfrom_rev = pool_intern("Node-copyfrom-rev"); - keys.text_content_length = pool_intern("Text-content-length"); - keys.prop_content_length = pool_intern("Prop-content-length"); - keys.content_length = pool_intern("Content-length"); - keys.svn_fs_dump_format_version = pool_intern("SVN-fs-dump-format-version"); + if (buffer_ferror(&input)) + die_errno("error reading dump file"); + die("invalid dump: unexpected end of file"); } static void read_props(void) { - uint32_t len; - uint32_t key = ~0; - char *val = NULL; - char *t; - while ((t = buffer_read_line()) && strcmp(t, "PROPS-END")) { - if (!strncmp(t, "K ", 2)) { - len = atoi(&t[2]); - key = pool_intern(buffer_read_string(len)); - buffer_read_line(); - } else if (!strncmp(t, "V ", 2)) { - len = atoi(&t[2]); - val = buffer_read_string(len); - if (key == keys.svn_log) { - /* Value length excludes terminating nul. */ - rev_ctx.log = log_copy(len + 1, val); - } else if (key == keys.svn_author) { - rev_ctx.author = pool_intern(val); - } else if (key == keys.svn_date) { - if (parse_date_basic(val, &rev_ctx.timestamp, NULL)) - fprintf(stderr, "Invalid timestamp: %s\n", val); - } else if (key == keys.svn_executable) { - node_ctx.type = REPO_MODE_EXE; - } else if (key == keys.svn_special) { - node_ctx.type = REPO_MODE_LNK; - } - key = ~0; - buffer_read_line(); + static struct strbuf key = STRBUF_INIT; + static struct strbuf val = STRBUF_INIT; + const char *t; + /* + * NEEDSWORK: to support simple mode changes like + * K 11 + * svn:special + * V 1 + * * + * D 14 + * svn:executable + * we keep track of whether a mode has been set and reset to + * plain file only if not. We should be keeping track of the + * symlink and executable bits separately instead. + */ + uint32_t type_set = 0; + while ((t = buffer_read_line(&input)) && strcmp(t, "PROPS-END")) { + uint32_t len; + const char type = t[0]; + int ch; + + if (!type || t[1] != ' ') + die("invalid property line: %s\n", t); + len = atoi(&t[2]); + strbuf_reset(&val); + buffer_read_binary(&input, &val, len); + if (val.len < len) + die_short_read(); + + /* Discard trailing newline. */ + ch = buffer_read_char(&input); + if (ch == EOF) + die_short_read(); + if (ch != '\n') + die("invalid dump: expected newline after %s", val.buf); + + switch (type) { + case 'K': + strbuf_swap(&key, &val); + continue; + case 'D': + handle_property(&val, NULL, &type_set); + continue; + case 'V': + handle_property(&key, &val, &type_set); + strbuf_reset(&key); + continue; + default: + die("invalid property line: %s\n", t); } } } static void handle_node(void) { - if (node_ctx.propLength != LENGTH_UNKNOWN && node_ctx.propLength) - read_props(); - - if (node_ctx.srcRev) - node_ctx.srcMode = repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst); - - if (node_ctx.textLength != LENGTH_UNKNOWN && - node_ctx.type != REPO_MODE_DIR) - node_ctx.mark = next_blob_mark(); + uint32_t mark = 0; + const uint32_t type = node_ctx.type; + const int have_props = node_ctx.propLength != LENGTH_UNKNOWN; + const int have_text = node_ctx.textLength != LENGTH_UNKNOWN; + if (node_ctx.text_delta) + die("text deltas not supported"); + if (have_text) + mark = next_blob_mark(); if (node_ctx.action == NODEACT_DELETE) { + if (have_text || have_props || node_ctx.srcRev) + die("invalid dump: deletion node has " + "copyfrom info, text, or properties"); + repo_delete(node_ctx.dst); + return; + } + if (node_ctx.action == NODEACT_REPLACE) { repo_delete(node_ctx.dst); - } else if (node_ctx.action == NODEACT_CHANGE || - node_ctx.action == NODEACT_REPLACE) { - if (node_ctx.action == NODEACT_REPLACE && - node_ctx.type == REPO_MODE_DIR) - repo_replace(node_ctx.dst, node_ctx.mark); - else if (node_ctx.propLength != LENGTH_UNKNOWN) - repo_modify(node_ctx.dst, node_ctx.type, node_ctx.mark); - else if (node_ctx.textLength != LENGTH_UNKNOWN) - node_ctx.srcMode = repo_replace(node_ctx.dst, node_ctx.mark); + node_ctx.action = NODEACT_ADD; + } + if (node_ctx.srcRev) { + repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst); + if (node_ctx.action == NODEACT_ADD) + node_ctx.action = NODEACT_CHANGE; + } + if (have_text && type == REPO_MODE_DIR) + die("invalid dump: directories cannot have text attached"); + + /* + * Decide on the new content (mark) and mode (node_ctx.type). + */ + if (node_ctx.action == NODEACT_CHANGE && !~*node_ctx.dst) { + if (type != REPO_MODE_DIR) + die("invalid dump: root of tree is not a regular file"); + } else if (node_ctx.action == NODEACT_CHANGE) { + uint32_t mode; + if (!have_text) + mark = repo_read_path(node_ctx.dst); + mode = repo_read_mode(node_ctx.dst); + if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR) + die("invalid dump: cannot modify a directory into a file"); + if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR) + die("invalid dump: cannot modify a file into a directory"); + node_ctx.type = mode; } else if (node_ctx.action == NODEACT_ADD) { - if (node_ctx.srcRev && node_ctx.propLength != LENGTH_UNKNOWN) - repo_modify(node_ctx.dst, node_ctx.type, node_ctx.mark); - else if (node_ctx.srcRev && node_ctx.textLength != LENGTH_UNKNOWN) - node_ctx.srcMode = repo_replace(node_ctx.dst, node_ctx.mark); - else if ((node_ctx.type == REPO_MODE_DIR && !node_ctx.srcRev) || - node_ctx.textLength != LENGTH_UNKNOWN) - repo_add(node_ctx.dst, node_ctx.type, node_ctx.mark); + if (!have_text && type != REPO_MODE_DIR) + die("invalid dump: adds node without text"); + } else { + die("invalid dump: Node-path block lacks Node-action"); } - if (node_ctx.propLength == LENGTH_UNKNOWN && node_ctx.srcMode) - node_ctx.type = node_ctx.srcMode; + /* + * Adjust mode to reflect properties. + */ + if (have_props) { + if (!node_ctx.prop_delta) + node_ctx.type = type; + if (node_ctx.propLength) + read_props(); + } - if (node_ctx.mark) - fast_export_blob(node_ctx.type, node_ctx.mark, node_ctx.textLength); - else if (node_ctx.textLength != LENGTH_UNKNOWN) - buffer_skip_bytes(node_ctx.textLength); + /* + * Save the result. + */ + repo_add(node_ctx.dst, node_ctx.type, mark); + if (have_text) + fast_export_blob(node_ctx.type, mark, + node_ctx.textLength, &input); } static void handle_revision(void) { if (rev_ctx.revision) - repo_commit(rev_ctx.revision, rev_ctx.author, rev_ctx.log, - dump_ctx.uuid, dump_ctx.url, rev_ctx.timestamp); + repo_commit(rev_ctx.revision, rev_ctx.author.buf, + &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf, + rev_ctx.timestamp); } void svndump_read(const char *url) @@ -197,44 +286,65 @@ void svndump_read(const char *url) char *t; uint32_t active_ctx = DUMP_CTX; uint32_t len; - uint32_t key; - reset_dump_ctx(pool_intern(url)); - while ((t = buffer_read_line())) { - val = strstr(t, ": "); + reset_dump_ctx(url); + while ((t = buffer_read_line(&input))) { + val = strchr(t, ':'); if (!val) continue; - *val++ = '\0'; - *val++ = '\0'; - key = pool_intern(t); + val++; + if (*val != ' ') + continue; + val++; - if (key == keys.svn_fs_dump_format_version) { + /* strlen(key) + 1 */ + switch (val - t - 1) { + case sizeof("SVN-fs-dump-format-version"): + if (constcmp(t, "SVN-fs-dump-format-version")) + continue; dump_ctx.version = atoi(val); - if (dump_ctx.version > 2) - die("expected svn dump format version <= 2, found %"PRIu32, + if (dump_ctx.version > 3) + die("expected svn dump format version <= 3, found %"PRIu32, dump_ctx.version); - } else if (key == keys.uuid) { - dump_ctx.uuid = pool_intern(val); - } else if (key == keys.revision_number) { + break; + case sizeof("UUID"): + if (constcmp(t, "UUID")) + continue; + strbuf_reset(&dump_ctx.uuid); + strbuf_addstr(&dump_ctx.uuid, val); + break; + case sizeof("Revision-number"): + if (constcmp(t, "Revision-number")) + continue; if (active_ctx == NODE_CTX) handle_node(); if (active_ctx != DUMP_CTX) handle_revision(); active_ctx = REV_CTX; reset_rev_ctx(atoi(val)); - } else if (key == keys.node_path) { - if (active_ctx == NODE_CTX) - handle_node(); - active_ctx = NODE_CTX; - reset_node_ctx(val); - } else if (key == keys.node_kind) { + break; + case sizeof("Node-path"): + if (prefixcmp(t, "Node-")) + continue; + if (!constcmp(t + strlen("Node-"), "path")) { + if (active_ctx == NODE_CTX) + handle_node(); + active_ctx = NODE_CTX; + reset_node_ctx(val); + break; + } + if (constcmp(t + strlen("Node-"), "kind")) + continue; if (!strcmp(val, "dir")) node_ctx.type = REPO_MODE_DIR; else if (!strcmp(val, "file")) node_ctx.type = REPO_MODE_BLB; else fprintf(stderr, "Unknown node-kind: %s\n", val); - } else if (key == keys.node_action) { + break; + case sizeof("Node-action"): + if (constcmp(t, "Node-action")) + continue; if (!strcmp(val, "delete")) { node_ctx.action = NODEACT_DELETE; } else if (!strcmp(val, "add")) { @@ -247,17 +357,44 @@ void svndump_read(const char *url) fprintf(stderr, "Unknown node-action: %s\n", val); node_ctx.action = NODEACT_UNKNOWN; } - } else if (key == keys.node_copyfrom_path) { + break; + case sizeof("Node-copyfrom-path"): + if (constcmp(t, "Node-copyfrom-path")) + continue; pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val); - } else if (key == keys.node_copyfrom_rev) { + break; + case sizeof("Node-copyfrom-rev"): + if (constcmp(t, "Node-copyfrom-rev")) + continue; node_ctx.srcRev = atoi(val); - } else if (key == keys.text_content_length) { - node_ctx.textLength = atoi(val); - } else if (key == keys.prop_content_length) { + break; + case sizeof("Text-content-length"): + if (!constcmp(t, "Text-content-length")) { + node_ctx.textLength = atoi(val); + break; + } + if (constcmp(t, "Prop-content-length")) + continue; node_ctx.propLength = atoi(val); - } else if (key == keys.content_length) { + break; + case sizeof("Text-delta"): + if (!constcmp(t, "Text-delta")) { + node_ctx.text_delta = !strcmp(val, "true"); + break; + } + if (constcmp(t, "Prop-delta")) + continue; + node_ctx.prop_delta = !strcmp(val, "true"); + break; + case sizeof("Content-length"): + if (constcmp(t, "Content-length")) + continue; len = atoi(val); - buffer_read_line(); + t = buffer_read_line(&input); + if (!t) + die_short_read(); + if (*t) + die("invalid dump: expected blank line after content length header"); if (active_ctx == REV_CTX) { read_props(); } else if (active_ctx == NODE_CTX) { @@ -265,34 +402,42 @@ void svndump_read(const char *url) active_ctx = REV_CTX; } else { fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len); - buffer_skip_bytes(len); + if (buffer_skip_bytes(&input, len) != len) + die_short_read(); } } } + if (buffer_ferror(&input)) + die_short_read(); if (active_ctx == NODE_CTX) handle_node(); if (active_ctx != DUMP_CTX) handle_revision(); } -void svndump_init(const char *filename) +int svndump_init(const char *filename) { - buffer_init(filename); + if (buffer_init(&input, filename)) + return error("cannot open %s: %s", filename, strerror(errno)); repo_init(); - reset_dump_ctx(~0); + strbuf_init(&dump_ctx.uuid, 4096); + strbuf_init(&dump_ctx.url, 4096); + strbuf_init(&rev_ctx.log, 4096); + strbuf_init(&rev_ctx.author, 4096); + reset_dump_ctx(NULL); reset_rev_ctx(0); reset_node_ctx(NULL); - init_keys(); + return 0; } void svndump_deinit(void) { - log_reset(); repo_reset(); - reset_dump_ctx(~0); + reset_dump_ctx(NULL); reset_rev_ctx(0); reset_node_ctx(NULL); - if (buffer_deinit()) + strbuf_release(&rev_ctx.log); + if (buffer_deinit(&input)) fprintf(stderr, "Input error\n"); if (ferror(stdout)) fprintf(stderr, "Output error\n"); @@ -300,10 +445,10 @@ void svndump_deinit(void) void svndump_reset(void) { - log_reset(); - buffer_reset(); + buffer_reset(&input); repo_reset(); - reset_dump_ctx(~0); - reset_rev_ctx(0); - reset_node_ctx(NULL); + strbuf_release(&dump_ctx.uuid); + strbuf_release(&dump_ctx.url); + strbuf_release(&rev_ctx.log); + strbuf_release(&rev_ctx.author); } diff --git a/vcs-svn/svndump.h b/vcs-svn/svndump.h index 93c412f14a..df9ceb0e8d 100644 --- a/vcs-svn/svndump.h +++ b/vcs-svn/svndump.h @@ -1,7 +1,7 @@ #ifndef SVNDUMP_H_ #define SVNDUMP_H_ -void svndump_init(const char *filename); +int svndump_init(const char *filename); void svndump_read(const char *url); void svndump_deinit(void); void svndump_reset(void); diff --git a/vcs-svn/trp.txt b/vcs-svn/trp.txt index 5ca6b42edb..177ebca335 100644 --- a/vcs-svn/trp.txt +++ b/vcs-svn/trp.txt @@ -96,7 +96,7 @@ node_type *foo_search(struct trp_root \*treap, node_type \*key):: node_type *foo_nsearch(struct trp_root \*treap, node_type \*key):: - Like `foo_search`, but if if the key is missing return what + Like `foo_search`, but if the key is missing return what would be key's successor, were key in treap (NULL if no successor). @@ -53,7 +53,7 @@ void *xmalloc(size_t size) void *xmallocz(size_t size) { void *ret; - if (size + 1 < size) + if (unsigned_add_overflows(size, 1)) die("Data too large to fit into virtual memory space."); ret = xmalloc(size + 1); ((char*)ret)[size] = 0; @@ -148,8 +148,10 @@ ssize_t read_in_full(int fd, void *buf, size_t count) while (count > 0) { ssize_t loaded = xread(fd, p, count); - if (loaded <= 0) - return total ? total : loaded; + if (loaded < 0) + return -1; + if (loaded == 0) + return total; count -= loaded; p += loaded; total += loaded; @@ -198,10 +200,22 @@ FILE *xfdopen(int fd, const char *mode) int xmkstemp(char *template) { int fd; + char origtemplate[PATH_MAX]; + strlcpy(origtemplate, template, sizeof(origtemplate)); fd = mkstemp(template); - if (fd < 0) - die_errno("Unable to create temporary file"); + if (fd < 0) { + int saved_errno = errno; + const char *nonrelative_template; + + if (!template[0]) + template = origtemplate; + + nonrelative_template = absolute_path(template); + errno = saved_errno; + die_errno("Unable to create temporary file '%s'", + nonrelative_template); + } return fd; } @@ -321,10 +335,22 @@ int gitmkstemps(char *pattern, int suffix_len) int xmkstemp_mode(char *template, int mode) { int fd; + char origtemplate[PATH_MAX]; + strlcpy(origtemplate, template, sizeof(origtemplate)); fd = git_mkstemp_mode(template, mode); - if (fd < 0) - die_errno("Unable to create temporary file"); + if (fd < 0) { + int saved_errno = errno; + const char *nonrelative_template; + + if (!template[0]) + template = origtemplate; + + nonrelative_template = absolute_path(template); + errno = saved_errno; + die_errno("Unable to create temporary file '%s'", + nonrelative_template); + } return fd; } diff --git a/wt-status.c b/wt-status.c index 123582b6cb..9f4e0ba9c1 100644 --- a/wt-status.c +++ b/wt-status.c @@ -32,6 +32,80 @@ static const char *color(int slot, struct wt_status *s) return c; } +static void status_vprintf(struct wt_status *s, int at_bol, const char *color, + const char *fmt, va_list ap, const char *trail) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf linebuf = STRBUF_INIT; + const char *line, *eol; + + strbuf_vaddf(&sb, fmt, ap); + if (!sb.len) { + strbuf_addch(&sb, '#'); + if (!trail) + strbuf_addch(&sb, ' '); + color_print_strbuf(s->fp, color, &sb); + if (trail) + fprintf(s->fp, "%s", trail); + strbuf_release(&sb); + return; + } + for (line = sb.buf; *line; line = eol + 1) { + eol = strchr(line, '\n'); + + strbuf_reset(&linebuf); + if (at_bol) { + strbuf_addch(&linebuf, '#'); + if (*line != '\n' && *line != '\t') + strbuf_addch(&linebuf, ' '); + } + if (eol) + strbuf_add(&linebuf, line, eol - line); + else + strbuf_addstr(&linebuf, line); + color_print_strbuf(s->fp, color, &linebuf); + if (eol) + fprintf(s->fp, "\n"); + else + break; + at_bol = 1; + } + if (trail) + fprintf(s->fp, "%s", trail); + strbuf_release(&linebuf); + strbuf_release(&sb); +} + +void status_printf_ln(struct wt_status *s, const char *color, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + status_vprintf(s, 1, color, fmt, ap, "\n"); + va_end(ap); +} + +void status_printf(struct wt_status *s, const char *color, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + status_vprintf(s, 1, color, fmt, ap, NULL); + va_end(ap); +} + +void status_printf_more(struct wt_status *s, const char *color, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + status_vprintf(s, 0, color, fmt, ap, NULL); + va_end(ap); +} + void wt_status_prepare(struct wt_status *s) { unsigned char sha1[20]; @@ -57,33 +131,33 @@ static void wt_status_print_unmerged_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); - color_fprintf_ln(s->fp, c, "# Unmerged paths:"); + status_printf_ln(s, c, _("Unmerged paths:")); if (!advice_status_hints) return; - if (s->in_merge) + if (s->whence != FROM_COMMIT) ; else if (!s->is_initial) - color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); + status_printf_ln(s, c, _(" (use \"git reset %s <file>...\" to unstage)"), s->reference); else - color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); - color_fprintf_ln(s->fp, c, "# (use \"git add/rm <file>...\" as appropriate to mark resolution)"); - color_fprintf_ln(s->fp, c, "#"); + status_printf_ln(s, c, _(" (use \"git rm --cached <file>...\" to unstage)")); + status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)")); + status_printf_ln(s, c, ""); } static void wt_status_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); - color_fprintf_ln(s->fp, c, "# Changes to be committed:"); + status_printf_ln(s, c, _("Changes to be committed:")); if (!advice_status_hints) return; - if (s->in_merge) + if (s->whence != FROM_COMMIT) ; /* NEEDSWORK: use "git reset --unresolve"??? */ else if (!s->is_initial) - color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); + status_printf_ln(s, c, _(" (use \"git reset %s <file>...\" to unstage)"), s->reference); else - color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); - color_fprintf_ln(s->fp, c, "#"); + status_printf_ln(s, c, _(" (use \"git rm --cached <file>...\" to unstage)")); + status_printf_ln(s, c, ""); } static void wt_status_print_dirty_header(struct wt_status *s, @@ -92,17 +166,17 @@ static void wt_status_print_dirty_header(struct wt_status *s, { const char *c = color(WT_STATUS_HEADER, s); - color_fprintf_ln(s->fp, c, "# Changes not staged for commit:"); + status_printf_ln(s, c, _("Changes not staged for commit:")); if (!advice_status_hints) return; if (!has_deleted) - color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to update what will be committed)"); + status_printf_ln(s, c, _(" (use \"git add <file>...\" to update what will be committed)")); 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)"); + status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" to update what will be committed)")); + status_printf_ln(s, 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, "#"); + status_printf_ln(s, c, _(" (commit or discard the untracked or modified content in submodules)")); + status_printf_ln(s, c, ""); } static void wt_status_print_other_header(struct wt_status *s, @@ -110,16 +184,16 @@ static void wt_status_print_other_header(struct wt_status *s, const char *how) { const char *c = color(WT_STATUS_HEADER, s); - color_fprintf_ln(s->fp, c, "# %s files:", what); + status_printf_ln(s, c, _("%s files:"), what); if (!advice_status_hints) return; - color_fprintf_ln(s->fp, c, "# (use \"git %s <file>...\" to include in what will be committed)", how); - color_fprintf_ln(s->fp, c, "#"); + status_printf_ln(s, c, _(" (use \"git %s <file>...\" to include in what will be committed)"), how); + status_printf_ln(s, c, ""); } static void wt_status_print_trailer(struct wt_status *s) { - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); + status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); } #define quote_path quote_path_relative @@ -130,20 +204,20 @@ static void wt_status_print_unmerged_data(struct wt_status *s, const char *c = color(WT_STATUS_UNMERGED, s); struct wt_status_change_data *d = it->util; struct strbuf onebuf = STRBUF_INIT; - const char *one, *how = "bug"; + const char *one, *how = _("bug"); one = quote_path(it->string, -1, &onebuf, s->prefix); - color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); + status_printf(s, color(WT_STATUS_HEADER, s), "\t"); switch (d->stagemask) { - case 1: how = "both deleted:"; break; - case 2: how = "added by us:"; break; - case 3: how = "deleted by them:"; break; - case 4: how = "added by them:"; break; - case 5: how = "deleted by us:"; break; - case 6: how = "both added:"; break; - case 7: how = "both modified:"; break; - } - color_fprintf(s->fp, c, "%-20s%s\n", how, one); + case 1: how = _("both deleted:"); break; + case 2: how = _("added by us:"); break; + case 3: how = _("deleted by them:"); break; + case 4: how = _("added by them:"); break; + case 5: how = _("deleted by us:"); break; + case 6: how = _("both added:"); break; + case 7: how = _("both modified:"); break; + } + status_printf_more(s, c, "%-20s%s\n", how, one); strbuf_release(&onebuf); } @@ -171,11 +245,11 @@ static void wt_status_print_change_data(struct wt_status *s, if (d->new_submodule_commits || d->dirty_submodule) { strbuf_addstr(&extra, " ("); if (d->new_submodule_commits) - strbuf_addf(&extra, "new commits, "); + strbuf_addf(&extra, _("new commits, ")); if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) - strbuf_addf(&extra, "modified content, "); + strbuf_addf(&extra, _("modified content, ")); if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) - strbuf_addf(&extra, "untracked content, "); + strbuf_addf(&extra, _("untracked content, ")); strbuf_setlen(&extra, extra.len - 2); strbuf_addch(&extra, ')'); } @@ -186,40 +260,40 @@ static void wt_status_print_change_data(struct wt_status *s, one = quote_path(one_name, -1, &onebuf, s->prefix); two = quote_path(two_name, -1, &twobuf, s->prefix); - color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); + status_printf(s, color(WT_STATUS_HEADER, s), "\t"); switch (status) { case DIFF_STATUS_ADDED: - color_fprintf(s->fp, c, "new file: %s", one); + status_printf_more(s, c, _("new file: %s"), one); break; case DIFF_STATUS_COPIED: - color_fprintf(s->fp, c, "copied: %s -> %s", one, two); + status_printf_more(s, c, _("copied: %s -> %s"), one, two); break; case DIFF_STATUS_DELETED: - color_fprintf(s->fp, c, "deleted: %s", one); + status_printf_more(s, c, _("deleted: %s"), one); break; case DIFF_STATUS_MODIFIED: - color_fprintf(s->fp, c, "modified: %s", one); + status_printf_more(s, c, _("modified: %s"), one); break; case DIFF_STATUS_RENAMED: - color_fprintf(s->fp, c, "renamed: %s -> %s", one, two); + status_printf_more(s, c, _("renamed: %s -> %s"), one, two); break; case DIFF_STATUS_TYPE_CHANGED: - color_fprintf(s->fp, c, "typechange: %s", one); + status_printf_more(s, c, _("typechange: %s"), one); break; case DIFF_STATUS_UNKNOWN: - color_fprintf(s->fp, c, "unknown: %s", one); + status_printf_more(s, c, _("unknown: %s"), one); break; case DIFF_STATUS_UNMERGED: - color_fprintf(s->fp, c, "unmerged: %s", one); + status_printf_more(s, c, _("unmerged: %s"), one); break; default: - die("bug: unhandled diff status %c", status); + die(_("bug: unhandled diff status %c"), status); } if (extra.len) { - color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf); + status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf); strbuf_release(&extra); } - fprintf(s->fp, "\n"); + status_printf_more(s, GIT_COLOR_NORMAL, "\n"); strbuf_release(&onebuf); strbuf_release(&twobuf); } @@ -323,7 +397,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) } rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; - rev.prune_data = s->pathspec; + init_pathspec(&rev.prune_data, s->pathspec); run_diff_files(&rev, 0); } @@ -348,20 +422,22 @@ static void wt_status_collect_changes_index(struct wt_status *s) rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 200; rev.diffopt.break_opt = 0; - rev.prune_data = s->pathspec; + init_pathspec(&rev.prune_data, s->pathspec); run_diff_index(&rev, 1); } static void wt_status_collect_changes_initial(struct wt_status *s) { + struct pathspec pathspec; int i; + init_pathspec(&pathspec, s->pathspec); for (i = 0; i < active_nr; i++) { struct string_list_item *it; struct wt_status_change_data *d; struct cache_entry *ce = active_cache[i]; - if (!ce_path_match(ce, s->pathspec)) + if (!ce_path_match(ce, &pathspec)) continue; it = string_list_insert(&s->change, ce->name); d = it->util; @@ -376,6 +452,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s) else d->index_status = DIFF_STATUS_ADDED; } + free_pathspec(&pathspec); } static void wt_status_collect_untracked(struct wt_status *s) @@ -573,9 +650,9 @@ static void wt_status_print_other(struct wt_status *s, for (i = 0; i < l->nr; i++) { struct string_list_item *it; 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), + status_printf(s, color(WT_STATUS_HEADER, s), "\t"); + status_printf_more(s, color(WT_STATUS_UNTRACKED, s), + "%s\n", quote_path(it->string, strlen(it->string), &buf, s->prefix)); } strbuf_release(&buf); @@ -633,26 +710,26 @@ void wt_status_print(struct wt_status *s) const char *branch_status_color = color(WT_STATUS_HEADER, s); if (s->branch) { - const char *on_what = "On branch "; + const char *on_what = _("On branch "); const char *branch_name = s->branch; if (!prefixcmp(branch_name, "refs/heads/")) branch_name += 11; else if (!strcmp(branch_name, "HEAD")) { branch_name = ""; branch_status_color = color(WT_STATUS_NOBRANCH, s); - on_what = "Not currently on any branch."; + on_what = _("Not currently on any branch."); } - color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# "); - color_fprintf(s->fp, branch_status_color, "%s", on_what); - color_fprintf_ln(s->fp, branch_color, "%s", branch_name); + status_printf(s, color(WT_STATUS_HEADER, s), ""); + status_printf_more(s, branch_status_color, "%s", on_what); + status_printf_more(s, branch_color, "%s\n", branch_name); if (!s->is_initial) wt_status_print_tracking(s); } if (s->is_initial) { - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit"); - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); + status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); + status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit")); + status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); } wt_status_print_updated(s); @@ -665,38 +742,38 @@ void wt_status_print(struct wt_status *s) wt_status_print_submodule_summary(s, 1); /* unstaged */ } if (s->show_untracked_files) { - wt_status_print_other(s, &s->untracked, "Untracked", "add"); + wt_status_print_other(s, &s->untracked, _("Untracked"), "add"); if (s->show_ignored_files) - wt_status_print_other(s, &s->ignored, "Ignored", "add -f"); + wt_status_print_other(s, &s->ignored, _("Ignored"), "add -f"); } else if (s->commitable) - fprintf(s->fp, "# Untracked files not listed%s\n", + status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"), advice_status_hints - ? " (use -u option to show untracked files)" : ""); + ? _(" (use -u option to show untracked files)") : ""); if (s->verbose) wt_status_print_verbose(s); if (!s->commitable) { if (s->amend) - fprintf(s->fp, "# No changes\n"); + status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes")); else if (s->nowarn) ; /* nothing */ else if (s->workdir_dirty) - printf("no changes added to commit%s\n", + printf(_("no changes added to commit%s\n"), advice_status_hints - ? " (use \"git add\" and/or \"git commit -a\")" : ""); + ? _(" (use \"git add\" and/or \"git commit -a\")") : ""); else if (s->untracked.nr) - printf("nothing added to commit but untracked files present%s\n", + printf(_("nothing added to commit but untracked files present%s\n"), advice_status_hints - ? " (use \"git add\" to track)" : ""); + ? _(" (use \"git add\" to track)") : ""); else if (s->is_initial) - printf("nothing to commit%s\n", advice_status_hints - ? " (create/copy files and use \"git add\" to track)" : ""); + 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%s\n", advice_status_hints - ? " (use -u to show untracked files)" : ""); + printf(_("nothing to commit%s\n"), advice_status_hints + ? _(" (use -u to show untracked files)") : ""); else - printf("nothing to commit%s\n", advice_status_hints - ? " (working directory clean)" : ""); + printf(_("nothing to commit%s\n"), advice_status_hints + ? _(" (working directory clean)") : ""); } } @@ -804,13 +881,13 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) if (!prefixcmp(branch_name, "refs/heads/")) branch_name += 11; else if (!strcmp(branch_name, "HEAD")) { - branch_name = "HEAD (no branch)"; + 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 "); + 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); @@ -825,15 +902,15 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) color_fprintf(s->fp, header_color, " ["); if (!num_ours) { - color_fprintf(s->fp, header_color, "behind "); + 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, 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, header_color, _("ahead ")); color_fprintf(s->fp, branch_color_local, "%d", num_ours); - color_fprintf(s->fp, header_color, ", behind "); + color_fprintf(s->fp, header_color, _(", behind ")); color_fprintf(s->fp, branch_color_remote, "%d", num_theirs); } diff --git a/wt-status.h b/wt-status.h index 20b17cf439..682b4c8f7d 100644 --- a/wt-status.h +++ b/wt-status.h @@ -24,6 +24,13 @@ enum untracked_status_type { SHOW_ALL_UNTRACKED_FILES }; +/* from where does this commit originate */ +enum commit_whence { + FROM_COMMIT, /* normal */ + FROM_MERGE, /* commit came from merge */ + FROM_CHERRY_PICK /* commit came from cherry-pick */ +}; + struct wt_status_change_data { int worktree_status; int index_status; @@ -40,7 +47,7 @@ struct wt_status { const char **pathspec; int verbose; int amend; - int in_merge; + enum commit_whence whence; int nowarn; int use_color; int relative_paths; @@ -68,4 +75,11 @@ void wt_status_collect(struct wt_status *s); 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); +void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...) + ; +void status_printf(struct wt_status *s, const char *color, const char *fmt, ...) + ; +void status_printf_more(struct wt_status *s, const char *color, const char *fmt, ...) + __attribute__((format(printf, 3, 4))); + #endif /* STATUS_H */ diff --git a/xdiff-interface.c b/xdiff-interface.c index 164581f87f..0e2c169227 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -347,7 +347,7 @@ int git_xmerge_style = -1; int git_xmerge_config(const char *var, const char *value, void *cb) { - if (!strcasecmp(var, "merge.conflictstyle")) { + if (!strcmp(var, "merge.conflictstyle")) { if (!value) die("'%s' is not a boolean", var); if (!strcmp(value, "diff3")) |