Merge branch 'mk/grep-pcre'
authorJunio C Hamano <gitster@pobox.com>
Mon, 20 Jun 2011 21:49:44 +0000 (14:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 20 Jun 2011 21:49:44 +0000 (14:49 -0700)
* mk/grep-pcre:
t7810: avoid unportable use of "echo"

327 files changed:
.gitignore
Documentation/RelNotes/1.7.5.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.5.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.5.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.5.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/blame-options.txt
Documentation/config.txt
Documentation/diff-config.txt [new file with mode: 0644]
Documentation/diff-options.txt
Documentation/git-add.txt
Documentation/git-blame.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-config.txt
Documentation/git-cvsserver.txt
Documentation/git-diff-index.txt
Documentation/git-diff-tree.txt
Documentation/git-fast-import.txt
Documentation/git-format-patch.txt
Documentation/git-fsck.txt
Documentation/git-imap-send.txt
Documentation/git-init-db.txt
Documentation/git-init.txt
Documentation/git-log.txt
Documentation/git-ls-remote.txt
Documentation/git-merge-base.txt
Documentation/git-notes.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-remote-helpers.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-send-email.txt
Documentation/git-sh-i18n--envsubst.txt [new file with mode: 0644]
Documentation/git-sh-i18n.txt [new file with mode: 0644]
Documentation/git-sh-setup.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/glossary-content.txt
Documentation/merge-config.txt
Documentation/pretty-options.txt
Documentation/rev-list-options.txt
Documentation/technical/api-diff.txt
GIT-VERSION-GEN
LGPL-2.1 [new file with mode: 0644]
Makefile
RelNotes
archive.c
attr.c
bisect.c
builtin/add.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/commit.c
builtin/config.c
builtin/diff-tree.c
builtin/diff.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/gc.c
builtin/grep.c
builtin/hash-object.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/ls-files.c
builtin/ls-remote.c
builtin/ls-tree.c
builtin/mailinfo.c
builtin/merge-base.c
builtin/merge-tree.c
builtin/merge.c
builtin/mktag.c
builtin/notes.c
builtin/pack-objects.c
builtin/read-tree.c
builtin/receive-pack.c
builtin/remote.c
builtin/rerere.c
builtin/rev-list.c
builtin/rev-parse.c
builtin/revert.c
builtin/send-pack.c
builtin/shortlog.c
builtin/show-branch.c
builtin/tag.c
builtin/update-index.c
cache.h
color.c
color.h
commit.h
compat/fnmatch/fnmatch.c
compat/mingw.c
compat/mingw.h
config.c
config.mak.in
connect.c
contrib/completion/git-completion.bash
contrib/fast-import/git-p4
contrib/fast-import/git-p4.txt
convert.c
ctype.c
date.c
diff-lib.c
diff.c
diff.h
diffcore-rename.c
dir.c
dir.h
environment.c
fast-import.c
fsck.c
git-add--interactive.perl
git-compat-util.h
git-cvsimport.perl
git-merge-one-file.sh
git-mergetool--lib.sh
git-mergetool.sh
git-parse-remote.sh
git-pull.sh
git-rebase--am.sh [new file with mode: 0644]
git-rebase--interactive.sh [changed mode: 0755->0644]
git-rebase--merge.sh [new file with mode: 0644]
git-rebase.sh
git-send-email.perl
git-sh-i18n.sh [new file with mode: 0644]
git-sh-setup.sh
git-stash.sh
git-submodule.sh
git-svn.perl
git.c
gitweb/INSTALL
gitweb/Makefile
gitweb/README
gitweb/gitweb.perl
gitweb/static/gitweb.css
gitweb/static/gitweb.js [deleted file]
gitweb/static/js/README [new file with mode: 0644]
gitweb/static/js/adjust-timezone.js [new file with mode: 0644]
gitweb/static/js/blame_incremental.js [new file with mode: 0644]
gitweb/static/js/javascript-detection.js [new file with mode: 0644]
gitweb/static/js/lib/common-lib.js [new file with mode: 0644]
gitweb/static/js/lib/cookies.js [new file with mode: 0644]
gitweb/static/js/lib/datetime.js [new file with mode: 0644]
graph.c
http-push.c
http-walker.c
http.c
http.h
ident.c
imap-send.c
list-objects.c
log-tree.c
merge-file.c
merge-file.h [new file with mode: 0644]
merge-recursive.c
notes-merge.c
notes.c
notes.h
object.c
pretty.c
read-cache.c
refs.c
remote-curl.c
replace_object.c
rerere.c
rerere.h
revision.c
revision.h
run-command.c
setup.c
sh-i18n--envsubst.c [new file with mode: 0644]
sha1-array.c [new file with mode: 0644]
sha1-array.h [new file with mode: 0644]
sha1_file.c
sha1_name.c
shell.c
strbuf.c
strbuf.h
submodule.c
t/Makefile
t/README
t/annotate-tests.sh
t/lib-httpd.sh
t/lib-read-tree.sh [new file with mode: 0644]
t/t0001-init.sh
t/t0006-date.sh
t/t0061-run-command.sh
t/t0081-line-buffer.sh
t/t0201-gettext-fallbacks.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh
t/t1001-read-tree-m-2way.sh
t/t1002-read-tree-m-u-2way.sh
t/t1004-read-tree-m-u-wf.sh
t/t1005-read-tree-reset.sh
t/t1008-read-tree-overlay.sh
t/t1011-read-tree-sparse-checkout.sh
t/t1012-read-tree-df.sh
t/t1020-subdirectory.sh
t/t1050-large.sh [new file with mode: 0755]
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1303-wacky-config.sh
t/t1400-update-ref.sh
t/t1411-reflog-show.sh
t/t1501-worktree.sh
t/t1506-rev-parse-diagnosis.sh
t/t2011-checkout-invalid-head.sh
t/t2019-checkout-ambiguous-ref.sh
t/t2020-checkout-detach.sh
t/t2200-add-update.sh
t/t2201-add-update-typechange.sh
t/t2204-add-ignored.sh
t/t3030-merge-recursive.sh
t/t3102-ls-tree-wildcards.sh [new file with mode: 0755]
t/t3200-branch.sh
t/t3203-branch-output.sh
t/t3301-notes.sh
t/t3400-rebase.sh
t/t3403-rebase-skip.sh
t/t3404-rebase-interactive.sh
t/t3407-rebase-abort.sh
t/t3409-rebase-preserve-merges.sh
t/t3418-rebase-continue.sh
t/t3501-revert-cherry-pick.sh
t/t3503-cherry-pick-root.sh
t/t3507-cherry-pick-conflict.sh
t/t3600-rm.sh
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t3703-add-magic-pathspec.sh [new file with mode: 0755]
t/t3903-stash.sh
t/t3904-stash-patch.sh
t/t4001-diff-rename.sh
t/t4002-diff-basic.sh
t/t4013-diff-various.sh
t/t4013/diff.diff_--dirstat-by-file_initial_rearrange [new file with mode: 0644]
t/t4013/diff.diff_--dirstat_initial_rearrange [new file with mode: 0644]
t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
t/t4013/diff.log_--decorate=full_--all
t/t4013/diff.log_--decorate_--all
t/t4014-format-patch.sh
t/t4018-diff-funcname.sh
t/t4020-diff-external.sh
t/t4022-diff-rewrite.sh
t/t4027-diff-submodule.sh
t/t4034-diff-words.sh
t/t4040-whitespace-status.sh
t/t4046-diff-unmerged.sh [new file with mode: 0755]
t/t4047-diff-dirstat.sh [new file with mode: 0755]
t/t4152-am-subjects.sh [new file with mode: 0755]
t/t4202-log.sh
t/t4203-mailmap.sh
t/t4205-log-pretty-formats.sh
t/t4208-log-magic-pathspec.sh [new file with mode: 0755]
t/t5400-send-pack.sh
t/t5505-remote.sh
t/t5506-remote-groups.sh
t/t5512-ls-remote.sh
t/t5516-fetch-push.sh
t/t5526-fetch-submodules.sh
t/t5532-fetch-proxy.sh [new file with mode: 0755]
t/t5541-http-push.sh
t/t5601-clone.sh
t/t6007-rev-list-cherry-pick-file.sh
t/t6010-merge-base.sh
t/t6017-rev-list-stdin.sh
t/t6018-rev-list-glob.sh
t/t6040-tracking-info.sh
t/t6050-replace.sh
t/t6060-merge-index.sh [new file with mode: 0755]
t/t6120-describe.sh
t/t7004-tag.sh
t/t7011-skip-worktree-reading.sh
t/t7012-skip-worktree-writing.sh
t/t7060-wtstatus.sh
t/t7102-reset.sh
t/t7110-reset-merge.sh
t/t7201-co.sh
t/t7300-clean.sh
t/t7406-submodule-update.sh
t/t7500-commit.sh
t/t7500/add-whitespaced-content [new file with mode: 0755]
t/t7501-commit.sh
t/t7502-commit.sh
t/t7506-status-submodule.sh
t/t7508-status.sh
t/t7600-merge.sh
t/t7607-merge-overwrite.sh
t/t7610-mergetool.sh
t/t7611-merge-abort.sh
t/t7811-grep-open.sh
t/t8002-blame.sh
t/t8008-blame-formats.sh [new file with mode: 0755]
t/t9116-git-svn-log.sh
t/t9146-git-svn-empty-dirs.sh
t/t9300-fast-import.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9502-gitweb-standalone-parse-output.sh
t/t9800-git-p4.sh
t/test-lib.sh
test-run-command.c
transport.c
transport.h
tree-diff.c
tree-walk.c
tree.c
tree.h
unpack-trees.c
unpack-trees.h
upload-pack.c
userdiff.c
vcs-svn/svndump.c
wrapper.c
xdiff-interface.c
index 2cf3ca5808b51be3921a0f25b5871f806ce447e1..acffdfaae684dc49d7205d580fd8921ca4696f7f 100644 (file)
 /git-quiltimport
 /git-read-tree
 /git-rebase
+/git-rebase--am
 /git-rebase--interactive
+/git-rebase--merge
 /git-receive-pack
 /git-reflog
 /git-relink
 /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
 /gitk-git/gitk-wish
 /gitweb/GITWEB-BUILD-OPTIONS
 /gitweb/gitweb.cgi
+/gitweb/static/gitweb.js
 /gitweb/static/gitweb.min.*
 /test-chmtime
 /test-ctype
diff --git a/Documentation/RelNotes/1.7.5.1.txt b/Documentation/RelNotes/1.7.5.1.txt
new file mode 100644 (file)
index 0000000..c6ebd76
--- /dev/null
@@ -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 (file)
index 0000000..951eb7c
--- /dev/null
@@ -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 (file)
index 0000000..9c03353
--- /dev/null
@@ -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 (file)
index 0000000..cf3f455
--- /dev/null
@@ -0,0 +1,21 @@
+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.
+
+ * Setting a git command that uses custom configuration via "-c var=val"
+   as an alias caused a crash due to a realloc(3) failure.
+
+ * "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.6.txt b/Documentation/RelNotes/1.7.6.txt
new file mode 100644 (file)
index 0000000..9fd85f5
--- /dev/null
@@ -0,0 +1,142 @@
+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 interactive session.
+
+ * "git commit" learned a "--patch" option to directly jump to the
+   per-hunk selection UI of the interactive mode.
+
+ * "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 format-patch" can take an empty --subject-prefix now.
+
+ * "git grep" learned the "-P" option to take pcre regular expressions.
+
+ * "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 read-tree -m" learned "--dry-run" option that reports if a merge
+   would fail without touching the index nor the working tree.
+
+ * "git rebase" that does not specify on top of which branch to rebase
+   the current branch now uses @{upstream} of the current branch.
+
+ * "git rebase" finished either normally or with --abort did not
+   update the reflog for HEAD to record the event to come back to
+   where it started from.
+
+ * "git remote add -t only-this-branch --mirror=fetch" is now allowed. Earlier
+   a fetch-mode mirror meant mirror everything, but now it only means refs are
+   not renamed.
+
+ * "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.
+
+ * "git config" used to choke with an insanely long line.
+   (merge ef/maint-strbuf-init later)
+
+ * "git diff --quiet" did not work well with --diff-filter.
+   (merge jk/diff-not-so-quick later)
+
+ * "git status -z" did not default to --porcelain output format.
+   (merge bc/maint-status-z-to-use-porcelain later)
+
+---
+exec >/var/tmp/1
+echo O=$(git describe master)
+O=v1.7.6-rc2
+git shortlog --no-merges ^maint ^$O master
index c6a503291205f0b8047a213d1ebeb6a509ff6fa8..938eccf2a5ac9dd629be1bc6bb53a59723c99083 100644 (file)
@@ -344,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
@@ -443,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
 ----
@@ -540,71 +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).
index 16e3c685762a311eda1bbc39adb8ab19e000ec97..e76195ac9721277e9ef270be8eb97dba2462bc37 100644 (file)
@@ -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.
index 750c86d4f51b256faac1b1b5eb26275384d87886..6b937771994f5b0a532b6f2cc522a9d3f35c9c09 100644 (file)
@@ -442,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
@@ -589,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
@@ -643,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
@@ -708,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
@@ -796,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
@@ -812,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
@@ -977,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
@@ -1349,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.
diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt
new file mode 100644 (file)
index 0000000..1aed79e
--- /dev/null
@@ -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.
index c93124be79809e36d9a29fd69ce77cd7f22413e1..c7ed94635786789baca1de41d482cd6334d1181a 100644 (file)
@@ -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
index 7eebbefe7b6f665a8f6a5003eb37d7bcb3c196bd..9c1d3957223355e00eea917d4a7dff6d50343f32 100644 (file)
@@ -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::
 
index c4d1ff86c90d22ffea3fff173ee6b76d88013dab..9516914236bbfa675006994620b1c8f61855de1d 100644 (file)
@@ -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
 -----------------
index 1063f69023f97357c747cf73f8a4bcc9d9a59c4b..c0a96e6c1eede3b511689f35e9130a26ea81e003 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 '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
 -----------
@@ -45,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
@@ -183,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,
index 01db83039fc36aa4bc08eab730db48ec513ec583..9d8fe0d26145ae45f566d3e8d4253c396630b41f 100644 (file)
@@ -51,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
index 86eb4c93682aa3d92fe001ae871913fc0dec8607..b093e45497248076c335391f3e95b6384520ef6b 100644 (file)
@@ -12,7 +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|-L <git dir>]
+         [--separate-git-dir <git dir>]
          [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
          [<directory>]
 
@@ -177,7 +177,6 @@ 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)
 
--L=<git dir>::
 --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,
index d0534b8c05c5780a7f9debc664dd21af6739a3ec..7951cb7b005bf472c449a563d073036ceb8b921a 100644 (file)
@@ -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
index 8804de327fd5973ae5d36bff18cfcac3e738763d..e7ecf5d803e14dfa452671cf01e7730dce48b984 100644 (file)
@@ -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:
-
-. 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.
-
+This command will fail (with exit code ret) if:
+
+. 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
 -------
index 88d814af0e050eb3b31f5763cbf12f03a5f6cbc3..827bc988ed5562c2dbc6566d6732b01f20bb4f05 100644 (file)
@@ -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
index 6d184864022cef90e284113a627103db99327052..2ea22abca2bc72abb0cedfe6f47616c52a60d3f4 100644 (file)
@@ -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
index 4e5f127efa634dd4ea68bb73508c59bd1cad32e5..1439486e40ec4a8a1146310bf929d688eb01d533 100644 (file)
@@ -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:
index 2c2ea12c5c4e19907674609c228997062d305983..249249aac7b2b9def824794b2bf71e2c7fe497ce 100644 (file)
@@ -83,7 +83,7 @@ OPTIONS
        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
@@ -93,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>::
index a5525e9aa87e8c2c724542bc9e54dfa7e34548fd..d13c9b23f7bce6379f638e5bf86b30c95abef30e 100644 (file)
@@ -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
 --------
 
index c9ede794b03460ec2911b6ceddc781ab08fb8ded..a2a508dc2829b88e143ceb5c8e5edf0880263b2b 100644 (file)
@@ -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
 
index d3013d6a29f2696e05e3c77b0d6f0159d0a98a76..4e09708cc96fbf88b468141c0234095ee35fee3c 100644 (file)
@@ -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,6 +149,10 @@ 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
 
+SEE ALSO
+--------
+linkgit:git-format-patch[1], linkgit:git-send-email[1], mbox(5)
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 2c4c716f33f9dd3f0b876a013e22ac115ac3c936..9f97f5a91584a7469a3b0d2f8705febbe5c5ab58 100644 (file)
@@ -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
index 58cd01145ab8605d30a9c670ee70391b9d974a32..f2777a7786e6bf9c0cb907c77134b06321523654 100644 (file)
@@ -9,7 +9,7 @@ git-init - Create an empty git repository or reinitialize an existing one
 SYNOPSIS
 --------
 'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
-         [--separate-git-dir|-L <git dir>]
+         [--separate-git-dir <git dir>]
          [--shared[=<permissions>]] [directory]
 
 
@@ -54,7 +54,6 @@ current working directory.
 Specify the directory from which templates will be used.  (See the "TEMPLATE
 DIRECTORY" section below.)
 
--L=<git dir>::
 --separate-git-dir=<git dir>::
 
 Instead of initializing the repository where it is supposed to be,
index 2c84028838d566d424082efc71516f565b0ebbd2..de5c0d37a5c1c8c948aa01e276c3e198eb617be6 100644 (file)
@@ -178,9 +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.
+and overridden by the `--notes=<ref>` option.
 
 GIT
 ---
index c3df8c0ebe48af7192f7d593c13af69825b6560f..7a9b86a58a1c00c08803e9ef40bb6a17146125a9 100644 (file)
@@ -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
index ba36ec04f4b9fa4733b6e0a4c7e41aa3e43327a0..b295bf83302de3fdac2833df756799a7aad6b404 100644 (file)
@@ -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:
 
index 296f314eae5af684e68965f4b04bd9acbd4c35a1..6a187f2e2336575fb4974b8c492359913a8c7a9e 100644 (file)
@@ -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
index 26fdadc642a3534c4005488cb9ca2b2a47a495a1..46a96f2313e0cb48bff9d5de26f403b35d327d2c 100644 (file)
@@ -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.
 
index 620d50e71f1a04f587079180ff2d987521d38786..9a075bc4d24d59c7b585e204e6feb50326c762f3 100644 (file)
@@ -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
@@ -217,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.
index 87cd11f2c47b66387d31c38f648c02f4c2734955..58f6ad4994dae770b1898888d3073fca777743af 100644 (file)
@@ -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
 -------------------
index 8481f9db746b70c10a2f20a97a8ef4a68cb56d75..b2832fc7eb809af9865d83cdb20829354165f62e 100644 (file)
@@ -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
index 415f4f0b303da22e9eec19c1b9e24732a74ecf67..38fafcaa6b4173ad7f900c71efaa8ea25470072f 100644 (file)
@@ -29,6 +29,7 @@ SYNOPSIS
             [ \--tags[=<pattern>] ]
             [ \--remotes[=<pattern>] ]
             [ \--glob=<glob-pattern> ]
+            [ \--ignore-missing ]
             [ \--stdin ]
             [ \--quiet ]
             [ \--topo-order ]
index ee14f74fd3af8dd6cfd9e52002993355d63b97c8..5a168cfab2206cf67453bb5f1fd93dbe75bb4ef0 100644 (file)
@@ -348,10 +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
@@ -359,9 +361,20 @@ Add the following section to the config file:
                smtpuser = yourname@gmail.com
                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/*
+
 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
 ---
 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 (file)
index 0000000..61e4c08
--- /dev/null
@@ -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 (file)
index 0000000..3b1f7ac
--- /dev/null
@@ -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
index 053df505bc3aaa5e74c3a9266858eef0de1c2bd2..27fd8ba854698dcd31f5348ddc00a607abb64965 100644 (file)
@@ -58,9 +58,14 @@ 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,
index 79abc38e50a9a82243085383401b2d93468efaf3..15f051fa44e4b2e90da8383318c62695359772ce 100644 (file)
@@ -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.
index 00b699fef71b45c78e298fa48609b553a32fd175..38cb741f180e0869a91a61a5f7706eb664b86d91 100644 (file)
@@ -32,9 +32,10 @@ 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>]::
@@ -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
index 1a16ff60448a72bebcba2daff7dbabc5feed8a4c..5e7a4130eeec48c27abf92b37834613692446723 100644 (file)
@@ -186,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
index 30a67486d2dc9eb4169a9ba5ebd5db513a3a84f8..713e523034d5b95071e2285252c1796cd83c6fcd 100644 (file)
@@ -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;
@@ -350,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
@@ -572,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
 ----------------
@@ -680,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
@@ -774,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
@@ -787,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.
@@ -846,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
index 9d5949229abac83b3e38b4f08565653f10492360..5c45446a4ac955630c8709e2184e52163185b8d4 100644 (file)
@@ -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,13 @@ 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.5/git.html[documentation for release 1.7.5]
+* link:v1.7.5.4/git.html[documentation for release 1.7.5.4]
 
 * release notes for
+  link:RelNotes/1.7.5.4.txt[1.7.5.4],
+  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]
@@ -287,8 +291,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::
index 15aebc60623d97053aa14f3e6463d12eb8f60f26..412c55b549354471a56a4e56ca5533277c83bc54 100644 (file)
@@ -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
 ^^^^^^^^^^^^^^^^^^^^^^^
 
index 33716a31d0c60093c14f894ef07a87bee73fafc9..8f62d1abeeb9b80fd36aa8ea12120c640a98414e 100644 (file)
@@ -277,7 +277,8 @@ This commit is referred to as a "merge commit", or sometimes just a
        Pattern used to specify paths.
 +
 Pathspecs are used on the command line of "git ls-files", "git
-ls-tree", "git grep", "git checkout", and many other commands to
+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
@@ -296,6 +297,37 @@ 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
index 8920258baa515b048555be3448edea0403ce05be..861bd6f55352a12db6d1d680508e1d02a8e90d12 100644 (file)
@@ -16,6 +16,16 @@ merge.defaultToUpstream::
        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
index 50923e2ce9a5bdc3bcabb072a67921c6cf8171c1..2a3dc8664f16957a05bc4d81824d7995517ac89c 100644 (file)
@@ -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.
index 73111bb0512476cb1d1786d65c416b7f4efd94cf..7e7ba68781f98b55f4189fe0fb4dd6e96c47c0af 100644 (file)
@@ -139,6 +139,10 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
        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::
@@ -730,7 +734,10 @@ ifdef::git-rev-list[]
        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.
+       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[]
 
 
index 20b0241d30026747391fa4b6b38de5cf959cee70..2d2ebc04b74ef2b1bf39df892d303d37076325d1 100644 (file)
@@ -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`
index 68691b3c124d27c74401fa736a318dda2494aede..08e864869bdc70a5afe63d7d99de5c30e725a4a9 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.5
+DEF_VER=v1.7.6-rc2
 
 LF='
 '
diff --git a/LGPL-2.1 b/LGPL-2.1
new file mode 100644 (file)
index 0000000..d38b1b9
--- /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.
+\f
+  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.
+\f
+                  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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
+\f
+           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!
index c449f5d6deae63e199575a525325a435c5a5ef4e..e40ac0c7f5ec2f304b88d92f47ff94272f5ce2a4 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -76,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
@@ -280,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.
@@ -297,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 = :
@@ -329,9 +324,7 @@ 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 =
 
 
 
@@ -376,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
@@ -386,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
@@ -420,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))
 
@@ -429,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
@@ -441,8 +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_NEED_X += test-mktemp
 
 TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
 
@@ -533,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
@@ -553,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
@@ -655,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
@@ -930,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
@@ -1183,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 =
@@ -1198,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 =
@@ -1595,6 +1605,7 @@ ifndef V
        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
@@ -1690,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)"'
@@ -1762,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' \
@@ -1986,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
 
@@ -2068,10 +2059,14 @@ XGETTEXT_FLAGS = \
        --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$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C)
+       $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \
+               $(LOCALIZED_SH)
        mv $@+ $@
 
 pot: po/git.pot
@@ -2176,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 $(patsubst %.o, %.c, $(GIT_OBJS)); \
-               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; \
index 540b756d2f64d8c8ebf927a707b9b2cc0a4c6151..5fcc4ef74770e8587a1469fff5e3f5610dabf0e1 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.5.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.6.txt
\ No newline at end of file
index 1944ed4e4dc36addaf6a8b408703a7ff418bd26c..42f2d2fdc81a7c656e3ec006227b092addd13044 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -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,
diff --git a/attr.c b/attr.c
index 0e28ba871f9ba0da129610019cd0d0246f3a15f0..f6b3f7e850f9fcf5672031049ef1d6f43e619f63 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -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,7 +473,7 @@ 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);
 }
index 060c042f8bcc2d402cb9908be3bdbd3bb180a862..dd7e8ed69bc0c1268dc3b0a6590501ba9c10d7db 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -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) {
index d39a6ab930adf2e3cd716d3e9925350befed8947..c59b0c98fefefc413c8330715fffcc83142d5b2d 100644 (file)
@@ -26,6 +26,27 @@ struct update_callback_data {
        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)
 {
@@ -35,30 +56,9 @@ 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.
-                        */
                case DIFF_STATUS_MODIFIED:
                case DIFF_STATUS_TYPE_CHANGED:
                        if (add_file_to_index(&the_index, path, data->flags)) {
@@ -91,6 +91,7 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
        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;
 }
@@ -241,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;
 
@@ -252,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);
 }
 
@@ -330,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;
        }
@@ -377,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));
index 36e150768ba88e427e6257900d7ec4c67c8303c1..530d4bb7e76da8e402f78ab7f981a080d25d6e78 100644 (file)
@@ -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';
@@ -2430,9 +2431,9 @@ static void update_image(struct image *img,
        memcpy(img->line + applied_pos,
               postimage->line,
               postimage->nr * sizeof(*img->line));
-       for (i = 0; i < postimage->nr; i++)
-               img->line[applied_pos + i].flag |= LINE_PATCHED;
-
+       if (!allow_overlap)
+               for (i = 0; i < postimage->nr; i++)
+                       img->line[applied_pos + i].flag |= LINE_PATCHED;
        img->nr = nr;
 }
 
@@ -3889,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",
index f6b03f750a0b69ef835caa0a2e4988f9a08abb85..26a5d424b8ceb0fd403a492e46e3637fd35068ba 100644 (file)
@@ -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;
@@ -1377,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;
@@ -1483,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;
@@ -1528,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");
        }
@@ -1617,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_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)
+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;
@@ -1632,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++;
@@ -1670,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)
@@ -1755,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);
                }
@@ -2299,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),
@@ -2310,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()
        };
 
@@ -2345,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);
 
index 9cca1b9afc20caf6333d0269386249f41fa8746f..d6ab93bfbb369965e76e384e6984a29b667cdfbe 100644 (file)
@@ -399,9 +399,7 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item,
        struct commit *commit = item->commit;
 
        if (commit && !parse_commit(commit)) {
-               struct pretty_print_context ctx = {0};
-               pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                   &subject, &ctx);
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject);
                sub = subject.buf;
        }
 
index 94632dbdb400f9a2986d10f06dd16119ce1b4e54..07bd984084fbbfbb826ef5b784fc68069675e73c 100644 (file)
@@ -187,6 +187,8 @@ static int batch_one_object(const char *obj_name, int print_contents)
        if (type <= 0) {
                printf("%s missing\n", obj_name);
                fflush(stdout);
+               if (print_contents == BATCH)
+                       free(contents);
                return 0;
        }
 
index eece5d6ac0f48ae76dc76c0f2ebda633cc0ded58..28cdc51b85e7d433dca085c0080f964d19a391b4 100644 (file)
@@ -79,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
@@ -303,9 +306,8 @@ static void show_local_changes(struct object *head, struct diff_options *opts)
 static void describe_detached_head(const char *msg, struct commit *commit)
 {
        struct strbuf sb = STRBUF_INIT;
-       struct pretty_print_context ctx = {0};
        parse_commit(commit);
-       pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx);
+       pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
        fprintf(stderr, "%s %s... %s\n", msg,
                find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
        strbuf_release(&sb);
@@ -620,14 +622,12 @@ static int clear_commit_marks_from_one_ref(const char *refname,
 
 static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
 {
-       struct pretty_print_context ctx = { 0 };
-
        parse_commit(commit);
        strbuf_addstr(sb, "  ");
        strbuf_addstr(sb,
                find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
        strbuf_addch(sb, ' ');
-       pretty_print_commit(CMIT_FMT_ONELINE, commit, sb, &ctx);
+       pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
        strbuf_addch(sb, '\n');
 }
 
@@ -648,18 +648,30 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
                if (more == 1)
                        describe_one_orphan(&sb, last);
                else
-                       strbuf_addf(&sb, " ... and %d more.\n", more);
+                       strbuf_addf(&sb, _(" ... and %d more.\n"), more);
        }
 
        fprintf(stderr,
-               "Warning: you are leaving %d commit%s behind, "
+               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",
-               lost, ((1 < lost) ? "s" : ""),
+               /* Give ngettext() the count */
+               lost),
+               lost,
                sb.buf,
                sha1_to_hex(commit->object.sha1));
        strbuf_release(&sb);
@@ -961,9 +973,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                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");
+               die(_("--detach cannot be used with -b/-B/--orphan"));
        if (opts.force_detach && 0 < opts.track)
-               die("--detach cannot be used with -t");
+               die(_("--detach cannot be used with -t"));
 
        /* --track without -b should DWIM */
        if (0 < opts.track && !opts.new_branch) {
@@ -1043,7 +1055,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                }
 
                if (opts.force_detach)
-                       die("git checkout: --detach does not take a path argument");
+                       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."));
index 4144bcf5ca320057b5ef1f5dac9c2df19aba250b..f579794d9a93a0e55289921f20b8f68b85211ca1 100644 (file)
@@ -81,7 +81,7 @@ 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('L', "separate-git-dir", &real_git_dir, "gitdir",
+       OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                   "separate git dir from working tree"),
 
        OPT_END()
@@ -417,7 +417,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (path)
                repo = xstrdup(absolute_path(repo_name));
        else if (!strchr(repo_name, ':'))
-               die("repository '%s' does not exist", repo_name);
+               die(_("repository '%s' does not exist"), repo_name);
        else
                repo = repo_name;
        is_local = path && !is_bundle;
index 67757e999fba514b62767d3351031e9eb6ef8c10..e1af9b19f0be71484ae9341762dc2bf89cabb70c 100644 (file)
@@ -83,7 +83,7 @@ static const char *template_file;
 static const char *author_message, *author_message_buffer;
 static char *edit_message, *use_message;
 static char *fixup_message, *squash_message;
-static int all, edit_flag, also, interactive, 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;
@@ -152,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"),
@@ -336,18 +337,11 @@ 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);
@@ -355,6 +349,33 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
        if (read_cache_preload(pathspec) < 0)
                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.
         *
@@ -615,6 +636,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
        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;
@@ -681,6 +703,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                if (strbuf_read_file(&sb, template_file, 0) < 0)
                        die_errno(_("could not read '%s'"), template_file);
                hook_arg1 = "template";
+               clean_message_contents = 0;
        }
 
        /*
@@ -708,7 +731,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
        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) {
@@ -1041,8 +1064,11 @@ static int parse_and_validate_options(int argc, const char *argv[],
                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."));
        if (argc == 0 && only && amend)
@@ -1064,8 +1090,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
        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."));
 
        if (null_termination && status_format == STATUS_FORMAT_LONG)
                status_format = STATUS_FORMAT_PORCELAIN;
@@ -1183,9 +1207,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_status_usage, builtin_status_options);
 
-       if (null_termination && status_format == STATUS_FORMAT_LONG)
-               status_format = STATUS_FORMAT_PORCELAIN;
-
        wt_status_prepare(&s);
        gitmodules_config();
        git_config(git_status_config, &s);
@@ -1193,6 +1214,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
+
+       if (null_termination && status_format == STATUS_FORMAT_LONG)
+               status_format = STATUS_FORMAT_PORCELAIN;
+
        handle_untracked_files_arg(&s);
        if (show_ignored_in_status)
                s.show_ignored_files = 1;
index 3e3c52849773348a568241f678b3e3ee2e8d123a..211e118d575411b712ccf0387db2408708576902 100644 (file)
@@ -436,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);
index 0d2a3e9fa2023cc673f1f6d9e17d3deeca1c7e9d..be6417d166abf428d379a70b9d67f894da4641d1 100644 (file)
@@ -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);
index 717fa1a3414e18cbdd4c6eb1e0785761687688d7..69cd5eed78cb402839813e7eca65b5598afa4a90 100644 (file)
@@ -182,6 +182,7 @@ static int builtin_diff_combined(struct rev_info *revs,
                hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
        diff_tree_combined(parent[0], parent + 1, ents - 1,
                           revs->dense_combined_merges, revs);
+       free(parent);
        return 0;
 }
 
@@ -202,7 +203,6 @@ static void refresh_index_quietly(void)
 
 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] == '-') {
@@ -236,8 +236,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
                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)
index 85aff029b225c023f09fda635f16cc00a22ed7ca..436798410210b868cfbe439a1603b0a340a32c50 100644 (file)
@@ -226,7 +226,7 @@ static void insert_one_alternate_ref(const struct ref *ref, void *unused)
 
 static void insert_alternate_refs(void)
 {
-       foreach_alt_odb(refs_from_alternate_cb, insert_one_alternate_ref);
+       for_each_alternate_ref(insert_one_alternate_ref, NULL);
 }
 
 #define INITIAL_FLUSH 16
@@ -472,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;
 }
index f9c41da475289b84fe849f7641406ebc94059630..93c99385a95e6ab525fa64c1dbb70d5d4ca103ba 100644 (file)
@@ -875,6 +875,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
 {
        int i;
        static const char **refs = NULL;
+       struct refspec *refspec;
        int ref_nr = 0;
        int exit_code;
 
@@ -915,8 +916,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
 
        sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
-       exit_code = do_fetch(transport,
-                       parse_fetch_refspec(ref_nr, refs), ref_nr);
+       refspec = parse_fetch_refspec(ref_nr, refs);
+       exit_code = do_fetch(transport, refspec, ref_nr);
+       free(refspec);
        transport_disconnect(transport);
        transport = NULL;
        return exit_code;
index ff5f73ba87b23fc7e361a1751c2d9a64e0267163..0498094711d1addd40f526f0c76dd8ddb76ef550 100644 (file)
@@ -225,7 +225,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        fprintf(stderr,
                                        _("Auto packing the repository for optimum performance. You may also\n"
                                        "run \"git gc\" manually. See "
-                                       "\"git help gc\" for more information."));
+                                       "\"git help gc\" for more information.\n"));
        } else
                append_option(argv_repack,
                              prune_expire && !strcmp(prune_expire, "now")
index 298f763b702f80c4f8d9f37b3356d36b2b8da32f..871afaa3c76ea34b67671a6e4a46957a08a946e5 100644 (file)
@@ -533,18 +533,18 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
 static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
                     struct tree_desc *tree, struct strbuf *base, int tn_len)
 {
-       int hit = 0, matched = 0;
+       int hit = 0, match = 0;
        struct name_entry entry;
        int old_baselen = base->len;
 
        while (tree_entry(tree, &entry)) {
                int te_len = tree_entry_len(entry.path, entry.sha1);
 
-               if (matched != 2) {
-                       matched = tree_entry_interesting(&entry, base, tn_len, pathspec);
-                       if (matched == -1)
-                               break; /* no more matches */
-                       if (!matched)
+               if (match != 2) {
+                       match = tree_entry_interesting(&entry, base, tn_len, pathspec);
+                       if (match < 0)
+                               break;
+                       if (match == 0)
                                continue;
                }
 
@@ -1003,13 +1003,7 @@ 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;
index b96f46acf52d23f68c1ec164be8264396dea22db..33911fd5e9325210dae63b3252fa3b56d42b545c 100644 (file)
@@ -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, 1))
+           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);
index 31f001f1059ec533ee62d6bd951b068d7f924f50..e40451ffb4ecd31107bfe51817cdce18e1dfe859 100644 (file)
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
 #include "delta.h"
 #include "pack.h"
 #include "csum-file.h"
index b7370d9bb8f235f1cf39b1ffa768498ca1e88654..025aa47c804e4400cfa16ae8acd8dc2a5175e7c0 100644 (file)
@@ -319,10 +319,10 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir,
                struct stat st;
 
                if (!exist_ok && !stat(git_dir, &st))
-                       die("%s already exists", git_dir);
+                       die(_("%s already exists"), git_dir);
 
                if (!exist_ok && !stat(real_git_dir, &st))
-                       die("%s already exists", real_git_dir);
+                       die(_("%s already exists"), real_git_dir);
 
                /*
                 * make sure symlinks are resolved because we'll be
@@ -351,15 +351,15 @@ static void separate_git_dir(const char *git_dir)
                else if (S_ISDIR(st.st_mode))
                        src = git_link;
                else
-                       die("unable to handle file type %d", st.st_mode);
+                       die(_("unable to handle file type %d"), st.st_mode);
 
                if (rename(src, git_dir))
-                       die_errno("unable to move %s to %s", 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);
+               die(_("Could not create git link %s"), git_link);
        fprintf(fp, "gitdir: %s\n", git_dir);
        fclose(fp);
 }
@@ -490,7 +490,7 @@ 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('L', "separate-git-dir", &real_git_dir, "gitdir",
+               OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                           "separate git dir from working tree"),
                OPT_END()
        };
index 9db43edb063bbcb4da3b213d92d14b5c92aa4cfb..5c2af590047d92554d87acc5d7113a2f1f730a96 100644 (file)
 /* 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);
@@ -92,35 +129,21 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
                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.
@@ -247,6 +277,8 @@ 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);
@@ -277,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;
@@ -291,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")) {
@@ -333,9 +376,11 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
 static void show_tagger(char *buf, int len, struct rev_info *rev)
 {
        struct strbuf out = STRBUF_INIT;
+       struct pretty_print_context pp = {0};
 
-       pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
-               get_log_output_encoding());
+       pp.fmt = rev->commit_format;
+       pp.date_mode = rev->date_mode;
+       pp_user_info(&pp, "Tagger", &out, buf, get_log_output_encoding());
        printf("%s", out.buf);
        strbuf_release(&out);
 }
@@ -396,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);
@@ -403,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;
@@ -449,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;
@@ -482,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);
 }
@@ -623,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;
@@ -639,7 +681,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev)
 
        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)
@@ -718,13 +760,12 @@ 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 *subject_start = NULL;
        const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
        const char *msg;
-       const char *extra_headers = rev->extra_headers;
        struct shortlog log;
        struct strbuf sb = STRBUF_INIT;
        int i;
@@ -732,6 +773,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
        struct diff_options opts;
        int need_8bit_cte = 0;
        struct commit *commit = NULL;
+       struct pretty_print_context pp = {0};
 
        if (rev->commit_format != CMIT_FMT_EMAIL)
                die(_("Cover letter needs email format"));
@@ -754,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) {
@@ -763,7 +805,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
                free(commit);
        }
 
-       log_write_email_headers(rev, head, &subject_start, &extra_headers,
+       log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
                                &need_8bit_cte);
 
        for (i = 0; !need_8bit_cte && i < nr; i++)
@@ -771,11 +813,11 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
                        need_8bit_cte = 1;
 
        msg = body;
-       pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
-                    encoding);
-       pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
-                     encoding, need_8bit_cte);
-       pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+       pp.fmt = CMIT_FMT_EMAIL;
+       pp.date_mode = DATE_RFC2822;
+       pp_user_info(&pp, NULL, &sb, committer, encoding);
+       pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
+       pp_remainder(&pp, &msg, &sb, 0);
        printf("%s\n", sb.buf);
 
        strbuf_release(&sb);
@@ -995,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",
@@ -1050,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()
        };
 
@@ -1136,6 +1181,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                die (_("-n and -k are mutually exclusive."));
        if (keep_subject && subject_prefix)
                die (_("--subject-prefix and -k are mutually exclusive."));
+       rev.preserve_subject = keep_subject;
 
        argc = setup_revisions(argc, argv, &rev, &s_r_opt);
        if (argc > 1)
@@ -1259,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--;
        }
@@ -1305,7 +1351,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                }
 
                if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
-                                                &rev))
+                                                &rev, quiet))
                        die(_("Failed to create output files"));
                shown = log_tree_commit(&rev, commit);
                free(commit->buffer);
@@ -1366,8 +1412,7 @@ static void print_commit(char sign, struct commit *commit, int verbose,
                       find_unique_abbrev(commit->object.sha1, abbrev));
        } else {
                struct strbuf buf = STRBUF_INIT;
-               struct pretty_print_context ctx = {0};
-               pretty_print_commit(CMIT_FMT_ONELINE, commit, &buf, &ctx);
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);
                printf("%c %s %s\n", sign,
                       find_unique_abbrev(commit->object.sha1, abbrev),
                       buf.buf);
index fb2d5f4b1fb0ce9ef2fb0c4099b5fea6d07b6838..15701233e29b240cfa1abdb56bd489306e74c677 100644 (file)
@@ -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++) {
index 1a1ff87e8f9ed7db84f106960f36888835f7057b..10223092a9ebfed4092db680529a42ca8886a9f2 100644 (file)
@@ -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
@@ -35,6 +35,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        unsigned flags = 0;
        int get_url = 0;
        int quiet = 0;
+       int status = 0;
        const char *uploadpack = NULL;
        const char **pattern = NULL;
 
@@ -74,6 +75,11 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
                                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;
@@ -121,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;
 }
index f73e6bd9626a0e929a513fed1fd1227c28732ec7..f08c5b0c942eec58b85ab41e5e5fb1ec216df683 100644 (file)
@@ -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;
 }
index 71e6262a87d883a4a82953db5f742bccf99e5c77..bfb32b7233850a68bdc226038a9c0f973037499b 100644 (file)
@@ -400,7 +400,7 @@ static int read_one_header_line(struct strbuf *line, FILE *in)
                        break;
                if (strbuf_getline(&continuation, in, '\n'))
                        break;
-               continuation.buf[0] = '\n';
+               continuation.buf[0] = ' ';
                strbuf_rtrim(&continuation);
                strbuf_addbuf(line, &continuation);
        }
index 96dd160731ce29b18be646893057929bab97cb8a..4f30f1b0c8b14e78e272ec54a86584d66501f754 100644 (file)
@@ -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
 };
index 19917426fba19cf60b838ff9b07d20b5e44259a5..897a563bc6662e108c29656d633b7400384154f9 100644 (file)
@@ -3,6 +3,7 @@
 #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;
index d54e7ddbb12286beb4f9622864f2eeaa79453d32..325891edb610945d01899b102993202af279bf3f 100644 (file)
@@ -56,6 +56,7 @@ 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;
@@ -338,13 +339,14 @@ static void squash_message(void)
 
        ctx.abbrev = rev.abbrev;
        ctx.date_mode = rev.date_mode;
+       ctx.fmt = rev.commit_format;
 
        strbuf_addstr(&out, "Squashed commit of the following:\n");
        while ((commit = get_revision(&rev)) != NULL) {
                strbuf_addch(&out, '\n');
                strbuf_addf(&out, "commit %s\n",
                        sha1_to_hex(commit->object.sha1));
-               pretty_print_commit(rev.commit_format, commit, &out, &ctx);
+               pretty_print_commit(&ctx, commit, &out);
        }
        if (write(fd, out.buf, out.len) < 0)
                die_errno(_("Writing SQUASH_MSG"));
@@ -503,26 +505,34 @@ static void merge_name(const char *remote, struct strbuf *msg)
        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"))
@@ -541,6 +551,15 @@ static int git_merge_config(const char *k, const char *v, void *cb)
                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;
@@ -590,6 +609,14 @@ static void write_tree_trivial(unsigned char *sha1)
                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, size_t xopts_nr,
                      const char **xopts, struct commit_list *common,
                      const char *head_arg, struct commit_list *remotes)
@@ -610,11 +637,11 @@ int try_merge_command(const char *strategy, size_t xopts_nr,
                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);
@@ -822,7 +849,7 @@ 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"));
+               die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
 }
 
 static void run_prepare_commit_msg(void)
@@ -962,16 +989,16 @@ static int setup_with_upstream(const char ***argv)
        const char **args;
 
        if (!branch)
-               die("No current branch.");
+               die(_("No current branch."));
        if (!branch->remote)
-               die("No remote for the current branch.");
+               die(_("No remote for the current branch."));
        if (!branch->merge_nr)
-               die("No default upstream defined for the current branch.");
+               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",
+                       die(_("No remote tracking branch for %s from %s"),
                            branch->merge[i]->src, branch->remote_name);
                args[i] = branch->merge[i]->dst;
        }
@@ -1010,6 +1037,8 @@ 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);
 
@@ -1043,10 +1072,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        }
        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.");
+                       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 cherry-pick (CHERRY_PICK_HEAD exists).");
+                       die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."));
        }
        resolve_undo_clear();
 
@@ -1062,9 +1091,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        if (!allow_fast_forward && fast_forward_only)
                die(_("You cannot combine --no-ff with --ff-only."));
 
-       if (!argc && !abort_current_merge && default_to_upstream)
-               argc = setup_with_upstream(&argv);
-
+       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);
index 324a267163d758b5dea51405265ae89042a657ad..640ab64f418208842ae3901ce37ac8918740037e 100644 (file)
@@ -23,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))
index d6dcfcb0149ab6dc45f4a71e21cf77d13b494e18..f8e437db0156043f1586e66adf343e34ef6cf4dc 100644 (file)
@@ -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)
@@ -529,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;
@@ -556,14 +548,14 @@ 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) {
+       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);
@@ -573,6 +565,18 @@ static int add(int argc, const char **argv, const char *prefix)
 
        if (note) {
                if (!force) {
+                       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));
@@ -949,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;
 }
index b0503b202afb4356caaca794518fb4e21082a48f..f402a843bb5ee23360d29bdbbc6cc9c5b9c72008 100644 (file)
@@ -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);
 }
index 93c92814cf28855aee87cabf4a3b906e11a36125..df6c4c8819e7903f34cd6dc6bb0078a6cddac310 100644 (file)
@@ -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 */
 
        /*
index e1ba4dc697abd1b2582713744c5a1892ef4a4882..e1a687ad0761e46f6ec95659bb585b9014d4abf4 100644 (file)
@@ -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,14 +732,23 @@ static int delete_only(struct command *commands)
        return 1;
 }
 
-static void add_one_alternate_ref(const struct ref *ref, void *unused)
+static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused)
 {
-       add_extra_ref(".have", ref->old_sha1, 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(refs_from_alternate_cb, add_one_alternate_ref);
+       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)
index 8424152269e309cb86e4ff530b9feacc23fc17a6..9ff1cac69b9fd21c5f7dfc06b5859fedccc67644 100644 (file)
@@ -193,8 +193,8 @@ static int add(int argc, const char **argv)
 
        if (mirror && master)
                die("specifying a master branch makes no sense with --mirror");
-       if (mirror && track.nr)
-               die("specifying branches to track 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];
index 82358855d1da90b75f94c3d424a5da5c0f97960e..08213c7c0bc6fa5d649a38520dc87222019babe2 100644 (file)
@@ -12,74 +12,6 @@ static const char * const rerere_usage[] = {
        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;
@@ -148,14 +80,9 @@ 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);
index 9bfb94201f298f5902c6da13b12f4ca539bb86ed..56727e8c1d9d87fe3e3bf3919ba86d27d0735758 100644 (file)
@@ -55,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++;
@@ -102,7 +104,8 @@ static void show_commit(struct commit *commit, void *data)
                struct pretty_print_context ctx = {0};
                ctx.abbrev = revs->abbrev;
                ctx.date_mode = revs->date_mode;
-               pretty_print_commit(revs->commit_format, commit, &buf, &ctx);
+               ctx.fmt = revs->commit_format;
+               pretty_print_commit(&ctx, commit, &buf);
                if (revs->graph) {
                        if (buf.len) {
                                if (revs->commit_format != CMIT_FMT_ONELINE)
@@ -406,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);
        }
index adb1cae4f27ad3993aa2418f7b0fd355d19cdab9..4c19f844a977b6413afcd5df6b4f8bc0875775a8 100644 (file)
@@ -44,6 +44,7 @@ static int is_rev_argument(const char *arg)
                "--branches=",
                "--branches",
                "--header",
+               "--ignore-missing",
                "--max-age=",
                "--max-count=",
                "--min-age=",
index f697e6695374d06e7b08c9faac1ebaefe4ff31d7..1f27c63343904a969e60ab19408495007f59d133 100644 (file)
@@ -408,8 +408,6 @@ static int do_pick_commit(void)
        discard_cache();
 
        if (!commit->parents) {
-               if (action == REVERT)
-                       die (_("Cannot revert a root commit"));
                parent = NULL;
        }
        else if (commit->parents->next) {
@@ -467,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));
                }
index 8b0911c0d2ac5a60f8a3c7a43e7960617ded9d7a..c1f6ddd927d61fbc2558ee3624224af405d918c3 100644 (file)
@@ -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;
index f5efc67c9cad24fe54d5c6ab7257d38a4b1727ac..37f3193a3366725a6b11bdb55db7b6ffc6e9b3ce 100644 (file)
@@ -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)
@@ -141,9 +138,8 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
        const char *author = NULL, *buffer;
        struct strbuf buf = STRBUF_INIT;
        struct strbuf ufbuf = STRBUF_INIT;
-       struct pretty_print_context ctx = {0};
 
-       pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+       pp_commit_easy(CMIT_FMT_RAW, commit, &buf);
        buffer = buf.buf;
        while (*buffer && *buffer != '\n') {
                const char *eol = strchr(buffer, '\n');
@@ -162,11 +158,12 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
                    sha1_to_hex(commit->object.sha1));
        if (log->user_format) {
                struct pretty_print_context ctx = {0};
+               ctx.fmt = CMIT_FMT_USERFORMAT;
                ctx.abbrev = log->abbrev;
                ctx.subject = "";
                ctx.after_subject = "";
                ctx.date_mode = DATE_NORMAL;
-               pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
+               pretty_print_commit(&ctx, commit, &ufbuf);
                buffer = ufbuf.buf;
        } else if (*buffer) {
                buffer++;
index da695815e26c281cbacfbf865dfa72da44df9f19..facc63a79ec0c86fd9df59792cd4f20030af5db1 100644 (file)
@@ -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 "";
 }
 
@@ -293,8 +283,7 @@ static void show_one_commit(struct commit *commit, int no_name)
        struct commit_name *name = commit->util;
 
        if (commit->object.parsed) {
-               struct pretty_print_context ctx = {0};
-               pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx);
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
                pretty_str = pretty.buf;
        }
        if (!prefixcmp(pretty_str, "[PATCH] "))
@@ -892,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]);
                        }
@@ -954,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(' ');
index b66b34a1820b3c258ddd85180442df865becee15..ec926fc8ee4512abddc67309421b4394706329f5 100644 (file)
@@ -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;
 
@@ -452,12 +463,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (get_sha1(object_ref, object))
                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))
+       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);
@@ -466,14 +475,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                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));
 
        strbuf_release(&buf);
+       strbuf_release(&ref);
        return 0;
 }
index d7850c6309aec0c1a4f640999fb757d7b32ba1b8..a6a23fa1f3c7782566d7fcfe470dd424b876e4a5 100644 (file)
@@ -99,8 +99,11 @@ 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)) {
+               free(ce);
                return -1;
+       }
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
        if (add_cache_entry(ce, option))
diff --git a/cache.h b/cache.h
index 2674f4cf5a74aaa8e6137985d552d7b6123a432c..e11cf6ab1c73ac97c94e76e8c8699d55af95b978 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -511,15 +511,18 @@ struct pathspec {
        struct pathspec_item {
                const char *match;
                int len;
-               unsigned int has_wildcard:1;
+               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);
-extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path, int format_check);
-extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
+
+#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 */
@@ -573,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;
@@ -605,7 +609,7 @@ enum eol {
 #endif
 };
 
-extern enum eol eol;
+extern enum eol core_eol;
 
 enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
@@ -675,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);
@@ -745,13 +759,23 @@ 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 *);
@@ -789,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! */
@@ -964,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;
@@ -1001,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);
diff --git a/color.c b/color.c
index 417cf8fb2812b09ad4f5bfa2674b35d7e11f32b9..3db214c24720496f13481b718e46810c21f53b8d 100644 (file)
--- a/color.c
+++ b/color.c
@@ -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[] = {
diff --git a/color.h b/color.h
index c0528cf08713ac8e101cfdeac2b3de193a0c5094..68a926a2cdfb870ae0da0bfbc4c5b36681609911 100644 (file)
--- a/color.h
+++ b/color.h
@@ -53,6 +53,9 @@ struct strbuf;
  */
 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.
index 41985130d1473573af2cfd4ca97a579e999b0c0b..a2d571b97410fa857b4c177325c4556dac50fe3f 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -69,9 +69,11 @@ enum cmit_fmt {
 };
 
 struct pretty_print_context {
+       enum cmit_fmt fmt;
        int abbrev;
        const char *subject;
        const char *after_subject;
+       int preserve_subject;
        enum date_mode date_mode;
        int need_8bit_cte;
        int show_notes;
@@ -90,24 +92,26 @@ 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,
                                  const struct pretty_print_context *context);
-extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
-                               struct strbuf *sb,
-                               const struct pretty_print_context *context);
-void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
-                  const char *line, enum date_mode dmode,
-                  const char *encoding);
-void pp_title_line(enum cmit_fmt fmt,
+extern void pretty_print_commit(const struct pretty_print_context *pp,
+                               const struct commit *commit,
+                               struct strbuf *sb);
+extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+                          struct strbuf *sb);
+void pp_user_info(const struct pretty_print_context *pp,
+                 const char *what, struct strbuf *sb,
+                 const char *line, const char *encoding);
+void pp_title_line(const struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
-                  const char *subject,
-                  const char *after_subject,
                   const char *encoding,
                   int need_8bit_cte);
-void pp_remainder(enum cmit_fmt fmt,
+void pp_remainder(const struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent);
@@ -143,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);
@@ -159,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);
 
index 14feac7fe179069908df6dbc084b2adcc78c9242..9473aed2bbcb91994f166cf66d24459c66ac2fcb 100644 (file)
@@ -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
index 4423961768b7389e07090ba8531e086f21d678cf..f6e9ff7762356e099d2fe20fd31359bc0a1f68a2 100644 (file)
@@ -1381,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)
 {
index 62eccd33911e2f332dc9d0faf097ecff6d6a7aa6..547568b9181d61d50f06cf5b4b0ab43af78e5aa2 100644 (file)
@@ -217,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
 
index 0abcada9381a3e6638f94bcf41bf9ee7b96f4de8..e0b3b80d92ece9f37e0e76292bd61ce97271fcce 100644 (file)
--- a/config.c
+++ b/config.c
@@ -20,14 +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++)
@@ -47,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);
@@ -59,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;
@@ -92,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;
@@ -102,7 +90,7 @@ int git_config_parse_environment(void) {
 
        free(argv);
        free(envw);
-       return 0;
+       return nr > 0;
 }
 
 static int get_next_char(void)
@@ -133,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;
                }
@@ -160,7 +146,7 @@ static char *parse_value(void)
                        }
                }
                for (; space; space--)
-                       value[len++] = ' ';
+                       strbuf_addch(&value, ' ');
                if (c == '\\') {
                        c = get_next_char();
                        switch (c) {
@@ -182,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);
        }
 }
 
@@ -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;
        }
@@ -833,22 +825,6 @@ int git_config_system(void)
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 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;
@@ -878,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;
 }
@@ -1119,12 +1102,12 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_)
 
        if (last_dot == NULL || last_dot == key) {
                error("key does not contain a section: %s", key);
-               return -2;
+               return -CONFIG_NO_SECTION_OR_NAME;
        }
 
        if (!last_dot[1]) {
                error("key does not contain variable name: %s", key);
-               return -2;
+               return -CONFIG_NO_SECTION_OR_NAME;
        }
 
        baselen = last_dot - key;
@@ -1161,7 +1144,7 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_)
 
 out_free_ret_1:
        free(*store_key);
-       return -1;
+       return -CONFIG_INVALID_KEY;
 }
 
 /*
@@ -1217,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;
        }
 
@@ -1231,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;
                }
 
@@ -1264,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;
                        }
                }
@@ -1286,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;
                }
 
@@ -1299,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;
                }
 
@@ -1360,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;
        }
 
index f30130b17a22a6227496b135812cd2ac660ed19b..ab371012a22f39bf31f512e276e12f55b6d1b8b7 100644 (file)
@@ -18,6 +18,7 @@ bindir = @bindir@
 gitexecdir = @libexecdir@/git-core
 datarootdir = @datarootdir@
 template_dir = @datadir@/git-core/templates
+sysconfdir = @sysconfdir@
 
 mandir=@mandir@
 
index 57dc20c43ca1ba205ec0a18e263f9dde081390f4..2119c3f74edcd2a976ff0e375c5d8d60d40da2ec 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -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);
index 2facd08d1639115188d8e0bcec35c47f4ac793c4..b36290fa6015d9b66dc6a5891ff12f85f279a1c3 100755 (executable)
@@ -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/).
 # 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
 #       git@vger.kernel.org
 #
 
+if [[ -n ${ZSH_VERSION-} ]]; then
+       autoload -U +X bashcompinit && bashcompinit
+fi
+
 case "$COMP_WORDBREAKS" in
 *:*) : great ;;
 *)   COMP_WORDBREAKS="$COMP_WORDBREAKS:"
@@ -489,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=()
                ;;
@@ -502,7 +502,7 @@ __gitcomp ()
                local IFS=$'\n'
                COMPREPLY=($(compgen -P "${2-}" \
                        -W "$(__gitcomp_1 "${1-}" "${4-}")" \
-                       -- "$cur"))
+                       -- "$cur_"))
                ;;
        esac
 }
@@ -551,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/*)
@@ -629,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*/}"
@@ -666,19 +665,18 @@ __git_compute_merge_strategies ()
 
 __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/"
                        ;;
@@ -708,17 +706,17 @@ __git_complete_revlist_file ()
                                           s,$,/,
                                       }
                                       s/^.*    //')" \
-                       -- "$cur"))
+                       -- "$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"
+               pfx="${cur_%..*}.."
+               cur_="${cur_#*..}"
+               __gitcomp "$(__git_refs)" "$pfx" "$cur_"
                ;;
        *)
                __gitcomp "$(__git_refs)"
@@ -739,9 +737,7 @@ __git_complete_revlist ()
 
 __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]}"
@@ -771,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
@@ -812,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)
@@ -991,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
@@ -1007,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
@@ -1022,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
@@ -1047,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=}"
@@ -1071,8 +1060,6 @@ _git_add ()
 {
        __git_has_doubledash && return
 
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "
@@ -1086,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=}"
@@ -1135,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
@@ -1167,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)
@@ -1191,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=}"
@@ -1222,8 +1202,6 @@ _git_cherry ()
 
 _git_cherry_pick ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "--edit --no-commit"
@@ -1238,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"
@@ -1251,8 +1227,6 @@ _git_clean ()
 
 _git_clone ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "
@@ -1279,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
@@ -1315,8 +1287,6 @@ _git_commit ()
 
 _git_describe ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "
@@ -1348,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
@@ -1370,8 +1338,6 @@ _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=}"
@@ -1396,8 +1362,6 @@ __git_fetch_options="
 
 _git_fetch ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "$__git_fetch_options"
@@ -1409,8 +1373,6 @@ _git_fetch ()
 
 _git_format_patch ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --thread=*)
                __gitcomp "
@@ -1442,8 +1404,6 @@ _git_format_patch ()
 
 _git_fsck ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "
@@ -1458,8 +1418,6 @@ _git_fsck ()
 
 _git_gc ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "--prune --aggressive"
@@ -1478,8 +1436,6 @@ _git_grep ()
 {
        __git_has_doubledash && return
 
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "
@@ -1503,8 +1459,6 @@ _git_grep ()
 
 _git_help ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "--all --info --man --web"
@@ -1522,8 +1476,6 @@ _git_help ()
 
 _git_init ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --shared=*)
                __gitcomp "
@@ -1543,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
@@ -1585,7 +1535,7 @@ __git_log_common_options="
 __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="
@@ -1605,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)
@@ -1660,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"
@@ -1672,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=}"
@@ -1694,8 +1638,6 @@ _git_merge_base ()
 
 _git_mv ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "--dry-run"
@@ -1714,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
        ,--*)
@@ -1765,8 +1705,6 @@ _git_pull ()
 {
        __git_complete_strategy && return
 
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "
@@ -1782,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)"
@@ -1808,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
@@ -1851,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 "
@@ -1894,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]}"
@@ -1926,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)"
@@ -2013,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
@@ -2397,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"
@@ -2410,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"
@@ -2425,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"
@@ -2440,8 +2352,6 @@ _git_shortlog ()
 {
        __git_has_doubledash && return
 
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "
@@ -2459,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)
@@ -2484,8 +2392,6 @@ _git_show ()
 
 _git_show_branch ()
 {
-       local cur
-       _get_comp_words_by_ref -n =: cur
        case "$cur" in
        --*)
                __gitcomp "
@@ -2502,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")"
@@ -2548,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"
@@ -2593,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"
@@ -2666,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
@@ -2711,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
@@ -2762,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 "
@@ -2801,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
@@ -2824,4 +2731,8 @@ if [[ -n ${ZSH_VERSION-} ]]; then
                        return 1
                esac
        }
+else
+       __git_shopt () {
+               shopt "$@"
+       }
 fi
index 78e5b3aaf4b70b87afbd5df0c9f839776310d18a..98d2aee67fc1f6bfdfc960527b6f0bf6acf5122a 100755 (executable)
@@ -474,6 +474,47 @@ class Command:
         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)
@@ -554,13 +595,16 @@ 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="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]"
@@ -568,7 +612,9 @@ class P4Submit(Command):
         self.origin = ""
         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:
@@ -602,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 = ""
@@ -631,6 +770,8 @@ class P4Submit(Command):
     def applyCommit(self, id):
         print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
 
+        (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"
@@ -748,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 = ""
@@ -764,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()
@@ -781,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) ")
@@ -795,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);
@@ -831,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
 
@@ -858,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:]
@@ -877,11 +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"),
@@ -1236,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 = {}
 
index e09da445b692db2a1cbdba7ee49456de6e2571e9..caa4bb3e30c081394b464de6e92ca3298a87fdd1 100644 (file)
@@ -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
 
@@ -196,6 +202,36 @@ 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...
 =========================
 
index 7eb51b16ed03e650f3af11383c5c43c00d7d9812..efc7e07d475c66f7835dc6cbbd3bc358f01c41c3 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -12,7 +12,7 @@
  * translation when the "text" attribute or "auto_crlf" option is set.
  */
 
-enum action {
+enum crlf_action {
        CRLF_GUESS = -1,
        CRLF_BINARY = 0,
        CRLF_TEXT,
@@ -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/ctype.c b/ctype.c
index de600279eef4765db497599e6654c2bedd978129..b5d856fd26bd892a5f18202b054fc53e7c953429 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -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 */
 };
diff --git a/date.c b/date.c
index 00f9eb5d0b9730107a8e08e92eb04d4cc9233595..896fbb48060c673b8dc2f2aa6ea3de3c1042fbd2 100644 (file)
--- a/date.c
+++ b/date.c
@@ -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" : ""));
index 2870de400ed533d83c77269ee1654af212c6510c..9c29293bbc05d175ba13338813e8532c7ad677cf 100644 (file)
@@ -102,9 +102,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                int changed;
                unsigned dirty_submodule = 0;
 
-               if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
-                   !revs->diffopt.filter &&
-                   DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
+               if (diff_can_quit_early(&revs->diffopt))
                        break;
 
                if (!ce_path_match(ce, &revs->prune_data))
@@ -112,6 +110,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 
                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;
 
@@ -130,7 +130,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);
@@ -138,7 +138,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];
@@ -184,7 +186,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;
                }
@@ -373,8 +377,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;
        }
 
diff --git a/diff.c b/diff.c
index 9fa841010cc21bebc464926b15232b2aace0f5ba..61bedaed57216cadaf2716ef7aa0d63889571f8a 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -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);
 
@@ -581,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);
@@ -1050,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;
        }
@@ -1452,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,
@@ -1499,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;
                        }
@@ -1530,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;
@@ -1538,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;
+
+               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;
+               }
 
-               name = p->one->path ? p->one->path : p->two->path;
+               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);
@@ -1563,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;
@@ -1587,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;
@@ -1949,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)
@@ -1969,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;
@@ -2853,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;
@@ -3111,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];
@@ -3130,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;
@@ -3168,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)
@@ -3955,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
@@ -3980,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));
@@ -3995,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)) {
@@ -4236,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;
@@ -4248,6 +4456,13 @@ int diff_result_code(struct diff_options *opt, int status)
        return result;
 }
 
+int diff_can_quit_early(struct diff_options *opt)
+{
+       return (DIFF_OPT_TST(opt, QUICK) &&
+               !opt->filter &&
+               DIFF_OPT_TST(opt, HAS_CHANGES));
+}
+
 /*
  * Shall changes to this submodule be ignored?
  *
@@ -4349,20 +4564,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,
diff --git a/diff.h b/diff.h
index 007a0554d4b252e83e98f5578758d51ec0c6e120..6d303c1d50aa799ec90aeb64b2a1b1f55811dd46 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -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,6 +105,7 @@ struct diff_options {
        int interhunkcontext;
        int break_opt;
        int detect_rename;
+       int irreversible_delete;
        int skip_stat_unmatch;
        int line_termination;
        int output_format;
@@ -111,8 +113,9 @@ struct diff_options {
        int rename_score;
        int rename_limit;
        int needed_rename_limit;
+       int degraded_cc_to_c;
        int show_rename_progress;
-       int dirstat_percent;
+       int dirstat_permille;
        int setup;
        int abbrev;
        const char *prefix;
@@ -195,6 +198,8 @@ extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_
 
 void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b);
 
+extern int diff_can_quit_early(struct diff_options *);
+
 extern void diff_addremove(struct diff_options *,
                           int addremove,
                           unsigned mode,
@@ -208,10 +213,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
@@ -273,6 +275,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'
index d40e40a3ac9c919475d6157464fa906e37b0dd80..f639601c762ebbd12374fa739d1d63efaf265e2a 100644 (file)
@@ -55,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) {
@@ -90,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]);
 }
@@ -205,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++;
 
@@ -389,7 +390,7 @@ static int find_exact_renames(struct diff_options *options)
 
        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);
@@ -419,6 +420,55 @@ 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;
@@ -432,7 +482,7 @@ static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, i
                dst = &rename_dst[mx[i].dst];
                if (dst->pair)
                        continue; /* already done, either exact or fuzzy. */
-               if (!copies && rename_src[mx[i].src].one->rename_used)
+               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++;
@@ -444,12 +494,11 @@ 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)
@@ -466,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,
@@ -476,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) {
                        /*
@@ -484,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)
@@ -505,29 +554,20 @@ 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)
-        */
-       options->needed_rename_limit = 0;
-       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)) {
-               options->needed_rename_limit =
-                       num_src > num_create ? num_src : num_create;
+       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) {
@@ -549,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);
@@ -586,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
                         *
diff --git a/dir.c b/dir.c
index 325fb56ad395c9b47dcbdea47b2833a4287198bc..08281d2ef74ea7790913e71f08d00299a1825765 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -230,7 +230,7 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
                        return MATCHED_RECURSIVELY;
        }
 
-       if (item->has_wildcard && !fnmatch(match, name, 0))
+       if (item->use_wildcard && !fnmatch(match, name, 0))
                return MATCHED_FNMATCH;
 
        return 0;
@@ -1105,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;
-
-       if (!dir)
-               return NULL;
-       if (!getcwd(buffer, size))
-               die_errno("can't find the current directory");
+       int offset = 0;
 
-       if (!is_absolute_path(dir))
-               dir = real_path(dir);
+       assert(dir && subdir && *dir && *subdir);
 
-       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)
@@ -1192,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, '/');
 
@@ -1286,8 +1274,8 @@ int init_pathspec(struct pathspec *pathspec, const char **paths)
 
                item->match = path;
                item->len = strlen(path);
-               item->has_wildcard = !no_wildcard(path);
-               if (item->has_wildcard)
+               item->use_wildcard = !no_wildcard(path);
+               if (item->use_wildcard)
                        pathspec->has_wildcard = 1;
        }
 
diff --git a/dir.h b/dir.h
index aa511da77b0ec54cbdbd3786faad0c1285df7182..433b5b4cd4c51e9b7557d3056570ed46b7ceba92 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -85,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)
 {
index f4549d3f7b0367b31b0fa0042fb12ff183beb081..94d58fd24413ec1d1a5769029bd5149b609f0d4e 100644 (file)
@@ -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;
index 65d65bf8f91b7a584c7dc77f84a5dec263c3d11c..78d978684da2e2b79340c138e1b6300100df1221 100644 (file)
@@ -274,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;
@@ -3136,11 +3135,11 @@ static int parse_one_feature(const char *feature, int from_stream)
                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 */
@@ -3206,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);
 }
 
diff --git a/fsck.c b/fsck.c
index 6f266c1ea4b8df503c211170741fa5b490a6a02a..60bd4bbf6a2206e2b1991093ea5d84be7f338f73 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -349,7 +349,7 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...)
        va_list ap;
        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);
        strbuf_vaddf(&sb, fmt, ap);
index a329c5a1f8c3b63ee6976a8e0e3903a11783324b..8f0839d205e0c4010e256bb5cf81c73cc2f438ab 100755 (executable)
@@ -45,6 +45,9 @@
 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")) {
                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 @@ sub edit_hunk_manually {
 
 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 @@ sub help_patch_cmd {
 
 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;
index 40498b33c9f09ef9cac1f7340a9a1ceb2ffcd50d..e0bb81ed8d0bd89f18b31b1c03d3e23744aea5a1 100644 (file)
@@ -463,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)
@@ -473,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)
 {
index bbf327f3e891cacdd14c0b18b51702ce14b2b6df..8d41610bcf260545bcc5b6a8c30b43d427c70491 100755 (executable)
@@ -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);
index b86402afa5d079df8d1cf6eebb15e5727cc8a5de..7aeb96952f668baef2fd18383806e13a20415f19 100755 (executable)
@@ -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
index fb3f52ba2507b4fb78db150b5c183f24bd2c57c1..4db9212331259664732f031438b7b87b7a10244f 100644 (file)
@@ -258,12 +258,9 @@ run_merge_tool () {
                ;;
        p4merge)
                if merge_mode; then
-                   touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
-                       else
-                               "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED"
-                       fi
+                       touch "$BACKUP"
+                       $base_present || >"$BASE"
+                       "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
                        check_unchanged
                else
                        "$merge_tool_path" "$LOCAL" "$REMOTE"
index bacbda2bb75854235b912903c7e45fc94d38e719..3aab5aae844b963fc4178675a56da20e02488dfd 100755 (executable)
@@ -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"
index ea093d251d98af6a3d4df19e900d56df34d51572..b24119d69c092e3bd345cff1b6bafd48f5fd1e1b 100644 (file)
@@ -50,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
+}
index 4e9e0e49ecf2532ac8af1bfd46033e68c6f1042d..fb9e2df9312e96ecf8ad5ab2bde4b74b979fe02e 100755 (executable)
@@ -168,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."
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
new file mode 100644 (file)
index 0000000..c815a24
--- /dev/null
@@ -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
old mode 100755 (executable)
new mode 100644 (file)
index 5873ba4..65690af
 # 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 (file)
index 0000000..26afc75
--- /dev/null
@@ -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
index cbb0ea90ed410d5af68ce814b7fd93a90829f3ee..d7855ea1c6dae8d8cbe35ae39f9c65e7bc9d7b11 100755 (executable)
@@ -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
index 76565de2ee517f48001ffacca32e3c08320cfe38..98ab33aae7e35c2d288bc34ddb2bbc71f8a16cdd 100755 (executable)
@@ -1091,7 +1091,7 @@ sub send_message {
                            "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 (file)
index 0000000..32ca59d
--- /dev/null
@@ -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
+
index aa16b8356507e4e669ee5c4cb4b5a667942d559e..94e26ed5e8dcf84c4f238c76b6c508dc84d0b7ea 100644 (file)
@@ -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."
index a305fb19f11bc4ae80e585b102ecb5d982d1a0f5..0a9403653d7dbbb6927973dcfcc41bfd9a904e05 100755 (executable)
@@ -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) ||
index b010a673097a9cfcf009b307114669b6221d066c..d189a24c71c44806a9c1381e2a8e5993269e568a 100755 (executable)
@@ -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>...]"
@@ -402,6 +402,9 @@ cmd_update()
                -N|--no-fetch)
                        nofetch=1
                        ;;
+               -f|--force)
+                       force=$1
+                       ;;
                -r|--rebase)
                        update="rebase"
                        ;;
@@ -480,10 +483,11 @@ 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"
@@ -491,7 +495,7 @@ cmd_update()
                                # Run fetch only if $sha1 isn't present or it
                                # is not reachable from a ref.
                                (clear_local_git_env; cd "$path" &&
-                                       ((rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
+                                       ( (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
@@ -515,7 +519,7 @@ cmd_update()
                                msg="merged in"
                                ;;
                        *)
-                               command="git checkout $force -q"
+                               command="git checkout $subforce -q"
                                action="checkout"
                                msg="checked out"
                                ;;
index bf0451b46835357af7811096538e9e7d75311166..7849cfc141d384bc28479c2f37fd128c77fe0fbe 100755 (executable)
@@ -784,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');
@@ -807,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 {
@@ -1245,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 {
@@ -5752,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};
diff --git a/git.c b/git.c
index ef598c3e7053b8dd2859f4d582ce2917a804fe42..89721d420a09bfc8eb6a2d4010f86f5685cfd255 100644 (file)
--- a/git.c
+++ b/git.c
@@ -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)
@@ -179,6 +183,8 @@ static int handle_alias(int *argcp, const char ***argv)
                if (alias_string[0] == '!') {
                        const char **alias_argv;
                        int argc = *argcp, i;
+                       struct strbuf sb = STRBUF_INIT;
+                       const char *env[2];
 
                        commit_pager_choice();
 
@@ -189,7 +195,13 @@ static int handle_alias(int *argcp, const char ***argv)
                                alias_argv[i] = (*argv)[i];
                        alias_argv[argc] = NULL;
 
-                       ret = run_command_v_opt(alias_argv, RUN_USING_SHELL);
+                       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);
 
index 4964a679b3013caa9bfa2f438dc110eaa0274b69..c5236fee9dced35ab35d02eb7d42109f0107a165 100644 (file)
@@ -25,11 +25,25 @@ The above example assumes that your web server is configured to run
 scripts).
 
 
+Requirements
+------------
+
+ - Core git tools
+ - Perl
+ - 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
+
+
 Build time configuration
 ------------------------
 
-See also "How to configure gitweb for your local system" in README
-file for gitweb (in gitweb/README).
+See also "How to configure gitweb for your local system" section below.
 
 - There are many configuration variables which affect building of
   gitweb.cgi; see "default configuration for gitweb" section in main
@@ -73,6 +87,125 @@ file for gitweb (in gitweb/README).
   substitute gitweb.min.js and gitweb.min.css for all uses of gitweb.js and
   gitweb.css in the help files.
 
+
+How to configure gitweb for your local system
+---------------------------------------------
+
+You can specify the following configuration variables when building GIT:
+
+ * GIT_BINDIR
+   Points where to find the git executable.  You should set it up to
+   the place where the git binary was installed (usually /usr/bin) if you
+   don't install git from sources together with gitweb.  [Default: $(bindir)]
+ * GITWEB_SITENAME
+   Shown in the title of all generated pages, defaults to the server name
+   (SERVER_NAME CGI environment variable) if not set. [No default]
+ * GITWEB_PROJECTROOT
+   The root directory for all projects shown by gitweb. Must be set
+   correctly for gitweb to find repositories to display.  See also
+   "Gitweb repositories" in the INSTALL file for gitweb.  [Default: /pub/git]
+ * GITWEB_PROJECT_MAXDEPTH
+   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).
+   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
+   if not set / if empty) or to a file with explicit listing of projects
+   (together with projects' ownership). See "Generating projects list
+   using gitweb" in INSTALL file for gitweb to find out how to generate
+   such file from scan of a directory. [No default, which means use root
+   directory for projects]
+ * GITWEB_EXPORT_OK
+   Show repository only if this file exists (in repository).  Only
+   effective if this variable evaluates to true.  [No default / Not set]
+ * GITWEB_STRICT_EXPORT
+   Only allow viewing of repositories also shown on the overview page.
+   This for example makes GITWEB_EXPORT_OK to decide if repository is
+   available and not only if it is shown.  If GITWEB_LIST points to
+   file with list of project, only those repositories listed would be
+   available for gitweb.  [No default]
+ * GITWEB_HOMETEXT
+   Points to an .html file which is included on the gitweb project
+   overview page ('projects_list' view), if it exists.  Relative to
+   gitweb.cgi script.  [Default: indextext.html]
+ * GITWEB_SITE_HEADER
+   Filename of html text to include at top of each page.  Relative to
+   gitweb.cgi script.  [No default]
+ * GITWEB_SITE_FOOTER
+   Filename of html text to include at bottom of each page.  Relative to
+   gitweb.cgi script.  [No default]
+ * GITWEB_HOME_LINK_STR
+   String of the home link on top of all pages, leading to $home_link
+   (usually main gitweb page, which means projects list).  Used as first
+   part of gitweb view "breadcrumb trail": <home> / <project> / <view>.
+   [Default: projects]
+ * GITWEB_SITENAME
+   Name of your site or organization to appear in page titles.  Set it
+   to something descriptive for clearer bookmarks etc.  If not set
+   (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if
+   SERVER_NAME CGI environment variable is not set (e.g. if running
+   gitweb as standalone script).  [No default]
+ * GITWEB_BASE_URL
+   Git base URLs used for URL to where fetch project from, i.e. full
+   URL is "$git_base_url/$project".  Shown on projects summary page.
+   Repository URL for project can be also configured per repository; this
+   takes precedence over URLs composed from base URL and a project name.
+   Note that you can setup multiple base URLs (for example one for
+   git:// protocol access, another for http:// access) from the gitweb
+   config file.  [No default]
+ * GITWEB_CSS
+   Points to the location where you put gitweb.css on your web server
+   (or to be more generic, the URI of gitweb stylesheet).  Relative to the
+   base URI of gitweb.  Note that you can setup multiple stylesheets from
+   the gitweb config file.  [Default: static/gitweb.css (or
+   static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier
+   is used)]
+ * GITWEB_JS
+   Points to the location where you put gitweb.js on your web server
+   (or to be more generic URI of JavaScript code used by gitweb).
+   Relative to base URI of gitweb.  [Default: static/gitweb.js (or
+   static/gitweb.min.js if JSMIN build variable is defined / JavaScript
+   minifier is used)]
+ * CSSMIN, JSMIN
+   Invocation of a CSS minifier or a JavaScript minifier, respectively,
+   working as a filter (source on standard input, minified result on
+   standard output).  If set, it is used to generate a minified version of
+   'static/gitweb.css' or 'static/gitweb.js', respectively.  *Note* that
+   minified files would have *.min.css and *.min.js extension, which is
+   important if you also set GITWEB_CSS and/or GITWEB_JS.  [No default]
+ * GITWEB_LOGO
+   Points to the location where you put git-logo.png on your web server
+   (or to be more generic URI of logo, 72x27 size, displayed in top right
+   corner of each gitweb page, and used as logo for Atom feed).  Relative
+   to base URI of gitweb.  [Default: static/git-logo.png]
+ * GITWEB_FAVICON
+   Points to the location where you put git-favicon.png on your web server
+   (or to be more generic URI of favicon, assumed to be image/png type;
+   web browsers that support favicons (website icons) may display them
+   in the browser's URL bar and next to site name in bookmarks).  Relative
+   to base URI of gitweb.  [Default: static/git-favicon.png]
+ * GITWEB_CONFIG
+   This Perl file will be loaded using 'do' and can be used to override any
+   of the options above as well as some other options -- see the "Runtime
+   gitweb configuration" section below, and top of 'gitweb.cgi' for their
+   full list and description.  If the environment variable GITWEB_CONFIG
+   is set when gitweb.cgi is executed, then the file specified in the
+   environment variable will be loaded instead of the file specified
+   when gitweb.cgi was created.  [Default: gitweb_config.perl]
+ * GITWEB_CONFIG_SYSTEM
+   This Perl file will be loaded using 'do' as a fallback if GITWEB_CONFIG
+   does not exist.  If the environment variable GITWEB_CONFIG_SYSTEM is set
+   when gitweb.cgi is executed, then the file specified in the environment
+   variable will be loaded instead of the file specified when gitweb.cgi was
+   created.  [Default: /etc/gitweb.conf]
+ * HIGHLIGHT_BIN
+   Path to the highlight executable to use (must be the one from
+   http://www.andre-simon.de due to assumptions about parameters and output).
+   Useful if highlight is not installed on your webserver's PATH.
+   [Default: highlight]
+
 Build example
 ~~~~~~~~~~~~~
 
@@ -229,21 +362,6 @@ $projects_list variable in gitweb config):
        perl -- /var/www/cgi-bin/gitweb.cgi
 
 
-Requirements
-------------
-
- - Core git tools
- - Perl
- - 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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
index 0a6ac00631ed8eac5c20248a345074e4b8ff3707..5d20515fba9251123e29be623178047d4131fc01 100644 (file)
@@ -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:
index a92bde7f1430043e5955c61eba287a22d4810d22..a4cfcb42cd989c063c49b6511393ba936e93978a 100644 (file)
@@ -7,120 +7,6 @@ The one working on:
 From the git version 1.4.0 gitweb is bundled with git.
 
 
-How to configure gitweb for your local system
----------------------------------------------
-
-See also the "Build time configuration" section in the INSTALL
-file for gitweb (in gitweb/INSTALL).
-
-You can specify the following configuration variables when building GIT:
- * GIT_BINDIR
-   Points where to find the git executable.  You should set it up to
-   the place where the git binary was installed (usually /usr/bin) if you
-   don't install git from sources together with gitweb.  [Default: $(bindir)]
- * GITWEB_SITENAME
-   Shown in the title of all generated pages, defaults to the server name
-   (SERVER_NAME CGI environment variable) if not set. [No default]
- * GITWEB_PROJECTROOT
-   The root directory for all projects shown by gitweb. Must be set
-   correctly for gitweb to find repositories to display.  See also
-   "Gitweb repositories" in the INSTALL file for gitweb.  [Default: /pub/git]
- * GITWEB_PROJECT_MAXDEPTH
-   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).
-   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
-   if not set / if empty) or to a file with explicit listing of projects
-   (together with projects' ownership). See "Generating projects list
-   using gitweb" in INSTALL file for gitweb to find out how to generate
-   such file from scan of a directory. [No default, which means use root
-   directory for projects]
- * GITWEB_EXPORT_OK
-   Show repository only if this file exists (in repository).  Only
-   effective if this variable evaluates to true.  [No default / Not set]
- * GITWEB_STRICT_EXPORT
-   Only allow viewing of repositories also shown on the overview page.
-   This for example makes GITWEB_EXPORT_OK to decide if repository is
-   available and not only if it is shown.  If GITWEB_LIST points to
-   file with list of project, only those repositories listed would be
-   available for gitweb.  [No default]
- * GITWEB_HOMETEXT
-   Points to an .html file which is included on the gitweb project
-   overview page ('projects_list' view), if it exists.  Relative to
-   gitweb.cgi script.  [Default: indextext.html]
- * GITWEB_SITE_HEADER
-   Filename of html text to include at top of each page.  Relative to
-   gitweb.cgi script.  [No default]
- * GITWEB_SITE_FOOTER
-   Filename of html text to include at bottom of each page.  Relative to
-   gitweb.cgi script.  [No default]
- * GITWEB_HOME_LINK_STR
-   String of the home link on top of all pages, leading to $home_link
-   (usually main gitweb page, which means projects list).  Used as first
-   part of gitweb view "breadcrumb trail": <home> / <project> / <view>.
-   [Default: projects]
- * GITWEB_SITENAME
-   Name of your site or organization to appear in page titles.  Set it
-   to something descriptive for clearer bookmarks etc.  If not set
-   (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if
-   SERVER_NAME CGI environment variable is not set (e.g. if running
-   gitweb as standalone script).  [No default]
- * GITWEB_BASE_URL
-   Git base URLs used for URL to where fetch project from, i.e. full
-   URL is "$git_base_url/$project".  Shown on projects summary page.
-   Repository URL for project can be also configured per repository; this
-   takes precedence over URLs composed from base URL and a project name.
-   Note that you can setup multiple base URLs (for example one for
-   git:// protocol access, another for http:// access) from the gitweb
-   config file.  [No default]
- * GITWEB_CSS
-   Points to the location where you put gitweb.css on your web server
-   (or to be more generic, the URI of gitweb stylesheet).  Relative to the
-   base URI of gitweb.  Note that you can setup multiple stylesheets from
-   the gitweb config file.  [Default: static/gitweb.css (or
-   static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier
-   is used)]
- * GITWEB_LOGO
-   Points to the location where you put git-logo.png on your web server
-   (or to be more generic URI of logo, 72x27 size, displayed in top right
-   corner of each gitweb page, and used as logo for Atom feed).  Relative
-   to base URI of gitweb.  [Default: static/git-logo.png]
- * GITWEB_FAVICON
-   Points to the location where you put git-favicon.png on your web server
-   (or to be more generic URI of favicon, assumed to be image/png type;
-   web browsers that support favicons (website icons) may display them
-   in the browser's URL bar and next to site name in bookmarks).  Relative
-   to base URI of gitweb.  [Default: static/git-favicon.png]
- * GITWEB_JS
-   Points to the location where you put gitweb.js on your web server
-   (or to be more generic URI of JavaScript code used by gitweb).
-   Relative to base URI of gitweb.  [Default: static/gitweb.js (or
-   static/gitweb.min.js if JSMIN build variable is defined / JavaScript
-   minifier is used)]
- * GITWEB_CONFIG
-   This Perl file will be loaded using 'do' and can be used to override any
-   of the options above as well as some other options -- see the "Runtime
-   gitweb configuration" section below, and top of 'gitweb.cgi' for their
-   full list and description.  If the environment variable GITWEB_CONFIG
-   is set when gitweb.cgi is executed, then the file specified in the
-   environment variable will be loaded instead of the file specified
-   when gitweb.cgi was created.  [Default: gitweb_config.perl]
- * GITWEB_CONFIG_SYSTEM
-   This Perl file will be loaded using 'do' as a fallback if GITWEB_CONFIG
-   does not exist.  If the environment variable GITWEB_CONFIG_SYSTEM is set
-   when gitweb.cgi is executed, then the file specified in the environment
-   variable will be loaded instead of the file specified when gitweb.cgi was
-   created.  [Default: /etc/gitweb.conf]
- * HIGHLIGHT_BIN
-   Path to the highlight executable to use (must be the one from
-   http://www.andre-simon.de due to assumptions about parameters and output).
-   Useful if highlight is not installed on your webserver's PATH.
-   [Default: highlight]
-
-
 Runtime gitweb configuration
 ----------------------------
 
@@ -207,6 +93,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 +209,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
index ee69ea683aedf81cd6e6eec0f500bddf2ff54450..1b83a8d0a0adca8ffcc948bdca55d1f8e2bbaa48 100755 (executable)
@@ -115,6 +115,14 @@ sub evaluate_uri {
 # 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 @@ sub evaluate_uri {
                'type' => 'application/x-gzip',
                'suffix' => '.tar.gz',
                'format' => 'tar',
-               'compressor' => ['gzip']},
+               'compressor' => ['gzip', '-n']},
 
        'tbz2' => {
                'display' => 'tar.bz2',
@@ -412,20 +420,23 @@ sub evaluate_uri {
                '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 @@ sub evaluate_uri {
                '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 @@ sub check_loadavg {
        snapshot_format => "sf",
        extra_options => "opt",
        search_use_regexp => "sr",
+       ctag => "by_tag",
        # this must be last entry (for manipulation from JavaScript)
        javascript => "js"
 );
@@ -2558,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 {
@@ -2606,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/ /&nbsp;/g;
                        $title =~ s/^/&nbsp;/g;
                        $title =~ s/$/&nbsp;/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>';
        }
 }
 
@@ -2651,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
@@ -2680,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;
@@ -2700,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>) {
@@ -2711,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 = {
@@ -2744,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;
@@ -2753,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 {
 
@@ -3732,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!;
        }
 
@@ -3938,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
@@ -3966,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,
@@ -3986,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";
        }
 }
@@ -4738,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');
@@ -4762,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'});
+               }
+               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);
                }
-               $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
+
                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 {
@@ -4802,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;
-       }
-
-       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);
-       }
+       $to = $#$projlist if (!defined $to || $#$projlist < $to);
 
-       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";
@@ -4873,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";
                }
@@ -4898,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) {
@@ -5357,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',
@@ -5399,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();
@@ -5410,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
@@ -5428,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";
@@ -5862,7 +6127,7 @@ sub git_blob_plain {
        # want to be sure not to break that by serving the image as an
        # attachment (though Firefox 3 doesn't seem to care).
        my $sandbox = $prevent_xss &&
-               $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
+               $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))(?:[ ;]|$)!;
 
        print $cgi->header(
                -type => $type,
@@ -7319,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',
index 79d7eebba797f3bd80a639f6ca0835e15016762d..7d88509208417e4b1222629002ea339ecc32526e 100644 (file)
@@ -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/gitweb.js b/gitweb/static/gitweb.js
deleted file mode 100644 (file)
index 40ec084..0000000
+++ /dev/null
@@ -1,889 +0,0 @@
-// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
-//               2007, Petr Baudis <pasky@suse.cz>
-//          2008-2009, Jakub Narebski <jnareb@gmail.com>
-
-/**
- * @fileOverview JavaScript code for gitweb (git web interface).
- * @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.
- *
- * innerHTML is non-standard IE extension, though supported by most
- * browsers; however Firefox up to version 1.5 didn't implement it in
- * a strict mode (application/xml+xhtml mimetype).
- *
- * Also my simple benchmarks show that using elem.firstChild.data =
- * 'content' is slightly faster than elem.innerHTML = 'content'.  It
- * is however more fragile (text element fragment must exists), and
- * less feature-rich (we cannot add HTML).
- *
- * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the
- * equivalent using DOM 2 Core is usually shown in comments.
- */
-
-
-/* ============================================================ */
-/* 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
-var projectUrl; // partial query + separator ('?' or ';')
-
-// 'commits' is an associative map. It maps SHA1s to Commit objects.
-var commits = {};
-
-/**
- * constructor for Commit objects, used in 'blame'
- * @class Represents a blamed commit
- * @param {String} sha1: SHA-1 identifier of a commit
- */
-function Commit(sha1) {
-       if (this instanceof Commit) {
-               this.sha1 = sha1;
-               this.nprevious = 0; /* number of 'previous', effective parents */
-       } else {
-               return new Commit(sha1);
-       }
-}
-
-/* ............................................................ */
-/* progress info, timing, error reporting */
-
-var blamedLines = 0;
-var totalLines  = '???';
-var div_progress_bar;
-var div_progress_info;
-
-/**
- * Detects how many lines does a blamed file have,
- * This information is used in progress info
- *
- * @returns {Number|String} Number of lines in file, or string '...'
- */
-function countLines() {
-       var table =
-               document.getElementById('blame_table') ||
-               document.getElementsByTagName('table')[0];
-
-       if (table) {
-               return table.getElementsByTagName('tr').length - 1; // for header
-       } else {
-               return '...';
-       }
-}
-
-/**
- * update progress info and length (width) of progress bar
- *
- * @globals div_progress_info, div_progress_bar, blamedLines, totalLines
- */
-function updateProgressInfo() {
-       if (!div_progress_info) {
-               div_progress_info = document.getElementById('progress_info');
-       }
-       if (!div_progress_bar) {
-               div_progress_bar = document.getElementById('progress_bar');
-       }
-       if (!div_progress_info && !div_progress_bar) {
-               return;
-       }
-
-       var percentage = Math.floor(100.0*blamedLines/totalLines);
-
-       if (div_progress_info) {
-               div_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +
-                       ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)';
-       }
-
-       if (div_progress_bar) {
-               //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
-               div_progress_bar.style.width = percentage + '%';
-       }
-}
-
-
-var t_interval_server = '';
-var cmds_server = '';
-var t0 = new Date();
-
-/**
- * write how much it took to generate data, and to run script
- *
- * @globals t0, t_interval_server, cmds_server
- */
-function writeTimeInterval() {
-       var info_time = document.getElementById('generating_time');
-       if (!info_time || !t_interval_server) {
-               return;
-       }
-       var t1 = new Date();
-       info_time.firstChild.data += ' + (' +
-               t_interval_server + ' sec server blame_data / ' +
-               (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';
-
-       var info_cmds = document.getElementById('generating_cmd');
-       if (!info_time || !cmds_server) {
-               return;
-       }
-       info_cmds.firstChild.data += ' + ' + cmds_server;
-}
-
-/**
- * show an error message alert to user within page (in prohress info area)
- * @param {String} str: plain text error message (no HTML)
- *
- * @globals div_progress_info
- */
-function errorInfo(str) {
-       if (!div_progress_info) {
-               div_progress_info = document.getElementById('progress_info');
-       }
-       if (div_progress_info) {
-               div_progress_info.className = 'error';
-               div_progress_info.firstChild.data = str;
-       }
-}
-
-/* ............................................................ */
-/* coloring rows during blame_data (git blame --incremental) run */
-
-/**
- * used to extract N from 'colorN', where N is a number,
- * @constant
- */
-var colorRe = /\bcolor([0-9]*)\b/;
-
-/**
- * return N if <tr class="colorN">, otherwise return null
- * (some browsers require CSS class names to begin with letter)
- *
- * @param {HTMLElement} tr: table row element to check
- * @param {String} tr.className: 'class' attribute of tr element
- * @returns {Number|null} N if tr.className == 'colorN', otherwise null
- *
- * @globals colorRe
- */
-function getColorNo(tr) {
-       if (!tr) {
-               return null;
-       }
-       var className = tr.className;
-       if (className) {
-               var match = colorRe.exec(className);
-               if (match) {
-                       return parseInt(match[1], 10);
-               }
-       }
-       return null;
-}
-
-var colorsFreq = [0, 0, 0];
-/**
- * return one of given possible colors (curently least used one)
- * example: chooseColorNoFrom(2, 3) returns 2 or 3
- *
- * @param {Number[]} arguments: one or more numbers
- *        assumes that  1 <= arguments[i] <= colorsFreq.length
- * @returns {Number} Least used color number from arguments
- * @globals colorsFreq
- */
-function chooseColorNoFrom() {
-       // choose the color which is least used
-       var colorNo = arguments[0];
-       for (var i = 1; i < arguments.length; i++) {
-               if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {
-                       colorNo = arguments[i];
-               }
-       }
-       colorsFreq[colorNo-1]++;
-       return colorNo;
-}
-
-/**
- * given two neigbour <tr> elements, find color which would be different
- * from color of both of neighbours; used to 3-color blame table
- *
- * @param {HTMLElement} tr_prev
- * @param {HTMLElement} tr_next
- * @returns {Number} color number N such that
- * colorN != tr_prev.className && colorN != tr_next.className
- */
-function findColorNo(tr_prev, tr_next) {
-       var color_prev = getColorNo(tr_prev);
-       var color_next = getColorNo(tr_next);
-
-
-       // neither of neighbours 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
-       // THEN we can use any color except given
-       var color;
-       if (color_prev === color_next) {
-               color = color_prev; // = color_next;
-       } else if (!color_prev) {
-               color = color_next;
-       } else if (!color_next) {
-               color = color_prev;
-       }
-       if (color) {
-               return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
-       }
-
-       // neighbours have different colors
-       // THEN there is only one color left
-       return (3 - ((color_prev + color_next) % 3));
-}
-
-/* ............................................................ */
-/* coloring rows like 'blame' after 'blame_data' finishes */
-
-/**
- * returns true if given row element (tr) is first in commit group
- * to be used only after 'blame_data' finishes (after processing)
- *
- * @param {HTMLElement} tr: table row
- * @returns {Boolean} true if TR is first in commit group
- */
-function isStartOfGroup(tr) {
-       return tr.firstChild.className === 'sha1';
-}
-
-/**
- * change colors to use zebra coloring (2 colors) instead of 3 colors
- * concatenate neighbour commit groups belonging to the same commit
- *
- * @globals colorRe
- */
-function fixColorsAndGroups() {
-       var colorClasses = ['light', 'dark'];
-       var linenum = 1;
-       var tr, prev_group;
-       var colorClass = 0;
-       var table =
-               document.getElementById('blame_table') ||
-               document.getElementsByTagName('table')[0];
-
-       while ((tr = document.getElementById('l'+linenum))) {
-       // index origin is 0, which is table header; start from 1
-       //while ((tr = table.rows[linenum])) { // <- it is slower
-               if (isStartOfGroup(tr, linenum, document)) {
-                       if (prev_group &&
-                           prev_group.firstChild.firstChild.href ===
-                                   tr.firstChild.firstChild.href) {
-                               // we have to concatenate groups
-                               var prev_rows = prev_group.firstChild.rowSpan || 1;
-                               var curr_rows =         tr.firstChild.rowSpan || 1;
-                               prev_group.firstChild.rowSpan = prev_rows + curr_rows;
-                               //tr.removeChild(tr.firstChild);
-                               tr.deleteCell(0); // DOM2 HTML way
-                       } else {
-                               colorClass = (colorClass + 1) % 2;
-                               prev_group = tr;
-                       }
-               }
-               var tr_class = tr.className;
-               tr.className = tr_class.replace(colorRe, colorClasses[colorClass]);
-               linenum++;
-       }
-}
-
-/* ............................................................ */
-/* 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])$/;
-
-/**
- * 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 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;
-}
-
-/* ............................................................ */
-/* 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 */
-
-/**
- * Function called for each blame entry, as soon as it finishes.
- * It updates page via DOM manipulation, adding sha1 info, etc.
- *
- * @param {Commit} commit: blamed commit
- * @param {Object} group: object representing group of lines,
- *                        which blame the same commit (blame entry)
- *
- * @globals blamedLines
- */
-function handleLine(commit, group) {
-       /*
-          This is the structure of the HTML fragment we are working
-          with:
-
-          <tr id="l123" class="">
-            <td class="sha1" title=""><a href=""> </a></td>
-            <td class="linenr"><a class="linenr" href="">123</a></td>
-            <td class="pre"># times (my ext3 doesn&#39;t).</td>
-          </tr>
-       */
-
-       var resline = group.resline;
-
-       // format date and time string only once per commit
-       if (!commit.info) {
-               /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */
-               commit.info = commit.author + ', ' +
-                       formatDateISOLocal(commit.authorTime, commit.authorTimezone);
-       }
-
-       // color depends on group of lines, not only on blamed commit
-       var colorNo = findColorNo(
-               document.getElementById('l'+(resline-1)),
-               document.getElementById('l'+(resline+group.numlines))
-       );
-
-       // loop over lines in commit group
-       for (var i = 0; i < group.numlines; i++, resline++) {
-               var tr = document.getElementById('l'+resline);
-               if (!tr) {
-                       break;
-               }
-               /*
-                       <tr id="l123" class="">
-                         <td class="sha1" title=""><a href=""> </a></td>
-                         <td class="linenr"><a class="linenr" href="">123</a></td>
-                         <td class="pre"># times (my ext3 doesn&#39;t).</td>
-                       </tr>
-               */
-               var td_sha1  = tr.firstChild;
-               var a_sha1   = td_sha1.firstChild;
-               var a_linenr = td_sha1.nextSibling.firstChild;
-
-               /* <tr id="l123" class=""> */
-               var tr_class = '';
-               if (colorNo !== null) {
-                       tr_class = 'color'+colorNo;
-               }
-               if (commit.boundary) {
-                       tr_class += ' boundary';
-               }
-               if (commit.nprevious === 0) {
-                       tr_class += ' no-previous';
-               } else if (commit.nprevious > 1) {
-                       tr_class += ' multiple-previous';
-               }
-               tr.className = tr_class;
-
-               /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
-               if (i === 0) {
-                       td_sha1.title = commit.info;
-                       td_sha1.rowSpan = group.numlines;
-
-                       a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
-                       if (a_sha1.firstChild) {
-                               a_sha1.firstChild.data = commit.sha1.substr(0, 8);
-                       } else {
-                               a_sha1.appendChild(
-                                       document.createTextNode(commit.sha1.substr(0, 8)));
-                       }
-                       if (group.numlines >= 2) {
-                               var fragment = document.createDocumentFragment();
-                               var br   = document.createElement("br");
-                               var match = commit.author.match(/\b([A-Z])\B/g);
-                               if (match) {
-                                       var text = document.createTextNode(
-                                                       match.join(''));
-                               }
-                               if (br && text) {
-                                       var elem = fragment || td_sha1;
-                                       elem.appendChild(br);
-                                       elem.appendChild(text);
-                                       if (fragment) {
-                                               td_sha1.appendChild(fragment);
-                                       }
-                               }
-                       }
-               } else {
-                       //tr.removeChild(td_sha1); // DOM2 Core way
-                       tr.deleteCell(0); // DOM2 HTML way
-               }
-
-               /* <td class="linenr"><a class="linenr" href="?">123</a></td> */
-               var linenr_commit =
-                       ('previous' in commit ? commit.previous : commit.sha1);
-               var linenr_filename =
-                       ('file_parent' in commit ? commit.file_parent : commit.filename);
-               a_linenr.href = projectUrl + 'a=blame_incremental' +
-                       ';hb=' + linenr_commit +
-                       ';f='  + encodeURIComponent(linenr_filename) +
-                       '#l' + (group.srcline + i);
-
-               blamedLines++;
-
-               //updateProgressInfo();
-       }
-}
-
-// ----------------------------------------------------------------------
-
-var inProgress = false;   // are we processing response
-
-/**#@+
- * @constant
- */
-var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;
-var infoRe = /^([a-z-]+) ?(.*)/;
-var endRe  = /^END ?([^ ]*) ?(.*)/;
-/**@-*/
-
-var curCommit = new Commit();
-var curGroup  = {};
-
-var pollTimer = null;
-
-/**
- * Parse output from 'git blame --incremental [...]', received via
- * XMLHttpRequest from server (blamedataUrl), and call handleLine
- * (which updates page) as soon as blame entry is completed.
- *
- * @param {String[]} lines: new complete lines from blamedata server
- *
- * @globals commits, curCommit, curGroup, t_interval_server, cmds_server
- * @globals sha1Re, infoRe, endRe
- */
-function processBlameLines(lines) {
-       var match;
-
-       for (var i = 0, len = lines.length; i < len; i++) {
-
-               if ((match = sha1Re.exec(lines[i]))) {
-                       var sha1 = match[1];
-                       var srcline  = parseInt(match[2], 10);
-                       var resline  = parseInt(match[3], 10);
-                       var numlines = parseInt(match[4], 10);
-
-                       var c = commits[sha1];
-                       if (!c) {
-                               c = new Commit(sha1);
-                               commits[sha1] = c;
-                       }
-                       curCommit = c;
-
-                       curGroup.srcline = srcline;
-                       curGroup.resline = resline;
-                       curGroup.numlines = numlines;
-
-               } else if ((match = infoRe.exec(lines[i]))) {
-                       var info = match[1];
-                       var data = match[2];
-                       switch (info) {
-                       case 'filename':
-                               curCommit.filename = unquote(data);
-                               // 'filename' information terminates the entry
-                               handleLine(curCommit, curGroup);
-                               updateProgressInfo();
-                               break;
-                       case 'author':
-                               curCommit.author = data;
-                               break;
-                       case 'author-time':
-                               curCommit.authorTime = parseInt(data, 10);
-                               break;
-                       case 'author-tz':
-                               curCommit.authorTimezone = data;
-                               break;
-                       case 'previous':
-                               curCommit.nprevious++;
-                               // store only first 'previous' header
-                               if (!'previous' in curCommit) {
-                                       var parts = data.split(' ', 2);
-                                       curCommit.previous    = parts[0];
-                                       curCommit.file_parent = unquote(parts[1]);
-                               }
-                               break;
-                       case 'boundary':
-                               curCommit.boundary = true;
-                               break;
-                       } // end switch
-
-               } else if ((match = endRe.exec(lines[i]))) {
-                       t_interval_server = match[1];
-                       cmds_server = match[2];
-
-               } else if (lines[i] !== '') {
-                       // malformed line
-
-               } // end if (match)
-
-       } // end for (lines)
-}
-
-/**
- * Process new data and return pointer to end of processed part
- *
- * @param {String} unprocessed: new data (from nextReadPos)
- * @param {Number} nextReadPos: end of last processed data
- * @return {Number} end of processed data (new value for nextReadPos)
- */
-function processData(unprocessed, nextReadPos) {
-       var lastLineEnd = unprocessed.lastIndexOf('\n');
-       if (lastLineEnd !== -1) {
-               var lines = unprocessed.substring(0, lastLineEnd).split('\n');
-               nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
-
-               processBlameLines(lines);
-       } // end if
-
-       return nextReadPos;
-}
-
-/**
- * Handle XMLHttpRequest errors
- *
- * @param {XMLHttpRequest} xhr: XMLHttpRequest object
- *
- * @globals pollTimer, commits, inProgress
- */
-function handleError(xhr) {
-       errorInfo('Server error: ' +
-               xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
-
-       clearInterval(pollTimer);
-       commits = {}; // free memory
-
-       inProgress = false;
-}
-
-/**
- * Called after XMLHttpRequest finishes (loads)
- *
- * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
- *
- * @globals pollTimer, commits, inProgress
- */
-function responseLoaded(xhr) {
-       clearInterval(pollTimer);
-
-       fixColorsAndGroups();
-       writeTimeInterval();
-       commits = {}; // free memory
-
-       inProgress = false;
-}
-
-/**
- * handler for XMLHttpRequest onreadystatechange event
- * @see startBlame
- *
- * @globals xhr, inProgress
- */
-function handleResponse() {
-
-       /*
-        * xhr.readyState
-        *
-        *  Value  Constant (W3C)    Description
-        *  -------------------------------------------------------------------
-        *  0      UNSENT            open() has not been called yet.
-        *  1      OPENED            send() has not been called yet.
-        *  2      HEADERS_RECEIVED  send() has been called, and headers
-        *                           and status are available.
-        *  3      LOADING           Downloading; responseText holds partial data.
-        *  4      DONE              The operation is complete.
-        */
-
-       if (xhr.readyState !== 4 && xhr.readyState !== 3) {
-               return;
-       }
-
-       // the server returned error
-       // try ... catch block is to work around bug in IE8
-       try {
-               if (xhr.readyState === 3 && xhr.status !== 200) {
-                       return;
-               }
-       } catch (e) {
-               return;
-       }
-       if (xhr.readyState === 4 && xhr.status !== 200) {
-               handleError(xhr);
-               return;
-       }
-
-       // In konqueror xhr.responseText is sometimes null here...
-       if (xhr.responseText === null) {
-               return;
-       }
-
-       // in case we were called before finished processing
-       if (inProgress) {
-               return;
-       } else {
-               inProgress = true;
-       }
-
-       // extract new whole (complete) lines, and process them
-       while (xhr.prevDataLength !== xhr.responseText.length) {
-               if (xhr.readyState === 4 &&
-                   xhr.prevDataLength === xhr.responseText.length) {
-                       break;
-               }
-
-               xhr.prevDataLength = xhr.responseText.length;
-               var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
-               xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
-       } // end while
-
-       // did we finish work?
-       if (xhr.readyState === 4 &&
-           xhr.prevDataLength === xhr.responseText.length) {
-               responseLoaded(xhr);
-       }
-
-       inProgress = false;
-}
-
-// ============================================================
-// ------------------------------------------------------------
-
-/**
- * Incrementally update line data in blame_incremental view in gitweb.
- *
- * @param {String} blamedataUrl: URL to server script generating blame data.
- * @param {String} bUrl: partial URL to project, used to generate links.
- *
- * Called from 'blame_incremental' view after loading table with
- * file contents, a base for blame view.
- *
- * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
-*/
-function startBlame(blamedataUrl, bUrl) {
-
-       xhr = createRequestObject();
-       if (!xhr) {
-               errorInfo('ERROR: XMLHttpRequest not supported');
-               return;
-       }
-
-       t0 = new Date();
-       projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');
-       if ((div_progress_bar = document.getElementById('progress_bar'))) {
-               //div_progress_bar.setAttribute('style', 'width: 100%;');
-               div_progress_bar.style.cssText = 'width: 100%;';
-       }
-       totalLines = countLines();
-       updateProgressInfo();
-
-       /* add extra properties to xhr object to help processing response */
-       xhr.prevDataLength = -1;  // used to detect if we have new data
-       xhr.nextReadPos = 0;      // where unread part of response starts
-
-       xhr.onreadystatechange = handleResponse;
-       //xhr.onreadystatechange = function () { handleResponse(xhr); };
-
-       xhr.open('GET', blamedataUrl);
-       xhr.setRequestHeader('Accept', 'text/plain');
-       xhr.send(null);
-
-       // not all browsers call onreadystatechange event on each server flush
-       // poll response using timer every second to handle this issue
-       pollTimer = setInterval(xhr.onreadystatechange, 1000);
-}
-
-// end of gitweb.js
diff --git a/gitweb/static/js/README b/gitweb/static/js/README
new file mode 100644 (file)
index 0000000..f8460ed
--- /dev/null
@@ -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 (file)
index 0000000..0c67779
--- /dev/null
@@ -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&plusmn;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/js/blame_incremental.js b/gitweb/static/js/blame_incremental.js
new file mode 100644 (file)
index 0000000..676da6b
--- /dev/null
@@ -0,0 +1,687 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+//               2007, Petr Baudis <pasky@suse.cz>
+//          2008-2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview JavaScript side of Ajax-y 'blame_incremental' view in gitweb
+ * @license GPLv2 or later
+ */
+
+/* ============================================================ */
+/*
+ * This code uses DOM methods instead of (nonstandard) innerHTML
+ * to modify page.
+ *
+ * innerHTML is non-standard IE extension, though supported by most
+ * browsers; however Firefox up to version 1.5 didn't implement it in
+ * a strict mode (application/xml+xhtml mimetype).
+ *
+ * Also my simple benchmarks show that using elem.firstChild.data =
+ * 'content' is slightly faster than elem.innerHTML = 'content'.  It
+ * is however more fragile (text element fragment must exists), and
+ * less feature-rich (we cannot add HTML).
+ *
+ * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the
+ * equivalent using DOM 2 Core is usually shown in comments.
+ */
+
+
+/* ............................................................ */
+/* utility/helper functions (and variables) */
+
+var xhr;        // XMLHttpRequest object
+var projectUrl; // partial query + separator ('?' or ';')
+
+// 'commits' is an associative map. It maps SHA1s to Commit objects.
+var commits = {};
+
+/**
+ * constructor for Commit objects, used in 'blame'
+ * @class Represents a blamed commit
+ * @param {String} sha1: SHA-1 identifier of a commit
+ */
+function Commit(sha1) {
+       if (this instanceof Commit) {
+               this.sha1 = sha1;
+               this.nprevious = 0; /* number of 'previous', effective parents */
+       } else {
+               return new Commit(sha1);
+       }
+}
+
+/* ............................................................ */
+/* progress info, timing, error reporting */
+
+var blamedLines = 0;
+var totalLines  = '???';
+var div_progress_bar;
+var div_progress_info;
+
+/**
+ * Detects how many lines does a blamed file have,
+ * This information is used in progress info
+ *
+ * @returns {Number|String} Number of lines in file, or string '...'
+ */
+function countLines() {
+       var table =
+               document.getElementById('blame_table') ||
+               document.getElementsByTagName('table')[0];
+
+       if (table) {
+               return table.getElementsByTagName('tr').length - 1; // for header
+       } else {
+               return '...';
+       }
+}
+
+/**
+ * update progress info and length (width) of progress bar
+ *
+ * @globals div_progress_info, div_progress_bar, blamedLines, totalLines
+ */
+function updateProgressInfo() {
+       if (!div_progress_info) {
+               div_progress_info = document.getElementById('progress_info');
+       }
+       if (!div_progress_bar) {
+               div_progress_bar = document.getElementById('progress_bar');
+       }
+       if (!div_progress_info && !div_progress_bar) {
+               return;
+       }
+
+       var percentage = Math.floor(100.0*blamedLines/totalLines);
+
+       if (div_progress_info) {
+               div_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +
+                       ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)';
+       }
+
+       if (div_progress_bar) {
+               //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
+               div_progress_bar.style.width = percentage + '%';
+       }
+}
+
+
+var t_interval_server = '';
+var cmds_server = '';
+var t0 = new Date();
+
+/**
+ * write how much it took to generate data, and to run script
+ *
+ * @globals t0, t_interval_server, cmds_server
+ */
+function writeTimeInterval() {
+       var info_time = document.getElementById('generating_time');
+       if (!info_time || !t_interval_server) {
+               return;
+       }
+       var t1 = new Date();
+       info_time.firstChild.data += ' + (' +
+               t_interval_server + ' sec server blame_data / ' +
+               (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';
+
+       var info_cmds = document.getElementById('generating_cmd');
+       if (!info_time || !cmds_server) {
+               return;
+       }
+       info_cmds.firstChild.data += ' + ' + cmds_server;
+}
+
+/**
+ * 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
+ */
+function errorInfo(str) {
+       if (!div_progress_info) {
+               div_progress_info = document.getElementById('progress_info');
+       }
+       if (div_progress_info) {
+               div_progress_info.className = 'error';
+               div_progress_info.firstChild.data = str;
+       }
+}
+
+/* ............................................................ */
+/* coloring rows during blame_data (git blame --incremental) run */
+
+/**
+ * used to extract N from 'colorN', where N is a number,
+ * @constant
+ */
+var colorRe = /\bcolor([0-9]*)\b/;
+
+/**
+ * return N if <tr class="colorN">, otherwise return null
+ * (some browsers require CSS class names to begin with letter)
+ *
+ * @param {HTMLElement} tr: table row element to check
+ * @param {String} tr.className: 'class' attribute of tr element
+ * @returns {Number|null} N if tr.className == 'colorN', otherwise null
+ *
+ * @globals colorRe
+ */
+function getColorNo(tr) {
+       if (!tr) {
+               return null;
+       }
+       var className = tr.className;
+       if (className) {
+               var match = colorRe.exec(className);
+               if (match) {
+                       return parseInt(match[1], 10);
+               }
+       }
+       return null;
+}
+
+var colorsFreq = [0, 0, 0];
+/**
+ * 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
+ *        assumes that  1 <= arguments[i] <= colorsFreq.length
+ * @returns {Number} Least used color number from arguments
+ * @globals colorsFreq
+ */
+function chooseColorNoFrom() {
+       // choose the color which is least used
+       var colorNo = arguments[0];
+       for (var i = 1; i < arguments.length; i++) {
+               if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {
+                       colorNo = arguments[i];
+               }
+       }
+       colorsFreq[colorNo-1]++;
+       return colorNo;
+}
+
+/**
+ * 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
+ * @returns {Number} color number N such that
+ * colorN != tr_prev.className && colorN != tr_next.className
+ */
+function findColorNo(tr_prev, tr_next) {
+       var color_prev = getColorNo(tr_prev);
+       var color_next = getColorNo(tr_next);
+
+
+       // 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 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) {
+               color = color_prev; // = color_next;
+       } else if (!color_prev) {
+               color = color_next;
+       } else if (!color_next) {
+               color = color_prev;
+       }
+       if (color) {
+               return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
+       }
+
+       // neighbors have different colors
+       // THEN there is only one color left
+       return (3 - ((color_prev + color_next) % 3));
+}
+
+/* ............................................................ */
+/* coloring rows like 'blame' after 'blame_data' finishes */
+
+/**
+ * returns true if given row element (tr) is first in commit group
+ * to be used only after 'blame_data' finishes (after processing)
+ *
+ * @param {HTMLElement} tr: table row
+ * @returns {Boolean} true if TR is first in commit group
+ */
+function isStartOfGroup(tr) {
+       return tr.firstChild.className === 'sha1';
+}
+
+/**
+ * change colors to use zebra coloring (2 colors) instead of 3 colors
+ * concatenate neighbor commit groups belonging to the same commit
+ *
+ * @globals colorRe
+ */
+function fixColorsAndGroups() {
+       var colorClasses = ['light', 'dark'];
+       var linenum = 1;
+       var tr, prev_group;
+       var colorClass = 0;
+       var table =
+               document.getElementById('blame_table') ||
+               document.getElementsByTagName('table')[0];
+
+       while ((tr = document.getElementById('l'+linenum))) {
+       // index origin is 0, which is table header; start from 1
+       //while ((tr = table.rows[linenum])) { // <- it is slower
+               if (isStartOfGroup(tr, linenum, document)) {
+                       if (prev_group &&
+                           prev_group.firstChild.firstChild.href ===
+                                   tr.firstChild.firstChild.href) {
+                               // we have to concatenate groups
+                               var prev_rows = prev_group.firstChild.rowSpan || 1;
+                               var curr_rows =         tr.firstChild.rowSpan || 1;
+                               prev_group.firstChild.rowSpan = prev_rows + curr_rows;
+                               //tr.removeChild(tr.firstChild);
+                               tr.deleteCell(0); // DOM2 HTML way
+                       } else {
+                               colorClass = (colorClass + 1) % 2;
+                               prev_group = tr;
+                       }
+               }
+               var tr_class = tr.className;
+               tr.className = tr_class.replace(colorRe, colorClasses[colorClass]);
+               linenum++;
+       }
+}
+
+
+/* ============================================================ */
+/* main part: parsing response */
+
+/**
+ * Function called for each blame entry, as soon as it finishes.
+ * It updates page via DOM manipulation, adding sha1 info, etc.
+ *
+ * @param {Commit} commit: blamed commit
+ * @param {Object} group: object representing group of lines,
+ *                        which blame the same commit (blame entry)
+ *
+ * @globals blamedLines
+ */
+function handleLine(commit, group) {
+       /*
+          This is the structure of the HTML fragment we are working
+          with:
+
+          <tr id="l123" class="">
+            <td class="sha1" title=""><a href=""> </a></td>
+            <td class="linenr"><a class="linenr" href="">123</a></td>
+            <td class="pre"># times (my ext3 doesn&#39;t).</td>
+          </tr>
+       */
+
+       var resline = group.resline;
+
+       // format date and time string only once per commit
+       if (!commit.info) {
+               /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */
+               commit.info = commit.author + ', ' +
+                       formatDateISOLocal(commit.authorTime, commit.authorTimezone);
+       }
+
+       // color depends on group of lines, not only on blamed commit
+       var colorNo = findColorNo(
+               document.getElementById('l'+(resline-1)),
+               document.getElementById('l'+(resline+group.numlines))
+       );
+
+       // loop over lines in commit group
+       for (var i = 0; i < group.numlines; i++, resline++) {
+               var tr = document.getElementById('l'+resline);
+               if (!tr) {
+                       break;
+               }
+               /*
+                       <tr id="l123" class="">
+                         <td class="sha1" title=""><a href=""> </a></td>
+                         <td class="linenr"><a class="linenr" href="">123</a></td>
+                         <td class="pre"># times (my ext3 doesn&#39;t).</td>
+                       </tr>
+               */
+               var td_sha1  = tr.firstChild;
+               var a_sha1   = td_sha1.firstChild;
+               var a_linenr = td_sha1.nextSibling.firstChild;
+
+               /* <tr id="l123" class=""> */
+               var tr_class = '';
+               if (colorNo !== null) {
+                       tr_class = 'color'+colorNo;
+               }
+               if (commit.boundary) {
+                       tr_class += ' boundary';
+               }
+               if (commit.nprevious === 0) {
+                       tr_class += ' no-previous';
+               } else if (commit.nprevious > 1) {
+                       tr_class += ' multiple-previous';
+               }
+               tr.className = tr_class;
+
+               /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
+               if (i === 0) {
+                       td_sha1.title = commit.info;
+                       td_sha1.rowSpan = group.numlines;
+
+                       a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
+                       if (a_sha1.firstChild) {
+                               a_sha1.firstChild.data = commit.sha1.substr(0, 8);
+                       } else {
+                               a_sha1.appendChild(
+                                       document.createTextNode(commit.sha1.substr(0, 8)));
+                       }
+                       if (group.numlines >= 2) {
+                               var fragment = document.createDocumentFragment();
+                               var br   = document.createElement("br");
+                               var match = commit.author.match(/\b([A-Z])\B/g);
+                               if (match) {
+                                       var text = document.createTextNode(
+                                                       match.join(''));
+                               }
+                               if (br && text) {
+                                       var elem = fragment || td_sha1;
+                                       elem.appendChild(br);
+                                       elem.appendChild(text);
+                                       if (fragment) {
+                                               td_sha1.appendChild(fragment);
+                                       }
+                               }
+                       }
+               } else {
+                       //tr.removeChild(td_sha1); // DOM2 Core way
+                       tr.deleteCell(0); // DOM2 HTML way
+               }
+
+               /* <td class="linenr"><a class="linenr" href="?">123</a></td> */
+               var linenr_commit =
+                       ('previous' in commit ? commit.previous : commit.sha1);
+               var linenr_filename =
+                       ('file_parent' in commit ? commit.file_parent : commit.filename);
+               a_linenr.href = projectUrl + 'a=blame_incremental' +
+                       ';hb=' + linenr_commit +
+                       ';f='  + encodeURIComponent(linenr_filename) +
+                       '#l' + (group.srcline + i);
+
+               blamedLines++;
+
+               //updateProgressInfo();
+       }
+}
+
+// ----------------------------------------------------------------------
+
+var inProgress = false;   // are we processing response
+
+/**#@+
+ * @constant
+ */
+var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;
+var infoRe = /^([a-z-]+) ?(.*)/;
+var endRe  = /^END ?([^ ]*) ?(.*)/;
+/**@-*/
+
+var curCommit = new Commit();
+var curGroup  = {};
+
+var pollTimer = null;
+
+/**
+ * Parse output from 'git blame --incremental [...]', received via
+ * XMLHttpRequest from server (blamedataUrl), and call handleLine
+ * (which updates page) as soon as blame entry is completed.
+ *
+ * @param {String[]} lines: new complete lines from blamedata server
+ *
+ * @globals commits, curCommit, curGroup, t_interval_server, cmds_server
+ * @globals sha1Re, infoRe, endRe
+ */
+function processBlameLines(lines) {
+       var match;
+
+       for (var i = 0, len = lines.length; i < len; i++) {
+
+               if ((match = sha1Re.exec(lines[i]))) {
+                       var sha1 = match[1];
+                       var srcline  = parseInt(match[2], 10);
+                       var resline  = parseInt(match[3], 10);
+                       var numlines = parseInt(match[4], 10);
+
+                       var c = commits[sha1];
+                       if (!c) {
+                               c = new Commit(sha1);
+                               commits[sha1] = c;
+                       }
+                       curCommit = c;
+
+                       curGroup.srcline = srcline;
+                       curGroup.resline = resline;
+                       curGroup.numlines = numlines;
+
+               } else if ((match = infoRe.exec(lines[i]))) {
+                       var info = match[1];
+                       var data = match[2];
+                       switch (info) {
+                       case 'filename':
+                               curCommit.filename = unquote(data);
+                               // 'filename' information terminates the entry
+                               handleLine(curCommit, curGroup);
+                               updateProgressInfo();
+                               break;
+                       case 'author':
+                               curCommit.author = data;
+                               break;
+                       case 'author-time':
+                               curCommit.authorTime = parseInt(data, 10);
+                               break;
+                       case 'author-tz':
+                               curCommit.authorTimezone = data;
+                               break;
+                       case 'previous':
+                               curCommit.nprevious++;
+                               // store only first 'previous' header
+                               if (!'previous' in curCommit) {
+                                       var parts = data.split(' ', 2);
+                                       curCommit.previous    = parts[0];
+                                       curCommit.file_parent = unquote(parts[1]);
+                               }
+                               break;
+                       case 'boundary':
+                               curCommit.boundary = true;
+                               break;
+                       } // end switch
+
+               } else if ((match = endRe.exec(lines[i]))) {
+                       t_interval_server = match[1];
+                       cmds_server = match[2];
+
+               } else if (lines[i] !== '') {
+                       // malformed line
+
+               } // end if (match)
+
+       } // end for (lines)
+}
+
+/**
+ * Process new data and return pointer to end of processed part
+ *
+ * @param {String} unprocessed: new data (from nextReadPos)
+ * @param {Number} nextReadPos: end of last processed data
+ * @return {Number} end of processed data (new value for nextReadPos)
+ */
+function processData(unprocessed, nextReadPos) {
+       var lastLineEnd = unprocessed.lastIndexOf('\n');
+       if (lastLineEnd !== -1) {
+               var lines = unprocessed.substring(0, lastLineEnd).split('\n');
+               nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
+
+               processBlameLines(lines);
+       } // end if
+
+       return nextReadPos;
+}
+
+/**
+ * Handle XMLHttpRequest errors
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function handleError(xhr) {
+       errorInfo('Server error: ' +
+               xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
+
+       clearInterval(pollTimer);
+       commits = {}; // free memory
+
+       inProgress = false;
+}
+
+/**
+ * Called after XMLHttpRequest finishes (loads)
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function responseLoaded(xhr) {
+       clearInterval(pollTimer);
+
+       fixColorsAndGroups();
+       writeTimeInterval();
+       commits = {}; // free memory
+
+       inProgress = false;
+}
+
+/**
+ * handler for XMLHttpRequest onreadystatechange event
+ * @see startBlame
+ *
+ * @globals xhr, inProgress
+ */
+function handleResponse() {
+
+       /*
+        * xhr.readyState
+        *
+        *  Value  Constant (W3C)    Description
+        *  -------------------------------------------------------------------
+        *  0      UNSENT            open() has not been called yet.
+        *  1      OPENED            send() has not been called yet.
+        *  2      HEADERS_RECEIVED  send() has been called, and headers
+        *                           and status are available.
+        *  3      LOADING           Downloading; responseText holds partial data.
+        *  4      DONE              The operation is complete.
+        */
+
+       if (xhr.readyState !== 4 && xhr.readyState !== 3) {
+               return;
+       }
+
+       // the server returned error
+       // try ... catch block is to work around bug in IE8
+       try {
+               if (xhr.readyState === 3 && xhr.status !== 200) {
+                       return;
+               }
+       } catch (e) {
+               return;
+       }
+       if (xhr.readyState === 4 && xhr.status !== 200) {
+               handleError(xhr);
+               return;
+       }
+
+       // In konqueror xhr.responseText is sometimes null here...
+       if (xhr.responseText === null) {
+               return;
+       }
+
+       // in case we were called before finished processing
+       if (inProgress) {
+               return;
+       } else {
+               inProgress = true;
+       }
+
+       // extract new whole (complete) lines, and process them
+       while (xhr.prevDataLength !== xhr.responseText.length) {
+               if (xhr.readyState === 4 &&
+                   xhr.prevDataLength === xhr.responseText.length) {
+                       break;
+               }
+
+               xhr.prevDataLength = xhr.responseText.length;
+               var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
+               xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
+       } // end while
+
+       // did we finish work?
+       if (xhr.readyState === 4 &&
+           xhr.prevDataLength === xhr.responseText.length) {
+               responseLoaded(xhr);
+       }
+
+       inProgress = false;
+}
+
+// ============================================================
+// ------------------------------------------------------------
+
+/**
+ * Incrementally update line data in blame_incremental view in gitweb.
+ *
+ * @param {String} blamedataUrl: URL to server script generating blame data.
+ * @param {String} bUrl: partial URL to project, used to generate links.
+ *
+ * Called from 'blame_incremental' view after loading table with
+ * file contents, a base for blame view.
+ *
+ * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
+*/
+function startBlame(blamedataUrl, bUrl) {
+
+       xhr = createRequestObject();
+       if (!xhr) {
+               errorInfo('ERROR: XMLHttpRequest not supported');
+               return;
+       }
+
+       t0 = new Date();
+       projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');
+       if ((div_progress_bar = document.getElementById('progress_bar'))) {
+               //div_progress_bar.setAttribute('style', 'width: 100%;');
+               div_progress_bar.style.cssText = 'width: 100%;';
+       }
+       totalLines = countLines();
+       updateProgressInfo();
+
+       /* add extra properties to xhr object to help processing response */
+       xhr.prevDataLength = -1;  // used to detect if we have new data
+       xhr.nextReadPos = 0;      // where unread part of response starts
+
+       xhr.onreadystatechange = handleResponse;
+       //xhr.onreadystatechange = function () { handleResponse(xhr); };
+
+       xhr.open('GET', blamedataUrl);
+       xhr.setRequestHeader('Accept', 'text/plain');
+       xhr.send(null);
+
+       // not all browsers call onreadystatechange event on each server flush
+       // poll response using timer every second to handle this issue
+       pollTimer = setInterval(xhr.onreadystatechange, 1000);
+}
+
+/* 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 (file)
index 0000000..93dd2bd
--- /dev/null
@@ -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 (file)
index 0000000..018bbb7
--- /dev/null
@@ -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 '&nbsp;' 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 (file)
index 0000000..72b51cd
--- /dev/null
@@ -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 (file)
index 0000000..f78c60a
--- /dev/null
@@ -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 */
diff --git a/graph.c b/graph.c
index ef2e24e85a41dc97cf00bb7b19985ec01b8662bf..2f6893dc4f97dfbccad6f22f66861c24021a3c46 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -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;
index d18346c0f5c9ce73f3fe9a1efc7b1b63e7380bb8..28bfe768f7749e455f522fcfedeb2350da646416 100644 (file)
@@ -169,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) {
@@ -197,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;
@@ -272,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;
@@ -395,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;
@@ -427,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;
@@ -458,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)) {
@@ -797,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)
@@ -876,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 &&
@@ -909,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;
@@ -987,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)) {
@@ -1167,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);
@@ -1250,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);
@@ -1436,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);
@@ -1471,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;
@@ -1572,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);
@@ -1660,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;
@@ -1742,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);
index 9bc8114c3bb2f6f87f7f61962a64c4e5b012dcf8..51a906e9e3316582561304995e1a1bbbdcd6ca7c 100644 (file)
@@ -185,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;
 
@@ -218,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;
 
diff --git a/http.c b/http.c
index 9e767723ed1045445e91e79489cb8ec8bb861965..b2ae8de16db3abe2cad27249ae767f421aa6bb24 100644 (file)
--- a/http.c
+++ b/http.c
@@ -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 @@ struct http_pack_request *new_http_pack_request(
 }
 
 /* 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];
diff --git a/http.h b/http.h
index e9ed3c2e8272e43208e75459ec29fd8b6548ae4d..19b7134fa5c7ff9b65565dcbddccae1354f024c7 100644 (file)
--- a/http.h
+++ b/http.h
@@ -66,9 +66,9 @@ struct buffer {
 };
 
 /* 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
diff --git a/ident.c b/ident.c
index 1c4adb0a9a7e94936ba64d286cedd65f4b8255a6..35a6f264737e700ddd45714a0e966422a0d756c7 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -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)
index 9adf4b981953080676aa24907fead1038b920e60..e1ad1a48ce3b8bd8517568a67477d8d0e32dfaa8 100644 (file)
@@ -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
index 838b6a732e4758265432cbc9c8e8fc81568a2b50..0fb44e7ed7e8d7a98b8609778191715148f56c8d 100644 (file)
@@ -68,7 +68,7 @@ static void process_tree(struct rev_info *revs,
        struct tree_desc desc;
        struct name_entry entry;
        struct name_path me;
-       int all_interesting = (revs->diffopt.pathspec.nr == 0);
+       int match = revs->diffopt.pathspec.nr == 0 ? 2 : 0;
        int baselen = base->len;
 
        if (!revs->tree_objects)
@@ -85,7 +85,7 @@ static void process_tree(struct rev_info *revs,
        me.elem = name;
        me.elem_len = strlen(name);
 
-       if (!all_interesting) {
+       if (!match) {
                strbuf_addstr(base, name);
                if (base->len)
                        strbuf_addch(base, '/');
@@ -94,17 +94,13 @@ static void process_tree(struct rev_info *revs,
        init_tree_desc(&desc, tree->buffer, tree->size);
 
        while (tree_entry(&desc, &entry)) {
-               if (!all_interesting) {
-                       int showit = tree_entry_interesting(&entry,
-                                                           base, 0,
-                                                           &revs->diffopt.pathspec);
-
-                       if (showit < 0)
+               if (match != 2) {
+                       match = tree_entry_interesting(&entry, base, 0,
+                                                      &revs->diffopt.pathspec);
+                       if (match < 0)
                                break;
-                       else if (!showit)
+                       if (match == 0)
                                continue;
-                       else if (showit == 2)
-                               all_interesting = 1;
                }
 
                if (S_ISDIR(entry.mode))
index 2a1e3a94c910cd74e41303d8b7aefa539817c831..e9457019d5ac7aff1e185e195f37b851732f6210 100644 (file)
@@ -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;
@@ -484,8 +485,10 @@ void show_log(struct rev_info *opt)
        ctx.date_mode = opt->date_mode;
        ctx.abbrev = opt->diffopt.abbrev;
        ctx.after_subject = extra_headers;
+       ctx.preserve_subject = opt->preserve_subject;
        ctx.reflog_info = opt->reflog_info;
-       pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx);
+       ctx.fmt = opt->commit_format;
+       pretty_print_commit(&ctx, commit, &msgbuf);
 
        if (opt->add_signoff)
                append_signoff(&msgbuf, opt->add_signoff);
index f7f4533926e35867725db00f0c89522c4b290898..7845528e88eea6a42f914b5bf596473eb33dea40 100644 (file)
@@ -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 (file)
index 0000000..9b3b83a
--- /dev/null
@@ -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
index af131508ec7ffa3ac7b1b1b1cadfcda0040becf2..db9ba19ddf94fec3a5cfff450b35f5bb7b46c35e 100644 (file)
 #include "dir.h"
 #include "submodule.h"
 
-static const char rename_limit_advice[] =
-"inexact rename detection was skipped because there were too many\n"
-"  files. You may want to set your merge.renamelimit variable to at least\n"
-"  %d and retry this merge.";
-
 static struct tree *shift_tree_object(struct tree *one, struct tree *two,
                                      const char *subtree_shift)
 {
@@ -278,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;
@@ -1652,8 +1649,9 @@ int merge_recursive(struct merge_options *o,
                commit_list_insert(h2, &(*result)->parents->next);
        }
        flush_output(o);
-       if (o->needed_rename_limit)
-               warning(rename_limit_advice, o->needed_rename_limit);
+       if (show(o, 2))
+               diff_warn_rename_limit("merge.renamelimit",
+                                      o->needed_rename_limit, 0);
        return clean;
 }
 
@@ -1711,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;
        }
index 28046a998426e88dec9ad6ab431624bb41a81ce1..e1aaf43b438d0cfb6b7b0c723060a662a915bcea 100644 (file)
@@ -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",
diff --git a/notes.c b/notes.c
index a013c1bc638dbf5a8111183a3f9d154721ec5e04..f6ce8489d31e48d30dfbc6aef4edf6dbb8314234 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -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);
+}
diff --git a/notes.h b/notes.h
index 83bd6e0ec02a1a8650004f14cd7476eedbdff518..c716694b9e2ec89a59b98d1828fd78f59471e2c4 100644 (file)
--- a/notes.h
+++ b/notes.h
@@ -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
index 7e1f2bbed2ad7f331fefc78d759acb14050bea48..31976b5d70b6310552b04ce79c7ea0b07bc536d7 100644 (file)
--- a/object.c
+++ b/object.c
@@ -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;
index e1d8a8f414bf052a59fbe78d022e611f44ed8361..f45eb54e4c99b8d67e4aa85f9a6218ea7a560592 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -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 == '_'));
@@ -266,16 +318,16 @@ static void add_rfc2047(struct strbuf *sb, const char *line, int len,
        strbuf_addstr(sb, "?=");
 }
 
-void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
-                 const char *line, enum date_mode dmode,
-                 const char *encoding)
+void pp_user_info(const struct pretty_print_context *pp,
+                 const char *what, struct strbuf *sb,
+                 const char *line, const char *encoding)
 {
        char *date;
        int namelen;
        unsigned long time;
        int tz;
 
-       if (fmt == CMIT_FMT_ONELINE)
+       if (pp->fmt == CMIT_FMT_ONELINE)
                return;
        date = strchr(line, '>');
        if (!date)
@@ -284,32 +336,48 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
        time = strtoul(date, &date, 10);
        tz = strtol(date, NULL, 10);
 
-       if (fmt == CMIT_FMT_EMAIL) {
+       if (pp->fmt == CMIT_FMT_EMAIL) {
                char *name_tail = strchr(line, '<');
                int display_name_length;
+               int final_line;
                if (!name_tail)
                        return;
                while (line < name_tail && isspace(name_tail[-1]))
                        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(&quoted, line, display_name_length);
+                       add_rfc2047(sb, quoted.buf, quoted.len, encoding);
+                       strbuf_release(&quoted);
+               }
+               for (final_line = 0; final_line < sb->len; final_line++)
+                       if (sb->buf[sb->len - final_line - 1] == '\n')
+                               break;
+               if (namelen - display_name_length + final_line > 78) {
+                       strbuf_addch(sb, '\n');
+                       if (!isspace(name_tail[0]))
+                               strbuf_addch(sb, ' ');
+               }
                strbuf_add(sb, name_tail, namelen - display_name_length);
                strbuf_addch(sb, '\n');
        } else {
                strbuf_addf(sb, "%s: %.*s%.*s\n", what,
-                             (fmt == CMIT_FMT_FULLER) ? 4 : 0,
+                             (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0,
                              "    ", namelen, line);
        }
-       switch (fmt) {
+       switch (pp->fmt) {
        case CMIT_FMT_MEDIUM:
-               strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, dmode));
+               strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, pp->date_mode));
                break;
        case CMIT_FMT_EMAIL:
                strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822));
                break;
        case CMIT_FMT_FULLER:
-               strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, dmode));
+               strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, pp->date_mode));
                break;
        default:
                /* notin' */
@@ -340,12 +408,12 @@ static const char *skip_empty_lines(const char *msg)
        return msg;
 }
 
-static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
-                       const struct commit *commit, int abbrev)
+static void add_merge_info(const struct pretty_print_context *pp,
+                          struct strbuf *sb, const struct commit *commit)
 {
        struct commit_list *parent = commit->parents;
 
-       if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
+       if ((pp->fmt == CMIT_FMT_ONELINE) || (pp->fmt == CMIT_FMT_EMAIL) ||
            !parent || !parent->next)
                return;
 
@@ -354,8 +422,8 @@ static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
        while (parent) {
                struct commit *p = parent->item;
                const char *hex = NULL;
-               if (abbrev)
-                       hex = find_unique_abbrev(p->object.sha1, abbrev);
+               if (pp->abbrev)
+                       hex = find_unique_abbrev(p->object.sha1, pp->abbrev);
                if (!hex)
                        hex = sha1_to_hex(p->object.sha1);
                parent = parent->next;
@@ -1016,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);
 }
 
@@ -1048,9 +1116,7 @@ void format_commit_message(const struct commit *commit,
                free(context.message);
 }
 
-static void pp_header(enum cmit_fmt fmt,
-                     int abbrev,
-                     enum date_mode dmode,
+static void pp_header(const struct pretty_print_context *pp,
                      const char *encoding,
                      const struct commit *commit,
                      const char **msg_p,
@@ -1070,7 +1136,7 @@ static void pp_header(enum cmit_fmt fmt,
                        /* End of header */
                        return;
 
-               if (fmt == CMIT_FMT_RAW) {
+               if (pp->fmt == CMIT_FMT_RAW) {
                        strbuf_add(sb, line, linelen);
                        continue;
                }
@@ -1090,7 +1156,7 @@ static void pp_header(enum cmit_fmt fmt,
                                ;
                        /* with enough slop */
                        strbuf_grow(sb, num * 50 + 20);
-                       add_merge_info(fmt, sb, commit, abbrev);
+                       add_merge_info(pp, sb, commit);
                        parents_shown = 1;
                }
 
@@ -1101,32 +1167,31 @@ static void pp_header(enum cmit_fmt fmt,
                 */
                if (!memcmp(line, "author ", 7)) {
                        strbuf_grow(sb, linelen + 80);
-                       pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+                       pp_user_info(pp, "Author", sb, line + 7, encoding);
                }
                if (!memcmp(line, "committer ", 10) &&
-                   (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
+                   (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
-                       pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+                       pp_user_info(pp, "Commit", sb, line + 10, encoding);
                }
        }
 }
 
-void pp_title_line(enum cmit_fmt fmt,
+void pp_title_line(const struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
-                  const char *subject,
-                  const char *after_subject,
                   const char *encoding,
                   int need_8bit_cte)
 {
        struct strbuf title;
 
        strbuf_init(&title, 80);
-       *msg_p = format_subject(&title, *msg_p, " ");
+       *msg_p = format_subject(&title, *msg_p,
+                               pp->preserve_subject ? "\n" : " ");
 
        strbuf_grow(sb, title.len + 1024);
-       if (subject) {
-               strbuf_addstr(sb, subject);
+       if (pp->subject) {
+               strbuf_addstr(sb, pp->subject);
                add_rfc2047(sb, title.buf, title.len, encoding);
        } else {
                strbuf_addbuf(sb, &title);
@@ -1140,16 +1205,16 @@ void pp_title_line(enum cmit_fmt fmt,
                        "Content-Transfer-Encoding: 8bit\n";
                strbuf_addf(sb, header_fmt, encoding);
        }
-       if (after_subject) {
-               strbuf_addstr(sb, after_subject);
+       if (pp->after_subject) {
+               strbuf_addstr(sb, pp->after_subject);
        }
-       if (fmt == CMIT_FMT_EMAIL) {
+       if (pp->fmt == CMIT_FMT_EMAIL) {
                strbuf_addch(sb, '\n');
        }
        strbuf_release(&title);
 }
 
-void pp_remainder(enum cmit_fmt fmt,
+void pp_remainder(const struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent)
@@ -1166,7 +1231,7 @@ void pp_remainder(enum cmit_fmt fmt,
                if (is_empty_line(line, &linelen)) {
                        if (first)
                                continue;
-                       if (fmt == CMIT_FMT_SHORT)
+                       if (pp->fmt == CMIT_FMT_SHORT)
                                break;
                }
                first = 0;
@@ -1191,19 +1256,19 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding
        return logmsg_reencode(commit, encoding);
 }
 
-void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
-                        struct strbuf *sb,
-                        const struct pretty_print_context *context)
+void pretty_print_commit(const struct pretty_print_context *pp,
+                        const struct commit *commit,
+                        struct strbuf *sb)
 {
        unsigned long beginning_of_body;
        int indent = 4;
        const char *msg = commit->buffer;
        char *reencoded;
        const char *encoding;
-       int need_8bit_cte = context->need_8bit_cte;
+       int need_8bit_cte = pp->need_8bit_cte;
 
-       if (fmt == CMIT_FMT_USERFORMAT) {
-               format_commit_message(commit, user_format, sb, context);
+       if (pp->fmt == CMIT_FMT_USERFORMAT) {
+               format_commit_message(commit, user_format, sb, pp);
                return;
        }
 
@@ -1212,14 +1277,14 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                msg = reencoded;
        }
 
-       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+       if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
                indent = 0;
 
        /*
         * We need to check and emit Content-type: to mark it
         * as 8-bit if we haven't done so.
         */
-       if (fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
+       if (pp->fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
                int i, ch, in_body;
 
                for (in_body = i = 0; (ch = msg[i]); i++) {
@@ -1238,9 +1303,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                }
        }
 
-       pp_header(fmt, context->abbrev, context->date_mode, encoding,
-                 commit, &msg, sb);
-       if (fmt != CMIT_FMT_ONELINE && !context->subject) {
+       pp_header(pp, encoding, commit, &msg, sb);
+       if (pp->fmt != CMIT_FMT_ONELINE && !pp->subject) {
                strbuf_addch(sb, '\n');
        }
 
@@ -1248,17 +1312,16 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
        msg = skip_empty_lines(msg);
 
        /* These formats treat the title line specially. */
-       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
-               pp_title_line(fmt, &msg, sb, context->subject,
-                             context->after_subject, encoding, need_8bit_cte);
+       if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
+               pp_title_line(pp, &msg, sb, encoding, need_8bit_cte);
 
        beginning_of_body = sb->len;
-       if (fmt != CMIT_FMT_ONELINE)
-               pp_remainder(fmt, &msg, sb, indent);
+       if (pp->fmt != CMIT_FMT_ONELINE)
+               pp_remainder(pp, &msg, sb, indent);
        strbuf_rtrim(sb);
 
        /* Make sure there is an EOLN for the non-oneline case */
-       if (fmt != CMIT_FMT_ONELINE)
+       if (pp->fmt != CMIT_FMT_ONELINE)
                strbuf_addch(sb, '\n');
 
        /*
@@ -1266,12 +1329,20 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
         * format.  Make sure we did not strip the blank line
         * between the header and the body.
         */
-       if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
+       if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
                strbuf_addch(sb, '\n');
 
-       if (context->show_notes)
+       if (pp->show_notes)
                format_display_notes(commit->object.sha1, sb, encoding,
                                     NOTES_SHOW_HEADER | NOTES_INDENT);
 
        free(reencoded);
 }
+
+void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+                   struct strbuf *sb)
+{
+       struct pretty_print_context pp = {0};
+       pp.fmt = fmt;
+       pretty_print_commit(&pp, commit, sb);
+}
index f38471cac3ac57d8d52429cde2dcc4cf5b92557b..4ac9a037f478e769a69072c324a47876e298cae4 100644 (file)
@@ -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, 0))
+               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);
diff --git a/refs.c b/refs.c
index e3c05110e58ca5684f9c43b1e1a5eb2587c96828..b10419a69815ef1c005915f3606c992659e60c77 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1451,7 +1451,7 @@ int write_ref_sha1(struct ref_lock *lock,
        }
        o = parse_object(sha1);
        if (!o) {
-               error("Trying to write ref %s with nonexistant object %s",
+               error("Trying to write ref %s with nonexistent object %s",
                        lock->ref_name, sha1_to_hex(sha1));
                unlock_ref(lock);
                return -1;
index 775d6143037aa4573c0202715fe1b1a0f99e2799..b5be25ce9623936e42566ca52b9e4d1d33153111 100644 (file)
@@ -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;
@@ -811,19 +811,21 @@ static void parse_push(struct strbuf *buf)
 
                strbuf_reset(buf);
                if (strbuf_getline(buf, stdin, '\n') == EOF)
-                       return;
+                       goto free_specs;
                if (!*buf->buf)
                        break;
        } while (1);
 
        if (push(nr_spec, specs))
                exit(128); /* error already reported */
-       for (i = 0; i < nr_spec; i++)
-               free(specs[i]);
-       free(specs);
 
        printf("\n");
        fflush(stdout);
+
+ free_specs:
+       for (i = 0; i < nr_spec; i++)
+               free(specs[i]);
+       free(specs);
 }
 
 int main(int argc, const char **argv)
index 7c6c7544ada4585e62341f0c0854a919f10c2277..d0b1548726e9d2362d27c6947eb61a0647d0eac2 100644 (file)
@@ -85,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;
index 22dfc843da1528ffd0466d496c50c56702e55a24..dcb525a4d03faeba53d108d2e175a6d04f99d160 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -47,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;
@@ -671,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"));
+}
index 595f49f701dc1003927679085ca6eea8ad1083ae..fcd8bc10ba587d023d174d68020a185eab0f8d40 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -19,6 +19,8 @@ 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")
index 0f38364cf3f81a9088305ca802adf8b7c9260e91..c46cfaa3e4d2f06fd67ccd71c7ba47891796fd60 100644 (file)
@@ -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;
 }
@@ -906,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)
@@ -955,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,
@@ -1042,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",
@@ -1088,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);
@@ -1096,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);
-       }
-       if (prune) {
-               ALLOC_GROW(prune, prune_nr+1, prune_alloc);
-               prune[prune_nr] = NULL;
+               ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
+               prune->path[prune->nr++] = xstrdup(sb->buf);
        }
-       *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;
@@ -1178,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;
@@ -1365,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);
@@ -1418,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;
@@ -1464,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)
@@ -1498,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;
 }
 
 /*
@@ -1536,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;
 
@@ -1553,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;
        }
@@ -1566,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;
@@ -1672,8 +1674,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                        got_rev_arg = 1;
        }
 
-       if (prune_data)
-               init_pathspec(&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;
index 9fd8f3016fe8f935f176f57c615f72df8ba8d157..3d64adad18e2c889b7a7b7367f99c239f958dc70 100644 (file)
@@ -36,7 +36,8 @@ struct rev_info {
        const char *prefix;
        const char *def;
        struct pathspec prune_data;
-       unsigned int early_output;
+       unsigned int    early_output:1,
+                       ignore_missing:1;
 
        /* Traversal flags */
        unsigned int    dense:1,
@@ -90,9 +91,11 @@ 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;
+                       date_mode_explicit:1,
+                       preserve_subject:1;
        unsigned int    disable_stdin:1;
 
        enum date_mode date_mode;
@@ -141,6 +144,7 @@ struct rev_info {
        /* commit counts */
        int count_left;
        int count_right;
+       int count_same;
 };
 
 #define REV_TREE_SAME          0
index f91e446c86be8e27f98554567143d7ce6f934bd1..70e8a249d0fe07b6a32b4da4ac8224d1c2f06b1b 100644 (file)
@@ -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
diff --git a/setup.c b/setup.c
index 03cd84f2fcbf9cdbc25116308a9c2c9407af8e71..ce87900ce3c68ace0f231827bdda0e9a65ef15b3 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -85,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);
@@ -126,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;
@@ -147,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;
@@ -275,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;
@@ -325,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);
@@ -390,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 */
+       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 */
diff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c
new file mode 100644 (file)
index 0000000..9d2e971
--- /dev/null
@@ -0,0 +1,443 @@
+/*
+ * 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 <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, &note_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 (file)
index 0000000..b2f47f9
--- /dev/null
@@ -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 (file)
index 0000000..4499b5d
--- /dev/null
@@ -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 */
index 889fe7183065ae8bc12821aadebb69a17bc7635c..064a33040812ba8782bf602c693abf08613d6ec7 100644 (file)
@@ -11,6 +11,7 @@
 #include "pack.h"
 #include "blob.h"
 #include "commit.h"
+#include "run-command.h"
 #include "tag.h"
 #include "tree.h"
 #include "tree-walk.h"
@@ -31,8 +32,6 @@ 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
@@ -227,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.
@@ -360,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)) {
@@ -475,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)
@@ -757,7 +757,7 @@ static int open_packed_git_1(struct packed_git *p)
        while (pack_max_fds <= pack_open_fds && unuse_one_window(NULL, -1))
                ; /* nothing */
 
-       p->pack_fd = git_open_noatime(p->pack_name, p);
+       p->pack_fd = git_open_noatime(p->pack_name);
        if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
                return -1;
        pack_open_fds++;
@@ -1145,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;
 
@@ -1170,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;
 
@@ -1179,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;
        }
@@ -1308,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.
@@ -2206,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));
@@ -2581,10 +2579,11 @@ static void check_tag(const void *buf, size_t size)
 }
 
 static int index_mem(unsigned char *sha1, void *buf, size_t size,
-                    int write_object, enum object_type type,
-                    const char *path, int format_check)
+                    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;
@@ -2600,7 +2599,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
                        re_allocated = 1;
                }
        }
-       if (format_check) {
+       if (flags & HASH_FORMAT_CHECK) {
                if (type == OBJ_TREE)
                        check_tree(buf, size);
                if (type == OBJ_COMMIT)
@@ -2618,44 +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, int format_check)
+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, format_check);
-               else
-                       ret = -1;
-               strbuf_release(&sbuf);
-       } else if (!size) {
-               ret = index_mem(sha1, NULL, size, write_object, type, path,
-                               format_check);
+       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, format_check);
+                       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,
-                               format_check);
+               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;
@@ -2666,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) < 0)
+               if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0)
                        return error("%s: failed to insert into database",
                                     path);
                break;
@@ -2676,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",
index faea58dc8c27de23e8fbaff17b39cb9e57708510..ff5992acc971ac5a67fa3d4def1e0c064b90519f 100644 (file)
@@ -1012,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);
@@ -1065,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))
@@ -1080,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;
 }
@@ -1108,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);
@@ -1130,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);
@@ -1173,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;
@@ -1189,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';
@@ -1202,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);
@@ -1215,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);
                }
        }
diff --git a/shell.c b/shell.c
index dea4cfdd2c230af6afd6233cacfa5c776a9962f9..abb862246ef01743424e4a447ee169f3fdbb9f51 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -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
index 77444a94df3d4a0cda6403957fd13ea262d3ab24..09c43ae59a7d4715c26f13d72ca37bc759d1c76d 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -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)
index 07060ce893d8303b7eb4d78db0f7a5575527f88f..9e6d9fa53fc04156452bf850af1a9b2d3ba830f2 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -3,8 +3,6 @@
 
 /* See Documentation/technical/api-strbuf.txt */
 
-#include <assert.h>
-
 extern char strbuf_slopbuf[];
 struct strbuf {
        size_t alloc;
@@ -33,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';
 }
index 5294cef641ef74ec525d8d747c82faf0bef7352a..b6dec70bd1a6b35c8ecc6a1a9953d64bfe6c4510 100644 (file)
@@ -14,6 +14,15 @@ 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)
 {
@@ -63,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);
        }
 }
 
@@ -82,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);
        }
 }
@@ -434,7 +460,8 @@ int fetch_populated_submodules(int num_options, const char **options,
                                        default_argv = "on-demand";
                                }
                        } else {
-                               if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF)
+                               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))
index 47cbeb6e68ec910a691f2feca18b7b160efd0ced..9046ec98164f44811b67124e869353db4050ec06 100644 (file)
@@ -71,7 +71,7 @@ gitweb-test:
        $(MAKE) $(TGITWEB)
 
 valgrind:
-       GIT_TEST_OPTS=--valgrind $(MAKE)
+       $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
 
 # Smoke testing targets
 -include ../GIT-VERSION-FILE
index 238729c5b7168919ab6f79cf0a0b11c4833ddeae..c85abaffb3b8c2142e87f6e0525fa67c6b62c1a1 100644 (file)
--- a/t/README
+++ b/t/README
@@ -379,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.
 
@@ -390,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' \
@@ -446,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
@@ -456,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
@@ -526,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>
 
index d34208cc27623a6105f6b12f863c648429d2d34d..c56a77d2378117006694d4b575445f89446ae80c 100644 (file)
@@ -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'
index d3829b8d0a18d15590f4cbde4606d130a9293ead..b8996a373a7444b54823f36159ea1a8634e4aeee 100644 (file)
@@ -157,8 +157,7 @@ test_http_push_nonff() {
                grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" output
        '
 
-       test_expect_success C_LOCALE_OUTPUT 'non-fast-forward push shows help message' '
-               grep "To prevent you from losing history, non-fast-forward updates were rejected" \
-                       output
+       test_expect_success 'non-fast-forward push shows help message' '
+               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 (file)
index 0000000..abc2c6f
--- /dev/null
@@ -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 "$@"
+}
index 54520f6fa69bef682e200497c531c9cd7367b9ea..ad664105646d4579a4e31ea5d230268acc33c0f5 100755 (executable)
@@ -180,7 +180,7 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' '
        fi
 '
 
-test_expect_success C_LOCALE_OUTPUT 'reinit' '
+test_expect_success 'reinit' '
 
        (
                sane_unset GIT_CONFIG GIT_WORK_TREE GIT_CONFIG &&
@@ -190,11 +190,11 @@ test_expect_success C_LOCALE_OUTPUT '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' '
@@ -409,7 +409,7 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
        cd newdir &&
        mv .git here &&
        ln -s here .git &&
-       git init -L ../realgitdir
+       git init --separate-git-dir ../realgitdir
        ) &&
        echo "gitdir: `pwd`/realgitdir" >expected &&
        test_cmp expected newdir/.git &&
index 1d4d0a5c7d2549c67334a8d5dd3f462962db81f2..f87abb5a0682d7a2fc7c3947789301c4157d331d 100755 (executable)
@@ -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
index 10b26e4d8ef3ea7b6b9b2ae7aeebbbae1ee46703..8d4938f019ca406c0f248d19d046356dff1ac09a 100755 (executable)
@@ -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
index 5067d1e15b566721c1a3ae79052cbcb023a03656..bd83ed371acb5075b2b3cbb7866d36aa4bd48519 100755 (executable)
@@ -2,74 +2,14 @@
 
 test_description="Test the svn importer's input handling routines.
 
-These tests exercise the line_buffer library, but their real purpose
-is to check the assumptions that library makes of the platform's input
-routines.  Processes engaged in bi-directional communication would
-hang if fread or fgets is too greedy.
+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 -n "$GIT_REMOTE_SVN_TEST_BIG_FILES" && test_set_prereq EXPENSIVE
-
-generate_tens_of_lines () {
-       tens=$1 &&
-       line=$2 &&
-
-       i=0 &&
-       while test $i -lt "$tens"
-       do
-               for j in a b c d e f g h i j
-               do
-                       echo "$line"
-               done &&
-               : $((i = $i + 1)) ||
-               return
-       done
-}
-
-long_read_test () {
-       : each line is 10 bytes, including newline &&
-       line=abcdefghi &&
-       echo "$line" >expect &&
-
-       if ! test_declared_prereq PIPE
-       then
-               echo >&4 "long_read_test: need to declare PIPE prerequisite"
-               return 127
-       fi &&
-       tens_of_lines=$(($1 / 100 + 1)) &&
-       lines=$(($tens_of_lines * 10)) &&
-       readsize=$((($lines - 1) * 10 + 3)) &&
-       copysize=7 &&
-       rm -f input &&
-       mkfifo input &&
-       {
-               (
-                       generate_tens_of_lines $tens_of_lines "$line" &&
-                       exec sleep 100
-               ) >input &
-       } &&
-       test-line-buffer input <<-EOF >output &&
-       binary $readsize
-       copy $copysize
-       EOF
-       kill $! &&
-       test_line_count = $lines output &&
-       tail -n 1 <output >actual &&
-       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 'hello world' '
        echo ">HELLO" >expect &&
        test-line-buffer <<-\EOF >actual &&
@@ -79,21 +19,6 @@ test_expect_success 'hello world' '
        test_cmp expect actual
 '
 
-test_expect_success PIPE '0-length read, no input available' '
-       printf ">" >expect &&
-       rm -f input &&
-       mkfifo input &&
-       {
-               sleep 100 >input &
-       } &&
-       test-line-buffer input <<-\EOF >actual &&
-       binary 0
-       copy 0
-       EOF
-       kill $! &&
-       test_cmp expect actual
-'
-
 test_expect_success '0-length read, send along greeting' '
        echo ">HELLO" >expect &&
        test-line-buffer <<-\EOF >actual &&
@@ -104,33 +29,6 @@ test_expect_success '0-length read, send along greeting' '
        test_cmp expect actual
 '
 
-test_expect_success PIPE '1-byte read, no input available' '
-       printf ">%s" ab >expect &&
-       rm -f input &&
-       mkfifo input &&
-       {
-               (
-                       printf "%s" a &&
-                       printf "%s" b &&
-                       exec sleep 100
-               ) >input &
-       } &&
-       test-line-buffer input <<-\EOF >actual &&
-       binary 1
-       copy 1
-       EOF
-       kill $! &&
-       test_cmp expect actual
-'
-
-test_expect_success PIPE 'long read (around 8192 bytes)' '
-       long_read_test 8192
-'
-
-test_expect_success PIPE,EXPENSIVE 'longer read (around 65536 bytes)' '
-       long_read_test 65536
-'
-
 test_expect_success 'read from file descriptor' '
        rm -f input &&
        echo hello >expect &&
diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh
new file mode 100755 (executable)
index 0000000..54d98b9
--- /dev/null
@@ -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
index ca8a4098fa06afbdbe5c427983d3953550457bc2..babcdd23433348cd0e2397b2fb6968f7cfae4b0a 100755 (executable)
@@ -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
index 680d992f229cda9f1904240f593d66dc1cf3e32d..acaab07fac672e4b1b74161b58baf1f5e153840e 100755 (executable)
@@ -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
 '
index a4a17e001739a45fc7d53e76bd8bbb14426492f9..a847709a1354a824c90a13d2c9e9ab6d6bc69b6c 100755 (executable)
@@ -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'
index eb8e3d447613ce7383f53a7a09039c01d72f35ef..b3ae7d52c6c76895434c1d2e8dfb9a31b12ebc62 100755 (executable)
@@ -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)
index 849911683a799981a565416a88fe9092b3727853..f53de79e565afcbe53a016a56a59b27831a3ce42 100755 (executable)
@@ -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) &&
   (
index f9e00285db662504b200fb25f0ed06c796346c52..4c50ed955eb18da1a60dd9d635f3c32d081cfb76 100755 (executable)
@@ -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
index de84e35c4357c7329b3fae749228df28f31a9d3f..018c3546b6f05228a20dfb5ec9364d0a1575e4ff 100755 (executable)
@@ -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
 '
 
index 9811d467da5a54eefd68ecfad4c68b67e5b3734f..a6a04b6b90d290a445ed7c33e5928a9a168ed28b 100755 (executable)
@@ -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
index 1fd187c5eb188671934f6afe85ca5003c529a942..f6a44c9ee07329c8909662d2a807acf0dd6a8e43 100755 (executable)
@@ -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/t1050-large.sh b/t/t1050-large.sh
new file mode 100755 (executable)
index 0000000..deba111
--- /dev/null
@@ -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
index 3264fefbad8b941bd90b243948f618b65fe4929f..5e29e13782ffad74915128adf163d57eef16f4e8 100755 (executable)
@@ -166,8 +166,8 @@ test_expect_success 'git resolve' '
                -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output
 '
 
-test_expect_success C_LOCALE_OUTPUT 'git resolve output' '
-       test_cmp resolve.expect resolve.output
+test_expect_success 'git resolve output' '
+       test_i18ncmp resolve.expect resolve.output
 '
 
 cat > show-branch2.expect << EOF
index 53fb8228cf18e2b58f3ea63e98a0220fa0fd39f2..3db56267ee89e1b585a548f7bee13386e47395f4 100755 (executable)
@@ -897,4 +897,11 @@ test_expect_success 'key sanity-checking' '
        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
index 080117c6bcbb61078539f36011ecd62780bae305..46103a1591680c9803588d24a135abf79fe64ae9 100755 (executable)
@@ -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
index ff747f8229bb46df070ea9aad2702f4d1c703420..4fd83a667ac8b08bef090d303bc4434969874780 100755 (executable)
@@ -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 '
 
index ba25ff354d6fc4998237b1145737faf6c836966e..caa687b5b46cea65ed16c70c29cc11e9e8b771f1 100755 (executable)
@@ -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
index da6252b1179c47d39393c983d88c75d84a507cb7..63849836c8b088eb495dc565ff909c367c868301 100755 (executable)
@@ -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
 
        (
index 9f8adb1f824a5bafe8829e5ab613e2ab83065f4f..0843a1c13b3e1458418ee59c548e5441f113bbe7 100755 (executable)
@@ -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
 '
index 15ebdc26ebaaf7881b1f51eb788c2eb2922b6d4f..300f8bf25c34cf4ea4e011d1daa525285ca94c5a 100755 (executable)
@@ -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 --
 '
 
index cc34e5535bc5ab7441ba375540e5feec2dfb2d6b..b99d5192a96ec77ef6a99cb86518375c447b48a9 100755 (executable)
@@ -29,9 +29,9 @@ test_expect_success 'checkout chooses branch over tag' '
        test_cmp expect file
 '
 
-test_expect_success C_LOCALE_OUTPUT 'checkout reports switch to branch' '
-       grep "Switched to branch" stderr &&
-       ! grep "^HEAD is now at" stderr
+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' '
@@ -51,9 +51,9 @@ test_expect_success VAGUENESS_SUCCESS 'checkout chooses branch over tag' '
        test_cmp expect file
 '
 
-test_expect_success VAGUENESS_SUCCESS,C_LOCALE_OUTPUT 'checkout reports switch to branch' '
-       grep "Switched to branch" stderr &&
-       ! grep "^HEAD is now at" stderr
+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
index 569b27fe8d488833c3ec0d9952ae064764a0183f..2366f0f4141656666db9cdcd8975335df57b6a8f 100755 (executable)
@@ -13,10 +13,10 @@ check_not_detached () {
 
 ORPHAN_WARNING='you are leaving .* commit.*behind'
 check_orphan_warning() {
-       grep "$ORPHAN_WARNING" "$1"
+       test_i18ngrep "$ORPHAN_WARNING" "$1"
 }
 check_no_orphan_warning() {
-       ! grep "$ORPHAN_WARNING" "$1"
+       test_i18ngrep ! "$ORPHAN_WARNING" "$1"
 }
 
 reset () {
@@ -108,21 +108,30 @@ test_expect_success 'checkout warns on orphan commits' '
        echo content >orphan &&
        git add orphan &&
        git commit -a -m orphan &&
-       git checkout master 2>stderr &&
+       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 &&
+       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 &&
+       git checkout master 2>stderr
+'
+
+test_expect_success 'checkout does not warn leaving reachable commit' '
        check_no_orphan_warning stderr
 '
 
index 856e7da1f2d3aba299d30aca9a1a6d5a288d2d91..4cdebda6a5c9b893f46e99b5b565cc4b70efb2db 100755 (executable)
@@ -111,7 +111,7 @@ test_expect_success 'touch and then add explicitly' '
 
 '
 
-test_expect_success C_LOCALE_OUTPUT 'add -n -u should not add but just report' '
+test_expect_success 'add -n -u should not add but just report' '
 
        (
                echo "add '\''check'\''" &&
@@ -124,7 +124,7 @@ test_expect_success C_LOCALE_OUTPUT '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' '
index 2e8f70245204bd4dc78e67f227e86838e1cdad5b..954fc51e5b560af918661a0ee051521a43f4efba 100755 (executable)
@@ -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) &&
index 49753362f0974627f1b5e5e11144484238875494..8340ac2f073446963f7f5dab39ac87771264da54 100755 (executable)
@@ -34,8 +34,8 @@ do
                ! test -s out
        '
 
-       test_expect_success C_LOCALE_OUTPUT "complaints for ignored $i output" '
-               grep -e "Use -f if" err
+       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" '
@@ -44,8 +44,8 @@ do
                git ls-files "$i" >out &&
                ! test -s out
        '
-       test_expect_success C_LOCALE_OUTPUT "complaints for ignored $i with unignored file output" '
-               grep -e "Use -f if" err
+       test_expect_success "complaints for ignored $i with unignored file output" '
+               test_i18ngrep -e "Use -f if" err
        '
 done
 
@@ -61,10 +61,10 @@ do
                )
        '
 
-       test_expect_success C_LOCALE_OUTPUT "complaints for ignored $i in dir output" '
+       test_expect_success "complaints for ignored $i in dir output" '
                (
                        cd dir &&
-                       grep -e "Use -f if" err
+                       test_i18ngrep -e "Use -f if" err
                )
        '
 done
@@ -81,10 +81,10 @@ do
                )
        '
 
-       test_expect_success C_LOCALE_OUTPUT "complaints for ignored $i in sub output" '
+       test_expect_success "complaints for ignored $i in sub output" '
                (
                        cd sub &&
-                       grep -e "Use -f if" err
+                       test_i18ngrep -e "Use -f if" err
                )
        '
 done
index 806fdccce1db994899509cb3dd9d7801b84c48ba..0c02d569520ddc81b32db7d248197562d00bd7ea 100755 (executable)
@@ -312,20 +312,20 @@ test_expect_success 'merge-recursive result' '
 
 '
 
-test_expect_success C_LOCALE_OUTPUT 'fail if the index has unresolved entries' '
+test_expect_success 'fail if the index has unresolved entries' '
 
        rm -fr [abcd] &&
        git checkout -f "$c1" &&
 
        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/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh
new file mode 100755 (executable)
index 0000000..c286854
--- /dev/null
@@ -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
index 286a2a68692dd7a70e34851f75098506a1fc783a..9e69c8c926620f06343e64e7b3aa3e4ada5a6b69 100755 (executable)
@@ -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' \
@@ -203,10 +203,12 @@ test_expect_success 'test deleting branch deletes branch config' \
      test -z "$(git config branch.my7.remote)" &&
      test -z "$(git config branch.my7.merge)"'
 
-test_expect_success C_LOCALE_OUTPUT 'test deleting branch without 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 &&
@@ -230,7 +232,7 @@ test_expect_success \
 
 # 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' \
index 4ef7d09115678ff97717a5f6a3027a87490dac79..6b7c118e4fcbf3855318b2e9e3b721d07950fd25 100755 (executable)
@@ -72,10 +72,10 @@ cat >expect <<'EOF'
   branch-two
   master
 EOF
-test_expect_success C_LOCALE_OUTPUT 'git branch shows detached HEAD properly' '
+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
index 1921ca3a73370d378e6aedd0820fd8d2a030da03..16de05aff941c73b3e5f5371bd4739959f679efa 100755 (executable)
@@ -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
index 349eebd54268c927249322dc22fc478869860f7c..6eaecec906c49749237b243f772f2e33eb0efedd 100755 (executable)
@@ -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' '
index 64446e3db3afed68e970de6fc3c0780db25c85d1..826500bd18a520a37e3490b9deeca94fb9e14405 100755 (executable)
@@ -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
        '
index 7d8147bb93df482cf7e11338ac96acdf43cf551e..47c8371c7edfe7945ded380bb6f96a00d26c04eb 100755 (executable)
@@ -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 &&
index e573dc845b3d72004b2a96f344528c68977c52e1..a6a6c40a98512b190f8610391aa153a294e4b5cb 100755 (executable)
@@ -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
index 19341e5ca1c7f415afdf2c82f67930b8fed5478b..08201e2331d36af769d832e3d9551c48331b7033 100755 (executable)
@@ -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
index 3b0d27350e8a0fe43bb7735bc614462347aed3e0..1e855cdae55ff46cbb878b724328b57cdb8069ce 100755 (executable)
@@ -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
index 753a6c972cda61d2da3d2ecf7bd9c231004d6ad0..595d2ff990ad3305f47977431c0b7d102ca3866b 100755 (executable)
@@ -91,12 +91,12 @@ test_expect_success 'cherry-pick on stat-dirty working tree' '
        )
 '
 
-test_expect_success C_LOCALE_OUTPUT 'revert forbidden on dirty working tree' '
+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
 
 '
 
index b0faa299183df5fe06ccaf383bce47cbb9a0cf89..9aefe3a1becac200f2c29beee84fab278f9fcfa0 100755 (executable)
@@ -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
 
 '
 
index c0c8330c20e80f26d51b62c8999533c5692094de..212ec54aaf0d805bbdecd93f3311d248a57d5b08 100755 (executable)
@@ -44,7 +44,7 @@ test_expect_success 'failed cherry-pick does not advance HEAD' '
        test "$head" = "$newhead"
 '
 
-test_expect_success C_LOCALE_OUTPUT 'advice from failed cherry-pick' "
+test_expect_success 'advice from failed cherry-pick' "
        pristine_detach initial &&
 
        picked=\$(git rev-parse --short picked) &&
@@ -56,7 +56,7 @@ test_expect_success C_LOCALE_OUTPUT 'advice from failed cherry-pick' "
        EOF
        test_must_fail git cherry-pick picked 2>actual &&
 
-       test_cmp expected actual
+       test_i18ncmp expected actual
 "
 
 test_expect_success 'failed cherry-pick sets CHERRY_PICK_HEAD' '
index cd093bd34730a3dba6eec7719d3b0170e517fb5e..9fd28bcf3435f831fa24285dd703c82b8396d635 100755 (executable)
@@ -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-*" | :;
index 7de42faf4887c6e8137c2498a96b41a4afe4e672..575d9508a09911f9d95dad2786fb2f1494abc093 100755 (executable)
@@ -271,9 +271,9 @@ test_expect_success 'git add --dry-run of non-existing file' "
        test_must_fail git add --dry-run track-this ignored-file >actual 2>&1
 "
 
-test_expect_success C_LOCALE_OUTPUT 'git add --dry-run of an existing file output' "
+test_expect_success 'git add --dry-run of an existing file output' "
        echo \"fatal: pathspec 'ignored-file' did not match any files\" >expect &&
-       test_cmp expect actual
+       test_i18ncmp expect actual
 "
 
 cat >expect.err <<\EOF
@@ -290,9 +290,9 @@ 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_expect_success C_LOCALE_OUTPUT 'git add --dry-run --ignore-missing of non-existing file output' '
-       test_cmp expect.out actual.out &&
-       test_cmp expect.err 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
index d6327e7c74cd0cd3d433a71b1e9bb89eaf088ed5..9e236f9cc0bf43155d377c1706c8142d843c417d 100755 (executable)
@@ -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 (executable)
index 0000000..5115de7
--- /dev/null
@@ -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
+
+if mkdir ":" 2>/dev/null
+then
+       test_set_prereq COLON_DIR
+fi
+
+test_expect_success COLON_DIR 'a file with the same (long) magic name exists' '
+       : >":(icase)ha" &&
+       test_must_fail git add -n ":(icase)ha" &&
+       git add -n "./:(icase)ha"
+'
+
+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
index f62aaf5816f6ecf4e2c4e8fe5ae60925e61ccfcd..7197aae1ed10add49aa72748188fecb4c2ef90a4 100755 (executable)
@@ -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 &&
@@ -510,7 +536,7 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re
        git reset --hard HEAD
 '
 
-test_expect_success 'ref with non-existant reflog' '
+test_expect_success 'ref with non-existent reflog' '
        git stash clear &&
        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
 '
 
index 1e7193ac0bc650868f187d3103be32c3004a95b2..781fd716815d90956575b042d72601e67834231c 100755 (executable)
@@ -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
 '
index cad85450b78891a98a0b60af1422c62c7288265c..844277cfa605f51cccd5d78a48a83fb75c03af9b 100755 (executable)
@@ -64,17 +64,42 @@ test_expect_success \
     'validate the output.' \
     'compare_diff_patch current expected'
 
-test_expect_success C_LOCALE_OUTPUT 'favour same basenames over different ones' '
+test_expect_success 'favour same basenames over different ones' '
        cp path1 another-path &&
        git add another-path &&
        git commit -m 1 &&
        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 C_LOCALE_OUTPUT  '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
index 9fb8ca06a84b3f3e60f466cc33bc2de786a9fc90..a5e8b830834f4b5feb531f8f4f4d08462325b1de 100755 (executable)
@@ -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
 }
 
index 5daa0f2a0c9c8cd6ed2c06e12c8fd421c178d4f0..93a6f208710befc064b7b99bcd758bb8b6381918 100755 (executable)
@@ -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'`
@@ -287,6 +300,8 @@ 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' '
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 (file)
index 0000000..e48e33f
--- /dev/null
@@ -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 (file)
index 0000000..5fb02c1
--- /dev/null
@@ -0,0 +1,3 @@
+$ git diff --dirstat initial rearrange
+ 100.0% dir/
+$
index 1f0f9ad44b241e57e867c0676b73f37a1dc93d60..3b4e113012568dc936bd230fe33c251ab8bff3dd 100644 (file)
@@ -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 ***
index d155e0bab29000a99eb797681450be1174185846..44d45257da708f25bd0eb2c631bd7e68a38a7354 100644 (file)
@@ -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>
index fd7c3e64396b4ea57c3b03d2e120580205263462..27d3eabc26f35401581d64f0a933fb552e5f4d52 100644 (file)
@@ -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>
index c3cdb5205350985f3362268b0cd63bc59bee2444..92248d24c48a65ab89a5b0ad255357f6034e6ad4 100755 (executable)
@@ -614,13 +614,13 @@ echo "fatal: --name-only does not make sense" > expect.name-only
 echo "fatal: --name-status does not make sense" > expect.name-status
 echo "fatal: --check does not make sense" > expect.check
 
-test_expect_success C_LOCALE_OUTPUT 'options no longer allowed for format-patch' '
+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 &&
@@ -793,4 +793,80 @@ test_expect_success 'format-patch wraps extremely long headers (rfc2047)' '
        test_cmp expect subject
 '
 
+M8="foo_bar_"
+M64=$M8$M8$M8$M8$M8$M8$M8$M8
+cat >expect <<EOF
+From: $M64
+ <foobar@foo.bar>
+EOF
+test_expect_success 'format-patch wraps non-quotable headers' '
+       rm -rf patches/ &&
+       echo content >>file &&
+       git add file &&
+       git commit -mfoo --author "$M64 <foobar@foo.bar>" &&
+       git format-patch --stdout -1 >patch &&
+       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
index 364693062399228cd6a18b52a21db173519d8e94..b68c56b68c0f80a0d96b513733bc5fb17db6fed4 100755 (executable)
@@ -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
index a7602cf923d95a51643753ce44fa65cf406aba9c..083f62d1d6bb6bfd908b0a03db1a47f80071ef91 100755 (executable)
@@ -4,8 +4,6 @@ test_description='external diff interface test'
 
 . ./test-lib.sh
 
-_z40=0000000000000000000000000000000000000000
-
 test_expect_success setup '
 
        test_tick &&
index 2a537a21e8e6793e6ffa2fe1522e30f877b209fe..c00a94b9ba4498e72a31856aac9cf653bbce8ba3 100755 (executable)
@@ -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
 
index 241a74d2a20276d711944d3e203083b515486e77..518bf9524e0b55181f3c440d202b69c12a0b314f 100755 (executable)
@@ -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 &&
index 37aeab0d5c7415a54384c90708626f6a5807acee..c374aa4c1c60e9a12cf1ebf5587daf3656e4851a 100755 (executable)
@@ -307,4 +307,30 @@ 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
index abc49348b196cf0fec232b6f2399356e4fe324d5..3c728a3ebf9ce52e5c24c81525d5cb749cfb2957 100755 (executable)
@@ -67,4 +67,9 @@ test_expect_success 'diff-files --diff-filter --quiet' '
        test_must_fail git diff-files --diff-filter=M --quiet
 '
 
+test_expect_success 'diff-tree --diff-filter --quiet' '
+       git commit -a -m "worktree state" &&
+       test_must_fail git diff-tree --diff-filter=M --quiet HEAD^ HEAD
+'
+
 test_done
diff --git a/t/t4046-diff-unmerged.sh b/t/t4046-diff-unmerged.sh
new file mode 100755 (executable)
index 0000000..25d50a6
--- /dev/null
@@ -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 (executable)
index 0000000..29e80a5
--- /dev/null
@@ -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/t4152-am-subjects.sh b/t/t4152-am-subjects.sh
new file mode 100755 (executable)
index 0000000..4c68245
--- /dev/null
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='test subject preservation with format-patch | am'
+. ./test-lib.sh
+
+make_patches() {
+       type=$1
+       subject=$2
+       test_expect_success "create patches with $type subject" '
+               git reset --hard baseline &&
+               echo $type >file &&
+               git commit -a -m "$subject" &&
+               git format-patch -1 --stdout >$type.patch &&
+               git format-patch -1 --stdout -k >$type-k.patch
+       '
+}
+
+check_subject() {
+       git reset --hard baseline &&
+       git am $2 $1.patch &&
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'setup baseline commit' '
+       test_commit baseline file
+'
+
+SHORT_SUBJECT='short subject'
+make_patches short "$SHORT_SUBJECT"
+
+LONG_SUBJECT1='this is a long subject that is virtually guaranteed'
+LONG_SUBJECT2='to require wrapping via format-patch if it is all'
+LONG_SUBJECT3='going to appear on a single line'
+LONG_SUBJECT="$LONG_SUBJECT1 $LONG_SUBJECT2 $LONG_SUBJECT3"
+make_patches long "$LONG_SUBJECT"
+
+MULTILINE_SUBJECT="$LONG_SUBJECT1
+$LONG_SUBJECT2
+$LONG_SUBJECT3"
+make_patches multiline "$MULTILINE_SUBJECT"
+
+echo "$SHORT_SUBJECT" >expect
+test_expect_success 'short subject preserved (format-patch | am)' '
+       check_subject short
+'
+test_expect_success 'short subject preserved (format-patch -k | am)' '
+       check_subject short-k
+'
+test_expect_success 'short subject preserved (format-patch -k | am -k)' '
+       check_subject short-k -k
+'
+
+echo "$LONG_SUBJECT" >expect
+test_expect_success 'long subject preserved (format-patch | am)' '
+       check_subject long
+'
+test_expect_success 'long subject preserved (format-patch -k | am)' '
+       check_subject long-k
+'
+test_expect_success 'long subject preserved (format-patch -k | am -k)' '
+       check_subject long-k -k
+'
+
+echo "$LONG_SUBJECT" >expect
+test_expect_success 'multiline subject unwrapped (format-patch | am)' '
+       check_subject multiline
+'
+test_expect_success 'multiline subject unwrapped (format-patch -k | am)' '
+       check_subject multiline-k
+'
+echo "$MULTILINE_SUBJECT" >expect
+test_expect_success 'multiline subject preserved (format-patch -k | am -k)' '
+       check_subject multiline-k -k
+'
+
+test_done
index 2fcc31a6f3d346e533b697eecf853e219d174cd0..983e34bec67a4cc4ba1182332e495377cc879ac9 100755 (executable)
@@ -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"' '
index e818de6ddd904378265cb11f2d48075cda474f5f..1f182f612c7e2376b503cf0b9cf7389e37903239 100755 (executable)
@@ -94,7 +94,7 @@ nick1 (1):
 
 EOF
 
-test_expect_success 'mailmap.file non-existant' '
+test_expect_success 'mailmap.file non-existent' '
        rm internal_mailmap/.mailmap &&
        rmdir internal_mailmap &&
        git shortlog HEAD >actual &&
index cb9f2bdd2956244955d56ec8a0cd2320726e1030..2ae9faa8b37821db6e7c28ae3d98e53cb25264b1 100755 (executable)
@@ -45,7 +45,7 @@ test_expect_success 'alias user-defined tformat' '
        test_cmp expected actual
 '
 
-test_expect_success 'alias non-existant format' '
+test_expect_success 'alias non-existent format' '
        git config pretty.test-alias format-that-will-never-exist &&
        test_must_fail git log --pretty=test-alias
 '
diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh
new file mode 100755 (executable)
index 0000000..2c482b6
--- /dev/null
@@ -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
index b0b2684a1f879cc173bf2bc67902724ed8f25d48..0eace37a03d75a7205575c36a0ec0de3ec401b1b 100755 (executable)
@@ -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"
 '
 
index 4e69c907d810c5a82892f94f05374b824a003771..0d0222ea2ace733ffe3fbfbdee58520abcd40e92 100755 (executable)
@@ -347,6 +347,21 @@ test_expect_success 'fetch mirrors do not act as mirrors during push' '
        )
 '
 
+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 &&
@@ -382,6 +397,13 @@ test_expect_success 'push mirrors do not act as mirrors during fetch' '
        )
 '
 
+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 &&
index b7b7ddaa403d7a06c0203b89e484d56d20863f4a..530b01678e5c9cfc1aed2fe2c6fcd96b9a09a90d 100755 (executable)
@@ -43,10 +43,10 @@ test_expect_success 'no group updates all' '
        repo_fetched two
 '
 
-test_expect_success 'nonexistant group produces error' '
-       mark nonexistant &&
+test_expect_success 'nonexistent group produces error' '
+       mark nonexistent &&
        update_repos &&
-       test_must_fail git remote update nonexistant &&
+       test_must_fail git remote update nonexistent &&
        ! repo_fetched one &&
        ! repo_fetched two
 '
index d1912351db7da4a7c457fd44895b5ea076c84e7e..5c546c99a58854ae0f430e469dad525af52e9292 100755 (executable)
@@ -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
index d73731e6446f71480db1ec7cceb73f27ad51ecd3..3abb2907ea91c5ef6298ca7ee64b7a8156d65d96 100755 (executable)
@@ -367,7 +367,7 @@ test_expect_success 'push with colon-less refspec (4)' '
 
 '
 
-test_expect_success 'push head with non-existant, incomplete dest' '
+test_expect_success 'push head with non-existent, incomplete dest' '
 
        mk_test &&
        git push testrepo master:branch &&
@@ -375,7 +375,7 @@ test_expect_success 'push head with non-existant, incomplete dest' '
 
 '
 
-test_expect_success 'push tag with non-existant, incomplete dest' '
+test_expect_success 'push tag with non-existent, incomplete dest' '
 
        mk_test &&
        git tag -f v1.0 &&
@@ -384,14 +384,14 @@ test_expect_success 'push tag with non-existant, incomplete dest' '
 
 '
 
-test_expect_success 'push sha1 with non-existant, incomplete dest' '
+test_expect_success 'push sha1 with non-existent, incomplete dest' '
 
        mk_test &&
        test_must_fail git push testrepo `git rev-parse master`:foo
 
 '
 
-test_expect_success 'push ref expression with non-existant, incomplete dest' '
+test_expect_success 'push ref expression with non-existent, incomplete dest' '
 
        mk_test &&
        test_must_fail git push testrepo master^:branch
@@ -436,7 +436,7 @@ test_expect_success 'push with +HEAD' '
 
 '
 
-test_expect_success 'push HEAD with non-existant, incomplete dest' '
+test_expect_success 'push HEAD with non-existent, incomplete dest' '
 
        mk_test &&
        git checkout master &&
index af78e21ba913b465e7bf2e4149549d32de9d4240..a1fddd4d15fb2c3cb5d0ab6e09a461314fb4b1f1 100755 (executable)
@@ -66,12 +66,9 @@ test_expect_success "fetch --recurse-submodules recurses into submodules" '
        (
                cd downstream &&
                git fetch --recurse-submodules >../actual.out 2>../actual.err
-       )
-'
-
-test_expect_success C_LOCALE_OUTPUT "fetch --recurse-submodules recurses into submodules: output" '
-       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" '
@@ -98,12 +95,9 @@ test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses i
                cd downstream &&
                git config -f .gitmodules submodule.submodule.fetchRecurseSubmodules true &&
                git fetch >../actual.out 2>../actual.err
-       )
-'
-
-test_expect_success C_LOCALE_OUTPUT "using fetchRecurseSubmodules=true in .gitmodules recurses into submodules" '
-       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" '
@@ -132,12 +126,9 @@ test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setti
                git fetch --recurse-submodules >../actual.out 2>../actual.err &&
                git config --unset -f .gitmodules submodule.submodule.fetchRecurseSubmodules &&
                git config --unset submodule.submodule.fetchRecurseSubmodules
-       )
-'
-
-test_expect_success C_LOCALE_OUTPUT "--recurse-submodules overrides fetchRecurseSubmodules setting from .git/config: output" '
-       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" '
@@ -154,24 +145,18 @@ test_expect_success "--dry-run propagates to submodules" '
        (
                cd downstream &&
                git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err
-       )
-'
-
-test_expect_success C_LOCALE_OUTPUT "--dry-run propagates to submodules: output" '
-       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_expect_success C_LOCALE_OUTPUT "Without --dry-run propagates to submodules: output" '
-       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" '
@@ -180,12 +165,9 @@ test_expect_success "recurseSubmodules=true propagates into submodules" '
                cd downstream &&
                git config fetch.recurseSubmodules true
                git fetch >../actual.out 2>../actual.err
-       )
-'
-
-test_expect_success C_LOCALE_OUTPUT "recurseSubmodules=true propagates into submodules: output" '
-       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" '
@@ -197,12 +179,9 @@ test_expect_success "--recurse-submodules overrides config in submodule" '
                        git config fetch.recurseSubmodules false
                ) &&
                git fetch --recurse-submodules >../actual.out 2>../actual.err
-       )
-'
-
-test_expect_success C_LOCALE_OUTPUT "--recurse-submodules overrides config in submodule: output" '
-       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" '
@@ -243,8 +222,8 @@ test_expect_success "Recursion stops when no new submodule commits are fetched"
                cd downstream &&
                git fetch >../actual.out 2>../actual.err
        ) &&
-       test_cmp expect.err.sub actual.err &&
-       test_cmp expect.out.sub actual.out
+       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" '
@@ -261,7 +240,7 @@ test_expect_success "Recursion doesn't happen when new superproject commits don'
                git fetch >../actual.out 2>../actual.err
        ) &&
        ! test -s actual.out &&
-       test_cmp expect.err.file actual.err
+       test_i18ncmp expect.err.file actual.err
 '
 
 test_expect_success "Recursion picks up config in submodule" '
@@ -289,8 +268,8 @@ test_expect_success "Recursion picks up config in submodule" '
                        git config --unset fetch.recurseSubmodules
                )
        ) &&
-       test_cmp expect.err.sub actual.err &&
-       test_cmp expect.out actual.out
+       test_i18ncmp expect.err.sub actual.err &&
+       test_i18ncmp expect.out actual.out
 '
 
 test_expect_success "Recursion picks up all submodules when necessary" '
@@ -321,8 +300,8 @@ test_expect_success "Recursion picks up all submodules when necessary" '
                cd downstream &&
                git fetch >../actual.out 2>../actual.err
        ) &&
-       test_cmp expect.err.2 actual.err &&
-       test_cmp expect.out actual.out
+       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)" '
@@ -375,8 +354,8 @@ test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necess
                        git config --unset -f .gitmodules submodule.deepsubmodule.fetchRecursive
                )
        ) &&
-       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=on-demand' stops when no new submodule commits are found in the superproject (and ignores config)" '
@@ -393,7 +372,7 @@ test_expect_success "'--recurse-submodules=on-demand' stops when no new submodul
                git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err
        ) &&
        ! test -s actual.out &&
-       test_cmp expect.err.file actual.err
+       test_i18ncmp expect.err.file actual.err
 '
 
 test_expect_success "'fetch.recurseSubmodules=on-demand' overrides global config" '
@@ -420,8 +399,8 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' overrides global config
                cd downstream &&
                git config --unset fetch.recurseSubmodules
        ) &&
-       test_cmp expect.out.sub actual.out &&
-       test_cmp expect.err.2 actual.err
+       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" '
@@ -448,8 +427,8 @@ test_expect_success "'submodule.<sub>.fetchRecurseSubmodules=on-demand' override
                cd downstream &&
                git config --unset submodule.submodule.fetchRecurseSubmodules
        ) &&
-       test_cmp expect.out.sub actual.out &&
-       test_cmp expect.err.2 actual.err
+       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" '
@@ -468,7 +447,7 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea
                git fetch >../actual.out 2>../actual.err
        ) &&
        ! test -s actual.out &&
-       test_cmp expect.err actual.err
+       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 (executable)
index 0000000..62f2460
--- /dev/null
@@ -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
index 0492877d516b8f33a8fd1555ec77a2aa3b34352c..a73c82635ff6fba6fcef4f139ff09205c1b9b6de 100755 (executable)
@@ -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))
 '
@@ -135,10 +137,22 @@ test_expect_success 'push fails for non-fast-forward refs unmatched by remote he
        grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output
 '
 
-test_expect_success C_LOCALE_OUTPUT 'push fails for non-fast-forward refs unmatched by remote helper: our 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
index 5a068b21e4aeac79863df657d6388ec4c2732d55..151ea531bdfeb5a012e57407749beb4f26de6d2a 100755 (executable)
@@ -194,11 +194,14 @@ test_expect_success 'do not respect url-encoding of non-url path' '
 test_expect_success 'clone separate gitdir' '
        rm -rf dst &&
        git clone --separate-git-dir realgitdir src dst &&
-       echo "gitdir: `pwd`/realgitdir" >expected &&
-       test_cmp expected dst/.git &&
        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
index cacf3de6c9f31889fd8df0bc0cebbbc74e3525bd..28d4f6b259c1696435698cb3826c837c8eaf90e3 100755 (executable)
@@ -157,6 +157,33 @@ test_expect_success '--cherry' '
        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' '
        git symbolic-ref HEAD refs/heads/independent &&
        rm .git/index &&
index 082032edc36268f1b8e26ca6f409080093ac0f2b..f80bba871cb45a4afb098c96e7925b6361cf7f95 100755 (executable)
@@ -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' '
index f1c32dba423316641197dbfe19b2b95581acebad..667b37564e3d31ca060216e841b9a5d9e385c165 100755 (executable)
@@ -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
index fb8291c812854608777dd8d35edfcda78df5069a..f00cebff3e42cf0a38d06a03b6f14ef08dd16372 100755 (executable)
@@ -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
index 6c3719141aa567b97de1228f53a6c49eaf139e8e..a9b0ac1efc0eac3f50bd9fe26e5900307eddaba2 100755 (executable)
@@ -42,13 +42,13 @@ b3 behind 1
 b4 ahead 2
 EOF
 
-test_expect_success C_LOCALE_OUTPUT 'branch -v' '
+test_expect_success 'branch -v' '
        (
                cd test &&
                git branch -v
        ) |
        sed -n -e "$script" >actual &&
-       test_cmp expect actual
+       test_i18ncmp expect actual
 '
 
 test_expect_success 'checkout' '
index ae2194e07d835aa088a1c5b5f5fa5001f41ea7d5..5c87f28e4ed8c8c8dc40fd561516dd8d0db791c9 100755 (executable)
@@ -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 (executable)
index 0000000..debadbd
--- /dev/null
@@ -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
index 18269962458248034598d2bb15315f0d2a121027..f67aa6ff6a3c2af2d0c1999e8203da3a922b0555 100755 (executable)
@@ -123,8 +123,8 @@ cat - >err.expect <<EOF
 warning: tag 'A' is really 'Q' here
 EOF
 check_describe A-* HEAD
-test_expect_success C_LOCALE_OUTPUT 'warning was displayed for Q' '
-       test_cmp err.expect err.actual
+test_expect_success 'warning was displayed for Q' '
+       test_i18ncmp err.expect err.actual
 '
 test_expect_success 'rename tag Q back to A' '
        mv .git/refs/tags/Q .git/refs/tags/A
index 1dedfd0c836c06eb3d2f66aa8e4e7eac54368508..2ac1c66079b7656a60a25fbbb91c48c0b1db010b 100755 (executable)
@@ -1120,13 +1120,11 @@ test_expect_success \
        ! (GIT_EDITOR=cat git tag -a initial-comment > actual)
 '
 
-test_expect_success \
-       C_LOCALE_OUTPUT \
-       '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 \
index bb4066f76762520b98caeba1cddaf442d80a4bf2..8f3b54d826f5a9c8e7b08df7f6a58398b5a62480 100755 (executable)
@@ -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 &&
index 14fcb1c7f5d8366827bc6e3256f118a757d2e996..9ceaa4049f960f20ca37d58e58e9e6c4e9ff4398 100755 (executable)
@@ -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 &&
@@ -124,16 +124,16 @@ cat >expected <<EOF
 Would remove expected
 Would remove result
 EOF
-test_expect_success C_LOCALE_OUTPUT 'git-clean, absent case' '
+test_expect_success 'git-clean, absent case' '
        setup_absent &&
        git clean -n > result &&
-       test_cmp expected result
+       test_i18ncmp expected result
 '
 
-test_expect_success C_LOCALE_OUTPUT 'git-clean, dirty case' '
+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
index b4fcc86a10f15178da25da159e160b38d717f318..b8cb4906aa7de022543b594399b6fa9a83ffc847 100755 (executable)
@@ -38,7 +38,7 @@ cat >expect <<EOF
 no changes added to commit (use "git add" and/or "git commit -a")
 EOF
 
-test_expect_success C_LOCALE_OUTPUT 'M/D conflict does not segfault' '
+test_expect_success 'M/D conflict does not segfault' '
        mkdir mdconflict &&
        (
                cd mdconflict &&
@@ -50,10 +50,72 @@ test_expect_success C_LOCALE_OUTPUT '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
index 7be2ff38fc92088d4caff9ea13b1d78a1b040b40..f1cfc9ac959ee281d50f7b8a51fdcefd2d0670e7 100755 (executable)
@@ -423,10 +423,10 @@ Unstaged changes after reset:
 M      file2
 EOF
 
-test_expect_success C_LOCALE_OUTPUT '--mixed refreshes the index' '
+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)' '
index b42820ad6976590fe195637d5e85d1b4db963cda..a82a07a04a8500cdac2cfb7ef39fb3f5981f437f 100755 (executable)
@@ -233,11 +233,11 @@ test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
 #           working index HEAD target         working index HEAD
 #           ----------------------------------------------------
 # file1:     X       U     B    C     --keep   (disallowed)
-test_expect_success C_LOCALE_OUTPUT '"reset --keep HEAD^" fails with pending merge' '
+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:
@@ -259,11 +259,11 @@ test_expect_success '"reset --merge HEAD" is ok with pending merge' '
 #           working index HEAD target         working index HEAD
 #           ----------------------------------------------------
 # file1:     X       U     B    B     --keep   (disallowed)
-test_expect_success C_LOCALE_OUTPUT '"reset --keep HEAD" fails with pending merge' '
+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' '
@@ -280,7 +280,7 @@ test_expect_success '--merge is ok with added/deleted merge' '
     git diff --exit-code --cached
 '
 
-test_expect_success C_LOCALE_OUTPUT '--keep fails with added/deleted merge' '
+test_expect_success '--keep fails with added/deleted merge' '
     git reset --hard third &&
     rm -f file2 &&
     test_must_fail git merge branch3 &&
@@ -289,7 +289,7 @@ test_expect_success C_LOCALE_OUTPUT '--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
index 37ed0931d9499791a6b3336efb97385356df6157..07fb53adcbc06e260b078de546bd07f11093071d 100755 (executable)
@@ -223,12 +223,12 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '
        test_cmp two expect
 '
 
-test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD (with advice declined)' '
+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) &&
@@ -242,11 +242,11 @@ test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD (with advice declin
        fi
 '
 
-test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD' '
+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) &&
@@ -260,7 +260,7 @@ test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD' '
        fi
 '
 
-test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with branchname^' '
+test_expect_success 'checkout to detach HEAD with branchname^' '
 
        git checkout -f master && git clean -f &&
        git checkout renamer^ &&
@@ -276,7 +276,7 @@ test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with branchname^' '
        fi
 '
 
-test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with :/message' '
+test_expect_success 'checkout to detach HEAD with :/message' '
 
        git checkout -f master && git clean -f &&
        git checkout ":/Initial" &&
@@ -292,7 +292,7 @@ test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with :/message' '
        fi
 '
 
-test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with HEAD^0' '
+test_expect_success 'checkout to detach HEAD with HEAD^0' '
 
        git checkout -f master && git clean -f &&
        git checkout HEAD^0 &&
index 7e1be444027621e1ea94b0b9ed0e129fce922b02..800b5368a5248835bb9817c0e0c8409131306b3c 100755 (executable)
@@ -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
index bf7c788735d6e3e0ecf56e7a2f82826701ee1789..4f16fcce2bfcb63f437fa6b495fdb5c4370fccc1 100755 (executable)
@@ -94,6 +94,29 @@ test_expect_success 'submodule update does not fetch already present commits' '
        ! 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
index bcdf0847d0970f544e96afa0036ec12800422fd8..1c908f4d3966cb9a2769465652981bef831f312d 100755 (executable)
@@ -15,7 +15,7 @@ commit_msg_is () {
 
        printf "%s" "$(git log --pretty=format:%s%b -1)" >$expect &&
        printf "%s" "$1" >$actual &&
-       test_cmp $expect $actual
+       test_i18ncmp $expect $actual
 }
 
 # A sanity check to see if commit is working at all.
@@ -72,7 +72,7 @@ test_expect_success 'adding comments to a template should not commit' '
        )
 '
 
-test_expect_success C_LOCALE_OUTPUT 'adding real content to a template should commit' '
+test_expect_success 'adding real content to a template should commit' '
        (
                test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
                git commit --template "$TEMPLATE"
@@ -80,7 +80,7 @@ test_expect_success C_LOCALE_OUTPUT 'adding real content to a template should co
        commit_msg_is "template linecommit message"
 '
 
-test_expect_success C_LOCALE_OUTPUT '-t option should be short for --template' '
+test_expect_success '-t option should be short for --template' '
        echo "short template" > "$TEMPLATE" &&
        echo "new content" >> foo &&
        git add foo &&
@@ -91,7 +91,7 @@ test_expect_success C_LOCALE_OUTPUT '-t option should be short for --template' '
        commit_msg_is "short templatecommit message"
 '
 
-test_expect_success C_LOCALE_OUTPUT 'config-specified template should commit' '
+test_expect_success 'config-specified template should commit' '
        echo "new template" > "$TEMPLATE" &&
        git config commit.template "$TEMPLATE" &&
        echo "more content" >> foo &&
@@ -123,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 &&
@@ -290,7 +304,7 @@ test_expect_success 'commit --squash works with -c for same commit' '
        commit_msg_is "squash! edited commit"
 '
 
-test_expect_success C_LOCALE_OUTPUT 'commit --squash works with editor' '
+test_expect_success 'commit --squash works with editor' '
        commit_for_rebase_autosquash_setup &&
        test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
        git commit --squash HEAD~1 &&
diff --git a/t/t7500/add-whitespaced-content b/t/t7500/add-whitespaced-content
new file mode 100755 (executable)
index 0000000..ccf07c6
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+sed -e 's/|$//' >>"$1" <<\EOF
+
+ |
+commit message          |
+
+EOF
+exit 0
index a76c47419590ed16c53b8d4d82c2d92c11853154..3ad04363b5920497617f806a18a3a5a0083ac1b9 100755 (executable)
@@ -16,9 +16,10 @@ test_expect_success \
        "echo 'bongo bongo' >file &&
         git add file"
 
-test_expect_success C_LOCALE_OUTPUT \
-       "Constructing initial commit" \
-       "git status | grep 'Initial commit'"
+test_expect_success "Constructing initial commit" '
+       git status >actual &&
+       test_i18ngrep "Initial commit" actual
+'
 
 test_expect_success \
        "fail initial amend" \
@@ -41,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" \
@@ -130,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"
index cfb569eaba657501cf0361bee8c3f7c172ed1c06..3f3adc31b98773d26715089c25d8d923dd342717 100755 (executable)
@@ -22,10 +22,7 @@ check_summary_oneline() {
        SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')"
        echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp &&
 
-       if test_have_prereq C_LOCALE_OUTPUT
-       then
-               test_cmp exp act
-       fi
+       test_i18ncmp exp act
 }
 
 test_expect_success 'output summary format' '
@@ -234,23 +231,19 @@ 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 C_LOCALE_OUTPUT 'cleanup commit messages (strip,-F,-e): output' '
-       test_cmp expect actual
+test_expect_success 'cleanup commit messages (strip,-F,-e): output' '
+       test_i18ncmp expect actual
 '
 
 echo "#
 # Author:    $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
 #" >> expect
 
-test_expect_success C_LOCALE_OUTPUT 'author different from committer' '
-
+test_expect_success 'author different from committer' '
        echo >>negative &&
-       git commit -e -m "sample"
-       head -n 7 .git/COMMIT_EDITMSG >actual
-'
-
-test_expect_success C_LOCALE_OUTPUT 'author different from committer: output' '
-       test_cmp expect actual
+       test_might_fail git commit -e -m "sample" &&
+       head -n 7 .git/COMMIT_EDITMSG >actual &&
+       test_i18ncmp expect actual
 '
 
 mv expect expect.tmp
@@ -259,7 +252,7 @@ rm -f expect.tmp
 echo "# Committer:
 #" >> expect
 
-test_expect_success C_LOCALE_OUTPUT 'committer is automatic' '
+test_expect_success 'committer is automatic' '
 
        echo >>negative &&
        (
@@ -270,10 +263,7 @@ test_expect_success C_LOCALE_OUTPUT 'committer is automatic' '
        ) &&
        head -n 8 .git/COMMIT_EDITMSG | \
        sed "s/^# Committer: .*/# Committer:/" >actual
-'
-
-test_expect_success C_LOCALE_OUTPUT 'committer is automatic: output' '
-       test_cmp expect actual
+       test_i18ncmp expect actual
 '
 
 pwd=`pwd`
@@ -376,78 +366,78 @@ 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
 }
 
 try_commit_status_combo () {
 
-       test_expect_success C_LOCALE_OUTPUT 'commit' '
+       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 C_LOCALE_OUTPUT 'commit' '
+       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 C_LOCALE_OUTPUT 'commit --status' '
+       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 C_LOCALE_OUTPUT 'commit --no-status' '
+       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 C_LOCALE_OUTPUT 'commit with commit.status = yes' '
+       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 C_LOCALE_OUTPUT 'commit with commit.status = no' '
+       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 C_LOCALE_OUTPUT 'commit --status with commit.status = yes' '
+       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 C_LOCALE_OUTPUT 'commit --no-status with commit.status = yes' '
+       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 C_LOCALE_OUTPUT 'commit --status with commit.status = no' '
+       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 C_LOCALE_OUTPUT 'commit --no-status with commit.status = no' '
+       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
        '
 
 }
index c56733253fe335af9cbd12d19e776e35bd335994..d31b34da83d5ad336f2355acc0d2536b9d30e3ce 100755 (executable)
@@ -4,37 +4,41 @@ 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"
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status clean' '
+test_expect_success 'status clean' '
        git status >output &&
-       grep "nothing to commit" output
+       test_i18ngrep "nothing to commit" output
 '
 
-test_expect_success C_LOCALE_OUTPUT 'commit --dry-run -a clean' '
+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 C_LOCALE_OUTPUT 'status with modified file in submodule' '
+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)' '
@@ -46,10 +50,10 @@ test_expect_success 'status with modified file in submodule (porcelain)' '
        EOF
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status with added file in submodule' '
+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)' '
@@ -60,16 +64,16 @@ test_expect_success 'status with added file in submodule (porcelain)' '
        EOF
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status with untracked file in submodule' '
+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 C_LOCALE_OUTPUT 'status -uno with untracked file in submodule' '
+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)' '
@@ -79,11 +83,11 @@ test_expect_success 'status with untracked file in submodule (porcelain)' '
        EOF
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status with added and untracked file in submodule' '
+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)' '
@@ -95,13 +99,13 @@ test_expect_success 'status with added and untracked file in submodule (porcelai
        EOF
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status with modified file in modified submodule' '
+test_expect_success 'status with modified file in modified submodule' '
        (cd sub && git reset --hard) &&
        rm sub/new-file &&
        (cd sub && echo "next change" >foo && git commit -m "next change" foo) &&
        echo "changed" >sub/foo &&
        git status >output &&
-       grep "modified:   sub (new commits, modified content)" output
+       test_i18ngrep "modified:   sub (new commits, modified content)" output
 '
 
 test_expect_success 'status with modified file in modified submodule (porcelain)' '
@@ -113,10 +117,10 @@ test_expect_success 'status with modified file in modified submodule (porcelain)
        EOF
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status with added file in modified submodule' '
+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)' '
@@ -127,11 +131,11 @@ test_expect_success 'status with added file in modified submodule (porcelain)' '
        EOF
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status with untracked file in modified submodule' '
+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)' '
@@ -141,11 +145,11 @@ test_expect_success 'status with untracked file in modified submodule (porcelain
        EOF
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status with added and untracked file in modified submodule' '
+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)' '
@@ -167,24 +171,104 @@ test_expect_success 'setup .git file for sub' '
         git commit -m "added .real to .gitignore" .gitignore
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status with added file in modified submodule with .git file' '
+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' '
        rm -rf sub/* sub/.git
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status clean (empty submodule dir)' '
+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 C_LOCALE_OUTPUT 'status -a clean (empty submodule dir)' '
+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
index a93e70fac446f0b062abe9109658d54b7c321819..1fdfbd38654a83771f677f2219403e3713abef51 100755 (executable)
@@ -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' '
@@ -55,10 +55,8 @@ test_expect_success 'setup' '
        git add dir2/added
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status (1)' '
-
-       grep "use \"git rm --cached <file>\.\.\.\" to unstage" output
-
+test_expect_success 'status (1)' '
+       test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output
 '
 
 cat >expect <<\EOF
@@ -85,11 +83,9 @@ cat >expect <<\EOF
 #      untracked
 EOF
 
-test_expect_success C_LOCALE_OUTPUT 'status (2)' '
-
+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 C_LOCALE_OUTPUT 'status (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
@@ -178,16 +171,16 @@ cat >expect <<EOF
 #
 # Untracked files not listed (use -u option to show untracked files)
 EOF
-test_expect_success C_LOCALE_OUTPUT 'status -uno' '
+test_expect_success 'status -uno' '
        git status -uno >output &&
-       test_cmp expect output
+       test_i18ncmp expect output
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status (status.showUntrackedFiles no)' '
+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
@@ -201,9 +194,9 @@ cat >expect <<EOF
 # Untracked files not listed
 EOF
 git config advice.statusHints false
-test_expect_success C_LOCALE_OUTPUT 'status -uno (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
 
@@ -246,16 +239,16 @@ cat >expect <<EOF
 #      output
 #      untracked
 EOF
-test_expect_success C_LOCALE_OUTPUT 'status -unormal' '
+test_expect_success 'status -unormal' '
        git status -unormal >output &&
-       test_cmp expect output
+       test_i18ncmp expect output
 '
 
-test_expect_success C_LOCALE_OUTPUT 'status (status.showUntrackedFiles normal)' '
+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
@@ -305,15 +298,16 @@ cat >expect <<EOF
 #      output
 #      untracked
 EOF
-test_expect_success C_LOCALE_OUTPUT 'status -uall' '
+test_expect_success 'status -uall' '
        git status -uall >output &&
-       test_cmp expect output
+       test_i18ncmp expect output
 '
-test_expect_success C_LOCALE_OUTPUT 'status (status.showUntrackedFiles all)' '
+
+test_expect_success 'status (status.showUntrackedFiles all)' '
        git config status.showuntrackedfiles all
        test_when_finished "git config --unset status.showuntrackedfiles" &&
        git status >output &&
-       test_cmp expect output
+       test_i18ncmp expect output
 '
 
 test_expect_success 'teardown dir3' '
@@ -367,11 +361,9 @@ cat >expect <<\EOF
 #      ../untracked
 EOF
 
-test_expect_success C_LOCALE_OUTPUT 'status with relative paths' '
-
+test_expect_success 'status with relative paths' '
        (cd dir1 && git status) >output &&
-       test_cmp expect output
-
+       test_i18ncmp expect output
 '
 
 cat >expect <<\EOF
@@ -440,22 +432,18 @@ cat >expect <<\EOF
 #      <BLUE>untracked<RESET>
 EOF
 
-test_expect_success C_LOCALE_OUTPUT 'status with color.ui' '
-
+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 C_LOCALE_OUTPUT 'status with color.status' '
-
+test_expect_success 'status with color.status' '
        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
@@ -570,12 +558,12 @@ cat >expect <<\EOF
 EOF
 
 
-test_expect_success C_LOCALE_OUTPUT 'status without relative paths' '
+test_expect_success 'status without relative paths' '
 
        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
 
 '
 
@@ -616,11 +604,8 @@ cat <<EOF >expect
 #      untracked
 EOF
 test_expect_success 'dry-run of partial commit excluding new file in index' '
-       git commit --dry-run dir1/modified >output
-'
-
-test_expect_success C_LOCALE_OUTPUT 'dry-run of partial commit excluding new file in index: output' '
-       test_cmp expect output
+       git commit --dry-run dir1/modified >output &&
+       test_i18ncmp expect output
 '
 
 cat >expect <<EOF
@@ -667,15 +652,15 @@ cat >expect <<EOF
 #      output
 #      untracked
 EOF
-test_expect_success C_LOCALE_OUTPUT 'status submodule summary is disabled by default' '
+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 C_LOCALE_OUTPUT 'status --untracked-files=all does not show submodule' '
+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
@@ -731,10 +716,10 @@ cat >expect <<EOF
 #      output
 #      untracked
 EOF
-test_expect_success C_LOCALE_OUTPUT 'status submodule summary' '
+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
@@ -773,15 +758,12 @@ cat >expect <<EOF
 no changes added to commit (use "git add" and/or "git commit -a")
 EOF
 test_expect_success 'status submodule summary (clean submodule): commit' '
-       git commit -m "commit submodule"
-'
-
-test_expect_success C_LOCALE_OUTPUT 'status submodule summary (clean submodule): output' '
+       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
@@ -798,6 +780,13 @@ test_expect_success 'status -s submodule summary (clean submodule)' '
        test_cmp expect output
 '
 
+test_expect_success 'status -z implies porcelain' '
+       git status --porcelain |
+       perl -pe "s/\012/\000/g" >expect &&
+       git status -z >output &&
+       test_cmp expect output
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
@@ -827,10 +816,10 @@ cat >expect <<EOF
 #      output
 #      untracked
 EOF
-test_expect_success C_LOCALE_OUTPUT 'commit --dry-run submodule summary (--amend)' '
+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' '
@@ -882,84 +871,84 @@ cat > expect << EOF
 #      untracked
 EOF
 
-test_expect_success C_LOCALE_OUTPUT '--ignore-submodules=untracked suppresses submodules with untracked content' '
-       echo modified > sm/untracked &&
-       git status --ignore-submodules=untracked > output &&
-       test_cmp expect output
+test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' '
+       echo modified  sm/untracked &&
+       git status --ignore-submodules=untracked >output &&
+       test_i18ncmp expect output
 '
 
-test_expect_success C_LOCALE_OUTPUT '.gitmodules ignore=untracked suppresses submodules with untracked content' '
+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
 '
 
-test_expect_success C_LOCALE_OUTPUT '.git/config ignore=untracked suppresses submodules with untracked content' '
+test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' '
        git config --add -f .gitmodules submodule.subname.ignore none &&
        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 C_LOCALE_OUTPUT '--ignore-submodules=dirty suppresses submodules with untracked content' '
-       git status --ignore-submodules=dirty > output &&
-       test_cmp expect output
+test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' '
+       git status --ignore-submodules=dirty >output &&
+       test_i18ncmp expect output
 '
 
-test_expect_success C_LOCALE_OUTPUT '.gitmodules ignore=dirty suppresses submodules with untracked content' '
+test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' '
        git config diff.ignoreSubmodules dirty &&
        git status >output &&
        ! 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
 '
 
-test_expect_success C_LOCALE_OUTPUT '.git/config ignore=dirty suppresses submodules with untracked content' '
+test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' '
        git config --add -f .gitmodules submodule.subname.ignore none &&
        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 C_LOCALE_OUTPUT '--ignore-submodules=dirty suppresses submodules with modified content' '
-       echo modified > sm/foo &&
-       git status --ignore-submodules=dirty > output &&
-       test_cmp expect output
+test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' '
+       echo modified >sm/foo &&
+       git status --ignore-submodules=dirty >output &&
+       test_i18ncmp expect output
 '
 
-test_expect_success C_LOCALE_OUTPUT '.gitmodules ignore=dirty suppresses submodules with modified content' '
+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
 '
 
-test_expect_success C_LOCALE_OUTPUT '.git/config ignore=dirty suppresses submodules with modified content' '
+test_expect_success '.git/config ignore=dirty suppresses submodules with modified content' '
        git config --add -f .gitmodules submodule.subname.ignore none &&
        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
 '
@@ -996,26 +985,26 @@ cat > expect << EOF
 #      untracked
 EOF
 
-test_expect_success C_LOCALE_OUTPUT "--ignore-submodules=untracked doesn't suppress submodules with modified content" '
+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 C_LOCALE_OUTPUT ".gitmodules ignore=untracked doesn't suppress submodules with modified content" '
+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
 '
 
-test_expect_success C_LOCALE_OUTPUT ".git/config ignore=untracked doesn't suppress submodules with modified content" '
+test_expect_success ".git/config ignore=untracked doesn't suppress submodules with modified content" '
        git config --add -f .gitmodules submodule.subname.ignore none &&
        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
 '
@@ -1058,49 +1047,49 @@ cat > expect << EOF
 #      untracked
 EOF
 
-test_expect_success C_LOCALE_OUTPUT "--ignore-submodules=untracked doesn't suppress submodule summary" '
+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 C_LOCALE_OUTPUT ".gitmodules ignore=untracked doesn't suppress submodule summary" '
+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
 '
 
-test_expect_success C_LOCALE_OUTPUT ".git/config ignore=untracked doesn't suppress submodule summary" '
+test_expect_success ".git/config ignore=untracked doesn't suppress submodule summary" '
        git config --add -f .gitmodules submodule.subname.ignore none &&
        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 C_LOCALE_OUTPUT "--ignore-submodules=dirty doesn't suppress submodule summary" '
+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 C_LOCALE_OUTPUT ".gitmodules ignore=dirty doesn't suppress submodule summary" '
+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
 '
 
-test_expect_success C_LOCALE_OUTPUT ".git/config ignore=dirty doesn't suppress submodule summary" '
+test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary" '
        git config --add -f .gitmodules submodule.subname.ignore none &&
        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
 '
@@ -1126,9 +1115,9 @@ cat > expect << EOF
 no changes added to commit (use "git add" and/or "git commit -a")
 EOF
 
-test_expect_success C_LOCALE_OUTPUT "--ignore-submodules=all suppresses submodule summary" '
+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' '
index 87d5d788cb1884fdfe218ec30582f5d3da8b5d7d..87aac835a1b864eab083b25940892b93af491326 100755 (executable)
@@ -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
 '
@@ -495,10 +578,10 @@ test_expect_success 'merge fast-forward in a dirty tree' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
-test_expect_success C_LOCALE_OUTPUT 'in-index merge' '
+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
 '
 
index ef84f04102ed067768c66ea9608b98d47851eb8b..72a8731d5e28d71577d0c53fc25a5ddc1d2ad6de 100755 (executable)
@@ -150,11 +150,8 @@ test_expect_success 'will not overwrite untracked file on unborn branch' '
        git rm -fr . &&
        git checkout --orphan new &&
        cp important c0.c &&
-       test_must_fail git merge c0 2>out
-'
-
-test_expect_success C_LOCALE_OUTPUT 'will not overwrite untracked file on unborn branch: output' '
-       test_cmp out expect
+       test_must_fail git merge c0 2>out &&
+       test_i18ncmp out expect
 '
 
 test_expect_success 'will not overwrite untracked file on unborn branch .git/MERGE_HEAD sanity etc.' '
index dc838c93bcb00a80ad56c4d1233009fe9220b90e..cbc08e32761c6e35a3eb25519873e74218fd441a 100755 (executable)
@@ -22,26 +22,50 @@ test_expect_success 'setup' '
     echo master file14 >file14 &&
     mkdir subdir &&
     echo master sub >subdir/file3 &&
-    git add file1 file1[1-4] subdir/file3 &&
+    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 file11 file13 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 file12 file14 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" &&
 
@@ -52,15 +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"
 '
 
@@ -73,9 +100,12 @@ test_expect_success 'mergetool crlf' '
     ( 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
@@ -83,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 &&
@@ -98,18 +129,22 @@ test_expect_success 'mergetool on file in parent dir' '
        ( 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
@@ -120,10 +155,13 @@ test_expect_success 'mergetool merges all from subdir' '
        cd subdir &&
        git config rerere.enabled false &&
        test_must_fail git merge master &&
+       ( 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" &&
+       ( cd .. && git submodule update -N ) &&
+       test "$(cat ../submod/bar)" = "master submodule" &&
        git commit -m "branch2 resolved by mergetool from subdir"
     )
 '
@@ -132,11 +170,257 @@ 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
index cdb3f444cd4a5970df6f375382f5cea82ccaee06..7b4798e8e4791c78e94da1354ef7279fa456983d 100755 (executable)
@@ -46,11 +46,8 @@ test_expect_success 'setup' '
 pre_merge_head="$(git rev-parse HEAD)"
 
 test_expect_success 'fails without MERGE_HEAD (unstarted merge)' '
-       test_must_fail git merge --abort 2>output
-'
-
-test_expect_success C_LOCALE_OUTPUT 'fails without MERGE_HEAD (unstarted merge): fatal output' '
-       grep -q MERGE_HEAD output
+       test_must_fail git merge --abort 2>output &&
+       test_i18ngrep MERGE_HEAD output
 '
 
 test_expect_success 'fails without MERGE_HEAD (unstarted merge): .git/MERGE_HEAD sanity' '
@@ -63,11 +60,8 @@ test_expect_success 'fails without MERGE_HEAD (completed merge)' '
        test ! -f .git/MERGE_HEAD &&
        # Merge successfully completed
        post_merge_head="$(git rev-parse HEAD)" &&
-       test_must_fail git merge --abort 2>output
-'
-
-test_expect_success C_LOCALE_OUTPUT 'fails without MERGE_HEAD (completed merge): output' '
-       grep -q MERGE_HEAD output
+       test_must_fail git merge --abort 2>output &&
+       test_i18ngrep MERGE_HEAD output
 '
 
 test_expect_success 'fails without MERGE_HEAD (completed merge): .git/MERGE_HEAD sanity' '
index aedf484feeb9da05054f18ab2f4913865048fe35..a8957782cfb8fae4b7c171d4c9db24ce6cd5505e 100755 (executable)
@@ -61,9 +61,9 @@ test_expect_success SIMPLEPAGER 'git grep -O' '
        test_cmp empty out
 '
 
-test_expect_success C_LOCALE_OUTPUT 'git grep -O --cached' '
+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' '
index d3a51e12698bed50d4eef1474e75dbfd0be0d034..e2896cffc17c519d208c26f215e4605d85edb918 100755 (executable)
@@ -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/t8008-blame-formats.sh b/t/t8008-blame-formats.sh
new file mode 100755 (executable)
index 0000000..d15f8b3
--- /dev/null
@@ -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
index 5d477e4bdadd2168d0331e2ab809c5c86c8fa532..cf4c05261b42ddd0711f78951adfc4a49ffdcc1e 100755 (executable)
@@ -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' "
index 158c8e33ef3381f3310ac39a99932d185290d685..6d3130e61856335dff7004903741a490ae94fc08 100755 (executable)
@@ -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"/"! !"
 '
index 6b1ba6c858562b7c085951fb1d69d4d2371e866c..2a53640c5b29c19a2327570148638dbf94bc2afa 100755 (executable)
@@ -1893,7 +1893,7 @@ test_expect_success \
     test_cmp marks.out marks.new'
 
 cat >input <<EOF
-feature import-marks=nonexistant.marks
+feature import-marks=nonexistent.marks
 feature export-marks=marks.new
 EOF
 
@@ -1904,7 +1904,7 @@ test_expect_success \
 
 
 cat >input <<EOF
-feature import-marks=nonexistant.marks
+feature import-marks=nonexistent.marks
 feature export-marks=combined.marks
 EOF
 
index afac5b56a87f3dcc6467b8ad260e1b59d041eb03..53297156a314a5b5e03385a3ad887eff959359c4 100755 (executable)
@@ -595,4 +595,69 @@ test_expect_success HIGHLIGHT \
         git commit -m "Add test.sh" &&
         gitweb_run "p=.git;a=blob;f=test.sh"'
 
+# ----------------------------------------------------------------------
+# 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"'
+
+test_expect_success \
+       'ctags: malformed tag weights' \
+       'mkdir -p .git/ctags &&
+        echo "not-a-number" > .git/ctags/nan &&
+        echo "not-a-number-2" > .git/ctags/nan2 &&
+        echo "0.1" >.git/ctags/floating-point &&
+        gitweb_run'
+
+# ----------------------------------------------------------------------
+# categories
+
+test_expect_success \
+       'categories: projects list, only default category' \
+       'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl &&
+        gitweb_run'
+
 test_done
index dd8389000187b11fe126f6ca76f578324075bd6f..731e64c3adbd4d887bfa2c96eb060aa890377cfc 100755 (executable)
@@ -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
index a523473954a43193e8111beb58027b6734cb664b..33b0127651da0c4c178e62c269179b641e15f9f6 100755 (executable)
@@ -12,6 +12,8 @@ test_description='git-p4 tests'
 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"
@@ -129,6 +131,129 @@ test_expect_success 'clone bare' '
        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" &&
index d3ed59803f6e7c91a60cbabd38419ab0c8f8a797..8c57a0059574ff78626ad0e26d3f75da9204b23d 100644 (file)
@@ -89,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...
@@ -575,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."
@@ -728,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.
@@ -801,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
@@ -880,8 +884,13 @@ then
        }
 
        make_valgrind_symlink () {
-               # handle only executables
-               test -x "$1" || return
+               # handle only executables, unless they are shell libraries that
+               # need to be in the exec-path.  We will just use "#!" as a
+               # guess for a shell-script, since we have no idea what the user
+               # may have configured as the shell path.
+               test -x "$1" ||
+               test "#!" = "$(head -c 2 <"$1")" ||
+               return;
 
                base=$(basename "$1")
                symlink_target=$GIT_BUILD_DIR/$base
@@ -1078,6 +1087,32 @@ 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
 rm -f y
index 0612bfa7cd1909c8c4292b384fd7a56b8baf3c2a..37918e15f5ce06d0079ce09ae1a7be7b14533c48 100644 (file)
@@ -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;
index a02f79aae3d91ea4109d21a9ed9ca2d962125375..c9c8056f9de69bd378cd271d70363b5560f13e07 100644 (file)
@@ -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). */
@@ -1190,14 +1190,20 @@ char *transport_anonymize_url(const char *url)
        return xstrdup(url);
 }
 
-int refs_from_alternate_cb(struct alternate_object_database *e, void *cb)
+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;
-       alternate_ref_fn *ref_fn = cb;
+       struct alternate_refs_data *cb = data;
 
        e->name[-1] = '\0';
        other = xstrdup(real_path(e->base));
@@ -1218,8 +1224,16 @@ int refs_from_alternate_cb(struct alternate_object_database *e, void *cb)
        for (extra = transport_get_remote_refs(transport);
             extra;
             extra = extra->next)
-               ref_fn(extra, NULL);
+               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);
+}
index efb19688692b5f1be4fc085cc36d737ace2df417..161d724bba5c132950b45866286c558148560d01 100644 (file)
@@ -167,6 +167,6 @@ 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 int refs_from_alternate_cb(struct alternate_object_database *e, void *cb);
+extern void for_each_alternate_ref(alternate_ref_fn, void *);
 
 #endif
index 76f83fcc27e50b12ddbc8f72badd8d29f5b4230d..b3cc2e4753447d4734ed08a70e3396771257dced 100644 (file)
@@ -64,23 +64,17 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
 static void show_tree(struct diff_options *opt, const char *prefix,
                      struct tree_desc *desc, struct strbuf *base)
 {
-       int all_interesting = 0;
-       while (desc->size) {
-               int show;
-
-               if (all_interesting)
-                       show = 1;
-               else {
-                       show = tree_entry_interesting(&desc->entry, base, 0,
-                                                     &opt->pathspec);
-                       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);
-               update_tree_entry(desc);
+               show_entry(opt, prefix, desc, base);
        }
 }
 
@@ -120,20 +114,16 @@ static void show_entry(struct diff_options *opt, const char *prefix,
 }
 
 static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
-                              struct diff_options *opt, int *all_interesting)
+                              struct diff_options *opt, int *match)
 {
        while (t->size) {
-               int show = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
-               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);
        }
 }
 
@@ -142,8 +132,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
 {
        struct strbuf base;
        int baselen = strlen(base_str);
-       int all_t1_interesting = 0;
-       int all_t2_interesting = 0;
+       int t1_match = 0, t2_match = 0;
 
        /* Enable recursion indefinitely */
        opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
@@ -153,14 +142,11 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
        strbuf_add(&base, base_str, baselen);
 
        for (;;) {
-               if (DIFF_OPT_TST(opt, QUICK) &&
-                   DIFF_OPT_TST(opt, HAS_CHANGES))
+               if (diff_can_quit_early(opt))
                        break;
                if (opt->pathspec.nr) {
-                       if (!all_t1_interesting)
-                               skip_uninteresting(t1, &base, opt, &all_t1_interesting);
-                       if (!all_t2_interesting)
-                               skip_uninteresting(t2, &base, opt, &all_t2_interesting);
+                       skip_uninteresting(t1, &base, opt, &t1_match);
+                       skip_uninteresting(t2, &base, opt, &t2_match);
                }
                if (!t1->size) {
                        if (!t2->size)
index 322becc3b4ad50b8e87fae8f6d5d38f7edc51f01..33f749e1e77484694e7b8f40a65755a7818c4abb 100644 (file)
@@ -598,7 +598,7 @@ int tree_entry_interesting(const struct name_entry *entry,
                                        &never_interesting))
                                return 1;
 
-                       if (ps->items[i].has_wildcard) {
+                       if (ps->items[i].use_wildcard) {
                                if (!fnmatch(match + baselen, entry->path, 0))
                                        return 1;
 
@@ -614,7 +614,7 @@ int tree_entry_interesting(const struct name_entry *entry,
                }
 
 match_wildcards:
-               if (!ps->items[i].has_wildcard)
+               if (!ps->items[i].use_wildcard)
                        continue;
 
                /*
diff --git a/tree.c b/tree.c
index 5ab90af256a664366f3f92b467f52634c0df3f79..698ecf7af13871cf9639e969f368ba5d7b2e940a 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -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;
diff --git a/tree.h b/tree.h
index 2ff01a4f839ecc2206fcc1c13fee9d5d202b1128..69bcb5e0ec27de6699e349b8dcec26f1cbc4e741 100644 (file)
--- a/tree.h
+++ b/tree.h
@@ -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 */
index 500ebcfd545772fb17e5fb14bba42bf4be468b75..07f83642443601d107e0a2425407b3250c022dcd 100644 (file)
@@ -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);
                        }
                }
@@ -814,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);
 }
 
 /*
@@ -871,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;
 
@@ -882,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++;
@@ -911,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) {
@@ -922,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++;
        }
@@ -943,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);
 }
 
 /*
index cd11a08365ab3e27b1321b3df87bcab6b9278f90..64f02cb03ab242ac08ea0f1afbb24e71cf6664aa 100644 (file)
@@ -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;
index bba053f0aac8eef982d6779983dc6fd979d69d69..ce5cbbea6be45a6dfa79bcc8c8244ae0b99c7ca3 100644 (file)
@@ -157,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";
@@ -183,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;
@@ -192,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;
index 1ff47977d549992ada8c1236187d4516b648ee7d..e55310cd02c8e7d1f0404b2fee9539e7340993d9 100644 (file)
@@ -60,10 +60,24 @@ PATTERNS("pascal",
         "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
         "|<>|<=|>=|:=|\\.\\."),
 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_]*"
index 572a99596657b85cbda372a9107a43c6c2fb44b2..bc792223b2638bce4196eb0dc6626beb32f48da4 100644 (file)
@@ -13,6 +13,7 @@
 #include "line_buffer.h"
 #include "string_pool.h"
 #include "strbuf.h"
+#include "svndump.h"
 
 /*
  * Compare start of string to literal of equal length;
index 28290002b9f6434d716a39612f3afc9958c292af..85f09df747637b94e0488ad65984c3f97c732034 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -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;
index 164581f87f49935f0d1b1885420960a4d11dea56..0e2c169227ad29b5bf546c6c1b97e1a1d8ed7409 100644 (file)
@@ -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"))