Code

Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Wed, 12 Mar 2008 04:40:47 +0000 (21:40 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 12 Mar 2008 04:40:47 +0000 (21:40 -0700)
* maint:
  git-svn: fix find-rev error message when missing arg
  t0021: tr portability fix for Solaris
  launch_editor(): allow spaces in the filename
  git rebase --abort: always restore the right commit

320 files changed:
.gitattributes [new file with mode: 0644]
.gitignore
.mailmap
Documentation/.gitattributes [new file with mode: 0644]
Documentation/CodingGuidelines
Documentation/RelNotes-1.5.5.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-am.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-checkout.txt
Documentation/git-commit.txt
Documentation/git-cvsimport.txt
Documentation/git-describe.txt
Documentation/git-fast-import.txt
Documentation/git-fetch-pack.txt
Documentation/git-format-patch.txt
Documentation/git-gc.txt
Documentation/git-grep.txt
Documentation/git-help.txt
Documentation/git-index-pack.txt
Documentation/git-instaweb.txt
Documentation/git-merge-index.txt
Documentation/git-mergetool.txt
Documentation/git-pack-objects.txt
Documentation/git-pull.txt
Documentation/git-rebase.txt
Documentation/git-reflog.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-send-email.txt
Documentation/git-stash.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-verify-pack.txt
Documentation/git-web--browse.txt [new file with mode: 0644]
Documentation/git-whatchanged.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitignore.txt
Documentation/hooks.txt
Documentation/pretty-options.txt
Documentation/rev-list-options.txt
Documentation/technical/api-remote.txt [new file with mode: 0644]
Documentation/technical/api-run-command.txt
Documentation/urls.txt
GIT-VERSION-GEN
Makefile
RelNotes
alias.c [new file with mode: 0644]
branch.c [new file with mode: 0644]
branch.h [new file with mode: 0644]
builtin-apply.c
builtin-blame.c
builtin-branch.c
builtin-checkout.c [new file with mode: 0644]
builtin-clean.c
builtin-commit.c
builtin-config.c
builtin-describe.c
builtin-diff.c
builtin-fast-export.c
builtin-fetch-pack.c
builtin-fetch.c
builtin-fmt-merge-msg.c
builtin-for-each-ref.c
builtin-fsck.c
builtin-gc.c
builtin-grep.c
builtin-http-fetch.c
builtin-init-db.c
builtin-log.c
builtin-ls-files.c
builtin-ls-remote.c
builtin-merge-file.c
builtin-merge-recursive.c [new file with mode: 0644]
builtin-mv.c
builtin-name-rev.c
builtin-pack-objects.c
builtin-push.c
builtin-read-tree.c
builtin-reflog.c
builtin-rerere.c
builtin-reset.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-revert.c
builtin-send-pack.c
builtin-shortlog.c
builtin-show-ref.c
builtin-tag.c
builtin-unpack-objects.c
builtin-update-index.c
builtin-verify-pack.c
builtin-verify-tag.c
builtin-write-tree.c
builtin.h
bundle.c
cache-tree.c
cache-tree.h
cache.h
color.c
color.h
commit.c
commit.h
compat/fopen.c [new file with mode: 0644]
compat/qsort.c [new file with mode: 0644]
compat/snprintf.c [new file with mode: 0644]
config.c
config.mak.in
configure.ac
connect.c
contrib/completion/git-completion.bash
contrib/emacs/git-blame.el
contrib/emacs/git.el
contrib/examples/git-checkout.sh [new file with mode: 0755]
contrib/fast-import/git-p4
contrib/hooks/post-receive-email
contrib/stats/packinfo.pl
convert.c
diff-lib.c
diff.c
diff.h
diffcore-rename.c
dir.c
dir.h
entry.c
environment.c
fast-import.c
fetch-pack.h
fsck.c [new file with mode: 0644]
fsck.h [new file with mode: 0644]
git-add--interactive.perl
git-am.sh
git-bisect.sh
git-checkout.sh [deleted file]
git-clone.sh
git-compat-util.h
git-cvsimport.perl
git-cvsserver.perl
git-gui/git-gui.sh
git-gui/lib/about.tcl
git-gui/lib/checkout_op.tcl
git-gui/lib/commit.tcl
git-gui/lib/index.tcl
git-gui/lib/merge.tcl
git-gui/lib/option.tcl
git-gui/lib/spellcheck.tcl [new file with mode: 0644]
git-gui/po/de.po
git-gui/po/git-gui.pot
git-gui/po/glossary/de.po
git-help--browse.sh [deleted file]
git-instaweb.sh
git-merge.sh
git-mergetool.sh
git-pull.sh
git-rebase.sh
git-remote.perl
git-send-email.perl
git-sh-setup.sh
git-stash.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh [new file with mode: 0755]
git.c
gitk-git/gitk
gitweb/README
gitweb/gitweb.perl
hash-object.c
help.c
http-push.c
imap-send.c
index-pack.c
interpolate.c
list-objects.c
ll-merge.c [new file with mode: 0644]
ll-merge.h [new file with mode: 0644]
log-tree.c
log-tree.h
merge-index.c
merge-recursive.c [deleted file]
merge-recursive.h [new file with mode: 0644]
object-refs.c [deleted file]
object.c
object.h
pack-check.c
pack-revindex.c [new file with mode: 0644]
pack-revindex.h [new file with mode: 0644]
pager.c
parse-options.c
parse-options.h
path.c
pretty.c
quote.c
quote.h
reachable.c
read-cache.c
receive-pack.c
refs.c
refs.h
remote.c
remote.h
revision.c
revision.h
run-command.c
run-command.h
setup.c
sha1_file.c
sha1_name.c
shallow.c
shortlog.h [new file with mode: 0644]
strbuf.c
strbuf.h
t/.gitattributes [new file with mode: 0644]
t/README
t/lib-httpd.sh [new file with mode: 0644]
t/lib-httpd/apache.conf [new file with mode: 0644]
t/lib-httpd/ssl.cnf [new file with mode: 0644]
t/t0000-basic.sh
t/t0020-crlf.sh
t/t0030-stripspace.sh
t/t0040-parse-options.sh
t/t0050-filesystem.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh
t/t1005-read-tree-reset.sh [new file with mode: 0755]
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1302-repo-version.sh
t/t1400-update-ref.sh
t/t1410-reflog.sh
t/t1500-rev-parse.sh
t/t2000-checkout-cache-clash.sh
t/t2002-checkout-cache-u.sh
t/t2009-checkout-statinfo.sh [new file with mode: 0755]
t/t2100-update-cache-badpath.sh
t/t3001-ls-files-others-exclude.sh
t/t3020-ls-files-error-unmatch.sh
t/t3101-ls-tree-dirname.sh
t/t3200-branch.sh
t/t3210-pack-refs.sh
t/t3400-rebase.sh
t/t3403-rebase-skip.sh
t/t3404-rebase-interactive.sh
t/t3600-rm.sh
t/t3701-add-interactive.sh [new file with mode: 0755]
t/t3903-stash.sh
t/t4013-diff-various.sh
t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ [new file with mode: 0644]
t/t4014-format-patch.sh
t/t4019-diff-wserror.sh
t/t4027-diff-submodule.sh [new file with mode: 0755]
t/t4103-apply-binary.sh
t/t4105-apply-fuzz.sh [new file with mode: 0755]
t/t4113-apply-ending.sh
t/t4125-apply-ws-fuzz.sh [new file with mode: 0755]
t/t4150-am-subdir.sh [new file with mode: 0755]
t/t5300-pack-object.sh
t/t5302-pack-index.sh
t/t5303-hash-object.sh [new file with mode: 0755]
t/t5305-include-tag.sh [new file with mode: 0755]
t/t5401-update-hooks.sh
t/t5402-post-merge-hook.sh
t/t5500-fetch-pack.sh
t/t5503-tagfollow.sh [new file with mode: 0755]
t/t5510-fetch.sh
t/t5516-fetch-push.sh
t/t5530-upload-pack-error.sh
t/t5540-http-push.sh [new file with mode: 0755]
t/t5600-clone-fail-cleanup.sh
t/t5701-clone-local.sh
t/t5710-info-alternate.sh
t/t6009-rev-list-parent.sh [new file with mode: 0755]
t/t6023-merge-file.sh
t/t6024-recursive-merge.sh
t/t6025-merge-symlinks.sh
t/t6029-merge-subtree.sh [new file with mode: 0755]
t/t6101-rev-parse-parents.sh
t/t6120-describe.sh
t/t6300-for-each-ref.sh
t/t7001-mv.sh
t/t7002-grep.sh
t/t7004-tag.sh
t/t7010-setup.sh [new file with mode: 0755]
t/t7101-reset.sh
t/t7104-reset.sh [new file with mode: 0755]
t/t7201-co.sh
t/t7300-clean.sh
t/t7501-commit.sh
t/t7502-commit.sh
t/t7503-pre-commit-hook.sh
t/t7504-commit-msg-hook.sh
t/t7505-prepare-commit-msg-hook.sh [new file with mode: 0755]
t/t7610-mergetool.sh [new file with mode: 0644]
t/t9001-send-email.sh
t/t9100-git-svn-basic.sh
t/t9106-git-svn-commit-diff-clobber.sh
t/t9106-git-svn-dcommit-clobber-series.sh
t/t9300-fast-import.sh
t/t9400-git-cvsserver-server.sh
t/test-lib.sh
tag.c
templates/hooks--commit-msg
templates/hooks--prepare-commit-msg [new file with mode: 0644]
test-parse-options.c
thread-utils.c [new file with mode: 0644]
thread-utils.h [new file with mode: 0644]
transport.c
transport.h
tree.c
unpack-trees.c
unpack-trees.h
upload-pack.c
walker.c
ws.c
wt-status.c
xdiff-interface.c
xdiff/xdiff.h
xdiff/xmerge.c

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..6b9c715
--- /dev/null
@@ -0,0 +1,2 @@
+* whitespace=!indent,trail,space
+*.[ch] whitespace
index 7f8421dcd0f6ea01c3a5c0757c588bcbfb9b493c..4ff2fec2785d21055bdcb5d9661d5090624b92da 100644 (file)
@@ -1,3 +1,4 @@
+GIT-BUILD-OPTIONS
 GIT-CFLAGS
 GIT-GUI-VARS
 GIT-VERSION-FILE
@@ -50,7 +51,6 @@ git-gc
 git-get-tar-commit-id
 git-grep
 git-hash-object
-git-help--browse
 git-http-fetch
 git-http-push
 git-imap-send
@@ -136,6 +136,7 @@ git-upload-pack
 git-var
 git-verify-pack
 git-verify-tag
+git-web--browse
 git-whatchanged
 git-write-tree
 git-core-*/?*
index a32d9e2a3f95a4d6cdfb050f2454e31192dea34e..f88ae77a1f298004320bb8cc9e6a2bd9b0cafd06 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -17,6 +17,7 @@ H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
 H. Peter Anvin <hpa@tazenda.sc.orionmulti.com>
 H. Peter Anvin <hpa@trantor.hos.anvin.org>
 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+Jay Soffian <jaysoffian+git@gmail.com>
 Joachim Berdal Haga <cjhaga@fys.uio.no>
 Jon Loeliger <jdl@freescale.com>
 Jon Seymour <jon@blackcubes.dyndns.org>
diff --git a/Documentation/.gitattributes b/Documentation/.gitattributes
new file mode 100644 (file)
index 0000000..ddb0301
--- /dev/null
@@ -0,0 +1 @@
+*.txt whitespace
index 3b042db624980dc962bbd67af1f3defa82ea64b1..994eb9159a2b0e8a10f4f9510165d420004203bf 100644 (file)
@@ -53,6 +53,18 @@ For shell scripts specifically (not exhaustive):
  - We do not write the noiseword "function" in front of shell
    functions.
 
+ - As to use of grep, stick to a subset of BRE (namely, no \{m,n\},
+   [::], [==], nor [..]) for portability.
+
+   - We do not use \{m,n\};
+
+   - We do not use -E;
+
+   - We do not use ? nor + (which are \{0,1\} and \{1,\}
+     respectively in BRE) but that goes without saying as these
+     are ERE elements not BRE (note that \? and \+ are not even part
+     of BRE -- making them accessible from BRE is a GNU extension).
+
 For C programs:
 
  - We use tabs to indent, and interpret tabs as taking up to
diff --git a/Documentation/RelNotes-1.5.5.txt b/Documentation/RelNotes-1.5.5.txt
new file mode 100644 (file)
index 0000000..874dad9
--- /dev/null
@@ -0,0 +1,173 @@
+GIT v1.5.5 Release Notes
+========================
+
+Updates since v1.5.4
+--------------------
+
+(subsystems)
+
+ * Comes with git-gui 0.9.3
+
+(performance)
+
+ * On platforms with suboptimal qsort(3) implementation, there
+   is an option to use more reasonable substitute we ship with
+   our software.
+
+ * New configuration variable "pack.packsizelimit" can be used
+   in place of command line option --max-pack-size.
+
+ * "git fetch" over the native git protocol used to make a
+   connection to find out the set of current remote refs and
+   another to actually download the pack data.  We now use only
+   one connection for these tasks.
+
+ * "git commit" does not run lstat(2) more than necessary
+   anymore.
+
+(usability, bells and whistles)
+
+ * You can be warned when core.autocrlf conversion is applied in
+   such a way that results in an irreversible conversion.
+
+ * A catch-all "color.ui" configuration variable can be used to
+   enable coloring of all color-capable commands, instead of
+   individual ones such as "color.status" and "color.branch".
+
+ * The commands refused to take absolute pathnames where they
+   require pathnames relative to the work tree or the current
+   subdirectory.  They now can take absolute pathnames in such a
+   case as long as the pathnames do not refer outside of the
+   work tree.  E.g. "git add $(pwd)/foo" now works.
+
+ * Error messages used to be sent to stderr, only to get hidden,
+   when $PAGER was in use.  They now are sent to stdout along
+   with the command output to be shown in the $PAGER.
+
+ * A pattern "foo/" in .gitignore file now matches a directory
+   "foo".  Pattern "foo" also matches as before.
+
+ * bash completion's prompt helper function can talk about
+   operation in-progress (e.g. merge, rebase, etc.).
+
+ * Configuration variables "url.<usethis>.insteadof = <otherurl>" can be
+   used to tell "git-fetch" and "git-push" to use different URL than what
+   is given from the command line.
+
+ * "git push <somewhere> HEAD" and "git push <somewhere> +HEAD" works as
+   expected; they push the current branch (and only the current branch).
+   In addition, HEAD can be written as the value of "remote.<there>.push"
+   configuration variable.
+
+ * "git add -i" behaves better even before you make an initial commit.
+
+ * "git am" refused to run from a subdirectory without a good reason.
+
+ * After "git apply --whitespace=fix" fixes whitespace errors in a patch,
+   a line before the fix can appear as a context or preimage line in a
+   later patch, causing the patch not to apply.  The command now knows to
+   see through whitespace fixes done to context lines to successfully
+   apply such a patch series.
+
+ * "git branch" (and "git checkout -b") to branch from a local branch can
+   optionally set "branch.<name>.merge" to mark the new branch to build on
+   the other local branch, when "branch.autosetupmerge" is set to
+   "always".  By default, this does not happen when branching from a local
+   branch.
+
+ * "git checkout" to switch to a branch that has "branch.<name>.merge" set
+   (i.e. marked to build on another branch) reports how much the branch
+   and the other branch diverged.
+
+ * When "git checkout" has to update a lot of paths, it used to be silent
+   for 4 seconds before it showed any progress report.  It is now a bit
+   more impatient and starts showing progress report early.
+
+ * "git commit" learned a new hook "prepare-commit-msg" that can
+   inspect what is going to be committed and prepare the commit
+   log message template to be edited.
+
+ * "git cvsimport" can now take more than one -M options.
+
+ * "git describe" learned to limit the tags to be used for
+   naming with --match option.
+
+ * "git describe --contains" now barfs when the named commit
+   cannot be described.
+
+ * "git describe --exact-match" describes only commits that are tagged.
+
+ * "git describe --long" describes a tagged commit as $tag-0-$sha1,
+   instead of just showing the exact tagname.
+
+ * "git describe" warns when using a tag whose name and path contradict
+   with each other.
+
+ * "git diff" learned "--relative" option to limit and output paths
+   relative to the current directory when working in a subdirectory.
+
+ * "git diff" learned "--dirstat" option to show birds-eye-summary of
+   changes more concisely than "--diffstat".
+
+ * "git format-patch" learned --cover-letter option to generate a cover
+   letter template.
+
+ * "git gc" learned --quiet option.
+
+ * "git grep" now knows "--name-only" is a synonym for the "-l" option.
+
+ * "git help <alias>" now reports "'git <alias>' is alias to <what>",
+   instead of saying "No manual entry for git-<alias>".
+
+ * "git log --grep=<what>" learned "--fixed-strings" option to look for
+   <what> without treating it as a regular expression.
+
+ * "git gui" learned an auto-spell checking.
+
+ * "git send-email" learned to prompt for passwords
+   interactively.
+
+ * "git send-email" learned an easier way to suppress CC
+   recipients.
+
+ * When the configuration variable "pack.threads" is set to 0, "git
+   repack" auto detects the number of CPUs and uses that many threads.
+
+ * Various "git cvsimport", "git cvsexportcommit", "git svn" and
+   "git p4" improvements.
+
+(internal)
+
+ * Duplicated code between git-help and git-instaweb that
+   launches user's preferred browser has been refactored.
+
+ * It is now easier to write test scripts that records known
+   breakages.
+
+ * "git checkout" is rewritten in C.
+
+ * Two conflict hunks that are separated by a very short span of common
+   lines are now coalesced into one larger hunk, to make the result easier
+   to read.
+
+ * Run-command API's use of file descriptors is documented clearer and
+   is more consistent now.
+
+
+Fixes since v1.5.4
+------------------
+
+All of the fixes in v1.5.4 maintenance series are included in
+this release, unless otherwise noted.
+
+ * "git-http-push" did not allow deletion of remote ref with the usual
+   "push <remote> :<branch>" syntax.
+
+ * "git-rebase --abort" did not go back to the right location if
+   "git-reset" was run during the "git-rebase" session.
+
+---
+exec >/var/tmp/1
+O=v1.5.4.3-428-g6b48990
+echo O=`git describe refs/heads/master`
+git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
index de08d094e3e3683913bac49f643bb6b49fbec04d..0e155c936c25255706bc6d47651c2b336c628417 100644 (file)
@@ -34,9 +34,9 @@ Checklist (and a short version for the impatient):
        - if your name is not writable in ASCII, make sure that
          you send off a message in the correct encoding.
        - send the patch to the list (git@vger.kernel.org) and the
-         maintainer (gitster@pobox.com). If you use
-         git-send-email(1), please test it first by sending
-         email to yourself.
+         maintainer (gitster@pobox.com) if (and only if) the patch
+         is ready for inclusion. If you use git-send-email(1),
+         please test it first by sending email to yourself.
 
 Long version:
 
@@ -112,7 +112,12 @@ lose tabs that way if you are not careful.
 
 It is a common convention to prefix your subject line with
 [PATCH].  This lets people easily distinguish patches from other
-e-mail discussions.
+e-mail discussions.  Use of additional markers after PATCH and
+the closing bracket to mark the nature of the patch is also
+encouraged.  E.g. [PATCH/RFC] is often used when the patch is
+not ready to be applied but it is for discussion, [PATCH v2],
+[PATCH v3] etc. are often seen when you are sending an update to
+what you have previously sent.
 
 "git format-patch" command follows the best current practice to
 format the body of an e-mail message.  At the beginning of the
@@ -157,7 +162,8 @@ Note that your maintainer does not necessarily read everything
 on the git mailing list.  If your patch is for discussion first,
 send it "To:" the mailing list, and optionally "cc:" him.  If it
 is trivially correct or after the list reached a consensus, send
-it "To:" the maintainer and optionally "cc:" the list.
+it "To:" the maintainer and optionally "cc:" the list for
+inclusion.
 
 Also note that your maintainer does not actively involve himself in
 maintaining what are in contrib/ hierarchy.  When you send fixes and
@@ -210,10 +216,53 @@ then you just add a line saying
 This line can be automatically added by git if you run the git-commit
 command with the -s option.
 
-Some people also put extra tags at the end.  They'll just be ignored for
-now, but you can do this to mark internal company procedures or just
-point out some special detail about the sign-off.
+Notice that you can place your own Signed-off-by: line when
+forwarding somebody else's patch with the above rules for
+D-C-O.  Indeed you are encouraged to do so.  Do not forget to
+place an in-body "From: " line at the beginning to properly attribute
+the change to its true author (see (2) above).
 
+Some people also put extra tags at the end.
+
+"Acked-by:" says that the patch was reviewed by the person who
+is more familiar with the issues and the area the patch attempts
+to modify.  "Tested-by:" says the patch was tested by the person
+and found to have the desired effect.
+
+------------------------------------------------
+An ideal patch flow
+
+Here is an ideal patch flow for this project the current maintainer
+suggests to the contributors:
+
+ (0) You come up with an itch.  You code it up.
+
+ (1) Send it to the list and cc people who may need to know about
+     the change.
+
+     The people who may need to know are the ones whose code you
+     are butchering.  These people happen to be the ones who are
+     most likely to be knowledgeable enough to help you, but
+     they have no obligation to help you (i.e. you ask for help,
+     don't demand).  "git log -p -- $area_you_are_modifying" would
+     help you find out who they are.
+
+ (2) You get comments and suggestions for improvements.  You may
+     even get them in a "on top of your change" patch form.
+
+ (3) Polish, refine, and re-send to the list and the people who
+     spend their time to improve your patch.  Go back to step (2).
+
+ (4) The list forms consensus that the last round of your patch is
+     good.  Send it to the list and cc the maintainer.
+
+ (5) A topic branch is created with the patch and is merged to 'next',
+     and cooked further and eventually graduates to 'master'.
+
+In any time between the (2)-(3) cycle, the maintainer may pick it up
+from the list and queue it to 'pu', in order to make it easier for
+people play with it without having to pick up and apply the patch to
+their trees themselves.
 
 ------------------------------------------------
 MUA specific hints
index 531ec46e957f85af2dfad69461ba81990500289e..c5e094a9c4a31568e4efb2c70af7e7808ae63528 100644 (file)
@@ -139,6 +139,51 @@ core.autocrlf::
        "text" (i.e. be subjected to the autocrlf mechanism) is
        decided purely based on the contents.
 
+core.safecrlf::
+       If true, makes git check if converting `CRLF` as controlled by
+       `core.autocrlf` is reversible.  Git will verify if a command
+       modifies a file in the work tree either directly or indirectly.
+       For example, committing a file followed by checking out the
+       same file should yield the original file in the work tree.  If
+       this is not the case for the current setting of
+       `core.autocrlf`, git will reject the file.  The variable can
+       be set to "warn", in which case git will only warn about an
+       irreversible conversion but continue the operation.
++
+CRLF conversion bears a slight chance of corrupting data.
+autocrlf=true will convert CRLF to LF during commit and LF to
+CRLF during checkout.  A file that contains a mixture of LF and
+CRLF before the commit cannot be recreated by git.  For text
+files this is the right thing to do: it corrects line endings
+such that we have only LF line endings in the repository.
+But for binary files that are accidentally classified as text the
+conversion can corrupt data.
++
+If you recognize such corruption early you can easily fix it by
+setting the conversion type explicitly in .gitattributes.  Right
+after committing you still have the original file in your work
+tree and this file is not yet corrupted.  You can explicitly tell
+git that this file is binary and git will handle the file
+appropriately.
++
+Unfortunately, the desired effect of cleaning up text files with
+mixed line endings and the undesired effect of corrupting binary
+files cannot be distinguished.  In both cases CRLFs are removed
+in an irreversible way.  For text files this is the right thing
+to do because CRLFs are line endings, while for binary files
+converting CRLFs corrupts data.
++
+Note, this safety check does not mean that a checkout will generate a
+file identical to the original file for a different setting of
+`core.autocrlf`, but only for the current one.  For example, a text
+file with `LF` would be accepted with `core.autocrlf=input` and could
+later be checked out with `core.autocrlf=true`, in which case the
+resulting file would contain `CRLF`, although the original file
+contained `LF`.  However, in both work trees the line endings would be
+consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
+file with mixed line endings would be reported by the `core.safecrlf`
+mechanism.
+
 core.symlinks::
        If false, symbolic links are checked out as small plain files that
        contain the link text. linkgit:git-update-index[1] and
@@ -308,6 +353,10 @@ core.whitespace::
   error (enabled by default).
 * `indent-with-non-tab` treats a line that is indented with 8 or more
   space characters as an error (not enabled by default).
+* `cr-at-eol` treats a carriage-return at the end of line as
+  part of the line terminator, i.e. with it, `trailing-space`
+  does not trigger if the character before such a carriage-return
+  is not a whitespace (not enabled by default).
 
 alias.*::
        Command aliases for the linkgit:git[1] command wrapper - e.g.
@@ -330,10 +379,14 @@ apply.whitespace::
 
 branch.autosetupmerge::
        Tells `git-branch` and `git-checkout` to setup new branches
-       so that linkgit:git-pull[1] will appropriately merge from that
-       remote branch.  Note that even if this option is not set,
+       so that linkgit:git-pull[1] will appropriately merge from the
+       starting point branch. Note that even if this option is not set,
        this behavior can be chosen per-branch using the `--track`
-       and `--no-track` options.  This option defaults to true.
+       and `--no-track` options. The valid settings are: `false` -- no
+       automatic setup is done; `true` -- automatic setup is done when the
+       starting point is a remote branch; `always` -- automatic setup is
+       done when the starting point is either a local branch or remote
+       branch. This option defaults to true.
 
 branch.<name>.remote::
        When in branch <name>, it tells `git fetch` which remote to fetch.
@@ -444,6 +497,13 @@ color.status.<slot>::
 commit.template::
        Specify a file to use as the template for new commit messages.
 
+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.
+
 diff.autorefreshindex::
        When using `git diff` to compare with work tree
        files, do not consider stat-only change as changed.
@@ -496,6 +556,11 @@ format.suffix::
        `.patch`. Use this variable to change that suffix (make sure to
        include the dot if you want it).
 
+format.pretty::
+       The default pretty format for log/show/whatchanged command,
+       See linkgit:git-log[1], linkgit:git-show[1],
+       linkgit:git-whatchanged[1].
+
 gc.aggressiveWindow::
        The window size parameter used in the delta compression
        algorithm used by 'git gc --aggressive'.  This defaults
@@ -689,8 +754,10 @@ merge.summary::
 
 merge.tool::
        Controls which merge resolution program is used by
-       linkgit:git-mergetool[1].  Valid values are: "kdiff3", "tkdiff",
-       "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and "opendiff".
+       linkgit:git-mergetool[1].  Valid built-in values are: "kdiff3",
+       "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and
+       "opendiff".  Any other value is treated is custom merge tool
+       and there must be a corresponing mergetool.<tool>.cmd option.
 
 merge.verbosity::
        Controls the amount of output shown by the recursive merge
@@ -717,6 +784,31 @@ mergetool.<tool>.path::
        Override the path for the given tool.  This is useful in case
        your tool is not in the PATH.
 
+mergetool.<tool>.cmd::
+       Specify the command to invoke the specified merge tool.  The
+       specified command is evaluated in shell with the following
+       variables available: 'BASE' is the name of a temporary file
+       containing the common base of the files to be merged, if available;
+       'LOCAL' is the name of a temporary file containing the contents of
+       the file on the current branch; 'REMOTE' is the name of a temporary
+       file containing the contents of the file from the branch being
+       merged; 'MERGED' contains the name of the file to which the merge
+       tool should write the results of a successful merge.
+
+mergetool.<tool>.trustExitCode::
+       For a custom merge command, specify whether the exit code of
+       the merge command can be used to determine whether the merge was
+       successful.  If this is not set to true then the merge target file
+       timestamp is checked and the merge assumed to have been successful
+       if the file has been updated, otherwise the user is prompted to
+       indicate the success of the merge.
+
+mergetool.keepBackup::
+       After performing a merge, the original file with conflict markers
+       can be saved as a file with a `.orig` extension.  If this variable
+       is set to `false` then this file is not preserved.  Defaults to
+       `true` (i.e. keep the backup files).
+
 pack.window::
        The size of the window used by linkgit:git-pack-objects[1] when no
        window size is given on the command line. Defaults to 10.
@@ -756,6 +848,8 @@ pack.threads::
        warning. This is meant to reduce packing time on multiprocessor
        machines. The required amount of memory for the delta search window
        is however multiplied by the number of threads.
+       Specifying 0 will cause git to auto-detect the number of CPU's
+       and set the number of threads accordingly.
 
 pack.indexVersion::
        Specify the default pack index version.  Valid values are 1 for
@@ -766,6 +860,12 @@ pack.indexVersion::
        whenever the corresponding pack is larger than 2 GB.  Otherwise
        the default is 1.
 
+pack.packSizeLimit:
+       The default maximum size of a pack.  This setting only affects
+       packing to a file, i.e. the git:// protocol is unaffected.  It
+       can be overridden by the `\--max-pack-size` option of
+       linkgit:git-repack[1].
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@ -835,6 +935,17 @@ tar.umask::
        archiving user's umask will be used instead.  See umask(2) and
        linkgit:git-archive[1].
 
+url.<base>.insteadOf::
+       Any URL that starts with this value will be rewritten to
+       start, instead, with <base>. In cases where some site serves a
+       large number of repositories, and serves them with multiple
+       access methods, and some users need to use different access
+       methods, this feature allows people to specify any of the
+       equivalent URLs and have git automatically rewrite the URL to
+       the best alternative for the particular user, even for a
+       never-before-seen repository on the site.  When more than one
+       insteadOf strings match a given URL, the longest match is used.
+
 user.email::
        Your email address to be recorded in any newly created commits.
        Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
index 8d35cbd60d9d6fb2a0aeef8a6b956e099743cb28..8dc5b001c42376be1b9f814d516ea43f5c711071 100644 (file)
@@ -170,6 +170,14 @@ endif::git-format-patch[]
        Swap two inputs; that is, show differences from index or
        on-disk file to tree contents.
 
+--relative[=<path>]::
+       When run from a subdirectory of the project, it can be
+       told to exclude changes outside the directory and show
+       pathnames relative to it with this option.  When you are
+       not in a subdirectory (e.g. in a bare repository), you
+       can name which subdirectory to make the output relative
+       to by giving a <path> as an argument.
+
 --text::
        Treat all files as text.
 
index e640fc75cd3c463370f62b623a4060b014d9371a..2387a8d6c2472fa87136cd18c0a1145e4dc23bd2 100644 (file)
@@ -9,7 +9,7 @@ git-am - Apply a series of patches from a mailbox
 SYNOPSIS
 --------
 [verse]
-'git-am' [--signoff] [--dotest=<dir>] [--keep] [--utf8 | --no-utf8]
+'git-am' [--signoff] [--keep] [--utf8 | --no-utf8]
          [--3way] [--interactive] [--binary]
          [--whitespace=<option>] [-C<n>] [-p<n>]
          <mbox>|<Maildir>...
@@ -32,10 +32,6 @@ OPTIONS
        Add `Signed-off-by:` line to the commit message, using
        the committer identity of yourself.
 
--d=<dir>, --dotest=<dir>::
-       Instead of `.dotest` directory, use <dir> as a working
-       area to store extracted patches.
-
 -k, --keep::
        Pass `-k` flag to `git-mailinfo` (see linkgit:git-mailinfo[1]).
 
index 7e8874acaacbd6990ccaf66d5bddbff6e4c1e5ba..6f07a17a2c310a67f23034c520ab10670407c006 100644 (file)
@@ -35,11 +35,10 @@ working tree to it; use "git checkout <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote branch, git sets up the
-branch so that linkgit:git-pull[1] will appropriately merge from that
-remote branch.  If this behavior is not desired, it is possible to
-disable it using the global `branch.autosetupmerge` configuration
-flag.  That setting can be overridden by using the `--track`
-and `--no-track` options.
+branch so that linkgit:git-pull[1] will appropriately merge from
+the remote branch. This behavior may be changed via the global
+`branch.autosetupmerge` configuration flag. That setting can be
+overridden by using the `--track` and `--no-track` options.
 
 With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
 If <oldbranch> had a corresponding reflog, it is renamed to match
@@ -105,20 +104,19 @@ OPTIONS
        Display the full sha1s in output listing rather than abbreviating them.
 
 --track::
-       Set up configuration so that git-pull will automatically
-       retrieve data from the remote branch.  Use this if you always
-       pull from the same remote branch into the new branch, or if you
-       don't want to use "git pull <repository> <refspec>" explicitly.
-       This behavior is the default.  Set the
-       branch.autosetupmerge configuration variable to false if you
-       want git-checkout and git-branch to always behave as if
-       '--no-track' were given.
+       When creating a new branch, set up configuration so that git-pull
+       will automatically retrieve data from the start point, which must be
+       a branch. Use this if you always pull from the same upstream branch
+       into the new branch, and if you don't want to use "git pull
+       <repository> <refspec>" explicitly. This behavior is the default
+       when the start point is a remote branch. Set the
+       branch.autosetupmerge configuration variable to `false` if you want
+       git-checkout and git-branch to always behave as if '--no-track' were
+       given. Set it to `always` if you want this behavior when the
+       start-point is either a local or remote branch.
 
 --no-track::
-       When a branch is created off a remote branch,
-       set up configuration so that git-pull will not retrieve data
-       from the remote branch, ignoring the branch.autosetupmerge
-       configuration variable.
+       Ignore the branch.autosetupmerge configuration variable.
 
 <branchname>::
        The name of the branch to create or delete.
index 72f080a9728e076de21c19e77fc760a038e9885e..505ac056e6586e0b4b19e5a8bbbb4bef86b6faf0 100644 (file)
@@ -99,36 +99,62 @@ Assume two repositories exist as R1 on machine A, and R2 on machine B.
 For whatever reason, direct connection between A and B is not allowed,
 but we can move data from A to B via some mechanism (CD, email, etc).
 We want to update R2 with developments made on branch master in R1.
+
+To create the bundle you have to specify the basis. You have some options:
+
+- Without basis.
++
+This is useful when sending the whole history.
+
+------------
+$ git bundle create mybundle master
+------------
+
+- Using temporally tags.
++
 We set a tag in R1 (lastR2bundle) after the previous such transport,
 and move it afterwards to help build the bundle.
 
-in R1 on A:
-
 ------------
 $ git-bundle create mybundle master ^lastR2bundle
 $ git tag -f lastR2bundle master
 ------------
 
-(move mybundle from A to B by some mechanism)
+- Using a tag present in both repositories
+
+------------
+$ git bundle create mybundle master ^v1.0.0
+------------
+
+- A basis based on time.
+
+------------
+$ git bundle create mybundle master --since=10.days.ago
+------------
 
-in R2 on B:
+- With a limit on the number of commits
 
 ------------
-$ git-bundle verify mybundle
-$ git-fetch mybundle  refspec
+$ git bundle create mybundle master -n 10
 ------------
 
-where refspec is refInBundle:localRef
+Then you move mybundle from A to B, and in R2 on B:
 
+------------
+$ git-bundle verify mybundle
+$ git-fetch mybundle master:localRef
+------------
 
-Also, with something like this in your config:
+With something like this in the config in R2:
 
+------------------------
 [remote "bundle"]
     url = /home/me/tmp/file.bdl
     fetch = refs/heads/*:refs/remotes/origin/*
+------------------------
 
 You can first sneakernet the bundle file to ~/tmp/file.bdl and
-then these commands:
+then these commands on machine B:
 
 ------------
 $ git ls-remote bundle
index b4cfa044bbb969add6f434070a8666fc0c325d15..4014e7256d48262c97a38a543415d5a53f5407b9 100644 (file)
@@ -48,21 +48,19 @@ OPTIONS
        may restrict the characters allowed in a branch name.
 
 --track::
-       When -b is given and a branch is created off a remote branch,
-       set up configuration so that git-pull will automatically
-       retrieve data from the remote branch.  Use this if you always
-       pull from the same remote branch into the new branch, or if you
-       don't want to use "git pull <repository> <refspec>" explicitly.
-       This behavior is the default.  Set the
-       branch.autosetupmerge configuration variable to false if you
-       want git-checkout and git-branch to always behave as if
-       '--no-track' were given.
+       When creating a new branch, set up configuration so that git-pull
+       will automatically retrieve data from the start point, which must be
+       a branch. Use this if you always pull from the same upstream branch
+       into the new branch, and if you don't want to use "git pull
+       <repository> <refspec>" explicitly. This behavior is the default
+       when the start point is a remote branch. Set the
+       branch.autosetupmerge configuration variable to `false` if you want
+       git-checkout and git-branch to always behave as if '--no-track' were
+       given. Set it to `always` if you want this behavior when the
+       start-point is either a local or remote branch.
 
 --no-track::
-       When -b is given and a branch is created off a remote branch,
-       set up configuration so that git-pull will not retrieve data
-       from the remote branch, ignoring the branch.autosetupmerge
-       configuration variable.
+       Ignore the branch.autosetupmerge configuration variable.
 
 -l::
        Create the new branch's reflog.  This activates recording of
index c3725b2ed9958cdc02b312895f35d016e3579d10..b4ae61ff46f4b7ff9e71e9e91a999fdc8fc17560 100644 (file)
@@ -280,8 +280,8 @@ order).
 
 HOOKS
 -----
-This command can run `commit-msg`, `pre-commit`, and
-`post-commit` hooks.  See link:hooks.html[hooks] for more
+This command can run `commit-msg`, `prepare-commit-msg`, `pre-commit`,
+and `post-commit` hooks.  See link:hooks.html[hooks] for more
 information.
 
 
index 6f91b9ea2a17df8fc8e3c00fdf0d5c74a6c17950..58eefd42e504cf34178719080fd4f75a0b9ea621 100644 (file)
@@ -102,13 +102,17 @@ If you need to pass multiple options, separate them with a comma.
 
 -m::
        Attempt to detect merges based on the commit message. This option
-       will enable default regexes that try to capture the name source
+       will enable default regexes that try to capture the source
        branch name from the commit message.
 
 -M <regex>::
        Attempt to detect merges based on the commit message with a custom
        regex. It can be used with '-m' to enable the default regexes
        as well. You must escape forward slashes.
++
+The regex must capture the source branch name in $1.
++
+This option can be used several times to provide several detection regexes.
 
 -S <regex>::
        Skip paths matching the regex.
index 0742152b811e23ea304feb368c8d949785e2fdfb..d9aa2f29809eb07385468edd2587daf55339389a 100644 (file)
@@ -45,12 +45,30 @@ OPTIONS
        candidates to describe the input committish consider
        up to <n> candidates.  Increasing <n> above 10 will take
        slightly longer but may produce a more accurate result.
+       An <n> of 0 will cause only exact matches to be output.
+
+--exact-match::
+       Only output exact matches (a tag directly references the
+       supplied commit).  This is a synonym for --candidates=0.
 
 --debug::
        Verbosely display information about the searching strategy
        being employed to standard error.  The tag name will still
        be printed to standard out.
 
+--long::
+       Always output the long format (the tag, the number of commits
+       and the abbreviated commit name) even when it matches a tag.
+       This is useful when you want to see parts of the commit object name
+       in "describe" output, even when the commit in question happens to be
+       a tagged version.  Instead of just emitting the tag name, it will
+       describe such a commit as v1.2-0-deadbeef (0th commit since tag v1.2
+       that points at object deadbeef....).
+
+--match <pattern>::
+       Only consider tags matching the given pattern (can be used to avoid
+       leaking private tags made from the repository).
+
 EXAMPLES
 --------
 
index bd625ababfc950318714b3271b02da938fbf609b..96f6767075b4173b81acf3eb413f5e1a35acb940 100644 (file)
@@ -805,6 +805,93 @@ Placing a `progress` command immediately after a `checkpoint` will
 inform the reader when the `checkpoint` has been completed and it
 can safely access the refs that fast-import updated.
 
+Crash Reports
+-------------
+If fast-import is supplied invalid input it will terminate with a
+non-zero exit status and create a crash report in the top level of
+the Git repository it was importing into.  Crash reports contain
+a snapshot of the internal fast-import state as well as the most
+recent commands that lead up to the crash.
+
+All recent commands (including stream comments, file changes and
+progress commands) are shown in the command history within the crash
+report, but raw file data and commit messages are excluded from the
+crash report.  This exclusion saves space within the report file
+and reduces the amount of buffering that fast-import must perform
+during execution.
+
+After writing a crash report fast-import will close the current
+packfile and export the marks table.  This allows the frontend
+developer to inspect the repository state and resume the import from
+the point where it crashed.  The modified branches and tags are not
+updated during a crash, as the import did not complete successfully.
+Branch and tag information can be found in the crash report and
+must be applied manually if the update is needed.
+
+An example crash:
+
+====
+       $ cat >in <<END_OF_INPUT
+       # my very first test commit
+       commit refs/heads/master
+       committer Shawn O. Pearce <spearce> 19283 -0400
+       # who is that guy anyway?
+       data <<EOF
+       this is my commit
+       EOF
+       M 644 inline .gitignore
+       data <<EOF
+       .gitignore
+       EOF
+       M 777 inline bob
+       END_OF_INPUT
+
+       $ git-fast-import <in
+       fatal: Corrupt mode: M 777 inline bob
+       fast-import: dumping crash report to .git/fast_import_crash_8434
+
+       $ cat .git/fast_import_crash_8434
+       fast-import crash report:
+           fast-import process: 8434
+           parent process     : 1391
+           at Sat Sep 1 00:58:12 2007
+
+       fatal: Corrupt mode: M 777 inline bob
+
+       Most Recent Commands Before Crash
+       ---------------------------------
+         # my very first test commit
+         commit refs/heads/master
+         committer Shawn O. Pearce <spearce> 19283 -0400
+         # who is that guy anyway?
+         data <<EOF
+         M 644 inline .gitignore
+         data <<EOF
+       * M 777 inline bob
+
+       Active Branch LRU
+       -----------------
+           active_branches = 1 cur, 5 max
+
+         pos  clock name
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          1)      0 refs/heads/master
+
+       Inactive Branches
+       -----------------
+       refs/heads/master:
+         status      : active loaded dirty
+         tip commit  : 0000000000000000000000000000000000000000
+         old tree    : 0000000000000000000000000000000000000000
+         cur tree    : 0000000000000000000000000000000000000000
+         commit clock: 0
+         last pack   :
+
+
+       -------------------
+       END OF CRASH REPORT
+====
+
 Tips and Tricks
 ---------------
 The following tips and tricks have been collected from various
index 2b8ffe5324c427d3b80f5b21d4a9d2ef8bfea4fd..57598eb0566c8eabc3537fb67b7841c65bcf5c2c 100644 (file)
@@ -8,7 +8,7 @@ git-fetch-pack - Receive missing objects from another repository
 
 SYNOPSIS
 --------
-'git-fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]
+'git-fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]
 
 DESCRIPTION
 -----------
@@ -45,6 +45,12 @@ OPTIONS
        Spend extra cycles to minimize the number of objects to be sent.
        Use it on slower connection.
 
+\--include-tag::
+       If the remote side supports it, annotated tags objects will
+       be downloaded on the same connection as the other objects if
+       the object the tag references is downloaded.  The caller must
+       otherwise determine the tags this option made available.
+
 \--upload-pack=<git-upload-pack>::
        Use this to specify the path to 'git-upload-pack' on the
        remote side, if is not found on your $PATH.
index 651efe6ca16a02841c49f4b6a57ae2cf5ae8183d..b5207b76040d02e34452175779340658b943a84e 100644 (file)
@@ -10,13 +10,15 @@ SYNOPSIS
 --------
 [verse]
 'git-format-patch' [-k] [-o <dir> | --stdout] [--thread]
-                   [--attach[=<boundary>] | --inline[=<boundary>]]
-                   [-s | --signoff] [<common diff options>]
-                   [-n | --numbered | -N | --no-numbered]
-                   [--start-number <n>] [--numbered-files]
-                   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
-                   [--ignore-if-in-upstream]
-                   [--subject-prefix=Subject-Prefix]
+                  [--attach[=<boundary>] | --inline[=<boundary>]]
+                  [-s | --signoff] [<common diff options>]
+                  [-n | --numbered | -N | --no-numbered]
+                  [--start-number <n>] [--numbered-files]
+                  [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+                  [--ignore-if-in-upstream]
+                  [--subject-prefix=Subject-Prefix]
+                  [--cc=<email>]
+                  [--cover-letter]
                   [ <since> | <revision range> ]
 
 DESCRIPTION
@@ -135,6 +137,15 @@ include::diff-options.txt[]
        allows for useful naming of a patch series, and can be
        combined with the --numbered option.
 
+--cc=<email>::
+       Add a "Cc:" header to the email headers. This is in addition
+       to any configured headers, and may be used multiple times.
+
+--cover-letter::
+       Generate a cover letter template.  You still have to fill in
+       a description, but the shortlog and the diffstat will be
+       generated for you.
+
 --suffix=.<sfx>::
        Instead of using `.patch` as the suffix for generated
        filenames, use specified suffix.  A common alternative is
index 4b2dfefa6ac6088322f7928836e7bb59f27a51e2..2e7be916aa5dede578b181e06457189551c16240 100644 (file)
@@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
 
 SYNOPSIS
 --------
-'git-gc' [--prune] [--aggressive] [--auto]
+'git-gc' [--prune] [--aggressive] [--auto] [--quiet]
 
 DESCRIPTION
 -----------
@@ -63,6 +63,9 @@ are consolidated into a single pack by using the `-A` option of
 `git-repack`. Setting `gc.autopacklimit` to 0 disables
 automatic consolidation of packs.
 
+--quiet::
+       Suppress all progress reports.
+
 Configuration
 -------------
 
index f3cb24f252e2091d4d2f346b61ce87088fa7b02e..a97f0557f4aa6cf1d795470d7bbe94ce35abc56a 100644 (file)
@@ -75,9 +75,11 @@ OPTIONS
 -n::
        Prefix the line number to matching lines.
 
--l | --files-with-matches | -L | --files-without-match::
+-l | --files-with-matches | --name-only | -L | --files-without-match::
        Instead of showing every matched line, show only the
        names of files that contain (or do not contain) matches.
+       For better compatibility with git-diff, --name-only is a
+       synonym for --files-with-matches.
 
 -c | --count::
        Instead of showing every matched line, show the number of
index fb77ca3a57d9377e8939c6d32601cf1386f0261b..0926dc12bab23285256b8cd7906b79b7e5f5616f 100644 (file)
@@ -47,27 +47,9 @@ OPTIONS
 +
 The web browser can be specified using the configuration variable
 'help.browser', or 'web.browser' if the former is not set. If none of
-these config variables is set, the 'git-help--browse' helper script
-(called by 'git-help') will pick a suitable default.
-+
-You can explicitly provide a full path to your preferred browser by
-setting the configuration variable 'browser.<tool>.path'. For example,
-you can configure the absolute path to firefox by setting
-'browser.firefox.path'. Otherwise, 'git-help--browse' assumes the tool
-is available in PATH.
-+
-Note that the script tries, as much as possible, to display the HTML
-page in a new tab on an already opened browser.
-+
-The following browsers are currently supported by 'git-help--browse':
-+
-* firefox (this is the default under X Window when not using KDE)
-* iceweasel
-* konqueror (this is the default under KDE)
-* w3m (this is the default outside X Window)
-* links
-* lynx
-* dillo
+these config variables is set, the 'git-web--browse' helper script
+(called by 'git-help') will pick a suitable default. See
+linkgit:git-web--browse[1] for more information about this.
 
 CONFIGURATION VARIABLES
 -----------------------
@@ -84,7 +66,7 @@ line option:
 The 'help.browser', 'web.browser' and 'browser.<tool>.path' will also
 be checked if the 'web' format is chosen (either by command line
 option or configuration variable). See '-w|--web' in the OPTIONS
-section above.
+section above and linkgit:git-web--browse[1].
 
 Note that these configuration variables should probably be set using
 the '--global' flag, for example like this:
index 72b5d001161f8a05e9c132b15409d7a504b0ced3..a7825b6144b67d486b5b81be0217b57db5197e65 100644 (file)
@@ -75,6 +75,9 @@ OPTIONS
        to force the version for the generated pack index, and to force
        64-bit index entries on objects located above the given offset.
 
+--strict::
+       Die, if the pack contains broken objects or links.
+
 
 Note
 ----
index 841e8fac7fd61f8e5250c1747935156c93999394..51f1532ef7ce34786ec0dcae32b9e60b710cc9c4 100644 (file)
@@ -38,10 +38,11 @@ OPTIONS
        The port number to bind the httpd to.  (Default: 1234)
 
 -b|--browser::
-
-       The web browser command-line to execute to view the gitweb page.
-       If blank, the URL of the gitweb instance will be printed to
-       stdout.  (Default: 'firefox')
+       The web browser that should be used to view the gitweb
+       page. This will be passed to the 'git-web--browse' helper
+       script along with the URL of the gitweb instance. See
+       linkgit:git-web--browse[1] for more information about this. If
+       the script fails, the URL will be printed to stdout.
 
 --start::
        Start the httpd instance and exit.  This does not generate
@@ -72,7 +73,8 @@ You may specify configuration in your .git/config
 -----------------------------------------------------------------------
 
 If the configuration variable 'instaweb.browser' is not set,
-'web.browser' will be used instead if it is defined.
+'web.browser' will be used instead if it is defined. See
+linkgit:git-web--browse[1] for more information about this.
 
 Author
 ------
index 5d816d0d8b02222517cd4bf320dc3d345fb8d94b..19ee017aede4bf8fa0a0046b2bf347e3d75c4a12 100644 (file)
@@ -8,7 +8,7 @@ git-merge-index - Run a merge for files needing merging
 
 SYNOPSIS
 --------
-'git-merge-index' [-o] [-q] <merge-program> (-a | \-- | <file>\*)
+'git-merge-index' [-o] [-q] <merge-program> (-a | [--] <file>\*)
 
 DESCRIPTION
 -----------
index 50f106ec5b49b6d46f4be6fa012148b24d6e8801..8ed44947ef877d2f960b1506a39ab0c166cce514 100644 (file)
@@ -12,12 +12,12 @@ SYNOPSIS
 DESCRIPTION
 -----------
 
-Use 'git mergetool' to run one of several merge utilities to resolve
+Use `git mergetool` to run one of several merge utilities to resolve
 merge conflicts.  It is typically run after linkgit:git-merge[1].
 
 If one or more <file> parameters are given, the merge tool program will
 be run to resolve differences on each file.  If no <file> names are
-specified, 'git mergetool' will run the merge tool program on every file
+specified, `git mergetool` will run the merge tool program on every file
 with merge conflicts.
 
 OPTIONS
@@ -27,16 +27,38 @@ OPTIONS
        Valid merge tools are:
        kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff
 +
-If a merge resolution program is not specified, 'git mergetool'
-will use the configuration variable merge.tool.  If the
-configuration variable merge.tool is not set, 'git mergetool'
+If a merge resolution program is not specified, `git mergetool`
+will use the configuration variable `merge.tool`.  If the
+configuration variable `merge.tool` is not set, `git mergetool`
 will pick a suitable default.
 +
 You can explicitly provide a full path to the tool by setting the
-configuration variable mergetool.<tool>.path. For example, you
+configuration variable `mergetool.<tool>.path`. For example, you
 can configure the absolute path to kdiff3 by setting
-mergetool.kdiff3.path. Otherwise, 'git mergetool' assumes the tool
-is available in PATH.
+`mergetool.kdiff3.path`. Otherwise, `git mergetool` assumes the
+tool is available in PATH.
++
+Instead of running one of the known merge tool programs
+`git mergetool` can be customized to run an alternative program
+by specifying the command line to invoke in a configration
+variable `mergetool.<tool>.cmd`.
++
+When `git mergetool` is invoked with this tool (either through the
+`-t` or `--tool` option or the `merge.tool` configuration
+variable) the configured command line will be invoked with `$BASE`
+set to the name of a temporary file containing the common base for
+the merge, if available; `$LOCAL` set to the name of a temporary
+file containing the contents of the file on the current branch;
+`$REMOTE` set to the name of a temporary file containing the
+contents of the file to be merged, and `$MERGED` set to the name
+of the file to which the merge tool should write the result of the
+merge resolution.
++
+If the custom merge tool correctly indicates the success of a
+merge resolution with its exit code then the configuration
+variable `mergetool.<tool>.trustExitCode` can be set to `true`.
+Otherwise, `git mergetool` will prompt the user to indicate the
+success of the resolution after the custom tool has exited.
 
 Author
 ------
index 74cc7c1cb831c700c14406f6e306e7a3002054d2..eed0a94c6e4d6f520923a2ee86f1fcfbee228185 100644 (file)
@@ -73,6 +73,11 @@ base-name::
        as if all refs under `$GIT_DIR/refs` are specified to be
        included.
 
+--include-tag::
+       Include unasked-for annotated tags if the object they
+       reference was included in the resulting packfile.  This
+       can be useful to send new tags to native git clients.
+
 --window=[N], --depth=[N]::
        These two options affect how the objects contained in
        the pack are stored using delta compression.  The
@@ -99,7 +104,8 @@ base-name::
 --max-pack-size=<n>::
        Maximum size of each output packfile, expressed in MiB.
        If specified,  multiple packfiles may be created.
-       The default is unlimited.
+       The default is unlimited, unless the config variable
+       `pack.packSizeLimit` is set.
 
 --incremental::
        This flag causes an object already in a pack ignored
@@ -176,6 +182,8 @@ base-name::
        This is meant to reduce packing time on multiprocessor machines.
        The required amount of memory for the delta search window is
        however multiplied by the number of threads.
+       Specifying 0 will cause git to auto-detect the number of CPU's
+       and set the number of threads accordingly.
 
 --index-version=<version>[,<offset>]::
        This is intended to be used by the test suite only. It allows
index f7b90a326f8e244c2c9df286bb63afc655c67318..3405ca09e85e9c81b9d48c753a2f117e7edca4eb 100644 (file)
@@ -15,6 +15,7 @@ DESCRIPTION
 -----------
 Runs `git-fetch` with the given parameters, and calls `git-merge`
 to merge the retrieved head(s) into the current branch.
+With `--rebase`, calls `git-rebase` instead of `git-merge`.
 
 Note that you can use `.` (current directory) as the
 <repository> to pull from the local repository -- this is useful
@@ -28,19 +29,14 @@ OPTIONS
 include::merge-options.txt[]
 
 :git-pull: 1
-include::fetch-options.txt[]
-
-include::pull-fetch-param.txt[]
-
-include::urls-remotes.txt[]
-
-include::merge-strategies.txt[]
 
 \--rebase::
        Instead of a merge, perform a rebase after fetching.  If
        there is a remote ref for the upstream branch, and this branch
        was rebased since last fetched, the rebase uses that information
-       to avoid rebasing non-local changes.
+       to avoid rebasing non-local changes. To make this the default
+       for branch `<name>`, set configuration `branch.<name>.rebase`
+       to `true`.
 +
 *NOTE:* This is a potentially _dangerous_ mode of operation.
 It rewrites history, which does not bode well when you
@@ -50,6 +46,14 @@ unless you have read linkgit:git-rebase[1] carefully.
 \--no-rebase::
        Override earlier \--rebase.
 
+include::fetch-options.txt[]
+
+include::pull-fetch-param.txt[]
+
+include::urls-remotes.txt[]
+
+include::merge-strategies.txt[]
+
 DEFAULT BEHAVIOUR
 -----------------
 
index c11c6453ea5d6cc8d7a727e66dafc6e628ea5b2e..e0412e0866db8f80e8bbc8f33caaa659461d90aa 100644 (file)
@@ -9,6 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
+       [-s <strategy> | --strategy=<strategy>]
        [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
        [--onto <newbase>] <upstream> [<branch>]
 'git-rebase' --continue | --skip | --abort
@@ -261,8 +262,7 @@ hook if one exists.  You can use this hook to do sanity checks and
 reject the rebase if it isn't appropriate.  Please see the template
 pre-rebase hook script for an example.
 
-You must be in the top directory of your project to start (or continue)
-a rebase.  Upon completion, <branch> will be the current branch.
+Upon completion, <branch> will be the current branch.
 
 INTERACTIVE MODE
 ----------------
index f9bba36c2309d5b4a9587d0f4a406cf57fcd8348..047e3ce14dfac6f1848fa8f1772202c26efa1055 100644 (file)
@@ -19,6 +19,8 @@ depending on the subcommand:
 git reflog expire [--dry-run] [--stale-fix] [--verbose]
        [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...
 
+git reflog delete ref@\{specifier\}...
+
 git reflog [show] [log-options] [<ref>]
 
 Reflog is a mechanism to record when the tip of branches are
@@ -43,6 +45,9 @@ two moves ago", `master@\{one.week.ago\}` means "where master used to
 point to one week ago", and so on. See linkgit:git-rev-parse[1] for
 more details.
 
+To delete single entries from the reflog, use the subcommand "delete"
+and specify the _exact_ entry (e.g. ``git reflog delete master@\{2\}'').
+
 
 OPTIONS
 -------
@@ -75,6 +80,15 @@ them.
 --all::
        Instead of listing <refs> explicitly, prune all refs.
 
+--updateref::
+       Update the ref with the sha1 of the top reflog entry (i.e.
+       <ref>@\{0\}) after expiring or deleting.
+
+--rewrite::
+       While expiring or deleting, adjust each reflog entry to ensure
+       that the `old` sha1 field points to the `new` sha1 field of the
+       previous entry.
+
 --verbose::
        Print extra information on screen.
 
index 5b96eabfce9eecbfccaff16096c616105a040c3d..d80cdf55024e4c8d67178aabd24b055d53a70438 100644 (file)
@@ -20,6 +20,9 @@ SYNOPSIS
             [ \--full-history ]
             [ \--not ]
             [ \--all ]
+            [ \--branches ]
+            [ \--tags ]
+            [ \--remotes ]
             [ \--stdin ]
             [ \--quiet ]
             [ \--topo-order ]
@@ -31,6 +34,7 @@ SYNOPSIS
             [ \--(author|committer|grep)=<pattern> ]
             [ \--regexp-ignore-case | \-i ]
             [ \--extended-regexp | \-E ]
+            [ \--fixed-strings | \-F ]
             [ \--date={local|relative|default|iso|rfc|short} ]
             [ [\--objects | \--objects-edge] [ \--unpacked ] ]
             [ \--pretty | \--header ]
index f02f6bbb49182c0dffce205b24a4199d824dc776..6513c2efe17668d5fc25a21385ed117ec76b40b5 100644 (file)
@@ -325,7 +325,7 @@ The lines after the separator describe the options.
 Each line of options has this format:
 
 ------------
-<opt_spec><arg_spec>? SP+ help LF
+<opt_spec><flags>* SP+ help LF
 ------------
 
 `<opt_spec>`::
@@ -334,10 +334,17 @@ Each line of options has this format:
        is necessary. `h,help`, `dry-run` and `f` are all three correct
        `<opt_spec>`.
 
-`<arg_spec>`::
-       an `<arg_spec>` tells the option parser if the option has an argument
-       (`=`), an optional one (`?` though its use is discouraged) or none
-       (no `<arg_spec>` in that case).
+`<flags>`::
+       `<flags>` are of `*`, `=`, `?` or `!`.
+       * Use `=` if the option takes an argument.
+
+       * Use `?` to mean that the option is optional (though its use is discouraged).
+
+       * Use `*` to mean that this option should not be listed in the usage
+         generated for the `-h` argument. It's shown for `--help-all` as
+         documented in linkgit:gitcli[5].
+
+       * Use `!` to not make the corresponding negated long option available.
 
 The remainder of the line, after stripping the spaces, is used
 as the help associated to the option.
index 0554f2b3741bef4213bf7197bf1bb6335c1268c4..336d797e80f5d2cc21b633588b3a3461a991ca71 100644 (file)
@@ -96,11 +96,40 @@ The --cc option must be repeated for each user you want on the cc list.
        servers typically listen to smtp port 25 and ssmtp port
        465).
 
---smtp-user, --smtp-pass::
-       Username and password for SMTP-AUTH. Defaults are the values of
-       the configuration values 'sendemail.smtpuser' and
-       'sendemail.smtppass', but see also 'sendemail.identity'.
-       If not set, authentication is not attempted.
+--smtp-user::
+       Username for SMTP-AUTH. In place of this option, the following
+       configuration variables can be specified:
++
+--
+               * sendemail.smtpuser
+               * sendemail.<identity>.smtpuser (see sendemail.identity).
+--
++
+However, --smtp-user always overrides these variables.
++
+If a username is not specified (with --smtp-user or a
+configuration variable), then authentication is not attempted.
+
+--smtp-pass::
+       Password for SMTP-AUTH. The argument is optional: If no
+       argument is specified, then the empty string is used as
+       the password.
++
+In place of this option, the following configuration variables
+can be specified:
++
+--
+               * sendemail.smtppass
+               * sendemail.<identity>.smtppass (see sendemail.identity).
+--
++
+However, --smtp-pass always overrides these variables.
++
+Furthermore, passwords need not be specified in configuration files
+or on the command line. If a username has been specified (with
+--smtp-user or a configuration variable), but no password has been
+specified (with --smtp-pass or a configuration variable), then the
+user is prompted for a password while the input is masked for privacy.
 
 --smtp-ssl::
        If set, connects to the SMTP server using SSL.
@@ -117,6 +146,17 @@ The --cc option must be repeated for each user you want on the cc list.
         Default is the value of 'sendemail.suppressfrom' configuration value;
         if that is unspecified, default to --no-suppress-from.
 
+--suppress-cc::
+       Specify an additional category of recipients to suppress the
+       auto-cc of.  'self' will avoid including the sender, 'author' will
+       avoid including the patch author, 'cc' will avoid including anyone
+       mentioned in Cc lines in the patch, 'sob' will avoid including
+       anyone mentioned in Signed-off-by lines, and 'cccmd' will avoid
+       running the --cc-cmd.  'all' will suppress all auto cc values.
+       Default is the value of 'sendemail.suppresscc' configuration value;
+       if that is unspecified, default to 'self' if --suppress-from is
+       specified, as well as 'sob' if --no-signed-off-cc is specified.
+
 --thread, --no-thread::
        If this is set, the In-Reply-To header will be set on each email sent.
        If disabled with "--no-thread", no emails will have the In-Reply-To
index 48e6f5a3f7a85e886639381dfa70434ecdeccfc2..8dc35d493e81bc0c5515ec279dbe95c464c57469 100644 (file)
@@ -8,7 +8,7 @@ git-stash - Stash the changes in a dirty working directory away
 SYNOPSIS
 --------
 [verse]
-'git-stash' (list | show [<stash>] | apply [<stash>] | clear)
+'git-stash' (list | show [<stash>] | apply [<stash>] | clear | drop [<stash>] | pop [<stash>])
 'git-stash' [save [<message>]]
 
 DESCRIPTION
@@ -85,6 +85,17 @@ clear::
        Remove all the stashed states. Note that those states will then
        be subject to pruning, and may be difficult or impossible to recover.
 
+drop [<stash>]::
+
+       Remove a single stashed state from the stash list. When no `<stash>`
+       is given, it removes the latest one. i.e. `stash@\{0}`
+
+pop [<stash>]::
+
+       Remove a single stashed state from the stash list and apply on top
+       of the current working tree state. When no `<stash>` is given,
+       `stash@\{0}` is assumed. See also `apply`.
+
 
 DISCUSSION
 ----------
index e818e6e7895a94af0b05f04d2c8ecdae64583efd..b4d01605a521279e298d9764ea90f25a8ee367d2 100644 (file)
@@ -18,8 +18,9 @@ COMMANDS
 --------
 add::
        Add the given repository as a submodule at the given path
-       to the changeset to be committed next.  In particular, the
-       repository is cloned at the specified path, added to the
+       to the changeset to be committed next.  If path is a valid
+       repository within the project, it is added as is. Otherwise,
+       repository is cloned at the specified path. path is added to the
        changeset and registered in .gitmodules.   If no path is
        specified, the path is deduced from the repository specification.
        If the repository url begins with ./ or ../, it is stored as
index 115b8bee0d762cefd4cd91c297830584cbc34a68..bec9accc8915c712bdea5e8975598e4f26db633f 100644 (file)
@@ -165,6 +165,13 @@ environment). This command has the same behaviour.
 +
 Any other arguments are passed directly to `git log'
 
+'blame'::
+       Show what revision and author last modified each line of a file. This is
+       identical to `git blame', but SVN revision numbers are shown instead of git
+       commit hashes.
++
+All arguments are passed directly to `git blame'.
+
 --
 'find-rev'::
        When given an SVN revision number of the form 'rN', returns the
index db019a2b8d1bb283789726bf47ee0f7b9e4a523e..ba2a157299566b4dcc88f8ecb6f0eaed755f95bb 100644 (file)
@@ -32,11 +32,11 @@ OUTPUT FORMAT
 -------------
 When specifying the -v option the format used is:
 
-       SHA1 type size offset-in-packfile
+       SHA1 type size size-in-pack-file offset-in-packfile
 
 for objects that are not deltified in the pack, and
 
-       SHA1 type size offset-in-packfile depth base-SHA1
+       SHA1 type size size-in-packfile offset-in-packfile depth base-SHA1
 
 for objects that are deltified.
 
diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt
new file mode 100644 (file)
index 0000000..df57d01
--- /dev/null
@@ -0,0 +1,78 @@
+git-web--browse(1)
+==================
+
+NAME
+----
+git-web--browse - git helper script to launch a web browser
+
+SYNOPSIS
+--------
+'git-web--browse' [OPTIONS] URL/FILE ...
+
+DESCRIPTION
+-----------
+
+This script tries, as much as possible, to display the URLs and FILEs
+that are passed as arguments, as HTML pages in new tabs on an already
+opened web browser.
+
+The following browsers (or commands) are currently supported:
+
+* firefox (this is the default under X Window when not using KDE)
+* iceweasel
+* konqueror (this is the default under KDE)
+* w3m (this is the default outside graphical environments)
+* links
+* lynx
+* dillo
+* open (this is the default under Mac OS X GUI)
+
+OPTIONS
+-------
+-b BROWSER|--browser=BROWSER::
+       Use the specified BROWSER. It must be in the list of supported
+       browsers.
+
+-t BROWSER|--tool=BROWSER::
+       Same as above.
+
+-c CONF.VAR|--config=CONF.VAR::
+       CONF.VAR is looked up in the git config files. If it's set,
+       then its value specify the browser that should be used.
+
+CONFIGURATION VARIABLES
+-----------------------
+
+The web browser can be specified using a configuration variable passed
+with the -c (or --config) command line option, or the 'web.browser'
+configuration variable if the former is not used.
+
+You can explicitly provide a full path to your preferred browser by
+setting the configuration variable 'browser.<tool>.path'. For example,
+you can configure the absolute path to firefox by setting
+'browser.firefox.path'. Otherwise, 'git-web--browse' assumes the tool
+is available in PATH.
+
+Note that these configuration variables should probably be set using
+the '--global' flag, for example like this:
+
+------------------------------------------------
+$ git config --global web.browser firefox
+------------------------------------------------
+
+as they are probably more user specific than repository specific.
+See linkgit:git-config[1] for more information about this.
+
+Author
+------
+Written by Christian Couder <chriscool@tuxfamily.org> and the git-list
+<git@vger.kernel.org>, based on git-mergetool by Theodore Y. Ts'o.
+
+Documentation
+-------------
+Documentation by Christian Couder <chriscool@tuxfamily.org> and the
+git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the linkgit:git[7] suite
index 54947b676969585c4641bf7a4f538623e059dd15..a6e7bd4c8b99ad2d6a19acf4a412b81580ef3027 100644 (file)
@@ -38,11 +38,6 @@ OPTIONS
        Show git internal diff output, but for the whole tree,
        not just the top level.
 
---pretty=<format>::
-       Controls the output format for the commit logs.
-       <format> can be one of 'raw', 'medium', 'short', 'full',
-       and 'oneline'.
-
 -m::
        By default, differences for merge commits are not shown.
        With this flag, show differences to that commit from all
@@ -51,6 +46,10 @@ OPTIONS
 However, it is not very useful in general, although it
 *is* useful on a file-by-file basis.
 
+include::pretty-options.txt[]
+
+include::pretty-formats.txt[]
+
 Examples
 --------
 git-whatchanged -p v2.6.12.. include/scsi drivers/scsi::
index 17aee93ec5c89e1f1d0d499acaf08bf3cc300662..3ed24d449a1f277c40dbb49654a196e38d1fa3e4 100644 (file)
@@ -43,9 +43,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.5.4/git.html[documentation for release 1.5.4]
+* link:v1.5.4.4/git.html[documentation for release 1.5.4.4]
 
 * release notes for
+  link:RelNotes-1.5.4.4.txt[1.5.4.4],
+  link:RelNotes-1.5.4.3.txt[1.5.4.3],
+  link:RelNotes-1.5.4.2.txt[1.5.4.2],
+  link:RelNotes-1.5.4.1.txt[1.5.4.1],
   link:RelNotes-1.5.4.txt[1.5.4].
 
 * link:v1.5.3.8/git.html[documentation for release 1.5.3.8]
index 35a29fd60c4aa70fbbd7c05ed40e4b545225c74d..84ec9623a26e2c36f7346a430a46a4a4915e0382 100644 (file)
@@ -133,6 +133,26 @@ When `core.autocrlf` is set to "input", line endings are
 converted to LF upon checkin, but there is no conversion done
 upon checkout.
 
+If `core.safecrlf` is set to "true" or "warn", git verifies if
+the conversion is reversible for the current setting of
+`core.autocrlf`.  For "true", git rejects irreversible
+conversions; for "warn", git only prints a warning but accepts
+an irreversible conversion.  The safety triggers to prevent such
+a conversion done to the files in the work tree, but there are a
+few exceptions.  Even though...
+
+- "git add" itself does not touch the files in the work tree, the
+  next checkout would, so the safety triggers;
+
+- "git apply" to update a text file with a patch does touch the files
+  in the work tree, but the operation is about text files and CRLF
+  conversion is about fixing the line ending inconsistencies, so the
+  safety does not trigger;
+
+- "git diff" itself does not touch the files in the work tree, it is
+  often run to inspect the changes you intend to next "git add".  To
+  catch potential problems early, safety triggers.
+
 
 `ident`
 ^^^^^^^
index 08373f52bb7e5537049b12abff3e07c02769e7b6..e847b3ba63f3fae3d5497d6ede1ea0ee416a74de 100644 (file)
@@ -57,6 +57,13 @@ Patterns have the following format:
    included again.  If a negated pattern matches, this will
    override lower precedence patterns sources.
 
+ - If the pattern ends with a slash, it is removed for the
+   purpose of the following description, but it would only find
+   a match with a directory.  In other words, `foo/` will match a
+   directory `foo` and paths underneath it, but will not match a
+   regular file or a symbolic link `foo` (this is consistent
+   with the way how pathspec works in general in git).
+
  - If the pattern does not contain a slash '/', git treats it as
    a shell glob pattern and checks for a match against the
    pathname without leading directories.
index f110162b0155b3b17bc3133c5f42504290c1de4d..76b8d77460e961fda9a5505a4ce0cf20788de133 100644 (file)
@@ -61,6 +61,35 @@ The default 'pre-commit' hook, when enabled, catches introduction
 of lines with trailing whitespaces and aborts the commit when
 such a line is found.
 
+All the `git-commit` hooks are invoked with the environment
+variable `GIT_EDITOR=:` if the command will not bring up an editor
+to modify the commit message.
+
+prepare-commit-msg
+------------------
+
+This hook is invoked by `git-commit` right after preparing the
+default log message, and before the editor is started.
+
+It takes one to three parameters.  The first is the name of the file
+that the commit log message.  The second is the source of the commit
+message, and can be: `message` (if a `\-m` or `\-F` option was
+given); `template` (if a `\-t` option was given or the
+configuration option `commit.template` is set); `merge` (if the
+commit is a merge or a `.git/MERGE_MSG` file exists); `squash`
+(if a `.git/SQUASH_MSG` file exists); or `commit`, followed by
+a commit SHA1 (if a `\-c`, `\-C` or `\--amend` option was given).
+
+If the exit status is non-zero, `git-commit` will abort.
+
+The purpose of the hook is to edit the message file in place, and
+it is not suppressed by the `\--no-verify` option.  A non-zero exit
+means a failure of the hook and aborts the commit.  It should not
+be used as replacement for pre-commit hook.
+
+The sample `prepare-commit-msg` hook that comes with git comments
+out the `Conflicts:` part of a merge's commit message.
+
 commit-msg
 ----------
 
index 973d8dd733f954abf93aac7c68efef1154e9f315..6d66c74cc11e6622892061f8328d04dfe38f87bf 100644 (file)
@@ -4,6 +4,9 @@
        where '<format>' can be one of 'oneline', 'short', 'medium',
        'full', 'fuller', 'email', 'raw' and 'format:<string>'.
        When omitted, the format defaults to 'medium'.
++
+Note: you can specify the default pretty format in the repository
+configuration (see linkgit:git-config[1]).
 
 --abbrev-commit::
        Instead of showing the full 40-byte hexadecimal commit object
index a8138e27a1a10fb0c93608af149ef22323ba9dc5..2648a550850bf07128e4a0a14c82860d6bad17b8 100644 (file)
@@ -130,9 +130,11 @@ limiting may be applied.
 
        Show commits older than a specific date.
 
+ifdef::git-rev-list[]
 --max-age='timestamp', --min-age='timestamp'::
 
        Limit the commits output to specified time range.
+endif::git-rev-list[]
 
 --author='pattern', --committer='pattern'::
 
@@ -153,6 +155,11 @@ limiting may be applied.
        Consider the limiting patterns to be extended regular expressions
        instead of the default basic regular expressions.
 
+-F, --fixed-strings::
+
+       Consider the limiting patterns to be fixed strings (don't interpret
+       pattern as a regular expression).
+
 --remove-empty::
 
        Stop when a given path disappears from the tree.
diff --git a/Documentation/technical/api-remote.txt b/Documentation/technical/api-remote.txt
new file mode 100644 (file)
index 0000000..073b22b
--- /dev/null
@@ -0,0 +1,123 @@
+Remotes configuration API
+=========================
+
+The API in remote.h gives access to the configuration related to
+remotes. It handles all three configuration mechanisms historically
+and currently used by git, and presents the information in a uniform
+fashion. Note that the code also handles plain URLs without any
+configuration, giving them just the default information.
+
+struct remote
+-------------
+
+`name`::
+
+       The user's nickname for the remote
+
+`url`::
+
+       An array of all of the url_nr URLs configured for the remote
+
+`push`::
+
+        An array of refspecs configured for pushing, with
+        push_refspec being the literal strings, and push_refspec_nr
+        being the quantity.
+
+`fetch`::
+
+       An array of refspecs configured for fetching, with
+       fetch_refspec being the literal strings, and fetch_refspec_nr
+       being the quantity.
+
+`fetch_tags`::
+
+       The setting for whether to fetch tags (as a separate rule from
+       the configured refspecs); -1 means never to fetch tags, 0
+       means to auto-follow tags based on the default heuristic, 1
+       means to always auto-follow tags, and 2 means to fetch all
+       tags.
+
+`receivepack`, `uploadpack`::
+
+       The configured helper programs to run on the remote side, for
+       git-native protocols.
+
+`http_proxy`::
+
+       The proxy to use for curl (http, https, ftp, etc.) URLs.
+
+struct remotes can be found by name with remote_get(), and iterated
+through with for_each_remote(). remote_get(NULL) will return the
+default remote, given the current branch and configuration.
+
+struct refspec
+--------------
+
+A struct refspec holds the parsed interpretation of a refspec. If it
+will force updates (starts with a '+'), force is true. If it is a
+pattern (sides end with '*') pattern is true. src and dest are the two
+sides (if a pattern, only the part outside of the wildcards); if there
+is only one side, it is src, and dst is NULL; if sides exist but are
+empty (i.e., the refspec either starts or ends with ':'), the
+corresponding side is "".
+
+This parsing can be done to an array of strings to give an array of
+struct refpsecs with parse_ref_spec().
+
+remote_find_tracking(), given a remote and a struct refspec with
+either src or dst filled out, will fill out the other such that the
+result is in the "fetch" specification for the remote (note that this
+evaluates patterns and returns a single result).
+
+struct branch
+-------------
+
+Note that this may end up moving to branch.h
+
+struct branch holds the configuration for a branch. It can be looked
+up with branch_get(name) for "refs/heads/{name}", or with
+branch_get(NULL) for HEAD.
+
+It contains:
+
+`name`::
+
+       The short name of the branch.
+
+`refname`::
+
+       The full path for the branch ref.
+
+`remote_name`::
+
+       The name of the remote listed in the configuration.
+
+`remote`::
+
+       The struct remote for that remote.
+
+`merge_name`::
+
+       An array of the "merge" lines in the configuration.
+
+`merge`::
+
+       An array of the struct refspecs used for the merge lines. That
+       is, merge[i]->dst is a local tracking ref which should be
+       merged into this branch by default.
+
+`merge_nr`::
+
+       The number of merge configurations
+
+branch_has_merge_config() returns true if the given branch has merge
+configuration given.
+
+Other stuff
+-----------
+
+There is other stuff in remote.h that is related, in general, to the
+process of interacting with remotes.
+
+(Daniel Barkalow)
index 19d2f64f73f34b12c6fdb954174b33946562b304..c364a22c8f98f6fbb71d4059f7458ed0fa78ed43 100644 (file)
 run-command API
 ===============
 
-Talk about <run-command.h>, and things like:
+The run-command API offers a versatile tool to run sub-processes with
+redirected input and output as well as with a modified environment
+and an alternate current directory.
 
-* Environment the command runs with (e.g. GIT_DIR);
-* File descriptors and pipes;
-* Exit status;
+A similar API offers the capability to run a function asynchronously,
+which is primarily used to capture the output that the function
+produces in the caller in order to process it.
 
-(Hannes, Dscho, Shawn)
+
+Functions
+---------
+
+`start_command`::
+
+       Start a sub-process. Takes a pointer to a `struct child_process`
+       that specifies the details and returns pipe FDs (if requested).
+       See below for details.
+
+`finish_command`::
+
+       Wait for the completion of a sub-process that was started with
+       start_command().
+
+`run_command`::
+
+       A convenience function that encapsulates a sequence of
+       start_command() followed by finish_command(). Takes a pointer
+       to a `struct child_process` that specifies the details.
+
+`run_command_v_opt`, `run_command_v_opt_dir`, `run_command_v_opt_cd_env`::
+
+       Convenience functions that encapsulate a sequence of
+       start_command() followed by finish_command(). The argument argv
+       specifies the program and its arguments. The argument opt is zero
+       or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`, or
+       `RUN_COMMAND_STDOUT_TO_STDERR` that correspond to the members
+       .no_stdin, .git_cmd, .stdout_to_stderr of `struct child_process`.
+       The argument dir corresponds the member .dir. The argument env
+       corresponds to the member .env.
+
+`start_async`::
+
+       Run a function asynchronously. Takes a pointer to a `struct
+       async` that specifies the details and returns a pipe FD
+       from which the caller reads. See below for details.
+
+`finish_async`::
+
+       Wait for the completion of an asynchronous function that was
+       started with start_async().
+
+
+Data structures
+---------------
+
+* `struct child_process`
+
+This describes the arguments, redirections, and environment of a
+command to run in a sub-process.
+
+The caller:
+
+1. allocates and clears (memset(&chld, '0', sizeof(chld));) a
+   struct child_process variable;
+2. initializes the members;
+3. calls start_command();
+4. processes the data;
+5. closes file descriptors (if necessary; see below);
+6. calls finish_command().
+
+The .argv member is set up as an array of string pointers (NULL
+terminated), of which .argv[0] is the program name to run (usually
+without a path). If the command to run is a git command, set argv[0] to
+the command name without the 'git-' prefix and set .git_cmd = 1.
+
+The members .in, .out, .err are used to redirect stdin, stdout,
+stderr as follows:
+
+. Specify 0 to request no special redirection. No new file descriptor
+  is allocated. The child process simply inherits the channel from the
+  parent.
+
+. Specify -1 to have a pipe allocated; start_command() replaces -1
+  by the pipe FD in the following way:
+
+       .in: Returns the writable pipe end into which the caller writes;
+               the readable end of the pipe becomes the child's stdin.
+
+       .out, .err: Returns the readable pipe end from which the caller
+               reads; the writable end of the pipe end becomes child's
+               stdout/stderr.
+
+  The caller of start_command() must close the so returned FDs
+  after it has completed reading from/writing to it!
+
+. Specify a file descriptor > 0 to be used by the child:
+
+       .in: The FD must be readable; it becomes child's stdin.
+       .out: The FD must be writable; it becomes child's stdout.
+       .err > 0 is not supported.
+
+  The specified FD is closed by start_command(), even if it fails to
+  run the sub-process!
+
+. Special forms of redirection are available by setting these members
+  to 1:
+
+       .no_stdin, .no_stdout, .no_stderr: The respective channel is
+               redirected to /dev/null.
+
+       .stdout_to_stderr: stdout of the child is redirected to its
+               stderr. This happens after stderr is itself redirected.
+               So stdout will follow stderr to wherever it is
+               redirected.
+
+To modify the environment of the sub-process, specify an array of
+string pointers (NULL terminated) in .env:
+
+. If the string is of the form "VAR=value", i.e. it contains '='
+  the variable is added to the child process's environment.
+
+. If the string does not contain '=', it names an environment
+  variable that will be removed from the child process's environment.
+
+To specify a new initial working directory for the sub-process,
+specify it in the .dir member.
+
+
+* `struct async`
+
+This describes a function to run asynchronously, whose purpose is
+to produce output that the caller reads.
+
+The caller:
+
+1. allocates and clears (memset(&asy, '0', sizeof(asy));) a
+   struct async variable;
+2. initializes .proc and .data;
+3. calls start_async();
+4. processes the data by reading from the fd in .out;
+5. closes .out;
+6. calls finish_async().
+
+The function pointer in .proc has the following signature:
+
+       int proc(int fd, void *data);
+
+. fd specifies a writable file descriptor to which the function must
+  write the data that it produces. The function *must* close this
+  descriptor before it returns.
+
+. data is the value that the caller has specified in the .data member
+  of struct async.
+
+. The return value of the function is 0 on success and non-zero
+  on failure. If the function indicates failure, finish_async() will
+  report failure as well.
+
+
+There are serious restrictions on what the asynchronous function can do
+because this facility is implemented by a pipe to a forked process on
+UNIX, but by a thread in the same address space on Windows:
+
+. It cannot change the program's state (global variables, environment,
+  etc.) in a way that the caller notices; in other words, .out is the
+  only communication channel to the caller.
+
+. It must not change the program's state that the caller of the
+  facility also uses.
index 81ac17f32a0587e3d2d41eb8ee89dd85e13f1802..fa34c6747194aaecf9e8124462129b8bbc9ae7d4 100644 (file)
@@ -44,3 +44,26 @@ endif::git-clone[]
 ifdef::git-clone[]
 They are equivalent, except the former implies --local option.
 endif::git-clone[]
+
+
+If there are a large number of similarly-named remote repositories and
+you want to use a different format for them (such that the URLs you
+use will be rewritten into URLs that work), you can create a
+configuration section of the form:
+
+------------
+       [url "<actual url base>"]
+               insteadOf = <other url base>
+------------
+
+For example, with this:
+
+------------
+       [url "git://git.host.xz/"]
+               insteadOf = host.xz:/path/to/
+               insteadOf = work:
+------------
+
+a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be
+rewritten in any context that takes a URL to be "git://git.host.xz/repo.git".
+
index 33dc1ab222d6199b57f87594a6521f224f2d4210..6ddf04d2161e5fd959c2fd69a7331520e8075d98 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.5.4.4.GIT
+DEF_VER=v1.5.4.GIT
 
 LF='
 '
index 7a3c6d1456c32cb79f04cda9dd245bdc9de77a67..514738046f94abc9ab16bfebc3b0d3e0118b5a5f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,13 @@ all::
 
 # Define V=1 to have a more verbose compile.
 #
+# Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf()
+# or vsnprintf() return -1 instead of number of characters which would
+# have been written to the final string if enough space had been available.
+#
+# Define FREAD_READS_DIRECTORIES if your are on a system which succeeds
+# when attempting to read from an fopen'ed directory.
+#
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 # This also implies MOZILLA_SHA1.
 #
@@ -137,6 +144,10 @@ all::
 # Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit
 # parallel delta searching when packing objects.
 #
+# Define INTERNAL_QSORT to use Git's implementation of qsort(), which
+# is a simplified version of the merge sort used in glibc. This is
+# recommended if Git triggers O(n^2) behavior in your platform's qsort().
+#
 
 GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -219,7 +230,7 @@ BASIC_CFLAGS =
 BASIC_LDFLAGS =
 
 SCRIPT_SH = \
-       git-bisect.sh git-checkout.sh \
+       git-bisect.sh \
        git-clone.sh \
        git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
        git-pull.sh git-rebase.sh git-rebase--interactive.sh \
@@ -231,7 +242,7 @@ SCRIPT_SH = \
        git-lost-found.sh git-quiltimport.sh git-submodule.sh \
        git-filter-branch.sh \
        git-stash.sh \
-       git-help--browse.sh
+       git-web--browse.sh
 
 SCRIPT_PERL = \
        git-add--interactive.perl \
@@ -258,23 +269,23 @@ PROGRAMS = \
        git-upload-pack$X \
        git-pack-redundant$X git-var$X \
        git-merge-tree$X git-imap-send$X \
-       git-merge-recursive$X \
        $(EXTRA_PROGRAMS)
 
 # Empty...
 EXTRA_PROGRAMS =
 
+# List built-in command $C whose implementation cmd_$C() is not in
+# builtin-$C.o but is linked in as part of some other command.
 BUILT_INS = \
        git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
        git-get-tar-commit-id$X git-init$X git-repo-config$X \
        git-fsck-objects$X git-cherry-pick$X git-peek-remote$X git-status$X \
+       git-merge-subtree$X \
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
 
-ALL_PROGRAMS += git-merge-subtree$X
-
 # what 'all' will build but not install in gitexecdir
 OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
 
@@ -297,7 +308,8 @@ LIB_H = \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
        tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
        utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
-       mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h
+       mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h ll-merge.h fsck.h \
+       pack-revindex.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -312,7 +324,7 @@ LIB_OBJS = \
        patch-ids.o \
        object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \
        sideband.o reachable.o reflog-walk.o \
-       quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
+       quote.o read-cache.o refs.o run-command.o dir.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        revision.o pager.o tree-walk.o xdiff-interface.o \
@@ -320,7 +332,8 @@ LIB_OBJS = \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
        convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
-       transport.o bundle.o walker.o parse-options.o ws.o archive.o
+       transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o \
+       ll-merge.o alias.o fsck.o pack-revindex.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
@@ -332,6 +345,7 @@ BUILTIN_OBJS = \
        builtin-bundle.o \
        builtin-cat-file.o \
        builtin-check-attr.o \
+       builtin-checkout.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
        builtin-clean.o \
@@ -362,6 +376,7 @@ BUILTIN_OBJS = \
        builtin-merge-base.o \
        builtin-merge-file.o \
        builtin-merge-ours.o \
+       builtin-merge-recursive.o \
        builtin-mv.o \
        builtin-name-rev.o \
        builtin-pack-objects.o \
@@ -467,6 +482,7 @@ ifeq ($(uname_S),FreeBSD)
        NO_MEMMEM = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
+       DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
 endif
 ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
@@ -618,6 +634,14 @@ endif
 ifdef NO_C99_FORMAT
        BASIC_CFLAGS += -DNO_C99_FORMAT
 endif
+ifdef SNPRINTF_RETURNS_BOGUS
+       COMPAT_CFLAGS += -DSNPRINTF_RETURNS_BOGUS
+       COMPAT_OBJS += compat/snprintf.o
+endif
+ifdef FREAD_READS_DIRECTORIES
+       COMPAT_CFLAGS += -DFREAD_READS_DIRECTORIES
+       COMPAT_OBJS += compat/fopen.o
+endif
 ifdef NO_SYMLINK_HEAD
        BASIC_CFLAGS += -DNO_SYMLINK_HEAD
 endif
@@ -722,10 +746,18 @@ ifdef NO_MEMMEM
        COMPAT_CFLAGS += -DNO_MEMMEM
        COMPAT_OBJS += compat/memmem.o
 endif
+ifdef INTERNAL_QSORT
+       COMPAT_CFLAGS += -DINTERNAL_QSORT
+       COMPAT_OBJS += compat/qsort.o
+endif
 
 ifdef THREADED_DELTA_SEARCH
        BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
        EXTLIBS += -lpthread
+       LIB_OBJS += thread-utils.o
+endif
+ifdef DIR_HAS_BSD_GROUP_SEMANTICS
+       COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
 endif
 
 ifeq ($(TCLTK_PATH),)
@@ -793,7 +825,7 @@ export TAR INSTALL DESTDIR SHELL_PATH
 
 ### Build rules
 
-all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS)
+all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
 ifneq (,$X)
        $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), $(RM) '$p';)
 endif
@@ -819,12 +851,10 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
 
 help.o: help.c common-cmds.h GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
+               '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
                '-DGIT_MAN_PATH="$(mandir_SQ)"' \
                '-DGIT_INFO_PATH="$(infodir_SQ)"' $<
 
-git-merge-subtree$X: git-merge-recursive$X
-       $(QUIET_BUILT_IN)$(RM) $@ && ln git-merge-recursive$X $@
-
 $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
 
@@ -839,7 +869,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
            -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           -e 's|@@HTMLDIR@@|$(htmldir_SQ)|g' \
            $@.sh >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
@@ -995,6 +1024,9 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
                echo "$$FLAGS" >GIT-CFLAGS; \
             fi
 
+GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
+       @echo SHELL_PATH=\''$(SHELL_PATH_SQ)'\' >$@
+
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
 TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)')
@@ -1150,10 +1182,11 @@ ifndef NO_TCLTK
        $(MAKE) -C gitk-git clean
        $(MAKE) -C git-gui clean
 endif
-       $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
+       $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
 
 .PHONY: all install clean strip
 .PHONY: .FORCE-GIT-VERSION-FILE TAGS tags cscope .FORCE-GIT-CFLAGS
+.PHONY: .FORCE-GIT-BUILD-OPTIONS
 
 ### Check documentation
 #
index f84c4c7f11b0d2df58151efc5dcc01da4ebd4be6..3e77358f704fde7271f18ccbf90a57419550b265 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.5.4.4.txt
\ No newline at end of file
+Documentation/RelNotes-1.5.5.txt
\ No newline at end of file
diff --git a/alias.c b/alias.c
new file mode 100644 (file)
index 0000000..116cac8
--- /dev/null
+++ b/alias.c
@@ -0,0 +1,22 @@
+#include "cache.h"
+
+static const char *alias_key;
+static char *alias_val;
+static int alias_lookup_cb(const char *k, const char *v)
+{
+       if (!prefixcmp(k, "alias.") && !strcmp(k+6, alias_key)) {
+               if (!v)
+                       return config_error_nonbool(k);
+               alias_val = xstrdup(v);
+               return 0;
+       }
+       return 0;
+}
+
+char *alias_lookup(const char *alias)
+{
+       alias_key = alias;
+       alias_val = NULL;
+       git_config(alias_lookup_cb);
+       return alias_val;
+}
diff --git a/branch.c b/branch.c
new file mode 100644 (file)
index 0000000..daf862e
--- /dev/null
+++ b/branch.c
@@ -0,0 +1,152 @@
+#include "cache.h"
+#include "branch.h"
+#include "refs.h"
+#include "remote.h"
+#include "commit.h"
+
+struct tracking {
+       struct refspec spec;
+       char *src;
+       const char *remote;
+       int matches;
+};
+
+static int find_tracked_branch(struct remote *remote, void *priv)
+{
+       struct tracking *tracking = priv;
+
+       if (!remote_find_tracking(remote, &tracking->spec)) {
+               if (++tracking->matches == 1) {
+                       tracking->src = tracking->spec.src;
+                       tracking->remote = remote->name;
+               } else {
+                       free(tracking->spec.src);
+                       if (tracking->src) {
+                               free(tracking->src);
+                               tracking->src = NULL;
+                       }
+               }
+               tracking->spec.src = NULL;
+       }
+
+       return 0;
+}
+
+/*
+ * This is called when new_ref is branched off of orig_ref, and tries
+ * to infer the settings for branch.<new_ref>.{remote,merge} from the
+ * config.
+ */
+static int setup_tracking(const char *new_ref, const char *orig_ref,
+                          enum branch_track track)
+{
+       char key[1024];
+       struct tracking tracking;
+
+       if (strlen(new_ref) > 1024 - 7 - 7 - 1)
+               return error("Tracking not set up: name too long: %s",
+                               new_ref);
+
+       memset(&tracking, 0, sizeof(tracking));
+       tracking.spec.dst = (char *)orig_ref;
+       if (for_each_remote(find_tracked_branch, &tracking))
+               return 1;
+
+       if (!tracking.matches)
+               switch (track) {
+               case BRANCH_TRACK_ALWAYS:
+               case BRANCH_TRACK_EXPLICIT:
+                       break;
+               default:
+                       return 1;
+               }
+
+       if (tracking.matches > 1)
+               return error("Not tracking: ambiguous information for ref %s",
+                               orig_ref);
+
+       sprintf(key, "branch.%s.remote", new_ref);
+       git_config_set(key, tracking.remote ?  tracking.remote : ".");
+       sprintf(key, "branch.%s.merge", new_ref);
+       git_config_set(key, tracking.src ? tracking.src : orig_ref);
+       free(tracking.src);
+       printf("Branch %s set up to track %s branch %s.\n", new_ref,
+               tracking.remote ? "remote" : "local", orig_ref);
+
+       return 0;
+}
+
+void create_branch(const char *head,
+                  const char *name, const char *start_name,
+                  int force, int reflog, enum branch_track track)
+{
+       struct ref_lock *lock;
+       struct commit *commit;
+       unsigned char sha1[20];
+       char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
+       int forcing = 0;
+
+       snprintf(ref, sizeof ref, "refs/heads/%s", name);
+       if (check_ref_format(ref))
+               die("'%s' is not a valid branch name.", name);
+
+       if (resolve_ref(ref, sha1, 1, NULL)) {
+               if (!force)
+                       die("A branch named '%s' already exists.", name);
+               else if (!is_bare_repository() && !strcmp(head, name))
+                       die("Cannot force update the current branch.");
+               forcing = 1;
+       }
+
+       real_ref = NULL;
+       if (get_sha1(start_name, sha1))
+               die("Not a valid object name: '%s'.", start_name);
+
+       switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
+       case 0:
+               /* Not branching from any existing branch */
+               if (track == BRANCH_TRACK_EXPLICIT)
+                       die("Cannot setup tracking information; starting point is not a branch.");
+               break;
+       case 1:
+               /* Unique completion -- good */
+               break;
+       default:
+               die("Ambiguous object name: '%s'.", start_name);
+               break;
+       }
+
+       if ((commit = lookup_commit_reference(sha1)) == NULL)
+               die("Not a valid branch point: '%s'.", start_name);
+       hashcpy(sha1, commit->object.sha1);
+
+       lock = lock_any_ref_for_update(ref, NULL, 0);
+       if (!lock)
+               die("Failed to lock ref for update: %s.", strerror(errno));
+
+       if (reflog)
+               log_all_ref_updates = 1;
+
+       if (forcing)
+               snprintf(msg, sizeof msg, "branch: Reset from %s",
+                        start_name);
+       else
+               snprintf(msg, sizeof msg, "branch: Created from %s",
+                        start_name);
+
+       if (real_ref && track)
+               setup_tracking(name, real_ref, track);
+
+       if (write_ref_sha1(lock, sha1, msg) < 0)
+               die("Failed to write ref: %s.", strerror(errno));
+
+       free(real_ref);
+}
+
+void remove_branch_state(void)
+{
+       unlink(git_path("MERGE_HEAD"));
+       unlink(git_path("rr-cache/MERGE_RR"));
+       unlink(git_path("MERGE_MSG"));
+       unlink(git_path("SQUASH_MSG"));
+}
diff --git a/branch.h b/branch.h
new file mode 100644 (file)
index 0000000..9f0c2a2
--- /dev/null
+++ b/branch.h
@@ -0,0 +1,24 @@
+#ifndef BRANCH_H
+#define BRANCH_H
+
+/* Functions for acting on the information about branches. */
+
+/*
+ * Creates a new branch, where head is the branch currently checked
+ * out, name is the new branch name, start_name is the name of the
+ * existing branch that the new branch should start from, force
+ * enables overwriting an existing (non-head) branch, reflog creates a
+ * reflog for the branch, and track causes the new branch to be
+ * configured to merge the remote branch that start_name is a tracking
+ * branch for (if any).
+ */
+void create_branch(const char *head, const char *name, const char *start_name,
+                  int force, int reflog, enum branch_track track);
+
+/*
+ * Remove information about the state of working on the current
+ * branch. (E.g., MERGE_HEAD)
+ */
+void remove_branch_state(void);
+
+#endif
index a11b1bbeee9568adbf8a3e32f77d14b132985061..a3f075df4bd91de8d3423420cfcffd9722ad9586 100644 (file)
@@ -161,6 +161,84 @@ struct patch {
        struct patch *next;
 };
 
+/*
+ * A line in a file, len-bytes long (includes the terminating LF,
+ * except for an incomplete line at the end if the file ends with
+ * one), and its contents hashes to 'hash'.
+ */
+struct line {
+       size_t len;
+       unsigned hash : 24;
+       unsigned flag : 8;
+#define LINE_COMMON     1
+};
+
+/*
+ * This represents a "file", which is an array of "lines".
+ */
+struct image {
+       char *buf;
+       size_t len;
+       size_t nr;
+       size_t alloc;
+       struct line *line_allocated;
+       struct line *line;
+};
+
+static uint32_t hash_line(const char *cp, size_t len)
+{
+       size_t i;
+       uint32_t h;
+       for (i = 0, h = 0; i < len; i++) {
+               if (!isspace(cp[i])) {
+                       h = h * 3 + (cp[i] & 0xff);
+               }
+       }
+       return h;
+}
+
+static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
+{
+       ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
+       img->line_allocated[img->nr].len = len;
+       img->line_allocated[img->nr].hash = hash_line(bol, len);
+       img->line_allocated[img->nr].flag = flag;
+       img->nr++;
+}
+
+static void prepare_image(struct image *image, char *buf, size_t len,
+                         int prepare_linetable)
+{
+       const char *cp, *ep;
+
+       memset(image, 0, sizeof(*image));
+       image->buf = buf;
+       image->len = len;
+
+       if (!prepare_linetable)
+               return;
+
+       ep = image->buf + image->len;
+       cp = image->buf;
+       while (cp < ep) {
+               const char *next;
+               for (next = cp; next < ep && *next != '\n'; next++)
+                       ;
+               if (next < ep)
+                       next++;
+               add_line_info(image, cp, next - cp, 0);
+               cp = next;
+       }
+       image->line = image->line_allocated;
+}
+
+static void clear_image(struct image *image)
+{
+       free(image->buf);
+       image->buf = NULL;
+       image->len = 0;
+}
+
 static void say_patch_name(FILE *output, const char *pre,
                           struct patch *patch, const char *post)
 {
@@ -1430,234 +1508,345 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
        case S_IFREG:
                if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
                        return error("unable to open or read %s", path);
-               convert_to_git(path, buf->buf, buf->len, buf);
+               convert_to_git(path, buf->buf, buf->len, buf, 0);
                return 0;
        default:
                return -1;
        }
 }
 
-static int find_offset(const char *buf, unsigned long size,
-                      const char *fragment, unsigned long fragsize,
-                      int line, int *lines)
+static void update_pre_post_images(struct image *preimage,
+                                  struct image *postimage,
+                                  char *buf,
+                                  size_t len)
 {
-       int i;
-       unsigned long start, backwards, forwards;
+       int i, ctx;
+       char *new, *old, *fixed;
+       struct image fixed_preimage;
 
-       if (fragsize > size)
-               return -1;
+       /*
+        * Update the preimage with whitespace fixes.  Note that we
+        * are not losing preimage->buf -- apply_one_fragment() will
+        * free "oldlines".
+        */
+       prepare_image(&fixed_preimage, buf, len, 1);
+       assert(fixed_preimage.nr == preimage->nr);
+       for (i = 0; i < preimage->nr; i++)
+               fixed_preimage.line[i].flag = preimage->line[i].flag;
+       free(preimage->line_allocated);
+       *preimage = fixed_preimage;
 
-       start = 0;
-       if (line > 1) {
-               unsigned long offset = 0;
-               i = line-1;
-               while (offset + fragsize <= size) {
-                       if (buf[offset++] == '\n') {
-                               start = offset;
-                               if (!--i)
-                                       break;
-                       }
+       /*
+        * Adjust the common context lines in postimage, in place.
+        * This is possible because whitespace fixing does not make
+        * the string grow.
+        */
+       new = old = postimage->buf;
+       fixed = preimage->buf;
+       for (i = ctx = 0; i < postimage->nr; i++) {
+               size_t len = postimage->line[i].len;
+               if (!(postimage->line[i].flag & LINE_COMMON)) {
+                       /* an added line -- no counterparts in preimage */
+                       memmove(new, old, len);
+                       old += len;
+                       new += len;
+                       continue;
                }
+
+               /* a common context -- skip it in the original postimage */
+               old += len;
+
+               /* and find the corresponding one in the fixed preimage */
+               while (ctx < preimage->nr &&
+                      !(preimage->line[ctx].flag & LINE_COMMON)) {
+                       fixed += preimage->line[ctx].len;
+                       ctx++;
+               }
+               if (preimage->nr <= ctx)
+                       die("oops");
+
+               /* and copy it in, while fixing the line length */
+               len = preimage->line[ctx].len;
+               memcpy(new, fixed, len);
+               new += len;
+               fixed += len;
+               postimage->line[i].len = len;
+               ctx++;
        }
 
-       /* Exact line number? */
-       if ((start + fragsize <= size) &&
-           !memcmp(buf + start, fragment, fragsize))
-               return start;
+       /* Fix the length of the whole thing */
+       postimage->len = new - postimage->buf;
+}
+
+static int match_fragment(struct image *img,
+                         struct image *preimage,
+                         struct image *postimage,
+                         unsigned long try,
+                         int try_lno,
+                         unsigned ws_rule,
+                         int match_beginning, int match_end)
+{
+       int i;
+       char *fixed_buf, *buf, *orig, *target;
+
+       if (preimage->nr + try_lno > img->nr)
+               return 0;
+
+       if (match_beginning && try_lno)
+               return 0;
+
+       if (match_end && preimage->nr + try_lno != img->nr)
+               return 0;
+
+       /* Quick hash check */
+       for (i = 0; i < preimage->nr; i++)
+               if (preimage->line[i].hash != img->line[try_lno + i].hash)
+                       return 0;
+
+       /*
+        * Do we have an exact match?  If we were told to match
+        * at the end, size must be exactly at try+fragsize,
+        * otherwise try+fragsize must be still within the preimage,
+        * and either case, the old piece should match the preimage
+        * exactly.
+        */
+       if ((match_end
+            ? (try + preimage->len == img->len)
+            : (try + preimage->len <= img->len)) &&
+           !memcmp(img->buf + try, preimage->buf, preimage->len))
+               return 1;
+
+       if (ws_error_action != correct_ws_error)
+               return 0;
+
+       /*
+        * The hunk does not apply byte-by-byte, but the hash says
+        * it might with whitespace fuzz.
+        */
+       fixed_buf = xmalloc(preimage->len + 1);
+       buf = fixed_buf;
+       orig = preimage->buf;
+       target = img->buf + try;
+       for (i = 0; i < preimage->nr; i++) {
+               size_t fixlen; /* length after fixing the preimage */
+               size_t oldlen = preimage->line[i].len;
+               size_t tgtlen = img->line[try_lno + i].len;
+               size_t tgtfixlen; /* length after fixing the target line */
+               char tgtfixbuf[1024], *tgtfix;
+               int match;
+
+               /* Try fixing the line in the preimage */
+               fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL);
+
+               /* Try fixing the line in the target */
+               if (sizeof(tgtfixbuf) < tgtlen)
+                       tgtfix = tgtfixbuf;
+               else
+                       tgtfix = xmalloc(tgtlen);
+               tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL);
+
+               /*
+                * If they match, either the preimage was based on
+                * a version before our tree fixed whitespace breakage,
+                * or we are lacking a whitespace-fix patch the tree
+                * the preimage was based on already had (i.e. target
+                * has whitespace breakage, the preimage doesn't).
+                * In either case, we are fixing the whitespace breakages
+                * so we might as well take the fix together with their
+                * real change.
+                */
+               match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen));
+
+               if (tgtfix != tgtfixbuf)
+                       free(tgtfix);
+               if (!match)
+                       goto unmatch_exit;
+
+               orig += oldlen;
+               buf += fixlen;
+               target += tgtlen;
+       }
+
+       /*
+        * Yes, the preimage is based on an older version that still
+        * has whitespace breakages unfixed, and fixing them makes the
+        * hunk match.  Update the context lines in the postimage.
+        */
+       update_pre_post_images(preimage, postimage,
+                              fixed_buf, buf - fixed_buf);
+       return 1;
+
+ unmatch_exit:
+       free(fixed_buf);
+       return 0;
+}
+
+static int find_pos(struct image *img,
+                   struct image *preimage,
+                   struct image *postimage,
+                   int line,
+                   unsigned ws_rule,
+                   int match_beginning, int match_end)
+{
+       int i;
+       unsigned long backwards, forwards, try;
+       int backwards_lno, forwards_lno, try_lno;
+
+       if (preimage->nr > img->nr)
+               return -1;
+
+       /*
+        * If match_begining or match_end is specified, there is no
+        * point starting from a wrong line that will never match and
+        * wander around and wait for a match at the specified end.
+        */
+       if (match_beginning)
+               line = 0;
+       else if (match_end)
+               line = img->nr - preimage->nr;
+
+       if (line > img->nr)
+               line = img->nr;
+
+       try = 0;
+       for (i = 0; i < line; i++)
+               try += img->line[i].len;
 
        /*
         * There's probably some smart way to do this, but I'll leave
         * that to the smart and beautiful people. I'm simple and stupid.
         */
-       backwards = start;
-       forwards = start;
+       backwards = try;
+       backwards_lno = line;
+       forwards = try;
+       forwards_lno = line;
+       try_lno = line;
+
        for (i = 0; ; i++) {
-               unsigned long try;
-               int n;
+               if (match_fragment(img, preimage, postimage,
+                                  try, try_lno, ws_rule,
+                                  match_beginning, match_end))
+                       return try_lno;
+
+       again:
+               if (backwards_lno == 0 && forwards_lno == img->nr)
+                       break;
 
-               /* "backward" */
                if (i & 1) {
-                       if (!backwards) {
-                               if (forwards + fragsize > size)
-                                       break;
-                               continue;
+                       if (backwards_lno == 0) {
+                               i++;
+                               goto again;
                        }
-                       do {
-                               --backwards;
-                       } while (backwards && buf[backwards-1] != '\n');
+                       backwards_lno--;
+                       backwards -= img->line[backwards_lno].len;
                        try = backwards;
+                       try_lno = backwards_lno;
                } else {
-                       while (forwards + fragsize <= size) {
-                               if (buf[forwards++] == '\n')
-                                       break;
+                       if (forwards_lno == img->nr) {
+                               i++;
+                               goto again;
                        }
+                       forwards += img->line[forwards_lno].len;
+                       forwards_lno++;
                        try = forwards;
+                       try_lno = forwards_lno;
                }
 
-               if (try + fragsize > size)
-                       continue;
-               if (memcmp(buf + try, fragment, fragsize))
-                       continue;
-               n = (i >> 1)+1;
-               if (i & 1)
-                       n = -n;
-               *lines = n;
-               return try;
        }
-
-       /*
-        * We should start searching forward and backward.
-        */
        return -1;
 }
 
-static void remove_first_line(const char **rbuf, int *rsize)
+static void remove_first_line(struct image *img)
 {
-       const char *buf = *rbuf;
-       int size = *rsize;
-       unsigned long offset;
-       offset = 0;
-       while (offset <= size) {
-               if (buf[offset++] == '\n')
-                       break;
-       }
-       *rsize = size - offset;
-       *rbuf = buf + offset;
+       img->buf += img->line[0].len;
+       img->len -= img->line[0].len;
+       img->line++;
+       img->nr--;
 }
 
-static void remove_last_line(const char **rbuf, int *rsize)
+static void remove_last_line(struct image *img)
 {
-       const char *buf = *rbuf;
-       int size = *rsize;
-       unsigned long offset;
-       offset = size - 1;
-       while (offset > 0) {
-               if (buf[--offset] == '\n')
-                       break;
-       }
-       *rsize = offset + 1;
+       img->len -= img->line[--img->nr].len;
 }
 
-static int apply_line(char *output, const char *patch, int plen,
-                     unsigned ws_rule)
+static void update_image(struct image *img,
+                        int applied_pos,
+                        struct image *preimage,
+                        struct image *postimage)
 {
        /*
-        * plen is number of bytes to be copied from patch,
-        * starting at patch+1 (patch[0] is '+').  Typically
-        * patch[plen] is '\n', unless this is the incomplete
-        * last line.
+        * remove the copy of preimage at offset in img
+        * and replace it with postimage
         */
-       int i;
-       int add_nl_to_tail = 0;
-       int fixed = 0;
-       int last_tab_in_indent = 0;
-       int last_space_in_indent = 0;
-       int need_fix_leading_space = 0;
-       char *buf;
-
-       if ((ws_error_action != correct_ws_error) || !whitespace_error ||
-           *patch != '+') {
-               memcpy(output, patch + 1, plen);
-               return plen;
-       }
-
-       /*
-        * Strip trailing whitespace
-        */
-       if ((ws_rule & WS_TRAILING_SPACE) &&
-           (1 < plen && isspace(patch[plen-1]))) {
-               if (patch[plen] == '\n')
-                       add_nl_to_tail = 1;
-               plen--;
-               while (0 < plen && isspace(patch[plen]))
-                       plen--;
-               fixed = 1;
-       }
-
-       /*
-        * Check leading whitespaces (indent)
-        */
-       for (i = 1; i < plen; i++) {
-               char ch = patch[i];
-               if (ch == '\t') {
-                       last_tab_in_indent = i;
-                       if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
-                           0 < last_space_in_indent)
-                           need_fix_leading_space = 1;
-               } else if (ch == ' ') {
-                       last_space_in_indent = i;
-                       if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
-                           8 <= i - last_tab_in_indent)
-                               need_fix_leading_space = 1;
-               }
-               else
-                       break;
-       }
-
-       buf = output;
-       if (need_fix_leading_space) {
-               int consecutive_spaces = 0;
-               int last = last_tab_in_indent + 1;
-
-               if (ws_rule & WS_INDENT_WITH_NON_TAB) {
-                       /* have "last" point at one past the indent */
-                       if (last_tab_in_indent < last_space_in_indent)
-                               last = last_space_in_indent + 1;
-                       else
-                               last = last_tab_in_indent + 1;
-               }
+       int i, nr;
+       size_t remove_count, insert_count, applied_at = 0;
+       char *result;
 
+       for (i = 0; i < applied_pos; i++)
+               applied_at += img->line[i].len;
+
+       remove_count = 0;
+       for (i = 0; i < preimage->nr; i++)
+               remove_count += img->line[applied_pos + i].len;
+       insert_count = postimage->len;
+
+       /* Adjust the contents */
+       result = xmalloc(img->len + insert_count - remove_count + 1);
+       memcpy(result, img->buf, applied_at);
+       memcpy(result + applied_at, postimage->buf, postimage->len);
+       memcpy(result + applied_at + postimage->len,
+              img->buf + (applied_at + remove_count),
+              img->len - (applied_at + remove_count));
+       free(img->buf);
+       img->buf = result;
+       img->len += insert_count - remove_count;
+       result[img->len] = '\0';
+
+       /* Adjust the line table */
+       nr = img->nr + postimage->nr - preimage->nr;
+       if (preimage->nr < postimage->nr) {
                /*
-                * between patch[1..last], strip the funny spaces,
-                * updating them to tab as needed.
+                * NOTE: this knows that we never call remove_first_line()
+                * on anything other than pre/post image.
                 */
-               for (i = 1; i < last; i++, plen--) {
-                       char ch = patch[i];
-                       if (ch != ' ') {
-                               consecutive_spaces = 0;
-                               *output++ = ch;
-                       } else {
-                               consecutive_spaces++;
-                               if (consecutive_spaces == 8) {
-                                       *output++ = '\t';
-                                       consecutive_spaces = 0;
-                               }
-                       }
-               }
-               while (0 < consecutive_spaces--)
-                       *output++ = ' ';
-               fixed = 1;
-               i = last;
+               img->line = xrealloc(img->line, nr * sizeof(*img->line));
+               img->line_allocated = img->line;
        }
-       else
-               i = 1;
-
-       memcpy(output, patch + i, plen);
-       if (add_nl_to_tail)
-               output[plen++] = '\n';
-       if (fixed)
-               applied_after_fixing_ws++;
-       return output + plen - buf;
+       if (preimage->nr != postimage->nr)
+               memmove(img->line + applied_pos + postimage->nr,
+                       img->line + applied_pos + preimage->nr,
+                       (img->nr - (applied_pos + preimage->nr)) *
+                       sizeof(*img->line));
+       memcpy(img->line + applied_pos,
+              postimage->line,
+              postimage->nr * sizeof(*img->line));
+       img->nr = nr;
 }
 
-static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
+static int apply_one_fragment(struct image *img, struct fragment *frag,
                              int inaccurate_eof, unsigned ws_rule)
 {
        int match_beginning, match_end;
        const char *patch = frag->patch;
-       int offset, size = frag->size;
-       char *old = xmalloc(size);
-       char *new = xmalloc(size);
-       const char *oldlines, *newlines;
-       int oldsize = 0, newsize = 0;
+       int size = frag->size;
+       char *old, *new, *oldlines, *newlines;
        int new_blank_lines_at_end = 0;
        unsigned long leading, trailing;
-       int pos, lines;
+       int pos, applied_pos;
+       struct image preimage;
+       struct image postimage;
+
+       memset(&preimage, 0, sizeof(preimage));
+       memset(&postimage, 0, sizeof(postimage));
+       oldlines = xmalloc(size);
+       newlines = xmalloc(size);
 
+       old = oldlines;
+       new = newlines;
        while (size > 0) {
                char first;
                int len = linelen(patch, size);
-               int plen;
+               int plen, added;
                int added_blank_line = 0;
 
                if (!len)
@@ -1670,7 +1859,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                 * followed by "\ No newline", then we also remove the
                 * last one (which is the newline, of course).
                 */
-               plen = len-1;
+               plen = len - 1;
                if (len < size && patch[len] == '\\')
                        plen--;
                first = *patch;
@@ -1687,25 +1876,40 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                        if (plen < 0)
                                /* ... followed by '\No newline'; nothing */
                                break;
-                       old[oldsize++] = '\n';
-                       new[newsize++] = '\n';
+                       *old++ = '\n';
+                       *new++ = '\n';
+                       add_line_info(&preimage, "\n", 1, LINE_COMMON);
+                       add_line_info(&postimage, "\n", 1, LINE_COMMON);
                        break;
                case ' ':
                case '-':
-                       memcpy(old + oldsize, patch + 1, plen);
-                       oldsize += plen;
+                       memcpy(old, patch + 1, plen);
+                       add_line_info(&preimage, old, plen,
+                                     (first == ' ' ? LINE_COMMON : 0));
+                       old += plen;
                        if (first == '-')
                                break;
                /* Fall-through for ' ' */
                case '+':
-                       if (first != '+' || !no_add) {
-                               int added = apply_line(new + newsize, patch,
-                                                      plen, ws_rule);
-                               newsize += added;
-                               if (first == '+' &&
-                                   added == 1 && new[newsize-1] == '\n')
-                                       added_blank_line = 1;
+                       /* --no-add does not add new lines */
+                       if (first == '+' && no_add)
+                               break;
+
+                       if (first != '+' ||
+                           !whitespace_error ||
+                           ws_error_action != correct_ws_error) {
+                               memcpy(new, patch + 1, plen);
+                               added = plen;
                        }
+                       else {
+                               added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
+                       }
+                       add_line_info(&postimage, new, added,
+                                     (first == '+' ? 0 : LINE_COMMON));
+                       new += added;
+                       if (first == '+' &&
+                           added == 1 && new[-1] == '\n')
+                               added_blank_line = 1;
                        break;
                case '@': case '\\':
                        /* Ignore it, we already handled it */
@@ -1722,16 +1926,13 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                patch += len;
                size -= len;
        }
-
        if (inaccurate_eof &&
-           oldsize > 0 && old[oldsize - 1] == '\n' &&
-           newsize > 0 && new[newsize - 1] == '\n') {
-               oldsize--;
-               newsize--;
+           old > oldlines && old[-1] == '\n' &&
+           new > newlines && new[-1] == '\n') {
+               old--;
+               new--;
        }
 
-       oldlines = old;
-       newlines = new;
        leading = frag->leading;
        trailing = frag->trailing;
 
@@ -1752,33 +1953,21 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                match_end = !trailing;
        }
 
-       lines = 0;
-       pos = frag->newpos;
+       pos = frag->newpos ? (frag->newpos - 1) : 0;
+       preimage.buf = oldlines;
+       preimage.len = old - oldlines;
+       postimage.buf = newlines;
+       postimage.len = new - newlines;
+       preimage.line = preimage.line_allocated;
+       postimage.line = postimage.line_allocated;
+
        for (;;) {
-               offset = find_offset(buf->buf, buf->len,
-                                    oldlines, oldsize, pos, &lines);
-               if (match_end && offset + oldsize != buf->len)
-                       offset = -1;
-               if (match_beginning && offset)
-                       offset = -1;
-               if (offset >= 0) {
-                       if (ws_error_action == correct_ws_error &&
-                           (buf->len - oldsize - offset == 0)) /* end of file? */
-                               newsize -= new_blank_lines_at_end;
-
-                       /* Warn if it was necessary to reduce the number
-                        * of context lines.
-                        */
-                       if ((leading != frag->leading) ||
-                           (trailing != frag->trailing))
-                               fprintf(stderr, "Context reduced to (%ld/%ld)"
-                                       " to apply fragment at %d\n",
-                                       leading, trailing, pos + lines);
-
-                       strbuf_splice(buf, offset, oldsize, newlines, newsize);
-                       offset = 0;
+
+               applied_pos = find_pos(img, &preimage, &postimage, pos,
+                                      ws_rule, match_beginning, match_end);
+
+               if (applied_pos >= 0)
                        break;
-               }
 
                /* Am I at my context limits? */
                if ((leading <= p_context) && (trailing <= p_context))
@@ -1787,33 +1976,64 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                        match_beginning = match_end = 0;
                        continue;
                }
+
                /*
                 * Reduce the number of context lines; reduce both
                 * leading and trailing if they are equal otherwise
                 * just reduce the larger context.
                 */
                if (leading >= trailing) {
-                       remove_first_line(&oldlines, &oldsize);
-                       remove_first_line(&newlines, &newsize);
+                       remove_first_line(&preimage);
+                       remove_first_line(&postimage);
                        pos--;
                        leading--;
                }
                if (trailing > leading) {
-                       remove_last_line(&oldlines, &oldsize);
-                       remove_last_line(&newlines, &newsize);
+                       remove_last_line(&preimage);
+                       remove_last_line(&postimage);
                        trailing--;
                }
        }
 
-       if (offset && apply_verbosely)
-               error("while searching for:\n%.*s", oldsize, oldlines);
+       if (applied_pos >= 0) {
+               if (ws_error_action == correct_ws_error &&
+                   new_blank_lines_at_end &&
+                   postimage.nr + applied_pos == img->nr) {
+                       /*
+                        * If the patch application adds blank lines
+                        * at the end, and if the patch applies at the
+                        * end of the image, remove those added blank
+                        * lines.
+                        */
+                       while (new_blank_lines_at_end--)
+                               remove_last_line(&postimage);
+               }
 
-       free(old);
-       free(new);
-       return offset;
+               /*
+                * Warn if it was necessary to reduce the number
+                * of context lines.
+                */
+               if ((leading != frag->leading) ||
+                   (trailing != frag->trailing))
+                       fprintf(stderr, "Context reduced to (%ld/%ld)"
+                               " to apply fragment at %d\n",
+                               leading, trailing, applied_pos+1);
+               update_image(img, applied_pos, &preimage, &postimage);
+       } else {
+               if (apply_verbosely)
+                       error("while searching for:\n%.*s",
+                             (int)(old - oldlines), oldlines);
+       }
+
+       free(oldlines);
+       free(newlines);
+       free(preimage.line_allocated);
+       free(postimage.line_allocated);
+
+       return (applied_pos < 0);
 }
 
-static int apply_binary_fragment(struct strbuf *buf, struct patch *patch)
+static int apply_binary_fragment(struct image *img, struct patch *patch)
 {
        struct fragment *fragment = patch->fragments;
        unsigned long len;
@@ -1830,22 +2050,26 @@ static int apply_binary_fragment(struct strbuf *buf, struct patch *patch)
        }
        switch (fragment->binary_patch_method) {
        case BINARY_DELTA_DEFLATED:
-               dst = patch_delta(buf->buf, buf->len, fragment->patch,
+               dst = patch_delta(img->buf, img->len, fragment->patch,
                                  fragment->size, &len);
                if (!dst)
                        return -1;
-               /* XXX patch_delta NUL-terminates */
-               strbuf_attach(buf, dst, len, len + 1);
+               clear_image(img);
+               img->buf = dst;
+               img->len = len;
                return 0;
        case BINARY_LITERAL_DEFLATED:
-               strbuf_reset(buf);
-               strbuf_add(buf, fragment->patch, fragment->size);
+               clear_image(img);
+               img->len = fragment->size;
+               img->buf = xmalloc(img->len+1);
+               memcpy(img->buf, fragment->patch, img->len);
+               img->buf[img->len] = '\0';
                return 0;
        }
        return -1;
 }
 
-static int apply_binary(struct strbuf *buf, struct patch *patch)
+static int apply_binary(struct image *img, struct patch *patch)
 {
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
        unsigned char sha1[20];
@@ -1866,7 +2090,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
                 * See if the old one matches what the patch
                 * applies to.
                 */
-               hash_sha1_file(buf->buf, buf->len, blob_type, sha1);
+               hash_sha1_file(img->buf, img->len, blob_type, sha1);
                if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
                        return error("the patch applies to '%s' (%s), "
                                     "which does not match the "
@@ -1875,14 +2099,14 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
        }
        else {
                /* Otherwise, the old one must be empty. */
-               if (buf->len)
+               if (img->len)
                        return error("the patch applies to an empty "
                                     "'%s' but it is not empty", name);
        }
 
        get_sha1_hex(patch->new_sha1_prefix, sha1);
        if (is_null_sha1(sha1)) {
-               strbuf_release(buf);
+               clear_image(img);
                return 0; /* deletion patch */
        }
 
@@ -1897,20 +2121,21 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
                        return error("the necessary postimage %s for "
                                     "'%s' cannot be read",
                                     patch->new_sha1_prefix, name);
-               /* XXX read_sha1_file NUL-terminates */
-               strbuf_attach(buf, result, size, size + 1);
+               clear_image(img);
+               img->buf = result;
+               img->len = size;
        } else {
                /*
                 * We have verified buf matches the preimage;
                 * apply the patch data to it, which is stored
                 * in the patch->fragments->{patch,size}.
                 */
-               if (apply_binary_fragment(buf, patch))
+               if (apply_binary_fragment(img, patch))
                        return error("binary patch does not apply to '%s'",
                                     name);
 
                /* verify that the result matches */
-               hash_sha1_file(buf->buf, buf->len, blob_type, sha1);
+               hash_sha1_file(img->buf, img->len, blob_type, sha1);
                if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
                        return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
                                name, patch->new_sha1_prefix, sha1_to_hex(sha1));
@@ -1919,7 +2144,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
        return 0;
 }
 
-static int apply_fragments(struct strbuf *buf, struct patch *patch)
+static int apply_fragments(struct image *img, struct patch *patch)
 {
        struct fragment *frag = patch->fragments;
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
@@ -1927,10 +2152,10 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch)
        unsigned inaccurate_eof = patch->inaccurate_eof;
 
        if (patch->is_binary)
-               return apply_binary(buf, patch);
+               return apply_binary(img, patch);
 
        while (frag) {
-               if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) {
+               if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) {
                        error("patch failed: %s:%ld", name, frag->oldpos);
                        if (!apply_with_reject)
                                return -1;
@@ -1946,7 +2171,7 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
        if (!ce)
                return 0;
 
-       if (S_ISGITLINK(ntohl(ce->ce_mode))) {
+       if (S_ISGITLINK(ce->ce_mode)) {
                strbuf_grow(buf, 100);
                strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
        } else {
@@ -1966,6 +2191,9 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
 static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
 {
        struct strbuf buf;
+       struct image image;
+       size_t len;
+       char *img;
 
        strbuf_init(&buf, 0);
        if (cached) {
@@ -1988,9 +2216,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
                }
        }
 
-       if (apply_fragments(&buf, patch) < 0)
+       img = strbuf_detach(&buf, &len);
+       prepare_image(&image, img, len, !patch->is_binary);
+
+       if (apply_fragments(&image, patch) < 0)
                return -1; /* note with --reject this succeeds. */
-       patch->result = strbuf_detach(&buf, &patch->resultsize);
+       patch->result = image.buf;
+       patch->resultsize = image.len;
+       free(image.line_allocated);
 
        if (0 < patch->is_delete && patch->resultsize)
                return error("removal patch leaves file contents");
@@ -2023,7 +2256,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists)
 
 static int verify_index_match(struct cache_entry *ce, struct stat *st)
 {
-       if (S_ISGITLINK(ntohl(ce->ce_mode))) {
+       if (S_ISGITLINK(ce->ce_mode)) {
                if (!S_ISDIR(st->st_mode))
                        return -1;
                return 0;
@@ -2082,12 +2315,12 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
                                return error("%s: does not match index",
                                             old_name);
                        if (cached)
-                               st_mode = ntohl(ce->ce_mode);
+                               st_mode = ce->ce_mode;
                } else if (stat_ret < 0)
                        return error("%s: %s", old_name, strerror(errno));
 
                if (!cached)
-                       st_mode = ntohl(ce_mode_from_stat(ce, st.st_mode));
+                       st_mode = ce_mode_from_stat(ce, st.st_mode);
 
                if (patch->is_new < 0)
                        patch->is_new = 0;
@@ -2388,7 +2621,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
        ce = xcalloc(1, ce_size);
        memcpy(ce->name, path, namelen);
        ce->ce_mode = create_ce_mode(mode);
-       ce->ce_flags = htons(namelen);
+       ce->ce_flags = namelen;
        if (S_ISGITLINK(mode)) {
                const char *s = buf;
 
index 9b4c02e87f7ef4a20d269ab9315b6977cf7fb3fb..bfd562d7d2b897ad4ab3d35dda33701e315b7d5d 100644 (file)
@@ -123,8 +123,7 @@ static inline struct origin *origin_incref(struct origin *o)
 static void origin_decref(struct origin *o)
 {
        if (o && --o->refcnt <= 0) {
-               if (o->file.ptr)
-                       free(o->file.ptr);
+               free(o->file.ptr);
                free(o);
        }
 }
@@ -1894,9 +1893,7 @@ static unsigned parse_score(const char *arg)
 
 static const char *add_prefix(const char *prefix, const char *path)
 {
-       if (!prefix || !prefix[0])
-               return path;
-       return prefix_path(prefix, strlen(prefix), path);
+       return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
 }
 
 /*
@@ -2073,7 +2070,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
                if (strbuf_read(&buf, 0, 0) < 0)
                        die("read error %s from stdin", strerror(errno));
        }
-       convert_to_git(path, buf.buf, buf.len, &buf);
+       convert_to_git(path, buf.buf, buf.len, &buf, 0);
        origin->file.ptr = buf.buf;
        origin->file.size = buf.len;
        pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
@@ -2092,7 +2089,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
        if (!mode) {
                int pos = cache_name_pos(path, len);
                if (0 <= pos)
-                       mode = ntohl(active_cache[pos]->ce_mode);
+                       mode = active_cache[pos]->ce_mode;
                else
                        /* Let's not bother reading from HEAD tree */
                        mode = S_IFREG | 0644;
@@ -2369,7 +2366,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
         * bottom commits we would reach while traversing as
         * uninteresting.
         */
-       prepare_revision_walk(&revs);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
 
        if (is_null_sha1(sb.final->object.sha1)) {
                char *buf;
index e414c8898317736d632064d6c0545d7d128c6bc8..5bc4526f645d40a4a32c50240975ad26f49b8e3b 100644 (file)
@@ -12,6 +12,7 @@
 #include "builtin.h"
 #include "remote.h"
 #include "parse-options.h"
+#include "branch.h"
 
 static const char * const builtin_branch_usage[] = {
        "git-branch [options] [-r | -a]",
@@ -29,9 +30,7 @@ static const char * const builtin_branch_usage[] = {
 static const char *head;
 static unsigned char head_sha1[20];
 
-static int branch_track = 1;
-
-static int branch_use_color;
+static int branch_use_color = -1;
 static char branch_colors[][COLOR_MAXLEN] = {
        "\033[m",       /* reset */
        "",             /* PLAIN (normal) */
@@ -75,16 +74,12 @@ static int git_branch_config(const char *var, const char *value)
                color_parse(value, var, branch_colors[slot]);
                return 0;
        }
-       if (!strcmp(var, "branch.autosetupmerge")) {
-               branch_track = git_config_bool(var, value);
-               return 0;
-       }
-       return git_default_config(var, value);
+       return git_color_default_config(var, value);
 }
 
 static const char *branch_get_color(enum color_branch ix)
 {
-       if (branch_use_color)
+       if (branch_use_color > 0)
                return branch_colors[ix];
        return "";
 }
@@ -126,8 +121,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                        continue;
                }
 
-               if (name)
-                       free(name);
+               free(name);
 
                name = xstrdup(mkpath(fmt, argv[i]));
                if (!resolve_ref(name, sha1, 1, NULL)) {
@@ -172,8 +166,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                }
        }
 
-       if (name)
-               free(name);
+       free(name);
 
        return(ret);
 }
@@ -359,141 +352,6 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
        free_ref_list(&ref_list);
 }
 
-struct tracking {
-       struct refspec spec;
-       char *src;
-       const char *remote;
-       int matches;
-};
-
-static int find_tracked_branch(struct remote *remote, void *priv)
-{
-       struct tracking *tracking = priv;
-
-       if (!remote_find_tracking(remote, &tracking->spec)) {
-               if (++tracking->matches == 1) {
-                       tracking->src = tracking->spec.src;
-                       tracking->remote = remote->name;
-               } else {
-                       free(tracking->spec.src);
-                       if (tracking->src) {
-                               free(tracking->src);
-                               tracking->src = NULL;
-                       }
-               }
-               tracking->spec.src = NULL;
-       }
-
-       return 0;
-}
-
-
-/*
- * This is called when new_ref is branched off of orig_ref, and tries
- * to infer the settings for branch.<new_ref>.{remote,merge} from the
- * config.
- */
-static int setup_tracking(const char *new_ref, const char *orig_ref)
-{
-       char key[1024];
-       struct tracking tracking;
-
-       if (strlen(new_ref) > 1024 - 7 - 7 - 1)
-               return error("Tracking not set up: name too long: %s",
-                               new_ref);
-
-       memset(&tracking, 0, sizeof(tracking));
-       tracking.spec.dst = (char *)orig_ref;
-       if (for_each_remote(find_tracked_branch, &tracking) ||
-                       !tracking.matches)
-               return 1;
-
-       if (tracking.matches > 1)
-               return error("Not tracking: ambiguous information for ref %s",
-                               orig_ref);
-
-       if (tracking.matches == 1) {
-               sprintf(key, "branch.%s.remote", new_ref);
-               git_config_set(key, tracking.remote ?  tracking.remote : ".");
-               sprintf(key, "branch.%s.merge", new_ref);
-               git_config_set(key, tracking.src);
-               free(tracking.src);
-               printf("Branch %s set up to track remote branch %s.\n",
-                              new_ref, orig_ref);
-       }
-
-       return 0;
-}
-
-static void create_branch(const char *name, const char *start_name,
-                         int force, int reflog, int track)
-{
-       struct ref_lock *lock;
-       struct commit *commit;
-       unsigned char sha1[20];
-       char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
-       int forcing = 0;
-
-       snprintf(ref, sizeof ref, "refs/heads/%s", name);
-       if (check_ref_format(ref))
-               die("'%s' is not a valid branch name.", name);
-
-       if (resolve_ref(ref, sha1, 1, NULL)) {
-               if (!force)
-                       die("A branch named '%s' already exists.", name);
-               else if (!is_bare_repository() && !strcmp(head, name))
-                       die("Cannot force update the current branch.");
-               forcing = 1;
-       }
-
-       real_ref = NULL;
-       if (get_sha1(start_name, sha1))
-               die("Not a valid object name: '%s'.", start_name);
-
-       switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
-       case 0:
-               /* Not branching from any existing branch */
-               real_ref = NULL;
-               break;
-       case 1:
-               /* Unique completion -- good */
-               break;
-       default:
-               die("Ambiguous object name: '%s'.", start_name);
-               break;
-       }
-
-       if ((commit = lookup_commit_reference(sha1)) == NULL)
-               die("Not a valid branch point: '%s'.", start_name);
-       hashcpy(sha1, commit->object.sha1);
-
-       lock = lock_any_ref_for_update(ref, NULL, 0);
-       if (!lock)
-               die("Failed to lock ref for update: %s.", strerror(errno));
-
-       if (reflog)
-               log_all_ref_updates = 1;
-
-       if (forcing)
-               snprintf(msg, sizeof msg, "branch: Reset from %s",
-                        start_name);
-       else
-               snprintf(msg, sizeof msg, "branch: Created from %s",
-                        start_name);
-
-       /* When branching off a remote branch, set up so that git-pull
-          automatically merges from there.  So far, this is only done for
-          remotes registered via .git/config.  */
-       if (real_ref && track)
-               setup_tracking(name, real_ref);
-
-       if (write_ref_sha1(lock, sha1, msg) < 0)
-               die("Failed to write ref: %s.", strerror(errno));
-
-       if (real_ref)
-               free(real_ref);
-}
-
 static void rename_branch(const char *oldname, const char *newname, int force)
 {
        char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
@@ -554,14 +412,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, force_create = 0;
        int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
-       int reflog = 0, track;
+       int reflog = 0;
+       enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
        struct commit_list *with_commit = NULL;
 
        struct option options[] = {
                OPT_GROUP("Generic options"),
                OPT__VERBOSE(&verbose),
-               OPT_BOOLEAN( 0 , "track",  &track, "set up tracking mode (see git-pull(1))"),
+               OPT_SET_INT( 0 , "track",  &track, "set up tracking mode (see git-pull(1))",
+                       BRANCH_TRACK_EXPLICIT),
                OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
                OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
@@ -588,7 +448,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        };
 
        git_config(git_branch_config);
-       track = branch_track;
+
+       if (branch_use_color == -1)
+               branch_use_color = git_use_color_default;
+
+       track = git_branch_track;
        argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
        if (!!delete + !!rename + !!force_create > 1)
                usage_with_options(builtin_branch_usage, options);
@@ -614,7 +478,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        else if (rename && (argc == 2))
                rename_branch(argv[0], argv[1], rename > 1);
        else if (argc <= 2)
-               create_branch(argv[0], (argc == 2) ? argv[1] : head,
+               create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
                              force_create, reflog, track);
        else
                usage_with_options(builtin_branch_usage, options);
diff --git a/builtin-checkout.c b/builtin-checkout.c
new file mode 100644 (file)
index 0000000..6b08016
--- /dev/null
@@ -0,0 +1,568 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "unpack-trees.h"
+#include "dir.h"
+#include "run-command.h"
+#include "merge-recursive.h"
+#include "branch.h"
+#include "diff.h"
+#include "revision.h"
+#include "remote.h"
+
+static const char * const checkout_usage[] = {
+       "git checkout [options] <branch>",
+       "git checkout [options] [<branch>] -- <file>...",
+       NULL,
+};
+
+static int post_checkout_hook(struct commit *old, struct commit *new,
+                             int changed)
+{
+       struct child_process proc;
+       const char *name = git_path("hooks/post-checkout");
+       const char *argv[5];
+
+       if (access(name, X_OK) < 0)
+               return 0;
+
+       memset(&proc, 0, sizeof(proc));
+       argv[0] = name;
+       argv[1] = xstrdup(sha1_to_hex(old->object.sha1));
+       argv[2] = xstrdup(sha1_to_hex(new->object.sha1));
+       argv[3] = changed ? "1" : "0";
+       argv[4] = NULL;
+       proc.argv = argv;
+       proc.no_stdin = 1;
+       proc.stdout_to_stderr = 1;
+       return run_command(&proc);
+}
+
+static int update_some(const unsigned char *sha1, const char *base, int baselen,
+                      const char *pathname, unsigned mode, int stage)
+{
+       int len;
+       struct cache_entry *ce;
+
+       if (S_ISGITLINK(mode))
+               return 0;
+
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
+
+       len = baselen + strlen(pathname);
+       ce = xcalloc(1, cache_entry_size(len));
+       hashcpy(ce->sha1, sha1);
+       memcpy(ce->name, base, baselen);
+       memcpy(ce->name + baselen, pathname, len - baselen);
+       ce->ce_flags = create_ce_flags(len, 0);
+       ce->ce_mode = create_ce_mode(mode);
+       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+       return 0;
+}
+
+static int read_tree_some(struct tree *tree, const char **pathspec)
+{
+       read_tree_recursive(tree, "", 0, 0, pathspec, update_some);
+
+       /* update the index with the given tree's info
+        * for all args, expanding wildcards, and exit
+        * with any non-zero return code.
+        */
+       return 0;
+}
+
+static int checkout_paths(struct tree *source_tree, const char **pathspec)
+{
+       int pos;
+       struct checkout state;
+       static char *ps_matched;
+       unsigned char rev[20];
+       int flag;
+       struct commit *head;
+
+       int newfd;
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+       newfd = hold_locked_index(lock_file, 1);
+       read_cache();
+
+       if (source_tree)
+               read_tree_some(source_tree, pathspec);
+
+       for (pos = 0; pathspec[pos]; pos++)
+               ;
+       ps_matched = xcalloc(1, pos);
+
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               pathspec_match(pathspec, ps_matched, ce->name, 0);
+       }
+
+       if (report_path_error(ps_matched, pathspec, 0))
+               return 1;
+
+       memset(&state, 0, sizeof(state));
+       state.force = 1;
+       state.refresh_cache = 1;
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+                       checkout_entry(ce, &state, NULL);
+               }
+       }
+
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(lock_file))
+               die("unable to write new index file");
+
+       resolve_ref("HEAD", rev, 0, &flag);
+       head = lookup_commit_reference_gently(rev, 1);
+
+       return post_checkout_hook(head, head, 0);
+}
+
+static void show_local_changes(struct object *head)
+{
+       struct rev_info rev;
+       /* I think we want full paths, even if we're in a subdirectory. */
+       init_revisions(&rev, NULL);
+       rev.abbrev = 0;
+       rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+       add_pending_object(&rev, head, NULL);
+       run_diff_index(&rev, 0);
+}
+
+static void describe_detached_head(char *msg, struct commit *commit)
+{
+       struct strbuf sb;
+       strbuf_init(&sb, 0);
+       parse_commit(commit);
+       pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, "", "", 0, 0);
+       fprintf(stderr, "%s %s... %s\n", msg,
+               find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
+       strbuf_release(&sb);
+}
+
+static int reset_to_new(struct tree *tree, int quiet)
+{
+       struct unpack_trees_options opts;
+       struct tree_desc tree_desc;
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = -1;
+       opts.update = 1;
+       opts.reset = 1;
+       opts.merge = 1;
+       opts.fn = oneway_merge;
+       opts.verbose_update = !quiet;
+       parse_tree(tree);
+       init_tree_desc(&tree_desc, tree->buffer, tree->size);
+       if (unpack_trees(1, &tree_desc, &opts))
+               return 128;
+       return 0;
+}
+
+static void reset_clean_to_new(struct tree *tree, int quiet)
+{
+       struct unpack_trees_options opts;
+       struct tree_desc tree_desc;
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = -1;
+       opts.skip_unmerged = 1;
+       opts.reset = 1;
+       opts.merge = 1;
+       opts.fn = oneway_merge;
+       opts.verbose_update = !quiet;
+       parse_tree(tree);
+       init_tree_desc(&tree_desc, tree->buffer, tree->size);
+       if (unpack_trees(1, &tree_desc, &opts))
+               exit(128);
+}
+
+struct checkout_opts {
+       int quiet;
+       int merge;
+       int force;
+
+       char *new_branch;
+       int new_branch_log;
+       enum branch_track track;
+};
+
+struct branch_info {
+       const char *name; /* The short name used */
+       const char *path; /* The full name of a real branch */
+       struct commit *commit; /* The named commit */
+};
+
+static void setup_branch_path(struct branch_info *branch)
+{
+       struct strbuf buf;
+       strbuf_init(&buf, 0);
+       strbuf_addstr(&buf, "refs/heads/");
+       strbuf_addstr(&buf, branch->name);
+       branch->path = strbuf_detach(&buf, NULL);
+}
+
+static int merge_working_tree(struct checkout_opts *opts,
+                             struct branch_info *old, struct branch_info *new)
+{
+       int ret;
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+       int newfd = hold_locked_index(lock_file, 1);
+       read_cache();
+
+       if (opts->force) {
+               ret = reset_to_new(new->commit->tree, opts->quiet);
+               if (ret)
+                       return ret;
+       } else {
+               struct tree_desc trees[2];
+               struct tree *tree;
+               struct unpack_trees_options topts;
+               memset(&topts, 0, sizeof(topts));
+               topts.head_idx = -1;
+
+               refresh_cache(REFRESH_QUIET);
+
+               if (unmerged_cache()) {
+                       error("you need to resolve your current index first");
+                       return 1;
+               }
+
+               /* 2-way merge to the new branch */
+               topts.update = 1;
+               topts.merge = 1;
+               topts.gently = opts->merge;
+               topts.verbose_update = !opts->quiet;
+               topts.fn = twoway_merge;
+               topts.dir = xcalloc(1, sizeof(*topts.dir));
+               topts.dir->show_ignored = 1;
+               topts.dir->exclude_per_dir = ".gitignore";
+               tree = parse_tree_indirect(old->commit->object.sha1);
+               init_tree_desc(&trees[0], tree->buffer, tree->size);
+               tree = parse_tree_indirect(new->commit->object.sha1);
+               init_tree_desc(&trees[1], tree->buffer, tree->size);
+
+               if (unpack_trees(2, trees, &topts)) {
+                       /*
+                        * Unpack couldn't do a trivial merge; either
+                        * give up or do a real merge, depending on
+                        * whether the merge flag was used.
+                        */
+                       struct tree *result;
+                       struct tree *work;
+                       if (!opts->merge)
+                               return 1;
+                       parse_commit(old->commit);
+
+                       /* Do more real merge */
+
+                       /*
+                        * We update the index fully, then write the
+                        * tree from the index, then merge the new
+                        * branch with the current tree, with the old
+                        * branch as the base. Then we reset the index
+                        * (but not the working tree) to the new
+                        * branch, leaving the working tree as the
+                        * merged version, but skipping unmerged
+                        * entries in the index.
+                        */
+
+                       add_files_to_cache(0, NULL, NULL);
+                       work = write_tree_from_memory();
+
+                       ret = reset_to_new(new->commit->tree, opts->quiet);
+                       if (ret)
+                               return ret;
+                       merge_trees(new->commit->tree, work, old->commit->tree,
+                                   new->name, "local", &result);
+                       reset_clean_to_new(new->commit->tree, opts->quiet);
+               }
+       }
+
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(lock_file))
+               die("unable to write new index file");
+
+       if (!opts->force)
+               show_local_changes(&new->commit->object);
+
+       return 0;
+}
+
+static void report_tracking(struct branch_info *new, struct checkout_opts *opts)
+{
+       /*
+        * We have switched to a new branch; is it building on
+        * top of another branch, and if so does that other branch
+        * have changes we do not have yet?
+        */
+       char *base;
+       unsigned char sha1[20];
+       struct commit *ours, *theirs;
+       char symmetric[84];
+       struct rev_info revs;
+       const char *rev_argv[10];
+       int rev_argc;
+       int num_ours, num_theirs;
+       const char *remote_msg;
+       struct branch *branch = branch_get(new->name);
+
+       /*
+        * Nothing to report unless we are marked to build on top of
+        * somebody else.
+        */
+       if (!branch || !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
+               return;
+
+       /*
+        * If what we used to build on no longer exists, there is
+        * nothing to report.
+        */
+       base = branch->merge[0]->dst;
+       if (!resolve_ref(base, sha1, 1, NULL))
+               return;
+
+       theirs = lookup_commit(sha1);
+       ours = new->commit;
+       if (!hashcmp(sha1, ours->object.sha1))
+               return; /* we are the same */
+
+       /* Run "rev-list --left-right ours...theirs" internally... */
+       rev_argc = 0;
+       rev_argv[rev_argc++] = NULL;
+       rev_argv[rev_argc++] = "--left-right";
+       rev_argv[rev_argc++] = symmetric;
+       rev_argv[rev_argc++] = "--";
+       rev_argv[rev_argc] = NULL;
+
+       strcpy(symmetric, sha1_to_hex(ours->object.sha1));
+       strcpy(symmetric + 40, "...");
+       strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
+
+       init_revisions(&revs, NULL);
+       setup_revisions(rev_argc, rev_argv, &revs, NULL);
+       prepare_revision_walk(&revs);
+
+       /* ... and count the commits on each side. */
+       num_ours = 0;
+       num_theirs = 0;
+       while (1) {
+               struct commit *c = get_revision(&revs);
+               if (!c)
+                       break;
+               if (c->object.flags & SYMMETRIC_LEFT)
+                       num_ours++;
+               else
+                       num_theirs++;
+       }
+
+       if (!prefixcmp(base, "refs/remotes/")) {
+               remote_msg = " remote";
+               base += strlen("refs/remotes/");
+       } else {
+               remote_msg = "";
+       }
+
+       if (!num_theirs)
+               printf("Your branch is ahead of the tracked%s branch '%s' "
+                      "by %d commit%s.\n",
+                      remote_msg, base,
+                      num_ours, (num_ours == 1) ? "" : "s");
+       else if (!num_ours)
+               printf("Your branch is behind the tracked%s branch '%s' "
+                      "by %d commit%s,\n"
+                      "and can be fast-forwarded.\n",
+                      remote_msg, base,
+                      num_theirs, (num_theirs == 1) ? "" : "s");
+       else
+               printf("Your branch and the tracked%s branch '%s' "
+                      "have diverged,\nand respectively "
+                      "have %d and %d different commit(s) each.\n",
+                      remote_msg, base,
+                      num_ours, num_theirs);
+}
+
+static void update_refs_for_switch(struct checkout_opts *opts,
+                                  struct branch_info *old,
+                                  struct branch_info *new)
+{
+       struct strbuf msg;
+       const char *old_desc;
+       if (opts->new_branch) {
+               create_branch(old->name, opts->new_branch, new->name, 0,
+                             opts->new_branch_log, opts->track);
+               new->name = opts->new_branch;
+               setup_branch_path(new);
+       }
+
+       strbuf_init(&msg, 0);
+       old_desc = old->name;
+       if (!old_desc)
+               old_desc = sha1_to_hex(old->commit->object.sha1);
+       strbuf_addf(&msg, "checkout: moving from %s to %s",
+                   old_desc, new->name);
+
+       if (new->path) {
+               create_symref("HEAD", new->path, msg.buf);
+               if (!opts->quiet) {
+                       if (old->path && !strcmp(new->path, old->path))
+                               fprintf(stderr, "Already on \"%s\"\n",
+                                       new->name);
+                       else
+                               fprintf(stderr, "Switched to%s branch \"%s\"\n",
+                                       opts->new_branch ? " a new" : "",
+                                       new->name);
+               }
+       } else if (strcmp(new->name, "HEAD")) {
+               update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+                          REF_NODEREF, DIE_ON_ERR);
+               if (!opts->quiet) {
+                       if (old->path)
+                               fprintf(stderr, "Note: moving to \"%s\" which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n  git checkout -b <new_branch_name>\n", new->name);
+                       describe_detached_head("HEAD is now at", new->commit);
+               }
+       }
+       remove_branch_state();
+       strbuf_release(&msg);
+       if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+               report_tracking(new, opts);
+}
+
+static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
+{
+       int ret = 0;
+       struct branch_info old;
+       unsigned char rev[20];
+       int flag;
+       memset(&old, 0, sizeof(old));
+       old.path = resolve_ref("HEAD", rev, 0, &flag);
+       old.commit = lookup_commit_reference_gently(rev, 1);
+       if (!(flag & REF_ISSYMREF))
+               old.path = NULL;
+
+       if (old.path && !prefixcmp(old.path, "refs/heads/"))
+               old.name = old.path + strlen("refs/heads/");
+
+       if (!new->name) {
+               new->name = "HEAD";
+               new->commit = old.commit;
+               if (!new->commit)
+                       die("You are on a branch yet to be born");
+               parse_commit(new->commit);
+       }
+
+       /*
+        * If the new thing isn't a branch and isn't HEAD and we're
+        * not starting a new branch, and we want messages, and we
+        * weren't on a branch, and we're moving to a new commit,
+        * describe the old commit.
+        */
+       if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch &&
+           !opts->quiet && !old.path && new->commit != old.commit)
+               describe_detached_head("Previous HEAD position was", old.commit);
+
+       if (!old.commit) {
+               if (!opts->quiet) {
+                       fprintf(stderr, "warning: You appear to be on a branch yet to be born.\n");
+                       fprintf(stderr, "warning: Forcing checkout of %s.\n", new->name);
+               }
+               opts->force = 1;
+       }
+
+       ret = merge_working_tree(opts, &old, new);
+       if (ret)
+               return ret;
+
+       update_refs_for_switch(opts, &old, new);
+
+       return post_checkout_hook(old.commit, new->commit, 1);
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+       struct checkout_opts opts;
+       unsigned char rev[20];
+       const char *arg;
+       struct branch_info new;
+       struct tree *source_tree = NULL;
+       struct option options[] = {
+               OPT__QUIET(&opts.quiet),
+               OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
+               OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
+               OPT_SET_INT( 0 , "track",  &opts.track, "track",
+                       BRANCH_TRACK_EXPLICIT),
+               OPT_BOOLEAN('f', NULL, &opts.force, "force"),
+               OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
+               OPT_END(),
+       };
+
+       memset(&opts, 0, sizeof(opts));
+       memset(&new, 0, sizeof(new));
+
+       git_config(git_default_config);
+
+       opts.track = git_branch_track;
+
+       argc = parse_options(argc, argv, options, checkout_usage, 0);
+       if (argc) {
+               arg = argv[0];
+               if (get_sha1(arg, rev))
+                       ;
+               else if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
+                       new.name = arg;
+                       setup_branch_path(&new);
+                       if (resolve_ref(new.path, rev, 1, NULL))
+                               new.commit = lookup_commit_reference(rev);
+                       else
+                               new.path = NULL;
+                       parse_commit(new.commit);
+                       source_tree = new.commit->tree;
+                       argv++;
+                       argc--;
+               } else if ((source_tree = parse_tree_indirect(rev))) {
+                       argv++;
+                       argc--;
+               }
+       }
+
+       if (argc && !strcmp(argv[0], "--")) {
+               argv++;
+               argc--;
+       }
+
+       if (!opts.new_branch && (opts.track != git_branch_track))
+               die("git checkout: --track and --no-track require -b");
+
+       if (opts.force && opts.merge)
+               die("git checkout: -f and -m are incompatible");
+
+       if (argc) {
+               const char **pathspec = get_pathspec(prefix, argv);
+
+               if (!pathspec)
+                       die("invalid path specification");
+
+               /* Checkout paths */
+               if (opts.new_branch || opts.force || opts.merge) {
+                       if (argc == 1) {
+                               die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+                       } else {
+                               die("git checkout: updating paths is incompatible with switching branches/forcing");
+                       }
+               }
+
+               return checkout_paths(source_tree, pathspec);
+       }
+
+       if (new.name && !new.commit) {
+               die("Cannot switch branch to a non-commit.");
+       }
+
+       return switch_branches(&opts, &new);
+}
index eb853a37cf993a875f2acd1992ee5191783740a1..fefec3010c434219234e7832051ada9ed2e52335 100644 (file)
@@ -10,6 +10,7 @@
 #include "cache.h"
 #include "dir.h"
 #include "parse-options.h"
+#include "quote.h"
 
 static int force = -1; /* unset */
 
@@ -29,12 +30,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 {
        int i;
        int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
-       int ignored_only = 0, baselen = 0, config_set = 0;
+       int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
        struct strbuf directory;
        struct dir_struct dir;
        const char *path, *base;
        static const char **pathspec;
-       int prefix_offset = 0;
+       struct strbuf buf;
+       const char *qname;
        char *seen = NULL;
        struct option options[] = {
                OPT__QUIET(&quiet),
@@ -56,6 +58,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 
        argc = parse_options(argc, argv, options, builtin_clean_usage, 0);
 
+       strbuf_init(&buf, 0);
        memset(&dir, 0, sizeof(dir));
        if (ignored_only)
                dir.show_ignored = 1;
@@ -72,8 +75,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
        if (!ignored)
                setup_standard_excludes(&dir);
 
-       if (prefix)
-               prefix_offset = strlen(prefix);
        pathspec = get_pathspec(prefix, argv);
        read_cache();
 
@@ -134,39 +135,40 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 
                if (S_ISDIR(st.st_mode)) {
                        strbuf_addstr(&directory, ent->name);
+                       qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
                        if (show_only && (remove_directories || matches)) {
-                               printf("Would remove %s\n",
-                                      directory.buf + prefix_offset);
-                       } else if (quiet && (remove_directories || matches)) {
-                               remove_dir_recursively(&directory, 0);
+                               printf("Would remove %s\n", qname);
                        } else if (remove_directories || matches) {
-                               printf("Removing %s\n",
-                                      directory.buf + prefix_offset);
-                               remove_dir_recursively(&directory, 0);
+                               if (!quiet)
+                                       printf("Removing %s\n", qname);
+                               if (remove_dir_recursively(&directory, 0) != 0) {
+                                       warning("failed to remove '%s'", qname);
+                                       errors++;
+                               }
                        } else if (show_only) {
-                               printf("Would not remove %s\n",
-                                      directory.buf + prefix_offset);
+                               printf("Would not remove %s\n", qname);
                        } else {
-                               printf("Not removing %s\n",
-                                      directory.buf + prefix_offset);
+                               printf("Not removing %s\n", qname);
                        }
                        strbuf_reset(&directory);
                } else {
                        if (pathspec && !matches)
                                continue;
+                       qname = quote_path_relative(ent->name, -1, &buf, prefix);
                        if (show_only) {
-                               printf("Would remove %s\n",
-                                      ent->name + prefix_offset);
+                               printf("Would remove %s\n", qname);
                                continue;
                        } else if (!quiet) {
-                               printf("Removing %s\n",
-                                      ent->name + prefix_offset);
+                               printf("Removing %s\n", qname);
+                       }
+                       if (unlink(ent->name) != 0) {
+                               warning("failed to remove '%s'", qname);
+                               errors++;
                        }
-                       unlink(ent->name);
                }
        }
        free(seen);
 
        strbuf_release(&directory);
-       return 0;
+       return (errors != 0);
 }
index 45232a11c408530e7ca011f7262532df236c2576..f49c22e64255225e492614bb628c1d1776521424 100644 (file)
@@ -7,6 +7,7 @@
 
 #include "cache.h"
 #include "cache-tree.h"
+#include "color.h"
 #include "dir.h"
 #include "builtin.h"
 #include "diff.h"
@@ -160,7 +161,7 @@ static int list_paths(struct path_list *list, const char *with_tree,
 
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
-               if (ce->ce_flags & htons(CE_UPDATE))
+               if (ce->ce_flags & CE_UPDATE)
                        continue;
                if (!pathspec_match(pattern, m, ce->name, 0))
                        continue;
@@ -204,7 +205,8 @@ static void create_base_index(void)
                die("failed to unpack HEAD tree object");
        parse_tree(tree);
        init_tree_desc(&t, tree->buffer, tree->size);
-       unpack_trees(1, &t, &opts);
+       if (unpack_trees(1, &t, &opts))
+               exit(128); /* We've already reported the error, finish dying */
 }
 
 static char *prepare_index(int argc, const char **argv, const char *prefix)
@@ -347,45 +349,107 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
        return s.commitable;
 }
 
+static int run_hook(const char *index_file, const char *name, ...)
+{
+       struct child_process hook;
+       const char *argv[10], *env[2];
+       char index[PATH_MAX];
+       va_list args;
+       int i;
+
+       va_start(args, name);
+       argv[0] = git_path("hooks/%s", name);
+       i = 0;
+       do {
+               if (++i >= ARRAY_SIZE(argv))
+                       die ("run_hook(): too many arguments");
+               argv[i] = va_arg(args, const char *);
+       } while (argv[i]);
+       va_end(args);
+
+       snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+       env[0] = index;
+       env[1] = NULL;
+
+       if (access(argv[0], X_OK) < 0)
+               return 0;
+
+       memset(&hook, 0, sizeof(hook));
+       hook.argv = argv;
+       hook.no_stdin = 1;
+       hook.stdout_to_stderr = 1;
+       hook.env = env;
+
+       return run_command(&hook);
+}
+
+static int is_a_merge(const unsigned char *sha1)
+{
+       struct commit *commit = lookup_commit(sha1);
+       if (!commit || parse_commit(commit))
+               die("could not parse HEAD commit");
+       return !!(commit->parents && commit->parents->next);
+}
+
 static const char sign_off_header[] = "Signed-off-by: ";
 
-static int prepare_log_message(const char *index_file, const char *prefix)
+static int prepare_to_commit(const char *index_file, const char *prefix)
 {
        struct stat statbuf;
        int commitable, saved_color_setting;
        struct strbuf sb;
        char *buffer;
        FILE *fp;
+       const char *hook_arg1 = NULL;
+       const char *hook_arg2 = NULL;
+
+       if (!no_verify && run_hook(index_file, "pre-commit", NULL))
+               return 0;
 
        strbuf_init(&sb, 0);
        if (message.len) {
                strbuf_addbuf(&sb, &message);
+               hook_arg1 = "message";
        } else if (logfile && !strcmp(logfile, "-")) {
                if (isatty(0))
                        fprintf(stderr, "(reading log message from standard input)\n");
                if (strbuf_read(&sb, 0, 0) < 0)
                        die("could not read log from standard input");
+               hook_arg1 = "message";
        } else if (logfile) {
                if (strbuf_read_file(&sb, logfile, 0) < 0)
                        die("could not read log file '%s': %s",
                            logfile, strerror(errno));
+               hook_arg1 = "message";
        } else if (use_message) {
                buffer = strstr(use_message_buffer, "\n\n");
                if (!buffer || buffer[2] == '\0')
                        die("commit has empty message");
                strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+               hook_arg1 = "commit";
+               hook_arg2 = use_message;
        } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
                if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
                        die("could not read MERGE_MSG: %s", strerror(errno));
+               hook_arg1 = "merge";
        } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
                if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
                        die("could not read SQUASH_MSG: %s", strerror(errno));
+               hook_arg1 = "squash";
        } else if (template_file && !stat(template_file, &statbuf)) {
                if (strbuf_read_file(&sb, template_file, 0) < 0)
                        die("could not read %s: %s",
                            template_file, strerror(errno));
+               hook_arg1 = "template";
        }
 
+       /*
+        * This final case does not modify the template message,
+        * it just sets the argument to the prepare-commit-msg hook.
+        */
+       else if (in_merge)
+               hook_arg1 = "merge";
+
        fp = fopen(git_path(commit_editmsg), "w");
        if (fp == NULL)
                die("could not open %s", git_path(commit_editmsg));
@@ -417,13 +481,38 @@ static int prepare_log_message(const char *index_file, const char *prefix)
 
        strbuf_release(&sb);
 
-       if (!use_editor) {
+       if (use_editor) {
+               if (in_merge)
+                       fprintf(fp,
+                               "#\n"
+                               "# It looks like you may be committing a MERGE.\n"
+                               "# If this is not correct, please remove the file\n"
+                               "#      %s\n"
+                               "# and try again.\n"
+                               "#\n",
+                               git_path("MERGE_HEAD"));
+
+               fprintf(fp,
+                       "\n"
+                       "# Please enter the commit message for your changes.\n"
+                       "# (Comment lines starting with '#' will ");
+               if (cleanup_mode == CLEANUP_ALL)
+                       fprintf(fp, "not be included)\n");
+               else /* CLEANUP_SPACE, that is. */
+                       fprintf(fp, "be kept.\n"
+                               "# You can remove them yourself if you want to)\n");
+               if (only_include_assumed)
+                       fprintf(fp, "# %s\n", only_include_assumed);
+
+               saved_color_setting = wt_status_use_color;
+               wt_status_use_color = 0;
+               commitable = run_status(fp, index_file, prefix, 1);
+               wt_status_use_color = saved_color_setting;
+       } else {
                struct rev_info rev;
                unsigned char sha1[20];
                const char *parent = "HEAD";
 
-               fclose(fp);
-
                if (!active_nr && read_cache() < 0)
                        die("Cannot read index");
 
@@ -431,48 +520,60 @@ static int prepare_log_message(const char *index_file, const char *prefix)
                        parent = "HEAD^1";
 
                if (get_sha1(parent, sha1))
-                       return !!active_nr;
+                       commitable = !!active_nr;
+               else {
+                       init_revisions(&rev, "");
+                       rev.abbrev = 0;
+                       setup_revisions(0, NULL, &rev, parent);
+                       DIFF_OPT_SET(&rev.diffopt, QUIET);
+                       DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
+                       run_diff_index(&rev, 1 /* cached */);
+
+                       commitable = !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
+               }
+       }
 
-               init_revisions(&rev, "");
-               rev.abbrev = 0;
-               setup_revisions(0, NULL, &rev, parent);
-               DIFF_OPT_SET(&rev.diffopt, QUIET);
-               DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
-               run_diff_index(&rev, 1 /* cached */);
+       fclose(fp);
 
-               return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
+       if (!commitable && !in_merge && !allow_empty &&
+           !(amend && is_a_merge(head_sha1))) {
+               run_status(stdout, index_file, prefix, 0);
+               unlink(commit_editmsg);
+               return 0;
        }
 
-       if (in_merge)
-               fprintf(fp,
-                       "#\n"
-                       "# It looks like you may be committing a MERGE.\n"
-                       "# If this is not correct, please remove the file\n"
-                       "#      %s\n"
-                       "# and try again.\n"
-                       "#\n",
-                       git_path("MERGE_HEAD"));
-
-       fprintf(fp,
-               "\n"
-               "# Please enter the commit message for your changes.\n"
-               "# (Comment lines starting with '#' will ");
-       if (cleanup_mode == CLEANUP_ALL)
-               fprintf(fp, "not be included)\n");
-       else /* CLEANUP_SPACE, that is. */
-               fprintf(fp, "be kept.\n"
-                       "# You can remove them yourself if you want to)\n");
-       if (only_include_assumed)
-               fprintf(fp, "# %s\n", only_include_assumed);
-
-       saved_color_setting = wt_status_use_color;
-       wt_status_use_color = 0;
-       commitable = run_status(fp, index_file, prefix, 1);
-       wt_status_use_color = saved_color_setting;
+       /*
+        * Re-read the index as pre-commit hook could have updated it,
+        * and write it out as a tree.  We must do this before we invoke
+        * the editor and after we invoke run_status above.
+        */
+       discard_cache();
+       read_cache_from(index_file);
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+       if (cache_tree_update(active_cache_tree,
+                             active_cache, active_nr, 0, 0) < 0) {
+               error("Error building trees");
+               return 0;
+       }
 
-       fclose(fp);
+       if (run_hook(index_file, "prepare-commit-msg",
+                    git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
+               return 0;
+
+       if (use_editor) {
+               char index[PATH_MAX];
+               const char *env[2] = { index, NULL };
+               snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+               launch_editor(git_path(commit_editmsg), NULL, env);
+       }
 
-       return commitable;
+       if (!no_verify &&
+           run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
+               return 0;
+       }
+
+       return 1;
 }
 
 /*
@@ -569,6 +670,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                use_editor = 0;
        if (edit_flag)
                use_editor = 1;
+       if (!use_editor)
+               setenv("GIT_EDITOR", ":", 1);
 
        if (get_sha1("HEAD", head_sha1))
                initial_commit = 1;
@@ -670,6 +773,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 
        git_config(git_status_config);
 
+       if (wt_status_use_color == -1)
+               wt_status_use_color = git_use_color_default;
+
        argc = parse_and_validate_options(argc, argv, builtin_status_usage);
 
        index_file = prepare_index(argc, argv, prefix);
@@ -681,31 +787,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        return commitable ? 0 : 1;
 }
 
-static int run_hook(const char *index_file, const char *name, const char *arg)
-{
-       struct child_process hook;
-       const char *argv[3], *env[2];
-       char index[PATH_MAX];
-
-       argv[0] = git_path("hooks/%s", name);
-       argv[1] = arg;
-       argv[2] = NULL;
-       snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
-       env[0] = index;
-       env[1] = NULL;
-
-       if (access(argv[0], X_OK) < 0)
-               return 0;
-
-       memset(&hook, 0, sizeof(hook));
-       hook.argv = argv;
-       hook.no_stdin = 1;
-       hook.stdout_to_stderr = 1;
-       hook.env = env;
-
-       return run_command(&hook);
-}
-
 static void print_summary(const char *prefix, const unsigned char *sha1)
 {
        struct rev_info rev;
@@ -756,14 +837,6 @@ int git_commit_config(const char *k, const char *v)
        return git_status_config(k, v);
 }
 
-static int is_a_merge(const unsigned char *sha1)
-{
-       struct commit *commit = lookup_commit(sha1);
-       if (!commit || parse_commit(commit))
-               die("could not parse HEAD commit");
-       return !!(commit->parents && commit->parents->next);
-}
-
 static const char commit_utf8_warn[] =
 "Warning: commit message does not conform to UTF-8.\n"
 "You may want to amend it after fixing the message, or set the config\n"
@@ -795,33 +868,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
        index_file = prepare_index(argc, argv, prefix);
 
-       if (!no_verify && run_hook(index_file, "pre-commit", NULL)) {
+       /* Set up everything for writing the commit object.  This includes
+          running hooks, writing the trees, and interacting with the user.  */
+       if (!prepare_to_commit(index_file, prefix)) {
                rollback_index_files();
                return 1;
        }
 
-       if (!prepare_log_message(index_file, prefix) && !in_merge &&
-           !allow_empty && !(amend && is_a_merge(head_sha1))) {
-               run_status(stdout, index_file, prefix, 0);
-               rollback_index_files();
-               unlink(commit_editmsg);
-               return 1;
-       }
-
-       /*
-        * Re-read the index as pre-commit hook could have updated it,
-        * and write it out as a tree.
-        */
-       discard_cache();
-       read_cache_from(index_file);
-       if (!active_cache_tree)
-               active_cache_tree = cache_tree();
-       if (cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0) < 0) {
-               rollback_index_files();
-               die("Error building trees");
-       }
-
        /*
         * The commit object
         */
@@ -873,19 +926,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                strbuf_addf(&sb, "encoding %s\n", git_commit_encoding);
        strbuf_addch(&sb, '\n');
 
-       /* Get the commit message and validate it */
+       /* Finally, get the commit message */
        header_len = sb.len;
-       if (use_editor) {
-               char index[PATH_MAX];
-               const char *env[2] = { index, NULL };
-               snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
-               launch_editor(git_path(commit_editmsg), NULL, env);
-       }
-       if (!no_verify &&
-           run_hook(index_file, "commit-msg", git_path(commit_editmsg))) {
-               rollback_index_files();
-               exit(1);
-       }
        if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
                rollback_index_files();
                die("could not read commit message");
index 077d8ef2df4ae38d668293cc35810b39e0488f6b..2b9a4261d4e98281855ce856041eb730565a4869 100644 (file)
@@ -79,9 +79,10 @@ static int get_value(const char* key_, const char* regex_)
                local = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!local)
                        local = repo_config = xstrdup(git_path("config"));
-               if (home)
+               if (git_config_global() && home)
                        global = xstrdup(mkpath("%s/.gitconfig", home));
-               system_wide = git_etc_gitconfig();
+               if (git_config_system())
+                       system_wide = git_etc_gitconfig();
        }
 
        key = xstrdup(key_);
index 7a148a2c26591d82e6057d610182445eae5fe171..df554b30af3ddcb4d0141838c06f82d433cbf3a4 100644 (file)
@@ -17,11 +17,16 @@ static const char * const describe_usage[] = {
 static int debug;      /* Display lots of verbose info */
 static int all;        /* Default to annotated tags only */
 static int tags;       /* But allow any tags if --tags is specified */
+static int longformat;
 static int abbrev = DEFAULT_ABBREV;
 static int max_candidates = 10;
+const char *pattern = NULL;
+static int always;
 
 struct commit_name {
+       struct tag *tag;
        int prio; /* annotated tag = 2, tag = 1, head = 0 */
+       unsigned char sha1[20];
        char path[FLEX_ARRAY]; /* more */
 };
 static const char *prio_names[] = {
@@ -30,14 +35,17 @@ static const char *prio_names[] = {
 
 static void add_to_known_names(const char *path,
                               struct commit *commit,
-                              int prio)
+                              int prio,
+                              const unsigned char *sha1)
 {
        struct commit_name *e = commit->util;
        if (!e || e->prio < prio) {
                size_t len = strlen(path)+1;
                free(e);
                e = xmalloc(sizeof(struct commit_name) + len);
+               e->tag = NULL;
                e->prio = prio;
+               hashcpy(e->sha1, sha1);
                memcpy(e->path, path, len);
                commit->util = e;
        }
@@ -45,21 +53,38 @@ static void add_to_known_names(const char *path,
 
 static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+       int might_be_tag = !prefixcmp(path, "refs/tags/");
+       struct commit *commit;
        struct object *object;
-       int prio;
+       unsigned char peeled[20];
+       int is_tag, prio;
 
-       if (!commit)
+       if (!all && !might_be_tag)
                return 0;
-       object = parse_object(sha1);
+
+       if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
+               commit = lookup_commit_reference_gently(peeled, 1);
+               if (!commit)
+                       return 0;
+               is_tag = !!hashcmp(sha1, commit->object.sha1);
+       } else {
+               commit = lookup_commit_reference_gently(sha1, 1);
+               object = parse_object(sha1);
+               if (!commit || !object)
+                       return 0;
+               is_tag = object->type == OBJ_TAG;
+       }
+
        /* If --all, then any refs are used.
         * If --tags, then any tags are used.
         * Otherwise only annotated tags are used.
         */
-       if (!prefixcmp(path, "refs/tags/")) {
-               if (object->type == OBJ_TAG)
+       if (might_be_tag) {
+               if (is_tag) {
                        prio = 2;
-               else
+                       if (pattern && fnmatch(pattern, path + 10, 0))
+                               prio = 0;
+               } else
                        prio = 1;
        }
        else
@@ -71,7 +96,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
                if (!tags && prio < 2)
                        return 0;
        }
-       add_to_known_names(all ? path + 5 : path + 10, commit, prio);
+       add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1);
        return 0;
 }
 
@@ -128,6 +153,27 @@ static unsigned long finish_depth_computation(
        return seen_commits;
 }
 
+static void display_name(struct commit_name *n)
+{
+       if (n->prio == 2 && !n->tag) {
+               n->tag = lookup_tag(n->sha1);
+               if (!n->tag || parse_tag(n->tag) || !n->tag->tag)
+                       die("annotated tag %s not available", n->path);
+               if (strcmp(n->tag->tag, n->path))
+                       warning("tag '%s' is really '%s' here", n->tag->tag, n->path);
+       }
+
+       if (n->tag)
+               printf("%s", n->tag->tag);
+       else
+               printf("%s", n->path);
+}
+
+static void show_suffix(int depth, const unsigned char *sha1)
+{
+       printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev));
+}
+
 static void describe(const char *arg, int last_one)
 {
        unsigned char sha1[20];
@@ -152,10 +198,18 @@ static void describe(const char *arg, int last_one)
 
        n = cmit->util;
        if (n) {
-               printf("%s\n", n->path);
+               /*
+                * Exact match to an existing ref.
+                */
+               display_name(n);
+               if (longformat)
+                       show_suffix(0, n->tag->tagged->sha1);
+               printf("\n");
                return;
        }
 
+       if (!max_candidates)
+               die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
        if (debug)
                fprintf(stderr, "searching to describe %s\n", arg);
 
@@ -204,8 +258,14 @@ static void describe(const char *arg, int last_one)
                }
        }
 
-       if (!match_cnt)
-               die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));
+       if (!match_cnt) {
+               const unsigned char *sha1 = cmit->object.sha1;
+               if (always) {
+                       printf("%s\n", find_unique_abbrev(sha1, abbrev));
+                       return;
+               }
+               die("cannot describe '%s'", sha1_to_hex(sha1));
+       }
 
        qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
 
@@ -232,12 +292,11 @@ static void describe(const char *arg, int last_one)
                                sha1_to_hex(gave_up_on->object.sha1));
                }
        }
-       if (abbrev == 0)
-               printf("%s\n", all_matches[0].name->path );
-       else
-               printf("%s-%d-g%s\n", all_matches[0].name->path,
-                      all_matches[0].depth,
-                      find_unique_abbrev(cmit->object.sha1, abbrev));
+
+       display_name(all_matches[0].name);
+       if (abbrev)
+               show_suffix(all_matches[0].depth, cmit->object.sha1);
+       printf("\n");
 
        if (!last_one)
                clear_commit_marks(cmit, -1);
@@ -251,27 +310,46 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN(0, "debug",      &debug, "debug search strategy on stderr"),
                OPT_BOOLEAN(0, "all",        &all, "use any ref in .git/refs"),
                OPT_BOOLEAN(0, "tags",       &tags, "use any tag in .git/refs/tags"),
+               OPT_BOOLEAN(0, "long",       &longformat, "always use long format"),
                OPT__ABBREV(&abbrev),
+               OPT_SET_INT(0, "exact-match", &max_candidates,
+                           "only output exact matches", 0),
                OPT_INTEGER(0, "candidates", &max_candidates,
-                                       "consider <n> most recent tags (default: 10)"),
+                           "consider <n> most recent tags (default: 10)"),
+               OPT_STRING(0, "match",       &pattern, "pattern",
+                          "only consider tags matching <pattern>"),
+               OPT_BOOLEAN(0, "always",     &always,
+                          "show abbreviated commit object as fallback"),
                OPT_END(),
        };
 
        argc = parse_options(argc, argv, options, describe_usage, 0);
-       if (max_candidates < 1)
-               max_candidates = 1;
+       if (max_candidates < 0)
+               max_candidates = 0;
        else if (max_candidates > MAX_TAGS)
                max_candidates = MAX_TAGS;
 
        save_commit_buffer = 0;
 
+       if (longformat && abbrev == 0)
+               die("--long is incompatible with --abbrev=0");
+
        if (contains) {
-               const char **args = xmalloc((4 + argc) * sizeof(char*));
+               const char **args = xmalloc((7 + argc) * sizeof(char*));
                int i = 0;
                args[i++] = "name-rev";
                args[i++] = "--name-only";
-               if (!all)
+               args[i++] = "--no-undefined";
+               if (always)
+                       args[i++] = "--always";
+               if (!all) {
                        args[i++] = "--tags";
+                       if (pattern) {
+                               char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
+                               sprintf(s, "--refs=refs/tags/%s", pattern);
+                               args[i++] = s;
+                       }
+               }
                memcpy(args + i, argv, argc * sizeof(char*));
                args[i + argc] = NULL;
                return cmd_name_rev(i + argc, args, prefix);
index 8d7a5697f2b429f6240a89e28422f48ef18aee3b..444ff2fd92da38ab4002af6a1882839c1c3d930a 100644 (file)
@@ -4,6 +4,7 @@
  * Copyright (c) 2006 Junio C Hamano
  */
 #include "cache.h"
+#include "color.h"
 #include "commit.h"
 #include "blob.h"
 #include "tag.h"
@@ -43,12 +44,17 @@ static void stuff_change(struct diff_options *opt,
                tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
                tmp_c = old_name; old_name = new_name; new_name = tmp_c;
        }
+
+       if (opt->prefix &&
+           (strncmp(old_name, opt->prefix, opt->prefix_length) ||
+            strncmp(new_name, opt->prefix, opt->prefix_length)))
+               return;
+
        one = alloc_filespec(old_name);
        two = alloc_filespec(new_name);
        fill_filespec(one, old_sha1, old_mode);
        fill_filespec(two, new_sha1, new_mode);
 
-       /* NEEDSWORK: shouldn't this part of diffopt??? */
        diff_queue(&diff_queued_diff, one, two);
 }
 
@@ -229,6 +235,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 
        prefix = setup_git_directory_gently(&nongit);
        git_config(git_diff_ui_config);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
        init_revisions(&rev, prefix);
        rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
 
@@ -241,6 +251,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                if (diff_setup_done(&rev.diffopt) < 0)
                        die("diff_setup_done failed");
        }
+       if (rev.diffopt.prefix && nongit) {
+               rev.diffopt.prefix = NULL;
+               rev.diffopt.prefix_length = 0;
+       }
        DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
        DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
 
index 724cff35d37a42daa85edd41b97f0ef20d01f684..e1c56303e5cb7882bc79980c4f8e6c6791ad323b 100755 (executable)
@@ -196,8 +196,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
                          ? strlen(reencoded) : message
                          ? strlen(message) : 0),
               reencoded ? reencoded : message ? message : "");
-       if (reencoded)
-               free(reencoded);
+       free(reencoded);
 
        for (i = 0, p = commit->parents; p; p = p->next) {
                int mark = get_object_mark(&p->item->object);
@@ -383,7 +382,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
 
        get_tags_and_duplicates(&revs.pending, &extra_refs);
 
-       prepare_revision_walk(&revs);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
        revs.diffopt.format_callback = show_filemodify;
        DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
        while ((commit = get_revision(&revs))) {
index e68e01592d044a2c2570f096668856eef5caa1a2..7b280242244094774c909f67d6a8ed7902389c5a 100644 (file)
@@ -7,6 +7,7 @@
 #include "pack.h"
 #include "sideband.h"
 #include "fetch-pack.h"
+#include "remote.h"
 #include "run-command.h"
 
 static int transfer_unpack_limit = -1;
@@ -17,7 +18,7 @@ static struct fetch_pack_args args = {
 };
 
 static const char fetch_pack_usage[] =
-"git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
+"git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
 
 #define COMPLETE       (1U << 0)
 #define COMMON         (1U << 1)
@@ -40,7 +41,8 @@ static void rev_list_push(struct commit *commit, int mark)
                commit->object.flags |= mark;
 
                if (!(commit->object.parsed))
-                       parse_commit(commit);
+                       if (parse_commit(commit))
+                               return;
 
                insert_by_date(commit, &rev_list);
 
@@ -82,7 +84,8 @@ static void mark_common(struct commit *commit,
                        if (!ancestors_only && !(o->flags & POPPED))
                                non_common_revs--;
                        if (!o->parsed && !dont_parse)
-                               parse_commit(commit);
+                               if (parse_commit(commit))
+                                       return;
 
                        for (parents = commit->parents;
                                        parents;
@@ -102,20 +105,20 @@ static const unsigned char* get_rev(void)
 
        while (commit == NULL) {
                unsigned int mark;
-               struct commit_list* parents;
+               struct commit_list *parents = NULL;
 
                if (rev_list == NULL || non_common_revs == 0)
                        return NULL;
 
                commit = rev_list->item;
                if (!(commit->object.parsed))
-                       parse_commit(commit);
+                       if (!parse_commit(commit))
+                               parents = commit->parents;
+
                commit->object.flags |= POPPED;
                if (!(commit->object.flags & COMMON))
                        non_common_revs--;
 
-               parents = commit->parents;
-
                if (commit->object.flags & COMMON) {
                        /* do not send "have", and ignore ancestors */
                        commit = NULL;
@@ -173,13 +176,14 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                }
 
                if (!fetching)
-                       packet_write(fd[1], "want %s%s%s%s%s%s%s\n",
+                       packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n",
                                     sha1_to_hex(remote),
                                     (multi_ack ? " multi_ack" : ""),
                                     (use_sideband == 2 ? " side-band-64k" : ""),
                                     (use_sideband == 1 ? " side-band" : ""),
                                     (args.use_thin_pack ? " thin-pack" : ""),
                                     (args.no_progress ? " no-progress" : ""),
+                                    (args.include_tag ? " include-tag" : ""),
                                     " ofs-delta");
                else
                        packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
@@ -211,7 +215,8 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                                if (!lookup_object(sha1))
                                        die("object not found: %s", line);
                                /* make sure that it is parsed as shallow */
-                               parse_object(sha1);
+                               if (!parse_object(sha1))
+                                       die("error in object: %s", line);
                                if (unregister_shallow(sha1))
                                        die("no shallow found: %s", line);
                                continue;
@@ -385,7 +390,6 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
        int retval;
        unsigned long cutoff = 0;
 
-       track_object_refs = 0;
        save_commit_buffer = 0;
 
        for (ref = *refs; ref; ref = ref->next) {
@@ -537,8 +541,10 @@ static int get_pack(int xd[2], char **pack_lockfile)
        cmd.git_cmd = 1;
        if (start_command(&cmd))
                die("fetch-pack: unable to fork off %s", argv[0]);
-       if (do_keep && pack_lockfile)
+       if (do_keep && pack_lockfile) {
                *pack_lockfile = index_pack_lockfile(cmd.out);
+               close(cmd.out);
+       }
 
        if (finish_command(&cmd))
                die("%s failed", argv[0]);
@@ -548,14 +554,14 @@ static int get_pack(int xd[2], char **pack_lockfile)
 }
 
 static struct ref *do_fetch_pack(int fd[2],
+               const struct ref *orig_ref,
                int nr_match,
                char **match,
                char **pack_lockfile)
 {
-       struct ref *ref;
+       struct ref *ref = copy_ref_list(orig_ref);
        unsigned char sha1[20];
 
-       get_remote_heads(fd[0], &ref, 0, NULL, 0);
        if (is_repository_shallow() && !server_supports("shallow"))
                die("Server does not support shallow clients");
        if (server_supports("multi_ack")) {
@@ -573,10 +579,6 @@ static struct ref *do_fetch_pack(int fd[2],
                        fprintf(stderr, "Server supports side-band\n");
                use_sideband = 1;
        }
-       if (!ref) {
-               packet_flush(fd[1]);
-               die("no matching remote head");
-       }
        if (everything_local(&ref, nr_match, match)) {
                packet_flush(fd[1]);
                goto all_done;
@@ -650,8 +652,10 @@ static void fetch_pack_setup(void)
 int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
 {
        int i, ret, nr_heads;
-       struct ref *ref;
+       struct ref *ref = NULL;
        char *dest = NULL, **heads;
+       int fd[2];
+       struct child_process *conn;
 
        nr_heads = 0;
        heads = NULL;
@@ -680,6 +684,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
                                args.use_thin_pack = 1;
                                continue;
                        }
+                       if (!strcmp("--include-tag", arg)) {
+                               args.include_tag = 1;
+                               continue;
+                       }
                        if (!strcmp("--all", arg)) {
                                args.fetch_all = 1;
                                continue;
@@ -706,9 +714,33 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        if (!dest)
                usage(fetch_pack_usage);
 
-       ref = fetch_pack(&args, dest, nr_heads, heads, NULL);
+       conn = git_connect(fd, (char *)dest, args.uploadpack,
+                          args.verbose ? CONNECT_VERBOSE : 0);
+       if (conn) {
+               get_remote_heads(fd[0], &ref, 0, NULL, 0);
+
+               ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL);
+               close(fd[0]);
+               close(fd[1]);
+               if (finish_connect(conn))
+                       ref = NULL;
+       } else {
+               ref = NULL;
+       }
        ret = !ref;
 
+       if (!ret && nr_heads) {
+               /* If the heads to pull were given, we should have
+                * consumed all of them by matching the remote.
+                * Otherwise, 'git-fetch remote no-such-ref' would
+                * silently succeed without issuing an error.
+                */
+               for (i = 0; i < nr_heads; i++)
+                       if (heads[i] && heads[i][0]) {
+                               error("no such remote ref %s", heads[i]);
+                               ret = 1;
+                       }
+       }
        while (ref) {
                printf("%s %s\n",
                       sha1_to_hex(ref->old_sha1), ref->name);
@@ -719,16 +751,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
 }
 
 struct ref *fetch_pack(struct fetch_pack_args *my_args,
+                      int fd[], struct child_process *conn,
+                      const struct ref *ref,
                const char *dest,
                int nr_heads,
                char **heads,
                char **pack_lockfile)
 {
-       int i, ret;
-       int fd[2];
-       struct child_process *conn;
-       struct ref *ref;
        struct stat st;
+       struct ref *ref_cpy;
 
        fetch_pack_setup();
        memcpy(&args, my_args, sizeof(args));
@@ -737,29 +768,15 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
                        st.st_mtime = 0;
        }
 
-       conn = git_connect(fd, (char *)dest, args.uploadpack,
-                          args.verbose ? CONNECT_VERBOSE : 0);
        if (heads && nr_heads)
                nr_heads = remove_duplicates(nr_heads, heads);
-       ref = do_fetch_pack(fd, nr_heads, heads, pack_lockfile);
-       close(fd[0]);
-       close(fd[1]);
-       ret = finish_connect(conn);
-
-       if (!ret && nr_heads) {
-               /* If the heads to pull were given, we should have
-                * consumed all of them by matching the remote.
-                * Otherwise, 'git-fetch remote no-such-ref' would
-                * silently succeed without issuing an error.
-                */
-               for (i = 0; i < nr_heads; i++)
-                       if (heads[i] && heads[i][0]) {
-                               error("no such remote ref %s", heads[i]);
-                               ret = 1;
-                       }
+       if (!ref) {
+               packet_flush(fd[1]);
+               die("no matching remote head");
        }
+       ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile);
 
-       if (!ret && args.depth > 0) {
+       if (args.depth > 0) {
                struct cache_time mtime;
                char *shallow = git_path("shallow");
                int fd;
@@ -787,8 +804,5 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
                }
        }
 
-       if (ret)
-               ref = NULL;
-
-       return ref;
+       return ref_cpy;
 }
index 320e235682340f07cb28b0a4de0c39b6bd9da383..55f611e3c26e55df43eca1f40df74d335c850cc0 100644 (file)
@@ -101,6 +101,10 @@ static void add_merge_config(struct ref **head,
        }
 }
 
+static void find_non_local_tags(struct transport *transport,
+                       struct ref **head,
+                       struct ref ***tail);
+
 static struct ref *get_ref_map(struct transport *transport,
                               struct refspec *refs, int ref_count, int tags,
                               int *autotags)
@@ -157,8 +161,11 @@ static struct ref *get_ref_map(struct transport *transport,
                        if (!ref_map)
                                die("Couldn't find remote ref HEAD");
                        ref_map->merge = 1;
+                       tail = &ref_map->next;
                }
        }
+       if (tags == TAGS_DEFAULT && *autotags)
+               find_non_local_tags(transport, &ref_map, &tail);
        ref_remove_duplicates(ref_map);
 
        return ref_map;
@@ -452,18 +459,28 @@ static int add_existing(const char *refname, const unsigned char *sha1,
        return 0;
 }
 
-static struct ref *find_non_local_tags(struct transport *transport,
-                                      struct ref *fetch_map)
+static int will_fetch(struct ref **head, const unsigned char *sha1)
+{
+       struct ref *rm = *head;
+       while (rm) {
+               if (!hashcmp(rm->old_sha1, sha1))
+                       return 1;
+               rm = rm->next;
+       }
+       return 0;
+}
+
+static void find_non_local_tags(struct transport *transport,
+                       struct ref **head,
+                       struct ref ***tail)
 {
-       static struct path_list existing_refs = { NULL, 0, 0, 0 };
+       struct path_list existing_refs = { NULL, 0, 0, 0 };
        struct path_list new_refs = { NULL, 0, 0, 1 };
        char *ref_name;
        int ref_name_len;
        const unsigned char *ref_sha1;
        const struct ref *tag_ref;
        struct ref *rm = NULL;
-       struct ref *ref_map = NULL;
-       struct ref **tail = &ref_map;
        const struct ref *ref;
 
        for_each_ref(add_existing, &existing_refs);
@@ -489,7 +506,8 @@ static struct ref *find_non_local_tags(struct transport *transport,
 
                if (!path_list_has_path(&existing_refs, ref_name) &&
                    !path_list_has_path(&new_refs, ref_name) &&
-                   has_sha1_file(ref->old_sha1)) {
+                   (has_sha1_file(ref->old_sha1) ||
+                    will_fetch(head, ref->old_sha1))) {
                        path_list_insert(ref_name, &new_refs);
 
                        rm = alloc_ref(strlen(ref_name) + 1);
@@ -498,19 +516,19 @@ static struct ref *find_non_local_tags(struct transport *transport,
                        strcpy(rm->peer_ref->name, ref_name);
                        hashcpy(rm->old_sha1, ref_sha1);
 
-                       *tail = rm;
-                       tail = &rm->next;
+                       **tail = rm;
+                       *tail = &rm->next;
                }
                free(ref_name);
        }
-
-       return ref_map;
+       path_list_clear(&existing_refs, 0);
+       path_list_clear(&new_refs, 0);
 }
 
 static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
 {
-       struct ref *ref_map, *fetch_map;
+       struct ref *ref_map;
        struct ref *rm;
        int autotags = (transport->remote->fetch_tags == 1);
        if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
@@ -537,25 +555,29 @@ static int do_fetch(struct transport *transport,
                        read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
        }
 
+       if (tags == TAGS_DEFAULT && autotags)
+               transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
        if (fetch_refs(transport, ref_map)) {
                free_refs(ref_map);
                return 1;
        }
-
-       fetch_map = ref_map;
+       free_refs(ref_map);
 
        /* if neither --no-tags nor --tags was specified, do automated tag
         * following ... */
        if (tags == TAGS_DEFAULT && autotags) {
-               ref_map = find_non_local_tags(transport, fetch_map);
+               struct ref **tail = &ref_map;
+               ref_map = NULL;
+               find_non_local_tags(transport, &ref_map, &tail);
                if (ref_map) {
+                       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
                        transport_set_option(transport, TRANS_OPT_DEPTH, "0");
                        fetch_refs(transport, ref_map);
                }
                free_refs(ref_map);
        }
 
-       free_refs(fetch_map);
+       transport_disconnect(transport);
 
        return 0;
 }
index 6163bd4975e3e361e36ffc89ea4c91d0edd02949..ebb3f37cf158dc479f364111893279805fa9a230 100644 (file)
@@ -187,7 +187,8 @@ static void shortlog(const char *name, unsigned char *sha1,
        add_pending_object(rev, branch, name);
        add_pending_object(rev, &head->object, "^HEAD");
        head->object.flags |= UNINTERESTING;
-       prepare_revision_walk(rev);
+       if (prepare_revision_walk(rev))
+               die("revision walk setup failed");
        while ((commit = get_revision(rev)) != NULL) {
                char *oneline, *bol, *eol;
 
index f36a43c26459bd386618e551e2e93743cd8030aa..07d9c572125523e2eb8f82e4cab907ee7dc94348 100644 (file)
@@ -165,7 +165,7 @@ static int verify_format(const char *format)
        for (cp = format; *cp && (sp = find_next(cp)); ) {
                const char *ep = strchr(sp, ')');
                if (!ep)
-                       return error("malformatted format string %s", sp);
+                       return error("malformed format string %s", sp);
                /* sp points at "%(" and ep points at the closing ")" */
                parse_atom(sp + 2, ep);
                cp = ep + 1;
index 2a6e94deaf6b27f27ad7655ebf6072a05b25017c..78a6e1ff7101f7bb616365e5d4c9d9cf7f8963ae 100644 (file)
@@ -8,6 +8,7 @@
 #include "pack.h"
 #include "cache-tree.h"
 #include "tree-walk.h"
+#include "fsck.h"
 #include "parse-options.h"
 
 #define REACHABLE 0x0001
@@ -54,13 +55,75 @@ static int objerror(struct object *obj, const char *err, ...)
        return -1;
 }
 
-static int objwarning(struct object *obj, const char *err, ...)
+static int fsck_error_func(struct object *obj, int type, const char *err, ...)
 {
        va_list params;
        va_start(params, err);
-       objreport(obj, "warning", err, params);
+       objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params);
        va_end(params);
-       return -1;
+       return (type == FSCK_WARN) ? 0 : 1;
+}
+
+static int mark_object(struct object *obj, int type, void *data)
+{
+       struct tree *tree = NULL;
+       struct object *parent = data;
+       int result;
+
+       if (!obj) {
+               printf("broken link from %7s %s\n",
+                          typename(parent->type), sha1_to_hex(parent->sha1));
+               printf("broken link from %7s %s\n",
+                          (type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
+               errors_found |= ERROR_REACHABLE;
+               return 1;
+       }
+
+       if (type != OBJ_ANY && obj->type != type)
+               objerror(parent, "wrong object type in link");
+
+       if (obj->flags & REACHABLE)
+               return 0;
+       obj->flags |= REACHABLE;
+       if (!obj->parsed) {
+               if (parent && !has_sha1_file(obj->sha1)) {
+                       printf("broken link from %7s %s\n",
+                                typename(parent->type), sha1_to_hex(parent->sha1));
+                       printf("              to %7s %s\n",
+                                typename(obj->type), sha1_to_hex(obj->sha1));
+                       errors_found |= ERROR_REACHABLE;
+               }
+               return 1;
+       }
+
+       if (obj->type == OBJ_TREE) {
+               obj->parsed = 0;
+               tree = (struct tree *)obj;
+               if (parse_tree(tree) < 0)
+                       return 1; /* error already displayed */
+       }
+       result = fsck_walk(obj, mark_object, obj);
+       if (tree) {
+               free(tree->buffer);
+               tree->buffer = NULL;
+       }
+       if (result < 0)
+               result = 1;
+
+       return result;
+}
+
+static void mark_object_reachable(struct object *obj)
+{
+       mark_object(obj, OBJ_ANY, 0);
+}
+
+static int mark_used(struct object *obj, int type, void *data)
+{
+       if (!obj)
+               return 1;
+       obj->used = 1;
+       return 0;
 }
 
 /*
@@ -68,8 +131,6 @@ static int objwarning(struct object *obj, const char *err, ...)
  */
 static void check_reachable_object(struct object *obj)
 {
-       const struct object_refs *refs;
-
        /*
         * We obviously want the object to be parsed,
         * except if it was in a pack-file and we didn't
@@ -82,25 +143,6 @@ static void check_reachable_object(struct object *obj)
                errors_found |= ERROR_REACHABLE;
                return;
        }
-
-       /*
-        * Check that everything that we try to reference is also good.
-        */
-       refs = lookup_object_refs(obj);
-       if (refs) {
-               unsigned j;
-               for (j = 0; j < refs->count; j++) {
-                       struct object *ref = refs->ref[j];
-                       if (ref->parsed ||
-                           (has_sha1_file(ref->sha1)))
-                               continue;
-                       printf("broken link from %7s %s\n",
-                              typename(obj->type), sha1_to_hex(obj->sha1));
-                       printf("              to %7s %s\n",
-                              typename(ref->type), sha1_to_hex(ref->sha1));
-                       errors_found |= ERROR_REACHABLE;
-               }
-       }
 }
 
 /*
@@ -204,230 +246,56 @@ static void check_connectivity(void)
        }
 }
 
-/*
- * The entries in a tree are ordered in the _path_ order,
- * which means that a directory entry is ordered by adding
- * a slash to the end of it.
- *
- * So a directory called "a" is ordered _after_ a file
- * called "a.c", because "a/" sorts after "a.c".
- */
-#define TREE_UNORDERED (-1)
-#define TREE_HAS_DUPS  (-2)
-
-static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, const char *name2)
+static int fsck_sha1(const unsigned char *sha1)
 {
-       int len1 = strlen(name1);
-       int len2 = strlen(name2);
-       int len = len1 < len2 ? len1 : len2;
-       unsigned char c1, c2;
-       int cmp;
-
-       cmp = memcmp(name1, name2, len);
-       if (cmp < 0)
+       struct object *obj = parse_object(sha1);
+       if (!obj) {
+               errors_found |= ERROR_OBJECT;
+               return error("%s: object corrupt or missing",
+                            sha1_to_hex(sha1));
+       }
+       if (obj->flags & SEEN)
                return 0;
-       if (cmp > 0)
-               return TREE_UNORDERED;
-
-       /*
-        * Ok, the first <len> characters are the same.
-        * Now we need to order the next one, but turn
-        * a '\0' into a '/' for a directory entry.
-        */
-       c1 = name1[len];
-       c2 = name2[len];
-       if (!c1 && !c2)
-               /*
-                * git-write-tree used to write out a nonsense tree that has
-                * entries with the same name, one blob and one tree.  Make
-                * sure we do not have duplicate entries.
-                */
-               return TREE_HAS_DUPS;
-       if (!c1 && S_ISDIR(mode1))
-               c1 = '/';
-       if (!c2 && S_ISDIR(mode2))
-               c2 = '/';
-       return c1 < c2 ? 0 : TREE_UNORDERED;
-}
-
-static int fsck_tree(struct tree *item)
-{
-       int retval;
-       int has_full_path = 0;
-       int has_empty_name = 0;
-       int has_zero_pad = 0;
-       int has_bad_modes = 0;
-       int has_dup_entries = 0;
-       int not_properly_sorted = 0;
-       struct tree_desc desc;
-       unsigned o_mode;
-       const char *o_name;
-       const unsigned char *o_sha1;
+       obj->flags |= SEEN;
 
        if (verbose)
-               fprintf(stderr, "Checking tree %s\n",
-                               sha1_to_hex(item->object.sha1));
-
-       init_tree_desc(&desc, item->buffer, item->size);
-
-       o_mode = 0;
-       o_name = NULL;
-       o_sha1 = NULL;
-       while (desc.size) {
-               unsigned mode;
-               const char *name;
-               const unsigned char *sha1;
-
-               sha1 = tree_entry_extract(&desc, &name, &mode);
-
-               if (strchr(name, '/'))
-                       has_full_path = 1;
-               if (!*name)
-                       has_empty_name = 1;
-               has_zero_pad |= *(char *)desc.buffer == '0';
-               update_tree_entry(&desc);
-
-               switch (mode) {
-               /*
-                * Standard modes..
-                */
-               case S_IFREG | 0755:
-               case S_IFREG | 0644:
-               case S_IFLNK:
-               case S_IFDIR:
-               case S_IFGITLINK:
-                       break;
-               /*
-                * This is nonstandard, but we had a few of these
-                * early on when we honored the full set of mode
-                * bits..
-                */
-               case S_IFREG | 0664:
-                       if (!check_strict)
-                               break;
-               default:
-                       has_bad_modes = 1;
-               }
+               fprintf(stderr, "Checking %s %s\n",
+                       typename(obj->type), sha1_to_hex(obj->sha1));
 
-               if (o_name) {
-                       switch (verify_ordered(o_mode, o_name, mode, name)) {
-                       case TREE_UNORDERED:
-                               not_properly_sorted = 1;
-                               break;
-                       case TREE_HAS_DUPS:
-                               has_dup_entries = 1;
-                               break;
-                       default:
-                               break;
-                       }
-               }
+       if (fsck_walk(obj, mark_used, 0))
+               objerror(obj, "broken links");
+       if (fsck_object(obj, check_strict, fsck_error_func))
+               return -1;
 
-               o_mode = mode;
-               o_name = name;
-               o_sha1 = sha1;
-       }
-       free(item->buffer);
-       item->buffer = NULL;
+       if (obj->type == OBJ_TREE) {
+               struct tree *item = (struct tree *) obj;
 
-       retval = 0;
-       if (has_full_path) {
-               objwarning(&item->object, "contains full pathnames");
+               free(item->buffer);
+               item->buffer = NULL;
        }
-       if (has_empty_name) {
-               objwarning(&item->object, "contains empty pathname");
-       }
-       if (has_zero_pad) {
-               objwarning(&item->object, "contains zero-padded file modes");
-       }
-       if (has_bad_modes) {
-               objwarning(&item->object, "contains bad file modes");
-       }
-       if (has_dup_entries) {
-               retval = objerror(&item->object, "contains duplicate file entries");
-       }
-       if (not_properly_sorted) {
-               retval = objerror(&item->object, "not properly sorted");
-       }
-       return retval;
-}
 
-static int fsck_commit(struct commit *commit)
-{
-       char *buffer = commit->buffer;
-       unsigned char tree_sha1[20], sha1[20];
+       if (obj->type == OBJ_COMMIT) {
+               struct commit *commit = (struct commit *) obj;
 
-       if (verbose)
-               fprintf(stderr, "Checking commit %s\n",
-                       sha1_to_hex(commit->object.sha1));
-
-       if (memcmp(buffer, "tree ", 5))
-               return objerror(&commit->object, "invalid format - expected 'tree' line");
-       if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
-               return objerror(&commit->object, "invalid 'tree' line format - bad sha1");
-       buffer += 46;
-       while (!memcmp(buffer, "parent ", 7)) {
-               if (get_sha1_hex(buffer+7, sha1) || buffer[47] != '\n')
-                       return objerror(&commit->object, "invalid 'parent' line format - bad sha1");
-               buffer += 48;
-       }
-       if (memcmp(buffer, "author ", 7))
-               return objerror(&commit->object, "invalid format - expected 'author' line");
-       free(commit->buffer);
-       commit->buffer = NULL;
-       if (!commit->tree)
-               return objerror(&commit->object, "could not load commit's tree %s", tree_sha1);
-       if (!commit->parents && show_root)
-               printf("root %s\n", sha1_to_hex(commit->object.sha1));
-       if (!commit->date)
-               printf("bad commit date in %s\n",
-                      sha1_to_hex(commit->object.sha1));
-       return 0;
-}
+               free(commit->buffer);
+               commit->buffer = NULL;
 
-static int fsck_tag(struct tag *tag)
-{
-       struct object *tagged = tag->tagged;
+               if (!commit->parents && show_root)
+                       printf("root %s\n", sha1_to_hex(commit->object.sha1));
+       }
 
-       if (verbose)
-               fprintf(stderr, "Checking tag %s\n",
-                       sha1_to_hex(tag->object.sha1));
+       if (obj->type == OBJ_TAG) {
+               struct tag *tag = (struct tag *) obj;
 
-       if (!tagged) {
-               return objerror(&tag->object, "could not load tagged object");
+               if (show_tags && tag->tagged) {
+                       printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1));
+                       printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
+               }
        }
-       if (!show_tags)
-               return 0;
 
-       printf("tagged %s %s", typename(tagged->type), sha1_to_hex(tagged->sha1));
-       printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
        return 0;
 }
 
-static int fsck_sha1(const unsigned char *sha1)
-{
-       struct object *obj = parse_object(sha1);
-       if (!obj) {
-               errors_found |= ERROR_OBJECT;
-               return error("%s: object corrupt or missing",
-                            sha1_to_hex(sha1));
-       }
-       if (obj->flags & SEEN)
-               return 0;
-       obj->flags |= SEEN;
-       if (obj->type == OBJ_BLOB)
-               return 0;
-       if (obj->type == OBJ_TREE)
-               return fsck_tree((struct tree *) obj);
-       if (obj->type == OBJ_COMMIT)
-               return fsck_commit((struct commit *) obj);
-       if (obj->type == OBJ_TAG)
-               return fsck_tag((struct tag *) obj);
-
-       /* By now, parse_object() would've returned NULL instead. */
-       return objerror(obj, "unknown type '%d' (internal fsck error)",
-                       obj->type);
-}
-
 /*
  * This is the sorting chunk size: make it reasonably
  * big so that we can sort well..
@@ -538,13 +406,13 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
                obj = lookup_object(osha1);
                if (obj) {
                        obj->used = 1;
-                       mark_reachable(obj, REACHABLE);
+                       mark_object_reachable(obj);
                }
        }
        obj = lookup_object(nsha1);
        if (obj) {
                obj->used = 1;
-               mark_reachable(obj, REACHABLE);
+               mark_object_reachable(obj);
        }
        return 0;
 }
@@ -574,7 +442,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
                error("%s: not a commit", refname);
        default_refs++;
        obj->used = 1;
-       mark_reachable(obj, REACHABLE);
+       mark_object_reachable(obj);
 
        return 0;
 }
@@ -660,7 +528,7 @@ static int fsck_cache_tree(struct cache_tree *it)
                              sha1_to_hex(it->sha1));
                        return 1;
                }
-               mark_reachable(obj, REACHABLE);
+               mark_object_reachable(obj);
                obj->used = 1;
                if (obj->type != OBJ_TREE)
                        err |= objerror(obj, "non-tree in cache-tree");
@@ -693,7 +561,6 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
 {
        int i, heads;
 
-       track_object_refs = 1;
        errors_found = 0;
 
        argc = parse_options(argc, argv, fsck_opts, fsck_usage, 0);
@@ -741,7 +608,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
                                continue;
 
                        obj->used = 1;
-                       mark_reachable(obj, REACHABLE);
+                       mark_object_reachable(obj);
                        heads++;
                        continue;
                }
@@ -765,7 +632,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
                        struct blob *blob;
                        struct object *obj;
 
-                       mode = ntohl(active_cache[i]->ce_mode);
+                       mode = active_cache[i]->ce_mode;
                        if (S_ISGITLINK(mode))
                                continue;
                        blob = lookup_blob(active_cache[i]->sha1);
@@ -773,7 +640,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
                                continue;
                        obj = &blob->object;
                        obj->used = 1;
-                       mark_reachable(obj, REACHABLE);
+                       mark_object_reachable(obj);
                }
                if (active_cache_tree)
                        fsck_cache_tree(active_cache_tree);
index ad4a75eedddb1dfc0ecef2b536727970335673f9..045bf0e487a73e98d8d78990fcf85cc25ad5f96d 100644 (file)
@@ -172,12 +172,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        int prune = 0;
        int aggressive = 0;
        int auto_gc = 0;
+       int quiet = 0;
        char buf[80];
 
        struct option builtin_gc_options[] = {
                OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects"),
                OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
                OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
+               OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"),
                OPT_END()
        };
 
@@ -197,6 +199,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        append_option(argv_repack, buf, MAX_ADD);
                }
        }
+       if (quiet)
+               append_option(argv_repack, "-q", MAX_ADD);
 
        if (auto_gc) {
                /*
index 0d6cc7361f6e1a70e4d3d9e24913d60e8ceb9e58..f4f4ecb11b587c74a009c94cb7b230e7c93590e4 100644 (file)
@@ -331,7 +331,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                struct cache_entry *ce = active_cache[i];
                char *name;
                int kept;
-               if (!S_ISREG(ntohl(ce->ce_mode)))
+               if (!S_ISREG(ce->ce_mode))
                        continue;
                if (!pathspec_matches(paths, ce->name))
                        continue;
@@ -387,7 +387,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 
        for (nr = 0; nr < active_nr; nr++) {
                struct cache_entry *ce = active_cache[nr];
-               if (!S_ISREG(ntohl(ce->ce_mode)))
+               if (!S_ISREG(ce->ce_mode))
                        continue;
                if (!pathspec_matches(paths, ce->name))
                        continue;
@@ -578,6 +578,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp("-l", arg) ||
+                   !strcmp("--name-only", arg) ||
                    !strcmp("--files-with-matches", arg)) {
                        opt.name_only = 1;
                        continue;
index 48128c610e4b36789efbe16fb974b2f3389093ff..b1f33891c36fe38d855ace535cf9218fe9603541 100644 (file)
@@ -80,8 +80,7 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
 
        walker_free(walker);
 
-       if (rewritten_url)
-               free(rewritten_url);
+       free(rewritten_url);
 
        return rc;
 }
index ff6e87777ae4c2a883db3836cb9acfee31a7281d..79eaf8d6edf18675897f6eed571289fce450526a 100644 (file)
@@ -120,9 +120,9 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
                 */
                template_dir = DEFAULT_GIT_TEMPLATE_DIR;
                if (!is_absolute_path(template_dir)) {
-                       const char *exec_path = git_exec_path();
-                       template_dir = prefix_path(exec_path, strlen(exec_path),
-                                                  template_dir);
+                       struct strbuf d = STRBUF_INIT;
+                       strbuf_addf(&d, "%s/%s", git_exec_path(), template_dir);
+                       template_dir = strbuf_detach(&d, NULL);
                }
        }
        strcpy(template_path, template_dir);
index 99d69f079132ed67e2d5bc83bf95e6e60ba22f6f..d983cbc7bc98f1b28332c18946e5111a6dcba9ab 100644 (file)
@@ -5,6 +5,7 @@
  *              2006 Junio Hamano
  */
 #include "cache.h"
+#include "color.h"
 #include "commit.h"
 #include "diff.h"
 #include "revision.h"
 #include "reflog-walk.h"
 #include "patch-ids.h"
 #include "refs.h"
+#include "run-command.h"
+#include "shortlog.h"
 
 static int default_show_root = 1;
 static const char *fmt_patch_subject_prefix = "PATCH";
+static const char *fmt_pretty;
 
 static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
 {
@@ -51,6 +55,8 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
+       if (fmt_pretty)
+               rev->commit_format = get_commit_format(fmt_pretty);
        rev->verbose_header = 1;
        DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
        rev->show_root_diff = default_show_root;
@@ -197,7 +203,8 @@ static int cmd_log_walk(struct rev_info *rev)
        if (rev->early_output)
                setup_early_output(rev);
 
-       prepare_revision_walk(rev);
+       if (prepare_revision_walk(rev))
+               die("revision walk setup failed");
 
        if (rev->early_output)
                finish_early_output(rev);
@@ -217,6 +224,8 @@ static int cmd_log_walk(struct rev_info *rev)
 
 static int git_log_config(const char *var, const char *value)
 {
+       if (!strcmp(var, "format.pretty"))
+               return git_config_string(&fmt_pretty, var, value);
        if (!strcmp(var, "format.subjectprefix")) {
                if (!value)
                        config_error_nonbool(var);
@@ -235,6 +244,10 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
        struct rev_info rev;
 
        git_config(git_log_config);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.simplify_history = 0;
@@ -307,6 +320,10 @@ int cmd_show(int argc, const char **argv, const char *prefix)
        int i, count, ret = 0;
 
        git_config(git_log_config);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.combine_merges = 1;
@@ -367,6 +384,10 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
        struct rev_info rev;
 
        git_config(git_log_config);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
        init_revisions(&rev, prefix);
        init_reflog_walk(&rev.reflog_info);
        rev.abbrev_commit = 1;
@@ -395,6 +416,10 @@ int cmd_log(int argc, const char **argv, const char *prefix)
        struct rev_info rev;
 
        git_config(git_log_config);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
        init_revisions(&rev, prefix);
        rev.always_show_header = 1;
        cmd_log_init(argc, argv, prefix, &rev);
@@ -410,24 +435,47 @@ static int istitlechar(char c)
                (c >= '0' && c <= '9') || c == '.' || c == '_';
 }
 
-static char *extra_headers = NULL;
-static int extra_headers_size = 0;
 static const char *fmt_patch_suffix = ".patch";
 static int numbered = 0;
 static int auto_number = 0;
 
+static char **extra_hdr;
+static int extra_hdr_nr;
+static int extra_hdr_alloc;
+
+static char **extra_to;
+static int extra_to_nr;
+static int extra_to_alloc;
+
+static char **extra_cc;
+static int extra_cc_nr;
+static int extra_cc_alloc;
+
+static void add_header(const char *value)
+{
+       int len = strlen(value);
+       while (value[len - 1] == '\n')
+               len--;
+       if (!strncasecmp(value, "to: ", 4)) {
+               ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc);
+               extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4);
+               return;
+       }
+       if (!strncasecmp(value, "cc: ", 4)) {
+               ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+               extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4);
+               return;
+       }
+       ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc);
+       extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
+}
+
 static int git_format_config(const char *var, const char *value)
 {
        if (!strcmp(var, "format.headers")) {
-               int len;
-
                if (!value)
                        die("format.headers without value");
-               len = strlen(value);
-               extra_headers_size += len + 1;
-               extra_headers = xrealloc(extra_headers, extra_headers_size);
-               extra_headers[extra_headers_size - len - 1] = 0;
-               strcat(extra_headers, value);
+               add_header(value);
                return 0;
        }
        if (!strcmp(var, "format.suffix")) {
@@ -452,74 +500,81 @@ static int git_format_config(const char *var, const char *value)
 }
 
 
+static const char *get_oneline_for_filename(struct commit *commit,
+                                           int keep_subject)
+{
+       static char filename[PATH_MAX];
+       char *sol;
+       int len = 0;
+       int suffix_len = strlen(fmt_patch_suffix) + 1;
+
+       sol = strstr(commit->buffer, "\n\n");
+       if (!sol)
+               filename[0] = '\0';
+       else {
+               int j, space = 0;
+
+               sol += 2;
+               /* strip [PATCH] or [PATCH blabla] */
+               if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
+                       char *eos = strchr(sol + 6, ']');
+                       if (eos) {
+                               while (isspace(*eos))
+                                       eos++;
+                               sol = eos;
+                       }
+               }
+
+               for (j = 0;
+                    j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
+                            len < sizeof(filename) - suffix_len &&
+                            sol[j] && sol[j] != '\n';
+                    j++) {
+                       if (istitlechar(sol[j])) {
+                               if (space) {
+                                       filename[len++] = '-';
+                                       space = 0;
+                               }
+                               filename[len++] = sol[j];
+                               if (sol[j] == '.')
+                                       while (sol[j + 1] == '.')
+                                               j++;
+                       } else
+                               space = 1;
+               }
+               while (filename[len - 1] == '.'
+                      || filename[len - 1] == '-')
+                       len--;
+               filename[len] = '\0';
+       }
+       return filename;
+}
+
 static FILE *realstdout = NULL;
 static const char *output_directory = NULL;
 
-static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
-                        int numbered_files)
+static int reopen_stdout(const char *oneline, int nr, int total)
 {
        char filename[PATH_MAX];
-       char *sol;
        int len = 0;
        int suffix_len = strlen(fmt_patch_suffix) + 1;
 
        if (output_directory) {
-               if (strlen(output_directory) >=
+               len = snprintf(filename, sizeof(filename), "%s",
+                               output_directory);
+               if (len >=
                    sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len)
                        return error("name of output directory is too long");
-               strlcpy(filename, output_directory, sizeof(filename) - suffix_len);
-               len = strlen(filename);
                if (filename[len - 1] != '/')
                        filename[len++] = '/';
        }
 
-       if (numbered_files) {
-               sprintf(filename + len, "%d", nr);
-               len = strlen(filename);
-
-       } else {
-               sprintf(filename + len, "%04d", nr);
-               len = strlen(filename);
-
-               sol = strstr(commit->buffer, "\n\n");
-               if (sol) {
-                       int j, space = 1;
-
-                       sol += 2;
-                       /* strip [PATCH] or [PATCH blabla] */
-                       if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
-                               char *eos = strchr(sol + 6, ']');
-                               if (eos) {
-                                       while (isspace(*eos))
-                                               eos++;
-                                       sol = eos;
-                               }
-                       }
-
-                       for (j = 0;
-                            j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
-                                    len < sizeof(filename) - suffix_len &&
-                                    sol[j] && sol[j] != '\n';
-                            j++) {
-                               if (istitlechar(sol[j])) {
-                                       if (space) {
-                                               filename[len++] = '-';
-                                               space = 0;
-                                       }
-                                       filename[len++] = sol[j];
-                                       if (sol[j] == '.')
-                                               while (sol[j + 1] == '.')
-                                                       j++;
-                               } else
-                                       space = 1;
-                       }
-                       while (filename[len - 1] == '.'
-                              || filename[len - 1] == '-')
-                               len--;
-                       filename[len] = 0;
-               }
-               if (len + suffix_len >= sizeof(filename))
-                       return error("Patch pathname too long");
+       if (!oneline)
+               len += sprintf(filename + len, "%d", nr);
+       else {
+               len += sprintf(filename + len, "%04d-", nr);
+               len += snprintf(filename + len, sizeof(filename) - len - 1
+                               - suffix_len, "%s", oneline);
                strcpy(filename + len, fmt_patch_suffix);
        }
 
@@ -556,7 +611,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
        o2->flags ^= UNINTERESTING;
        add_pending_object(&check_rev, o1, "o1");
        add_pending_object(&check_rev, o2, "o2");
-       prepare_revision_walk(&check_rev);
+       if (prepare_revision_walk(&check_rev))
+               die("revision walk setup failed");
 
        while ((commit = get_revision(&check_rev)) != NULL) {
                /* ignore merges */
@@ -575,16 +631,90 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
        o2->flags = flags2;
 }
 
-static void gen_message_id(char *dest, unsigned int length, char *base)
+static void gen_message_id(struct rev_info *info, char *base)
 {
        const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
        const char *email_start = strrchr(committer, '<');
        const char *email_end = strrchr(committer, '>');
-       if(!email_start || !email_end || email_start > email_end - 1)
+       struct strbuf buf;
+       if (!email_start || !email_end || email_start > email_end - 1)
                die("Could not extract email from committer identity.");
-       snprintf(dest, length, "%s.%lu.git.%.*s", base,
-                (unsigned long) time(NULL),
-                (int)(email_end - email_start - 1), email_start + 1);
+       strbuf_init(&buf, 0);
+       strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
+                   (unsigned long) time(NULL),
+                   (int)(email_end - email_start - 1), email_start + 1);
+       info->message_id = strbuf_detach(&buf, NULL);
+}
+
+static void make_cover_letter(struct rev_info *rev, int use_stdout,
+                             int numbered, int numbered_files,
+                             struct commit *origin,
+                             int nr, struct commit **list, struct commit *head)
+{
+       const char *committer;
+       char *head_sha1;
+       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;
+       int i;
+       const char *encoding = "utf-8";
+       struct diff_options opts;
+
+       if (rev->commit_format != CMIT_FMT_EMAIL)
+               die("Cover letter needs email format");
+
+       if (!use_stdout && reopen_stdout(numbered_files ?
+                               NULL : "cover-letter", 0, rev->total))
+               return;
+
+       head_sha1 = sha1_to_hex(head->object.sha1);
+
+       log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers);
+
+       committer = git_committer_info(0);
+
+       msg = body;
+       strbuf_init(&sb, 0);
+       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, 0);
+       pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+       printf("%s\n", sb.buf);
+
+       strbuf_release(&sb);
+
+       shortlog_init(&log);
+       log.wrap_lines = 1;
+       log.wrap = 72;
+       log.in1 = 2;
+       log.in2 = 4;
+       for (i = 0; i < nr; i++)
+               shortlog_add_commit(&log, list[i]);
+
+       shortlog_output(&log);
+
+       /*
+        * We can only do diffstat with a unique reference point
+        */
+       if (!origin)
+               return;
+
+       memcpy(&opts, &rev->diffopt, sizeof(opts));
+       opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+
+       diff_setup_done(&opts);
+
+       diff_tree_sha1(origin->tree->object.sha1,
+                      head->tree->object.sha1,
+                      "", &opts);
+       diffcore_std(&opts);
+       diff_flush(&opts);
+
+       printf("\n");
 }
 
 static const char *clean_message_id(const char *msg_id)
@@ -622,11 +752,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int subject_prefix = 0;
        int ignore_if_in_upstream = 0;
        int thread = 0;
+       int cover_letter = 0;
+       int boundary_count = 0;
+       struct commit *origin = NULL, *head = NULL;
        const char *in_reply_to = NULL;
        struct patch_ids ids;
        char *add_signoff = NULL;
-       char message_id[1024];
-       char ref_message_id[1024];
+       struct strbuf buf;
 
        git_config(git_format_config);
        init_revisions(&rev, prefix);
@@ -639,7 +771,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
 
        rev.subject_prefix = fmt_patch_subject_prefix;
-       rev.extra_headers = extra_headers;
 
        /*
         * Parse the arguments before setup_revisions(), or something
@@ -667,6 +798,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                                die("Need a number for --start-number");
                        start_number = strtol(argv[i], NULL, 10);
                }
+               else if (!prefixcmp(argv[i], "--cc=")) {
+                       ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+                       extra_cc[extra_cc_nr++] = xstrdup(argv[i] + 5);
+               }
                else if (!strcmp(argv[i], "-k") ||
                                !strcmp(argv[i], "--keep-subject")) {
                        keep_subject = 1;
@@ -723,11 +858,44 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        rev.subject_prefix = argv[i] + 17;
                } else if (!prefixcmp(argv[i], "--suffix="))
                        fmt_patch_suffix = argv[i] + 9;
+               else if (!strcmp(argv[i], "--cover-letter"))
+                       cover_letter = 1;
                else
                        argv[j++] = argv[i];
        }
        argc = j;
 
+       strbuf_init(&buf, 0);
+
+       for (i = 0; i < extra_hdr_nr; i++) {
+               strbuf_addstr(&buf, extra_hdr[i]);
+               strbuf_addch(&buf, '\n');
+       }
+
+       if (extra_to_nr)
+               strbuf_addstr(&buf, "To: ");
+       for (i = 0; i < extra_to_nr; i++) {
+               if (i)
+                       strbuf_addstr(&buf, "    ");
+               strbuf_addstr(&buf, extra_to[i]);
+               if (i + 1 < extra_to_nr)
+                       strbuf_addch(&buf, ',');
+               strbuf_addch(&buf, '\n');
+       }
+
+       if (extra_cc_nr)
+               strbuf_addstr(&buf, "Cc: ");
+       for (i = 0; i < extra_cc_nr; i++) {
+               if (i)
+                       strbuf_addstr(&buf, "    ");
+               strbuf_addstr(&buf, extra_cc[i]);
+               if (i + 1 < extra_cc_nr)
+                       strbuf_addch(&buf, ',');
+               strbuf_addch(&buf, '\n');
+       }
+
+       rev.extra_headers = strbuf_detach(&buf, 0);
+
        if (start_number < 0)
                start_number = 1;
        if (numbered && keep_subject)
@@ -774,6 +942,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                 * get_revision() to do the usual traversal.
                 */
        }
+       if (cover_letter) {
+               /* remember the range */
+               int i;
+               for (i = 0; i < rev.pending.nr; i++) {
+                       struct object *o = rev.pending.objects[i].item;
+                       if (!(o->flags & UNINTERESTING))
+                               head = (struct commit *)o;
+               }
+               /* We can't generate a cover letter without any patches */
+               if (!head)
+                       return 0;
+       }
 
        if (ignore_if_in_upstream)
                get_patch_ids(&rev, &ids, prefix);
@@ -781,8 +961,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        if (!use_stdout)
                realstdout = xfdopen(xdup(1), "w");
 
-       prepare_revision_walk(&rev);
+       if (prepare_revision_walk(&rev))
+               die("revision walk setup failed");
+       rev.boundary = 1;
        while ((commit = get_revision(&rev)) != NULL) {
+               if (commit->object.flags & BOUNDARY) {
+                       boundary_count++;
+                       origin = (boundary_count == 1) ? commit : NULL;
+                       continue;
+               }
+
                /* ignore merges */
                if (commit->parents && commit->parents->next)
                        continue;
@@ -800,29 +988,42 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                numbered = 1;
        if (numbered)
                rev.total = total + start_number - 1;
-       rev.add_signoff = add_signoff;
        if (in_reply_to)
                rev.ref_message_id = clean_message_id(in_reply_to);
+       if (cover_letter) {
+               if (thread)
+                       gen_message_id(&rev, "cover");
+               make_cover_letter(&rev, use_stdout, numbered, numbered_files,
+                                 origin, nr, list, head);
+               total++;
+               start_number--;
+       }
+       rev.add_signoff = add_signoff;
        while (0 <= --nr) {
                int shown;
                commit = list[nr];
                rev.nr = total - nr + (start_number - 1);
                /* Make the second and subsequent mails replies to the first */
                if (thread) {
-                       if (nr == (total - 2)) {
-                               strncpy(ref_message_id, message_id,
-                                       sizeof(ref_message_id));
-                               ref_message_id[sizeof(ref_message_id)-1]='\0';
-                               rev.ref_message_id = ref_message_id;
+                       /* Have we already had a message ID? */
+                       if (rev.message_id) {
+                               /*
+                                * If we've got the ID to be a reply
+                                * to, discard the current ID;
+                                * otherwise, make everything a reply
+                                * to that.
+                                */
+                               if (rev.ref_message_id)
+                                       free(rev.message_id);
+                               else
+                                       rev.ref_message_id = rev.message_id;
                        }
-                       gen_message_id(message_id, sizeof(message_id),
-                                      sha1_to_hex(commit->object.sha1));
-                       rev.message_id = message_id;
+                       gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
                }
-               if (!use_stdout)
-                       if (reopen_stdout(commit, rev.nr, keep_subject,
-                                         numbered_files))
-                               die("Failed to create output files");
+               if (!use_stdout && reopen_stdout(numbered_files ? NULL :
+                               get_oneline_for_filename(commit, keep_subject),
+                               rev.nr, rev.total))
+                       die("Failed to create output files");
                shown = log_tree_commit(&rev, commit);
                free(commit->buffer);
                commit->buffer = NULL;
@@ -923,7 +1124,8 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
                die("Unknown commit %s", limit);
 
        /* reverse the list of commits */
-       prepare_revision_walk(&revs);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
        while ((commit = get_revision(&revs)) != NULL) {
                /* ignore merges */
                if (commit->parents && commit->parents->next)
index 0f0ab2da167c57402efad9dc09e58964759c23cd..dc7eab89b34fed32dbb198a9aa9a7503fc162216 100644 (file)
@@ -189,7 +189,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
                return;
 
        if (tag && *tag && show_valid_bit &&
-           (ce->ce_flags & htons(CE_VALID))) {
+           (ce->ce_flags & CE_VALID)) {
                static char alttag[4];
                memcpy(alttag, tag, 3);
                if (isalpha(tag[0]))
@@ -210,7 +210,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        } else {
                printf("%s%06o %s %d\t",
                       tag,
-                      ntohl(ce->ce_mode),
+                      ce->ce_mode,
                       abbrev ? find_unique_abbrev(ce->sha1,abbrev)
                                : sha1_to_hex(ce->sha1),
                       ce_stage(ce));
@@ -238,11 +238,12 @@ static void show_files(struct dir_struct *dir, const char *prefix)
        if (show_cached | show_stage) {
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
-                       if (excluded(dir, ce->name) != dir->show_ignored)
+                       int dtype = ce_to_dtype(ce);
+                       if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
                                continue;
                        if (show_unmerged && !ce_stage(ce))
                                continue;
-                       if (ce->ce_flags & htons(CE_UPDATE))
+                       if (ce->ce_flags & CE_UPDATE)
                                continue;
                        show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
                }
@@ -252,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                        struct cache_entry *ce = active_cache[i];
                        struct stat st;
                        int err;
-                       if (excluded(dir, ce->name) != dir->show_ignored)
+                       int dtype = ce_to_dtype(ce);
+                       if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
                                continue;
                        err = lstat(ce->name, &st);
                        if (show_deleted && err)
@@ -350,7 +352,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
                struct cache_entry *ce = active_cache[i];
                if (!ce_stage(ce))
                        continue;
-               ce->ce_flags |= htons(CE_STAGEMASK);
+               ce->ce_flags |= CE_STAGEMASK;
        }
 
        if (prefix) {
@@ -379,7 +381,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
                         */
                        if (last_stage0 &&
                            !strcmp(last_stage0->name, ce->name))
-                               ce->ce_flags |= htons(CE_UPDATE);
+                               ce->ce_flags |= CE_UPDATE;
                }
        }
 }
index 720280e3900dc65a5063306a55880b8bc1330c2a..8907a89d6c3914f4652ef387776f581015778396 100644 (file)
@@ -94,6 +94,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
                transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
 
        ref = transport_get_remote_refs(transport);
+       if (transport_disconnect(transport))
+               return 1;
        for ( ; ref; ref = ref->next) {
                if (!check_ref_type(ref, flags))
                        continue;
index 58deb62ac08507901c40e89aec0cea7fcdc78f1e..adce6d4635a4153428368073677cd74a9bafc045 100644 (file)
@@ -46,7 +46,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
        }
 
        ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
-                       &xpp, XDL_MERGE_ZEALOUS, &result);
+                       &xpp, XDL_MERGE_ZEALOUS_ALNUM, &result);
 
        for (i = 0; i < 3; i++)
                free(mmfs[i].ptr);
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
new file mode 100644 (file)
index 0000000..5a0a7b5
--- /dev/null
@@ -0,0 +1,1401 @@
+/*
+ * Recursive Merge algorithm stolen from git-merge-recursive.py by
+ * Fredrik Kuivinen.
+ * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
+ */
+#include "cache.h"
+#include "cache-tree.h"
+#include "commit.h"
+#include "blob.h"
+#include "builtin.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "tag.h"
+#include "unpack-trees.h"
+#include "path-list.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "interpolate.h"
+#include "attr.h"
+#include "merge-recursive.h"
+
+static int subtree_merge;
+
+static struct tree *shift_tree_object(struct tree *one, struct tree *two)
+{
+       unsigned char shifted[20];
+
+       /*
+        * NEEDSWORK: this limits the recursion depth to hardcoded
+        * value '2' to avoid excessive overhead.
+        */
+       shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
+       if (!hashcmp(two->object.sha1, shifted))
+               return two;
+       return lookup_tree(shifted);
+}
+
+/*
+ * A virtual commit has
+ * - (const char *)commit->util set to the name, and
+ * - *(int *)commit->object.sha1 set to the virtual id.
+ */
+
+static unsigned commit_list_count(const struct commit_list *l)
+{
+       unsigned c = 0;
+       for (; l; l = l->next )
+               c++;
+       return c;
+}
+
+static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
+{
+       struct commit *commit = xcalloc(1, sizeof(struct commit));
+       static unsigned virtual_id = 1;
+       commit->tree = tree;
+       commit->util = (void*)comment;
+       *(int*)commit->object.sha1 = virtual_id++;
+       /* avoid warnings */
+       commit->object.parsed = 1;
+       return commit;
+}
+
+/*
+ * Since we use get_tree_entry(), which does not put the read object into
+ * the object pool, we cannot rely on a == b.
+ */
+static int sha_eq(const unsigned char *a, const unsigned char *b)
+{
+       if (!a && !b)
+               return 2;
+       return a && b && hashcmp(a, b) == 0;
+}
+
+/*
+ * Since we want to write the index eventually, we cannot reuse the index
+ * for these (temporary) data.
+ */
+struct stage_data
+{
+       struct
+       {
+               unsigned mode;
+               unsigned char sha[20];
+       } stages[4];
+       unsigned processed:1;
+};
+
+static struct path_list current_file_set = {NULL, 0, 0, 1};
+static struct path_list current_directory_set = {NULL, 0, 0, 1};
+
+static int call_depth = 0;
+static int verbosity = 2;
+static int rename_limit = -1;
+static int buffer_output = 1;
+static struct strbuf obuf = STRBUF_INIT;
+
+static int show(int v)
+{
+       return (!call_depth && verbosity >= v) || verbosity >= 5;
+}
+
+static void flush_output(void)
+{
+       if (obuf.len) {
+               fputs(obuf.buf, stdout);
+               strbuf_reset(&obuf);
+       }
+}
+
+static void output(int v, const char *fmt, ...)
+{
+       int len;
+       va_list ap;
+
+       if (!show(v))
+               return;
+
+       strbuf_grow(&obuf, call_depth * 2 + 2);
+       memset(obuf.buf + obuf.len, ' ', call_depth * 2);
+       strbuf_setlen(&obuf, obuf.len + call_depth * 2);
+
+       va_start(ap, fmt);
+       len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap);
+       va_end(ap);
+
+       if (len < 0)
+               len = 0;
+       if (len >= strbuf_avail(&obuf)) {
+               strbuf_grow(&obuf, len + 2);
+               va_start(ap, fmt);
+               len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap);
+               va_end(ap);
+               if (len >= strbuf_avail(&obuf)) {
+                       die("this should not happen, your snprintf is broken");
+               }
+       }
+       strbuf_setlen(&obuf, obuf.len + len);
+       strbuf_add(&obuf, "\n", 1);
+       if (!buffer_output)
+               flush_output();
+}
+
+static void output_commit_title(struct commit *commit)
+{
+       int i;
+       flush_output();
+       for (i = call_depth; i--;)
+               fputs("  ", stdout);
+       if (commit->util)
+               printf("virtual %s\n", (char *)commit->util);
+       else {
+               printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+               if (parse_commit(commit) != 0)
+                       printf("(bad commit)\n");
+               else {
+                       const char *s;
+                       int len;
+                       for (s = commit->buffer; *s; s++)
+                               if (*s == '\n' && s[1] == '\n') {
+                                       s += 2;
+                                       break;
+                               }
+                       for (len = 0; s[len] && '\n' != s[len]; len++)
+                               ; /* do nothing */
+                       printf("%.*s\n", len, s);
+               }
+       }
+}
+
+static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
+               const char *path, int stage, int refresh, int options)
+{
+       struct cache_entry *ce;
+       ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
+       if (!ce)
+               return error("addinfo_cache failed for path '%s'", path);
+       return add_cache_entry(ce, options);
+}
+
+/*
+ * This is a global variable which is used in a number of places but
+ * only written to in the 'merge' function.
+ *
+ * index_only == 1    => Don't leave any non-stage 0 entries in the cache and
+ *                       don't update the working directory.
+ *               0    => Leave unmerged entries in the cache and update
+ *                       the working directory.
+ */
+static int index_only = 0;
+
+static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
+{
+       parse_tree(tree);
+       init_tree_desc(desc, tree->buffer, tree->size);
+}
+
+static int git_merge_trees(int index_only,
+                          struct tree *common,
+                          struct tree *head,
+                          struct tree *merge)
+{
+       int rc;
+       struct tree_desc t[3];
+       struct unpack_trees_options opts;
+
+       memset(&opts, 0, sizeof(opts));
+       if (index_only)
+               opts.index_only = 1;
+       else
+               opts.update = 1;
+       opts.merge = 1;
+       opts.head_idx = 2;
+       opts.fn = threeway_merge;
+
+       init_tree_desc_from_tree(t+0, common);
+       init_tree_desc_from_tree(t+1, head);
+       init_tree_desc_from_tree(t+2, merge);
+
+       rc = unpack_trees(3, t, &opts);
+       cache_tree_free(&active_cache_tree);
+       return rc;
+}
+
+struct tree *write_tree_from_memory(void)
+{
+       struct tree *result = NULL;
+
+       if (unmerged_cache()) {
+               int i;
+               output(0, "There are unmerged index entries:");
+               for (i = 0; i < active_nr; i++) {
+                       struct cache_entry *ce = active_cache[i];
+                       if (ce_stage(ce))
+                               output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name);
+               }
+               return NULL;
+       }
+
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+
+       if (!cache_tree_fully_valid(active_cache_tree) &&
+           cache_tree_update(active_cache_tree,
+                             active_cache, active_nr, 0, 0) < 0)
+               die("error building trees");
+
+       result = lookup_tree(active_cache_tree->sha1);
+
+       return result;
+}
+
+static int save_files_dirs(const unsigned char *sha1,
+               const char *base, int baselen, const char *path,
+               unsigned int mode, int stage)
+{
+       int len = strlen(path);
+       char *newpath = xmalloc(baselen + len + 1);
+       memcpy(newpath, base, baselen);
+       memcpy(newpath + baselen, path, len);
+       newpath[baselen + len] = '\0';
+
+       if (S_ISDIR(mode))
+               path_list_insert(newpath, &current_directory_set);
+       else
+               path_list_insert(newpath, &current_file_set);
+       free(newpath);
+
+       return READ_TREE_RECURSIVE;
+}
+
+static int get_files_dirs(struct tree *tree)
+{
+       int n;
+       if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0)
+               return 0;
+       n = current_file_set.nr + current_directory_set.nr;
+       return n;
+}
+
+/*
+ * Returns an index_entry instance which doesn't have to correspond to
+ * a real cache entry in Git's index.
+ */
+static struct stage_data *insert_stage_data(const char *path,
+               struct tree *o, struct tree *a, struct tree *b,
+               struct path_list *entries)
+{
+       struct path_list_item *item;
+       struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
+       get_tree_entry(o->object.sha1, path,
+                       e->stages[1].sha, &e->stages[1].mode);
+       get_tree_entry(a->object.sha1, path,
+                       e->stages[2].sha, &e->stages[2].mode);
+       get_tree_entry(b->object.sha1, path,
+                       e->stages[3].sha, &e->stages[3].mode);
+       item = path_list_insert(path, entries);
+       item->util = e;
+       return e;
+}
+
+/*
+ * Create a dictionary mapping file names to stage_data objects. The
+ * dictionary contains one entry for every path with a non-zero stage entry.
+ */
+static struct path_list *get_unmerged(void)
+{
+       struct path_list *unmerged = xcalloc(1, sizeof(struct path_list));
+       int i;
+
+       unmerged->strdup_paths = 1;
+
+       for (i = 0; i < active_nr; i++) {
+               struct path_list_item *item;
+               struct stage_data *e;
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+
+               item = path_list_lookup(ce->name, unmerged);
+               if (!item) {
+                       item = path_list_insert(ce->name, unmerged);
+                       item->util = xcalloc(1, sizeof(struct stage_data));
+               }
+               e = item->util;
+               e->stages[ce_stage(ce)].mode = ce->ce_mode;
+               hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1);
+       }
+
+       return unmerged;
+}
+
+struct rename
+{
+       struct diff_filepair *pair;
+       struct stage_data *src_entry;
+       struct stage_data *dst_entry;
+       unsigned processed:1;
+};
+
+/*
+ * Get information of all renames which occurred between 'o_tree' and
+ * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and
+ * 'b_tree') to be able to associate the correct cache entries with
+ * the rename information. 'tree' is always equal to either a_tree or b_tree.
+ */
+static struct path_list *get_renames(struct tree *tree,
+                                       struct tree *o_tree,
+                                       struct tree *a_tree,
+                                       struct tree *b_tree,
+                                       struct path_list *entries)
+{
+       int i;
+       struct path_list *renames;
+       struct diff_options opts;
+
+       renames = xcalloc(1, sizeof(struct path_list));
+       diff_setup(&opts);
+       DIFF_OPT_SET(&opts, RECURSIVE);
+       opts.detect_rename = DIFF_DETECT_RENAME;
+       opts.rename_limit = rename_limit;
+       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       if (diff_setup_done(&opts) < 0)
+               die("diff setup failed");
+       diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
+       diffcore_std(&opts);
+       for (i = 0; i < diff_queued_diff.nr; ++i) {
+               struct path_list_item *item;
+               struct rename *re;
+               struct diff_filepair *pair = diff_queued_diff.queue[i];
+               if (pair->status != 'R') {
+                       diff_free_filepair(pair);
+                       continue;
+               }
+               re = xmalloc(sizeof(*re));
+               re->processed = 0;
+               re->pair = pair;
+               item = path_list_lookup(re->pair->one->path, entries);
+               if (!item)
+                       re->src_entry = insert_stage_data(re->pair->one->path,
+                                       o_tree, a_tree, b_tree, entries);
+               else
+                       re->src_entry = item->util;
+
+               item = path_list_lookup(re->pair->two->path, entries);
+               if (!item)
+                       re->dst_entry = insert_stage_data(re->pair->two->path,
+                                       o_tree, a_tree, b_tree, entries);
+               else
+                       re->dst_entry = item->util;
+               item = path_list_insert(pair->one->path, renames);
+               item->util = re;
+       }
+       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_queued_diff.nr = 0;
+       diff_flush(&opts);
+       return renames;
+}
+
+static int update_stages(const char *path, struct diff_filespec *o,
+                        struct diff_filespec *a, struct diff_filespec *b,
+                        int clear)
+{
+       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
+       if (clear)
+               if (remove_file_from_cache(path))
+                       return -1;
+       if (o)
+               if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
+                       return -1;
+       if (a)
+               if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
+                       return -1;
+       if (b)
+               if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
+                       return -1;
+       return 0;
+}
+
+static int remove_path(const char *name)
+{
+       int ret;
+       char *slash, *dirs;
+
+       ret = unlink(name);
+       if (ret)
+               return ret;
+       dirs = xstrdup(name);
+       while ((slash = strrchr(name, '/'))) {
+               *slash = '\0';
+               if (rmdir(name) != 0)
+                       break;
+       }
+       free(dirs);
+       return ret;
+}
+
+static int remove_file(int clean, const char *path, int no_wd)
+{
+       int update_cache = index_only || clean;
+       int update_working_directory = !index_only && !no_wd;
+
+       if (update_cache) {
+               if (remove_file_from_cache(path))
+                       return -1;
+       }
+       if (update_working_directory) {
+               unlink(path);
+               if (errno != ENOENT || errno != EISDIR)
+                       return -1;
+               remove_path(path);
+       }
+       return 0;
+}
+
+static char *unique_path(const char *path, const char *branch)
+{
+       char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1);
+       int suffix = 0;
+       struct stat st;
+       char *p = newpath + strlen(path);
+       strcpy(newpath, path);
+       *(p++) = '~';
+       strcpy(p, branch);
+       for (; *p; ++p)
+               if ('/' == *p)
+                       *p = '_';
+       while (path_list_has_path(&current_file_set, newpath) ||
+              path_list_has_path(&current_directory_set, newpath) ||
+              lstat(newpath, &st) == 0)
+               sprintf(p, "_%d", suffix++);
+
+       path_list_insert(newpath, &current_file_set);
+       return newpath;
+}
+
+static int mkdir_p(const char *path, unsigned long mode)
+{
+       /* path points to cache entries, so xstrdup before messing with it */
+       char *buf = xstrdup(path);
+       int result = safe_create_leading_directories(buf);
+       free(buf);
+       return result;
+}
+
+static void flush_buffer(int fd, const char *buf, unsigned long size)
+{
+       while (size > 0) {
+               long ret = write_in_full(fd, buf, size);
+               if (ret < 0) {
+                       /* Ignore epipe */
+                       if (errno == EPIPE)
+                               break;
+                       die("merge-recursive: %s", strerror(errno));
+               } else if (!ret) {
+                       die("merge-recursive: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+}
+
+static int make_room_for_path(const char *path)
+{
+       int status;
+       const char *msg = "failed to create path '%s'%s";
+
+       status = mkdir_p(path, 0777);
+       if (status) {
+               if (status == -3) {
+                       /* something else exists */
+                       error(msg, path, ": perhaps a D/F conflict?");
+                       return -1;
+               }
+               die(msg, path, "");
+       }
+
+       /* Successful unlink is good.. */
+       if (!unlink(path))
+               return 0;
+       /* .. and so is no existing file */
+       if (errno == ENOENT)
+               return 0;
+       /* .. but not some other error (who really cares what?) */
+       return error(msg, path, ": perhaps a D/F conflict?");
+}
+
+static void update_file_flags(const unsigned char *sha,
+                             unsigned mode,
+                             const char *path,
+                             int update_cache,
+                             int update_wd)
+{
+       if (index_only)
+               update_wd = 0;
+
+       if (update_wd) {
+               enum object_type type;
+               void *buf;
+               unsigned long size;
+
+               if (S_ISGITLINK(mode))
+                       die("cannot read object %s '%s': It is a submodule!",
+                           sha1_to_hex(sha), path);
+
+               buf = read_sha1_file(sha, &type, &size);
+               if (!buf)
+                       die("cannot read object %s '%s'", sha1_to_hex(sha), path);
+               if (type != OBJ_BLOB)
+                       die("blob expected for %s '%s'", sha1_to_hex(sha), path);
+
+               if (make_room_for_path(path) < 0) {
+                       update_wd = 0;
+                       goto update_index;
+               }
+               if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
+                       int fd;
+                       if (mode & 0100)
+                               mode = 0777;
+                       else
+                               mode = 0666;
+                       fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
+                       if (fd < 0)
+                               die("failed to open %s: %s", path, strerror(errno));
+                       flush_buffer(fd, buf, size);
+                       close(fd);
+               } else if (S_ISLNK(mode)) {
+                       char *lnk = xmemdupz(buf, size);
+                       mkdir_p(path, 0777);
+                       unlink(path);
+                       symlink(lnk, path);
+                       free(lnk);
+               } else
+                       die("do not know what to do with %06o %s '%s'",
+                           mode, sha1_to_hex(sha), path);
+       }
+ update_index:
+       if (update_cache)
+               add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+}
+
+static void update_file(int clean,
+                       const unsigned char *sha,
+                       unsigned mode,
+                       const char *path)
+{
+       update_file_flags(sha, mode, path, index_only || clean, !index_only);
+}
+
+/* Low level file merging, update and removal */
+
+struct merge_file_info
+{
+       unsigned char sha[20];
+       unsigned mode;
+       unsigned clean:1,
+                merge:1;
+};
+
+static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
+{
+       unsigned long size;
+       enum object_type type;
+
+       if (!hashcmp(sha1, null_sha1)) {
+               mm->ptr = xstrdup("");
+               mm->size = 0;
+               return;
+       }
+
+       mm->ptr = read_sha1_file(sha1, &type, &size);
+       if (!mm->ptr || type != OBJ_BLOB)
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+       mm->size = size;
+}
+
+static int merge_3way(mmbuffer_t *result_buf,
+                     struct diff_filespec *o,
+                     struct diff_filespec *a,
+                     struct diff_filespec *b,
+                     const char *branch1,
+                     const char *branch2)
+{
+       mmfile_t orig, src1, src2;
+       char *name1, *name2;
+       int merge_status;
+
+       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
+
+       fill_mm(o->sha1, &orig);
+       fill_mm(a->sha1, &src1);
+       fill_mm(b->sha1, &src2);
+
+       merge_status = ll_merge(result_buf, a->path, &orig,
+                               &src1, name1, &src2, name2,
+                               index_only);
+
+       free(name1);
+       free(name2);
+       free(orig.ptr);
+       free(src1.ptr);
+       free(src2.ptr);
+       return merge_status;
+}
+
+static struct merge_file_info merge_file(struct diff_filespec *o,
+               struct diff_filespec *a, struct diff_filespec *b,
+               const char *branch1, const char *branch2)
+{
+       struct merge_file_info result;
+       result.merge = 0;
+       result.clean = 1;
+
+       if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
+               result.clean = 0;
+               if (S_ISREG(a->mode)) {
+                       result.mode = a->mode;
+                       hashcpy(result.sha, a->sha1);
+               } else {
+                       result.mode = b->mode;
+                       hashcpy(result.sha, b->sha1);
+               }
+       } else {
+               if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1))
+                       result.merge = 1;
+
+               result.mode = a->mode == o->mode ? b->mode: a->mode;
+
+               if (sha_eq(a->sha1, o->sha1))
+                       hashcpy(result.sha, b->sha1);
+               else if (sha_eq(b->sha1, o->sha1))
+                       hashcpy(result.sha, a->sha1);
+               else if (S_ISREG(a->mode)) {
+                       mmbuffer_t result_buf;
+                       int merge_status;
+
+                       merge_status = merge_3way(&result_buf, o, a, b,
+                                                 branch1, branch2);
+
+                       if ((merge_status < 0) || !result_buf.ptr)
+                               die("Failed to execute internal merge");
+
+                       if (write_sha1_file(result_buf.ptr, result_buf.size,
+                                           blob_type, result.sha))
+                               die("Unable to add %s to database",
+                                   a->path);
+
+                       free(result_buf.ptr);
+                       result.clean = (merge_status == 0);
+               } else if (S_ISGITLINK(a->mode)) {
+                       result.clean = 0;
+                       hashcpy(result.sha, a->sha1);
+               } else if (S_ISLNK(a->mode)) {
+                       hashcpy(result.sha, a->sha1);
+
+                       if (!sha_eq(a->sha1, b->sha1))
+                               result.clean = 0;
+               } else {
+                       die("unsupported object type in the tree");
+               }
+       }
+
+       return result;
+}
+
+static void conflict_rename_rename(struct rename *ren1,
+                                  const char *branch1,
+                                  struct rename *ren2,
+                                  const char *branch2)
+{
+       char *del[2];
+       int delp = 0;
+       const char *ren1_dst = ren1->pair->two->path;
+       const char *ren2_dst = ren2->pair->two->path;
+       const char *dst_name1 = ren1_dst;
+       const char *dst_name2 = ren2_dst;
+       if (path_list_has_path(&current_directory_set, ren1_dst)) {
+               dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
+               output(1, "%s is a directory in %s added as %s instead",
+                      ren1_dst, branch2, dst_name1);
+               remove_file(0, ren1_dst, 0);
+       }
+       if (path_list_has_path(&current_directory_set, ren2_dst)) {
+               dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
+               output(1, "%s is a directory in %s added as %s instead",
+                      ren2_dst, branch1, dst_name2);
+               remove_file(0, ren2_dst, 0);
+       }
+       if (index_only) {
+               remove_file_from_cache(dst_name1);
+               remove_file_from_cache(dst_name2);
+               /*
+                * Uncomment to leave the conflicting names in the resulting tree
+                *
+                * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
+                * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
+                */
+       } else {
+               update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
+               update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+       }
+       while (delp--)
+               free(del[delp]);
+}
+
+static void conflict_rename_dir(struct rename *ren1,
+                               const char *branch1)
+{
+       char *new_path = unique_path(ren1->pair->two->path, branch1);
+       output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path);
+       remove_file(0, ren1->pair->two->path, 0);
+       update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
+       free(new_path);
+}
+
+static void conflict_rename_rename_2(struct rename *ren1,
+                                    const char *branch1,
+                                    struct rename *ren2,
+                                    const char *branch2)
+{
+       char *new_path1 = unique_path(ren1->pair->two->path, branch1);
+       char *new_path2 = unique_path(ren2->pair->two->path, branch2);
+       output(1, "Renamed %s to %s and %s to %s instead",
+              ren1->pair->one->path, new_path1,
+              ren2->pair->one->path, new_path2);
+       remove_file(0, ren1->pair->two->path, 0);
+       update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
+       update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
+       free(new_path2);
+       free(new_path1);
+}
+
+static int process_renames(struct path_list *a_renames,
+                          struct path_list *b_renames,
+                          const char *a_branch,
+                          const char *b_branch)
+{
+       int clean_merge = 1, i, j;
+       struct path_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
+       const struct rename *sre;
+
+       for (i = 0; i < a_renames->nr; i++) {
+               sre = a_renames->items[i].util;
+               path_list_insert(sre->pair->two->path, &a_by_dst)->util
+                       = sre->dst_entry;
+       }
+       for (i = 0; i < b_renames->nr; i++) {
+               sre = b_renames->items[i].util;
+               path_list_insert(sre->pair->two->path, &b_by_dst)->util
+                       = sre->dst_entry;
+       }
+
+       for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
+               int compare;
+               char *src;
+               struct path_list *renames1, *renames2, *renames2Dst;
+               struct rename *ren1 = NULL, *ren2 = NULL;
+               const char *branch1, *branch2;
+               const char *ren1_src, *ren1_dst;
+
+               if (i >= a_renames->nr) {
+                       compare = 1;
+                       ren2 = b_renames->items[j++].util;
+               } else if (j >= b_renames->nr) {
+                       compare = -1;
+                       ren1 = a_renames->items[i++].util;
+               } else {
+                       compare = strcmp(a_renames->items[i].path,
+                                       b_renames->items[j].path);
+                       if (compare <= 0)
+                               ren1 = a_renames->items[i++].util;
+                       if (compare >= 0)
+                               ren2 = b_renames->items[j++].util;
+               }
+
+               /* TODO: refactor, so that 1/2 are not needed */
+               if (ren1) {
+                       renames1 = a_renames;
+                       renames2 = b_renames;
+                       renames2Dst = &b_by_dst;
+                       branch1 = a_branch;
+                       branch2 = b_branch;
+               } else {
+                       struct rename *tmp;
+                       renames1 = b_renames;
+                       renames2 = a_renames;
+                       renames2Dst = &a_by_dst;
+                       branch1 = b_branch;
+                       branch2 = a_branch;
+                       tmp = ren2;
+                       ren2 = ren1;
+                       ren1 = tmp;
+               }
+               src = ren1->pair->one->path;
+
+               ren1->dst_entry->processed = 1;
+               ren1->src_entry->processed = 1;
+
+               if (ren1->processed)
+                       continue;
+               ren1->processed = 1;
+
+               ren1_src = ren1->pair->one->path;
+               ren1_dst = ren1->pair->two->path;
+
+               if (ren2) {
+                       const char *ren2_src = ren2->pair->one->path;
+                       const char *ren2_dst = ren2->pair->two->path;
+                       /* Renamed in 1 and renamed in 2 */
+                       if (strcmp(ren1_src, ren2_src) != 0)
+                               die("ren1.src != ren2.src");
+                       ren2->dst_entry->processed = 1;
+                       ren2->processed = 1;
+                       if (strcmp(ren1_dst, ren2_dst) != 0) {
+                               clean_merge = 0;
+                               output(1, "CONFLICT (rename/rename): "
+                                      "Rename \"%s\"->\"%s\" in branch \"%s\" "
+                                      "rename \"%s\"->\"%s\" in \"%s\"%s",
+                                      src, ren1_dst, branch1,
+                                      src, ren2_dst, branch2,
+                                      index_only ? " (left unresolved)": "");
+                               if (index_only) {
+                                       remove_file_from_cache(src);
+                                       update_file(0, ren1->pair->one->sha1,
+                                                   ren1->pair->one->mode, src);
+                               }
+                               conflict_rename_rename(ren1, branch1, ren2, branch2);
+                       } else {
+                               struct merge_file_info mfi;
+                               remove_file(1, ren1_src, 1);
+                               mfi = merge_file(ren1->pair->one,
+                                                ren1->pair->two,
+                                                ren2->pair->two,
+                                                branch1,
+                                                branch2);
+                               if (mfi.merge || !mfi.clean)
+                                       output(1, "Renamed %s->%s", src, ren1_dst);
+
+                               if (mfi.merge)
+                                       output(2, "Auto-merged %s", ren1_dst);
+
+                               if (!mfi.clean) {
+                                       output(1, "CONFLICT (content): merge conflict in %s",
+                                              ren1_dst);
+                                       clean_merge = 0;
+
+                                       if (!index_only)
+                                               update_stages(ren1_dst,
+                                                             ren1->pair->one,
+                                                             ren1->pair->two,
+                                                             ren2->pair->two,
+                                                             1 /* clear */);
+                               }
+                               update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+                       }
+               } else {
+                       /* Renamed in 1, maybe changed in 2 */
+                       struct path_list_item *item;
+                       /* we only use sha1 and mode of these */
+                       struct diff_filespec src_other, dst_other;
+                       int try_merge, stage = a_renames == renames1 ? 3: 2;
+
+                       remove_file(1, ren1_src, index_only || stage == 3);
+
+                       hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
+                       src_other.mode = ren1->src_entry->stages[stage].mode;
+                       hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
+                       dst_other.mode = ren1->dst_entry->stages[stage].mode;
+
+                       try_merge = 0;
+
+                       if (path_list_has_path(&current_directory_set, ren1_dst)) {
+                               clean_merge = 0;
+                               output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s "
+                                      " directory %s added in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren1_dst, branch2);
+                               conflict_rename_dir(ren1, branch1);
+                       } else if (sha_eq(src_other.sha1, null_sha1)) {
+                               clean_merge = 0;
+                               output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s "
+                                      "and deleted in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      branch2);
+                               update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
+                       } else if (!sha_eq(dst_other.sha1, null_sha1)) {
+                               const char *new_path;
+                               clean_merge = 0;
+                               try_merge = 1;
+                               output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. "
+                                      "%s added in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren1_dst, branch2);
+                               new_path = unique_path(ren1_dst, branch2);
+                               output(1, "Added as %s instead", new_path);
+                               update_file(0, dst_other.sha1, dst_other.mode, new_path);
+                       } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) {
+                               ren2 = item->util;
+                               clean_merge = 0;
+                               ren2->processed = 1;
+                               output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. "
+                                      "Renamed %s->%s in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren2->pair->one->path, ren2->pair->two->path, branch2);
+                               conflict_rename_rename_2(ren1, branch1, ren2, branch2);
+                       } else
+                               try_merge = 1;
+
+                       if (try_merge) {
+                               struct diff_filespec *o, *a, *b;
+                               struct merge_file_info mfi;
+                               src_other.path = (char *)ren1_src;
+
+                               o = ren1->pair->one;
+                               if (a_renames == renames1) {
+                                       a = ren1->pair->two;
+                                       b = &src_other;
+                               } else {
+                                       b = ren1->pair->two;
+                                       a = &src_other;
+                               }
+                               mfi = merge_file(o, a, b,
+                                               a_branch, b_branch);
+
+                               if (mfi.clean &&
+                                   sha_eq(mfi.sha, ren1->pair->two->sha1) &&
+                                   mfi.mode == ren1->pair->two->mode)
+                                       /*
+                                        * This messaged is part of
+                                        * t6022 test. If you change
+                                        * it update the test too.
+                                        */
+                                       output(3, "Skipped %s (merged same as existing)", ren1_dst);
+                               else {
+                                       if (mfi.merge || !mfi.clean)
+                                               output(1, "Renamed %s => %s", ren1_src, ren1_dst);
+                                       if (mfi.merge)
+                                               output(2, "Auto-merged %s", ren1_dst);
+                                       if (!mfi.clean) {
+                                               output(1, "CONFLICT (rename/modify): Merge conflict in %s",
+                                                      ren1_dst);
+                                               clean_merge = 0;
+
+                                               if (!index_only)
+                                                       update_stages(ren1_dst,
+                                                                     o, a, b, 1);
+                                       }
+                                       update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+                               }
+                       }
+               }
+       }
+       path_list_clear(&a_by_dst, 0);
+       path_list_clear(&b_by_dst, 0);
+
+       return clean_merge;
+}
+
+static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
+{
+       return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
+}
+
+/* Per entry merge function */
+static int process_entry(const char *path, struct stage_data *entry,
+                        const char *branch1,
+                        const char *branch2)
+{
+       /*
+       printf("processing entry, clean cache: %s\n", index_only ? "yes": "no");
+       print_index_entry("\tpath: ", entry);
+       */
+       int clean_merge = 1;
+       unsigned o_mode = entry->stages[1].mode;
+       unsigned a_mode = entry->stages[2].mode;
+       unsigned b_mode = entry->stages[3].mode;
+       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
+       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
+       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
+
+       if (o_sha && (!a_sha || !b_sha)) {
+               /* Case A: Deleted in one */
+               if ((!a_sha && !b_sha) ||
+                   (sha_eq(a_sha, o_sha) && !b_sha) ||
+                   (!a_sha && sha_eq(b_sha, o_sha))) {
+                       /* Deleted in both or deleted in one and
+                        * unchanged in the other */
+                       if (a_sha)
+                               output(2, "Removed %s", path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(1, path, !a_sha);
+               } else {
+                       /* Deleted in one and changed in the other */
+                       clean_merge = 0;
+                       if (!a_sha) {
+                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
+                                      "and modified in %s. Version %s of %s left in tree.",
+                                      path, branch1,
+                                      branch2, branch2, path);
+                               update_file(0, b_sha, b_mode, path);
+                       } else {
+                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
+                                      "and modified in %s. Version %s of %s left in tree.",
+                                      path, branch2,
+                                      branch1, branch1, path);
+                               update_file(0, a_sha, a_mode, path);
+                       }
+               }
+
+       } else if ((!o_sha && a_sha && !b_sha) ||
+                  (!o_sha && !a_sha && b_sha)) {
+               /* Case B: Added in one. */
+               const char *add_branch;
+               const char *other_branch;
+               unsigned mode;
+               const unsigned char *sha;
+               const char *conf;
+
+               if (a_sha) {
+                       add_branch = branch1;
+                       other_branch = branch2;
+                       mode = a_mode;
+                       sha = a_sha;
+                       conf = "file/directory";
+               } else {
+                       add_branch = branch2;
+                       other_branch = branch1;
+                       mode = b_mode;
+                       sha = b_sha;
+                       conf = "directory/file";
+               }
+               if (path_list_has_path(&current_directory_set, path)) {
+                       const char *new_path = unique_path(path, add_branch);
+                       clean_merge = 0;
+                       output(1, "CONFLICT (%s): There is a directory with name %s in %s. "
+                              "Added %s as %s",
+                              conf, path, other_branch, path, new_path);
+                       remove_file(0, path, 0);
+                       update_file(0, sha, mode, new_path);
+               } else {
+                       output(2, "Added %s", path);
+                       update_file(1, sha, mode, path);
+               }
+       } else if (a_sha && b_sha) {
+               /* Case C: Added in both (check for same permissions) and */
+               /* case D: Modified in both, but differently. */
+               const char *reason = "content";
+               struct merge_file_info mfi;
+               struct diff_filespec o, a, b;
+
+               if (!o_sha) {
+                       reason = "add/add";
+                       o_sha = (unsigned char *)null_sha1;
+               }
+               output(2, "Auto-merged %s", path);
+               o.path = a.path = b.path = (char *)path;
+               hashcpy(o.sha1, o_sha);
+               o.mode = o_mode;
+               hashcpy(a.sha1, a_sha);
+               a.mode = a_mode;
+               hashcpy(b.sha1, b_sha);
+               b.mode = b_mode;
+
+               mfi = merge_file(&o, &a, &b,
+                                branch1, branch2);
+
+               clean_merge = mfi.clean;
+               if (mfi.clean)
+                       update_file(1, mfi.sha, mfi.mode, path);
+               else if (S_ISGITLINK(mfi.mode))
+                       output(1, "CONFLICT (submodule): Merge conflict in %s "
+                              "- needs %s", path, sha1_to_hex(b.sha1));
+               else {
+                       output(1, "CONFLICT (%s): Merge conflict in %s",
+                                       reason, path);
+
+                       if (index_only)
+                               update_file(0, mfi.sha, mfi.mode, path);
+                       else
+                               update_file_flags(mfi.sha, mfi.mode, path,
+                                             0 /* update_cache */, 1 /* update_working_directory */);
+               }
+       } else if (!o_sha && !a_sha && !b_sha) {
+               /*
+                * this entry was deleted altogether. a_mode == 0 means
+                * we had that path and want to actively remove it.
+                */
+               remove_file(1, path, !a_mode);
+       } else
+               die("Fatal merge failure, shouldn't happen.");
+
+       return clean_merge;
+}
+
+int merge_trees(struct tree *head,
+               struct tree *merge,
+               struct tree *common,
+               const char *branch1,
+               const char *branch2,
+               struct tree **result)
+{
+       int code, clean;
+
+       if (subtree_merge) {
+               merge = shift_tree_object(head, merge);
+               common = shift_tree_object(head, common);
+       }
+
+       if (sha_eq(common->object.sha1, merge->object.sha1)) {
+               output(0, "Already uptodate!");
+               *result = head;
+               return 1;
+       }
+
+       code = git_merge_trees(index_only, common, head, merge);
+
+       if (code != 0)
+               die("merging of trees %s and %s failed",
+                   sha1_to_hex(head->object.sha1),
+                   sha1_to_hex(merge->object.sha1));
+
+       if (unmerged_cache()) {
+               struct path_list *entries, *re_head, *re_merge;
+               int i;
+               path_list_clear(&current_file_set, 1);
+               path_list_clear(&current_directory_set, 1);
+               get_files_dirs(head);
+               get_files_dirs(merge);
+
+               entries = get_unmerged();
+               re_head  = get_renames(head, common, head, merge, entries);
+               re_merge = get_renames(merge, common, head, merge, entries);
+               clean = process_renames(re_head, re_merge,
+                               branch1, branch2);
+               for (i = 0; i < entries->nr; i++) {
+                       const char *path = entries->items[i].path;
+                       struct stage_data *e = entries->items[i].util;
+                       if (!e->processed
+                               && !process_entry(path, e, branch1, branch2))
+                               clean = 0;
+               }
+
+               path_list_clear(re_merge, 0);
+               path_list_clear(re_head, 0);
+               path_list_clear(entries, 1);
+
+       }
+       else
+               clean = 1;
+
+       if (index_only)
+               *result = write_tree_from_memory();
+
+       return clean;
+}
+
+static struct commit_list *reverse_commit_list(struct commit_list *list)
+{
+       struct commit_list *next = NULL, *current, *backup;
+       for (current = list; current; current = backup) {
+               backup = current->next;
+               current->next = next;
+               next = current;
+       }
+       return next;
+}
+
+/*
+ * Merge the commits h1 and h2, return the resulting virtual
+ * commit object and a flag indicating the cleanness of the merge.
+ */
+int merge_recursive(struct commit *h1,
+                   struct commit *h2,
+                   const char *branch1,
+                   const char *branch2,
+                   struct commit_list *ca,
+                   struct commit **result)
+{
+       struct commit_list *iter;
+       struct commit *merged_common_ancestors;
+       struct tree *mrtree = mrtree;
+       int clean;
+
+       if (show(4)) {
+               output(4, "Merging:");
+               output_commit_title(h1);
+               output_commit_title(h2);
+       }
+
+       if (!ca) {
+               ca = get_merge_bases(h1, h2, 1);
+               ca = reverse_commit_list(ca);
+       }
+
+       if (show(5)) {
+               output(5, "found %u common ancestor(s):", commit_list_count(ca));
+               for (iter = ca; iter; iter = iter->next)
+                       output_commit_title(iter->item);
+       }
+
+       merged_common_ancestors = pop_commit(&ca);
+       if (merged_common_ancestors == NULL) {
+               /* if there is no common ancestor, make an empty tree */
+               struct tree *tree = xcalloc(1, sizeof(struct tree));
+
+               tree->object.parsed = 1;
+               tree->object.type = OBJ_TREE;
+               pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+               merged_common_ancestors = make_virtual_commit(tree, "ancestor");
+       }
+
+       for (iter = ca; iter; iter = iter->next) {
+               call_depth++;
+               /*
+                * When the merge fails, the result contains files
+                * with conflict markers. The cleanness flag is
+                * ignored, it was never actually used, as result of
+                * merge_trees has always overwritten it: the committed
+                * "conflicts" were already resolved.
+                */
+               discard_cache();
+               merge_recursive(merged_common_ancestors, iter->item,
+                               "Temporary merge branch 1",
+                               "Temporary merge branch 2",
+                               NULL,
+                               &merged_common_ancestors);
+               call_depth--;
+
+               if (!merged_common_ancestors)
+                       die("merge returned no commit");
+       }
+
+       discard_cache();
+       if (!call_depth) {
+               read_cache();
+               index_only = 0;
+       } else
+               index_only = 1;
+
+       clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,
+                           branch1, branch2, &mrtree);
+
+       if (index_only) {
+               *result = make_virtual_commit(mrtree, "merged tree");
+               commit_list_insert(h1, &(*result)->parents);
+               commit_list_insert(h2, &(*result)->parents->next);
+       }
+       flush_output();
+       return clean;
+}
+
+static const char *better_branch_name(const char *branch)
+{
+       static char githead_env[8 + 40 + 1];
+       char *name;
+
+       if (strlen(branch) != 40)
+               return branch;
+       sprintf(githead_env, "GITHEAD_%s", branch);
+       name = getenv(githead_env);
+       return name ? name : branch;
+}
+
+static struct commit *get_ref(const char *ref)
+{
+       unsigned char sha1[20];
+       struct object *object;
+
+       if (get_sha1(ref, sha1))
+               die("Could not resolve ref '%s'", ref);
+       object = deref_tag(parse_object(sha1), ref, strlen(ref));
+       if (!object)
+               return NULL;
+       if (object->type == OBJ_TREE)
+               return make_virtual_commit((struct tree*)object,
+                       better_branch_name(ref));
+       if (object->type != OBJ_COMMIT)
+               return NULL;
+       if (parse_commit((struct commit *)object))
+               die("Could not parse commit '%s'", sha1_to_hex(object->sha1));
+       return (struct commit *)object;
+}
+
+static int merge_config(const char *var, const char *value)
+{
+       if (!strcasecmp(var, "merge.verbosity")) {
+               verbosity = git_config_int(var, value);
+               return 0;
+       }
+       if (!strcasecmp(var, "diff.renamelimit")) {
+               rename_limit = git_config_int(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
+int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
+{
+       static const char *bases[20];
+       static unsigned bases_count = 0;
+       int i, clean;
+       const char *branch1, *branch2;
+       struct commit *result, *h1, *h2;
+       struct commit_list *ca = NULL;
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       int index_fd;
+
+       if (argv[0]) {
+               int namelen = strlen(argv[0]);
+               if (8 < namelen &&
+                   !strcmp(argv[0] + namelen - 8, "-subtree"))
+                       subtree_merge = 1;
+       }
+
+       git_config(merge_config);
+       if (getenv("GIT_MERGE_VERBOSITY"))
+               verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
+
+       if (argc < 4)
+               die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
+
+       for (i = 1; i < argc; ++i) {
+               if (!strcmp(argv[i], "--"))
+                       break;
+               if (bases_count < sizeof(bases)/sizeof(*bases))
+                       bases[bases_count++] = argv[i];
+       }
+       if (argc - i != 3) /* "--" "<head>" "<remote>" */
+               die("Not handling anything other than two heads merge.");
+       if (verbosity >= 5)
+               buffer_output = 0;
+
+       branch1 = argv[++i];
+       branch2 = argv[++i];
+
+       h1 = get_ref(branch1);
+       h2 = get_ref(branch2);
+
+       branch1 = better_branch_name(branch1);
+       branch2 = better_branch_name(branch2);
+
+       if (show(3))
+               printf("Merging %s with %s\n", branch1, branch2);
+
+       index_fd = hold_locked_index(lock, 1);
+
+       for (i = 0; i < bases_count; i++) {
+               struct commit *ancestor = get_ref(bases[i]);
+               ca = commit_list_insert(ancestor, &ca);
+       }
+       clean = merge_recursive(h1, h2, branch1, branch2, ca, &result);
+
+       if (active_cache_changed &&
+           (write_cache(index_fd, active_cache, active_nr) ||
+            commit_locked_index(lock)))
+                       die ("unable to write %s", get_index_file());
+
+       return clean ? 0: 1;
+}
index 990e21355d189096bdc0c808fb536b7bfb85b0a1..94f6dd2aad2da512bab0be2e84859eced6553cc9 100644 (file)
@@ -164,7 +164,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                }
 
                                dst = add_slash(dst);
-                               dst_len = strlen(dst) - 1;
+                               dst_len = strlen(dst);
 
                                for (j = 0; j < last - first; j++) {
                                        const char *path =
@@ -172,7 +172,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                        source[argc + j] = path;
                                        destination[argc + j] =
                                                prefix_path(dst, dst_len,
-                                                       path + length);
+                                                       path + length + 1);
                                        modes[argc + j] = INDEX;
                                }
                                argc += last - first;
index a0c89a827b666d8e01ba64597b732205ce1a643e..384da4db13d9ff82b9bd45e6ab26b5dbf925dc22 100644 (file)
@@ -125,18 +125,18 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
 }
 
 /* returns a static buffer */
-static const char* get_rev_name(struct object *o)
+static const char *get_rev_name(const struct object *o)
 {
        static char buffer[1024];
        struct rev_name *n;
        struct commit *c;
 
        if (o->type != OBJ_COMMIT)
-               return "undefined";
+               return NULL;
        c = (struct commit *) o;
        n = c->util;
        if (!n)
-               return "undefined";
+               return NULL;
 
        if (!n->generation)
                return n->tip_name;
@@ -151,6 +151,26 @@ static const char* get_rev_name(struct object *o)
        }
 }
 
+static void show_name(const struct object *obj,
+                     const char *caller_name,
+                     int always, int allow_undefined, int name_only)
+{
+       const char *name;
+       const unsigned char *sha1 = obj->sha1;
+
+       if (!name_only)
+               printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1));
+       name = get_rev_name(obj);
+       if (name)
+               printf("%s\n", name);
+       else if (allow_undefined)
+               printf("undefined\n");
+       else if (always)
+               printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV));
+       else
+               die("cannot describe '%s'", sha1_to_hex(sha1));
+}
+
 static char const * const name_rev_usage[] = {
        "git-name-rev [options] ( --all | --stdin | <commit>... )",
        NULL
@@ -159,7 +179,7 @@ static char const * const name_rev_usage[] = {
 int cmd_name_rev(int argc, const char **argv, const char *prefix)
 {
        struct object_array revs = { 0, 0, NULL };
-       int all = 0, transform_stdin = 0;
+       int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0;
        struct name_ref_data data = { 0, 0, NULL };
        struct option opts[] = {
                OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"),
@@ -169,6 +189,9 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                OPT_GROUP(""),
                OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"),
                OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"),
+               OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"),
+               OPT_BOOLEAN(0, "always",     &always,
+                          "show abbreviated commit object as fallback"),
                OPT_END(),
        };
 
@@ -226,7 +249,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                                else if (++forty == 40 &&
                                                !ishex(*(p+1))) {
                                        unsigned char sha1[40];
-                                       const char *name = "undefined";
+                                       const char *name = NULL;
                                        char c = *(p+1);
 
                                        forty = 0;
@@ -240,11 +263,10 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                                        }
                                        *(p+1) = c;
 
-                                       if (!strcmp(name, "undefined"))
+                                       if (!name)
                                                continue;
 
-                                       fwrite(p_start, p - p_start + 1, 1,
-                                              stdout);
+                                       fwrite(p_start, p - p_start + 1, 1, stdout);
                                        printf(" (%s)", name);
                                        p_start = p + 1;
                                }
@@ -258,21 +280,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                int i, max;
 
                max = get_max_object_index();
-               for (i = 0; i < max; i++) {
-                       struct object * obj = get_indexed_object(i);
-                       if (!obj)
-                               continue;
-                       if (!data.name_only)
-                               printf("%s ", sha1_to_hex(obj->sha1));
-                       printf("%s\n", get_rev_name(obj));
-               }
+               for (i = 0; i < max; i++)
+                       show_name(get_indexed_object(i), NULL,
+                                 always, allow_undefined, data.name_only);
        } else {
                int i;
-               for (i = 0; i < revs.nr; i++) {
-                       if (!data.name_only)
-                               printf("%s ", revs.objects[i].name);
-                       printf("%s\n", get_rev_name(revs.objects[i].item));
-               }
+               for (i = 0; i < revs.nr; i++)
+                       show_name(revs.objects[i].item, revs.objects[i].name,
+                                 always, allow_undefined, data.name_only);
        }
 
        return 0;
index d3efeff03f89cc0f4d0da463ddf878c28effb31e..f504cff7566159332c89f830a871da0ea05868fe 100644 (file)
@@ -8,14 +8,17 @@
 #include "tree.h"
 #include "delta.h"
 #include "pack.h"
+#include "pack-revindex.h"
 #include "csum-file.h"
 #include "tree-walk.h"
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
 #include "progress.h"
+#include "refs.h"
 
 #ifdef THREADED_DELTA_SEARCH
+#include "thread-utils.h"
 #include <pthread.h>
 #endif
 
@@ -25,7 +28,8 @@ git-pack-objects [{ -q | --progress | --all-progress }] \n\
        [--window=N] [--window-memory=N] [--depth=N] \n\
        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
        [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
-       [--stdout | base-name] [--keep-unreachable] [<ref-list | <object-list]";
+       [--stdout | base-name] [--include-tag] [--keep-unreachable] \n\
+       [<ref-list | <object-list]";
 
 struct object_entry {
        struct pack_idx_entry idx;
@@ -61,14 +65,14 @@ static struct pack_idx_entry **written_list;
 static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
 
 static int non_empty;
-static int no_reuse_delta, no_reuse_object, keep_unreachable;
+static int no_reuse_delta, no_reuse_object, keep_unreachable, include_tag;
 static int local;
 static int incremental;
 static int allow_ofs_delta;
 static const char *base_name;
 static int progress = 1;
 static int window = 10;
-static uint32_t pack_size_limit;
+static uint32_t pack_size_limit, pack_size_limit_cfg;
 static int depth = 50;
 static int delta_search_threads = 1;
 static int pack_to_stdout;
@@ -91,158 +95,12 @@ static unsigned long window_memory_limit = 0;
 static int *object_ix;
 static int object_ix_hashsz;
 
-/*
- * Pack index for existing packs give us easy access to the offsets into
- * corresponding pack file where each object's data starts, but the entries
- * do not store the size of the compressed representation (uncompressed
- * size is easily available by examining the pack entry header).  It is
- * also rather expensive to find the sha1 for an object given its offset.
- *
- * We build a hashtable of existing packs (pack_revindex), and keep reverse
- * index here -- pack index file is sorted by object name mapping to offset;
- * this pack_revindex[].revindex array is a list of offset/index_nr pairs
- * ordered by offset, so if you know the offset of an object, next offset
- * is where its packed representation ends and the index_nr can be used to
- * get the object sha1 from the main index.
- */
-struct revindex_entry {
-       off_t offset;
-       unsigned int nr;
-};
-struct pack_revindex {
-       struct packed_git *p;
-       struct revindex_entry *revindex;
-};
-static struct  pack_revindex *pack_revindex;
-static int pack_revindex_hashsz;
-
 /*
  * stats
  */
 static uint32_t written, written_delta;
 static uint32_t reused, reused_delta;
 
-static int pack_revindex_ix(struct packed_git *p)
-{
-       unsigned long ui = (unsigned long)p;
-       int i;
-
-       ui = ui ^ (ui >> 16); /* defeat structure alignment */
-       i = (int)(ui % pack_revindex_hashsz);
-       while (pack_revindex[i].p) {
-               if (pack_revindex[i].p == p)
-                       return i;
-               if (++i == pack_revindex_hashsz)
-                       i = 0;
-       }
-       return -1 - i;
-}
-
-static void prepare_pack_ix(void)
-{
-       int num;
-       struct packed_git *p;
-       for (num = 0, p = packed_git; p; p = p->next)
-               num++;
-       if (!num)
-               return;
-       pack_revindex_hashsz = num * 11;
-       pack_revindex = xcalloc(sizeof(*pack_revindex), pack_revindex_hashsz);
-       for (p = packed_git; p; p = p->next) {
-               num = pack_revindex_ix(p);
-               num = - 1 - num;
-               pack_revindex[num].p = p;
-       }
-       /* revindex elements are lazily initialized */
-}
-
-static int cmp_offset(const void *a_, const void *b_)
-{
-       const struct revindex_entry *a = a_;
-       const struct revindex_entry *b = b_;
-       return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0;
-}
-
-/*
- * Ordered list of offsets of objects in the pack.
- */
-static void prepare_pack_revindex(struct pack_revindex *rix)
-{
-       struct packed_git *p = rix->p;
-       int num_ent = p->num_objects;
-       int i;
-       const char *index = p->index_data;
-
-       rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
-       index += 4 * 256;
-
-       if (p->index_version > 1) {
-               const uint32_t *off_32 =
-                       (uint32_t *)(index + 8 + p->num_objects * (20 + 4));
-               const uint32_t *off_64 = off_32 + p->num_objects;
-               for (i = 0; i < num_ent; i++) {
-                       uint32_t off = ntohl(*off_32++);
-                       if (!(off & 0x80000000)) {
-                               rix->revindex[i].offset = off;
-                       } else {
-                               rix->revindex[i].offset =
-                                       ((uint64_t)ntohl(*off_64++)) << 32;
-                               rix->revindex[i].offset |=
-                                       ntohl(*off_64++);
-                       }
-                       rix->revindex[i].nr = i;
-               }
-       } else {
-               for (i = 0; i < num_ent; i++) {
-                       uint32_t hl = *((uint32_t *)(index + 24 * i));
-                       rix->revindex[i].offset = ntohl(hl);
-                       rix->revindex[i].nr = i;
-               }
-       }
-
-       /* This knows the pack format -- the 20-byte trailer
-        * follows immediately after the last object data.
-        */
-       rix->revindex[num_ent].offset = p->pack_size - 20;
-       rix->revindex[num_ent].nr = -1;
-       qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset);
-}
-
-static struct revindex_entry * find_packed_object(struct packed_git *p,
-                                                 off_t ofs)
-{
-       int num;
-       int lo, hi;
-       struct pack_revindex *rix;
-       struct revindex_entry *revindex;
-       num = pack_revindex_ix(p);
-       if (num < 0)
-               die("internal error: pack revindex uninitialized");
-       rix = &pack_revindex[num];
-       if (!rix->revindex)
-               prepare_pack_revindex(rix);
-       revindex = rix->revindex;
-       lo = 0;
-       hi = p->num_objects + 1;
-       do {
-               int mi = (lo + hi) / 2;
-               if (revindex[mi].offset == ofs) {
-                       return revindex + mi;
-               }
-               else if (ofs < revindex[mi].offset)
-                       hi = mi;
-               else
-                       lo = mi + 1;
-       } while (lo < hi);
-       die("internal error: pack revindex corrupt");
-}
-
-static const unsigned char *find_packed_object_name(struct packed_git *p,
-                                                   off_t ofs)
-{
-       struct revindex_entry *entry = find_packed_object(p, ofs);
-       return nth_packed_object_sha1(p, entry->nr);
-}
 
 static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
 {
@@ -509,7 +367,7 @@ static unsigned long write_object(struct sha1file *f,
                }
                hdrlen = encode_header(obj_type, entry->size, header);
                offset = entry->in_pack_offset;
-               revidx = find_packed_object(p, offset);
+               revidx = find_pack_revindex(p, offset);
                datalen = revidx[1].offset - offset;
                if (!pack_to_stdout && p->index_version > 1 &&
                    check_pack_crc(p, &w_curs, offset, datalen, revidx->nr))
@@ -1161,8 +1019,11 @@ static void check_object(struct object_entry *entry)
                                die("delta base offset out of bound for %s",
                                    sha1_to_hex(entry->idx.sha1));
                        ofs = entry->in_pack_offset - ofs;
-                       if (!no_reuse_delta && !entry->preferred_base)
-                               base_ref = find_packed_object_name(p, ofs);
+                       if (!no_reuse_delta && !entry->preferred_base) {
+                               struct revindex_entry *revidx;
+                               revidx = find_pack_revindex(p, ofs);
+                               base_ref = nth_packed_object_sha1(p, revidx->nr);
+                       }
                        entry->in_pack_header_size = used + used_0;
                        break;
                }
@@ -1239,9 +1100,11 @@ 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);
 
-       prepare_pack_ix();
+       init_pack_revindex();
+
        for (i = 0; i < nr_objects; i++)
                check_object(sorted_by_offset[i]);
+
        free(sorted_by_offset);
 }
 
@@ -1428,8 +1291,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
         * accounting lock.  Compiler will optimize the strangeness
         * away when THREADED_DELTA_SEARCH is not defined.
         */
-       if (trg_entry->delta_data)
-               free(trg_entry->delta_data);
+       free(trg_entry->delta_data);
        cache_lock();
        if (trg_entry->delta_data) {
                delta_cache_size -= trg_entry->delta_size;
@@ -1770,6 +1632,18 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
 #define ll_find_deltas(l, s, w, d, p)  find_deltas(l, &s, w, d, p)
 #endif
 
+static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       unsigned char peeled[20];
+
+       if (!prefixcmp(path, "refs/tags/") && /* is a tag? */
+           !peel_ref(path, peeled)        && /* peelable? */
+           !is_null_sha1(peeled)          && /* annotated tag? */
+           locate_object_entry(peeled))      /* object packed? */
+               add_object_entry(sha1, OBJ_TAG, NULL, 0);
+       return 0;
+}
+
 static void prepare_pack(int window, int depth)
 {
        struct object_entry **delta_list;
@@ -1852,11 +1726,11 @@ static int git_pack_config(const char *k, const char *v)
        }
        if (!strcmp(k, "pack.threads")) {
                delta_search_threads = git_config_int(k, v);
-               if (delta_search_threads < 1)
+               if (delta_search_threads < 0)
                        die("invalid number of threads specified (%d)",
                            delta_search_threads);
 #ifndef THREADED_DELTA_SEARCH
-               if (delta_search_threads > 1)
+               if (delta_search_threads != 1)
                        warning("no threads support, ignoring %s", k);
 #endif
                return 0;
@@ -1867,6 +1741,10 @@ static int git_pack_config(const char *k, const char *v)
                        die("bad pack.indexversion=%d", pack_idx_default_version);
                return 0;
        }
+       if (!strcmp(k, "pack.packsizelimit")) {
+               pack_size_limit_cfg = git_config_ulong(k, v);
+               return 0;
+       }
        return git_default_config(k, v);
 }
 
@@ -2009,7 +1887,6 @@ static void get_object_list(int ac, const char **av)
 
        init_revisions(&revs, NULL);
        save_commit_buffer = 0;
-       track_object_refs = 0;
        setup_revisions(ac, av, &revs, NULL);
 
        while (fgets(line, sizeof(line), stdin) != NULL) {
@@ -2029,7 +1906,8 @@ static void get_object_list(int ac, const char **av)
                        die("bad revision '%s'", line);
        }
 
-       prepare_revision_walk(&revs);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
        mark_edges_uninteresting(revs.commits, &revs, show_edge);
        traverse_commit_list(&revs, show_commit, show_object);
 
@@ -2096,6 +1974,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
                if (!prefixcmp(arg, "--max-pack-size=")) {
                        char *end;
+                       pack_size_limit_cfg = 0;
                        pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024;
                        if (!arg[16] || *end)
                                usage(pack_usage);
@@ -2116,10 +1995,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                if (!prefixcmp(arg, "--threads=")) {
                        char *end;
                        delta_search_threads = strtoul(arg+10, &end, 0);
-                       if (!arg[10] || *end || delta_search_threads < 1)
+                       if (!arg[10] || *end || delta_search_threads < 0)
                                usage(pack_usage);
 #ifndef THREADED_DELTA_SEARCH
-                       if (delta_search_threads > 1)
+                       if (delta_search_threads != 1)
                                warning("no threads support, "
                                        "ignoring %s", arg);
 #endif
@@ -2168,6 +2047,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        keep_unreachable = 1;
                        continue;
                }
+               if (!strcmp("--include-tag", arg)) {
+                       include_tag = 1;
+                       continue;
+               }
                if (!strcmp("--unpacked", arg) ||
                    !prefixcmp(arg, "--unpacked=") ||
                    !strcmp("--reflog", arg) ||
@@ -2220,12 +2103,20 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (pack_to_stdout != !base_name)
                usage(pack_usage);
 
+       if (!pack_to_stdout && !pack_size_limit)
+               pack_size_limit = pack_size_limit_cfg;
+
        if (pack_to_stdout && pack_size_limit)
                die("--max-pack-size cannot be used to build a pack for transfer.");
 
        if (!pack_to_stdout && thin)
                die("--thin cannot be used to build an indexable pack.");
 
+#ifdef THREADED_DELTA_SEARCH
+       if (!delta_search_threads)      /* --threads=0 means autodetect */
+               delta_search_threads = online_cpus();
+#endif
+
        prepare_packed_git();
 
        if (progress)
@@ -2236,6 +2127,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                rp_av[rp_ac] = NULL;
                get_object_list(rp_ac, rp_av);
        }
+       if (include_tag && nr_result)
+               for_each_ref(add_ref_tag, NULL);
        stop_progress(&progress_state);
 
        if (non_empty && !nr_result)
index 9f727c00f689ae23373502c7fb21b2234a1c1748..b68c6813b8c71e0e00790eb422e57f8e219095fb 100644 (file)
@@ -44,15 +44,6 @@ static void set_refspecs(const char **refs, int nr)
                        strcat(tag, refs[i]);
                        ref = tag;
                }
-               if (!strcmp("HEAD", ref)) {
-                       unsigned char sha1_dummy[20];
-                       ref = resolve_ref(ref, sha1_dummy, 1, NULL);
-                       if (!ref)
-                               die("HEAD cannot be resolved.");
-                       if (prefixcmp(ref, "refs/heads/"))
-                               die("HEAD cannot be resolved to branch.");
-                       ref = xstrdup(ref + 11);
-               }
                add_refspec(ref);
        }
 }
index c0ea0342b755c6749391803d19f852182c0c512e..0138f5a9172034b2ce34222ff077a975f8998005 100644 (file)
@@ -41,12 +41,12 @@ static int read_cache_unmerged(void)
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce)) {
+                       remove_index_entry(ce);
                        if (last && !strcmp(ce->name, last->name))
                                continue;
                        cache_tree_invalidate_path(active_cache_tree, ce->name);
                        last = ce;
-                       ce->ce_mode = 0;
-                       ce->ce_flags &= ~htons(CE_STAGEMASK);
+                       continue;
                }
                *dst++ = ce;
        }
@@ -269,7 +269,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                parse_tree(tree);
                init_tree_desc(t+i, tree->buffer, tree->size);
        }
-       unpack_trees(nr_trees, t, &opts);
+       if (unpack_trees(nr_trees, t, &opts))
+               return 128;
 
        /*
         * When reading only one tree (either the most basic form,
index ab53c8cb7c08298c3ab0f69bf600a70385dafa64..280e24e1514f2989b571acc768d7067cbb223933 100644 (file)
@@ -14,6 +14,8 @@
 
 static const char reflog_expire_usage[] =
 "git-reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+static const char reflog_delete_usage[] =
+"git-reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
 
 static unsigned long default_reflog_expire;
 static unsigned long default_reflog_expire_unreachable;
@@ -22,9 +24,12 @@ struct cmd_reflog_expire_cb {
        struct rev_info revs;
        int dry_run;
        int stalefix;
+       int rewrite;
+       int updateref;
        int verbose;
        unsigned long expire_total;
        unsigned long expire_unreachable;
+       int recno;
 };
 
 struct expire_reflog_cb {
@@ -32,6 +37,7 @@ struct expire_reflog_cb {
        const char *ref;
        struct commit *ref_commit;
        struct cmd_reflog_expire_cb *cmd;
+       unsigned char last_kept_sha1[20];
 };
 
 struct collected_reflog {
@@ -213,6 +219,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
        if (timestamp < cb->cmd->expire_total)
                goto prune;
 
+       if (cb->cmd->rewrite)
+               osha1 = cb->last_kept_sha1;
+
        old = new = NULL;
        if (cb->cmd->stalefix &&
            (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
@@ -230,6 +239,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
                        goto prune;
        }
 
+       if (cb->cmd->recno && --(cb->cmd->recno) == 0)
+               goto prune;
+
        if (cb->newlog) {
                char sign = (tz < 0) ? '-' : '+';
                int zone = (tz < 0) ? (-tz) : tz;
@@ -237,6 +249,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
                        sha1_to_hex(osha1), sha1_to_hex(nsha1),
                        email, timestamp, sign, zone,
                        message);
+               hashcpy(cb->last_kept_sha1, nsha1);
        }
        if (cb->cmd->verbose)
                printf("keep %s", message);
@@ -280,10 +293,20 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
                        status |= error("%s: %s", strerror(errno),
                                        newlog_path);
                        unlink(newlog_path);
+               } else if (cmd->updateref &&
+                       (write_in_full(lock->lock_fd,
+                               sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
+                        write_in_full(lock->lock_fd, "\n", 1) != 1 ||
+                        close_ref(lock) < 0)) {
+                       status |= error("Couldn't write %s",
+                               lock->lk->filename);
+                       unlink(newlog_path);
                } else if (rename(newlog_path, log_file)) {
                        status |= error("cannot rename %s to %s",
                                        newlog_path, log_file);
                        unlink(newlog_path);
+               } else if (cmd->updateref && commit_ref(lock)) {
+                       status |= error("Couldn't set %s", lock->ref_name);
                }
        }
        free(newlog_path);
@@ -358,6 +381,10 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                        cb.expire_unreachable = approxidate(arg + 21);
                else if (!strcmp(arg, "--stale-fix"))
                        cb.stalefix = 1;
+               else if (!strcmp(arg, "--rewrite"))
+                       cb.rewrite = 1;
+               else if (!strcmp(arg, "--updateref"))
+                       cb.updateref = 1;
                else if (!strcmp(arg, "--all"))
                        do_all = 1;
                else if (!strcmp(arg, "--verbose"))
@@ -406,6 +433,78 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
        return status;
 }
 
+static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct cmd_reflog_expire_cb *cb = cb_data;
+       if (!cb->expire_total || timestamp < cb->expire_total)
+               cb->recno++;
+       return 0;
+}
+
+static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
+{
+       struct cmd_reflog_expire_cb cb;
+       int i, status = 0;
+
+       memset(&cb, 0, sizeof(cb));
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+                       cb.dry_run = 1;
+               else if (!strcmp(arg, "--rewrite"))
+                       cb.rewrite = 1;
+               else if (!strcmp(arg, "--updateref"))
+                       cb.updateref = 1;
+               else if (!strcmp(arg, "--verbose"))
+                       cb.verbose = 1;
+               else if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               else if (arg[0] == '-')
+                       usage(reflog_delete_usage);
+               else
+                       break;
+       }
+
+       if (argc - i < 1)
+               return error("Nothing to delete?");
+
+       for ( ; i < argc; i++) {
+               const char *spec = strstr(argv[i], "@{");
+               unsigned char sha1[20];
+               char *ep, *ref;
+               int recno;
+
+               if (!spec) {
+                       status |= error("Not a reflog: %s", argv[i]);
+                       continue;
+               }
+
+               if (!dwim_ref(argv[i], spec - argv[i], sha1, &ref)) {
+                       status |= error("%s points nowhere!", argv[i]);
+                       continue;
+               }
+
+               recno = strtoul(spec + 2, &ep, 10);
+               if (*ep == '}') {
+                       cb.recno = -recno;
+                       for_each_reflog_ent(ref, count_reflog_ent, &cb);
+               } else {
+                       cb.expire_total = approxidate(spec + 2);
+                       for_each_reflog_ent(ref, count_reflog_ent, &cb);
+                       cb.expire_total = 0;
+               }
+
+               status |= expire_reflog(ref, sha1, 0, &cb);
+               free(ref);
+       }
+       return status;
+}
+
 /*
  * main "reflog"
  */
@@ -425,6 +524,9 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
        if (!strcmp(argv[1], "expire"))
                return cmd_reflog_expire(argc - 1, argv + 1, prefix);
 
+       if (!strcmp(argv[1], "delete"))
+               return cmd_reflog_delete(argc - 1, argv + 1, prefix);
+
        /* Not a recognized reflog command..*/
        usage(reflog_usage);
 }
index b2971f34456d15cd155b9ac7d207602f9822d149..c607aade637423f0129167163fa4055b08546248 100644 (file)
@@ -149,8 +149,8 @@ static int find_conflict(struct path_list *conflict)
                if (ce_stage(e2) == 2 &&
                    ce_stage(e3) == 3 &&
                    ce_same_name(e2, e3) &&
-                   S_ISREG(ntohl(e2->ce_mode)) &&
-                   S_ISREG(ntohl(e3->ce_mode))) {
+                   S_ISREG(e2->ce_mode) &&
+                   S_ISREG(e3->ce_mode)) {
                        path_list_insert((const char *)e2->name, conflict);
                        i++; /* skip over both #2 and #3 */
                }
index 7ee811f0b8de34025a14a53d42f4fceb477a598e..79424bb26efb4525b6bc2080e639b51d59eaabd9 100644 (file)
 #include "diff.h"
 #include "diffcore.h"
 #include "tree.h"
+#include "branch.h"
+#include "parse-options.h"
 
-static const char builtin_reset_usage[] =
-"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]";
+static const char * const git_reset_usage[] = {
+       "git-reset [--mixed | --soft | --hard] [-q] [<commit>]",
+       "git-reset [--mixed] <commit> [--] <paths>...",
+       NULL
+};
 
 static char *args_to_str(const char **argv)
 {
@@ -44,18 +49,6 @@ static inline int is_merge(void)
        return !access(git_path("MERGE_HEAD"), F_OK);
 }
 
-static int unmerged_files(void)
-{
-       int i;
-       read_cache();
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce))
-                       return 1;
-       }
-       return 0;
-}
-
 static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
 {
        int i = 0;
@@ -74,14 +67,10 @@ static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
 
 static void print_new_head_line(struct commit *commit)
 {
-       const char *hex, *dots = "...", *body;
+       const char *hex, *body;
 
        hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
-       if (!hex) {
-               hex = sha1_to_hex(commit->object.sha1);
-               dots = "";
-       }
-       printf("HEAD is now at %s%s", hex, dots);
+       printf("HEAD is now at %s", hex);
        body = strstr(commit->buffer, "\n\n");
        if (body) {
                const char *eol;
@@ -180,40 +169,31 @@ static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
 
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
-       int i = 1, reset_type = NONE, update_ref_status = 0, quiet = 0;
+       int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
        const char *rev = "HEAD";
        unsigned char sha1[20], *orig = NULL, sha1_orig[20],
                                *old_orig = NULL, sha1_old_orig[20];
        struct commit *commit;
        char *reflog_action, msg[1024];
+       const struct option options[] = {
+               OPT_SET_INT(0, "mixed", &reset_type,
+                                               "reset HEAD and index", MIXED),
+               OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
+               OPT_SET_INT(0, "hard", &reset_type,
+                               "reset HEAD, index and working tree", HARD),
+               OPT_BOOLEAN('q', NULL, &quiet,
+                               "disable showing new HEAD in hard reset"),
+               OPT_END()
+       };
 
        git_config(git_default_config);
 
+       argc = parse_options(argc, argv, options, git_reset_usage,
+                                               PARSE_OPT_KEEP_DASHDASH);
        reflog_action = args_to_str(argv);
        setenv("GIT_REFLOG_ACTION", reflog_action, 0);
 
-       while (i < argc) {
-               if (!strcmp(argv[i], "--mixed")) {
-                       reset_type = MIXED;
-                       i++;
-               }
-               else if (!strcmp(argv[i], "--soft")) {
-                       reset_type = SOFT;
-                       i++;
-               }
-               else if (!strcmp(argv[i], "--hard")) {
-                       reset_type = HARD;
-                       i++;
-               }
-               else if (!strcmp(argv[i], "-q")) {
-                       quiet = 1;
-                       i++;
-               }
-               else
-                       break;
-       }
-
-       if (i < argc && argv[i][0] != '-')
+       if (i < argc && strcmp(argv[i], "--"))
                rev = argv[i++];
 
        if (get_sha1(rev, sha1))
@@ -226,8 +206,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
        if (i < argc && !strcmp(argv[i], "--"))
                i++;
-       else if (i < argc && argv[i][0] == '-')
-               usage(builtin_reset_usage);
 
        /* git reset tree [--] paths... can be used to
         * load chosen paths from the tree into the index without
@@ -250,7 +228,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
         * at all, but requires them in a good order.  Other resets reset
         * the index file to the tree object we are switching to. */
        if (reset_type == SOFT) {
-               if (is_merge() || unmerged_files())
+               if (is_merge() || read_cache() < 0 || unmerged_cache())
                        die("Cannot do a soft reset in the middle of a merge.");
        }
        else if (reset_index_file(sha1, (reset_type == HARD)))
@@ -282,10 +260,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                break;
        }
 
-       unlink(git_path("MERGE_HEAD"));
-       unlink(git_path("rr-cache/MERGE_RR"));
-       unlink(git_path("MERGE_MSG"));
-       unlink(git_path("SQUASH_MSG"));
+       remove_branch_state();
 
        free(reflog_action);
 
index de80158fd4762aa692193edb7c0f8e85e6189877..d0a14169211aa499c726a436de827ea4d328f6c2 100644 (file)
@@ -25,6 +25,9 @@ static const char rev_list_usage[] =
 "    --no-merges\n"
 "    --remove-empty\n"
 "    --all\n"
+"    --branches\n"
+"    --tags\n"
+"    --remotes\n"
 "    --stdin\n"
 "    --quiet\n"
 "  ordering output:\n"
@@ -60,6 +63,8 @@ static void show_commit(struct commit *commit)
                fputs(header_prefix, stdout);
        if (commit->object.flags & BOUNDARY)
                putchar('-');
+       else if (commit->object.flags & UNINTERESTING)
+               putchar('^');
        else if (revs.left_right) {
                if (commit->object.flags & SYMMETRIC_LEFT)
                        putchar('<');
@@ -84,7 +89,7 @@ static void show_commit(struct commit *commit)
        else
                putchar('\n');
 
-       if (revs.verbose_header) {
+       if (revs.verbose_header && commit->buffer) {
                struct strbuf buf;
                strbuf_init(&buf, 0);
                pretty_print_commit(revs.commit_format, commit,
@@ -605,11 +610,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                usage(rev_list_usage);
 
        save_commit_buffer = revs.verbose_header || revs.grep_filter;
-       track_object_refs = 0;
        if (bisect_list)
                revs.limited = 1;
 
-       prepare_revision_walk(&revs);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
        if (revs.tree_objects)
                mark_edges_uninteresting(revs.commits, &revs, show_edge);
 
index 90dbb9d7c1a28fd463ba827cb8da2eab42e489df..0351d54435b566d5030f3a83df57fb140fffc143 100644 (file)
@@ -322,18 +322,24 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
                o->type = OPTION_CALLBACK;
                o->help = xstrdup(skipspaces(s));
                o->value = &parsed;
+               o->flags = PARSE_OPT_NOARG;
                o->callback = &parseopt_dump;
-               switch (s[-1]) {
-               case '=':
-                       s--;
-                       break;
-               case '?':
-                       o->flags = PARSE_OPT_OPTARG;
-                       s--;
-                       break;
-               default:
-                       o->flags = PARSE_OPT_NOARG;
-                       break;
+               while (s > sb.buf && strchr("*=?!", s[-1])) {
+                       switch (*--s) {
+                       case '=':
+                               o->flags &= ~PARSE_OPT_NOARG;
+                               break;
+                       case '?':
+                               o->flags &= ~PARSE_OPT_NOARG;
+                               o->flags |= PARSE_OPT_OPTARG;
+                               break;
+                       case '!':
+                               o->flags |= PARSE_OPT_NONEG;
+                               break;
+                       case '*':
+                               o->flags |= PARSE_OPT_HIDDEN;
+                               break;
+                       }
                }
 
                if (s - sb.buf == 1) /* short option only */
index 64f0d0ee028fd143ea065cad93ce7be03d94839d..607a2f0337c3d3f1fb8bdac7443e3a7f56e92305 100644 (file)
@@ -8,6 +8,7 @@
 #include "exec_cmd.h"
 #include "utf8.h"
 #include "parse-options.h"
+#include "cache-tree.h"
 #include "diff.h"
 #include "revision.h"
 
@@ -283,7 +284,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                 * that represents the "current" state for merge-recursive
                 * to work on.
                 */
-               if (write_tree(head, 0, NULL))
+               if (write_cache_as_tree(head, 0, NULL))
                        die ("Your index file is unmerged.");
        } else {
                if (get_sha1("HEAD", head))
@@ -369,7 +370,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        if (merge_recursive(sha1_to_hex(base->object.sha1),
                                sha1_to_hex(head), "HEAD",
                                sha1_to_hex(next->object.sha1), oneline) ||
-                       write_tree(head, 0, NULL)) {
+                       write_cache_as_tree(head, 0, NULL)) {
                add_to_msg("\nConflicts:\n\n");
                read_cache();
                for (i = 0; i < active_nr;) {
@@ -408,8 +409,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                else
                        return execl_git_cmd("commit", "-n", "-F", defmsg, NULL);
        }
-       if (reencoded_message)
-               free(reencoded_message);
+       free(reencoded_message);
 
        return 0;
 }
index 8afb1d0bca0635dc22f658455477d9fada231290..930e0fb3fdfee2293d0fca650d7139f09f2d381e 100644 (file)
@@ -71,6 +71,7 @@ static int pack_objects(int fd, struct ref *refs)
                refs = refs->next;
        }
 
+       close(po.in);
        if (finish_command(&po))
                return error("pack-objects died with strange error");
        return 0;
@@ -263,9 +264,7 @@ static void print_ref_status(char flag, const char *summary, struct ref *to, str
 
 static const char *status_abbrev(unsigned char sha1[20])
 {
-       const char *abbrev;
-       abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
-       return abbrev ? abbrev : sha1_to_hex(sha1);
+       return find_unique_abbrev(sha1, DEFAULT_ABBREV);
 }
 
 static void print_ok_ref_status(struct ref *ref)
@@ -403,12 +402,15 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
        if (!remote_tail)
                remote_tail = &remote_refs;
        if (match_refs(local_refs, remote_refs, &remote_tail,
-                                              nr_refspec, refspec, flags))
+                      nr_refspec, refspec, flags)) {
+               close(out);
                return -1;
+       }
 
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
                        "Perhaps you should specify a branch such as 'master'.\n");
+               close(out);
                return 0;
        }
 
@@ -495,12 +497,11 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
 
        packet_flush(out);
        if (new_refs && !args.dry_run) {
-               if (pack_objects(out, remote_refs) < 0) {
-                       close(out);
+               if (pack_objects(out, remote_refs) < 0)
                        return -1;
-               }
        }
-       close(out);
+       else
+               close(out);
 
        if (expect_status_report)
                ret = receive_status(in, remote_refs);
@@ -648,7 +649,7 @@ int send_pack(struct send_pack_args *my_args,
        conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
        ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
        close(fd[0]);
-       close(fd[1]);
+       /* do_send_pack always closes fd[1] */
        ret |= finish_connect(conn);
        return !!ret;
 }
index f08095bc91b92ad93560459d060259bd5df52c4a..b22b0edd65eefacabb648962b9c20c695609a3f5 100644 (file)
@@ -6,13 +6,11 @@
 #include "revision.h"
 #include "utf8.h"
 #include "mailmap.h"
+#include "shortlog.h"
 
 static const char shortlog_usage[] =
 "git-shortlog [-n] [-s] [-e] [<commit-id>... ]";
 
-static char *common_repo_prefix;
-static int email;
-
 static int compare_by_number(const void *a1, const void *a2)
 {
        const struct path_list_item *i1 = a1, *i2 = a2;
@@ -26,13 +24,11 @@ static int compare_by_number(const void *a1, const void *a2)
                return -1;
 }
 
-static struct path_list mailmap = {NULL, 0, 0, 0};
-
-static void insert_one_record(struct path_list *list,
+static void insert_one_record(struct shortlog *log,
                              const char *author,
                              const char *oneline)
 {
-       const char *dot3 = common_repo_prefix;
+       const char *dot3 = log->common_repo_prefix;
        char *buffer, *p;
        struct path_list_item *item;
        struct path_list *onelines;
@@ -47,7 +43,7 @@ static void insert_one_record(struct path_list *list,
        eoemail = strchr(boemail, '>');
        if (!eoemail)
                return;
-       if (!map_email(&mailmap, boemail+1, namebuf, sizeof(namebuf))) {
+       if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
                while (author < boemail && isspace(*author))
                        author++;
                for (len = 0;
@@ -61,14 +57,14 @@ static void insert_one_record(struct path_list *list,
        else
                len = strlen(namebuf);
 
-       if (email) {
+       if (log->email) {
                size_t room = sizeof(namebuf) - len - 1;
                int maillen = eoemail - boemail + 1;
                snprintf(namebuf + len, room, " %.*s", maillen, boemail);
        }
 
        buffer = xstrdup(namebuf);
-       item = path_list_insert(buffer, list);
+       item = path_list_insert(buffer, &log->list);
        if (item->util == NULL)
                item->util = xcalloc(1, sizeof(struct path_list));
        else
@@ -115,7 +111,7 @@ static void insert_one_record(struct path_list *list,
        onelines->items[onelines->nr++].path = buffer;
 }
 
-static void read_from_stdin(struct path_list *list)
+static void read_from_stdin(struct shortlog *log)
 {
        char author[1024], oneline[1024];
 
@@ -129,38 +125,43 @@ static void read_from_stdin(struct path_list *list)
                while (fgets(oneline, sizeof(oneline), stdin) &&
                       oneline[0] == '\n')
                        ; /* discard blanks */
-               insert_one_record(list, author + 8, oneline);
+               insert_one_record(log, author + 8, oneline);
        }
 }
 
-static void get_from_rev(struct rev_info *rev, struct path_list *list)
+void shortlog_add_commit(struct shortlog *log, struct commit *commit)
 {
-       struct commit *commit;
-
-       prepare_revision_walk(rev);
-       while ((commit = get_revision(rev)) != NULL) {
-               const char *author = NULL, *buffer;
+       const char *author = NULL, *buffer;
 
-               buffer = commit->buffer;
-               while (*buffer && *buffer != '\n') {
-                       const char *eol = strchr(buffer, '\n');
+       buffer = commit->buffer;
+       while (*buffer && *buffer != '\n') {
+               const char *eol = strchr(buffer, '\n');
 
-                       if (eol == NULL)
-                               eol = buffer + strlen(buffer);
-                       else
-                               eol++;
+               if (eol == NULL)
+                       eol = buffer + strlen(buffer);
+               else
+                       eol++;
 
-                       if (!prefixcmp(buffer, "author "))
-                               author = buffer + 7;
-                       buffer = eol;
-               }
-               if (!author)
-                       die("Missing author: %s",
-                           sha1_to_hex(commit->object.sha1));
-               if (*buffer)
-                       buffer++;
-               insert_one_record(list, author, !*buffer ? "<none>" : buffer);
+               if (!prefixcmp(buffer, "author "))
+                       author = buffer + 7;
+               buffer = eol;
        }
+       if (!author)
+               die("Missing author: %s",
+                   sha1_to_hex(commit->object.sha1));
+       if (*buffer)
+               buffer++;
+       insert_one_record(log, author, !*buffer ? "<none>" : buffer);
+}
+
+static void get_from_rev(struct rev_info *rev, struct shortlog *log)
+{
+       struct commit *commit;
+
+       if (prepare_revision_walk(rev))
+               die("revision walk setup failed");
+       while ((commit = get_revision(rev)) != NULL)
+               shortlog_add_commit(log, commit);
 }
 
 static int parse_uint(char const **arg, int comma)
@@ -212,29 +213,38 @@ static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap)
                die(wrap_arg_usage);
 }
 
+void shortlog_init(struct shortlog *log)
+{
+       memset(log, 0, sizeof(*log));
+
+       read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
+
+       log->list.strdup_paths = 1;
+       log->wrap = DEFAULT_WRAPLEN;
+       log->in1 = DEFAULT_INDENT1;
+       log->in2 = DEFAULT_INDENT2;
+}
+
 int cmd_shortlog(int argc, const char **argv, const char *prefix)
 {
+       struct shortlog log;
        struct rev_info rev;
-       struct path_list list = { NULL, 0, 0, 1 };
-       int i, j, sort_by_number = 0, summary = 0;
-       int wrap_lines = 0;
-       int wrap = DEFAULT_WRAPLEN;
-       int in1 = DEFAULT_INDENT1;
-       int in2 = DEFAULT_INDENT2;
+
+       shortlog_init(&log);
 
        /* since -n is a shadowed rev argument, parse our args first */
        while (argc > 1) {
                if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
-                       sort_by_number = 1;
+                       log.sort_by_number = 1;
                else if (!strcmp(argv[1], "-s") ||
                                !strcmp(argv[1], "--summary"))
-                       summary = 1;
+                       log.summary = 1;
                else if (!strcmp(argv[1], "-e") ||
                         !strcmp(argv[1], "--email"))
-                       email = 1;
+                       log.email = 1;
                else if (!prefixcmp(argv[1], "-w")) {
-                       wrap_lines = 1;
-                       parse_wrap_args(argv[1], &in1, &in2, &wrap);
+                       log.wrap_lines = 1;
+                       parse_wrap_args(argv[1], &log.in1, &log.in2, &log.wrap);
                }
                else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
                        usage(shortlog_usage);
@@ -248,34 +258,38 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
        if (argc > 1)
                die ("unrecognized argument: %s", argv[1]);
 
-       read_mailmap(&mailmap, ".mailmap", &common_repo_prefix);
-
        /* assume HEAD if from a tty */
        if (!rev.pending.nr && isatty(0))
                add_head_to_pending(&rev);
        if (rev.pending.nr == 0) {
-               read_from_stdin(&list);
+               read_from_stdin(&log);
        }
        else
-               get_from_rev(&rev, &list);
+               get_from_rev(&rev, &log);
 
-       if (sort_by_number)
-               qsort(list.items, list.nr, sizeof(struct path_list_item),
-                       compare_by_number);
+       shortlog_output(&log);
+       return 0;
+}
 
-       for (i = 0; i < list.nr; i++) {
-               struct path_list *onelines = list.items[i].util;
+void shortlog_output(struct shortlog *log)
+{
+       int i, j;
+       if (log->sort_by_number)
+               qsort(log->list.items, log->list.nr, sizeof(struct path_list_item),
+                       compare_by_number);
+       for (i = 0; i < log->list.nr; i++) {
+               struct path_list *onelines = log->list.items[i].util;
 
-               if (summary) {
-                       printf("%6d\t%s\n", onelines->nr, list.items[i].path);
+               if (log->summary) {
+                       printf("%6d\t%s\n", onelines->nr, log->list.items[i].path);
                } else {
-                       printf("%s (%d):\n", list.items[i].path, onelines->nr);
+                       printf("%s (%d):\n", log->list.items[i].path, onelines->nr);
                        for (j = onelines->nr - 1; j >= 0; j--) {
                                const char *msg = onelines->items[j].path;
 
-                               if (wrap_lines) {
-                                       int col = print_wrapped_text(msg, in1, in2, wrap);
-                                       if (col != wrap)
+                               if (log->wrap_lines) {
+                                       int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap);
+                                       if (col != log->wrap)
                                                putchar('\n');
                                }
                                else
@@ -287,13 +301,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
                onelines->strdup_paths = 1;
                path_list_clear(onelines, 1);
                free(onelines);
-               list.items[i].util = NULL;
+               log->list.items[i].util = NULL;
        }
 
-       list.strdup_paths = 1;
-       path_list_clear(&list, 1);
-       mailmap.strdup_paths = 1;
-       path_list_clear(&mailmap, 1);
-
-       return 0;
+       log->list.strdup_paths = 1;
+       path_list_clear(&log->list, 1);
+       log->mailmap.strdup_paths = 1;
+       path_list_clear(&log->mailmap, 1);
 }
index 65051d14fde44c14d12099df656ac423bc1c347e..a323633e296cef1797ab1d218cea83c891bc3b8d 100644 (file)
@@ -86,6 +86,9 @@ match:
                            sha1_to_hex(sha1));
                if (obj->type == OBJ_TAG) {
                        obj = deref_tag(obj, refname, 0);
+                       if (!obj)
+                               die("git-show-ref: bad tag at ref %s (%s)", refname,
+                                   sha1_to_hex(sha1));
                        hex = find_unique_abbrev(obj->sha1, abbrev);
                        printf("%s %s^{}\n", hex, refname);
                }
index 9a59caf70d74cae9b7eae858402233ad6f7c3984..8dd959fe1c74507023f8e82c7f4682c1588ebac6 100644 (file)
@@ -230,19 +230,17 @@ static int do_sign(struct strbuf *buffer)
 
        if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
                close(gpg.in);
+               close(gpg.out);
                finish_command(&gpg);
                return error("gpg did not accept the tag data");
        }
        close(gpg.in);
-       gpg.close_in = 0;
        len = strbuf_read(buffer, gpg.out, 1024);
+       close(gpg.out);
 
        if (finish_command(&gpg) || !len || len < 0)
                return error("gpg failed to sign the tag");
 
-       if (len < 0)
-               return error("could not read the entire signature from gpg.");
-
        return 0;
 }
 
index 1e51865c52231e80cfdbbb19c8b6fa86ee8855d2..50e07faa12f495b3270e1446573b88a255336877 100644 (file)
@@ -8,6 +8,7 @@
 #include "tag.h"
 #include "tree.h"
 #include "progress.h"
+#include "decorate.h"
 
 static int dry_run, quiet, recover, has_errors;
 static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file";
@@ -18,6 +19,18 @@ static unsigned int offset, len;
 static off_t consumed_bytes;
 static SHA_CTX ctx;
 
+struct obj_buffer {
+       char *buffer;
+       unsigned long size;
+};
+
+static struct decoration obj_decorate;
+
+static struct obj_buffer *lookup_object_buffer(struct object *base)
+{
+       return lookup_decoration(&obj_decorate, base);
+}
+
 /*
  * Make sure at least "min" bytes are available in the buffer, and
  * return the pointer to the buffer.
@@ -189,6 +202,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
        void *delta_data, *base;
        unsigned long base_size;
        unsigned char base_sha1[20];
+       struct object *obj;
 
        if (type == OBJ_REF_DELTA) {
                hashcpy(base_sha1, fill(20));
@@ -252,6 +266,15 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
                }
        }
 
+       obj = lookup_object(base_sha1);
+       if (obj) {
+               struct obj_buffer *obj_buf = lookup_object_buffer(obj);
+               if (obj_buf) {
+                       resolve_delta(nr, obj->type, obj_buf->buffer, obj_buf->size, delta_data, delta_size);
+                       return;
+               }
+       }
+
        base = read_sha1_file(base_sha1, &type, &base_size);
        if (!base) {
                error("failed to read delta-pack base object %s",
index c3a14c74ed00db592db55a018db37657148a9900..a8795d3d5fea9f340dfe21a71190f120c63856cd 100644 (file)
@@ -47,10 +47,10 @@ static int mark_valid(const char *path)
        if (0 <= pos) {
                switch (mark_valid_only) {
                case MARK_VALID:
-                       active_cache[pos]->ce_flags |= htons(CE_VALID);
+                       active_cache[pos]->ce_flags |= CE_VALID;
                        break;
                case UNMARK_VALID:
-                       active_cache[pos]->ce_flags &= ~htons(CE_VALID);
+                       active_cache[pos]->ce_flags &= ~CE_VALID;
                        break;
                }
                cache_tree_invalidate_path(active_cache_tree, path);
@@ -95,7 +95,7 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru
        size = cache_entry_size(len);
        ce = xcalloc(1, size);
        memcpy(ce->name, path, len);
-       ce->ce_flags = htons(len);
+       ce->ce_flags = len;
        fill_stat_cache_info(ce, st);
        ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
 
@@ -139,7 +139,7 @@ static int process_directory(const char *path, int len, struct stat *st)
        /* Exact match: file or existing gitlink */
        if (pos >= 0) {
                struct cache_entry *ce = active_cache[pos];
-               if (S_ISGITLINK(ntohl(ce->ce_mode))) {
+               if (S_ISGITLINK(ce->ce_mode)) {
 
                        /* Do nothing to the index if there is no HEAD! */
                        if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
@@ -183,7 +183,7 @@ static int process_file(const char *path, int len, struct stat *st)
        int pos = cache_name_pos(path, len);
        struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
 
-       if (ce && S_ISGITLINK(ntohl(ce->ce_mode)))
+       if (ce && S_ISGITLINK(ce->ce_mode))
                return error("%s is already a gitlink, not replacing", path);
 
        return add_one_path(ce, path, len, st);
@@ -226,7 +226,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
        ce->ce_flags = create_ce_flags(len, stage);
        ce->ce_mode = create_ce_mode(mode);
        if (assume_unchanged)
-               ce->ce_flags |= htons(CE_VALID);
+               ce->ce_flags |= CE_VALID;
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
        if (add_cache_entry(ce, option))
@@ -246,14 +246,14 @@ static void chmod_path(int flip, const char *path)
        if (pos < 0)
                goto fail;
        ce = active_cache[pos];
-       mode = ntohl(ce->ce_mode);
+       mode = ce->ce_mode;
        if (!S_ISREG(mode))
                goto fail;
        switch (flip) {
        case '+':
-               ce->ce_mode |= htonl(0111); break;
+               ce->ce_mode |= 0111; break;
        case '-':
-               ce->ce_mode &= htonl(~0111); break;
+               ce->ce_mode &= ~0111; break;
        default:
                goto fail;
        }
index 4e31c273f48e3983aaf99dc6525982d34b6fed06..4958bbbf11f5f796feedfa7480b827029f912d01 100644 (file)
@@ -40,8 +40,8 @@ static int verify_one_pack(const char *path, int verbose)
        if (!pack)
                return error("packfile %s not found.", arg);
 
+       install_packed_git(pack);
        err = verify_pack(pack, verbose);
-       free(pack);
 
        return err;
 }
index cc4c55d7ee35ceeaf8c4a857d5dad857a7eb2664..f3ef11fa2d582682b428d6e165da65f05e2d7511 100644 (file)
@@ -45,14 +45,12 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
        memset(&gpg, 0, sizeof(gpg));
        gpg.argv = args_gpg;
        gpg.in = -1;
-       gpg.out = 1;
        args_gpg[2] = path;
        if (start_command(&gpg))
                return error("could not run gpg.");
 
        write_in_full(gpg.in, buf, len);
        close(gpg.in);
-       gpg.close_in = 0;
        ret = finish_command(&gpg);
 
        unlink(path);
index d16b9ed0098fd5c12fe83638f51cc3f10fd87ed7..e838d01233eb1d6cfaa0885c6b37c8726079b0ac 100644 (file)
 static const char write_tree_usage[] =
 "git-write-tree [--missing-ok] [--prefix=<prefix>/]";
 
-int write_tree(unsigned char *sha1, int missing_ok, const char *prefix)
-{
-       int entries, was_valid, newfd;
-
-       /* We can't free this memory, it becomes part of a linked list parsed atexit() */
-       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-
-       newfd = hold_locked_index(lock_file, 1);
-
-       entries = read_cache();
-       if (entries < 0)
-               die("git-write-tree: error reading cache");
-
-       if (!active_cache_tree)
-               active_cache_tree = cache_tree();
-
-       was_valid = cache_tree_fully_valid(active_cache_tree);
-
-       if (!was_valid) {
-               if (cache_tree_update(active_cache_tree,
-                                     active_cache, active_nr,
-                                     missing_ok, 0) < 0)
-                       die("git-write-tree: error building trees");
-               if (0 <= newfd) {
-                       if (!write_cache(newfd, active_cache, active_nr) &&
-                           !commit_lock_file(lock_file))
-                               newfd = -1;
-               }
-               /* Not being able to write is fine -- we are only interested
-                * in updating the cache-tree part, and if the next caller
-                * ends up using the old index with unupdated cache-tree part
-                * it misses the work we did here, but that is just a
-                * performance penalty and not a big deal.
-                */
-       }
-
-       if (prefix) {
-               struct cache_tree *subtree =
-                       cache_tree_find(active_cache_tree, prefix);
-               if (!subtree)
-                       die("git-write-tree: prefix %s not found", prefix);
-               hashcpy(sha1, subtree->sha1);
-       }
-       else
-               hashcpy(sha1, active_cache_tree->sha1);
-
-       if (0 <= newfd)
-               rollback_lock_file(lock_file);
-
-       return 0;
-}
-
 int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
 {
        int missing_ok = 0, ret;
        const char *prefix = NULL;
        unsigned char sha1[20];
+       const char *me = "git-write-tree";
 
        git_config(git_default_config);
        while (1 < argc) {
@@ -84,8 +33,20 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
        if (argc > 2)
                die("too many options");
 
-       ret = write_tree(sha1, missing_ok, prefix);
-       printf("%s\n", sha1_to_hex(sha1));
-
+       ret = write_cache_as_tree(sha1, missing_ok, prefix);
+       switch (ret) {
+       case 0:
+               printf("%s\n", sha1_to_hex(sha1));
+               break;
+       case WRITE_TREE_UNREADABLE_INDEX:
+               die("%s: error reading the index", me);
+               break;
+       case WRITE_TREE_UNMERGED_INDEX:
+               die("%s: error building trees; the index is unmerged?", me);
+               break;
+       case WRITE_TREE_PREFIX_ERROR:
+               die("%s: prefix %s not found", me, prefix);
+               break;
+       }
        return ret;
 }
index cb675c4d7a80b99a06d6d04156e767ea025eb512..674c8a141faf808883c9de283d10da01b3f9c2d5 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -8,7 +8,6 @@ extern const char git_usage_string[];
 
 extern void list_common_cmds_help(void);
 extern void help_unknown_cmd(const char *cmd);
-extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
 extern void prune_packed_objects(int);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
@@ -19,6 +18,7 @@ extern int cmd_blame(int argc, const char **argv, const char *prefix);
 extern int cmd_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_bundle(int argc, const char **argv, const char *prefix);
 extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
+extern int cmd_checkout(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
 extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
@@ -57,6 +57,7 @@ extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
index 5c95eca07d19a6d3ed4e9000b5bb47eef90ff66e..0ba5df17e15d679b03fe38af40260c118c9588fa 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -128,7 +128,8 @@ int verify_bundle(struct bundle_header *header, int verbose)
                add_object_array(e->item, e->name, &refs);
        }
 
-       prepare_revision_walk(&revs);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
 
        i = req_nr;
        while (i && (commit = get_revision(&revs)))
@@ -332,10 +333,12 @@ int create_bundle(struct bundle_header *header, const char *path,
                write_or_die(rls.in, sha1_to_hex(object->sha1), 40);
                write_or_die(rls.in, "\n", 1);
        }
+       close(rls.in);
        if (finish_command(&rls))
                return error ("pack-objects died");
-
-       return bundle_to_stdout ? close(bundle_fd) : commit_lock_file(&lock);
+       if (!bundle_to_stdout)
+               commit_lock_file(&lock);
+       return 0;
 }
 
 int unbundle(struct bundle_header *header, int bundle_fd)
index 50b35264fd0405a299700ef8bf4a61f416f30e46..39da54d1e56b5905655eafed1aff0f51c2540a8e 100644 (file)
@@ -320,13 +320,13 @@ static int update_one(struct cache_tree *it,
                }
                else {
                        sha1 = ce->sha1;
-                       mode = ntohl(ce->ce_mode);
+                       mode = ce->ce_mode;
                        entlen = pathlen - baselen;
                }
                if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1))
                        return error("invalid object %s", sha1_to_hex(sha1));
 
-               if (!ce->ce_mode)
+               if (ce->ce_flags & CE_REMOVE)
                        continue; /* entry being removed */
 
                strbuf_grow(&buffer, entlen + 100);
@@ -529,3 +529,58 @@ struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
        }
        return it;
 }
+
+int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix)
+{
+       int entries, was_valid, newfd;
+
+       /*
+        * We can't free this memory, it becomes part of a linked list
+        * parsed atexit()
+        */
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+       newfd = hold_locked_index(lock_file, 1);
+
+       entries = read_cache();
+       if (entries < 0)
+               return WRITE_TREE_UNREADABLE_INDEX;
+
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+
+       was_valid = cache_tree_fully_valid(active_cache_tree);
+
+       if (!was_valid) {
+               if (cache_tree_update(active_cache_tree,
+                                     active_cache, active_nr,
+                                     missing_ok, 0) < 0)
+                       return WRITE_TREE_UNMERGED_INDEX;
+               if (0 <= newfd) {
+                       if (!write_cache(newfd, active_cache, active_nr) &&
+                           !commit_lock_file(lock_file))
+                               newfd = -1;
+               }
+               /* Not being able to write is fine -- we are only interested
+                * in updating the cache-tree part, and if the next caller
+                * ends up using the old index with unupdated cache-tree part
+                * it misses the work we did here, but that is just a
+                * performance penalty and not a big deal.
+                */
+       }
+
+       if (prefix) {
+               struct cache_tree *subtree =
+                       cache_tree_find(active_cache_tree, prefix);
+               if (!subtree)
+                       return WRITE_TREE_PREFIX_ERROR;
+               hashcpy(sha1, subtree->sha1);
+       }
+       else
+               hashcpy(sha1, active_cache_tree->sha1);
+
+       if (0 <= newfd)
+               rollback_lock_file(lock_file);
+
+       return 0;
+}
index 8243228e49ffd7078a783582be6ce79c97541a9c..44aad426d319b83eda013e115f35e066cc590cb8 100644 (file)
@@ -30,4 +30,9 @@ int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int)
 
 struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
 
+#define WRITE_TREE_UNREADABLE_INDEX (-1)
+#define WRITE_TREE_UNMERGED_INDEX (-2)
+#define WRITE_TREE_PREFIX_ERROR (-3)
+
+int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix);
 #endif
diff --git a/cache.h b/cache.h
index 98cfed63ee7f11ef647474057077fdb4e669a376..bdefcc9188dbac3937bfb1bf1b19407330ad9782 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -3,6 +3,7 @@
 
 #include "git-compat-util.h"
 #include "strbuf.h"
+#include "hash.h"
 
 #include SHA1_HEADER
 #include <zlib.h>
@@ -94,66 +95,148 @@ struct cache_time {
  * We save the fields in big-endian order to allow using the
  * index file over NFS transparently.
  */
+struct ondisk_cache_entry {
+       struct cache_time ctime;
+       struct cache_time mtime;
+       unsigned int dev;
+       unsigned int ino;
+       unsigned int mode;
+       unsigned int uid;
+       unsigned int gid;
+       unsigned int size;
+       unsigned char sha1[20];
+       unsigned short flags;
+       char name[FLEX_ARRAY]; /* more */
+};
+
 struct cache_entry {
-       struct cache_time ce_ctime;
-       struct cache_time ce_mtime;
+       unsigned int ce_ctime;
+       unsigned int ce_mtime;
        unsigned int ce_dev;
        unsigned int ce_ino;
        unsigned int ce_mode;
        unsigned int ce_uid;
        unsigned int ce_gid;
        unsigned int ce_size;
+       unsigned int ce_flags;
        unsigned char sha1[20];
-       unsigned short ce_flags;
+       struct cache_entry *next;
        char name[FLEX_ARRAY]; /* more */
 };
 
 #define CE_NAMEMASK  (0x0fff)
 #define CE_STAGEMASK (0x3000)
-#define CE_UPDATE    (0x4000)
 #define CE_VALID     (0x8000)
 #define CE_STAGESHIFT 12
 
-#define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
-#define ce_namelen(ce) (CE_NAMEMASK & ntohs((ce)->ce_flags))
+/* In-memory only */
+#define CE_UPDATE    (0x10000)
+#define CE_REMOVE    (0x20000)
+#define CE_UPTODATE  (0x40000)
+
+#define CE_HASHED    (0x100000)
+#define CE_UNHASHED  (0x200000)
+
+/*
+ * Copy the sha1 and stat state of a cache entry from one to
+ * another. But we never change the name, or the hash state!
+ */
+#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
+static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
+{
+       unsigned int state = dst->ce_flags & CE_STATE_MASK;
+
+       /* Don't copy hash chain and name */
+       memcpy(dst, src, offsetof(struct cache_entry, next));
+
+       /* Restore the hash state */
+       dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
+}
+
+/*
+ * We don't actually *remove* it, we can just mark it invalid so that
+ * we won't find it in lookups.
+ *
+ * Not only would we have to search the lists (simple enough), but
+ * we'd also have to rehash other hash buckets in case this makes the
+ * hash bucket empty (common). So it's much better to just mark
+ * it.
+ */
+static inline void remove_index_entry(struct cache_entry *ce)
+{
+       ce->ce_flags |= CE_UNHASHED;
+}
+
+static inline unsigned create_ce_flags(size_t len, unsigned stage)
+{
+       if (len >= CE_NAMEMASK)
+               len = CE_NAMEMASK;
+       return (len | (stage << CE_STAGESHIFT));
+}
+
+static inline size_t ce_namelen(const struct cache_entry *ce)
+{
+       size_t len = ce->ce_flags & CE_NAMEMASK;
+       if (len < CE_NAMEMASK)
+               return len;
+       return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK;
+}
+
 #define ce_size(ce) cache_entry_size(ce_namelen(ce))
-#define ce_stage(ce) ((CE_STAGEMASK & ntohs((ce)->ce_flags)) >> CE_STAGESHIFT)
+#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce))
+#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
+#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
+#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
 
 #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
 static inline unsigned int create_ce_mode(unsigned int mode)
 {
        if (S_ISLNK(mode))
-               return htonl(S_IFLNK);
+               return S_IFLNK;
        if (S_ISDIR(mode) || S_ISGITLINK(mode))
-               return htonl(S_IFGITLINK);
-       return htonl(S_IFREG | ce_permissions(mode));
+               return S_IFGITLINK;
+       return S_IFREG | ce_permissions(mode);
 }
 static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
 {
        extern int trust_executable_bit, has_symlinks;
        if (!has_symlinks && S_ISREG(mode) &&
-           ce && S_ISLNK(ntohl(ce->ce_mode)))
+           ce && S_ISLNK(ce->ce_mode))
                return ce->ce_mode;
        if (!trust_executable_bit && S_ISREG(mode)) {
-               if (ce && S_ISREG(ntohl(ce->ce_mode)))
+               if (ce && S_ISREG(ce->ce_mode))
                        return ce->ce_mode;
                return create_ce_mode(0666);
        }
        return create_ce_mode(mode);
 }
+static inline int ce_to_dtype(const struct cache_entry *ce)
+{
+       unsigned ce_mode = ntohl(ce->ce_mode);
+       if (S_ISREG(ce_mode))
+               return DT_REG;
+       else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))
+               return DT_DIR;
+       else if (S_ISLNK(ce_mode))
+               return DT_LNK;
+       else
+               return DT_UNKNOWN;
+}
 #define canon_mode(mode) \
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
        S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
 
 #define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
+#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7)
 
 struct index_state {
        struct cache_entry **cache;
        unsigned int cache_nr, cache_alloc, cache_changed;
        struct cache_tree *cache_tree;
        time_t timestamp;
-       void *mmap;
-       size_t mmap_size;
+       void *alloc;
+       unsigned name_hash_initialized : 1;
+       struct hash_table name_hash;
 };
 
 extern struct index_state the_index;
@@ -169,6 +252,7 @@ extern struct index_state the_index;
 #define read_cache_from(path) read_index_from(&the_index, (path))
 #define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
 #define discard_cache() discard_index(&the_index)
+#define unmerged_cache() unmerged_index(&the_index)
 #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
 #define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
 #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
@@ -177,6 +261,7 @@ extern struct index_state the_index;
 #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
 #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
 #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
+#define cache_name_exists(name, namelen) index_name_exists(&the_index, (name), (namelen))
 #endif
 
 enum object_type {
@@ -189,6 +274,7 @@ enum object_type {
        /* 5 for future expansion */
        OBJ_OFS_DELTA = 6,
        OBJ_REF_DELTA = 7,
+       OBJ_ANY,
        OBJ_MAX,
 };
 
@@ -262,7 +348,9 @@ extern int read_index(struct index_state *);
 extern int read_index_from(struct index_state *, const char *path);
 extern int write_index(struct index_state *, int newfd);
 extern int discard_index(struct index_state *);
+extern int unmerged_index(struct index_state *);
 extern int verify_path(const char *path);
+extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
 extern int index_name_pos(struct index_state *, const char *name, int namelen);
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
@@ -330,6 +418,23 @@ extern size_t packed_git_limit;
 extern size_t delta_base_cache_limit;
 extern int auto_crlf;
 
+enum safe_crlf {
+       SAFE_CRLF_FALSE = 0,
+       SAFE_CRLF_FAIL = 1,
+       SAFE_CRLF_WARN = 2,
+};
+
+extern enum safe_crlf safe_crlf;
+
+enum branch_track {
+       BRANCH_TRACK_NEVER = 0,
+       BRANCH_TRACK_REMOTE,
+       BRANCH_TRACK_ALWAYS,
+       BRANCH_TRACK_EXPLICIT,
+};
+
+extern enum branch_track git_branch_track;
+
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
 extern int check_repository_format(void);
@@ -438,6 +543,9 @@ extern void *read_object_with_reference(const unsigned char *sha1,
                                        unsigned long *size,
                                        unsigned char *sha1_ret);
 
+extern struct object *peel_to_type(const char *name, int namelen,
+                                  struct object *o, enum object_type);
+
 enum date_mode {
        DATE_NORMAL = 0,
        DATE_RELATIVE,
@@ -590,6 +698,9 @@ extern int git_config_set_multivar(const char *, const char *, const char *, int
 extern int git_config_rename_section(const char *, const char *);
 extern const char *git_etc_gitconfig(void);
 extern int check_repository_format_version(const char *var, const char *value);
+extern int git_env_bool(const char *, int);
+extern int git_config_system(void);
+extern int git_config_global(void);
 extern int config_error_nonbool(const char *);
 
 #define MAX_GITNAME (1000)
@@ -636,7 +747,8 @@ extern void trace_argv_printf(const char **argv, const char *format, ...);
 
 /* convert.c */
 /* returns 1 if *dst was used */
-extern int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst);
+extern int convert_to_git(const char *path, const char *src, size_t len,
+                          struct strbuf *dst, enum safe_crlf checksafe);
 extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
 
 /* add */
@@ -655,6 +767,7 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i
 #define WS_TRAILING_SPACE      01
 #define WS_SPACE_BEFORE_TAB    02
 #define WS_INDENT_WITH_NON_TAB 04
+#define WS_CR_AT_EOL           010
 #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
 extern unsigned whitespace_rule_cfg;
 extern unsigned whitespace_rule(const char *);
@@ -663,10 +776,13 @@ extern unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
     FILE *stream, const char *set,
     const char *reset, const char *ws);
 extern char *whitespace_error_string(unsigned ws);
+extern int ws_fix_copy(char *, const char *, int, unsigned, int *);
 
 /* ls-files */
 int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
 int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
 void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
+char *alias_lookup(const char *alias);
+
 #endif /* CACHE_H */
diff --git a/color.c b/color.c
index cb70340420b6285786b988d4a98d2023a37d4ffc..12a6453f90eb4ce2b39f85762bf3acd1cff2b9f2 100644 (file)
--- a/color.c
+++ b/color.c
@@ -3,6 +3,8 @@
 
 #define COLOR_RESET "\033[m"
 
+int git_use_color_default = 0;
+
 static int parse_color(const char *name, int len)
 {
        static const char * const color_names[] = {
@@ -143,6 +145,16 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
        return 0;
 }
 
+int git_color_default_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "color.ui")) {
+               git_use_color_default = git_config_colorbool(var, value, -1);
+               return 0;
+       }
+
+       return git_default_config(var, value);
+}
+
 static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
                va_list args, const char *trail)
 {
diff --git a/color.h b/color.h
index ff63513d39b1553e65230ef98583549506670950..ecda5569a2cccabaf16fcf0aad3bce57dfb3aca4 100644 (file)
--- a/color.h
+++ b/color.h
@@ -4,6 +4,17 @@
 /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
 #define COLOR_MAXLEN 24
 
+/*
+ * This variable stores the value of color.ui
+ */
+extern int git_use_color_default;
+
+
+/*
+ * Use this instead of git_default_config if you need the value of color.ui.
+ */
+int git_color_default_config(const char *var, const char *value);
+
 int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
 void color_parse(const char *var, const char *value, char *dst);
 int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
index 8b8fb04d1f6107da15cb142485d80cabe7a3828e..94d5b3d2618d335585084170ef6a3a8e67f34c62 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -193,7 +193,7 @@ static void prepare_commit_graft(void)
        commit_graft_prepared = 1;
 }
 
-static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
+struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
 {
        int pos;
        prepare_commit_graft();
@@ -290,17 +290,6 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
        }
        item->date = parse_commit_date(bufptr, tail);
 
-       if (track_object_refs) {
-               unsigned i = 0;
-               struct commit_list *p;
-               struct object_refs *refs = alloc_object_refs(n_refs);
-               if (item->tree)
-                       refs->ref[i++] = &item->tree->object;
-               for (p = item->parents; p; p = p->next)
-                       refs->ref[i++] = &p->item->object;
-               set_object_refs(&item->object, refs);
-       }
-
        return 0;
 }
 
@@ -311,6 +300,8 @@ int parse_commit(struct commit *item)
        unsigned long size;
        int ret;
 
+       if (!item)
+               return -1;
        if (item->object.parsed)
                return 0;
        buffer = read_sha1_file(item->object.sha1, &type, &size);
@@ -385,8 +376,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
 
        while (parents) {
                struct commit *commit = parents->item;
-               parse_commit(commit);
-               if (!(commit->object.flags & mark)) {
+               if (!parse_commit(commit) && !(commit->object.flags & mark)) {
                        commit->object.flags |= mark;
                        insert_by_date(commit, list);
                }
@@ -552,8 +542,10 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
                 */
                return commit_list_insert(one, &result);
 
-       parse_commit(one);
-       parse_commit(two);
+       if (parse_commit(one))
+               return NULL;
+       if (parse_commit(two))
+               return NULL;
 
        one->object.flags |= PARENT1;
        two->object.flags |= PARENT2;
@@ -586,7 +578,8 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
                        parents = parents->next;
                        if ((p->object.flags & flags) == flags)
                                continue;
-                       parse_commit(p);
+                       if (parse_commit(p))
+                               return NULL;
                        p->object.flags |= flags;
                        insert_by_date(p, &list);
                }
index 10e2b5d4cfdc7ac129ead711421ccc51d2667f02..a1e95914263355e2021f54be4837ed25f1210757 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -71,6 +71,21 @@ extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
                                 int abbrev, const char *subject,
                                 const char *after_subject, enum date_mode,
                                int non_ascii_present);
+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,
+                  const char **msg_p,
+                  struct strbuf *sb,
+                  const char *subject,
+                  const char *after_subject,
+                  const char *encoding,
+                  int plain_non_ascii);
+void pp_remainder(enum cmit_fmt fmt,
+                 const char **msg_p,
+                 struct strbuf *sb,
+                 int indent);
+
 
 /** Removes the first commit from a list sorted by date, and adds all
  * of its parents.
@@ -101,6 +116,7 @@ struct commit_graft {
 struct commit_graft *read_graft_line(char *buf, int len);
 int register_commit_graft(struct commit_graft *, int);
 int read_graft_file(const char *graft_file);
+struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
 
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
 
diff --git a/compat/fopen.c b/compat/fopen.c
new file mode 100644 (file)
index 0000000..ccb9e89
--- /dev/null
@@ -0,0 +1,26 @@
+#include "../git-compat-util.h"
+#undef fopen
+FILE *git_fopen(const char *path, const char *mode)
+{
+       FILE *fp;
+       struct stat st;
+
+       if (mode[0] == 'w' || mode[0] == 'a')
+               return fopen(path, mode);
+
+       if (!(fp = fopen(path, mode)))
+               return NULL;
+
+       if (fstat(fileno(fp), &st)) {
+               fclose(fp);
+               return NULL;
+       }
+
+       if (S_ISDIR(st.st_mode)) {
+               fclose(fp);
+               errno = EISDIR;
+               return NULL;
+       }
+
+       return fp;
+}
diff --git a/compat/qsort.c b/compat/qsort.c
new file mode 100644 (file)
index 0000000..d93dce2
--- /dev/null
@@ -0,0 +1,62 @@
+#include "../git-compat-util.h"
+
+/*
+ * A merge sort implementation, simplified from the qsort implementation
+ * by Mike Haertel, which is a part of the GNU C Library.
+ */
+
+static void msort_with_tmp(void *b, size_t n, size_t s,
+                          int (*cmp)(const void *, const void *),
+                          char *t)
+{
+       char *tmp;
+       char *b1, *b2;
+       size_t n1, n2;
+
+       if (n <= 1)
+               return;
+
+       n1 = n / 2;
+       n2 = n - n1;
+       b1 = b;
+       b2 = (char *)b + (n1 * s);
+
+       msort_with_tmp(b1, n1, s, cmp, t);
+       msort_with_tmp(b2, n2, s, cmp, t);
+
+       tmp = t;
+
+       while (n1 > 0 && n2 > 0) {
+               if (cmp(b1, b2) <= 0) {
+                       memcpy(tmp, b1, s);
+                       tmp += s;
+                       b1 += s;
+                       --n1;
+               } else {
+                       memcpy(tmp, b2, s);
+                       tmp += s;
+                       b2 += s;
+                       --n2;
+               }
+       }
+       if (n1 > 0)
+               memcpy(tmp, b1, n1 * s);
+       memcpy(b, t, (n - n2) * s);
+}
+
+void git_qsort(void *b, size_t n, size_t s,
+              int (*cmp)(const void *, const void *))
+{
+       const size_t size = n * s;
+       char buf[1024];
+
+       if (size < sizeof(buf)) {
+               /* The temporary array fits on the small on-stack buffer. */
+               msort_with_tmp(b, n, s, cmp, buf);
+       } else {
+               /* It's somewhat large, so malloc it.  */
+               char *tmp = malloc(size);
+               msort_with_tmp(b, n, s, cmp, tmp);
+               free(tmp);
+       }
+}
diff --git a/compat/snprintf.c b/compat/snprintf.c
new file mode 100644 (file)
index 0000000..dbfc2d6
--- /dev/null
@@ -0,0 +1,40 @@
+#include "../git-compat-util.h"
+
+#undef vsnprintf
+int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
+{
+       char *s;
+       int ret;
+
+       ret = vsnprintf(str, maxsize, format, ap);
+       if (ret != -1)
+               return ret;
+
+       s = NULL;
+       if (maxsize < 128)
+               maxsize = 128;
+
+       while (ret == -1) {
+               maxsize *= 4;
+               str = realloc(s, maxsize);
+               if (! str)
+                       break;
+               s = str;
+               ret = vsnprintf(str, maxsize, format, ap);
+       }
+       free(s);
+       return ret;
+}
+
+int git_snprintf(char *str, size_t maxsize, const char *format, ...)
+{
+       va_list ap;
+       int ret;
+
+       va_start(ap, format);
+       ret = git_vsnprintf(str, maxsize, format, ap);
+       va_end(ap);
+
+       return ret;
+}
+
index b82907cb857c9f745a2afa7abe786ee0c7ae1ff8..062449459e1a4cfc2a605c065ed281669e0e7452 100644 (file)
--- a/config.c
+++ b/config.c
@@ -280,11 +280,18 @@ int git_parse_ulong(const char *value, unsigned long *ret)
        return 0;
 }
 
+static void die_bad_config(const char *name)
+{
+       if (config_file_name)
+               die("bad config value for '%s' in %s", name, config_file_name);
+       die("bad config value for '%s'", name);
+}
+
 int git_config_int(const char *name, const char *value)
 {
        long ret;
        if (!git_parse_long(value, &ret))
-               die("bad config value for '%s' in %s", name, config_file_name);
+               die_bad_config(name);
        return ret;
 }
 
@@ -292,7 +299,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
        unsigned long ret;
        if (!git_parse_ulong(value, &ret))
-               die("bad config value for '%s' in %s", name, config_file_name);
+               die_bad_config(name);
        return ret;
 }
 
@@ -415,6 +422,15 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.safecrlf")) {
+               if (value && !strcasecmp(value, "warn")) {
+                       safe_crlf = SAFE_CRLF_WARN;
+                       return 0;
+               }
+               safe_crlf = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "user.name")) {
                if (!value)
                        return config_error_nonbool(var);
@@ -455,6 +471,14 @@ int git_default_config(const char *var, const char *value)
                whitespace_rule_cfg = parse_whitespace_rule(value);
                return 0;
        }
+       if (!strcmp(var, "branch.autosetupmerge")) {
+               if (value && !strcasecmp(value, "always")) {
+                       git_branch_track = BRANCH_TRACK_ALWAYS;
+                       return 0;
+               }
+               git_branch_track = git_config_bool(var, value);
+               return 0;
+       }
 
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
@@ -485,14 +509,30 @@ const char *git_etc_gitconfig(void)
                system_wide = ETC_GITCONFIG;
                if (!is_absolute_path(system_wide)) {
                        /* interpret path relative to exec-dir */
-                       const char *exec_path = git_exec_path();
-                       system_wide = prefix_path(exec_path, strlen(exec_path),
-                                               system_wide);
+                       struct strbuf d = STRBUF_INIT;
+                       strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide);
+                       system_wide = strbuf_detach(&d, NULL);
                }
        }
        return system_wide;
 }
 
+int git_env_bool(const char *k, int def)
+{
+       const char *v = getenv(k);
+       return v ? git_config_bool(k, v) : def;
+}
+
+int git_config_system(void)
+{
+       return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
+}
+
+int git_config_global(void)
+{
+       return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0);
+}
+
 int git_config(config_fn_t fn)
 {
        int ret = 0;
@@ -505,7 +545,7 @@ int git_config(config_fn_t fn)
         * config file otherwise. */
        filename = getenv(CONFIG_ENVIRONMENT);
        if (!filename) {
-               if (!access(git_etc_gitconfig(), R_OK))
+               if (git_config_system() && !access(git_etc_gitconfig(), R_OK))
                        ret += git_config_from_file(fn, git_etc_gitconfig());
                home = getenv("HOME");
                filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
@@ -513,7 +553,7 @@ int git_config(config_fn_t fn)
                        filename = repo_config = xstrdup(git_path("config"));
        }
 
-       if (home) {
+       if (git_config_global() && home) {
                char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
                if (!access(user_config, R_OK))
                        ret = git_config_from_file(fn, user_config);
index ee6c33df0385291623b900ac2b2cc088011b764d..8e1cd5f03de41ca929b6575ea8ed46e6bcfe2e7e 100644 (file)
@@ -46,3 +46,4 @@ NO_MKDTEMP=@NO_MKDTEMP@
 NO_ICONV=@NO_ICONV@
 OLD_ICONV=@OLD_ICONV@
 NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
+SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
index 85d7ef570d390d56d3ec3ba8af406eee30d700ea..287149d304b8a631d879bd3be7b059c6f074e5fe 100644 (file)
@@ -326,6 +326,40 @@ else
        NO_C99_FORMAT=
 fi
 AC_SUBST(NO_C99_FORMAT)
+#
+# Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf()
+# or vsnprintf() return -1 instead of number of characters which would
+# have been written to the final string if enough space had been available.
+AC_CACHE_CHECK([whether snprintf() and/or vsnprintf() return bogus value],
+ [ac_cv_snprintf_returns_bogus],
+[
+AC_RUN_IFELSE(
+       [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT
+               #include "stdarg.h"
+
+               int test_vsnprintf(char *str, size_t maxsize, const char *format, ...)
+               {
+                 int ret;
+                 va_list ap;
+                 va_start(ap, format);
+                 ret = vsnprintf(str, maxsize, format, ap);
+                 va_end(ap);
+                 return ret;
+               }],
+               [[char buf[6];
+                 if (test_vsnprintf(buf, 3, "%s", "12345") != 5
+                     || strcmp(buf, "12")) return 1;
+                 if (snprintf(buf, 3, "%s", "12345") != 5
+                     || strcmp(buf, "12")) return 1]])],
+       [ac_cv_snprintf_returns_bogus=no],
+       [ac_cv_snprintf_returns_bogus=yes])
+])
+if test $ac_cv_snprintf_returns_bogus = yes; then
+       SNPRINTF_RETURNS_BOGUS=UnfortunatelyYes
+else
+       SNPRINTF_RETURNS_BOGUS=
+fi
+AC_SUBST(SNPRINTF_RETURNS_BOGUS)
 
 
 ## Checks for library functions.
index 71597d4920ff11ed474be1a8bd39b4791611e422..d12b105970bca82017998cae52f5807629736bb9 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -68,8 +68,7 @@ struct ref **get_remote_heads(int in, struct ref **list,
 
                name_len = strlen(name);
                if (len != name_len + 41) {
-                       if (server_capabilities)
-                               free(server_capabilities);
+                       free(server_capabilities);
                        server_capabilities = xstrdup(name + name_len + 1);
                }
 
@@ -474,14 +473,18 @@ char *get_port(char *host)
        return NULL;
 }
 
+static struct child_process no_fork;
+
 /*
- * This returns NULL if the transport protocol does not need fork(2), or a
- * struct child_process object if it does.  Once done, finish the connection
- * with finish_connect() with the value returned from this function
- * (it is safe to call finish_connect() with NULL to support the former
- * case).
+ * This returns a dummy child_process if the transport protocol does not
+ * need fork(2), or a struct child_process object if it does.  Once done,
+ * finish the connection with finish_connect() with the value returned from
+ * this function (it is safe to call finish_connect() with NULL to support
+ * the former case).
  *
- * If it returns, the connect is successful; it just dies on errors.
+ * If it returns, the connect is successful; it just dies on errors (this
+ * will hopefully be changed in a libification effort, to return NULL when
+ * the connection failed).
  */
 struct child_process *git_connect(int fd[2], const char *url_orig,
                                  const char *prog, int flags)
@@ -579,7 +582,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                free(url);
                if (free_path)
                        free(path);
-               return NULL;
+               return &no_fork;
        }
 
        conn = xcalloc(1, sizeof(*conn));
@@ -637,7 +640,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
 int finish_connect(struct child_process *conn)
 {
        int code;
-       if (!conn)
+       if (!conn || conn == &no_fork)
                return 0;
 
        code = finish_command(conn);
index 0d33f9a3dc9d5a593ce2691ab850ba1e38aa32de..5046f699349aa87bedbc79a997dfcb9ea54e7618 100755 (executable)
@@ -64,12 +64,52 @@ __gitdir ()
 
 __git_ps1 ()
 {
-       local b="$(git symbolic-ref HEAD 2>/dev/null)"
-       if [ -n "$b" ]; then
+       local g="$(git rev-parse --git-dir 2>/dev/null)"
+       if [ -n "$g" ]; then
+               local r
+               local b
+               if [ -d "$g/../.dotest" ]
+               then
+                       if test -f "$g/../.dotest/rebasing"
+                       then
+                               r="|REBASE"
+                       elif test -f "$g/../.dotest/applying"
+                       then
+                               r="|AM"
+                       else
+                               r="|AM/REBASE"
+                       fi
+                       b="$(git symbolic-ref HEAD 2>/dev/null)"
+               elif [ -f "$g/.dotest-merge/interactive" ]
+               then
+                       r="|REBASE-i"
+                       b="$(cat "$g/.dotest-merge/head-name")"
+               elif [ -d "$g/.dotest-merge" ]
+               then
+                       r="|REBASE-m"
+                       b="$(cat "$g/.dotest-merge/head-name")"
+               elif [ -f "$g/MERGE_HEAD" ]
+               then
+                       r="|MERGING"
+                       b="$(git symbolic-ref HEAD 2>/dev/null)"
+               else
+                       if [ -f "$g/BISECT_LOG" ]
+                       then
+                               r="|BISECTING"
+                       fi
+                       if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
+                       then
+                               if ! b="$(git describe --exact-match HEAD 2>/dev/null)"
+                               then
+                                       b="$(cut -c1-7 "$g/HEAD")..."
+                               fi
+                       fi
+               fi
+
                if [ -n "$1" ]; then
-                       printf "$1" "${b##refs/heads/}"
+                       printf "$1" "${b##refs/heads/}$r"
                else
-                       printf " (%s)" "${b##refs/heads/}"
+                       printf " (%s)" "${b##refs/heads/}$r"
                fi
        fi
 }
@@ -81,13 +121,21 @@ __gitcomp ()
        if [ $# -gt 2 ]; then
                cur="$3"
        fi
-       for c in $1; do
-               case "$c$4" in
-               --*=*) all="$all$c$4$s" ;;
-               *.)    all="$all$c$4$s" ;;
-               *)     all="$all$c$4 $s" ;;
-               esac
-       done
+       case "$cur" in
+       --*=)
+               COMPREPLY=()
+               return
+               ;;
+       *)
+               for c in $1; do
+                       case "$c$4" in
+                       --*=*) all="$all$c$4$s" ;;
+                       *.)    all="$all$c$4$s" ;;
+                       *)     all="$all$c$4 $s" ;;
+                       esac
+               done
+               ;;
+       esac
        IFS=$s
        COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur"))
        return
@@ -344,7 +392,6 @@ __git_commands ()
                show-index)       : plumbing;;
                ssh-*)            : transport;;
                stripspace)       : plumbing;;
-               svn)              : import export;;
                symbolic-ref)     : plumbing;;
                tar-tree)         : deprecated;;
                unpack-file)      : plumbing;;
@@ -388,6 +435,22 @@ __git_aliased_command ()
        done
 }
 
+__git_find_subcommand ()
+{
+       local word subcommand c=1
+
+       while [ $c -lt $COMP_CWORD ]; do
+               word="${COMP_WORDS[c]}"
+               for subcommand in $1; do
+                       if [ "$subcommand" = "$word" ]; then
+                               echo "$subcommand"
+                               return
+                       fi
+               done
+               c=$((++c))
+       done
+}
+
 __git_whitespacelist="nowarn warn error error-all strip"
 
 _git_am ()
@@ -445,24 +508,14 @@ _git_add ()
 
 _git_bisect ()
 {
-       local i c=1 command
-       while [ $c -lt $COMP_CWORD ]; do
-               i="${COMP_WORDS[c]}"
-               case "$i" in
-               start|bad|good|reset|visualize|replay|log)
-                       command="$i"
-                       break
-                       ;;
-               esac
-               c=$((++c))
-       done
-
-       if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
-               __gitcomp "start bad good reset visualize replay log"
+       local subcommands="start bad good reset visualize replay log"
+       local subcommand="$(__git_find_subcommand "$subcommands")"
+       if [ -z "$subcommand" ]; then
+               __gitcomp "$subcommands"
                return
        fi
 
-       case "$command" in
+       case "$subcommand" in
        bad|good|reset)
                __gitcomp "$(__git_refs)"
                ;;
@@ -474,7 +527,33 @@ _git_bisect ()
 
 _git_branch ()
 {
-       __gitcomp "$(__git_refs)"
+       local i c=1 only_local_ref="n" has_r="n"
+
+       while [ $c -lt $COMP_CWORD ]; do
+               i="${COMP_WORDS[c]}"
+               case "$i" in
+               -d|-m)  only_local_ref="y" ;;
+               -r)     has_r="y" ;;
+               esac
+               c=$((++c))
+       done
+
+       case "${COMP_WORDS[COMP_CWORD]}" in
+       --*=*)  COMPREPLY=() ;;
+       --*)
+               __gitcomp "
+                       --color --no-color --verbose --abbrev= --no-abbrev
+                       --track --no-track
+                       "
+               ;;
+       *)
+               if [ $only_local_ref = "y" -a $has_r = "n" ]; then
+                       __gitcomp "$(__git_heads)"
+               else
+                       __gitcomp "$(__git_refs)"
+               fi
+               ;;
+       esac
 }
 
 _git_bundle ()
@@ -616,6 +695,7 @@ _git_format_patch ()
                        --in-reply-to=
                        --full-index --binary
                        --not --all
+                       --cover-letter
                        "
                return
                ;;
@@ -769,8 +849,8 @@ _git_push ()
 
 _git_rebase ()
 {
-       local cur="${COMP_WORDS[COMP_CWORD]}"
-       if [ -d .dotest ] || [ -d .git/.dotest-merge ]; then
+       local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+       if [ -d .dotest ] || [ -d "$dir"/.dotest-merge ]; then
                __gitcomp "--continue --skip --abort"
                return
        fi
@@ -889,7 +969,6 @@ _git_config ()
                core.sharedRepository
                core.warnAmbiguousRefs
                core.compression
-               core.legacyHeaders
                core.packedGitWindowSize
                core.packedGitLimit
                clean.requireForce
@@ -966,21 +1045,13 @@ _git_config ()
 
 _git_remote ()
 {
-       local i c=1 command
-       while [ $c -lt $COMP_CWORD ]; do
-               i="${COMP_WORDS[c]}"
-               case "$i" in
-               add|rm|show|prune|update) command="$i"; break ;;
-               esac
-               c=$((++c))
-       done
-
-       if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
-               __gitcomp "add rm show prune update"
+       local subcommands="add rm show prune update"
+       local subcommand="$(__git_find_subcommand "$subcommands")"
+       if [ -z "$subcommand" ]; then
                return
        fi
 
-       case "$command" in
+       case "$subcommand" in
        rm|show|prune)
                __gitcomp "$(__git_remotes)"
                ;;
@@ -1054,34 +1125,107 @@ _git_show ()
 
 _git_stash ()
 {
-       __gitcomp 'list show apply clear'
+       local subcommands='save list show apply clear drop pop create'
+       if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
+               __gitcomp "$subcommands"
+       fi
 }
 
 _git_submodule ()
 {
-       local i c=1 command
-       while [ $c -lt $COMP_CWORD ]; do
-               i="${COMP_WORDS[c]}"
-               case "$i" in
-               add|status|init|update) command="$i"; break ;;
-               esac
-               c=$((++c))
-       done
-
-       if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
+       local subcommands="add status init update"
+       if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
                local cur="${COMP_WORDS[COMP_CWORD]}"
                case "$cur" in
                --*)
                        __gitcomp "--quiet --cached"
                        ;;
                *)
-                       __gitcomp "add status init update"
+                       __gitcomp "$subcommands"
                        ;;
                esac
                return
        fi
 }
 
+_git_svn ()
+{
+       local subcommands="
+               init fetch clone rebase dcommit log find-rev
+               set-tree commit-diff info create-ignore propget
+               proplist show-ignore show-externals
+               "
+       local subcommand="$(__git_find_subcommand "$subcommands")"
+       if [ -z "$subcommand" ]; then
+               __gitcomp "$subcommands"
+       else
+               local remote_opts="--username= --config-dir= --no-auth-cache"
+               local fc_opts="
+                       --follow-parent --authors-file= --repack=
+                       --no-metadata --use-svm-props --use-svnsync-props
+                       --log-window-size= --no-checkout --quiet
+                       --repack-flags --user-log-author $remote_opts
+                       "
+               local init_opts="
+                       --template= --shared= --trunk= --tags=
+                       --branches= --stdlayout --minimize-url
+                       --no-metadata --use-svm-props --use-svnsync-props
+                       --rewrite-root= $remote_opts
+                       "
+               local cmt_opts="
+                       --edit --rmdir --find-copies-harder --copy-similarity=
+                       "
+
+               local cur="${COMP_WORDS[COMP_CWORD]}"
+               case "$subcommand,$cur" in
+               fetch,--*)
+                       __gitcomp "--revision= --fetch-all $fc_opts"
+                       ;;
+               clone,--*)
+                       __gitcomp "--revision= $fc_opts $init_opts"
+                       ;;
+               init,--*)
+                       __gitcomp "$init_opts"
+                       ;;
+               dcommit,--*)
+                       __gitcomp "
+                               --merge --strategy= --verbose --dry-run
+                               --fetch-all --no-rebase $cmt_opts $fc_opts
+                               "
+                       ;;
+               set-tree,--*)
+                       __gitcomp "--stdin $cmt_opts $fc_opts"
+                       ;;
+               create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\
+               show-externals,--*)
+                       __gitcomp "--revision="
+                       ;;
+               log,--*)
+                       __gitcomp "
+                               --limit= --revision= --verbose --incremental
+                               --oneline --show-commit --non-recursive
+                               --authors-file=
+                               "
+                       ;;
+               rebase,--*)
+                       __gitcomp "
+                               --merge --verbose --strategy= --local
+                               --fetch-all $fc_opts
+                               "
+                       ;;
+               commit-diff,--*)
+                       __gitcomp "--message= --file= --revision= $cmt_opts"
+                       ;;
+               info,--*)
+                       __gitcomp "--url"
+                       ;;
+               *)
+                       COMPREPLY=()
+                       ;;
+               esac
+       fi
+}
+
 _git_tag ()
 {
        local i c=1 f=0
@@ -1131,15 +1275,18 @@ _git ()
                c=$((++c))
        done
 
-       if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
+       if [ -z "$command" ]; then
                case "${COMP_WORDS[COMP_CWORD]}" in
                --*=*) COMPREPLY=() ;;
                --*)   __gitcomp "
+                       --paginate
                        --no-pager
                        --git-dir=
                        --bare
                        --version
                        --exec-path
+                       --work-tree=
+                       --help
                        "
                        ;;
                *)     __gitcomp "$(__git_commands) $(__git_aliases)" ;;
@@ -1183,6 +1330,7 @@ _git ()
        show-branch) _git_log ;;
        stash)       _git_stash ;;
        submodule)   _git_submodule ;;
+       svn)         _git_svn ;;
        tag)         _git_tag ;;
        whatchanged) _git_log ;;
        *)           COMPREPLY=() ;;
@@ -1233,6 +1381,7 @@ complete -o default -o nospace -F _git_shortlog git-shortlog
 complete -o default -o nospace -F _git_show git-show
 complete -o default -o nospace -F _git_stash git-stash
 complete -o default -o nospace -F _git_submodule git-submodule
+complete -o default -o nospace -F _git_svn git-svn
 complete -o default -o nospace -F _git_log git-show-branch
 complete -o default -o nospace -F _git_tag git-tag
 complete -o default -o nospace -F _git_log git-whatchanged
index bb671d561ebc9af51bb9a5d52017e71fd81881e9..9f92cd250b2ba2c3057bff87daa1b7990f85403d 100644 (file)
@@ -105,6 +105,13 @@ selected element from l."
      (setq ,l (remove e ,l))
      e))
 
+(defvar git-blame-log-oneline-format
+  "format:[%cr] %cn: %s"
+  "*Formatting option used for describing current line in the minibuffer.
+
+This option is used to pass to git log --pretty= command-line option,
+and describe which commit the current line was made.")
+
 (defvar git-blame-dark-colors
   (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c")
   "*List of colors (format #RGB) to use in a dark environment.
@@ -371,7 +378,8 @@ See also function `git-blame-mode'."
 (defun git-describe-commit (hash)
   (with-temp-buffer
     (call-process "git" nil t nil
-                  "log" "-1" "--pretty=oneline"
+                  "log" "-1"
+                 (concat "--pretty=" git-blame-log-oneline-format)
                   hash)
     (buffer-substring (point-min) (1- (point-max)))))
 
index 0312d891fd1ddfe87f452611c24af7178313092e..4fa853fae76dc2ac132e489f5f1b630b3fc0f1de 100644 (file)
@@ -35,7 +35,6 @@
 ;;
 ;; TODO
 ;;  - portability to XEmacs
-;;  - better handling of subprocess errors
 ;;  - diff against other branch
 ;;  - renaming files from the status buffer
 ;;  - creating tags
@@ -186,14 +185,25 @@ if there is already one that displays the same directory."
 
 (defun git-call-process-env (buffer env &rest args)
   "Wrapper for call-process that sets environment strings."
-  (if env
-      (apply #'call-process "env" nil buffer nil
-             (append (git-get-env-strings env) (list "git") args))
+  (let ((process-environment (append (git-get-env-strings env)
+                                     process-environment)))
     (apply #'call-process "git" nil buffer nil args)))
 
+(defun git-call-process-display-error (&rest args)
+  "Wrapper for call-process that displays error messages."
+  (let* ((dir default-directory)
+         (buffer (get-buffer-create "*Git Command Output*"))
+         (ok (with-current-buffer buffer
+               (let ((default-directory dir)
+                     (buffer-read-only nil))
+                 (erase-buffer)
+                 (eq 0 (apply 'call-process "git" nil (list buffer t) nil args))))))
+    (unless ok (display-message-or-buffer buffer))
+    ok))
+
 (defun git-call-process-env-string (env &rest args)
   "Wrapper for call-process that sets environment strings,
-and returns the process output as a string."
+and returns the process output as a string, or nil if the git failed."
   (with-temp-buffer
     (and (eq 0 (apply #' git-call-process-env t env args))
          (buffer-string))))
@@ -377,7 +387,7 @@ and returns the process output as a string."
     (when reason
      (push reason args)
      (push "-m" args))
-    (eq 0 (apply #'git-call-process-env nil nil "update-ref" args))))
+    (apply 'git-call-process-display-error "update-ref" args)))
 
 (defun git-read-tree (tree &optional index-file)
   "Read a tree into the index file."
@@ -558,12 +568,15 @@ and returns the process output as a string."
                     (?\100 "   (type change file -> subproject)")
                     (?\120 "   (type change symlink -> subproject)")
                     (t "   (subproject)")))
+                  (?\110 nil)  ;; directory (internal, not a real git state)
                  (?\000  ;; deleted or unknown
                   (case old-type
                     (?\120 "   (symlink)")
                     (?\160 "   (subproject)")))
                  (t (format "   (unknown type %o)" new-type)))))
-    (if str (propertize str 'face 'git-status-face) "")))
+    (cond (str (propertize str 'face 'git-status-face))
+          ((eq new-type ?\110) "/")
+          (t ""))))
 
 (defun git-rename-as-string (info)
   "Return a string describing the copy or rename associated with INFO, or an empty string if none."
@@ -666,9 +679,11 @@ Return the list of files that haven't been handled."
     (with-temp-buffer
       (apply #'git-call-process-env t nil "ls-files" "-z" (append options (list "--") files))
       (goto-char (point-min))
-      (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
+      (while (re-search-forward "\\([^\0]*?\\)\\(/?\\)\0" nil t 1)
         (let ((name (match-string 1)))
-          (push (git-create-fileinfo default-state name) infolist)
+          (push (git-create-fileinfo default-state name 0
+                                     (if (string-equal "/" (match-string 2)) (lsh ?\110 9) 0))
+                infolist)
           (setq files (delete name files)))))
     (git-insert-info-list status infolist)
     files))
@@ -713,7 +728,7 @@ Return the list of files that haven't been handled."
 (defun git-run-ls-files-with-excludes (status files default-state &rest options)
   "Run git-ls-files on FILES with appropriate --exclude-from options."
   (let ((exclude-files (git-get-exclude-files)))
-    (apply #'git-run-ls-files status files default-state
+    (apply #'git-run-ls-files status files default-state "--directory" "--no-empty-directory"
            (concat "--exclude-per-directory=" git-per-dir-ignore-file)
            (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
 
@@ -735,6 +750,27 @@ Return the list of files that haven't been handled."
     (git-refresh-files)
     (git-refresh-ewoc-hf git-status)))
 
+(defun git-mark-files (status files)
+  "Mark all the specified FILES, and unmark the others."
+  (setq files (sort files #'string-lessp))
+  (let ((file (and files (pop files)))
+        (node (ewoc-nth status 0)))
+    (while node
+      (let ((info (ewoc-data node)))
+        (if (and file (string-equal (git-fileinfo->name info) file))
+            (progn
+              (unless (git-fileinfo->marked info)
+                (setf (git-fileinfo->marked info) t)
+                (setf (git-fileinfo->needs-refresh info) t))
+              (setq file (pop files))
+              (setq node (ewoc-next status node)))
+          (when (git-fileinfo->marked info)
+            (setf (git-fileinfo->marked info) nil)
+            (setf (git-fileinfo->needs-refresh info) t))
+          (if (and file (string-lessp file (git-fileinfo->name info)))
+              (setq file (pop files))
+            (setq node (ewoc-next status node))))))))
+
 (defun git-marked-files ()
   "Return a list of all marked files, or if none a list containing just the file at cursor position."
   (unless git-status (error "Not in git-status buffer."))
@@ -840,16 +876,17 @@ Return the list of files that haven't been handled."
                       (if (or (not (string-equal tree head-tree))
                               (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
                           (let ((commit (git-commit-tree buffer tree head)))
-                            (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
-                            (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
-                            (with-current-buffer buffer (erase-buffer))
-                           (git-update-status-files (git-get-filenames files) 'uptodate)
-                            (git-call-process-env nil nil "rerere")
-                            (git-call-process-env nil nil "gc" "--auto")
-                            (git-refresh-files)
-                            (git-refresh-ewoc-hf git-status)
-                            (message "Committed %s." commit)
-                            (git-run-hook "post-commit" nil))
+                            (when commit
+                              (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
+                              (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
+                              (with-current-buffer buffer (erase-buffer))
+                              (git-update-status-files (git-get-filenames files) 'uptodate)
+                              (git-call-process-env nil nil "rerere")
+                              (git-call-process-env nil nil "gc" "--auto")
+                              (git-refresh-files)
+                              (git-refresh-ewoc-hf git-status)
+                              (message "Committed %s." commit)
+                              (git-run-hook "post-commit" nil)))
                         (message "Commit aborted."))))
                 (message "No files to commit.")))
           (delete-file index-file))))))
@@ -957,11 +994,12 @@ Return the list of files that haven't been handled."
   "Add marked file(s) to the index cache."
   (interactive)
   (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored))))
+    ;; FIXME: add support for directories
     (unless files
       (push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
-    (apply #'git-call-process-env nil nil "update-index" "--add" "--" files)
-    (git-update-status-files files 'uptodate)
-    (git-success-message "Added" files)))
+    (when (apply 'git-call-process-display-error "update-index" "--add" "--" files)
+      (git-update-status-files files 'uptodate)
+      (git-success-message "Added" files))))
 
 (defun git-ignore-file ()
   "Add marked file(s) to the ignore list."
@@ -983,16 +1021,19 @@ Return the list of files that haven't been handled."
          (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
         (progn
           (dolist (name files)
-            (when (file-exists-p name) (delete-file name)))
-          (apply #'git-call-process-env nil nil "update-index" "--remove" "--" files)
-          (git-update-status-files files nil)
-          (git-success-message "Removed" files))
+            (ignore-errors
+              (if (file-directory-p name)
+                  (delete-directory name)
+                (delete-file name))))
+          (when (apply 'git-call-process-display-error "update-index" "--remove" "--" files)
+            (git-update-status-files files nil)
+            (git-success-message "Removed" files)))
       (message "Aborting"))))
 
 (defun git-revert-file ()
   "Revert changes to the marked file(s)."
   (interactive)
-  (let ((files (git-marked-files))
+  (let ((files (git-marked-files-state 'added 'deleted 'modified 'unmerged))
         added modified)
     (when (and files
                (yes-or-no-p
@@ -1003,21 +1044,31 @@ Return the list of files that haven't been handled."
           ('deleted (push (git-fileinfo->name info) modified))
           ('unmerged (push (git-fileinfo->name info) modified))
           ('modified (push (git-fileinfo->name info) modified))))
-      (when added
-        (apply #'git-call-process-env nil nil "update-index" "--force-remove" "--" added))
-      (when modified
-        (apply #'git-call-process-env nil nil "checkout" "HEAD" modified))
-      (git-update-status-files (append added modified) 'uptodate)
-      (git-success-message "Reverted" (git-get-filenames files)))))
+      ;; check if a buffer contains one of the files and isn't saved
+      (dolist (file modified)
+        (let ((buffer (get-file-buffer file)))
+          (when (and buffer (buffer-modified-p buffer))
+            (error "Buffer %s is modified. Please kill or save modified buffers before reverting." (buffer-name buffer)))))
+      (let ((ok (and
+                 (or (not added)
+                     (apply 'git-call-process-display-error "update-index" "--force-remove" "--" added))
+                 (or (not modified)
+                     (apply 'git-call-process-display-error "checkout" "HEAD" modified)))))
+        (git-update-status-files (append added modified) 'uptodate)
+        (when ok
+          (dolist (file modified)
+            (let ((buffer (get-file-buffer file)))
+              (when buffer (with-current-buffer buffer (revert-buffer t t t)))))
+          (git-success-message "Reverted" (git-get-filenames files)))))))
 
 (defun git-resolve-file ()
   "Resolve conflicts in marked file(s)."
   (interactive)
   (let ((files (git-get-filenames (git-marked-files-state 'unmerged))))
     (when files
-      (apply #'git-call-process-env nil nil "update-index" "--" files)
-      (git-update-status-files files 'uptodate)
-      (git-success-message "Resolved" files))))
+      (when (apply 'git-call-process-display-error "update-index" "--" files)
+        (git-update-status-files files 'uptodate)
+        (git-success-message "Resolved" files)))))
 
 (defun git-remove-handled ()
   "Remove handled files from the status list."
@@ -1063,6 +1114,16 @@ Return the list of files that haven't been handled."
         (message "Inserting unknown files...done"))
     (git-remove-handled)))
 
+(defun git-expand-directory (info)
+  "Expand the directory represented by INFO to list its files."
+  (when (eq (lsh (git-fileinfo->new-perm info) -9) ?\110)
+    (let ((dir (git-fileinfo->name info)))
+      (git-set-filenames-state git-status (list dir) nil)
+      (git-run-ls-files-with-excludes git-status (list (concat dir "/")) 'unknown "-o")
+      (git-refresh-files)
+      (git-refresh-ewoc-hf git-status)
+      t)))
+
 (defun git-setup-diff-buffer (buffer)
   "Setup a buffer for displaying a diff."
   (let ((dir default-directory))
@@ -1199,7 +1260,8 @@ Return the list of files that haven't been handled."
       (goto-char (point-min))
       (when (re-search-forward "\n+\\'" nil t)
         (replace-match "\n" t t))
-      (when sign-off (git-append-sign-off committer-name committer-email)))))
+      (when sign-off (git-append-sign-off committer-name committer-email)))
+    buffer))
 
 (defun git-commit-file ()
   "Commit the marked file(s), asking for a commit message."
@@ -1232,14 +1294,61 @@ Return the list of files that haven't been handled."
       (setq buffer-file-coding-system coding-system)
       (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
 
+(defun git-setup-commit-buffer (commit)
+  "Setup the commit buffer with the contents of COMMIT."
+  (let (author-name author-email subject date msg)
+    (with-temp-buffer
+      (let ((coding-system (git-get-logoutput-coding-system)))
+        (git-call-process-env t nil "log" "-1" "--pretty=medium" commit)
+        (goto-char (point-min))
+        (when (re-search-forward "^Author: *\\(.*\\) <\\(.*\\)>$" nil t)
+          (setq author-name (match-string 1))
+          (setq author-email (match-string 2)))
+        (when (re-search-forward "^Date: *\\(.*\\)$" nil t)
+          (setq date (match-string 1)))
+        (while (re-search-forward "^    \\(.*\\)$" nil t)
+          (push (match-string 1) msg))
+        (setq msg (nreverse msg))
+        (setq subject (pop msg))
+        (while (and msg (zerop (length (car msg))) (pop msg)))))
+    (git-setup-log-buffer (get-buffer-create "*git-commit*")
+                          author-name author-email subject date
+                          (mapconcat #'identity msg "\n"))))
+
+(defun git-get-commit-files (commit)
+  "Retrieve the list of files modified by COMMIT."
+  (let (files)
+    (with-temp-buffer
+      (git-call-process-env t nil "diff-tree" "-r" "-z" "--name-only" "--no-commit-id" commit)
+      (goto-char (point-min))
+      (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
+        (push (match-string 1) files)))
+    files))
+
+(defun git-amend-commit ()
+  "Undo the last commit on HEAD, and set things up to commit an
+amended version of it."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (when (git-empty-db-p) (error "No commit to amend."))
+  (let* ((commit (git-rev-parse "HEAD"))
+         (files (git-get-commit-files commit)))
+    (when (git-call-process-display-error "reset" "--soft" "HEAD^")
+      (git-update-status-files (copy-sequence files) 'uptodate)
+      (git-mark-files git-status files)
+      (git-refresh-files)
+      (git-setup-commit-buffer commit)
+      (git-commit-file))))
+
 (defun git-find-file ()
   "Visit the current file in its own buffer."
   (interactive)
   (unless git-status (error "Not in git-status buffer."))
   (let ((info (ewoc-data (ewoc-locate git-status))))
-    (find-file (git-fileinfo->name info))
-    (when (eq 'unmerged (git-fileinfo->state info))
-      (smerge-mode 1))))
+    (unless (git-expand-directory info)
+      (find-file (git-fileinfo->name info))
+      (when (eq 'unmerged (git-fileinfo->state info))
+        (smerge-mode 1)))))
 
 (defun git-find-file-other-window ()
   "Visit the current file in its own buffer in another window."
@@ -1309,6 +1418,7 @@ Return the list of files that haven't been handled."
 
 (unless git-status-mode-map
   (let ((map (make-keymap))
+        (commit-map (make-sparse-keymap))
         (diff-map (make-sparse-keymap))
         (toggle-map (make-sparse-keymap)))
     (suppress-keymap map)
@@ -1317,6 +1427,7 @@ Return the list of files that haven't been handled."
     (define-key map " "   'git-next-file)
     (define-key map "a"   'git-add-file)
     (define-key map "c"   'git-commit-file)
+    (define-key map "\C-c" commit-map)
     (define-key map "d"    diff-map)
     (define-key map "="   'git-diff-file)
     (define-key map "f"   'git-find-file)
@@ -1342,6 +1453,8 @@ Return the list of files that haven't been handled."
     (define-key map "x"   'git-remove-handled)
     (define-key map "\C-?" 'git-unmark-file-up)
     (define-key map "\M-\C-?" 'git-unmark-all)
+    ; the commit submap
+    (define-key commit-map "\C-a" 'git-amend-commit)
     ; the diff submap
     (define-key diff-map "b" 'git-diff-file-base)
     (define-key diff-map "c" 'git-diff-file-combined)
diff --git a/contrib/examples/git-checkout.sh b/contrib/examples/git-checkout.sh
new file mode 100755 (executable)
index 0000000..1a7689a
--- /dev/null
@@ -0,0 +1,302 @@
+#!/bin/sh
+
+OPTIONS_KEEPDASHDASH=t
+OPTIONS_SPEC="\
+git-checkout [options] [<branch>] [<paths>...]
+--
+b=          create a new branch started at <branch>
+l           create the new branch's reflog
+track       arrange that the new branch tracks the remote branch
+f           proceed even if the index or working tree is not HEAD
+m           merge local modifications into the new branch
+q,quiet     be quiet
+"
+SUBDIRECTORY_OK=Sometimes
+. git-sh-setup
+require_work_tree
+
+old_name=HEAD
+old=$(git rev-parse --verify $old_name 2>/dev/null)
+oldbranch=$(git symbolic-ref $old_name 2>/dev/null)
+new=
+new_name=
+force=
+branch=
+track=
+newbranch=
+newbranch_log=
+merge=
+quiet=
+v=-v
+LF='
+'
+
+while test $# != 0; do
+       case "$1" in
+       -b)
+               shift
+               newbranch="$1"
+               [ -z "$newbranch" ] &&
+                       die "git checkout: -b needs a branch name"
+               git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
+                       die "git checkout: branch $newbranch already exists"
+               git check-ref-format "heads/$newbranch" ||
+                       die "git checkout: we do not like '$newbranch' as a branch name."
+               ;;
+       -l)
+               newbranch_log=-l
+               ;;
+       --track|--no-track)
+               track="$1"
+               ;;
+       -f)
+               force=1
+               ;;
+       -m)
+               merge=1
+               ;;
+       -q|--quiet)
+               quiet=1
+               v=
+               ;;
+       --)
+               shift
+               break
+               ;;
+       *)
+               usage
+               ;;
+       esac
+       shift
+done
+
+arg="$1"
+rev=$(git rev-parse --verify "$arg" 2>/dev/null)
+if rev=$(git rev-parse --verify "$rev^0" 2>/dev/null)
+then
+       [ -z "$rev" ] && die "unknown flag $arg"
+       new_name="$arg"
+       if git show-ref --verify --quiet -- "refs/heads/$arg"
+       then
+               rev=$(git rev-parse --verify "refs/heads/$arg^0")
+               branch="$arg"
+       fi
+       new="$rev"
+       shift
+elif rev=$(git rev-parse --verify "$rev^{tree}" 2>/dev/null)
+then
+       # checking out selected paths from a tree-ish.
+       new="$rev"
+       new_name="$rev^{tree}"
+       shift
+fi
+[ "$1" = "--" ] && shift
+
+case "$newbranch,$track" in
+,--*)
+       die "git checkout: --track and --no-track require -b"
+esac
+
+case "$force$merge" in
+11)
+       die "git checkout: -f and -m are incompatible"
+esac
+
+# The behaviour of the command with and without explicit path
+# parameters is quite different.
+#
+# Without paths, we are checking out everything in the work tree,
+# possibly switching branches.  This is the traditional behaviour.
+#
+# With paths, we are _never_ switching branch, but checking out
+# the named paths from either index (when no rev is given),
+# or the named tree-ish (when rev is given).
+
+if test "$#" -ge 1
+then
+       hint=
+       if test "$#" -eq 1
+       then
+               hint="
+Did you intend to checkout '$@' which can not be resolved as commit?"
+       fi
+       if test '' != "$newbranch$force$merge"
+       then
+               die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
+       fi
+       if test '' != "$new"
+       then
+               # from a specific tree-ish; note that this is for
+               # rescuing paths and is never meant to remove what
+               # is not in the named tree-ish.
+               git ls-tree --full-name -r "$new" "$@" |
+               git update-index --index-info || exit $?
+       fi
+
+       # Make sure the request is about existing paths.
+       git ls-files --full-name --error-unmatch -- "$@" >/dev/null || exit
+       git ls-files --full-name -- "$@" |
+               (cd_to_toplevel && git checkout-index -f -u --stdin)
+
+       # Run a post-checkout hook -- the HEAD does not change so the
+       # current HEAD is passed in for both args
+       if test -x "$GIT_DIR"/hooks/post-checkout; then
+           "$GIT_DIR"/hooks/post-checkout $old $old 0
+       fi
+
+       exit $?
+else
+       # Make sure we did not fall back on $arg^{tree} codepath
+       # since we are not checking out from an arbitrary tree-ish,
+       # but switching branches.
+       if test '' != "$new"
+       then
+               git rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
+               die "Cannot switch branch to a non-commit."
+       fi
+fi
+
+# We are switching branches and checking out trees, so
+# we *NEED* to be at the toplevel.
+cd_to_toplevel
+
+[ -z "$new" ] && new=$old && new_name="$old_name"
+
+# If we don't have an existing branch that we're switching to,
+# and we don't have a new branch name for the target we
+# are switching to, then we are detaching our HEAD from any
+# branch.  However, if "git checkout HEAD" detaches the HEAD
+# from the current branch, even though that may be logically
+# correct, it feels somewhat funny.  More importantly, we do not
+# want "git checkout" nor "git checkout -f" to detach HEAD.
+
+detached=
+detach_warn=
+
+describe_detached_head () {
+       test -n "$quiet" || {
+               printf >&2 "$1 "
+               GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" --
+       }
+}
+
+if test -z "$branch$newbranch" && test "$new_name" != "$old_name"
+then
+       detached="$new"
+       if test -n "$oldbranch" && test -z "$quiet"
+       then
+               detach_warn="Note: moving to \"$new_name\" which isn't a local branch
+If you want to create a new branch from this checkout, you may do so
+(now or later) by using -b with the checkout command again. Example:
+  git checkout -b <new_branch_name>"
+       fi
+elif test -z "$oldbranch" && test "$new" != "$old"
+then
+       describe_detached_head 'Previous HEAD position was' "$old"
+fi
+
+if [ "X$old" = X ]
+then
+       if test -z "$quiet"
+       then
+               echo >&2 "warning: You appear to be on a branch yet to be born."
+               echo >&2 "warning: Forcing checkout of $new_name."
+       fi
+       force=1
+fi
+
+if [ "$force" ]
+then
+    git read-tree $v --reset -u $new
+else
+    git update-index --refresh >/dev/null
+    git read-tree $v -m -u --exclude-per-directory=.gitignore $old $new || (
+       case "$merge,$v" in
+       ,*)
+               exit 1 ;;
+       1,)
+               ;; # quiet
+       *)
+               echo >&2 "Falling back to 3-way merge..." ;;
+       esac
+
+       # Match the index to the working tree, and do a three-way.
+       git diff-files --name-only | git update-index --remove --stdin &&
+       work=`git write-tree` &&
+       git read-tree $v --reset -u $new || exit
+
+       eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
+       eval GITHEAD_$work=local &&
+       export GITHEAD_$new GITHEAD_$work &&
+       git merge-recursive $old -- $new $work
+
+       # Do not register the cleanly merged paths in the index yet.
+       # this is not a real merge before committing, but just carrying
+       # the working tree changes along.
+       unmerged=`git ls-files -u`
+       git read-tree $v --reset $new
+       case "$unmerged" in
+       '')     ;;
+       *)
+               (
+                       z40=0000000000000000000000000000000000000000
+                       echo "$unmerged" |
+                       sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
+                       echo "$unmerged"
+               ) | git update-index --index-info
+               ;;
+       esac
+       exit 0
+    )
+    saved_err=$?
+    if test "$saved_err" = 0 && test -z "$quiet"
+    then
+       git diff-index --name-status "$new"
+    fi
+    (exit $saved_err)
+fi
+
+#
+# Switch the HEAD pointer to the new branch if we
+# checked out a branch head, and remove any potential
+# old MERGE_HEAD's (subsequent commits will clearly not
+# be based on them, since we re-set the index)
+#
+if [ "$?" -eq 0 ]; then
+       if [ "$newbranch" ]; then
+               git branch $track $newbranch_log "$newbranch" "$new_name" || exit
+               branch="$newbranch"
+       fi
+       if test -n "$branch"
+       then
+               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
+               GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch"
+               if test -n "$quiet"
+               then
+                       true    # nothing
+               elif test "refs/heads/$branch" = "$oldbranch"
+               then
+                       echo >&2 "Already on branch \"$branch\""
+               else
+                       echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
+               fi
+       elif test -n "$detached"
+       then
+               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
+               git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" ||
+                       die "Cannot detach HEAD"
+               if test -n "$detach_warn"
+               then
+                       echo >&2 "$detach_warn"
+               fi
+               describe_detached_head 'HEAD is now at' HEAD
+       fi
+       rm -f "$GIT_DIR/MERGE_HEAD"
+else
+       exit 1
+fi
+
+# Run a post-checkout hook
+if test -x "$GIT_DIR"/hooks/post-checkout; then
+       "$GIT_DIR"/hooks/post-checkout $old $new 1
+fi
index c80a6da2522b690e15f84fedf52a132078cd265a..650ea341762650cc9a6b517322d9ee3d7aa3b604 100755 (executable)
@@ -464,75 +464,47 @@ class P4Submit(Command):
     def __init__(self):
         Command.__init__(self)
         self.options = [
-                optparse.make_option("--continue", action="store_false", dest="firstTime"),
                 optparse.make_option("--verbose", dest="verbose", action="store_true"),
                 optparse.make_option("--origin", dest="origin"),
-                optparse.make_option("--reset", action="store_true", dest="reset"),
-                optparse.make_option("--log-substitutions", dest="substFile"),
-                optparse.make_option("--dry-run", action="store_true"),
-                optparse.make_option("--direct", dest="directSubmit", action="store_true"),
-                optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"),
                 optparse.make_option("-M", dest="detectRename", action="store_true"),
         ]
         self.description = "Submit changes from git to the perforce depot."
         self.usage += " [name of git branch to submit into perforce depot]"
-        self.firstTime = True
-        self.reset = False
         self.interactive = True
-        self.dryRun = False
-        self.substFile = ""
-        self.firstTime = True
         self.origin = ""
-        self.directSubmit = False
-        self.trustMeLikeAFool = False
         self.detectRename = False
         self.verbose = False
         self.isWindows = (platform.system() == "Windows")
 
-        self.logSubstitutions = {}
-        self.logSubstitutions["<enter description here>"] = "%log%"
-        self.logSubstitutions["\tDetails:"] = "\tDetails:  %log%"
-
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
             die("You have files opened with perforce! Close them before starting the sync.")
 
-    def start(self):
-        if len(self.config) > 0 and not self.reset:
-            die("Cannot start sync. Previous sync config found at %s\n"
-                "If you want to start submitting again from scratch "
-                "maybe you want to call git-p4 submit --reset" % self.configFile)
-
-        commits = []
-        if self.directSubmit:
-            commits.append("0")
-        else:
-            for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
-                commits.append(line.strip())
-            commits.reverse()
-
-        self.config["commits"] = commits
-
+    # replaces everything between 'Description:' and the next P4 submit template field with the
+    # commit message
     def prepareLogMessage(self, template, message):
         result = ""
 
+        inDescriptionSection = False
+
         for line in template.split("\n"):
             if line.startswith("#"):
                 result += line + "\n"
                 continue
 
-            substituted = False
-            for key in self.logSubstitutions.keys():
-                if line.find(key) != -1:
-                    value = self.logSubstitutions[key]
-                    value = value.replace("%log%", message)
-                    if value != "@remove@":
-                        result += line.replace(key, value) + "\n"
-                    substituted = True
-                    break
+            if inDescriptionSection:
+                if line.startswith("Files:"):
+                    inDescriptionSection = False
+                else:
+                    continue
+            else:
+                if line.startswith("Description:"):
+                    inDescriptionSection = True
+                    line += "\n"
+                    for messageLine in message.split("\n"):
+                        line += "\t" + messageLine + "\n"
 
-            if not substituted:
-                result += line + "\n"
+            result += line + "\n"
 
         return result
 
@@ -561,13 +533,9 @@ class P4Submit(Command):
         return template
 
     def applyCommit(self, id):
-        if self.directSubmit:
-            print "Applying local change in working directory/index"
-            diff = self.diffStatus
-        else:
-            print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
-            diffOpts = ("", "-M")[self.detectRename]
-            diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
+        print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
+        diffOpts = ("", "-M")[self.detectRename]
+        diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
         filesToAdd = set()
         filesToDelete = set()
         editedFiles = set()
@@ -602,10 +570,7 @@ class P4Submit(Command):
             else:
                 die("unknown modifier %s for %s" % (modifier, path))
 
-        if self.directSubmit:
-            diffcmd = "cat \"%s\"" % self.diffFile
-        else:
-            diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
+        diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
         patchcmd = diffcmd + " | git apply "
         tryPatchCmd = patchcmd + "--check -"
         applyPatchCmd = patchcmd + "--check --apply -"
@@ -653,13 +618,10 @@ class P4Submit(Command):
             mode = filesToChangeExecBit[f]
             setP4ExecBit(f, mode)
 
-        logMessage = ""
-        if not self.directSubmit:
-            logMessage = extractLogMessageFromGitCommit(id)
-            logMessage = logMessage.replace("\n", "\n\t")
-            if self.isWindows:
-                logMessage = logMessage.replace("\n", "\r\n")
-            logMessage = logMessage.strip()
+        logMessage = extractLogMessageFromGitCommit(id)
+        if self.isWindows:
+            logMessage = logMessage.replace("\n", "\r\n")
+        logMessage = logMessage.strip()
 
         template = self.prepareSubmitTemplate()
 
@@ -681,57 +643,24 @@ class P4Submit(Command):
                 separatorLine += "\r"
             separatorLine += "\n"
 
-            response = "e"
-            if self.trustMeLikeAFool:
-                response = "y"
-
-            firstIteration = True
-            while response == "e":
-                if not firstIteration:
-                    response = raw_input("Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip ")
-                firstIteration = False
-                if response == "e":
-                    [handle, fileName] = tempfile.mkstemp()
-                    tmpFile = os.fdopen(handle, "w+")
-                    tmpFile.write(submitTemplate + separatorLine + diff)
-                    tmpFile.close()
-                    defaultEditor = "vi"
-                    if platform.system() == "Windows":
-                        defaultEditor = "notepad"
-                    editor = os.environ.get("EDITOR", defaultEditor);
-                    system(editor + " " + fileName)
-                    tmpFile = open(fileName, "rb")
-                    message = tmpFile.read()
-                    tmpFile.close()
-                    os.remove(fileName)
-                    submitTemplate = message[:message.index(separatorLine)]
-                    if self.isWindows:
-                        submitTemplate = submitTemplate.replace("\r\n", "\n")
-
-            if response == "y" or response == "yes":
-               if self.dryRun:
-                   print submitTemplate
-                   raw_input("Press return to continue...")
-               else:
-                   if self.directSubmit:
-                       print "Submitting to git first"
-                       os.chdir(self.oldWorkingDirectory)
-                       write_pipe("git commit -a -F -", submitTemplate)
-                       os.chdir(self.clientPath)
-
-                   write_pipe("p4 submit -i", submitTemplate)
-            elif response == "s":
-                for f in editedFiles:
-                    system("p4 revert \"%s\"" % f);
-                for f in filesToAdd:
-                    system("p4 revert \"%s\"" % f);
-                    system("rm %s" %f)
-                for f in filesToDelete:
-                    system("p4 delete \"%s\"" % f);
-                return
-            else:
-                print "Not submitting!"
-                self.interactive = False
+            [handle, fileName] = tempfile.mkstemp()
+            tmpFile = os.fdopen(handle, "w+")
+            tmpFile.write(submitTemplate + separatorLine + diff)
+            tmpFile.close()
+            defaultEditor = "vi"
+            if platform.system() == "Windows":
+                defaultEditor = "notepad"
+            editor = os.environ.get("EDITOR", defaultEditor);
+            system(editor + " " + fileName)
+            tmpFile = open(fileName, "rb")
+            message = tmpFile.read()
+            tmpFile.close()
+            os.remove(fileName)
+            submitTemplate = message[:message.index(separatorLine)]
+            if self.isWindows:
+                submitTemplate = submitTemplate.replace("\r\n", "\n")
+
+            write_pipe("p4 submit -i", submitTemplate)
         else:
             fileName = "submit.txt"
             file = open(fileName, "w+")
@@ -772,67 +701,33 @@ class P4Submit(Command):
         print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
         self.oldWorkingDirectory = os.getcwd()
 
-        if self.directSubmit:
-            self.diffStatus = read_pipe_lines("git diff -r --name-status HEAD")
-            if len(self.diffStatus) == 0:
-                print "No changes in working directory to submit."
-                return True
-            patch = read_pipe("git diff -p --binary --diff-filter=ACMRTUXB HEAD")
-            self.diffFile = self.gitdir + "/p4-git-diff"
-            f = open(self.diffFile, "wb")
-            f.write(patch)
-            f.close();
-
         os.chdir(self.clientPath)
         print "Syncronizing p4 checkout..."
         system("p4 sync ...")
 
-        if self.reset:
-            self.firstTime = True
-
-        if len(self.substFile) > 0:
-            for line in open(self.substFile, "r").readlines():
-                tokens = line.strip().split("=")
-                self.logSubstitutions[tokens[0]] = tokens[1]
-
         self.check()
-        self.configFile = self.gitdir + "/p4-git-sync.cfg"
-        self.config = shelve.open(self.configFile, writeback=True)
 
-        if self.firstTime:
-            self.start()
-
-        commits = self.config.get("commits", [])
+        commits = []
+        for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
+            commits.append(line.strip())
+        commits.reverse()
 
         while len(commits) > 0:
-            self.firstTime = False
             commit = commits[0]
             commits = commits[1:]
-            self.config["commits"] = commits
             self.applyCommit(commit)
             if not self.interactive:
                 break
 
-        self.config.close()
-
-        if self.directSubmit:
-            os.remove(self.diffFile)
-
         if len(commits) == 0:
-            if self.firstTime:
-                print "No changes found to apply between %s and current HEAD" % self.origin
-            else:
-                print "All changes applied!"
-                os.chdir(self.oldWorkingDirectory)
+            print "All changes applied!"
+            os.chdir(self.oldWorkingDirectory)
 
-                sync = P4Sync()
-                sync.run([])
+            sync = P4Sync()
+            sync.run([])
 
-                response = raw_input("Do you want to rebase current HEAD from Perforce now using git-p4 rebase? [y]es/[n]o ")
-                if response == "y" or response == "yes":
-                    rebase = P4Rebase()
-                    rebase.rebase()
-            os.remove(self.configFile)
+            rebase = P4Rebase()
+            rebase.rebase()
 
         return True
 
@@ -850,7 +745,9 @@ class P4Sync(Command):
                                      help="Import into refs/heads/ , not refs/remotes"),
                 optparse.make_option("--max-changes", dest="maxChanges"),
                 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
-                                     help="Keep entire BRANCH/DIR/SUBDIR prefix during import")
+                                     help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
+                optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
+                                     help="Only sync files that are included in the Perforce Client Spec")
         ]
         self.description = """Imports from Perforce into a git repository.\n
     example:
@@ -876,18 +773,27 @@ class P4Sync(Command):
         self.keepRepoPath = False
         self.depotPaths = None
         self.p4BranchesInGit = []
+        self.cloneExclude = []
+        self.useClientSpec = False
+        self.clientSpecDirs = []
 
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
 
     def extractFilesFromCommit(self, commit):
+        self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
+                             for path in self.cloneExclude]
         files = []
         fnum = 0
         while commit.has_key("depotFile%s" % fnum):
             path =  commit["depotFile%s" % fnum]
 
-            found = [p for p in self.depotPaths
-                     if path.startswith (p)]
+            if [p for p in self.cloneExclude
+                if path.startswith (p)]:
+                found = False
+            else:
+                found = [p for p in self.depotPaths
+                         if path.startswith (p)]
             if not found:
                 fnum = fnum + 1
                 continue
@@ -944,19 +850,32 @@ class P4Sync(Command):
 
     ## Should move this out, doesn't use SELF.
     def readP4Files(self, files):
-        files = [f for f in files
-                 if f['action'] != 'delete']
+        filesForCommit = []
+        filesToRead = []
 
-        if not files:
-            return
+        for f in files:
+            includeFile = True
+            for val in self.clientSpecDirs:
+                if f['path'].startswith(val[0]):
+                    if val[1] <= 0:
+                        includeFile = False
+                    break
+
+            if includeFile:
+                filesForCommit.append(f)
+                if f['action'] != 'delete':
+                    filesToRead.append(f)
 
-        filedata = p4CmdList('-x - print',
-                             stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
-                                              for f in files]),
-                             stdin_mode='w+')
-        if "p4ExitCode" in filedata[0]:
-            die("Problems executing p4. Error: [%d]."
-                % (filedata[0]['p4ExitCode']));
+        filedata = []
+        if len(filesToRead) > 0:
+            filedata = p4CmdList('-x - print',
+                                 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
+                                                  for f in filesToRead]),
+                                 stdin_mode='w+')
+
+            if "p4ExitCode" in filedata[0]:
+                die("Problems executing p4. Error: [%d]."
+                    % (filedata[0]['p4ExitCode']));
 
         j = 0;
         contents = {}
@@ -964,9 +883,13 @@ class P4Sync(Command):
             stat = filedata[j]
             j += 1
             text = ''
-            while j < len(filedata) and filedata[j]['code'] in ('text',
-                                                                'binary'):
-                text += filedata[j]['data']
+            while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
+                tmp = filedata[j]['data']
+                if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
+                    tmp = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', tmp)
+                elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
+                    tmp = re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', tmp)
+                text += tmp
                 j += 1
 
 
@@ -976,9 +899,12 @@ class P4Sync(Command):
 
             contents[stat['depotFile']] = text
 
-        for f in files:
-            assert not f.has_key('data')
-            f['data'] = contents[f['path']]
+        for f in filesForCommit:
+            path = f['path']
+            if contents.has_key(path):
+                f['data'] = contents[path]
+
+        return filesForCommit
 
     def commit(self, details, files, branch, branchPrefixes, parent = ""):
         epoch = details["time"]
@@ -995,11 +921,7 @@ class P4Sync(Command):
                 new_files.append (f)
             else:
                 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
-        files = new_files
-        self.readP4Files(files)
-
-
-
+        files = self.readP4Files(new_files)
 
         self.gitStream.write("commit %s\n" % branch)
 #        gitStream.write("mark :%s\n" % details["change"])
@@ -1414,6 +1336,26 @@ class P4Sync(Command):
             print self.gitError.read()
 
 
+    def getClientSpec(self):
+        specList = p4CmdList( "client -o" )
+        temp = {}
+        for entry in specList:
+            for k,v in entry.iteritems():
+                if k.startswith("View"):
+                    if v.startswith('"'):
+                        start = 1
+                    else:
+                        start = 0
+                    index = v.find("...")
+                    v = v[start:index]
+                    if v.startswith("-"):
+                        v = v[1:]
+                        temp[v] = -len(v)
+                    else:
+                        temp[v] = len(v)
+        self.clientSpecDirs = temp.items()
+        self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
+
     def run(self, args):
         self.depotPaths = []
         self.changeRange = ""
@@ -1446,6 +1388,9 @@ class P4Sync(Command):
             if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
                 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
 
+        if self.useClientSpec or gitConfig("p4.useclientspec") == "true":
+            self.getClientSpec()
+
         # TODO: should always look at previous commits,
         # merge with previous imports, if possible.
         if args == []:
@@ -1640,6 +1585,11 @@ class P4Rebase(Command):
         return self.rebase()
 
     def rebase(self):
+        if os.system("git update-index --refresh") != 0:
+            die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
+        if len(read_pipe("git diff-index HEAD --")) > 0:
+            die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
+
         [upstream, settings] = findUpstreamBranchPoint()
         if len(upstream) == 0:
             die("Cannot find upstream branchpoint for rebase")
@@ -1658,19 +1608,29 @@ class P4Clone(P4Sync):
         P4Sync.__init__(self)
         self.description = "Creates a new git repository and imports from Perforce into it"
         self.usage = "usage: %prog [options] //depot/path[@revRange]"
-        self.options.append(
+        self.options += [
             optparse.make_option("--destination", dest="cloneDestination",
                                  action='store', default=None,
-                                 help="where to leave result of the clone"))
+                                 help="where to leave result of the clone"),
+            optparse.make_option("-/", dest="cloneExclude",
+                                 action="append", type="string",
+                                 help="exclude depot path")
+        ]
         self.cloneDestination = None
         self.needsGit = False
 
+    # This is required for the "append" cloneExclude action
+    def ensure_value(self, attr, value):
+        if not hasattr(self, attr) or getattr(self, attr) is None:
+            setattr(self, attr, value)
+        return getattr(self, attr)
+
     def defaultDestination(self, args):
         ## TODO: use common prefix of args?
         depotPath = args[0]
         depotDir = re.sub("(@[^@]*)$", "", depotPath)
         depotDir = re.sub("(#[^#]*)$", "", depotDir)
-        depotDir = re.sub(r"\.\.\.$,", "", depotDir)
+        depotDir = re.sub(r"\.\.\.$", "", depotDir)
         depotDir = re.sub(r"/$", "", depotDir)
         return os.path.split(depotDir)[1]
 
@@ -1688,6 +1648,7 @@ class P4Clone(P4Sync):
             self.cloneDestination = depotPaths[-1]
             depotPaths = depotPaths[:-1]
 
+        self.cloneExclude = ["/"+p for p in self.cloneExclude]
         for p in depotPaths:
             if not p.startswith("//"):
                 return False
index 77c88ebf1f1029083614c2ff63011bd1b2d269fb..62a740c48248d94e1e0577c8676b13b7a726f21c 100644 (file)
@@ -567,7 +567,7 @@ generate_general_email()
        echo ""
        if [ "$newrev_type" = "commit" ]; then
                echo $LOGBEGIN
-               git show --no-color --root -s $newrev
+               git show --no-color --root -s --pretty=medium $newrev
                echo $LOGEND
        else
                # What can we do here?  The tag marks an object that is not
index aab501ea08129cc3b8304fb8f76f206578273f85..f4a7b62cd9f1a397118b95792c04c2f70f910f9e 100755 (executable)
@@ -93,7 +93,7 @@ my %depths;
 my @depths;
 
 while (<STDIN>) {
-    my ($sha1, $type, $size, $offset, $depth, $parent) = split(/\s+/, $_);
+    my ($sha1, $type, $size, $space, $offset, $depth, $parent) = split(/\s+/, $_);
     next unless ($sha1 =~ /^[0-9a-f]{40}$/);
     $depths{$sha1} = $depth || 0;
     push(@depths, $depth || 0);
index 552707e8e65997ebfc2120887783c4fc5698e19f..d8c94cb3edeceff7a79b2f5e6ba7cad250e6b301 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -85,8 +85,39 @@ static int is_binary(unsigned long size, struct text_stat *stats)
        return 0;
 }
 
+static void check_safe_crlf(const char *path, int action,
+                            struct text_stat *stats, enum safe_crlf checksafe)
+{
+       if (!checksafe)
+               return;
+
+       if (action == CRLF_INPUT || auto_crlf <= 0) {
+               /*
+                * CRLFs would not be restored by checkout:
+                * check if we'd remove CRLFs
+                */
+               if (stats->crlf) {
+                       if (checksafe == SAFE_CRLF_WARN)
+                               warning("CRLF will be replaced by LF in %s.", path);
+                       else /* i.e. SAFE_CRLF_FAIL */
+                               die("CRLF would be replaced by LF in %s.", path);
+               }
+       } else if (auto_crlf > 0) {
+               /*
+                * CRLFs would be added by checkout:
+                * check if we have "naked" LFs
+                */
+               if (stats->lf != stats->crlf) {
+                       if (checksafe == SAFE_CRLF_WARN)
+                               warning("LF will be replaced by CRLF in %s", path);
+                       else /* i.e. SAFE_CRLF_FAIL */
+                               die("LF would be replaced by CRLF in %s", path);
+               }
+       }
+}
+
 static int crlf_to_git(const char *path, const char *src, size_t len,
-                       struct strbuf *buf, int action)
+                       struct strbuf *buf, int action, enum safe_crlf checksafe)
 {
        struct text_stat stats;
        char *dst;
@@ -95,9 +126,6 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
                return 0;
 
        gather_stats(src, len, &stats);
-       /* No CR? Nothing to convert, regardless. */
-       if (!stats.cr)
-               return 0;
 
        if (action == CRLF_GUESS) {
                /*
@@ -115,6 +143,12 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
                        return 0;
        }
 
+       check_safe_crlf(path, action, &stats, checksafe);
+
+       /* Optimization: No CR? Nothing to convert, regardless. */
+       if (!stats.cr)
+               return 0;
+
        /* only grow if not in place */
        if (strbuf_avail(buf) + buf->len < len)
                strbuf_grow(buf, len - buf->len);
@@ -536,7 +570,8 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check)
        return !!ATTR_TRUE(value);
 }
 
-int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst)
+int convert_to_git(const char *path, const char *src, size_t len,
+                   struct strbuf *dst, enum safe_crlf checksafe)
 {
        struct git_attr_check check[3];
        int crlf = CRLF_GUESS;
@@ -558,7 +593,7 @@ int convert_to_git(const char *path, const char *src, size_t len, struct strbuf
                src = dst->buf;
                len = dst->len;
        }
-       ret |= crlf_to_git(path, src, len, dst, crlf);
+       ret |= crlf_to_git(path, src, len, dst, crlf, checksafe);
        if (ret) {
                src = dst->buf;
                len = dst->len;
index d85d8f34ba055ad7e02bf5d61ae4a675ce0be1d3..4581b594d0a65d5bbe782b1db5d69f33189a0e50 100644 (file)
@@ -9,6 +9,7 @@
 #include "revision.h"
 #include "cache-tree.h"
 #include "path-list.h"
+#include "unpack-trees.h"
 
 /*
  * diff-files
@@ -37,7 +38,7 @@ static int get_mode(const char *path, int *mode)
        if (!path || !strcmp(path, "/dev/null"))
                *mode = 0;
        else if (!strcmp(path, "-"))
-               *mode = ntohl(create_ce_mode(0666));
+               *mode = create_ce_mode(0666);
        else if (stat(path, &st))
                return error("Could not access '%s'", path);
        else
@@ -384,7 +385,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                                        continue;
                        }
                        else
-                               dpath->mode = ntohl(ce_mode_from_stat(ce, st.st_mode));
+                               dpath->mode = ce_mode_from_stat(ce, st.st_mode);
 
                        while (i < entries) {
                                struct cache_entry *nce = active_cache[i];
@@ -398,10 +399,10 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                                 */
                                stage = ce_stage(nce);
                                if (2 <= stage) {
-                                       int mode = ntohl(nce->ce_mode);
+                                       int mode = nce->ce_mode;
                                        num_compare_stages++;
                                        hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
-                                       dpath->parent[stage-2].mode = ntohl(ce_mode_from_stat(nce, mode));
+                                       dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);
                                        dpath->parent[stage-2].status =
                                                DIFF_STATUS_MODIFIED;
                                }
@@ -435,6 +436,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                                continue;
                }
 
+               if (ce_uptodate(ce))
+                       continue;
                if (lstat(ce->name, &st) < 0) {
                        if (errno != ENOENT && errno != ENOTDIR) {
                                perror(ce->name);
@@ -442,15 +445,15 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        }
                        if (silent_on_removed)
                                continue;
-                       diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
+                       diff_addremove(&revs->diffopt, '-', ce->ce_mode,
                                       ce->sha1, ce->name, NULL);
                        continue;
                }
                changed = ce_match_stat(ce, &st, ce_option);
                if (!changed && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
                        continue;
-               oldmode = ntohl(ce->ce_mode);
-               newmode = ntohl(ce_mode_from_stat(ce, st.st_mode));
+               oldmode = ce->ce_mode;
+               newmode = ce_mode_from_stat(ce, st.st_mode);
                diff_change(&revs->diffopt, oldmode, newmode,
                            ce->sha1, (changed ? null_sha1 : ce->sha1),
                            ce->name, NULL);
@@ -469,22 +472,21 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 static void diff_index_show_file(struct rev_info *revs,
                                 const char *prefix,
                                 struct cache_entry *ce,
-                                unsigned char *sha1, unsigned int mode)
+                                const unsigned char *sha1, unsigned int mode)
 {
-       diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
+       diff_addremove(&revs->diffopt, prefix[0], mode,
                       sha1, ce->name, NULL);
 }
 
 static int get_stat_data(struct cache_entry *ce,
-                        unsigned char **sha1p,
+                        const unsigned char **sha1p,
                         unsigned int *modep,
                         int cached, int match_missing)
 {
-       unsigned char *sha1 = ce->sha1;
+       const unsigned char *sha1 = ce->sha1;
        unsigned int mode = ce->ce_mode;
 
        if (!cached) {
-               static unsigned char no_sha1[20];
                int changed;
                struct stat st;
                if (lstat(ce->name, &st) < 0) {
@@ -498,7 +500,7 @@ static int get_stat_data(struct cache_entry *ce,
                changed = ce_match_stat(ce, &st, 0);
                if (changed) {
                        mode = ce_mode_from_stat(ce, st.st_mode);
-                       sha1 = no_sha1;
+                       sha1 = null_sha1;
                }
        }
 
@@ -511,7 +513,7 @@ static void show_new_file(struct rev_info *revs,
                          struct cache_entry *new,
                          int cached, int match_missing)
 {
-       unsigned char *sha1;
+       const unsigned char *sha1;
        unsigned int mode;
 
        /* New file in the index: it might actually be different in
@@ -530,7 +532,7 @@ static int show_modified(struct rev_info *revs,
                         int cached, int match_missing)
 {
        unsigned int mode, oldmode;
-       unsigned char *sha1;
+       const unsigned char *sha1;
 
        if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
                if (report_missing)
@@ -550,14 +552,14 @@ static int show_modified(struct rev_info *revs,
                p->len = pathlen;
                memcpy(p->path, new->name, pathlen);
                p->path[pathlen] = 0;
-               p->mode = ntohl(mode);
+               p->mode = mode;
                hashclr(p->sha1);
                memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent));
                p->parent[0].status = DIFF_STATUS_MODIFIED;
-               p->parent[0].mode = ntohl(new->ce_mode);
+               p->parent[0].mode = new->ce_mode;
                hashcpy(p->parent[0].sha1, new->sha1);
                p->parent[1].status = DIFF_STATUS_MODIFIED;
-               p->parent[1].mode = ntohl(old->ce_mode);
+               p->parent[1].mode = old->ce_mode;
                hashcpy(p->parent[1].sha1, old->sha1);
                show_combined_diff(p, 2, revs->dense_combined_merges, revs);
                free(p);
@@ -569,119 +571,154 @@ static int show_modified(struct rev_info *revs,
            !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
                return 0;
 
-       mode = ntohl(mode);
-       oldmode = ntohl(oldmode);
-
        diff_change(&revs->diffopt, oldmode, mode,
                    old->sha1, sha1, old->name, NULL);
        return 0;
 }
 
-static int diff_cache(struct rev_info *revs,
-                     struct cache_entry **ac, int entries,
-                     const char **pathspec,
-                     int cached, int match_missing)
+/*
+ * This turns all merge entries into "stage 3". That guarantees that
+ * when we read in the new tree (into "stage 1"), we won't lose sight
+ * of the fact that we had unmerged entries.
+ */
+static void mark_merge_entries(void)
 {
-       while (entries) {
-               struct cache_entry *ce = *ac;
-               int same = (entries > 1) && ce_same_name(ce, ac[1]);
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= CE_STAGEMASK;
+       }
+}
 
-               if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
-                       DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
-                       break;
+/*
+ * This gets a mix of an existing index and a tree, one pathname entry
+ * at a time. The index entry may be a single stage-0 one, but it could
+ * also be multiple unmerged entries (in which case idx_pos/idx_nr will
+ * give you the position and number of entries in the index).
+ */
+static void do_oneway_diff(struct unpack_trees_options *o,
+       struct cache_entry *idx,
+       struct cache_entry *tree,
+       int idx_pos, int idx_nr)
+{
+       struct rev_info *revs = o->unpack_data;
+       int match_missing, cached;
 
-               if (!ce_path_match(ce, pathspec))
-                       goto skip_entry;
+       /*
+        * Backward compatibility wart - "diff-index -m" does
+        * not mean "do not ignore merges", but "match_missing".
+        *
+        * But with the revision flag parsing, that's found in
+        * "!revs->ignore_merges".
+        */
+       cached = o->index_only;
+       match_missing = !revs->ignore_merges;
+
+       if (cached && idx && ce_stage(idx)) {
+               if (tree)
+                       diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1);
+               return;
+       }
+
+       /*
+        * Something added to the tree?
+        */
+       if (!tree) {
+               show_new_file(revs, idx, cached, match_missing);
+               return;
+       }
 
-               switch (ce_stage(ce)) {
-               case 0:
-                       /* No stage 1 entry? That means it's a new file */
-                       if (!same) {
-                               show_new_file(revs, ce, cached, match_missing);
+       /*
+        * Something removed from the tree?
+        */
+       if (!idx) {
+               diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode);
+               return;
+       }
+
+       /* Show difference between old and new */
+       show_modified(revs, tree, idx, 1, cached, match_missing);
+}
+
+/*
+ * Count how many index entries go with the first one
+ */
+static inline int count_skip(const struct cache_entry *src, int pos)
+{
+       int skip = 1;
+
+       /* We can only have multiple entries if the first one is not stage-0 */
+       if (ce_stage(src)) {
+               struct cache_entry **p = active_cache + pos;
+               int namelen = ce_namelen(src);
+
+               for (;;) {
+                       const struct cache_entry *ce;
+                       pos++;
+                       if (pos >= active_nr)
                                break;
-                       }
-                       /* Show difference between old and new */
-                       show_modified(revs, ac[1], ce, 1,
-                                     cached, match_missing);
-                       break;
-               case 1:
-                       /* No stage 3 (merge) entry?
-                        * That means it's been deleted.
-                        */
-                       if (!same) {
-                               diff_index_show_file(revs, "-", ce,
-                                                    ce->sha1, ce->ce_mode);
+                       ce = *++p;
+                       if (ce_namelen(ce) != namelen)
                                break;
-                       }
-                       /* We come here with ce pointing at stage 1
-                        * (original tree) and ac[1] pointing at stage
-                        * 3 (unmerged).  show-modified with
-                        * report-missing set to false does not say the
-                        * file is deleted but reports true if work
-                        * tree does not have it, in which case we
-                        * fall through to report the unmerged state.
-                        * Otherwise, we show the differences between
-                        * the original tree and the work tree.
-                        */
-                       if (!cached &&
-                           !show_modified(revs, ce, ac[1], 0,
-                                          cached, match_missing))
+                       if (memcmp(ce->name, src->name, namelen))
                                break;
-                       diff_unmerge(&revs->diffopt, ce->name,
-                                    ntohl(ce->ce_mode), ce->sha1);
-                       break;
-               case 3:
-                       diff_unmerge(&revs->diffopt, ce->name,
-                                    0, null_sha1);
-                       break;
-
-               default:
-                       die("impossible cache entry stage");
+                       skip++;
                }
-
-skip_entry:
-               /*
-                * Ignore all the different stages for this file,
-                * we've handled the relevant cases now.
-                */
-               do {
-                       ac++;
-                       entries--;
-               } while (entries && ce_same_name(ce, ac[0]));
        }
-       return 0;
+       return skip;
 }
 
 /*
- * This turns all merge entries into "stage 3". That guarantees that
- * when we read in the new tree (into "stage 1"), we won't lose sight
- * of the fact that we had unmerged entries.
+ * The unpack_trees() interface is designed for merging, so
+ * the different source entries are designed primarily for
+ * the source trees, with the old index being really mainly
+ * used for being replaced by the result.
+ *
+ * For diffing, the index is more important, and we only have a
+ * single tree.
+ *
+ * We're supposed to return how many index entries we want to skip.
+ *
+ * This wrapper makes it all more readable, and takes care of all
+ * the fairly complex unpack_trees() semantic requirements, including
+ * the skipping, the path matching, the type conflict cases etc.
  */
-static void mark_merge_entries(void)
+static int oneway_diff(struct cache_entry **src,
+       struct unpack_trees_options *o,
+       int index_pos)
 {
-       int i;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-               ce->ce_flags |= htons(CE_STAGEMASK);
-       }
+       int skip = 0;
+       struct cache_entry *idx = src[0];
+       struct cache_entry *tree = src[1];
+       struct rev_info *revs = o->unpack_data;
+
+       if (index_pos >= 0)
+               skip = count_skip(idx, index_pos);
+
+       /*
+        * Unpack-trees generates a DF/conflict entry if
+        * there was a directory in the index and a tree
+        * in the tree. From a diff standpoint, that's a
+        * delete of the tree and a create of the file.
+        */
+       if (tree == o->df_conflict_entry)
+               tree = NULL;
+
+       if (ce_path_match(idx ? idx : tree, revs->prune_data))
+               do_oneway_diff(o, idx, tree, index_pos, skip);
+
+       return skip;
 }
 
 int run_diff_index(struct rev_info *revs, int cached)
 {
-       int ret;
        struct object *ent;
        struct tree *tree;
        const char *tree_name;
-       int match_missing = 0;
-
-       /*
-        * Backward compatibility wart - "diff-index -m" does
-        * not mean "do not ignore merges", but totally different.
-        */
-       if (!revs->ignore_merges)
-               match_missing = 1;
+       struct unpack_trees_options opts;
+       struct tree_desc t;
 
        mark_merge_entries();
 
@@ -690,13 +727,21 @@ int run_diff_index(struct rev_info *revs, int cached)
        tree = parse_tree_indirect(ent->sha1);
        if (!tree)
                return error("bad tree object %s", tree_name);
-       if (read_tree(tree, 1, revs->prune_data))
-               return error("unable to read tree object %s", tree_name);
-       ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
-                        cached, match_missing);
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.index_only = cached;
+       opts.merge = 1;
+       opts.fn = oneway_diff;
+       opts.unpack_data = revs;
+
+       init_tree_desc(&t, tree->buffer, tree->size);
+       if (unpack_trees(1, &t, &opts))
+               exit(128);
+
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
-       return ret;
+       return 0;
 }
 
 int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
@@ -706,6 +751,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        int i;
        struct cache_entry **dst;
        struct cache_entry *last = NULL;
+       struct unpack_trees_options opts;
+       struct tree_desc t;
 
        /*
         * This is used by git-blame to run diff-cache internally;
@@ -722,8 +769,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
                        cache_tree_invalidate_path(active_cache_tree,
                                                   ce->name);
                        last = ce;
-                       ce->ce_mode = 0;
-                       ce->ce_flags &= ~htons(CE_STAGEMASK);
+                       ce->ce_flags |= CE_REMOVE;
                }
                *dst++ = ce;
        }
@@ -734,8 +780,16 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        tree = parse_tree_indirect(tree_sha1);
        if (!tree)
                die("bad tree object %s", sha1_to_hex(tree_sha1));
-       if (read_tree(tree, 1, opt->paths))
-               return error("unable to read tree %s", sha1_to_hex(tree_sha1));
-       return diff_cache(&revs, active_cache, active_nr, revs.prune_data,
-                         1, 0);
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.index_only = 1;
+       opts.merge = 1;
+       opts.fn = oneway_diff;
+       opts.unpack_data = &revs;
+
+       init_tree_desc(&t, tree->buffer, tree->size);
+       if (unpack_trees(1, &t, &opts))
+               exit(128);
+       return 0;
 }
diff --git a/diff.c b/diff.c
index 76ba5f4afc162d6cbc31bfaa6cb1875a3975833e..00e1590c6eec291c2a6b3f831b62f402768c7042 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -20,7 +20,7 @@
 
 static int diff_detect_rename_default;
 static int diff_rename_limit_default = 100;
-static int diff_use_color_default;
+int diff_use_color_default = -1;
 static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 
@@ -118,8 +118,7 @@ static int parse_funcname_pattern(const char *var, const char *ep, const char *v
                pp->next = funcname_pattern_list;
                funcname_pattern_list = pp;
        }
-       if (pp->pattern)
-               free(pp->pattern);
+       free(pp->pattern);
        pp->pattern = xstrdup(value);
        return 0;
 }
@@ -191,7 +190,7 @@ int git_diff_basic_config(const char *var, const char *value)
                }
        }
 
-       return git_default_config(var, value);
+       return git_color_default_config(var, value);
 }
 
 static char *quote_two(const char *one, const char *two)
@@ -492,10 +491,8 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
                                ecbdata->diff_words->plus.text.size)
                        diff_words_show(ecbdata->diff_words);
 
-               if (ecbdata->diff_words->minus.text.ptr)
-                       free (ecbdata->diff_words->minus.text.ptr);
-               if (ecbdata->diff_words->plus.text.ptr)
-                       free (ecbdata->diff_words->plus.text.ptr);
+               free (ecbdata->diff_words->minus.text.ptr);
+               free (ecbdata->diff_words->plus.text.ptr);
                free(ecbdata->diff_words);
                ecbdata->diff_words = NULL;
        }
@@ -982,6 +979,90 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
        }
 }
 
+struct diffstat_dir {
+       struct diffstat_file **files;
+       int nr, percent, cumulative;
+};
+
+static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen)
+{
+       unsigned long this_dir = 0;
+       unsigned int sources = 0;
+
+       while (dir->nr) {
+               struct diffstat_file *f = *dir->files;
+               int namelen = strlen(f->name);
+               unsigned long this;
+               char *slash;
+
+               if (namelen < baselen)
+                       break;
+               if (memcmp(f->name, base, baselen))
+                       break;
+               slash = strchr(f->name + baselen, '/');
+               if (slash) {
+                       int newbaselen = slash + 1 - f->name;
+                       this = gather_dirstat(dir, changed, f->name, newbaselen);
+                       sources++;
+               } else {
+                       if (f->is_unmerged || f->is_binary)
+                               this = 0;
+                       else
+                               this = f->added + f->deleted;
+                       dir->files++;
+                       dir->nr--;
+                       sources += 2;
+               }
+               this_dir += this;
+       }
+
+       /*
+        * We don't report dirstat's for
+        *  - the top level
+        *  - or cases where everything came from a single directory
+        *    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) {
+                               printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base);
+                               if (!dir->cumulative)
+                                       return 0;
+                       }
+               }
+       }
+       return this_dir;
+}
+
+static void show_dirstat(struct diffstat_t *data, struct diff_options *options)
+{
+       int i;
+       unsigned long changed;
+       struct diffstat_dir dir;
+
+       /* Calculate total changes */
+       changed = 0;
+       for (i = 0; i < data->nr; i++) {
+               if (data->files[i]->is_binary || data->files[i]->is_unmerged)
+                       continue;
+               changed += data->files[i]->added;
+               changed += data->files[i]->deleted;
+       }
+
+       /* This can happen even with many files, if everything was renames */
+       if (!changed)
+               return;
+
+       /* Show all directories with more than x% of the changes */
+       dir.files = data->files;
+       dir.nr = data->nr;
+       dir.percent = options->dirstat_percent;
+       dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE;
+       gather_dirstat(&dir, changed, "", 0);
+}
+
 static void free_diffstat_info(struct diffstat_t *diffstat)
 {
        int i;
@@ -1199,7 +1280,7 @@ static struct builtin_funcname_pattern {
                        "new\\|return\\|switch\\|throw\\|while\\)\n"
                        "^[     ]*\\(\\([       ]*"
                        "[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}"
-                       "[      ]*([^;]*$\\)" },
+                       "[      ]*([^;]*\\)$" },
        { "tex", "^\\(\\\\\\(sub\\)*section{.*\\)$" },
 };
 
@@ -1399,6 +1480,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
 }
 
 static void builtin_checkdiff(const char *name_a, const char *name_b,
+                             const char *attr_path,
                             struct diff_filespec *one,
                             struct diff_filespec *two, struct diff_options *o)
 {
@@ -1413,7 +1495,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
        data.filename = name_b ? name_b : name_a;
        data.lineno = 0;
        data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
-       data.ws_rule = whitespace_rule(data.filename);
+       data.ws_rule = whitespace_rule(attr_path);
 
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
@@ -1512,17 +1594,22 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
        if (pos < 0)
                return 0;
        ce = active_cache[pos];
-       if ((lstat(name, &st) < 0) ||
-           !S_ISREG(st.st_mode) || /* careful! */
-           ce_match_stat(ce, &st, 0) ||
-           hashcmp(sha1, ce->sha1))
+
+       /*
+        * This is not the sha1 we are looking for, or
+        * unreusable because it is not a regular file.
+        */
+       if (hashcmp(sha1, ce->sha1) || !S_ISREG(ce->ce_mode))
                return 0;
-       /* we return 1 only when we can stat, it is a regular file,
-        * stat information matches, and sha1 recorded in the cache
-        * matches.  I.e. we know the file in the work tree really is
-        * the same as the <name, sha1> pair.
+
+       /*
+        * If ce matches the file in the work tree, we can reuse it.
         */
-       return 1;
+       if (ce_uptodate(ce) ||
+           (!lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
+               return 1;
+
+       return 0;
 }
 
 static int populate_from_stdin(struct diff_filespec *s)
@@ -1626,7 +1713,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
                 * Convert from working tree format to canonical git format
                 */
                strbuf_init(&buf, 0);
-               if (convert_to_git(s->path, s->data, s->size, &buf)) {
+               if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) {
                        size_t size = 0;
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
@@ -1833,6 +1920,9 @@ static const char *external_diff_attr(const char *name)
 {
        struct git_attr_check attr_diff_check;
 
+       if (!name)
+               return NULL;
+
        setup_diff_attr_check(&attr_diff_check);
        if (!git_checkattr(name, 1, &attr_diff_check)) {
                const char *value = attr_diff_check.value;
@@ -1852,6 +1942,7 @@ static const char *external_diff_attr(const char *name)
 static void run_diff_cmd(const char *pgm,
                         const char *name,
                         const char *other,
+                        const char *attr_path,
                         struct diff_filespec *one,
                         struct diff_filespec *two,
                         const char *xfrm_msg,
@@ -1861,7 +1952,7 @@ static void run_diff_cmd(const char *pgm,
        if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
                pgm = NULL;
        else {
-               const char *cmd = external_diff_attr(name);
+               const char *cmd = external_diff_attr(attr_path);
                if (cmd)
                        pgm = cmd;
        }
@@ -1902,6 +1993,15 @@ static int similarity_index(struct diff_filepair *p)
        return p->score * 100 / MAX_SCORE;
 }
 
+static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
+{
+       /* Strip the prefix but do not molest /dev/null and absolute paths */
+       if (*namep && **namep != '/')
+               *namep += prefix_length;
+       if (*otherp && **otherp != '/')
+               *otherp += prefix_length;
+}
+
 static void run_diff(struct diff_filepair *p, struct diff_options *o)
 {
        const char *pgm = external_diff();
@@ -1911,16 +2011,21 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
        struct diff_filespec *two = p->two;
        const char *name;
        const char *other;
+       const char *attr_path;
        int complete_rewrite = 0;
 
+       name  = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       attr_path = name;
+       if (o->prefix_length)
+               strip_prefix(o->prefix_length, &name, &other);
 
        if (DIFF_PAIR_UNMERGED(p)) {
-               run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0);
+               run_diff_cmd(pgm, name, NULL, attr_path,
+                            NULL, NULL, NULL, o, 0);
                return;
        }
 
-       name  = p->one->path;
-       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
        diff_fill_sha1_info(one);
        diff_fill_sha1_info(two);
 
@@ -1983,15 +2088,17 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
                 * needs to be split into deletion and creation.
                 */
                struct diff_filespec *null = alloc_filespec(two->path);
-               run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0);
+               run_diff_cmd(NULL, name, other, attr_path,
+                            one, null, xfrm_msg, o, 0);
                free(null);
                null = alloc_filespec(one->path);
-               run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0);
+               run_diff_cmd(NULL, name, other, attr_path,
+                            null, two, xfrm_msg, o, 0);
                free(null);
        }
        else
-               run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o,
-                            complete_rewrite);
+               run_diff_cmd(pgm, name, other, attr_path,
+                            one, two, xfrm_msg, o, complete_rewrite);
 
        strbuf_release(&msg);
 }
@@ -2012,6 +2119,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
        name = p->one->path;
        other = (strcmp(name, p->two->path) ? p->two->path : NULL);
 
+       if (o->prefix_length)
+               strip_prefix(o->prefix_length, &name, &other);
+
        diff_fill_sha1_info(p->one);
        diff_fill_sha1_info(p->two);
 
@@ -2024,6 +2134,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 {
        const char *name;
        const char *other;
+       const char *attr_path;
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
@@ -2032,11 +2143,15 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 
        name = p->one->path;
        other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       attr_path = other ? other : name;
+
+       if (o->prefix_length)
+               strip_prefix(o->prefix_length, &name, &other);
 
        diff_fill_sha1_info(p->one);
        diff_fill_sha1_info(p->two);
 
-       builtin_checkdiff(name, other, p->one, p->two, o);
+       builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
 }
 
 void diff_setup(struct diff_options *options)
@@ -2045,12 +2160,13 @@ void diff_setup(struct diff_options *options)
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
+       options->dirstat_percent = 3;
        options->context = 3;
        options->msg_sep = "";
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
-       if (diff_use_color_default)
+       if (diff_use_color_default > 0)
                DIFF_OPT_SET(options, COLOR_DIFF);
        else
                DIFF_OPT_CLR(options, COLOR_DIFF);
@@ -2078,6 +2194,13 @@ int diff_setup_done(struct diff_options *options)
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
 
+       if (!DIFF_OPT_TST(options, RELATIVE_NAME))
+               options->prefix = NULL;
+       if (options->prefix)
+               options->prefix_length = strlen(options->prefix);
+       else
+               options->prefix_length = 0;
+
        if (options->output_format & (DIFF_FORMAT_NAME |
                                      DIFF_FORMAT_NAME_STATUS |
                                      DIFF_FORMAT_CHECKDIFF |
@@ -2086,6 +2209,7 @@ int diff_setup_done(struct diff_options *options)
                                            DIFF_FORMAT_NUMSTAT |
                                            DIFF_FORMAT_DIFFSTAT |
                                            DIFF_FORMAT_SHORTSTAT |
+                                           DIFF_FORMAT_DIRSTAT |
                                            DIFF_FORMAT_SUMMARY |
                                            DIFF_FORMAT_PATCH);
 
@@ -2097,6 +2221,7 @@ int diff_setup_done(struct diff_options *options)
                                      DIFF_FORMAT_NUMSTAT |
                                      DIFF_FORMAT_DIFFSTAT |
                                      DIFF_FORMAT_SHORTSTAT |
+                                     DIFF_FORMAT_DIRSTAT |
                                      DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
                DIFF_OPT_SET(options, RECURSIVE);
@@ -2207,6 +2332,10 @@ 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_CUMULATIVE;
        else if (!strcmp(arg, "--check"))
                options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
@@ -2266,6 +2395,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
        else if (!strcmp(arg, "--no-renames"))
                options->detect_rename = 0;
+       else if (!strcmp(arg, "--relative"))
+               DIFF_OPT_SET(options, RELATIVE_NAME);
+       else if (!prefixcmp(arg, "--relative=")) {
+               DIFF_OPT_SET(options, RELATIVE_NAME);
+               options->prefix = arg + 11;
+       }
 
        /* xdiff options */
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
@@ -2446,8 +2581,6 @@ const char *diff_unique_abbrev(const unsigned char *sha1, int len)
                return sha1_to_hex(sha1);
 
        abbrev = find_unique_abbrev(sha1, len);
-       if (!abbrev)
-               return sha1_to_hex(sha1);
        abblen = strlen(abbrev);
        if (abblen < 37) {
                static char hex[41];
@@ -2477,12 +2610,20 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
                printf("%c%c", p->status, inter_name_termination);
        }
 
-       if (p->status == DIFF_STATUS_COPIED || p->status == DIFF_STATUS_RENAMED) {
-               write_name_quoted(p->one->path, stdout, inter_name_termination);
-               write_name_quoted(p->two->path, stdout, line_termination);
+       if (p->status == DIFF_STATUS_COPIED ||
+           p->status == DIFF_STATUS_RENAMED) {
+               const char *name_a, *name_b;
+               name_a = p->one->path;
+               name_b = p->two->path;
+               strip_prefix(opt->prefix_length, &name_a, &name_b);
+               write_name_quoted(name_a, stdout, inter_name_termination);
+               write_name_quoted(name_b, stdout, line_termination);
        } else {
-               const char *path = p->one->mode ? p->one->path : p->two->path;
-               write_name_quoted(path, stdout, line_termination);
+               const char *name_a, *name_b;
+               name_a = p->one->mode ? p->one->path : p->two->path;
+               name_b = NULL;
+               strip_prefix(opt->prefix_length, &name_a, &name_b);
+               write_name_quoted(name_a, stdout, line_termination);
        }
 }
 
@@ -2679,8 +2820,13 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
                diff_flush_checkdiff(p, opt);
        else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS))
                diff_flush_raw(p, opt);
-       else if (fmt & DIFF_FORMAT_NAME)
-               write_name_quoted(p->two->path, stdout, opt->line_termination);
+       else if (fmt & DIFF_FORMAT_NAME) {
+               const char *name_a, *name_b;
+               name_a = p->two->path;
+               name_b = NULL;
+               strip_prefix(opt->prefix_length, &name_a, &name_b);
+               write_name_quoted(name_a, stdout, opt->line_termination);
+       }
 }
 
 static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
@@ -2925,7 +3071,7 @@ 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_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) {
                struct diffstat_t diffstat;
 
                memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2935,6 +3081,8 @@ void diff_flush(struct diff_options *options)
                        if (check_pair_status(p))
                                diff_flush_stat(p, options, &diffstat);
                }
+               if (output_format & DIFF_FORMAT_DIRSTAT)
+                       show_dirstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_NUMSTAT)
                        show_numstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -3039,11 +3187,8 @@ static void diffcore_apply_filter(const char *filter)
 static int diff_filespec_is_identical(struct diff_filespec *one,
                                      struct diff_filespec *two)
 {
-       if (S_ISGITLINK(one->mode)) {
-               diff_fill_sha1_info(one);
-               diff_fill_sha1_info(two);
-               return !hashcmp(one->sha1, two->sha1);
-       }
+       if (S_ISGITLINK(one->mode))
+               return 0;
        if (diff_populate_filespec(one, 0))
                return 0;
        if (diff_populate_filespec(two, 0))
@@ -3166,6 +3311,11 @@ void diff_addremove(struct diff_options *options,
 
        if (!path) path = "";
        sprintf(concatpath, "%s%s", base, path);
+
+       if (options->prefix &&
+           strncmp(concatpath, options->prefix, options->prefix_length))
+               return;
+
        one = alloc_filespec(concatpath);
        two = alloc_filespec(concatpath);
 
@@ -3195,6 +3345,11 @@ void diff_change(struct diff_options *options,
        }
        if (!path) path = "";
        sprintf(concatpath, "%s%s", base, path);
+
+       if (options->prefix &&
+           strncmp(concatpath, options->prefix, options->prefix_length))
+               return;
+
        one = alloc_filespec(concatpath);
        two = alloc_filespec(concatpath);
        fill_filespec(one, old_sha1, old_mode);
@@ -3209,6 +3364,11 @@ void diff_unmerge(struct diff_options *options,
                  unsigned mode, const unsigned char *sha1)
 {
        struct diff_filespec *one, *two;
+
+       if (options->prefix &&
+           strncmp(path, options->prefix, options->prefix_length))
+               return;
+
        one = alloc_filespec(path);
        two = alloc_filespec(path);
        fill_filespec(one, sha1, mode);
diff --git a/diff.h b/diff.h
index 073d5cbf1ba97acf527770d912870cda01195489..9a652e7f38e3c1aeb7c7eaf840dea7bcea285311 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -30,6 +30,8 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_FORMAT_SUMMARY    0x0008
 #define DIFF_FORMAT_PATCH      0x0010
 #define DIFF_FORMAT_SHORTSTAT  0x0020
+#define DIFF_FORMAT_DIRSTAT    0x0040
+#define DIFF_FORMAT_CUMULATIVE 0x0080
 
 /* These override all above */
 #define DIFF_FORMAT_NAME       0x0100
@@ -60,6 +62,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_EXIT_WITH_STATUS    (1 << 14)
 #define DIFF_OPT_REVERSE_DIFF        (1 << 15)
 #define DIFF_OPT_CHECK_FAILED        (1 << 16)
+#define DIFF_OPT_RELATIVE_NAME       (1 << 17)
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
 #define DIFF_OPT_CLR(opts, flag)    ((opts)->flags &= ~DIFF_OPT_##flag)
@@ -80,8 +83,11 @@ struct diff_options {
        int pickaxe_opts;
        int rename_score;
        int rename_limit;
+       int dirstat_percent;
        int setup;
        int abbrev;
+       const char *prefix;
+       int prefix_length;
        const char *msg_sep;
        const char *stat_sep;
        long xdl_opts;
@@ -174,6 +180,7 @@ extern void diff_unmerge(struct diff_options *,
 
 extern int git_diff_basic_config(const char *var, const char *value);
 extern int git_diff_ui_config(const char *var, const char *value);
+extern int diff_use_color_default;
 extern void diff_setup(struct diff_options *);
 extern int diff_opt_parse(struct diff_options *, const char **, int);
 extern int diff_setup_done(struct diff_options *);
index 3d377251bef8ea843b7a7fa41f98d611daecbcc1..31941bcbbf457fe02097f8c46d25f1f9fc7c4f9f 100644 (file)
@@ -468,10 +468,11 @@ void diffcore_rename(struct diff_options *options)
         */
        if (rename_limit <= 0 || rename_limit > 32767)
                rename_limit = 32767;
-       if (num_create > rename_limit && num_src > rename_limit)
-               goto cleanup;
-       if (num_create * num_src > rename_limit * rename_limit)
+       if ((num_create > rename_limit && num_src > rename_limit) ||
+           (num_create * num_src > rename_limit * rename_limit)) {
+               warning("too many files, skipping inexact rename detection");
                goto cleanup;
+       }
 
        mx = xmalloc(sizeof(*mx) * num_create * num_src);
        for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
diff --git a/dir.c b/dir.c
index 3e345c2fc508eb82e66f4ffe2c825670f0bddd99..edc458e020772a7ab704e9cf69786d3aa641bcd4 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -17,6 +17,7 @@ struct path_simplify {
 static int read_directory_recursive(struct dir_struct *dir,
        const char *path, const char *base, int baselen,
        int check_only, const struct path_simplify *simplify);
+static int get_dtype(struct dirent *de, const char *path);
 
 int common_prefix(const char **pathspec)
 {
@@ -126,18 +127,34 @@ static int no_wildcard(const char *string)
 void add_exclude(const char *string, const char *base,
                 int baselen, struct exclude_list *which)
 {
-       struct exclude *x = xmalloc(sizeof (*x));
+       struct exclude *x;
+       size_t len;
+       int to_exclude = 1;
+       int flags = 0;
 
-       x->to_exclude = 1;
        if (*string == '!') {
-               x->to_exclude = 0;
+               to_exclude = 0;
                string++;
        }
-       x->pattern = string;
+       len = strlen(string);
+       if (len && string[len - 1] == '/') {
+               char *s;
+               x = xmalloc(sizeof(*x) + len);
+               s = (char*)(x+1);
+               memcpy(s, string, len - 1);
+               s[len - 1] = '\0';
+               string = s;
+               x->pattern = s;
+               flags = EXC_FLAG_MUSTBEDIR;
+       } else {
+               x = xmalloc(sizeof(*x));
+               x->pattern = string;
+       }
+       x->to_exclude = to_exclude;
        x->patternlen = strlen(string);
        x->base = base;
        x->baselen = baselen;
-       x->flags = 0;
+       x->flags = flags;
        if (!strchr(string, '/'))
                x->flags |= EXC_FLAG_NODIR;
        if (no_wildcard(string))
@@ -261,7 +278,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
  * Return 1 for exclude, 0 for include and -1 for undecided.
  */
 static int excluded_1(const char *pathname,
-                     int pathlen, const char *basename,
+                     int pathlen, const char *basename, int *dtype,
                      struct exclude_list *el)
 {
        int i;
@@ -272,6 +289,13 @@ static int excluded_1(const char *pathname,
                        const char *exclude = x->pattern;
                        int to_exclude = x->to_exclude;
 
+                       if (x->flags & EXC_FLAG_MUSTBEDIR) {
+                               if (*dtype == DT_UNKNOWN)
+                                       *dtype = get_dtype(NULL, pathname);
+                               if (*dtype != DT_DIR)
+                                       continue;
+                       }
+
                        if (x->flags & EXC_FLAG_NODIR) {
                                /* match basename */
                                if (x->flags & EXC_FLAG_NOWILDCARD) {
@@ -314,7 +338,7 @@ static int excluded_1(const char *pathname,
        return -1; /* undecided */
 }
 
-int excluded(struct dir_struct *dir, const char *pathname)
+int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 {
        int pathlen = strlen(pathname);
        int st;
@@ -323,7 +347,8 @@ int excluded(struct dir_struct *dir, const char *pathname)
 
        prep_exclude(dir, pathname, basename-pathname);
        for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {
+               switch (excluded_1(pathname, pathlen, basename,
+                                  dtype_p, &dir->exclude_list[st])) {
                case 0:
                        return 0;
                case 1:
@@ -346,7 +371,7 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)
 
 struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
 {
-       if (cache_name_pos(pathname, len) >= 0)
+       if (cache_name_exists(pathname, len))
                return NULL;
 
        ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
@@ -391,7 +416,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
                        break;
                if (endchar == '/')
                        return index_directory;
-               if (!endchar && S_ISGITLINK(ntohl(ce->ce_mode)))
+               if (!endchar && S_ISGITLINK(ce->ce_mode))
                        return index_gitdir;
        }
        return index_nonexistent;
@@ -508,7 +533,7 @@ static int in_pathspec(const char *path, int len, const struct path_simplify *si
 
 static int get_dtype(struct dirent *de, const char *path)
 {
-       int dtype = DTYPE(de);
+       int dtype = de ? DTYPE(de) : DT_UNKNOWN;
        struct stat st;
 
        if (dtype != DT_UNKNOWN)
@@ -560,7 +585,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        if (simplify_away(fullname, baselen + len, simplify))
                                continue;
 
-                       exclude = excluded(dir, fullname);
+                       dtype = DTYPE(de);
+                       exclude = excluded(dir, fullname, &dtype);
                        if (exclude && dir->collect_ignored
                            && in_pathspec(fullname, baselen + len, simplify))
                                dir_add_ignored(dir, fullname, baselen + len);
@@ -572,7 +598,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        if (exclude && !dir->show_ignored)
                                continue;
 
-                       dtype = get_dtype(de, fullname);
+                       if (dtype == DT_UNKNOWN)
+                               dtype = get_dtype(de, fullname);
 
                        /*
                         * Do we want to see just the ignored files?
@@ -677,8 +704,7 @@ static struct path_simplify *create_simplify(const char **pathspec)
 
 static void free_simplify(struct path_simplify *simplify)
 {
-       if (simplify)
-               free(simplify);
+       free(simplify);
 }
 
 int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
diff --git a/dir.h b/dir.h
index d8814dccb2dd57af21d75c91a52026e09fdf1b95..2df15defb6720a742282f24721233c4816deceb6 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -9,6 +9,7 @@ struct dir_entry {
 #define EXC_FLAG_NODIR 1
 #define EXC_FLAG_NOWILDCARD 2
 #define EXC_FLAG_ENDSWITH 4
+#define EXC_FLAG_MUSTBEDIR 8
 
 struct exclude_list {
        int nr;
@@ -67,7 +68,7 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,
 
 extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);
 
-extern int excluded(struct dir_struct *, const char *);
+extern int excluded(struct dir_struct *, const char *, int *);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *which);
diff --git a/entry.c b/entry.c
index 257ab46e943f1f8b7445f01c10d949224c17112f..44f4b897d4ff7afff7dbab92ce8754645ad8da6a 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -103,7 +103,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        int fd;
        long wrote;
 
-       switch (ntohl(ce->ce_mode) & S_IFMT) {
+       switch (ce->ce_mode & S_IFMT) {
                char *new;
                struct strbuf buf;
                unsigned long size;
@@ -129,7 +129,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                        strcpy(path, ".merge_file_XXXXXX");
                        fd = mkstemp(path);
                } else
-                       fd = create_file(path, ntohl(ce->ce_mode));
+                       fd = create_file(path, ce->ce_mode);
                if (fd < 0) {
                        free(new);
                        return error("git-checkout-index: unable to create file %s (%s)",
@@ -221,7 +221,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
                unlink(path);
                if (S_ISDIR(st.st_mode)) {
                        /* If it is a gitlink, leave it alone! */
-                       if (S_ISGITLINK(ntohl(ce->ce_mode)))
+                       if (S_ISGITLINK(ce->ce_mode))
                                return 0;
                        if (!state->force)
                                return error("%s is a directory", path);
index fa3633372b8249899ff1e57a2f1431de029b211d..6739a3f41745fe1c01dc767e09af8746d6f2815f 100644 (file)
@@ -35,7 +35,9 @@ int pager_use_color = 1;
 const char *editor_program;
 const char *excludes_file;
 int auto_crlf = 0;     /* 1: both ways, -1: only when adding git objects */
+enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
+enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
 
 /* This is set by setup_git_dir_gently() and/or git_default_config() */
 char *git_work_tree_cfg;
index 32ec159915611f6a21c218969bf82a09b6720c48..655913ddb27f758ce494fc18551e852c9a6aa9f1 100644 (file)
@@ -372,6 +372,8 @@ static void write_branch_report(FILE *rpt, struct branch *b)
        fputc('\n', rpt);
 }
 
+static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
+
 static void write_crash_report(const char *err)
 {
        char *loc = git_path("fast_import_crash_%d", getpid());
@@ -430,12 +432,37 @@ static void write_crash_report(const char *err)
                        write_branch_report(rpt, b);
        }
 
+       if (first_tag) {
+               struct tag *tg;
+               fputc('\n', rpt);
+               fputs("Annotated Tags\n", rpt);
+               fputs("--------------\n", rpt);
+               for (tg = first_tag; tg; tg = tg->next_tag) {
+                       fputs(sha1_to_hex(tg->sha1), rpt);
+                       fputc(' ', rpt);
+                       fputs(tg->name, rpt);
+                       fputc('\n', rpt);
+               }
+       }
+
+       fputc('\n', rpt);
+       fputs("Marks\n", rpt);
+       fputs("-----\n", rpt);
+       if (mark_file)
+               fprintf(rpt, "  exported to %s\n", mark_file);
+       else
+               dump_marks_helper(rpt, 0, marks);
+
        fputc('\n', rpt);
        fputs("-------------------\n", rpt);
        fputs("END OF CRASH REPORT\n", rpt);
        fclose(rpt);
 }
 
+static void end_packfile(void);
+static void unkeep_all_packs(void);
+static void dump_marks(void);
+
 static NORETURN void die_nicely(const char *err, va_list params)
 {
        static int zombie;
@@ -449,6 +476,9 @@ static NORETURN void die_nicely(const char *err, va_list params)
        if (!zombie) {
                zombie = 1;
                write_crash_report(message);
+               end_packfile();
+               unkeep_all_packs();
+               dump_marks();
        }
        exit(128);
 }
@@ -2348,6 +2378,7 @@ int main(int argc, const char **argv)
 {
        unsigned int i, show_stats = 1;
 
+       setup_git_directory();
        git_config(git_pack_config);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
index a7888ea302cde44b072cc019394ae43dbb4cf95d..8bd9c32561e79d194d27fa10cc98a26aa2cb673c 100644 (file)
@@ -12,10 +12,13 @@ struct fetch_pack_args
                use_thin_pack:1,
                fetch_all:1,
                verbose:1,
-               no_progress:1;
+               no_progress:1,
+               include_tag:1;
 };
 
 struct ref *fetch_pack(struct fetch_pack_args *args,
+               int fd[], struct child_process *conn,
+               const struct ref *ref,
                const char *dest,
                int nr_heads,
                char **heads,
diff --git a/fsck.c b/fsck.c
new file mode 100644 (file)
index 0000000..797e317
--- /dev/null
+++ b/fsck.c
@@ -0,0 +1,333 @@
+#include "cache.h"
+#include "object.h"
+#include "blob.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "commit.h"
+#include "tag.h"
+#include "fsck.h"
+
+static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+       int res = 0;
+
+       if (parse_tree(tree))
+               return -1;
+
+       init_tree_desc(&desc, tree->buffer, tree->size);
+       while (tree_entry(&desc, &entry)) {
+               int result;
+
+               if (S_ISGITLINK(entry.mode))
+                       continue;
+               if (S_ISDIR(entry.mode))
+                       result = walk(&lookup_tree(entry.sha1)->object, OBJ_TREE, data);
+               else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode))
+                       result = walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data);
+               else {
+                       result = error("in tree %s: entry %s has bad mode %.6o\n",
+                                       sha1_to_hex(tree->object.sha1), entry.path, entry.mode);
+               }
+               if (result < 0)
+                       return result;
+               if (!res)
+                       res = result;
+       }
+       return res;
+}
+
+static int fsck_walk_commit(struct commit *commit, fsck_walk_func walk, void *data)
+{
+       struct commit_list *parents;
+       int res;
+       int result;
+
+       if (parse_commit(commit))
+               return -1;
+
+       result = walk((struct object *)commit->tree, OBJ_TREE, data);
+       if (result < 0)
+               return result;
+       res = result;
+
+       parents = commit->parents;
+       while (parents) {
+               result = walk((struct object *)parents->item, OBJ_COMMIT, data);
+               if (result < 0)
+                       return result;
+               if (!res)
+                       res = result;
+               parents = parents->next;
+       }
+       return res;
+}
+
+static int fsck_walk_tag(struct tag *tag, fsck_walk_func walk, void *data)
+{
+       if (parse_tag(tag))
+               return -1;
+       return walk(tag->tagged, OBJ_ANY, data);
+}
+
+int fsck_walk(struct object *obj, fsck_walk_func walk, void *data)
+{
+       if (!obj)
+               return -1;
+       switch (obj->type) {
+       case OBJ_BLOB:
+               return 0;
+       case OBJ_TREE:
+               return fsck_walk_tree((struct tree *)obj, walk, data);
+       case OBJ_COMMIT:
+               return fsck_walk_commit((struct commit *)obj, walk, data);
+       case OBJ_TAG:
+               return fsck_walk_tag((struct tag *)obj, walk, data);
+       default:
+               error("Unknown object type for %s", sha1_to_hex(obj->sha1));
+               return -1;
+       }
+}
+
+/*
+ * The entries in a tree are ordered in the _path_ order,
+ * which means that a directory entry is ordered by adding
+ * a slash to the end of it.
+ *
+ * So a directory called "a" is ordered _after_ a file
+ * called "a.c", because "a/" sorts after "a.c".
+ */
+#define TREE_UNORDERED (-1)
+#define TREE_HAS_DUPS  (-2)
+
+static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, const char *name2)
+{
+       int len1 = strlen(name1);
+       int len2 = strlen(name2);
+       int len = len1 < len2 ? len1 : len2;
+       unsigned char c1, c2;
+       int cmp;
+
+       cmp = memcmp(name1, name2, len);
+       if (cmp < 0)
+               return 0;
+       if (cmp > 0)
+               return TREE_UNORDERED;
+
+       /*
+        * Ok, the first <len> characters are the same.
+        * Now we need to order the next one, but turn
+        * a '\0' into a '/' for a directory entry.
+        */
+       c1 = name1[len];
+       c2 = name2[len];
+       if (!c1 && !c2)
+               /*
+                * git-write-tree used to write out a nonsense tree that has
+                * entries with the same name, one blob and one tree.  Make
+                * sure we do not have duplicate entries.
+                */
+               return TREE_HAS_DUPS;
+       if (!c1 && S_ISDIR(mode1))
+               c1 = '/';
+       if (!c2 && S_ISDIR(mode2))
+               c2 = '/';
+       return c1 < c2 ? 0 : TREE_UNORDERED;
+}
+
+static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
+{
+       int retval;
+       int has_full_path = 0;
+       int has_empty_name = 0;
+       int has_zero_pad = 0;
+       int has_bad_modes = 0;
+       int has_dup_entries = 0;
+       int not_properly_sorted = 0;
+       struct tree_desc desc;
+       unsigned o_mode;
+       const char *o_name;
+       const unsigned char *o_sha1;
+
+       init_tree_desc(&desc, item->buffer, item->size);
+
+       o_mode = 0;
+       o_name = NULL;
+       o_sha1 = NULL;
+
+       while (desc.size) {
+               unsigned mode;
+               const char *name;
+               const unsigned char *sha1;
+
+               sha1 = tree_entry_extract(&desc, &name, &mode);
+
+               if (strchr(name, '/'))
+                       has_full_path = 1;
+               if (!*name)
+                       has_empty_name = 1;
+               has_zero_pad |= *(char *)desc.buffer == '0';
+               update_tree_entry(&desc);
+
+               switch (mode) {
+               /*
+                * Standard modes..
+                */
+               case S_IFREG | 0755:
+               case S_IFREG | 0644:
+               case S_IFLNK:
+               case S_IFDIR:
+               case S_IFGITLINK:
+                       break;
+               /*
+                * This is nonstandard, but we had a few of these
+                * early on when we honored the full set of mode
+                * bits..
+                */
+               case S_IFREG | 0664:
+                       if (!strict)
+                               break;
+               default:
+                       has_bad_modes = 1;
+               }
+
+               if (o_name) {
+                       switch (verify_ordered(o_mode, o_name, mode, name)) {
+                       case TREE_UNORDERED:
+                               not_properly_sorted = 1;
+                               break;
+                       case TREE_HAS_DUPS:
+                               has_dup_entries = 1;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+
+               o_mode = mode;
+               o_name = name;
+               o_sha1 = sha1;
+       }
+
+       retval = 0;
+       if (has_full_path)
+               retval += error_func(&item->object, FSCK_WARN, "contains full pathnames");
+       if (has_empty_name)
+               retval += error_func(&item->object, FSCK_WARN, "contains empty pathname");
+       if (has_zero_pad)
+               retval += error_func(&item->object, FSCK_WARN, "contains zero-padded file modes");
+       if (has_bad_modes)
+               retval += error_func(&item->object, FSCK_WARN, "contains bad file modes");
+       if (has_dup_entries)
+               retval += error_func(&item->object, FSCK_ERROR, "contains duplicate file entries");
+       if (not_properly_sorted)
+               retval += error_func(&item->object, FSCK_ERROR, "not properly sorted");
+       return retval;
+}
+
+static int fsck_commit(struct commit *commit, fsck_error error_func)
+{
+       char *buffer = commit->buffer;
+       unsigned char tree_sha1[20], sha1[20];
+       struct commit_graft *graft;
+       int parents = 0;
+
+       if (!commit->date)
+               return error_func(&commit->object, FSCK_ERROR, "invalid author/committer line");
+
+       if (memcmp(buffer, "tree ", 5))
+               return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line");
+       if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
+               return error_func(&commit->object, FSCK_ERROR, "invalid 'tree' line format - bad sha1");
+       buffer += 46;
+       while (!memcmp(buffer, "parent ", 7)) {
+               if (get_sha1_hex(buffer+7, sha1) || buffer[47] != '\n')
+                       return error_func(&commit->object, FSCK_ERROR, "invalid 'parent' line format - bad sha1");
+               buffer += 48;
+               parents++;
+       }
+       graft = lookup_commit_graft(commit->object.sha1);
+       if (graft) {
+               struct commit_list *p = commit->parents;
+               parents = 0;
+               while (p) {
+                       p = p->next;
+                       parents++;
+               }
+               if (graft->nr_parent == -1 && !parents)
+                       ; /* shallow commit */
+               else if (graft->nr_parent != parents)
+                       return error_func(&commit->object, FSCK_ERROR, "graft objects missing");
+       } else {
+               struct commit_list *p = commit->parents;
+               while (p && parents) {
+                       p = p->next;
+                       parents--;
+               }
+               if (p || parents)
+                       return error_func(&commit->object, FSCK_ERROR, "parent objects missing");
+       }
+       if (memcmp(buffer, "author ", 7))
+               return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'author' line");
+       if (!commit->tree)
+               return error_func(&commit->object, FSCK_ERROR, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
+
+       return 0;
+}
+
+static int fsck_tag(struct tag *tag, fsck_error error_func)
+{
+       struct object *tagged = tag->tagged;
+
+       if (!tagged)
+               return error_func(&tag->object, FSCK_ERROR, "could not load tagged object");
+       return 0;
+}
+
+int fsck_object(struct object *obj, int strict, fsck_error error_func)
+{
+       if (!obj)
+               return error_func(obj, FSCK_ERROR, "no valid object to fsck");
+
+       if (obj->type == OBJ_BLOB)
+               return 0;
+       if (obj->type == OBJ_TREE)
+               return fsck_tree((struct tree *) obj, strict, error_func);
+       if (obj->type == OBJ_COMMIT)
+               return fsck_commit((struct commit *) obj, error_func);
+       if (obj->type == OBJ_TAG)
+               return fsck_tag((struct tag *) obj, error_func);
+
+       return error_func(obj, FSCK_ERROR, "unknown type '%d' (internal fsck error)",
+                         obj->type);
+}
+
+int fsck_error_function(struct object *obj, int type, const char *fmt, ...)
+{
+       va_list ap;
+       int len;
+       struct strbuf sb;
+
+       strbuf_init(&sb, 0);
+       strbuf_addf(&sb, "object %s:", obj->sha1?sha1_to_hex(obj->sha1):"(null)");
+
+       va_start(ap, fmt);
+       len = vsnprintf(sb.buf + sb.len, strbuf_avail(&sb), fmt, ap);
+       va_end(ap);
+
+       if (len < 0)
+               len = 0;
+       if (len >= strbuf_avail(&sb)) {
+               strbuf_grow(&sb, len + 2);
+               va_start(ap, fmt);
+               len = vsnprintf(sb.buf + sb.len, strbuf_avail(&sb), fmt, ap);
+               va_end(ap);
+               if (len >= strbuf_avail(&sb))
+                       die("this should not happen, your snprintf is broken");
+       }
+
+       error(sb.buf);
+       strbuf_release(&sb);
+       return 1;
+}
diff --git a/fsck.h b/fsck.h
new file mode 100644 (file)
index 0000000..990ee02
--- /dev/null
+++ b/fsck.h
@@ -0,0 +1,32 @@
+#ifndef GIT_FSCK_H
+#define GIT_FSCK_H
+
+#define FSCK_ERROR 1
+#define FSCK_WARN 2
+
+/*
+ * callback function for fsck_walk
+ * type is the expected type of the object or OBJ_ANY
+ * the return value is:
+ *     0       everything OK
+ *     <0      error signaled and abort
+ *     >0      error signaled and do not abort
+ */
+typedef int (*fsck_walk_func)(struct object *obj, int type, void *data);
+
+/* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */
+typedef int (*fsck_error)(struct object *obj, int type, const char *err, ...);
+
+int fsck_error_function(struct object *obj, int type, const char *fmt, ...);
+
+/* descend in all linked child objects
+ * the return value is:
+ *    -1       error in processing the object
+ *    <0       return value of the callback, which lead to an abort
+ *    >0       return value of the first sigaled error >0 (in the case of no other errors)
+ *    0                everything OK
+ */
+int fsck_walk(struct object *obj, fsck_walk_func walk, void *data);
+int fsck_object(struct object *obj, int strict, fsck_error error_func);
+
+#endif
index 17ca5b84f0c077d61b9392bd7900954ce35d6cbf..a0a81f134a6288dfc1d87431698f29597ed5e488 100755 (executable)
@@ -82,6 +82,19 @@ sub list_untracked {
 my $status_fmt = '%12s %12s %s';
 my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
 
+{
+       my $initial;
+       sub is_initial_commit {
+               $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
+                       unless defined $initial;
+               return $initial;
+       }
+}
+
+sub get_empty_tree {
+       return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
+}
+
 # Returns list of hashes, contents of each of which are:
 # VALUE:       pathname
 # BINARY:      is a binary path
@@ -103,8 +116,10 @@ sub list_modified {
                return if (!@tracked);
        }
 
+       my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
        for (run_cmd_pipe(qw(git diff-index --cached
-                            --numstat --summary HEAD --), @tracked)) {
+                            --numstat --summary), $reference,
+                            '--', @tracked)) {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
                        my ($change, $bin);
@@ -476,21 +491,27 @@ sub revert_cmd {
                                       HEADER => $status_head, },
                                     list_modified());
        if (@update) {
-               my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
-                                        map { $_->{VALUE} } @update);
-               my $fh;
-               open $fh, '| git update-index --index-info'
-                   or die;
-               for (@lines) {
-                       print $fh $_;
+               if (is_initial_commit()) {
+                       system(qw(git rm --cached),
+                               map { $_->{VALUE} } @update);
                }
-               close($fh);
-               for (@update) {
-                       if ($_->{INDEX_ADDDEL} &&
-                           $_->{INDEX_ADDDEL} eq 'create') {
-                               system(qw(git update-index --force-remove --),
-                                      $_->{VALUE});
-                               print "note: $_->{VALUE} is untracked now.\n";
+               else {
+                       my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
+                                                map { $_->{VALUE} } @update);
+                       my $fh;
+                       open $fh, '| git update-index --index-info'
+                           or die;
+                       for (@lines) {
+                               print $fh $_;
+                       }
+                       close($fh);
+                       for (@update) {
+                               if ($_->{INDEX_ADDDEL} &&
+                                   $_->{INDEX_ADDDEL} eq 'create') {
+                                       system(qw(git update-index --force-remove --),
+                                              $_->{VALUE});
+                                       print "note: $_->{VALUE} is untracked now.\n";
+                               }
                        }
                }
                refresh();
@@ -956,7 +977,9 @@ sub diff_cmd {
                                     HEADER => $status_head, },
                                   @mods);
        return if (!@them);
-       system(qw(git diff -p --cached HEAD --), map { $_->{VALUE} } @them);
+       my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
+       system(qw(git diff -p --cached), $reference, '--',
+               map { $_->{VALUE} } @them);
 }
 
 sub quit_cmd {
index 2ecebc45a93784f1ca377941cd9b96569b3ade1c..1f6b5e02c5e7da45a8e7966b600946cff9c24832 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -2,13 +2,14 @@
 #
 # Copyright (c) 2005, 2006 Junio C Hamano
 
+SUBDIRECTORY_OK=Yes
 OPTIONS_KEEPDASHDASH=
 OPTIONS_SPEC="\
 git-am [options] <mbox>|<Maildir>...
 git-am [options] --resolved
 git-am [options] --skip
 --
-d,dotest=       use <dir> and not .dotest
+d,dotest=       (removed -- do not use)
 i,interactive   run interactively
 b,binary        pass --allo-binary-replacement to git-apply
 3,3way          allow fall back on 3way merging if needed
@@ -20,11 +21,14 @@ C=              pass it through git-apply
 p=              pass it through git-apply
 resolvemsg=     override error message when patch failure occurs
 r,resolved      to be used after a patch failure
-skip            skip the current patch"
+skip            skip the current patch
+rebasing        (internal use for git-rebase)"
 
 . git-sh-setup
+prefix=$(git rev-parse --show-prefix)
 set_reflog_action am
 require_work_tree
+cd_to_toplevel
 
 git var GIT_COMMITTER_IDENT >/dev/null || exit
 
@@ -47,10 +51,6 @@ stop_here_user_resolve () {
     then
         cmdline="$cmdline -3"
     fi
-    if test '.dotest' != "$dotest"
-    then
-        cmdline="$cmdline -d=$dotest"
-    fi
     echo "When you have resolved this problem run \"$cmdline --resolved\"."
     echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
 
@@ -122,7 +122,8 @@ reread_subject () {
 }
 
 prec=4
-dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary=
+dotest=".dotest"
+sign= utf8=t keep= skip= interactive= resolved= binary= rebasing=
 resolvemsg= resume=
 git_apply_opt=
 
@@ -147,8 +148,11 @@ do
                resolved=t ;;
        --skip)
                skip=t ;;
+       --rebasing)
+               rebasing=t threeway=t keep=t binary=t ;;
        -d|--dotest)
-               shift; dotest=$1;;
+               die "-d option is no longer supported.  Do not use."
+               ;;
        --resolvemsg)
                shift; resolvemsg=$1 ;;
        --whitespace)
@@ -184,7 +188,7 @@ then
        0,)
                # No file input but without resume parameters; catch
                # user error to feed us a patch from standard input
-               # when there is already .dotest.  This is somewhat
+               # when there is already $dotest.  This is somewhat
                # unreliable -- stdin could be /dev/null for example
                # and the caller did not intend to feed us a patch but
                # wanted to continue unattended.
@@ -204,6 +208,24 @@ else
        # Start afresh.
        mkdir -p "$dotest" || exit
 
+       if test -n "$prefix" && test $# != 0
+       then
+               first=t
+               for arg
+               do
+                       test -n "$first" && {
+                               set x
+                               first=
+                       }
+                       case "$arg" in
+                       /*)
+                               set "$@" "$arg" ;;
+                       *)
+                               set "$@" "$prefix$arg" ;;
+                       esac
+               done
+               shift
+       fi
        git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
                rm -fr "$dotest"
                exit 1
@@ -218,6 +240,12 @@ else
        echo "$utf8" >"$dotest/utf8"
        echo "$keep" >"$dotest/keep"
        echo 1 >"$dotest/next"
+       if test -n "$rebasing"
+       then
+               : >"$dotest/rebasing"
+       else
+               : >"$dotest/applying"
+       fi
 fi
 
 case "$resolved" in
index f8857747c4cc5cb3b958e925192e0040157ea911..2c32d0b9ebcdb9bf43124721e109cfac72149990 100755 (executable)
@@ -333,9 +333,9 @@ bisect_visualize() {
 
        if test $# = 0
        then
-               case "${DISPLAY+set}" in
+               case "${DISPLAY+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
                '')     set git log ;;
-               set   set gitk ;;
+               set*)   set gitk ;;
                esac
        else
                case "$1" in
diff --git a/git-checkout.sh b/git-checkout.sh
deleted file mode 100755 (executable)
index bd74d70..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-#!/bin/sh
-
-OPTIONS_KEEPDASHDASH=t
-OPTIONS_SPEC="\
-git-checkout [options] [<branch>] [<paths>...]
---
-b=          create a new branch started at <branch>
-l           create the new branch's reflog
-track       arrange that the new branch tracks the remote branch
-f           proceed even if the index or working tree is not HEAD
-m           merge local modifications into the new branch
-q,quiet     be quiet
-"
-SUBDIRECTORY_OK=Sometimes
-. git-sh-setup
-require_work_tree
-
-old_name=HEAD
-old=$(git rev-parse --verify $old_name 2>/dev/null)
-oldbranch=$(git symbolic-ref $old_name 2>/dev/null)
-new=
-new_name=
-force=
-branch=
-track=
-newbranch=
-newbranch_log=
-merge=
-quiet=
-v=-v
-LF='
-'
-
-while test $# != 0; do
-       case "$1" in
-       -b)
-               shift
-               newbranch="$1"
-               [ -z "$newbranch" ] &&
-                       die "git checkout: -b needs a branch name"
-               git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
-                       die "git checkout: branch $newbranch already exists"
-               git check-ref-format "heads/$newbranch" ||
-                       die "git checkout: we do not like '$newbranch' as a branch name."
-               ;;
-       -l)
-               newbranch_log=-l
-               ;;
-       --track|--no-track)
-               track="$1"
-               ;;
-       -f)
-               force=1
-               ;;
-       -m)
-               merge=1
-               ;;
-       -q|--quiet)
-               quiet=1
-               v=
-               ;;
-       --)
-               shift
-               break
-               ;;
-       *)
-               usage
-               ;;
-       esac
-       shift
-done
-
-arg="$1"
-rev=$(git rev-parse --verify "$arg" 2>/dev/null)
-if rev=$(git rev-parse --verify "$rev^0" 2>/dev/null)
-then
-       [ -z "$rev" ] && die "unknown flag $arg"
-       new_name="$arg"
-       if git show-ref --verify --quiet -- "refs/heads/$arg"
-       then
-               rev=$(git rev-parse --verify "refs/heads/$arg^0")
-               branch="$arg"
-       fi
-       new="$rev"
-       shift
-elif rev=$(git rev-parse --verify "$rev^{tree}" 2>/dev/null)
-then
-       # checking out selected paths from a tree-ish.
-       new="$rev"
-       new_name="$rev^{tree}"
-       shift
-fi
-[ "$1" = "--" ] && shift
-
-case "$newbranch,$track" in
-,--*)
-       die "git checkout: --track and --no-track require -b"
-esac
-
-case "$force$merge" in
-11)
-       die "git checkout: -f and -m are incompatible"
-esac
-
-# The behaviour of the command with and without explicit path
-# parameters is quite different.
-#
-# Without paths, we are checking out everything in the work tree,
-# possibly switching branches.  This is the traditional behaviour.
-#
-# With paths, we are _never_ switching branch, but checking out
-# the named paths from either index (when no rev is given),
-# or the named tree-ish (when rev is given).
-
-if test "$#" -ge 1
-then
-       hint=
-       if test "$#" -eq 1
-       then
-               hint="
-Did you intend to checkout '$@' which can not be resolved as commit?"
-       fi
-       if test '' != "$newbranch$force$merge"
-       then
-               die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
-       fi
-       if test '' != "$new"
-       then
-               # from a specific tree-ish; note that this is for
-               # rescuing paths and is never meant to remove what
-               # is not in the named tree-ish.
-               git ls-tree --full-name -r "$new" "$@" |
-               git update-index --index-info || exit $?
-       fi
-
-       # Make sure the request is about existing paths.
-       git ls-files --full-name --error-unmatch -- "$@" >/dev/null || exit
-       git ls-files --full-name -- "$@" |
-               (cd_to_toplevel && git checkout-index -f -u --stdin)
-
-       # Run a post-checkout hook -- the HEAD does not change so the
-       # current HEAD is passed in for both args
-       if test -x "$GIT_DIR"/hooks/post-checkout; then
-           "$GIT_DIR"/hooks/post-checkout $old $old 0
-       fi
-
-       exit $?
-else
-       # Make sure we did not fall back on $arg^{tree} codepath
-       # since we are not checking out from an arbitrary tree-ish,
-       # but switching branches.
-       if test '' != "$new"
-       then
-               git rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
-               die "Cannot switch branch to a non-commit."
-       fi
-fi
-
-# We are switching branches and checking out trees, so
-# we *NEED* to be at the toplevel.
-cd_to_toplevel
-
-[ -z "$new" ] && new=$old && new_name="$old_name"
-
-# If we don't have an existing branch that we're switching to,
-# and we don't have a new branch name for the target we
-# are switching to, then we are detaching our HEAD from any
-# branch.  However, if "git checkout HEAD" detaches the HEAD
-# from the current branch, even though that may be logically
-# correct, it feels somewhat funny.  More importantly, we do not
-# want "git checkout" nor "git checkout -f" to detach HEAD.
-
-detached=
-detach_warn=
-
-describe_detached_head () {
-       test -n "$quiet" || {
-               printf >&2 "$1 "
-               GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" --
-       }
-}
-
-if test -z "$branch$newbranch" && test "$new_name" != "$old_name"
-then
-       detached="$new"
-       if test -n "$oldbranch" && test -z "$quiet"
-       then
-               detach_warn="Note: moving to \"$new_name\" which isn't a local branch
-If you want to create a new branch from this checkout, you may do so
-(now or later) by using -b with the checkout command again. Example:
-  git checkout -b <new_branch_name>"
-       fi
-elif test -z "$oldbranch" && test "$new" != "$old"
-then
-       describe_detached_head 'Previous HEAD position was' "$old"
-fi
-
-if [ "X$old" = X ]
-then
-       if test -z "$quiet"
-       then
-               echo >&2 "warning: You appear to be on a branch yet to be born."
-               echo >&2 "warning: Forcing checkout of $new_name."
-       fi
-       force=1
-fi
-
-if [ "$force" ]
-then
-    git read-tree $v --reset -u $new
-else
-    git update-index --refresh >/dev/null
-    merge_error=$(git read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
-       case "$merge" in
-       '')
-               echo >&2 "$merge_error"
-               exit 1 ;;
-       esac
-
-       # Match the index to the working tree, and do a three-way.
-       git diff-files --name-only | git update-index --remove --stdin &&
-       work=`git write-tree` &&
-       git read-tree $v --reset -u $new || exit
-
-       eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
-       eval GITHEAD_$work=local &&
-       export GITHEAD_$new GITHEAD_$work &&
-       git merge-recursive $old -- $new $work
-
-       # Do not register the cleanly merged paths in the index yet.
-       # this is not a real merge before committing, but just carrying
-       # the working tree changes along.
-       unmerged=`git ls-files -u`
-       git read-tree $v --reset $new
-       case "$unmerged" in
-       '')     ;;
-       *)
-               (
-                       z40=0000000000000000000000000000000000000000
-                       echo "$unmerged" |
-                       sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
-                       echo "$unmerged"
-               ) | git update-index --index-info
-               ;;
-       esac
-       exit 0
-    )
-    saved_err=$?
-    if test "$saved_err" = 0 && test -z "$quiet"
-    then
-       git diff-index --name-status "$new"
-    fi
-    (exit $saved_err)
-fi
-
-#
-# Switch the HEAD pointer to the new branch if we
-# checked out a branch head, and remove any potential
-# old MERGE_HEAD's (subsequent commits will clearly not
-# be based on them, since we re-set the index)
-#
-if [ "$?" -eq 0 ]; then
-       if [ "$newbranch" ]; then
-               git branch $track $newbranch_log "$newbranch" "$new_name" || exit
-               branch="$newbranch"
-       fi
-       if test -n "$branch"
-       then
-               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
-               GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch"
-               if test -n "$quiet"
-               then
-                       true    # nothing
-               elif test "refs/heads/$branch" = "$oldbranch"
-               then
-                       echo >&2 "Already on branch \"$branch\""
-               else
-                       echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
-               fi
-       elif test -n "$detached"
-       then
-               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
-               git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" ||
-                       die "Cannot detach HEAD"
-               if test -n "$detach_warn"
-               then
-                       echo >&2 "$detach_warn"
-               fi
-               describe_detached_head 'HEAD is now at' HEAD
-       fi
-       rm -f "$GIT_DIR/MERGE_HEAD"
-else
-       exit 1
-fi
-
-# Run a post-checkout hook
-if test -x "$GIT_DIR"/hooks/post-checkout; then
-       "$GIT_DIR"/hooks/post-checkout $old $new 1
-fi
index 0d686c3a03a15d73e086d3a54afeee269365ffcd..e98112277839349a47c26e69ce30982604d12d2c 100755 (executable)
@@ -210,11 +210,28 @@ if base=$(get_repo_base "$repo"); then
        then
                local=yes
        fi
+elif test -f "$repo"
+then
+       case "$repo" in /*) ;; *) repo="$PWD/$repo" ;; esac
+fi
+
+# Decide the directory name of the new repository
+if test -n "$2"
+then
+       dir="$2"
+else
+       # Derive one from the repository name
+       # Try using "humanish" part of source repo if user didn't specify one
+       if test -f "$repo"
+       then
+               # Cloning from a bundle
+               dir=$(echo "$repo" | sed -e 's|/*\.bundle$||' -e 's|.*/||g')
+       else
+               dir=$(echo "$repo" |
+                       sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+       fi
 fi
 
-dir="$2"
-# Try using "humanish" part of source repo if user didn't specify one
-[ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
 [ -e "$dir" ] && die "destination directory '$dir' already exists."
 [ yes = "$bare" ] && unset GIT_WORK_TREE
 [ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
@@ -364,11 +381,17 @@ yes)
                fi
                ;;
        *)
-               case "$upload_pack" in
-               '') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
-               *) git-fetch-pack --all -k $quiet "$upload_pack" $depth $no_progress "$repo" ;;
-               esac >"$GIT_DIR/CLONE_HEAD" ||
+               if [ -f "$repo" ] ; then
+                       git bundle unbundle "$repo" > "$GIT_DIR/CLONE_HEAD" ||
+                       die "unbundle from '$repo' failed."
+               else
+                       case "$upload_pack" in
+                       '') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
+                       *) git-fetch-pack --all -k \
+                               $quiet "$upload_pack" $depth $no_progress "$repo" ;;
+                       esac >"$GIT_DIR/CLONE_HEAD" ||
                        die "fetch-pack from '$repo' failed."
+               fi
                ;;
        esac
        ;;
index 4df90cb34e61deb56ecb49797e48e67bdc98ff3b..73968e02b024f22068eb5500033f7a2f41d31e8f 100644 (file)
@@ -204,6 +204,20 @@ void *gitmemmem(const void *haystack, size_t haystacklen,
                 const void *needle, size_t needlelen);
 #endif
 
+#ifdef FREAD_READS_DIRECTORIES
+#define fopen(a,b) git_fopen(a,b)
+extern FILE *git_fopen(const char*, const char*);
+#endif
+
+#ifdef SNPRINTF_RETURNS_BOGUS
+#define snprintf git_snprintf
+extern int git_snprintf(char *str, size_t maxsize,
+                       const char *format, ...);
+#define vsnprintf git_vsnprintf
+extern int git_vsnprintf(char *str, size_t maxsize,
+                        const char *format, va_list ap);
+#endif
+
 #ifdef __GLIBC_PREREQ
 #if __GLIBC_PREREQ(2, 1)
 #define HAVE_STRCHRNUL
@@ -426,4 +440,16 @@ static inline int strtol_i(char const *s, int base, int *result)
        return 0;
 }
 
+#ifdef INTERNAL_QSORT
+void git_qsort(void *base, size_t nmemb, size_t size,
+              int(*compar)(const void *, const void *));
+#define qsort git_qsort
+#endif
+
+#ifndef DIR_HAS_BSD_GROUP_SEMANTICS
+# define FORCE_DIR_SET_GID S_ISGID
+#else
+# define FORCE_DIR_SET_GID 0
+#endif
+
 #endif
index 9516242338d95687b4354a6d21692cbd10547899..47f116f37ee1030ac0cab1f91feec04d673d94bd 100755 (executable)
@@ -15,7 +15,7 @@
 
 use strict;
 use warnings;
-use Getopt::Std;
+use Getopt::Long;
 use File::Spec;
 use File::Temp qw(tempfile tmpnam);
 use File::Path qw(mkpath);
@@ -29,7 +29,7 @@ use IPC::Open2;
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r);
+our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r);
 my (%conv_author_name, %conv_author_email);
 
 sub usage(;$) {
@@ -112,7 +112,12 @@ sub read_repo_config {
 
 my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:";
 read_repo_config($opts);
-getopts($opts) or usage();
+Getopt::Long::Configure( 'no_ignore_case', 'bundling' );
+
+# turn the Getopt::Std specification in a Getopt::Long one,
+# with support for multiple -M options
+GetOptions( map { s/:/=s/; /M/ ? "$_\@" : $_ } split( /(?!:)/, $opts ) )
+    or usage();
 usage if $opt_h;
 
 if (@ARGV == 0) {
@@ -164,10 +169,10 @@ if ($#ARGV == 0) {
 
 our @mergerx = ();
 if ($opt_m) {
-       @mergerx = ( qr/\b(?:from|of|merge|merging|merged) (\w+)/i );
+       @mergerx = ( qr/\b(?:from|of|merge|merging|merged) ([-\w]+)/i );
 }
-if ($opt_M) {
-       push (@mergerx, qr/$opt_M/);
+if (@opt_M) {
+       push (@mergerx, map { qr/$_/ } @opt_M);
 }
 
 # Remember UTC of our starting time
index afe3d0b7fe4d643688e672f62844cc86ec64fab5..7f632af20defd7a3cfd92008c51f7e599be13830 100755 (executable)
@@ -2556,7 +2556,7 @@ sub update
                     if ($base) {
                         my @merged;
                         # print "want to log between  $base $parent \n";
-                        open(GITLOG, '-|', 'git-log', "$base..$parent")
+                        open(GITLOG, '-|', 'git-log', '--pretty=medium', "$base..$parent")
                          or die "Cannot call git-log: $!";
                         my $mergedhash;
                         while (<GITLOG>) {
index 04bd42576eae325b848b1438f1a4ff2820645df3..238a2393ff7811675003e0e846f3d9409fb04e0c 100755 (executable)
@@ -612,6 +612,7 @@ set default_config(gui.pruneduringfetch) false
 set default_config(gui.trustmtime) false
 set default_config(gui.diffcontext) 5
 set default_config(gui.newbranchtemplate) {}
+set default_config(gui.spellingdictionary) {}
 set default_config(gui.fontui) [font configure font_ui]
 set default_config(gui.fontdiff) [font configure font_diff]
 set font_descs {
@@ -1683,6 +1684,7 @@ set is_quitting 0
 proc do_quit {} {
        global ui_comm is_quitting repo_config commit_type
        global GITGUI_BCK_exists GITGUI_BCK_i
+       global ui_comm_spell
 
        if {$is_quitting} return
        set is_quitting 1
@@ -1710,6 +1712,12 @@ proc do_quit {} {
                        }
                }
 
+               # -- Cancel our spellchecker if its running.
+               #
+               if {[info exists ui_comm_spell]} {
+                       $ui_comm_spell stop
+               }
+
                # -- Remove our editor backup, its not needed.
                #
                after cancel $GITGUI_BCK_i
@@ -2454,7 +2462,7 @@ $ctxm add separator
 $ctxm add command \
        -label [mc "Sign Off"] \
        -command do_signoff
-bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
+set ui_comm_ctxm $ctxm
 
 # -- Diff Header
 #
@@ -2857,6 +2865,30 @@ if {[winfo exists $ui_comm]} {
        }
 
        backup_commit_buffer
+
+       # -- If the user has aspell available we can drive it
+       #    in pipe mode to spellcheck the commit message.
+       #
+       set spell_cmd [list |]
+       set spell_dict [get_config gui.spellingdictionary]
+       lappend spell_cmd aspell
+       if {$spell_dict ne {}} {
+               lappend spell_cmd --master=$spell_dict
+       }
+       lappend spell_cmd --mode=none
+       lappend spell_cmd --encoding=utf-8
+       lappend spell_cmd pipe
+       if {$spell_dict eq {none}
+        || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
+               bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
+       } else {
+               set ui_comm_spell [spellcheck::init \
+                       $spell_fd \
+                       $ui_comm \
+                       $ui_comm_ctxm \
+               ]
+       }
+       unset -nocomplain spell_cmd spell_fd spell_err spell_dict
 }
 
 lock_index begin-read
index 719fc547b3e157cdb14a8a09ba77a1d7c5f1f585..241ab892cd5b731f07571acf7a0ca3150a763f4f 100644 (file)
@@ -4,6 +4,7 @@
 proc do_about {} {
        global appvers copyright oguilib
        global tcl_patchLevel tk_patchLevel
+       global ui_comm_spell
 
        set w .about_dialog
        toplevel $w
@@ -40,6 +41,11 @@ proc do_about {} {
                append v "Tcl version $tcl_patchLevel"
                append v ", Tk version $tk_patchLevel"
        }
+       if {[info exists ui_comm_spell]
+               && [$ui_comm_spell version] ne {}} {
+               append v "\n"
+               append v [$ui_comm_spell version]
+       }
 
        set d {}
        append d "git wrapper: $::_git\n"
index f24396692474e3da66f9502b2f06f13583a3deeb..6e1411711bd46df08c09fd2bc371b017050a8368 100644 (file)
@@ -280,7 +280,7 @@ The rescan will be automatically started now.
        } elseif {[is_config_true gui.trustmtime]} {
                _readtree $this
        } else {
-               ui_status {Refreshing file status...}
+               ui_status [mc "Refreshing file status..."]
                set fd [git_read update-index \
                        -q \
                        --unmerged \
@@ -320,7 +320,7 @@ method _readtree {} {
        set readtree_d {}
        $::main_status start \
                [mc "Updating working directory to '%s'..." [_name $this]] \
-               {files checked out}
+               [mc "files checked out"]
 
        set fd [git_read --stderr read-tree \
                -m \
@@ -447,7 +447,7 @@ If you wanted to be on a branch, create one now starting from 'This Detached Che
        } else {
                repository_state commit_type HEAD MERGE_HEAD
                set PARENT $HEAD
-               ui_status "Checked out '$name'."
+               ui_status [mc "Checked out '%s'." $name]
        }
        delete_this
 }
index 947b201c328eef044cfe21b463e8228031f6f8cd..40a710355751836e78b65101592b753266f507ca 100644 (file)
@@ -218,7 +218,7 @@ A good commit message has the following format:
                return
        }
 
-       ui_status {Calling pre-commit hook...}
+       ui_status [mc "Calling pre-commit hook..."]
        set pch_error {}
        fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
        fileevent $fd_ph readable \
@@ -233,7 +233,7 @@ proc commit_prehook_wait {fd_ph curHEAD msg_p} {
        if {[eof $fd_ph]} {
                if {[catch {close $fd_ph}]} {
                        catch {file delete $msg_p}
-                       ui_status {Commit declined by pre-commit hook.}
+                       ui_status [mc "Commit declined by pre-commit hook."]
                        hook_failed_popup pre-commit $pch_error
                        unlock_index
                } else {
@@ -256,7 +256,7 @@ proc commit_commitmsg {curHEAD msg_p} {
                return
        }
 
-       ui_status {Calling commit-msg hook...}
+       ui_status [mc "Calling commit-msg hook..."]
        set pch_error {}
        fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
        fileevent $fd_ph readable \
@@ -271,7 +271,7 @@ proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
        if {[eof $fd_ph]} {
                if {[catch {close $fd_ph}]} {
                        catch {file delete $msg_p}
-                       ui_status {Commit declined by commit-msg hook.}
+                       ui_status [mc "Commit declined by commit-msg hook."]
                        hook_failed_popup commit-msg $pch_error
                        unlock_index
                } else {
@@ -284,7 +284,7 @@ proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
 }
 
 proc commit_writetree {curHEAD msg_p} {
-       ui_status {Committing changes...}
+       ui_status [mc "Committing changes..."]
        set fd_wt [git_read write-tree]
        fileevent $fd_wt readable \
                [list commit_committree $fd_wt $curHEAD $msg_p]
@@ -301,7 +301,7 @@ proc commit_committree {fd_wt curHEAD msg_p} {
        if {[catch {close $fd_wt} err]} {
                catch {file delete $msg_p}
                error_popup [strcat [mc "write-tree failed:"] "\n\n$err"]
-               ui_status {Commit failed.}
+               ui_status [mc "Commit failed."]
                unlock_index
                return
        }
@@ -345,7 +345,7 @@ A rescan will be automatically started now.
        if {[catch {set cmt_id [eval git $cmd]} err]} {
                catch {file delete $msg_p}
                error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
-               ui_status {Commit failed.}
+               ui_status [mc "Commit failed."]
                unlock_index
                return
        }
@@ -365,7 +365,7 @@ A rescan will be automatically started now.
                } err]} {
                catch {file delete $msg_p}
                error_popup [strcat [mc "update-ref failed:"] "\n\n$err"]
-               ui_status {Commit failed.}
+               ui_status [mc "Commit failed."]
                unlock_index
                return
        }
index 30a244cc170a344ba99be1c7be07fdebe4333bde..3c1fce7475d362d1880d915ff4bdf168fda28593 100644 (file)
@@ -310,7 +310,7 @@ proc add_helper {txt paths} {
                update_index \
                        $txt \
                        $pathList \
-                       [concat $after {ui_status {Ready to commit.}}]
+                       [concat $after {ui_status [mc "Ready to commit."]}]
        }
 }
 
index 63e14279c183b1d0b8a62926816bb44ab6dc519c..cc26b0780882b6e8bde122c2a724d110f439d84f 100644 (file)
@@ -116,8 +116,7 @@ method _start {} {
        lappend cmd HEAD
        lappend cmd $name
 
-       set msg [mc "Merging %s and %s" $current_branch $stitle]
-       ui_status "$msg..."
+       ui_status [mc "Merging %s and %s..." $current_branch $stitle]
        set cons [console::new [mc "Merge"] "merge $stitle"]
        console::exec $cons $cmd [cb _finish $cons]
 
@@ -236,7 +235,7 @@ Continue with resetting the current changes?"]
                set fd [git_read --stderr read-tree --reset -u -v HEAD]
                fconfigure $fd -blocking 0 -translation binary
                fileevent $fd readable [namespace code [list _reset_wait $fd]]
-               $::main_status start [mc "Aborting"] {files reset}
+               $::main_status start [mc "Aborting"] [mc "files reset"]
        } else {
                unlock_index
        }
index f812e5e89a1f21e2ee96a90e83958a472539bdd5..ea80df009226d0ff14e8fe423abfe3d7a94ef56b 100644 (file)
@@ -5,6 +5,7 @@ proc save_config {} {
        global default_config font_descs
        global repo_config global_config
        global repo_config_new global_config_new
+       global ui_comm_spell
 
        foreach option $font_descs {
                set name [lindex $option 0]
@@ -52,11 +53,23 @@ proc save_config {} {
                        set repo_config($name) $value
                }
        }
+
+       if {[info exists repo_config(gui.spellingdictionary)]} {
+               set value $repo_config(gui.spellingdictionary)
+               if {$value eq {none}} {
+                       if {[info exists ui_comm_spell]} {
+                               $ui_comm_spell stop
+                       }
+               } elseif {[info exists ui_comm_spell]} {
+                       $ui_comm_spell lang $value
+               }
+       }
 }
 
 proc do_options {} {
        global repo_config global_config font_descs
        global repo_config_new global_config_new
+       global ui_comm_spell
 
        array unset repo_config_new
        array unset global_config_new
@@ -159,6 +172,32 @@ proc do_options {} {
                }
        }
 
+       set all_dicts [linsert \
+               [spellcheck::available_langs] \
+               0 \
+               none]
+       incr optid
+       foreach f {repo global} {
+               if {![info exists ${f}_config_new(gui.spellingdictionary)]} {
+                       if {[info exists ui_comm_spell]} {
+                               set value [$ui_comm_spell lang]
+                       } else {
+                               set value none
+                       }
+                       set ${f}_config_new(gui.spellingdictionary) $value
+               }
+
+               frame $w.$f.$optid
+               label $w.$f.$optid.l -text [mc "Spelling Dictionary:"]
+               eval tk_optionMenu $w.$f.$optid.v \
+                       ${f}_config_new(gui.spellingdictionary) \
+                       $all_dicts
+               pack $w.$f.$optid.l -side left -anchor w -fill x
+               pack $w.$f.$optid.v -side right -anchor e -padx 5
+               pack $w.$f.$optid -side top -anchor w -fill x
+       }
+       unset all_dicts
+
        set all_fonts [lsort [font families]]
        foreach option $font_descs {
                set name [lindex $option 0]
diff --git a/git-gui/lib/spellcheck.tcl b/git-gui/lib/spellcheck.tcl
new file mode 100644 (file)
index 0000000..9be7486
--- /dev/null
@@ -0,0 +1,408 @@
+# git-gui spellchecking support through ispell/aspell
+# Copyright (C) 2008 Shawn Pearce
+
+class spellcheck {
+
+field s_fd      {} ; # pipe to ispell/aspell
+field s_version {} ; # ispell/aspell version string
+field s_lang    {} ; # current language code
+field s_prog aspell; # are we actually old ispell?
+field s_failed   0 ; # is $s_prog bogus and not working?
+
+field w_text      ; # text widget we are spelling
+field w_menu      ; # context menu for the widget
+field s_menuidx 0 ; # last index of insertion into $w_menu
+
+field s_i           {} ; # timer registration for _run callbacks
+field s_clear        0 ; # did we erase mispelled tags yet?
+field s_seen    [list] ; # lines last seen from $w_text in _run
+field s_checked [list] ; # lines already checked
+field s_pending [list] ; # [$line $data] sent to ispell/aspell
+field s_suggest        ; # array, list of suggestions, keyed by misspelling
+
+constructor init {pipe_fd ui_text ui_menu} {
+       set w_text $ui_text
+       set w_menu $ui_menu
+       array unset s_suggest
+
+       bind_button3 $w_text [cb _popup_suggest %X %Y @%x,%y]
+       _connect $this $pipe_fd
+       return $this
+}
+
+method _connect {pipe_fd} {
+       fconfigure $pipe_fd \
+               -encoding utf-8 \
+               -eofchar {} \
+               -translation lf
+
+       if {[gets $pipe_fd s_version] <= 0} {
+               if {[catch {close $pipe_fd} err]} {
+
+                       # Eh?  Is this actually ispell choking on aspell options?
+                       #
+                       if {$s_prog eq {aspell}
+                               && [regexp -nocase {^Usage: } $err]
+                               && ![catch {
+                                               set pipe_fd [open [list | $s_prog -v] r]
+                                               gets $pipe_fd s_version
+                                               close $pipe_fd
+                               }]
+                               && $s_version ne {}} {
+                               if {{@(#) } eq [string range $s_version 0 4]} {
+                                       set s_version [string range $s_version 5 end]
+                               }
+                               set s_failed 1
+                               error_popup [strcat \
+                                       [mc "Unsupported spell checker"] \
+                                       ":\n\n$s_version"]
+                               set s_version {}
+                               return
+                       }
+
+                       regsub -nocase {^Error: } $err {} err
+                       if {$s_fd eq {}} {
+                               error_popup [strcat [mc "Spell checking is unavailable"] ":\n\n$err"]
+                       } else {
+                               error_popup [strcat \
+                                       [mc "Invalid spell checking configuration"] \
+                                       ":\n\n$err\n\n" \
+                                       [mc "Reverting dictionary to %s." $s_lang]]
+                       }
+               } else {
+                       error_popup [mc "Spell checker silently failed on startup"]
+               }
+               return
+       }
+
+       if {{@(#) } ne [string range $s_version 0 4]} {
+               catch {close $pipe_fd}
+               error_popup [strcat [mc "Unrecognized spell checker"] ":\n\n$s_version"]
+               return
+       }
+       set s_version [string range $s_version 5 end]
+       regexp \
+               {International Ispell Version .* \(but really (Aspell .*?)\)$} \
+               $s_version _junk s_version
+
+       puts $pipe_fd !             ; # enable terse mode
+       puts $pipe_fd {$$cr master} ; # fetch the language
+       flush $pipe_fd
+
+       gets $pipe_fd s_lang
+       regexp {[/\\]([^/\\]+)\.[^\.]+$} $s_lang _ s_lang
+
+       if {$::default_config(gui.spellingdictionary) eq {}
+        && [get_config gui.spellingdictionary] eq {}} {
+               set ::default_config(gui.spellingdictionary) $s_lang
+       }
+
+       if {$s_fd ne {}} {
+               catch {close $s_fd}
+       }
+       set s_fd $pipe_fd
+
+       fconfigure $s_fd -blocking 0
+       fileevent $s_fd readable [cb _read]
+
+       $w_text tag conf misspelled \
+               -foreground red \
+               -underline 1
+
+       array unset s_suggest
+       set s_seen    [list]
+       set s_checked [list]
+       set s_pending [list]
+       _run $this
+}
+
+method lang {{n {}}} {
+       if {$n ne {} && $s_lang ne $n && !$s_failed} {
+               set spell_cmd [list |]
+               lappend spell_cmd aspell
+               lappend spell_cmd --master=$n
+               lappend spell_cmd --mode=none
+               lappend spell_cmd --encoding=UTF-8
+               lappend spell_cmd pipe
+               _connect $this [open $spell_cmd r+]
+       }
+       return $s_lang
+}
+
+method version {} {
+       if {$s_version ne {}} {
+               return "$s_version, $s_lang"
+       }
+       return {}
+}
+
+method stop {} {
+       while {$s_menuidx > 0} {
+               $w_menu delete 0
+               incr s_menuidx -1
+       }
+       $w_text tag delete misspelled
+
+       catch {close $s_fd}
+       catch {after cancel $s_i}
+       set s_fd {}
+       set s_i {}
+       set s_lang {}
+}
+
+method _popup_suggest {X Y pos} {
+       while {$s_menuidx > 0} {
+               $w_menu delete 0
+               incr s_menuidx -1
+       }
+
+       set b_loc [$w_text index "$pos wordstart"]
+       set e_loc [_wordend $this $b_loc]
+       set orig  [$w_text get $b_loc $e_loc]
+       set tags  [$w_text tag names $b_loc]
+
+       if {[lsearch -exact $tags misspelled] >= 0} {
+               if {[info exists s_suggest($orig)]} {
+                       set cnt 0
+                       foreach s $s_suggest($orig) {
+                               if {$cnt < 5} {
+                                       $w_menu insert $s_menuidx command \
+                                               -label $s \
+                                               -command [cb _replace $b_loc $e_loc $s]
+                                       incr s_menuidx
+                                       incr cnt
+                               } else {
+                                       break
+                               }
+                       }
+               } else {
+                       $w_menu insert $s_menuidx command \
+                               -label [mc "No Suggestions"] \
+                               -state disabled
+                       incr s_menuidx
+               }
+               $w_menu insert $s_menuidx separator
+               incr s_menuidx
+       }
+
+       $w_text mark set saved-insert insert
+       tk_popup $w_menu $X $Y
+}
+
+method _replace {b_loc e_loc word} {
+       $w_text configure -autoseparators 0
+       $w_text edit separator
+
+       $w_text delete $b_loc $e_loc
+       $w_text insert $b_loc $word
+
+       $w_text edit separator
+       $w_text configure -autoseparators 1
+       $w_text mark set insert saved-insert
+}
+
+method _restart_timer {} {
+       set s_i [after 300 [cb _run]]
+}
+
+proc _match_length {max_line arr_name} {
+       upvar $arr_name a
+
+       if {[llength $a] > $max_line} {
+               set a [lrange $a 0 $max_line]
+       }
+       while {[llength $a] <= $max_line} {
+               lappend a {}
+       }
+}
+
+method _wordend {pos} {
+       set pos  [$w_text index "$pos wordend"]
+       set tags [$w_text tag names $pos]
+       while {[lsearch -exact $tags misspelled] >= 0} {
+               set pos  [$w_text index "$pos +1c"]
+               set tags [$w_text tag names $pos]
+       }
+       return $pos
+}
+
+method _run {} {
+       set cur_pos  [$w_text index {insert -1c}]
+       set cur_line [lindex [split $cur_pos .] 0]
+       set max_line [lindex [split [$w_text index end] .] 0]
+       _match_length $max_line s_seen
+       _match_length $max_line s_checked
+
+       # Nothing in the message buffer?  Nothing to spellcheck.
+       #
+       if {$cur_line == 1
+        && $max_line == 2
+        && [$w_text get 1.0 end] eq "\n"} {
+               array unset s_suggest
+               _restart_timer $this
+               return
+       }
+
+       set active 0
+       for {set n 1} {$n <= $max_line} {incr n} {
+               set s [$w_text get "$n.0" "$n.end"]
+
+               # Don't spellcheck the current line unless we are at
+               # a word boundary.  The user might be typing on it.
+               #
+               if {$n == $cur_line
+                && ![regexp {^\W$} [$w_text get $cur_pos insert]]} {
+
+                       # If the current word is mispelled remove the tag
+                       # but force a spellcheck later.
+                       #
+                       set tags [$w_text tag names $cur_pos]
+                       if {[lsearch -exact $tags misspelled] >= 0} {
+                               $w_text tag remove misspelled \
+                                       "$cur_pos wordstart" \
+                                       [_wordend $this $cur_pos]
+                               lset s_seen    $n $s
+                               lset s_checked $n {}
+                       }
+
+                       continue
+               }
+
+               if {[lindex $s_seen    $n] eq $s
+                && [lindex $s_checked $n] ne $s} {
+                       # Don't send empty lines to Aspell it doesn't check them.
+                       #
+                       if {$s eq {}} {
+                               lset s_checked $n $s
+                               continue
+                       }
+
+                       # Don't send typical s-b-o lines as the emails are
+                       # almost always misspelled according to Aspell.
+                       #
+                       if {[regexp -nocase {^[a-z-]+-by:.*<.*@.*>$} $s]} {
+                               $w_text tag remove misspelled "$n.0" "$n.end"
+                               lset s_checked $n $s
+                               continue
+                       }
+
+                       puts $s_fd ^$s
+                       lappend s_pending [list $n $s]
+                       set active 1
+               } else {
+                       # Delay until another idle loop to make sure we don't
+                       # spellcheck lines the user is actively changing.
+                       #
+                       lset s_seen $n $s
+               }
+       }
+
+       if {$active} {
+               set s_clear 1
+               flush $s_fd
+       } else {
+               _restart_timer $this
+       }
+}
+
+method _read {} {
+       while {[gets $s_fd line] >= 0} {
+               set lineno [lindex $s_pending 0 0]
+
+               if {$s_clear} {
+                       $w_text tag remove misspelled "$lineno.0" "$lineno.end"
+                       set s_clear 0
+               }
+
+               if {$line eq {}} {
+                       lset s_checked $lineno [lindex $s_pending 0 1]
+                       set s_pending [lrange $s_pending 1 end]
+                       set s_clear 1
+                       continue
+               }
+
+               set sugg [list]
+               switch -- [string range $line 0 1] {
+               {& } {
+                       set line [split [string range $line 2 end] :]
+                       set info [split [lindex $line 0] { }]
+                       set orig [lindex $info 0]
+                       set offs [lindex $info 2]
+                       foreach s [split [lindex $line 1] ,] {
+                               lappend sugg [string range $s 1 end]
+                       }
+               }
+               {# } {
+                       set info [split [string range $line 2 end] { }]
+                       set orig [lindex $info 0]
+                       set offs [lindex $info 1]
+               }
+               default {
+                       puts stderr "<spell> $line"
+                       continue
+               }
+               }
+
+               incr offs -1
+               set b_loc "$lineno.$offs"
+               set e_loc [$w_text index "$lineno.$offs wordend"]
+               set curr [$w_text get $b_loc $e_loc]
+
+               # At least for English curr = "bob", orig = "bob's"
+               # so Tk didn't include the 's but Aspell did.  We
+               # try to round out the word.
+               #
+               while {$curr ne $orig
+                && [string equal -length [string length $curr] $curr $orig]} {
+                       set n_loc  [$w_text index "$e_loc +1c"]
+                       set n_curr [$w_text get $b_loc $n_loc]
+                       if {$n_curr eq $curr} {
+                               break
+                       }
+                       set curr  $n_curr
+                       set e_loc $n_loc
+               }
+
+               if {$curr eq $orig} {
+                       $w_text tag add misspelled $b_loc $e_loc
+                       if {[llength $sugg] > 0} {
+                               set s_suggest($orig) $sugg
+                       } else {
+                               unset -nocomplain s_suggest($orig)
+                       }
+               } else {
+                       unset -nocomplain s_suggest($orig)
+               }
+       }
+
+       fconfigure $s_fd -block 1
+       if {[eof $s_fd]} {
+               if {![catch {close $s_fd} err]} {
+                       set err [mc "Unexpected EOF from spell checker"]
+               }
+               catch {after cancel $s_i}
+               $w_text tag remove misspelled 1.0 end
+               error_popup [strcat [mc "Spell Checker Failed"] "\n\n" $err]
+               return
+       }
+       fconfigure $s_fd -block 0
+
+       if {[llength $s_pending] == 0} {
+               _restart_timer $this
+       }
+}
+
+proc available_langs {} {
+       set langs [list]
+       catch {
+               set fd [open [list | aspell dump dicts] r]
+               while {[gets $fd line] >= 0} {
+                       if {$line eq {}} continue
+                       lappend langs $line
+               }
+               close $fd
+       }
+       return $langs
+}
+
+}
index 2dfe07e06f7bbbdc1dd439431e8c56ecf9c60cae..e84e1c7e087b1c64245124643d798a4f74d39f22 100644 (file)
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-11-24 10:36+0100\n"
-"PO-Revision-Date: 2008-01-15 20:33+0100\n"
+"POT-Creation-Date: 2008-02-16 21:24+0100\n"
+"PO-Revision-Date: 2008-02-16 21:52+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
 "MIME-Version: 1.0\n"
@@ -343,7 +343,9 @@ msgstr "Online-Dokumentation"
 #: git-gui.sh:2201
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
-msgstr "Fehler: Verzeichnis Â»%s« kann nicht gelesen werden: Datei oder Verzeichnis nicht gefunden"
+msgstr ""
+"Fehler: Verzeichnis Â»%s« kann nicht gelesen werden: Datei oder Verzeichnis "
+"nicht gefunden"
 
 #: git-gui.sh:2234
 msgid "Current Branch:"
@@ -371,19 +373,19 @@ msgstr "Erste Versionsbeschreibung:"
 
 #: git-gui.sh:2370
 msgid "Amended Commit Message:"
-msgstr "Nachgebesserte Versionsbeschreibung:"
+msgstr "Nachgebesserte Beschreibung:"
 
 #: git-gui.sh:2371
 msgid "Amended Initial Commit Message:"
-msgstr "Nachgebesserte erste Versionsbeschreibung:"
+msgstr "Nachgebesserte erste Beschreibung:"
 
 #: git-gui.sh:2372
 msgid "Amended Merge Commit Message:"
-msgstr "Nachgebesserte Zusammenführungs-Versionsbeschreibung:"
+msgstr "Nachgebesserte Zusammenführungs-Beschreibung:"
 
 #: git-gui.sh:2373
 msgid "Merge Commit Message:"
-msgstr "Zusammenführungs-Versionsbeschreibung:"
+msgstr "Zusammenführungs-Beschreibung:"
 
 #: git-gui.sh:2374
 msgid "Commit Message:"
@@ -397,31 +399,31 @@ msgstr "Alle kopieren"
 msgid "File:"
 msgstr "Datei:"
 
-#: git-gui.sh:2545
-msgid "Refresh"
-msgstr "Aktualisieren"
-
-#: git-gui.sh:2566
+#: git-gui.sh:2573
 msgid "Apply/Reverse Hunk"
 msgstr "Kontext anwenden/umkehren"
 
-#: git-gui.sh:2572
-msgid "Decrease Font Size"
-msgstr "Schriftgröße verkleinern"
-
-#: git-gui.sh:2576
-msgid "Increase Font Size"
-msgstr "Schriftgröße vergrößern"
-
-#: git-gui.sh:2581
+#: git-gui.sh:2579
 msgid "Show Less Context"
 msgstr "Weniger Zeilen anzeigen"
 
-#: git-gui.sh:2588
+#: git-gui.sh:2586
 msgid "Show More Context"
 msgstr "Mehr Zeilen anzeigen"
 
-#: git-gui.sh:2602
+#: git-gui.sh:2594
+msgid "Refresh"
+msgstr "Aktualisieren"
+
+#: git-gui.sh:2615
+msgid "Decrease Font Size"
+msgstr "Schriftgröße verkleinern"
+
+#: git-gui.sh:2619
+msgid "Increase Font Size"
+msgstr "Schriftgröße vergrößern"
+
+#: git-gui.sh:2630
 msgid "Unstage Hunk From Commit"
 msgstr "Kontext aus Bereitstellung herausnehmen"
 
@@ -542,7 +544,7 @@ msgstr "Kopiert oder verschoben durch:"
 
 #: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
 msgid "Checkout Branch"
-msgstr "Zweig umstellen"
+msgstr "Auf Zweig umstellen"
 
 #: lib/branch_checkout.tcl:23
 msgid "Checkout"
@@ -651,7 +653,7 @@ msgstr "Lokale Zweige"
 
 #: lib/branch_delete.tcl:52
 msgid "Delete Only If Merged Into"
-msgstr "Nur löschen, wenn darin zusammengeführt"
+msgstr "Nur löschen, wenn zusammengeführt nach"
 
 #: lib/branch_delete.tcl:54
 msgid "Always (Do not perform merge test.)"
@@ -805,11 +807,15 @@ msgstr ""
 msgid "Updating working directory to '%s'..."
 msgstr "Arbeitskopie umstellen auf Â»%s«..."
 
+#: lib/checkout_op.tcl:323
+msgid "files checked out"
+msgstr "Dateien aktualisiert"
+
 #: lib/checkout_op.tcl:353
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr ""
-"Zweig umstellen von Â»%s« abgebrochen (Zusammenführen der Dateien ist "
+"Auf Zweig Â»%s« umstellen abgebrochen (Zusammenführen der Dateien ist "
 "notwendig)."
 
 #: lib/checkout_op.tcl:354
@@ -1069,15 +1075,21 @@ msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s"
 
 #: lib/choose_repository.tcl:847
 msgid "Cannot fetch branches and objects.  See console output for details."
-msgstr "Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die Ausgaben auf der Konsole für weitere Angaben."
+msgstr ""
+"Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
 #: lib/choose_repository.tcl:858
 msgid "Cannot fetch tags.  See console output for details."
-msgstr "Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die Ausgaben auf der Konsole für weitere Angaben."
+msgstr ""
+"Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
 #: lib/choose_repository.tcl:882
 msgid "Cannot determine HEAD.  See console output for details."
-msgstr "Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die Ausgaben auf der Konsole für weitere Angaben."
+msgstr ""
+"Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
 #: lib/choose_repository.tcl:891
 #, tcl-format
@@ -1273,11 +1285,40 @@ msgstr ""
 "\n"
 "- Rest: Eine ausführliche Beschreibung, warum diese Ã„nderung hilfreich ist.\n"
 
-#: lib/commit.tcl:257
+#: lib/commit.tcl:207
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung Â»%s« nicht."
+
+#: lib/commit.tcl:221
+msgid "Calling pre-commit hook..."
+msgstr "Aufrufen der Vor-Eintragen-Kontrolle..."
+
+#: lib/commit.tcl:236
+msgid "Commit declined by pre-commit hook."
+msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
+
+#: lib/commit.tcl:259
+msgid "Calling commit-msg hook..."
+msgstr "Aufrufen der Versionsbeschreibungs-Kontrolle..."
+
+#: lib/commit.tcl:274
+msgid "Commit declined by commit-msg hook."
+msgstr "Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message hook«)."
+
+#: lib/commit.tcl:287
+msgid "Committing changes..."
+msgstr "Änderungen eintragen..."
+
+#: lib/commit.tcl:303
 msgid "write-tree failed:"
 msgstr "write-tree fehlgeschlagen:"
 
-#: lib/commit.tcl:275
+#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368
+msgid "Commit failed."
+msgstr "Eintragen fehlgeschlagen."
+
+#: lib/commit.tcl:321
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr "Version Â»%s« scheint beschädigt zu sein"
@@ -1301,12 +1342,7 @@ msgstr ""
 msgid "No changes to commit."
 msgstr "Keine Ã„nderungen, die eingetragen werden können."
 
-#: lib/commit.tcl:303
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung Â»%s« nicht."
-
-#: lib/commit.tcl:317
+#: lib/commit.tcl:347
 msgid "commit-tree failed:"
 msgstr "commit-tree fehlgeschlagen:"
 
@@ -1353,7 +1389,7 @@ msgstr "Festplattenplatz von komprimierten Objekten"
 
 #: lib/database.tcl:48
 msgid "Packed objects waiting for pruning"
-msgstr "Komprimierte Objekte, die zum Entfernen vorgesehen sind"
+msgstr "Komprimierte Objekte, die zum Aufräumen vorgesehen sind"
 
 #: lib/database.tcl:49
 msgid "Garbage files"
@@ -1440,7 +1476,8 @@ msgstr "Fehler beim Laden des Vergleichs:"
 
 #: lib/diff.tcl:302
 msgid "Failed to unstage selected hunk."
-msgstr "Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
+msgstr ""
+"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
 
 #: lib/diff.tcl:309
 msgid "Failed to stage selected hunk."
@@ -1471,7 +1508,10 @@ msgstr "Fehler in Bereitstellung"
 msgid ""
 "Updating the Git index failed.  A rescan will be automatically started to "
 "resynchronize git-gui."
-msgstr "Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu synchronisieren."
+msgstr ""
+"Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine "
+"Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu "
+"synchronisieren."
 
 #: lib/index.tcl:27
 msgid "Continue"
@@ -1486,6 +1526,10 @@ msgstr "Bereitstellung freigeben"
 msgid "Unstaging %s from commit"
 msgstr "Datei Â»%s« aus der Bereitstellung herausnehmen"
 
+#: lib/index.tcl:313
+msgid "Ready to commit."
+msgstr "Bereit zum Eintragen."
+
 #: lib/index.tcl:326
 #, tcl-format
 msgid "Adding %s"
@@ -1503,7 +1547,8 @@ msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
 
 #: lib/index.tcl:389
 msgid "Any unstaged changes will be permanently lost by the revert."
-msgstr "Alle nicht bereitgestellten Ã„nderungen werden beim Verwerfen verloren gehen."
+msgstr ""
+"Alle nicht bereitgestellten Ã„nderungen werden beim Verwerfen verloren gehen."
 
 #: lib/index.tcl:392
 msgid "Do Nothing"
@@ -1577,10 +1622,10 @@ msgstr "%s von %s"
 
 #: lib/merge.tcl:119
 #, tcl-format
-msgid "Merging %s and %s"
-msgstr "Zusammenführen von %s und %s"
+msgid "Merging %s and %s..."
+msgstr "Zusammenführen von %s und %s..."
 
-#: lib/merge.tcl:131
+#: lib/merge.tcl:130
 msgid "Merge completed successfully."
 msgstr "Zusammenführen erfolgreich abgeschlossen."
 
@@ -1591,7 +1636,7 @@ msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
 #: lib/merge.tcl:158
 #, tcl-format
 msgid "Merge Into %s"
-msgstr "Zusammenführen in %s"
+msgstr "Zusammenführen in Â»%s«"
 
 #: lib/merge.tcl:177
 msgid "Revision To Merge"
@@ -1641,7 +1686,11 @@ msgstr ""
 msgid "Aborting"
 msgstr "Abbruch"
 
-#: lib/merge.tcl:266
+#: lib/merge.tcl:238
+msgid "files reset"
+msgstr "Dateien zurückgesetzt"
+
+#: lib/merge.tcl:265
 msgid "Abort failed."
 msgstr "Abbruch fehlgeschlagen."
 
@@ -1692,7 +1741,7 @@ msgstr "Auf Dateiänderungsdatum verlassen"
 
 #: lib/option.tcl:111
 msgid "Prune Tracking Branches During Fetch"
-msgstr "Ãœbernahmezweige entfernen während Anforderung"
+msgstr "Ãœbernahmezweige aufräumen während Anforderung"
 
 #: lib/option.tcl:112
 msgid "Match Tracking Branches"
@@ -1706,7 +1755,11 @@ msgstr "Anzahl der Kontextzeilen beim Vergleich"
 msgid "New Branch Name Template"
 msgstr "Namensvorschlag für neue Zweige"
 
-#: lib/option.tcl:176
+#: lib/option.tcl:191
+msgid "Spelling Dictionary:"
+msgstr "Wörterbuch Rechtschreibprüfung:"
+
+#: lib/option.tcl:215
 msgid "Change Font"
 msgstr "Schriftart Ã¤ndern"
 
@@ -1729,11 +1782,11 @@ msgstr "Optionen konnten nicht gespeichert werden:"
 
 #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
 msgid "Delete Remote Branch"
-msgstr "Zweig aus anderem Projektarchiv löschen"
+msgstr "Zweig in anderem Projektarchiv löschen"
 
 #: lib/remote_branch_delete.tcl:47
 msgid "From Repository"
-msgstr "Von Projektarchiv"
+msgstr "In Projektarchiv"
 
 #: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
 msgid "Remote:"
@@ -1741,7 +1794,7 @@ msgstr "Anderes Archiv:"
 
 #: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
 msgid "Arbitrary URL:"
-msgstr "Kommunikation mit URL:"
+msgstr "Archiv-URL:"
 
 #: lib/remote_branch_delete.tcl:84
 msgid "Branches"
@@ -1749,11 +1802,11 @@ msgstr "Zweige"
 
 #: lib/remote_branch_delete.tcl:109
 msgid "Delete Only If"
-msgstr "Löschen, falls"
+msgstr "Nur löschen, wenn"
 
 #: lib/remote_branch_delete.tcl:111
 msgid "Merged Into:"
-msgstr "Zusammenführen mit:"
+msgstr "Zusammengeführt mit:"
 
 #: lib/remote_branch_delete.tcl:119
 msgid "Always (Do not perform merge checks)"
@@ -1815,7 +1868,7 @@ msgstr "»%s« laden..."
 
 #: lib/remote.tcl:165
 msgid "Prune from"
-msgstr "Entfernen von"
+msgstr "Aufräumen von"
 
 #: lib/remote.tcl:170
 msgid "Fetch from"
@@ -1833,6 +1886,26 @@ msgstr "Fehler beim Schreiben der Verknüpfung:"
 msgid "Cannot write icon:"
 msgstr "Fehler beim Erstellen des Icons:"
 
+#: lib/spellcheck.tcl:37
+msgid "Not connected to aspell"
+msgstr "Keine Verbindung zu Â»aspell«"
+
+#: lib/spellcheck.tcl:41
+msgid "Unrecognized aspell version"
+msgstr "Unbekannte Version von Â»aspell«"
+
+#: lib/spellcheck.tcl:135
+msgid "No Suggestions"
+msgstr "Keine Vorschläge"
+
+#: lib/spellcheck.tcl:336
+msgid "Unexpected EOF from aspell"
+msgstr "Unerwartetes EOF von Â»aspell«"
+
+#: lib/spellcheck.tcl:340
+msgid "Spell Checker Failed"
+msgstr "Rechtschreibprüfung fehlgeschlagen"
+
 #: lib/status_bar.tcl:83
 #, tcl-format
 msgid "%s ... %*i of %*i %s (%3i%%)"
@@ -1851,12 +1924,12 @@ msgstr "Neue Ã„nderungen von Â»%s« holen"
 #: lib/transport.tcl:18
 #, tcl-format
 msgid "remote prune %s"
-msgstr "Entfernen von Â»%s« aus anderem Archiv"
+msgstr "Aufräumen von Â»%s«"
 
 #: lib/transport.tcl:19
 #, tcl-format
 msgid "Pruning tracking branches deleted from %s"
-msgstr "Ãœbernahmezweige entfernen, die in Â»%s« gelöscht wurden"
+msgstr "Ãœbernahmezweige aufräumen und entfernen, die in Â»%s« gelöscht wurden"
 
 #: lib/transport.tcl:25 lib/transport.tcl:71
 #, tcl-format
@@ -1879,7 +1952,7 @@ msgstr "Zweige versenden"
 
 #: lib/transport.tcl:103
 msgid "Source Branches"
-msgstr "Herkunftszweige"
+msgstr "Lokale Zweige"
 
 #: lib/transport.tcl:120
 msgid "Destination Repository"
index dfa48ae26364b5d30a810bfa9b55984bba582692..2e332849fb21ab6931f671bd918e740c01589edc 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-11-24 10:36+0100\n"
+"POT-Creation-Date: 2008-02-16 21:24+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -386,31 +386,31 @@ msgstr ""
 msgid "File:"
 msgstr ""
 
-#: git-gui.sh:2545
-msgid "Refresh"
+#: git-gui.sh:2573
+msgid "Apply/Reverse Hunk"
 msgstr ""
 
-#: git-gui.sh:2566
-msgid "Apply/Reverse Hunk"
+#: git-gui.sh:2579
+msgid "Show Less Context"
 msgstr ""
 
-#: git-gui.sh:2572
-msgid "Decrease Font Size"
+#: git-gui.sh:2586
+msgid "Show More Context"
 msgstr ""
 
-#: git-gui.sh:2576
-msgid "Increase Font Size"
+#: git-gui.sh:2594
+msgid "Refresh"
 msgstr ""
 
-#: git-gui.sh:2581
-msgid "Show Less Context"
+#: git-gui.sh:2615
+msgid "Decrease Font Size"
 msgstr ""
 
-#: git-gui.sh:2588
-msgid "Show More Context"
+#: git-gui.sh:2619
+msgid "Increase Font Size"
 msgstr ""
 
-#: git-gui.sh:2602
+#: git-gui.sh:2630
 msgid "Unstage Hunk From Commit"
 msgstr ""
 
@@ -766,6 +766,10 @@ msgstr ""
 msgid "Updating working directory to '%s'..."
 msgstr ""
 
+#: lib/checkout_op.tcl:323
+msgid "files checked out"
+msgstr ""
+
 #: lib/checkout_op.tcl:353
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
@@ -1182,11 +1186,40 @@ msgid ""
 "- Remaining lines: Describe why this change is good.\n"
 msgstr ""
 
-#: lib/commit.tcl:257
+#: lib/commit.tcl:207
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr ""
+
+#: lib/commit.tcl:221
+msgid "Calling pre-commit hook..."
+msgstr ""
+
+#: lib/commit.tcl:236
+msgid "Commit declined by pre-commit hook."
+msgstr ""
+
+#: lib/commit.tcl:259
+msgid "Calling commit-msg hook..."
+msgstr ""
+
+#: lib/commit.tcl:274
+msgid "Commit declined by commit-msg hook."
+msgstr ""
+
+#: lib/commit.tcl:287
+msgid "Committing changes..."
+msgstr ""
+
+#: lib/commit.tcl:303
 msgid "write-tree failed:"
 msgstr ""
 
-#: lib/commit.tcl:275
+#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368
+msgid "Commit failed."
+msgstr ""
+
+#: lib/commit.tcl:321
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr ""
@@ -1204,12 +1237,7 @@ msgstr ""
 msgid "No changes to commit."
 msgstr ""
 
-#: lib/commit.tcl:303
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr ""
-
-#: lib/commit.tcl:317
+#: lib/commit.tcl:347
 msgid "commit-tree failed:"
 msgstr ""
 
@@ -1373,6 +1401,10 @@ msgstr ""
 msgid "Unstaging %s from commit"
 msgstr ""
 
+#: lib/index.tcl:313
+msgid "Ready to commit."
+msgstr ""
+
 #: lib/index.tcl:326
 #, tcl-format
 msgid "Adding %s"
@@ -1442,10 +1474,10 @@ msgstr ""
 
 #: lib/merge.tcl:119
 #, tcl-format
-msgid "Merging %s and %s"
+msgid "Merging %s and %s..."
 msgstr ""
 
-#: lib/merge.tcl:131
+#: lib/merge.tcl:130
 msgid "Merge completed successfully."
 msgstr ""
 
@@ -1491,7 +1523,11 @@ msgstr ""
 msgid "Aborting"
 msgstr ""
 
-#: lib/merge.tcl:266
+#: lib/merge.tcl:238
+msgid "files reset"
+msgstr ""
+
+#: lib/merge.tcl:265
 msgid "Abort failed."
 msgstr ""
 
@@ -1556,7 +1592,11 @@ msgstr ""
 msgid "New Branch Name Template"
 msgstr ""
 
-#: lib/option.tcl:176
+#: lib/option.tcl:191
+msgid "Spelling Dictionary:"
+msgstr ""
+
+#: lib/option.tcl:215
 msgid "Change Font"
 msgstr ""
 
@@ -1673,6 +1713,26 @@ msgstr ""
 msgid "Cannot write icon:"
 msgstr ""
 
+#: lib/spellcheck.tcl:37
+msgid "Not connected to aspell"
+msgstr ""
+
+#: lib/spellcheck.tcl:41
+msgid "Unrecognized aspell version"
+msgstr ""
+
+#: lib/spellcheck.tcl:135
+msgid "No Suggestions"
+msgstr ""
+
+#: lib/spellcheck.tcl:336
+msgid "Unexpected EOF from aspell"
+msgstr ""
+
+#: lib/spellcheck.tcl:340
+msgid "Spell Checker Failed"
+msgstr ""
+
 #: lib/status_bar.tcl:83
 #, tcl-format
 msgid "%s ... %*i of %*i %s (%3i%%)"
index 0b33c572bf769a7e5ca0c93875c1adc3a2697079..35764d1d22da45e90638b2db3e0bfbcb332e8696 100644 (file)
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui glossary\n"
 "POT-Creation-Date: 2008-01-07 21:20+0100\n"
-"PO-Revision-Date: 2008-01-15 20:32+0100\n"
+"PO-Revision-Date: 2008-02-16 21:48+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German \n"
 "MIME-Version: 1.0\n"
@@ -114,7 +114,7 @@ msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
 
 #. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
 msgid "prune"
-msgstr "entfernen"
+msgstr "aufräumen (entfernen?)"
 
 #. "Pulling a branch means to fetch it and merge it."
 msgid "pull"
diff --git a/git-help--browse.sh b/git-help--browse.sh
deleted file mode 100755 (executable)
index 10b0a36..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-#!/bin/sh
-#
-# This program launch a web browser on the html page
-# describing a git command.
-#
-# Copyright (c) 2007 Christian Couder
-# Copyright (c) 2006 Theodore Y. Ts'o
-#
-# This file is heavily stolen from git-mergetool.sh, by
-# Theodore Y. Ts'o (thanks) that is:
-#
-# Copyright (c) 2006 Theodore Y. Ts'o
-#
-# This file is licensed under the GPL v2, or a later version
-# at the discretion of Junio C Hamano or any other official
-# git maintainer.
-#
-
-USAGE='[--browser=browser|--tool=browser] [cmd to display] ...'
-
-# This must be capable of running outside of git directory, so
-# the vanilla git-sh-setup should not be used.
-NONGIT_OK=Yes
-. git-sh-setup
-
-# Install data.
-html_dir="@@HTMLDIR@@"
-
-test -f "$html_dir/git.html" || die "No documentation directory found."
-
-valid_tool() {
-       case "$1" in
-               firefox | iceweasel | konqueror | w3m | links | lynx | dillo)
-                       ;; # happy
-               *)
-                       return 1
-                       ;;
-       esac
-}
-
-init_browser_path() {
-       browser_path=`git config browser.$1.path`
-       test -z "$browser_path" && browser_path=$1
-}
-
-while test $# != 0
-do
-    case "$1" in
-       -b|--browser*|-t|--tool*)
-           case "$#,$1" in
-               *,*=*)
-                   browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-                   ;;
-               1,*)
-                   usage ;;
-               *)
-                   browser="$2"
-                   shift ;;
-           esac
-           ;;
-       --)
-           break
-           ;;
-       -*)
-           usage
-           ;;
-       *)
-           break
-           ;;
-    esac
-    shift
-done
-
-if test -z "$browser"
-then
-    for opt in "help.browser" "web.browser"
-    do
-       browser="`git config $opt`"
-       test -z "$browser" || break
-    done
-    if test -n "$browser" && ! valid_tool "$browser"; then
-           echo >&2 "git config option $opt set to unknown browser: $browser"
-           echo >&2 "Resetting to default..."
-           unset browser
-    fi
-fi
-
-if test -z "$browser" ; then
-    if test -n "$DISPLAY"; then
-       browser_candidates="firefox iceweasel konqueror w3m links lynx dillo"
-       if test "$KDE_FULL_SESSION" = "true"; then
-           browser_candidates="konqueror $browser_candidates"
-       fi
-    else
-       browser_candidates="w3m links lynx"
-    fi
-
-    for i in $browser_candidates; do
-       init_browser_path $i
-       if type "$browser_path" > /dev/null 2>&1; then
-           browser=$i
-           break
-       fi
-    done
-    test -z "$browser" && die "No known browser available."
-else
-    valid_tool "$browser" || die "Unknown browser '$browser'."
-
-    init_browser_path "$browser"
-
-    if ! type "$browser_path" > /dev/null 2>&1; then
-       die "The browser $browser is not available as '$browser_path'."
-    fi
-fi
-
-pages=$(for p in "$@"; do echo "$html_dir/$p.html" ; done)
-test -z "$pages" && pages="$html_dir/git.html"
-
-case "$browser" in
-    firefox|iceweasel)
-       # Check version because firefox < 2.0 does not support "-new-tab".
-       vers=$(expr "$($browser_path -version)" : '.* \([0-9][0-9]*\)\..*')
-       NEWTAB='-new-tab'
-       test "$vers" -lt 2 && NEWTAB=''
-       nohup "$browser_path" $NEWTAB $pages &
-       ;;
-    konqueror)
-       case "$(basename "$browser_path")" in
-           konqueror)
-               # It's simpler to use kfmclient to open a new tab in konqueror.
-               browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')"
-               type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found."
-               eval "$browser_path" newTab $pages
-               ;;
-           kfmclient)
-               eval "$browser_path" newTab $pages
-               ;;
-           *)
-               nohup "$browser_path" $pages &
-               ;;
-       esac
-       ;;
-    w3m|links|lynx)
-       eval "$browser_path" $pages
-       ;;
-    dillo)
-       nohup "$browser_path" $pages &
-       ;;
-esac
index 3e4452bc4bc1fe57c75848ccd9c073aeb065a980..6f91c8f845b2c4ef426a6451ac07abae2bce9488 100755 (executable)
@@ -24,8 +24,6 @@ restart        restart the web server
 fqgitdir="$GIT_DIR"
 local="`git config --bool --get instaweb.local`"
 httpd="`git config --get instaweb.httpd`"
-browser="`git config --get instaweb.browser`"
-test -z "$browser" && browser="`git config --get web.browser`"
 port=`git config --get instaweb.port`
 module_path="`git config --get instaweb.modulepath`"
 
@@ -36,9 +34,6 @@ conf="$GIT_DIR/gitweb/httpd.conf"
 # if installed, it doesn't need further configuration (module_path)
 test -z "$httpd" && httpd='lighttpd -f'
 
-# probably the most popular browser among gitweb users
-test -z "$browser" && browser='firefox'
-
 # any untaken local port will do...
 test -z "$port" && port=1234
 
@@ -274,14 +269,11 @@ webrick)
        ;;
 esac
 
-init_browser_path() {
-       browser_path="`git config browser.$1.path`"
-       test -z "$browser_path" && browser_path="$1"
-}
-
 start_httpd
 url=http://127.0.0.1:$port
-test -n "$browser" && {
-       init_browser_path "$browser"
-       "$browser_path" $url
-} || echo $url
+
+if test -n "$browser"; then
+       git web--browse -b "$browser" $url || echo $url
+else
+       git web--browse -c "instaweb.browser" $url || echo $url
+fi
index 03cd39873aef7b7ce00c4c2a826eab867ecc29ac..7dbbb1d79dc1d66db3a662045a13554f8d574004 100755 (executable)
@@ -71,7 +71,7 @@ finish_up_to_date () {
 squash_message () {
        echo Squashed commit of the following:
        echo
-       git log --no-merges ^"$head" $remoteheads
+       git log --no-merges --pretty=medium ^"$head" $remoteheads
 }
 
 finish () {
index cbbb707959cc64427f7bbd7bfefb0a8f0f263596..5c86f69229042c16704d11ce404e43297cc7b72c 100755 (executable)
@@ -34,7 +34,7 @@ base_present () {
 
 cleanup_temp_files () {
     if test "$1" = --save-backup ; then
-       mv -- "$BACKUP" "$path.orig"
+       mv -- "$BACKUP" "$MERGED.orig"
        rm -f -- "$LOCAL" "$REMOTE" "$BASE"
     else
        rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
@@ -67,14 +67,14 @@ resolve_symlink_merge () {
        read ans
        case "$ans" in
            [lL]*)
-               git checkout-index -f --stage=2 -- "$path"
-               git add -- "$path"
+               git checkout-index -f --stage=2 -- "$MERGED"
+               git add -- "$MERGED"
                cleanup_temp_files --save-backup
                return
                ;;
            [rR]*)
-               git checkout-index -f --stage=3 -- "$path"
-               git add -- "$path"
+               git checkout-index -f --stage=3 -- "$MERGED"
+               git add -- "$MERGED"
                cleanup_temp_files --save-backup
                return
                ;;
@@ -95,12 +95,12 @@ resolve_deleted_merge () {
        read ans
        case "$ans" in
            [mMcC]*)
-               git add -- "$path"
+               git add -- "$MERGED"
                cleanup_temp_files --save-backup
                return
                ;;
            [dD]*)
-               git rm -- "$path" > /dev/null
+               git rm -- "$MERGED" > /dev/null
                cleanup_temp_files
                return
                ;;
@@ -112,11 +112,11 @@ resolve_deleted_merge () {
 }
 
 check_unchanged () {
-    if test "$path" -nt "$BACKUP" ; then
+    if test "$MERGED" -nt "$BACKUP" ; then
        status=0;
     else
        while true; do
-           echo "$path seems unchanged."
+           echo "$MERGED seems unchanged."
            printf "Was the merge successful? [y/n] "
            read answer < /dev/tty
            case "$answer" in
@@ -127,50 +127,38 @@ check_unchanged () {
     fi
 }
 
-save_backup () {
-    if test "$status" -eq 0; then
-       mv -- "$BACKUP" "$path.orig"
-    fi
-}
-
-remove_backup () {
-    if test "$status" -eq 0; then
-       rm "$BACKUP"
-    fi
-}
-
 merge_file () {
-    path="$1"
+    MERGED="$1"
 
-    f=`git ls-files -u -- "$path"`
+    f=`git ls-files -u -- "$MERGED"`
     if test -z "$f" ; then
-       if test ! -f "$path" ; then
-           echo "$path: file not found"
+       if test ! -f "$MERGED" ; then
+           echo "$MERGED: file not found"
        else
-           echo "$path: file does not need merging"
+           echo "$MERGED: file does not need merging"
        fi
        exit 1
     fi
 
-    ext="$$$(expr "$path" : '.*\(\.[^/]*\)$')"
-    BACKUP="$path.BACKUP.$ext"
-    LOCAL="$path.LOCAL.$ext"
-    REMOTE="$path.REMOTE.$ext"
-    BASE="$path.BASE.$ext"
+    ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
+    BACKUP="$MERGED.BACKUP.$ext"
+    LOCAL="$MERGED.LOCAL.$ext"
+    REMOTE="$MERGED.REMOTE.$ext"
+    BASE="$MERGED.BASE.$ext"
 
-    mv -- "$path" "$BACKUP"
-    cp -- "$BACKUP" "$path"
+    mv -- "$MERGED" "$BACKUP"
+    cp -- "$BACKUP" "$MERGED"
 
-    base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'`
-    local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'`
-    remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'`
+    base_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}'`
+    local_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}'`
+    remote_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}'`
 
-    base_present   && git cat-file blob ":1:$prefix$path" >"$BASE" 2>/dev/null
-    local_present  && git cat-file blob ":2:$prefix$path" >"$LOCAL" 2>/dev/null
-    remote_present && git cat-file blob ":3:$prefix$path" >"$REMOTE" 2>/dev/null
+    base_present   && git cat-file blob ":1:$prefix$MERGED" >"$BASE" 2>/dev/null
+    local_present  && git cat-file blob ":2:$prefix$MERGED" >"$LOCAL" 2>/dev/null
+    remote_present && git cat-file blob ":3:$prefix$MERGED" >"$REMOTE" 2>/dev/null
 
     if test -z "$local_mode" -o -z "$remote_mode"; then
-       echo "Deleted merge conflict for '$path':"
+       echo "Deleted merge conflict for '$MERGED':"
        describe_file "$local_mode" "local" "$LOCAL"
        describe_file "$remote_mode" "remote" "$REMOTE"
        resolve_deleted_merge
@@ -178,14 +166,14 @@ merge_file () {
     fi
 
     if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
-       echo "Symbolic link merge conflict for '$path':"
+       echo "Symbolic link merge conflict for '$MERGED':"
        describe_file "$local_mode" "local" "$LOCAL"
        describe_file "$remote_mode" "remote" "$REMOTE"
        resolve_symlink_merge
        return
     fi
 
-    echo "Normal merge conflict for '$path':"
+    echo "Normal merge conflict for '$MERGED':"
     describe_file "$local_mode" "local" "$LOCAL"
     describe_file "$remote_mode" "remote" "$REMOTE"
     printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
@@ -194,36 +182,32 @@ merge_file () {
     case "$merge_tool" in
        kdiff3)
            if base_present ; then
-               ("$merge_tool_path" --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \
-                   -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
+               ("$merge_tool_path" --auto --L1 "$MERGED (Base)" --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" \
+                   -o "$MERGED" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
            else
-               ("$merge_tool_path" --auto --L1 "$path (Local)" --L2 "$path (Remote)" \
-                   -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
+               ("$merge_tool_path" --auto --L1 "$MERGED (Local)" --L2 "$MERGED (Remote)" \
+                   -o "$MERGED" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
            fi
            status=$?
-           remove_backup
            ;;
        tkdiff)
            if base_present ; then
-               "$merge_tool_path" -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
+               "$merge_tool_path" -a "$BASE" -o "$MERGED" -- "$LOCAL" "$REMOTE"
            else
-               "$merge_tool_path" -o "$path" -- "$LOCAL" "$REMOTE"
+               "$merge_tool_path" -o "$MERGED" -- "$LOCAL" "$REMOTE"
            fi
            status=$?
-           save_backup
            ;;
        meld|vimdiff)
            touch "$BACKUP"
-           "$merge_tool_path" -- "$LOCAL" "$path" "$REMOTE"
+           "$merge_tool_path" -- "$LOCAL" "$MERGED" "$REMOTE"
            check_unchanged
-           save_backup
            ;;
        gvimdiff)
-               touch "$BACKUP"
-               "$merge_tool_path" -f -- "$LOCAL" "$path" "$REMOTE"
-               check_unchanged
-               save_backup
-               ;;
+           touch "$BACKUP"
+           "$merge_tool_path" -f -- "$LOCAL" "$MERGED" "$REMOTE"
+           check_unchanged
+           ;;
        xxdiff)
            touch "$BACKUP"
            if base_present ; then
@@ -231,53 +215,68 @@ merge_file () {
                    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
                    -R 'Accel.Search: "Ctrl+F"' \
                    -R 'Accel.SearchForward: "Ctrl-G"' \
-                   --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE"
+                   --merged-file "$MERGED" -- "$LOCAL" "$BASE" "$REMOTE"
            else
                "$merge_tool_path" -X --show-merged-pane \
                    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
                    -R 'Accel.Search: "Ctrl+F"' \
                    -R 'Accel.SearchForward: "Ctrl-G"' \
-                   --merged-file "$path" -- "$LOCAL" "$REMOTE"
+                   --merged-file "$MERGED" -- "$LOCAL" "$REMOTE"
            fi
            check_unchanged
-           save_backup
            ;;
        opendiff)
            touch "$BACKUP"
            if base_present; then
-               "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat
+               "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED" | cat
            else
-               "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$path" | cat
+               "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED" | cat
            fi
            check_unchanged
-           save_backup
            ;;
        ecmerge)
            touch "$BACKUP"
            if base_present; then
-               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$path"
+               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$MERGED"
            else
-               "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$path"
+               "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$MERGED"
            fi
            check_unchanged
-           save_backup
            ;;
        emerge)
            if base_present ; then
-               "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")"
+               "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$MERGED")"
            else
-               "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")"
+               "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
            fi
            status=$?
-           save_backup
+           ;;
+       *)
+           if test -n "$merge_tool_cmd"; then
+               if test "$merge_tool_trust_exit_code" = "false"; then
+                   touch "$BACKUP"
+                   ( eval $merge_tool_cmd )
+                   check_unchanged
+               else
+                   ( eval $merge_tool_cmd )
+                   status=$?
+               fi
+           fi
            ;;
     esac
     if test "$status" -ne 0; then
-       echo "merge of $path failed" 1>&2
-       mv -- "$BACKUP" "$path"
+       echo "merge of $MERGED failed" 1>&2
+       mv -- "$BACKUP" "$MERGED"
        exit 1
     fi
-    git add -- "$path"
+
+    if test "$merge_keep_backup" = "true"; then
+       mv -- "$BACKUP" "$MERGED.orig"
+    else
+       rm -- "$BACKUP"
+    fi
+
+    git add -- "$MERGED"
     cleanup_temp_files
 }
 
@@ -309,12 +308,20 @@ do
     shift
 done
 
+valid_custom_tool()
+{
+    merge_tool_cmd="$(git config mergetool.$1.cmd)"
+    test -n "$merge_tool_cmd"
+}
+
 valid_tool() {
        case "$1" in
                kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
                        ;; # happy
                *)
-                       return 1
+                       if ! valid_custom_tool "$1"; then
+                               return 1
+                       fi
                        ;;
        esac
 }
@@ -380,10 +387,16 @@ else
 
     init_merge_tool_path "$merge_tool"
 
-    if ! type "$merge_tool_path" > /dev/null 2>&1; then
+    merge_keep_backup="$(git config --bool merge.keepBackup || echo true)"
+
+    if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
         echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
         exit 1
     fi
+
+    if ! test -z "$merge_tool_cmd"; then
+        merge_tool_trust_exit_code="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)"
+    fi
 fi
 
 
index 46da0f4ca2bfc2b30e4da8eb551565a06c876fb1..3ce32b5f211bd20f1d154c860eb5bf7f9005ca3c 100755 (executable)
@@ -174,6 +174,7 @@ fi
 
 merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
 test true = "$rebase" &&
-       exec git-rebase --onto $merge_head ${oldremoteref:-$merge_head}
+       exec git-rebase $strategy_args --onto $merge_head \
+       ${oldremoteref:-$merge_head}
 exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \
        "$merge_name" HEAD $merge_head
index 6b9af962a9cee734510518e82cf2537441ed843a..ff66af3ba8348ff86b25c91a1875b93f885a048c 100755 (executable)
@@ -18,8 +18,7 @@ original <branch> and remove the .dotest working files, use the command
 git rebase --abort instead.
 
 Note that if <branch> is not specified on the command line, the
-currently checked out branch is used.  You must be in the top
-directory of your project to start (or continue) a rebase.
+currently checked out branch is used.
 
 Example:       git-rebase master~1 topic
 
@@ -376,7 +375,7 @@ fi
 if test -z "$do_merge"
 then
        git format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
-       git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG" &&
+       git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
        move_to_original_branch
        ret=$?
        test 0 != $ret -a -d .dotest &&
index d13e4c1fea93f0c345f6638bfd8a3715c73fa693..b30ed734e7102b6a5d88c01364fa7d18db8f8bfe 100755 (executable)
@@ -1,15 +1,16 @@
 #!/usr/bin/perl -w
 
+use strict;
 use Git;
 my $git = Git->repository();
 
 sub add_remote_config {
        my ($hash, $name, $what, $value) = @_;
        if ($what eq 'url') {
-               if (exists $hash->{$name}{'URL'}) {
-                       print STDERR "Warning: more than one remote.$name.url\n";
+               # Having more than one is Ok -- it is used for push.
+               if (! exists $hash->{'URL'}) {
+                       $hash->{$name}{'URL'} = $value;
                }
-               $hash->{$name}{'URL'} = $value;
        }
        elsif ($what eq 'fetch') {
                $hash->{$name}{'FETCH'} ||= [];
@@ -296,12 +297,13 @@ sub add_remote {
 
 sub update_remote {
        my ($name) = @_;
+       my @remotes;
 
         my $conf = $git->config("remotes." . $name);
        if (defined($conf)) {
                @remotes = split(' ', $conf);
        } elsif ($name eq 'default') {
-               undef @remotes;
+               @remotes = ();
                for (sort keys %$remote) {
                        my $do_fetch = $git->config_bool("remote." . $_ .
                                                    ".skipDefaultUpdate");
@@ -341,7 +343,7 @@ sub rm_remote {
        my @refs = $git->command('for-each-ref',
                '--format=%(refname) %(objectname)', "refs/remotes/$name");
        for (@refs) {
-               ($ref, $object) = split;
+               my ($ref, $object) = split;
                $git->command(qw(update-ref -d), $ref, $object);
        }
        return 0;
@@ -352,7 +354,7 @@ sub add_usage {
        exit(1);
 }
 
-local $VERBOSE = 0;
+my $VERBOSE = 0;
 @ARGV = grep {
        if ($_ eq '-v' or $_ eq '--verbose') {
                $VERBOSE=1;
@@ -395,7 +397,7 @@ elsif ($ARGV[0] eq 'update') {
                update_remote("default");
                exit(1);
        }
-       for ($i = 1; $i < @ARGV; $i++) {
+       for (my $i = 1; $i < @ARGV; $i++) {
                update_remote($ARGV[$i]);
        }
 }
index e5d67f1b6c3237ff67673037be5ca8997cdb191d..be4a20d7cd562e9b00d93fe71313aed70afe50af 100755 (executable)
@@ -24,8 +24,6 @@ use Data::Dumper;
 use Term::ANSIColor;
 use Git;
 
-$SIG{INT} = sub { print color("reset"), "\n"; exit };
-
 package FakeTerm;
 sub new {
        my ($class, $reason) = @_;
@@ -88,6 +86,12 @@ Options:
 
    --smtp-ssl     If set, connects to the SMTP server using SSL.
 
+   --suppress-cc  Suppress the specified category of auto-CC.  The category
+                 can be one of 'author' for the patch author, 'self' to
+                 avoid copying yourself, 'sob' for Signed-off-by lines,
+                 'cccmd' for the output of the cccmd, or 'all' to suppress
+                 all of these.
+
    --suppress-from Suppress sending emails to yourself. Defaults to off.
 
    --thread       Specify that the "In-Reply-To:" header should be set on all
@@ -157,7 +161,7 @@ my $compose_filename = ".msg.$$";
 
 # Variables we fill in automatically, or via prompting:
 my (@to,@cc,@initial_cc,@bcclist,@xh,
-       $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time);
+       $initial_reply_to,$initial_subject,@files,$author,$sender,$smtp_authpass,$compose,$time);
 
 my $envelope_sender;
 
@@ -179,15 +183,16 @@ my ($quiet, $dry_run) = (0, 0);
 
 # Variables with corresponding config settings
 my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd);
-my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_authpass, $smtp_ssl);
+my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_ssl);
 my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
 my ($no_validate);
+my (@suppress_cc);
 
 my %config_bool_settings = (
     "thread" => [\$thread, 1],
     "chainreplyto" => [\$chain_reply_to, 1],
-    "suppressfrom" => [\$suppress_from, 0],
-    "signedoffcc" => [\$signed_off_cc, 1],
+    "suppressfrom" => [\$suppress_from, undef],
+    "signedoffcc" => [\$signed_off_cc, undef],
     "smtpssl" => [\$smtp_ssl, 0],
 );
 
@@ -201,8 +206,32 @@ my %config_settings = (
     "aliasfiletype" => \$aliasfiletype,
     "bcc" => \@bcclist,
     "aliasesfile" => \@alias_files,
+    "suppresscc" => \@suppress_cc,
 );
 
+# Handle Uncouth Termination
+sub signal_handler {
+
+       # Make text normal
+       print color("reset"), "\n";
+
+       # SMTP password masked
+       system "stty echo";
+
+       # tmp files from --compose
+       if (-e $compose_filename) {
+               print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
+       }
+       if (-e ($compose_filename . ".final")) {
+               print "'$compose_filename.final' contains the composed email.\n"
+       }
+
+       exit;
+};
+
+$SIG{TERM} = \&signal_handler;
+$SIG{INT}  = \&signal_handler;
+
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
 
@@ -216,13 +245,14 @@ my $rc = GetOptions("sender|from=s" => \$sender,
                    "smtp-server=s" => \$smtp_server,
                    "smtp-server-port=s" => \$smtp_server_port,
                    "smtp-user=s" => \$smtp_authuser,
-                   "smtp-pass=s" => \$smtp_authpass,
+                   "smtp-pass:s" => \$smtp_authpass,
                    "smtp-ssl!" => \$smtp_ssl,
                    "identity=s" => \$identity,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
                    "cc-cmd=s" => \$cc_cmd,
                    "suppress-from!" => \$suppress_from,
+                   "suppress-cc=s" => \@suppress_cc,
                    "signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
                    "dry-run" => \$dry_run,
                    "envelope-sender=s" => \$envelope_sender,
@@ -268,6 +298,35 @@ foreach my $setting (values %config_bool_settings) {
        ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]}));
 }
 
+# Set CC suppressions
+my(%suppress_cc);
+if (@suppress_cc) {
+       foreach my $entry (@suppress_cc) {
+               die "Unknown --suppress-cc field: '$entry'\n"
+                       unless $entry =~ /^(all|cccmd|cc|author|self|sob)$/;
+               $suppress_cc{$entry} = 1;
+       }
+}
+
+if ($suppress_cc{'all'}) {
+       foreach my $entry (qw (ccmd cc author self sob)) {
+               $suppress_cc{$entry} = 1;
+       }
+       delete $suppress_cc{'all'};
+}
+
+# If explicit old-style ones are specified, they trump --suppress-cc.
+$suppress_cc{'self'} = $suppress_from if defined $suppress_from;
+$suppress_cc{'sob'} = !$signed_off_cc if defined $signed_off_cc;
+
+# Debugging, print out the suppressions.
+if (0) {
+       print "suppressions:\n";
+       foreach my $entry (keys %suppress_cc) {
+               printf "  %-5s -> $suppress_cc{$entry}\n", $entry;
+       }
+}
+
 my ($repoauthor) = $repo->ident_person('author');
 my ($repocommitter) = $repo->ident_person('committer');
 
@@ -357,9 +416,12 @@ if (@files) {
 my $prompting = 0;
 if (!defined $sender) {
        $sender = $repoauthor || $repocommitter;
-       do {
+
+       while (1) {
                $_ = $term->readline("Who should the emails appear to be from? [$sender] ");
-       } while (!defined $_);
+               last if defined $_;
+               print "\n";
+       }
 
        $sender = $_ if ($_);
        print "Emails will be sent from: ", $sender, "\n";
@@ -367,10 +429,14 @@ if (!defined $sender) {
 }
 
 if (!@to) {
-       do {
-               $_ = $term->readline("Who should the emails be sent to? ",
-                               "");
-       } while (!defined $_);
+
+
+       while (1) {
+               $_ = $term->readline("Who should the emails be sent to? ", "");
+               last if defined $_;
+               print "\n";
+       }
+
        my $to = $_;
        push @to, split /,/, $to;
        $prompting++;
@@ -392,19 +458,22 @@ sub expand_aliases {
 @bcclist = expand_aliases(@bcclist);
 
 if (!defined $initial_subject && $compose) {
-       do {
-               $_ = $term->readline("What subject should the initial email start with? ",
-                       $initial_subject);
-       } while (!defined $_);
+       while (1) {
+               $_ = $term->readline("What subject should the initial email start with? ", $initial_subject);
+               last if defined $_;
+               print "\n";
+       }
+
        $initial_subject = $_;
        $prompting++;
 }
 
 if ($thread && !defined $initial_reply_to && $prompting) {
-       do {
-               $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
-                       $initial_reply_to);
-       } while (!defined $_);
+       while (1) {
+               $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ", $initial_reply_to);
+               last if defined $_;
+               print "\n";
+       }
 
        $initial_reply_to = $_;
 }
@@ -456,9 +525,11 @@ EOT
        close(C);
        close(C2);
 
-       do {
+       while (1) {
                $_ = $term->readline("Send this email? (y|n) ");
-       } while (!defined $_);
+               last if defined $_;
+               print "\n";
+       }
 
        if (uc substr($_,0,1) ne 'Y') {
                cleanup_compose_files();
@@ -650,9 +721,26 @@ X-Mailer: git-send-email $gitversion
                        die "Unable to initialize SMTP properly.  Is there something wrong with your config?";
                }
 
-               if ((defined $smtp_authuser) && (defined $smtp_authpass)) {
+               if (defined $smtp_authuser) {
+
+                       if (!defined $smtp_authpass) {
+
+                               system "stty -echo";
+
+                               do {
+                                       print "Password: ";
+                                       $_ = <STDIN>;
+                                       print "\n";
+                               } while (!defined $_);
+
+                               chomp($smtp_authpass = $_);
+
+                               system "stty echo";
+                       }
+
                        $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message;
                }
+
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
                $smtp->data or die $smtp->message;
@@ -714,11 +802,14 @@ foreach my $t (@files) {
 
                                } elsif (/^(Cc|From):\s+(.*)$/) {
                                        if (unquote_rfc2047($2) eq $sender) {
-                                               next if ($suppress_from);
+                                               next if ($suppress_cc{'self'});
                                        }
                                        elsif ($1 eq 'From') {
                                                ($author, $author_encoding)
                                                  = unquote_rfc2047($2);
+                                               next if ($suppress_cc{'author'});
+                                       } else {
+                                               next if ($suppress_cc{'cc'});
                                        }
                                        printf("(mbox) Adding cc: %s from line '%s'\n",
                                                $2, $_) unless $quiet;
@@ -745,7 +836,7 @@ foreach my $t (@files) {
                                # line 2 = subject
                                # So let's support that, too.
                                $input_format = 'lots';
-                               if (@cc == 0) {
+                               if (@cc == 0 && !$suppress_cc{'cc'}) {
                                        printf("(non-mbox) Adding cc: %s from line '%s'\n",
                                                $_, $_) unless $quiet;
 
@@ -762,10 +853,12 @@ foreach my $t (@files) {
                        }
                } else {
                        $message .=  $_;
-                       if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) {
+                       if (/^(Signed-off-by|Cc): (.*)$/i) {
+                               next if ($suppress_cc{'sob'});
+                               chomp;
                                my $c = $2;
                                chomp $c;
-                               next if ($c eq $sender and $suppress_from);
+                               next if ($c eq $sender and $suppress_cc{'self'});
                                push @cc, $c;
                                printf("(sob) Adding cc: %s from line '%s'\n",
                                        $c, $_) unless $quiet;
@@ -774,7 +867,7 @@ foreach my $t (@files) {
        }
        close F;
 
-       if (defined $cc_cmd) {
+       if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
                open(F, "$cc_cmd $t |")
                        or die "(cc-cmd) Could not execute '$cc_cmd'";
                while(<F>) {
index f38827529f2fd60743f5571948742fada975cf93..a44b1c74a38d6310d7a1bde969cbbcb51f977188 100755 (executable)
@@ -127,20 +127,14 @@ get_author_ident_from_commit () {
 # if we require to be in a git repository.
 if test -z "$NONGIT_OK"
 then
+       GIT_DIR=$(git rev-parse --git-dir) || exit
        if [ -z "$SUBDIRECTORY_OK" ]
        then
-               : ${GIT_DIR=.git}
                test -z "$(git rev-parse --show-cdup)" || {
                        exit=$?
                        echo >&2 "You need to run this command from the toplevel of the working tree."
                        exit $exit
                }
-       else
-               GIT_DIR=$(git rev-parse --git-dir) || {
-                   exit=$?
-                   echo >&2 "Failed to find a valid git directory."
-                   exit $exit
-               }
        fi
        test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || {
                echo >&2 "Unable to determine absolute path of git directory"
index b00f8881693ff09516ea058816dc96697ca56483..c2b68205a2b69eb91b2403e11238093fc65c04ef 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 # Copyright (c) 2007, Nanako Shiraishi
 
-USAGE='[  | save | list | show | apply | clear | create ]'
+USAGE='[  | save | list | show | apply | clear | drop | pop | create ]'
 
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
@@ -196,6 +196,28 @@ apply_stash () {
        fi
 }
 
+drop_stash () {
+       have_stash || die 'No stash entries to drop'
+
+       if test $# = 0
+       then
+               set x "$ref_stash@{0}"
+               shift
+       fi
+       # Verify supplied argument looks like a stash entry
+       s=$(git rev-parse --revs-only --no-flags "$@") &&
+       git rev-parse --verify "$s:"   > /dev/null 2>&1 &&
+       git rev-parse --verify "$s^1:" > /dev/null 2>&1 &&
+       git rev-parse --verify "$s^2:" > /dev/null 2>&1 ||
+               die "$*: not a valid stashed state"
+
+       git reflog delete --updateref --rewrite "$@" &&
+               echo "Dropped $* ($s)" || die "$*: Could not drop stash entry"
+
+       # clear_stash if we just dropped the last stash entry
+       git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
+}
+
 # Main command set
 case "$1" in
 list)
@@ -230,6 +252,18 @@ create)
        fi
        create_stash "$*" && echo "$w_commit"
        ;;
+drop)
+       shift
+       drop_stash "$@"
+       ;;
+pop)
+       shift
+       if apply_stash "$@"
+       then
+               test -z "$unstash_index" || shift
+               drop_stash "$@"
+       fi
+       ;;
 *)
        if test $# -eq 0
        then
index 67d3224c8c4c7106287aeb095ea545fe7a26b726..7171cb60ab4e6a49b84500bde43c04ea40501d6c 100755 (executable)
@@ -153,20 +153,6 @@ cmd_add()
                usage
        fi
 
-       case "$repo" in
-       ./*|../*)
-               # dereference source url relative to parent's url
-               realrepo="$(resolve_relative_url $repo)" ;;
-       *)
-               # Turn the source into an absolute path if
-               # it is local
-               if base=$(get_repo_base "$repo"); then
-                       repo="$base"
-               fi
-               realrepo=$repo
-               ;;
-       esac
-
        # Guess path from repo if not specified or strip trailing slashes
        if test -z "$path"; then
                path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
@@ -174,15 +160,39 @@ cmd_add()
                path=$(echo "$path" | sed -e 's|/*$||')
        fi
 
-       test -e "$path" &&
-       die "'$path' already exists"
-
        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
        die "'$path' already exists in the index"
 
-       module_clone "$path" "$realrepo" || exit
-       (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
-       die "Unable to checkout submodule '$path'"
+       # perhaps the path exists and is already a git repo, else clone it
+       if test -e "$path"
+       then
+               if test -d "$path/.git" &&
+               test "$(unset GIT_DIR; cd $path; git rev-parse --git-dir)" = ".git"
+               then
+                       echo "Adding existing repo at '$path' to the index"
+               else
+                       die "'$path' already exists and is not a valid git repo"
+               fi
+       else
+               case "$repo" in
+               ./*|../*)
+                       # dereference source url relative to parent's url
+                       realrepo="$(resolve_relative_url $repo)" ;;
+               *)
+                       # Turn the source into an absolute path if
+                       # it is local
+                       if base=$(get_repo_base "$repo"); then
+                               repo="$base"
+                       fi
+                       realrepo=$repo
+                       ;;
+               esac
+
+               module_clone "$path" "$realrepo" || exit
+               (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
+               die "Unable to checkout submodule '$path'"
+       fi
+
        git add "$path" ||
        die "Failed to add submodule '$path'"
 
index 38e1d5944d309f6b1f4b58fd5857a1bfb0545cc9..d8b38c9a47c0b77dd55781a66e42e4dec26fd4b7 100755 (executable)
@@ -186,6 +186,9 @@ my %cmd = (
                    "Show info about the latest SVN revision
                     on the current branch",
                    { 'url' => \$_url, } ],
+       'blame' => [ \&Git::SVN::Log::cmd_blame,
+                   "Show what revision and author last modified each line of a file",
+                   {} ],
 );
 
 my $cmd;
@@ -1248,7 +1251,8 @@ use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
 use IPC::Open3;
 
-my $_repack_nr;
+my ($_gc_nr, $_gc_period);
+
 # properties that we do not log:
 my %SKIP_PROP;
 BEGIN {
@@ -1409,9 +1413,10 @@ sub read_all_remotes {
 }
 
 sub init_vars {
-       $_repack = 1000 unless (defined $_repack && $_repack > 0);
-       $_repack_nr = $_repack;
-       $_repack_flags ||= '-d';
+       $_gc_nr = $_gc_period = 1000;
+       if (defined $_repack || defined $_repack_flags) {
+              warn "Repack options are obsolete; they have no effect.\n";
+       }
 }
 
 sub verify_remotes_sanity {
@@ -2102,6 +2107,10 @@ sub restore_commit_header_env {
        }
 }
 
+sub gc {
+       command_noisy('gc', '--auto');
+};
+
 sub do_git_commit {
        my ($self, $log_entry) = @_;
        my $lr = $self->last_rev;
@@ -2155,12 +2164,9 @@ sub do_git_commit {
                                   0, $self->svm_uuid);
        }
        print " = $commit ($self->{ref_id})\n";
-       if ($_repack && (--$_repack_nr == 0)) {
-               $_repack_nr = $_repack;
-               # repack doesn't use any arguments with spaces in them, does it?
-               print "Running git repack $_repack_flags ...\n";
-               command_noisy('repack', split(/\s+/, $_repack_flags));
-               print "Done repacking\n";
+       if (--$_gc_nr == 0) {
+               $_gc_nr = $_gc_period;
+               gc();
        }
        return $commit;
 }
@@ -2232,7 +2238,12 @@ sub find_parent_branch {
                # just grow a tail if we're not unique enough :x
                $ref_id .= '-' while find_ref($ref_id);
                print STDERR "Initializing parent: $ref_id\n";
-               $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1);
+               my ($u, $p) = ($new_url, '');
+               if ($u =~ s#^\Q$url\E(/|$)##) {
+                       $p = $u;
+                       $u = $url;
+               }
+               $gs = Git::SVN->init($u, $p, $self->{repo_id}, $ref_id, 1);
        }
        my ($r0, $parent) = $gs->find_rev_before($r, 1);
        if (!defined $r0 || !defined $parent) {
@@ -3990,6 +4001,7 @@ sub gs_fetch_loop_common {
                $max += $inc;
                $max = $head if ($max > $head);
        }
+       Git::SVN::gc();
 }
 
 sub match_globs {
@@ -4446,6 +4458,24 @@ out:
        print commit_log_separator unless $incremental || $oneline;
 }
 
+sub cmd_blame {
+       my $path = shift;
+
+       config_pager();
+       run_pager();
+
+       my ($fh, $ctx) = command_output_pipe('blame', @_, $path);
+       while (my $line = <$fh>) {
+               if ($line =~ /^\^?([[:xdigit:]]+)\s/) {
+                       my (undef, $rev, undef) = ::cmt_metadata($1);
+                       $rev = sprintf('%-10s', $rev);
+                       $line =~ s/^\^?[[:xdigit:]]+(\s)/$rev$1/;
+               }
+               print $line;
+       }
+       command_close_pipe($fh, $ctx);
+}
+
 package Git::SVN::Migration;
 # these version numbers do NOT correspond to actual version numbers
 # of git nor git-svn.  They are just relative.
diff --git a/git-web--browse.sh b/git-web--browse.sh
new file mode 100755 (executable)
index 0000000..1023b90
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# This program launch a web browser on the html page
+# describing a git command.
+#
+# Copyright (c) 2007 Christian Couder
+# Copyright (c) 2006 Theodore Y. Ts'o
+#
+# This file is heavily stolen from git-mergetool.sh, by
+# Theodore Y. Ts'o (thanks) that is:
+#
+# Copyright (c) 2006 Theodore Y. Ts'o
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Junio C Hamano or any other official
+# git maintainer.
+#
+
+USAGE='[--browser=browser|--tool=browser] [--config=conf.var] url/file ...'
+
+# This must be capable of running outside of git directory, so
+# the vanilla git-sh-setup should not be used.
+NONGIT_OK=Yes
+. git-sh-setup
+
+valid_tool() {
+       case "$1" in
+               firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open)
+                       ;; # happy
+               *)
+                       return 1
+                       ;;
+       esac
+}
+
+init_browser_path() {
+       browser_path=$(git config "browser.$1.path")
+       test -z "$browser_path" && browser_path="$1"
+}
+
+while test $# != 0
+do
+    case "$1" in
+       -b|--browser*|-t|--tool*)
+           case "$#,$1" in
+               *,*=*)
+                   browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+                   ;;
+               1,*)
+                   usage ;;
+               *)
+                   browser="$2"
+                   shift ;;
+           esac
+           ;;
+       -c|--config*)
+           case "$#,$1" in
+               *,*=*)
+                   conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+                   ;;
+               1,*)
+                   usage ;;
+               *)
+                   conf="$2"
+                   shift ;;
+           esac
+           ;;
+       --)
+           break
+           ;;
+       -*)
+           usage
+           ;;
+       *)
+           break
+           ;;
+    esac
+    shift
+done
+
+test $# = 0 && usage
+
+if test -z "$browser"
+then
+    for opt in "$conf" "web.browser"
+    do
+       test -z "$opt" && continue
+       browser="`git config $opt`"
+       test -z "$browser" || break
+    done
+    if test -n "$browser" && ! valid_tool "$browser"; then
+       echo >&2 "git config option $opt set to unknown browser: $browser"
+       echo >&2 "Resetting to default..."
+       unset browser
+    fi
+fi
+
+if test -z "$browser" ; then
+    if test -n "$DISPLAY"; then
+       browser_candidates="firefox iceweasel konqueror w3m links lynx dillo"
+       if test "$KDE_FULL_SESSION" = "true"; then
+           browser_candidates="konqueror $browser_candidates"
+       fi
+    else
+       browser_candidates="w3m links lynx"
+    fi
+    # SECURITYSESSIONID indicates an OS X GUI login session
+    if test -n "$SECURITYSESSIONID"; then
+       browser_candidates="open $browser_candidates"
+    fi
+
+    for i in $browser_candidates; do
+       init_browser_path $i
+       if type "$browser_path" > /dev/null 2>&1; then
+           browser=$i
+           break
+       fi
+    done
+    test -z "$browser" && die "No known browser available."
+else
+    valid_tool "$browser" || die "Unknown browser '$browser'."
+
+    init_browser_path "$browser"
+
+    if ! type "$browser_path" > /dev/null 2>&1; then
+       die "The browser $browser is not available as '$browser_path'."
+    fi
+fi
+
+case "$browser" in
+    firefox|iceweasel)
+       # Check version because firefox < 2.0 does not support "-new-tab".
+       vers=$(expr "$($browser_path -version)" : '.* \([0-9][0-9]*\)\..*')
+       NEWTAB='-new-tab'
+       test "$vers" -lt 2 && NEWTAB=''
+       "$browser_path" $NEWTAB "$@" &
+       ;;
+    konqueror)
+       case "$(basename "$browser_path")" in
+           konqueror)
+               # It's simpler to use kfmclient to open a new tab in konqueror.
+               browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')"
+               type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found."
+               eval "$browser_path" newTab "$@"
+               ;;
+           kfmclient)
+               eval "$browser_path" newTab "$@"
+               ;;
+           *)
+               "$browser_path" "$@" &
+               ;;
+       esac
+       ;;
+    w3m|links|lynx|open)
+       eval "$browser_path" "$@"
+       ;;
+    dillo)
+       "$browser_path" "$@" &
+       ;;
+esac
diff --git a/git.c b/git.c
index 0cb86884d738a8314f164c82e4cc619a9998f3db..9cca81a60e6d4f93cf3132b76fd8f147a6a5d98f 100644 (file)
--- a/git.c
+++ b/git.c
@@ -87,19 +87,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
        return handled;
 }
 
-static const char *alias_command;
-static char *alias_string;
-
-static int git_alias_config(const char *var, const char *value)
-{
-       if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) {
-               if (!value)
-                       return config_error_nonbool(var);
-               alias_string = xstrdup(value);
-       }
-       return 0;
-}
-
 static int split_cmdline(char *cmdline, const char ***argv)
 {
        int src, dst, count = 0, size = 16;
@@ -159,11 +146,13 @@ static int handle_alias(int *argcp, const char ***argv)
        const char *subdir;
        int count, option_count;
        const char** new_argv;
+       const char *alias_command;
+       char *alias_string;
 
        subdir = setup_git_directory_gently(&nongit);
 
        alias_command = (*argv)[0];
-       git_config(git_alias_config);
+       alias_string = alias_lookup(alias_command);
        if (alias_string) {
                if (alias_string[0] == '!') {
                        if (*argcp > 1) {
@@ -289,6 +278,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "branch", cmd_branch, RUN_SETUP },
                { "bundle", cmd_bundle },
                { "cat-file", cmd_cat_file, RUN_SETUP },
+               { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
                { "checkout-index", cmd_checkout_index,
                        RUN_SETUP | NEED_WORK_TREE},
                { "check-ref-format", cmd_check_ref_format },
@@ -332,6 +322,8 @@ static void handle_internal_command(int argc, const char **argv)
                { "merge-base", cmd_merge_base, RUN_SETUP },
                { "merge-file", cmd_merge_file },
                { "merge-ours", cmd_merge_ours, RUN_SETUP },
+               { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
                { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
index 5560e4dc560587c4a551c24a1ee59df394726fc9..f1f21e97bf5e89bd7609cf5b6d1f417e35a43b6c 100644 (file)
@@ -240,11 +240,12 @@ proc getcommitlines {fd view}  {
        set listed 1
        if {$j >= 0 && [string match "commit *" $cmit]} {
            set ids [string range $cmit 7 [expr {$j - 1}]]
-           if {[string match {[-<>]*} $ids]} {
+           if {[string match {[-^<>]*} $ids]} {
                switch -- [string index $ids 0] {
                    "-" {set listed 0}
-                   "<" {set listed 2}
-                   ">" {set listed 3}
+                   "^" {set listed 2}
+                   "<" {set listed 3}
+                   ">" {set listed 4}
                }
                set ids [string range $ids 1 end]
            }
@@ -632,6 +633,7 @@ proc makewindow {} {
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
     global diffcontextstring diffcontext
+    global ignorespace
     global maincursor textcursor curtextcursor
     global rowctxmenu fakerowmenu mergemax wrapcomment
     global highlight_files gdttype
@@ -849,6 +851,9 @@ proc makewindow {} {
     trace add variable diffcontextstring write diffcontextchange
     lappend entries .bleft.mid.diffcontext
     pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
+    checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
+       -command changeignorespace -variable ignorespace
+    pack .bleft.mid.ignspace -side left -padx 5
     set ctext .bleft.ctext
     text $ctext -background $bgcolor -foreground $fgcolor \
        -state disabled -font textfont \
@@ -1307,45 +1312,45 @@ proc keys {} {
     }
     toplevel $w
     wm title $w [mc "Gitk key bindings"]
-    message $w.m -text [mc "
-Gitk key bindings:
-
-<$M1T-Q>               Quit
-<Home>         Move to first commit
-<End>          Move to last commit
-<Up>, p, i     Move up one commit
-<Down>, n, k   Move down one commit
-<Left>, z, j   Go back in history list
-<Right>, x, l  Go forward in history list
-<PageUp>       Move up one page in commit list
-<PageDown>     Move down one page in commit list
-<$M1T-Home>    Scroll to top of commit list
-<$M1T-End>     Scroll to bottom of commit list
-<$M1T-Up>      Scroll commit list up one line
-<$M1T-Down>    Scroll commit list down one line
-<$M1T-PageUp>  Scroll commit list up one page
-<$M1T-PageDown>        Scroll commit list down one page
-<Shift-Up>     Find backwards (upwards, later commits)
-<Shift-Down>   Find forwards (downwards, earlier commits)
-<Delete>, b    Scroll diff view up one page
-<Backspace>    Scroll diff view up one page
-<Space>                Scroll diff view down one page
-u              Scroll diff view up 18 lines
-d              Scroll diff view down 18 lines
-<$M1T-F>               Find
-<$M1T-G>               Move to next find hit
-<Return>       Move to next find hit
-/              Move to next find hit, or redo find
-?              Move to previous find hit
-f              Scroll diff view to next file
-<$M1T-S>               Search for next hit in diff view
-<$M1T-R>               Search for previous hit in diff view
-<$M1T-KP+>     Increase font size
-<$M1T-plus>    Increase font size
-<$M1T-KP->     Decrease font size
-<$M1T-minus>   Decrease font size
-<F5>           Update
-"] \
+    message $w.m -text "
+[mc "Gitk key bindings:"]
+
+[mc "<%s-Q>            Quit" $M1T]
+[mc "<Home>            Move to first commit"]
+[mc "<End>             Move to last commit"]
+[mc "<Up>, p, i        Move up one commit"]
+[mc "<Down>, n, k      Move down one commit"]
+[mc "<Left>, z, j      Go back in history list"]
+[mc "<Right>, x, l     Go forward in history list"]
+[mc "<PageUp>  Move up one page in commit list"]
+[mc "<PageDown>        Move down one page in commit list"]
+[mc "<%s-Home> Scroll to top of commit list" $M1T]
+[mc "<%s-End>  Scroll to bottom of commit list" $M1T]
+[mc "<%s-Up>   Scroll commit list up one line" $M1T]
+[mc "<%s-Down> Scroll commit list down one line" $M1T]
+[mc "<%s-PageUp>       Scroll commit list up one page" $M1T]
+[mc "<%s-PageDown>     Scroll commit list down one page" $M1T]
+[mc "<Shift-Up>        Find backwards (upwards, later commits)"]
+[mc "<Shift-Down>      Find forwards (downwards, earlier commits)"]
+[mc "<Delete>, b       Scroll diff view up one page"]
+[mc "<Backspace>       Scroll diff view up one page"]
+[mc "<Space>           Scroll diff view down one page"]
+[mc "u         Scroll diff view up 18 lines"]
+[mc "d         Scroll diff view down 18 lines"]
+[mc "<%s-F>            Find" $M1T]
+[mc "<%s-G>            Move to next find hit" $M1T]
+[mc "<Return>  Move to next find hit"]
+[mc "/         Move to next find hit, or redo find"]
+[mc "?         Move to previous find hit"]
+[mc "f         Scroll diff view to next file"]
+[mc "<%s-S>            Search for next hit in diff view" $M1T]
+[mc "<%s-R>            Search for previous hit in diff view" $M1T]
+[mc "<%s-KP+>  Increase font size" $M1T]
+[mc "<%s-plus> Increase font size" $M1T]
+[mc "<%s-KP->  Decrease font size" $M1T]
+[mc "<%s-minus>        Decrease font size" $M1T]
+[mc "<F5>              Update"]
+" \
            -justify left -bg white -border 2 -relief groove
     pack $w.m -side top -fill both -padx 2 -pady 2
     button $w.ok -text [mc "Close"] -command "destroy $w" -default active
@@ -3627,23 +3632,23 @@ proc drawcmittext {id row col} {
     global linehtag linentag linedtag selectedline
     global canvxmax boldrows boldnamerows fgcolor nullid nullid2
 
-    # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
+    # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
     set listed [lindex $commitlisted $row]
     if {$id eq $nullid} {
        set ofill red
     } elseif {$id eq $nullid2} {
        set ofill green
     } else {
-       set ofill [expr {$listed != 0? "blue": "white"}]
+       set ofill [expr {$listed != 0 ? $listed == 2 ? "gray" : "blue" : "white"}]
     }
     set x [xc $row $col]
     set y [yc $row]
     set orad [expr {$linespc / 3}]
-    if {$listed <= 1} {
+    if {$listed <= 2} {
        set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
                   [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
-    } elseif {$listed == 2} {
+    } elseif {$listed == 3} {
        # triangle pointing left for left-side commits
        set t [$canv create polygon \
                   [expr {$x - $orad}] $y \
@@ -5027,13 +5032,14 @@ proc getblobline {bf id} {
 proc mergediff {id l} {
     global diffmergeid mdifffd
     global diffids
+    global diffcontext
     global parentlist
     global limitdiffs viewfiles curview
 
     set diffmergeid $id
     set diffids $id
     # this doesn't seem to actually affect anything...
-    set cmd [concat | git diff-tree --no-commit-id --cc $id]
+    set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
     if {$limitdiffs && $viewfiles($curview) ne {}} {
        set cmd [concat $cmd -- $viewfiles($curview)]
     }
@@ -5270,13 +5276,21 @@ proc diffcontextchange {n1 n2 op} {
     }
 }
 
+proc changeignorespace {} {
+    reselectline
+}
+
 proc getblobdiffs {ids} {
     global blobdifffd diffids env
     global diffinhdr treediffs
     global diffcontext
+    global ignorespace
     global limitdiffs viewfiles curview
 
     set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
+    if {$ignorespace} {
+       append cmd " -w"
+    }
     if {$limitdiffs && $viewfiles($curview) ne {}} {
        set cmd [concat $cmd -- $viewfiles($curview)]
     }
@@ -6137,11 +6151,7 @@ proc domktag {} {
        return
     }
     if {[catch {
-       set dir [gitdir]
-       set fname [file join $dir "refs/tags" $tag]
-       set f [open $fname w]
-       puts $f $id
-       close $f
+       exec git tag $tag $id
     } err]} {
        error_popup "[mc "Error creating tag:"] $err"
        return
@@ -8459,6 +8469,7 @@ set bgcolor white
 set fgcolor black
 set diffcolors {red "#00a000" blue}
 set diffcontext 3
+set ignorespace 0
 set selectbgcolor gray85
 
 ## For msgcat loading, first locate the installation location.
index 4c8bedf7449266229e8102efac249e68fd9d8b39..2163071047a0f4a7620eebf2f4e18008f592c049 100644 (file)
@@ -233,6 +233,10 @@ You can use the following files in repository:
    Displayed in the project summary page. You can use multiple-valued
    gitweb.url repository configuration variable for that, but the file
    takes precendence.
+ * gitweb.owner
+   You can use the gitweb.owner repository configuration variable to set
+   repository's owner. It is displayed in the project list and summary
+   page. If it's not set, filesystem directory's owner is used.
  * various gitweb.* config variables (in config)
    Read description of %feature hash for detailed list, and some
    descriptions.
index ae2d05763fc2d41c9bffdad8809e4e370389412c..ec73cb1256ba99a990648c548348ecdc3340a592 100755 (executable)
@@ -472,13 +472,15 @@ if (defined $searchtype) {
        }
 }
 
+our $search_use_regexp = $cgi->param('sr');
+
 our $searchtext = $cgi->param('s');
 our $search_regexp;
 if (defined $searchtext) {
        if (length($searchtext) < 2) {
                die_error(undef, "At least two characters are required for search parameter");
        }
-       $search_regexp = quotemeta $searchtext;
+       $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
 }
 
 # now read PATH_INFO and use it as alternative to parameters
@@ -608,9 +610,12 @@ sub href(%) {
                searchtype => "st",
                snapshot_format => "sf",
                extra_options => "opt",
+               search_use_regexp => "sr",
        );
        my %mapping = @mapping;
 
+       $params{'project'} = $project unless exists $params{'project'};
+
        if ($params{-replay}) {
                while (my ($name, $symbol) = each %mapping) {
                        if (!exists $params{$name}) {
@@ -620,8 +625,6 @@ sub href(%) {
                }
        }
 
-       $params{'project'} = $project unless exists $params{'project'};
-
        my ($use_pathinfo) = gitweb_check_feature('pathinfo');
        if ($use_pathinfo) {
                # use PATH_INFO for project name
@@ -753,29 +756,40 @@ sub esc_path {
 # Make control characters "printable", using character escape codes (CEC)
 sub quot_cec {
        my $cntrl = shift;
+       my %opts = @_;
        my %es = ( # character escape codes, aka escape sequences
-                  "\t" => '\t',   # tab            (HT)
-                  "\n" => '\n',   # line feed      (LF)
-                  "\r" => '\r',   # carrige return (CR)
-                  "\f" => '\f',   # form feed      (FF)
-                  "\b" => '\b',   # backspace      (BS)
-                  "\a" => '\a',   # alarm (bell)   (BEL)
-                  "\e" => '\e',   # escape         (ESC)
-                  "\013" => '\v', # vertical tab   (VT)
-                  "\000" => '\0', # nul character  (NUL)
-                  );
+               "\t" => '\t',   # tab            (HT)
+               "\n" => '\n',   # line feed      (LF)
+               "\r" => '\r',   # carrige return (CR)
+               "\f" => '\f',   # form feed      (FF)
+               "\b" => '\b',   # backspace      (BS)
+               "\a" => '\a',   # alarm (bell)   (BEL)
+               "\e" => '\e',   # escape         (ESC)
+               "\013" => '\v', # vertical tab   (VT)
+               "\000" => '\0', # nul character  (NUL)
+       );
        my $chr = ( (exists $es{$cntrl})
                    ? $es{$cntrl}
                    : sprintf('\%03o', ord($cntrl)) );
-       return "<span class=\"cntrl\">$chr</span>";
+       if ($opts{-nohtml}) {
+               return $chr;
+       } else {
+               return "<span class=\"cntrl\">$chr</span>";
+       }
 }
 
 # Alternatively use unicode control pictures codepoints,
 # Unicode "printable representation" (PR)
 sub quot_upr {
        my $cntrl = shift;
+       my %opts = @_;
+
        my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
-       return "<span class=\"cntrl\">$chr</span>";
+       if ($opts{-nohtml}) {
+               return $chr;
+       } else {
+               return "<span class=\"cntrl\">$chr</span>";
+       }
 }
 
 # git may return quoted and escaped filenames
@@ -800,7 +814,7 @@ sub unquote {
                        return chr(oct($seq));
                } elsif (exists $es{$seq}) {
                        # C escape sequence, aka character escape code
-                       return $es{$seq}
+                       return $es{$seq};
                }
                # quoted ordinary character
                return $seq;
@@ -837,37 +851,78 @@ sub project_in_list {
 ## ----------------------------------------------------------------------
 ## HTML aware string manipulation
 
+# Try to chop given string on a word boundary between position
+# $len and $len+$add_len. If there is no word boundary there,
+# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
+# (marking chopped part) would be longer than given string.
 sub chop_str {
        my $str = shift;
        my $len = shift;
        my $add_len = shift || 10;
+       my $where = shift || 'right'; # 'left' | 'center' | 'right'
 
        # allow only $len chars, but don't cut a word if it would fit in $add_len
        # if it doesn't fit, cut it if it's still longer than the dots we would add
-       $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
-       my $body = $1;
-       my $tail = $2;
-       if (length($tail) > 4) {
-               $tail = " ...";
-               $body =~ s/&[^;]*$//; # remove chopped character entities
+       # remove chopped character entities entirely
+
+       # when chopping in the middle, distribute $len into left and right part
+       # return early if chopping wouldn't make string shorter
+       if ($where eq 'center') {
+               return $str if ($len + 5 >= length($str)); # filler is length 5
+               $len = int($len/2);
+       } else {
+               return $str if ($len + 4 >= length($str)); # filler is length 4
+       }
+
+       # regexps: ending and beginning with word part up to $add_len
+       my $endre = qr/.{$len}\w{0,$add_len}/;
+       my $begre = qr/\w{0,$add_len}.{$len}/;
+
+       if ($where eq 'left') {
+               $str =~ m/^(.*?)($begre)$/;
+               my ($lead, $body) = ($1, $2);
+               if (length($lead) > 4) {
+                       $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
+                       $lead = " ...";
+               }
+               return "$lead$body";
+
+       } elsif ($where eq 'center') {
+               $str =~ m/^($endre)(.*)$/;
+               my ($left, $str)  = ($1, $2);
+               $str =~ m/^(.*?)($begre)$/;
+               my ($mid, $right) = ($1, $2);
+               if (length($mid) > 5) {
+                       $left  =~ s/&[^;]*$//;
+                       $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
+                       $mid = " ... ";
+               }
+               return "$left$mid$right";
+
+       } else {
+               $str =~ m/^($endre)(.*)$/;
+               my $body = $1;
+               my $tail = $2;
+               if (length($tail) > 4) {
+                       $body =~ s/&[^;]*$//;
+                       $tail = "... ";
+               }
+               return "$body$tail";
        }
-       return "$body$tail";
 }
 
 # takes the same arguments as chop_str, but also wraps a <span> around the
 # result with a title attribute if it does get chopped. Additionally, the
 # string is HTML-escaped.
 sub chop_and_escape_str {
-       my $str = shift;
-       my $len = shift;
-       my $add_len = shift || 10;
+       my ($str) = @_;
 
-       my $chopped = chop_str($str, $len, $add_len);
+       my $chopped = chop_str(@_);
        if ($chopped eq $str) {
                return esc_html($chopped);
        } else {
-               return qq{<span title="} . esc_html($str) . qq{">} .
-                       esc_html($chopped) . qq{</span>};
+               $str =~ s/([[:cntrl:]])/?/g;
+               return $cgi->span({-title=>$str}, esc_html($chopped));
        }
 }
 
@@ -1620,7 +1675,7 @@ sub git_get_project_url_list {
        my $path = shift;
 
        $git_dir = "$projectroot/$path";
-       open my $fd, "$projectroot/$path/cloneurl"
+       open my $fd, "$git_dir/cloneurl"
                or return wantarray ?
                @{ config_to_multi(git_get_project_config('url')) } :
                   config_to_multi(git_get_project_config('url'));
@@ -1759,6 +1814,7 @@ sub git_get_project_owner {
        my $owner;
 
        return undef unless $project;
+       $git_dir = "$projectroot/$project";
 
        if (!defined $gitweb_project_owner) {
                git_get_project_list_from_file();
@@ -1767,8 +1823,11 @@ sub git_get_project_owner {
        if (exists $gitweb_project_owner->{$project}) {
                $owner = $gitweb_project_owner->{$project};
        }
+       if (!defined $owner){
+               $owner = git_get_project_config('owner');
+       }
        if (!defined $owner) {
-               $owner = get_file_owner("$projectroot/$project");
+               $owner = get_file_owner("$git_dir");
        }
 
        return $owner;
@@ -2023,7 +2082,7 @@ sub parse_commit {
 }
 
 sub parse_commits {
-       my ($commit_id, $maxcount, $skip, $arg, $filename) = @_;
+       my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
        my @cos;
 
        $maxcount ||= 1;
@@ -2033,7 +2092,7 @@ sub parse_commits {
 
        open my $fd, "-|", git_cmd(), "rev-list",
                "--header",
-               ($arg ? ($arg) : ()),
+               @args,
                ("--max-count=" . $maxcount),
                ("--skip=" . $skip),
                @extra_options,
@@ -2528,6 +2587,10 @@ EOF
                      $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
                      " search:\n",
                      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+                     "<span title=\"Extended regular expression\">" .
+                     $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
+                                    -checked => $search_use_regexp) .
+                     "</span>" .
                      "</div>" .
                      $cgi->end_form() . "\n";
        }
@@ -3769,18 +3832,24 @@ sub git_search_grep_body {
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
                      "<td><i>" . $author . "</i></td>\n" .
                      "<td>" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
-                              chop_and_escape_str($co{'title'}, 50) . "<br/>");
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
+                              -class => "list subject"},
+                             chop_and_escape_str($co{'title'}, 50) . "<br/>");
                my $comment = $co{'comment'};
                foreach my $line (@$comment) {
-                       if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
-                               my $lead = esc_html($1) || "";
-                               $lead = chop_str($lead, 30, 10);
-                               my $match = esc_html($2) || "";
-                               my $trail = esc_html($3) || "";
-                               $trail = chop_str($trail, 30, 10);
-                               my $text = "$lead<span class=\"match\">$match</span>$trail";
-                               print chop_str($text, 80, 5) . "<br/>\n";
+                       if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
+                               my ($lead, $match, $trail) = ($1, $2, $3);
+                               $match = chop_str($match, 70, 5, 'center');
+                               my $contextlen = int((80 - length($match))/2);
+                               $contextlen = 30 if ($contextlen > 30);
+                               $lead  = chop_str($lead,  $contextlen, 10, 'left');
+                               $trail = chop_str($trail, $contextlen, 10, 'right');
+
+                               $lead  = esc_html($lead);
+                               $match = esc_html($match);
+                               $trail = esc_html($trail);
+
+                               print "$lead<span class=\"match\">$match</span>$trail<br />";
                        }
                }
                print "</td>\n" .
@@ -5110,7 +5179,7 @@ sub git_history {
                $ftype = git_get_type($hash);
        }
 
-       my @commitlist = parse_commits($hash_base, 101, (100 * $page), "--full-history", $file_name);
+       my @commitlist = parse_commits($hash_base, 101, (100 * $page), $file_name, "--full-history");
 
        my $paging_nav = '';
        if ($page > 0) {
@@ -5192,14 +5261,17 @@ sub git_search {
                } elsif ($searchtype eq 'committer') {
                        $greptype = "--committer=";
                }
-               $greptype .= $search_regexp;
-               my @commitlist = parse_commits($hash, 101, (100 * $page), $greptype);
+               $greptype .= $searchtext;
+               my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
+                                              $greptype, '--regexp-ignore-case',
+                                              $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
 
                my $paging_nav = '';
                if ($page > 0) {
                        $paging_nav .=
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype)},
+                                                      searchtext=>$searchtext,
+                                                      searchtype=>$searchtype)},
                                        "first");
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(-replay=>1, page=>$page-1),
@@ -5233,50 +5305,19 @@ sub git_search {
                print "<table class=\"pickaxe search\">\n";
                my $alternate = 1;
                $/ = "\n";
-               my $git_command = git_cmd_str();
-               my $searchqtext = $searchtext;
-               $searchqtext =~ s/'/'\\''/;
-               open my $fd, "-|", "$git_command rev-list $hash | " .
-                       "$git_command diff-tree -r --stdin -S\'$searchqtext\'";
+               open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
+                       '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
+                       ($search_use_regexp ? '--pickaxe-regex' : ());
                undef %co;
                my @files;
                while (my $line = <$fd>) {
-                       if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
-                               my %set;
-                               $set{'file'} = $6;
-                               $set{'from_id'} = $3;
-                               $set{'to_id'} = $4;
-                               $set{'id'} = $set{'to_id'};
-                               if ($set{'id'} =~ m/0{40}/) {
-                                       $set{'id'} = $set{'from_id'};
-                               }
-                               if ($set{'id'} =~ m/0{40}/) {
-                                       next;
-                               }
-                               push @files, \%set;
-                       } elsif ($line =~ m/^([0-9a-fA-F]{40})$/){
+                       chomp $line;
+                       next unless $line;
+
+                       my %set = parse_difftree_raw_line($line);
+                       if (defined $set{'commit'}) {
+                               # finish previous commit
                                if (%co) {
-                                       if ($alternate) {
-                                               print "<tr class=\"dark\">\n";
-                                       } else {
-                                               print "<tr class=\"light\">\n";
-                                       }
-                                       $alternate ^= 1;
-                                       my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
-                                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                                             "<td><i>" . $author . "</i></td>\n" .
-                                             "<td>" .
-                                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
-                                                     -class => "list subject"},
-                                                     chop_and_escape_str($co{'title'}, 50) . "<br/>");
-                                       while (my $setref = shift @files) {
-                                               my %set = %$setref;
-                                               print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
-                                                                            hash=>$set{'id'}, file_name=>$set{'file'}),
-                                                             -class => "list"},
-                                                             "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
-                                                     "<br/>\n";
-                                       }
                                        print "</td>\n" .
                                              "<td class=\"link\">" .
                                              $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
@@ -5285,11 +5326,44 @@ sub git_search {
                                        print "</td>\n" .
                                              "</tr>\n";
                                }
-                               %co = parse_commit($1);
+
+                               if ($alternate) {
+                                       print "<tr class=\"dark\">\n";
+                               } else {
+                                       print "<tr class=\"light\">\n";
+                               }
+                               $alternate ^= 1;
+                               %co = parse_commit($set{'commit'});
+                               my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
+                               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+                                     "<td><i>$author</i></td>\n" .
+                                     "<td>" .
+                                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
+                                             -class => "list subject"},
+                                             chop_and_escape_str($co{'title'}, 50) . "<br/>");
+                       } elsif (defined $set{'to_id'}) {
+                               next if ($set{'to_id'} =~ m/^0{40}$/);
+
+                               print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
+                                                            hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
+                                             -class => "list"},
+                                             "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
+                                     "<br/>\n";
                        }
                }
                close $fd;
 
+               # finish last commit (warning: repetition!)
+               if (%co) {
+                       print "</td>\n" .
+                             "<td class=\"link\">" .
+                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
+                             " | " .
+                             $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
+                       print "</td>\n" .
+                             "</tr>\n";
+               }
+
                print "</table>\n";
        }
 
@@ -5301,7 +5375,9 @@ sub git_search {
                my $alternate = 1;
                my $matches = 0;
                $/ = "\n";
-               open my $fd, "-|", git_cmd(), 'grep', '-n', '-i', '-E', $searchtext, $co{'tree'};
+               open my $fd, "-|", git_cmd(), 'grep', '-n',
+                       $search_use_regexp ? ('-E', '-i') : '-F',
+                       $searchtext, $co{'tree'};
                my $lastfile = '';
                while (my $line = <$fd>) {
                        chomp $line;
@@ -5331,7 +5407,7 @@ sub git_search {
                                print "<div class=\"binary\">Binary file</div>\n";
                        } else {
                                $ltext = untabify($ltext);
-                               if ($ltext =~ m/^(.*)($searchtext)(.*)$/i) {
+                               if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
                                        $ltext = esc_html($1, -nbsp=>1);
                                        $ltext .= '<span class="match">';
                                        $ltext .= esc_html($2, -nbsp=>1);
@@ -5366,27 +5442,31 @@ sub git_search_help {
        git_header_html();
        git_print_page_nav('','', $hash,$hash,$hash);
        print <<EOT;
+<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
+regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
+the pattern entered is recognized as the POSIX extended
+<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
+insensitive).</p>
 <dl>
 <dt><b>commit</b></dt>
-<dd>The commit messages and authorship information will be scanned for the given string.</dd>
+<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
 EOT
        my ($have_grep) = gitweb_check_feature('grep');
        if ($have_grep) {
                print <<EOT;
 <dt><b>grep</b></dt>
 <dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
-    a different one) are searched for the given
-<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a>
-(POSIX extended) and the matches are listed. On large
-trees, this search can take a while and put some strain on the server, so please use it with
-some consideration.</dd>
+    a different one) are searched for the given pattern. On large trees, this search can take
+a while and put some strain on the server, so please use it with some consideration. Note that
+due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
+case-sensitive.</dd>
 EOT
        }
        print <<EOT;
 <dt><b>author</b></dt>
-<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd>
+<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given pattern.</dd>
 <dt><b>committer</b></dt>
-<dd>Name and e-mail of the committer and date of commit will be scanned for the given string.</dd>
+<dd>Name and e-mail of the committer and date of commit will be scanned for the given pattern.</dd>
 EOT
        my ($have_pickaxe) = gitweb_check_feature('pickaxe');
        if ($have_pickaxe) {
@@ -5394,7 +5474,8 @@ EOT
 <dt><b>pickaxe</b></dt>
 <dd>All commits that caused the string to appear or disappear from any file (changes that
 added, removed or "modified" the string) will be listed. This search can take a while and
-takes a lot of strain on the server, so please use it wisely.</dd>
+takes a lot of strain on the server, so please use it wisely. Note that since you may be
+interested even in changes just changing the case as well, this search is case sensitive.</dd>
 EOT
        }
        print "</dl>\n";
@@ -5445,7 +5526,7 @@ sub git_feed {
 
        # log/feed of current (HEAD) branch, log of given branch, history of file/directory
        my $head = $hash || 'HEAD';
-       my @commitlist = parse_commits($head, 150, 0, undef, $file_name);
+       my @commitlist = parse_commits($head, 150, 0, $file_name);
 
        my %latest_commit;
        my %latest_date;
@@ -5565,7 +5646,7 @@ XML
                        or next;
 
                # print element (entry, item)
-               my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
+               my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
                if ($format eq 'rss') {
                        print "<item>\n" .
                              "<title>" . esc_html($co{'title'}) . "</title>\n" .
index 0a58f3f1267dcb4dbd67c89fc165367c6840f1da..61e7160b361f60e6d673ab64aa3d22c1a783d057 100644 (file)
@@ -41,6 +41,7 @@ int main(int argc, char **argv)
        const char *prefix = NULL;
        int prefix_length = -1;
        int no_more_flags = 0;
+       int hashstdin = 0;
 
        git_config(git_default_config);
 
@@ -65,13 +66,20 @@ int main(int argc, char **argv)
                        else if (!strcmp(argv[i], "--help"))
                                usage(hash_object_usage);
                        else if (!strcmp(argv[i], "--stdin")) {
-                               hash_stdin(type, write_object);
+                               if (hashstdin)
+                                       die("Multiple --stdin arguments are not supported");
+                               hashstdin = 1;
                        }
                        else
                                usage(hash_object_usage);
                }
                else {
                        const char *arg = argv[i];
+
+                       if (hashstdin) {
+                               hash_stdin(type, write_object);
+                               hashstdin = 0;
+                       }
                        if (0 <= prefix_length)
                                arg = prefix_filename(prefix, prefix_length,
                                                      arg);
@@ -79,5 +87,7 @@ int main(int argc, char **argv)
                        no_more_flags = 1;
                }
        }
+       if (hashstdin)
+               hash_stdin(type, write_object);
        return 0;
 }
diff --git a/help.c b/help.c
index 95e7640fedebb1574a4259d85d7b95d5220f0e6f..e57a50ed59badbb968ce508b6c1802f1fcb68903 100644 (file)
--- a/help.c
+++ b/help.c
@@ -7,33 +7,38 @@
 #include "builtin.h"
 #include "exec_cmd.h"
 #include "common-cmds.h"
-
-static const char *help_default_format;
-
-static enum help_format {
-       man_format,
-       info_format,
-       web_format,
-} help_format = man_format;
-
-static void parse_help_format(const char *format)
+#include "parse-options.h"
+
+enum help_format {
+       HELP_FORMAT_MAN,
+       HELP_FORMAT_INFO,
+       HELP_FORMAT_WEB,
+};
+
+static int show_all = 0;
+static enum help_format help_format = HELP_FORMAT_MAN;
+static struct option builtin_help_options[] = {
+       OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
+       OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
+       OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
+                       HELP_FORMAT_WEB),
+       OPT_SET_INT('i', "info", &help_format, "show info page",
+                       HELP_FORMAT_INFO),
+};
+
+static const char * const builtin_help_usage[] = {
+       "git-help [--all] [--man|--web|--info] [command]",
+       NULL
+};
+
+static enum help_format parse_help_format(const char *format)
 {
-       if (!format) {
-               help_format = man_format;
-               return;
-       }
-       if (!strcmp(format, "man")) {
-               help_format = man_format;
-               return;
-       }
-       if (!strcmp(format, "info")) {
-               help_format = info_format;
-               return;
-       }
-       if (!strcmp(format, "web") || !strcmp(format, "html")) {
-               help_format = web_format;
-               return;
-       }
+       if (!strcmp(format, "man"))
+               return HELP_FORMAT_MAN;
+       if (!strcmp(format, "info"))
+               return HELP_FORMAT_INFO;
+       if (!strcmp(format, "web") || !strcmp(format, "html"))
+               return HELP_FORMAT_WEB;
        die("unrecognized help format '%s'", format);
 }
 
@@ -42,7 +47,7 @@ static int git_help_config(const char *var, const char *value)
        if (!strcmp(var, "help.format")) {
                if (!value)
                        return config_error_nonbool(var);
-               help_default_format = xstrdup(value);
+               help_format = parse_help_format(value);
                return 0;
        }
        return git_default_config(var, value);
@@ -205,7 +210,7 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds,
        return longest;
 }
 
-static void list_commands(void)
+static unsigned int load_command_list(void)
 {
        unsigned int longest = 0;
        unsigned int len;
@@ -245,6 +250,14 @@ static void list_commands(void)
        uniq(&other_cmds);
        exclude_cmds(&other_cmds, &main_cmds);
 
+       return longest;
+}
+
+static void list_commands(void)
+{
+       unsigned int longest = load_command_list();
+       const char *exec_path = git_exec_path();
+
        if (main_cmds.cnt) {
                printf("available git commands in '%s'\n", exec_path);
                printf("----------------------------");
@@ -279,6 +292,22 @@ void list_common_cmds_help(void)
        }
 }
 
+static int is_in_cmdlist(struct cmdnames *c, const char *s)
+{
+       int i;
+       for (i = 0; i < c->cnt; i++)
+               if (!strcmp(s, c->names[i]->name))
+                       return 1;
+       return 0;
+}
+
+static int is_git_command(const char *s)
+{
+       load_command_list();
+       return is_in_cmdlist(&main_cmds, s) ||
+               is_in_cmdlist(&other_cmds, s);
+}
+
 static const char *cmd_to_page(const char *git_cmd)
 {
        if (!git_cmd)
@@ -330,10 +359,26 @@ static void show_info_page(const char *git_cmd)
        execlp("info", "info", "gitman", page, NULL);
 }
 
+static void get_html_page_path(struct strbuf *page_path, const char *page)
+{
+       struct stat st;
+
+       /* Check that we have a git documentation directory. */
+       if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
+               die("'%s': not a documentation directory.", GIT_HTML_PATH);
+
+       strbuf_init(page_path, 0);
+       strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
+}
+
 static void show_html_page(const char *git_cmd)
 {
        const char *page = cmd_to_page(git_cmd);
-       execl_git_cmd("help--browse", page, NULL);
+       struct strbuf page_path; /* it leaks but we exec bellow */
+
+       get_html_page_path(&page_path, page);
+
+       execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
 }
 
 void help_unknown_cmd(const char *cmd)
@@ -350,50 +395,43 @@ int cmd_version(int argc, const char **argv, const char *prefix)
 
 int cmd_help(int argc, const char **argv, const char *prefix)
 {
-       const char *help_cmd = argv[1];
+       int nongit;
+       const char *alias;
 
-       if (argc < 2) {
-               printf("usage: %s\n\n", git_usage_string);
-               list_common_cmds_help();
-               exit(0);
-       }
+       setup_git_directory_gently(&nongit);
+       git_config(git_help_config);
 
-       if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
+       argc = parse_options(argc, argv, builtin_help_options,
+                       builtin_help_usage, 0);
+
+       if (show_all) {
                printf("usage: %s\n\n", git_usage_string);
                list_commands();
+               return 0;
        }
 
-       else if (!strcmp(help_cmd, "--web") || !strcmp(help_cmd, "-w")) {
-               show_html_page(argc > 2 ? argv[2] : NULL);
-       }
-
-       else if (!strcmp(help_cmd, "--info") || !strcmp(help_cmd, "-i")) {
-               show_info_page(argc > 2 ? argv[2] : NULL);
+       if (!argv[0]) {
+               printf("usage: %s\n\n", git_usage_string);
+               list_common_cmds_help();
+               return 0;
        }
 
-       else if (!strcmp(help_cmd, "--man") || !strcmp(help_cmd, "-m")) {
-               show_man_page(argc > 2 ? argv[2] : NULL);
+       alias = alias_lookup(argv[0]);
+       if (alias && !is_git_command(argv[0])) {
+               printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+               return 0;
        }
 
-       else {
-               int nongit;
-
-               setup_git_directory_gently(&nongit);
-               git_config(git_help_config);
-               if (help_default_format)
-                       parse_help_format(help_default_format);
-
-               switch (help_format) {
-               case man_format:
-                       show_man_page(help_cmd);
-                       break;
-               case info_format:
-                       show_info_page(help_cmd);
-                       break;
-               case web_format:
-                       show_html_page(help_cmd);
-                       break;
-               }
+       switch (help_format) {
+       case HELP_FORMAT_MAN:
+               show_man_page(argv[0]);
+               break;
+       case HELP_FORMAT_INFO:
+               show_info_page(argv[0]);
+               break;
+       case HELP_FORMAT_WEB:
+               show_html_page(argv[0]);
+               break;
        }
 
        return 0;
index e3e34decd2d4bf3443a00026a5c63c9f93f38802..5b230380ccd33d8b04f8dfce24050237ae9206ad 100644 (file)
@@ -664,8 +664,7 @@ static void release_request(struct transfer_request *request)
                close(request->local_fileno);
        if (request->local_stream)
                fclose(request->local_stream);
-       if (request->url != NULL)
-               free(request->url);
+       free(request->url);
        free(request);
 }
 
@@ -1283,10 +1282,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
        strbuf_release(&in_buffer);
 
        if (lock->token == NULL || lock->timeout <= 0) {
-               if (lock->token != NULL)
-                       free(lock->token);
-               if (lock->owner != NULL)
-                       free(lock->owner);
+               free(lock->token);
+               free(lock->owner);
                free(url);
                free(lock);
                lock = NULL;
@@ -1344,8 +1341,7 @@ static int unlock_remote(struct remote_lock *lock)
                        prev->next = prev->next->next;
        }
 
-       if (lock->owner != NULL)
-               free(lock->owner);
+       free(lock->owner);
        free(lock->url);
        free(lock->token);
        free(lock);
@@ -2035,8 +2031,7 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
        }
        free(url);
 
-       if (*symref != NULL)
-               free(*symref);
+       free(*symref);
        *symref = NULL;
        hashclr(sha1);
 
@@ -2138,6 +2133,8 @@ static int delete_remote_branch(char *pattern, int force)
 
        /* Send delete request */
        fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name);
+       if (dry_run)
+               return 0;
        url = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1);
        sprintf(url, "%s%s", remote->url, remote_ref->name);
        slot = get_active_slot();
@@ -2311,6 +2308,16 @@ int main(int argc, char **argv)
 
                if (!ref->peer_ref)
                        continue;
+
+               if (is_zero_sha1(ref->peer_ref->new_sha1)) {
+                       if (delete_remote_branch(ref->name, 1) == -1) {
+                               error("Could not remove %s", ref->name);
+                               rc = -4;
+                       }
+                       new_refs++;
+                       continue;
+               }
+
                if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
                        if (push_verbosely || 1)
                                fprintf(stderr, "'%s': up-to-date\n", ref->name);
@@ -2342,11 +2349,6 @@ int main(int argc, char **argv)
                        }
                }
                hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               if (is_zero_sha1(ref->new_sha1)) {
-                       error("cannot happen anymore");
-                       rc = -3;
-                       continue;
-               }
                new_refs++;
                strcpy(old_hex, sha1_to_hex(ref->old_sha1));
                new_hex = sha1_to_hex(ref->new_sha1);
@@ -2390,7 +2392,8 @@ int main(int argc, char **argv)
 
                /* Generate a list of objects that need to be pushed */
                pushing = 0;
-               prepare_revision_walk(&revs);
+               if (prepare_revision_walk(&revs))
+                       die("revision walk setup failed");
                mark_edges_uninteresting(revs.commits);
                objects_to_send = get_delta(&revs, ref_lock);
                finish_all_active_slots();
@@ -2434,8 +2437,7 @@ int main(int argc, char **argv)
        }
 
  cleanup:
-       if (rewritten_url)
-               free(rewritten_url);
+       free(rewritten_url);
        if (info_ref_lock)
                unlock_remote(info_ref_lock);
        free(remote);
index 9025d9aa3ef37b1a1ce4ae5d4b447e3b0918cb7e..10cce15a427646a1281afa5197f40def39151154 100644 (file)
@@ -472,7 +472,7 @@ v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb,
        if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) {
                free( cmd->cmd );
                free( cmd );
-               if (cb && cb->data)
+               if (cb)
                        free( cb->data );
                return NULL;
        }
@@ -858,8 +858,7 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
                  normal:
                        if (cmdp->cb.done)
                                cmdp->cb.done( ctx, cmdp, resp );
-                       if (cmdp->cb.data)
-                               free( cmdp->cb.data );
+                       free( cmdp->cb.data );
                        free( cmdp->cmd );
                        free( cmdp );
                        if (!tcmd || tcmd == cmdp)
index 9fd6982a979a40e701dcb6bcaf117eafbf6ff161..9c0c27813fd6736218d1caaed6248e4ae4d388b5 100644 (file)
@@ -7,9 +7,10 @@
 #include "tag.h"
 #include "tree.h"
 #include "progress.h"
+#include "fsck.h"
 
 static const char index_pack_usage[] =
-"git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
+"git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
 
 struct object_entry
 {
@@ -31,6 +32,9 @@ union delta_base {
  */
 #define UNION_BASE_SZ  20
 
+#define FLAG_LINK (1u<<20)
+#define FLAG_CHECKED (1u<<21)
+
 struct delta_entry
 {
        union delta_base base;
@@ -44,6 +48,7 @@ static int nr_deltas;
 static int nr_resolved_deltas;
 
 static int from_stdin;
+static int strict;
 static int verbose;
 
 static struct progress *progress;
@@ -56,6 +61,48 @@ static SHA_CTX input_ctx;
 static uint32_t input_crc32;
 static int input_fd, output_fd, pack_fd;
 
+static int mark_link(struct object *obj, int type, void *data)
+{
+       if (!obj)
+               return -1;
+
+       if (type != OBJ_ANY && obj->type != type)
+               die("object type mismatch at %s", sha1_to_hex(obj->sha1));
+
+       obj->flags |= FLAG_LINK;
+       return 0;
+}
+
+/* The content of each linked object must have been checked
+   or it must be already present in the object database */
+static void check_object(struct object *obj)
+{
+       if (!obj)
+               return;
+
+       if (!(obj->flags & FLAG_LINK))
+               return;
+
+       if (!(obj->flags & FLAG_CHECKED)) {
+               unsigned long size;
+               int type = sha1_object_info(obj->sha1, &size);
+               if (type != obj->type || type <= 0)
+                       die("object of unexpected type");
+               obj->flags |= FLAG_CHECKED;
+               return;
+       }
+}
+
+static void check_objects(void)
+{
+       unsigned i, max;
+
+       max = get_max_object_index();
+       for (i = 0; i < max; i++)
+               check_object(get_indexed_object(i));
+}
+
+
 /* Discard current buffer used content. */
 static void flush(void)
 {
@@ -341,6 +388,41 @@ static void sha1_object(const void *data, unsigned long size,
                        die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
                free(has_data);
        }
+       if (strict) {
+               if (type == OBJ_BLOB) {
+                       struct blob *blob = lookup_blob(sha1);
+                       if (blob)
+                               blob->object.flags |= FLAG_CHECKED;
+                       else
+                               die("invalid blob object %s", sha1_to_hex(sha1));
+               } else {
+                       struct object *obj;
+                       int eaten;
+                       void *buf = (void *) data;
+
+                       /*
+                        * we do not need to free the memory here, as the
+                        * buf is deleted by the caller.
+                        */
+                       obj = parse_object_buffer(sha1, type, size, buf, &eaten);
+                       if (!obj)
+                               die("invalid %s", typename(type));
+                       if (fsck_object(obj, 1, fsck_error_function))
+                               die("Error in object");
+                       if (fsck_walk(obj, mark_link, 0))
+                               die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
+
+                       if (obj->type == OBJ_TREE) {
+                               struct tree *item = (struct tree *) obj;
+                               item->buffer = NULL;
+                       }
+                       if (obj->type == OBJ_COMMIT) {
+                               struct commit *commit = (struct commit *) obj;
+                               commit->buffer = NULL;
+                       }
+                       obj->flags |= FLAG_CHECKED;
+               }
+       }
 }
 
 static void resolve_delta(struct object_entry *delta_obj, void *base_data,
@@ -714,6 +796,8 @@ int main(int argc, char **argv)
                                from_stdin = 1;
                        } else if (!strcmp(arg, "--fix-thin")) {
                                fix_thin_pack = 1;
+                       } else if (!strcmp(arg, "--strict")) {
+                               strict = 1;
                        } else if (!strcmp(arg, "--keep")) {
                                keep_msg = "";
                        } else if (!prefixcmp(arg, "--keep=")) {
@@ -812,6 +896,8 @@ int main(int argc, char **argv)
                            nr_deltas - nr_resolved_deltas);
        }
        free(deltas);
+       if (strict)
+               check_objects();
 
        idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
        for (i = 0; i < nr_objects; i++)
index 6ef53f246511a1943e375d5d5913a4ec52e2c663..7f03bd99c5b66afa6cc7fa11a2430301a3042656 100644 (file)
@@ -11,8 +11,7 @@ void interp_set_entry(struct interp *table, int slot, const char *value)
        char *oldval = table[slot].value;
        char *newval = NULL;
 
-       if (oldval)
-               free(oldval);
+       free(oldval);
 
        if (value)
                newval = xstrdup(value);
index 4ef58e7ec01ebdfc6f036ccafaf837b2b38ed7a5..c8b8375e4983794e601ba69a1c217a3c711835e9 100644 (file)
@@ -18,6 +18,8 @@ static void process_blob(struct rev_info *revs,
 
        if (!revs->blob_objects)
                return;
+       if (!obj)
+               die("bad blob object");
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
        obj->flags |= SEEN;
@@ -69,6 +71,8 @@ static void process_tree(struct rev_info *revs,
 
        if (!revs->tree_objects)
                return;
+       if (!obj)
+               die("bad tree object");
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
        if (parse_tree(tree) < 0)
diff --git a/ll-merge.c b/ll-merge.c
new file mode 100644 (file)
index 0000000..5ae7433
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ * Low level 3-way in-core file merge.
+ *
+ * Copyright (c) 2007 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "attr.h"
+#include "xdiff-interface.h"
+#include "run-command.h"
+#include "interpolate.h"
+#include "ll-merge.h"
+
+struct ll_merge_driver;
+
+typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+                          mmbuffer_t *result,
+                          const char *path,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          int virtual_ancestor);
+
+struct ll_merge_driver {
+       const char *name;
+       const char *description;
+       ll_merge_fn fn;
+       const char *recursive;
+       struct ll_merge_driver *next;
+       char *cmdline;
+};
+
+/*
+ * Built-in low-levels
+ */
+static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+                          mmbuffer_t *result,
+                          const char *path_unused,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          int virtual_ancestor)
+{
+       /*
+        * The tentative merge result is "ours" for the final round,
+        * or common ancestor for an internal merge.  Still return
+        * "conflicted merge" status.
+        */
+       mmfile_t *stolen = virtual_ancestor ? orig : src1;
+
+       result->ptr = stolen->ptr;
+       result->size = stolen->size;
+       stolen->ptr = NULL;
+       return 1;
+}
+
+static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+                       mmbuffer_t *result,
+                       const char *path_unused,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       int virtual_ancestor)
+{
+       xpparam_t xpp;
+
+       if (buffer_is_binary(orig->ptr, orig->size) ||
+           buffer_is_binary(src1->ptr, src1->size) ||
+           buffer_is_binary(src2->ptr, src2->size)) {
+               warning("Cannot merge binary files: %s vs. %s\n",
+                       name1, name2);
+               return ll_binary_merge(drv_unused, result,
+                                      path_unused,
+                                      orig, src1, name1,
+                                      src2, name2,
+                                      virtual_ancestor);
+       }
+
+       memset(&xpp, 0, sizeof(xpp));
+       return xdl_merge(orig,
+                        src1, name1,
+                        src2, name2,
+                        &xpp, XDL_MERGE_ZEALOUS,
+                        result);
+}
+
+static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+                         mmbuffer_t *result,
+                         const char *path_unused,
+                         mmfile_t *orig,
+                         mmfile_t *src1, const char *name1,
+                         mmfile_t *src2, const char *name2,
+                         int virtual_ancestor)
+{
+       char *src, *dst;
+       long size;
+       const int marker_size = 7;
+
+       int status = ll_xdl_merge(drv_unused, result, path_unused,
+                                 orig, src1, NULL, src2, NULL,
+                                 virtual_ancestor);
+       if (status <= 0)
+               return status;
+       size = result->size;
+       src = dst = result->ptr;
+       while (size) {
+               char ch;
+               if ((marker_size < size) &&
+                   (*src == '<' || *src == '=' || *src == '>')) {
+                       int i;
+                       ch = *src;
+                       for (i = 0; i < marker_size; i++)
+                               if (src[i] != ch)
+                                       goto not_a_marker;
+                       if (src[marker_size] != '\n')
+                               goto not_a_marker;
+                       src += marker_size + 1;
+                       size -= marker_size + 1;
+                       continue;
+               }
+       not_a_marker:
+               do {
+                       ch = *src++;
+                       *dst++ = ch;
+                       size--;
+               } while (ch != '\n' && size);
+       }
+       result->size = dst - result->ptr;
+       return 0;
+}
+
+#define LL_BINARY_MERGE 0
+#define LL_TEXT_MERGE 1
+#define LL_UNION_MERGE 2
+static struct ll_merge_driver ll_merge_drv[] = {
+       { "binary", "built-in binary merge", ll_binary_merge },
+       { "text", "built-in 3-way text merge", ll_xdl_merge },
+       { "union", "built-in union merge", ll_union_merge },
+};
+
+static void create_temp(mmfile_t *src, char *path)
+{
+       int fd;
+
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = xmkstemp(path);
+       if (write_in_full(fd, src->ptr, src->size) != src->size)
+               die("unable to write temp-file");
+       close(fd);
+}
+
+/*
+ * User defined low-level merge driver support.
+ */
+static int ll_ext_merge(const struct ll_merge_driver *fn,
+                       mmbuffer_t *result,
+                       const char *path,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       int virtual_ancestor)
+{
+       char temp[3][50];
+       char cmdbuf[2048];
+       struct interp table[] = {
+               { "%O" },
+               { "%A" },
+               { "%B" },
+       };
+       struct child_process child;
+       const char *args[20];
+       int status, fd, i;
+       struct stat st;
+
+       if (fn->cmdline == NULL)
+               die("custom merge driver %s lacks command line.", fn->name);
+
+       result->ptr = NULL;
+       result->size = 0;
+       create_temp(orig, temp[0]);
+       create_temp(src1, temp[1]);
+       create_temp(src2, temp[2]);
+
+       interp_set_entry(table, 0, temp[0]);
+       interp_set_entry(table, 1, temp[1]);
+       interp_set_entry(table, 2, temp[2]);
+
+       interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
+
+       memset(&child, 0, sizeof(child));
+       child.argv = args;
+       args[0] = "sh";
+       args[1] = "-c";
+       args[2] = cmdbuf;
+       args[3] = NULL;
+
+       status = run_command(&child);
+       if (status < -ERR_RUN_COMMAND_FORK)
+               ; /* failure in run-command */
+       else
+               status = -status;
+       fd = open(temp[1], O_RDONLY);
+       if (fd < 0)
+               goto bad;
+       if (fstat(fd, &st))
+               goto close_bad;
+       result->size = st.st_size;
+       result->ptr = xmalloc(result->size + 1);
+       if (read_in_full(fd, result->ptr, result->size) != result->size) {
+               free(result->ptr);
+               result->ptr = NULL;
+               result->size = 0;
+       }
+ close_bad:
+       close(fd);
+ bad:
+       for (i = 0; i < 3; i++)
+               unlink(temp[i]);
+       return status;
+}
+
+/*
+ * merge.default and merge.driver configuration items
+ */
+static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
+static const char *default_ll_merge;
+
+static int read_merge_config(const char *var, const char *value)
+{
+       struct ll_merge_driver *fn;
+       const char *ep, *name;
+       int namelen;
+
+       if (!strcmp(var, "merge.default")) {
+               if (value)
+                       default_ll_merge = strdup(value);
+               return 0;
+       }
+
+       /*
+        * We are not interested in anything but "merge.<name>.variable";
+        * especially, we do not want to look at variables such as
+        * "merge.summary", "merge.tool", and "merge.verbosity".
+        */
+       if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
+               return 0;
+
+       /*
+        * Find existing one as we might be processing merge.<name>.var2
+        * after seeing merge.<name>.var1.
+        */
+       name = var + 6;
+       namelen = ep - name;
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
+                       break;
+       if (!fn) {
+               fn = xcalloc(1, sizeof(struct ll_merge_driver));
+               fn->name = xmemdupz(name, namelen);
+               fn->fn = ll_ext_merge;
+               *ll_user_merge_tail = fn;
+               ll_user_merge_tail = &(fn->next);
+       }
+
+       ep++;
+
+       if (!strcmp("name", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               fn->description = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp("driver", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               /*
+                * merge.<name>.driver specifies the command line:
+                *
+                *      command-line
+                *
+                * The command-line will be interpolated with the following
+                * tokens and is given to the shell:
+                *
+                *    %O - temporary file name for the merge base.
+                *    %A - temporary file name for our version.
+                *    %B - temporary file name for the other branches' version.
+                *
+                * The external merge driver should write the results in the
+                * file named by %A, and signal that it has done with zero exit
+                * status.
+                */
+               fn->cmdline = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp("recursive", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               fn->recursive = strdup(value);
+               return 0;
+       }
+
+       return 0;
+}
+
+static void initialize_ll_merge(void)
+{
+       if (ll_user_merge_tail)
+               return;
+       ll_user_merge_tail = &ll_user_merge;
+       git_config(read_merge_config);
+}
+
+static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
+{
+       struct ll_merge_driver *fn;
+       const char *name;
+       int i;
+
+       initialize_ll_merge();
+
+       if (ATTR_TRUE(merge_attr))
+               return &ll_merge_drv[LL_TEXT_MERGE];
+       else if (ATTR_FALSE(merge_attr))
+               return &ll_merge_drv[LL_BINARY_MERGE];
+       else if (ATTR_UNSET(merge_attr)) {
+               if (!default_ll_merge)
+                       return &ll_merge_drv[LL_TEXT_MERGE];
+               else
+                       name = default_ll_merge;
+       }
+       else
+               name = merge_attr;
+
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strcmp(fn->name, name))
+                       return fn;
+
+       for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
+               if (!strcmp(ll_merge_drv[i].name, name))
+                       return &ll_merge_drv[i];
+
+       /* default to the 3-way */
+       return &ll_merge_drv[LL_TEXT_MERGE];
+}
+
+static const char *git_path_check_merge(const char *path)
+{
+       static struct git_attr_check attr_merge_check;
+
+       if (!attr_merge_check.attr)
+               attr_merge_check.attr = git_attr("merge", 5);
+
+       if (git_checkattr(path, 1, &attr_merge_check))
+               return NULL;
+       return attr_merge_check.value;
+}
+
+int ll_merge(mmbuffer_t *result_buf,
+            const char *path,
+            mmfile_t *ancestor,
+            mmfile_t *ours, const char *our_label,
+            mmfile_t *theirs, const char *their_label,
+            int virtual_ancestor)
+{
+       const char *ll_driver_name;
+       const struct ll_merge_driver *driver;
+
+       ll_driver_name = git_path_check_merge(path);
+       driver = find_ll_merge_driver(ll_driver_name);
+
+       if (virtual_ancestor && driver->recursive)
+               driver = find_ll_merge_driver(driver->recursive);
+       return driver->fn(driver, result_buf, path,
+                         ancestor,
+                         ours, our_label,
+                         theirs, their_label, virtual_ancestor);
+}
diff --git a/ll-merge.h b/ll-merge.h
new file mode 100644 (file)
index 0000000..5388422
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Low level 3-way in-core file merge.
+ */
+
+#ifndef LL_MERGE_H
+#define LL_MERGE_H
+
+int ll_merge(mmbuffer_t *result_buf,
+            const char *path,
+            mmfile_t *ancestor,
+            mmfile_t *ours, const char *our_label,
+            mmfile_t *theirs, const char *their_label,
+            int virtual_ancestor);
+
+#endif
index 1f3fcf16ad7a101eb9eab53da84bd2640f97ab00..608f697cf3860245510bff907a643d9b6143e724 100644 (file)
@@ -137,6 +137,72 @@ static int has_non_ascii(const char *s)
        return 0;
 }
 
+void log_write_email_headers(struct rev_info *opt, const char *name,
+                            const char **subject_p, const char **extra_headers_p)
+{
+       const char *subject = NULL;
+       const char *extra_headers = opt->extra_headers;
+       if (opt->total > 0) {
+               static char buffer[64];
+               snprintf(buffer, sizeof(buffer),
+                        "Subject: [%s %0*d/%d] ",
+                        opt->subject_prefix,
+                        digits_in_number(opt->total),
+                        opt->nr, opt->total);
+               subject = buffer;
+       } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
+               static char buffer[256];
+               snprintf(buffer, sizeof(buffer),
+                        "Subject: [%s] ",
+                        opt->subject_prefix);
+               subject = buffer;
+       } else {
+               subject = "Subject: ";
+       }
+
+       printf("From %s Mon Sep 17 00:00:00 2001\n", name);
+       if (opt->message_id)
+               printf("Message-Id: <%s>\n", opt->message_id);
+       if (opt->ref_message_id)
+               printf("In-Reply-To: <%s>\nReferences: <%s>\n",
+                      opt->ref_message_id, opt->ref_message_id);
+       if (opt->mime_boundary) {
+               static char subject_buffer[1024];
+               static char buffer[1024];
+               snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+                        "%s"
+                        "MIME-Version: 1.0\n"
+                        "Content-Type: multipart/mixed;"
+                        " boundary=\"%s%s\"\n"
+                        "\n"
+                        "This is a multi-part message in MIME "
+                        "format.\n"
+                        "--%s%s\n"
+                        "Content-Type: text/plain; "
+                        "charset=UTF-8; format=fixed\n"
+                        "Content-Transfer-Encoding: 8bit\n\n",
+                        extra_headers ? extra_headers : "",
+                        mime_boundary_leader, opt->mime_boundary,
+                        mime_boundary_leader, opt->mime_boundary);
+               extra_headers = subject_buffer;
+
+               snprintf(buffer, sizeof(buffer) - 1,
+                        "--%s%s\n"
+                        "Content-Type: text/x-patch;"
+                        " name=\"%s.diff\"\n"
+                        "Content-Transfer-Encoding: 8bit\n"
+                        "Content-Disposition: %s;"
+                        " filename=\"%s.diff\"\n\n",
+                        mime_boundary_leader, opt->mime_boundary,
+                        name,
+                        opt->no_inline ? "attachment" : "inline",
+                        name);
+               opt->diffopt.stat_sep = buffer;
+       }
+       *subject_p = subject;
+       *extra_headers_p = extra_headers;
+}
+
 void show_log(struct rev_info *opt, const char *sep)
 {
        struct strbuf msgbuf;
@@ -149,10 +215,12 @@ void show_log(struct rev_info *opt, const char *sep)
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
-               if (opt->left_right) {
-                       if (commit->object.flags & BOUNDARY)
-                               putchar('-');
-                       else if (commit->object.flags & SYMMETRIC_LEFT)
+               if (commit->object.flags & BOUNDARY)
+                       putchar('-');
+               else if (commit->object.flags & UNINTERESTING)
+                       putchar('^');
+               else if (opt->left_right) {
+                       if (commit->object.flags & SYMMETRIC_LEFT)
                                putchar('<');
                        else
                                putchar('>');
@@ -186,70 +254,16 @@ void show_log(struct rev_info *opt, const char *sep)
         */
 
        if (opt->commit_format == CMIT_FMT_EMAIL) {
-               char *sha1 = sha1_to_hex(commit->object.sha1);
-               if (opt->total > 0) {
-                       static char buffer[64];
-                       snprintf(buffer, sizeof(buffer),
-                                       "Subject: [%s %0*d/%d] ",
-                                       opt->subject_prefix,
-                                       digits_in_number(opt->total),
-                                       opt->nr, opt->total);
-                       subject = buffer;
-               } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
-                       static char buffer[256];
-                       snprintf(buffer, sizeof(buffer),
-                                       "Subject: [%s] ",
-                                       opt->subject_prefix);
-                       subject = buffer;
-               } else {
-                       subject = "Subject: ";
-               }
-
-               printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
-               if (opt->message_id)
-                       printf("Message-Id: <%s>\n", opt->message_id);
-               if (opt->ref_message_id)
-                       printf("In-Reply-To: <%s>\nReferences: <%s>\n",
-                              opt->ref_message_id, opt->ref_message_id);
-               if (opt->mime_boundary) {
-                       static char subject_buffer[1024];
-                       static char buffer[1024];
-                       snprintf(subject_buffer, sizeof(subject_buffer) - 1,
-                                "%s"
-                                "MIME-Version: 1.0\n"
-                                "Content-Type: multipart/mixed;"
-                                " boundary=\"%s%s\"\n"
-                                "\n"
-                                "This is a multi-part message in MIME "
-                                "format.\n"
-                                "--%s%s\n"
-                                "Content-Type: text/plain; "
-                                "charset=UTF-8; format=fixed\n"
-                                "Content-Transfer-Encoding: 8bit\n\n",
-                                extra_headers ? extra_headers : "",
-                                mime_boundary_leader, opt->mime_boundary,
-                                mime_boundary_leader, opt->mime_boundary);
-                       extra_headers = subject_buffer;
-
-                       snprintf(buffer, sizeof(buffer) - 1,
-                                "--%s%s\n"
-                                "Content-Type: text/x-patch;"
-                                " name=\"%s.diff\"\n"
-                                "Content-Transfer-Encoding: 8bit\n"
-                                "Content-Disposition: %s;"
-                                " filename=\"%s.diff\"\n\n",
-                                mime_boundary_leader, opt->mime_boundary,
-                                sha1,
-                                opt->no_inline ? "attachment" : "inline",
-                                sha1);
-                       opt->diffopt.stat_sep = buffer;
-               }
+               log_write_email_headers(opt, sha1_to_hex(commit->object.sha1),
+                                       &subject, &extra_headers);
        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
                fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
                if (opt->commit_format != CMIT_FMT_ONELINE)
                        fputs("commit ", stdout);
                if (commit->object.flags & BOUNDARY)
                        putchar('-');
+               else if (commit->object.flags & UNINTERESTING)
+                       putchar('^');
                else if (opt->left_right) {
                        if (commit->object.flags & SYMMETRIC_LEFT)
                                putchar('<');
@@ -278,6 +292,9 @@ void show_log(struct rev_info *opt, const char *sep)
                }
        }
 
+       if (!commit->buffer)
+               return;
+
        /*
         * And then the pretty-printed message itself
         */
index b33f7cd7ac2ef6a2587109c4ee618d63ccedae96..0cc9344eabdd0046edf360958376b2037ef643dc 100644 (file)
@@ -13,5 +13,7 @@ int log_tree_commit(struct rev_info *, struct commit *);
 int log_tree_opt_parse(struct rev_info *, const char **, int);
 void show_log(struct rev_info *opt, const char *sep);
 void show_decorations(struct commit *commit);
+void log_write_email_headers(struct rev_info *opt, const char *name,
+                            const char **subject_p, const char **extra_headers_p);
 
 #endif
index fa719cb0b1bd227423587c9e41eed77c755465a4..7491c56ad25332fb4aae6a075bf0577a1d800c3b 100644 (file)
@@ -48,7 +48,7 @@ static int merge_entry(int pos, const char *path)
                        break;
                found++;
                strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
-               sprintf(ownbuf[stage], "%o", ntohl(ce->ce_mode));
+               sprintf(ownbuf[stage], "%o", ce->ce_mode);
                arguments[stage] = hexbuf[stage];
                arguments[stage + 4] = ownbuf[stage];
        } while (++pos < active_nr);
@@ -91,7 +91,7 @@ int main(int argc, char **argv)
        signal(SIGCHLD, SIG_DFL);
 
        if (argc < 3)
-               usage("git-merge-index [-o] [-q] <merge-program> (-a | <filename>*)");
+               usage("git-merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
 
        setup_git_directory();
        read_cache();
diff --git a/merge-recursive.c b/merge-recursive.c
deleted file mode 100644 (file)
index 34e3167..0000000
+++ /dev/null
@@ -1,1761 +0,0 @@
-/*
- * Recursive Merge algorithm stolen from git-merge-recursive.py by
- * Fredrik Kuivinen.
- * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
- */
-#include "cache.h"
-#include "cache-tree.h"
-#include "commit.h"
-#include "blob.h"
-#include "tree-walk.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "run-command.h"
-#include "tag.h"
-#include "unpack-trees.h"
-#include "path-list.h"
-#include "xdiff-interface.h"
-#include "interpolate.h"
-#include "attr.h"
-
-static int subtree_merge;
-
-static struct tree *shift_tree_object(struct tree *one, struct tree *two)
-{
-       unsigned char shifted[20];
-
-       /*
-        * NEEDSWORK: this limits the recursion depth to hardcoded
-        * value '2' to avoid excessive overhead.
-        */
-       shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
-       if (!hashcmp(two->object.sha1, shifted))
-               return two;
-       return lookup_tree(shifted);
-}
-
-/*
- * A virtual commit has
- * - (const char *)commit->util set to the name, and
- * - *(int *)commit->object.sha1 set to the virtual id.
- */
-
-static unsigned commit_list_count(const struct commit_list *l)
-{
-       unsigned c = 0;
-       for (; l; l = l->next )
-               c++;
-       return c;
-}
-
-static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
-{
-       struct commit *commit = xcalloc(1, sizeof(struct commit));
-       static unsigned virtual_id = 1;
-       commit->tree = tree;
-       commit->util = (void*)comment;
-       *(int*)commit->object.sha1 = virtual_id++;
-       /* avoid warnings */
-       commit->object.parsed = 1;
-       return commit;
-}
-
-/*
- * Since we use get_tree_entry(), which does not put the read object into
- * the object pool, we cannot rely on a == b.
- */
-static int sha_eq(const unsigned char *a, const unsigned char *b)
-{
-       if (!a && !b)
-               return 2;
-       return a && b && hashcmp(a, b) == 0;
-}
-
-/*
- * Since we want to write the index eventually, we cannot reuse the index
- * for these (temporary) data.
- */
-struct stage_data
-{
-       struct
-       {
-               unsigned mode;
-               unsigned char sha[20];
-       } stages[4];
-       unsigned processed:1;
-};
-
-static struct path_list current_file_set = {NULL, 0, 0, 1};
-static struct path_list current_directory_set = {NULL, 0, 0, 1};
-
-static int call_depth = 0;
-static int verbosity = 2;
-static int rename_limit = -1;
-static int buffer_output = 1;
-static struct strbuf obuf = STRBUF_INIT;
-
-static int show(int v)
-{
-       return (!call_depth && verbosity >= v) || verbosity >= 5;
-}
-
-static void flush_output(void)
-{
-       if (obuf.len) {
-               fputs(obuf.buf, stdout);
-               strbuf_reset(&obuf);
-       }
-}
-
-static void output(int v, const char *fmt, ...)
-{
-       int len;
-       va_list ap;
-
-       if (!show(v))
-               return;
-
-       strbuf_grow(&obuf, call_depth * 2 + 2);
-       memset(obuf.buf + obuf.len, ' ', call_depth * 2);
-       strbuf_setlen(&obuf, obuf.len + call_depth * 2);
-
-       va_start(ap, fmt);
-       len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap);
-       va_end(ap);
-
-       if (len < 0)
-               len = 0;
-       if (len >= strbuf_avail(&obuf)) {
-               strbuf_grow(&obuf, len + 2);
-               va_start(ap, fmt);
-               len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap);
-               va_end(ap);
-               if (len >= strbuf_avail(&obuf)) {
-                       die("this should not happen, your snprintf is broken");
-               }
-       }
-       strbuf_setlen(&obuf, obuf.len + len);
-       strbuf_add(&obuf, "\n", 1);
-       if (!buffer_output)
-               flush_output();
-}
-
-static void output_commit_title(struct commit *commit)
-{
-       int i;
-       flush_output();
-       for (i = call_depth; i--;)
-               fputs("  ", stdout);
-       if (commit->util)
-               printf("virtual %s\n", (char *)commit->util);
-       else {
-               printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
-               if (parse_commit(commit) != 0)
-                       printf("(bad commit)\n");
-               else {
-                       const char *s;
-                       int len;
-                       for (s = commit->buffer; *s; s++)
-                               if (*s == '\n' && s[1] == '\n') {
-                                       s += 2;
-                                       break;
-                               }
-                       for (len = 0; s[len] && '\n' != s[len]; len++)
-                               ; /* do nothing */
-                       printf("%.*s\n", len, s);
-               }
-       }
-}
-
-static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
-               const char *path, int stage, int refresh, int options)
-{
-       struct cache_entry *ce;
-       ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
-       if (!ce)
-               return error("addinfo_cache failed for path '%s'", path);
-       return add_cache_entry(ce, options);
-}
-
-/*
- * This is a global variable which is used in a number of places but
- * only written to in the 'merge' function.
- *
- * index_only == 1    => Don't leave any non-stage 0 entries in the cache and
- *                       don't update the working directory.
- *               0    => Leave unmerged entries in the cache and update
- *                       the working directory.
- */
-static int index_only = 0;
-
-static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
-{
-       parse_tree(tree);
-       init_tree_desc(desc, tree->buffer, tree->size);
-}
-
-static int git_merge_trees(int index_only,
-                          struct tree *common,
-                          struct tree *head,
-                          struct tree *merge)
-{
-       int rc;
-       struct tree_desc t[3];
-       struct unpack_trees_options opts;
-
-       memset(&opts, 0, sizeof(opts));
-       if (index_only)
-               opts.index_only = 1;
-       else
-               opts.update = 1;
-       opts.merge = 1;
-       opts.head_idx = 2;
-       opts.fn = threeway_merge;
-
-       init_tree_desc_from_tree(t+0, common);
-       init_tree_desc_from_tree(t+1, head);
-       init_tree_desc_from_tree(t+2, merge);
-
-       rc = unpack_trees(3, t, &opts);
-       cache_tree_free(&active_cache_tree);
-       return rc;
-}
-
-static int unmerged_index(void)
-{
-       int i;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce))
-                       return 1;
-       }
-       return 0;
-}
-
-static struct tree *git_write_tree(void)
-{
-       struct tree *result = NULL;
-
-       if (unmerged_index()) {
-               int i;
-               output(0, "There are unmerged index entries:");
-               for (i = 0; i < active_nr; i++) {
-                       struct cache_entry *ce = active_cache[i];
-                       if (ce_stage(ce))
-                               output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name);
-               }
-               return NULL;
-       }
-
-       if (!active_cache_tree)
-               active_cache_tree = cache_tree();
-
-       if (!cache_tree_fully_valid(active_cache_tree) &&
-           cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0) < 0)
-               die("error building trees");
-
-       result = lookup_tree(active_cache_tree->sha1);
-
-       return result;
-}
-
-static int save_files_dirs(const unsigned char *sha1,
-               const char *base, int baselen, const char *path,
-               unsigned int mode, int stage)
-{
-       int len = strlen(path);
-       char *newpath = xmalloc(baselen + len + 1);
-       memcpy(newpath, base, baselen);
-       memcpy(newpath + baselen, path, len);
-       newpath[baselen + len] = '\0';
-
-       if (S_ISDIR(mode))
-               path_list_insert(newpath, &current_directory_set);
-       else
-               path_list_insert(newpath, &current_file_set);
-       free(newpath);
-
-       return READ_TREE_RECURSIVE;
-}
-
-static int get_files_dirs(struct tree *tree)
-{
-       int n;
-       if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0)
-               return 0;
-       n = current_file_set.nr + current_directory_set.nr;
-       return n;
-}
-
-/*
- * Returns an index_entry instance which doesn't have to correspond to
- * a real cache entry in Git's index.
- */
-static struct stage_data *insert_stage_data(const char *path,
-               struct tree *o, struct tree *a, struct tree *b,
-               struct path_list *entries)
-{
-       struct path_list_item *item;
-       struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
-       get_tree_entry(o->object.sha1, path,
-                       e->stages[1].sha, &e->stages[1].mode);
-       get_tree_entry(a->object.sha1, path,
-                       e->stages[2].sha, &e->stages[2].mode);
-       get_tree_entry(b->object.sha1, path,
-                       e->stages[3].sha, &e->stages[3].mode);
-       item = path_list_insert(path, entries);
-       item->util = e;
-       return e;
-}
-
-/*
- * Create a dictionary mapping file names to stage_data objects. The
- * dictionary contains one entry for every path with a non-zero stage entry.
- */
-static struct path_list *get_unmerged(void)
-{
-       struct path_list *unmerged = xcalloc(1, sizeof(struct path_list));
-       int i;
-
-       unmerged->strdup_paths = 1;
-
-       for (i = 0; i < active_nr; i++) {
-               struct path_list_item *item;
-               struct stage_data *e;
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-
-               item = path_list_lookup(ce->name, unmerged);
-               if (!item) {
-                       item = path_list_insert(ce->name, unmerged);
-                       item->util = xcalloc(1, sizeof(struct stage_data));
-               }
-               e = item->util;
-               e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode);
-               hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1);
-       }
-
-       return unmerged;
-}
-
-struct rename
-{
-       struct diff_filepair *pair;
-       struct stage_data *src_entry;
-       struct stage_data *dst_entry;
-       unsigned processed:1;
-};
-
-/*
- * Get information of all renames which occurred between 'o_tree' and
- * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and
- * 'b_tree') to be able to associate the correct cache entries with
- * the rename information. 'tree' is always equal to either a_tree or b_tree.
- */
-static struct path_list *get_renames(struct tree *tree,
-                                       struct tree *o_tree,
-                                       struct tree *a_tree,
-                                       struct tree *b_tree,
-                                       struct path_list *entries)
-{
-       int i;
-       struct path_list *renames;
-       struct diff_options opts;
-
-       renames = xcalloc(1, sizeof(struct path_list));
-       diff_setup(&opts);
-       DIFF_OPT_SET(&opts, RECURSIVE);
-       opts.detect_rename = DIFF_DETECT_RENAME;
-       opts.rename_limit = rename_limit;
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       if (diff_setup_done(&opts) < 0)
-               die("diff setup failed");
-       diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
-       diffcore_std(&opts);
-       for (i = 0; i < diff_queued_diff.nr; ++i) {
-               struct path_list_item *item;
-               struct rename *re;
-               struct diff_filepair *pair = diff_queued_diff.queue[i];
-               if (pair->status != 'R') {
-                       diff_free_filepair(pair);
-                       continue;
-               }
-               re = xmalloc(sizeof(*re));
-               re->processed = 0;
-               re->pair = pair;
-               item = path_list_lookup(re->pair->one->path, entries);
-               if (!item)
-                       re->src_entry = insert_stage_data(re->pair->one->path,
-                                       o_tree, a_tree, b_tree, entries);
-               else
-                       re->src_entry = item->util;
-
-               item = path_list_lookup(re->pair->two->path, entries);
-               if (!item)
-                       re->dst_entry = insert_stage_data(re->pair->two->path,
-                                       o_tree, a_tree, b_tree, entries);
-               else
-                       re->dst_entry = item->util;
-               item = path_list_insert(pair->one->path, renames);
-               item->util = re;
-       }
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_queued_diff.nr = 0;
-       diff_flush(&opts);
-       return renames;
-}
-
-static int update_stages(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
-{
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
-       if (clear)
-               if (remove_file_from_cache(path))
-                       return -1;
-       if (o)
-               if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
-                       return -1;
-       if (a)
-               if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
-                       return -1;
-       if (b)
-               if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
-                       return -1;
-       return 0;
-}
-
-static int remove_path(const char *name)
-{
-       int ret;
-       char *slash, *dirs;
-
-       ret = unlink(name);
-       if (ret)
-               return ret;
-       dirs = xstrdup(name);
-       while ((slash = strrchr(name, '/'))) {
-               *slash = '\0';
-               if (rmdir(name) != 0)
-                       break;
-       }
-       free(dirs);
-       return ret;
-}
-
-static int remove_file(int clean, const char *path, int no_wd)
-{
-       int update_cache = index_only || clean;
-       int update_working_directory = !index_only && !no_wd;
-
-       if (update_cache) {
-               if (remove_file_from_cache(path))
-                       return -1;
-       }
-       if (update_working_directory) {
-               unlink(path);
-               if (errno != ENOENT || errno != EISDIR)
-                       return -1;
-               remove_path(path);
-       }
-       return 0;
-}
-
-static char *unique_path(const char *path, const char *branch)
-{
-       char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1);
-       int suffix = 0;
-       struct stat st;
-       char *p = newpath + strlen(path);
-       strcpy(newpath, path);
-       *(p++) = '~';
-       strcpy(p, branch);
-       for (; *p; ++p)
-               if ('/' == *p)
-                       *p = '_';
-       while (path_list_has_path(&current_file_set, newpath) ||
-              path_list_has_path(&current_directory_set, newpath) ||
-              lstat(newpath, &st) == 0)
-               sprintf(p, "_%d", suffix++);
-
-       path_list_insert(newpath, &current_file_set);
-       return newpath;
-}
-
-static int mkdir_p(const char *path, unsigned long mode)
-{
-       /* path points to cache entries, so xstrdup before messing with it */
-       char *buf = xstrdup(path);
-       int result = safe_create_leading_directories(buf);
-       free(buf);
-       return result;
-}
-
-static void flush_buffer(int fd, const char *buf, unsigned long size)
-{
-       while (size > 0) {
-               long ret = write_in_full(fd, buf, size);
-               if (ret < 0) {
-                       /* Ignore epipe */
-                       if (errno == EPIPE)
-                               break;
-                       die("merge-recursive: %s", strerror(errno));
-               } else if (!ret) {
-                       die("merge-recursive: disk full?");
-               }
-               size -= ret;
-               buf += ret;
-       }
-}
-
-static int make_room_for_path(const char *path)
-{
-       int status;
-       const char *msg = "failed to create path '%s'%s";
-
-       status = mkdir_p(path, 0777);
-       if (status) {
-               if (status == -3) {
-                       /* something else exists */
-                       error(msg, path, ": perhaps a D/F conflict?");
-                       return -1;
-               }
-               die(msg, path, "");
-       }
-
-       /* Successful unlink is good.. */
-       if (!unlink(path))
-               return 0;
-       /* .. and so is no existing file */
-       if (errno == ENOENT)
-               return 0;
-       /* .. but not some other error (who really cares what?) */
-       return error(msg, path, ": perhaps a D/F conflict?");
-}
-
-static void update_file_flags(const unsigned char *sha,
-                             unsigned mode,
-                             const char *path,
-                             int update_cache,
-                             int update_wd)
-{
-       if (index_only)
-               update_wd = 0;
-
-       if (update_wd) {
-               enum object_type type;
-               void *buf;
-               unsigned long size;
-
-               if (S_ISGITLINK(mode))
-                       die("cannot read object %s '%s': It is a submodule!",
-                           sha1_to_hex(sha), path);
-
-               buf = read_sha1_file(sha, &type, &size);
-               if (!buf)
-                       die("cannot read object %s '%s'", sha1_to_hex(sha), path);
-               if (type != OBJ_BLOB)
-                       die("blob expected for %s '%s'", sha1_to_hex(sha), path);
-
-               if (make_room_for_path(path) < 0) {
-                       update_wd = 0;
-                       goto update_index;
-               }
-               if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
-                       int fd;
-                       if (mode & 0100)
-                               mode = 0777;
-                       else
-                               mode = 0666;
-                       fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
-                       if (fd < 0)
-                               die("failed to open %s: %s", path, strerror(errno));
-                       flush_buffer(fd, buf, size);
-                       close(fd);
-               } else if (S_ISLNK(mode)) {
-                       char *lnk = xmemdupz(buf, size);
-                       mkdir_p(path, 0777);
-                       unlink(path);
-                       symlink(lnk, path);
-                       free(lnk);
-               } else
-                       die("do not know what to do with %06o %s '%s'",
-                           mode, sha1_to_hex(sha), path);
-       }
- update_index:
-       if (update_cache)
-               add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
-}
-
-static void update_file(int clean,
-                       const unsigned char *sha,
-                       unsigned mode,
-                       const char *path)
-{
-       update_file_flags(sha, mode, path, index_only || clean, !index_only);
-}
-
-/* Low level file merging, update and removal */
-
-struct merge_file_info
-{
-       unsigned char sha[20];
-       unsigned mode;
-       unsigned clean:1,
-                merge:1;
-};
-
-static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
-{
-       unsigned long size;
-       enum object_type type;
-
-       if (!hashcmp(sha1, null_sha1)) {
-               mm->ptr = xstrdup("");
-               mm->size = 0;
-               return;
-       }
-
-       mm->ptr = read_sha1_file(sha1, &type, &size);
-       if (!mm->ptr || type != OBJ_BLOB)
-               die("unable to read blob object %s", sha1_to_hex(sha1));
-       mm->size = size;
-}
-
-/*
- * Customizable low-level merge drivers support.
- */
-
-struct ll_merge_driver;
-typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
-                          const char *path,
-                          mmfile_t *orig,
-                          mmfile_t *src1, const char *name1,
-                          mmfile_t *src2, const char *name2,
-                          mmbuffer_t *result);
-
-struct ll_merge_driver {
-       const char *name;
-       const char *description;
-       ll_merge_fn fn;
-       const char *recursive;
-       struct ll_merge_driver *next;
-       char *cmdline;
-};
-
-/*
- * Built-in low-levels
- */
-static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
-                          const char *path_unused,
-                          mmfile_t *orig,
-                          mmfile_t *src1, const char *name1,
-                          mmfile_t *src2, const char *name2,
-                          mmbuffer_t *result)
-{
-       /*
-        * The tentative merge result is "ours" for the final round,
-        * or common ancestor for an internal merge.  Still return
-        * "conflicted merge" status.
-        */
-       mmfile_t *stolen = index_only ? orig : src1;
-
-       result->ptr = stolen->ptr;
-       result->size = stolen->size;
-       stolen->ptr = NULL;
-       return 1;
-}
-
-static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
-                       const char *path_unused,
-                       mmfile_t *orig,
-                       mmfile_t *src1, const char *name1,
-                       mmfile_t *src2, const char *name2,
-                       mmbuffer_t *result)
-{
-       xpparam_t xpp;
-
-       if (buffer_is_binary(orig->ptr, orig->size) ||
-           buffer_is_binary(src1->ptr, src1->size) ||
-           buffer_is_binary(src2->ptr, src2->size)) {
-               warning("Cannot merge binary files: %s vs. %s\n",
-                       name1, name2);
-               return ll_binary_merge(drv_unused, path_unused,
-                                      orig, src1, name1,
-                                      src2, name2,
-                                      result);
-       }
-
-       memset(&xpp, 0, sizeof(xpp));
-       return xdl_merge(orig,
-                        src1, name1,
-                        src2, name2,
-                        &xpp, XDL_MERGE_ZEALOUS,
-                        result);
-}
-
-static int ll_union_merge(const struct ll_merge_driver *drv_unused,
-                         const char *path_unused,
-                         mmfile_t *orig,
-                         mmfile_t *src1, const char *name1,
-                         mmfile_t *src2, const char *name2,
-                         mmbuffer_t *result)
-{
-       char *src, *dst;
-       long size;
-       const int marker_size = 7;
-
-       int status = ll_xdl_merge(drv_unused, path_unused,
-                                 orig, src1, NULL, src2, NULL, result);
-       if (status <= 0)
-               return status;
-       size = result->size;
-       src = dst = result->ptr;
-       while (size) {
-               char ch;
-               if ((marker_size < size) &&
-                   (*src == '<' || *src == '=' || *src == '>')) {
-                       int i;
-                       ch = *src;
-                       for (i = 0; i < marker_size; i++)
-                               if (src[i] != ch)
-                                       goto not_a_marker;
-                       if (src[marker_size] != '\n')
-                               goto not_a_marker;
-                       src += marker_size + 1;
-                       size -= marker_size + 1;
-                       continue;
-               }
-       not_a_marker:
-               do {
-                       ch = *src++;
-                       *dst++ = ch;
-                       size--;
-               } while (ch != '\n' && size);
-       }
-       result->size = dst - result->ptr;
-       return 0;
-}
-
-#define LL_BINARY_MERGE 0
-#define LL_TEXT_MERGE 1
-#define LL_UNION_MERGE 2
-static struct ll_merge_driver ll_merge_drv[] = {
-       { "binary", "built-in binary merge", ll_binary_merge },
-       { "text", "built-in 3-way text merge", ll_xdl_merge },
-       { "union", "built-in union merge", ll_union_merge },
-};
-
-static void create_temp(mmfile_t *src, char *path)
-{
-       int fd;
-
-       strcpy(path, ".merge_file_XXXXXX");
-       fd = xmkstemp(path);
-       if (write_in_full(fd, src->ptr, src->size) != src->size)
-               die("unable to write temp-file");
-       close(fd);
-}
-
-/*
- * User defined low-level merge driver support.
- */
-static int ll_ext_merge(const struct ll_merge_driver *fn,
-                       const char *path,
-                       mmfile_t *orig,
-                       mmfile_t *src1, const char *name1,
-                       mmfile_t *src2, const char *name2,
-                       mmbuffer_t *result)
-{
-       char temp[3][50];
-       char cmdbuf[2048];
-       struct interp table[] = {
-               { "%O" },
-               { "%A" },
-               { "%B" },
-       };
-       struct child_process child;
-       const char *args[20];
-       int status, fd, i;
-       struct stat st;
-
-       if (fn->cmdline == NULL)
-               die("custom merge driver %s lacks command line.", fn->name);
-
-       result->ptr = NULL;
-       result->size = 0;
-       create_temp(orig, temp[0]);
-       create_temp(src1, temp[1]);
-       create_temp(src2, temp[2]);
-
-       interp_set_entry(table, 0, temp[0]);
-       interp_set_entry(table, 1, temp[1]);
-       interp_set_entry(table, 2, temp[2]);
-
-       output(1, "merging %s using %s", path,
-              fn->description ? fn->description : fn->name);
-
-       interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
-
-       memset(&child, 0, sizeof(child));
-       child.argv = args;
-       args[0] = "sh";
-       args[1] = "-c";
-       args[2] = cmdbuf;
-       args[3] = NULL;
-
-       status = run_command(&child);
-       if (status < -ERR_RUN_COMMAND_FORK)
-               ; /* failure in run-command */
-       else
-               status = -status;
-       fd = open(temp[1], O_RDONLY);
-       if (fd < 0)
-               goto bad;
-       if (fstat(fd, &st))
-               goto close_bad;
-       result->size = st.st_size;
-       result->ptr = xmalloc(result->size + 1);
-       if (read_in_full(fd, result->ptr, result->size) != result->size) {
-               free(result->ptr);
-               result->ptr = NULL;
-               result->size = 0;
-       }
- close_bad:
-       close(fd);
- bad:
-       for (i = 0; i < 3; i++)
-               unlink(temp[i]);
-       return status;
-}
-
-/*
- * merge.default and merge.driver configuration items
- */
-static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
-static const char *default_ll_merge;
-
-static int read_merge_config(const char *var, const char *value)
-{
-       struct ll_merge_driver *fn;
-       const char *ep, *name;
-       int namelen;
-
-       if (!strcmp(var, "merge.default")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               default_ll_merge = strdup(value);
-               return 0;
-       }
-
-       /*
-        * We are not interested in anything but "merge.<name>.variable";
-        * especially, we do not want to look at variables such as
-        * "merge.summary", "merge.tool", and "merge.verbosity".
-        */
-       if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
-               return 0;
-
-       /*
-        * Find existing one as we might be processing merge.<name>.var2
-        * after seeing merge.<name>.var1.
-        */
-       name = var + 6;
-       namelen = ep - name;
-       for (fn = ll_user_merge; fn; fn = fn->next)
-               if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
-                       break;
-       if (!fn) {
-               fn = xcalloc(1, sizeof(struct ll_merge_driver));
-               fn->name = xmemdupz(name, namelen);
-               fn->fn = ll_ext_merge;
-               *ll_user_merge_tail = fn;
-               ll_user_merge_tail = &(fn->next);
-       }
-
-       ep++;
-
-       if (!strcmp("name", ep)) {
-               if (!value)
-                       return config_error_nonbool(var);
-               fn->description = strdup(value);
-               return 0;
-       }
-
-       if (!strcmp("driver", ep)) {
-               if (!value)
-                       return config_error_nonbool(var);
-               /*
-                * merge.<name>.driver specifies the command line:
-                *
-                *      command-line
-                *
-                * The command-line will be interpolated with the following
-                * tokens and is given to the shell:
-                *
-                *    %O - temporary file name for the merge base.
-                *    %A - temporary file name for our version.
-                *    %B - temporary file name for the other branches' version.
-                *
-                * The external merge driver should write the results in the
-                * file named by %A, and signal that it has done with zero exit
-                * status.
-                */
-               fn->cmdline = strdup(value);
-               return 0;
-       }
-
-       if (!strcmp("recursive", ep)) {
-               if (!value)
-                       return config_error_nonbool(var);
-               fn->recursive = strdup(value);
-               return 0;
-       }
-
-       return 0;
-}
-
-static void initialize_ll_merge(void)
-{
-       if (ll_user_merge_tail)
-               return;
-       ll_user_merge_tail = &ll_user_merge;
-       git_config(read_merge_config);
-}
-
-static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
-{
-       struct ll_merge_driver *fn;
-       const char *name;
-       int i;
-
-       initialize_ll_merge();
-
-       if (ATTR_TRUE(merge_attr))
-               return &ll_merge_drv[LL_TEXT_MERGE];
-       else if (ATTR_FALSE(merge_attr))
-               return &ll_merge_drv[LL_BINARY_MERGE];
-       else if (ATTR_UNSET(merge_attr)) {
-               if (!default_ll_merge)
-                       return &ll_merge_drv[LL_TEXT_MERGE];
-               else
-                       name = default_ll_merge;
-       }
-       else
-               name = merge_attr;
-
-       for (fn = ll_user_merge; fn; fn = fn->next)
-               if (!strcmp(fn->name, name))
-                       return fn;
-
-       for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
-               if (!strcmp(ll_merge_drv[i].name, name))
-                       return &ll_merge_drv[i];
-
-       /* default to the 3-way */
-       return &ll_merge_drv[LL_TEXT_MERGE];
-}
-
-static const char *git_path_check_merge(const char *path)
-{
-       static struct git_attr_check attr_merge_check;
-
-       if (!attr_merge_check.attr)
-               attr_merge_check.attr = git_attr("merge", 5);
-
-       if (git_checkattr(path, 1, &attr_merge_check))
-               return NULL;
-       return attr_merge_check.value;
-}
-
-static int ll_merge(mmbuffer_t *result_buf,
-                   struct diff_filespec *o,
-                   struct diff_filespec *a,
-                   struct diff_filespec *b,
-                   const char *branch1,
-                   const char *branch2)
-{
-       mmfile_t orig, src1, src2;
-       char *name1, *name2;
-       int merge_status;
-       const char *ll_driver_name;
-       const struct ll_merge_driver *driver;
-
-       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
-       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
-
-       fill_mm(o->sha1, &orig);
-       fill_mm(a->sha1, &src1);
-       fill_mm(b->sha1, &src2);
-
-       ll_driver_name = git_path_check_merge(a->path);
-       driver = find_ll_merge_driver(ll_driver_name);
-
-       if (index_only && driver->recursive)
-               driver = find_ll_merge_driver(driver->recursive);
-       merge_status = driver->fn(driver, a->path,
-                                 &orig, &src1, name1, &src2, name2,
-                                 result_buf);
-
-       free(name1);
-       free(name2);
-       free(orig.ptr);
-       free(src1.ptr);
-       free(src2.ptr);
-       return merge_status;
-}
-
-static struct merge_file_info merge_file(struct diff_filespec *o,
-               struct diff_filespec *a, struct diff_filespec *b,
-               const char *branch1, const char *branch2)
-{
-       struct merge_file_info result;
-       result.merge = 0;
-       result.clean = 1;
-
-       if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
-               result.clean = 0;
-               if (S_ISREG(a->mode)) {
-                       result.mode = a->mode;
-                       hashcpy(result.sha, a->sha1);
-               } else {
-                       result.mode = b->mode;
-                       hashcpy(result.sha, b->sha1);
-               }
-       } else {
-               if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1))
-                       result.merge = 1;
-
-               result.mode = a->mode == o->mode ? b->mode: a->mode;
-
-               if (sha_eq(a->sha1, o->sha1))
-                       hashcpy(result.sha, b->sha1);
-               else if (sha_eq(b->sha1, o->sha1))
-                       hashcpy(result.sha, a->sha1);
-               else if (S_ISREG(a->mode)) {
-                       mmbuffer_t result_buf;
-                       int merge_status;
-
-                       merge_status = ll_merge(&result_buf, o, a, b,
-                                               branch1, branch2);
-
-                       if ((merge_status < 0) || !result_buf.ptr)
-                               die("Failed to execute internal merge");
-
-                       if (write_sha1_file(result_buf.ptr, result_buf.size,
-                                           blob_type, result.sha))
-                               die("Unable to add %s to database",
-                                   a->path);
-
-                       free(result_buf.ptr);
-                       result.clean = (merge_status == 0);
-               } else if (S_ISGITLINK(a->mode)) {
-                       result.clean = 0;
-                       hashcpy(result.sha, a->sha1);
-               } else if (S_ISLNK(a->mode)) {
-                       hashcpy(result.sha, a->sha1);
-
-                       if (!sha_eq(a->sha1, b->sha1))
-                               result.clean = 0;
-               } else {
-                       die("unsupported object type in the tree");
-               }
-       }
-
-       return result;
-}
-
-static void conflict_rename_rename(struct rename *ren1,
-                                  const char *branch1,
-                                  struct rename *ren2,
-                                  const char *branch2)
-{
-       char *del[2];
-       int delp = 0;
-       const char *ren1_dst = ren1->pair->two->path;
-       const char *ren2_dst = ren2->pair->two->path;
-       const char *dst_name1 = ren1_dst;
-       const char *dst_name2 = ren2_dst;
-       if (path_list_has_path(&current_directory_set, ren1_dst)) {
-               dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
-               output(1, "%s is a directory in %s added as %s instead",
-                      ren1_dst, branch2, dst_name1);
-               remove_file(0, ren1_dst, 0);
-       }
-       if (path_list_has_path(&current_directory_set, ren2_dst)) {
-               dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
-               output(1, "%s is a directory in %s added as %s instead",
-                      ren2_dst, branch1, dst_name2);
-               remove_file(0, ren2_dst, 0);
-       }
-       if (index_only) {
-               remove_file_from_cache(dst_name1);
-               remove_file_from_cache(dst_name2);
-               /*
-                * Uncomment to leave the conflicting names in the resulting tree
-                *
-                * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
-                * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
-                */
-       } else {
-               update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
-               update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
-       }
-       while (delp--)
-               free(del[delp]);
-}
-
-static void conflict_rename_dir(struct rename *ren1,
-                               const char *branch1)
-{
-       char *new_path = unique_path(ren1->pair->two->path, branch1);
-       output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path);
-       remove_file(0, ren1->pair->two->path, 0);
-       update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
-       free(new_path);
-}
-
-static void conflict_rename_rename_2(struct rename *ren1,
-                                    const char *branch1,
-                                    struct rename *ren2,
-                                    const char *branch2)
-{
-       char *new_path1 = unique_path(ren1->pair->two->path, branch1);
-       char *new_path2 = unique_path(ren2->pair->two->path, branch2);
-       output(1, "Renamed %s to %s and %s to %s instead",
-              ren1->pair->one->path, new_path1,
-              ren2->pair->one->path, new_path2);
-       remove_file(0, ren1->pair->two->path, 0);
-       update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
-       update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
-       free(new_path2);
-       free(new_path1);
-}
-
-static int process_renames(struct path_list *a_renames,
-                          struct path_list *b_renames,
-                          const char *a_branch,
-                          const char *b_branch)
-{
-       int clean_merge = 1, i, j;
-       struct path_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
-       const struct rename *sre;
-
-       for (i = 0; i < a_renames->nr; i++) {
-               sre = a_renames->items[i].util;
-               path_list_insert(sre->pair->two->path, &a_by_dst)->util
-                       = sre->dst_entry;
-       }
-       for (i = 0; i < b_renames->nr; i++) {
-               sre = b_renames->items[i].util;
-               path_list_insert(sre->pair->two->path, &b_by_dst)->util
-                       = sre->dst_entry;
-       }
-
-       for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
-               int compare;
-               char *src;
-               struct path_list *renames1, *renames2, *renames2Dst;
-               struct rename *ren1 = NULL, *ren2 = NULL;
-               const char *branch1, *branch2;
-               const char *ren1_src, *ren1_dst;
-
-               if (i >= a_renames->nr) {
-                       compare = 1;
-                       ren2 = b_renames->items[j++].util;
-               } else if (j >= b_renames->nr) {
-                       compare = -1;
-                       ren1 = a_renames->items[i++].util;
-               } else {
-                       compare = strcmp(a_renames->items[i].path,
-                                       b_renames->items[j].path);
-                       if (compare <= 0)
-                               ren1 = a_renames->items[i++].util;
-                       if (compare >= 0)
-                               ren2 = b_renames->items[j++].util;
-               }
-
-               /* TODO: refactor, so that 1/2 are not needed */
-               if (ren1) {
-                       renames1 = a_renames;
-                       renames2 = b_renames;
-                       renames2Dst = &b_by_dst;
-                       branch1 = a_branch;
-                       branch2 = b_branch;
-               } else {
-                       struct rename *tmp;
-                       renames1 = b_renames;
-                       renames2 = a_renames;
-                       renames2Dst = &a_by_dst;
-                       branch1 = b_branch;
-                       branch2 = a_branch;
-                       tmp = ren2;
-                       ren2 = ren1;
-                       ren1 = tmp;
-               }
-               src = ren1->pair->one->path;
-
-               ren1->dst_entry->processed = 1;
-               ren1->src_entry->processed = 1;
-
-               if (ren1->processed)
-                       continue;
-               ren1->processed = 1;
-
-               ren1_src = ren1->pair->one->path;
-               ren1_dst = ren1->pair->two->path;
-
-               if (ren2) {
-                       const char *ren2_src = ren2->pair->one->path;
-                       const char *ren2_dst = ren2->pair->two->path;
-                       /* Renamed in 1 and renamed in 2 */
-                       if (strcmp(ren1_src, ren2_src) != 0)
-                               die("ren1.src != ren2.src");
-                       ren2->dst_entry->processed = 1;
-                       ren2->processed = 1;
-                       if (strcmp(ren1_dst, ren2_dst) != 0) {
-                               clean_merge = 0;
-                               output(1, "CONFLICT (rename/rename): "
-                                      "Rename \"%s\"->\"%s\" in branch \"%s\" "
-                                      "rename \"%s\"->\"%s\" in \"%s\"%s",
-                                      src, ren1_dst, branch1,
-                                      src, ren2_dst, branch2,
-                                      index_only ? " (left unresolved)": "");
-                               if (index_only) {
-                                       remove_file_from_cache(src);
-                                       update_file(0, ren1->pair->one->sha1,
-                                                   ren1->pair->one->mode, src);
-                               }
-                               conflict_rename_rename(ren1, branch1, ren2, branch2);
-                       } else {
-                               struct merge_file_info mfi;
-                               remove_file(1, ren1_src, 1);
-                               mfi = merge_file(ren1->pair->one,
-                                                ren1->pair->two,
-                                                ren2->pair->two,
-                                                branch1,
-                                                branch2);
-                               if (mfi.merge || !mfi.clean)
-                                       output(1, "Renamed %s->%s", src, ren1_dst);
-
-                               if (mfi.merge)
-                                       output(2, "Auto-merged %s", ren1_dst);
-
-                               if (!mfi.clean) {
-                                       output(1, "CONFLICT (content): merge conflict in %s",
-                                              ren1_dst);
-                                       clean_merge = 0;
-
-                                       if (!index_only)
-                                               update_stages(ren1_dst,
-                                                             ren1->pair->one,
-                                                             ren1->pair->two,
-                                                             ren2->pair->two,
-                                                             1 /* clear */);
-                               }
-                               update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
-                       }
-               } else {
-                       /* Renamed in 1, maybe changed in 2 */
-                       struct path_list_item *item;
-                       /* we only use sha1 and mode of these */
-                       struct diff_filespec src_other, dst_other;
-                       int try_merge, stage = a_renames == renames1 ? 3: 2;
-
-                       remove_file(1, ren1_src, index_only || stage == 3);
-
-                       hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
-                       src_other.mode = ren1->src_entry->stages[stage].mode;
-                       hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
-                       dst_other.mode = ren1->dst_entry->stages[stage].mode;
-
-                       try_merge = 0;
-
-                       if (path_list_has_path(&current_directory_set, ren1_dst)) {
-                               clean_merge = 0;
-                               output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s "
-                                      " directory %s added in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren1_dst, branch2);
-                               conflict_rename_dir(ren1, branch1);
-                       } else if (sha_eq(src_other.sha1, null_sha1)) {
-                               clean_merge = 0;
-                               output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s "
-                                      "and deleted in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      branch2);
-                               update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
-                       } else if (!sha_eq(dst_other.sha1, null_sha1)) {
-                               const char *new_path;
-                               clean_merge = 0;
-                               try_merge = 1;
-                               output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. "
-                                      "%s added in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren1_dst, branch2);
-                               new_path = unique_path(ren1_dst, branch2);
-                               output(1, "Added as %s instead", new_path);
-                               update_file(0, dst_other.sha1, dst_other.mode, new_path);
-                       } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) {
-                               ren2 = item->util;
-                               clean_merge = 0;
-                               ren2->processed = 1;
-                               output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. "
-                                      "Renamed %s->%s in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren2->pair->one->path, ren2->pair->two->path, branch2);
-                               conflict_rename_rename_2(ren1, branch1, ren2, branch2);
-                       } else
-                               try_merge = 1;
-
-                       if (try_merge) {
-                               struct diff_filespec *o, *a, *b;
-                               struct merge_file_info mfi;
-                               src_other.path = (char *)ren1_src;
-
-                               o = ren1->pair->one;
-                               if (a_renames == renames1) {
-                                       a = ren1->pair->two;
-                                       b = &src_other;
-                               } else {
-                                       b = ren1->pair->two;
-                                       a = &src_other;
-                               }
-                               mfi = merge_file(o, a, b,
-                                               a_branch, b_branch);
-
-                               if (mfi.clean &&
-                                   sha_eq(mfi.sha, ren1->pair->two->sha1) &&
-                                   mfi.mode == ren1->pair->two->mode)
-                                       /*
-                                        * This messaged is part of
-                                        * t6022 test. If you change
-                                        * it update the test too.
-                                        */
-                                       output(3, "Skipped %s (merged same as existing)", ren1_dst);
-                               else {
-                                       if (mfi.merge || !mfi.clean)
-                                               output(1, "Renamed %s => %s", ren1_src, ren1_dst);
-                                       if (mfi.merge)
-                                               output(2, "Auto-merged %s", ren1_dst);
-                                       if (!mfi.clean) {
-                                               output(1, "CONFLICT (rename/modify): Merge conflict in %s",
-                                                      ren1_dst);
-                                               clean_merge = 0;
-
-                                               if (!index_only)
-                                                       update_stages(ren1_dst,
-                                                                     o, a, b, 1);
-                                       }
-                                       update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
-                               }
-                       }
-               }
-       }
-       path_list_clear(&a_by_dst, 0);
-       path_list_clear(&b_by_dst, 0);
-
-       return clean_merge;
-}
-
-static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
-{
-       return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
-}
-
-/* Per entry merge function */
-static int process_entry(const char *path, struct stage_data *entry,
-                        const char *branch1,
-                        const char *branch2)
-{
-       /*
-       printf("processing entry, clean cache: %s\n", index_only ? "yes": "no");
-       print_index_entry("\tpath: ", entry);
-       */
-       int clean_merge = 1;
-       unsigned o_mode = entry->stages[1].mode;
-       unsigned a_mode = entry->stages[2].mode;
-       unsigned b_mode = entry->stages[3].mode;
-       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
-       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
-       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
-
-       if (o_sha && (!a_sha || !b_sha)) {
-               /* Case A: Deleted in one */
-               if ((!a_sha && !b_sha) ||
-                   (sha_eq(a_sha, o_sha) && !b_sha) ||
-                   (!a_sha && sha_eq(b_sha, o_sha))) {
-                       /* Deleted in both or deleted in one and
-                        * unchanged in the other */
-                       if (a_sha)
-                               output(2, "Removed %s", path);
-                       /* do not touch working file if it did not exist */
-                       remove_file(1, path, !a_sha);
-               } else {
-                       /* Deleted in one and changed in the other */
-                       clean_merge = 0;
-                       if (!a_sha) {
-                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
-                                      "and modified in %s. Version %s of %s left in tree.",
-                                      path, branch1,
-                                      branch2, branch2, path);
-                               update_file(0, b_sha, b_mode, path);
-                       } else {
-                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
-                                      "and modified in %s. Version %s of %s left in tree.",
-                                      path, branch2,
-                                      branch1, branch1, path);
-                               update_file(0, a_sha, a_mode, path);
-                       }
-               }
-
-       } else if ((!o_sha && a_sha && !b_sha) ||
-                  (!o_sha && !a_sha && b_sha)) {
-               /* Case B: Added in one. */
-               const char *add_branch;
-               const char *other_branch;
-               unsigned mode;
-               const unsigned char *sha;
-               const char *conf;
-
-               if (a_sha) {
-                       add_branch = branch1;
-                       other_branch = branch2;
-                       mode = a_mode;
-                       sha = a_sha;
-                       conf = "file/directory";
-               } else {
-                       add_branch = branch2;
-                       other_branch = branch1;
-                       mode = b_mode;
-                       sha = b_sha;
-                       conf = "directory/file";
-               }
-               if (path_list_has_path(&current_directory_set, path)) {
-                       const char *new_path = unique_path(path, add_branch);
-                       clean_merge = 0;
-                       output(1, "CONFLICT (%s): There is a directory with name %s in %s. "
-                              "Added %s as %s",
-                              conf, path, other_branch, path, new_path);
-                       remove_file(0, path, 0);
-                       update_file(0, sha, mode, new_path);
-               } else {
-                       output(2, "Added %s", path);
-                       update_file(1, sha, mode, path);
-               }
-       } else if (a_sha && b_sha) {
-               /* Case C: Added in both (check for same permissions) and */
-               /* case D: Modified in both, but differently. */
-               const char *reason = "content";
-               struct merge_file_info mfi;
-               struct diff_filespec o, a, b;
-
-               if (!o_sha) {
-                       reason = "add/add";
-                       o_sha = (unsigned char *)null_sha1;
-               }
-               output(2, "Auto-merged %s", path);
-               o.path = a.path = b.path = (char *)path;
-               hashcpy(o.sha1, o_sha);
-               o.mode = o_mode;
-               hashcpy(a.sha1, a_sha);
-               a.mode = a_mode;
-               hashcpy(b.sha1, b_sha);
-               b.mode = b_mode;
-
-               mfi = merge_file(&o, &a, &b,
-                                branch1, branch2);
-
-               clean_merge = mfi.clean;
-               if (mfi.clean)
-                       update_file(1, mfi.sha, mfi.mode, path);
-               else if (S_ISGITLINK(mfi.mode))
-                       output(1, "CONFLICT (submodule): Merge conflict in %s "
-                              "- needs %s", path, sha1_to_hex(b.sha1));
-               else {
-                       output(1, "CONFLICT (%s): Merge conflict in %s",
-                                       reason, path);
-
-                       if (index_only)
-                               update_file(0, mfi.sha, mfi.mode, path);
-                       else
-                               update_file_flags(mfi.sha, mfi.mode, path,
-                                             0 /* update_cache */, 1 /* update_working_directory */);
-               }
-       } else if (!o_sha && !a_sha && !b_sha) {
-               /*
-                * this entry was deleted altogether. a_mode == 0 means
-                * we had that path and want to actively remove it.
-                */
-               remove_file(1, path, !a_mode);
-       } else
-               die("Fatal merge failure, shouldn't happen.");
-
-       return clean_merge;
-}
-
-static int merge_trees(struct tree *head,
-                      struct tree *merge,
-                      struct tree *common,
-                      const char *branch1,
-                      const char *branch2,
-                      struct tree **result)
-{
-       int code, clean;
-
-       if (subtree_merge) {
-               merge = shift_tree_object(head, merge);
-               common = shift_tree_object(head, common);
-       }
-
-       if (sha_eq(common->object.sha1, merge->object.sha1)) {
-               output(0, "Already uptodate!");
-               *result = head;
-               return 1;
-       }
-
-       code = git_merge_trees(index_only, common, head, merge);
-
-       if (code != 0)
-               die("merging of trees %s and %s failed",
-                   sha1_to_hex(head->object.sha1),
-                   sha1_to_hex(merge->object.sha1));
-
-       if (unmerged_index()) {
-               struct path_list *entries, *re_head, *re_merge;
-               int i;
-               path_list_clear(&current_file_set, 1);
-               path_list_clear(&current_directory_set, 1);
-               get_files_dirs(head);
-               get_files_dirs(merge);
-
-               entries = get_unmerged();
-               re_head  = get_renames(head, common, head, merge, entries);
-               re_merge = get_renames(merge, common, head, merge, entries);
-               clean = process_renames(re_head, re_merge,
-                               branch1, branch2);
-               for (i = 0; i < entries->nr; i++) {
-                       const char *path = entries->items[i].path;
-                       struct stage_data *e = entries->items[i].util;
-                       if (!e->processed
-                               && !process_entry(path, e, branch1, branch2))
-                               clean = 0;
-               }
-
-               path_list_clear(re_merge, 0);
-               path_list_clear(re_head, 0);
-               path_list_clear(entries, 1);
-
-       }
-       else
-               clean = 1;
-
-       if (index_only)
-               *result = git_write_tree();
-
-       return clean;
-}
-
-static struct commit_list *reverse_commit_list(struct commit_list *list)
-{
-       struct commit_list *next = NULL, *current, *backup;
-       for (current = list; current; current = backup) {
-               backup = current->next;
-               current->next = next;
-               next = current;
-       }
-       return next;
-}
-
-/*
- * Merge the commits h1 and h2, return the resulting virtual
- * commit object and a flag indicating the cleanness of the merge.
- */
-static int merge(struct commit *h1,
-                struct commit *h2,
-                const char *branch1,
-                const char *branch2,
-                struct commit_list *ca,
-                struct commit **result)
-{
-       struct commit_list *iter;
-       struct commit *merged_common_ancestors;
-       struct tree *mrtree = mrtree;
-       int clean;
-
-       if (show(4)) {
-               output(4, "Merging:");
-               output_commit_title(h1);
-               output_commit_title(h2);
-       }
-
-       if (!ca) {
-               ca = get_merge_bases(h1, h2, 1);
-               ca = reverse_commit_list(ca);
-       }
-
-       if (show(5)) {
-               output(5, "found %u common ancestor(s):", commit_list_count(ca));
-               for (iter = ca; iter; iter = iter->next)
-                       output_commit_title(iter->item);
-       }
-
-       merged_common_ancestors = pop_commit(&ca);
-       if (merged_common_ancestors == NULL) {
-               /* if there is no common ancestor, make an empty tree */
-               struct tree *tree = xcalloc(1, sizeof(struct tree));
-
-               tree->object.parsed = 1;
-               tree->object.type = OBJ_TREE;
-               pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
-               merged_common_ancestors = make_virtual_commit(tree, "ancestor");
-       }
-
-       for (iter = ca; iter; iter = iter->next) {
-               call_depth++;
-               /*
-                * When the merge fails, the result contains files
-                * with conflict markers. The cleanness flag is
-                * ignored, it was never actually used, as result of
-                * merge_trees has always overwritten it: the committed
-                * "conflicts" were already resolved.
-                */
-               discard_cache();
-               merge(merged_common_ancestors, iter->item,
-                     "Temporary merge branch 1",
-                     "Temporary merge branch 2",
-                     NULL,
-                     &merged_common_ancestors);
-               call_depth--;
-
-               if (!merged_common_ancestors)
-                       die("merge returned no commit");
-       }
-
-       discard_cache();
-       if (!call_depth) {
-               read_cache();
-               index_only = 0;
-       } else
-               index_only = 1;
-
-       clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,
-                           branch1, branch2, &mrtree);
-
-       if (index_only) {
-               *result = make_virtual_commit(mrtree, "merged tree");
-               commit_list_insert(h1, &(*result)->parents);
-               commit_list_insert(h2, &(*result)->parents->next);
-       }
-       flush_output();
-       return clean;
-}
-
-static const char *better_branch_name(const char *branch)
-{
-       static char githead_env[8 + 40 + 1];
-       char *name;
-
-       if (strlen(branch) != 40)
-               return branch;
-       sprintf(githead_env, "GITHEAD_%s", branch);
-       name = getenv(githead_env);
-       return name ? name : branch;
-}
-
-static struct commit *get_ref(const char *ref)
-{
-       unsigned char sha1[20];
-       struct object *object;
-
-       if (get_sha1(ref, sha1))
-               die("Could not resolve ref '%s'", ref);
-       object = deref_tag(parse_object(sha1), ref, strlen(ref));
-       if (object->type == OBJ_TREE)
-               return make_virtual_commit((struct tree*)object,
-                       better_branch_name(ref));
-       if (object->type != OBJ_COMMIT)
-               return NULL;
-       if (parse_commit((struct commit *)object))
-               die("Could not parse commit '%s'", sha1_to_hex(object->sha1));
-       return (struct commit *)object;
-}
-
-static int merge_config(const char *var, const char *value)
-{
-       if (!strcasecmp(var, "merge.verbosity")) {
-               verbosity = git_config_int(var, value);
-               return 0;
-       }
-       if (!strcasecmp(var, "diff.renamelimit")) {
-               rename_limit = git_config_int(var, value);
-               return 0;
-       }
-       return git_default_config(var, value);
-}
-
-int main(int argc, char *argv[])
-{
-       static const char *bases[20];
-       static unsigned bases_count = 0;
-       int i, clean;
-       const char *branch1, *branch2;
-       struct commit *result, *h1, *h2;
-       struct commit_list *ca = NULL;
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-       int index_fd;
-
-       if (argv[0]) {
-               int namelen = strlen(argv[0]);
-               if (8 < namelen &&
-                   !strcmp(argv[0] + namelen - 8, "-subtree"))
-                       subtree_merge = 1;
-       }
-
-       git_config(merge_config);
-       if (getenv("GIT_MERGE_VERBOSITY"))
-               verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
-
-       if (argc < 4)
-               die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
-
-       for (i = 1; i < argc; ++i) {
-               if (!strcmp(argv[i], "--"))
-                       break;
-               if (bases_count < sizeof(bases)/sizeof(*bases))
-                       bases[bases_count++] = argv[i];
-       }
-       if (argc - i != 3) /* "--" "<head>" "<remote>" */
-               die("Not handling anything other than two heads merge.");
-       if (verbosity >= 5)
-               buffer_output = 0;
-
-       branch1 = argv[++i];
-       branch2 = argv[++i];
-
-       h1 = get_ref(branch1);
-       h2 = get_ref(branch2);
-
-       branch1 = better_branch_name(branch1);
-       branch2 = better_branch_name(branch2);
-
-       if (show(3))
-               printf("Merging %s with %s\n", branch1, branch2);
-
-       index_fd = hold_locked_index(lock, 1);
-
-       for (i = 0; i < bases_count; i++) {
-               struct commit *ancestor = get_ref(bases[i]);
-               ca = commit_list_insert(ancestor, &ca);
-       }
-       clean = merge(h1, h2, branch1, branch2, ca, &result);
-
-       if (active_cache_changed &&
-           (write_cache(index_fd, active_cache, active_nr) ||
-            commit_locked_index(lock)))
-                       die ("unable to write %s", get_index_file());
-
-       return clean ? 0: 1;
-}
diff --git a/merge-recursive.h b/merge-recursive.h
new file mode 100644 (file)
index 0000000..f37630a
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef MERGE_RECURSIVE_H
+#define MERGE_RECURSIVE_H
+
+int merge_recursive(struct commit *h1,
+                   struct commit *h2,
+                   const char *branch1,
+                   const char *branch2,
+                   struct commit_list *ancestors,
+                   struct commit **result);
+
+int merge_trees(struct tree *head,
+               struct tree *merge,
+               struct tree *common,
+               const char *branch1,
+               const char *branch2,
+               struct tree **result);
+
+struct tree *write_tree_from_memory(void);
+
+#endif
diff --git a/object-refs.c b/object-refs.c
deleted file mode 100644 (file)
index 5345671..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-#include "cache.h"
-#include "object.h"
-#include "decorate.h"
-
-int track_object_refs = 0;
-
-static struct decoration ref_decorate;
-
-struct object_refs *lookup_object_refs(struct object *base)
-{
-       return lookup_decoration(&ref_decorate, base);
-}
-
-static void add_object_refs(struct object *obj, struct object_refs *refs)
-{
-       if (add_decoration(&ref_decorate, obj, refs))
-               die("object %s tried to add refs twice!", sha1_to_hex(obj->sha1));
-}
-
-struct object_refs *alloc_object_refs(unsigned count)
-{
-       struct object_refs *refs;
-       size_t size = sizeof(*refs) + count*sizeof(struct object *);
-
-       refs = xcalloc(1, size);
-       refs->count = count;
-       return refs;
-}
-
-static int compare_object_pointers(const void *a, const void *b)
-{
-       const struct object * const *pa = a;
-       const struct object * const *pb = b;
-       if (*pa == *pb)
-               return 0;
-       else if (*pa < *pb)
-               return -1;
-       else
-               return 1;
-}
-
-void set_object_refs(struct object *obj, struct object_refs *refs)
-{
-       unsigned int i, j;
-
-       /* Do not install empty list of references */
-       if (refs->count < 1) {
-               free(refs);
-               return;
-       }
-
-       /* Sort the list and filter out duplicates */
-       qsort(refs->ref, refs->count, sizeof(refs->ref[0]),
-             compare_object_pointers);
-       for (i = j = 1; i < refs->count; i++) {
-               if (refs->ref[i] != refs->ref[i - 1])
-                       refs->ref[j++] = refs->ref[i];
-       }
-       if (j < refs->count) {
-               /* Duplicates were found - reallocate list */
-               size_t size = sizeof(*refs) + j*sizeof(struct object *);
-               refs->count = j;
-               refs = xrealloc(refs, size);
-       }
-
-       for (i = 0; i < refs->count; i++)
-               refs->ref[i]->used = 1;
-       add_object_refs(obj, refs);
-}
-
-void mark_reachable(struct object *obj, unsigned int mask)
-{
-       const struct object_refs *refs;
-
-       if (!track_object_refs)
-               die("cannot do reachability with object refs turned off");
-       /* If we've been here already, don't bother */
-       if (obj->flags & mask)
-               return;
-       obj->flags |= mask;
-       refs = lookup_object_refs(obj);
-       if (refs) {
-               unsigned i;
-               for (i = 0; i < refs->count; i++)
-                       mark_reachable(refs->ref[i], mask);
-       }
-}
index 5a5ebe27b0db0506dc1a6606d30e328c53275c18..50b6528001fe4bafdfe70126dc2078860c3d1969 100644 (file)
--- a/object.c
+++ b/object.c
@@ -140,7 +140,8 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
        if (type == OBJ_BLOB) {
                struct blob *blob = lookup_blob(sha1);
                if (blob) {
-                       parse_blob_buffer(blob, buffer, size);
+                       if (parse_blob_buffer(blob, buffer, size))
+                               return NULL;
                        obj = &blob->object;
                }
        } else if (type == OBJ_TREE) {
@@ -148,14 +149,16 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
                if (tree) {
                        obj = &tree->object;
                        if (!tree->object.parsed) {
-                               parse_tree_buffer(tree, buffer, size);
+                               if (parse_tree_buffer(tree, buffer, size))
+                                       return NULL;
                                eaten = 1;
                        }
                }
        } else if (type == OBJ_COMMIT) {
                struct commit *commit = lookup_commit(sha1);
                if (commit) {
-                       parse_commit_buffer(commit, buffer, size);
+                       if (parse_commit_buffer(commit, buffer, size))
+                               return NULL;
                        if (!commit->buffer) {
                                commit->buffer = buffer;
                                eaten = 1;
@@ -165,7 +168,8 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
        } else if (type == OBJ_TAG) {
                struct tag *tag = lookup_tag(sha1);
                if (tag) {
-                       parse_tag_buffer(tag, buffer, size);
+                       if (parse_tag_buffer(tag, buffer, size))
+                              return NULL;
                        obj = &tag->object;
                }
        } else {
index 397bbfa090cd281214013c42a5f0b1de6063a861..036bd66fe9b6591e959e6df51160e636ab1a682e 100644 (file)
--- a/object.h
+++ b/object.h
@@ -35,14 +35,11 @@ struct object {
        unsigned char sha1[20];
 };
 
-extern int track_object_refs;
-
 extern const char *typename(unsigned int type);
 extern int type_from_string(const char *str);
 
 extern unsigned int get_max_object_index(void);
 extern struct object *get_indexed_object(unsigned int);
-extern struct object_refs *lookup_object_refs(struct object *);
 
 /** Internal only **/
 struct object *lookup_object(const unsigned char *sha1);
@@ -61,11 +58,6 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
 /** Returns the object, with potentially excess memory allocated. **/
 struct object *lookup_unknown_object(const unsigned  char *sha1);
 
-struct object_refs *alloc_object_refs(unsigned count);
-void set_object_refs(struct object *obj, struct object_refs *refs);
-
-void mark_reachable(struct object *obj, unsigned int mask);
-
 struct object_list *object_list_insert(struct object *item,
                                       struct object_list **list_p);
 
index d7dd62bb8346c4cac8dbd7334e999a450c21c5ab..0f8ad2c00f21d9a0a64b541ed95fff91650d7175 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "pack.h"
+#include "pack-revindex.h"
 
 struct idx_entry
 {
@@ -101,8 +102,10 @@ static int verify_packfile(struct packed_git *p,
 static void show_pack_info(struct packed_git *p)
 {
        uint32_t nr_objects, i, chain_histogram[MAX_CHAIN+1];
+
        nr_objects = p->num_objects;
        memset(chain_histogram, 0, sizeof(chain_histogram));
+       init_pack_revindex();
 
        for (i = 0; i < nr_objects; i++) {
                const unsigned char *sha1;
@@ -125,11 +128,11 @@ static void show_pack_info(struct packed_git *p)
                                                 base_sha1);
                printf("%s ", sha1_to_hex(sha1));
                if (!delta_chain_length)
-                       printf("%-6s %lu %"PRIuMAX"\n",
-                              type, size, (uintmax_t)offset);
+                       printf("%-6s %lu %lu %"PRIuMAX"\n",
+                              type, size, store_size, (uintmax_t)offset);
                else {
-                       printf("%-6s %lu %"PRIuMAX" %u %s\n",
-                              type, size, (uintmax_t)offset,
+                       printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
+                              type, size, store_size, (uintmax_t)offset,
                               delta_chain_length, sha1_to_hex(base_sha1));
                        if (delta_chain_length <= MAX_CHAIN)
                                chain_histogram[delta_chain_length]++;
diff --git a/pack-revindex.c b/pack-revindex.c
new file mode 100644 (file)
index 0000000..a8aa2cd
--- /dev/null
@@ -0,0 +1,142 @@
+#include "cache.h"
+#include "pack-revindex.h"
+
+/*
+ * Pack index for existing packs give us easy access to the offsets into
+ * corresponding pack file where each object's data starts, but the entries
+ * do not store the size of the compressed representation (uncompressed
+ * size is easily available by examining the pack entry header).  It is
+ * also rather expensive to find the sha1 for an object given its offset.
+ *
+ * We build a hashtable of existing packs (pack_revindex), and keep reverse
+ * index here -- pack index file is sorted by object name mapping to offset;
+ * this pack_revindex[].revindex array is a list of offset/index_nr pairs
+ * ordered by offset, so if you know the offset of an object, next offset
+ * is where its packed representation ends and the index_nr can be used to
+ * get the object sha1 from the main index.
+ */
+
+struct pack_revindex {
+       struct packed_git *p;
+       struct revindex_entry *revindex;
+};
+
+static struct pack_revindex *pack_revindex;
+static int pack_revindex_hashsz;
+
+static int pack_revindex_ix(struct packed_git *p)
+{
+       unsigned long ui = (unsigned long)p;
+       int i;
+
+       ui = ui ^ (ui >> 16); /* defeat structure alignment */
+       i = (int)(ui % pack_revindex_hashsz);
+       while (pack_revindex[i].p) {
+               if (pack_revindex[i].p == p)
+                       return i;
+               if (++i == pack_revindex_hashsz)
+                       i = 0;
+       }
+       return -1 - i;
+}
+
+void init_pack_revindex(void)
+{
+       int num;
+       struct packed_git *p;
+
+       for (num = 0, p = packed_git; p; p = p->next)
+               num++;
+       if (!num)
+               return;
+       pack_revindex_hashsz = num * 11;
+       pack_revindex = xcalloc(sizeof(*pack_revindex), pack_revindex_hashsz);
+       for (p = packed_git; p; p = p->next) {
+               num = pack_revindex_ix(p);
+               num = - 1 - num;
+               pack_revindex[num].p = p;
+       }
+       /* revindex elements are lazily initialized */
+}
+
+static int cmp_offset(const void *a_, const void *b_)
+{
+       const struct revindex_entry *a = a_;
+       const struct revindex_entry *b = b_;
+       return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0;
+}
+
+/*
+ * Ordered list of offsets of objects in the pack.
+ */
+static void create_pack_revindex(struct pack_revindex *rix)
+{
+       struct packed_git *p = rix->p;
+       int num_ent = p->num_objects;
+       int i;
+       const char *index = p->index_data;
+
+       rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
+       index += 4 * 256;
+
+       if (p->index_version > 1) {
+               const uint32_t *off_32 =
+                       (uint32_t *)(index + 8 + p->num_objects * (20 + 4));
+               const uint32_t *off_64 = off_32 + p->num_objects;
+               for (i = 0; i < num_ent; i++) {
+                       uint32_t off = ntohl(*off_32++);
+                       if (!(off & 0x80000000)) {
+                               rix->revindex[i].offset = off;
+                       } else {
+                               rix->revindex[i].offset =
+                                       ((uint64_t)ntohl(*off_64++)) << 32;
+                               rix->revindex[i].offset |=
+                                       ntohl(*off_64++);
+                       }
+                       rix->revindex[i].nr = i;
+               }
+       } else {
+               for (i = 0; i < num_ent; i++) {
+                       uint32_t hl = *((uint32_t *)(index + 24 * i));
+                       rix->revindex[i].offset = ntohl(hl);
+                       rix->revindex[i].nr = i;
+               }
+       }
+
+       /* This knows the pack format -- the 20-byte trailer
+        * follows immediately after the last object data.
+        */
+       rix->revindex[num_ent].offset = p->pack_size - 20;
+       rix->revindex[num_ent].nr = -1;
+       qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset);
+}
+
+struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs)
+{
+       int num;
+       int lo, hi;
+       struct pack_revindex *rix;
+       struct revindex_entry *revindex;
+
+       num = pack_revindex_ix(p);
+       if (num < 0)
+               die("internal error: pack revindex uninitialized");
+
+       rix = &pack_revindex[num];
+       if (!rix->revindex)
+               create_pack_revindex(rix);
+       revindex = rix->revindex;
+
+       lo = 0;
+       hi = p->num_objects + 1;
+       do {
+               int mi = (lo + hi) / 2;
+               if (revindex[mi].offset == ofs) {
+                       return revindex + mi;
+               } else if (ofs < revindex[mi].offset)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       } while (lo < hi);
+       die("internal error: pack revindex corrupt");
+}
diff --git a/pack-revindex.h b/pack-revindex.h
new file mode 100644 (file)
index 0000000..c3527a7
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef PACK_REVINDEX_H
+#define PACK_REVINDEX_H
+
+struct revindex_entry {
+       off_t offset;
+       unsigned int nr;
+};
+
+void init_pack_revindex(void);
+struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs);
+
+#endif
diff --git a/pager.c b/pager.c
index 0376953cb1b8a4095346e0d12ecef6e7db9c48c9..ca002f9f791b6cdb8257836be9e13fe648afa1cc 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -57,6 +57,7 @@ void setup_pager(void)
        /* return in the child */
        if (!pid) {
                dup2(fd[1], 1);
+               dup2(fd[1], 2);
                close(fd[0]);
                close(fd[1]);
                return;
index d9562ba5047ff1c9994755a4dbec5162b551c788..b32c9ea66c7ae1e83564cd25d63d5cfd6c1589b5 100644 (file)
@@ -6,7 +6,8 @@
 
 struct optparse_t {
        const char **argv;
-       int argc;
+       const char **out;
+       int argc, cpidx;
        const char *opt;
 };
 
@@ -159,6 +160,16 @@ static int parse_long_opt(struct optparse_t *p, const char *arg,
                        continue;
 
                rest = skip_prefix(arg, options->long_name);
+               if (options->type == OPTION_ARGUMENT) {
+                       if (!rest)
+                               continue;
+                       if (*rest == '=')
+                               return opterror(options, "takes no value", flags);
+                       if (*rest)
+                               continue;
+                       p->out[p->cpidx++] = arg - 2;
+                       return 0;
+               }
                if (!rest) {
                        /* abbreviated? */
                        if (!strncmp(options->long_name, arg, arg_end - arg)) {
@@ -242,14 +253,13 @@ static NORETURN void usage_with_options_internal(const char * const *,
 int parse_options(int argc, const char **argv, const struct option *options,
                   const char * const usagestr[], int flags)
 {
-       struct optparse_t args = { argv + 1, argc - 1, NULL };
-       int j = 0;
+       struct optparse_t args = { argv + 1, argv, argc - 1, 0, NULL };
 
        for (; args.argc; args.argc--, args.argv++) {
                const char *arg = args.argv[0];
 
                if (*arg != '-' || !arg[1]) {
-                       argv[j++] = args.argv[0];
+                       args.out[args.cpidx++] = args.argv[0];
                        continue;
                }
 
@@ -286,9 +296,9 @@ int parse_options(int argc, const char **argv, const struct option *options,
                        usage_with_options(usagestr, options);
        }
 
-       memmove(argv + j, args.argv, args.argc * sizeof(*argv));
-       argv[j + args.argc] = NULL;
-       return j + args.argc;
+       memmove(args.out + args.cpidx, args.argv, args.argc * sizeof(*args.out));
+       args.out[args.cpidx + args.argc] = NULL;
+       return args.cpidx + args.argc;
 }
 
 #define USAGE_OPTS_WIDTH 24
@@ -328,6 +338,8 @@ void usage_with_options_internal(const char * const *usagestr,
                        pos += fprintf(stderr, "--%s", opts->long_name);
 
                switch (opts->type) {
+               case OPTION_ARGUMENT:
+                       break;
                case OPTION_INTEGER:
                        if (opts->flags & PARSE_OPT_OPTARG)
                                pos += fprintf(stderr, " [<n>]");
index 102ac31fb727acfdc3c2159e1525c7bcca94e1ef..dc0807834f9135b82c289b429a8d2ea13ee53be8 100644 (file)
@@ -4,6 +4,7 @@
 enum parse_opt_type {
        /* special types */
        OPTION_END,
+       OPTION_ARGUMENT,
        OPTION_GROUP,
        /* options with no arguments */
        OPTION_BIT,
@@ -84,6 +85,7 @@ struct option {
 };
 
 #define OPT_END()                   { OPTION_END }
+#define OPT_ARGUMENT(l, h)          { OPTION_ARGUMENT, 0, (l), NULL, NULL, (h) }
 #define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
 #define OPT_BIT(s, l, v, h, b)      { OPTION_BIT, (s), (l), (v), NULL, (h), 0, NULL, (b) }
 #define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v), NULL, (h) }
diff --git a/path.c b/path.c
index 42609524a55ad017557d48bedf26906cc4405d0a..f4ed979997b6a968ba1987a199beae39035d5da2 100644 (file)
--- a/path.c
+++ b/path.c
@@ -283,7 +283,7 @@ int adjust_shared_perm(const char *path)
                            ? (S_IXGRP|S_IXOTH)
                            : 0));
        if (S_ISDIR(mode))
-               mode |= S_ISGID;
+               mode |= FORCE_DIR_SET_GID;
        if ((mode & st.st_mode) != mode && chmod(path, mode) < 0)
                return -2;
        return 0;
@@ -311,8 +311,10 @@ const char *make_absolute_path(const char *path)
                        if (last_slash) {
                                *last_slash = '\0';
                                last_elem = xstrdup(last_slash + 1);
-                       } else
+                       } else {
                                last_elem = xstrdup(buf);
+                               *buf = '\0';
+                       }
                }
 
                if (*buf) {
index b987ff245b310a6693dc69ba8c71ef2915da7864..703f52176bf0b5cbf52cf1f77ba55ba6d87fac94 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -30,8 +30,7 @@ enum cmit_fmt get_commit_format(const char *arg)
        if (*arg == '=')
                arg++;
        if (!prefixcmp(arg, "format:")) {
-               if (user_format)
-                       free(user_format);
+               free(user_format);
                user_format = xstrdup(arg + 7);
                return CMIT_FMT_USERFORMAT;
        }
@@ -110,9 +109,9 @@ needquote:
        strbuf_addstr(sb, "?=");
 }
 
-static void add_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 char *what, enum cmit_fmt fmt, struct strbuf *sb,
+                 const char *line, enum date_mode dmode,
+                 const char *encoding)
 {
        char *date;
        int namelen;
@@ -282,59 +281,59 @@ static char *logmsg_reencode(const struct commit *commit,
        return out;
 }
 
-static void format_person_part(struct strbuf *sb, char part,
+static size_t format_person_part(struct strbuf *sb, char part,
                                const char *msg, int len)
 {
+       /* currently all placeholders have same length */
+       const int placeholder_len = 2;
        int start, end, tz = 0;
-       unsigned long date;
+       unsigned long date = 0;
        char *ep;
 
-       /* parse name */
+       /* advance 'end' to point to email start delimiter */
        for (end = 0; end < len && msg[end] != '<'; end++)
                ; /* do nothing */
+
        /*
-        * If it does not even have a '<' and '>', that is
-        * quite a bogus commit author and we discard it;
-        * this is in line with add_user_info() that is used
-        * in the normal codepath.  When end points at the '<'
-        * that we found, it should have matching '>' later,
-        * which means start (beginning of email address) must
-        * be strictly below len.
+        * When end points at the '<' that we found, it should have
+        * matching '>' later, which means 'end' must be strictly
+        * below len - 1.
         */
-       start = end + 1;
-       if (start >= len - 1)
-               return;
-       while (end > 0 && isspace(msg[end - 1]))
-               end--;
+       if (end >= len - 2)
+               goto skip;
+
        if (part == 'n') {      /* name */
+               while (end > 0 && isspace(msg[end - 1]))
+                       end--;
                strbuf_add(sb, msg, end);
-               return;
+               return placeholder_len;
        }
+       start = ++end; /* save email start position */
 
-       /* parse email */
-       for (end = start; end < len && msg[end] != '>'; end++)
+       /* advance 'end' to point to email end delimiter */
+       for ( ; end < len && msg[end] != '>'; end++)
                ; /* do nothing */
 
        if (end >= len)
-               return;
+               goto skip;
 
        if (part == 'e') {      /* email */
                strbuf_add(sb, msg + start, end - start);
-               return;
+               return placeholder_len;
        }
 
-       /* parse date */
+       /* advance 'start' to point to date start delimiter */
        for (start = end + 1; start < len && isspace(msg[start]); start++)
                ; /* do nothing */
        if (start >= len)
-               return;
+               goto skip;
        date = strtoul(msg + start, &ep, 10);
        if (msg + start == ep)
-               return;
+               goto skip;
 
        if (part == 't') {      /* date, UNIX timestamp */
                strbuf_add(sb, msg + start, ep - (msg + start));
-               return;
+               return placeholder_len;
        }
 
        /* parse tz */
@@ -349,17 +348,28 @@ static void format_person_part(struct strbuf *sb, char part,
        switch (part) {
        case 'd':       /* date */
                strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));
-               return;
+               return placeholder_len;
        case 'D':       /* date, RFC2822 style */
                strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
-               return;
+               return placeholder_len;
        case 'r':       /* date, relative */
                strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));
-               return;
+               return placeholder_len;
        case 'i':       /* date, ISO 8601 */
                strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));
-               return;
+               return placeholder_len;
        }
+
+skip:
+       /*
+        * bogus commit, 'sb' cannot be updated, but we still need to
+        * compute a valid return value.
+        */
+       if (part == 'n' || part == 'e' || part == 't' || part == 'd'
+           || part == 'D' || part == 'r' || part == 'i')
+               return placeholder_len;
+
+       return 0; /* unknown placeholder */
 }
 
 struct chunk {
@@ -440,7 +450,7 @@ static void parse_commit_header(struct format_commit_context *context)
        context->commit_header_parsed = 1;
 }
 
-static void format_commit_item(struct strbuf *sb, const char *placeholder,
+static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
                                void *context)
 {
        struct format_commit_context *c = context;
@@ -451,23 +461,23 @@ static void format_commit_item(struct strbuf *sb, const char *placeholder,
        /* these are independent of the commit */
        switch (placeholder[0]) {
        case 'C':
-               switch (placeholder[3]) {
-               case 'd':       /* red */
+               if (!prefixcmp(placeholder + 1, "red")) {
                        strbuf_addstr(sb, "\033[31m");
-                       return;
-               case 'e':       /* green */
+                       return 4;
+               } else if (!prefixcmp(placeholder + 1, "green")) {
                        strbuf_addstr(sb, "\033[32m");
-                       return;
-               case 'u':       /* blue */
+                       return 6;
+               } else if (!prefixcmp(placeholder + 1, "blue")) {
                        strbuf_addstr(sb, "\033[34m");
-                       return;
-               case 's':       /* reset color */
+                       return 5;
+               } else if (!prefixcmp(placeholder + 1, "reset")) {
                        strbuf_addstr(sb, "\033[m");
-                       return;
-               }
+                       return 6;
+               } else
+                       return 0;
        case 'n':               /* newline */
                strbuf_addch(sb, '\n');
-               return;
+               return 1;
        }
 
        /* these depend on the commit */
@@ -477,34 +487,34 @@ static void format_commit_item(struct strbuf *sb, const char *placeholder,
        switch (placeholder[0]) {
        case 'H':               /* commit hash */
                strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
-               return;
+               return 1;
        case 'h':               /* abbreviated commit hash */
                if (add_again(sb, &c->abbrev_commit_hash))
-                       return;
+                       return 1;
                strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
                                                     DEFAULT_ABBREV));
                c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
-               return;
+               return 1;
        case 'T':               /* tree hash */
                strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));
-               return;
+               return 1;
        case 't':               /* abbreviated tree hash */
                if (add_again(sb, &c->abbrev_tree_hash))
-                       return;
+                       return 1;
                strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
                                                     DEFAULT_ABBREV));
                c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
-               return;
+               return 1;
        case 'P':               /* parent hashes */
                for (p = commit->parents; p; p = p->next) {
                        if (p != commit->parents)
                                strbuf_addch(sb, ' ');
                        strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1));
                }
-               return;
+               return 1;
        case 'p':               /* abbreviated parent hashes */
                if (add_again(sb, &c->abbrev_parent_hashes))
-                       return;
+                       return 1;
                for (p = commit->parents; p; p = p->next) {
                        if (p != commit->parents)
                                strbuf_addch(sb, ' ');
@@ -513,14 +523,14 @@ static void format_commit_item(struct strbuf *sb, const char *placeholder,
                }
                c->abbrev_parent_hashes.len = sb->len -
                                              c->abbrev_parent_hashes.off;
-               return;
+               return 1;
        case 'm':               /* left/right/bottom */
                strbuf_addch(sb, (commit->object.flags & BOUNDARY)
                                 ? '-'
                                 : (commit->object.flags & SYMMETRIC_LEFT)
                                 ? '<'
                                 : '>');
-               return;
+               return 1;
        }
 
        /* For the rest we have to parse the commit header. */
@@ -528,66 +538,33 @@ static void format_commit_item(struct strbuf *sb, const char *placeholder,
                parse_commit_header(c);
 
        switch (placeholder[0]) {
-       case 's':
+       case 's':       /* subject */
                strbuf_add(sb, msg + c->subject.off, c->subject.len);
-               return;
-       case 'a':
-               format_person_part(sb, placeholder[1],
+               return 1;
+       case 'a':       /* author ... */
+               return format_person_part(sb, placeholder[1],
                                   msg + c->author.off, c->author.len);
-               return;
-       case 'c':
-               format_person_part(sb, placeholder[1],
+       case 'c':       /* committer ... */
+               return format_person_part(sb, placeholder[1],
                                   msg + c->committer.off, c->committer.len);
-               return;
-       case 'e':
+       case 'e':       /* encoding */
                strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
-               return;
-       case 'b':
+               return 1;
+       case 'b':       /* body */
                strbuf_addstr(sb, msg + c->body_off);
-               return;
+               return 1;
        }
+       return 0;       /* unknown placeholder */
 }
 
 void format_commit_message(const struct commit *commit,
                            const void *format, struct strbuf *sb)
 {
-       const char *placeholders[] = {
-               "H",            /* commit hash */
-               "h",            /* abbreviated commit hash */
-               "T",            /* tree hash */
-               "t",            /* abbreviated tree hash */
-               "P",            /* parent hashes */
-               "p",            /* abbreviated parent hashes */
-               "an",           /* author name */
-               "ae",           /* author email */
-               "ad",           /* author date */
-               "aD",           /* author date, RFC2822 style */
-               "ar",           /* author date, relative */
-               "at",           /* author date, UNIX timestamp */
-               "ai",           /* author date, ISO 8601 */
-               "cn",           /* committer name */
-               "ce",           /* committer email */
-               "cd",           /* committer date */
-               "cD",           /* committer date, RFC2822 style */
-               "cr",           /* committer date, relative */
-               "ct",           /* committer date, UNIX timestamp */
-               "ci",           /* committer date, ISO 8601 */
-               "e",            /* encoding */
-               "s",            /* subject */
-               "b",            /* body */
-               "Cred",         /* red */
-               "Cgreen",       /* green */
-               "Cblue",        /* blue */
-               "Creset",       /* reset color */
-               "n",            /* newline */
-               "m",            /* left/right/bottom */
-               NULL
-       };
        struct format_commit_context context;
 
        memset(&context, 0, sizeof(context));
        context.commit = commit;
-       strbuf_expand(sb, format, placeholders, format_commit_item, &context);
+       strbuf_expand(sb, format, format_commit_item, &context);
 }
 
 static void pp_header(enum cmit_fmt fmt,
@@ -643,23 +620,23 @@ static void pp_header(enum cmit_fmt fmt,
                 */
                if (!memcmp(line, "author ", 7)) {
                        strbuf_grow(sb, linelen + 80);
-                       add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+                       pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
                }
                if (!memcmp(line, "committer ", 10) &&
                    (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
-                       add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+                       pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
                }
        }
 }
 
-static void pp_title_line(enum cmit_fmt fmt,
-                         const char **msg_p,
-                         struct strbuf *sb,
-                         const char *subject,
-                         const char *after_subject,
-                         const char *encoding,
-                         int plain_non_ascii)
+void pp_title_line(enum cmit_fmt fmt,
+                  const char **msg_p,
+                  struct strbuf *sb,
+                  const char *subject,
+                  const char *after_subject,
+                  const char *encoding,
+                  int plain_non_ascii)
 {
        struct strbuf title;
 
@@ -708,10 +685,10 @@ static void pp_title_line(enum cmit_fmt fmt,
        strbuf_release(&title);
 }
 
-static void pp_remainder(enum cmit_fmt fmt,
-                        const char **msg_p,
-                        struct strbuf *sb,
-                        int indent)
+void pp_remainder(enum cmit_fmt fmt,
+                 const char **msg_p,
+                 struct strbuf *sb,
+                 int indent)
 {
        int first = 1;
        for (;;) {
diff --git a/quote.c b/quote.c
index 40702f6b725efade3e1fd61339421940f9fdb47d..d5cf9d8f94f37fe8ff9f964998c7f0525617e5bc 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -260,6 +260,48 @@ extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,
        fputc(terminator, fp);
 }
 
+/* quote path as relative to the given prefix */
+char *quote_path_relative(const char *in, int len,
+                         struct strbuf *out, const char *prefix)
+{
+       int needquote;
+
+       if (len < 0)
+               len = strlen(in);
+
+       /* "../" prefix itself does not need quoting, but "in" might. */
+       needquote = next_quote_pos(in, len) < len;
+       strbuf_setlen(out, 0);
+       strbuf_grow(out, len);
+
+       if (needquote)
+               strbuf_addch(out, '"');
+       if (prefix) {
+               int off = 0;
+               while (prefix[off] && off < len && prefix[off] == in[off])
+                       if (prefix[off] == '/') {
+                               prefix += off + 1;
+                               in += off + 1;
+                               len -= off + 1;
+                               off = 0;
+                       } else
+                               off++;
+
+               for (; *prefix; prefix++)
+                       if (*prefix == '/')
+                               strbuf_addstr(out, "../");
+       }
+
+       quote_c_style_counted (in, len, out, NULL, 1);
+
+       if (needquote)
+               strbuf_addch(out, '"');
+       if (!out->len)
+               strbuf_addstr(out, "./");
+
+       return out->buf;
+}
+
 /*
  * C-style name unquoting.
  *
diff --git a/quote.h b/quote.h
index 4da110ec01331f346705199dde709436622967cb..c5eea6f18e2dfabd071b73e6507c34c2b7b5e39f 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -47,6 +47,10 @@ extern void write_name_quoted(const char *name, FILE *, int terminator);
 extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,
                                  const char *name, FILE *, int terminator);
 
+/* quote path as relative to the given prefix */
+char *quote_path_relative(const char *in, int len,
+                         struct strbuf *out, const char *prefix);
+
 /* quoting as a string literal for other languages */
 extern void perl_quote_print(FILE *stream, const char *src);
 extern void python_quote_print(FILE *stream, const char *src);
index 6383401e2dd6bef3251a105ac136312dfdcdeb3c..3b1c18ff9b9060d0dd437ce89aedb8871c66c54c 100644 (file)
@@ -15,6 +15,8 @@ static void process_blob(struct blob *blob,
 {
        struct object *obj = &blob->object;
 
+       if (!blob)
+               die("bad blob object");
        if (obj->flags & SEEN)
                return;
        obj->flags |= SEEN;
@@ -39,6 +41,8 @@ static void process_tree(struct tree *tree,
        struct name_entry entry;
        struct name_path me;
 
+       if (!tree)
+               die("bad tree object");
        if (obj->flags & SEEN)
                return;
        obj->flags |= SEEN;
@@ -79,7 +83,8 @@ static void process_tag(struct tag *tag, struct object_array *p, const char *nam
 
        if (parse_tag(tag) < 0)
                die("bad tag object %s", sha1_to_hex(obj->sha1));
-       add_object(tag->tagged, p, NULL, name);
+       if (tag->tagged)
+               add_object(tag->tagged, p, NULL, name);
 }
 
 static void walk_commit_list(struct rev_info *revs)
@@ -150,7 +155,8 @@ static int add_one_reflog(const char *path, const unsigned char *sha1, int flag,
 static void add_one_tree(const unsigned char *sha1, struct rev_info *revs)
 {
        struct tree *tree = lookup_tree(sha1);
-       add_pending_object(revs, &tree->object, "");
+       if (tree)
+               add_pending_object(revs, &tree->object, "");
 }
 
 static void add_cache_tree(struct cache_tree *it, struct rev_info *revs)
@@ -176,7 +182,7 @@ static void add_cache_refs(struct rev_info *revs)
                 * lookup_blob() on them, to avoid populating the hash table
                 * with invalid information
                 */
-               if (S_ISGITLINK(ntohl(active_cache[i]->ce_mode)))
+               if (S_ISGITLINK(active_cache[i]->ce_mode))
                        continue;
 
                lookup_blob(active_cache[i]->sha1);
@@ -215,6 +221,7 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
         * Set up the revision walk - this will move all commits
         * from the pending list to the commit walking list.
         */
-       prepare_revision_walk(revs);
+       if (prepare_revision_walk(revs))
+               die("revision walk setup failed");
        walk_commit_list(revs);
 }
index 7db55883d65fd28c2eaa291b5688273532988d88..657f0c5894c65831b80ceee54d161d0beac1d733 100644 (file)
 
 struct index_state the_index;
 
+static unsigned int hash_name(const char *name, int namelen)
+{
+       unsigned int hash = 0x123;
+
+       do {
+               unsigned char c = *name++;
+               hash = hash*101 + c;
+       } while (--namelen);
+       return hash;
+}
+
+static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
+{
+       void **pos;
+       unsigned int hash;
+
+       if (ce->ce_flags & CE_HASHED)
+               return;
+       ce->ce_flags |= CE_HASHED;
+       ce->next = NULL;
+       hash = hash_name(ce->name, ce_namelen(ce));
+       pos = insert_hash(hash, ce, &istate->name_hash);
+       if (pos) {
+               ce->next = *pos;
+               *pos = ce;
+       }
+}
+
+static void lazy_init_name_hash(struct index_state *istate)
+{
+       int nr;
+
+       if (istate->name_hash_initialized)
+               return;
+       for (nr = 0; nr < istate->cache_nr; nr++)
+               hash_index_entry(istate, istate->cache[nr]);
+       istate->name_hash_initialized = 1;
+}
+
+static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
+{
+       ce->ce_flags &= ~CE_UNHASHED;
+       istate->cache[nr] = ce;
+       if (istate->name_hash_initialized)
+               hash_index_entry(istate, ce);
+}
+
+static void replace_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
+{
+       struct cache_entry *old = istate->cache[nr];
+
+       remove_index_entry(old);
+       set_index_entry(istate, nr, ce);
+       istate->cache_changed = 1;
+}
+
+int index_name_exists(struct index_state *istate, const char *name, int namelen)
+{
+       unsigned int hash = hash_name(name, namelen);
+       struct cache_entry *ce;
+
+       lazy_init_name_hash(istate);
+       ce = lookup_hash(hash, &istate->name_hash);
+
+       while (ce) {
+               if (!(ce->ce_flags & CE_UNHASHED)) {
+                       if (!cache_name_compare(name, namelen, ce->name, ce->ce_flags))
+                               return 1;
+               }
+               ce = ce->next;
+       }
+       return 0;
+}
+
 /*
  * This only updates the "non-critical" parts of the directory
  * cache, ie the parts that aren't tracked by GIT, and only used
@@ -30,20 +104,19 @@ struct index_state the_index;
  */
 void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
 {
-       ce->ce_ctime.sec = htonl(st->st_ctime);
-       ce->ce_mtime.sec = htonl(st->st_mtime);
-#ifdef USE_NSEC
-       ce->ce_ctime.nsec = htonl(st->st_ctim.tv_nsec);
-       ce->ce_mtime.nsec = htonl(st->st_mtim.tv_nsec);
-#endif
-       ce->ce_dev = htonl(st->st_dev);
-       ce->ce_ino = htonl(st->st_ino);
-       ce->ce_uid = htonl(st->st_uid);
-       ce->ce_gid = htonl(st->st_gid);
-       ce->ce_size = htonl(st->st_size);
+       ce->ce_ctime = st->st_ctime;
+       ce->ce_mtime = st->st_mtime;
+       ce->ce_dev = st->st_dev;
+       ce->ce_ino = st->st_ino;
+       ce->ce_uid = st->st_uid;
+       ce->ce_gid = st->st_gid;
+       ce->ce_size = st->st_size;
 
        if (assume_unchanged)
-               ce->ce_flags |= htons(CE_VALID);
+               ce->ce_flags |= CE_VALID;
+
+       if (S_ISREG(st->st_mode))
+               ce_mark_uptodate(ce);
 }
 
 static int ce_compare_data(struct cache_entry *ce, struct stat *st)
@@ -116,7 +189,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
                        return DATA_CHANGED;
                break;
        case S_IFDIR:
-               if (S_ISGITLINK(ntohl(ce->ce_mode)))
+               if (S_ISGITLINK(ce->ce_mode))
                        return 0;
        default:
                return TYPE_CHANGED;
@@ -128,14 +201,17 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
 {
        unsigned int changed = 0;
 
-       switch (ntohl(ce->ce_mode) & S_IFMT) {
+       if (ce->ce_flags & CE_REMOVE)
+               return MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED;
+
+       switch (ce->ce_mode & S_IFMT) {
        case S_IFREG:
                changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0;
                /* We consider only the owner x bit to be relevant for
                 * "mode changes"
                 */
                if (trust_executable_bit &&
-                   (0100 & (ntohl(ce->ce_mode) ^ st->st_mode)))
+                   (0100 & (ce->ce_mode ^ st->st_mode)))
                        changed |= MODE_CHANGED;
                break;
        case S_IFLNK:
@@ -149,32 +225,18 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
                else if (ce_compare_gitlink(ce))
                        changed |= DATA_CHANGED;
                return changed;
-       case 0: /* Special case: unmerged file in index */
-               return MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED;
        default:
-               die("internal error: ce_mode is %o", ntohl(ce->ce_mode));
+               die("internal error: ce_mode is %o", ce->ce_mode);
        }
-       if (ce->ce_mtime.sec != htonl(st->st_mtime))
-               changed |= MTIME_CHANGED;
-       if (ce->ce_ctime.sec != htonl(st->st_ctime))
-               changed |= CTIME_CHANGED;
-
-#ifdef USE_NSEC
-       /*
-        * nsec seems unreliable - not all filesystems support it, so
-        * as long as it is in the inode cache you get right nsec
-        * but after it gets flushed, you get zero nsec.
-        */
-       if (ce->ce_mtime.nsec != htonl(st->st_mtim.tv_nsec))
+       if (ce->ce_mtime != (unsigned int) st->st_mtime)
                changed |= MTIME_CHANGED;
-       if (ce->ce_ctime.nsec != htonl(st->st_ctim.tv_nsec))
+       if (ce->ce_ctime != (unsigned int) st->st_ctime)
                changed |= CTIME_CHANGED;
-#endif
 
-       if (ce->ce_uid != htonl(st->st_uid) ||
-           ce->ce_gid != htonl(st->st_gid))
+       if (ce->ce_uid != (unsigned int) st->st_uid ||
+           ce->ce_gid != (unsigned int) st->st_gid)
                changed |= OWNER_CHANGED;
-       if (ce->ce_ino != htonl(st->st_ino))
+       if (ce->ce_ino != (unsigned int) st->st_ino)
                changed |= INODE_CHANGED;
 
 #ifdef USE_STDEV
@@ -183,16 +245,22 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
         * clients will have different views of what "device"
         * the filesystem is on
         */
-       if (ce->ce_dev != htonl(st->st_dev))
+       if (ce->ce_dev != (unsigned int) st->st_dev)
                changed |= INODE_CHANGED;
 #endif
 
-       if (ce->ce_size != htonl(st->st_size))
+       if (ce->ce_size != (unsigned int) st->st_size)
                changed |= DATA_CHANGED;
 
        return changed;
 }
 
+static int is_racy_timestamp(struct index_state *istate, struct cache_entry *ce)
+{
+       return (istate->timestamp &&
+               ((unsigned int)istate->timestamp) <= ce->ce_mtime);
+}
+
 int ie_match_stat(struct index_state *istate,
                  struct cache_entry *ce, struct stat *st,
                  unsigned int options)
@@ -205,7 +273,7 @@ int ie_match_stat(struct index_state *istate,
         * If it's marked as always valid in the index, it's
         * valid whatever the checked-out copy says.
         */
-       if (!ignore_valid && (ce->ce_flags & htons(CE_VALID)))
+       if (!ignore_valid && (ce->ce_flags & CE_VALID))
                return 0;
 
        changed = ce_match_stat_basic(ce, st);
@@ -226,9 +294,7 @@ int ie_match_stat(struct index_state *istate,
         * whose mtime are the same as the index file timestamp more
         * carefully than others.
         */
-       if (!changed &&
-           istate->timestamp &&
-           istate->timestamp <= ntohl(ce->ce_mtime.sec)) {
+       if (!changed && is_racy_timestamp(istate, ce)) {
                if (assume_racy_is_modified)
                        changed |= DATA_CHANGED;
                else
@@ -257,7 +323,7 @@ int ie_modified(struct index_state *istate,
         * the length field is zero.  For other cases the ce_size
         * should match the SHA1 recorded in the index entry.
         */
-       if ((changed & DATA_CHANGED) && ce->ce_size != htonl(0))
+       if ((changed & DATA_CHANGED) && ce->ce_size != 0)
                return changed;
 
        changed_fs = ce_modified_check_fs(ce, st);
@@ -320,7 +386,7 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen)
        while (last > first) {
                int next = (last + first) >> 1;
                struct cache_entry *ce = istate->cache[next];
-               int cmp = cache_name_compare(name, namelen, ce->name, ntohs(ce->ce_flags));
+               int cmp = cache_name_compare(name, namelen, ce->name, ce->ce_flags);
                if (!cmp)
                        return next;
                if (cmp < 0) {
@@ -335,6 +401,9 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen)
 /* Remove entry, return true if there are more entries to go.. */
 int remove_index_entry_at(struct index_state *istate, int pos)
 {
+       struct cache_entry *ce = istate->cache[pos];
+
+       remove_index_entry(ce);
        istate->cache_changed = 1;
        istate->cache_nr--;
        if (pos >= istate->cache_nr)
@@ -405,7 +474,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
        size = cache_entry_size(namelen);
        ce = xcalloc(1, size);
        memcpy(ce->name, path, namelen);
-       ce->ce_flags = htons(namelen);
+       ce->ce_flags = namelen;
        fill_stat_cache_info(ce, &st);
 
        if (trust_executable_bit && has_symlinks)
@@ -427,6 +496,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
            !ie_match_stat(istate, istate->cache[pos], &st, ce_option)) {
                /* Nothing changed, really */
                free(ce);
+               ce_mark_uptodate(istate->cache[pos]);
                return 0;
        }
 
@@ -583,7 +653,7 @@ static int has_file_name(struct index_state *istate,
                        continue;
                if (p->name[len] != '/')
                        continue;
-               if (!ce_stage(p) && !p->ce_mode)
+               if (p->ce_flags & CE_REMOVE)
                        continue;
                retval = -1;
                if (!ok_to_replace)
@@ -616,7 +686,7 @@ static int has_dir_name(struct index_state *istate,
                }
                len = slash - name;
 
-               pos = index_name_pos(istate, name, ntohs(create_ce_flags(len, stage)));
+               pos = index_name_pos(istate, name, create_ce_flags(len, stage));
                if (pos >= 0) {
                        /*
                         * Found one, but not so fast.  This could
@@ -626,7 +696,7 @@ static int has_dir_name(struct index_state *istate,
                         * it is Ok to have a directory at the same
                         * path.
                         */
-                       if (stage || istate->cache[pos]->ce_mode) {
+                       if (!(istate->cache[pos]->ce_flags & CE_REMOVE)) {
                                retval = -1;
                                if (!ok_to_replace)
                                        break;
@@ -648,8 +718,9 @@ static int has_dir_name(struct index_state *istate,
                            (p->name[len] != '/') ||
                            memcmp(p->name, name, len))
                                break; /* not our subdirectory */
-                       if (ce_stage(p) == stage && (stage || p->ce_mode))
-                               /* p is at the same stage as our entry, and
+                       if (ce_stage(p) == stage && !(p->ce_flags & CE_REMOVE))
+                               /*
+                                * p is at the same stage as our entry, and
                                 * is a subdirectory of what we are looking
                                 * at, so we cannot have conflicts at our
                                 * level or anything shorter.
@@ -679,7 +750,7 @@ static int check_file_directory_conflict(struct index_state *istate,
        /*
         * When ce is an "I am going away" entry, we allow it to be added
         */
-       if (!ce_stage(ce) && !ce->ce_mode)
+       if (ce->ce_flags & CE_REMOVE)
                return 0;
 
        /*
@@ -704,12 +775,11 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
        int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
 
        cache_tree_invalidate_path(istate->cache_tree, ce->name);
-       pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
+       pos = index_name_pos(istate, ce->name, ce->ce_flags);
 
        /* existing match? Just replace it. */
        if (pos >= 0) {
-               istate->cache_changed = 1;
-               istate->cache[pos] = ce;
+               replace_index_entry(istate, pos, ce);
                return 0;
        }
        pos = -pos-1;
@@ -736,7 +806,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
                if (!ok_to_replace)
                        return error("'%s' appears as both a file and as a directory",
                                     ce->name);
-               pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
+               pos = index_name_pos(istate, ce->name, ce->ce_flags);
                pos = -pos-1;
        }
        return pos + 1;
@@ -769,7 +839,7 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
                memmove(istate->cache + pos + 1,
                        istate->cache + pos,
                        (istate->cache_nr - pos - 1) * sizeof(ce));
-       istate->cache[pos] = ce;
+       set_index_entry(istate, pos, ce);
        istate->cache_changed = 1;
        return 0;
 }
@@ -794,6 +864,9 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
        int changed, size;
        int ignore_valid = options & CE_MATCH_IGNORE_VALID;
 
+       if (ce_uptodate(ce))
+               return ce;
+
        if (lstat(ce->name, &st) < 0) {
                if (err)
                        *err = errno;
@@ -810,10 +883,17 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
                 * valid again, under "assume unchanged" mode.
                 */
                if (ignore_valid && assume_unchanged &&
-                   !(ce->ce_flags & htons(CE_VALID)))
+                   !(ce->ce_flags & CE_VALID))
                        ; /* mark this one VALID again */
-               else
+               else {
+                       /*
+                        * We do not mark the index itself "modified"
+                        * because CE_UPTODATE flag is in-core only;
+                        * we are not going to write this change out.
+                        */
+                       ce_mark_uptodate(ce);
                        return ce;
+               }
        }
 
        if (ie_modified(istate, ce, &st, options)) {
@@ -826,7 +906,6 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
        updated = xmalloc(size);
        memcpy(updated, ce, size);
        fill_stat_cache_info(updated, &st);
-
        /*
         * If ignore_valid is not set, we should leave CE_VALID bit
         * alone.  Otherwise, paths marked with --no-assume-unchanged
@@ -834,8 +913,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
         * automatically, which is not really what we want.
         */
        if (!ignore_valid && assume_unchanged &&
-           !(ce->ce_flags & htons(CE_VALID)))
-               updated->ce_flags &= ~htons(CE_VALID);
+           !(ce->ce_flags & CE_VALID))
+               updated->ce_flags &= ~CE_VALID;
 
        return updated;
 }
@@ -880,7 +959,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                                /* If we are doing --really-refresh that
                                 * means the index is not valid anymore.
                                 */
-                               ce->ce_flags &= ~htons(CE_VALID);
+                               ce->ce_flags &= ~CE_VALID;
                                istate->cache_changed = 1;
                        }
                        if (quiet)
@@ -889,11 +968,8 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                        has_errors = 1;
                        continue;
                }
-               istate->cache_changed = 1;
-               /* You can NOT just free istate->cache[i] here, since it
-                * might not be necessarily malloc()ed but can also come
-                * from mmap(). */
-               istate->cache[i] = new;
+
+               replace_index_entry(istate, i, new);
        }
        return has_errors;
 }
@@ -942,16 +1018,58 @@ int read_index(struct index_state *istate)
        return read_index_from(istate, get_index_file());
 }
 
+static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce)
+{
+       size_t len;
+
+       ce->ce_ctime = ntohl(ondisk->ctime.sec);
+       ce->ce_mtime = ntohl(ondisk->mtime.sec);
+       ce->ce_dev   = ntohl(ondisk->dev);
+       ce->ce_ino   = ntohl(ondisk->ino);
+       ce->ce_mode  = ntohl(ondisk->mode);
+       ce->ce_uid   = ntohl(ondisk->uid);
+       ce->ce_gid   = ntohl(ondisk->gid);
+       ce->ce_size  = ntohl(ondisk->size);
+       /* On-disk flags are just 16 bits */
+       ce->ce_flags = ntohs(ondisk->flags);
+       hashcpy(ce->sha1, ondisk->sha1);
+
+       len = ce->ce_flags & CE_NAMEMASK;
+       if (len == CE_NAMEMASK)
+               len = strlen(ondisk->name);
+       /*
+        * NEEDSWORK: If the original index is crafted, this copy could
+        * go unchecked.
+        */
+       memcpy(ce->name, ondisk->name, len + 1);
+}
+
+static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
+{
+       long per_entry;
+
+       per_entry = sizeof(struct cache_entry) - sizeof(struct ondisk_cache_entry);
+
+       /*
+        * Alignment can cause differences. This should be "alignof", but
+        * since that's a gcc'ism, just use the size of a pointer.
+        */
+       per_entry += sizeof(void *);
+       return ondisk_size + entries*per_entry;
+}
+
 /* remember to discard_cache() before reading a different cache! */
 int read_index_from(struct index_state *istate, const char *path)
 {
        int fd, i;
        struct stat st;
-       unsigned long offset;
+       unsigned long src_offset, dst_offset;
        struct cache_header *hdr;
+       void *mmap;
+       size_t mmap_size;
 
        errno = EBUSY;
-       if (istate->mmap)
+       if (istate->alloc)
                return istate->cache_nr;
 
        errno = ENOENT;
@@ -967,31 +1085,47 @@ int read_index_from(struct index_state *istate, const char *path)
                die("cannot stat the open index (%s)", strerror(errno));
 
        errno = EINVAL;
-       istate->mmap_size = xsize_t(st.st_size);
-       if (istate->mmap_size < sizeof(struct cache_header) + 20)
+       mmap_size = xsize_t(st.st_size);
+       if (mmap_size < sizeof(struct cache_header) + 20)
                die("index file smaller than expected");
 
-       istate->mmap = xmmap(NULL, istate->mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+       mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
        close(fd);
+       if (mmap == MAP_FAILED)
+               die("unable to map index file");
 
-       hdr = istate->mmap;
-       if (verify_hdr(hdr, istate->mmap_size) < 0)
+       hdr = mmap;
+       if (verify_hdr(hdr, mmap_size) < 0)
                goto unmap;
 
        istate->cache_nr = ntohl(hdr->hdr_entries);
        istate->cache_alloc = alloc_nr(istate->cache_nr);
        istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
 
-       offset = sizeof(*hdr);
+       /*
+        * The disk format is actually larger than the in-memory format,
+        * due to space for nsec etc, so even though the in-memory one
+        * has room for a few  more flags, we can allocate using the same
+        * index size
+        */
+       istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr));
+
+       src_offset = sizeof(*hdr);
+       dst_offset = 0;
        for (i = 0; i < istate->cache_nr; i++) {
+               struct ondisk_cache_entry *disk_ce;
                struct cache_entry *ce;
 
-               ce = (struct cache_entry *)((char *)(istate->mmap) + offset);
-               offset = offset + ce_size(ce);
-               istate->cache[i] = ce;
+               disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
+               ce = (struct cache_entry *)((char *)istate->alloc + dst_offset);
+               convert_from_disk(disk_ce, ce);
+               set_index_entry(istate, i, ce);
+
+               src_offset += ondisk_ce_size(ce);
+               dst_offset += ce_size(ce);
        }
        istate->timestamp = st.st_mtime;
-       while (offset <= istate->mmap_size - 20 - 8) {
+       while (src_offset <= mmap_size - 20 - 8) {
                /* After an array of active_nr index entries,
                 * there can be arbitrary number of extended
                 * sections, each of which is prefixed with
@@ -999,40 +1133,47 @@ int read_index_from(struct index_state *istate, const char *path)
                 * in 4-byte network byte order.
                 */
                unsigned long extsize;
-               memcpy(&extsize, (char *)(istate->mmap) + offset + 4, 4);
+               memcpy(&extsize, (char *)mmap + src_offset + 4, 4);
                extsize = ntohl(extsize);
                if (read_index_extension(istate,
-                                        ((const char *) (istate->mmap)) + offset,
-                                        (char *) (istate->mmap) + offset + 8,
+                                        (const char *) mmap + src_offset,
+                                        (char *) mmap + src_offset + 8,
                                         extsize) < 0)
                        goto unmap;
-               offset += 8;
-               offset += extsize;
+               src_offset += 8;
+               src_offset += extsize;
        }
+       munmap(mmap, mmap_size);
        return istate->cache_nr;
 
 unmap:
-       munmap(istate->mmap, istate->mmap_size);
+       munmap(mmap, mmap_size);
        errno = EINVAL;
        die("index file corrupt");
 }
 
 int discard_index(struct index_state *istate)
 {
-       int ret;
-
        istate->cache_nr = 0;
        istate->cache_changed = 0;
        istate->timestamp = 0;
+       free_hash(&istate->name_hash);
        cache_tree_free(&(istate->cache_tree));
-       if (istate->mmap == NULL)
-               return 0;
-       ret = munmap(istate->mmap, istate->mmap_size);
-       istate->mmap = NULL;
-       istate->mmap_size = 0;
+       free(istate->alloc);
+       istate->alloc = NULL;
 
        /* no need to throw away allocated active_cache */
-       return ret;
+       return 0;
+}
+
+int unmerged_index(struct index_state *istate)
+{
+       int i;
+       for (i = 0; i < istate->cache_nr; i++) {
+               if (ce_stage(istate->cache[i]))
+                       return 1;
+       }
+       return 0;
 }
 
 #define WRITE_BUFFER_SIZE 8192
@@ -1144,10 +1285,32 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
                 * file, and never calls us, so the cached size information
                 * for "frotz" stays 6 which does not match the filesystem.
                 */
-               ce->ce_size = htonl(0);
+               ce->ce_size = 0;
        }
 }
 
+static int ce_write_entry(SHA_CTX *c, int fd, struct cache_entry *ce)
+{
+       int size = ondisk_ce_size(ce);
+       struct ondisk_cache_entry *ondisk = xcalloc(1, size);
+
+       ondisk->ctime.sec = htonl(ce->ce_ctime);
+       ondisk->ctime.nsec = 0;
+       ondisk->mtime.sec = htonl(ce->ce_mtime);
+       ondisk->mtime.nsec = 0;
+       ondisk->dev  = htonl(ce->ce_dev);
+       ondisk->ino  = htonl(ce->ce_ino);
+       ondisk->mode = htonl(ce->ce_mode);
+       ondisk->uid  = htonl(ce->ce_uid);
+       ondisk->gid  = htonl(ce->ce_gid);
+       ondisk->size = htonl(ce->ce_size);
+       hashcpy(ondisk->sha1, ce->sha1);
+       ondisk->flags = htons(ce->ce_flags);
+       memcpy(ondisk->name, ce->name, ce_namelen(ce));
+
+       return ce_write(c, fd, ondisk, size);
+}
+
 int write_index(struct index_state *istate, int newfd)
 {
        SHA_CTX c;
@@ -1157,7 +1320,7 @@ int write_index(struct index_state *istate, int newfd)
        int entries = istate->cache_nr;
 
        for (i = removed = 0; i < entries; i++)
-               if (!cache[i]->ce_mode)
+               if (cache[i]->ce_flags & CE_REMOVE)
                        removed++;
 
        hdr.hdr_signature = htonl(CACHE_SIGNATURE);
@@ -1170,12 +1333,11 @@ int write_index(struct index_state *istate, int newfd)
 
        for (i = 0; i < entries; i++) {
                struct cache_entry *ce = cache[i];
-               if (!ce->ce_mode)
+               if (ce->ce_flags & CE_REMOVE)
                        continue;
-               if (istate->timestamp &&
-                   istate->timestamp <= ntohl(ce->ce_mtime.sec))
+               if (is_racy_timestamp(istate, ce))
                        ce_smudge_racily_clean_entry(ce);
-               if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
+               if (ce_write_entry(&c, newfd, ce) < 0)
                        return -1;
        }
 
index c90ec7dde28ec135f0c90e6c731b730ad4a1f7a8..f83ae87e150ff93728da989f1d35ce0ad7c10f60 100644 (file)
@@ -132,6 +132,7 @@ static int run_hook(const char *hook_name)
                                break;
                }
        }
+       close(proc.in);
        return hook_status(finish_command(&proc), hook_name);
 }
 
@@ -414,6 +415,7 @@ static const char *unpack(void)
                if (start_command(&ip))
                        return "index-pack fork failed";
                pack_lockfile = index_pack_lockfile(ip.out);
+               close(ip.out);
                status = finish_command(&ip);
                if (!status) {
                        reprepare_packed_git();
diff --git a/refs.c b/refs.c
index fb33da111240d9a3d579dfebb05eef951fecee23..1b0050eee4e037aec8396edd0ff6c14bda5985a2 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -157,6 +157,7 @@ static struct cached_refs {
        struct ref_list *loose;
        struct ref_list *packed;
 } cached_refs;
+static struct ref_list *current_ref;
 
 static void free_ref_list(struct ref_list *list)
 {
@@ -476,6 +477,7 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
                error("%s does not point to a valid object!", entry->name);
                return 0;
        }
+       current_ref = entry;
        return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
 }
 
@@ -485,6 +487,16 @@ int peel_ref(const char *ref, unsigned char *sha1)
        unsigned char base[20];
        struct object *o;
 
+       if (current_ref && (current_ref->name == ref
+               || !strcmp(current_ref->name, ref))) {
+               if (current_ref->flag & REF_KNOWS_PEELED) {
+                       hashcpy(sha1, current_ref->peeled);
+                       return 0;
+               }
+               hashcpy(base, current_ref->sha1);
+               goto fallback;
+       }
+
        if (!resolve_ref(ref, base, 1, &flag))
                return -1;
 
@@ -504,7 +516,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
                }
        }
 
-       /* fallback - callers should not call this for unpacked refs */
+fallback:
        o = parse_object(base);
        if (o && o->type == OBJ_TAG) {
                o = deref_tag(o, ref, 0);
@@ -519,7 +531,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
 static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
                           void *cb_data)
 {
-       int retval;
+       int retval = 0;
        struct ref_list *packed = get_packed_refs();
        struct ref_list *loose = get_loose_refs();
 
@@ -539,15 +551,18 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
                }
                retval = do_one_ref(base, fn, trim, cb_data, entry);
                if (retval)
-                       return retval;
+                       goto end_each;
        }
 
        for (packed = packed ? packed : loose; packed; packed = packed->next) {
                retval = do_one_ref(base, fn, trim, cb_data, packed);
                if (retval)
-                       return retval;
+                       goto end_each;
        }
-       return 0;
+
+end_each:
+       current_ref = NULL;
+       return retval;
 }
 
 int head_ref(each_ref_fn fn, void *cb_data)
@@ -1018,7 +1033,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        return 1;
 }
 
-static int close_ref(struct ref_lock *lock)
+int close_ref(struct ref_lock *lock)
 {
        if (close_lock_file(lock->lk))
                return -1;
@@ -1026,7 +1041,7 @@ static int close_ref(struct ref_lock *lock)
        return 0;
 }
 
-static int commit_ref(struct ref_lock *lock)
+int commit_ref(struct ref_lock *lock)
 {
        if (commit_lock_file(lock->lk))
                return -1;
diff --git a/refs.h b/refs.h
index 9cd16f82956d89f2800827046151abd7866fb9da..06abee15266cc3b234ec64cd27362c482874e54b 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -33,6 +33,12 @@ extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_
 #define REF_NODEREF    0x01
 extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags);
 
+/** Close the file descriptor owned by a lock and return the status */
+extern int close_ref(struct ref_lock *lock);
+
+/** Close and commit the ref locked by the lock */
+extern int commit_ref(struct ref_lock *lock);
+
 /** Release any lock taken but not written. **/
 extern void unlock_ref(struct ref_lock *lock);
 
index 6b56473f5bb7d8ab2226ed83805f30c9e21ba773..7e1937286b1cabec84865c7e07e2635feb8bbc70 100644 (file)
--- a/remote.c
+++ b/remote.c
 #include "remote.h"
 #include "refs.h"
 
+struct counted_string {
+       size_t len;
+       const char *s;
+};
+struct rewrite {
+       const char *base;
+       size_t baselen;
+       struct counted_string *instead_of;
+       int instead_of_nr;
+       int instead_of_alloc;
+};
+
 static struct remote **remotes;
-static int allocated_remotes;
+static int remotes_alloc;
+static int remotes_nr;
 
 static struct branch **branches;
-static int allocated_branches;
+static int branches_alloc;
+static int branches_nr;
 
 static struct branch *current_branch;
 static const char *default_remote_name;
 
+static struct rewrite **rewrite;
+static int rewrite_alloc;
+static int rewrite_nr;
+
 #define BUF_SIZE (2048)
 static char buffer[BUF_SIZE];
 
+static const char *alias_url(const char *url)
+{
+       int i, j;
+       char *ret;
+       struct counted_string *longest;
+       int longest_i;
+
+       longest = NULL;
+       longest_i = -1;
+       for (i = 0; i < rewrite_nr; i++) {
+               if (!rewrite[i])
+                       continue;
+               for (j = 0; j < rewrite[i]->instead_of_nr; j++) {
+                       if (!prefixcmp(url, rewrite[i]->instead_of[j].s) &&
+                           (!longest ||
+                            longest->len < rewrite[i]->instead_of[j].len)) {
+                               longest = &(rewrite[i]->instead_of[j]);
+                               longest_i = i;
+                       }
+               }
+       }
+       if (!longest)
+               return url;
+
+       ret = malloc(rewrite[longest_i]->baselen +
+                    (strlen(url) - longest->len) + 1);
+       strcpy(ret, rewrite[longest_i]->base);
+       strcpy(ret + rewrite[longest_i]->baselen, url + longest->len);
+       return ret;
+}
+
 static void add_push_refspec(struct remote *remote, const char *ref)
 {
-       int nr = remote->push_refspec_nr + 1;
-       remote->push_refspec =
-               xrealloc(remote->push_refspec, nr * sizeof(char *));
-       remote->push_refspec[nr-1] = ref;
-       remote->push_refspec_nr = nr;
+       ALLOC_GROW(remote->push_refspec,
+                  remote->push_refspec_nr + 1,
+                  remote->push_refspec_alloc);
+       remote->push_refspec[remote->push_refspec_nr++] = ref;
 }
 
 static void add_fetch_refspec(struct remote *remote, const char *ref)
 {
-       int nr = remote->fetch_refspec_nr + 1;
-       remote->fetch_refspec =
-               xrealloc(remote->fetch_refspec, nr * sizeof(char *));
-       remote->fetch_refspec[nr-1] = ref;
-       remote->fetch_refspec_nr = nr;
+       ALLOC_GROW(remote->fetch_refspec,
+                  remote->fetch_refspec_nr + 1,
+                  remote->fetch_refspec_alloc);
+       remote->fetch_refspec[remote->fetch_refspec_nr++] = ref;
 }
 
 static void add_url(struct remote *remote, const char *url)
 {
-       int nr = remote->url_nr + 1;
-       remote->url =
-               xrealloc(remote->url, nr * sizeof(char *));
-       remote->url[nr-1] = url;
-       remote->url_nr = nr;
+       ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
+       remote->url[remote->url_nr++] = url;
+}
+
+static void add_url_alias(struct remote *remote, const char *url)
+{
+       add_url(remote, alias_url(url));
 }
 
 static struct remote *make_remote(const char *name, int len)
 {
-       int i, empty = -1;
+       struct remote *ret;
+       int i;
 
-       for (i = 0; i < allocated_remotes; i++) {
-               if (!remotes[i]) {
-                       if (empty < 0)
-                               empty = i;
-               } else {
-                       if (len ? (!strncmp(name, remotes[i]->name, len) &&
-                                  !remotes[i]->name[len]) :
-                           !strcmp(name, remotes[i]->name))
-                               return remotes[i];
-               }
+       for (i = 0; i < remotes_nr; i++) {
+               if (len ? (!strncmp(name, remotes[i]->name, len) &&
+                          !remotes[i]->name[len]) :
+                   !strcmp(name, remotes[i]->name))
+                       return remotes[i];
        }
 
-       if (empty < 0) {
-               empty = allocated_remotes;
-               allocated_remotes += allocated_remotes ? allocated_remotes : 1;
-               remotes = xrealloc(remotes,
-                                  sizeof(*remotes) * allocated_remotes);
-               memset(remotes + empty, 0,
-                      (allocated_remotes - empty) * sizeof(*remotes));
-       }
-       remotes[empty] = xcalloc(1, sizeof(struct remote));
+       ret = xcalloc(1, sizeof(struct remote));
+       ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
+       remotes[remotes_nr++] = ret;
        if (len)
-               remotes[empty]->name = xstrndup(name, len);
+               ret->name = xstrndup(name, len);
        else
-               remotes[empty]->name = xstrdup(name);
-       return remotes[empty];
+               ret->name = xstrdup(name);
+       return ret;
 }
 
 static void add_merge(struct branch *branch, const char *name)
 {
-       int nr = branch->merge_nr + 1;
-       branch->merge_name =
-               xrealloc(branch->merge_name, nr * sizeof(char *));
-       branch->merge_name[nr-1] = name;
-       branch->merge_nr = nr;
+       ALLOC_GROW(branch->merge_name, branch->merge_nr + 1,
+                  branch->merge_alloc);
+       branch->merge_name[branch->merge_nr++] = name;
 }
 
 static struct branch *make_branch(const char *name, int len)
 {
-       int i, empty = -1;
+       struct branch *ret;
+       int i;
        char *refname;
 
-       for (i = 0; i < allocated_branches; i++) {
-               if (!branches[i]) {
-                       if (empty < 0)
-                               empty = i;
-               } else {
-                       if (len ? (!strncmp(name, branches[i]->name, len) &&
-                                  !branches[i]->name[len]) :
-                           !strcmp(name, branches[i]->name))
-                               return branches[i];
-               }
+       for (i = 0; i < branches_nr; i++) {
+               if (len ? (!strncmp(name, branches[i]->name, len) &&
+                          !branches[i]->name[len]) :
+                   !strcmp(name, branches[i]->name))
+                       return branches[i];
        }
 
-       if (empty < 0) {
-               empty = allocated_branches;
-               allocated_branches += allocated_branches ? allocated_branches : 1;
-               branches = xrealloc(branches,
-                                  sizeof(*branches) * allocated_branches);
-               memset(branches + empty, 0,
-                      (allocated_branches - empty) * sizeof(*branches));
-       }
-       branches[empty] = xcalloc(1, sizeof(struct branch));
+       ALLOC_GROW(branches, branches_nr + 1, branches_alloc);
+       ret = xcalloc(1, sizeof(struct branch));
+       branches[branches_nr++] = ret;
        if (len)
-               branches[empty]->name = xstrndup(name, len);
+               ret->name = xstrndup(name, len);
        else
-               branches[empty]->name = xstrdup(name);
+               ret->name = xstrdup(name);
        refname = malloc(strlen(name) + strlen("refs/heads/") + 1);
        strcpy(refname, "refs/heads/");
-       strcpy(refname + strlen("refs/heads/"),
-              branches[empty]->name);
-       branches[empty]->refname = refname;
+       strcpy(refname + strlen("refs/heads/"), ret->name);
+       ret->refname = refname;
+
+       return ret;
+}
+
+static struct rewrite *make_rewrite(const char *base, int len)
+{
+       struct rewrite *ret;
+       int i;
+
+       for (i = 0; i < rewrite_nr; i++) {
+               if (len
+                   ? (len == rewrite[i]->baselen &&
+                      !strncmp(base, rewrite[i]->base, len))
+                   : !strcmp(base, rewrite[i]->base))
+                       return rewrite[i];
+       }
 
-       return branches[empty];
+       ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc);
+       ret = xcalloc(1, sizeof(struct rewrite));
+       rewrite[rewrite_nr++] = ret;
+       if (len) {
+               ret->base = xstrndup(base, len);
+               ret->baselen = len;
+       }
+       else {
+               ret->base = xstrdup(base);
+               ret->baselen = strlen(base);
+       }
+       return ret;
+}
+
+static void add_instead_of(struct rewrite *rewrite, const char *instead_of)
+{
+       ALLOC_GROW(rewrite->instead_of, rewrite->instead_of_nr + 1, rewrite->instead_of_alloc);
+       rewrite->instead_of[rewrite->instead_of_nr].s = instead_of;
+       rewrite->instead_of[rewrite->instead_of_nr].len = strlen(instead_of);
+       rewrite->instead_of_nr++;
 }
 
 static void read_remotes_file(struct remote *remote)
@@ -154,7 +215,7 @@ static void read_remotes_file(struct remote *remote)
 
                switch (value_list) {
                case 0:
-                       add_url(remote, xstrdup(s));
+                       add_url_alias(remote, xstrdup(s));
                        break;
                case 1:
                        add_push_refspec(remote, xstrdup(s));
@@ -206,7 +267,7 @@ static void read_branches_file(struct remote *remote)
        } else {
                branch = "refs/heads/master";
        }
-       add_url(remote, p);
+       add_url_alias(remote, p);
        add_fetch_refspec(remote, branch);
        remote->fetch_tags = 1; /* always auto-follow */
 }
@@ -236,6 +297,19 @@ static int handle_config(const char *key, const char *value)
                }
                return 0;
        }
+       if (!prefixcmp(key, "url.")) {
+               struct rewrite *rewrite;
+               name = key + 5;
+               subkey = strrchr(name, '.');
+               if (!subkey)
+                       return 0;
+               rewrite = make_rewrite(name, subkey - name);
+               if (!strcmp(subkey, ".insteadof")) {
+                       if (!value)
+                               return config_error_nonbool(key);
+                       add_instead_of(rewrite, xstrdup(value));
+               }
+       }
        if (prefixcmp(key,  "remote."))
                return 0;
        name = key + 7;
@@ -287,6 +361,18 @@ static int handle_config(const char *key, const char *value)
        return 0;
 }
 
+static void alias_all_urls(void)
+{
+       int i, j;
+       for (i = 0; i < remotes_nr; i++) {
+               if (!remotes[i])
+                       continue;
+               for (j = 0; j < remotes[i]->url_nr; j++) {
+                       remotes[i]->url[j] = alias_url(remotes[i]->url[j]);
+               }
+       }
+}
+
 static void read_config(void)
 {
        unsigned char sha1[20];
@@ -303,6 +389,7 @@ static void read_config(void)
                        make_branch(head_ref + strlen("refs/heads/"), 0);
        }
        git_config(handle_config);
+       alias_all_urls();
 }
 
 struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
@@ -368,7 +455,7 @@ struct remote *remote_get(const char *name)
                        read_branches_file(ret);
        }
        if (!ret->url)
-               add_url(ret, name);
+               add_url_alias(ret, name);
        if (!ret->url)
                return NULL;
        ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec);
@@ -380,7 +467,7 @@ int for_each_remote(each_remote_fn fn, void *priv)
 {
        int i, result = 0;
        read_config();
-       for (i = 0; i < allocated_remotes && !result; i++) {
+       for (i = 0; i < remotes_nr && !result; i++) {
                struct remote *r = remotes[i];
                if (!r)
                        continue;
@@ -506,8 +593,7 @@ void free_refs(struct ref *ref)
        struct ref *next;
        while (ref) {
                next = ref->next;
-               if (ref->peer_ref)
-                       free(ref->peer_ref);
+               free(ref->peer_ref);
                free(ref);
                ref = next;
        }
@@ -643,9 +729,17 @@ static int match_explicit(struct ref *src, struct ref *dst,
                errs = 1;
 
        if (!dst_value) {
+               unsigned char sha1[20];
+               int flag;
+
                if (!matched_src)
                        return errs;
-               dst_value = matched_src->name;
+               dst_value = resolve_ref(matched_src->name, sha1, 1, &flag);
+               if (!dst_value ||
+                   ((flag & REF_ISSYMREF) &&
+                    prefixcmp(dst_value, "refs/heads/")))
+                       die("%s cannot be resolved to branch.",
+                           matched_src->name);
        }
 
        switch (count_refspec_match(dst_value, dst, &matched_dst)) {
index 86e036d61006a577ad091bdc30e58987871415b0..0f6033fb258c5a44971c7479e1dfe938393ce398 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -6,14 +6,17 @@ struct remote {
 
        const char **url;
        int url_nr;
+       int url_alloc;
 
        const char **push_refspec;
        struct refspec *push;
        int push_refspec_nr;
+       int push_refspec_alloc;
 
        const char **fetch_refspec;
        struct refspec *fetch;
        int fetch_refspec_nr;
+       int fetch_refspec_alloc;
 
        /*
         * -1 to never fetch tags
@@ -100,6 +103,7 @@ struct branch {
        const char **merge_name;
        struct refspec **merge;
        int merge_nr;
+       int merge_alloc;
 };
 
 struct branch *branch_get(const char *name);
index a399f27144bbabe3ee942408cdb90398990c3c85..63bf2c5c2dd314cfff2f54f76c4f5fd85b3b207e 100644 (file)
@@ -46,6 +46,8 @@ void add_object(struct object *obj,
 
 static void mark_blob_uninteresting(struct blob *blob)
 {
+       if (!blob)
+               return;
        if (blob->object.flags & UNINTERESTING)
                return;
        blob->object.flags |= UNINTERESTING;
@@ -57,6 +59,8 @@ void mark_tree_uninteresting(struct tree *tree)
        struct name_entry entry;
        struct object *obj = &tree->object;
 
+       if (!tree)
+               return;
        if (obj->flags & UNINTERESTING)
                return;
        obj->flags |= UNINTERESTING;
@@ -173,6 +177,8 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
                struct tag *tag = (struct tag *) object;
                if (revs->tag_objects && !(flags & UNINTERESTING))
                        add_pending_object(revs, object, tag->tag);
+               if (!tag->tagged)
+                       die("bad tag");
                object = parse_object(tag->tagged->sha1);
                if (!object)
                        die("bad object %s", sha1_to_hex(tag->tagged->sha1));
@@ -558,6 +564,12 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
        free_patch_ids(&ids);
 }
 
+static void add_to_list(struct commit_list **p, struct commit *commit, struct commit_list *n)
+{
+       p = &commit_list_insert(commit, p)->next;
+       *p = n;
+}
+
 static int limit_list(struct rev_info *revs)
 {
        struct commit_list *list = revs->commits;
@@ -579,9 +591,13 @@ static int limit_list(struct rev_info *revs)
                        return -1;
                if (obj->flags & UNINTERESTING) {
                        mark_parents_uninteresting(commit);
-                       if (everybody_uninteresting(list))
+                       if (everybody_uninteresting(list)) {
+                               if (revs->show_all)
+                                       add_to_list(p, commit, list);
                                break;
-                       continue;
+                       }
+                       if (!revs->show_all)
+                               continue;
                }
                if (revs->min_age != -1 && (commit->date > revs->min_age))
                        continue;
@@ -617,12 +633,13 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
        return 0;
 }
 
-static void handle_all(struct rev_info *revs, unsigned flags)
+static void handle_refs(struct rev_info *revs, unsigned flags,
+               int (*for_each)(each_ref_fn, void *))
 {
        struct all_refs_cb cb;
        cb.all_revs = revs;
        cb.all_flags = flags;
-       for_each_ref(handle_one_ref, &cb);
+       for_each(handle_one_ref, &cb);
 }
 
 static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
@@ -685,6 +702,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
                it = get_reference(revs, arg, sha1, 0);
                if (it->type != OBJ_TAG)
                        break;
+               if (!((struct tag*)it)->tagged)
+                       return 0;
                hashcpy(sha1, ((struct tag*)it)->tagged->sha1);
        }
        if (it->type != OBJ_COMMIT)
@@ -720,6 +739,10 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->commit_format = CMIT_FMT_DEFAULT;
 
        diff_setup(&revs->diffopt);
+       if (prefix && !revs->diffopt.prefix) {
+               revs->diffopt.prefix = prefix;
+               revs->diffopt.prefix_length = strlen(prefix);
+       }
 }
 
 static void add_pending_commit_list(struct rev_info *revs,
@@ -920,6 +943,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        int left = 1;
        int all_match = 0;
        int regflags = 0;
+       int fixed = 0;
 
        /* First, search for "--" */
        seen_dashdash = 0;
@@ -988,7 +1012,19 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                continue;
                        }
                        if (!strcmp(arg, "--all")) {
-                               handle_all(revs, flags);
+                               handle_refs(revs, flags, for_each_ref);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--branches")) {
+                               handle_refs(revs, flags, for_each_branch_ref);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--tags")) {
+                               handle_refs(revs, flags, for_each_tag_ref);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--remotes")) {
+                               handle_refs(revs, flags, for_each_remote_ref);
                                continue;
                        }
                        if (!strcmp(arg, "--first-parent")) {
@@ -1051,6 +1087,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->dense = 0;
                                continue;
                        }
+                       if (!strcmp(arg, "--show-all")) {
+                               revs->show_all = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--remove-empty")) {
                                revs->remove_empty_trees = 1;
                                continue;
@@ -1212,6 +1252,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                regflags |= REG_ICASE;
                                continue;
                        }
+                       if (!strcmp(arg, "--fixed-strings") ||
+                           !strcmp(arg, "-F")) {
+                               fixed = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--all-match")) {
                                all_match = 1;
                                continue;
@@ -1267,8 +1312,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                }
        }
 
-       if (revs->grep_filter)
+       if (revs->grep_filter) {
                revs->grep_filter->regflags |= regflags;
+               revs->grep_filter->fixed = fixed;
+       }
 
        if (show_merge)
                prepare_show_merge(revs);
@@ -1434,6 +1481,8 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
                return commit_ignore;
        if (revs->unpacked && has_sha1_pack(commit->object.sha1, revs->ignore_packed))
                return commit_ignore;
+       if (revs->show_all)
+               return commit_show;
        if (commit->object.flags & UNINTERESTING)
                return commit_ignore;
        if (revs->min_age != -1 && (commit->date > revs->min_age))
index 8572315954d1fde8c36c426d66c665534c22d1f2..c8b3b948ecc1cc8b45859a6e1ea11763addfb8b5 100644 (file)
@@ -33,6 +33,7 @@ struct rev_info {
                        prune:1,
                        no_merges:1,
                        no_walk:1,
+                       show_all:1,
                        remove_empty_trees:1,
                        simplify_history:1,
                        lifo:1,
@@ -74,7 +75,7 @@ struct rev_info {
        struct log_info *loginfo;
        int             nr, total;
        const char      *mime_boundary;
-       const char      *message_id;
+       char            *message_id;
        const char      *ref_message_id;
        const char      *add_signoff;
        const char      *extra_headers;
index 476d00c2182e3af82a0cfe495c61c9df1eb44d26..44100a749b09439121073e32e7a3143b54185d31 100644 (file)
@@ -20,12 +20,19 @@ int start_command(struct child_process *cmd)
        int need_in, need_out, need_err;
        int fdin[2], fdout[2], fderr[2];
 
+       /*
+        * In case of errors we must keep the promise to close FDs
+        * that have been passed in via ->in and ->out.
+        */
+
        need_in = !cmd->no_stdin && cmd->in < 0;
        if (need_in) {
-               if (pipe(fdin) < 0)
+               if (pipe(fdin) < 0) {
+                       if (cmd->out > 0)
+                               close(cmd->out);
                        return -ERR_RUN_COMMAND_PIPE;
+               }
                cmd->in = fdin[1];
-               cmd->close_in = 1;
        }
 
        need_out = !cmd->no_stdout
@@ -35,10 +42,11 @@ int start_command(struct child_process *cmd)
                if (pipe(fdout) < 0) {
                        if (need_in)
                                close_pair(fdin);
+                       else if (cmd->in)
+                               close(cmd->in);
                        return -ERR_RUN_COMMAND_PIPE;
                }
                cmd->out = fdout[0];
-               cmd->close_out = 1;
        }
 
        need_err = !cmd->no_stderr && cmd->err < 0;
@@ -46,8 +54,12 @@ int start_command(struct child_process *cmd)
                if (pipe(fderr) < 0) {
                        if (need_in)
                                close_pair(fdin);
+                       else if (cmd->in)
+                               close(cmd->in);
                        if (need_out)
                                close_pair(fdout);
+                       else if (cmd->out)
+                               close(cmd->out);
                        return -ERR_RUN_COMMAND_PIPE;
                }
                cmd->err = fderr[0];
@@ -57,8 +69,12 @@ int start_command(struct child_process *cmd)
        if (cmd->pid < 0) {
                if (need_in)
                        close_pair(fdin);
+               else if (cmd->in)
+                       close(cmd->in);
                if (need_out)
                        close_pair(fdout);
+               else if (cmd->out)
+                       close(cmd->out);
                if (need_err)
                        close_pair(fderr);
                return -ERR_RUN_COMMAND_FORK;
@@ -75,6 +91,13 @@ int start_command(struct child_process *cmd)
                        close(cmd->in);
                }
 
+               if (cmd->no_stderr)
+                       dup_devnull(2);
+               else if (need_err) {
+                       dup2(fderr[1], 2);
+                       close_pair(fderr);
+               }
+
                if (cmd->no_stdout)
                        dup_devnull(1);
                else if (cmd->stdout_to_stderr)
@@ -87,13 +110,6 @@ int start_command(struct child_process *cmd)
                        close(cmd->out);
                }
 
-               if (cmd->no_stderr)
-                       dup_devnull(2);
-               else if (need_err) {
-                       dup2(fderr[1], 2);
-                       close_pair(fderr);
-               }
-
                if (cmd->dir && chdir(cmd->dir))
                        die("exec %s: cd to %s failed (%s)", cmd->argv[0],
                            cmd->dir, strerror(errno));
@@ -120,7 +136,7 @@ int start_command(struct child_process *cmd)
 
        if (need_out)
                close(fdout[1]);
-       else if (cmd->out > 1)
+       else if (cmd->out)
                close(cmd->out);
 
        if (need_err)
@@ -157,10 +173,6 @@ static int wait_or_whine(pid_t pid)
 
 int finish_command(struct child_process *cmd)
 {
-       if (cmd->close_in)
-               close(cmd->in);
-       if (cmd->close_out)
-               close(cmd->out);
        return wait_or_whine(cmd->pid);
 }
 
index 1fc781d7668468f9e74bd430b7569dc040440ba8..debe3074b5a01fb5a19e61f07ff66c250cdc4f82 100644 (file)
@@ -14,13 +14,29 @@ enum {
 struct child_process {
        const char **argv;
        pid_t pid;
+       /*
+        * Using .in, .out, .err:
+        * - Specify 0 for no redirections (child inherits stdin, stdout,
+        *   stderr from parent).
+        * - Specify -1 to have a pipe allocated as follows:
+        *     .in: returns the writable pipe end; parent writes to it,
+        *          the readable pipe end becomes child's stdin
+        *     .out, .err: returns the readable pipe end; parent reads from
+        *          it, the writable pipe end becomes child's stdout/stderr
+        *   The caller of start_command() must close the returned FDs
+        *   after it has completed reading from/writing to it!
+        * - Specify > 0 to set a channel to a particular FD as follows:
+        *     .in: a readable FD, becomes child's stdin
+        *     .out: a writable FD, becomes child's stdout/stderr
+        *     .err > 0 not supported
+        *   The specified FD is closed by start_command(), even in case
+        *   of errors!
+        */
        int in;
        int out;
        int err;
        const char *dir;
        const char *const *env;
-       unsigned close_in:1;
-       unsigned close_out:1;
        unsigned no_stdin:1;
        unsigned no_stdout:1;
        unsigned no_stderr:1;
diff --git a/setup.c b/setup.c
index 4509598d577baba8b1d7e8782d8e6ff8e74f9556..41e298b8f549dcce28fca335c5f5b7fe5aea27bb 100644 (file)
--- a/setup.c
+++ b/setup.c
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
 
-const char *prefix_path(const char *prefix, int len, const char *path)
+static int sanitary_path_copy(char *dst, const char *src)
 {
-       const char *orig = path;
+       char *dst0 = dst;
+
+       if (*src == '/') {
+               *dst++ = '/';
+               while (*src == '/')
+                       src++;
+       }
+
        for (;;) {
-               char c;
-               if (*path != '.')
-                       break;
-               c = path[1];
-               /* "." */
-               if (!c) {
-                       path++;
-                       break;
+               char c = *src;
+
+               /*
+                * A path component that begins with . could be
+                * special:
+                * (1) "." and ends   -- ignore and terminate.
+                * (2) "./"           -- ignore them, eat slash and continue.
+                * (3) ".." and ends  -- strip one and terminate.
+                * (4) "../"          -- strip one, eat slash and continue.
+                */
+               if (c == '.') {
+                       switch (src[1]) {
+                       case '\0':
+                               /* (1) */
+                               src++;
+                               break;
+                       case '/':
+                               /* (2) */
+                               src += 2;
+                               while (*src == '/')
+                                       src++;
+                               continue;
+                       case '.':
+                               switch (src[2]) {
+                               case '\0':
+                                       /* (3) */
+                                       src += 2;
+                                       goto up_one;
+                               case '/':
+                                       /* (4) */
+                                       src += 3;
+                                       while (*src == '/')
+                                               src++;
+                                       goto up_one;
+                               }
+                       }
                }
-               /* "./" */
+
+               /* copy up to the next '/', and eat all '/' */
+               while ((c = *src++) != '\0' && c != '/')
+                       *dst++ = c;
                if (c == '/') {
-                       path += 2;
-                       continue;
-               }
-               if (c != '.')
+                       *dst++ = c;
+                       while (c == '/')
+                               c = *src++;
+                       src--;
+               } else if (!c)
                        break;
-               c = path[2];
-               if (!c)
-                       path += 2;
-               else if (c == '/')
-                       path += 3;
-               else
-                       break;
-               /* ".." and "../" */
-               /* Remove last component of the prefix */
-               do {
-                       if (!len)
-                               die("'%s' is outside repository", orig);
-                       len--;
-               } while (len && prefix[len-1] != '/');
                continue;
+
+       up_one:
+               /*
+                * dst0..dst is prefix portion, and dst[-1] is '/';
+                * go up one level.
+                */
+               dst -= 2; /* go past trailing '/' if any */
+               if (dst < dst0)
+                       return -1;
+               while (1) {
+                       if (dst <= dst0)
+                               break;
+                       c = *dst--;
+                       if (c == '/') {
+                               dst += 2;
+                               break;
+                       }
+               }
        }
-       if (len) {
-               int speclen = strlen(path);
-               char *n = xmalloc(speclen + len + 1);
+       *dst = '\0';
+       return 0;
+}
 
-               memcpy(n, prefix, len);
-               memcpy(n + len, path, speclen+1);
-               path = n;
+const char *prefix_path(const char *prefix, int len, const char *path)
+{
+       const char *orig = path;
+       char *sanitized = xmalloc(len + strlen(path) + 1);
+       if (is_absolute_path(orig))
+               strcpy(sanitized, path);
+       else {
+               if (len)
+                       memcpy(sanitized, prefix, len);
+               strcpy(sanitized + len, path);
        }
-       return path;
+       if (sanitary_path_copy(sanitized, sanitized))
+               goto error_out;
+       if (is_absolute_path(orig)) {
+               const char *work_tree = get_git_work_tree();
+               size_t len = strlen(work_tree);
+               size_t total = strlen(sanitized) + 1;
+               if (strncmp(sanitized, work_tree, len) ||
+                   (sanitized[len] != '\0' && sanitized[len] != '/')) {
+               error_out:
+                       error("'%s' is outside repository", orig);
+                       free(sanitized);
+                       return NULL;
+               }
+               if (sanitized[len] == '/')
+                       len++;
+               memmove(sanitized, sanitized + len, total - len);
+       }
+       return sanitized;
 }
 
 /*
@@ -114,7 +181,7 @@ void verify_non_filename(const char *prefix, const char *arg)
 const char **get_pathspec(const char *prefix, const char **pathspec)
 {
        const char *entry = *pathspec;
-       const char **p;
+       const char **src, **dst;
        int prefixlen;
 
        if (!prefix && !entry)
@@ -128,12 +195,21 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
        }
 
        /* Otherwise we have to re-write the entries.. */
-       p = pathspec;
+       src = pathspec;
+       dst = pathspec;
        prefixlen = prefix ? strlen(prefix) : 0;
-       do {
-               *p = prefix_path(prefix, prefixlen, entry);
-       } while ((entry = *++p) != NULL);
-       return (const char **) pathspec;
+       while (*src) {
+               const char *p = prefix_path(prefix, prefixlen, *src);
+               if (p)
+                       *(dst++) = p;
+               else
+                       exit(128); /* error message already given */
+               src++;
+       }
+       *dst = NULL;
+       if (!*pathspec)
+               return NULL;
+       return pathspec;
 }
 
 /*
@@ -374,8 +450,7 @@ int check_repository_format_version(const char *var, const char *value)
        } else if (strcmp(var, "core.worktree") == 0) {
                if (!value)
                        return config_error_nonbool(var);
-               if (git_work_tree_cfg)
-                       free(git_work_tree_cfg);
+               free(git_work_tree_cfg);
                git_work_tree_cfg = xstrdup(value);
                inside_work_tree = -1;
        }
index 66a4e00fa83fd9fc853a1ba8a308b05cdc030967..445a871db31673af20017d36ff22fd106f77f510 100644 (file)
@@ -14,6 +14,7 @@
 #include "tag.h"
 #include "tree.h"
 #include "refs.h"
+#include "pack-revindex.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -1367,11 +1368,15 @@ const char *packed_object_info_detail(struct packed_git *p,
        unsigned long dummy;
        unsigned char *next_sha1;
        enum object_type type;
+       struct revindex_entry *revidx;
 
        *delta_chain_length = 0;
        curpos = obj_offset;
        type = unpack_object_header(p, &w_curs, &curpos, size);
 
+       revidx = find_pack_revindex(p, obj_offset);
+       *store_size = revidx[1].offset - obj_offset;
+
        for (;;) {
                switch (type) {
                default:
@@ -1381,14 +1386,13 @@ const char *packed_object_info_detail(struct packed_git *p,
                case OBJ_TREE:
                case OBJ_BLOB:
                case OBJ_TAG:
-                       *store_size = 0; /* notyet */
                        unuse_pack(&w_curs);
                        return typename(type);
                case OBJ_OFS_DELTA:
                        obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
                        if (*delta_chain_length == 0) {
-                               /* TODO: find base_sha1 as pointed by curpos */
-                               hashclr(base_sha1);
+                               revidx = find_pack_revindex(p, obj_offset);
+                               hashcpy(base_sha1, nth_packed_object_sha1(p, revidx->nr));
                        }
                        break;
                case OBJ_REF_DELTA:
@@ -1845,6 +1849,15 @@ static struct cached_object {
 } *cached_objects;
 static int cached_object_nr, cached_object_alloc;
 
+static struct cached_object empty_tree = {
+       /* empty tree sha1: 4b825dc642cb6eb9a060e54bf8d69288fbee4904 */
+       "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60"
+       "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04",
+       OBJ_TREE,
+       "",
+       0
+};
+
 static struct cached_object *find_cached_object(const unsigned char *sha1)
 {
        int i;
@@ -1854,6 +1867,8 @@ static struct cached_object *find_cached_object(const unsigned char *sha1)
                if (!hashcmp(co->sha1, sha1))
                        return co;
        }
+       if (!hashcmp(sha1, empty_tree.sha1))
+               return &empty_tree;
        return NULL;
 }
 
@@ -1943,7 +1958,8 @@ void *read_object_with_reference(const unsigned char *sha1,
                }
                ref_length = strlen(ref_type);
 
-               if (memcmp(buffer, ref_type, ref_length) ||
+               if (ref_length + 40 > isize ||
+                   memcmp(buffer, ref_type, ref_length) ||
                    get_sha1_hex((char *) buffer + ref_length, actual_sha1)) {
                        free(buffer);
                        return NULL;
@@ -2358,7 +2374,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
        if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
                struct strbuf nbuf;
                strbuf_init(&nbuf, 0);
-               if (convert_to_git(path, buf, size, &nbuf)) {
+               if (convert_to_git(path, buf, size, &nbuf,
+                                  write_object ? safe_crlf : 0)) {
                        munmap(buf, size);
                        buf = strbuf_detach(&nbuf, &size);
                        re_allocated = 1;
index 13e11645e1fd6b61812cc3e88d2ad6cd42cad9ce..8b6c76f68ed1433caddfa8aab6132db667197433 100644 (file)
@@ -192,26 +192,25 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1,
 
 const char *find_unique_abbrev(const unsigned char *sha1, int len)
 {
-       int status, is_null;
+       int status, exists;
        static char hex[41];
 
-       is_null = is_null_sha1(sha1);
+       exists = has_sha1_file(sha1);
        memcpy(hex, sha1_to_hex(sha1), 40);
        if (len == 40 || !len)
                return hex;
        while (len < 40) {
                unsigned char sha1_ret[20];
                status = get_short_sha1(hex, len, sha1_ret, 1);
-               if (!status ||
-                   (is_null && status != SHORT_NAME_AMBIGUOUS)) {
+               if (exists
+                   ? !status
+                   : status == SHORT_NAME_NOT_FOUND) {
                        hex[len] = 0;
                        return hex;
                }
-               if (status != SHORT_NAME_AMBIGUOUS)
-                       return NULL;
                len++;
        }
-       return NULL;
+       return hex;
 }
 
 static int ambiguous_path(const char *path, int len)
@@ -423,6 +422,37 @@ static int get_nth_ancestor(const char *name, int len,
        return 0;
 }
 
+struct object *peel_to_type(const char *name, int namelen,
+                           struct object *o, enum object_type expected_type)
+{
+       if (name && !namelen)
+               namelen = strlen(name);
+       if (!o) {
+               unsigned char sha1[20];
+               if (get_sha1_1(name, namelen, sha1))
+                       return NULL;
+               o = parse_object(sha1);
+       }
+       while (1) {
+               if (!o || (!o->parsed && !parse_object(o->sha1)))
+                       return NULL;
+               if (o->type == expected_type)
+                       return o;
+               if (o->type == OBJ_TAG)
+                       o = ((struct tag*) o)->tagged;
+               else if (o->type == OBJ_COMMIT)
+                       o = &(((struct commit *) o)->tree->object);
+               else {
+                       if (name)
+                               error("%.*s: expected %s type, but the object "
+                                     "dereferences to %s type",
+                                     namelen, name, typename(expected_type),
+                                     typename(o->type));
+                       return NULL;
+               }
+       }
+}
+
 static int peel_onion(const char *name, int len, unsigned char *sha1)
 {
        unsigned char outer[20];
@@ -474,29 +504,17 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
                hashcpy(sha1, o->sha1);
        }
        else {
-               /* At this point, the syntax look correct, so
+               /*
+                * At this point, the syntax look correct, so
                 * if we do not get the needed object, we should
                 * barf.
                 */
-
-               while (1) {
-                       if (!o || (!o->parsed && !parse_object(o->sha1)))
-                               return -1;
-                       if (o->type == expected_type) {
-                               hashcpy(sha1, o->sha1);
-                               return 0;
-                       }
-                       if (o->type == OBJ_TAG)
-                               o = ((struct tag*) o)->tagged;
-                       else if (o->type == OBJ_COMMIT)
-                               o = &(((struct commit *) o)->tree->object);
-                       else
-                               return error("%.*s: expected %s type, but the object dereferences to %s type",
-                                            len, name, typename(expected_type),
-                                            typename(o->type));
-                       if (!o->parsed)
-                               parse_object(o->sha1);
+               o = peel_to_type(name, len, o, expected_type);
+               if (o) {
+                       hashcpy(sha1, o->sha1);
+                       return 0;
                }
+               return -1;
        }
        return 0;
 }
@@ -578,8 +596,11 @@ static int handle_one_ref(const char *path,
        struct object *object = parse_object(sha1);
        if (!object)
                return 0;
-       if (object->type == OBJ_TAG)
+       if (object->type == OBJ_TAG) {
                object = deref_tag(object, path, strlen(path));
+               if (!object)
+                       return 0;
+       }
        if (object->type != OBJ_COMMIT)
                return 0;
        insert_by_date((struct commit *)object, list);
@@ -617,9 +638,9 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
                unsigned long size;
 
                commit = pop_most_recent_commit(&list, ONELINE_SEEN);
-               parse_object(commit->object.sha1);
-               if (temp_commit_buffer)
-                       free(temp_commit_buffer);
+               if (!parse_object(commit->object.sha1))
+                       continue;
+               free(temp_commit_buffer);
                if (commit->buffer)
                        p = commit->buffer;
                else {
@@ -636,8 +657,7 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
                        break;
                }
        }
-       if (temp_commit_buffer)
-               free(temp_commit_buffer);
+       free(temp_commit_buffer);
        free_commit_list(list);
        for (l = backup; l; l = l->next)
                clear_commit_marks(l->item, ONELINE_SEEN);
@@ -695,7 +715,7 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
                                break;
                        if (ce_stage(ce) == stage) {
                                hashcpy(sha1, ce->sha1);
-                               *mode = ntohl(ce->ce_mode);
+                               *mode = ce->ce_mode;
                                return 0;
                        }
                        pos++;
index dbd9f5ad0ac21e70fc3a095d8e2938f245c238d3..4d90eda19efe0a80c1cb39e8897ab3ed5e6fcf56 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -56,7 +56,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
                        if (i < heads->nr) {
                                commit = (struct commit *)
                                        deref_tag(heads->objects[i++].item, NULL, 0);
-                               if (commit->object.type != OBJ_COMMIT) {
+                               if (!commit || commit->object.type != OBJ_COMMIT) {
                                        commit = NULL;
                                        continue;
                                }
@@ -70,7 +70,8 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
                                cur_depth = *(int *)commit->util;
                        }
                }
-               parse_commit(commit);
+               if (parse_commit(commit))
+                       die("invalid commit");
                commit->object.flags |= not_shallow_flag;
                cur_depth++;
                for (p = commit->parents, commit = NULL; p; p = p->next) {
diff --git a/shortlog.h b/shortlog.h
new file mode 100644 (file)
index 0000000..31ff491
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef SHORTLOG_H
+#define SHORTLOG_H
+
+#include "path-list.h"
+
+struct shortlog {
+       struct path_list list;
+       int summary;
+       int wrap_lines;
+       int sort_by_number;
+       int wrap;
+       int in1;
+       int in2;
+
+       char *common_repo_prefix;
+       int email;
+       struct path_list mailmap;
+};
+
+void shortlog_init(struct shortlog *log);
+
+void shortlog_add_commit(struct shortlog *log, struct commit *commit);
+
+void shortlog_output(struct shortlog *log);
+
+#endif
index 5efcfc8860a82766b53a0d579fbdd0844d0f9b62..4aed75265e945d7b8dfafb36913006376768b4d3 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -146,11 +146,12 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
        strbuf_setlen(sb, sb->len + len);
 }
 
-void strbuf_expand(struct strbuf *sb, const char *format,
-                   const char **placeholders, expand_fn_t fn, void *context)
+void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
+                  void *context)
 {
        for (;;) {
-               const char *percent, **p;
+               const char *percent;
+               size_t consumed;
 
                percent = strchrnul(format, '%');
                strbuf_add(sb, format, percent - format);
@@ -158,14 +159,10 @@ void strbuf_expand(struct strbuf *sb, const char *format,
                        break;
                format = percent + 1;
 
-               for (p = placeholders; *p; p++) {
-                       if (!prefixcmp(format, *p))
-                               break;
-               }
-               if (*p) {
-                       fn(sb, *p, context);
-                       format += strlen(*p);
-               } else
+               consumed = fn(sb, format, context);
+               if (consumed)
+                       format += consumed;
+               else
                        strbuf_addch(sb, '%');
        }
 }
index 36d61db65728f61188ef3bedbdbe88d0f2d9a0b9..faec2291d9e622c76dcdb3ef13d0876c5e3e6f28 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -103,8 +103,8 @@ static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) {
 }
 extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
 
-typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
-extern void strbuf_expand(struct strbuf *sb, const char *format, const char **placeholders, expand_fn_t fn, void *context);
+typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
+extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
 
 __attribute__((format(printf,2,3)))
 extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
diff --git a/t/.gitattributes b/t/.gitattributes
new file mode 100644 (file)
index 0000000..562b12e
--- /dev/null
@@ -0,0 +1 @@
+* -whitespace
index 36f251761739c6cda0053bbe26cc6331ed7be40c..73ed11bfe24edf56879d90a43c8df0813332fb08 100644 (file)
--- a/t/README
+++ b/t/README
@@ -160,14 +160,12 @@ library for your script to use.
 
  - test_expect_failure <message> <script>
 
-   This is the opposite of test_expect_success.  If <script>
-   yields success, test is considered a failure.
-
-   Example:
-
-       test_expect_failure \
-           'git-update-index without --add should fail adding.' \
-           'git-update-index should-be-empty'
+   This is NOT the opposite of test_expect_success, but is used
+   to mark a test that demonstrates a known breakage.  Unlike
+   the usual test_expect_success tests, which say "ok" on
+   success and "FAIL" on failure, this will say "FIXED" on
+   success and "still broken" on failure.  Failures from these
+   tests won't cause -i (immediate) to stop.
 
  - test_debug <script>
 
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
new file mode 100644 (file)
index 0000000..7f206c5
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
+#
+
+if test -z "$GIT_TEST_HTTPD"
+then
+       say "skipping test, network testing disabled by default"
+       say "(define GIT_TEST_HTTPD to enable)"
+       test_done
+       exit
+fi
+
+LIB_HTTPD_PATH=${LIB_HTTPD_PATH-'/usr/sbin/apache2'}
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'8111'}
+
+TEST_PATH="$PWD"/../lib-httpd
+HTTPD_ROOT_PATH="$PWD"/httpd
+HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www
+
+if ! test -x "$LIB_HTTPD_PATH"
+then
+        say "skipping test, no web server found at '$LIB_HTTPD_PATH'"
+        test_done
+        exit
+fi
+
+HTTPD_VERSION=`$LIB_HTTPD_PATH -v | \
+       sed -n 's/^Server version: Apache\/\([0-9]*\)\..*$/\1/p; q'`
+
+if test -n "$HTTPD_VERSION"
+then
+       if test -z "$LIB_HTTPD_MODULE_PATH"
+       then
+               if ! test $HTTPD_VERSION -ge 2
+               then
+                       say "skipping test, at least Apache version 2 is required"
+                       test_done
+                       exit
+               fi
+
+               LIB_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
+       fi
+else
+       error "Could not identify web server at '$LIB_HTTPD_PATH'"
+fi
+
+HTTPD_PARA="-d $HTTPD_ROOT_PATH -f $TEST_PATH/apache.conf"
+
+prepare_httpd() {
+       mkdir -p $HTTPD_DOCUMENT_ROOT_PATH
+
+       ln -s $LIB_HTTPD_MODULE_PATH $HTTPD_ROOT_PATH/modules
+
+       if test -n "$LIB_HTTPD_SSL"
+       then
+               HTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT
+
+               RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \
+                       -config $TEST_PATH/ssl.cnf \
+                       -new -x509 -nodes \
+                       -out $HTTPD_ROOT_PATH/httpd.pem \
+                       -keyout $HTTPD_ROOT_PATH/httpd.pem
+               export GIT_SSL_NO_VERIFY=t
+               HTTPD_PARA="$HTTPD_PARA -DSSL"
+       else
+               HTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT
+       fi
+
+       if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN"
+       then
+               HTTPD_PARA="$HTTPD_PARA -DDAV"
+
+               if test -n "$LIB_HTTPD_SVN"
+               then
+                       HTTPD_PARA="$HTTPD_PARA -DSVN"
+                       rawsvnrepo="$HTTPD_ROOT_PATH/svnrepo"
+                       svnrepo="http://127.0.0.1:$LIB_HTTPD_PORT/svn"
+               fi
+       fi
+}
+
+start_httpd() {
+       prepare_httpd
+
+       trap 'stop_httpd; die' exit
+
+       "$LIB_HTTPD_PATH" $HTTPD_PARA \
+               -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start
+}
+
+stop_httpd() {
+       trap 'die' exit
+
+       "$LIB_HTTPD_PATH" $HTTPD_PARA -k stop
+}
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
new file mode 100644 (file)
index 0000000..a447346
--- /dev/null
@@ -0,0 +1,34 @@
+PidFile httpd.pid
+DocumentRoot www
+ErrorLog error.log
+
+<IfDefine SSL>
+LoadModule ssl_module modules/mod_ssl.so
+
+SSLCertificateFile httpd.pem
+SSLCertificateKeyFile httpd.pem
+SSLRandomSeed startup file:/dev/urandom 512
+SSLRandomSeed connect file:/dev/urandom 512
+SSLSessionCache none
+SSLMutex file:ssl_mutex
+SSLEngine On
+</IfDefine>
+
+<IfDefine DAV>
+       LoadModule dav_module modules/mod_dav.so
+       LoadModule dav_fs_module modules/mod_dav_fs.so
+
+       DAVLockDB DAVLock
+       <Location />
+               Dav on
+       </Location>
+</IfDefine>
+
+<IfDefine SVN>
+       LoadModule dav_svn_module modules/mod_dav_svn.so
+
+       <Location /svn>
+               DAV svn
+               SVNPath svnrepo
+       </Location>
+</IfDefine>
diff --git a/t/lib-httpd/ssl.cnf b/t/lib-httpd/ssl.cnf
new file mode 100644 (file)
index 0000000..6dab257
--- /dev/null
@@ -0,0 +1,8 @@
+RANDFILE                = $ENV::RANDFILE_PATH
+
+[ req ]
+default_bits            = 1024
+distinguished_name      = req_distinguished_name
+prompt                  = no
+[ req_distinguished_name ]
+commonName              = 127.0.0.1
index 4e49d590651363631a1f3a795d3c1679a70fb05f..27b54cbb1216beef8b5f315708574825a1c81da3 100755 (executable)
@@ -46,13 +46,25 @@ test_expect_success \
     '.git/objects should have 3 subdirectories.' \
     'test $(wc -l < full-of-directories) = 3'
 
+################################################################
+# Test harness
+test_expect_success 'success is reported like this' '
+    :
+'
+test_expect_failure 'pretend we have a known breakage' '
+    false
+'
+test_expect_failure 'pretend we have fixed a known breakage' '
+    :
+'
+
 ################################################################
 # Basics of the basics
 
 # updating a new file without --add should fail.
-test_expect_failure \
-    'git update-index without --add should fail adding.' \
-    'git update-index should-be-empty'
+test_expect_success 'git update-index without --add should fail adding.' '
+    ! git update-index should-be-empty
+'
 
 # and with --add it should succeed, even if it is empty (it used to fail).
 test_expect_success \
@@ -70,9 +82,9 @@ test_expect_success \
 
 # Removing paths.
 rm -f should-be-empty full-of-directories
-test_expect_failure \
-    'git update-index without --remove should fail removing.' \
-    'git update-index should-be-empty'
+test_expect_success 'git update-index without --remove should fail removing.' '
+    ! git update-index should-be-empty
+'
 
 test_expect_success \
     'git update-index with --remove should be able to remove.' \
@@ -204,9 +216,9 @@ test_expect_success \
     'put invalid objects into the index.' \
     'git update-index --index-info < badobjects'
 
-test_expect_failure \
-    'writing this tree without --missing-ok.' \
-    'git write-tree'
+test_expect_success 'writing this tree without --missing-ok.' '
+    ! git write-tree
+'
 
 test_expect_success \
     'writing this tree with --missing-ok.' \
@@ -292,9 +304,31 @@ test_expect_success 'absolute path works as expected' '
        test "$dir" = "$(test-absolute-path $dir2)" &&
        file="$dir"/index &&
        test "$file" = "$(test-absolute-path $dir2/index)" &&
+       basename=blub &&
+       test "$dir/$basename" = $(cd .git && test-absolute-path $basename) &&
        ln -s ../first/file .git/syml &&
        sym="$(cd first; pwd -P)"/file &&
        test "$sym" = "$(test-absolute-path $dir2/syml)"
 '
 
+test_expect_success 'very long name in the index handled sanely' '
+
+       a=a && # 1
+       a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 16
+       a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 256
+       a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 4096
+       a=${a}q &&
+
+       >path4 &&
+       git update-index --add path4 &&
+       (
+               git ls-files -s path4 |
+               sed -e "s/      .*/     /" |
+               tr -d "\012"
+               echo "$a"
+       ) | git update-index --index-info &&
+       len=$(git ls-files "a*" | wc -c) &&
+       test $len = 4098
+'
+
 test_done
index 8b27aa892b2b56056b21874cb81c1ddd7956a60a..90ea081db666d707246e37affc5676e6c3502741 100755 (executable)
@@ -8,6 +8,10 @@ q_to_nul () {
        tr Q '\000'
 }
 
+q_to_cr () {
+       tr Q '\015'
+}
+
 append_cr () {
        sed -e 's/$/Q/' | tr Q '\015'
 }
@@ -42,6 +46,60 @@ test_expect_success setup '
        echo happy.
 '
 
+test_expect_success 'safecrlf: autocrlf=input, all CRLF' '
+
+       git config core.autocrlf input &&
+       git config core.safecrlf true &&
+
+       for w in I am all CRLF; do echo $w; done | append_cr >allcrlf &&
+       ! git add allcrlf
+'
+
+test_expect_success 'safecrlf: autocrlf=input, mixed LF/CRLF' '
+
+       git config core.autocrlf input &&
+       git config core.safecrlf true &&
+
+       for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed &&
+       ! git add mixed
+'
+
+test_expect_success 'safecrlf: autocrlf=true, all LF' '
+
+       git config core.autocrlf true &&
+       git config core.safecrlf true &&
+
+       for w in I am all LF; do echo $w; done >alllf &&
+       ! git add alllf
+'
+
+test_expect_success 'safecrlf: autocrlf=true mixed LF/CRLF' '
+
+       git config core.autocrlf true &&
+       git config core.safecrlf true &&
+
+       for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed &&
+       ! git add mixed
+'
+
+test_expect_success 'safecrlf: print warning only once' '
+
+       git config core.autocrlf input &&
+       git config core.safecrlf warn &&
+
+       for w in I am all LF; do echo $w; done >doublewarn &&
+       git add doublewarn &&
+       git commit -m "nowarn" &&
+       for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >doublewarn &&
+       test $(git add doublewarn 2>&1 | grep "CRLF will be replaced by LF" | wc -l) = 1
+'
+
+test_expect_success 'switch off autocrlf, safecrlf, reset HEAD' '
+       git config core.autocrlf false &&
+       git config core.safecrlf false &&
+       git reset --hard HEAD^
+'
+
 test_expect_success 'update with autocrlf=input' '
 
        rm -f tmp one dir/two three &&
index cad95f35adad5864e99ef5cd1633c820ff25b6c0..818c8621f239dd7f61a2d79e9c65779dbbf9f6ef 100755 (executable)
@@ -243,14 +243,14 @@ test_expect_success \
     test `printf "$ttt$sss$sss$sss" | git stripspace | wc -l` -gt 0
 '
 
-test_expect_failure \
+test_expect_success \
     'text plus spaces without newline at end should not show spaces' '
-    printf "$ttt$sss" | git stripspace | grep -q "  " ||
-    printf "$ttt$ttt$sss" | git stripspace | grep -q "  " ||
-    printf "$ttt$ttt$ttt$sss" | git stripspace | grep -q "  " ||
-    printf "$ttt$sss$sss" | git stripspace | grep -q "  " ||
-    printf "$ttt$ttt$sss$sss" | git stripspace | grep -q "  " ||
-    printf "$ttt$sss$sss$sss" | git stripspace | grep -q "  "
+    ! (printf "$ttt$sss" | git stripspace | grep -q "  ") &&
+    ! (printf "$ttt$ttt$sss" | git stripspace | grep -q "  ") &&
+    ! (printf "$ttt$ttt$ttt$sss" | git stripspace | grep -q "  ") &&
+    ! (printf "$ttt$sss$sss" | git stripspace | grep -q "  ") &&
+    ! (printf "$ttt$ttt$sss$sss" | git stripspace | grep -q "  ") &&
+    ! (printf "$ttt$sss$sss$sss" | git stripspace | grep -q "  ")
 '
 
 test_expect_success \
@@ -280,14 +280,14 @@ test_expect_success \
     git diff expect actual
 '
 
-test_expect_failure \
+test_expect_success \
     'text plus spaces at end should not show spaces' '
-    echo "$ttt$sss" | git stripspace | grep -q "  " ||
-    echo "$ttt$ttt$sss" | git stripspace | grep -q "  " ||
-    echo "$ttt$ttt$ttt$sss" | git stripspace | grep -q "  " ||
-    echo "$ttt$sss$sss" | git stripspace | grep -q "  " ||
-    echo "$ttt$ttt$sss$sss" | git stripspace | grep -q "  " ||
-    echo "$ttt$sss$sss$sss" | git stripspace | grep -q "  "
+    ! (echo "$ttt$sss" | git stripspace | grep -q "  ") &&
+    ! (echo "$ttt$ttt$sss" | git stripspace | grep -q "  ") &&
+    ! (echo "$ttt$ttt$ttt$sss" | git stripspace | grep -q "  ") &&
+    ! (echo "$ttt$sss$sss" | git stripspace | grep -q "  ") &&
+    ! (echo "$ttt$ttt$sss$sss" | git stripspace | grep -q "  ") &&
+    ! (echo "$ttt$sss$sss$sss" | git stripspace | grep -q "  ")
 '
 
 test_expect_success \
@@ -339,13 +339,13 @@ test_expect_success \
     git diff expect actual
 '
 
-test_expect_failure \
+test_expect_success \
     'spaces without newline at end should not show spaces' '
-    printf "" | git stripspace | grep -q " " ||
-    printf "$sss" | git stripspace | grep -q " " ||
-    printf "$sss$sss" | git stripspace | grep -q " " ||
-    printf "$sss$sss$sss" | git stripspace | grep -q " " ||
-    printf "$sss$sss$sss$sss" | git stripspace | grep -q " "
+    ! (printf "" | git stripspace | grep -q " ") &&
+    ! (printf "$sss" | git stripspace | grep -q " ") &&
+    ! (printf "$sss$sss" | git stripspace | grep -q " ") &&
+    ! (printf "$sss$sss$sss" | git stripspace | grep -q " ") &&
+    ! (printf "$sss$sss$sss$sss" | git stripspace | grep -q " ")
 '
 
 test_expect_success \
index 0a3b55d1212ddeda87f1f3ffa4c4e2fbefb44553..c23f0ace85c83f54fd7562d72af6ef6c1ea2eca3 100755 (executable)
@@ -21,6 +21,9 @@ string options
     --st <st>             get another string (pervert ordering)
     -o <str>              get another string
 
+magic arguments
+    --quux                means --quux
+
 EOF
 
 test_expect_success 'test help' '
@@ -87,9 +90,9 @@ test_expect_success 'unambiguously abbreviated option with "="' '
        git diff expect output
 '
 
-test_expect_failure 'ambiguously abbreviated option' '
+test_expect_success 'ambiguously abbreviated option' '
        test-parse-options --strin 123;
-        test $? != 129
+       test $? = 129
 '
 
 cat > expect << EOF
@@ -114,4 +117,17 @@ test_expect_success 'detect possible typos' '
        git diff expect.err output.err
 '
 
+cat > expect <<EOF
+boolean: 0
+integer: 0
+string: (not set)
+arg 00: --quux
+EOF
+
+test_expect_success 'keep some options as arguments' '
+       test-parse-options --quux > output 2> output.err &&
+        test ! -s output.err &&
+        git diff expect output
+'
+
 test_done
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
new file mode 100755 (executable)
index 0000000..cd088b3
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description='Various filesystem issues'
+
+. ./test-lib.sh
+
+auml=`perl -CO -e 'print pack("U",0x00E4)'`
+aumlcdiar=`perl -CO -e 'print pack("U",0x0061).pack("U",0x0308)'`
+
+test_expect_success 'see if we expect ' '
+
+       test_case=test_expect_success
+       test_unicode=test_expect_success
+       mkdir junk &&
+       echo good >junk/CamelCase &&
+       echo bad >junk/camelcase &&
+       if test "$(cat junk/CamelCase)" != good
+       then
+               test_case=test_expect_failure
+               say "will test on a case insensitive filesystem"
+       fi &&
+       rm -fr junk &&
+       mkdir junk &&
+       >junk/"$auml" &&
+       case "$(cd junk && echo *)" in
+       "$aumlcdiar")
+               test_unicode=test_expect_failure
+               say "will test on a unicode corrupting filesystem"
+               ;;
+       *)      ;;
+       esac &&
+       rm -fr junk
+'
+
+test_expect_success "setup case tests" '
+
+       touch camelcase &&
+       git add camelcase &&
+       git commit -m "initial" &&
+       git tag initial &&
+       git checkout -b topic &&
+       git mv camelcase tmp &&
+       git mv tmp CamelCase &&
+       git commit -m "rename" &&
+       git checkout -f master
+
+'
+
+$test_case 'rename (case change)' '
+
+       git mv camelcase CamelCase &&
+       git commit -m "rename"
+
+'
+
+$test_case 'merge (case change)' '
+
+       git reset --hard initial &&
+       git merge topic
+
+'
+
+test_expect_success "setup unicode normalization tests" '
+
+  test_create_repo unicode &&
+  cd unicode &&
+  touch "$aumlcdiar" &&
+  git add "$aumlcdiar" &&
+  git commit -m initial
+  git tag initial &&
+  git checkout -b topic &&
+  git mv $aumlcdiar tmp &&
+  git mv tmp "$auml" &&
+  git commit -m rename &&
+  git checkout -f master
+
+'
+
+$test_unicode 'rename (silent unicode normalization)' '
+
+ git mv "$aumlcdiar" "$auml" &&
+ git commit -m rename
+
+'
+
+$test_unicode 'merge (silent unicode normalization)' '
+
+ git reset --hard initial &&
+ git merge topic
+
+'
+
+test_done
index 37add1b50472e23ccb6b938ac6cdadba0c097fb8..6c065bfa21b68e23fb92fbb25aea0907cb22aa3e 100755 (executable)
@@ -210,12 +210,12 @@ DF (file) when tree B require DF to be a directory by having DF/DF
 
 END_OF_CASE_TABLE
 
-test_expect_failure \
-    '1 - must not have an entry not in A.' \
-    "rm -f .git/index XX &&
+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 &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '2 - must match B in !O && !A && B case.' \
@@ -248,13 +248,14 @@ test_expect_success \
      echo extra >>AN &&
      git read-tree -m $tree_O $tree_A $tree_B"
 
-test_expect_failure \
-    '3 (fail) - must match A in !O && A && !B case.' \
-    "rm -f .git/index AN &&
+test_expect_success \
+    '3 (fail) - must match A in !O && A && !B case.' "
+     rm -f .git/index AN &&
      cp .orig-A/AN AN &&
      echo extra >>AN &&
      git update-index --add AN &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '4 - must match and be up-to-date in !O && A && B && A!=B case.' \
@@ -264,21 +265,23 @@ test_expect_success \
      git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
-test_expect_failure \
-    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
-    "rm -f .git/index AA &&
+test_expect_success \
+    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' "
+     rm -f .git/index AA &&
      cp .orig-A/AA AA &&
      git update-index --add AA &&
      echo extra >>AA &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
-test_expect_failure \
-    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
-    "rm -f .git/index AA &&
+test_expect_success \
+    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' "
+     rm -f .git/index AA &&
      cp .orig-A/AA AA &&
      echo extra >>AA &&
      git update-index --add AA &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '5 - must match in !O && A && B && A==B case.' \
@@ -297,34 +300,38 @@ test_expect_success \
      git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
-test_expect_failure \
-    '5 (fail) - must match A in !O && A && B && A==B case.' \
-    "rm -f .git/index LL &&
+test_expect_success \
+    '5 (fail) - must match A in !O && A && B && A==B case.' "
+     rm -f .git/index LL &&
      cp .orig-A/LL LL &&
      echo extra >>LL &&
      git update-index --add LL &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
-test_expect_failure \
-    '6 - must not exist in O && !A && !B case' \
-    "rm -f .git/index DD &&
+test_expect_success \
+    '6 - must not exist in O && !A && !B case' "
+     rm -f .git/index DD &&
      echo DD >DD
      git update-index --add DD &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
-test_expect_failure \
-    '7 - must not exist in O && !A && B && O!=B case' \
-    "rm -f .git/index DM &&
+test_expect_success \
+    '7 - must not exist in O && !A && B && O!=B case' "
+     rm -f .git/index DM &&
      cp .orig-B/DM DM &&
      git update-index --add DM &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
-test_expect_failure \
-    '8 - must not exist in O && !A && B && O==B case' \
-    "rm -f .git/index DN &&
+test_expect_success \
+    '8 - must not exist in O && !A && B && O==B case' "
+     rm -f .git/index DN &&
      cp .orig-B/DN DN &&
      git update-index --add DN &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '9 - must match and be up-to-date in O && A && !B && O!=A case' \
@@ -334,21 +341,23 @@ test_expect_success \
      git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
-test_expect_failure \
-    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
-    "rm -f .git/index MD &&
+test_expect_success \
+    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' "
+     rm -f .git/index MD &&
      cp .orig-A/MD MD &&
      git update-index --add MD &&
      echo extra >>MD &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
-test_expect_failure \
-    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
-    "rm -f .git/index MD &&
+test_expect_success \
+    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' "
+     rm -f .git/index MD &&
      cp .orig-A/MD MD &&
      echo extra >>MD &&
      git update-index --add MD &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '10 - must match and be up-to-date in O && A && !B && O==A case' \
@@ -358,21 +367,23 @@ test_expect_success \
      git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
-test_expect_failure \
-    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
-    "rm -f .git/index ND &&
+test_expect_success \
+    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' "
+     rm -f .git/index ND &&
      cp .orig-A/ND ND &&
      git update-index --add ND &&
      echo extra >>ND &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
-test_expect_failure \
-    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
-    "rm -f .git/index ND &&
+test_expect_success \
+    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' "
+     rm -f .git/index ND &&
      cp .orig-A/ND ND &&
      echo extra >>ND &&
      git update-index --add ND &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
@@ -382,21 +393,23 @@ test_expect_success \
      git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
-test_expect_failure \
-    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
-    "rm -f .git/index MM &&
+test_expect_success \
+    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' "
+     rm -f .git/index MM &&
      cp .orig-A/MM MM &&
      git update-index --add MM &&
      echo extra >>MM &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
-test_expect_failure \
-    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
-    "rm -f .git/index MM &&
+test_expect_success \
+    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' "
+     rm -f .git/index MM &&
      cp .orig-A/MM MM &&
      echo extra >>MM &&
      git update-index --add MM &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '12 - must match A in O && A && B && O!=A && A==B case' \
@@ -415,13 +428,14 @@ test_expect_success \
      git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
-test_expect_failure \
-    '12 (fail) - must match A in O && A && B && O!=A && A==B case' \
-    "rm -f .git/index SS &&
+test_expect_success \
+    '12 (fail) - must match A in O && A && B && O!=A && A==B case' "
+     rm -f .git/index SS &&
      cp .orig-A/SS SS &&
      echo extra >>SS &&
      git update-index --add SS &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '13 - must match A in O && A && B && O!=A && O==B case' \
@@ -457,21 +471,23 @@ test_expect_success \
      git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
-test_expect_failure \
-    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
-    "rm -f .git/index NM &&
+test_expect_success \
+    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' "
+     rm -f .git/index NM &&
      cp .orig-A/NM NM &&
      git update-index --add NM &&
      echo extra >>NM &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
-test_expect_failure \
-    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
-    "rm -f .git/index NM &&
+test_expect_success \
+    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' "
+     rm -f .git/index NM &&
      cp .orig-A/NM NM &&
      echo extra >>NM &&
      git update-index --add NM &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 test_expect_success \
     '15 - must match A in O && A && B && O==A && O==B case' \
@@ -490,13 +506,14 @@ test_expect_success \
      git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
-test_expect_failure \
-    '15 (fail) - must match A in O && A && B && O==A && O==B case' \
-    "rm -f .git/index NN &&
+test_expect_success \
+    '15 (fail) - must match A in O && A && B && O==A && O==B case' "
+     rm -f .git/index NN &&
      cp .orig-A/NN NN &&
      echo extra >>NN &&
      git update-index --add NN &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     ! git read-tree -m $tree_O $tree_A $tree_B
+"
 
 # #16
 test_expect_success \
diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh
new file mode 100755 (executable)
index 0000000..f1b1216
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+test_description='read-tree -u --reset'
+
+. ./test-lib.sh
+
+# two-tree test
+
+test_expect_success 'setup' '
+  git init &&
+  mkdir df &&
+  echo content >df/file &&
+  git add df/file &&
+  git commit -m one &&
+  git ls-files >expect &&
+  rm -rf df &&
+  echo content >df &&
+  git add df &&
+  echo content >new &&
+  git add new &&
+  git commit -m two
+'
+
+test_expect_failure 'reset should work' '
+  git read-tree -u --reset HEAD^ &&
+  git ls-files >actual &&
+  diff -u expect actual
+'
+
+test_done
index 991d3c5e9c5c8dc9e59b0105010f1b77d4bf3a3f..dcb3108c290813dae178394011bb2b44f2f7ca9e 100755 (executable)
@@ -101,8 +101,8 @@ echo "Play, play, play" >>hello
 echo "Lots of fun" >>example
 git commit -m 'Some fun.' -i hello example
 
-test_expect_failure 'git resolve now fails' '
-       git merge -m "Merge work in mybranch" mybranch
+test_expect_success 'git resolve now fails' '
+       git merge -m "Merge work in mybranch" mybranch
 '
 
 cat > hello << EOF
@@ -156,6 +156,8 @@ test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.outp
 
 test_expect_success 'git repack' 'git repack'
 test_expect_success 'git prune-packed' 'git prune-packed'
-test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
+test_expect_success '-> only packed objects' '
+       ! find -type f .git/objects/[0-9a-f][0-9a-f]
+'
 
 test_done
index d9e358e1b4d7d64c2347fe0aab4a0b736f82fcf3..4928a571144b3fb9ec38312d917e9f95e40ceb99 100755 (executable)
@@ -200,8 +200,9 @@ test_expect_success 'non-match' \
 test_expect_success 'non-match value' \
        'test wow = $(git config --get nextsection.nonewline !for)'
 
-test_expect_failure 'ambiguous get' \
-       'git config --get nextsection.nonewline'
+test_expect_success 'ambiguous get' '
+       ! git config --get nextsection.nonewline
+'
 
 test_expect_success 'get multivar' \
        'git config --get-all nextsection.nonewline'
@@ -221,13 +222,17 @@ EOF
 
 test_expect_success 'multivar replace' 'cmp .git/config expect'
 
-test_expect_failure 'ambiguous value' 'git config nextsection.nonewline'
+test_expect_success 'ambiguous value' '
+       ! git config nextsection.nonewline
+'
 
-test_expect_failure 'ambiguous unset' \
-       'git config --unset nextsection.nonewline'
+test_expect_success 'ambiguous unset' '
+       ! git config --unset nextsection.nonewline
+'
 
-test_expect_failure 'invalid unset' \
-       'git config --unset somesection.nonewline'
+test_expect_success 'invalid unset' '
+       ! git config --unset somesection.nonewline
+'
 
 git config --unset nextsection.nonewline "wow3$"
 
@@ -243,7 +248,7 @@ EOF
 
 test_expect_success 'multivar unset' 'cmp .git/config expect'
 
-test_expect_failure 'invalid key' 'git config inval.2key blabla'
+test_expect_success 'invalid key' '! git config inval.2key blabla'
 
 test_expect_success 'correct key' 'git config 123456.a123 987'
 
@@ -424,8 +429,9 @@ EOF
 
 test_expect_success "rename succeeded" "git diff expect .git/config"
 
-test_expect_failure "rename non-existing section" \
-       'git config --rename-section branch."world domination" branch.drei'
+test_expect_success "rename non-existing section" '
+       ! git config --rename-section branch."world domination" branch.drei
+'
 
 test_expect_success "rename succeeded" "git diff expect .git/config"
 
@@ -536,14 +542,14 @@ test_expect_success bool '
         done &&
        cmp expect result'
 
-test_expect_failure 'invalid bool (--get)' '
+test_expect_success 'invalid bool (--get)' '
 
        git config bool.nobool foobar &&
-       git config --bool --get bool.nobool'
+       git config --bool --get bool.nobool'
 
-test_expect_failure 'invalid bool (set)' '
+test_expect_success 'invalid bool (set)' '
 
-       git config --bool bool.nobool foobar'
+       git config --bool bool.nobool foobar'
 
 rm .git/config
 
@@ -604,8 +610,9 @@ EOF
 
 test_expect_success 'quoting' 'cmp .git/config expect'
 
-test_expect_failure 'key with newline' 'git config key.with\\\
-newline 123'
+test_expect_success 'key with newline' '
+       ! git config "key.with
+newline" 123'
 
 test_expect_success 'value with newline' 'git config key.sub value.with\\\
 newline'
index 37fc1c8d36ba6e0a12c83d0c38c87a37863870e5..9be0770e7627ee094349af85b0d4702e156ff6cd 100755 (executable)
@@ -40,7 +40,8 @@ test_expect_success 'gitdir required mode on normal repos' '
        (git apply --check --index test.patch &&
        cd test && git apply --check --index ../test.patch)'
 
-test_expect_failure 'gitdir required mode on unsupported repo' '
-       (cd test2 && git apply --check --index ../test.patch)'
+test_expect_success 'gitdir required mode on unsupported repo' '
+       (cd test2 && ! git apply --check --index ../test.patch)
+'
 
 test_done
index 71ab2dd0eedd5fa79300fe84fbd168b538dfd36c..78cd41245b301e731dc0fdd0066b3d3bac4f5405 100755 (executable)
@@ -51,23 +51,23 @@ test_expect_success \
         test $B"' = $(cat .git/'"$m"')'
 rm -f .git/$m
 
-test_expect_failure \
-       '(not) create HEAD with old sha1' \
-       "git update-ref HEAD $A $B"
-test_expect_failure \
-       "(not) prior created .git/$m" \
-       "test -f .git/$m"
+test_expect_success '(not) create HEAD with old sha1' "
+       ! git update-ref HEAD $A $B
+"
+test_expect_success "(not) prior created .git/$m" "
+       ! test -f .git/$m
+"
 rm -f .git/$m
 
 test_expect_success \
        "create HEAD" \
        "git update-ref HEAD $A"
-test_expect_failure \
-       '(not) change HEAD with wrong SHA1' \
-       "git update-ref HEAD $B $Z"
-test_expect_failure \
-       "(not) changed .git/$m" \
-       "test $B"' = $(cat .git/'"$m"')'
+test_expect_success '(not) change HEAD with wrong SHA1' "
+       ! git update-ref HEAD $B $Z
+"
+test_expect_success "(not) changed .git/$m" "
+       ! test $B"' = $(cat .git/'"$m"')
+'
 rm -f .git/$m
 
 : a repository with working tree always has reflog these days...
index f959aae84630ddbb68304868b1a025b7c2d33d10..24476bede5ce8f8720bf3d9eba7f7a944182dbf4 100755 (executable)
@@ -175,6 +175,33 @@ test_expect_success 'recover and check' '
 
 '
 
+test_expect_success 'delete' '
+       echo 1 > C &&
+       test_tick &&
+       git commit -m rat C &&
+
+       echo 2 > C &&
+       test_tick &&
+       git commit -m ox C &&
+
+       echo 3 > C &&
+       test_tick &&
+       git commit -m tiger C &&
+
+       test 5 = $(git reflog | wc -l) &&
+
+       git reflog delete master@{1} &&
+       git reflog show master > output &&
+       test 4 = $(wc -l < output) &&
+       ! grep ox < output &&
+
+       git reflog delete master@{07.04.2005.15:15:00.-0700} &&
+       git reflog show master > output &&
+       test 3 = $(wc -l < output) &&
+       ! grep dragon < output
+
+'
+
 test_expect_success 'prune --expire' '
 
        before=$(git count-objects | sed "s/ .*//") &&
index e474b3f1d5482305356239980248e2ebccbcf561..38a2bf09af286bf681deadb9cdaec80175d13e0c 100755 (executable)
@@ -33,9 +33,9 @@ test_rev_parse() {
 test_rev_parse toplevel false false true ''
 
 cd .git || exit 1
-test_rev_parse .git/ true true false ''
+test_rev_parse .git/ false true false ''
 cd objects || exit 1
-test_rev_parse .git/objects/ true true false ''
+test_rev_parse .git/objects/ false true false ''
 cd ../.. || exit 1
 
 mkdir -p sub/dir || exit 1
index ac84335b0a47fe1d26794e4c92f00d0ed051e540..5141fab7cf567fc5d16a8e9a296d2aff5f6c67e6 100755 (executable)
@@ -36,9 +36,9 @@ mkdir path0
 date >path0/file0
 date >path1
 
-test_expect_failure \
+test_expect_success \
     'git checkout-index without -f should fail on conflicting work tree.' \
-    'git checkout-index -a'
+    'git checkout-index -a'
 
 test_expect_success \
     'git checkout-index with -f should succeed.' \
index f7a00559209872fab5c79896ed5bc71ba64c884e..0f441bcef768ca68ead9bfa2d06b714b8d2c35ac 100755 (executable)
@@ -16,12 +16,12 @@ echo frotz >path0 &&
 git update-index --add path0 &&
 t=$(git write-tree)'
 
-test_expect_failure \
+test_expect_success \
 'without -u, git checkout-index smudges stat information.' '
 rm -f path0 &&
 git read-tree $t &&
 git checkout-index -f -a &&
-git diff-files | diff - /dev/null'
+git diff-files | diff - /dev/null'
 
 test_expect_success \
 'with -u, git checkout-index picks up stat information from new files.' '
diff --git a/t/t2009-checkout-statinfo.sh b/t/t2009-checkout-statinfo.sh
new file mode 100755 (executable)
index 0000000..f3c2152
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='checkout should leave clean stat info'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+       echo hello >world &&
+       git update-index --add world &&
+       git commit -m initial &&
+       git branch side &&
+       echo goodbye >world &&
+       git update-index --add world &&
+       git commit -m second
+
+'
+
+test_expect_success 'branch switching' '
+
+       git reset --hard &&
+       test "$(git diff-files --raw)" = "" &&
+
+       git checkout master &&
+       test "$(git diff-files --raw)" = "" &&
+
+       git checkout side &&
+       test "$(git diff-files --raw)" = "" &&
+
+       git checkout master &&
+       test "$(git diff-files --raw)" = ""
+
+'
+
+test_expect_success 'path checkout' '
+
+       git reset --hard &&
+       test "$(git diff-files --raw)" = "" &&
+
+       git checkout master world &&
+       test "$(git diff-files --raw)" = "" &&
+
+       git checkout side world &&
+       test "$(git diff-files --raw)" = "" &&
+
+       git checkout master world &&
+       test "$(git diff-files --raw)" = ""
+
+'
+
+test_done
+
index 04a1ed1a6b9dd4eabc2b95d348b77b0fd08b0da4..9beaecd18b25cb7e22b09c11234c05a8fa1d4116 100755 (executable)
@@ -44,8 +44,8 @@ date >path1/file1
 
 for p in path0/file0 path1/file1 path2 path3
 do
-       test_expect_failure \
+       test_expect_success \
            "git update-index to add conflicting path $p should fail." \
-           "git update-index --add -- $p"
+           "git update-index --add -- $p"
 done
 test_done
index e25b25568337ae36a5ef1377a14df031d5dfaeb1..b4297bacf2bd3d6da0f7fd01fba65741cf568003 100755 (executable)
@@ -99,4 +99,45 @@ EOF
 test_expect_success 'git-status honours core.excludesfile' \
        'diff -u expect output'
 
+test_expect_success 'trailing slash in exclude allows directory match(1)' '
+
+       git ls-files --others --exclude=one/ >output &&
+       if grep "^one/" output
+       then
+               echo Ooops
+               false
+       else
+               : happy
+       fi
+
+'
+
+test_expect_success 'trailing slash in exclude allows directory match (2)' '
+
+       git ls-files --others --exclude=one/two/ >output &&
+       if grep "^one/two/" output
+       then
+               echo Ooops
+               false
+       else
+               : happy
+       fi
+
+'
+
+test_expect_success 'trailing slash in exclude forces directory match (1)' '
+
+       >two
+       git ls-files --others --exclude=two/ >output &&
+       grep "^two" output
+
+'
+
+test_expect_success 'trailing slash in exclude forces directory match (2)' '
+
+       git ls-files --others --exclude=one/a.1/ >output &&
+       grep "^one/a.1" output
+
+'
+
 test_done
index c83f820ad2d8588b8e5e15b3cb55172b26b7c33e..f4da869932e40429f94d19a9cf2e18dc1e838f0a 100755 (executable)
@@ -15,9 +15,9 @@ touch foo bar
 git update-index --add foo bar
 git-commit -m "add foo bar"
 
-test_expect_failure \
+test_expect_success \
     'git ls-files --error-unmatch should fail with unmatched path.' \
-    'git ls-files --error-unmatch foo bar-does-not-match'
+    'git ls-files --error-unmatch foo bar-does-not-match'
 
 test_expect_success \
     'git ls-files --error-unmatch should succeed eith matched paths.' \
index 39fe2676dcd8e22451309d2321dee45410f90963..70f9ce9d52827cb35fee7ac0f13471023291d1fa 100755 (executable)
@@ -120,7 +120,7 @@ EOF
 # having 1.txt and path3
 test_expect_success \
     'ls-tree filter odd names' \
-    'git ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&
+    'git ls-tree $tree 1.txt ./1.txt .//1.txt path3/1.txt path3/./1.txt path3 path3// >current &&
      cat >expected <<\EOF &&
 100644 blob X  1.txt
 100644 blob X  path3/1.txt
index ef1eeb7d8ac349821559dd358470f10ae0531739..38a90adad6874d0073059da542835df210172938 100755 (executable)
@@ -15,12 +15,16 @@ test_expect_success \
     'echo Hello > A &&
      git update-index --add A &&
      git-commit -m "Initial commit." &&
+     echo World >> A &&
+     git update-index --add A &&
+     git-commit -m "Second commit." &&
      HEAD=$(git rev-parse --verify HEAD)'
 
-test_expect_failure \
-    'git branch --help should not have created a bogus branch' \
-    'git branch --help </dev/null >/dev/null 2>/dev/null || :
-     test -f .git/refs/heads/--help'
+test_expect_success \
+    'git branch --help should not have created a bogus branch' '
+     git branch --help </dev/null >/dev/null 2>/dev/null;
+     ! test -f .git/refs/heads/--help
+'
 
 test_expect_success \
     'git branch abc should create a branch' \
@@ -71,17 +75,17 @@ test_expect_success \
         git branch -m n/n n
         test -f .git/logs/refs/heads/n'
 
-test_expect_failure \
-    'git branch -m o/o o should fail when o/p exists' \
-       'git branch o/o &&
+test_expect_success 'git branch -m o/o o should fail when o/p exists' '
+       git branch o/o &&
         git branch o/p &&
-        git branch -m o/o o'
+       ! git branch -m o/o o
+'
 
-test_expect_failure \
-    'git branch -m q r/q should fail when r exists' \
-       'git branch q &&
-         git branch r &&
-         git branch -m q r/q'
+test_expect_success 'git branch -m q r/q should fail when r exists' '
+       git branch q &&
+       git branch r &&
+       ! git branch -m q r/q
+'
 
 mv .git/config .git/config-saved
 
@@ -108,12 +112,13 @@ test_expect_success 'config information was renamed, too' \
        "test $(git config branch.s.dummy) = Hello &&
         ! git config branch.s/s/dummy"
 
-test_expect_failure \
-    'git branch -m u v should fail when the reflog for u is a symlink' \
-    'git branch -l u &&
+test_expect_success \
+    'git branch -m u v should fail when the reflog for u is a symlink' '
+     git branch -l u &&
      mv .git/logs/refs/heads/u real-u &&
      ln -s real-u .git/logs/refs/heads/u &&
-     git branch -m u v'
+     ! git branch -m u v
+'
 
 test_expect_success 'test tracking setup via --track' \
     'git config remote.local.url . &&
@@ -169,7 +174,9 @@ test_expect_success 'test overriding tracking setup via --no-track' \
      ! test "$(git config branch.my2.merge)" = refs/heads/master'
 
 test_expect_success 'no tracking without .fetch entries' \
-    'git branch --track my6 s &&
+    'git config branch.autosetupmerge true &&
+     git branch my6 s &&
+     git config branch.automsetupmerge false &&
      test -z "$(git config branch.my6.remote)" &&
      test -z "$(git config branch.my6.merge)"'
 
@@ -190,6 +197,21 @@ test_expect_success 'test deleting branch without config' \
     'git branch my7 s &&
      test "$(git branch -d my7 2>&1)" = "Deleted branch my7."'
 
+test_expect_success 'test --track without .fetch entries' \
+    'git branch --track my8 &&
+     test "$(git config branch.my8.remote)" &&
+     test "$(git config branch.my8.merge)"'
+
+test_expect_success \
+    'branch from non-branch HEAD w/autosetupmerge=always' \
+    'git config branch.autosetupmerge always &&
+     git branch my9 HEAD^ &&
+     git config branch.autosetupmerge false'
+
+test_expect_success \
+    'branch from non-branch HEAD w/--track causes failure' \
+    '!(git branch --track my10 HEAD^)'
+
 # 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
index 4ddc6342a94b9e7b39153cf5ac93ae3c3fb3c31e..b64ccfbc5bcf40717f8e04bdadc841bc8cd6c51f 100755 (executable)
@@ -39,12 +39,12 @@ test_expect_success \
      git show-ref b >result &&
      diff expect result'
 
-test_expect_failure \
-    'git branch c/d should barf if branch c exists' \
-    'git branch c &&
+test_expect_success 'git branch c/d should barf if branch c exists' '
+     git branch c &&
      git pack-refs --all &&
-     rm .git/refs/heads/c &&
-     git branch c/d'
+     rm -f .git/refs/heads/c &&
+     ! git branch c/d
+'
 
 test_expect_success \
     'see if a branch still exists after git pack-refs --prune' \
@@ -54,11 +54,11 @@ test_expect_success \
      git show-ref e >result &&
      diff expect result'
 
-test_expect_failure \
-    'see if git pack-refs --prune remove ref files' \
-    'git branch f &&
+test_expect_success 'see if git pack-refs --prune remove ref files' '
+     git branch f &&
      git pack-refs --all --prune &&
-     ls .git/refs/heads/f'
+     ! test -f .git/refs/heads/f
+'
 
 test_expect_success \
     'git branch g should work when git branch g/h has been deleted' \
@@ -69,11 +69,11 @@ test_expect_success \
      git pack-refs --all &&
      git branch -d g'
 
-test_expect_failure \
-    'git branch i/j/k should barf if branch i exists' \
-    'git branch i &&
+test_expect_success 'git branch i/j/k should barf if branch i exists' '
+     git branch i &&
      git pack-refs --all --prune &&
-     git branch i/j/k'
+     ! git branch i/j/k
+'
 
 test_expect_success \
     'test git branch k after branch k/l/m and k/lm have been deleted' \
index 95e33b52100bb360ab3fcfa1b419b66321340b8a..496f4ec17217769228954116528b0fc0c1ef2a62 100755 (executable)
@@ -42,9 +42,9 @@ test_expect_success \
 test_expect_success 'rebase against master' '
      git rebase master'
 
-test_expect_failure \
+test_expect_success \
     'the rebase operation should not have destroyed author information' \
-    'git log | grep "Author:" | grep "<>"'
+    'git log | grep "Author:" | grep "<>"'
 
 test_expect_success 'rebase after merge master' '
      git reset --hard topic &&
index 657f68104d52558668119234a0637ac2bca33c0a..0a26099658f4307f06fe594feb3fc046ff267076 100755 (executable)
@@ -31,8 +31,8 @@ test_expect_success setup '
        git branch skip-merge skip-reference
        '
 
-test_expect_failure 'rebase with git am -3 (default)' '
-       git rebase master
+test_expect_success 'rebase with git am -3 (default)' '
+       git rebase master
 '
 
 test_expect_success 'rebase --skip with am -3' '
@@ -53,7 +53,7 @@ test_expect_success 'rebase moves back to skip-reference' '
 
 test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge'
 
-test_expect_failure 'rebase with --merge' 'git rebase --merge master'
+test_expect_success 'rebase with --merge' '! git rebase --merge master'
 
 test_expect_success 'rebase --skip with --merge' '
        git rebase --skip
index e5ed74545b8a2ac9c2063b2ae40d7c661bda0260..049aa375381b5929d1f35b7316c3fe6a647e4f93 100755 (executable)
@@ -61,8 +61,8 @@ test_expect_success 'setup' '
        git tag I
 '
 
-cat > fake-editor.sh <<\EOF
-#!/bin/sh
+echo "#!$SHELL_PATH" >fake-editor.sh
+cat >> fake-editor.sh <<\EOF
 case "$1" in
 */COMMIT_EDITMSG)
        test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
index b1ee622ef7887407c93bc55d95827f9eb7a2b951..f542f0af41989aee51f75b9844abf97943d9e33f 100755 (executable)
@@ -59,15 +59,16 @@ test_expect_success \
      echo "other content" > foo
      git rm --cached foo'
 
-test_expect_failure \
-    'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' \
-    'echo content > foo
+test_expect_success \
+    'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' '
+     echo content > foo
      git add foo
      git commit -m foo
      echo "other content" > foo
      git add foo
      echo "yet another content" > foo
-     git rm --cached foo'
+     ! git rm --cached foo
+'
 
 test_expect_success \
     'Test that git rm --cached -f foo works in case where --cached only did not' \
@@ -106,9 +107,9 @@ embedded'"
 
 if test "$test_failed_remove" = y; then
 chmod a-w .
-test_expect_failure \
+test_expect_success \
     'Test that "git rm -f" fails if its rm fails' \
-    'git rm -f baz'
+    'git rm -f baz'
 chmod 775 .
 else
     test_expect_success 'skipping removal failure (perhaps running as root?)' :
@@ -212,8 +213,8 @@ test_expect_success 'Recursive with -r -f' '
        ! test -d frotz
 '
 
-test_expect_failure 'Remove nonexistent file returns nonzero exit status' '
-       git rm nonexistent
+test_expect_success 'Remove nonexistent file returns nonzero exit status' '
+       git rm nonexistent
 '
 
 test_done
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
new file mode 100755 (executable)
index 0000000..c8dc1ac
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='add -i basic tests'
+. ./test-lib.sh
+
+test_expect_success 'setup (initial)' '
+       echo content >file &&
+       git add file &&
+       echo more >>file &&
+       echo lines >>file
+'
+test_expect_success 'status works (initial)' '
+       git add -i </dev/null >output &&
+       grep "+1/-0 *+2/-0 file" output
+'
+cat >expected <<EOF
+new file mode 100644
+index 0000000..d95f3ad
+--- /dev/null
++++ b/file
+@@ -0,0 +1 @@
++content
+EOF
+test_expect_success 'diff works (initial)' '
+       (echo d; echo 1) | git add -i >output &&
+       sed -ne "/new file/,/content/p" <output >diff &&
+       diff -u expected diff
+'
+test_expect_success 'revert works (initial)' '
+       git add file &&
+       (echo r; echo 1) | git add -i &&
+       git ls-files >output &&
+       ! grep . output
+'
+
+test_expect_success 'setup (commit)' '
+       echo baseline >file &&
+       git add file &&
+       git commit -m commit &&
+       echo content >>file &&
+       git add file &&
+       echo more >>file &&
+       echo lines >>file
+'
+test_expect_success 'status works (commit)' '
+       git add -i </dev/null >output &&
+       grep "+1/-0 *+2/-0 file" output
+'
+cat >expected <<EOF
+index 180b47c..b6f2c08 100644
+--- a/file
++++ b/file
+@@ -1 +1,2 @@
+ baseline
++content
+EOF
+test_expect_success 'diff works (commit)' '
+       (echo d; echo 1) | git add -i >output &&
+       sed -ne "/^index/,/content/p" <output >diff &&
+       diff -u expected diff
+'
+test_expect_success 'revert works (commit)' '
+       git add file &&
+       (echo r; echo 1) | git add -i &&
+       git add -i </dev/null >output &&
+       grep "unchanged *+3/-0 file" output
+'
+
+test_done
index 9a9a250d2c6bad80745b7ebd2df7ac947d30b521..aa282e1bc1e17f08dd12fc1980187c786f1de99b 100755 (executable)
@@ -40,8 +40,8 @@ test_expect_success 'parents of stash' '
 test_expect_success 'apply needs clean working directory' '
        echo 4 > other-file &&
        git add other-file &&
-       echo 5 > other-file
-       ! git stash apply
+       echo 5 > other-file &&
+       test_must_fail git stash apply
 '
 
 test_expect_success 'apply stashed changes' '
@@ -70,7 +70,51 @@ test_expect_success 'unstashing in a subdirectory' '
        git reset --hard HEAD &&
        mkdir subdir &&
        cd subdir &&
-       git stash apply
+       git stash apply &&
+       cd ..
+'
+
+test_expect_success 'drop top stash' '
+       git reset --hard &&
+       git stash list > stashlist1 &&
+       echo 7 > file &&
+       git stash &&
+       git stash drop &&
+       git stash list > stashlist2 &&
+       diff stashlist1 stashlist2 &&
+       git stash apply &&
+       test 3 = $(cat file) &&
+       test 1 = $(git show :file) &&
+       test 1 = $(git show HEAD:file)
+'
+
+test_expect_success 'drop middle stash' '
+       git reset --hard &&
+       echo 8 > file &&
+       git stash &&
+       echo 9 > file &&
+       git stash &&
+       git stash drop stash@{1} &&
+       test 2 = $(git stash list | wc -l) &&
+       git stash apply &&
+       test 9 = $(cat file) &&
+       test 1 = $(git show :file) &&
+       test 1 = $(git show HEAD:file) &&
+       git reset --hard &&
+       git stash drop &&
+       git stash apply &&
+       test 3 = $(cat file) &&
+       test 1 = $(git show :file) &&
+       test 1 = $(git show HEAD:file)
+'
+
+test_expect_success 'stash pop' '
+       git reset --hard &&
+       git stash pop &&
+       test 3 = $(cat file) &&
+       test 1 = $(git show :file) &&
+       test 1 = $(git show HEAD:file) &&
+       test 0 = $(git stash list | wc -l)
 '
 
 test_done
index 9eec754221d85856613b01ec878ef4cb492aceb0..6b4d1c52bbf1e3edf01dd67794bf221effc581a7 100755 (executable)
@@ -245,6 +245,7 @@ format-patch --inline --stdout initial..master
 format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
 config format.subjectprefix DIFFERENT_PREFIX
 format-patch --inline --stdout initial..master^^
+format-patch --stdout --cover-letter -n initial..master^
 
 diff --abbrev initial..side
 diff -r initial..side
diff --git a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
new file mode 100644 (file)
index 0000000..8dab4bf
--- /dev/null
@@ -0,0 +1,100 @@
+$ 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
+Subject: [DIFFERENT_PREFIX 0/2] *** SUBJECT HERE ***
+
+*** BLURB HERE ***
+
+A U Thor (2):
+  Second
+  Third
+
+ dir/sub |    4 ++++
+ file0   |    3 +++
+ file1   |    3 +++
+ file2   |    3 ---
+ 4 files changed, 10 insertions(+), 3 deletions(-)
+ create mode 100644 file1
+ delete mode 100644 file2
+
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [DIFFERENT_PREFIX 1/2] Second
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [DIFFERENT_PREFIX 2/2] Third
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+$
index 0a6fe53375bb26e5c3a69d503f6f13833a2020fa..b2b7a8db859fe7f5d8c74fb27b77e4eba583ebc4 100755 (executable)
@@ -88,4 +88,146 @@ test_expect_success 'replay did not screw up the log message' '
 
 '
 
+test_expect_success 'extra headers' '
+
+       git config format.headers "To: R. E. Cipient <rcipient@example.com>
+" &&
+       git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>
+" &&
+       git format-patch --stdout master..side > patch2 &&
+       sed -e "/^$/q" patch2 > hdrs2 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 &&
+       grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2
+       
+'
+
+test_expect_success 'extra headers without newlines' '
+
+       git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
+       git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" &&
+       git format-patch --stdout master..side >patch3 &&
+       sed -e "/^$/q" patch3 > hdrs3 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 &&
+       grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3
+       
+'
+
+test_expect_success 'extra headers with multiple To:s' '
+
+       git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
+       git config --add format.headers "To: S. E. Cipient <scipient@example.com>" &&
+       git format-patch --stdout master..side > patch4 &&
+       sed -e "/^$/q" patch4 > hdrs4 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 &&
+       grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4
+'
+
+test_expect_success 'additional command line cc' '
+
+       git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
+       git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 &&
+       grep "^ *S. E. Cipient <scipient@example.com>$" patch5
+'
+
+test_expect_success 'multiple files' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch -o patches/ master &&
+       ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch
+'
+
+test_expect_success 'thread' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch --thread -o patches/ master &&
+       FIRST_MID=$(grep "Message-Id:" patches/0001-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
+       for i in patches/0002-* patches/0003-*
+       do
+         grep "References: $FIRST_MID" $i &&
+         grep "In-Reply-To: $FIRST_MID" $i || break
+       done
+'
+
+test_expect_success 'thread in-reply-to' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch --in-reply-to="<test.message>" --thread -o patches/ master &&
+       FIRST_MID="<test.message>" &&
+       for i in patches/*
+       do
+         grep "References: $FIRST_MID" $i &&
+         grep "In-Reply-To: $FIRST_MID" $i || break
+       done
+'
+
+test_expect_success 'thread cover-letter' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch --cover-letter --thread -o patches/ master &&
+       FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
+       for i in patches/0001-* patches/0002-* patches/0003-* 
+       do
+         grep "References: $FIRST_MID" $i &&
+         grep "In-Reply-To: $FIRST_MID" $i || break
+       done
+'
+
+test_expect_success 'thread cover-letter in-reply-to' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch --cover-letter --in-reply-to="<test.message>" --thread -o patches/ master &&
+       FIRST_MID="<test.message>" &&
+       for i in patches/*
+       do
+         grep "References: $FIRST_MID" $i &&
+         grep "In-Reply-To: $FIRST_MID" $i || break
+       done
+'
+
+test_expect_success 'excessive subject' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >>file &&
+       git update-index file &&
+       git commit -m "This is an excessively long subject line for a message due to the habit some projects have of not having a short, one-line subject at the start of the commit message, but rather sticking a whole paragraph right at the start as the only thing in the commit message. It had better not become the filename for the patch." &&
+       git format-patch -o patches/ master..side &&
+       ls patches/0004-This-is-an-excessively-long-subject-line-for-a-messa.patch
+'
+
+test_expect_success 'cover-letter inherits diff options' '
+
+       git mv file foo &&
+       git commit -m foo &&
+       git format-patch --cover-letter -1 &&
+       ! grep "file => foo .* 0 *$" 0000-cover-letter.patch &&
+       git format-patch --cover-letter -1 -M &&
+       grep "file => foo .* 0 *$" 0000-cover-letter.patch
+
+'
+
+cat > expect << EOF
+  This is an excessively long subject line for a message due to the
+    habit some projects have of not having a short, one-line subject at
+    the start of the commit message, but rather sticking a whole
+    paragraph right at the start as the only thing in the commit
+    message. It had better not become the filename for the patch.
+  foo
+
+EOF
+
+test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
+
+       git format-patch --cover-letter -2 &&
+       sed -e "1,/A U Thor/d" -e "/^$/q" < 0000-cover-letter.patch > output &&
+       git diff expect output
+
+'
+
 test_done
index 67e080bdbe5a98a9c446af8aee41acc9fb9129b4..0d9cbb62615c0d94da784f63907989988b0e8151 100755 (executable)
@@ -12,6 +12,7 @@ test_expect_success setup '
        echo "         Eight SP indent" >>F &&
        echo "  HT and SP indent" >>F &&
        echo "With trailing SP " >>F &&
+       echo "Carriage ReturnQ" | tr Q "\015" >>F &&
        echo "No problem" >>F
 
 '
@@ -27,6 +28,7 @@ test_expect_success default '
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
        grep With error >/dev/null &&
+       grep Return error >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -41,6 +43,7 @@ test_expect_success 'without -trail' '
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
        grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -56,6 +59,7 @@ test_expect_success 'without -trail (attribute)' '
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
        grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -71,6 +75,7 @@ test_expect_success 'without -space' '
        grep Eight normal >/dev/null &&
        grep HT normal >/dev/null &&
        grep With error >/dev/null &&
+       grep Return error >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -86,6 +91,7 @@ test_expect_success 'without -space (attribute)' '
        grep Eight normal >/dev/null &&
        grep HT normal >/dev/null &&
        grep With error >/dev/null &&
+       grep Return error >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -101,6 +107,7 @@ test_expect_success 'with indent-non-tab only' '
        grep Eight error >/dev/null &&
        grep HT normal >/dev/null &&
        grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -116,6 +123,39 @@ test_expect_success 'with indent-non-tab only (attribute)' '
        grep Eight error >/dev/null &&
        grep HT normal >/dev/null &&
        grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
+test_expect_success 'with cr-at-eol' '
+
+       rm -f .gitattributes
+       git config core.whitespace cr-at-eol
+       git diff --color >output
+       grep "$blue_grep" output >error
+       grep -v "$blue_grep" output >normal
+
+       grep Eight normal >/dev/null &&
+       grep HT error >/dev/null &&
+       grep With error >/dev/null &&
+       grep Return normal >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
+test_expect_success 'with cr-at-eol (attribute)' '
+
+       git config --unset core.whitespace
+       echo "F whitespace=trailing,cr-at-eol" >.gitattributes
+       git diff --color >output
+       grep "$blue_grep" output >error
+       grep -v "$blue_grep" output >normal
+
+       grep Eight normal >/dev/null &&
+       grep HT error >/dev/null &&
+       grep With error >/dev/null &&
+       grep Return normal >/dev/null &&
        grep No normal >/dev/null
 
 '
diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh
new file mode 100755 (executable)
index 0000000..3d2d081
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+test_description='difference in submodules'
+
+. ./test-lib.sh
+. ../diff-lib.sh
+
+_z40=0000000000000000000000000000000000000000
+test_expect_success setup '
+       test_tick &&
+       test_create_repo sub &&
+       (
+               cd sub &&
+               echo hello >world &&
+               git add world &&
+               git commit -m submodule
+       ) &&
+
+       test_tick &&
+       echo frotz >nitfol &&
+       git add nitfol sub &&
+       git commit -m superproject &&
+
+       (
+               cd sub &&
+               echo goodbye >world &&
+               git add world &&
+               git commit -m "submodule #2"
+       ) &&
+
+       set x $(
+               cd sub &&
+               git rev-list HEAD
+       ) &&
+       echo ":160000 160000 $3 $_z40 M sub" >expect
+'
+
+test_expect_success 'git diff --raw HEAD' '
+       git diff --raw --abbrev=40 HEAD >actual &&
+       diff -u expect actual
+'
+
+test_expect_success 'git diff-index --raw HEAD' '
+       git diff-index --raw HEAD >actual.index &&
+       diff -u expect actual.index
+'
+
+test_expect_success 'git diff-files --raw' '
+       git diff-files --raw >actual.files &&
+       diff -u expect actual.files
+'
+
+test_done
index 74f06ec730c0bdf34b6197b6cd6e6b774055aab8..7c25634fc2962fc7b71d7d34ea6ac8d6c8061559 100755 (executable)
@@ -46,21 +46,25 @@ test_expect_success 'stat binary diff (copy) -- should not fail.' \
        'git-checkout master
         git apply --stat --summary C.diff'
 
-test_expect_failure 'check binary diff -- should fail.' \
-       'git-checkout master
-        git apply --check B.diff'
-
-test_expect_failure 'check binary diff (copy) -- should fail.' \
-       'git-checkout master
-        git apply --check C.diff'
-
-test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \
-       'git-checkout master
-        git apply --check --allow-binary-replacement B.diff'
+test_expect_success 'check binary diff -- should fail.' \
+       'git-checkout master &&
+        ! git apply --check B.diff'
+
+test_expect_success 'check binary diff (copy) -- should fail.' \
+       'git-checkout master &&
+        ! git apply --check C.diff'
+
+test_expect_success \
+       'check incomplete binary diff with replacement -- should fail.' '
+       git-checkout master &&
+       ! git apply --check --allow-binary-replacement B.diff
+'
 
-test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \
-       'git-checkout master
-        git apply --check --allow-binary-replacement C.diff'
+test_expect_success \
+    'check incomplete binary diff with replacement (copy) -- should fail.' '
+        git-checkout master &&
+        ! git apply --check --allow-binary-replacement C.diff
+'
 
 test_expect_success 'check binary diff with replacement.' \
        'git-checkout master
@@ -73,42 +77,42 @@ test_expect_success 'check binary diff with replacement (copy).' \
 # Now we start applying them.
 
 do_reset () {
-       rm -f file?
-       git-reset --hard
+       rm -f file? &&
+       git-reset --hard &&
        git-checkout -f master
 }
 
-test_expect_failure 'apply binary diff -- should fail.' \
-       'do_reset
-        git apply B.diff'
+test_expect_success 'apply binary diff -- should fail.' \
+       'do_reset &&
+        git apply B.diff'
 
-test_expect_failure 'apply binary diff -- should fail.' \
-       'do_reset
-        git apply --index B.diff'
+test_expect_success 'apply binary diff -- should fail.' \
+       'do_reset &&
+        git apply --index B.diff'
 
-test_expect_failure 'apply binary diff (copy) -- should fail.' \
-       'do_reset
-        git apply C.diff'
+test_expect_success 'apply binary diff (copy) -- should fail.' \
+       'do_reset &&
+        git apply C.diff'
 
-test_expect_failure 'apply binary diff (copy) -- should fail.' \
-       'do_reset
-        git apply --index C.diff'
+test_expect_success 'apply binary diff (copy) -- should fail.' \
+       'do_reset &&
+        git apply --index C.diff'
 
 test_expect_success 'apply binary diff without replacement.' \
-       'do_reset
+       'do_reset &&
         git apply BF.diff'
 
 test_expect_success 'apply binary diff without replacement (copy).' \
-       'do_reset
+       'do_reset &&
         git apply CF.diff'
 
 test_expect_success 'apply binary diff.' \
-       'do_reset
+       'do_reset &&
         git apply --allow-binary-replacement --index BF.diff &&
         test -z "$(git diff --name-status binary)"'
 
 test_expect_success 'apply binary diff (copy).' \
-       'do_reset
+       'do_reset &&
         git apply --allow-binary-replacement --index CF.diff &&
         test -z "$(git diff --name-status binary)"'
 
diff --git a/t/t4105-apply-fuzz.sh b/t/t4105-apply-fuzz.sh
new file mode 100755 (executable)
index 0000000..0e8d25f
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+test_description='apply with fuzz and offset'
+
+. ./test-lib.sh
+
+dotest () {
+       name="$1" && shift &&
+       test_expect_success "$name" "
+               git checkout-index -f -q -u file &&
+               git apply $* &&
+               diff -u expect file
+       "
+}
+
+test_expect_success setup '
+
+       for i in 1 2 3 4 5 6 7 8 9 10 11 12
+       do
+               echo $i
+       done >file &&
+       git update-index --add file &&
+       for i in 1 2 3 4 5 6 7 a b c d e 8 9 10 11 12
+       do
+               echo $i
+       done >file &&
+       cat file >expect &&
+       git diff >O0.diff &&
+
+       sed -e "s/@@ -5,6 +5,11 @@/@@ -2,6 +2,11 @@/" >O1.diff O0.diff &&
+       sed -e "s/@@ -5,6 +5,11 @@/@@ -7,6 +7,11 @@/" >O2.diff O0.diff &&
+       sed -e "s/@@ -5,6 +5,11 @@/@@ -19,6 +19,11 @@/" >O3.diff O0.diff &&
+
+       sed -e "s/^ 5/ S/" >F0.diff O0.diff &&
+       sed -e "s/^ 5/ S/" >F1.diff O1.diff &&
+       sed -e "s/^ 5/ S/" >F2.diff O2.diff &&
+       sed -e "s/^ 5/ S/" >F3.diff O3.diff
+
+'
+
+dotest 'unmodified patch' O0.diff
+
+dotest 'minus offset' O1.diff
+
+dotest 'plus offset' O2.diff
+
+dotest 'big offset' O3.diff
+
+dotest 'fuzz with no offset' -C2 F0.diff
+
+dotest 'fuzz with minus offset' -C2 F1.diff
+
+dotest 'fuzz with plus offset' -C2 F2.diff
+
+dotest 'fuzz with big offset' -C2 F3.diff
+
+test_done
index 1c6bec044a00faf24e275280e0b9fa667356f2b3..d74103988201b0c189e7a2564bfb0894e434c056 100755 (executable)
@@ -29,8 +29,8 @@ test_expect_success setup \
 
 # test
 
-test_expect_failure 'apply at the end' \
-    'git apply --index test-patch'
+test_expect_success 'apply at the end' \
+    'git apply --index test-patch'
 
 cat >test-patch <<\EOF
 diff a/file b/file
@@ -47,7 +47,7 @@ b
 c'
 git update-index file
 
-test_expect_failure 'apply at the beginning' \
-       'git apply --index test-patch'
+test_expect_success 'apply at the beginning' \
+       'git apply --index test-patch'
 
 test_done
diff --git a/t/t4125-apply-ws-fuzz.sh b/t/t4125-apply-ws-fuzz.sh
new file mode 100755 (executable)
index 0000000..d6f15be
--- /dev/null
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+test_description='applying patch that has broken whitespaces in context'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       >file &&
+       git add file &&
+
+       # file-0 is full of whitespace breakages
+       for l in a bb c d eeee f ggg h
+       do
+               echo "$l "
+       done >file-0 &&
+
+       # patch-0 creates a whitespace broken file
+       cat file-0 >file &&
+       git diff >patch-0 &&
+       git add file &&
+
+       # file-1 is still full of whitespace breakages,
+       # but has one line updated, without fixing any
+       # whitespaces.
+       # patch-1 records that change.
+       sed -e "s/d/D/" file-0 >file-1 &&
+       cat file-1 >file &&
+       git diff >patch-1 &&
+
+       # patch-all is the effect of both patch-0 and patch-1
+       >file &&
+       git add file &&
+       cat file-1 >file &&
+       git diff >patch-all &&
+
+       # patch-2 is the same as patch-1 but is based
+       # on a version that already has whitespace fixed,
+       # and does not introduce whitespace breakages.
+       sed -e "s/ $//" patch-1 >patch-2 &&
+
+       # If all whitespace breakages are fixed the contents
+       # should look like file-fixed
+       sed -e "s/ $//" file-1 >file-fixed
+
+'
+
+test_expect_success nofix '
+
+       >file &&
+       git add file &&
+
+       # Baseline.  Applying without fixing any whitespace
+       # breakages.
+       git apply --whitespace=nowarn patch-0 &&
+       git apply --whitespace=nowarn patch-1 &&
+
+       # The result should obviously match.
+       diff -u file-1 file
+'
+
+test_expect_success 'withfix (forward)' '
+
+       >file &&
+       git add file &&
+
+       # The first application will munge the context lines
+       # the second patch depends on.  We should be able to
+       # adjust and still apply.
+       git apply --whitespace=fix patch-0 &&
+       git apply --whitespace=fix patch-1 &&
+
+       diff -u file-fixed file
+'
+
+test_expect_success 'withfix (backward)' '
+
+       >file &&
+       git add file &&
+
+       # Now we have a whitespace breakages on our side.
+       git apply --whitespace=nowarn patch-0 &&
+
+       # And somebody sends in a patch based on image
+       # with whitespace already fixed.
+       git apply --whitespace=fix patch-2 &&
+
+       # The result should accept the whitespace fixed
+       # postimage.  But the line with "h" is beyond context
+       # horizon and left unfixed.
+
+       sed -e /h/d file-fixed >fixed-head &&
+       sed -e /h/d file >file-head &&
+       diff -u fixed-head file-head &&
+
+       sed -n -e /h/p file-fixed >fixed-tail &&
+       sed -n -e /h/p file >file-tail &&
+
+       ! diff -u fixed-tail file-tail
+
+'
+
+test_done
diff --git a/t/t4150-am-subdir.sh b/t/t4150-am-subdir.sh
new file mode 100755 (executable)
index 0000000..929d2cb
--- /dev/null
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+test_description='git am running from a subdirectory'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo hello >world &&
+       git add world &&
+       test_tick &&
+       git commit -m initial &&
+       git tag initial &&
+       echo goodbye >world &&
+       git add world &&
+       test_tick &&
+       git commit -m second &&
+       git format-patch --stdout HEAD^ >patchfile &&
+       : >expect
+'
+
+test_expect_success 'am regularly from stdin' '
+       git checkout initial &&
+       git am <patchfile &&
+       git diff master >actual &&
+       diff -u expect actual
+'
+
+test_expect_success 'am regularly from file' '
+       git checkout initial &&
+       git am patchfile &&
+       git diff master >actual &&
+       diff -u expect actual
+'
+
+test_expect_success 'am regularly from stdin in subdirectory' '
+       rm -fr subdir &&
+       git checkout initial &&
+       (
+               mkdir -p subdir &&
+               cd subdir &&
+               git am <../patchfile
+       ) &&
+       git diff master>actual &&
+       diff -u expect actual
+'
+
+test_expect_success 'am regularly from file in subdirectory' '
+       rm -fr subdir &&
+       git checkout initial &&
+       (
+               mkdir -p subdir &&
+               cd subdir &&
+               git am ../patchfile
+       ) &&
+       git diff master >actual &&
+       diff -u expect actual
+'
+
+test_expect_success 'am regularly from file in subdirectory with full path' '
+       rm -fr subdir &&
+       git checkout initial &&
+       P=$(pwd) &&
+       (
+               mkdir -p subdir &&
+               cd subdir &&
+               git am "$P/patchfile"
+       ) &&
+       git diff master >actual &&
+       diff -u expect actual
+'
+
+test_done
index 6e594bf1e211e246cdc02ab22d7be4a9da91d5d2..cd3c149800395553cc973317ef41e89e53771f60 100755 (executable)
@@ -264,8 +264,14 @@ test_expect_success \
      cp -f     .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \
                .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'
 
-test_expect_failure \
+test_expect_success \
     'make sure index-pack detects the SHA1 collision' \
-    'git-index-pack -o bad.idx test-3.pack'
+    '! git-index-pack -o bad.idx test-3.pack'
+
+test_expect_success \
+    'honor pack.packSizeLimit' \
+    'git config pack.packSizeLimit 200 &&
+     packname_4=$(git pack-objects test-4 <obj-list) &&
+     test 3 = $(ls test-4-*.pack | wc -l)'
 
 test_done
index 2a2878b57229016ad473ccfd65ff7f609ba7d966..67b9a7b84a6a411d44b6b5f90f7b33d6160b5702 100755 (executable)
@@ -42,9 +42,9 @@ test_expect_success \
     'both packs should be identical' \
     'cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"'
 
-test_expect_failure \
+test_expect_success \
     'index v1 and index v2 should be different' \
-    'cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"'
+    'cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"'
 
 test_expect_success \
     'index-pack with index version 1' \
@@ -78,9 +78,9 @@ test_expect_success \
     'git verify-pack -v "test-3-${pack3}.pack"'
 
 test "$have_64bits" &&
-test_expect_failure \
+test_expect_success \
     '64-bit offsets: should be different from previous index v2 results' \
-    'cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"'
+    'cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"'
 
 test "$have_64bits" &&
 test_expect_success \
@@ -112,22 +112,22 @@ test_expect_success \
          bs=1 count=20 conv=notrunc &&
        git cat-file blob "$delta_sha1" > blob_2 )'
 
-test_expect_failure \
+test_expect_success \
     '[index v1] 3) corrupted delta happily returned wrong data' \
-    'cmp blob_1 blob_2'
+    'cmp blob_1 blob_2'
 
-test_expect_failure \
+test_expect_success \
     '[index v1] 4) confirm that the pack is actually corrupted' \
-    'git fsck --full $commit'
+    'git fsck --full $commit'
 
 test_expect_success \
     '[index v1] 5) pack-objects happily reuses corrupted data' \
     'pack4=$(git pack-objects test-4 <obj-list) &&
      test -f "test-4-${pack1}.pack"'
 
-test_expect_failure \
+test_expect_success \
     '[index v1] 6) newly created pack is BAD !' \
-    'git verify-pack -v "test-4-${pack1}.pack"'
+    'git verify-pack -v "test-4-${pack1}.pack"'
 
 test_expect_success \
     '[index v2] 1) stream pack to repository' \
@@ -150,16 +150,16 @@ test_expect_success \
          bs=1 count=20 conv=notrunc &&
        git cat-file blob "$delta_sha1" > blob_4 )'
 
-test_expect_failure \
+test_expect_success \
     '[index v2] 3) corrupted delta happily returned wrong data' \
-    'cmp blob_3 blob_4'
+    'cmp blob_3 blob_4'
 
-test_expect_failure \
+test_expect_success \
     '[index v2] 4) confirm that the pack is actually corrupted' \
-    'git fsck --full $commit'
+    'git fsck --full $commit'
 
-test_expect_failure \
+test_expect_success \
     '[index v2] 5) pack-objects refuses to reuse corrupted data' \
-    'git pack-objects test-5 <obj-list'
+    'git pack-objects test-5 <obj-list'
 
 test_done
diff --git a/t/t5303-hash-object.sh b/t/t5303-hash-object.sh
new file mode 100755 (executable)
index 0000000..543c078
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description=git-hash-object
+
+. ./test-lib.sh
+
+test_expect_success \
+    'git hash-object -w --stdin saves the object' \
+    'obname=$(echo foo | git hash-object -w --stdin) &&
+    obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
+    test -r .git/objects/"$obpath" &&
+    rm -f .git/objects/"$obpath"'
+    
+test_expect_success \
+    'git hash-object --stdin -w saves the object' \
+    'obname=$(echo foo | git hash-object --stdin -w) &&
+    obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
+    test -r .git/objects/"$obpath" &&
+    rm -f .git/objects/"$obpath"'    
+
+test_expect_success \
+    'git hash-object --stdin file1 <file0 first operates on file0, then file1' \
+    'echo foo > file1 &&
+    obname0=$(echo bar | git hash-object --stdin) &&
+    obname1=$(git hash-object file1) &&
+    obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
+    obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
+    test "$obname0" = "$obname0new" &&
+    test "$obname1" = "$obname1new"'
+
+test_expect_success \
+    'git hash-object refuses multiple --stdin arguments' \
+    '! git hash-object --stdin --stdin < file1'
+
+test_done
diff --git a/t/t5305-include-tag.sh b/t/t5305-include-tag.sh
new file mode 100755 (executable)
index 0000000..0db2754
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+test_description='git-pack-object --include-tag'
+. ./test-lib.sh
+
+TRASH=`pwd`
+
+test_expect_success setup '
+       echo c >d &&
+       git update-index --add d &&
+       tree=`git write-tree` &&
+       commit=`git commit-tree $tree </dev/null` &&
+       echo "object $commit" >sig &&
+       echo "type commit" >>sig &&
+       echo "tag mytag" >>sig &&
+       echo "tagger $(git var GIT_COMMITTER_IDENT)" >>sig &&
+       echo >>sig &&
+       echo "our test tag" >>sig &&
+       tag=`git mktag <sig` &&
+       rm d sig &&
+       git update-ref refs/tags/mytag $tag && {
+               echo $tree &&
+               echo $commit &&
+               git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)        .*/\\1/"
+       } >obj-list
+'
+
+rm -rf clone.git
+test_expect_success 'pack without --include-tag' '
+       packname_1=$(git pack-objects \
+               --window=0 \
+               test-1 <obj-list)
+'
+
+test_expect_success 'unpack objects' '
+       (
+               GIT_DIR=clone.git &&
+               export GIT_DIR &&
+               git init &&
+               git unpack-objects -n <test-1-${packname_1}.pack &&
+               git unpack-objects <test-1-${packname_1}.pack
+       )
+'
+
+test_expect_success 'check unpacked result (have commit, no tag)' '
+       git rev-list --objects $commit >list.expect &&
+       (
+               GIT_DIR=clone.git &&
+               export GIT_DIR &&
+               test_must_fail git cat-file -e $tag &&
+               git rev-list --objects $commit
+       ) >list.actual &&
+       git diff list.expect list.actual
+'
+
+rm -rf clone.git
+test_expect_success 'pack with --include-tag' '
+       packname_1=$(git pack-objects \
+               --window=0 \
+               --include-tag \
+               test-2 <obj-list)
+'
+
+test_expect_success 'unpack objects' '
+       (
+               GIT_DIR=clone.git &&
+               export GIT_DIR &&
+               git init &&
+               git unpack-objects -n <test-2-${packname_1}.pack &&
+               git unpack-objects <test-2-${packname_1}.pack
+       )
+'
+
+test_expect_success 'check unpacked result (have commit, have tag)' '
+       git rev-list --objects mytag >list.expect &&
+       (
+               GIT_DIR=clone.git &&
+               export GIT_DIR &&
+               git rev-list --objects $tag
+       ) >list.actual &&
+       git diff list.expect list.actual
+'
+
+test_done
index 9734fc542f821c3659321be4c9ffa76ffd88a888..9a12024241aea21b6ebdbe692b2d7c173d4d2579 100755 (executable)
@@ -60,8 +60,8 @@ echo STDERR post-update >&2
 EOF
 chmod u+x victim/.git/hooks/post-update
 
-test_expect_failure push '
-       git-send-pack --force ./victim/.git master tofail >send.out 2>send.err
+test_expect_success push '
+    ! git-send-pack --force ./victim/.git master tofail >send.out 2>send.err
 '
 
 test_expect_success 'updated as expected' '
@@ -112,8 +112,8 @@ test_expect_success 'all *-receive hook args are empty' '
        ! test -s victim/.git/post-receive.args
 '
 
-test_expect_failure 'send-pack produced no output' '
-       test -s send.out
+test_expect_success 'send-pack produced no output' '
+       test -s send.out
 '
 
 cat <<EOF >expect
index 1c4b0b32ab90b2af0b08521045d4bfb1d5609b4d..1394047a8dc3e87476e223db42936d59845f803b 100755 (executable)
@@ -30,9 +30,9 @@ EOF
     chmod u+x clone${clone}/.git/hooks/post-merge
 done
 
-test_expect_failure 'post-merge does not run for up-to-date ' '
+test_expect_success 'post-merge does not run for up-to-date ' '
         GIT_DIR=clone1/.git git merge $commit0 &&
-       test -e clone1/.git/post-merge.args
+       ! test -f clone1/.git/post-merge.args
 '
 
 test_expect_success 'post-merge runs as expected ' '
index 7b6798d8b50f878c8957a60c058f6ad307f72789..788b4a5aae17d33ad688446bb60973952c3ca918 100755 (executable)
@@ -176,7 +176,7 @@ test_expect_success "deepening fetch in shallow repo" \
 test_expect_success "clone shallow object count" \
        "test \"count: 18\" = \"$(grep count count.shallow)\""
 
-test_expect_failure "pull in shallow repo with missing merge base" \
-       "(cd shallow; git pull --depth 4 .. A)"
+test_expect_success "pull in shallow repo with missing merge base" \
+       "(cd shallow && ! git pull --depth 4 .. A)"
 
 test_done
diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh
new file mode 100755 (executable)
index 0000000..86e5b9b
--- /dev/null
@@ -0,0 +1,150 @@
+#!/bin/sh
+
+test_description='test automatic tag following'
+
+. ./test-lib.sh
+
+# End state of the repository:
+#
+#         T - tag1          S - tag2
+#        /                 /
+#   L - A ------ O ------ B
+#    \   \                 \
+#     \   C - origin/cat    \
+#      origin/master         master
+
+test_expect_success setup '
+       test_tick &&
+       echo ichi >file &&
+       git add file &&
+       git commit -m L &&
+       L=$(git rev-parse --verify HEAD) &&
+
+       (
+               mkdir cloned &&
+               cd cloned &&
+               git init-db &&
+               git remote add -f origin ..
+       ) &&
+
+       test_tick &&
+       echo A >file &&
+       git add file &&
+       git commit -m A &&
+       A=$(git rev-parse --verify HEAD)
+'
+
+U=UPLOAD_LOG
+
+cat - <<EOF >expect
+#S
+want $A
+#E
+EOF
+test_expect_success 'fetch A (new commit : 1 connection)' '
+       rm -f $U
+       (
+               cd cloned &&
+               GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+               test $A = $(git rev-parse --verify origin/master)
+       ) &&
+       test -s $U &&
+       cut -d" " -f1,2 $U >actual &&
+       git diff expect actual
+'
+
+test_expect_success "create tag T on A, create C on branch cat" '
+       git tag -a -m tag1 tag1 $A &&
+       T=$(git rev-parse --verify tag1) &&
+
+       git checkout -b cat &&
+       echo C >file &&
+       git add file &&
+       git commit -m C &&
+       C=$(git rev-parse --verify HEAD) &&
+       git checkout master
+'
+
+cat - <<EOF >expect
+#S
+want $C
+want $T
+#E
+EOF
+test_expect_success 'fetch C, T (new branch, tag : 1 connection)' '
+       rm -f $U
+       (
+               cd cloned &&
+               GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+               test $C = $(git rev-parse --verify origin/cat) &&
+               test $T = $(git rev-parse --verify tag1) &&
+               test $A = $(git rev-parse --verify tag1^0)
+       ) &&
+       test -s $U &&
+       cut -d" " -f1,2 $U >actual &&
+       git diff expect actual
+'
+
+test_expect_success "create commits O, B, tag S on B" '
+       test_tick &&
+       echo O >file &&
+       git add file &&
+       git commit -m O &&
+
+       test_tick &&
+       echo B >file &&
+       git add file &&
+       git commit -m B &&
+       B=$(git rev-parse --verify HEAD) &&
+
+       git tag -a -m tag2 tag2 $B &&
+       S=$(git rev-parse --verify tag2)
+'
+
+cat - <<EOF >expect
+#S
+want $B
+want $S
+#E
+EOF
+test_expect_success 'fetch B, S (commit and tag : 1 connection)' '
+       rm -f $U
+       (
+               cd cloned &&
+               GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+               test $B = $(git rev-parse --verify origin/master) &&
+               test $B = $(git rev-parse --verify tag2^0) &&
+               test $S = $(git rev-parse --verify tag2)
+       ) &&
+       test -s $U &&
+       cut -d" " -f1,2 $U >actual &&
+       git diff expect actual
+'
+
+cat - <<EOF >expect
+#S
+want $B
+want $S
+#E
+EOF
+test_expect_success 'new clone fetch master and tags' '
+       git branch -D cat
+       rm -f $U
+       (
+               mkdir clone2 &&
+               cd clone2 &&
+               git init &&
+               git remote add origin .. &&
+               GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+               test $B = $(git rev-parse --verify origin/master) &&
+               test $S = $(git rev-parse --verify tag2) &&
+               test $B = $(git rev-parse --verify tag2^0) &&
+               test $T = $(git rev-parse --verify tag1) &&
+               test $A = $(git rev-parse --verify tag1^0)
+       ) &&
+       test -s $U &&
+       cut -d" " -f1,2 $U >actual &&
+       git diff expect actual
+'
+
+test_done
index 02882c1e4bdcef725b3575ccee6fb298f01c21b4..9b948c14e6f92e8154373154b32a6d450ead47af 100755 (executable)
@@ -95,7 +95,7 @@ test_expect_success 'fetch following tags' '
 
 '
 
-test_expect_failure 'fetch must not resolve short tag name' '
+test_expect_success 'fetch must not resolve short tag name' '
 
        cd "$D" &&
 
@@ -103,11 +103,11 @@ test_expect_failure 'fetch must not resolve short tag name' '
        cd five &&
        git init &&
 
-       git fetch .. anno:five
+       git fetch .. anno:five
 
 '
 
-test_expect_failure 'fetch must not resolve short remote name' '
+test_expect_success 'fetch must not resolve short remote name' '
 
        cd "$D" &&
        git-update-ref refs/remotes/six/HEAD HEAD
@@ -116,7 +116,7 @@ test_expect_failure 'fetch must not resolve short remote name' '
        cd six &&
        git init &&
 
-       git fetch .. six:six
+       git fetch .. six:six
 
 '
 
@@ -139,10 +139,10 @@ test_expect_success 'create bundle 2' '
        git bundle create bundle2 master~2..master
 '
 
-test_expect_failure 'unbundle 1' '
+test_expect_success 'unbundle 1' '
        cd "$D/bundle" &&
        git checkout -b some-branch &&
-       git fetch "$D/bundle1" master:master
+       git fetch "$D/bundle1" master:master
 '
 
 test_expect_success 'bundle 1 has only 3 files ' '
index 9d2dc33cbd0d1df19b0a9003e545104a982da694..793ffc6600202431193887a12981105c099d40df 100755 (executable)
@@ -100,6 +100,23 @@ test_expect_success 'fetch with wildcard' '
        )
 '
 
+test_expect_success 'fetch with insteadOf' '
+       mk_empty &&
+       (
+               TRASH=$(pwd) &&
+               cd testrepo &&
+               git config url./$TRASH/.insteadOf trash/
+               git config remote.up.url trash/. &&
+               git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
+               git fetch up &&
+
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
 test_expect_success 'push without wildcard' '
        mk_empty &&
 
@@ -126,6 +143,20 @@ test_expect_success 'push with wildcard' '
        )
 '
 
+test_expect_success 'push with insteadOf' '
+       mk_empty &&
+       TRASH=$(pwd) &&
+       git config url./$TRASH/.insteadOf trash/ &&
+       git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
+       (
+               cd testrepo &&
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
 test_expect_success 'push with matching heads' '
 
        mk_test heads/master &&
@@ -271,6 +302,49 @@ test_expect_success 'push with HEAD nonexisting at remote' '
        check_push_result $the_commit heads/local
 '
 
+test_expect_success 'push with +HEAD' '
+
+       mk_test heads/master &&
+       git checkout master &&
+       git branch -D local &&
+       git checkout -b local &&
+       git push testrepo master local &&
+       check_push_result $the_commit heads/master &&
+       check_push_result $the_commit heads/local &&
+
+       # Without force rewinding should fail
+       git reset --hard HEAD^ &&
+       ! git push testrepo HEAD &&
+       check_push_result $the_commit heads/local &&
+
+       # With force rewinding should succeed
+       git push testrepo +HEAD &&
+       check_push_result $the_first_commit heads/local
+
+'
+
+test_expect_success 'push with config remote.*.push = HEAD' '
+
+       mk_test heads/local &&
+       git checkout master &&
+       git branch -f local $the_commit &&
+       (
+               cd testrepo &&
+               git checkout local &&
+               git reset --hard $the_first_commit
+       ) &&
+       git config remote.there.url testrepo &&
+       git config remote.there.push HEAD &&
+       git config branch.master.remote there &&
+       git push &&
+       check_push_result $the_commit heads/master &&
+       check_push_result $the_first_commit heads/local
+'
+
+# clean up the cruft left with the previous one
+git config --remove-section remote.there
+git config --remove-section branch.master
+
 test_expect_success 'push with dry-run' '
 
        mk_test heads/master &&
index cc8949e3eff7b8d7802c5cdea5eddc7c2f1f9a53..8b0509106951c5ddf2995862bc2a3887c963bfe6 100755 (executable)
@@ -26,9 +26,8 @@ test_expect_success 'setup and corrupt repository' '
 
 '
 
-test_expect_failure 'fsck fails' '
-
-       git fsck
+test_expect_success 'fsck fails' '
+       ! git fsck
 '
 
 test_expect_success 'upload-pack fails due to error in pack-objects' '
@@ -46,9 +45,8 @@ test_expect_success 'corrupt repo differently' '
 
 '
 
-test_expect_failure 'fsck fails' '
-
-       git fsck
+test_expect_success 'fsck fails' '
+       ! git fsck
 '
 test_expect_success 'upload-pack fails due to error in rev-list' '
 
@@ -66,9 +64,9 @@ test_expect_success 'create empty repository' '
 
 '
 
-test_expect_failure 'fetch fails' '
+test_expect_success 'fetch fails' '
 
-       git fetch .. master
+       git fetch .. master
 
 '
 
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
new file mode 100755 (executable)
index 0000000..7372439
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
+#
+
+test_description='test http-push
+
+This test runs various sanity checks on http-push.'
+
+. ./test-lib.sh
+
+ROOT_PATH="$PWD"
+LIB_HTTPD_DAV=t
+
+. ../lib-httpd.sh
+
+if ! start_httpd >&3 2>&4
+then
+       say "skipping test, web server setup failed"
+       test_done
+       exit
+fi
+
+test_expect_success 'setup remote repository' '
+       cd "$ROOT_PATH" &&
+       mkdir test_repo &&
+       cd test_repo &&
+       git init &&
+       : >path1 &&
+       git add path1 &&
+       test_tick &&
+       git commit -m initial &&
+       cd - &&
+       git clone --bare test_repo test_repo.git &&
+       cd test_repo.git &&
+       git --bare update-server-info &&
+       chmod +x hooks/post-update &&
+       cd - &&
+       mv test_repo.git $HTTPD_DOCUMENT_ROOT_PATH
+'
+       
+test_expect_success 'clone remote repository' '
+       cd "$ROOT_PATH" &&
+       git clone $HTTPD_URL/test_repo.git test_repo_clone
+'
+
+test_expect_success 'push to remote repository' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       : >path2 &&
+       git add path2 &&
+       test_tick &&
+       git commit -m path2 &&
+       git push
+'
+
+test_expect_success 'create and delete remote branch' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       git checkout -b dev &&
+       : >path3 &&
+       git add path3 &&
+       test_tick &&
+       git commit -m dev &&
+       git push origin dev &&
+       git fetch &&
+       git push origin :dev &&
+       git branch -d -r origin/dev &&
+       git fetch &&
+       ! git show-ref --verify refs/remotes/origin/dev
+'
+
+stop_httpd
+
+test_done
index 1776b377f3c787977b145980f05aa74da5038657..acf34cec8f0ce5930f48a6e31ef84b8843097d74 100755 (executable)
@@ -11,13 +11,13 @@ remove the directory before attempting a clone again.'
 
 . ./test-lib.sh
 
-test_expect_failure \
+test_expect_success \
     'clone of non-existent source should fail' \
-    'git-clone foo bar'
+    'git-clone foo bar'
 
-test_expect_failure \
+test_expect_success \
     'failed clone should not leave a directory' \
-    'cd bar'
+    '! test -d bar'
 
 # Need a repo to clone
 test_create_repo foo
@@ -27,9 +27,9 @@ test_create_repo foo
 
 # source repository given to git-clone should be relative to the
 # current path not to the target dir
-test_expect_failure \
+test_expect_success \
     'clone of non-existent (relative to $PWD) source should fail' \
-    'git-clone ../foo baz'
+    'git-clone ../foo baz'
 
 test_expect_success \
     'clone should work now that source exists' \
index 59a165a6d44190a1ce9ef9575dc6c8a2ce9f4efb..8dfaaa456e115e85e36c438bb998d8053534104e 100755 (executable)
@@ -11,6 +11,11 @@ test_expect_success 'preparing origin repository' '
        git clone --bare . x &&
        test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true &&
        test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true
+       git bundle create b1.bundle --all HEAD &&
+       git bundle create b2.bundle --all &&
+       mkdir dir &&
+       cp b1.bundle dir/b3
+       cp b1.bundle b4
 '
 
 test_expect_success 'local clone without .git suffix' '
@@ -71,4 +76,44 @@ test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
        git fetch &&
        test ! -e .git/refs/remotes/origin/HEAD'
 
+test_expect_success 'bundle clone without .bundle suffix' '
+       cd "$D" &&
+       git clone dir/b3 &&
+       cd b3 &&
+       git fetch
+'
+
+test_expect_success 'bundle clone with .bundle suffix' '
+       cd "$D" &&
+       git clone b1.bundle &&
+       cd b1 &&
+       git fetch
+'
+
+test_expect_success 'bundle clone from b4' '
+       cd "$D" &&
+       git clone b4 bdl &&
+       cd bdl &&
+       git fetch
+'
+
+test_expect_success 'bundle clone from b4.bundle that does not exist' '
+       cd "$D" &&
+       if git clone b4.bundle bb
+       then
+               echo "Oops, should have failed"
+               false
+       else
+               echo happy
+       fi
+'
+
+test_expect_success 'bundle clone with nonexistent HEAD' '
+       cd "$D" &&
+       git clone b2.bundle b2 &&
+       cd b2 &&
+       git fetch
+       test ! -e .git/refs/heads/master
+'
+
 test_done
index 1908dc8b06aa5a5d217d70c26f64c3700d593be0..910ccb4fff561360ba7060ab8df71c0349c4207a 100755 (executable)
@@ -87,10 +87,10 @@ test_valid_repo"
 
 cd "$base_dir"
 
-test_expect_failure 'that info/alternates is necessary' \
+test_expect_success 'that info/alternates is necessary' \
 'cd C &&
-rm .git/objects/info/alternates &&
-test_valid_repo'
+rm -f .git/objects/info/alternates &&
+! (test_valid_repo)'
 
 cd "$base_dir"
 
@@ -101,9 +101,11 @@ test_valid_repo'
 
 cd "$base_dir"
 
-test_expect_failure 'that relative alternate is only possible for current dir' \
-'cd D &&
-test_valid_repo'
+test_expect_success \
+    'that relative alternate is only possible for current dir' '
+    cd D &&
+    ! (test_valid_repo)
+'
 
 cd "$base_dir"
 
diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh
new file mode 100755 (executable)
index 0000000..be3d238
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='properly cull all ancestors'
+
+. ./test-lib.sh
+
+commit () {
+       test_tick &&
+       echo $1 >file &&
+       git commit -a -m $1 &&
+       git tag $1
+}
+
+test_expect_success setup '
+
+       touch file &&
+       git add file &&
+
+       commit one &&
+
+       test_tick=$(($test_tick - 2400))
+
+       commit two &&
+       commit three &&
+       commit four &&
+
+       git log --pretty=oneline --abbrev-commit
+'
+
+test_expect_failure 'one is ancestor of others and should not be shown' '
+
+       git rev-list one --not four >result &&
+       >expect &&
+       diff -u expect result
+
+'
+
+test_done
index ae3b6f28315d54349601a5c4e162a25949b626ec..79dc58b2ce962ea133c4796d0dff66eb9684b48a 100755 (executable)
@@ -66,8 +66,8 @@ test_expect_success "merge result added missing LF" \
        "git diff test.txt test2.txt"
 
 cp test.txt backup.txt
-test_expect_failure "merge with conflicts" \
-       "git merge-file test.txt orig.txt new3.txt"
+test_expect_success "merge with conflicts" \
+       "git merge-file test.txt orig.txt new3.txt"
 
 cat > expect.txt << EOF
 <<<<<<< test.txt
@@ -89,8 +89,8 @@ EOF
 test_expect_success "expected conflict markers" "git diff test.txt expect.txt"
 
 cp backup.txt test.txt
-test_expect_failure "merge with conflicts, using -L" \
-       "git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
+test_expect_success "merge with conflicts, using -L" \
+       "git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
 
 cat > expect.txt << EOF
 <<<<<<< 1
@@ -113,8 +113,8 @@ test_expect_success "expected conflict markers, with -L" \
        "git diff test.txt expect.txt"
 
 sed "s/ tu / TU /" < new1.txt > new5.txt
-test_expect_failure "conflict in removed tail" \
-       "git merge-file -p orig.txt new1.txt new5.txt > out"
+test_expect_success "conflict in removed tail" \
+       "git merge-file -p orig.txt new1.txt new5.txt > out"
 
 cat > expect << EOF
 Dominus regit me,
@@ -139,4 +139,24 @@ test_expect_success 'binary files cannot be merged' '
        grep "Cannot merge binary files" merge.err
 '
 
+sed -e "s/deerit.$/deerit;/" -e "s/me;$/me./" < new5.txt > new6.txt
+sed -e "s/deerit.$/deerit,/" -e "s/me;$/me,/" < new5.txt > new7.txt
+
+test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
+
+       ! git merge-file -p new6.txt new5.txt new7.txt > output &&
+       test 1 = $(grep ======= < output | wc -l)
+
+'
+
+sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit;/" < new6.txt > new8.txt
+sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit --/" < new7.txt > new9.txt
+
+test_expect_success 'ZEALOUS_ALNUM' '
+
+       ! git merge-file -p new8.txt new5.txt new9.txt > merge.out &&
+       test 1 = $(grep ======= < merge.out | wc -l)
+
+'
+
 test_done
index c154f03cf5f80198b9b8d19f6b0c04db11c79965..23d24d3feb9d1f3b5875771639a8e6d991d5ed76 100755 (executable)
@@ -60,7 +60,7 @@ git update-index a1 &&
 GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
 '
 
-test_expect_failure "combined merge conflicts" "git merge -m final G"
+test_expect_success "combined merge conflicts" "! git merge -m final G"
 
 cat > expect << EOF
 <<<<<<< HEAD:a1
@@ -81,8 +81,8 @@ EOF
 
 test_expect_success "virtual trees were processed" "git diff expect out"
 
-git reset --hard
 test_expect_success 'refuse to merge binary files' '
+       git reset --hard &&
        printf "\0" > binary-file &&
        git add binary-file &&
        git commit -m binary &&
index 950c2e9b632f59a9405ba2100eb077836223291d..6004deb43228836f61e0c4b8762a2511a2c1780a 100755 (executable)
@@ -30,30 +30,29 @@ echo plain-file > symlink &&
 git add symlink &&
 git-commit -m b-file'
 
-test_expect_failure \
+test_expect_success \
 'merge master into b-symlink, which has a different symbolic link' '
-! git-checkout b-symlink ||
-git-merge master'
+git-checkout b-symlink &&
+git-merge master'
 
 test_expect_success \
 'the merge result must be a file' '
 test -f symlink'
 
-test_expect_failure \
+test_expect_success \
 'merge master into b-file, which has a file instead of a symbolic link' '
-! (git-reset --hard &&
-git-checkout b-file) ||
-git-merge master'
+git-reset --hard && git-checkout b-file &&
+! git-merge master'
 
 test_expect_success \
 'the merge result must be a file' '
 test -f symlink'
 
-test_expect_failure \
+test_expect_success \
 'merge b-file, which has a file instead of a symbolic link, into master' '
-! (git-reset --hard &&
-git-checkout master) ||
-git-merge b-file'
+git-reset --hard &&
+git-checkout master &&
+git-merge b-file'
 
 test_expect_success \
 'the merge result must be a file' '
diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh
new file mode 100755 (executable)
index 0000000..35d66e8
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='subtree merge strategy'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       s="1 2 3 4 5 6 7 8"
+       for i in $s; do echo $i; done >hello &&
+       git add hello &&
+       git commit -m initial &&
+       git checkout -b side &&
+       echo >>hello world &&
+       git add hello &&
+       git commit -m second &&
+       git checkout master &&
+       for i in mundo $s; do echo $i; done >hello &&
+       git add hello &&
+       git commit -m master
+
+'
+
+test_expect_success 'subtree available and works like recursive' '
+
+       git merge -s subtree side &&
+       for i in mundo $s world; do echo $i; done >expect &&
+       diff -u expect hello
+
+'
+
+test_expect_success 'setup' '
+       mkdir git-gui &&
+       cd git-gui &&
+       git init &&
+       echo git-gui > git-gui.sh &&
+       o1=$(git hash-object git-gui.sh) &&
+       git add git-gui.sh &&
+       git commit -m "initial git-gui" &&
+       cd .. &&
+       mkdir git &&
+       cd git &&
+       git init &&
+       echo git >git.c &&
+       o2=$(git hash-object git.c) &&
+       git add git.c &&
+       git commit -m "initial git"
+'
+
+test_expect_success 'initial merge' '
+       git remote add -f gui ../git-gui &&
+       git merge -s ours --no-commit gui/master &&
+       git read-tree --prefix=git-gui/ -u gui/master &&
+       git commit -m "Merge git-gui as our subdirectory" &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      git-gui/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       git diff -u expected actual
+'
+
+test_expect_success 'merge update' '
+       cd ../git-gui &&
+       echo git-gui2 > git-gui.sh &&
+       o3=$(git hash-object git-gui.sh) &&
+       git add git-gui.sh &&
+       git commit -m "update git-gui" &&
+       cd ../git &&
+       git pull -s subtree gui master &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o3 0      git-gui/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       git diff -u expected actual
+'
+
+test_done
index 0724864e562a53e7079c021f8b331c5b8213ac98..2328b699474cbe338def30179b07f25fa7fa357a 100755 (executable)
@@ -26,7 +26,7 @@ test_expect_success 'final^1^1^1 = final^^^' "test $(git rev-parse final^1^1^1)
 test_expect_success 'final^1^2' "test $(git rev-parse start2) = $(git rev-parse final^1^2)"
 test_expect_success 'final^1^2 != final^1^1' "test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)"
 test_expect_success 'final^1^3 not valid' "if git rev-parse --verify final^1^3; then false; else :; fi"
-test_expect_failure '--verify start2^1' 'git rev-parse --verify start2^1'
+test_expect_success '--verify start2^1' '! git rev-parse --verify start2^1'
 test_expect_success '--verify start2^0' 'git rev-parse --verify start2^0'
 
 test_expect_success 'repack for next test' 'git repack -a -d'
index ae8ee11183833fd4610adb1f83763eba381f7850..56bbd8519d69a1fc31293f30eb3c8a7b20a285cc 100755 (executable)
@@ -15,8 +15,11 @@ test_description='test describe
 check_describe () {
        expect="$1"
        shift
-       R=$(git describe "$@") &&
+       R=$(git describe "$@" 2>err.actual)
+       S=$?
+       cat err.actual >&3
        test_expect_success "describe $*" '
+       test $S = 0 &&
        case "$R" in
        $expect)        echo happy ;;
        *)      echo "Oops - $R is not $expect";
@@ -94,4 +97,24 @@ check_describe D-* --tags HEAD^^
 check_describe A-* --tags HEAD^^2
 check_describe B --tags HEAD^^2^
 
+check_describe B-0-* --long HEAD^^2^
+check_describe A-3-* --long HEAD^^2
+
+test_expect_success 'rename tag A to Q locally' '
+       mv .git/refs/tags/A .git/refs/tags/Q
+'
+cat - >err.expect <<EOF
+warning: tag 'A' is really 'Q' here
+EOF
+check_describe A-* HEAD
+test_expect_success 'warning was displayed for Q' '
+       git diff err.expect err.actual
+'
+test_expect_success 'rename tag Q back to A' '
+       mv .git/refs/tags/Q .git/refs/tags/A
+'
+
+test_expect_success 'pack tag refs' 'git pack-refs'
+check_describe A-* HEAD
+
 test_done
index 8a23aaf21b1977fab66aa2989cac56635251ecb6..f46ec93c83d990c0887d3ff9d59f9d365b4d5eed 100755 (executable)
@@ -43,8 +43,8 @@ test_expect_success 'Check atom names are valid' '
        test -z "$bad"
 '
 
-test_expect_failure 'Check invalid atoms names are errors' '
-       git-for-each-ref --format="%(INVALID)" refs/heads
+test_expect_success 'Check invalid atoms names are errors' '
+       git-for-each-ref --format="%(INVALID)" refs/heads
 '
 
 test_expect_success 'Check format specifiers are ignored in naming date atoms' '
@@ -63,8 +63,8 @@ test_expect_success 'Check valid format specifiers for date fields' '
        git-for-each-ref --format="%(authordate:rfc2822)" refs/heads
 '
 
-test_expect_failure 'Check invalid format specifiers are errors' '
-       git-for-each-ref --format="%(authordate:INVALID)" refs/heads
+test_expect_success 'Check invalid format specifiers are errors' '
+       git-for-each-ref --format="%(authordate:INVALID)" refs/heads
 '
 
 cat >expected <<\EOF
index b730c900b18543cd363ceaeb2dc6f6681c61524d..fa382c58da08a6eeca1aa51d4af47215e3e1c107 100755 (executable)
@@ -78,9 +78,9 @@ test_expect_success \
      git diff-tree -r -M --name-status  HEAD^ HEAD | \
      grep "^R100..*path2/README..*path1/path2/README"'
 
-test_expect_failure \
+test_expect_success \
     'do not move directory over existing directory' \
-    'mkdir path0 && mkdir path0/path2 && git mv path2 path0'
+    'mkdir path0 && mkdir path0/path2 && git mv path2 path0'
 
 test_expect_success \
     'move into "."' \
@@ -118,4 +118,42 @@ test_expect_success "Sergey Vlasov's test case" '
        git mv ab a
 '
 
+test_expect_success 'absolute pathname' '(
+
+       rm -fr mine &&
+       mkdir mine &&
+       cd mine &&
+       test_create_repo one &&
+       cd one &&
+       mkdir sub &&
+       >sub/file &&
+       git add sub/file &&
+
+       git mv sub "$(pwd)/in" &&
+       ! test -d sub &&
+       test -d in &&
+       git ls-files --error-unmatch in/file
+
+
+)'
+
+test_expect_success 'absolute pathname outside should fail' '(
+
+       rm -fr mine &&
+       mkdir mine &&
+       cd mine &&
+       out=$(pwd) &&
+       test_create_repo one &&
+       cd one &&
+       mkdir sub &&
+       >sub/file &&
+       git add sub/file &&
+
+       ! git mv sub "$out/out" &&
+       test -d sub &&
+       ! test -d ../in &&
+       git ls-files --error-unmatch sub/file
+
+)'
+
 test_done
index 68b2b92879c5e187a33d34d3daf54bb3c131e40f..c8b4f65f380f3941c75bd6ed52975777d2b28d67 100755 (executable)
@@ -107,8 +107,8 @@ do
                diff expected actual
        '
 
-        test_expect_failure "grep -c $L (no /dev/null)" '
-               git grep -c test $H | grep -q "/dev/null"
+       test_expect_success "grep -c $L (no /dev/null)" '
+               ! git grep -c test $H | grep -q /dev/null
         '
 
 done
index df496a95ff16b1a42cfa140cc3d1c4449e4023ab..75cd33bde8e5906ad68ecdcc904248745d79975c 100755 (executable)
@@ -26,8 +26,8 @@ test_expect_success 'listing all tags in an empty tree should output nothing' '
        test `git-tag | wc -l` -eq 0
 '
 
-test_expect_failure 'looking for a tag in an empty tree should fail' \
-       'tag_exists mytag'
+test_expect_success 'looking for a tag in an empty tree should fail' \
+       '! (tag_exists mytag)'
 
 test_expect_success 'creating a tag in an empty tree should fail' '
        ! git-tag mynotag &&
@@ -83,9 +83,9 @@ test_expect_success \
 
 # special cases for creating tags:
 
-test_expect_failure \
+test_expect_success \
        'trying to create a tag with the name of one existing should fail' \
-       'git tag mytag'
+       'git tag mytag'
 
 test_expect_success \
        'trying to create a tag with a non-valid name should fail' '
@@ -146,8 +146,8 @@ test_expect_success \
        ! tag_exists myhead
 '
 
-test_expect_failure 'trying to delete an already deleted tag should fail' \
-       'git-tag -d mytag'
+test_expect_success 'trying to delete an already deleted tag should fail' \
+       'git-tag -d mytag'
 
 # listing various tags with pattern matching:
 
@@ -265,16 +265,16 @@ test_expect_success \
        test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD)
 '
 
-test_expect_failure 'trying to verify an unknown tag should fail' \
-       'git-tag -v unknown-tag'
+test_expect_success 'trying to verify an unknown tag should fail' \
+       'git-tag -v unknown-tag'
 
-test_expect_failure \
+test_expect_success \
        'trying to verify a non-annotated and non-signed tag should fail' \
-       'git-tag -v non-annotated-tag'
+       'git-tag -v non-annotated-tag'
 
-test_expect_failure \
+test_expect_success \
        'trying to verify many non-annotated or unknown tags, should fail' \
-       'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
+       'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
 
 # creating annotated tags:
 
@@ -1027,21 +1027,21 @@ test_expect_success \
 
 # try to sign with bad user.signingkey
 git config user.signingkey BobTheMouse
-test_expect_failure \
+test_expect_success \
        'git-tag -s fails if gpg is misconfigured' \
-       'git tag -s -m tail tag-gpg-failure'
+       'git tag -s -m tail tag-gpg-failure'
 git config --unset user.signingkey
 
 # try to verify without gpg:
 
 rm -rf gpghome
-test_expect_failure \
+test_expect_success \
        'verify signed tag fails when public key is not present' \
-       'git-tag -v signed-tag'
+       'git-tag -v signed-tag'
 
-test_expect_failure \
+test_expect_success \
        'git-tag -a fails if tag annotation is empty' '
-       GIT_EDITOR=cat git tag -a initial-comment
+       ! (GIT_EDITOR=cat git tag -a initial-comment)
 '
 
 test_expect_success \
diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh
new file mode 100755 (executable)
index 0000000..bc8ab6a
--- /dev/null
@@ -0,0 +1,165 @@
+#!/bin/sh
+
+test_description='setup taking and sanitizing funny paths'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       mkdir -p a/b/c a/e &&
+       D=$(pwd) &&
+       >a/b/c/d &&
+       >a/e/f
+
+'
+
+test_expect_success 'git add (absolute)' '
+
+       git add "$D/a/b/c/d" &&
+       git ls-files >current &&
+       echo a/b/c/d >expect &&
+       diff -u expect current
+
+'
+
+
+test_expect_success 'git add (funny relative)' '
+
+       rm -f .git/index &&
+       (
+               cd a/b &&
+               git add "../e/./f"
+       ) &&
+       git ls-files >current &&
+       echo a/e/f >expect &&
+       diff -u expect current
+
+'
+
+test_expect_success 'git rm (absolute)' '
+
+       rm -f .git/index &&
+       git add a &&
+       git rm -f --cached "$D/a/b/c/d" &&
+       git ls-files >current &&
+       echo a/e/f >expect &&
+       diff -u expect current
+
+'
+
+test_expect_success 'git rm (funny relative)' '
+
+       rm -f .git/index &&
+       git add a &&
+       (
+               cd a/b &&
+               git rm -f --cached "../e/./f"
+       ) &&
+       git ls-files >current &&
+       echo a/b/c/d >expect &&
+       diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (absolute)' '
+
+       rm -f .git/index &&
+       git add a &&
+       git ls-files "$D/a/e/../b" >current &&
+       echo a/b/c/d >expect &&
+       diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #1)' '
+
+       rm -f .git/index &&
+       git add a &&
+       (
+               cd a/b &&
+               git ls-files "../b/c"
+       )  >current &&
+       echo c/d >expect &&
+       diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #2)' '
+
+       rm -f .git/index &&
+       git add a &&
+       (
+               cd a/b &&
+               git ls-files --full-name "../e/f"
+       )  >current &&
+       echo a/e/f >expect &&
+       diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #3)' '
+
+       rm -f .git/index &&
+       git add a &&
+       (
+               cd a/b &&
+               if git ls-files "../e/f"
+               then
+                       echo Gaah, should have failed
+                       exit 1
+               else
+                       : happy
+               fi
+       )
+
+'
+
+test_expect_success 'commit using absolute path names' '
+       git commit -m "foo" &&
+       echo aa >>a/b/c/d &&
+       git commit -m "aa" "$(pwd)/a/b/c/d"
+'
+
+test_expect_success 'log using absolute path names' '
+       echo bb >>a/b/c/d &&
+       git commit -m "bb" $(pwd)/a/b/c/d &&
+
+       git log a/b/c/d >f1.txt &&
+       git log "$(pwd)/a/b/c/d" >f2.txt &&
+       diff -u f1.txt f2.txt
+'
+
+test_expect_success 'blame using absolute path names' '
+       git blame a/b/c/d >f1.txt &&
+       git blame "$(pwd)/a/b/c/d" >f2.txt &&
+       diff -u f1.txt f2.txt
+'
+
+test_expect_success 'setup deeper work tree' '
+       test_create_repo tester
+'
+
+test_expect_success 'add a directory outside the work tree' '(
+       cd tester &&
+       d1="$(cd .. ; pwd)" &&
+       test_must_fail git add "$d1"
+)'
+
+
+test_expect_success 'add a file outside the work tree, nasty case 1' '(
+       cd tester &&
+       f="$(pwd)x" &&
+       echo "$f" &&
+       touch "$f" &&
+       test_must_fail git add "$f"
+)'
+
+test_expect_success 'add a file outside the work tree, nasty case 2' '(
+       cd tester &&
+       f="$(pwd | sed "s/.$//")x" &&
+       echo "$f" &&
+       touch "$f" &&
+       test_must_fail git add "$f"
+)'
+
+test_done
index 66d40430b293b2c1f8c7bc72416730e502c41f58..0d9874bfd7082f9ef16c1f6b3ff8a848a19d8937 100755 (executable)
@@ -36,28 +36,28 @@ test_expect_success \
     'test -d path0 &&
      test -f path0/COPYING'
 
-test_expect_failure \
+test_expect_success \
     'checking lack of path1/path2/COPYING' \
-    'test -f path1/path2/COPYING'
+    'test -f path1/path2/COPYING'
 
-test_expect_failure \
+test_expect_success \
     'checking lack of path1/COPYING' \
-    'test -f path1/COPYING'
+    'test -f path1/COPYING'
 
-test_expect_failure \
+test_expect_success \
     'checking lack of COPYING' \
-    'test -f COPYING'
+    'test -f COPYING'
 
-test_expect_failure \
+test_expect_success \
     'checking checking lack of path1/COPYING-TOO' \
-    'test -f path0/COPYING-TOO'
+    'test -f path0/COPYING-TOO'
 
-test_expect_failure \
+test_expect_success \
     'checking lack of path1/path2' \
-    'test -d path1/path2'
+    'test -d path1/path2'
 
-test_expect_failure \
+test_expect_success \
     'checking lack of path1' \
-    'test -d path1'
+    'test -d path1'
 
 test_done
diff --git a/t/t7104-reset.sh b/t/t7104-reset.sh
new file mode 100755 (executable)
index 0000000..f136ee7
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='reset --hard unmerged'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       mkdir before later &&
+       >before/1 &&
+       >before/2 &&
+       >hello &&
+       >later/3 &&
+       git add before hello later &&
+       git commit -m world &&
+
+       H=$(git rev-parse :hello) &&
+       git rm --cached hello &&
+       echo "100644 $H 2       hello" | git update-index --index-info &&
+
+       rm -f hello &&
+       mkdir -p hello &&
+       >hello/world &&
+       test "$(git ls-files -o)" = hello/world
+
+'
+
+test_expect_success 'reset --hard should restore unmerged ones' '
+
+       git reset --hard &&
+       git ls-files --error-unmatch before/1 before/2 hello later/3 &&
+       test -f hello
+
+'
+
+test_expect_success 'reset --hard did not corrupt index nor cached-tree' '
+
+       T=$(git write-tree) &&
+       rm -f .git/index &&
+       git add before hello later &&
+       U=$(git write-tree) &&
+       test "$T" = "$U"
+
+'
+
+test_done
index 73d8a00e2cca907e562c50c70a10c02e3c0e02ca..63915cd87b74aaddb943f45057bda10d283b4351 100755 (executable)
@@ -83,13 +83,13 @@ test_expect_success "checkout with unrelated dirty tree without -m" '
        fill 0 1 2 3 4 5 6 7 8 >same &&
        cp same kept
        git checkout side >messages &&
-       git diff same kept
+       diff -u same kept
        (cat > messages.expect <<EOF
 M      same
 EOF
 ) &&
        touch messages.expect &&
-       git diff messages.expect messages
+       diff -u messages.expect messages
 '
 
 test_expect_success "checkout -m with dirty tree" '
@@ -103,29 +103,22 @@ test_expect_success "checkout -m with dirty tree" '
        test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&
 
        (cat >expect.messages <<EOF
-Merging side with local
-Merging:
-ab76817 Side M one, D two, A three
-virtual local
-found 1 common ancestor(s):
-7329388 Initial A one, A two
-Auto-merged one
 M      one
 EOF
 ) &&
-       git diff expect.messages messages &&
+       diff -u expect.messages messages &&
 
        fill "M one" "A three" "D       two" >expect.master &&
        git diff --name-status master >current.master &&
-       diff expect.master current.master &&
+       diff -u expect.master current.master &&
 
        fill "M one" >expect.side &&
        git diff --name-status side >current.side &&
-       diff expect.side current.side &&
+       diff -u expect.side current.side &&
 
        : >expect.index &&
        git diff --cached >current.index &&
-       diff expect.index current.index
+       diff -u expect.index current.index
 '
 
 test_expect_success "checkout -m with dirty tree, renamed" '
@@ -143,7 +136,7 @@ test_expect_success "checkout -m with dirty tree, renamed" '
 
        git checkout -m renamer &&
        fill 1 3 4 5 7 8 >expect &&
-       diff expect uno &&
+       diff -u expect uno &&
        ! test -f one &&
        git diff --cached >current &&
        ! test -s current
@@ -168,7 +161,7 @@ test_expect_success 'checkout -m with merge conflict' '
        git diff master:one :3:uno |
        sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current &&
        fill d2 aT d7 aS >expect &&
-       diff current expect &&
+       diff -u current expect &&
        git diff --cached two >current &&
        ! test -s current
 '
@@ -185,7 +178,7 @@ If you want to create a new branch from this checkout, you may do so
 HEAD is now at 7329388... Initial A one, A two
 EOF
 ) &&
-       git diff messages.expect messages &&
+       diff -u messages.expect messages &&
        H=$(git rev-parse --verify HEAD) &&
        M=$(git show-ref -s --verify refs/heads/master) &&
        test "z$H" = "z$M" &&
@@ -214,6 +207,22 @@ test_expect_success 'checkout to detach HEAD with branchname^' '
        fi
 '
 
+test_expect_success 'checkout to detach HEAD with :/message' '
+
+       git checkout -f master && git clean -f &&
+       git checkout ":/Initial" &&
+       H=$(git rev-parse --verify HEAD) &&
+       M=$(git show-ref -s --verify refs/heads/master) &&
+       test "z$H" = "z$M" &&
+       if git symbolic-ref HEAD >/dev/null 2>&1
+       then
+               echo "OOPS, HEAD is still symbolic???"
+               false
+       else
+               : happy
+       fi
+'
+
 test_expect_success 'checkout to detach HEAD with HEAD^0' '
 
        git checkout -f master && git clean -f &&
@@ -270,4 +279,62 @@ test_expect_success 'checkout with ambiguous tag/branch names' '
 
 '
 
+test_expect_success 'switch branches while in subdirectory' '
+
+       git reset --hard &&
+       git checkout master &&
+
+       mkdir subs &&
+       (
+               cd subs &&
+               git checkout side
+       ) &&
+       ! test -f subs/one &&
+       rm -fr subs
+
+'
+
+test_expect_success 'checkout specific path while in subdirectory' '
+
+       git reset --hard &&
+       git checkout side &&
+       mkdir subs &&
+       >subs/bero &&
+       git add subs/bero &&
+       git commit -m "add subs/bero" &&
+
+       git checkout master &&
+       mkdir -p subs &&
+       (
+               cd subs &&
+               git checkout side -- bero
+       ) &&
+       test -f subs/bero
+
+'
+
+test_expect_success \
+    'checkout w/--track sets up tracking' '
+    git config branch.autosetupmerge false &&
+    git checkout master &&
+    git checkout --track -b track1 &&
+    test "$(git config branch.track1.remote)" &&
+    test "$(git config branch.track1.merge)"'
+
+test_expect_success \
+    'checkout w/autosetupmerge=always sets up tracking' '
+    git config branch.autosetupmerge always &&
+    git checkout master &&
+    git checkout -b track2 &&
+    test "$(git config branch.track2.remote)" &&
+    test "$(git config branch.track2.merge)"
+    git config branch.autosetupmerge false'
+
+test_expect_success \
+    'checkout w/--track from non-branch HEAD fails' '
+    git checkout -b delete-me master &&
+    rm .git/refs/heads/delete-me &&
+    test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
+    !(git checkout --track -b track)'
+
 test_done
index dfd118878fd37c096ccd426aa01fa2ac36581367..afccfc9973ba864167387b1e1caf6fe8b12f68f9 100755 (executable)
@@ -89,6 +89,58 @@ test_expect_success 'git-clean with prefix' '
        test -f build/lib.so
 
 '
+
+test_expect_success 'git-clean with relative prefix' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       would_clean=$(
+               cd docs &&
+               git clean -n ../src |
+               sed -n -e "s|^Would remove ||p"
+       ) &&
+       test "$would_clean" = ../src/part3.c || {
+               echo "OOps <$would_clean>"
+               false
+       }
+'
+
+test_expect_success 'git-clean with absolute path' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       would_clean=$(
+               cd docs &&
+               git clean -n $(pwd)/../src |
+               sed -n -e "s|^Would remove ||p"
+       ) &&
+       test "$would_clean" = ../src/part3.c || {
+               echo "OOps <$would_clean>"
+               false
+       }
+'
+
+test_expect_success 'git-clean with out of work tree relative path' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       (
+               cd docs &&
+               test_must_fail git clean -n ../..
+       )
+'
+
+test_expect_success 'git-clean with out of work tree absolute path' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       dd=$(cd .. && pwd) &&
+       (
+               cd docs &&
+               test_must_fail git clean -n $dd
+       )
+'
+
 test_expect_success 'git-clean -d with prefix and path' '
 
        mkdir -p build docs src/feature &&
@@ -316,4 +368,15 @@ test_expect_success 'core.excludesfile' '
 
 '
 
+test_expect_success 'removal failure' '
+
+       mkdir foo &&
+       touch foo/bar &&
+       exec <foo/bar &&
+       chmod 0 foo &&
+       test_must_fail git clean -f -d
+
+'
+chmod 755 foo
+
 test_done
index 55043d102f575bd92ea61f3beccc350ba3e7cd4c..361886c3d62b8bf81db0be5999c1e98c4e972f6a 100755 (executable)
@@ -17,49 +17,49 @@ test_expect_success \
         git-add file && \
         git-status | grep 'Initial commit'"
 
-test_expect_failure \
+test_expect_success \
        "fail initial amend" \
-       "git-commit --amend"
+       "git-commit --amend"
 
 test_expect_success \
        "initial commit" \
        "git-commit -m initial"
 
-test_expect_failure \
+test_expect_success \
        "invalid options 1" \
-       "git-commit -m foo -m bar -F file"
+       "git-commit -m foo -m bar -F file"
 
-test_expect_failure \
+test_expect_success \
        "invalid options 2" \
-       "git-commit -C HEAD -m illegal"
+       "git-commit -C HEAD -m illegal"
 
-test_expect_failure \
+test_expect_success \
        "using paths with -a" \
        "echo King of the bongo >file &&
-       git-commit -m foo -a file"
+       git-commit -m foo -a file"
 
-test_expect_failure \
+test_expect_success \
        "using paths with --interactive" \
        "echo bong-o-bong >file &&
-       echo 7 | git-commit -m foo --interactive file"
+       echo 7 | git-commit -m foo --interactive file"
 
-test_expect_failure \
+test_expect_success \
        "using invalid commit with -C" \
-       "git-commit -C bogus"
+       "git-commit -C bogus"
 
-test_expect_failure \
+test_expect_success \
        "testing nothing to commit" \
-       "git-commit -m initial"
+       "git-commit -m initial"
 
 test_expect_success \
        "next commit" \
        "echo 'bongo bongo bongo' >file \
         git-commit -m next -a"
 
-test_expect_failure \
+test_expect_success \
        "commit message from non-existing file" \
        "echo 'more bongo: bongo bongo bongo bongo' >file && \
-        git-commit -F gah -a"
+        git-commit -F gah -a"
 
 # Empty except stray tabs and spaces on a few lines.
 sed -e 's/@$//' >msg <<EOF
@@ -68,9 +68,9 @@ sed -e 's/@$//' >msg <<EOF
   @
 Signed-off-by: hula
 EOF
-test_expect_failure \
+test_expect_success \
        "empty commit message" \
-       "git-commit -F msg -a"
+       "git-commit -F msg -a"
 
 test_expect_success \
        "commit message from file" \
@@ -88,10 +88,10 @@ test_expect_success \
        "amend commit" \
        "VISUAL=./editor git-commit --amend"
 
-test_expect_failure \
+test_expect_success \
        "passing -m and -F" \
        "echo 'enough with the bongos' >file && \
-        git-commit -F msg -m amending ."
+        git-commit -F msg -m amending ."
 
 test_expect_success \
        "using message from other commit" \
index aaf497e6a5948ffa393ea07380bf577ceb5f347b..b780fddc08f9c01eaf1143b9178ab5b7ecd032db 100755 (executable)
@@ -154,4 +154,33 @@ test_expect_success 'cleanup commit messages (strip,-F,-e)' '
 
 '
 
+pwd=`pwd`
+cat >> .git/FAKE_EDITOR << EOF
+#! /bin/sh
+echo editor started > "$pwd/.git/result"
+exit 0
+EOF
+chmod +x .git/FAKE_EDITOR
+
+test_expect_success 'do not fire editor in the presence of conflicts' '
+
+       git clean
+       echo f>g
+       git add g
+       git commit -myes
+       git branch second
+       echo master>g
+       echo g>h
+       git add g h
+       git commit -mmaster
+       git checkout second
+       echo second>g
+       git add g
+       git commit -msecond
+       git cherry-pick -n master
+       echo "editor not started" > .git/result
+       GIT_EDITOR=`pwd`/.git/FAKE_EDITOR git commit && exit 1  # should fail
+       test "`cat .git/result`" = "editor not started"
+'
+
 test_done
index d787cac2f7c09c0d84cb4cdfdf68401b660d3c6c..2dd5a5e30279e6c3e5ac2be9425c19328a65aff1 100755 (executable)
@@ -52,11 +52,11 @@ cat > "$HOOK" <<EOF
 exit 1
 EOF
 
-test_expect_failure 'with failing hook' '
+test_expect_success 'with failing hook' '
 
        echo "another" >> file &&
        git add file &&
-       git commit -m "another"
+       git commit -m "another"
 
 '
 
index 751b11300bb887d552697879201217ca1dcff648..eff36aaee32200075f815de43af781cb115ea38e 100755 (executable)
@@ -98,20 +98,20 @@ cat > "$HOOK" <<EOF
 exit 1
 EOF
 
-test_expect_failure 'with failing hook' '
+test_expect_success 'with failing hook' '
 
        echo "another" >> file &&
        git add file &&
-       git commit -m "another"
+       git commit -m "another"
 
 '
 
-test_expect_failure 'with failing hook (editor)' '
+test_expect_success 'with failing hook (editor)' '
 
        echo "more another" >> file &&
        git add file &&
        echo "more another" > FAKE_MSG &&
-       GIT_EDITOR="$FAKE_EDITOR" git commit
+       ! (GIT_EDITOR="$FAKE_EDITOR" git commit)
 
 '
 
diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh
new file mode 100755 (executable)
index 0000000..7ddec99
--- /dev/null
@@ -0,0 +1,155 @@
+#!/bin/sh
+
+test_description='prepare-commit-msg hook'
+
+. ./test-lib.sh
+
+test_expect_success 'with no hook' '
+
+       echo "foo" > file &&
+       git add file &&
+       git commit -m "first"
+
+'
+
+# set up fake editor for interactive editing
+cat > fake-editor <<'EOF'
+#!/bin/sh
+exit 0
+EOF
+chmod +x fake-editor
+FAKE_EDITOR="$(pwd)/fake-editor"
+export FAKE_EDITOR
+
+# now install hook that always succeeds and adds a message
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+HOOK="$HOOKDIR/prepare-commit-msg"
+mkdir -p "$HOOKDIR"
+cat > "$HOOK" <<'EOF'
+#!/bin/sh
+if test "$2" = commit; then
+  source=$(git-rev-parse "$3")
+else
+  source=${2-default}
+fi
+if test "$GIT_EDITOR" = :; then
+  sed -e "1s/.*/$source (no editor)/" "$1" > msg.tmp
+else
+  sed -e "1s/.*/$source/" "$1" > msg.tmp
+fi
+mv msg.tmp "$1"
+exit 0
+EOF
+chmod +x "$HOOK"
+
+echo dummy template > "$(git rev-parse --git-dir)/template"
+
+test_expect_success 'with hook (-m)' '
+
+       echo "more" >> file &&
+       git add file &&
+       git commit -m "more" &&
+       test "`git log -1 --pretty=format:%s`" = "message (no editor)"
+
+'
+
+test_expect_success 'with hook (-m editor)' '
+
+       echo "more" >> file &&
+       git add file &&
+       GIT_EDITOR="$FAKE_EDITOR" git commit -e -m "more more" &&
+       test "`git log -1 --pretty=format:%s`" = message
+
+'
+
+test_expect_success 'with hook (-t)' '
+
+       echo "more" >> file &&
+       git add file &&
+       git commit -t "$(git rev-parse --git-dir)/template" &&
+       test "`git log -1 --pretty=format:%s`" = template
+
+'
+
+test_expect_success 'with hook (-F)' '
+
+       echo "more" >> file &&
+       git add file &&
+       (echo more | git commit -F -) &&
+       test "`git log -1 --pretty=format:%s`" = "message (no editor)"
+
+'
+
+test_expect_success 'with hook (-F editor)' '
+
+       echo "more" >> file &&
+       git add file &&
+       (echo more more | GIT_EDITOR="$FAKE_EDITOR" git commit -e -F -) &&
+       test "`git log -1 --pretty=format:%s`" = message
+
+'
+
+test_expect_success 'with hook (-C)' '
+
+       head=`git rev-parse HEAD` &&
+       echo "more" >> file &&
+       git add file &&
+       git commit -C $head &&
+       test "`git log -1 --pretty=format:%s`" = "$head (no editor)"
+
+'
+
+test_expect_success 'with hook (editor)' '
+
+       echo "more more" >> file &&
+       git add file &&
+       GIT_EDITOR="$FAKE_EDITOR" git commit &&
+       test "`git log -1 --pretty=format:%s`" = default
+
+'
+
+test_expect_success 'with hook (--amend)' '
+
+       head=`git rev-parse HEAD` &&
+       echo "more" >> file &&
+       git add file &&
+       GIT_EDITOR="$FAKE_EDITOR" git commit --amend &&
+       test "`git log -1 --pretty=format:%s`" = "$head"
+
+'
+
+test_expect_success 'with hook (-c)' '
+
+       head=`git rev-parse HEAD` &&
+       echo "more" >> file &&
+       git add file &&
+       GIT_EDITOR="$FAKE_EDITOR" git commit -c $head &&
+       test "`git log -1 --pretty=format:%s`" = "$head"
+
+'
+
+cat > "$HOOK" <<'EOF'
+#!/bin/sh
+exit 1
+EOF
+
+test_expect_success 'with failing hook' '
+
+       head=`git rev-parse HEAD` &&
+       echo "more" >> file &&
+       git add file &&
+       ! GIT_EDITOR="$FAKE_EDITOR" git commit -c $head
+
+'
+
+test_expect_success 'with failing hook (--no-verify)' '
+
+       head=`git rev-parse HEAD` &&
+       echo "more" >> file &&
+       git add file &&
+       ! GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify -c $head
+
+'
+
+
+test_done
diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh
new file mode 100644 (file)
index 0000000..6b0483f
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Charles Bailey
+#
+
+test_description='git-mergetool
+
+Testing basic merge tool invocation'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+    echo master >file1 &&
+    git add file1 &&
+    git commit -m "added file1" &&
+    git checkout -b branch1 master &&
+    echo branch1 change >file1 &&
+    echo branch1 newfile >file2 &&
+    git add file1 file2 &&
+    git commit -m "branch1 changes" &&
+    git checkout -b branch2 master &&
+    echo branch2 change >file1 &&
+    echo branch2 newfile >file2 &&
+    git add file1 file2 &&
+    git commit -m "branch2 changes" &&
+    git checkout master &&
+    echo master updated >file1 &&
+    echo master new >file2 &&
+    git add file1 file2 &&
+    git commit -m "master updates"
+'
+
+test_expect_success 'custom mergetool' '
+    git config merge.tool mytool &&
+    git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
+    git config mergetool.mytool.trustExitCode true &&
+       git checkout branch1 &&
+    ! git merge master >/dev/null 2>&1 &&
+    ( yes "" | git mergetool file1>/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file2>/dev/null 2>&1 ) &&
+    test "$(cat file1)" = "master updated" &&
+    test "$(cat file2)" = "master new" &&
+       git commit -m "branch1 resolved with mergetool"
+'
+
+test_done
index 2efaed441d6ae238ce5d6a6105230e088d5e56ff..cbbfa9cb4986403cb214bba6c2216c85471469c9 100755 (executable)
@@ -15,16 +15,22 @@ test_expect_success \
     'Setup helper tool' \
     '(echo "#!/bin/sh"
       echo shift
+      echo output=1
+      echo "while test -f commandline\$output; do output=\$((\$output+1)); done"
       echo for a
       echo do
       echo "  echo \"!\$a!\""
-      echo "done >commandline"
-      echo "cat > msgtxt"
+      echo "done >commandline\$output"
+      echo "cat > msgtxt\$output"
       ) >fake.sendmail &&
      chmod +x ./fake.sendmail &&
      git add fake.sendmail &&
      GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
 
+clean_fake_sendmail() {
+       rm -f commandline* msgtxt*
+}
+
 test_expect_success 'Extract patches' '
     patches=`git format-patch -n HEAD^1`
 '
@@ -39,7 +45,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'Verify commandline' \
-    'diff commandline expected'
+    'diff commandline1 expected'
 
 cat >expected-show-all-headers <<\EOF
 0001-Second.patch
@@ -82,7 +88,7 @@ z8=zzzzzzzz
 z64=$z8$z8$z8$z8$z8$z8$z8$z8
 z512=$z64$z64$z64$z64$z64$z64$z64$z64
 test_expect_success 'reject long lines' '
-       rm -f commandline &&
+       clean_fake_sendmail &&
        cp $patches longline.patch &&
        echo $z512$z512 >>longline.patch &&
        ! git send-email \
@@ -95,7 +101,7 @@ test_expect_success 'reject long lines' '
 '
 
 test_expect_success 'no patch was sent' '
-       ! test -e commandline
+       ! test -e commandline1
 '
 
 test_expect_success 'allow long lines with --no-validate' '
@@ -109,6 +115,7 @@ test_expect_success 'allow long lines with --no-validate' '
 '
 
 test_expect_success 'Invalid In-Reply-To' '
+       clean_fake_sendmail &&
        git send-email \
                --from="Example <nobody@example.com>" \
                --to=nobody@example.com \
@@ -116,17 +123,47 @@ test_expect_success 'Invalid In-Reply-To' '
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches
                2>errors
-       ! grep "^In-Reply-To: < *>" msgtxt
+       ! grep "^In-Reply-To: < *>" msgtxt1
 '
 
 test_expect_success 'Valid In-Reply-To when prompting' '
+       clean_fake_sendmail &&
        (echo "From Example <from@example.com>"
         echo "To Example <to@example.com>"
         echo ""
        ) | env GIT_SEND_EMAIL_NOTTY=1 git send-email \
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches 2>errors &&
-       ! grep "^In-Reply-To: < *>" msgtxt
+       ! grep "^In-Reply-To: < *>" msgtxt1
+'
+
+test_expect_success 'setup fake editor' '
+       (echo "#!/bin/sh" &&
+        echo "echo fake edit >>\$1"
+       ) >fake-editor &&
+       chmod +x fake-editor
+'
+
+test_expect_success '--compose works' '
+       clean_fake_sendmail &&
+       echo y | \
+               GIT_EDITOR=$(pwd)/fake-editor \
+               GIT_SEND_EMAIL_NOTTY=1 \
+               git send-email \
+               --compose --subject foo \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $patches \
+               2>errors
+'
+
+test_expect_success 'first message is compose text' '
+       grep "^fake edit" msgtxt1
+'
+
+test_expect_success 'second message is patch' '
+       grep "Subject:.*Second" msgtxt2
 '
 
 test_done
index 614cf50d195bb3b055fd8166d425eeffa7106509..4e24ab3a7db96d396c108dd0cbe677aa067c9460 100755 (executable)
@@ -56,19 +56,19 @@ test_expect_success "$name" "
 
 
 name='detect node change from file to directory #1'
-test_expect_failure "$name" "
+test_expect_success "$name" "
        mkdir dir/new_file &&
        mv dir/file dir/new_file/file &&
        mv dir/new_file dir/file &&
        git update-index --remove dir/file &&
        git update-index --add dir/file/file &&
-       git commit -m '$name'  &&
-       git-svn set-tree --find-copies-harder --rmdir \
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
                remotes/git-svn..mybranch" || true
 
 
 name='detect node change from directory to file #1'
-test_expect_failure "$name" "
+test_expect_success "$name" "
        rm -rf dir '$GIT_DIR'/index &&
        git checkout -f -b mybranch2 remotes/git-svn &&
        mv bar/zzz zzz &&
@@ -77,12 +77,12 @@ test_expect_failure "$name" "
        git update-index --remove -- bar/zzz &&
        git update-index --add -- bar &&
        git commit -m '$name' &&
-       git-svn set-tree --find-copies-harder --rmdir \
+       git-svn set-tree --find-copies-harder --rmdir \
                remotes/git-svn..mybranch2" || true
 
 
 name='detect node change from file to directory #2'
-test_expect_failure "$name" "
+test_expect_success "$name" "
        rm -f '$GIT_DIR'/index &&
        git checkout -f -b mybranch3 remotes/git-svn &&
        rm bar/zzz &&
@@ -91,12 +91,12 @@ test_expect_failure "$name" "
        echo yyy > bar/zzz/yyy &&
        git update-index --add bar/zzz/yyy &&
        git commit -m '$name' &&
-       git-svn set-tree --find-copies-harder --rmdir \
+       git-svn set-tree --find-copies-harder --rmdir \
                remotes/git-svn..mybranch3" || true
 
 
 name='detect node change from directory to file #2'
-test_expect_failure "$name" "
+test_expect_success "$name" "
        rm -f '$GIT_DIR'/index &&
        git checkout -f -b mybranch4 remotes/git-svn &&
        rm -rf dir &&
@@ -105,7 +105,7 @@ test_expect_failure "$name" "
        echo asdf > dir &&
        git update-index --add -- dir &&
        git commit -m '$name' &&
-       git-svn set-tree --find-copies-harder --rmdir \
+       git-svn set-tree --find-copies-harder --rmdir \
                remotes/git-svn..mybranch4" || true
 
 
@@ -213,18 +213,18 @@ EOF
 
 test_expect_success "$name" "git diff a expected"
 
-test_expect_failure 'exit if remote refs are ambigious' "
+test_expect_success 'exit if remote refs are ambigious' "
         git config --add svn-remote.svn.fetch \
                               bar:refs/remotes/git-svn &&
-        git-svn migrate
-        "
+       ! git-svn migrate
+"
 
-test_expect_failure 'exit if init-ing a would clobber a URL' "
+test_expect_success 'exit if init-ing a would clobber a URL' "
         svnadmin create ${PWD}/svnrepo2 &&
         svn mkdir -m 'mkdir bar' ${svnrepo}2/bar &&
         git config --unset svn-remote.svn.fetch \
                                 '^bar:refs/remotes/git-svn$' &&
-        git-svn init ${svnrepo}2/bar
+       ! git-svn init ${svnrepo}2/bar
         "
 
 test_expect_success \
index 79b7968eaf4d4bfa3673edf78e31ca43b54becb3..f74ab1269e3fa79a34aa9f9a3a64df08728e6354 100755 (executable)
@@ -24,11 +24,11 @@ test_expect_success 'commit change from svn side' "
        rm -rf t.svn
        "
 
-test_expect_failure 'commit conflicting change from git' "
+test_expect_success 'commit conflicting change from git' "
        echo second line from git >> file &&
        git commit -a -m 'second line from git' &&
-       git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo
-       " || true
+       git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo
+"
 
 test_expect_success 'commit complementing change from git' "
        git reset --hard HEAD~1 &&
@@ -39,7 +39,7 @@ test_expect_success 'commit complementing change from git' "
        git-svn commit-diff -r2 HEAD~1 HEAD $svnrepo
        "
 
-test_expect_failure 'dcommit fails to commit because of conflict' "
+test_expect_success 'dcommit fails to commit because of conflict' "
        git-svn init $svnrepo &&
        git-svn fetch &&
        git reset --hard refs/remotes/git-svn &&
@@ -52,8 +52,8 @@ test_expect_failure 'dcommit fails to commit because of conflict' "
        rm -rf t.svn &&
        echo 'fourth line from git' >> file &&
        git commit -a -m 'fourth line from git' &&
-       git-svn dcommit
-       " || true
+       git-svn dcommit
+       "
 
 test_expect_success 'dcommit does the svn equivalent of an index merge' "
        git reset --hard refs/remotes/git-svn &&
@@ -76,15 +76,15 @@ test_expect_success 'commit another change from svn side' "
        rm -rf t.svn
        "
 
-test_expect_failure 'multiple dcommit from git-svn will not clobber svn' "
+test_expect_success 'multiple dcommit from git-svn will not clobber svn' "
        git reset --hard refs/remotes/git-svn &&
        echo new file >> new-file &&
        git update-index --add new-file &&
        git commit -a -m 'new file' &&
        echo clobber > file &&
        git commit -a -m 'clobber' &&
-       git svn dcommit
-       " || true
+       git svn dcommit
+       "
 
 
 test_expect_success 'check that rebase really failed' 'test -d .dotest'
index 745254665dd2d8f73b8c511b39aca82682bf1bbb..ca8a00ed0af0382c20a4883e5aa5f1914b6ecd3f 100755 (executable)
@@ -54,10 +54,10 @@ test_expect_success 'change file but in unrelated area' "
                test x\"\`sed -n -e 61p < file\`\" = x6611
        "
 
-test_expect_failure 'attempt to dcommit with a dirty index' '
+test_expect_success 'attempt to dcommit with a dirty index' '
        echo foo >>file &&
        git add file &&
-       git svn dcommit
+       git svn dcommit
 '
 
 test_done
index 142d42f3b3f41adc99ada33d5ccac50739fb8a69..c4f4465dc687d30138a9111ae2efbc0e3aaf42b6 100755 (executable)
@@ -165,9 +165,9 @@ from refs/heads/master
 M 755 0000000000000000000000000000000000000001 zero1
 
 INPUT_END
-test_expect_failure \
-    'B: fail on invalid blob sha1' \
-    'git-fast-import <input'
+test_expect_success 'B: fail on invalid blob sha1' '
+    ! git-fast-import <input
+'
 rm -f .git/objects/pack_* .git/objects/index_*
 
 cat >input <<INPUT_END
@@ -180,9 +180,9 @@ COMMIT
 from refs/heads/master
 
 INPUT_END
-test_expect_failure \
-    'B: fail on invalid branch name ".badbranchname"' \
-    'git-fast-import <input'
+test_expect_success 'B: fail on invalid branch name ".badbranchname"' '
+    ! git-fast-import <input
+'
 rm -f .git/objects/pack_* .git/objects/index_*
 
 cat >input <<INPUT_END
@@ -195,9 +195,9 @@ COMMIT
 from refs/heads/master
 
 INPUT_END
-test_expect_failure \
-    'B: fail on invalid branch name "bad[branch]name"' \
-    'git-fast-import <input'
+test_expect_success 'B: fail on invalid branch name "bad[branch]name"' '
+    ! git-fast-import <input
+'
 rm -f .git/objects/pack_* .git/objects/index_*
 
 cat >input <<INPUT_END
@@ -339,9 +339,9 @@ COMMIT
 from refs/heads/branch^0
 
 INPUT_END
-test_expect_failure \
-    'E: rfc2822 date, --date-format=raw' \
-    'git-fast-import --date-format=raw <input'
+test_expect_success 'E: rfc2822 date, --date-format=raw' '
+    ! git-fast-import --date-format=raw <input
+'
 test_expect_success \
     'E: rfc2822 date, --date-format=rfc2822' \
     'git-fast-import --date-format=rfc2822 <input'
index 75d1ce433d3110e6fe24be00aef8dcc7592d8082..0a20971ebb693250ab1fa78cdd9ec269e7129f6e 100755 (executable)
@@ -156,15 +156,19 @@ test_expect_success 'req_Root (strict paths)' \
   'cat request-anonymous | git-cvsserver --strict-paths pserver $SERVERDIR >log 2>&1 &&
    tail -n1 log | grep -q "^I LOVE YOU$"'
 
-test_expect_failure 'req_Root failure (strict-paths)' \
-  'cat request-anonymous | git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1'
+test_expect_success 'req_Root failure (strict-paths)' '
+    ! cat request-anonymous |
+    git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1
+'
 
 test_expect_success 'req_Root (w/o strict-paths)' \
   'cat request-anonymous | git-cvsserver pserver $WORKDIR/ >log 2>&1 &&
    tail -n1 log | grep -q "^I LOVE YOU$"'
 
-test_expect_failure 'req_Root failure (w/o strict-paths)' \
-  'cat request-anonymous | git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1'
+test_expect_success 'req_Root failure (w/o strict-paths)' '
+    ! cat request-anonymous |
+    git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1
+'
 
 cat >request-base  <<EOF
 BEGIN AUTH REQUEST
@@ -179,8 +183,10 @@ test_expect_success 'req_Root (base-path)' \
   'cat request-base | git-cvsserver --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&
    tail -n1 log | grep -q "^I LOVE YOU$"'
 
-test_expect_failure 'req_Root failure (base-path)' \
-  'cat request-anonymous | git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1'
+test_expect_success 'req_Root failure (base-path)' '
+    ! cat request-anonymous |
+    git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1
+'
 
 GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1
 
@@ -188,9 +194,8 @@ test_expect_success 'req_Root (export-all)' \
   'cat request-anonymous | git-cvsserver --export-all pserver $WORKDIR >log 2>&1 &&
    tail -n1 log | grep -q "^I LOVE YOU$"'
 
-test_expect_failure 'req_Root failure (export-all w/o whitelist)' \
-  'cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 ||
-   false'
+test_expect_success 'req_Root failure (export-all w/o whitelist)' \
+  '! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)'
 
 test_expect_success 'req_Root (everything together)' \
   'cat request-base | git-cvsserver --export-all --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&
@@ -290,15 +295,16 @@ test_expect_success 'cvs update (update existing file)' \
 
 cd "$WORKDIR"
 #TODO: cvsserver doesn't support update w/o -d
-test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" \
-  'mkdir test &&
+test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" '
+   mkdir test &&
    echo >test/empty &&
    git add test &&
    git commit -q -m "Single Subdirectory" &&
    git push gitcvs.git >/dev/null &&
    cd cvswork &&
    GIT_CONFIG="$git_config" cvs -Q update &&
-   test ! -d test'
+   test ! -d test
+'
 
 cd "$WORKDIR"
 test_expect_success 'cvs update (subdirectories)' \
index 44f5776a1b30895e8d7b2d0601c8edc677790eeb..6aea0ea0a579973c9baddff608ea92445ef9fcbb 100644 (file)
@@ -86,7 +86,7 @@ do
        -q|--q|--qu|--qui|--quie|--quiet)
                quiet=t; shift ;;
        --no-color)
-           color=; shift ;;
+               color=; shift ;;
        --no-python)
                # noop now...
                shift ;;
@@ -149,8 +149,15 @@ fi
 
 test_failure=0
 test_count=0
+test_fixed=0
+test_broken=0
 
-trap 'echo >&5 "FATAL: Unexpected exit with code $?"; exit 1' exit
+die () {
+       echo >&5 "FATAL: Unexpected exit with code $?"
+       exit 1
+}
+
+trap 'die' exit
 
 test_tick () {
        if test -z "${test_tick+set}"
@@ -181,6 +188,17 @@ test_failure_ () {
        test "$immediate" = "" || { trap - exit; exit 1; }
 }
 
+test_known_broken_ok_ () {
+       test_count=$(expr "$test_count" + 1)
+       test_fixed=$(($test_fixed+1))
+       say_color "" "  FIXED $test_count: $@"
+}
+
+test_known_broken_failure_ () {
+       test_count=$(expr "$test_count" + 1)
+       test_broken=$(($test_broken+1))
+       say_color skip "  still broken $test_count: $@"
+}
 
 test_debug () {
        test "$debug" = "" || eval "$1"
@@ -221,13 +239,13 @@ test_expect_failure () {
        error "bug in the test script: not 2 parameters to test-expect-failure"
        if ! test_skip "$@"
        then
-               say >&3 "expecting failure: $2"
+               say >&3 "checking known breakage: $2"
                test_run_ "$2"
-               if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ]
+               if [ "$?" = 0 -a "$eval_ret" = 0 ]
                then
-                       test_ok_ "$1"
+                       test_known_broken_ok_ "$1"
                else
-                       test_failure_ "$@"
+                   test_known_broken_failure_ "$1"
                fi
        fi
        echo >&3 ""
@@ -301,6 +319,18 @@ test_create_repo () {
 
 test_done () {
        trap - exit
+
+       if test "$test_fixed" != 0
+       then
+               say_color pass "fixed $test_fixed known breakage(s)"
+       fi
+       if test "$test_broken" != 0
+       then
+               say_color error "still have $test_broken known breakage(s)"
+               msg="remaining $(($test_count-$test_broken)) test(s)"
+       else
+               msg="$test_count test(s)"
+       fi
        case "$test_failure" in
        0)
                # We could:
@@ -311,11 +341,11 @@ test_done () {
                # The Makefile provided will clean this test area so
                # we will leave things as they are.
 
-               say_color pass "passed all $test_count test(s)"
+               say_color pass "passed all $msg"
                exit 0 ;;
 
        *)
-               say_color error "failed $test_failure among $test_count test(s)"
+               say_color error "failed $test_failure among $msg"
                exit 1 ;;
 
        esac
@@ -326,8 +356,11 @@ test_done () {
 PATH=$(pwd)/..:$PATH
 GIT_EXEC_PATH=$(pwd)/..
 GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
-GIT_CONFIG=.git/config
-export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG
+unset GIT_CONFIG
+unset GIT_CONFIG_LOCAL
+GIT_CONFIG_NOSYSTEM=1
+GIT_CONFIG_NOGLOBAL=1
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
 
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
@@ -341,6 +374,8 @@ if ! test -x ../test-chmtime; then
        exit 1
 fi
 
+. ../GIT-BUILD-OPTIONS
+
 # Test repository
 test=trash
 rm -fr "$test"
diff --git a/tag.c b/tag.c
index 38bf9134f97c18973fe189c8703438f5e1135e49..4470d2bf78e1fbb00d00e487f41daa4373cf48e1 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -9,7 +9,10 @@ const char *tag_type = "tag";
 struct object *deref_tag(struct object *o, const char *warn, int warnlen)
 {
        while (o && o->type == OBJ_TAG)
-               o = parse_object(((struct tag *)o)->tagged->sha1);
+               if (((struct tag *)o)->tagged)
+                       o = parse_object(((struct tag *)o)->tagged->sha1);
+               else
+                       o = NULL;
        if (!o && warn) {
                if (!warnlen)
                        warnlen = strlen(warn);
@@ -84,12 +87,6 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
                item->tagged = NULL;
        }
 
-       if (item->tagged && track_object_refs) {
-               struct object_refs *refs = alloc_object_refs(1);
-               refs->ref[0] = item->tagged;
-               set_object_refs(&item->object, refs);
-       }
-
        return 0;
 }
 
index c5cdb9d7eeebab7edf048e77eb92437de24751bf..4ef86eb244bbbde9b793fd8cfab92042adb51485 100644 (file)
@@ -9,6 +9,9 @@
 # To enable this hook, make this file executable.
 
 # Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
 # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
 # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
 
diff --git a/templates/hooks--prepare-commit-msg b/templates/hooks--prepare-commit-msg
new file mode 100644 (file)
index 0000000..ff0f42a
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by git-commit with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source.  The hook's purpose is to edit the commit
+# message file.  If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, make this file executable.
+
+# This hook includes three examples.  The first comments out the
+# "Conflicts:" part of a merge commit.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output.  It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited.  This is rarely a good idea.
+
+case "$2 $3" in
+  merge)
+    sed -i '/^Conflicts:/,/#/!b;s/^/# &/;s/^# #/#/' "$1" ;;
+
+# ""|template)
+#   perl -i -pe '
+#      print "\n" . `git diff --cached --name-status -r`
+#       if /^#/ && $first++ == 0' "$1" ;;
+
+  *) ;;
+esac
+
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
index eed8a02c6589bcb9af46188b56054eace3cd4d19..73360d75126af2f231b3cfdad26a47e18fff0a39 100644 (file)
@@ -20,6 +20,8 @@ int main(int argc, const char **argv)
                OPT_STRING(0, "string2", &string, "str", "get another string"),
                OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"),
                OPT_STRING('o', NULL, &string, "str", "get another string"),
+               OPT_GROUP("magic arguments"),
+               OPT_ARGUMENT("quux", "means --quux"),
                OPT_END(),
        };
        int i;
diff --git a/thread-utils.c b/thread-utils.c
new file mode 100644 (file)
index 0000000..55e7e29
--- /dev/null
@@ -0,0 +1,48 @@
+#include "cache.h"
+
+#ifdef _WIN32
+#  define WIN32_LEAN_AND_MEAN
+#  include <windows.h>
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+#  include <sys/pstat.h>
+#endif
+
+/*
+ * By doing this in two steps we can at least get
+ * the function to be somewhat coherent, even
+ * with this disgusting nest of #ifdefs.
+ */
+#ifndef _SC_NPROCESSORS_ONLN
+#  ifdef _SC_NPROC_ONLN
+#    define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
+#  elif defined _SC_CRAY_NCPU
+#    define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU
+#  endif
+#endif
+
+int online_cpus(void)
+{
+#ifdef _SC_NPROCESSORS_ONLN
+       long ncpus;
+#endif
+
+#ifdef _WIN32
+       SYSTEM_INFO info;
+       GetSystemInfo(&info);
+
+       if ((int)info.dwNumberOfProcessors > 0)
+               return (int)info.dwNumberOfProcessors;
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+       struct pst_dynamic psd;
+
+       if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0))
+               return (int)psd.psd_proc_cnt;
+#endif
+
+#ifdef _SC_NPROCESSORS_ONLN
+       if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0)
+               return (int)ncpus;
+#endif
+
+       return 1;
+}
diff --git a/thread-utils.h b/thread-utils.h
new file mode 100644 (file)
index 0000000..cce4b77
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef THREAD_COMPAT_H
+#define THREAD_COMPAT_H
+
+extern int online_cpus(void);
+
+#endif /* THREAD_COMPAT_H */
index 97c59dce60eb01382bdd10e4d20cc476fd489cf0..393e0e8fe233ca51dc4c5db94c55e81ee53baa52 100644 (file)
@@ -560,7 +560,10 @@ static int close_bundle(struct transport *transport)
 struct git_transport_data {
        unsigned thin : 1;
        unsigned keep : 1;
+       unsigned followtags : 1;
        int depth;
+       struct child_process *conn;
+       int fd[2];
        const char *uploadpack;
        const char *receivepack;
 };
@@ -578,6 +581,9 @@ static int set_git_option(struct transport *connection,
        } else if (!strcmp(name, TRANS_OPT_THIN)) {
                data->thin = !!value;
                return 0;
+       } else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {
+               data->followtags = !!value;
+               return 0;
        } else if (!strcmp(name, TRANS_OPT_KEEP)) {
                data->keep = !!value;
                return 0;
@@ -591,20 +597,20 @@ static int set_git_option(struct transport *connection,
        return 1;
 }
 
+static int connect_setup(struct transport *transport)
+{
+       struct git_transport_data *data = transport->data;
+       data->conn = git_connect(data->fd, transport->url, data->uploadpack, 0);
+       return 0;
+}
+
 static struct ref *get_refs_via_connect(struct transport *transport)
 {
        struct git_transport_data *data = transport->data;
        struct ref *refs;
-       int fd[2];
-       char *dest = xstrdup(transport->url);
-       struct child_process *conn = git_connect(fd, dest, data->uploadpack, 0);
 
-       get_remote_heads(fd[0], &refs, 0, NULL, 0);
-       packet_flush(fd[1]);
-
-       finish_connect(conn);
-
-       free(dest);
+       connect_setup(transport);
+       get_remote_heads(data->fd[0], &refs, 0, NULL, 0);
 
        return refs;
 }
@@ -615,28 +621,44 @@ static int fetch_refs_via_pack(struct transport *transport,
        struct git_transport_data *data = transport->data;
        char **heads = xmalloc(nr_heads * sizeof(*heads));
        char **origh = xmalloc(nr_heads * sizeof(*origh));
-       struct ref *refs;
+       const struct ref *refs;
        char *dest = xstrdup(transport->url);
        struct fetch_pack_args args;
        int i;
+       struct ref *refs_tmp = NULL;
 
        memset(&args, 0, sizeof(args));
        args.uploadpack = data->uploadpack;
        args.keep_pack = data->keep;
        args.lock_pack = 1;
        args.use_thin_pack = data->thin;
+       args.include_tag = data->followtags;
        args.verbose = transport->verbose > 0;
        args.depth = data->depth;
 
        for (i = 0; i < nr_heads; i++)
                origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
-       refs = fetch_pack(&args, dest, nr_heads, heads, &transport->pack_lockfile);
+
+       if (!data->conn) {
+               connect_setup(transport);
+               get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0);
+       }
+
+       refs = fetch_pack(&args, data->fd, data->conn,
+                         refs_tmp ? refs_tmp : transport->remote_refs,
+                         dest, nr_heads, heads, &transport->pack_lockfile);
+       close(data->fd[0]);
+       close(data->fd[1]);
+       if (finish_connect(data->conn))
+               refs = NULL;
+       data->conn = NULL;
+
+       free_refs(refs_tmp);
 
        for (i = 0; i < nr_heads; i++)
                free(origh[i]);
        free(origh);
        free(heads);
-       free_refs(refs);
        free(dest);
        return (refs ? 0 : -1);
 }
@@ -659,7 +681,15 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const
 
 static int disconnect_git(struct transport *transport)
 {
-       free(transport->data);
+       struct git_transport_data *data = transport->data;
+       if (data->conn) {
+               packet_flush(data->fd[1]);
+               close(data->fd[0]);
+               close(data->fd[1]);
+               finish_connect(data->conn);
+       }
+
+       free(data);
        return 0;
 }
 
@@ -719,6 +749,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
                ret->disconnect = disconnect_git;
 
                data->thin = 1;
+               data->conn = NULL;
                data->uploadpack = "git-upload-pack";
                if (remote && remote->uploadpack)
                        data->uploadpack = remote->uploadpack;
index 6fb4526cda5fc1ee1178fdf1d0840c90b88aa26c..8abfc0ae60c18165b44ef93ed9115972b991e2dc 100644 (file)
@@ -53,6 +53,9 @@ struct transport *transport_get(struct remote *, const char *);
 /* Limit the depth of the fetch if not null */
 #define TRANS_OPT_DEPTH "depth"
 
+/* Aggressively fetch annotated tags if possible */
+#define TRANS_OPT_FOLLOWTAGS "followtags"
+
 /**
  * Returns 0 if the option was used, non-zero otherwise. Prints a
  * message to stderr if the option is not used.
diff --git a/tree.c b/tree.c
index 8c0819fa721771f6dc2e727860345376edb3ea60..4b1825c2adac94ad806d729862c90cf8dfa37e9a 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -142,8 +142,8 @@ static int cmp_cache_name_compare(const void *a_, const void *b_)
 
        ce1 = *((const struct cache_entry **)a_);
        ce2 = *((const struct cache_entry **)b_);
-       return cache_name_compare(ce1->name, ntohs(ce1->ce_flags),
-                                 ce2->name, ntohs(ce2->ce_flags));
+       return cache_name_compare(ce1->name, ce1->ce_flags,
+                                 ce2->name, ce2->ce_flags);
 }
 
 int read_tree(struct tree *tree, int stage, const char **match)
@@ -202,52 +202,6 @@ struct tree *lookup_tree(const unsigned char *sha1)
        return (struct tree *) obj;
 }
 
-/*
- * NOTE! Tree refs to external git repositories
- * (ie gitlinks) do not count as real references.
- *
- * You don't have to have those repositories
- * available at all, much less have the objects
- * accessible from the current repository.
- */
-static void track_tree_refs(struct tree *item)
-{
-       int n_refs = 0, i;
-       struct object_refs *refs;
-       struct tree_desc desc;
-       struct name_entry entry;
-
-       /* Count how many entries there are.. */
-       init_tree_desc(&desc, item->buffer, item->size);
-       while (tree_entry(&desc, &entry)) {
-               if (S_ISGITLINK(entry.mode))
-                       continue;
-               n_refs++;
-       }
-
-       /* Allocate object refs and walk it again.. */
-       i = 0;
-       refs = alloc_object_refs(n_refs);
-       init_tree_desc(&desc, item->buffer, item->size);
-       while (tree_entry(&desc, &entry)) {
-               struct object *obj;
-
-               if (S_ISGITLINK(entry.mode))
-                       continue;
-               if (S_ISDIR(entry.mode))
-                       obj = &lookup_tree(entry.sha1)->object;
-               else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode))
-                       obj = &lookup_blob(entry.sha1)->object;
-               else {
-                       warning("in tree %s: entry %s has bad mode %.6o\n",
-                            sha1_to_hex(item->object.sha1), entry.path, entry.mode);
-                       obj = lookup_unknown_object(entry.sha1);
-               }
-               refs->ref[i++] = obj;
-       }
-       set_object_refs(&item->object, refs);
-}
-
 int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
 {
        if (item->object.parsed)
@@ -256,8 +210,6 @@ int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
        item->buffer = buffer;
        item->size = size;
 
-       if (track_object_refs)
-               track_tree_refs(item);
        return 0;
 }
 
index aa2513ed798969c02fc5e14097666eb4c4c02bae..3e448d8974eb6d738fec2c35cc5a9ffbc8764411 100644 (file)
@@ -85,6 +85,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                int any_dirs = 0;
                char *cache_name;
                int ce_stage;
+               int skip_entry = 0;
 
                /* Find the first name in the input. */
 
@@ -122,13 +123,13 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 
 #if DBRT_DEBUG > 1
                if (first)
-                       printf("index %s\n", first);
+                       fprintf(stderr, "index %s\n", first);
 #endif
                for (i = 0; i < len; i++) {
                        if (!posns[i] || posns[i] == df_conflict_list)
                                continue;
 #if DBRT_DEBUG > 1
-                       printf("%d %s\n", i + 1, posns[i]->name);
+                       fprintf(stderr, "%d %s\n", i + 1, posns[i]->name);
 #endif
                        if (!first || entcmp(first, firstdir,
                                             posns[i]->name,
@@ -153,6 +154,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                        any_files = 1;
                        src[0] = active_cache[o->pos];
                        remove = o->pos;
+                       if (o->skip_unmerged && ce_stage(src[0]))
+                               skip_entry = 1;
                }
 
                for (i = 0; i < len; i++) {
@@ -181,6 +184,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                                continue;
                        }
 
+                       if (skip_entry) {
+                               subposns[i] = df_conflict_list;
+                               posns[i] = posns[i]->next;
+                               continue;
+                       }
+
                        if (!o->merge)
                                ce_stage = 0;
                        else if (i + 1 < o->head_idx)
@@ -205,23 +214,31 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                        posns[i] = posns[i]->next;
                }
                if (any_files) {
-                       if (o->merge) {
+                       if (skip_entry) {
+                               o->pos++;
+                               while (o->pos < active_nr &&
+                                      !strcmp(active_cache[o->pos]->name,
+                                              src[0]->name))
+                                       o->pos++;
+                       } else if (o->merge) {
                                int ret;
 
 #if DBRT_DEBUG > 1
-                               printf("%s:\n", first);
+                               fprintf(stderr, "%s:\n", first);
                                for (i = 0; i < src_size; i++) {
-                                       printf(" %d ", i);
+                                       fprintf(stderr, " %d ", i);
                                        if (src[i])
-                                               printf("%s\n", sha1_to_hex(src[i]->sha1));
+                                               fprintf(stderr, "%06x %s\n", src[i]->ce_mode, sha1_to_hex(src[i]->sha1));
                                        else
-                                               printf("\n");
+                                               fprintf(stderr, "\n");
                                }
 #endif
                                ret = o->fn(src, o, remove);
+                               if (ret < 0)
+                                       return ret;
 
 #if DBRT_DEBUG > 1
-                               printf("Added %d entries\n", ret);
+                               fprintf(stderr, "Added %d entries\n", ret);
 #endif
                                o->pos += ret;
                        } else {
@@ -286,39 +303,40 @@ static void unlink_entry(char *name, char *last_symlink)
 }
 
 static struct checkout state;
-static void check_updates(struct cache_entry **src, int nr,
-                       struct unpack_trees_options *o)
+static void check_updates(struct unpack_trees_options *o)
 {
-       unsigned short mask = htons(CE_UPDATE);
        unsigned cnt = 0, total = 0;
        struct progress *progress = NULL;
        char last_symlink[PATH_MAX];
+       int i;
 
        if (o->update && o->verbose_update) {
-               for (total = cnt = 0; cnt < nr; cnt++) {
-                       struct cache_entry *ce = src[cnt];
-                       if (!ce->ce_mode || ce->ce_flags & mask)
+               for (total = cnt = 0; cnt < active_nr; cnt++) {
+                       struct cache_entry *ce = active_cache[cnt];
+                       if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
                                total++;
                }
 
                progress = start_progress_delay("Checking out files",
-                                               total, 50, 2);
+                                               total, 50, 1);
                cnt = 0;
        }
 
        *last_symlink = '\0';
-       while (nr--) {
-               struct cache_entry *ce = *src++;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
 
-               if (!ce->ce_mode || ce->ce_flags & mask)
+               if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
                        display_progress(progress, ++cnt);
-               if (!ce->ce_mode) {
+               if (ce->ce_flags & CE_REMOVE) {
                        if (o->update)
                                unlink_entry(ce->name, last_symlink);
+                       remove_cache_entry_at(i);
+                       i--;
                        continue;
                }
-               if (ce->ce_flags & mask) {
-                       ce->ce_flags &= ~mask;
+               if (ce->ce_flags & CE_UPDATE) {
+                       ce->ce_flags &= ~CE_UPDATE;
                        if (o->update) {
                                checkout_entry(ce, &state, NULL);
                                *last_symlink = '\0';
@@ -355,23 +373,34 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                        posns[i] = create_tree_entry_list(t+i);
 
                if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
-                                    o, &df_conflict_list))
+                                    o, &df_conflict_list)) {
+                       if (o->gently) {
+                               discard_cache();
+                               read_cache();
+                       }
                        return -1;
+               }
        }
 
-       if (o->trivial_merges_only && o->nontrivial_merge)
-               die("Merge requires file-level merging");
+       if (o->trivial_merges_only && o->nontrivial_merge) {
+               if (o->gently) {
+                       discard_cache();
+                       read_cache();
+               }
+               return o->gently ? -1 :
+                       error("Merge requires file-level merging");
+       }
 
-       check_updates(active_cache, active_nr, o);
+       check_updates(o);
        return 0;
 }
 
 /* Here come the merge functions */
 
-static void reject_merge(struct cache_entry *ce)
+static int reject_merge(struct cache_entry *ce)
 {
-       die("Entry '%s' would be overwritten by merge. Cannot merge.",
-           ce->name);
+       return error("Entry '%s' would be overwritten by merge. Cannot merge.",
+                    ce->name);
 }
 
 static int same(struct cache_entry *a, struct cache_entry *b)
@@ -389,18 +418,18 @@ static int same(struct cache_entry *a, struct cache_entry *b)
  * When a CE gets turned into an unmerged entry, we
  * want it to be up-to-date
  */
-static void verify_uptodate(struct cache_entry *ce,
+static int verify_uptodate(struct cache_entry *ce,
                struct unpack_trees_options *o)
 {
        struct stat st;
 
        if (o->index_only || o->reset)
-               return;
+               return 0;
 
        if (!lstat(ce->name, &st)) {
                unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
                if (!changed)
-                       return;
+                       return 0;
                /*
                 * NEEDSWORK: the current default policy is to allow
                 * submodule to be out of sync wrt the supermodule
@@ -408,13 +437,14 @@ static void verify_uptodate(struct cache_entry *ce,
                 * submodules that are marked to be automatically
                 * checked out.
                 */
-               if (S_ISGITLINK(ntohl(ce->ce_mode)))
-                       return;
+               if (S_ISGITLINK(ce->ce_mode))
+                       return 0;
                errno = 0;
        }
        if (errno == ENOENT)
-               return;
-       die("Entry '%s' not uptodate. Cannot merge.", ce->name);
+               return 0;
+       return o->gently ? -1 :
+               error("Entry '%s' not uptodate. Cannot merge.", ce->name);
 }
 
 static void invalidate_ce_path(struct cache_entry *ce)
@@ -450,7 +480,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
        int cnt = 0;
        unsigned char sha1[20];
 
-       if (S_ISGITLINK(ntohl(ce->ce_mode)) &&
+       if (S_ISGITLINK(ce->ce_mode) &&
            resolve_gitlink_ref(ce->name, "HEAD", sha1) == 0) {
                /* If we are not going to update the submodule, then
                 * we don't care.
@@ -480,8 +510,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                 * ce->name is an entry in the subdirectory.
                 */
                if (!ce_stage(ce)) {
-                       verify_uptodate(ce, o);
-                       ce->ce_mode = 0;
+                       if (verify_uptodate(ce, o))
+                               return -1;
+                       ce->ce_flags |= CE_REMOVE;
                }
                cnt++;
        }
@@ -499,8 +530,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                d.exclude_per_dir = o->dir->exclude_per_dir;
        i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
        if (i)
-               die("Updating '%s' would lose untracked files in it",
-                   ce->name);
+               return o->gently ? -1 :
+                       error("Updating '%s' would lose untracked files in it",
+                             ce->name);
        free(pathbuf);
        return cnt;
 }
@@ -509,26 +541,27 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
  */
-static void verify_absent(struct cache_entry *ce, const char *action,
-               struct unpack_trees_options *o)
+static int verify_absent(struct cache_entry *ce, const char *action,
+                        struct unpack_trees_options *o)
 {
        struct stat st;
 
        if (o->index_only || o->reset || !o->update)
-               return;
+               return 0;
 
        if (has_symlink_leading_path(ce->name, NULL))
-               return;
+               return 0;
 
        if (!lstat(ce->name, &st)) {
                int cnt;
+               int dtype = ce_to_dtype(ce);
 
-               if (o->dir && excluded(o->dir, ce->name))
+               if (o->dir && excluded(o->dir, ce->name, &dtype))
                        /*
                         * ce->name is explicitly excluded, so it is Ok to
                         * overwrite it.
                         */
-                       return;
+                       return 0;
                if (S_ISDIR(st.st_mode)) {
                        /*
                         * We are checking out path "foo" and
@@ -557,7 +590,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
                         * deleted entries here.
                         */
                        o->pos += cnt;
-                       return;
+                       return 0;
                }
 
                /*
@@ -568,19 +601,21 @@ static void verify_absent(struct cache_entry *ce, const char *action,
                cnt = cache_name_pos(ce->name, strlen(ce->name));
                if (0 <= cnt) {
                        struct cache_entry *ce = active_cache[cnt];
-                       if (!ce_stage(ce) && !ce->ce_mode)
-                               return;
+                       if (ce->ce_flags & CE_REMOVE)
+                               return 0;
                }
 
-               die("Untracked working tree file '%s' "
-                   "would be %s by merge.", ce->name, action);
+               return o->gently ? -1 :
+                       error("Untracked working tree file '%s' "
+                             "would be %s by merge.", ce->name, action);
        }
+       return 0;
 }
 
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                struct unpack_trees_options *o)
 {
-       merge->ce_flags |= htons(CE_UPDATE);
+       merge->ce_flags |= CE_UPDATE;
        if (old) {
                /*
                 * See if we can re-use the old CE directly?
@@ -590,18 +625,20 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                 * a match.
                 */
                if (same(old, merge)) {
-                       memcpy(merge, old, offsetof(struct cache_entry, name));
+                       copy_cache_entry(merge, old);
                } else {
-                       verify_uptodate(old, o);
+                       if (verify_uptodate(old, o))
+                               return -1;
                        invalidate_ce_path(old);
                }
        }
        else {
-               verify_absent(merge, "overwritten", o);
+               if (verify_absent(merge, "overwritten", o))
+                       return -1;
                invalidate_ce_path(merge);
        }
 
-       merge->ce_flags &= ~htons(CE_STAGEMASK);
+       merge->ce_flags &= ~CE_STAGEMASK;
        add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
        return 1;
 }
@@ -609,11 +646,13 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
 static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
                struct unpack_trees_options *o)
 {
-       if (old)
-               verify_uptodate(old, o);
-       else
-               verify_absent(ce, "removed", o);
-       ce->ce_mode = 0;
+       if (old) {
+               if (verify_uptodate(old, o))
+                       return -1;
+       } else
+               if (verify_absent(ce, "removed", o))
+                       return -1;
+       ce->ce_flags |= CE_REMOVE;
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
        invalidate_ce_path(ce);
        return 1;
@@ -634,7 +673,7 @@ static void show_stage_entry(FILE *o,
        else
                fprintf(o, "%s%06o %s %d\t%s\n",
                        label,
-                       ntohl(ce->ce_mode),
+                       ce->ce_mode,
                        sha1_to_hex(ce->sha1),
                        ce_stage(ce),
                        ce->name);
@@ -700,16 +739,15 @@ int threeway_merge(struct cache_entry **stages,
        /* #14, #14ALT, #2ALT */
        if (remote && !df_conflict_head && head_match && !remote_match) {
                if (index && !same(index, remote) && !same(index, head))
-                       reject_merge(index);
+                       return o->gently ? -1 : reject_merge(index);
                return merged_entry(remote, index, o);
        }
        /*
         * If we have an entry in the index cache, then we want to
         * make sure that it matches head.
         */
-       if (index && !same(index, head)) {
-               reject_merge(index);
-       }
+       if (index && !same(index, head))
+               return o->gently ? -1 : reject_merge(index);
 
        if (head) {
                /* #5ALT, #15 */
@@ -759,8 +797,10 @@ int threeway_merge(struct cache_entry **stages,
                        remove_entry(remove);
                        if (index)
                                return deleted_entry(index, index, o);
-                       else if (ce && !head_deleted)
-                               verify_absent(ce, "removed", o);
+                       else if (ce && !head_deleted) {
+                               if (verify_absent(ce, "removed", o))
+                                       return -1;
+                       }
                        return 0;
                }
                /*
@@ -776,7 +816,8 @@ int threeway_merge(struct cache_entry **stages,
         * conflict resolution files.
         */
        if (index) {
-               verify_uptodate(index, o);
+               if (verify_uptodate(index, o))
+                       return -1;
        }
 
        remove_entry(remove);
@@ -856,11 +897,11 @@ int twoway_merge(struct cache_entry **src,
                        /* all other failures */
                        remove_entry(remove);
                        if (oldtree)
-                               reject_merge(oldtree);
+                               return o->gently ? -1 : reject_merge(oldtree);
                        if (current)
-                               reject_merge(current);
+                               return o->gently ? -1 : reject_merge(current);
                        if (newtree)
-                               reject_merge(newtree);
+                               return o->gently ? -1 : reject_merge(newtree);
                        return -1;
                }
        }
@@ -887,7 +928,8 @@ int bind_merge(struct cache_entry **src,
                return error("Cannot do a bind merge of %d trees\n",
                             o->merge_size);
        if (a && old)
-               die("Entry '%s' overlaps.  Cannot bind.", a->name);
+               return o->gently ? -1 :
+                       error("Entry '%s' overlaps.  Cannot bind.", a->name);
        if (!a)
                return keep_entry(old, o);
        else
@@ -920,7 +962,7 @@ int oneway_merge(struct cache_entry **src,
                        struct stat st;
                        if (lstat(old->name, &st) ||
                            ce_match_stat(old, &st, CE_MATCH_IGNORE_VALID))
-                               old->ce_flags |= htons(CE_UPDATE);
+                               old->ce_flags |= CE_UPDATE;
                }
                return keep_entry(old, o);
        }
index 5517faafad6ca3f327c81ab6e8e5f2701ff0ad14..a2df544d040adc21f7d854ad50c53e61cf74c9ae 100644 (file)
@@ -16,6 +16,8 @@ struct unpack_trees_options {
        int trivial_merges_only;
        int verbose_update;
        int aggressive;
+       int skip_unmerged;
+       int gently;
        const char *prefix;
        int pos;
        struct dir_struct *dir;
@@ -25,6 +27,7 @@ struct unpack_trees_options {
        int merge_size;
 
        struct cache_entry *df_conflict_entry;
+       void *unpack_data;
 };
 
 extern int unpack_trees(unsigned n, struct tree_desc *t,
index 51e3ec49d120f2e5a095cde45eb0a22bb7e624da..b46dd365ea289c6d397dc6fc994b6ba4227886fc 100644 (file)
@@ -27,7 +27,8 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n
 static unsigned long oldest_have;
 
 static int multi_ack, nr_our_refs;
-static int use_thin_pack, use_ofs_delta, no_progress;
+static int use_thin_pack, use_ofs_delta, use_include_tag;
+static int no_progress;
 static struct object_array have_obj;
 static struct object_array want_obj;
 static unsigned int timeout;
@@ -35,6 +36,7 @@ static unsigned int timeout;
  * otherwise maximum packet size (up to 65520 bytes).
  */
 static int use_sideband;
+static int debug_fd;
 
 static void reset_timeout(void)
 {
@@ -129,7 +131,8 @@ static int do_rev_list(int fd, void *create_full_pack)
                }
                setup_revisions(0, NULL, &revs, NULL);
        }
-       prepare_revision_walk(&revs);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
        mark_edges_uninteresting(revs.commits, &revs, show_edge);
        traverse_commit_list(&revs, show_commit, show_object);
        return 0;
@@ -160,6 +163,8 @@ static void create_pack_file(void)
                argv[arg++] = "--progress";
        if (use_ofs_delta)
                argv[arg++] = "--delta-base-offset";
+       if (use_include_tag)
+               argv[arg++] = "--include-tag";
        argv[arg++] = NULL;
 
        memset(&pack_objects, 0, sizeof(pack_objects));
@@ -392,7 +397,6 @@ static int get_common_commits(void)
        char hex[41], last_hex[41];
        int len;
 
-       track_object_refs = 0;
        save_commit_buffer = 0;
 
        for(;;) {
@@ -444,6 +448,8 @@ static void receive_needs(void)
        static char line[1000];
        int len, depth = 0;
 
+       if (debug_fd)
+               write_in_full(debug_fd, "#S\n", 3);
        for (;;) {
                struct object *o;
                unsigned char sha1_buf[20];
@@ -451,6 +457,8 @@ static void receive_needs(void)
                reset_timeout();
                if (!len)
                        break;
+               if (debug_fd)
+                       write_in_full(debug_fd, line, len);
 
                if (!prefixcmp(line, "shallow ")) {
                        unsigned char sha1[20];
@@ -489,6 +497,8 @@ static void receive_needs(void)
                        use_sideband = DEFAULT_PACKET_MAX;
                if (strstr(line+45, "no-progress"))
                        no_progress = 1;
+               if (strstr(line+45, "include-tag"))
+                       use_include_tag = 1;
 
                /* We have sent all our refs already, and the other end
                 * should have chosen out of them; otherwise they are
@@ -506,6 +516,8 @@ static void receive_needs(void)
                        add_object_array(o, NULL, &want_obj);
                }
        }
+       if (debug_fd)
+               write_in_full(debug_fd, "#E\n", 3);
        if (depth == 0 && shallows.nr == 0)
                return;
        if (depth > 0) {
@@ -533,7 +545,8 @@ static void receive_needs(void)
                                /* make sure the real parents are parsed */
                                unregister_shallow(object->sha1);
                                object->parsed = 0;
-                               parse_commit((struct commit *)object);
+                               if (parse_commit((struct commit *)object))
+                                       die("invalid commit");
                                parents = ((struct commit *)object)->parents;
                                while (parents) {
                                        add_object_array(&parents->item->object,
@@ -557,7 +570,8 @@ static void receive_needs(void)
 static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        static const char *capabilities = "multi_ack thin-pack side-band"
-               " side-band-64k ofs-delta shallow no-progress";
+               " side-band-64k ofs-delta shallow no-progress"
+               " include-tag";
        struct object *o = parse_object(sha1);
 
        if (!o)
@@ -575,7 +589,8 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
        }
        if (o->type == OBJ_TAG) {
                o = deref_tag(o, refname, 0);
-               packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+               if (o)
+                       packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
        }
        return 0;
 }
@@ -629,6 +644,8 @@ int main(int argc, char **argv)
                die("'%s': unable to chdir or not a git archive", dir);
        if (is_repository_shallow())
                die("attempt to fetch/clone from a shallow repository");
+       if (getenv("GIT_DEBUG_SEND_PACK"))
+               debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK"));
        upload_pack();
        return 0;
 }
index adc3e80ce14ab60b585e295d029261f5b53acb51..c10eca88261eed6b0e5c3fc2fdc19b8332c4ac6b 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -256,7 +256,6 @@ int walker_fetch(struct walker *walker, int targets, char **target,
        int i;
 
        save_commit_buffer = 0;
-       track_object_refs = 0;
 
        for (i = 0; i < targets; i++) {
                if (!write_ref || !write_ref[i])
diff --git a/ws.c b/ws.c
index d09b9df89a7e19367640d4c6ad64ff828d01d26f..ba7e834ca819b1d2ccce6cf125aa9f34efea8d5c 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -14,6 +14,7 @@ static struct whitespace_rule {
        { "trailing-space", WS_TRAILING_SPACE },
        { "space-before-tab", WS_SPACE_BEFORE_TAB },
        { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB },
+       { "cr-at-eol", WS_CR_AT_EOL },
 };
 
 unsigned parse_whitespace_rule(const char *string)
@@ -124,6 +125,7 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
        int written = 0;
        int trailing_whitespace = -1;
        int trailing_newline = 0;
+       int trailing_carriage_return = 0;
        int i;
 
        /* Logic is simpler if we temporarily ignore the trailing newline. */
@@ -131,6 +133,11 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
                trailing_newline = 1;
                len--;
        }
+       if ((ws_rule & WS_CR_AT_EOL) &&
+           len > 0 && line[len - 1] == '\r') {
+               trailing_carriage_return = 1;
+               len--;
+       }
 
        /* Check for trailing whitespace. */
        if (ws_rule & WS_TRAILING_SPACE) {
@@ -176,8 +183,10 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
        }
 
        if (stream) {
-               /* Now the rest of the line starts at written.
-                * The non-highlighted part ends at trailing_whitespace. */
+               /*
+                * Now the rest of the line starts at "written".
+                * The non-highlighted part ends at "trailing_whitespace".
+                */
                if (trailing_whitespace == -1)
                        trailing_whitespace = len;
 
@@ -196,8 +205,114 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
                            len - trailing_whitespace, 1, stream);
                        fputs(reset, stream);
                }
+               if (trailing_carriage_return)
+                       fputc('\r', stream);
                if (trailing_newline)
                        fputc('\n', stream);
        }
        return result;
 }
+
+/* Copy the line to the buffer while fixing whitespaces */
+int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *error_count)
+{
+       /*
+        * len is number of bytes to be copied from src, starting
+        * at src.  Typically src[len-1] is '\n', unless this is
+        * the incomplete last line.
+        */
+       int i;
+       int add_nl_to_tail = 0;
+       int add_cr_to_tail = 0;
+       int fixed = 0;
+       int last_tab_in_indent = -1;
+       int last_space_in_indent = -1;
+       int need_fix_leading_space = 0;
+       char *buf;
+
+       /*
+        * Strip trailing whitespace
+        */
+       if ((ws_rule & WS_TRAILING_SPACE) &&
+           (2 <= len && isspace(src[len-2]))) {
+               if (src[len - 1] == '\n') {
+                       add_nl_to_tail = 1;
+                       len--;
+                       if (1 < len && src[len - 1] == '\r') {
+                               add_cr_to_tail = !!(ws_rule & WS_CR_AT_EOL);
+                               len--;
+                       }
+               }
+               if (0 < len && isspace(src[len - 1])) {
+                       while (0 < len && isspace(src[len-1]))
+                               len--;
+                       fixed = 1;
+               }
+       }
+
+       /*
+        * Check leading whitespaces (indent)
+        */
+       for (i = 0; i < len; i++) {
+               char ch = src[i];
+               if (ch == '\t') {
+                       last_tab_in_indent = i;
+                       if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
+                           0 <= last_space_in_indent)
+                           need_fix_leading_space = 1;
+               } else if (ch == ' ') {
+                       last_space_in_indent = i;
+                       if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
+                           8 <= i - last_tab_in_indent)
+                               need_fix_leading_space = 1;
+               } else
+                       break;
+       }
+
+       buf = dst;
+       if (need_fix_leading_space) {
+               /* Process indent ourselves */
+               int consecutive_spaces = 0;
+               int last = last_tab_in_indent + 1;
+
+               if (ws_rule & WS_INDENT_WITH_NON_TAB) {
+                       /* have "last" point at one past the indent */
+                       if (last_tab_in_indent < last_space_in_indent)
+                               last = last_space_in_indent + 1;
+                       else
+                               last = last_tab_in_indent + 1;
+               }
+
+               /*
+                * between src[0..last-1], strip the funny spaces,
+                * updating them to tab as needed.
+                */
+               for (i = 0; i < last; i++) {
+                       char ch = src[i];
+                       if (ch != ' ') {
+                               consecutive_spaces = 0;
+                               *dst++ = ch;
+                       } else {
+                               consecutive_spaces++;
+                               if (consecutive_spaces == 8) {
+                                       *dst++ = '\t';
+                                       consecutive_spaces = 0;
+                               }
+                       }
+               }
+               while (0 < consecutive_spaces--)
+                       *dst++ = ' ';
+               len -= last;
+               src += last;
+               fixed = 1;
+       }
+
+       memcpy(dst, src, len);
+       if (add_cr_to_tail)
+               dst[len++] = '\r';
+       if (add_nl_to_tail)
+               dst[len++] = '\n';
+       if (fixed && error_count)
+               (*error_count)++;
+       return dst + len - buf;
+}
index 991e373785bec5f2aab2d2a09c7d1cb6e3640196..701d13da7c7b2d3de7ca67fbd663d6e9da813151 100644 (file)
@@ -7,9 +7,10 @@
 #include "diff.h"
 #include "revision.h"
 #include "diffcore.h"
+#include "quote.h"
 
 int wt_status_relative_paths = 1;
-int wt_status_use_color = 0;
+int wt_status_use_color = -1;
 static char wt_status_colors[][COLOR_MAXLEN] = {
        "",         /* WT_STATUS_HEADER: normal */
        "\033[32m", /* WT_STATUS_UPDATED: green */
@@ -40,7 +41,7 @@ static int parse_status_slot(const char *var, int offset)
 
 static const char* color(int slot)
 {
-       return wt_status_use_color ? wt_status_colors[slot] : "";
+       return wt_status_use_color > 0 ? wt_status_colors[slot] : "";
 }
 
 void wt_status_prepare(struct wt_status *s)
@@ -82,51 +83,7 @@ static void wt_status_print_trailer(struct wt_status *s)
        color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
 }
 
-static char *quote_path(const char *in, int len,
-                       struct strbuf *out, const char *prefix)
-{
-       if (len < 0)
-               len = strlen(in);
-
-       strbuf_grow(out, len);
-       strbuf_setlen(out, 0);
-       if (prefix) {
-               int off = 0;
-               while (prefix[off] && off < len && prefix[off] == in[off])
-                       if (prefix[off] == '/') {
-                               prefix += off + 1;
-                               in += off + 1;
-                               len -= off + 1;
-                               off = 0;
-                       } else
-                               off++;
-
-               for (; *prefix; prefix++)
-                       if (*prefix == '/')
-                               strbuf_addstr(out, "../");
-       }
-
-       for ( ; len > 0; in++, len--) {
-               int ch = *in;
-
-               switch (ch) {
-               case '\n':
-                       strbuf_addstr(out, "\\n");
-                       break;
-               case '\r':
-                       strbuf_addstr(out, "\\r");
-                       break;
-               default:
-                       strbuf_addch(out, ch);
-                       continue;
-               }
-       }
-
-       if (!out->len)
-               strbuf_addstr(out, "./");
-
-       return out->buf;
-}
+#define quote_path quote_path_relative
 
 static void wt_status_print_filepair(struct wt_status *s,
                                     int t, struct diff_filepair *p)
@@ -217,19 +174,12 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
                wt_status_print_trailer(s);
 }
 
-static void wt_read_cache(struct wt_status *s)
-{
-       discard_cache();
-       read_cache_from(s->index_file);
-}
-
 static void wt_status_print_initial(struct wt_status *s)
 {
        int i;
        struct strbuf buf;
 
        strbuf_init(&buf, 0);
-       wt_read_cache(s);
        if (active_nr) {
                s->commitable = 1;
                wt_status_print_cached_header(s);
@@ -256,7 +206,6 @@ static void wt_status_print_updated(struct wt_status *s)
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 100;
        rev.diffopt.break_opt = 0;
-       wt_read_cache(s);
        run_diff_index(&rev, 1);
 }
 
@@ -268,7 +217,6 @@ static void wt_status_print_changed(struct wt_status *s)
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_print_changed_cb;
        rev.diffopt.format_callback_data = s;
-       wt_read_cache(s);
        run_diff_files(&rev, 0);
 }
 
@@ -335,7 +283,6 @@ static void wt_status_print_verbose(struct wt_status *s)
        setup_revisions(0, NULL, &rev, s->reference);
        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
        rev.diffopt.detect_rename = 1;
-       wt_read_cache(s);
        run_diff_index(&rev, 1);
 
        fflush(stdout);
@@ -411,5 +358,5 @@ int git_status_config(const char *k, const char *v)
                wt_status_relative_paths = git_config_bool(k, v);
                return 0;
        }
-       return git_default_config(k, v);
+       return git_color_default_config(k, v);
 }
index 4b8e5cca804198b0e227454a585fa025281bbe2d..bba236428adb0b34421b9f1b5a3a1728911ee406 100644 (file)
@@ -233,8 +233,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value)
                        expression = value;
                if (regcomp(&reg->re, expression, 0))
                        die("Invalid regexp to look for hunk header: %s", expression);
-               if (buffer)
-                       free(buffer);
+               free(buffer);
                value = ep + 1;
        }
 }
index c00ddaa6e987407743d2c8877f9ca6e772f89c86..413082e1fdf537d230a0f58940cee7466b965d0e 100644 (file)
@@ -53,6 +53,7 @@ extern "C" {
 #define XDL_MERGE_MINIMAL 0
 #define XDL_MERGE_EAGER 1
 #define XDL_MERGE_ZEALOUS 2
+#define XDL_MERGE_ZEALOUS_ALNUM 3
 
 typedef struct s_mmfile {
        char *ptr;
index b83b3348cc3aab66b13cb565a0a0fabaef4b689b..82b3573e7ada8c6df13ac24a78650b80af91ea73 100644 (file)
@@ -248,10 +248,76 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
        return 0;
 }
 
+static int line_contains_alnum(const char *ptr, long size)
+{
+       while (size--)
+               if (isalnum(*(ptr++)))
+                       return 1;
+       return 0;
+}
+
+static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
+{
+       for (; chg; chg--, i++)
+               if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+                               xe->xdf2.recs[i]->size))
+                       return 1;
+       return 0;
+}
+
+/*
+ * This function merges m and m->next, marking everything between those hunks
+ * as conflicting, too.
+ */
+static void xdl_merge_two_conflicts(xdmerge_t *m)
+{
+       xdmerge_t *next_m = m->next;
+       m->chg1 = next_m->i1 + next_m->chg1 - m->i1;
+       m->chg2 = next_m->i2 + next_m->chg2 - m->i2;
+       m->next = next_m->next;
+       free(next_m);
+}
+
+/*
+ * If there are less than 3 non-conflicting lines between conflicts,
+ * it appears simpler -- because it takes up less (or as many) lines --
+ * if the lines are moved into the conflicts.
+ */
+static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
+                                     int simplify_if_no_alnum)
+{
+       int result = 0;
+
+       if (!m)
+               return result;
+       for (;;) {
+               xdmerge_t *next_m = m->next;
+               int begin, end;
+
+               if (!next_m)
+                       return result;
+
+               begin = m->i1 + m->chg1;
+               end = next_m->i1;
+
+               if (m->mode != 0 || next_m->mode != 0 ||
+                   (end - begin > 3 &&
+                    (!simplify_if_no_alnum ||
+                     lines_contain_alnum(xe1, begin, end - begin)))) {
+                       m = next_m;
+               } else {
+                       result++;
+                       xdl_merge_two_conflicts(m);
+               }
+       }
+}
+
 /*
  * level == 0: mark all overlapping changes as conflict
  * level == 1: mark overlapping changes as conflict only if not identical
  * level == 2: analyze non-identical changes for minimal conflict set
+ * level == 3: analyze non-identical changes for minimal conflict set, but
+ *             treat hunks not containing any letter or number as conflicting
  *
  * returns < 0 on error, == 0 for no conflicts, else number of conflicts
  */
@@ -355,7 +421,9 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
        if (!changes)
                changes = c;
        /* refine conflicts */
-       if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) {
+       if (level > 1 &&
+           (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 ||
+            xdl_simplify_non_conflicts(xe1, changes, level > 2) < 0)) {
                xdl_cleanup_merge(changes);
                return -1;
        }