Merge branch 'js/valgrind'
authorJunio C Hamano <gitster@pobox.com>
Thu, 5 Mar 2009 23:41:18 +0000 (15:41 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 5 Mar 2009 23:41:18 +0000 (15:41 -0800)
* js/valgrind:
valgrind: do not require valgrind 3.4.0 or newer
test-lib: avoid assuming that templates/ are in the GIT_EXEC_PATH
Tests: let --valgrind imply --verbose and --tee
Add a script to coalesce the valgrind outputs
t/Makefile: provide a 'valgrind' target
test-lib.sh: optionally output to test-results/$TEST.out, too
Valgrind support: check for more than just programming errors
valgrind: ignore ldso and more libz errors
Add valgrind support in test scripts

201 files changed:
Documentation/RelNotes-1.5.2.2.txt
Documentation/RelNotes-1.6.0.2.txt
Documentation/RelNotes-1.6.1.1.txt
Documentation/RelNotes-1.6.1.2.txt
Documentation/RelNotes-1.6.1.3.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.1.4.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.2.1.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.2.txt
Documentation/RelNotes-1.6.3.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/blame-options.txt
Documentation/config.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-annotate.txt
Documentation/git-apply.txt
Documentation/git-archive.txt
Documentation/git-bisect.txt
Documentation/git-blame.txt
Documentation/git-bundle.txt
Documentation/git-filter-branch.txt
Documentation/git-gc.txt
Documentation/git-imap-send.txt
Documentation/git-push.txt
Documentation/git-rebase.txt
Documentation/git-send-email.txt
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitk.txt
Documentation/howto/revert-a-faulty-merge.txt
Documentation/howto/setup-git-server-over-http.txt
Documentation/mailmap.txt [new file with mode: 0644]
Documentation/pretty-formats.txt
Documentation/rev-list-options.txt
Documentation/technical/api-strbuf.txt
Documentation/urls.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
README
RelNotes
archive.c
branch.c
builtin-add.c
builtin-apply.c
builtin-blame.c
builtin-branch.c
builtin-checkout.c
builtin-clone.c
builtin-commit.c
builtin-fast-export.c
builtin-fsck.c
builtin-gc.c
builtin-ls-files.c
builtin-ls-tree.c
builtin-merge.c
builtin-mv.c
builtin-pack-objects.c
builtin-receive-pack.c
builtin-remote.c
builtin-rev-list.c
builtin-revert.c
builtin-shortlog.c
builtin-symbolic-ref.c
builtin-update-index.c
builtin-upload-archive.c
builtin-verify-pack.c
cache.h
compat/mingw.c
compat/mingw.h
config.c
config.mak.in
contrib/completion/git-completion.bash
contrib/emacs/Makefile
contrib/emacs/README [new file with mode: 0644]
contrib/emacs/git.el
contrib/emacs/vc-git.el [deleted file]
contrib/examples/git-svnimport.perl
contrib/examples/git-svnimport.txt
contrib/fast-import/git-p4
contrib/git-resurrect.sh [new file with mode: 0755]
contrib/hooks/post-receive-email
daemon.c
date.c
diff-lib.c
diff-no-index.c
diff.c
diff.h
dump-cache-tree.c [deleted file]
exec_cmd.c
fast-import.c
git-add--interactive.perl
git-am.sh
git-bisect.sh
git-compat-util.h
git-filter-branch.sh
git-mergetool.sh
git-pull.sh
git-quiltimport.sh
git-rebase.sh
git-repack.sh
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.spec.in
gitk-git/gitk
gitweb/README
gitweb/gitweb.perl
http-push.c
index-pack.c
lockfile.c
log-tree.c
mailmap.c
mailmap.h
merge-index.c
merge-recursive.c
merge-tree.c
mktag.c
mktree.c
pack-write.c
patch-id.c
path.c
pretty.c
refs.c
refs.h
rerere.c
revision.c
setup.c
sha1_file.c
sha1_name.c
string-list.c
string-list.h
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/t0060-path-utils.sh
t/t0100-previous.sh [new file with mode: 0755]
t/t1401-symbolic-ref.sh [new file with mode: 0755]
t/t1450-fsck.sh [new file with mode: 0755]
t/t1500-rev-parse.sh
t/t1501-worktree.sh
t/t1504-ceiling-dirs.sh
t/t2300-cd-to-toplevel.sh
t/t3400-rebase.sh
t/t4013-diff-various.sh
t/t4013/diff.diff_--no-index_dir_dir3 [new file with mode: 0644]
t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.log_--patch-with-stat_master
t/t4013/diff.log_--patch-with-stat_master_--_dir_
t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_master
t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_-p_master
t/t4013/diff.log_--root_master
t/t4013/diff.log_-p_master
t/t4013/diff.log_master
t/t4013/diff.show_master
t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
t/t4020-diff-external.sh
t/t4150-am.sh
t/t4203-mailmap.sh [new file with mode: 0755]
t/t5000-tar-tree.sh
t/t5300-pack-object.sh
t/t5304-prune.sh
t/t5307-pack-missing-commit.sh [new file with mode: 0755]
t/t5400-send-pack.sh
t/t5505-remote.sh
t/t5516-fetch-push.sh
t/t5540-http-push.sh
t/t5601-clone.sh
t/t6030-bisect-porcelain.sh
t/t7001-mv.sh
t/t7003-filter-branch.sh
t/t7400-submodule-basic.sh
t/t7610-mergetool.sh
t/t9131-git-svn-empty-symlink.sh
t/t9135-git-svn-moved-branch-empty-file.sh [new file with mode: 0755]
t/t9135/svn.dump [new file with mode: 0644]
t/t9136-git-svn-recreated-branch-empty-file.sh [new file with mode: 0755]
t/t9136/svn.dump [new file with mode: 0644]
t/t9301-fast-export.sh
t/t9500-gitweb-standalone-no-errors.sh
t/test-lib.sh
templates/hooks--update.sample
templates/this--description
test-dump-cache-tree.c [new file with mode: 0644]
test-path-utils.c
trace.c
tree.c
unpack-file.c
upload-pack.c
utf8.c
utf8.h
walker.c
wrapper.c
index f6393f8a94f454f4f7b5cc5bb42881b6662cadff..7bfa3417506066c67546f601c6cd36cab8c08531 100644 (file)
@@ -45,7 +45,7 @@ Fixes since v1.5.2.1
     correctly when the branch name had slash in it.
 
   - The email address of the user specified with user.email
-    configuration was overriden by EMAIL environment variable.
+    configuration was overridden by EMAIL environment variable.
 
   - The tree parser did not warn about tree entries with
     nonsense file modes, and assumed they must be blobs.
index 7a9646fc4f3241dad0a7b5c699a4e1935d3100f4..51b32f5d94c050f6cb5eae0b94cedda187e00312 100644 (file)
@@ -7,7 +7,7 @@ Fixes since v1.6.0.1
 * Installation on platforms that needs .exe suffix to git-* programs were
   broken in 1.6.0.1.
 
-* Installation on filesystems without symbolic links support did nto
+* Installation on filesystems without symbolic links support did not
   work well.
 
 * In-tree documentations and test scripts now use "git foo" form to set a
index 88454c19734ffccb29864932f1d5f07a55cd2fe7..8c594ba02fe67fc234572456ea980f3e0fcc65e6 100644 (file)
@@ -41,11 +41,11 @@ Fixes since v1.6.1
   work tree upon delete/modify conflict.
 
 * "git merge -s recursive" didn't leave the index unmerged for entries with
-  rename/delete conflictd.
+  rename/delete conflicts.
 
 * "git merge -s recursive" clobbered untracked files in the work tree.
 
-* "git mv -k" with more than one errorneous paths misbehaved.
+* "git mv -k" with more than one erroneous paths misbehaved.
 
 * "git read-tree -m -u" hence branch switching incorrectly lost a
   subdirectory in rare cases.
index 230aa3d8e88e6f3a9281ab4a95ef3c9c61126550..be37cbb8583bf34f3a93864f16f453a1679fabdb 100644 (file)
@@ -4,8 +4,8 @@ GIT v1.6.1.2 Release Notes
 Fixes since v1.6.1.1
 --------------------
 
-* The logic for rename detectin in internal diff used by commands like
-  "git diff" and "git blame" have been optimized to avoid loading the same
+* The logic for rename detection in internal diff used by commands like
+  "git diff" and "git blame" has been optimized to avoid loading the same
   blob repeatedly.
 
 * We did not allow writing out a blob that is larger than 2GB for no good
diff --git a/Documentation/RelNotes-1.6.1.3.txt b/Documentation/RelNotes-1.6.1.3.txt
new file mode 100644 (file)
index 0000000..6f0bde1
--- /dev/null
@@ -0,0 +1,32 @@
+GIT v1.6.1.3 Release Notes
+==========================
+
+Fixes since v1.6.1.2
+--------------------
+
+* "git diff --binary | git apply" pipeline did not work well when
+  a binary blob is changed to a symbolic link.
+
+* Some combinations of -b/-w/--ignore-space-at-eol to "git diff" did
+  not work as expected.
+
+* "git grep" did not pass the -I (ignore binary) option when
+  calling out an external grep program.
+
+* "git log" and friends include HEAD to the set of starting points
+  when --all is given.  This makes a difference when you are not
+  on any branch.
+
+* "git mv" to move an untracked file to overwrite a tracked
+  contents misbehaved.
+
+* "git merge -s octopus" with many potential merge bases did not
+  work correctly.
+
+* RPM binary package installed the html manpages in a wrong place.
+
+Also includes minor documentation fixes and updates.
+
+
+--
+git shortlog --no-merges v1.6.1.2-33-gc789350..
diff --git a/Documentation/RelNotes-1.6.1.4.txt b/Documentation/RelNotes-1.6.1.4.txt
new file mode 100644 (file)
index 0000000..a9f1a6b
--- /dev/null
@@ -0,0 +1,19 @@
+GIT v1.6.1.4 Release Notes
+==========================
+
+Fixes since v1.6.1.3
+--------------------
+
+* "git fast-export" produced wrong output with some parents missing from
+  commits, when the history is clock-skewed.
+
+* "git fast-import" sometimes failed to read back objects it just wrote
+  out and aborted, because it failed to flush stale cached data.
+
+* "git repack" did not error out when necessary object was missing in the
+  repository.
+
+Also includes minor documentation fixes and updates.
+
+--
+git shortlog --no-merges v1.6.1.3..
diff --git a/Documentation/RelNotes-1.6.2.1.txt b/Documentation/RelNotes-1.6.2.1.txt
new file mode 100644 (file)
index 0000000..3a2d3bf
--- /dev/null
@@ -0,0 +1,6 @@
+GIT v1.6.2.1 Release Notes
+==========================
+
+Fixes since v1.6.2
+------------------
+
index 3151c85d880f520fbde338b8eaa0981e9ceaa510..ad060f4f8900dde20b66e1b6ef5b0187cdc82983 100644 (file)
@@ -1,6 +1,28 @@
 GIT v1.6.2 Release Notes
 ========================
 
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+
 Updates since v1.6.1
 --------------------
 
@@ -11,7 +33,9 @@ Updates since v1.6.1
 * gitweb updates, including a new patch view and RSS/Atom feed
   improvements.
 
-(portability)
+* (contrib/emacs) git.el now has commands for checking out a branch,
+  creating a branch, cherry-picking and reverting commits; vc-git.el
+  is not shipped with git anymore (it is part of official Emacs).
 
 (performance)
 
@@ -24,17 +48,36 @@ Updates since v1.6.1
 
 * @{-1} is a way to refer to the last branch you were on.  This is
   accepted not only where an object name is expected, but anywhere
-  a branch name is expected.  E.g. "git branch --track mybranch @{-1}"
-  "git rev-parse --symbolic-full-name @{-1}".
+  a branch name is expected and acts as if you typed the branch name.
+  E.g. "git branch --track mybranch @{-1}", "git merge @{-1}", and
+  "git rev-parse --symbolic-full-name @{-1}" would work as expected.
+
+* When refs/remotes/origin/HEAD points at a remote tracking branch that
+  has been pruned away, many git operations issued warning when they
+  internally enumerated the refs.  We now warn only when you say "origin"
+  to refer to that pruned branch.
+
+* The location of .mailmap file can be configured, and its file format was
+  enhanced to allow mapping an incorrect e-mail field as well.
 
 * "git add -p" learned 'g'oto action to jump directly to a hunk.
 
+* "git add -p" learned to find a hunk with given text with '/'.
+
+* "git add -p" optionally can be told to work with just the command letter
+  without Enter.
+
 * when "git am" stops upon a patch that does not apply, it shows the
   title of the offending patch.
 
 * "git am --directory=<dir>" and "git am --reject" passes these options
   to underlying "git apply".
 
+* "git am" learned --ignore-date option.
+
+* "git blame" aligns author names better when they are spelled in
+  non US-ASCII encoding.
+
 * "git clone" now makes its best effort when cloning from an empty
   repository to set up configuration variables to refer to the remote
   repository.
@@ -48,6 +91,9 @@ Updates since v1.6.1
   the commit log message it serves via gitcvs.commitmsgannotation
   configuration.
 
+* "git cvsserver" learned to handle 'noop' command some CVS clients seem
+  to expect to work.
+
 * "git diff" learned a new option --inter-hunk-context to coalesce close
   hunks together and show context between them.
 
@@ -56,20 +102,16 @@ Updates since v1.6.1
 
 * "git diff" learned --patience to run "patience diff" algorithm.
 
-* Some combinations of -b/-w/--ignore-space-at-eol to "git diff" did
-  not work as expected.
-
 * "git filter-branch" learned --prune-empty option that discards commits
   that do not change the contents.
 
-* "git grep -w" and "git grep" for fixed strings have been optimized.
+* "git fsck" now checks loose objects in alternate object stores, instead
+  of misreporting them as missing.
 
-* "git log" and friends include HEAD to the set of starting points
-  when --all is given.  This makes a difference when you are not on
-  any branch.
+* "git gc --prune" was resurrected to allow "git gc --no-prune" and
+  giving non-default expiration period e.g. "git gc --prune=now".
 
-* "git ls-tree" learned --full-tree option that shows the path in full
-  regardless of where in the work tree hierarchy the command was started.
+* "git grep -w" and "git grep" for fixed strings have been optimized.
 
 * "git mergetool" learned -y(--no-prompt) option to disable prompting.
 
@@ -80,7 +122,10 @@ Updates since v1.6.1
   "git checkout" switches branches, taking the local changes while
   switching to another commit.
 
-(internal)
+* "git submodule update" learned --no-fetch option.
+
+* "git tag" learned --contains that works the same way as the same option
+  from "git branch".
 
 
 Fixes since v1.6.1
@@ -89,18 +134,31 @@ Fixes since v1.6.1
 All of the fixes in v1.6.1.X maintenance series are included in this
 release, unless otherwise noted.
 
+Here are fixes that this release has, but have not been backported to
+v1.6.1.X series.
+
 * "git-add sub/file" when sub is a submodule incorrectly added the path to
   the superproject.
 
-* git-bundle did not exclude annotated tags even when a range given from the
-  command line wanted to.
+* "git bundle" did not exclude annotated tags even when a range given
+  from the command line wanted to.
+
+* "git filter-branch" unnecessarily refused to work when you had
+  checked out a different commit from what is recorded in the superproject
+  index in a submodule.
+
+* "git filter-branch" incorrectly tried to update a nonexistent work tree
+  at the end when it is run in a bare repository.
+
+* "git gc" did not work if your repository was created with an ancient git
+  and never had any pack files in it before.
+
+* "git mergetool" used to ignore autocrlf and other attributes
+  based content rewriting.
 
 * branch switching and merges had a silly bug that did not validate
   the correct directory when making sure an existing subdirectory is
   clean.
 
---
-exec >/var/tmp/1
-O=v1.6.1.2-252-g8c95d3c
-echo O=$(git describe master)
-git shortlog --no-merges $O..master ^maint
+* "git -p cmd" when cmd is not a built-in one left the display in funny state
+  when killed in the middle.
diff --git a/Documentation/RelNotes-1.6.3.txt b/Documentation/RelNotes-1.6.3.txt
new file mode 100644 (file)
index 0000000..2ab2328
--- /dev/null
@@ -0,0 +1,44 @@
+GIT v1.6.3 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+
+Updates since v1.6.2
+--------------------
+
+(subsystems)
+
+(performance)
+
+(usability, bells and whistles)
+
+
+Fixes since v1.6.2
+------------------
+
+All of the fixes in v1.6.2.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.2.X series.
+
index ba07c8c57107a8438d5b3ce1a49761dba7182d25..8d818a21600f386abb02dee902d842a716767027 100644 (file)
@@ -376,9 +376,36 @@ Thunderbird
 
 (A Large Angry SCM)
 
+By default, Thunderbird will both wrap emails as well as flag them as
+being 'format=flowed', both of which will make the resulting email unusable
+by git.
+
 Here are some hints on how to successfully submit patches inline using
 Thunderbird.
 
+There are two different approaches.  One approach is to configure
+Thunderbird to not mangle patches.  The second approach is to use
+an external editor to keep Thunderbird from mangling the patches.
+
+Approach #1 (configuration):
+
+This recipe is current as of Thunderbird 2.0.0.19.  Three steps:
+  1.  Configure your mail server composition as plain text
+      Edit...Account Settings...Composition & Addressing,
+        uncheck 'Compose Messages in HTML'.
+  2.  Configure your general composition window to not wrap
+      Edit..Preferences..Composition, wrap plain text messages at 0
+  3.  Disable the use of format=flowed
+      Edit..Preferences..Advanced..Config Editor.  Search for:
+        mailnews.send_plaintext_flowed
+      toggle it to make sure it is set to 'false'.
+
+After that is done, you should be able to compose email as you
+otherwise would (cut + paste, git-format-patch | git-imap-send, etc),
+and the patches should not be mangled.
+
+Approach #2 (external editor):
+
 This recipe appears to work with the current [*1*] Thunderbird from Suse.
 
 The following Thunderbird extensions are needed:
@@ -464,6 +491,12 @@ message, complete the addressing and subject fields, and press send.
 Gmail
 -----
 
+GMail does not appear to have any way to turn off line wrapping in the web
+interface, so this will mangle any emails that you send.  You can however
+use any IMAP email client to connect to the google imap server, and forward
+the emails through that.  Just make sure to disable line wrapping in that
+email client.  Alternatively, use "git send-email" instead.
+
 Submitting properly formatted patches via Gmail is simple now that
 IMAP support is available. First, edit your ~/.gitconfig to specify your
 account settings:
@@ -476,6 +509,9 @@ account settings:
        port = 993
        sslverify = false
 
+You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error
+that the "Folder doesn't exist".
+
 Next, ensure that your Gmail settings are correct. In "Settings" the
 "Use Unicode (UTF-8) encoding for outgoing messages" should be checked.
 
@@ -486,3 +522,4 @@ command to send the patch emails to your Gmail Drafts folder.
 
 Go to your Gmail account, open the Drafts folder, find the patch email, fill
 in the To: and CC: fields and send away!
+
index 1ab1b96cf9e4c72a7d4e2a8a1d5dd8f016ac9d79..df2a7c164130743b28f110bda3d323e978897bce 100644 (file)
@@ -41,6 +41,13 @@ of lines before or after the line given by <start>.
 -S <revs-file>::
        Use revs from revs-file instead of calling linkgit:git-rev-list[1].
 
+--reverse::
+       Walk history forward instead of backward. Instead of showing
+       the revision in which a line appeared, this shows the last
+       revision in which a line has existed. This requires a range of
+       revision like START..END where the path to blame exists in
+       START.
+
 -p::
 --porcelain::
        Show in a format designed for machine consumption.
@@ -67,7 +74,7 @@ of lines before or after the line given by <start>.
        Detect moving lines in the file as well.  When a commit
        moves a block of lines in a file (e.g. the original file
        has A and then B, and the commit changes it to B and
-       then A), traditional 'blame' algorithm typically blames
+       then A), the traditional 'blame' algorithm typically blames
        the lines that were moved up (i.e. B) to the parent and
        assigns blame to the lines that were moved down (i.e. A)
        to the child commit.  With this option, both groups of lines
@@ -83,8 +90,8 @@ commit.
        files that were modified in the same commit.  This is
        useful when you reorganize your program and move code
        around across files.  When this option is given twice,
-       the command looks for copies from all other files in the
-       parent for the commit that creates the file in addition.
+       the command additionally looks for copies from all other
+       files in the parent for the commit that creates the file.
 +
 <num> is optional but it is the lower bound on the number of
 alphanumeric characters that git must detect as moving
index e2b8775dd308d66027494d8705643c30581e2e65..f5152c5038b49ab5ebe3804e8e9b314d4a752690 100644 (file)
@@ -556,8 +556,8 @@ color.interactive::
 
 color.interactive.<slot>::
        Use customized color for 'git-add --interactive'
-       output. `<slot>` may be `prompt`, `header`, or `help`, for
-       three distinct types of normal output from interactive
+       output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
+       four distinct types of normal output from interactive
        programs.  The values of these variables may be specified as
        in color.branch.<slot>.
 
@@ -1000,6 +1000,13 @@ instaweb.port::
        The port number to bind the gitweb httpd to. See
        linkgit:git-instaweb[1].
 
+interactive.singlekey::
+       In interactive programs, allow the user to provide one-letter
+       input with a single key (i.e., without hitting enter).
+       Currently this is used only by the `\--patch` mode of
+       linkgit:git-add[1].  Note that this setting is silently
+       ignored if portable keystroke input is not available.
+
 log.date::
        Set default date-time mode for the log command. Setting log.date
        value is similar to using 'git-log'\'s --date option. The value is one of the
@@ -1012,6 +1019,14 @@ log.showroot::
        Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
        normally hide the root commit will now show it. True by default.
 
+mailmap.file::
+       The location of an augmenting mailmap file. The default
+       mailmap, located in the root of the repository, is loaded
+       first, then the mailmap file pointed to by this variable.
+       The location of the mailmap file may be in a repository
+       subdirectory, or somewhere outside of the repository itself.
+       See linkgit:git-shortlog[1] and linkgit:git-blame[1].
+
 man.viewer::
        Specify the programs that may be used to display help in the
        'man' format. See linkgit:git-help[1].
index 7c129cb24f0e1c902a81b996ce84ab7e614079c4..e4c711bbd2d1a95d501978673bc52f7a2957fba7 100644 (file)
@@ -136,7 +136,7 @@ $ git add Documentation/\\*.txt
 ------------
 +
 Note that the asterisk `\*` is quoted from the shell in this
-example; this lets the command to include the files from
+example; this lets the command include the files from
 subdirectories of `Documentation/` directory.
 
 * Considers adding content from all git-*.sh scripts:
@@ -145,7 +145,7 @@ subdirectories of `Documentation/` directory.
 $ git add git-*.sh
 ------------
 +
-Because this example lets shell expand the asterisk (i.e. you are
+Because this example lets the shell expand the asterisk (i.e. you are
 listing the files explicitly), it does not consider
 `subdir/git-foo.sh`.
 
@@ -198,8 +198,8 @@ one deletion).
 
 update::
 
-   This shows the status information and gives prompt
-   "Update>>".  When the prompt ends with double '>>', you can
+   This shows the status information and issues an "Update>>"
+   prompt.  When the prompt ends with double '>>', you can
    make more than one selection, concatenated with whitespace or
    comma.  Also you can say ranges.  E.g. "2-5 7,9" to choose
    2,3,4,5,7,9 from the list.  If the second number in a range is
@@ -238,8 +238,8 @@ add untracked::
 
 patch::
 
-  This lets you choose one path out of 'status' like selection.
-  After choosing the path, it presents diff between the index
+  This lets you choose one path out of 'status' like selection.
+  After choosing the path, it presents the diff between the index
   and the working tree file and asks you if you want to stage
   the change of each hunk.  You can say:
 
index efd311b1ceec1653281441b232da04decd122bc3..1e71dd536b1881b61a007af09f1dedea37fe984e 100644 (file)
@@ -10,7 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git am' [--signoff] [--keep] [--utf8 | --no-utf8]
-        [--3way] [--interactive]
+        [--3way] [--interactive] [--committer-date-is-author-date]
+        [--ignore-date]
         [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
         [--reject]
         [<mbox> | <Maildir>...]
@@ -26,8 +27,8 @@ OPTIONS
 -------
 <mbox>|<Maildir>...::
        The list of mailbox files to read patches from. If you do not
-       supply this argument, reads from the standard input. If you supply
-       directories, they'll be treated as Maildirs.
+       supply this argument, the command reads from the standard input.
+       If you supply directories, they will be treated as Maildirs.
 
 -s::
 --signoff::
@@ -47,7 +48,7 @@ OPTIONS
        preferred encoding if it is not UTF-8).
 +
 This was optional in prior versions of git, but now it is the
-default.   You could use `--no-utf8` to override this.
+default.   You can use `--no-utf8` to override this.
 
 --no-utf8::
        Pass `-n` flag to 'git-mailinfo' (see
@@ -56,8 +57,8 @@ default.   You could use `--no-utf8` to override this.
 -3::
 --3way::
        When the patch does not apply cleanly, fall back on
-       3-way merge, if the patch records the identity of blobs
-       it is supposed to apply to, and we have those blobs
+       3-way merge if the patch records the identity of blobs
+       it is supposed to apply to and we have those blobs
        available locally.
 
 --whitespace=<option>::
@@ -73,6 +74,20 @@ default.   You could use `--no-utf8` to override this.
 --interactive::
        Run interactively.
 
+--committer-date-is-author-date::
+       By default the command records the date from the e-mail
+       message as the commit author date, and uses the time of
+       commit creation as the committer date. This allows the
+       user to lie about the committer date by using the same
+       timestamp as the author date.
+
+--ignore-date::
+       By default the command records the date from the e-mail
+       message as the commit author date, and uses the time of
+       commit creation as the committer date. This allows the
+       user to lie about author timestamp by using the same
+       timestamp as the committer date.
+
 --skip::
        Skip the current patch.  This is only meaningful when
        restarting an aborted patch.
@@ -106,18 +121,18 @@ the commit, after stripping common prefix "[PATCH <anything>]".
 It is supposed to describe what the commit is about concisely as
 a one line text.
 
-The body of the message (iow, after a blank line that terminates
-RFC2822 headers) can begin with "Subject: " and "From: " lines
-that are different from those of the mail header, to override
-the values of these fields.
+The body of the message (the rest of the message after the blank line
+that terminates the RFC2822 headers) can begin with "Subject: " and
+"From: " lines that are different from those of the mail header,
+to override the values of these fields.
 
 The commit message is formed by the title taken from the
 "Subject: ", a blank line and the body of the message up to
-where the patch begins.  Excess whitespaces at the end of the
+where the patch begins.  Excess whitespace characters at the end of the
 lines are automatically stripped.
 
 The patch is expected to be inline, directly following the
-message.  Any line that is of form:
+message.  Any line that is of the form:
 
 * three-dashes and end-of-line, or
 * a line that begins with "diff -", or
@@ -126,18 +141,18 @@ message.  Any line that is of form:
 is taken as the beginning of a patch, and the commit log message
 is terminated before the first occurrence of such a line.
 
-When initially invoking it, you give it names of the mailboxes
-to crunch.  Upon seeing the first patch that does not apply, it
-aborts in the middle,.  You can recover from this in one of two ways:
+When initially invoking it, you give it the names of the mailboxes
+to process.  Upon seeing the first patch that does not apply, it
+aborts in the middle.  You can recover from this in one of two ways:
 
-. skip the current patch by re-running the command with '--skip'
+. skip the current patch by re-running the command with the '--skip'
   option.
 
 . hand resolve the conflict in the working directory, and update
-  the index file to bring it in a state that the patch should
-  have produced.  Then run the command with '--resolved' option.
+  the index file to bring it into a state that the patch should
+  have produced.  Then run the command with the '--resolved' option.
 
-The command refuses to process new mailboxes while `.git/rebase-apply`
+The command refuses to process new mailboxes while the `.git/rebase-apply`
 directory exists, so if you decide to start over from scratch,
 run `rm -f -r .git/rebase-apply` before running the command with mailbox
 names.
index 0aba022ba6838442721a0584f3eaa293a3a90f74..0590eec0566cf9f318ddd1ac27d10bb989b2c52f 100644 (file)
@@ -3,7 +3,7 @@ git-annotate(1)
 
 NAME
 ----
-git-annotate - Annotate file lines with commit info
+git-annotate - Annotate file lines with commit information
 
 SYNOPSIS
 --------
@@ -12,11 +12,11 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Annotates each line in the given file with information from the commit
-which introduced the line. Optionally annotate from a given revision.
+which introduced the line. Optionally annotates from a given revision.
 
 The only difference between this command and linkgit:git-blame[1] is that
 they use slightly different output formats, and this command exists only
-for backward compatibility to support existing scripts, and provide more
+for backward compatibility to support existing scripts, and provide more
 familiar command name for people coming from other SCM systems.
 
 OPTIONS
index 9400f6a5d050c1e3d0a37fb147e00c9ad8ca799e..9e5baa277731adf14e47fda8c6b13a076e0873db 100644 (file)
@@ -25,7 +25,7 @@ and a work tree.
 OPTIONS
 -------
 <patch>...::
-       The files to read patch from.  '-' can be used to read
+       The files to read the patch from.  '-' can be used to read
        from the standard input.
 
 --stat::
@@ -33,8 +33,8 @@ OPTIONS
        input.  Turns off "apply".
 
 --numstat::
-       Similar to \--stat, but shows number of added and
-       deleted lines in decimal notation and pathname without
+       Similar to \--stat, but shows the number of added and
+       deleted lines in decimal notation and the pathname without
        abbreviation, to make it more machine friendly.  For
        binary files, outputs two `-` instead of saying
        `0 0`.  Turns off "apply".
@@ -60,15 +60,15 @@ OPTIONS
        causes the index file to be updated.
 
 --cached::
-       Apply a patch without touching the working tree. Instead, take the
-       cached data, apply the patch, and store the result in the index,
+       Apply a patch without touching the working tree. Instead take the
+       cached data, apply the patch, and store the result in the index
        without using the working tree. This implies '--index'.
 
 --build-fake-ancestor=<file>::
        Newer 'git-diff' output has embedded 'index information'
        for each blob to help identify the original version that
        the patch applies to.  When this flag is given, and if
-       the original versions of the blobs is available locally,
+       the original versions of the blobs are available locally,
        builds a temporary index containing those blobs.
 +
 When a pure mode change is encountered (which has no index information),
@@ -109,13 +109,13 @@ the information is read from the current index instead.
        applying a diff generated with --unified=0. To bypass these
        checks use '--unidiff-zero'.
 +
-Note, for the reasons stated above usage of context-free patches are
+Note, for the reasons stated above usage of context-free patches is
 discouraged.
 
 --apply::
        If you use any of the options marked "Turns off
        'apply'" above, 'git-apply' reads and outputs the
-       information you asked without actually applying the
+       requested information without actually applying the
        patch.  Give this flag after those flags to also apply
        the patch.
 
@@ -124,7 +124,7 @@ discouraged.
        patch.  This can be used to extract the common part between
        two files by first running 'diff' on them and applying
        the result with this option, which would apply the
-       deletion part but not addition part.
+       deletion part but not the addition part.
 
 --allow-binary-replacement::
 --binary::
@@ -159,10 +159,10 @@ on the command line, and ignored if there is any include pattern.
        considered whitespace errors.
 +
 By default, the command outputs warning messages but applies the patch.
-When `git-apply is used for statistics and not applying a
+When `git-apply` is used for statistics and not applying a
 patch, it defaults to `nowarn`.
 +
-You can use different `<action>` to control this
+You can use different `<action>` values to control this
 behavior:
 +
 * `nowarn` turns off the trailing whitespace warning.
@@ -170,7 +170,7 @@ behavior:
   patch as-is (default).
 * `fix` outputs warnings for a few such errors, and applies the
   patch after fixing them (`strip` is a synonym --- the tool
-  used to consider only trailing whitespaces as errors, and the
+  used to consider only trailing whitespace characters as errors, and the
   fix involved 'stripping' them, but modern gits do more).
 * `error` outputs warnings for a few such errors, and refuses
   to apply the patch.
@@ -195,7 +195,7 @@ behavior:
        adjusting the hunk headers appropriately).
 
 --directory=<root>::
-       Prepend <root> to all filenames.  If a "-p" argument was passed, too,
+       Prepend <root> to all filenames.  If a "-p" argument was also passed,
        it is applied before prepending the new root.
 +
 For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh`
@@ -221,7 +221,7 @@ ignored, i.e., they are not required to be up-to-date or clean and they
 are not updated.
 
 If --index is not specified, then the submodule commits in the patch
-are ignored and only the absence of presence of the corresponding
+are ignored and only the absence or presence of the corresponding
 subdirectory is checked and (if possible) updated.
 
 Author
index 41cbf9c0819872a322321455b8a5cb805efcc26b..0eeefe00603814fcabc7d537d5c04e5801591c56 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
+             [--output=<file>]
              [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish>
              [path...]
 
@@ -47,6 +48,9 @@ OPTIONS
 --prefix=<prefix>/::
        Prepend <prefix>/ to each filename in the archive.
 
+--output=<file>::
+       Write the archive to <file> instead of stdout.
+
 <extra>::
        This can be any options that the archiver backend understand.
        See next section.
@@ -88,6 +92,18 @@ tar.umask::
        archiving user's umask will be used instead.  See umask(2) for
        details.
 
+ATTRIBUTES
+----------
+
+export-ignore::
+       Files and directories with the attribute export-ignore won't be
+       added to archive files.  See linkgit:gitattributes[5] for details.
+
+export-subst::
+       If the attribute export-subst is set for a file then git will
+       expand several placeholders when adding this file to an archive.
+       See linkgit:gitattributes[5] for details.
+
 EXAMPLES
 --------
 git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
@@ -110,6 +126,11 @@ git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs
        Put everything in the current head's Documentation/ directory
        into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
 
+
+SEE ALSO
+--------
+linkgit:gitattributes[5]
+
 Author
 ------
 Written by Franck Bui-Huu and Rene Scharfe.
index 147ea381977a459f1fa624408cc3af3e9b9f3bd5..e65c1cae8b3da4efae7571628c1586cbb56151ae 100644 (file)
@@ -212,7 +212,7 @@ If you have a script that can tell if the current source code is good
 or bad, you can automatically bisect using:
 
 ------------
-$ git bisect run my_script
+$ git bisect run my_script arguments
 ------------
 
 Note that the "run" script (`my_script` in the above example) should
@@ -252,6 +252,13 @@ $ git bisect start HEAD v1.2 --      # HEAD is bad, v1.2 is good
 $ git bisect run make                # "make" builds the app
 ------------
 
+* Automatically bisect a test failure between origin and HEAD:
++
+------------
+$ git bisect start HEAD origin --    # HEAD is bad, origin is good
+$ git bisect run make test           # "make test" builds and tests
+------------
+
 * Automatically bisect a broken test suite:
 +
 ------------
@@ -291,6 +298,15 @@ It's safer if both "test.sh" and "check_test_case.sh" scripts are
 outside the repo to prevent interactions between the bisect, make and
 test processes and the scripts.
 
+* Automatically bisect a broken test suite:
++
+------------
+$ git bisect start HEAD HEAD~10 --   # culprit is among the last 10
+$ git bisect run sh -c "make || exit 125; ~/check_test_case.sh"
+------------
++
+Does the same as the previous example, but on a single line.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index fba374d652723161c3683d1be98c08ba573057cc..4ef54d660280c921913a61afe117968d51960ec3 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m]
             [-S <revs-file>] [-M] [-C] [-C] [--since=<date>]
-            [<rev> | --contents <file>] [--] <file>
+           [<rev> | --contents <file> | --reverse <rev>] [--] <file>
 
 DESCRIPTION
 -----------
@@ -184,6 +184,12 @@ there is ever added information (like the commit encoding or extended
 commit commentary), a blame viewer won't ever care.
 
 
+MAPPING AUTHORS
+---------------
+
+include::mailmap.txt[]
+
+
 SEE ALSO
 --------
 linkgit:git-annotate[1]
index 1b66ab743c64d980a43a028d57ca2f6505d97845..57590b148011c44485d28d93d31c6a75a681bfb0 100644 (file)
@@ -84,7 +84,7 @@ defining the basis.  More than one reference may be packaged, and more
 than one basis can be specified.  The objects packaged are those not
 contained in the union of the given bases.  Each basis can be
 specified explicitly (e.g., ^master~10), or implicitly (e.g.,
-master~10..master, master --since=10.days.ago).
+master~10..master, --since=10.days.ago master).
 
 It is very important that the basis used be held by the destination.
 It is okay to err on the side of conservatism, causing the bundle file
@@ -94,75 +94,111 @@ when unpacking at the destination.
 EXAMPLE
 -------
 
-Assume two repositories exist as R1 on machine A, and R2 on machine B.
+Assume you want to transfer the history from a repository R1 on machine A
+to another repository 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:
+To bootstrap the process, you can first create a bundle that doesn't have
+any basis. You can use a tag to remember up to what commit you sent out
+in order to make it easy to later update the other repository with
+incremental bundle,
 
-- Without basis.
-+
-This is useful when sending the whole history.
+----------------
+machineA$ cd R1
+machineA$ git bundle create file.bundle master
+machineA$ git tag -f lastR2bundle master
+----------------
 
-------------
-$ git bundle create mybundle master
-------------
+Then you sneakernet file.bundle to the target machine B. Because you don't
+have to have any object to extract objects from such a bundle, not only
+you can fetch/pull from a bundle, you can clone from it as if it was a
+remote repository.
 
-- Using temporally tags.
-+
-We set a tag in R1 (lastR2bundle) after the previous such transport,
-and move it afterwards to help build the bundle.
+----------------
+machineB$ git clone /home/me/tmp/file.bundle R2
+----------------
 
-------------
-$ git bundle create mybundle master ^lastR2bundle
-$ git tag -f lastR2bundle master
-------------
+This will define a remote called "origin" in the resulting repository that
+lets you fetch and pull from the bundle. $GIT_DIR/config file in R2 may
+have an entry like this:
 
-- Using a tag present in both repositories
+------------------------
+[remote "origin"]
+    url = /home/me/tmp/file.bundle
+    fetch = refs/heads/*:refs/remotes/origin/*
+------------------------
+
+You can fetch/pull to update the resulting mine.git repository after
+replacing the bundle you store at /home/me/tmp/file.bundle with incremental
+updates from here on.
+
+After working more in the original repository, you can create an
+incremental bundle to update the other:
+
+----------------
+machineA$ cd R1
+machineA$ git bundle create file.bundle lastR2bundle..master
+machineA$ git tag -f lastR2bundle master
+----------------
+
+and sneakernet it to the other machine to replace /home/me/tmp/file.bundle,
+and pull from it.
+
+----------------
+machineB$ cd R2
+machineB$ git pull
+----------------
 
-------------
-$ git bundle create mybundle master ^v1.0.0
-------------
+If you know up to what commit the intended recipient repository should
+have the necessary objects for, you can use that knowledge to specify the
+basis, giving a cut-off point to limit the revisions and objects that go
+in the resulting bundle. The previous example used lastR2bundle tag
+for this purpose, but you can use other options you would give to
+the linkgit:git-log[1] command. Here are more examples:
 
-- A basis based on time.
+You can use a tag that is present in both.
 
-------------
-$ git bundle create mybundle master --since=10.days.ago
-------------
+----------------
+$ git bundle create mybundle v1.0.0..master
+----------------
 
-- With a limit on the number of commits
+You can use a basis based on time.
 
-------------
-$ git bundle create mybundle master -n 10
-------------
+----------------
+$ git bundle create mybundle --since=10.days master
+----------------
 
-Then you move mybundle from A to B, and in R2 on B:
+Or you can use the number of commits.
 
-------------
+----------------
+$ git bundle create mybundle -10 master
+----------------
+
+You can run `git-bundle verify` to see if you can extract from a bundle
+that was created with a basis.
+
+----------------
 $ git bundle verify mybundle
-$ git fetch mybundle master:localRef
-------------
+----------------
 
-With something like this in the config in R2:
+This will list what commits you must have in order to extract from the
+bundle and will error out if you don't have them.
 
-------------------------
-[remote "bundle"]
-    url = /home/me/tmp/file.bdl
-    fetch = refs/heads/*:refs/remotes/origin/*
-------------------------
+A bundle from a recipient repository's point of view is just like a
+regular repository it fetches/pulls from. You can for example map
+refs, like this example, when fetching:
 
-You can first sneakernet the bundle file to ~/tmp/file.bdl and
-then these commands on machine B:
+----------------
+$ git fetch mybundle master:localRef
+----------------
 
-------------
-$ git ls-remote bundle
-$ git fetch bundle
-$ git pull bundle
-------------
+Or see what refs it offers.
 
-would treat it as if it is talking with a remote side over the
-network.
+----------------
+$ git ls-remote mybundle
+----------------
 
 Author
 ------
index 451950bab67702a236c55e77d6f3a0723a812488..7ffe03f4279a8ca8110806260f33a58f849351e4 100644 (file)
@@ -212,6 +212,11 @@ git filter-branch --index-filter 'git rm --cached filename' HEAD
 
 Now, you will get the rewritten history saved in HEAD.
 
+As with using `rm filename`, `git rm --cached filename` will fail
+if the file is absent from the tree of a commit.  If it is not important
+whether the file is already absent from the tree, you can use
+`git rm --cached --ignore-unmatch filename` instead.
+
 To rewrite the repository to look as if `foodir/` had been its project
 root, and discard all other history:
 
@@ -334,6 +339,47 @@ git filter-branch --index-filter \
 ---------------------------------------------------------------
 
 
+
+Checklist for Shrinking a Repository
+------------------------------------
+
+git-filter-branch is often used to get rid of a subset of files,
+usually with some combination of `\--index-filter` and
+`\--subdirectory-filter`.  People expect the resulting repository to
+be smaller than the original, but you need a few more steps to
+actually make it smaller, because git tries hard not to lose your
+objects until you tell it to.  First make sure that:
+
+* You really removed all variants of a filename, if a blob was moved
+  over its lifetime.  `git log \--name-only \--follow \--all \--
+  filename` can help you find renames.
+
+* You really filtered all refs: use `\--tag-name-filter cat \--
+  \--all` when calling git-filter-branch.
+
+Then there are two ways to get a smaller repository.  A safer way is
+to clone, that keeps your original intact.
+
+* Clone it with `git clone +++file:///path/to/repo+++`.  The clone
+  will not have the removed objects.  See linkgit:git-clone[1].  (Note
+  that cloning with a plain path just hardlinks everything!)
+
+If you really don't want to clone it, for whatever reasons, check the
+following points instead (in this order).  This is a very destructive
+approach, so *make a backup* or go back to cloning it.  You have been
+warned.
+
+* Remove the original refs backed up by git-filter-branch: say `git
+  for-each-ref \--format="%(refname)" refs/original/ | xargs -n 1 git
+  update-ref -d`.
+
+* Expire all reflogs with `git reflog expire \--expire=now \--all`.
+
+* Garbage collect all unreferenced objects with `git gc \--prune=now`
+  (or if your git-gc is not new enough to support arguments to
+  `\--prune`, use `git repack -ad; git prune` instead).
+
+
 Author
 ------
 Written by Petr "Pasky" Baudis <pasky@suse.cz>,
index 7086eea74a38b036130f61db362bae209a065e47..b292e9843aa9da86cd44bd07d3ce35053be32177 100644 (file)
@@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
 
 SYNOPSIS
 --------
-'git gc' [--aggressive] [--auto] [--quiet]
+'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune]
 
 DESCRIPTION
 -----------
@@ -59,6 +59,14 @@ are consolidated into a single pack by using the `-A` option of
 'git-repack'. Setting `gc.autopacklimit` to 0 disables
 automatic consolidation of packs.
 
+--prune=<date>::
+       Prune loose objects older than date (default is 2 weeks ago,
+       overrideable by the config variable `gc.pruneExpire`).  This
+       option is on by default.
+
+--no-prune::
+       Do not prune any loose objects.
+
 --quiet::
        Suppress all progress reports.
 
index bd49a0aee8881f983077d74bd8d0fbe5764dfea5..1685f04efea3d162377325406e08a762dad94d40 100644 (file)
@@ -98,6 +98,20 @@ Using direct mode with SSL:
 ..........................
 
 
+CAUTION
+-------
+It is still your responsibility to make sure that the email message
+sent by your email program meets the standards of your project.
+Many projects do not like patches to be attached.  Some mail
+agents will transform patches (e.g. wrap lines, send them as
+format=flowed) in ways that make them fail.  You will get angry
+flames ridiculing you if you don't check this.
+
+Thunderbird in particular is known to be problematic.  Thunderbird
+users may wish to visit this web page for more information:
+  http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email
+
+
 BUGS
 ----
 Doesn't handle lines starting with "From " in the message body.
index 7d1eced7d22b8debf46908e39dd6b34b586effe5..4e7e5a719a4b0447159213466d46aa2360b7408e 100644 (file)
@@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
 SYNOPSIS
 --------
 [verse]
-'git push' [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>]
+'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>]
           [--repo=<repository>] [-f | --force] [-v | --verbose]
           [<repository> <refspec>...]
 
@@ -48,17 +48,19 @@ push. Arbitrary expressions cannot be used here, an actual ref must
 be named. If `:`<dst> is omitted, the same ref as <src> will be
 updated.
 +
-The object referenced by <src> is used to fast forward the ref <dst>
-on the remote side. If the optional leading plus `{plus}` is used, the
-remote ref is updated even if it does not result in a fast forward
-update.
+The object referenced by <src> is used to update the <dst> reference
+on the remote side, but by default this is only allowed if the
+update can fast forward <dst>.  By having the optional leading `{plus}`,
+you can tell git to update the <dst> ref even when the update is not a
+fast forward.  This does *not* attempt to merge <src> into <dst>.  See
+EXAMPLES below for details.
 +
 `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
 +
 Pushing an empty <src> allows you to delete the <dst> ref from
 the remote repository.
 +
-The special refspec `:` (or `+:` to allow non-fast forward updates)
+The special refspec `:` (or `{plus}:` to allow non-fast forward updates)
 directs git to push "matching" branches: for every branch that exists on
 the local side, the remote side is updated if a branch of the same name
 already exists on the remote side.  This is the default operation mode
@@ -218,6 +220,30 @@ git push origin :experimental::
        Find a ref that matches `experimental` in the `origin` repository
        (e.g. `refs/heads/experimental`), and delete it.
 
+git push origin {plus}dev:master::
+       Update the origin repository's master branch with the dev branch,
+       allowing non-fast forward updates.  *This can leave unreferenced
+       commits dangling in the origin repository.*  Consider the
+       following situation, where a fast forward is not possible:
++
+----
+           o---o---o---A---B  origin/master
+                    \
+                     X---Y---Z  dev
+----
++
+The above command would change the origin repository to
++
+----
+                     A---B  (unnamed branch)
+                    /
+           o---o---o---X---Y---Z  master
+----
++
+Commits A and B would no longer belong to a branch with a symbolic name,
+and so would be unreachable.  As such, these commits would be removed by
+a `git gc` command on the origin repository.
+
 
 Author
 ------
index 3d6d429e5e28d6a4520c3b54adb3ad065a3b774f..da3c38cd60dc0d143fe91417b742335cfff27809 100644 (file)
@@ -243,9 +243,10 @@ OPTIONS
        context exist they all must match.  By default no context is
        ever ignored.
 
---whitespace=<nowarn|warn|error|error-all|strip>::
+--whitespace=<option>::
        This flag is passed to the 'git-apply' program
        (see linkgit:git-apply[1]) that applies the patch.
+       Incompatible with the --interactive option.
 
 -i::
 --interactive::
index ff4aeff4e6d1df6840b500dafb19fc97e2197d68..66bf3b2fcdccda9cb29d66756b3c20e5a1545d46 100644 (file)
@@ -19,6 +19,19 @@ The header of the email is configurable by command line options.  If not
 specified on the command line, the user will be prompted with a ReadLine
 enabled interface to provide the necessary information.
 
+There are two formats accepted for patch files:
+
+1. mbox format files
++
+This is what linkgit:git-format-patch[1] generates.  Most headers and MIME
+formatting are ignored.
+
+2. The original format used by Greg Kroah-Hartman's 'send_lots_of_email.pl'
+script
++
+This format expects the first line of the file to contain the "Cc:" value
+and the "Subject:" of the message as the second line.
+
 
 OPTIONS
 -------
index 498bd289297803e372cf6bc0079d040c23b0a6bb..42463a955dd3f5dad25f3a45f0eec62d8712f731 100644 (file)
@@ -45,45 +45,16 @@ OPTIONS
        and subsequent lines are indented by `indent2` spaces. `width`,
        `indent1`, and `indent2` default to 76, 6 and 9 respectively.
 
-FILES
------
-
-If a file `.mailmap` exists at the toplevel of the repository,
-it is used to map an author email address to a canonical real name. This
-can be used to coalesce together commits by the same person where their
-name was spelled differently (whether with the same email address or
-not).
-
-Each line in the file consists, in this order, of the canonical real name
-of an author, whitespace, and an email address (enclosed by '<' and '>')
-to map to the name. Use hash '#' for comments, either on their own line,
-or after the email address.
-
-A canonical name may appear in more than one line, associated with
-different email addresses, but it doesn't make sense for a given address
-to appear more than once (if that happens, a later line overrides the
-earlier ones).
-
-So, for example, if your history contains commits by two authors, Jane
-and Joe, whose names appear in the repository under several forms:
-
-------------
-Joe Developer <joe@example.com>
-Joe R. Developer <joe@example.com>
-Jane Doe <jane@example.com>
-Jane Doe <jane@laptop.(none)>
-Jane D. <jane@desktop.(none)>
-------------
-
-Then, supposing Joe wants his middle name initial used, and Jane prefers
-her family name fully spelled out, a proper `.mailmap` file would look like:
-
-------------
-# Note how we don't need an entry for <jane@laptop.(none)>, because the
-# real name of that author is correct already, and coalesced directly.
-Jane Doe <jane@desktop.(none)>
-Joe R. Developer <joe@example.com>
-------------
+
+MAPPING AUTHORS
+---------------
+
+The `.mailmap` feature is used to coalesce together commits by the same
+person in the shortlog, where their name and/or email address was
+spelled differently.
+
+include::mailmap.txt[]
+
 
 Author
 ------
index 8277577a6f896f019793382735a8c0f4a6d5e1c3..7e9ff3762b111d8740a99b676ea516435018f3a8 100644 (file)
@@ -99,12 +99,12 @@ OPTIONS
        will show the revisions given by "git rev-list {caret}master
        topic1 topic2"
 
+-g::
 --reflog[=<n>[,<base>]] [<ref>]::
        Shows <n> most recent ref-log entries for the given
        ref.  If <base> is given, <n> entries going back from
        that entry.  <base> can be specified as count or date.
-       `-g` can be used as a short-hand for this option.  When
-       no explicit <ref> parameter is given, it defaults to the
+       When no explicit <ref> parameter is given, it defaults to the
        current branch (or `HEAD` if it is detached).
 
 Note that --more, --list, --independent and --merge-base options
index 2f207fbbda04c176cedd5a88c88f74e2ac97cf2e..3b8df4467377d73d613f76875c725cbf8544ee77 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git submodule' [--quiet] add [-b branch] [--] <repository> <path>
 'git submodule' [--quiet] status [--cached] [--] [<path>...]
 'git submodule' [--quiet] init [--] [<path>...]
-'git submodule' [--quiet] update [--init] [--] [<path>...]
+'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--] [<path>...]
 'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
 'git submodule' [--quiet] foreach <command>
 'git submodule' [--quiet] sync [--] [<path>...]
@@ -172,6 +172,11 @@ OPTIONS
        (the default). This limit only applies to modified submodules. The
        size is always limited to 1 for added/deleted/typechanged submodules.
 
+-N::
+--no-fetch::
+       This option is only valid for the update command.
+       Don't fetch new objects from the remote site.
+
 <path>...::
        Paths to submodule(s). When specified this will restrict the command
        to only operate on the submodules found at the specified paths.
index 7b654f7928c668abec364a33478ed4be70dfb01a..cda3389331edd615cab316574e4dedc5cc30bd31 100644 (file)
@@ -169,6 +169,10 @@ and have no uncommitted changes.
        reused if a user is later given access to an alternate transport
        method (e.g. `svn+ssh://` or `https://`) for commit.
 
+config key: svn-remote.<name>.commiturl
+
+config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
+
        Using this option for any other purpose (don't ask)
        is very strongly discouraged.
 --
@@ -499,6 +503,14 @@ svn-remote.<name>.rewriteRoot::
        the repository with a public http:// or svn:// URL in the
        metadata so users of it will see the public URL.
 
+svn.brokenSymlinkWorkaround::
+This disables potentially expensive checks to workaround broken symlinks
+checked into SVN by broken clients.  Set this option to "false" if you
+track a SVN repository with many empty blobs that are not symlinks.
+This option may be changed while "git-svn" is running and take effect on
+the next revision fetched.  If unset, git-svn assumes this option to be
+"true".
+
 --
 
 Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps
index cd527c6252a678e0bd5e6fa918771efbfe51bd47..9a26bde73e695effce9741558994e7799e81489a 100644 (file)
@@ -43,9 +43,16 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.6.1.1/git.html[documentation for release 1.6.1.1]
+* link:v1.6.2/git.html[documentation for release 1.6.2]
 
 * release notes for
+  link:RelNotes-1.6.2.txt[1.6.2].
+
+* link:v1.6.1.3/git.html[documentation for release 1.6.1.3]
+
+* release notes for
+  link:RelNotes-1.6.1.3.txt[1.6.1.3],
+  link:RelNotes-1.6.1.2.txt[1.6.1.2],
   link:RelNotes-1.6.1.1.txt[1.6.1.1],
   link:RelNotes-1.6.1.txt[1.6.1].
 
index 227934f59adc42e347cdf4698586d4bca26ca6a3..55668e345f9dd43f2f9850463503cda50f656641 100644 (file)
@@ -18,10 +18,10 @@ A `gitattributes` file is a simple text file that gives
 
 Each line in `gitattributes` file is of form:
 
-       glob    attr1 attr2 ...
+       pattern attr1 attr2 ...
 
-That is, a glob pattern followed by an attributes list,
-separated by whitespaces.  When the glob pattern matches the
+That is, a pattern followed by an attributes list,
+separated by whitespaces.  When the pattern matches the
 path in question, the attributes listed on the line are given to
 the path.
 
@@ -48,13 +48,14 @@ Set to a value::
 
 Unspecified::
 
-       No glob pattern matches the path, and nothing says if
+       No pattern matches the path, and nothing says if
        the path has or does not have the attribute, the
        attribute for the path is said to be Unspecified.
 
-When more than one glob pattern matches the path, a later line
+When more than one pattern matches the path, a later line
 overrides an earlier line.  This overriding is done per
-attribute.
+attribute.  The rules how the pattern matches paths are the
+same as in `.gitignore` files; see linkgit:gitignore[5].
 
 When deciding what attributes are assigned to a path, git
 consults `$GIT_DIR/info/attributes` file (which has the highest
index 4673a75a98da1f778c1323d8e5a4f7faff388589..cf465cb47e1f3fbb7452568591874cf865a16c2c 100644 (file)
@@ -47,7 +47,8 @@ frequently used options.
 
        After an attempt to merge stops with conflicts, show the commits on
        the history between two branches (i.e. the HEAD and the MERGE_HEAD)
-       that modify the conflicted files.
+       that modify the conflicted files and do not exist on all the heads
+       being merged.
 
 --argscmd=<command>::
        Command to be run each time gitk has to determine the list of
@@ -73,7 +74,7 @@ frequently used options.
 <path>...::
 
        Limit commits to the ones touching files in the given paths. Note, to
-       avoid ambiguity wrt. revision names use "--" to separate the paths
+       avoid ambiguity with respect to revision names use "--" to separate the paths
        from any preceding options.
 
 Examples
index 39b1da440a6f80a298ee50af0f1068f45a1565c1..3b4a390005b07c86ee320ee8ca1cf57e46458cb6 100644 (file)
@@ -39,7 +39,7 @@ Such a "revert" of a merge can be made with:
 
     $ git revert -m 1 M
 
-After the develpers of the side branch fixes their mistakes, the history
+After the developers of the side branch fix their mistakes, the history
 may look like this:
 
  ---o---o---o---M---x---x---W---x
@@ -116,7 +116,7 @@ If you reverted the revert in such a case as in the previous example:
               /                 \         /
        ---A---B                   A'--B'--C'
 
-where Y is the revert of W, A' and B'are rerolled A and B, and there may
+where Y is the revert of W, A' and B' are rerolled A and B, and there may
 also be a further fix-up C' on the side branch.  "diff Y^..Y" is similar
 to "diff -R W^..W" (which in turn means it is similar to "diff M^..M"),
 and "diff A'^..C'" by definition would be similar but different from that,
index 40327486084ac02874faff70fd100b619af83214..622ee5c8dd7c384794a21baa6093d85a47f89a54 100644 (file)
@@ -143,7 +143,7 @@ Then, add something like this to your httpd.conf
        Require valid-user
     </Location>
 
-    Debian automatically reads all files under /etc/apach2/conf.d.
+    Debian automatically reads all files under /etc/apache2/conf.d.
 
 The password file can be somewhere else, but it has to be readable by
 Apache and preferably not readable by the world.
diff --git a/Documentation/mailmap.txt b/Documentation/mailmap.txt
new file mode 100644 (file)
index 0000000..e25b154
--- /dev/null
@@ -0,0 +1,75 @@
+If the file `.mailmap` exists at the toplevel of the repository, or at
+the location pointed to by the mailmap.file configuration option, it
+is used to map author and committer names and email addresses to
+canonical real names and email addresses.
+
+In the simple form, each line in the file consists of the canonical
+real name of an author, whitespace, and an email address used in the
+commit (enclosed by '<' and '>') to map to the name. Thus, looks like
+this
+--
+       Proper Name <commit@email.xx>
+--
+
+The more complex forms are
+--
+       <proper@email.xx> <commit@email.xx>
+--
+which allows mailmap to replace only the email part of a commit, and
+--
+       Proper Name <proper@email.xx> <commit@email.xx>
+--
+which allows mailmap to replace both the name and the email of a
+commit matching the specified commit email address, and
+--
+       Proper Name <proper@email.xx> Commit Name <commit@email.xx>
+--
+which allows mailmap to replace both the name and the email of a
+commit matching both the specified commit name and email address.
+
+Example 1: Your history contains commits by two authors, Jane
+and Joe, whose names appear in the repository under several forms:
+
+------------
+Joe Developer <joe@example.com>
+Joe R. Developer <joe@example.com>
+Jane Doe <jane@example.com>
+Jane Doe <jane@laptop.(none)>
+Jane D. <jane@desktop.(none)>
+------------
+
+Now suppose that Joe wants his middle name initial used, and Jane
+prefers her family name fully spelled out. A proper `.mailmap` file
+would look like:
+
+------------
+Jane Doe         <jane@desktop.(none)>
+Joe R. Developer <joe@example.com>
+------------
+
+Note how we don't need an entry for <jane@laptop.(none)>, because the
+real name of that author is correct already.
+
+Example 2: Your repository contains commits from the following
+authors:
+
+------------
+nick1 <bugs@company.xx>
+nick2 <bugs@company.xx>
+nick2 <nick2@company.xx>
+santa <me@company.xx>
+claus <me@company.xx>
+CTO <cto@coompany.xx>
+------------
+
+Then, you might want a `.mailmap` file looking like:
+------------
+<cto@company.xx>                       <cto@coompany.xx>
+Some Dude <some@dude.xx>         nick1 <bugs@company.xx>
+Other Author <other@author.xx>   nick2 <bugs@company.xx>
+Other Author <other@author.xx>         <nick2@company.xx>
+Santa Claus <santa.claus@northpole.xx> <me@company.xx>
+------------
+
+Use hash '#' for comments that are either on their own line, or after
+the email address.
\ No newline at end of file
index 3d87d3edd55e2ea1d73907bbf391d42d9806fae3..159390c35a9413590b83f512c3f9f59e6a275e77 100644 (file)
@@ -101,16 +101,18 @@ The placeholders are:
 - '%P': parent hashes
 - '%p': abbreviated parent hashes
 - '%an': author name
-- '%aN': author name (respecting .mailmap)
+- '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%ae': author email
+- '%aE': author email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%ad': author date (format respects --date= option)
 - '%aD': author date, RFC2822 style
 - '%ar': author date, relative
 - '%at': author date, UNIX timestamp
 - '%ai': author date, ISO 8601 format
 - '%cn': committer name
-- '%cN': committer name (respecting .mailmap)
+- '%cN': committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%ce': committer email
+- '%cE': committer email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%cd': committer date
 - '%cD': committer date, RFC2822 style
 - '%cr': committer date, relative
index b9f6e4d1b7564480fac9c4e355fdc4936f6fa3db..507632287795ed7e63a1311b9cd783e71572ba3e 100644 (file)
@@ -13,7 +13,7 @@ include::pretty-options.txt[]
 
        Synonym for `--date=relative`.
 
---date={relative,local,default,iso,rfc,short}::
+--date={relative,local,default,iso,rfc,short,raw}::
 
        Only takes effect for dates shown in human-readable format, such
        as when using "--pretty". `log.date` config variable sets a default
@@ -31,6 +31,8 @@ format, often found in E-mail messages.
 +
 `--date=short` shows only date but not time, in `YYYY-MM-DD` format.
 +
+`--date=raw` shows the date in the internal raw git format `%s %z` format.
++
 `--date=default` shows timestamps in the original timezone
 (either committer's or author's).
 
index 9a4e3ea92c44b01694c4373f97b6c56c67952fbb..7438149249364ca8837811771b072b20990b3a5d 100644 (file)
@@ -21,7 +21,7 @@ allocated memory or not), use `strbuf_detach()` to unwrap a memory
 buffer from its strbuf shell in a safe way. That is the sole supported
 way. This will give you a malloced buffer that you can later `free()`.
 +
-However, it it totally safe to modify anything in the string pointed by
+However, it is totally safe to modify anything in the string pointed by
 the `buf` member, between the indices `0` and `len-1` (inclusive).
 
 . The `buf` member is a byte array that has at least `len + 1` bytes
@@ -222,7 +222,7 @@ which can be used by the programmer of the callback as she sees fit.
 
        Read a given size of data from a FILE* pointer to the buffer.
 +
-NOTE: The buffer is rewinded if the read fails. If -1 is returned,
+NOTE: The buffer is rewound if the read fails. If -1 is returned,
 `errno` must be consulted, like you would do for `read(3)`.
 `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline()` has the
 same behaviour as well.
index fa34c6747194aaecf9e8124462129b8bbc9ae7d4..5355ebc0f39114823f830e0651078a99f0ac2e70 100644 (file)
@@ -6,10 +6,10 @@ to name the remote repository:
 
 ===============================================================
 - rsync://host.xz/path/to/repo.git/
-- http://host.xz/path/to/repo.git/
-- https://host.xz/path/to/repo.git/
-- git://host.xz/path/to/repo.git/
-- git://host.xz/~user/path/to/repo.git/
+- http://host.xz{startsb}:port{endsb}/path/to/repo.git/
+- https://host.xz{startsb}:port{endsb}/path/to/repo.git/
+- git://host.xz{startsb}:port{endsb}/path/to/repo.git/
+- git://host.xz{startsb}:port{endsb}/~user/path/to/repo.git/
 - ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/path/to/repo.git/
 - ssh://{startsb}user@{endsb}host.xz/path/to/repo.git/
 - ssh://{startsb}user@{endsb}host.xz/~user/path/to/repo.git/
index 19f571ae3bcab2fd96288dfa156062a7fbf89b5e..96af8977f6cae5382728f13116ea24ba2d130bef 100644 (file)
@@ -1507,7 +1507,7 @@ so on a different branch and then coming back), unstash the
 work-in-progress changes.
 
 ------------------------------------------------
-$ git stash "work in progress for foo feature"
+$ git stash save "work in progress for foo feature"
 ------------------------------------------------
 
 This command will save your changes away to the `stash`, and
index 9c9fe640fbd425c4c35c6ecb5b0bf621e0c506d8..97fc1e0519936111beb295cbb345aa206cdd8893 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.1.GIT
+DEF_VER=v1.6.2.GIT
 
 LF='
 '
index 9f3a8ab0977bf23eb276e9cf5a6db96b206893e3..0675c43e73813800d616c3971a450157da707269 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -228,7 +228,7 @@ GITWEB_FAVICON = git-favicon.png
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
-export prefix bindir sharedir htmldir sysconfdir
+export prefix bindir sharedir sysconfdir
 
 CC = gcc
 AR = ar
@@ -317,8 +317,8 @@ PROGRAMS += git-var$X
 # builtin-$C.o but is linked in as part of some other command.
 BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
-BUILT_INS += git-cherry-pick$X
 BUILT_INS += git-cherry$X
+BUILT_INS += git-cherry-pick$X
 BUILT_INS += git-format-patch$X
 BUILT_INS += git-fsck-objects$X
 BUILT_INS += git-get-tar-commit-id$X
@@ -357,8 +357,8 @@ LIB_H += builtin.h
 LIB_H += cache.h
 LIB_H += cache-tree.h
 LIB_H += commit.h
-LIB_H += compat/mingw.h
 LIB_H += compat/cygwin.h
+LIB_H += compat/mingw.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
@@ -383,7 +383,6 @@ LIB_H += pack-refs.h
 LIB_H += pack-revindex.h
 LIB_H += parse-options.h
 LIB_H += patch-ids.h
-LIB_H += string-list.h
 LIB_H += pkt-line.h
 LIB_H += progress.h
 LIB_H += quote.h
@@ -397,6 +396,7 @@ LIB_H += sha1-lookup.h
 LIB_H += sideband.h
 LIB_H += sigchain.h
 LIB_H += strbuf.h
+LIB_H += string-list.h
 LIB_H += tag.h
 LIB_H += transport.h
 LIB_H += tree.h
@@ -435,8 +435,8 @@ LIB_OBJS += diffcore-order.o
 LIB_OBJS += diffcore-pickaxe.o
 LIB_OBJS += diffcore-rename.o
 LIB_OBJS += diff-delta.o
-LIB_OBJS += diff-no-index.o
 LIB_OBJS += diff-lib.o
+LIB_OBJS += diff-no-index.o
 LIB_OBJS += diff.o
 LIB_OBJS += dir.o
 LIB_OBJS += editor.o
@@ -468,9 +468,9 @@ LIB_OBJS += pager.o
 LIB_OBJS += parse-options.o
 LIB_OBJS += patch-delta.o
 LIB_OBJS += patch-ids.o
-LIB_OBJS += string-list.o
 LIB_OBJS += path.o
 LIB_OBJS += pkt-line.o
+LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += progress.o
 LIB_OBJS += quote.o
@@ -484,13 +484,14 @@ LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
-LIB_OBJS += sha1_file.o
 LIB_OBJS += sha1-lookup.o
+LIB_OBJS += sha1_file.o
 LIB_OBJS += sha1_name.o
 LIB_OBJS += shallow.o
 LIB_OBJS += sideband.o
 LIB_OBJS += sigchain.o
 LIB_OBJS += strbuf.o
+LIB_OBJS += string-list.o
 LIB_OBJS += symlinks.o
 LIB_OBJS += tag.o
 LIB_OBJS += trace.o
@@ -499,8 +500,8 @@ LIB_OBJS += tree-diff.o
 LIB_OBJS += tree.o
 LIB_OBJS += tree-walk.o
 LIB_OBJS += unpack-trees.o
-LIB_OBJS += userdiff.o
 LIB_OBJS += usage.o
+LIB_OBJS += userdiff.o
 LIB_OBJS += utf8.o
 LIB_OBJS += walker.o
 LIB_OBJS += wrapper.o
@@ -508,7 +509,6 @@ LIB_OBJS += write_or_die.o
 LIB_OBJS += ws.o
 LIB_OBJS += wt-status.o
 LIB_OBJS += xdiff-interface.o
-LIB_OBJS += preload-index.o
 
 BUILTIN_OBJS += builtin-add.o
 BUILTIN_OBJS += builtin-annotate.o
@@ -649,10 +649,12 @@ endif
 ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
-       ifneq ($(shell expr "$(uname_R)" : '9\.'),2)
+       ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2)
                OLD_ICONV = UnfortunatelyYes
        endif
-       NO_STRLCPY = YesPlease
+       ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2)
+               NO_STRLCPY = YesPlease
+       endif
        NO_MEMMEM = YesPlease
        THREADED_DELTA_SEARCH = YesPlease
 endif
@@ -1382,6 +1384,7 @@ TEST_PROGRAMS += test-chmtime$X
 TEST_PROGRAMS += test-ctype$X
 TEST_PROGRAMS += test-date$X
 TEST_PROGRAMS += test-delta$X
+TEST_PROGRAMS += test-dump-cache-tree$X
 TEST_PROGRAMS += test-genrandom$X
 TEST_PROGRAMS += test-match-trees$X
 TEST_PROGRAMS += test-parse-options$X
@@ -1433,14 +1436,14 @@ remove-dashes:
 
 ### Installation rules
 
-ifeq ($(abspath $(template_dir)),$(template_dir))
+ifneq ($(filter /%,$(firstword $(template_dir))),)
 template_instdir = $(template_dir)
 else
 template_instdir = $(prefix)/$(template_dir)
 endif
 export template_instdir
 
-ifeq ($(abspath $(gitexecdir)),$(gitexecdir))
+ifneq ($(filter /%,$(firstword $(gitexecdir))),)
 gitexec_instdir = $(gitexecdir)
 else
 gitexec_instdir = $(prefix)/$(gitexecdir)
@@ -1465,8 +1468,8 @@ endif
        bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
        execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
        { $(RM) "$$execdir/git-add$X" && \
-               ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \
-               cp git-add$X "$$execdir/git-add$X"; } && \
+               ln "$$bindir/git$X" "$$execdir/git-add$X" 2>/dev/null || \
+               cp "$$bindir/git$X" "$$execdir/git-add$X"; } && \
        { for p in $(filter-out git-add$X,$(BUILT_INS)); do \
                $(RM) "$$execdir/$$p" && \
                ln "$$execdir/git-add$X" "$$execdir/$$p" 2>/dev/null || \
diff --git a/README b/README
index 5fa41b7a18942a68bacb7b488984bdf98f6dfd1a..c932ab310510c84c3e69107b458a228db37cb1f6 100644 (file)
--- a/README
+++ b/README
@@ -24,10 +24,18 @@ It was originally written by Linus Torvalds with help of a group of
 hackers around the net. It is currently maintained by Junio C Hamano.
 
 Please read the file INSTALL for installation instructions.
+
 See Documentation/gittutorial.txt to get started, then see
-Documentation/everyday.txt for a useful minimum set of commands,
-and "man git-commandname" for documentation of each command.
-CVS users may also want to read Documentation/cvs-migration.txt.
+Documentation/everyday.txt for a useful minimum set of commands, and
+Documentation/git-commandname.txt for documentation of each command.
+If git has been correctly installed, then the tutorial can also be
+read with "man gittutorial" or "git help tutorial", and the
+documentation of each command with "man git-commandname" or "git help
+commandname".
+
+CVS users may also want to read Documentation/gitcvs-migration.txt
+("man gitcvs-migration" or "git help cvs-migration" if git is
+installed).
 
 Many Git online resources are accessible from http://git.or.cz/
 including full documentation and Git related tools.
index ecced08731fe83882f94171b14ec18012a07621e..dd8bc4bb4ab75e1148cb9b7aaf8ff7384ebb19ec 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.2.txt
\ No newline at end of file
+Documentation/RelNotes-1.6.3.txt
\ No newline at end of file
index 9ac455d889b72deba8c949da1d9efe2be3a50244..c6aea8358f5b632ee11cb102c1ec1b6c2b317835 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -132,7 +132,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
                err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0);
                if (err)
                        return err;
-               return READ_TREE_RECURSIVE;
+               return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
        }
 
        buffer = sha1_file_to_archive(path_without_prefix, sha1, mode,
@@ -239,6 +239,19 @@ static void parse_treeish_arg(const char **argv,
        ar_args->time = archive_time;
 }
 
+static void create_output_file(const char *output_file)
+{
+       int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+       if (output_fd < 0)
+               die("could not create archive file: %s ", output_file);
+       if (output_fd != 1) {
+               if (dup2(output_fd, 1) < 0)
+                       die("could not redirect output");
+               else
+                       close(output_fd);
+       }
+}
+
 #define OPT__COMPR(s, v, h, p) \
        { OPTION_SET_INT, (s), NULL, (v), NULL, (h), \
          PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, (p) }
@@ -253,6 +266,7 @@ static int parse_archive_args(int argc, const char **argv,
        const char *base = NULL;
        const char *remote = NULL;
        const char *exec = NULL;
+       const char *output = NULL;
        int compression_level = -1;
        int verbose = 0;
        int i;
@@ -262,6 +276,8 @@ static int parse_archive_args(int argc, const char **argv,
                OPT_STRING(0, "format", &format, "fmt", "archive format"),
                OPT_STRING(0, "prefix", &base, "prefix",
                        "prepend prefix to each pathname in the archive"),
+               OPT_STRING(0, "output", &output, "file",
+                       "write the archive to this file"),
                OPT__VERBOSE(&verbose),
                OPT__COMPR('0', &compression_level, "store only", 0),
                OPT__COMPR('1', &compression_level, "compress faster", 1),
@@ -294,6 +310,9 @@ static int parse_archive_args(int argc, const char **argv,
        if (!base)
                base = "";
 
+       if (output)
+               create_output_file(output);
+
        if (list) {
                for (i = 0; i < ARRAY_SIZE(archivers); i++)
                        printf("%s\n", archivers[i].name);
index b1ac837f3d30c826ddbe29492b906bf2d0de0a1a..1f00e44deb28afe74ae4f0b85b23039476032fe5 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -103,14 +103,22 @@ void create_branch(const char *head,
        struct ref_lock *lock;
        struct commit *commit;
        unsigned char sha1[20];
-       char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
+       char *real_ref, msg[PATH_MAX + 20];
+       struct strbuf ref = STRBUF_INIT;
        int forcing = 0;
+       int len;
 
-       snprintf(ref, sizeof ref, "refs/heads/%s", name);
-       if (check_ref_format(ref))
+       len = strlen(name);
+       if (interpret_nth_last_branch(name, &ref) != len) {
+               strbuf_reset(&ref);
+               strbuf_add(&ref, name, len);
+       }
+       strbuf_splice(&ref, 0, 0, "refs/heads/", 11);
+
+       if (check_ref_format(ref.buf))
                die("'%s' is not a valid branch name.", name);
 
-       if (resolve_ref(ref, sha1, 1, NULL)) {
+       if (resolve_ref(ref.buf, sha1, 1, NULL)) {
                if (!force)
                        die("A branch named '%s' already exists.", name);
                else if (!is_bare_repository() && !strcmp(head, name))
@@ -142,7 +150,7 @@ void create_branch(const char *head,
                die("Not a valid branch point: '%s'.", start_name);
        hashcpy(sha1, commit->object.sha1);
 
-       lock = lock_any_ref_for_update(ref, NULL, 0);
+       lock = lock_any_ref_for_update(ref.buf, NULL, 0);
        if (!lock)
                die("Failed to lock ref for update: %s.", strerror(errno));
 
@@ -162,6 +170,7 @@ void create_branch(const char *head,
        if (write_ref_sha1(lock, sha1, msg) < 0)
                die("Failed to write ref: %s.", strerror(errno));
 
+       strbuf_release(&ref);
        free(real_ref);
 }
 
index ac98c8354d84a7f556c22c53fbe9007832ac4346..08443f2f1ecf7d9edd21cec11fa74548c3326df5 100644 (file)
@@ -15,7 +15,7 @@ static const char * const builtin_add_usage[] = {
        "git add [options] [--] <filepattern>...",
        NULL
 };
-static int patch_interactive = 0, add_interactive = 0;
+static int patch_interactive, add_interactive;
 static int take_worktree_changes;
 
 static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
index 1e7f917d3908c0c2f70c7208cb8b0839782849e4..f312798af38553e0badeda9732736a62460eae05 100644 (file)
@@ -2441,7 +2441,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
                return error("%s: %s", old_name, strerror(errno));
        }
 
-       if (!cached)
+       if (!cached && !tpatch)
                st_mode = ce_mode_from_stat(*ce, st->st_mode);
 
        if (patch->is_new < 0)
index aae14ef8bb63abc598e2e3ce99bf64d5c07e0067..114a214ed3fef40ae5cc13737d037f29d6f8acfd 100644 (file)
@@ -19,6 +19,7 @@
 #include "string-list.h"
 #include "mailmap.h"
 #include "parse-options.h"
+#include "utf8.h"
 
 static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
 
@@ -1263,11 +1264,12 @@ struct commit_info
  * Parse author/committer line in the commit object buffer
  */
 static void get_ac_line(const char *inbuf, const char *what,
-                       int bufsz, char *person, const char **mail,
+                       int person_len, char *person,
+                       int mail_len, char *mail,
                        unsigned long *time, const char **tz)
 {
        int len, tzlen, maillen;
-       char *tmp, *endp, *timepos;
+       char *tmp, *endp, *timepos, *mailpos;
 
        tmp = strstr(inbuf, what);
        if (!tmp)
@@ -1278,10 +1280,11 @@ static void get_ac_line(const char *inbuf, const char *what,
                len = strlen(tmp);
        else
                len = endp - tmp;
-       if (bufsz <= len) {
+       if (person_len <= len) {
        error_out:
                /* Ugh */
-               *mail = *tz = "(unknown)";
+               *tz = "(unknown)";
+               strcpy(mail, *tz);
                *time = 0;
                return;
        }
@@ -1304,9 +1307,10 @@ static void get_ac_line(const char *inbuf, const char *what,
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
-       *mail = tmp + 1;
+       mailpos = tmp + 1;
        *tmp = 0;
        maillen = timepos - tmp;
+       memcpy(mail, mailpos, maillen);
 
        if (!mailmap.nr)
                return;
@@ -1315,20 +1319,23 @@ static void get_ac_line(const char *inbuf, const char *what,
         * mailmap expansion may make the name longer.
         * make room by pushing stuff down.
         */
-       tmp = person + bufsz - (tzlen + 1);
+       tmp = person + person_len - (tzlen + 1);
        memmove(tmp, *tz, tzlen);
        tmp[tzlen] = 0;
        *tz = tmp;
 
-       tmp = tmp - (maillen + 1);
-       memmove(tmp, *mail, maillen);
-       tmp[maillen] = 0;
-       *mail = tmp;
-
        /*
-        * Now, convert e-mail using mailmap
+        * Now, convert both name and e-mail using mailmap
         */
-       map_email(&mailmap, tmp + 1, person, tmp-person-1);
+       if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+               /* Add a trailing '>' to email, since map_user returns plain emails
+                  Note: It already has '<', since we replace from mail+1 */
+               mailpos = memchr(mail, '\0', mail_len);
+               if (mailpos && mailpos-mail < mail_len - 1) {
+                       *mailpos = '>';
+                       *(mailpos+1) = '\0';
+               }
+       }
 }
 
 static void get_commit_info(struct commit *commit,
@@ -1337,8 +1344,10 @@ static void get_commit_info(struct commit *commit,
 {
        int len;
        char *tmp, *endp, *reencoded, *message;
-       static char author_buf[1024];
-       static char committer_buf[1024];
+       static char author_name[1024];
+       static char author_mail[1024];
+       static char committer_name[1024];
+       static char committer_mail[1024];
        static char summary_buf[1024];
 
        /*
@@ -1356,9 +1365,11 @@ static void get_commit_info(struct commit *commit,
        }
        reencoded = reencode_commit_message(commit, NULL);
        message   = reencoded ? reencoded : commit->buffer;
-       ret->author = author_buf;
+       ret->author = author_name;
+       ret->author_mail = author_mail;
        get_ac_line(message, "\nauthor ",
-                   sizeof(author_buf), author_buf, &ret->author_mail,
+                   sizeof(author_name), author_name,
+                   sizeof(author_mail), author_mail,
                    &ret->author_time, &ret->author_tz);
 
        if (!detailed) {
@@ -1366,9 +1377,11 @@ static void get_commit_info(struct commit *commit,
                return;
        }
 
-       ret->committer = committer_buf;
+       ret->committer = committer_name;
+       ret->committer_mail = committer_mail;
        get_ac_line(message, "\ncommitter ",
-                   sizeof(committer_buf), committer_buf, &ret->committer_mail,
+                   sizeof(committer_name), committer_name,
+                   sizeof(committer_mail), committer_mail,
                    &ret->committer_time, &ret->committer_tz);
 
        ret->summary = summary_buf;
@@ -1618,13 +1631,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                                printf(" %*d", max_orig_digits,
                                       ent->s_lno + 1 + cnt);
 
-                       if (!(opt & OUTPUT_NO_AUTHOR))
-                               printf(" (%-*.*s %10s",
-                                      longest_author, longest_author,
-                                      ci.author,
+                       if (!(opt & OUTPUT_NO_AUTHOR)) {
+                               int pad = longest_author - utf8_strwidth(ci.author);
+                               printf(" (%s%*s %10s",
+                                      ci.author, pad, "",
                                       format_time(ci.author_time,
                                                   ci.author_tz,
                                                   show_raw_time));
+                       }
                        printf(" %*d) ",
                               max_digits, ent->lno + 1 + cnt);
                }
@@ -1755,7 +1769,7 @@ static void find_alignment(struct scoreboard *sb, int *option)
                if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
                        suspect->commit->object.flags |= METAINFO_SHOWN;
                        get_commit_info(suspect->commit, &ci, 1);
-                       num = strlen(ci.author);
+                       num = utf8_strwidth(ci.author);
                        if (longest_author < num)
                                longest_author = num;
                }
@@ -2394,7 +2408,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                die("reading graft file %s failed: %s",
                    revs_file, strerror(errno));
 
-       read_mailmap(&mailmap, ".mailmap", NULL);
+       read_mailmap(&mailmap, NULL);
 
        if (!incremental)
                setup_pager();
index 56a1971d690e32ca1cb0bc2d66ee05be48bfe565..504a981ad56f73a547c11bf3e18185f67111d000 100644 (file)
@@ -99,6 +99,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
        const char *fmt, *remote;
        int i;
        int ret = 0;
+       struct strbuf bname = STRBUF_INIT;
 
        switch (kinds) {
        case REF_REMOTE_BRANCH:
@@ -119,20 +120,25 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                if (!head_rev)
                        die("Couldn't look up commit object for HEAD");
        }
-       for (i = 0; i < argc; i++) {
-               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) {
+       for (i = 0; i < argc; i++, strbuf_release(&bname)) {
+               int len = strlen(argv[i]);
+
+               if (interpret_nth_last_branch(argv[i], &bname) != len)
+                       strbuf_add(&bname, argv[i], len);
+
+               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
                        error("Cannot delete the branch '%s' "
-                               "which you are currently on.", argv[i]);
+                             "which you are currently on.", bname.buf);
                        ret = 1;
                        continue;
                }
 
                free(name);
 
-               name = xstrdup(mkpath(fmt, argv[i]));
+               name = xstrdup(mkpath(fmt, bname.buf));
                if (!resolve_ref(name, sha1, 1, NULL)) {
                        error("%sbranch '%s' not found.",
-                                       remote, argv[i]);
+                                       remote, bname.buf);
                        ret = 1;
                        continue;
                }
@@ -152,22 +158,23 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                if (!force &&
                    !in_merge_bases(rev, &head_rev, 1)) {
                        error("The branch '%s' is not an ancestor of "
-                               "your current HEAD.\n"
-                               "If you are sure you want to delete it, "
-                               "run 'git branch -D %s'.", argv[i], argv[i]);
+                             "your current HEAD.\n"
+                             "If you are sure you want to delete it, "
+                             "run 'git branch -D %s'.", bname.buf, bname.buf);
                        ret = 1;
                        continue;
                }
 
                if (delete_ref(name, sha1, 0)) {
                        error("Error deleting %sbranch '%s'", remote,
-                              argv[i]);
+                             bname.buf);
                        ret = 1;
                } else {
                        struct strbuf buf = STRBUF_INIT;
-                       printf("Deleted %sbranch %s (%s).\n", remote, argv[i],
-                               find_unique_abbrev(sha1, DEFAULT_ABBREV));
-                       strbuf_addf(&buf, "branch.%s", argv[i]);
+                       printf("Deleted %sbranch %s (%s).\n", remote,
+                              bname.buf,
+                              find_unique_abbrev(sha1, DEFAULT_ABBREV));
+                       strbuf_addf(&buf, "branch.%s", bname.buf);
                        if (git_config_rename_section(buf.buf, NULL) < 0)
                                warning("Update of config-file failed");
                        strbuf_release(&buf);
index 20b34ce6e10d9b863226b501cf5a35178b898995..c315f63398bac5dd3fd465af881caa5f3b9c9184 100644 (file)
@@ -295,6 +295,8 @@ static void show_local_changes(struct object *head)
        init_revisions(&rev, NULL);
        rev.abbrev = 0;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+       if (diff_setup_done(&rev.diffopt) < 0)
+               die("diff_setup_done failed");
        add_pending_object(&rev, head, NULL);
        run_diff_index(&rev, 0);
 }
index f73029e2ba285bfb20bce1760cef711f7a55fd02..c338910b1c76f3c994e4a22c9c0a1da38843e050 100644 (file)
@@ -350,6 +350,19 @@ static struct ref *write_remote_refs(const struct ref *refs,
        return local_refs;
 }
 
+static void install_branch_config(const char *local,
+                                 const char *origin,
+                                 const char *remote)
+{
+       struct strbuf key = STRBUF_INIT;
+       strbuf_addf(&key, "branch.%s.remote", local);
+       git_config_set(key.buf, origin);
+       strbuf_reset(&key);
+       strbuf_addf(&key, "branch.%s.merge", local);
+       git_config_set(key.buf, remote);
+       strbuf_release(&key);
+}
+
 int cmd_clone(int argc, const char **argv, const char *prefix)
 {
        int use_local_hardlinks = 1;
@@ -539,6 +552,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                head_points_at = NULL;
                remote_head = NULL;
                option_no_checkout = 1;
+               if (!option_bare)
+                       install_branch_config("master", option_origin,
+                                             "refs/heads/master");
        }
 
        if (head_points_at) {
@@ -567,11 +583,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                      head_points_at->peer_ref->name,
                                      reflog_msg.buf);
 
-                       strbuf_addf(&key, "branch.%s.remote", head);
-                       git_config_set(key.buf, option_origin);
-                       strbuf_reset(&key);
-                       strbuf_addf(&key, "branch.%s.merge", head);
-                       git_config_set(key.buf, head_points_at->name);
+                       install_branch_config(head, option_origin,
+                                             head_points_at->name);
                }
        } else if (remote_head) {
                /* Source had detached HEAD pointing somewhere. */
index d6a3a6203aee399218c89d31ff5cb28f16dc0cc6..46e649cd7ca0a2ede8f010a6d3bf294f81b85d55 100644 (file)
@@ -561,7 +561,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
                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";
 
@@ -573,16 +572,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
 
                if (get_sha1(parent, sha1))
                        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);
-               }
+               else
+                       commitable = index_differs_from(parent, 0);
        }
 
        fclose(fp);
index e9ee2c79ac0395fde30de329d181733d7be88d40..fdf4ae9ebdba7832a0ac736d56a7b564bba41baa 100644 (file)
@@ -514,6 +514,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
 
        get_tags_and_duplicates(&revs.pending, &extra_refs);
 
+       revs.topo_order = 1;
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
        revs.diffopt.format_callback = show_filemodify;
index aecc8280a0aedb37437ccce446f3382ccba08829..64dffa542170fcceedc766ae6551134f61779893 100644 (file)
@@ -23,6 +23,7 @@ static int check_full;
 static int check_strict;
 static int keep_cache_objects;
 static unsigned char head_sha1[20];
+static const char *head_points_at;
 static int errors_found;
 static int write_lost_and_found;
 static int verbose;
@@ -473,6 +474,8 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
 
 static void get_default_heads(void)
 {
+       if (head_points_at && !is_null_sha1(head_sha1))
+               fsck_handle_ref("HEAD", head_sha1, 0, NULL);
        for_each_ref(fsck_handle_ref, NULL);
        if (include_reflogs)
                for_each_reflog(fsck_handle_reflog, NULL);
@@ -512,14 +515,13 @@ static void fsck_object_dir(const char *path)
 
 static int fsck_head_link(void)
 {
-       unsigned char sha1[20];
        int flag;
        int null_is_error = 0;
-       const char *head_points_at = resolve_ref("HEAD", sha1, 0, &flag);
 
        if (verbose)
                fprintf(stderr, "Checking HEAD link\n");
 
+       head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
        if (!head_points_at)
                return error("Invalid HEAD");
        if (!strcmp(head_points_at, "HEAD"))
@@ -528,7 +530,7 @@ static int fsck_head_link(void)
        else if (prefixcmp(head_points_at, "refs/heads/"))
                return error("HEAD points to something strange (%s)",
                             head_points_at);
-       if (is_null_sha1(sha1)) {
+       if (is_null_sha1(head_sha1)) {
                if (null_is_error)
                        return error("HEAD: detached HEAD points at nothing");
                fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
@@ -584,6 +586,7 @@ static struct option fsck_opts[] = {
 int cmd_fsck(int argc, const char **argv, const char *prefix)
 {
        int i, heads;
+       struct alternate_object_database *alt;
 
        errors_found = 0;
 
@@ -595,17 +598,19 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
 
        fsck_head_link();
        fsck_object_dir(get_object_directory());
+
+       prepare_alt_odb();
+       for (alt = alt_odb_list; alt; alt = alt->next) {
+               char namebuf[PATH_MAX];
+               int namelen = alt->name - alt->base;
+               memcpy(namebuf, alt->base, namelen);
+               namebuf[namelen - 1] = 0;
+               fsck_object_dir(namebuf);
+       }
+
        if (check_full) {
-               struct alternate_object_database *alt;
                struct packed_git *p;
-               prepare_alt_odb();
-               for (alt = alt_odb_list; alt; alt = alt->next) {
-                       char namebuf[PATH_MAX];
-                       int namelen = alt->name - alt->base;
-                       memcpy(namebuf, alt->base, namelen);
-                       namebuf[namelen - 1] = 0;
-                       fsck_object_dir(namebuf);
-               }
+
                prepare_packed_git();
                for (p = packed_git; p; p = p->next)
                        /* verify gives error messages itself */
@@ -624,8 +629,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        heads = 0;
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
-               if (!get_sha1(arg, head_sha1)) {
-                       struct object *obj = lookup_object(head_sha1);
+               unsigned char sha1[20];
+               if (!get_sha1(arg, sha1)) {
+                       struct object *obj = lookup_object(sha1);
 
                        /* Error is printed by lookup_object(). */
                        if (!obj)
index a2014388dad65fab8c7d32baf275295a0e36b210..8d990ed4935804591a17c864e280c0c9b4b7597f 100644 (file)
@@ -161,7 +161,8 @@ static int need_to_gc(void)
         */
        if (too_many_packs())
                append_option(argv_repack,
-                             !strcmp(prune_expire, "now") ? "-a" : "-A",
+                             prune_expire && !strcmp(prune_expire, "now") ?
+                             "-a" : "-A",
                              MAX_ADD);
        else if (!too_many_loose_objects())
                return 0;
@@ -173,14 +174,15 @@ static int need_to_gc(void)
 
 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 (deprecated)"),
+               { OPTION_STRING, 0, "prune", &prune_expire, "date",
+                       "prune unreferenced objects",
+                       PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
                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"),
@@ -218,7 +220,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        "\"git help gc\" for more information.\n");
        } else
                append_option(argv_repack,
-                             !strcmp(prune_expire, "now") ? "-a" : "-A",
+                             prune_expire && !strcmp(prune_expire, "now")
+                             ? "-a" : "-A",
                              MAX_ADD);
 
        if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
@@ -230,9 +233,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
                return error(FAILED_RUN, argv_repack[0]);
 
-       argv_prune[2] = prune_expire;
-       if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_prune[0]);
+       if (prune_expire) {
+               argv_prune[2] = prune_expire;
+               if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
+                       return error(FAILED_RUN, argv_prune[0]);
+       }
 
        if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
                return error(FAILED_RUN, argv_rerere[0]);
index 34340312953867fdcb4ae62d530bdae3162744a5..9dec282fba6ba22e37d13bdc5e35fa4a031433b5 100644 (file)
@@ -262,6 +262,21 @@ static const char *verify_pathspec(const char *prefix)
        return max ? xmemdupz(prev, max) : NULL;
 }
 
+static void strip_trailing_slash_from_submodules(void)
+{
+       const char **p;
+
+       for (p = pathspec; *p != NULL; p++) {
+               int len = strlen(*p), pos;
+
+               if (len < 1 || (*p)[len - 1] != '/')
+                       continue;
+               pos = cache_name_pos(*p, len - 1);
+               if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
+                       *p = xstrndup(*p, len - 1);
+       }
+}
+
 /*
  * Read the tree specified with --with-tree option
  * (typically, HEAD) into stage #1 and then
@@ -510,6 +525,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 
        pathspec = get_pathspec(prefix, argv + i);
 
+       /* be nice with submodule patsh ending in a slash */
+       read_cache();
+       if (pathspec)
+               strip_trailing_slash_from_submodules();
+
        /* Verify that the pathspec matches the prefix */
        if (pathspec)
                prefix = verify_pathspec(prefix);
@@ -533,7 +553,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
              show_killed | show_modified))
                show_cached = 1;
 
-       read_cache();
        if (prefix)
                prune_cache(prefix);
        if (with_tree) {
index 5b63e6eada5cd6de764acef694da624a70ce6dab..fca46312f681caf077565fb4bd5b59a70008eb59 100644 (file)
@@ -68,13 +68,8 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
                 *
                 * Something similar to this incomplete example:
                 *
-               if (show_subprojects(base, baselen, pathname)) {
-                       struct child_process ls_tree;
-
-                       ls_tree.dir = base;
-                       ls_tree.argv = ls-tree;
-                       start_command(&ls_tree);
-               }
+               if (show_subprojects(base, baselen, pathname))
+                       retval = READ_TREE_RECURSIVE;
                 *
                 */
                type = commit_type;
index e4555b01996050db8d2909bc6e6bf5110b2bf4c9..6d2160d0a3db1705d1372029dfa24752d453c8ec 100644 (file)
@@ -36,8 +36,8 @@ struct strategy {
 };
 
 static const char * const builtin_merge_usage[] = {
-       "git-merge [options] <remote>...",
-       "git-merge [options] <msg> HEAD <remote>",
+       "git merge [options] <remote>...",
+       "git merge [options] <msg> HEAD <remote>",
        NULL
 };
 
@@ -356,9 +356,14 @@ static void merge_name(const char *remote, struct strbuf *msg)
        struct object *remote_head;
        unsigned char branch_head[20], buf_sha[20];
        struct strbuf buf = STRBUF_INIT;
+       struct strbuf bname = STRBUF_INIT;
        const char *ptr;
        int len, early;
 
+       len = strlen(remote);
+       if (interpret_nth_last_branch(remote, &bname) == len)
+               remote = bname.buf;
+
        memset(branch_head, 0, sizeof(branch_head));
        remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
        if (!remote_head)
@@ -371,7 +376,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        if (!hashcmp(remote_head->sha1, branch_head)) {
                strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
                        sha1_to_hex(branch_head), remote);
-               return;
+               goto cleanup;
        }
 
        /* See if remote matches <name>^^^.. or <name>~<number> */
@@ -411,7 +416,8 @@ static void merge_name(const char *remote, struct strbuf *msg)
                                    sha1_to_hex(remote_head->sha1),
                                    truname.buf + 11,
                                    (early ? " (early part)" : ""));
-                       return;
+                       strbuf_release(&truname);
+                       goto cleanup;
                }
        }
 
@@ -432,10 +438,13 @@ static void merge_name(const char *remote, struct strbuf *msg)
                        strbuf_remove(&line, ptr-line.buf+1, 13);
                strbuf_addbuf(msg, &line);
                strbuf_release(&line);
-               return;
+               goto cleanup;
        }
        strbuf_addf(msg, "%s\t\tcommit '%s'\n",
                sha1_to_hex(remote_head->sha1), remote);
+cleanup:
+       strbuf_release(&buf);
+       strbuf_release(&bname);
 }
 
 static int git_merge_config(const char *k, const char *v, void *cb)
index bce9959293e30925c4b16c40ac33a3f2e0474e30..01270fefdfb04ed27379b1ca761a811b929ce887 100644 (file)
@@ -162,7 +162,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                }
                                argc += last - first;
                        }
-               } else if (lstat(dst, &st) == 0) {
+               } else if (cache_name_pos(src, length) < 0)
+                       bad = "not under version control";
+               else if (lstat(dst, &st) == 0) {
                        bad = "destination exists";
                        if (force) {
                                /*
@@ -177,9 +179,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                } else
                                        bad = "Cannot overwrite";
                        }
-               } else if (cache_name_pos(src, length) < 0)
-                       bad = "not under version control";
-               else if (string_list_has_string(&src_for_dst, dst))
+               } else if (string_list_has_string(&src_for_dst, dst))
                        bad = "multiple sources for the same target";
                else
                        string_list_insert(dst, &src_for_dst);
index cb51916fe33a904b8ab11b7c43dfe3a84ec11705..bcefa52c69481ceb301a96af6402ca2a029144fb 100644 (file)
@@ -488,9 +488,8 @@ static void write_pack_file(void)
                } else {
                        char tmpname[PATH_MAX];
                        int fd;
-                       snprintf(tmpname, sizeof(tmpname),
-                                "%s/pack/tmp_pack_XXXXXX", get_object_directory());
-                       fd = xmkstemp(tmpname);
+                       fd = odb_mkstemp(tmpname, sizeof(tmpname),
+                                        "pack/tmp_pack_XXXXXX");
                        pack_tmp_name = xstrdup(tmpname);
                        f = sha1fd(fd, pack_tmp_name);
                }
index 6564a97cefe9061ec94f5db6eb945a405376d02f..a970b39505b5dda0bfa32a1a9c14ad676d28ec92 100644 (file)
@@ -9,22 +9,25 @@
 #include "remote.h"
 #include "transport.h"
 
-static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 
 enum deny_action {
+       DENY_UNCONFIGURED,
        DENY_IGNORE,
        DENY_WARN,
        DENY_REFUSE,
 };
 
-static int deny_deletes = 0;
-static int deny_non_fast_forwards = 0;
-static enum deny_action deny_current_branch = DENY_WARN;
+static int deny_deletes;
+static int deny_non_fast_forwards;
+static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
+static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
 static int receive_fsck_objects;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
 static int report_status;
+static const char *head_name;
 
 static char capabilities[] = " report-status delete-refs ";
 static int capabilities_sent;
@@ -76,6 +79,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "receive.denydeletecurrent") == 0) {
+               deny_delete_current = parse_deny_action(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -202,16 +210,67 @@ static int run_update_hook(struct command *cmd)
 
 static int is_ref_checked_out(const char *ref)
 {
-       unsigned char sha1[20];
-       const char *head;
-
        if (is_bare_repository())
                return 0;
 
-       head = resolve_ref("HEAD", sha1, 0, NULL);
-       if (!head)
+       if (!head_name)
                return 0;
-       return !strcmp(head, ref);
+       return !strcmp(head_name, ref);
+}
+
+static char *warn_unconfigured_deny_msg[] = {
+       "Updating the currently checked out branch may cause confusion,",
+       "as the index and work tree do not reflect changes that are in HEAD.",
+       "As a result, you may see the changes you just pushed into it",
+       "reverted when you run 'git diff' over there, and you may want",
+       "to run 'git reset --hard' before starting to work to recover.",
+       "",
+       "You can set 'receive.denyCurrentBranch' configuration variable to",
+       "'refuse' in the remote repository to forbid pushing into its",
+       "current branch."
+       "",
+       "To allow pushing into the current branch, you can set it to 'ignore';",
+       "but this is not recommended unless you arranged to update its work",
+       "tree to match what you pushed in some other way.",
+       "",
+       "To squelch this message, you can set it to 'warn'.",
+       "",
+       "Note that the default will change in a future version of git",
+       "to refuse updating the current branch unless you have the",
+       "configuration variable set to either 'ignore' or 'warn'."
+};
+
+static void warn_unconfigured_deny(void)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
+               warning("%s", warn_unconfigured_deny_msg[i]);
+}
+
+static char *warn_unconfigured_deny_delete_current_msg[] = {
+       "Deleting the current branch can cause confusion by making the next",
+       "'git clone' not check out any file.",
+       "",
+       "You can set 'receive.denyDeleteCurrent' configuration variable to",
+       "'refuse' in the remote repository to disallow deleting the current",
+       "branch.",
+       "",
+       "You can set it to 'ignore' to allow such a delete without a warning.",
+       "",
+       "To make this warning message less loud, you can set it to 'warn'.",
+       "",
+       "Note that the default will change in a future version of git",
+       "to refuse deleting the current branch unless you have the",
+       "configuration variable set to either 'ignore' or 'warn'."
+};
+
+static void warn_unconfigured_deny_delete_current(void)
+{
+       int i;
+       for (i = 0;
+            i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
+            i++)
+               warning("%s", warn_unconfigured_deny_delete_current_msg[i]);
 }
 
 static const char *update(struct command *cmd)
@@ -227,22 +286,20 @@ static const char *update(struct command *cmd)
                return "funny refname";
        }
 
-       switch (deny_current_branch) {
-       case DENY_IGNORE:
-               break;
-       case DENY_WARN:
-               if (!is_ref_checked_out(name))
+       if (is_ref_checked_out(name)) {
+               switch (deny_current_branch) {
+               case DENY_IGNORE:
                        break;
-               warning("updating the currently checked out branch; this may"
-                       " cause confusion,\n"
-                       "as the index and working tree do not reflect changes"
-                       " that are now in HEAD.");
-               break;
-       case DENY_REFUSE:
-               if (!is_ref_checked_out(name))
+               case DENY_UNCONFIGURED:
+               case DENY_WARN:
+                       warning("updating the current branch");
+                       if (deny_current_branch == DENY_UNCONFIGURED)
+                               warn_unconfigured_deny();
                        break;
-               error("refusing to update checked out branch: %s", name);
-               return "branch is currently checked out";
+               case DENY_REFUSE:
+                       error("refusing to update checked out branch: %s", name);
+                       return "branch is currently checked out";
+               }
        }
 
        if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
@@ -250,12 +307,30 @@ static const char *update(struct command *cmd)
                      "but I can't find it!", sha1_to_hex(new_sha1));
                return "bad pack";
        }
-       if (deny_deletes && is_null_sha1(new_sha1) &&
-           !is_null_sha1(old_sha1) &&
-           !prefixcmp(name, "refs/heads/")) {
-               error("denying ref deletion for %s", name);
-               return "deletion prohibited";
+
+       if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
+               if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
+                       error("denying ref deletion for %s", name);
+                       return "deletion prohibited";
+               }
+
+               if (!strcmp(name, head_name)) {
+                       switch (deny_delete_current) {
+                       case DENY_IGNORE:
+                               break;
+                       case DENY_WARN:
+                       case DENY_UNCONFIGURED:
+                               if (deny_delete_current == DENY_UNCONFIGURED)
+                                       warn_unconfigured_deny_delete_current();
+                               warning("deleting the current branch");
+                               break;
+                       case DENY_REFUSE:
+                               error("refusing to delete the current branch: %s", name);
+                               return "deletion of the current branch prohibited";
+                       }
+               }
        }
+
        if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
            !is_null_sha1(old_sha1) &&
            !prefixcmp(name, "refs/heads/")) {
@@ -349,6 +424,7 @@ static void run_update_post_hook(struct command *cmd)
 static void execute_commands(const char *unpacker_error)
 {
        struct command *cmd = commands;
+       unsigned char sha1[20];
 
        if (unpacker_error) {
                while (cmd) {
@@ -366,6 +442,8 @@ static void execute_commands(const char *unpacker_error)
                return;
        }
 
+       head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
        while (cmd) {
                cmd->error_string = update(cmd);
                cmd = cmd->next;
@@ -597,7 +675,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        setup_path();
 
        if (!enter_repo(dir, 0))
-               die("'%s': unable to chdir or not a git archive", dir);
+               die("'%s' does not appear to be a git repository", dir);
 
        if (is_repository_shallow())
                die("attempt to push into a shallow repository");
index abc8dd8389be4a51b467b4f6d4f74e2037d65423..ac69d37c8af415a64cba88a888e9b80db95ef97a 100644 (file)
@@ -298,7 +298,7 @@ static int add_known_remote(struct remote *remote, void *cb_data)
 
 struct branches_for_remote {
        struct remote *remote;
-       struct string_list *branches;
+       struct string_list *branches, *skipped;
        struct known_remotes *keep;
 };
 
@@ -323,6 +323,16 @@ static int add_branch_for_removal(const char *refname,
                        return 0;
        }
 
+       /* don't delete non-remote refs */
+       if (prefixcmp(refname, "refs/remotes")) {
+               /* advise user how to delete local branches */
+               if (!prefixcmp(refname, "refs/heads/"))
+                       string_list_append(abbrev_branch(refname),
+                                          branches->skipped);
+               /* silently skip over other non-remote refs */
+               return 0;
+       }
+
        /* make sure that symrefs are deleted */
        if (flags & REF_ISSYMREF)
                return unlink(git_path("%s", refname));
@@ -542,8 +552,11 @@ static int rm(int argc, const char **argv)
        struct strbuf buf = STRBUF_INIT;
        struct known_remotes known_remotes = { NULL, NULL };
        struct string_list branches = { NULL, 0, 0, 1 };
-       struct branches_for_remote cb_data = { NULL, &branches, &known_remotes };
-       int i;
+       struct string_list skipped = { NULL, 0, 0, 1 };
+       struct branches_for_remote cb_data = {
+               NULL, &branches, &skipped, &known_remotes
+       };
+       int i, result;
 
        if (argc != 2)
                usage_with_options(builtin_remote_usage, options);
@@ -583,14 +596,26 @@ static int rm(int argc, const char **argv)
         * refs, which are invalidated when deleting a branch.
         */
        cb_data.remote = remote;
-       i = for_each_ref(add_branch_for_removal, &cb_data);
+       result = for_each_ref(add_branch_for_removal, &cb_data);
        strbuf_release(&buf);
 
-       if (!i)
-               i = remove_branches(&branches);
+       if (!result)
+               result = remove_branches(&branches);
        string_list_clear(&branches, 1);
 
-       return i;
+       if (skipped.nr) {
+               fprintf(stderr, skipped.nr == 1 ?
+                       "Note: A non-remote branch was not removed; "
+                       "to delete it, use:\n" :
+                       "Note: Non-remote branches were not removed; "
+                       "to delete them, use:\n");
+               for (i = 0; i < skipped.nr; i++)
+                       fprintf(stderr, "  git branch -d %s\n",
+                               skipped.items[i].string);
+       }
+       string_list_clear(&skipped, 0);
+
+       return result;
 }
 
 static void show_list(const char *title, struct string_list *list,
@@ -731,12 +756,17 @@ static int prune(int argc, const char **argv)
                OPT_END()
        };
        struct ref_states states;
+       const char *dangling_msg;
 
        argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
 
        if (argc < 1)
                usage_with_options(builtin_remote_usage, options);
 
+       dangling_msg = (dry_run
+                       ? " %s will become dangling!\n"
+                       : " %s has become dangling!\n");
+
        memset(&states, 0, sizeof(states));
        for (; argc; argc--, argv++) {
                int i;
@@ -759,6 +789,7 @@ static int prune(int argc, const char **argv)
 
                        printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
                               abbrev_ref(refname, "refs/remotes/"));
+                       warn_dangling_symref(dangling_msg, refname);
                }
 
                /* NEEDSWORK: free remote */
index 857742a14f82e049c5b9e8b234dae9e9e1a7dc30..40d5fcb6b0b26c76c271624408b531cc01e15f7b 100644 (file)
@@ -574,6 +574,45 @@ static struct commit_list *find_bisection(struct commit_list *list,
        return best;
 }
 
+static inline int log2i(int n)
+{
+       int log2 = 0;
+
+       for (; n > 1; n >>= 1)
+               log2++;
+
+       return log2;
+}
+
+static inline int exp2i(int n)
+{
+       return 1 << n;
+}
+
+/*
+ * Estimate the number of bisect steps left (after the current step)
+ *
+ * For any x between 0 included and 2^n excluded, the probability for
+ * n - 1 steps left looks like:
+ *
+ * P(2^n + x) == (2^n - x) / (2^n + x)
+ *
+ * and P(2^n + x) < 0.5 means 2^n < 3x
+ */
+static int estimate_bisect_steps(int all)
+{
+       int n, x, e;
+
+       if (all < 3)
+               return 0;
+
+       n = log2i(all);
+       e = exp2i(n);
+       x = all - e;
+
+       return (e < 3 * x) ? n : n - 1;
+}
+
 int cmd_rev_list(int argc, const char **argv, const char *prefix)
 {
        struct commit_list *list;
@@ -608,6 +647,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                if (!strcmp(arg, "--bisect-all")) {
                        bisect_list = 1;
                        bisect_find_all = 1;
+                       revs.show_decorations = 1;
                        continue;
                }
                if (!strcmp(arg, "--bisect-vars")) {
@@ -687,12 +727,14 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                               "bisect_nr=%d\n"
                               "bisect_good=%d\n"
                               "bisect_bad=%d\n"
-                              "bisect_all=%d\n",
+                              "bisect_all=%d\n"
+                              "bisect_steps=%d\n",
                               hex,
                               cnt - 1,
                               all - reaches - 1,
                               reaches - 1,
-                              all);
+                              all,
+                              estimate_bisect_steps(all));
                        return 0;
                }
        }
index d48313c7453c24e7ab4cbb3e024d05ec311edf7b..d210150671830c10f098463175e5a6e8d304c1a4 100644 (file)
@@ -223,17 +223,6 @@ static char *help_msg(const unsigned char *sha1)
        return helpbuf;
 }
 
-static int index_is_dirty(void)
-{
-       struct rev_info rev;
-       init_revisions(&rev, NULL);
-       setup_revisions(0, NULL, &rev, "HEAD");
-       DIFF_OPT_SET(&rev.diffopt, QUIET);
-       DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
-       run_diff_index(&rev, 1);
-       return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
-}
-
 static struct tree *empty_tree(void)
 {
        struct tree *tree = xcalloc(1, sizeof(struct tree));
@@ -279,7 +268,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        } else {
                if (get_sha1("HEAD", head))
                        die ("You do not have a valid HEAD");
-               if (index_is_dirty())
+               if (index_differs_from("HEAD", 0))
                        die ("Dirty index: cannot %s", me);
        }
        discard_cache();
index 5f9f3f09b11b2e7f54a26bdbce7cb0e6974740d5..badd9120388569c79b137f679505b495cc1dbbe9 100644 (file)
@@ -40,6 +40,7 @@ static void insert_one_record(struct shortlog *log,
        char *buffer, *p;
        struct string_list_item *item;
        char namebuf[1024];
+       char emailbuf[1024];
        size_t len;
        const char *eol;
        const char *boemail, *eoemail;
@@ -51,7 +52,19 @@ static void insert_one_record(struct shortlog *log,
        eoemail = strchr(boemail, '>');
        if (!eoemail)
                return;
-       if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
+
+       /* copy author name to namebuf, to support matching on both name and email */
+       memcpy(namebuf, author, boemail - author);
+       len = boemail - author;
+       while(len > 0 && isspace(namebuf[len-1]))
+               len--;
+       namebuf[len] = 0;
+
+       /* copy email name to emailbuf, to allow email replacement as well */
+       memcpy(emailbuf, boemail+1, eoemail - boemail);
+       emailbuf[eoemail - boemail - 1] = 0;
+
+       if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
                while (author < boemail && isspace(*author))
                        author++;
                for (len = 0;
@@ -67,8 +80,8 @@ static void insert_one_record(struct shortlog *log,
 
        if (log->email) {
                size_t room = sizeof(namebuf) - len - 1;
-               int maillen = eoemail - boemail + 1;
-               snprintf(namebuf + len, room, " %.*s", maillen, boemail);
+               int maillen = strlen(emailbuf);
+               snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
        }
 
        item = string_list_insert(namebuf, &log->list);
@@ -219,7 +232,7 @@ void shortlog_init(struct shortlog *log)
 {
        memset(log, 0, sizeof(*log));
 
-       read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
+       read_mailmap(&log->mailmap, &log->common_repo_prefix);
 
        log->list.strdup_strings = 1;
        log->wrap = DEFAULT_WRAPLEN;
@@ -248,6 +261,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
        struct parse_opt_ctx_t ctx;
 
        prefix = setup_git_directory_gently(&nongit);
+       git_config(git_default_config, NULL);
        shortlog_init(&log);
        init_revisions(&rev, prefix);
        parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
@@ -320,6 +334,5 @@ void shortlog_output(struct shortlog *log)
 
        log->list.strdup_strings = 1;
        string_list_clear(&log->list, 1);
-       log->mailmap.strdup_strings = 1;
-       string_list_clear(&log->mailmap, 1);
+       clear_mailmap(&log->mailmap);
 }
index bfc78bb3f6eff2f8e39649b9649ae7263f143ad9..6ae6bcc0e8d02d9af8a81a7d694c0bfd2c6c0514 100644 (file)
@@ -44,6 +44,9 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                check_symref(argv[0], quiet);
                break;
        case 2:
+               if (!strcmp(argv[0], "HEAD") &&
+                   prefixcmp(argv[1], "refs/"))
+                       die("Refusing to point HEAD outside of refs/");
                create_symref(argv[0], argv[1], msg);
                break;
        default:
index 560497750586ec61be4e34de6dedd9c307129817..dd43d5bef425af318a884bfc235a4aff40931633 100644 (file)
@@ -742,8 +742,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                if (newfd < 0) {
                        if (refresh_flags & REFRESH_QUIET)
                                exit(128);
-                       die("unable to create '%s.lock': %s",
-                           get_index_file(), strerror(lock_error));
+                       unable_to_lock_index_die(get_index_file(), lock_error);
                }
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_locked_index(lock_file))
index a9b02fa32f372a6810867c10560a20d58b5b2a91..0206b416cbf08ae4c1b0d753784fb0b61bac46a1 100644 (file)
@@ -35,7 +35,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
        strcpy(buf, argv[1]); /* enter-repo smudges its argument */
 
        if (!enter_repo(buf, 0))
-               die("not a git archive");
+               die("'%s' does not appear to be a git repository", buf);
 
        /* put received options in sent_argv[] */
        sent_argc = 1;
index 25a29f11a4b9642c1bd367b81779d8f09ae04604..0ee0a9af60b0601fe0e6db98ec582e059f5e9064 100644 (file)
@@ -107,7 +107,7 @@ static int verify_one_pack(const char *path, int verbose)
        return err;
 }
 
-static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>...";
+static const char verify_pack_usage[] = "git verify-pack [-v] <pack>...";
 
 int cmd_verify_pack(int argc, const char **argv, const char *prefix)
 {
diff --git a/cache.h b/cache.h
index 45e713e9283dcf7b241291ac121e4d4a771f5796..189151de25ffd1a6671b7a70f359fa9b94b82173 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -484,6 +484,7 @@ struct lock_file {
 };
 #define LOCK_DIE_ON_ERROR 1
 #define LOCK_NODEREF 2
+extern NORETURN void unable_to_lock_index_die(const char *path, int err);
 extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
 extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
 extern int commit_lock_file(struct lock_file *);
@@ -624,8 +625,9 @@ int is_directory(const char *);
 const char *make_absolute_path(const char *path);
 const char *make_nonrelative_path(const char *path);
 const char *make_relative_path(const char *abs, const char *base);
-int normalize_absolute_path(char *buf, const char *path);
+int normalize_path_copy(char *dst, const char *src);
 int longest_ancestor_length(const char *path, const char *prefix_list);
+char *strip_path_suffix(const char *path, const char *suffix);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 extern int sha1_object_info(const unsigned char *, unsigned long *);
@@ -694,7 +696,8 @@ enum date_mode {
        DATE_SHORT,
        DATE_LOCAL,
        DATE_ISO8601,
-       DATE_RFC2822
+       DATE_RFC2822,
+       DATE_RAW
 };
 
 const char *show_date(unsigned long time, int timezone, enum date_mode mode);
@@ -827,6 +830,7 @@ extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t
 extern void close_pack_windows(struct packed_git *);
 extern void unuse_pack(struct pack_window **);
 extern void free_pack_by_name(const char *);
+extern void clear_delta_base_cache(void);
 extern struct packed_git *add_packed_git(const char *, int, int);
 extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
 extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t);
@@ -867,6 +871,7 @@ extern int user_ident_explicitly_given;
 
 extern const char *git_commit_encoding;
 extern const char *git_log_output_encoding;
+extern const char *git_mailmap_file;
 
 /* IO helper functions */
 extern void maybe_flush_or_die(FILE *, const char *);
index 3dbe6a77ffa4675b19e7183fd49e11212cb2cda0..27bcf3fd6b481eb058b357241da320a02e3d4096 100644 (file)
@@ -46,7 +46,8 @@ static int do_lstat(const char *file_name, struct stat *buf)
                buf->st_uid = 0;
                buf->st_nlink = 1;
                buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
-               buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+               buf->st_size = fdata.nFileSizeLow |
+                       (((off_t)fdata.nFileSizeHigh)<<32);
                buf->st_dev = buf->st_rdev = 0; /* not used by Git */
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
@@ -101,7 +102,7 @@ int mingw_fstat(int fd, struct stat *buf)
        }
        /* direct non-file handles to MS's fstat() */
        if (GetFileType(fh) != FILE_TYPE_DISK)
-               return fstat(fd, buf);
+               return _fstati64(fd, buf);
 
        if (GetFileInformationByHandle(fh, &fdata)) {
                buf->st_ino = 0;
@@ -109,7 +110,8 @@ int mingw_fstat(int fd, struct stat *buf)
                buf->st_uid = 0;
                buf->st_nlink = 1;
                buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
-               buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+               buf->st_size = fdata.nFileSizeLow |
+                       (((off_t)fdata.nFileSizeHigh)<<32);
                buf->st_dev = buf->st_rdev = 0; /* not used by Git */
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
index a25589880130f2232aaf626cddcd739ac80dd378..cb9c4d4dd522998b00d853fc6e5f37d99759a0ef 100644 (file)
@@ -163,11 +163,14 @@ int mingw_rename(const char*, const char*);
 /* Use mingw_lstat() instead of lstat()/stat() and
  * mingw_fstat() instead of fstat() on Windows.
  */
+#define off_t off64_t
+#define stat _stati64
+#define lseek _lseeki64
 int mingw_lstat(const char *file_name, struct stat *buf);
 int mingw_fstat(int fd, struct stat *buf);
 #define fstat mingw_fstat
 #define lstat mingw_lstat
-#define stat(x,y) mingw_lstat(x,y)
+#define stat64(x,y) mingw_lstat(x,y)
 
 int mingw_utime(const char *file_name, const struct utimbuf *times);
 #define utime mingw_utime
index 790405a213b12a4d1c62d9354e1292e0dc6af057..0c8c76f13b03028ad400b1c5b72b3cf0a7ec0940 100644 (file)
--- a/config.c
+++ b/config.c
@@ -565,6 +565,15 @@ static int git_default_branch_config(const char *var, const char *value)
        return 0;
 }
 
+static int git_default_mailmap_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "mailmap.file"))
+               return git_config_string(&git_mailmap_file, var, value);
+
+       /* Add other config variables here and to Documentation/config.txt. */
+       return 0;
+}
+
 int git_default_config(const char *var, const char *value, void *dummy)
 {
        if (!prefixcmp(var, "core."))
@@ -579,6 +588,9 @@ int git_default_config(const char *var, const char *value, void *dummy)
        if (!prefixcmp(var, "branch."))
                return git_default_branch_config(var, value);
 
+       if (!prefixcmp(var, "mailmap."))
+               return git_default_mailmap_config(var, value);
+
        if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
                pager_use_color = git_config_bool(var,value);
                return 0;
index 55b25c3d2653d6228765a72ea3ba2aaa7c97f7df..7cce0c12d507b222d47d8469abf59bf3ef9096b9 100644 (file)
@@ -13,9 +13,9 @@ TCLTK_PATH = @TCLTK_PATH@
 prefix = @prefix@
 exec_prefix = @exec_prefix@
 bindir = @bindir@
-gitexecdir = @libexecdir@/git-core/
+gitexecdir = @libexecdir@/git-core
 datarootdir = @datarootdir@
-template_dir = @datadir@/git-core/templates/
+template_dir = @datadir@/git-core/templates
 
 mandir=@mandir@
 
index 81f70ec6449f3c15f26884040cdfe9e9cad489eb..0a3092f646872867b0060d79c0fd1c85a40e24f4 100755 (executable)
 #       are currently in a git repository.  The %s token will be
 #       the name of the current branch.
 #
+#       In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
+#       value, unstaged (*) and staged (+) changes will be shown next
+#       to the branch name.  You can configure this per-repository
+#       with the bash.showDirtyState variable, which defaults to true
+#       once GIT_PS1_SHOWDIRTYSTATE is enabled.
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -116,10 +122,26 @@ __git_ps1 ()
                        fi
                fi
 
+               local w
+               local i
+
+               if test -n "${GIT_PS1_SHOWDIRTYSTATE-}"; then
+                       if test "$(git config --bool bash.showDirtyState)" != "false"; then
+                               git diff --no-ext-diff --ignore-submodules \
+                                       --quiet --exit-code || w="*"
+                               if git rev-parse --quiet --verify HEAD >/dev/null; then
+                                       git diff-index --cached --quiet \
+                                               --ignore-submodules HEAD -- || i="+"
+                               else
+                                       i="#"
+                               fi
+                       fi
+               fi
+
                if [ -n "${1-}" ]; then
-                       printf "$1" "${b##refs/heads/}$r"
+                       printf "$1" "${b##refs/heads/}$w$i$r"
                else
-                       printf " (%s)" "${b##refs/heads/}$r"
+                       printf " (%s)" "${b##refs/heads/}$w$i$r"
                fi
        fi
 }
@@ -953,6 +975,27 @@ _git_ls_tree ()
        __git_complete_file
 }
 
+# Options that go well for log, shortlog and gitk
+__git_log_common_options="
+       --not --all
+       --branches --tags --remotes
+       --first-parent --no-merges
+       --max-count=
+       --max-age= --since= --after=
+       --min-age= --until= --before=
+"
+# Options that go well for log and gitk (not shortlog)
+__git_log_gitk_options="
+       --dense --sparse --full-history
+       --simplify-merges --simplify-by-decoration
+       --left-right
+"
+# Options that go well for log and shortlog (not gitk)
+__git_log_shortlog_options="
+       --author= --committer= --grep=
+       --all-match
+"
+
 __git_log_pretty_formats="oneline short medium full fuller email raw format:"
 
 _git_log ()
@@ -960,6 +1003,11 @@ _git_log ()
        __git_has_doubledash && return
 
        local cur="${COMP_WORDS[COMP_CWORD]}"
+       local g="$(git rev-parse --git-dir 2>/dev/null)"
+       local merge=""
+       if [ -f $g/MERGE_HEAD ]; then
+               merge="--merge"
+       fi
        case "$cur" in
        --pretty=*)
                __gitcomp "$__git_log_pretty_formats
@@ -974,22 +1022,20 @@ _git_log ()
                ;;
        --*)
                __gitcomp "
-                       --max-count= --max-age= --since= --after=
-                       --min-age= --before= --until=
+                       $__git_log_common_options
+                       $__git_log_shortlog_options
+                       $__git_log_gitk_options
                        --root --topo-order --date-order --reverse
-                       --no-merges --follow
+                       --follow
                        --abbrev-commit --abbrev=
                        --relative-date --date=
-                       --author= --committer= --grep=
-                       --all-match
                        --pretty=
-                       --not --all
-                       --left-right --cherry-pick
+                       --cherry-pick
                        --graph
                        --decorate
                        --walk-reflogs
-                       --parents --children --full-history
-                       --merge
+                       --parents --children
+                       $merge
                        $__git_diff_common_options
                        --pickaxe-all --pickaxe-regex
                        "
@@ -1015,6 +1061,7 @@ _git_merge ()
        --*)
                __gitcomp "
                        --no-commit --no-stat --log --no-log --squash --strategy
+                       --commit --stat --no-squash --ff --no-ff
                        "
                return
        esac
@@ -1174,10 +1221,14 @@ _git_config ()
                __gitcomp "$(__git_merge_strategies)"
                return
                ;;
-       color.branch|color.diff|color.status)
+       color.branch|color.diff|color.interactive|color.status|color.ui)
                __gitcomp "always never auto"
                return
                ;;
+       color.pager)
+               __gitcomp "false true"
+               return
+               ;;
        color.*.*)
                __gitcomp "
                        normal black red green yellow blue magenta cyan white
@@ -1469,12 +1520,8 @@ _git_shortlog ()
        case "$cur" in
        --*)
                __gitcomp "
-                       --max-count= --max-age= --since= --after=
-                       --min-age= --before= --until=
-                       --no-merges
-                       --author= --committer= --grep=
-                       --all-match
-                       --not --all
+                       $__git_log_common_options
+                       $__git_log_shortlog_options
                        --numbered --summary
                        "
                return
@@ -1573,7 +1620,8 @@ _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
+               proplist show-ignore show-externals branch tag blame
+               migrate
                "
        local subcommand="$(__git_find_subcommand "$subcommands")"
        if [ -z "$subcommand" ]; then
@@ -1584,13 +1632,15 @@ _git_svn ()
                        --follow-parent --authors-file= --repack=
                        --no-metadata --use-svm-props --use-svnsync-props
                        --log-window-size= --no-checkout --quiet
-                       --repack-flags --user-log-author --localtime $remote_opts
+                       --repack-flags --use-log-author --localtime
+                       --ignore-paths= $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
+                       --rewrite-root= --prefix= --use-log-author
+                       --add-author-from $remote_opts
                        "
                local cmt_opts="
                        --edit --rmdir --find-copies-harder --copy-similarity=
@@ -1610,7 +1660,8 @@ _git_svn ()
                dcommit,--*)
                        __gitcomp "
                                --merge --strategy= --verbose --dry-run
-                               --fetch-all --no-rebase $cmt_opts $fc_opts
+                               --fetch-all --no-rebase --commit-url
+                               --revision $cmt_opts $fc_opts
                                "
                        ;;
                set-tree,--*)
@@ -1624,13 +1675,13 @@ _git_svn ()
                        __gitcomp "
                                --limit= --revision= --verbose --incremental
                                --oneline --show-commit --non-recursive
-                               --authors-file=
+                               --authors-file= --color
                                "
                        ;;
                rebase,--*)
                        __gitcomp "
                                --merge --verbose --strategy= --local
-                               --fetch-all $fc_opts
+                               --fetch-all --dry-run $fc_opts
                                "
                        ;;
                commit-diff,--*)
@@ -1639,6 +1690,21 @@ _git_svn ()
                info,--*)
                        __gitcomp "--url"
                        ;;
+               branch,--*)
+                       __gitcomp "--dry-run --message --tag"
+                       ;;
+               tag,--*)
+                       __gitcomp "--dry-run --message"
+                       ;;
+               blame,--*)
+                       __gitcomp "--git-format"
+                       ;;
+               migrate,--*)
+                       __gitcomp "
+                               --config-dir= --ignore-paths= --minimize
+                               --no-auth-cache --username=
+                               "
+                       ;;
                *)
                        COMPREPLY=()
                        ;;
@@ -1782,7 +1848,11 @@ _gitk ()
        fi
        case "$cur" in
        --*)
-               __gitcomp "--not --all $merge"
+               __gitcomp "
+                       $__git_log_common_options
+                       $__git_log_gitk_options
+                       $merge
+                       "
                return
                ;;
        esac
index a48540a92b4aa5140a87469b36c1f9b3d8e46e7f..24d931294180c10646249bbbe31b5215f9996a66 100644 (file)
@@ -2,7 +2,7 @@
 
 EMACS = emacs
 
-ELC = git.elc vc-git.elc git-blame.elc
+ELC = git.elc git-blame.elc
 INSTALL ?= install
 INSTALL_ELC = $(INSTALL) -m 644
 prefix ?= $(HOME)
diff --git a/contrib/emacs/README b/contrib/emacs/README
new file mode 100644 (file)
index 0000000..82368bd
--- /dev/null
@@ -0,0 +1,39 @@
+This directory contains various modules for Emacs support.
+
+To make the modules available to Emacs, you should add this directory
+to your load-path, and then require the modules you want. This can be
+done by adding to your .emacs something like this:
+
+  (add-to-list 'load-path ".../git/contrib/emacs")
+  (require 'git)
+  (require 'git-blame)
+
+
+The following modules are available:
+
+* git.el:
+
+  Status manager that displays the state of all the files of the
+  project, and provides easy access to the most frequently used git
+  commands. The user interface is as far as possible compatible with
+  the pcl-cvs mode. It can be started with `M-x git-status'.
+
+* git-blame.el:
+
+  Emacs implementation of incremental git-blame.  When you turn it on
+  while viewing a file, the editor buffer will be updated by setting
+  the background of individual lines to a color that reflects which
+  commit it comes from.  And when you move around the buffer, a
+  one-line summary will be shown in the echo area.
+
+* vc-git.el:
+
+  This file used to contain the VC-mode backend for git, but it is no
+  longer distributed with git. It is now maintained as part of Emacs
+  and included in standard Emacs distributions starting from version
+  22.2.
+
+  If you have an earlier Emacs version, upgrading to Emacs 22 is
+  recommended, since the VC mode in older Emacs is not generic enough
+  to be able to support git in a reasonable manner, and no attempt has
+  been made to backport vc-git.el.
index 09e8bae3a41827a20f6f0693c28f91a98adcb8a4..eace9c18eb1d17075836694ce664a009f3e02038 100644 (file)
@@ -1,6 +1,6 @@
 ;;; git.el --- A user interface for git
 
-;; Copyright (C) 2005, 2006, 2007 Alexandre Julliard <julliard@winehq.org>
+;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org>
 
 ;; Version: 1.0
 
 ;; To start: `M-x git-status'
 ;;
 ;; TODO
-;;  - portability to XEmacs
 ;;  - diff against other branch
 ;;  - renaming files from the status buffer
 ;;  - creating tags
 ;;  - fetch/pull
-;;  - switching branches
 ;;  - revlist browser
 ;;  - git-show-branch browser
-;;  - menus
+;;
+
+;;; Compatibility:
+;;
+;; This file works on GNU Emacs 21 or later. It may work on older
+;; versions but this is not guaranteed.
+;;
+;; It may work on XEmacs 21, provided that you first install the ewoc
+;; and log-edit packages.
 ;;
 
 (eval-when-compile (require 'cl))
@@ -222,7 +228,7 @@ the process output as a string, or nil if the git command failed."
     (with-current-buffer buffer
       (cd dir)
       (apply #'call-process-region start end program
-             nil (list output-buffer nil) nil args))))
+             nil (list output-buffer t) nil args))))
 
 (defun git-run-command-buffer (buffer-name &rest args)
   "Run a git command, sending the output to a buffer named BUFFER-NAME."
@@ -239,13 +245,15 @@ the process output as a string, or nil if the git command failed."
 
 (defun git-run-command-region (buffer start end env &rest args)
   "Run a git command with specified buffer region as input."
-  (unless (eq 0 (if env
-                    (git-run-process-region
-                     buffer start end "env"
-                     (append (git-get-env-strings env) (list "git") args))
+  (with-temp-buffer
+    (if (eq 0 (if env
                   (git-run-process-region
-                   buffer start end "git" args)))
-    (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string))))
+                   buffer start end "env"
+                   (append (git-get-env-strings env) (list "git") args))
+                (git-run-process-region buffer start end "git" args)))
+        (buffer-string)
+      (display-message-or-buffer (current-buffer))
+      nil)))
 
 (defun git-run-hook (hook env &rest args)
   "Run a git hook and display its output if any."
@@ -397,6 +405,17 @@ the process output as a string, or nil if the git command failed."
     (unless newval (push "-d" args))
     (apply 'git-call-process-display-error "update-ref" args)))
 
+(defun git-for-each-ref (&rest specs)
+  "Return a list of refs using git-for-each-ref.
+Each entry is a cons of (SHORT-NAME . FULL-NAME)."
+  (let (refs)
+    (with-temp-buffer
+      (apply #'git-call-process t "for-each-ref" "--format=%(refname)" specs)
+      (goto-char (point-min))
+      (while (re-search-forward "^[^/\n]+/[^/\n]+/\\(.+\\)$" nil t)
+       (push (cons (match-string 1) (match-string 0)) refs)))
+    (nreverse refs)))
+
 (defun git-read-tree (tree &optional index-file)
   "Read a tree into the index file."
   (let ((process-environment
@@ -447,18 +466,16 @@ the process output as a string, or nil if the git command failed."
       (setq coding-system-for-write buffer-file-coding-system))
     (let ((commit
            (git-get-string-sha1
-            (with-output-to-string
-              (with-current-buffer standard-output
-                (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
-                             ("GIT_AUTHOR_EMAIL" . ,author-email)
-                             ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
-                             ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
-                  (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
-                  (apply #'git-run-command-region
-                         buffer log-start log-end env
-                         "commit-tree" tree (nreverse args))))))))
-      (and (git-update-ref "HEAD" commit head subject)
-           commit))))
+            (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
+                         ("GIT_AUTHOR_EMAIL" . ,author-email)
+                         ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
+                         ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
+              (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
+              (apply #'git-run-command-region
+                     buffer log-start log-end env
+                     "commit-tree" tree (nreverse args))))))
+      (when commit (git-update-ref "HEAD" commit head subject))
+      commit)))
 
 (defun git-empty-db-p ()
   "Check if the git db is empty (no commit done yet)."
@@ -513,9 +530,9 @@ the process output as a string, or nil if the git command failed."
           (git-fileinfo->needs-refresh info) t)))
 
 (defun git-status-filenames-map (status func files &rest args)
-  "Apply FUNC to the status files names in the FILES list."
+  "Apply FUNC to the status files names in the FILES list.
+The list must be sorted."
   (when files
-    (setq files (sort files #'string-lessp))
     (let ((file (pop files))
           (node (ewoc-nth status 0)))
       (while (and file node)
@@ -528,7 +545,7 @@ the process output as a string, or nil if the git command failed."
             (setq file (pop files))))))))
 
 (defun git-set-filenames-state (status files state)
-  "Set the state of a list of named files."
+  "Set the state of a list of named files. The list must be sorted"
   (when files
     (git-status-filenames-map status #'git-set-fileinfo-state files state)
     (unless state  ;; delete files whose state has been set to nil
@@ -562,29 +579,29 @@ the process output as a string, or nil if the git command failed."
   (let* ((old-type (lsh (or old-perm 0) -9))
         (new-type (lsh (or new-perm 0) -9))
         (str (case new-type
-               (?\100  ;; file
+               (64  ;; file
                 (case old-type
-                  (?\100 nil)
-                  (?\120 "   (type change symlink -> file)")
-                  (?\160 "   (type change subproject -> file)")))
-                (?\120  ;; symlink
+                  (64 nil)
+                  (80 "   (type change symlink -> file)")
+                  (112 "   (type change subproject -> file)")))
+                (80  ;; symlink
                  (case old-type
-                   (?\100 "   (type change file -> symlink)")
-                   (?\160 "   (type change subproject -> symlink)")
+                   (64 "   (type change file -> symlink)")
+                   (112 "   (type change subproject -> symlink)")
                    (t "   (symlink)")))
-                 (?\160  ;; subproject
+                 (112  ;; subproject
                   (case old-type
-                    (?\100 "   (type change file -> subproject)")
-                    (?\120 "   (type change symlink -> subproject)")
+                    (64 "   (type change file -> subproject)")
+                    (80 "   (type change symlink -> subproject)")
                     (t "   (subproject)")))
-                  (?\110 nil)  ;; directory (internal, not a real git state)
-                 (?\000  ;; deleted or unknown
+                  (72 nil)  ;; directory (internal, not a real git state)
+                 (0  ;; deleted or unknown
                   (case old-type
-                    (?\120 "   (symlink)")
-                    (?\160 "   (subproject)")))
+                    (80 "   (symlink)")
+                    (112 "   (subproject)")))
                  (t (format "   (unknown type %o)" new-type)))))
     (cond (str (propertize str 'face 'git-status-face))
-          ((eq new-type ?\110) "/")
+          ((eq new-type 72) "/")
           (t ""))))
 
 (defun git-rename-as-string (info)
@@ -733,6 +750,7 @@ Return the list of files that haven't been handled."
     (let (unmerged-files)
       (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
         (push (match-string 1) unmerged-files))
+      (setq unmerged-files (nreverse unmerged-files))  ;; assume it is sorted already
       (git-set-filenames-state status unmerged-files 'unmerged))))
 
 (defun git-get-exclude-files ()
@@ -753,17 +771,18 @@ Return the list of files that haven't been handled."
            (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
 
 (defun git-update-status-files (&optional files mark-files)
-  "Update the status of FILES from the index."
+  "Update the status of FILES from the index.
+The FILES list must be sorted."
   (unless git-status (error "Not in git-status buffer."))
   ;; set the needs-update flag on existing files
-  (if (setq files (sort files #'string-lessp))
+  (if files
       (git-status-filenames-map
        git-status (lambda (info) (setf (git-fileinfo->needs-update info) t)) files)
     (ewoc-map (lambda (info) (setf (git-fileinfo->needs-update info) t) nil) git-status)
     (git-call-process nil "update-index" "--refresh")
     (when git-show-uptodate
       (git-run-ls-files-cached git-status nil 'uptodate)))
-  (let* ((remaining-files
+  (let ((remaining-files
           (if (git-empty-db-p) ; we need some special handling for an empty db
              (git-run-ls-files-cached git-status files 'added)
             (git-run-diff-index git-status files))))
@@ -808,13 +827,13 @@ Return the list of files that haven't been handled."
       (list (ewoc-data (ewoc-locate git-status)))))
 
 (defun git-marked-files-state (&rest states)
-  "Return marked files that are in the specified states."
+  "Return a sorted list of marked files that are in the specified states."
   (let ((files (git-marked-files))
         result)
     (dolist (info files)
       (when (memq (git-fileinfo->state info) states)
         (push info result)))
-    result))
+    (nreverse result)))
 
 (defun git-refresh-files ()
   "Refresh all files that need it and clear the needs-refresh flag."
@@ -1049,7 +1068,9 @@ Return the list of files that haven't been handled."
     (unless files
       (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
     (if (yes-or-no-p
-         (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
+         (if (cdr files)
+             (format "Remove %d files? " (length files))
+           (format "Remove %s? " (car files))))
         (progn
           (dolist (name files)
             (ignore-errors
@@ -1068,7 +1089,9 @@ Return the list of files that haven't been handled."
         added modified)
     (when (and files
                (yes-or-no-p
-                (format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" ""))))
+                (if (cdr files)
+                    (format "Revert %d files? " (length files))
+                  (format "Revert %s? " (git-fileinfo->name (car files))))))
       (dolist (info files)
         (case (git-fileinfo->state info)
           ('added (push (git-fileinfo->name info) added))
@@ -1084,13 +1107,14 @@ Return the list of files that haven't been handled."
                  (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))
+                     (apply 'git-call-process-display-error "checkout" "HEAD" modified))))
+            (names (git-get-filenames files)))
+        (git-update-status-files names)
         (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)))))))
+          (git-success-message "Reverted" names))))))
 
 (defun git-resolve-file ()
   "Resolve conflicts in marked file(s)."
@@ -1320,6 +1344,7 @@ Return the list of files that haven't been handled."
                                         (log-edit-diff-function . git-log-edit-diff)) buffer)
        (log-edit 'git-do-commit nil 'git-log-edit-files buffer))
       (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
+      (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[        ]*$"))
       (setq buffer-file-coding-system coding-system)
       (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
 
@@ -1347,14 +1372,44 @@ Return the list of files that haven't been handled."
                           (mapconcat #'identity msg "\n"))))
 
 (defun git-get-commit-files (commit)
-  "Retrieve the list of files modified by COMMIT."
+  "Retrieve a sorted list of files modified by COMMIT."
   (let (files)
     (with-temp-buffer
       (git-call-process t "diff-tree" "-m" "-r" "-z" "--name-only" "--no-commit-id" "--root" commit)
       (goto-char (point-min))
       (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
         (push (match-string 1) files)))
-    files))
+    (sort files #'string-lessp)))
+
+(defun git-read-commit-name (prompt &optional default)
+  "Ask for a commit name, with completion for local branch, remote branch and tag."
+  (completing-read prompt
+                   (list* "HEAD" "ORIG_HEAD" "FETCH_HEAD" (mapcar #'car (git-for-each-ref)))
+                  nil nil nil nil default))
+
+(defun git-checkout (branch &optional merge)
+  "Checkout a branch, tag, or any commit.
+Use a prefix arg if git should merge while checking out."
+  (interactive
+   (list (git-read-commit-name "Checkout: ")
+         current-prefix-arg))
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((args (list branch "--")))
+    (when merge (push "-m" args))
+    (when (apply #'git-call-process-display-error "checkout" args)
+      (git-update-status-files))))
+
+(defun git-branch (branch)
+  "Create a branch from the current HEAD and switch to it."
+  (interactive (list (git-read-commit-name "Branch: ")))
+  (unless git-status (error "Not in git-status buffer."))
+  (if (git-rev-parse (concat "refs/heads/" branch))
+      (if (yes-or-no-p (format "Branch %s already exists, replace it? " branch))
+          (and (git-call-process-display-error "branch" "-f" branch)
+               (git-call-process-display-error "checkout" branch))
+        (message "Canceled."))
+    (git-call-process-display-error "checkout" "-b" branch))
+    (git-refresh-ewoc-hf git-status))
 
 (defun git-amend-commit ()
   "Undo the last commit on HEAD, and set things up to commit an
@@ -1372,6 +1427,44 @@ amended version of it."
       (git-setup-commit-buffer commit)
       (git-commit-file))))
 
+(defun git-cherry-pick-commit (arg)
+  "Cherry-pick a commit."
+  (interactive (list (git-read-commit-name "Cherry-pick commit: ")))
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((commit (git-rev-parse (concat arg "^0"))))
+    (unless commit (error "Not a valid commit '%s'." arg))
+    (when (git-rev-parse (concat commit "^2"))
+      (error "Cannot cherry-pick a merge commit."))
+    (let ((files (git-get-commit-files commit))
+          (ok (git-call-process-display-error "cherry-pick" "-n" commit)))
+      (git-update-status-files files ok)
+      (with-current-buffer (git-setup-commit-buffer commit)
+        (goto-char (point-min))
+        (if (re-search-forward "^\n*Signed-off-by:" nil t 1)
+            (goto-char (match-beginning 0))
+          (goto-char (point-max)))
+        (insert "(cherry picked from commit " commit ")\n"))
+      (when ok (git-commit-file)))))
+
+(defun git-revert-commit (arg)
+  "Revert a commit."
+  (interactive (list (git-read-commit-name "Revert commit: ")))
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((commit (git-rev-parse (concat arg "^0"))))
+    (unless commit (error "Not a valid commit '%s'." arg))
+    (when (git-rev-parse (concat commit "^2"))
+      (error "Cannot revert a merge commit."))
+    (let ((files (git-get-commit-files commit))
+          (subject (git-get-commit-description commit))
+          (ok (git-call-process-display-error "revert" "-n" commit)))
+      (git-update-status-files files ok)
+      (when (string-match "^[0-9a-f]+ - \\(.*\\)$" subject)
+        (setq subject (match-string 1 subject)))
+      (git-setup-log-buffer (get-buffer-create "*git-commit*")
+                            (git-get-merge-heads) nil nil (format "Revert \"%s\"" subject) nil
+                            (format "This reverts commit %s.\n" commit))
+      (when ok (git-commit-file)))))
+
 (defun git-find-file ()
   "Visit the current file in its own buffer."
   (interactive)
@@ -1471,6 +1564,10 @@ amended version of it."
     (define-key map "\M-\C-?" 'git-unmark-all)
     ; the commit submap
     (define-key commit-map "\C-a" 'git-amend-commit)
+    (define-key commit-map "\C-b" 'git-branch)
+    (define-key commit-map "\C-o" 'git-checkout)
+    (define-key commit-map "\C-p" 'git-cherry-pick-commit)
+    (define-key commit-map "\C-v" 'git-revert-commit)
     ; the diff submap
     (define-key diff-map "b" 'git-diff-file-base)
     (define-key diff-map "c" 'git-diff-file-combined)
@@ -1491,6 +1588,10 @@ amended version of it."
     `("Git"
       ["Refresh" git-refresh-status t]
       ["Commit" git-commit-file t]
+      ["Checkout..." git-checkout t]
+      ["New Branch..." git-branch t]
+      ["Cherry-pick Commit..." git-cherry-pick-commit t]
+      ["Revert Commit..." git-revert-commit t]
       ("Merge"
        ["Next Unmerged File" git-next-unmerged-file t]
        ["Prev Unmerged File" git-prev-unmerged-file t]
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
deleted file mode 100644 (file)
index b8f6be5..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-;;; vc-git.el --- VC backend for the git version control system
-
-;; Copyright (C) 2006 Alexandre Julliard
-
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
-;;
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE.  See the GNU General Public License for more details.
-;;
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, write to the Free
-;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
-;; MA 02111-1307 USA
-
-;;; Commentary:
-
-;; This file contains a VC backend for the git version control
-;; system.
-;;
-;; To install: put this file on the load-path and add GIT to the list
-;; of supported backends in `vc-handled-backends'; the following line,
-;; placed in your ~/.emacs, will accomplish this:
-;;
-;;     (add-to-list 'vc-handled-backends 'GIT)
-;;
-;; TODO
-;;  - changelog generation
-;;  - working with revisions other than HEAD
-;;
-
-(eval-when-compile (require 'cl))
-
-(defvar git-commits-coding-system 'utf-8
-  "Default coding system for git commits.")
-
-(defun vc-git--run-command-string (file &rest args)
-  "Run a git command on FILE and return its output as string."
-  (let* ((ok t)
-         (str (with-output-to-string
-                (with-current-buffer standard-output
-                  (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
-                                       (append args (list (file-relative-name file)))))
-                    (setq ok nil))))))
-    (and ok str)))
-
-(defun vc-git--run-command (file &rest args)
-  "Run a git command on FILE, discarding any output."
-  (let ((name (file-relative-name file)))
-    (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
-
-(defun vc-git-registered (file)
-  "Check whether FILE is registered with git."
-  (with-temp-buffer
-    (let* ((dir (file-name-directory file))
-           (name (file-relative-name file dir)))
-      (and (ignore-errors
-             (when dir (cd dir))
-             (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)))
-           (let ((str (buffer-string)))
-             (and (> (length str) (length name))
-                  (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
-
-(defun vc-git-state (file)
-  "git-specific version of `vc-state'."
-  (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
-    (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
-        'edited
-      'up-to-date)))
-
-(defun vc-git-workfile-version (file)
-  "git-specific version of `vc-workfile-version'."
-  (let ((str (with-output-to-string
-               (with-current-buffer standard-output
-                 (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
-    (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
-        (match-string 2 str)
-      str)))
-
-(defun vc-git-symbolic-commit (commit)
-  "Translate COMMIT string into symbolic form.
-Returns nil if not possible."
-  (and commit
-       (with-temp-buffer
-        (and
-         (zerop
-          (call-process "git" nil '(t nil) nil "name-rev"
-                        "--name-only" "--tags"
-                        commit))
-         (goto-char (point-min))
-         (= (forward-line 2) 1)
-         (bolp)
-         (buffer-substring-no-properties (point-min) (1- (point-max)))))))
-
-(defun vc-git-previous-version (file rev)
-  "git-specific version of `vc-previous-version'."
-  (let ((default-directory (file-name-directory (expand-file-name file)))
-       (file (file-name-nondirectory file)))
-    (vc-git-symbolic-commit
-     (with-temp-buffer
-       (and
-       (zerop
-        (call-process "git" nil '(t nil) nil "rev-list"
-                      "-2" rev "--" file))
-       (goto-char (point-max))
-       (bolp)
-       (zerop (forward-line -1))
-       (not (bobp))
-       (buffer-substring-no-properties
-          (point)
-          (1- (point-max))))))))
-
-(defun vc-git-next-version (file rev)
-  "git-specific version of `vc-next-version'."
-  (let* ((default-directory (file-name-directory
-                            (expand-file-name file)))
-       (file (file-name-nondirectory file))
-       (current-rev
-        (with-temp-buffer
-          (and
-           (zerop
-            (call-process "git" nil '(t nil) nil "rev-list"
-                          "-1" rev "--" file))
-           (goto-char (point-max))
-           (bolp)
-           (zerop (forward-line -1))
-           (bobp)
-           (buffer-substring-no-properties
-            (point)
-            (1- (point-max)))))))
-    (and current-rev
-        (vc-git-symbolic-commit
-         (with-temp-buffer
-           (and
-            (zerop
-             (call-process "git" nil '(t nil) nil "rev-list"
-                           "HEAD" "--" file))
-            (goto-char (point-min))
-            (search-forward current-rev nil t)
-            (zerop (forward-line -1))
-            (buffer-substring-no-properties
-             (point)
-             (progn (forward-line 1) (1- (point))))))))))
-
-(defun vc-git-revert (file &optional contents-done)
-  "Revert FILE to the version stored in the git repository."
-  (if contents-done
-      (vc-git--run-command file "update-index" "--")
-    (vc-git--run-command file "checkout" "HEAD")))
-
-(defun vc-git-checkout-model (file)
-  'implicit)
-
-(defun vc-git-workfile-unchanged-p (file)
-  (let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
-        (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
-    (and head
-         (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
-         (string= (car (split-string sha1 "\n")) (match-string 1 head)))))
-
-(defun vc-git-register (file &optional rev comment)
-  "Register FILE into the git version-control system."
-  (vc-git--run-command file "update-index" "--add" "--"))
-
-(defun vc-git-print-log (file &optional buffer)
-  (let ((name (file-relative-name file))
-        (coding-system-for-read git-commits-coding-system))
-    (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
-
-(defun vc-git-diff (file &optional rev1 rev2 buffer)
-  (let ((name (file-relative-name file))
-        (buf (or buffer "*vc-diff*")))
-    (if (and rev1 rev2)
-        (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
-      (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
-    ; git-diff-index doesn't set exit status like diff does
-    (if (vc-git-workfile-unchanged-p file) 0 1)))
-
-(defun vc-git-checkin (file rev comment)
-  (let ((coding-system-for-write git-commits-coding-system))
-    (vc-git--run-command file "commit" "-m" comment "--only" "--")))
-
-(defun vc-git-checkout (file &optional editable rev destfile)
-  (if destfile
-      (let ((fullname (substring
-                       (vc-git--run-command-string file "ls-files" "-z" "--full-name" "--")
-                       0 -1))
-            (coding-system-for-read 'no-conversion)
-            (coding-system-for-write 'no-conversion))
-        (with-temp-file destfile
-          (eq 0 (call-process "git" nil t nil "cat-file" "blob"
-                              (concat (or rev "HEAD") ":" fullname)))))
-    (vc-git--run-command file "checkout" (or rev "HEAD"))))
-
-(defun vc-git-annotate-command (file buf &optional rev)
-  ; FIXME: rev is ignored
-  (let ((name (file-relative-name file)))
-    (call-process "git" nil buf nil "blame" name)))
-
-(defun vc-git-annotate-time ()
-  (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t)
-       (vc-annotate-convert-time
-        (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
-
-;; Not really useful since we can't do anything with the revision yet
-;;(defun vc-annotate-extract-revision-at-line ()
-;;  (save-excursion
-;;    (move-beginning-of-line 1)
-;;    (and (looking-at "[0-9a-f]+")
-;;         (buffer-substring (match-beginning 0) (match-end 0)))))
-
-(provide 'vc-git)
index a13bb6afec2fe5e0b5249523ec8c62d8e517de88..4576c4a862c8ea0565a67eccdae6ef1ac4d9af9a 100755 (executable)
@@ -287,9 +287,9 @@ ($$)
 my $last_branch;
 my $current_rev = $opt_s || 1;
 unless(-d $git_dir) {
-       system("git-init");
+       system("git init");
        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
-       system("git-read-tree");
+       system("git read-tree");
        die "Cannot init an empty tree: $?\n" if $?;
 
        $last_branch = $opt_o;
@@ -303,7 +303,7 @@ ($$)
        -f "$git_dir/svn2git"
                or die "'$git_dir/svn2git' does not exist.\n".
                       "You need that file for incremental imports.\n";
-       open(F, "git-symbolic-ref HEAD |") or
+       open(F, "git symbolic-ref HEAD |") or
                die "Cannot run git-symbolic-ref: $!\n";
        chomp ($last_branch = <F>);
        $last_branch = basename($last_branch);
@@ -331,7 +331,7 @@ ($$)
                                "$git_dir/refs/heads/$opt_o") == 0;
 
        # populate index
-       system('git-read-tree', $last_rev);
+       system('git', 'read-tree', $last_rev);
        die "read-tree failed: $?\n" if $?;
 
        # Get the last import timestamps
@@ -399,7 +399,7 @@ ($$$)
        my $pid = open(my $F, '-|');
        die $! unless defined $pid;
        if (!$pid) {
-           exec("git-hash-object", "-w", $name)
+           exec("git", "hash-object", "-w", $name)
                or die "Cannot create object: $!\n";
        }
        my $sha = <$F>;
@@ -423,7 +423,7 @@ ($$$$$)
                my $pid = open(my $F, '-|');
                die $! unless defined $pid;
                if (!$pid) {
-                       exec("git-hash-object", "-w", $name)
+                       exec("git", "hash-object", "-w", $name)
                            or die "Cannot create object: $!\n";
                }
                my $sha = <$F>;
@@ -547,7 +547,7 @@ ($$$$$$$$)
        my $pid = open my $f,'-|';
        die $! unless defined $pid;
        if (!$pid) {
-               exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
+               exec("git","ls-tree","-r","-z",$gitrev,$srcpath)
                        or die $!;
        }
        local $/ = "\0";
@@ -634,7 +634,7 @@ sub commit {
 
        my $rev;
        if($revision > $opt_s and defined $parent) {
-               open(H,'-|',"git-rev-parse","--verify",$parent);
+               open(H,'-|',"git","rev-parse","--verify",$parent);
                $rev = <H>;
                close(H) or do {
                        print STDERR "$revision: cannot find commit '$parent'!\n";
@@ -671,7 +671,7 @@ sub commit {
                unlink($git_index);
        } elsif ($rev ne $last_rev) {
                print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
-               system("git-read-tree", $rev);
+               system("git", "read-tree", $rev);
                die "read-tree failed for $rev: $?\n" if $?;
                $last_rev = $rev;
        }
@@ -740,7 +740,7 @@ sub commit {
                        my $pid = open my $F, "-|";
                        die "$!" unless defined $pid;
                        if (!$pid) {
-                               exec("git-ls-files", "-z", @o1) or die $!;
+                               exec("git", "ls-files", "-z", @o1) or die $!;
                        }
                        @o1 = ();
                        local $/ = "\0";
@@ -758,7 +758,7 @@ sub commit {
                                        @o2 = @o1;
                                        @o1 = ();
                                }
-                               system("git-update-index","--force-remove","--",@o2);
+                               system("git","update-index","--force-remove","--",@o2);
                                die "Cannot remove files: $?\n" if $?;
                        }
                }
@@ -770,7 +770,7 @@ sub commit {
                                @n2 = @new;
                                @new = ();
                        }
-                       system("git-update-index","--add",
+                       system("git","update-index","--add",
                                (map { ('--cacheinfo', @$_) } @n2));
                        die "Cannot add files: $?\n" if $?;
                }
@@ -778,7 +778,7 @@ sub commit {
                my $pid = open(C,"-|");
                die "Cannot fork: $!" unless defined $pid;
                unless($pid) {
-                       exec("git-write-tree");
+                       exec("git","write-tree");
                        die "Cannot exec git-write-tree: $!\n";
                }
                chomp(my $tree = <C>);
@@ -830,7 +830,7 @@ sub commit {
                                "GIT_COMMITTER_NAME=$committer_name",
                                "GIT_COMMITTER_EMAIL=$committer_email",
                                "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-                               "git-commit-tree", $tree,@par);
+                               "git", "commit-tree", $tree,@par);
                        die "Cannot exec git-commit-tree: $!\n";
                }
                $pw->writer();
@@ -874,7 +874,7 @@ sub commit {
 
                $dest =~ tr/_/\./ if $opt_u;
 
-               system('git-tag', '-f', $dest, $cid) == 0
+               system('git', 'tag', '-f', $dest, $cid) == 0
                        or die "Cannot create tag $dest: $!\n";
 
                print "Created tag '$dest' on '$branch'\n" if $opt_v;
@@ -937,7 +937,7 @@ sub commit_all {
        my $pid = fork();
        die "Fork: $!\n" unless defined $pid;
        unless($pid) {
-               exec("git-repack", "-d")
+               exec("git", "repack", "-d")
                        or die "Cannot repack: $!\n";
        }
        waitpid($pid, 0);
@@ -958,7 +958,7 @@ sub commit_all {
        system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
                if $forward_master;
        unless ($opt_i) {
-               system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
+               system('git', 'read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
                die "read-tree failed: $?\n" if $?;
        }
 } else {
@@ -966,7 +966,7 @@ sub commit_all {
        print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
        system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
                unless -f "$git_dir/refs/heads/master";
-       system('git-update-ref', 'HEAD', "$orig_branch");
+       system('git', 'update-ref', 'HEAD', "$orig_branch");
        unless ($opt_i) {
                system('git checkout');
                die "checkout failed: $?\n" if $?;
index 71aad8b45bd4c5f59c2ce3746cb3299821729c2a..3bb871e42f9c786eb8c40553ea3b0e0eb433b195 100644 (file)
@@ -114,9 +114,9 @@ due to SVN memory leaks. (These have been worked around.)
 -R <repack_each_revs>::
        Specify how often git repository should be repacked.
 +
-The default value is 1000. git-svnimport will do import in chunks of 1000
-revisions, after each chunk git repository will be repacked. To disable
-this behavior specify some big value here which is mote than number of
+The default value is 1000. git-svnimport will do imports in chunks of 1000
+revisions, after each chunk the git repository will be repacked. To disable
+this behavior specify some large value here which is greater than the number of
 revisions to import.
 
 -P <path_from_trunk>::
index a85a7b2a583ee9270fc2d765ec8c8c6e9d6b5e32..3832f602253fbe793ddf81c61b61e5a2757ce89d 100755 (executable)
@@ -442,13 +442,14 @@ def p4ChangesForPaths(depotPaths, changeRange):
     output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
                                                         for p in depotPaths]))
 
-    changes = []
+    changes = {}
     for line in output:
-        changeNum = line.split(" ")[1]
-        changes.append(int(changeNum))
+       changeNum = int(line.split(" ")[1])
+       changes[changeNum] = True
 
-    changes.sort()
-    return changes
+    changelist = changes.keys()
+    changelist.sort()
+    return changelist
 
 class Command:
     def __init__(self):
diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh
new file mode 100755 (executable)
index 0000000..c364dda
--- /dev/null
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+USAGE="[-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>"
+LONG_USAGE="git-resurrect attempts to find traces of a branch tip
+called <name>, and tries to resurrect it.  Currently, the reflog is
+searched for checkout messages, and with -r also merge messages.  With
+-m and -t, the history of all refs is scanned for Merge <name> into
+other/Merge <other> into <name> (respectively) commit subjects, which
+is rather slow but allows you to resurrect other people's topic
+branches."
+
+OPTIONS_SPEC="\
+git resurrect $USAGE
+--
+b,branch=            save branch as <newname> instead of <name>
+a,all                same as -l -r -m -t
+k,keep-going         full rev-list scan (instead of first match)
+l,reflog             scan reflog for checkouts (enabled by default)
+r,reflog-merges      scan for merges recorded in reflog
+m,merges             scan for merges into other branches (slow)
+t,merge-targets      scan for merges of other branches into <name>
+n,dry-run            don't recreate the branch"
+
+. git-sh-setup
+
+search_reflog () {
+        sed -ne 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
+                < "$GIT_DIR"/logs/HEAD
+}
+
+search_reflog_merges () {
+       git rev-parse $(
+               sed -ne 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':.*~\1^2~p' \
+                       < "$GIT_DIR"/logs/HEAD
+       )
+}
+
+_x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+
+search_merges () {
+        git rev-list --all --grep="Merge branch '$1'" \
+                --pretty=tformat:"%P %s" |
+        sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}"
+}
+
+search_merge_targets () {
+       git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \
+               --pretty=tformat:"%H %s" --all |
+       sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} "
+}
+
+dry_run=
+early_exit=q
+scan_reflog=t
+scan_reflog_merges=
+scan_merges=
+scan_merge_targets=
+new_name=
+
+while test "$#" != 0; do
+       case "$1" in
+           -b|--branch)
+               shift
+               new_name="$1"
+               ;;
+           -n|--dry-run)
+               dry_run=t
+               ;;
+           --no-dry-run)
+               dry_run=
+               ;;
+           -k|--keep-going)
+               early_exit=
+               ;;
+           --no-keep-going)
+               early_exit=q
+               ;;
+           -m|--merges)
+               scan_merges=t
+               ;;
+           --no-merges)
+               scan_merges=
+               ;;
+           -l|--reflog)
+               scan_reflog=t
+               ;;
+           --no-reflog)
+               scan_reflog=
+               ;;
+           -r|--reflog_merges)
+               scan_reflog_merges=t
+               ;;
+           --no-reflog_merges)
+               scan_reflog_merges=
+               ;;
+           -t|--merge-targets)
+               scan_merge_targets=t
+               ;;
+           --no-merge-targets)
+               scan_merge_targets=
+               ;;
+           -a|--all)
+               scan_reflog=t
+               scan_reflog_merges=t
+               scan_merges=t
+               scan_merge_targets=t
+               ;;
+           --)
+               shift
+               break
+               ;;
+           *)
+               usage
+               ;;
+       esac
+       shift
+done
+
+test "$#" = 1 || usage
+
+all_strategies="$scan_reflog$scan_reflog_merges$scan_merges$scan_merge_targets"
+if test -z "$all_strategies"; then
+       die "must enable at least one of -lrmt"
+fi
+
+branch="$1"
+test -z "$new_name" && new_name="$branch"
+
+if test ! -z "$scan_reflog"; then
+       if test -r "$GIT_DIR"/logs/HEAD; then
+               candidates="$(search_reflog $branch)"
+       else
+               die 'reflog scanning requested, but' \
+                       '$GIT_DIR/logs/HEAD not readable'
+       fi
+fi
+if test ! -z "$scan_reflog_merges"; then
+       if test -r "$GIT_DIR"/logs/HEAD; then
+               candidates="$candidates $(search_reflog_merges $branch)"
+       else
+               die 'reflog scanning requested, but' \
+                       '$GIT_DIR/logs/HEAD not readable'
+       fi
+fi
+if test ! -z "$scan_merges"; then
+       candidates="$candidates $(search_merges $branch)"
+fi
+if test ! -z "$scan_merge_targets"; then
+       candidates="$candidates $(search_merge_targets $branch)"
+fi
+
+candidates="$(git rev-parse $candidates | sort -u)"
+
+if test -z "$candidates"; then
+       hint=
+       test "z$all_strategies" != "ztttt" \
+               && hint=" (maybe try again with -a)"
+       die "no candidates for $branch found$hint"
+fi
+
+echo "** Candidates for $branch **"
+for cmt in $candidates; do
+       git --no-pager log --pretty=tformat:"%ct:%h [%cr] %s" --abbrev-commit -1 $cmt
+done \
+| sort -n | cut -d: -f2-
+
+newest="$(git rev-list -1 $candidates)"
+if test ! -z "$dry_run"; then
+       printf "** Most recent: "
+       git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+elif ! git rev-parse --verify --quiet $new_name >/dev/null; then
+       printf "** Restoring $new_name to "
+       git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+       git branch $new_name $newest
+else
+       printf "Most recent: "
+       git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+       echo "** $new_name already exists, doing nothing"
+fi
index 28a3c0e46ecf9951f3f42a025a288a65c70e0424..60cbab65d3f8230be3041a13fac2fd9f9b3018d5 100644 (file)
@@ -615,7 +615,9 @@ show_new_revisions()
                revspec=$oldrev..$newrev
        fi
 
-       git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
+       other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ |
+           grep -F -v $refname)
+       git rev-parse --not $other_branches |
        if [ -z "$custom_showrev" ]
        then
                git rev-list --pretty --stdin $revspec
index d93cf960f9eaf05eec11b67746142e6e94d719cb..13401f1baf57dc87a7a95bd778f4e1becbdbe323 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -229,7 +229,7 @@ static char *path_ok(char *directory)
        }
 
        if (!path) {
-               logerror("'%s': unable to chdir or not a git archive", dir);
+               logerror("'%s' does not appear to be a git repository", dir);
                return NULL;
        }
 
diff --git a/date.c b/date.c
index 950b88fdcf74f550a582684f1702ffb58c62c7f9..d75dff42405404ec86820a534620c1ba68cd9116 100644 (file)
--- a/date.c
+++ b/date.c
@@ -89,6 +89,11 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        struct tm *tm;
        static char timebuf[200];
 
+       if (mode == DATE_RAW) {
+               snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
+               return timebuf;
+       }
+
        if (mode == DATE_RELATIVE) {
                unsigned long diff;
                struct timeval now;
@@ -615,6 +620,8 @@ enum date_mode parse_date_format(const char *format)
                return DATE_LOCAL;
        else if (!strcmp(format, "default"))
                return DATE_NORMAL;
+       else if (!strcmp(format, "raw"))
+               return DATE_RAW;
        else
                die("unknown date format %s", format);
 }
index a41e1ec07ccd707969aa51768dac3e2b6356ddc6..79d06068344f5a602f6c8799f6671ccbf98cf49c 100644 (file)
@@ -513,3 +513,18 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
                exit(128);
        return 0;
 }
+
+int index_differs_from(const char *def, int diff_flags)
+{
+       struct rev_info rev;
+
+       init_revisions(&rev, NULL);
+       setup_revisions(0, NULL, &rev, def);
+       DIFF_OPT_SET(&rev.diffopt, QUIET);
+       DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
+       rev.diffopt.flags |= diff_flags;
+       run_diff_index(&rev, 1);
+       if (rev.pending.alloc)
+               free(rev.pending.objects);
+       return (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0);
+}
index 0dbd9dad8b100cdd4918571636ad3e9a1a0a2abf..0a14268ba952da5ff66be753cc0cc147ba64ee2b 100644 (file)
@@ -247,6 +247,7 @@ void diff_no_index(struct rev_info *revs,
        else
                revs->diffopt.paths = argv + argc - 2;
        revs->diffopt.nr_paths = 2;
+       revs->diffopt.skip_stat_unmatch = 1;
 
        DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
        DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
diff --git a/diff.c b/diff.c
index a5a540fd381495d2a75527b0f4fbe23ad668708f..ff3624e9f65b928929045cf2f917ae604c0e0c3f 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -184,11 +184,11 @@ static int remove_tempfile_installed;
 static void remove_tempfile(void)
 {
        int i;
-       for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
-               if (diff_temp[i].name == diff_temp[i].tmp_path) {
+       for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
+               if (diff_temp[i].name == diff_temp[i].tmp_path)
                        unlink(diff_temp[i].name);
-                       diff_temp[i].name = NULL;
-               }
+               diff_temp[i].name = NULL;
+       }
 }
 
 static void remove_tempfile_on_signal(int signo)
@@ -2326,15 +2326,12 @@ void diff_setup(struct diff_options *options)
        options->break_opt = -1;
        options->rename_limit = -1;
        options->dirstat_percent = 3;
-       DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
        options->context = 3;
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
        if (diff_use_color_default > 0)
                DIFF_OPT_SET(options, COLOR_DIFF);
-       else
-               DIFF_OPT_CLR(options, COLOR_DIFF);
        options->detect_rename = diff_detect_rename_default;
 
        if (!diff_mnemonic_prefix) {
@@ -2570,13 +2567,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 
        /* xdiff options */
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
        else if (!strcmp(arg, "--ignore-space-at-eol"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--patience"))
-               options->xdl_opts |= XDF_PATIENCE_DIFF;
+               DIFF_XDL_SET(options, PATIENCE_DIFF);
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
@@ -2597,10 +2594,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, COLOR_DIFF);
        else if (!strcmp(arg, "--no-color"))
                DIFF_OPT_CLR(options, COLOR_DIFF);
-       else if (!strcmp(arg, "--color-words"))
-               options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
+       else if (!strcmp(arg, "--color-words")) {
+               DIFF_OPT_SET(options, COLOR_DIFF);
+               DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
+       }
        else if (!prefixcmp(arg, "--color-words=")) {
-               options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
+               DIFF_OPT_SET(options, COLOR_DIFF);
+               DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
                options->word_regex = arg + 14;
        }
        else if (!strcmp(arg, "--exit-code"))
diff --git a/diff.h b/diff.h
index 23cd90c2e64cf8be44999be812a0765cbe36c9f8..6616877ee5d15a8101cbdea0271cc18f8837d2f1 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -69,6 +69,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #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)
+#define DIFF_XDL_TST(opts, flag)    ((opts)->xdl_opts & XDF_##flag)
+#define DIFF_XDL_SET(opts, flag)    ((opts)->xdl_opts |= XDF_##flag)
+#define DIFF_XDL_CLR(opts, flag)    ((opts)->xdl_opts &= ~XDF_##flag)
 
 struct diff_options {
        const char *filter;
@@ -265,4 +268,6 @@ extern int diff_result_code(struct diff_options *, int);
 
 extern void diff_no_index(struct rev_info *, int, const char **, int, const char *);
 
+extern int index_differs_from(const char *def, int diff_flags);
+
 #endif /* DIFF_H */
diff --git a/dump-cache-tree.c b/dump-cache-tree.c
deleted file mode 100644 (file)
index 1f73f1e..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-#include "cache.h"
-#include "tree.h"
-#include "cache-tree.h"
-
-
-static void dump_one(struct cache_tree *it, const char *pfx, const char *x)
-{
-       if (it->entry_count < 0)
-               printf("%-40s %s%s (%d subtrees)\n",
-                      "invalid", x, pfx, it->subtree_nr);
-       else
-               printf("%s %s%s (%d entries, %d subtrees)\n",
-                      sha1_to_hex(it->sha1), x, pfx,
-                      it->entry_count, it->subtree_nr);
-}
-
-static int dump_cache_tree(struct cache_tree *it,
-                          struct cache_tree *ref,
-                          const char *pfx)
-{
-       int i;
-       int errs = 0;
-
-       if (!it || !ref)
-               /* missing in either */
-               return 0;
-
-       if (it->entry_count < 0) {
-               dump_one(it, pfx, "");
-               dump_one(ref, pfx, "#(ref) ");
-               if (it->subtree_nr != ref->subtree_nr)
-                       errs = 1;
-       }
-       else {
-               dump_one(it, pfx, "");
-               if (hashcmp(it->sha1, ref->sha1) ||
-                   ref->entry_count != it->entry_count ||
-                   ref->subtree_nr != it->subtree_nr) {
-                       dump_one(ref, pfx, "#(ref) ");
-                       errs = 1;
-               }
-       }
-
-       for (i = 0; i < it->subtree_nr; i++) {
-               char path[PATH_MAX];
-               struct cache_tree_sub *down = it->down[i];
-               struct cache_tree_sub *rdwn;
-
-               rdwn = cache_tree_sub(ref, down->name);
-               sprintf(path, "%s%.*s/", pfx, down->namelen, down->name);
-               if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
-                       errs = 1;
-       }
-       return errs;
-}
-
-int main(int ac, char **av)
-{
-       struct cache_tree *another = cache_tree();
-       if (read_cache() < 0)
-               die("unable to read index file");
-       cache_tree_update(another, active_cache, active_nr, 0, 1);
-       return dump_cache_tree(active_cache_tree, another, "");
-}
index f234066defd637dc3c048ba3d9eb43f1ecbad446..217c12577f52b8ff9d535a086ec75d54107ee01c 100644 (file)
@@ -23,35 +23,10 @@ const char *system_path(const char *path)
        assert(argv0_path);
        assert(is_absolute_path(argv0_path));
 
-       if (!prefix) {
-               const char *strip[] = {
-                       GIT_EXEC_PATH,
-                       BINDIR,
-                       0
-               };
-               const char **s;
-
-               for (s = strip; *s; s++) {
-                       const char *sargv = argv0_path + strlen(argv0_path);
-                       const char *ss = *s + strlen(*s);
-                       while (argv0_path < sargv && *s < ss
-                               && (*sargv == *ss ||
-                                   (is_dir_sep(*sargv) && is_dir_sep(*ss)))) {
-                               sargv--;
-                               ss--;
-                       }
-                       if (*s == ss) {
-                               struct strbuf d = STRBUF_INIT;
-                               /* We also skip the trailing directory separator. */
-                               assert(sargv - argv0_path - 1 >= 0);
-                               strbuf_add(&d, argv0_path, sargv - argv0_path - 1);
-                               prefix = strbuf_detach(&d, NULL);
-                               break;
-                       }
-               }
-       }
-
-       if (!prefix) {
+       if (!prefix &&
+           !(prefix = strip_path_suffix(argv0_path, GIT_EXEC_PATH)) &&
+           !(prefix = strip_path_suffix(argv0_path, BINDIR)) &&
+           !(prefix = strip_path_suffix(argv0_path, "git"))) {
                prefix = PREFIX;
                fprintf(stderr, "RUNTIME_PREFIX requested, "
                                "but prefix computation failed.  "
index 1935206be04f3ecc52512958879734b0895d1953..3748ddf48d9bdeea890af805016b69e76493a79d 100644 (file)
@@ -817,9 +817,8 @@ static void start_packfile(void)
        struct pack_header hdr;
        int pack_fd;
 
-       snprintf(tmpfile, sizeof(tmpfile),
-               "%s/pack/tmp_pack_XXXXXX", get_object_directory());
-       pack_fd = xmkstemp(tmpfile);
+       pack_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                             "pack/tmp_pack_XXXXXX");
        p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
        strcpy(p->pack_name, tmpfile);
        p->pack_fd = pack_fd;
@@ -869,7 +868,7 @@ static char *create_index(void)
        /* Generate the fan-out array. */
        c = idx;
        for (i = 0; i < 256; i++) {
-               struct object_entry **next = c;;
+               struct object_entry **next = c;
                while (next < last) {
                        if ((*next)->sha1[0] != i)
                                break;
@@ -879,9 +878,8 @@ static char *create_index(void)
                c = next;
        }
 
-       snprintf(tmpfile, sizeof(tmpfile),
-               "%s/pack/tmp_idx_XXXXXX", get_object_directory());
-       idx_fd = xmkstemp(tmpfile);
+       idx_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                            "pack/tmp_idx_XXXXXX");
        f = sha1fd(idx_fd, tmpfile);
        sha1write(f, array, 256 * sizeof(int));
        git_SHA1_Init(&ctx);
@@ -907,9 +905,7 @@ static char *keep_pack(char *curr_index_name)
        chmod(pack_data->pack_name, 0444);
        chmod(curr_index_name, 0444);
 
-       snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
-                get_object_directory(), sha1_to_hex(pack_data->sha1));
-       keep_fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+       keep_fd = odb_pack_keep(name, sizeof(name), pack_data->sha1);
        if (keep_fd < 0)
                die("cannot create keep file");
        write_or_die(keep_fd, keep_msg, strlen(keep_msg));
@@ -945,6 +941,7 @@ static void end_packfile(void)
 {
        struct packed_git *old_p = pack_data, *new_p;
 
+       clear_delta_base_cache();
        if (object_count) {
                char *idx_name;
                int i;
index ca60356d0082123cd1e9572572528d8265be43a3..5f129a42030917bc5dcab67aeaf67a5f00c17677 100755 (executable)
                $repo->get_color('color.interactive.header', 'bold'),
                $repo->get_color('color.interactive.help', 'red bold'),
        ) : ();
+my $error_color = ();
+if ($menu_use_color) {
+       my $help_color_spec = ($repo->config('color.interactive.help') or
+                               'red bold');
+       $error_color = $repo->get_color('color.interactive.error',
+                                       $help_color_spec);
+}
 
 my $diff_use_color = $repo->get_colorbool('color.diff');
 my ($fraginfo_color) =
 
 my $normal_color = $repo->get_color("", "reset");
 
+my $use_readkey = 0;
+sub ReadMode;
+sub ReadKey;
+if ($repo->config_bool("interactive.singlekey")) {
+       eval {
+               require Term::ReadKey;
+               Term::ReadKey->import;
+               $use_readkey = 1;
+       };
+}
+
 sub colored {
        my $color = shift;
        my $string = join("", @_);
@@ -325,6 +343,10 @@ sub highlight_prefix {
        return "$prompt_color$prefix$normal_color$remainder";
 }
 
+sub error_msg {
+       print STDERR colored $error_color, @_;
+}
+
 sub list_and_choose {
        my ($opts, @stuff) = @_;
        my (@chosen, @return);
@@ -420,12 +442,12 @@ sub list_and_choose {
                        else {
                                $bottom = $top = find_unique($choice, @stuff);
                                if (!defined $bottom) {
-                                       print "Huh ($choice)?\n";
+                                       error_msg "Huh ($choice)?\n";
                                        next TOPLOOP;
                                }
                        }
                        if ($opts->{SINGLETON} && $bottom != $top) {
-                               print "Huh ($choice)?\n";
+                               error_msg "Huh ($choice)?\n";
                                next TOPLOOP;
                        }
                        for ($i = $bottom-1; $i <= $top-1; $i++) {
@@ -758,11 +780,32 @@ sub diff_applies {
        return close $fh;
 }
 
+sub _restore_terminal_and_die {
+       ReadMode 'restore';
+       print "\n";
+       exit 1;
+}
+
+sub prompt_single_character {
+       if ($use_readkey) {
+               local $SIG{TERM} = \&_restore_terminal_and_die;
+               local $SIG{INT} = \&_restore_terminal_and_die;
+               ReadMode 'cbreak';
+               my $key = ReadKey 0;
+               ReadMode 'restore';
+               print "$key" if defined $key;
+               print "\n";
+               return $key;
+       } else {
+               return <STDIN>;
+       }
+}
+
 sub prompt_yesno {
        my ($prompt) = @_;
        while (1) {
                print colored $prompt_color, $prompt;
-               my $line = <STDIN>;
+               my $line = prompt_single_character;
                return 0 if $line =~ /^n/i;
                return 1 if $line =~ /^y/i;
        }
@@ -801,6 +844,7 @@ sub help_patch_cmd {
 a - stage this and all the remaining hunks in the file
 d - do not stage this hunk nor any of the remaining hunks in the file
 g - select a hunk to go to
+/ - search for a hunk matching the given regex
 j - leave this hunk undecided, see next undecided hunk
 J - leave this hunk undecided, see next hunk
 k - leave this hunk undecided, see previous undecided hunk
@@ -892,7 +936,7 @@ sub patch_update_file {
                        print @{$mode->{DISPLAY}};
                        print colored $prompt_color,
                                "Stage mode change [y/n/a/d/?]? ";
-                       my $line = <STDIN>;
+                       my $line = prompt_single_character;
                        if ($line =~ /^y/i) {
                                $mode->{USE} = 1;
                                last;
@@ -929,25 +973,25 @@ sub patch_update_file {
                for ($i = 0; $i < $ix; $i++) {
                        if (!defined $hunk[$i]{USE}) {
                                $prev = 1;
-                               $other .= '/k';
+                               $other .= ',k';
                                last;
                        }
                }
                if ($ix) {
-                       $other .= '/K';
+                       $other .= ',K';
                }
                for ($i = $ix + 1; $i < $num; $i++) {
                        if (!defined $hunk[$i]{USE}) {
                                $next = 1;
-                               $other .= '/j';
+                               $other .= ',j';
                                last;
                        }
                }
                if ($ix < $num - 1) {
-                       $other .= '/J';
+                       $other .= ',J';
                }
                if ($num > 1) {
-                       $other .= '/g';
+                       $other .= ',g';
                }
                for ($i = 0; $i < $num; $i++) {
                        if (!defined $hunk[$i]{USE}) {
@@ -958,14 +1002,14 @@ sub patch_update_file {
                last if (!$undecided);
 
                if (hunk_splittable($hunk[$ix]{TEXT})) {
-                       $other .= '/s';
+                       $other .= ',s';
                }
-               $other .= '/e';
+               $other .= ',e';
                for (@{$hunk[$ix]{DISPLAY}}) {
                        print;
                }
-               print colored $prompt_color, "Stage this hunk [y/n/a/d$other/?]? ";
-               my $line = <STDIN>;
+               print colored $prompt_color, "Stage this hunk [y,n,a,d,/$other,?]? ";
+               my $line = prompt_single_character;
                if ($line) {
                        if ($line =~ /^y/i) {
                                $hunk[$ix]{USE} = 1;
@@ -993,14 +1037,17 @@ sub patch_update_file {
                                        }
                                        print "go to which hunk$extra? ";
                                        $response = <STDIN>;
+                                       if (!defined $response) {
+                                               $response = '';
+                                       }
                                        chomp $response;
                                }
                                if ($response !~ /^\s*\d+\s*$/) {
-                                       print STDERR "Invalid number: '$response'\n";
+                                       error_msg "Invalid number: '$response'\n";
                                } elsif (0 < $response && $response <= $num) {
                                        $ix = $response - 1;
                                } else {
-                                       print STDERR "Sorry, only $num hunks available.\n";
+                                       error_msg "Sorry, only $num hunks available.\n";
                                }
                                next;
                        }
@@ -1013,30 +1060,76 @@ sub patch_update_file {
                                }
                                next;
                        }
-                       elsif ($other =~ /K/ && $line =~ /^K/) {
-                               $ix--;
-                               next;
-                       }
-                       elsif ($other =~ /J/ && $line =~ /^J/) {
-                               $ix++;
+                       elsif ($line =~ m|^/(.*)|) {
+                               my $regex = $1;
+                               if ($1 eq "") {
+                                       print colored $prompt_color, "search for regex? ";
+                                       $regex = <STDIN>;
+                                       if (defined $regex) {
+                                               chomp $regex;
+                                       }
+                               }
+                               my $search_string;
+                               eval {
+                                       $search_string = qr{$regex}m;
+                               };
+                               if ($@) {
+                                       my ($err,$exp) = ($@, $1);
+                                       $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
+                                       error_msg "Malformed search regexp $exp: $err\n";
+                                       next;
+                               }
+                               my $iy = $ix;
+                               while (1) {
+                                       my $text = join ("", @{$hunk[$iy]{TEXT}});
+                                       last if ($text =~ $search_string);
+                                       $iy++;
+                                       $iy = 0 if ($iy >= $num);
+                                       if ($ix == $iy) {
+                                               error_msg "No hunk matches the given pattern\n";
+                                               last;
+                                       }
+                               }
+                               $ix = $iy;
                                next;
                        }
-                       elsif ($other =~ /k/ && $line =~ /^k/) {
-                               while (1) {
+                       elsif ($line =~ /^K/) {
+                               if ($other =~ /K/) {
                                        $ix--;
-                                       last if (!$ix ||
-                                                !defined $hunk[$ix]{USE});
+                               }
+                               else {
+                                       error_msg "No previous hunk\n";
                                }
                                next;
                        }
-                       elsif ($other =~ /j/ && $line =~ /^j/) {
-                               while (1) {
+                       elsif ($line =~ /^J/) {
+                               if ($other =~ /J/) {
                                        $ix++;
-                                       last if ($ix >= $num ||
-                                                !defined $hunk[$ix]{USE});
+                               }
+                               else {
+                                       error_msg "No next hunk\n";
                                }
                                next;
                        }
+                       elsif ($line =~ /^k/) {
+                               if ($other =~ /k/) {
+                                       while (1) {
+                                               $ix--;
+                                               last if (!$ix ||
+                                                        !defined $hunk[$ix]{USE});
+                                       }
+                               }
+                               else {
+                                       error_msg "No previous hunk\n";
+                               }
+                               next;
+                       }
+                       elsif ($line =~ /^j/) {
+                               if ($other !~ /j/) {
+                                       error_msg "No next hunk\n";
+                                       next;
+                               }
+                       }
                        elsif ($other =~ /s/ && $line =~ /^s/) {
                                my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
                                if (1 < @split) {
index b598b4332a6224bb5c8363afdcff7cb9a3f3dbcd..d3390755fc687a611e89320a7bbfb4ead512c863 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -23,6 +23,8 @@ resolvemsg=     override error message when patch failure occurs
 r,resolved      to be used after a patch failure
 skip            skip the current patch
 abort           restore the original branch and abort the patching operation.
+committer-date-is-author-date    lie about committer date
+ignore-date     use current timestamp for author date
 rebasing*       (internal use for git-rebase)"
 
 . git-sh-setup
@@ -133,6 +135,8 @@ dotest="$GIT_DIR/rebase-apply"
 sign= utf8=t keep= skip= interactive= resolved= rebasing= abort=
 resolvemsg= resume=
 git_apply_opt=
+committer_date_is_author_date=
+ignore_date=
 
 while test $# != 0
 do
@@ -170,6 +174,10 @@ do
                git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
        --reject)
                git_apply_opt="$git_apply_opt $1" ;;
+       --committer-date-is-author-date)
+               committer_date_is_author_date=t ;;
+       --ignore-date)
+               ignore_date=t ;;
        --)
                shift; break ;;
        *)
@@ -213,6 +221,9 @@ then
        resume=yes
 
        case "$skip,$abort" in
+       t,t)
+               die "Please make up your mind. --skip or --abort?"
+               ;;
        t,)
                git rerere clear
                git read-tree --reset -u HEAD HEAD
@@ -221,12 +232,19 @@ then
                git update-ref ORIG_HEAD $orig_head
                ;;
        ,t)
+               if test -f "$dotest/rebasing"
+               then
+                       exec git rebase --abort
+               fi
                git rerere clear
-               git read-tree --reset -u HEAD ORIG_HEAD
-               git reset ORIG_HEAD
+               test -f "$dotest/dirtyindex" || {
+                       git read-tree --reset -u HEAD ORIG_HEAD
+                       git reset ORIG_HEAD
+               }
                rm -fr "$dotest"
                exit ;;
        esac
+       rm -f "$dotest/dirtyindex"
 else
        # Make sure we are not given --skip, --resolved, nor --abort
        test "$skip$resolved$abort" = "" ||
@@ -279,7 +297,11 @@ fi
 case "$resolved" in
 '')
        files=$(git diff-index --cached --name-only HEAD --) || exit
-       test "$files" && die "Dirty index: cannot apply patches (dirty: $files)"
+       if test "$files"
+       then
+               : >"$dotest/dirtyindex"
+               die "Dirty index: cannot apply patches (dirty: $files)"
+       fi
 esac
 
 if test "$(cat "$dotest/utf8")" = t
@@ -520,7 +542,18 @@ do
 
        tree=$(git write-tree) &&
        parent=$(git rev-parse --verify HEAD) &&
-       commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
+       commit=$(
+               if test -n "$ignore_date"
+               then
+                       GIT_AUTHOR_DATE=
+               fi
+               if test -n "$committer_date_is_author_date"
+               then
+                       GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
+                       export GIT_COMMITTER_DATE
+               fi &&
+               git commit-tree $tree -p $parent <"$dotest/final-commit"
+       ) &&
        git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
        stop_here $this
 
index 85db4ba40022e3a9e5790879d6d21fa59475b316..e313bdea70d0a765106aa42a17a66f01d3d0f7d8 100755 (executable)
@@ -284,62 +284,74 @@ filter_skipped() {
        _skip="$2"
 
        if [ -z "$_skip" ]; then
-               eval "$_eval"
+               eval "$_eval" | {
+                       while read line
+                       do
+                               echo "$line &&"
+                       done
+                       echo ':'
+               }
                return
        fi
 
        # Let's parse the output of:
        # "git rev-list --bisect-vars --bisect-all ..."
-       eval "$_eval" | while read hash line
-       do
-               case "$VARS,$FOUND,$TRIED,$hash" in
-                       # We display some vars.
-                       1,*,*,*) echo "$hash $line" ;;
-
-                       # Split line.
-                       ,*,*,---*) ;;
-
-                       # We had nothing to search.
+       eval "$_eval" | {
+               VARS= FOUND= TRIED=
+               while read hash line
+               do
+                       case "$VARS,$FOUND,$TRIED,$hash" in
+                       1,*,*,*)
+                               # "bisect_foo=bar" read from rev-list output.
+                               echo "$hash &&"
+                               ;;
+                       ,*,*,---*)
+                               # Separator
+                               ;;
                        ,,,bisect_rev*)
-                               echo "bisect_rev="
+                               # We had nothing to search.
+                               echo "bisect_rev= &&"
                                VARS=1
                                ;;
-
-                       # We did not find a good bisect rev.
-                       # This should happen only if the "bad"
-                       # commit is also a "skip" commit.
                        ,,*,bisect_rev*)
-                               echo "bisect_rev=$TRIED"
+                               # We did not find a good bisect rev.
+                               # This should happen only if the "bad"
+                               # commit is also a "skip" commit.
+                               echo "bisect_rev='$TRIED' &&"
                                VARS=1
                                ;;
-
-                       # We are searching.
                        ,,*,*)
+                               # We are searching.
                                TRIED="${TRIED:+$TRIED|}$hash"
                                case "$_skip" in
                                *$hash*) ;;
                                *)
-                                       echo "bisect_rev=$hash"
-                                       echo "bisect_tried=\"$TRIED\""
+                                       echo "bisect_rev=$hash &&"
+                                       echo "bisect_tried='$TRIED' &&"
                                        FOUND=1
                                        ;;
                                esac
                                ;;
-
-                       # We have already found a rev to be tested.
-                       ,1,*,bisect_rev*) VARS=1 ;;
-                       ,1,*,*) ;;
-
-                       # ???
-                       *) die "filter_skipped error " \
-                           "VARS: '$VARS' " \
-                           "FOUND: '$FOUND' " \
-                           "TRIED: '$TRIED' " \
-                           "hash: '$hash' " \
-                           "line: '$line'"
-                       ;;
-               esac
-       done
+                       ,1,*,bisect_rev*)
+                               # We have already found a rev to be tested.
+                               VARS=1
+                               ;;
+                       ,1,*,*)
+                               ;;
+                       *)
+                               # Unexpected input
+                               echo "die 'filter_skipped error'"
+                               die "filter_skipped error " \
+                                   "VARS: '$VARS' " \
+                                   "FOUND: '$FOUND' " \
+                                   "TRIED: '$TRIED' " \
+                                   "hash: '$hash' " \
+                                   "line: '$line'"
+                               ;;
+                       esac
+               done
+               echo ':'
+       }
 }
 
 exit_if_skipped_commits () {
@@ -500,7 +512,7 @@ bisect_next() {
        # commit is also a "skip" commit (see above).
        exit_if_skipped_commits "$bisect_rev"
 
-       bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this"
+       bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this (roughly $bisect_steps steps)"
 }
 
 bisect_visualize() {
index 079cbe9440dc73ba8703fe2efb70bd6677535a05..dcf41277502f5fb8fc99b310ebdaf63b6781b3ea 100644 (file)
@@ -303,6 +303,8 @@ extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern int xdup(int fd);
 extern FILE *xfdopen(int fd, const char *mode);
 extern int xmkstemp(char *template);
+extern int odb_mkstemp(char *template, size_t limit, const char *pattern);
+extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1);
 
 static inline size_t xsize_t(off_t len)
 {
index eb62f719b0ad1d015bb116e8447971c5fe7fdba6..20f6f5175070f62ab74eda4839c637afe1004d49 100755 (executable)
@@ -108,7 +108,7 @@ OPTIONS_SPEC=
 . git-sh-setup
 
 if [ "$(is_bare_repository)" = false ]; then
-       git diff-files --quiet &&
+       git diff-files --ignore-submodules --quiet &&
        git diff-index --cached --quiet HEAD -- ||
        die "Cannot rewrite branch(es) with a dirty working directory."
 fi
@@ -220,13 +220,21 @@ die ""
 # Remove tempdir on exit
 trap 'cd ../..; rm -rf "$tempdir"' 0
 
+ORIG_GIT_DIR="$GIT_DIR"
+ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
+ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
+GIT_WORK_TREE=.
+export GIT_DIR GIT_WORK_TREE
+
 # Make sure refs/original is empty
-git for-each-ref > "$tempdir"/backup-refs
+git for-each-ref > "$tempdir"/backup-refs || exit
 while read sha1 type name
 do
        case "$force,$name" in
        ,$orig_namespace*)
-               die "Namespace $orig_namespace not empty"
+               die "Cannot create a new backup.
+A previous backup already exists in $orig_namespace
+Force overwriting the backup with -f"
        ;;
        t,$orig_namespace*)
                git update-ref -d "$name" $sha1
@@ -234,15 +242,10 @@ do
        esac
 done < "$tempdir"/backup-refs
 
-ORIG_GIT_DIR="$GIT_DIR"
-ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
-ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
-GIT_WORK_TREE=.
-export GIT_DIR GIT_WORK_TREE
-
 # The refs should be updated if their heads were rewritten
-git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" |
-sed -e '/^^/d' >"$tempdir"/heads
+git rev-parse --no-flags --revs-only --symbolic-full-name \
+       --default HEAD "$@" > "$tempdir"/raw-heads || exit
+sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads
 
 test -s "$tempdir"/heads ||
        die "Which ref do you want to rewrite?"
@@ -251,8 +254,6 @@ GIT_INDEX_FILE="$(pwd)/../index"
 export GIT_INDEX_FILE
 git read-tree || die "Could not seed the index"
 
-ret=0
-
 # map old->new commit ids for rewriting parents
 mkdir ../map || die "Could not create map/ directory"
 
@@ -315,10 +316,11 @@ while read commit parents; do
                        die "tree filter failed: $filter_tree"
 
                (
-                       git diff-index -r --name-only $commit
+                       git diff-index -r --name-only $commit &&
                        git ls-files --others
-               ) |
-               git update-index --add --replace --remove --stdin
+               ) > "$tempdir"/tree-state || exit
+               git update-index --add --replace --remove --stdin \
+                       < "$tempdir"/tree-state || exit
        fi
 
        eval "$filter_index" < /dev/null ||
@@ -339,7 +341,8 @@ while read commit parents; do
                eval "$filter_msg" > ../message ||
                        die "msg filter failed: $filter_msg"
        @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
-               $(git write-tree) $parentstr < ../message > ../map/$commit
+               $(git write-tree) $parentstr < ../message > ../map/$commit ||
+                       die "could not write rewritten commit"
 done <../revs
 
 # In case of a subdirectory filter, it is possible that a specified head
@@ -407,7 +410,8 @@ do
                        die "Could not rewrite $ref"
        ;;
        esac
-       git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1
+       git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 ||
+                exit
 done < "$tempdir"/heads
 
 # TODO: This should possibly go, with the semantics that all positive given
@@ -469,20 +473,21 @@ rm -rf "$tempdir"
 
 trap - 0
 
+unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
+test -z "$ORIG_GIT_DIR" || {
+       GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
+}
+test -z "$ORIG_GIT_WORK_TREE" || {
+       GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
+       export GIT_WORK_TREE
+}
+test -z "$ORIG_GIT_INDEX_FILE" || {
+       GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
+       export GIT_INDEX_FILE
+}
+
 if [ "$(is_bare_repository)" = false ]; then
-       unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
-       test -z "$ORIG_GIT_DIR" || {
-               GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
-       }
-       test -z "$ORIG_GIT_WORK_TREE" || {
-               GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
-               export GIT_WORK_TREE
-       }
-       test -z "$ORIG_GIT_INDEX_FILE" || {
-               GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
-               export GIT_INDEX_FILE
-       }
-       git read-tree -u -m HEAD
+       git read-tree -u -m HEAD || exit
 fi
 
-exit $ret
+exit 0
index 09f3a1068f69718fa544e3e614b1040ed33ab292..87fa88af5526c8e27b823a65ca15bee4085f8ef2 100755 (executable)
@@ -13,7 +13,6 @@ SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 . git-sh-setup
 require_work_tree
-prefix=$(git rev-parse --show-prefix)
 
 # Returns true if the mode reflects a symlink
 is_symlink () {
@@ -127,6 +126,14 @@ check_unchanged () {
     fi
 }
 
+checkout_staged_file () {
+    tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^    ]*\)    ')
+
+    if test $? -eq 0 -a -n "$tmpfile" ; then
+       mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
+    fi
+}
+
 merge_file () {
     MERGED="$1"
 
@@ -153,9 +160,9 @@ merge_file () {
     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$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
+    base_present   && checkout_staged_file 1 "$MERGED" "$BASE"
+    local_present  && checkout_staged_file 2 "$MERGED" "$LOCAL"
+    remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
 
     if test -z "$local_mode" -o -z "$remote_mode"; then
        echo "Deleted merge conflict for '$MERGED':"
index 2c7f432dc04254dcb9906f4d078eb16d195848ca..25adddfddfc0a7e2cc33ea0dc1e1d7a101514972 100755 (executable)
@@ -171,6 +171,11 @@ case "$merge_head" in
                echo >&2 "Cannot merge multiple branches into empty head"
                exit 1
        fi
+       if test true = "$rebase"
+       then
+               echo >&2 "Cannot rebase onto multiple branches"
+               exit 1
+       fi
        ;;
 esac
 
index cebaee1cc9dfc28d80173583b144a480be2f9bfd..9a6ba2b9874e12d31adeba354d8d44436ceadaf3 100755 (executable)
@@ -63,7 +63,7 @@ tmp_info="$tmp_dir/info"
 commit=$(git rev-parse HEAD)
 
 mkdir $tmp_dir || exit 2
-while read patch_name level garbage
+while read patch_name level garbage <&3
 do
        case "$patch_name" in ''|'#'*) continue;; esac
        case "$level" in
@@ -134,5 +134,5 @@ do
                commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
                git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
        fi
-done <"$QUILT_PATCHES/series"
+done 3<"$QUILT_PATCHES/series"
 rm -rf $tmp_dir || exit 5
index 6d3eddbada5e1a5a38e2b909c75909b8e9d5fda8..368c0ef4342df990a5617fd6fbe2cd973b1b208b 100755 (executable)
@@ -310,6 +310,7 @@ do
        esac
        shift
 done
+test $# -gt 2 && usage
 
 # Make sure we do not have $GIT_DIR/rebase-apply
 if test -z "$do_merge"
index 458a497af810c7bb188a5aafb80c32aa0bc05264..be6db5e805d62e0440f7de3c1f6ee3fcd16a2ed3 100755 (executable)
@@ -88,32 +88,79 @@ if [ -z "$names" ]; then
                echo Nothing new to pack.
        fi
 fi
-for name in $names ; do
-       fullbases="$fullbases pack-$name"
-       chmod a-w "$PACKTMP-$name.pack"
-       chmod a-w "$PACKTMP-$name.idx"
-       mkdir -p "$PACKDIR" || exit
 
+# Ok we have prepared all new packfiles.
+mkdir -p "$PACKDIR" || exit
+
+# First see if there are packs of the same name and if so
+# if we can move them out of the way (this can happen if we
+# repacked immediately after packing fully.
+rollback=
+failed=
+for name in $names
+do
        for sfx in pack idx
        do
-               if test -f "$PACKDIR/pack-$name.$sfx"
-               then
-                       mv -f "$PACKDIR/pack-$name.$sfx" \
-                               "$PACKDIR/old-pack-$name.$sfx"
-               fi
-       done &&
+               file=pack-$name.$sfx
+               test -f "$PACKDIR/$file" || continue
+               rm -f "$PACKDIR/old-$file" &&
+               mv "$PACKDIR/$file" "$PACKDIR/old-$file" || {
+                       failed=t
+                       break
+               }
+               rollback="$rollback $file"
+       done
+       test -z "$failed" || break
+done
+
+# If renaming failed for any of them, roll the ones we have
+# already renamed back to their original names.
+if test -n "$failed"
+then
+       rollback_failure=
+       for file in $rollback
+       do
+               mv "$PACKDIR/old-$file" "$PACKDIR/$file" ||
+               rollback_failure="$rollback_failure $file"
+       done
+       if test -n "$rollback_failure"
+       then
+               echo >&2 "WARNING: Some packs in use have been renamed by"
+               echo >&2 "WARNING: prefixing old- to their name, in order to"
+               echo >&2 "WARNING: replace them with the new version of the"
+               echo >&2 "WARNING: file.  But the operation failed, and"
+               echo >&2 "WARNING: attempt to rename them back to their"
+               echo >&2 "WARNING: original names also failed."
+               echo >&2 "WARNING: Please rename them in $PACKDIR manually:"
+               for file in $rollback_failure
+               do
+                       echo >&2 "WARNING:   old-$file -> $file"
+               done
+       fi
+       exit 1
+fi
+
+# Now the ones with the same name are out of the way...
+fullbases=
+for name in $names
+do
+       fullbases="$fullbases pack-$name"
+       chmod a-w "$PACKTMP-$name.pack"
+       chmod a-w "$PACKTMP-$name.idx"
        mv -f "$PACKTMP-$name.pack" "$PACKDIR/pack-$name.pack" &&
-       mv -f "$PACKTMP-$name.idx"  "$PACKDIR/pack-$name.idx" &&
-       test -f "$PACKDIR/pack-$name.pack" &&
-       test -f "$PACKDIR/pack-$name.idx" || {
-               echo >&2 "Couldn't replace the existing pack with updated one."
-               echo >&2 "The original set of packs have been saved as"
-               echo >&2 "old-pack-$name.{pack,idx} in $PACKDIR."
-               exit 1
-       }
-       rm -f "$PACKDIR/old-pack-$name.pack" "$PACKDIR/old-pack-$name.idx"
+       mv -f "$PACKTMP-$name.idx"  "$PACKDIR/pack-$name.idx" ||
+       exit
+done
+
+# Remove the "old-" files
+for name in $names
+do
+       rm -f "$PACKDIR/old-pack-$name.idx"
+       rm -f "$PACKDIR/old-pack-$name.pack"
 done
 
+# End of pack replacement.
+
 if test "$remove_redundant" = t
 then
        # We know $existing are all redundant.
index 2142308bcc6d2e2c4962859d18e12070cd4c1b1d..838233926f7ed07f781f2eb2e7547a2a5a086071 100755 (executable)
@@ -85,27 +85,14 @@ cd_to_toplevel () {
        cdup=$(git rev-parse --show-cdup)
        if test ! -z "$cdup"
        then
-               case "$cdup" in
-               /*)
-                       # Not quite the same as if we did "cd -P '$cdup'" when
-                       # $cdup contains ".." after symlink path components.
-                       # Don't fix that case at least until Git switches to
-                       # "cd -P" across the board.
-                       phys="$cdup"
-                       ;;
-               ..|../*|*/..|*/../*)
-                       # Interpret $cdup relative to the physical, not logical, cwd.
-                       # Probably /bin/pwd is more portable than passing -P to cd or pwd.
-                       phys="$(unset PWD; /bin/pwd)/$cdup"
-                       ;;
-               *)
-                       # There's no "..", so no need to make things absolute.
-                       phys="$cdup"
-                       ;;
-               esac
-
-               cd "$phys" || {
-                       echo >&2 "Cannot chdir to $phys, the toplevel of the working tree"
+               # The "-P" option says to follow "physical" directory
+               # structure instead of following symbolic links.  When cdup is
+               # "../", this means following the ".." entry in the current
+               # directory instead textually removing a symlink path element
+               # from the PWD shell variable.  The "-P" behavior is more
+               # consistent with the C-style chdir used by most of Git.
+               cd -P "$cdup" || {
+                       echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
                        exit 1
                }
        fi
index 2f47e065fe8b7ca856f4527d6a507a28f1b2a06b..204aab671ef78edc24acff4019a2f40a71a59020 100755 (executable)
@@ -5,7 +5,7 @@
 # Copyright (c) 2007 Lars Hjemli
 
 USAGE="[--quiet] [--cached] \
-[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
+[add <repo> [-b branch] <path>]|[status|init|update [-i|--init] [-N|--no-fetch]|summary [-n|--summary-limit <n>] [<commit>]] \
 [--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
 OPTIONS_SPEC=
 . git-sh-setup
@@ -16,6 +16,7 @@ command=
 branch=
 quiet=
 cached=
+nofetch=
 
 #
 # print stuff on stdout unless -q was specified
@@ -59,7 +60,7 @@ resolve_relative_url ()
 #
 module_list()
 {
-       git ls-files --stage -- "$@" | grep '^160000 '
+       git ls-files --error-unmatch --stage -- "$@" | grep '^160000 '
 }
 
 #
@@ -300,6 +301,10 @@ cmd_update()
                        shift
                        cmd_init "$@" || return
                        ;;
+               -N|--no-fetch)
+                       shift
+                       nofetch=1
+                       ;;
                --)
                        shift
                        break
@@ -345,8 +350,16 @@ cmd_update()
                        then
                                force="-f"
                        fi
-                       (unset GIT_DIR; cd "$path" && git-fetch &&
-                               git-checkout $force -q "$sha1") ||
+
+                       if test -z "$nofetch"
+                       then
+                               (unset GIT_DIR; cd "$path" &&
+                                       git-fetch) ||
+                               die "Unable to fetch in submodule path '$path'"
+                       fi
+
+                       (unset GIT_DIR; cd "$path" &&
+                                 git-checkout $force -q "$sha1") ||
                        die "Unable to checkout '$sha1' in submodule path '$path'"
 
                        say "Submodule path '$path': checked out '$sha1'"
index 79888a05c400ef495157a39aad1de12cf16ea7d2..959eb52f3fbe01c5f13eeb169549da98b0b9d64f 100755 (executable)
@@ -438,7 +438,17 @@ sub cmd_dcommit {
                die "Unable to determine upstream SVN information from ",
                    "$head history.\nPerhaps the repository is empty.";
        }
-       $url = defined $_commit_url ? $_commit_url : $gs->full_url;
+
+       if (defined $_commit_url) {
+               $url = $_commit_url;
+       } else {
+               $url = eval { command_oneline('config', '--get',
+                             "svn-remote.$gs->{repo_id}.commiturl") };
+               if (!$url) {
+                       $url = $gs->full_url
+               }
+       }
+
        my $last_rev = $_revision if defined $_revision;
        if ($url) {
                print "Committing to $url ...\n";
@@ -670,7 +680,11 @@ sub cmd_create_ignore {
        $gs->prop_walk($gs->{path}, $r, sub {
                my ($gs, $path, $props) = @_;
                # $path is of the form /path/to/dir/
-               my $ignore = '.' . $path . '.gitignore';
+               $path = '.' . $path;
+               # SVN can have attributes on empty directories,
+               # which git won't track
+               mkpath([$path]) unless -d $path;
+               my $ignore = $path . '.gitignore';
                my $s = $props->{'svn:ignore'} or return;
                open(GITIGNORE, '>', $ignore)
                  or fatal("Failed to open `$ignore' for writing: $!");
@@ -1693,6 +1707,7 @@ sub find_by_url { # repos_root and, path are optional
                        my $prefix = '';
                        if ($rwr) {
                                $z = $rwr;
+                               remove_username($z);
                        } elsif (defined $svm) {
                                $z = $svm->{source};
                                $prefix = $svm->{replace};
@@ -2389,22 +2404,8 @@ sub find_parent_branch {
        print STDERR  "Found possible branch point: ",
                      "$new_url => ", $self->full_url, ", $r\n";
        $branch_from =~ s#^/##;
-       my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
-       unless ($gs) {
-               my $ref_id = $self->{ref_id};
-               $ref_id =~ s/\@\d+$//;
-               $ref_id .= "\@$r";
-               # 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";
-               my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
-               if ($u =~ s#^\Q$url\E(/|$)##) {
-                       $p = $u;
-                       $u = $url;
-                       $repo_id = $self->{repo_id};
-               }
-               $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
-       }
+       my $gs = $self->other_gs($new_url, $url, $repos_root,
+                                $branch_from, $r, $self->{ref_id});
        my ($r0, $parent) = $gs->find_rev_before($r, 1);
        {
                my ($base, $head);
@@ -2430,8 +2431,9 @@ sub find_parent_branch {
                        # do_switch works with svn/trunk >= r22312, but that
                        # is not included with SVN 1.4.3 (the latest version
                        # at the moment), so we can't rely on it
+                       $self->{last_rev} = $r0;
                        $self->{last_commit} = $parent;
-                       $ed = SVN::Git::Fetcher->new($self);
+                       $ed = SVN::Git::Fetcher->new($self, $gs->{path});
                        $gs->ra->gs_do_switch($r0, $rev, $gs,
                                              $self->full_url, $ed)
                          or die "SVN connection failed somewhere...\n";
@@ -2539,7 +2541,7 @@ sub get_untracked {
 sub parse_svn_date {
        my $date = shift || return '+0000 1970-01-01 00:00:00';
        my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
-                                           (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or
+                                           (\d\d)\:(\d\d)\:(\d\d)\.\d*Z$/x) or
                                         croak "Unable to parse date: $date\n";
        my $parsed_date;    # Set next.
 
@@ -2586,6 +2588,28 @@ sub parse_svn_date {
        return $parsed_date;
 }
 
+sub other_gs {
+       my ($self, $new_url, $url, $repos_root,
+           $branch_from, $r, $old_ref_id) = @_;
+       my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
+       unless ($gs) {
+               my $ref_id = $old_ref_id;
+               $ref_id =~ s/\@\d+$//;
+               $ref_id .= "\@$r";
+               # 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";
+               my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
+               if ($u =~ s#^\Q$url\E(/|$)##) {
+                       $p = $u;
+                       $u = $url;
+                       $repo_id = $self->{repo_id};
+               }
+               $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+       }
+       $gs
+}
+
 sub check_author {
        my ($author) = @_;
        if (!defined $author || length $author == 0) {
@@ -3250,12 +3274,13 @@ package SVN::Git::Fetcher;
 
 # file baton members: path, mode_a, mode_b, pool, fh, blob, base
 sub new {
-       my ($class, $git_svn) = @_;
+       my ($class, $git_svn, $switch_path) = @_;
        my $self = SVN::Delta::Editor->new;
        bless $self, $class;
        if (exists $git_svn->{last_commit}) {
                $self->{c} = $git_svn->{last_commit};
-               $self->{empty_symlinks} = _mark_empty_symlinks($git_svn);
+               $self->{empty_symlinks} =
+                                 _mark_empty_symlinks($git_svn, $switch_path);
        }
        $self->{empty} = {};
        $self->{dir_prop} = {};
@@ -3270,19 +3295,39 @@ sub new {
 # not inside them (when the Git::SVN::Fetcher object is passed) to
 # do_{switch,update}
 sub _mark_empty_symlinks {
-       my ($git_svn) = @_;
+       my ($git_svn, $switch_path) = @_;
+       my $bool = Git::config_bool('svn.brokenSymlinkWorkaround');
+       return {} if (!defined($bool)) || (defined($bool) && ! $bool);
+
        my %ret;
        my ($rev, $cmt) = $git_svn->last_rev_commit;
        return {} unless ($rev && $cmt);
 
+       # allow the warning to be printed for each revision we fetch to
+       # ensure the user sees it.  The user can also disable the workaround
+       # on the repository even while git svn is running and the next
+       # revision fetched will skip this expensive function.
+       my $printed_warning;
        chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);
        my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);
        local $/ = "\0";
-       my $pfx = $git_svn->{path};
+       my $pfx = defined($switch_path) ? $switch_path : $git_svn->{path};
        $pfx .= '/' if length($pfx);
        while (<$ls>) {
                chomp;
                s/\A100644 blob $empty_blob\t//o or next;
+               unless ($printed_warning) {
+                       print STDERR "Scanning for empty symlinks, ",
+                                    "this may take a while if you have ",
+                                    "many empty files\n",
+                                    "You may disable this with `",
+                                    "git config svn.brokenSymlinkWorkaround ",
+                                    "false'.\n",
+                                    "This may be done in a different ",
+                                    "terminal without restarting ",
+                                    "git svn\n";
+                       $printed_warning = 1;
+               }
                my $path = $_;
                my (undef, $props) =
                               $git_svn->ra->get_file($pfx.$path, $rev, undef);
@@ -4348,6 +4393,9 @@ sub gs_fetch_loop_common {
                }
                $self->get_log([$longest_path], $min, $max, 0, 1, 1,
                               sub { $revs{$_[1]} = _cb(@_) });
+               if ($err) {
+                       print "Checked through r$max\r";
+               }
                if ($err && $max >= $head) {
                        print STDERR "Path '$longest_path' ",
                                     "was probably deleted:\n",
@@ -4582,6 +4630,7 @@ package Git::SVN::Log;
 use strict;
 use warnings;
 use POSIX qw/strftime/;
+use Time::Local;
 use constant commit_log_separator => ('-' x 72) . "\n";
 use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
             %rusers $show_commit $incremental/;
@@ -4688,7 +4737,12 @@ sub run_pager {
 }
 
 sub format_svn_date {
-       return strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", localtime(shift));
+       # some systmes don't handle or mishandle %z, so be creative.
+       my $t = shift || time;
+       my $gm = timelocal(gmtime($t));
+       my $sign = qw( + + - )[ $t <=> $gm ];
+       my $gmoff = sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+       return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
 }
 
 sub parse_git_date {
index 78d236b77f6e2b52d89bf7b579762723ede86d15..7ed0faddcd75e024ce18d8b80994d41a9207a7ca 100755 (executable)
@@ -115,7 +115,7 @@ if test -z "$browser" ; then
        browser_candidates="open $browser_candidates"
     fi
     # /bin/start indicates MinGW
-    if test -n /bin/start; then
+    if test -x /bin/start; then
        browser_candidates="start $browser_candidates"
     fi
 
index 069ace050d2eec5a6f5f805d59f0208da01fdf1b..4be0834f0bc1cebc2b341ef6f54c3c37f48eb832 100644 (file)
@@ -97,7 +97,7 @@ BuildRequires:  perl(Error)
 %description -n perl-Git
 Perl interface to Git
 
-%define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-core-%{version}
+%define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-%{version}
 
 %prep
 %setup -q
@@ -190,6 +190,9 @@ rm -rf $RPM_BUILD_ROOT
 # No files for you!
 
 %changelog
+* Mon Feb 04 2009 David J. Mellor <dmellor@whistlingcat.com>
+- fixed broken git help -w after renaming the git-core package to git.
+
 * Fri Sep 12 2008 Quy Tonthat <qtonthat@gmail.com>
 - move git-cvsserver to bindir.
 
index dc2a439618ffd92a92d9ca954a8597ba31875dab..1773ae63eb54f3b602b782a3633034aab2f828ae 100644 (file)
@@ -701,16 +701,17 @@ proc newvarc {view id} {
 }
 
 proc splitvarc {p v} {
-    global varcid varcstart varccommits varctok
+    global varcid varcstart varccommits varctok vtokmod
     global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
 
     set oa $varcid($v,$p)
+    set otok [lindex $varctok($v) $oa]
     set ac $varccommits($v,$oa)
     set i [lsearch -exact $varccommits($v,$oa) $p]
     if {$i <= 0} return
     set na [llength $varctok($v)]
     # "%" sorts before "0"...
-    set tok "[lindex $varctok($v) $oa]%[strrep $i]"
+    set tok "$otok%[strrep $i]"
     lappend varctok($v) $tok
     lappend varcrow($v) {}
     lappend varcix($v) {}
@@ -730,6 +731,9 @@ proc splitvarc {p v} {
     for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
        lset vupptr($v) $b $na
     }
+    if {[string compare $otok $vtokmod($v)] <= 0} {
+       modify_arc $v $oa
+    }
 }
 
 proc renumbervarc {a v} {
@@ -3363,7 +3367,6 @@ proc external_blame {parent_idx {line {}}} {
     # being given an absolute path...
     set f [make_relative $f]
     lappend cmdline $base_commit $f
-    puts "cmdline={$cmdline}"
     if {[catch {eval exec $cmdline &} err]} {
        error_popup "[mc "git gui blame: command failed:"] $err"
     }
@@ -5731,7 +5734,6 @@ proc drawcommits {row {endrow {}}} {
     optimize_rows $ro1 0 $r2
     if {$need_redisplay || $nrows_drawn > 2000} {
        clear_display
-       drawvisible
     }
 
     # make the lines join to already-drawn rows either side
index a9dc2e57d9bd79a505ca165c693ce0f3ccd2f333..8433dd1d45780b3947cc4a0b31ee2059ea6e65bf 100644 (file)
@@ -212,6 +212,11 @@ not include variables usually directly set during build):
    Rename detection options for git-diff and git-diff-tree. By default
    ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or
    set it to () if you don't want to have renames detection.
+ * $prevent_xss
+   If true, some gitweb features are disabled to prevent content in
+   repositories from launching cross-site scripting (XSS) attacks.  Set this
+   to true if you don't trust the content of your repositories. The default
+   is false.
 
 
 Projects list file format
@@ -258,7 +263,9 @@ You can use the following files in repository:
    A .html file (HTML fragment) which is included on the gitweb project
    summary page inside <div> block element. You can use it for longer
    description of a project, to provide links (for example to project's
-   homepage), etc.
+   homepage), etc. This is recognized only if XSS prevention is off
+   ($prevent_xss is false); a way to include a readme safely when XSS
+   prevention is on may be worked out in the future.
  * description (or gitweb.description)
    Short (shortened by default to 25 characters in the projects list page)
    single line description of a project (of a repository). Plain text file;
index f27dbb6bf4acfe6f5381d7c86760b1170c8a10ff..33ef190ceb99565f804783ba9fe76987ad6bea89 100755 (executable)
@@ -27,13 +27,29 @@ BEGIN
 our $my_url = $cgi->url();
 our $my_uri = $cgi->url(-absolute => 1);
 
-# if we're called with PATH_INFO, we have to strip that
-# from the URL to find our real URL
-# we make $path_info global because it's also used later on
+# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
+# needed and used only for URLs with nonempty PATH_INFO
+our $base_url = $my_url;
+
+# When the script is used as DirectoryIndex, the URL does not contain the name
+# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
+# have to do it ourselves. We make $path_info global because it's also used
+# later on.
+#
+# Another issue with the script being the DirectoryIndex is that the resulting
+# $my_url data is not the full script URL: this is good, because we want
+# generated links to keep implying the script name if it wasn't explicitly
+# indicated in the URL we're handling, but it means that $my_url cannot be used
+# as base URL.
+# Therefore, if we needed to strip PATH_INFO, then we know that we have
+# to build the base URL ourselves:
 our $path_info = $ENV{"PATH_INFO"};
 if ($path_info) {
-       $my_url =~ s,\Q$path_info\E$,,;
-       $my_uri =~ s,\Q$path_info\E$,,;
+       if ($my_url =~ s,\Q$path_info\E$,, &&
+           $my_uri =~ s,\Q$path_info\E$,, &&
+           defined $ENV{'SCRIPT_NAME'}) {
+               $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+       }
 }
 
 # core git executable to use
@@ -132,6 +148,10 @@ BEGIN
 # - one might want to include '-B' option, e.g. '-B', '-M'
 our @diff_opts = ('-M'); # taken from git_commit
 
+# Disables features that would allow repository owners to inject script into
+# the gitweb domain.
+our $prevent_xss = 0;
+
 # information about snapshot formats that gitweb is capable of serving
 our %known_snapshot_formats = (
        # name => {
@@ -382,13 +402,13 @@ sub feature_bool {
        my $key = shift;
        my ($val) = git_get_project_config($key, '--bool');
 
-       if ($val eq 'true') {
+       if (!defined $val) {
+               return ($_[0]);
+       } elsif ($val eq 'true') {
                return (1);
        } elsif ($val eq 'false') {
                return (0);
        }
-
-       return ($_[0]);
 }
 
 sub feature_snapshot {
@@ -1364,13 +1384,11 @@ sub format_log_line_html {
        my $line = shift;
 
        $line = esc_html($line, -nbsp=>1);
-       if ($line =~ m/([0-9a-fA-F]{8,40})/) {
-               my $hash_text = $1;
-               my $link =
-                       $cgi->a({-href => href(action=>"object", hash=>$hash_text),
-                               -class => "text"}, $hash_text);
-               $line =~ s/$hash_text/$link/;
-       }
+       $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
+               $cgi->a({-href => href(action=>"object", hash=>$1),
+                                       -class => "text"}, $1);
+       }eg;
+
        return $line;
 }
 
@@ -1894,18 +1912,19 @@ sub git_parse_project_config {
        return %config;
 }
 
-# convert config value to boolean, 'true' or 'false'
+# convert config value to boolean: 'true' or 'false'
 # no value, number > 0, 'true' and 'yes' values are true
 # rest of values are treated as false (never as error)
 sub config_to_bool {
        my $val = shift;
 
+       return 1 if !defined $val;             # section.key
+
        # strip leading and trailing whitespace
        $val =~ s/^\s+//;
        $val =~ s/\s+$//;
 
-       return (!defined $val ||               # section.key
-               ($val =~ /^\d+$/ && $val) ||   # section.key = 1
+       return (($val =~ /^\d+$/ && $val) ||   # section.key = 1
                ($val =~ /^(?:true|yes)$/i));  # section.key = true
 }
 
@@ -1958,6 +1977,9 @@ sub git_get_project_config {
                $config_file = "$git_dir/config";
        }
 
+       # check if config variable (key) exists
+       return unless exists $config{"gitweb.$key"};
+
        # ensure given type
        if (!defined $type) {
                return $config{"gitweb.$key"};
@@ -2904,7 +2926,7 @@ sub git_header_html {
        # the stylesheet, favicon etc urls won't work correctly with path_info
        # unless we set the appropriate base URL
        if ($ENV{'PATH_INFO'}) {
-               print '<base href="'.esc_url($my_url).'" />\n';
+               print "<base href=\"".esc_url($base_url)."\" />\n";
        }
        # print out each stylesheet that exist, providing backwards capability
        # for those people who defined $stylesheet in a config file
@@ -4503,7 +4525,9 @@ sub git_summary {
 
        print "</table>\n";
 
-       if (-s "$projectroot/$project/README.html") {
+       # If XSS prevention is on, we don't include README.html.
+       # TODO: Allow a readme in some safe format.
+       if (!$prevent_xss && -s "$projectroot/$project/README.html") {
                print "<div class=\"title\">readme</div>\n" .
                      "<div class=\"readme\">\n";
                insert_file("$projectroot/$project/README.html");
@@ -4764,10 +4788,21 @@ sub git_blob_plain {
                $save_as .= '.txt';
        }
 
+       # With XSS prevention on, blobs of all types except a few known safe
+       # ones are served with "Content-Disposition: attachment" to make sure
+       # they don't run in our security domain.  For certain image types,
+       # blob view writes an <img> tag referring to blob_plain view, and we
+       # want to be sure not to break that by serving the image as an
+       # attachment (though Firefox 3 doesn't seem to care).
+       my $sandbox = $prevent_xss &&
+               $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
+
        print $cgi->header(
                -type => $type,
                -expires => $expires,
-               -content_disposition => 'inline; filename="' . $save_as . '"');
+               -content_disposition =>
+                       ($sandbox ? 'attachment' : 'inline')
+                       . '; filename="' . $save_as . '"');
        undef $/;
        binmode STDOUT, ':raw';
        print <$fd>;
index 18d81acd1b3743761b23ded035dc58e32b6aad80..30d2d340418f7f40b77823b1b58b307985347bdf 100644 (file)
@@ -153,6 +153,7 @@ struct remote_lock
        char *url;
        char *owner;
        char *token;
+       char tmpfile_suffix[41];
        time_t start_time;
        long timeout;
        int refreshing;
@@ -210,6 +211,15 @@ static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum d
        return dav_headers;
 }
 
+static void append_remote_object_url(struct strbuf *buf, const char *url,
+                                    const char *hex,
+                                    int only_two_digit_prefix)
+{
+       strbuf_addf(buf, "%sobjects/%.*s/", url, 2, hex);
+       if (!only_two_digit_prefix)
+               strbuf_addf(buf, "%s", hex+2);
+}
+
 static void finish_request(struct transfer_request *request);
 static void release_request(struct transfer_request *request);
 
@@ -222,6 +232,15 @@ static void process_response(void *callback_data)
 }
 
 #ifdef USE_CURL_MULTI
+
+static char *get_remote_object_url(const char *url, const char *hex,
+                                  int only_two_digit_prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       append_remote_object_url(&buf, url, hex, only_two_digit_prefix);
+       return strbuf_detach(&buf, NULL);
+}
+
 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
                               void *data)
 {
@@ -256,7 +275,6 @@ static void start_fetch_loose(struct transfer_request *request)
        char *filename;
        char prevfile[PATH_MAX];
        char *url;
-       char *posn;
        int prevlocal;
        unsigned char prev_buf[PREV_BUF_SIZE];
        ssize_t prev_read = 0;
@@ -306,17 +324,8 @@ static void start_fetch_loose(struct transfer_request *request)
 
        git_SHA1_Init(&request->c);
 
-       url = xmalloc(strlen(remote->url) + 50);
-       request->url = xmalloc(strlen(remote->url) + 50);
-       strcpy(url, remote->url);
-       posn = url + strlen(remote->url);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       *(posn++) = '/';
-       strcpy(posn, hex + 2);
-       strcpy(request->url, url);
+       url = get_remote_object_url(remote->url, hex, 0);
+       request->url = xstrdup(url);
 
        /* If a previous temp file is present, process what was already
           fetched. */
@@ -389,16 +398,8 @@ static void start_mkcol(struct transfer_request *request)
 {
        char *hex = sha1_to_hex(request->obj->sha1);
        struct active_request_slot *slot;
-       char *posn;
 
-       request->url = xmalloc(strlen(remote->url) + 13);
-       strcpy(request->url, remote->url);
-       posn = request->url + strlen(remote->url);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       strcpy(posn, "/");
+       request->url = get_remote_object_url(remote->url, hex, 1);
 
        slot = get_active_slot();
        slot->callback_func = process_response;
@@ -513,7 +514,7 @@ static void start_put(struct transfer_request *request)
 {
        char *hex = sha1_to_hex(request->obj->sha1);
        struct active_request_slot *slot;
-       char *posn;
+       struct strbuf buf = STRBUF_INIT;
        enum object_type type;
        char hdr[50];
        void *unpacked;
@@ -552,21 +553,13 @@ static void start_put(struct transfer_request *request)
 
        request->buffer.buf.len = stream.total_out;
 
-       request->url = xmalloc(strlen(remote->url) +
-                              strlen(request->lock->token) + 51);
-       strcpy(request->url, remote->url);
-       posn = request->url + strlen(remote->url);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       *(posn++) = '/';
-       strcpy(posn, hex + 2);
-       request->dest = xmalloc(strlen(request->url) + 14);
-       sprintf(request->dest, "Destination: %s", request->url);
-       posn += 38;
-       *(posn++) = '_';
-       strcpy(posn, request->lock->token);
+       strbuf_addstr(&buf, "Destination: ");
+       append_remote_object_url(&buf, remote->url, hex, 0);
+       request->dest = strbuf_detach(&buf, NULL);
+
+       append_remote_object_url(&buf, remote->url, hex, 0);
+       strbuf_add(&buf, request->lock->tmpfile_suffix, 41);
+       request->url = strbuf_detach(&buf, NULL);
 
        slot = get_active_slot();
        slot->callback_func = process_response;
@@ -1137,6 +1130,8 @@ static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
 static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
 {
        struct remote_lock *lock = (struct remote_lock *)ctx->userData;
+       git_SHA_CTX sha_ctx;
+       unsigned char lock_token_sha1[20];
 
        if (tag_closed && ctx->cdata) {
                if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) {
@@ -1149,6 +1144,13 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
                } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) {
                        lock->token = xmalloc(strlen(ctx->cdata) + 1);
                        strcpy(lock->token, ctx->cdata);
+
+                       git_SHA1_Init(&sha_ctx);
+                       git_SHA1_Update(&sha_ctx, lock->token, strlen(lock->token));
+                       git_SHA1_Final(lock_token_sha1, &sha_ctx);
+
+                       lock->tmpfile_suffix[0] = '_';
+                       memcpy(lock->tmpfile_suffix + 1, sha1_to_hex(lock_token_sha1), 40);
                }
        }
 }
index f7a38079e1ec1a68606ba728964efbd5b2a04c4c..7fee8725333860dbbd13d8de5ae7baf1ef33976d 100644 (file)
@@ -172,9 +172,8 @@ static char *open_pack_file(char *pack_name)
                input_fd = 0;
                if (!pack_name) {
                        static char tmpfile[PATH_MAX];
-                       snprintf(tmpfile, sizeof(tmpfile),
-                                "%s/pack/tmp_pack_XXXXXX", get_object_directory());
-                       output_fd = xmkstemp(tmpfile);
+                       output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                                               "pack/tmp_pack_XXXXXX");
                        pack_name = xstrdup(tmpfile);
                } else
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
@@ -794,22 +793,24 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 
        if (keep_msg) {
                int keep_fd, keep_msg_len = strlen(keep_msg);
-               if (!keep_name) {
-                       snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
-                                get_object_directory(), sha1_to_hex(sha1));
-                       keep_name = name;
-               }
-               keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
+               if (!keep_name)
+                       keep_fd = odb_pack_keep(name, sizeof(name), sha1);
+               else
+                       keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
                if (keep_fd < 0) {
                        if (errno != EEXIST)
-                               die("cannot write keep file");
+                               die("cannot write keep file '%s' (%s)",
+                                   keep_name, strerror(errno));
                } else {
                        if (keep_msg_len > 0) {
                                write_or_die(keep_fd, keep_msg, keep_msg_len);
                                write_or_die(keep_fd, "\n", 1);
                        }
                        if (close(keep_fd) != 0)
-                               die("cannot write keep file");
+                               die("cannot close written keep file '%s' (%s)",
+                                   keep_name, strerror(errno));
                        report = "keep";
                }
        }
index 021c3375c10711027269ee58bb9a201bc69c519a..3dbb2d1ff9407a7417be294adedc5312e7421a96 100644 (file)
@@ -155,11 +155,25 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
        return lk->fd;
 }
 
+
+NORETURN void unable_to_lock_index_die(const char *path, int err)
+{
+       if (err == EEXIST) {
+               die("Unable to create '%s.lock': %s.\n\n"
+                   "If no other git process is currently running, this probably means a\n"
+                   "git process crashed in this repository earlier. Make sure no other git\n"
+                   "process is running and remove the file manually to continue.",
+                   path, strerror(err));
+       } else {
+               die("Unable to create '%s.lock': %s", path, strerror(err));
+       }
+}
+
 int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags)
 {
        int fd = lock_file(lk, path, flags);
        if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
-               die("unable to create '%s.lock': %s", path, strerror(errno));
+               unable_to_lock_index_die(path, errno);
        return fd;
 }
 
@@ -170,7 +184,7 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags)
        fd = lock_file(lk, path, flags);
        if (fd < 0) {
                if (flags & LOCK_DIE_ON_ERROR)
-                       die("unable to create '%s.lock': %s", path, strerror(errno));
+                       unable_to_lock_index_die(path, errno);
                return fd;
        }
 
index 194ddb13da09668334cd9e1f6eeef517c3346ee9..84a74e544b7bcc20c887f321e389ecf3cfb560d6 100644 (file)
@@ -48,7 +48,7 @@ static void show_parents(struct commit *commit, int abbrev)
        struct commit_list *p;
        for (p = commit->parents; p ; p = p->next) {
                struct commit *parent = p->item;
-               printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+               printf(" %s", find_unique_abbrev(parent->object.sha1, abbrev));
        }
 }
 
@@ -280,7 +280,7 @@ void show_log(struct rev_info *opt)
                                        putchar('>');
                        }
                }
-               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
+               fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
                show_decorations(opt, commit);
@@ -348,13 +348,13 @@ void show_log(struct rev_info *opt)
                                        putchar('>');
                        }
                }
-               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
+               fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit),
                      stdout);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
                if (parent)
                        printf(" (from %s)",
-                              diff_unique_abbrev(parent->object.sha1,
+                              find_unique_abbrev(parent->object.sha1,
                                                  abbrev_commit));
                show_decorations(opt, commit);
                printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
index 88fc6f394684436967002ca477eac1e084537348..f12bb45a3f734e48f003a7a2943d168c26778603 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
 #include "string-list.h"
 #include "mailmap.h"
 
-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
+#define DEBUG_MAILMAP 0
+#if DEBUG_MAILMAP
+#define debug_mm(...) fprintf(stderr, __VA_ARGS__)
+#else
+static inline void debug_mm(const char *format, ...) {}
+#endif
+
+const char *git_mailmap_file;
+
+struct mailmap_info {
+       char *name;
+       char *email;
+};
+
+struct mailmap_entry {
+       /* name and email for the simple mail-only case */
+       char *name;
+       char *email;
+
+       /* name and email for the complex mail and name matching case */
+       struct string_list namemap;
+};
+
+static void free_mailmap_info(void *p, const char *s)
+{
+       struct mailmap_info *mi = (struct mailmap_info *)p;
+       debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", s, mi->name, mi->email);
+       free(mi->name);
+       free(mi->email);
+}
+
+static void free_mailmap_entry(void *p, const char *s)
+{
+       struct mailmap_entry *me = (struct mailmap_entry *)p;
+       debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", s, me->namemap.nr);
+       debug_mm("mailmap: - simple: '%s' <%s>\n", me->name, me->email);
+       free(me->name);
+       free(me->email);
+
+       me->namemap.strdup_strings = 1;
+       string_list_clear_func(&me->namemap, free_mailmap_info);
+}
+
+static void add_mapping(struct string_list *map,
+                       char *new_name, char *new_email, char *old_name, char *old_email)
+{
+       struct mailmap_entry *me;
+       int index;
+       if (old_email == NULL) {
+               old_email = new_email;
+               new_email = NULL;
+       }
+
+       if ((index = string_list_find_insert_index(map, old_email, 1)) < 0) {
+               /* mailmap entry exists, invert index value */
+               index = -1 - index;
+       } else {
+               /* create mailmap entry */
+               struct string_list_item *item = string_list_insert_at_index(index, old_email, map);
+               item->util = xmalloc(sizeof(struct mailmap_entry));
+               memset(item->util, 0, sizeof(struct mailmap_entry));
+               ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1;
+       }
+       me = (struct mailmap_entry *)map->items[index].util;
+
+       if (old_name == NULL) {
+               debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index);
+               /* Replace current name and new email for simple entry */
+               free(me->name);
+               free(me->email);
+               if (new_name)
+                       me->name = xstrdup(new_name);
+               if (new_email)
+                       me->email = xstrdup(new_email);
+       } else {
+               struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info));
+               debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index);
+               if (new_name)
+                       mi->name = xstrdup(new_name);
+               if (new_email)
+                       mi->email = xstrdup(new_email);
+               string_list_insert(old_name, &me->namemap)->util = mi;
+       }
+
+       debug_mm("mailmap:  '%s' <%s> -> '%s' <%s>\n",
+                old_name, old_email, new_name, new_email);
+}
+
+static char *parse_name_and_email(char *buffer, char **name, char **email)
+{
+       char *left, *right, *nstart, *nend;
+       *name = *email = 0;
+
+       if ((left = strchr(buffer, '<')) == NULL)
+               return NULL;
+       if ((right = strchr(left+1, '>')) == NULL)
+               return NULL;
+       if (left+1 == right)
+               return NULL;
+
+       /* remove whitespace from beginning and end of name */
+       nstart = buffer;
+       while (isspace(*nstart) && nstart < left)
+               ++nstart;
+       nend = left-1;
+       while (isspace(*nend) && nend > nstart)
+               --nend;
+
+       *name = (nstart < nend ? nstart : NULL);
+       *email = left+1;
+       *(nend+1) = '\0';
+       *right++ = '\0';
+
+       return (*right == '\0' ? NULL : right);
+}
+
+static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
 {
        char buffer[1024];
-       FILE *f = fopen(filename, "r");
+       FILE *f = (filename == NULL ? NULL : fopen(filename, "r"));
 
        if (f == NULL)
                return 1;
        while (fgets(buffer, sizeof(buffer), f) != NULL) {
-               char *end_of_name, *left_bracket, *right_bracket;
-               char *name, *email;
-               int i;
+               char *name1 = 0, *email1 = 0, *name2 = 0, *email2 = 0;
                if (buffer[0] == '#') {
                        static const char abbrev[] = "# repo-abbrev:";
                        int abblen = sizeof(abbrev) - 1;
@@ -36,41 +150,49 @@ int read_mailmap(struct string_list *map, const char *filename, char **repo_abbr
                        }
                        continue;
                }
-               if ((left_bracket = strchr(buffer, '<')) == NULL)
-                       continue;
-               if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
-                       continue;
-               if (right_bracket == left_bracket + 1)
-                       continue;
-               for (end_of_name = left_bracket;
-                    end_of_name != buffer && isspace(end_of_name[-1]);
-                    end_of_name--)
-                       ; /* keep on looking */
-               if (end_of_name == buffer)
-                       continue;
-               name = xmalloc(end_of_name - buffer + 1);
-               strlcpy(name, buffer, end_of_name - buffer + 1);
-               email = xmalloc(right_bracket - left_bracket);
-               for (i = 0; i < right_bracket - left_bracket - 1; i++)
-                       email[i] = tolower(left_bracket[i + 1]);
-               email[right_bracket - left_bracket - 1] = '\0';
-               string_list_insert(email, map)->util = name;
+               if ((name2 = parse_name_and_email(buffer, &name1, &email1)) != NULL)
+                       parse_name_and_email(name2, &name2, &email2);
+
+               if (email1)
+                       add_mapping(map, name1, email1, name2, email2);
        }
        fclose(f);
        return 0;
 }
 
-int map_email(struct string_list *map, const char *email, char *name, int maxlen)
+int read_mailmap(struct string_list *map, char **repo_abbrev)
+{
+       map->strdup_strings = 1;
+       /* each failure returns 1, so >1 means both calls failed */
+       return read_single_mailmap(map, ".mailmap", repo_abbrev) +
+              read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1;
+}
+
+void clear_mailmap(struct string_list *map)
+{
+       debug_mm("mailmap: clearing %d entries...\n", map->nr);
+       map->strdup_strings = 1;
+       string_list_clear_func(map, free_mailmap_entry);
+       debug_mm("mailmap: cleared\n");
+}
+
+int map_user(struct string_list *map,
+            char *email, int maxlen_email, char *name, int maxlen_name)
 {
        char *p;
        struct string_list_item *item;
+       struct mailmap_entry *me;
        char buf[1024], *mailbuf;
        int i;
 
-       /* autocomplete common developers */
+       /* figure out space requirement for email */
        p = strchr(email, '>');
-       if (!p)
-               return 0;
+       if (!p) {
+               /* email passed in might not be wrapped in <>, but end with a \0 */
+               p = memchr(email, '\0', maxlen_email);
+               if (p == 0)
+                       return 0;
+       }
        if (p - email + 1 < sizeof(buf))
                mailbuf = buf;
        else
@@ -80,13 +202,39 @@ int map_email(struct string_list *map, const char *email, char *name, int maxlen
        for (i = 0; i < p - email; i++)
                mailbuf[i] = tolower(email[i]);
        mailbuf[i] = 0;
+
+       debug_mm("map_user: map '%s' <%s>\n", name, mailbuf);
        item = string_list_lookup(mailbuf, map);
+       if (item != NULL) {
+               me = (struct mailmap_entry *)item->util;
+               if (me->namemap.nr) {
+                       /* The item has multiple items, so we'll look up on name too */
+                       /* If the name is not found, we choose the simple entry      */
+                       struct string_list_item *subitem = string_list_lookup(name, &me->namemap);
+                       if (subitem)
+                               item = subitem;
+               }
+       }
        if (mailbuf != buf)
                free(mailbuf);
        if (item != NULL) {
-               const char *realname = (const char *)item->util;
-               strlcpy(name, realname, maxlen);
+               struct mailmap_info *mi = (struct mailmap_info *)item->util;
+               if (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) {
+                       debug_mm("map_user:  -- (no simple mapping)\n");
+                       return 0;
+               }
+               if (maxlen_email && mi->email)
+                       strlcpy(email, mi->email, maxlen_email);
+               if (maxlen_name && mi->name)
+                       strlcpy(name, mi->name, maxlen_name);
+               debug_mm("map_user:  to '%s' <%s>\n", name, mi->email ? mi->email : "");
                return 1;
        }
+       debug_mm("map_user:  --\n");
        return 0;
 }
+
+int map_email(struct string_list *map, const char *email, char *name, int maxlen)
+{
+       return map_user(map, (char *)email, 0, name, maxlen);
+}
index 6e48f83cedd13e24d50cddf47f037791ddc5ad4b..4b2ca3a7de972c10f214b38a25be522abcbbafd0 100644 (file)
--- a/mailmap.h
+++ b/mailmap.h
@@ -1,7 +1,11 @@
 #ifndef MAILMAP_H
 #define MAILMAP_H
 
-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev);
+int read_mailmap(struct string_list *map, char **repo_abbrev);
+void clear_mailmap(struct string_list *map);
+
 int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
+int map_user(struct string_list *mailmap,
+            char *email, int maxlen_email, char *name, int maxlen_name);
 
 #endif
index c00a2b385a3373f88b9fd3f087d9681cdc46cbf6..aa9cf23a39ae271a53d1a0c05ac99be0e832b46a 100644 (file)
@@ -92,7 +92,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>*)");
 
        git_extract_argv0_path(argv[0]);
 
index b97026bd5cc1d2ef1b46a9ef3dcd7562ad52c377..ee853b990d8bfb15e0058fefbbd267bd58ed40fc 100644 (file)
@@ -237,7 +237,7 @@ static int save_files_dirs(const unsigned char *sha1,
                string_list_insert(newpath, &o->current_file_set);
        free(newpath);
 
-       return READ_TREE_RECURSIVE;
+       return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
 }
 
 static int get_files_dirs(struct merge_options *o, struct tree *tree)
index f18201acdb21f7eb3e09a546382417851b572fd4..f01e7c81aebea84b95154c9076af66979e52715f 100644 (file)
@@ -4,7 +4,7 @@
 #include "blob.h"
 #include "exec_cmd.h"
 
-static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
+static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
 static int resolve_directories = 1;
 
 struct merge_list {
diff --git a/mktag.c b/mktag.c
index 6d5083eaf04b9ad1b54dad5eaca33d9a73373bbd..99a356e9ee75cb247d80ed6dc0b251ceb0bd9e46 100644 (file)
--- a/mktag.c
+++ b/mktag.c
@@ -158,7 +158,7 @@ int main(int argc, char **argv)
        unsigned char result_sha1[20];
 
        if (argc != 1)
-               usage("git-mktag < signaturefile");
+               usage("git mktag < signaturefile");
 
        git_extract_argv0_path(argv[0]);
 
index 6283bc3d431eca77e79970d1de59c61ac36af028..137a0950f686691740ac87330cf0ac7bdea8b1e7 100644 (file)
--- a/mktree.c
+++ b/mktree.c
@@ -62,7 +62,7 @@ static void write_tree(unsigned char *sha1)
        write_sha1_file(buf.buf, buf.len, tree_type, sha1);
 }
 
-static const char mktree_usage[] = "git-mktree [-z]";
+static const char mktree_usage[] = "git mktree [-z]";
 
 int main(int ac, char **av)
 {
index b426006c5851c98fce8894bd9f76cd51a7cde170..7053538f4cf44e15a788ab46dfb680ee85ce4fc2 100644 (file)
@@ -44,9 +44,7 @@ char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
 
        if (!index_name) {
                static char tmpfile[PATH_MAX];
-               snprintf(tmpfile, sizeof(tmpfile),
-                        "%s/pack/tmp_idx_XXXXXX", get_object_directory());
-               fd = xmkstemp(tmpfile);
+               fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
                index_name = xstrdup(tmpfile);
        } else {
                unlink(index_name);
@@ -239,7 +237,7 @@ char *index_pack_lockfile(int ip_out)
        char packname[46];
 
        /*
-        * The first thing we expects from index-pack's output
+        * The first thing we expect from index-pack's output
         * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
         * %40s is the newly created pack SHA1 name.  In the "keep"
         * case, we need it to remove the corresponding .keep file
index 3660ad461d359a7fb968692f40cea75c9764f61a..0df4cb086ba26d1f4d56b8347a6a7bcf219832f5 100644 (file)
@@ -73,7 +73,7 @@ static void generate_id_list(void)
        flush_current_id(patchlen, sha1, &ctx);
 }
 
-static const char patch_id_usage[] = "git-patch-id < patch";
+static const char patch_id_usage[] = "git patch-id < patch";
 
 int main(int argc, char **argv)
 {
diff --git a/path.c b/path.c
index a074aea64921eb1fb90f079ede9087e6b8109f6a..e332b504a6b8c6b229da2526c4f810b8d0fb9889 100644 (file)
--- a/path.c
+++ b/path.c
@@ -363,56 +363,97 @@ const char *make_relative_path(const char *abs, const char *base)
 }
 
 /*
- * path = absolute path
- * buf = buffer of at least max(2, strlen(path)+1) bytes
- * It is okay if buf == path, but they should not overlap otherwise.
+ * It is okay if dst == src, but they should not overlap otherwise.
  *
- * Performs the following normalizations on path, storing the result in buf:
- * - Removes trailing slashes.
- * - Removes empty components.
+ * Performs the following normalizations on src, storing the result in dst:
+ * - Ensures that components are separated by '/' (Windows only)
+ * - Squashes sequences of '/'.
  * - Removes "." components.
  * - Removes ".." components, and the components the precede them.
- * "" and paths that contain only slashes are normalized to "/".
- * Returns the length of the output.
+ * Returns failure (non-zero) if a ".." component appears as first path
+ * component anytime during the normalization. Otherwise, returns success (0).
  *
  * Note that this function is purely textual.  It does not follow symlinks,
  * verify the existence of the path, or make any system calls.
  */
-int normalize_absolute_path(char *buf, const char *path)
+int normalize_path_copy(char *dst, const char *src)
 {
-       const char *comp_start = path, *comp_end = path;
-       char *dst = buf;
-       int comp_len;
-       assert(buf);
-       assert(path);
-
-       while (*comp_start) {
-               assert(*comp_start == '/');
-               while (*++comp_end && *comp_end != '/')
-                       ; /* nothing */
-               comp_len = comp_end - comp_start;
-
-               if (!strncmp("/",  comp_start, comp_len) ||
-                   !strncmp("/.", comp_start, comp_len))
-                       goto next;
-
-               if (!strncmp("/..", comp_start, comp_len)) {
-                       while (dst > buf && *--dst != '/')
-                               ; /* nothing */
-                       goto next;
-               }
+       char *dst0;
 
-               memmove(dst, comp_start, comp_len);
-               dst += comp_len;
-       next:
-               comp_start = comp_end;
+       if (has_dos_drive_prefix(src)) {
+               *dst++ = *src++;
+               *dst++ = *src++;
        }
+       dst0 = dst;
 
-       if (dst == buf)
+       if (is_dir_sep(*src)) {
                *dst++ = '/';
+               while (is_dir_sep(*src))
+                       src++;
+       }
 
+       for (;;) {
+               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 == '.') {
+                       if (!src[1]) {
+                               /* (1) */
+                               src++;
+                       } else if (is_dir_sep(src[1])) {
+                               /* (2) */
+                               src += 2;
+                               while (is_dir_sep(*src))
+                                       src++;
+                               continue;
+                       } else if (src[1] == '.') {
+                               if (!src[2]) {
+                                       /* (3) */
+                                       src += 2;
+                                       goto up_one;
+                               } else if (is_dir_sep(src[2])) {
+                                       /* (4) */
+                                       src += 3;
+                                       while (is_dir_sep(*src))
+                                               src++;
+                                       goto up_one;
+                               }
+                       }
+               }
+
+               /* copy up to the next '/', and eat all '/' */
+               while ((c = *src++) != '\0' && !is_dir_sep(c))
+                       *dst++ = c;
+               if (is_dir_sep(c)) {
+                       *dst++ = '/';
+                       while (is_dir_sep(c))
+                               c = *src++;
+                       src--;
+               } else if (!c)
+                       break;
+               continue;
+
+       up_one:
+               /*
+                * dst0..dst is prefix portion, and dst[-1] is '/';
+                * go up one level.
+                */
+               dst--;  /* go to trailing '/' */
+               if (dst <= dst0)
+                       return -1;
+               /* Windows: dst[-1] cannot be backslash anymore */
+               while (dst0 < dst && dst[-1] != '/')
+                       dst--;
+       }
        *dst = '\0';
-       return dst - buf;
+       return 0;
 }
 
 /*
@@ -438,15 +479,16 @@ int longest_ancestor_length(const char *path, const char *prefix_list)
                return -1;
 
        for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
-               for (colon = ceil; *colon && *colon != ':'; colon++);
+               for (colon = ceil; *colon && *colon != PATH_SEP; colon++);
                len = colon - ceil;
                if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
                        continue;
                strlcpy(buf, ceil, len+1);
-               len = normalize_absolute_path(buf, buf);
-               /* Strip "trailing slashes" from "/". */
-               if (len == 1)
-                       len = 0;
+               if (normalize_path_copy(buf, buf) < 0)
+                       continue;
+               len = strlen(buf);
+               if (len > 0 && buf[len-1] == '/')
+                       buf[--len] = '\0';
 
                if (!strncmp(path, buf, len) &&
                    path[len] == '/' &&
@@ -457,3 +499,39 @@ int longest_ancestor_length(const char *path, const char *prefix_list)
 
        return max_len;
 }
+
+/* strip arbitrary amount of directory separators at end of path */
+static inline int chomp_trailing_dir_sep(const char *path, int len)
+{
+       while (len && is_dir_sep(path[len - 1]))
+               len--;
+       return len;
+}
+
+/*
+ * If path ends with suffix (complete path components), returns the
+ * part before suffix (sans trailing directory separators).
+ * Otherwise returns NULL.
+ */
+char *strip_path_suffix(const char *path, const char *suffix)
+{
+       int path_len = strlen(path), suffix_len = strlen(suffix);
+
+       while (suffix_len) {
+               if (!path_len)
+                       return NULL;
+
+               if (is_dir_sep(path[path_len - 1])) {
+                       if (!is_dir_sep(suffix[suffix_len - 1]))
+                               return NULL;
+                       path_len = chomp_trailing_dir_sep(path, path_len);
+                       suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
+               }
+               else if (path[--path_len] != suffix[--suffix_len])
+                       return NULL;
+       }
+
+       if (path_len && !is_dir_sep(path[path_len - 1]))
+               return NULL;
+       return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
+}
index cc460b56970c3869e67feb816f76f4804e37e7ce..6cd91491d348b9aabea902c0ea60465074e9c06e 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -210,15 +210,13 @@ static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
        while (parent) {
                struct commit *p = parent->item;
                const char *hex = NULL;
-               const char *dots;
                if (abbrev)
                        hex = find_unique_abbrev(p->object.sha1, abbrev);
                if (!hex)
                        hex = sha1_to_hex(p->object.sha1);
-               dots = (abbrev && strlen(hex) != 40) ?  "..." : "";
                parent = parent->next;
 
-               strbuf_addf(sb, " %s%s", hex, dots);
+               strbuf_addf(sb, " %s", hex);
        }
        strbuf_addch(sb, '\n');
 }
@@ -305,23 +303,14 @@ static char *logmsg_reencode(const struct commit *commit,
        return out;
 }
 
-static int mailmap_name(struct strbuf *sb, const char *email)
+static int mailmap_name(char *email, int email_len, char *name, int name_len)
 {
        static struct string_list *mail_map;
-       char buffer[1024];
-
        if (!mail_map) {
                mail_map = xcalloc(1, sizeof(*mail_map));
-               read_mailmap(mail_map, ".mailmap", NULL);
+               read_mailmap(mail_map, NULL);
        }
-
-       if (!mail_map->nr)
-               return -1;
-
-       if (!map_email(mail_map, email, buffer, sizeof(buffer)))
-               return -1;
-       strbuf_addstr(sb, buffer);
-       return 0;
+       return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
 }
 
 static size_t format_person_part(struct strbuf *sb, char part,
@@ -332,6 +321,9 @@ static size_t format_person_part(struct strbuf *sb, char part,
        int start, end, tz = 0;
        unsigned long date = 0;
        char *ep;
+       const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
+       char person_name[1024];
+       char person_mail[1024];
 
        /* advance 'end' to point to email start delimiter */
        for (end = 0; end < len && msg[end] != '<'; end++)
@@ -345,25 +337,34 @@ static size_t format_person_part(struct strbuf *sb, char part,
        if (end >= len - 2)
                goto skip;
 
+       /* Seek for both name and email part */
+       name_start = msg;
+       name_end = msg+end;
+       while (name_end > name_start && isspace(*(name_end-1)))
+               name_end--;
+       mail_start = msg+end+1;
+       mail_end = mail_start;
+       while (mail_end < msg_end && *mail_end != '>')
+               mail_end++;
+       if (mail_end == msg_end)
+               goto skip;
+       end = mail_end-msg;
+
+       if (part == 'N' || part == 'E') { /* mailmap lookup */
+               strlcpy(person_name, name_start, name_end-name_start+1);
+               strlcpy(person_mail, mail_start, mail_end-mail_start+1);
+               mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
+               name_start = person_name;
+               name_end = name_start + strlen(person_name);
+               mail_start = person_mail;
+               mail_end = mail_start +  strlen(person_mail);
+       }
        if (part == 'n' || part == 'N') {       /* name */
-               while (end > 0 && isspace(msg[end - 1]))
-                       end--;
-               if (part != 'N' || !msg[end] || !msg[end + 1] ||
-                   mailmap_name(sb, msg + end + 2) < 0)
-                       strbuf_add(sb, msg, end);
+               strbuf_add(sb, name_start, name_end-name_start);
                return placeholder_len;
        }
-       start = ++end; /* save email start position */
-
-       /* advance 'end' to point to email end delimiter */
-       for ( ; end < len && msg[end] != '>'; end++)
-               ; /* do nothing */
-
-       if (end >= len)
-               goto skip;
-
-       if (part == 'e') {      /* email */
-               strbuf_add(sb, msg + start, end - start);
+       if (part == 'e' || part == 'E') {       /* email */
+               strbuf_add(sb, mail_start, mail_end-mail_start);
                return placeholder_len;
        }
 
diff --git a/refs.c b/refs.c
index 024211d72b5e21e1c87e98b0f7e3a6982525d82d..6eb5f5384611bb5d159d892a1bfd120d72e54b9b 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -275,10 +275,8 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
                                list = get_ref_dir(ref, list);
                                continue;
                        }
-                       if (!resolve_ref(ref, sha1, 1, &flag)) {
-                               error("%s points nowhere!", ref);
-                               continue;
-                       }
+                       if (!resolve_ref(ref, sha1, 1, &flag))
+                               hashclr(sha1);
                        list = add_ref(ref, sha1, flag, list, NULL);
                }
                free(ref);
@@ -287,6 +285,35 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
        return sort_ref_list(list);
 }
 
+struct warn_if_dangling_data {
+       const char *refname;
+       const char *msg_fmt;
+};
+
+static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
+                                  int flags, void *cb_data)
+{
+       struct warn_if_dangling_data *d = cb_data;
+       const char *resolves_to;
+       unsigned char junk[20];
+
+       if (!(flags & REF_ISSYMREF))
+               return 0;
+
+       resolves_to = resolve_ref(refname, junk, 0, NULL);
+       if (!resolves_to || strcmp(resolves_to, d->refname))
+               return 0;
+
+       printf(d->msg_fmt, refname);
+       return 0;
+}
+
+void warn_dangling_symref(const char *msg_fmt, const char *refname)
+{
+       struct warn_if_dangling_data data = { refname, msg_fmt };
+       for_each_rawref(warn_if_dangling_symref, &data);
+}
+
 static struct ref_list *get_loose_refs(void)
 {
        if (!cached_refs.did_loose) {
@@ -498,16 +525,19 @@ int read_ref(const char *ref, unsigned char *sha1)
        return -1;
 }
 
+#define DO_FOR_EACH_INCLUDE_BROKEN 01
 static int do_one_ref(const char *base, each_ref_fn fn, int trim,
-                     void *cb_data, struct ref_list *entry)
+                     int flags, void *cb_data, struct ref_list *entry)
 {
        if (strncmp(base, entry->name, trim))
                return 0;
-       if (is_null_sha1(entry->sha1))
-               return 0;
-       if (!has_sha1_file(entry->sha1)) {
-               error("%s does not point to a valid object!", entry->name);
-               return 0;
+       if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+               if (is_null_sha1(entry->sha1))
+                       return 0;
+               if (!has_sha1_file(entry->sha1)) {
+                       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);
@@ -561,7 +591,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 flags, void *cb_data)
 {
        int retval = 0;
        struct ref_list *packed = get_packed_refs();
@@ -570,7 +600,7 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
        struct ref_list *extra;
 
        for (extra = extra_refs; extra; extra = extra->next)
-               retval = do_one_ref(base, fn, trim, cb_data, extra);
+               retval = do_one_ref(base, fn, trim, flags, cb_data, extra);
 
        while (packed && loose) {
                struct ref_list *entry;
@@ -586,13 +616,13 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
                        entry = packed;
                        packed = packed->next;
                }
-               retval = do_one_ref(base, fn, trim, cb_data, entry);
+               retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
                if (retval)
                        goto end_each;
        }
 
        for (packed = packed ? packed : loose; packed; packed = packed->next) {
-               retval = do_one_ref(base, fn, trim, cb_data, packed);
+               retval = do_one_ref(base, fn, trim, flags, cb_data, packed);
                if (retval)
                        goto end_each;
        }
@@ -614,22 +644,28 @@ int head_ref(each_ref_fn fn, void *cb_data)
 
 int for_each_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/", fn, 0, cb_data);
+       return do_for_each_ref("refs/", fn, 0, 0, cb_data);
 }
 
 int for_each_tag_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/tags/", fn, 10, cb_data);
+       return do_for_each_ref("refs/tags/", fn, 10, 0, cb_data);
 }
 
 int for_each_branch_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/heads/", fn, 11, cb_data);
+       return do_for_each_ref("refs/heads/", fn, 11, 0, cb_data);
 }
 
 int for_each_remote_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
+       return do_for_each_ref("refs/remotes/", fn, 13, 0, cb_data);
+}
+
+int for_each_rawref(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref("refs/", fn, 0,
+                              DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
 /*
diff --git a/refs.h b/refs.h
index 3bb529d3870012feda9a0398cba15c09aee10cf9..29bdcecd4edb5e7281a4da36a06aa05e025f38a7 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -24,6 +24,11 @@ extern int for_each_tag_ref(each_ref_fn, void *);
 extern int for_each_branch_ref(each_ref_fn, void *);
 extern int for_each_remote_ref(each_ref_fn, void *);
 
+/* can be used to learn about broken ref and symref */
+extern int for_each_rawref(each_ref_fn, void *);
+
+extern void warn_dangling_symref(const char *msg_fmt, const char *refname);
+
 /*
  * Extra refs will be listed by for_each_ref() before any actual refs
  * for the duration of this process or until clear_extra_refs() is
index 718fb526dd1fbbc5de18784586d37d6069807b77..3518207c178904b91ce28f8d3bf2ba0ee560d0e7 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -290,7 +290,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                        hex = xstrdup(sha1_to_hex(sha1));
                        string_list_insert(path, rr)->util = hex;
                        if (mkdir(git_path("rr-cache/%s", hex), 0755))
-                               continue;;
+                               continue;
                        handle_file(path, NULL, rr_path(hex, "preimage"));
                        fprintf(stderr, "Recorded preimage for '%s'\n", path);
                }
index 8603c145817488a4ce80111051129d4d715c597b..286e416b757fa8df731330992fca96773082f75d 100644 (file)
@@ -1738,14 +1738,16 @@ static struct commit *get_revision_1(struct rev_info *revs)
                            (commit->date < revs->max_age))
                                continue;
                        if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0)
-                               return NULL;
+                               die("Failed to traverse parents of commit %s",
+                                   sha1_to_hex(commit->object.sha1));
                }
 
                switch (simplify_commit(revs, commit)) {
                case commit_ignore:
                        continue;
                case commit_error:
-                       return NULL;
+                       die("Failed to simplify parents of commit %s",
+                           sha1_to_hex(commit->object.sha1));
                default:
                        return commit;
                }
diff --git a/setup.c b/setup.c
index dfda532adc16f5e6d25d7cfc5add3e0e2b6a5209..6c2deda18492acb5a8597563d6843f9d0dd232c0 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -4,92 +4,6 @@
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
 
-static int sanitary_path_copy(char *dst, const char *src)
-{
-       char *dst0;
-
-       if (has_dos_drive_prefix(src)) {
-               *dst++ = *src++;
-               *dst++ = *src++;
-       }
-       dst0 = dst;
-
-       if (is_dir_sep(*src)) {
-               *dst++ = '/';
-               while (is_dir_sep(*src))
-                       src++;
-       }
-
-       for (;;) {
-               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 == '.') {
-                       if (!src[1]) {
-                               /* (1) */
-                               src++;
-                       } else if (is_dir_sep(src[1])) {
-                               /* (2) */
-                               src += 2;
-                               while (is_dir_sep(*src))
-                                       src++;
-                               continue;
-                       } else if (src[1] == '.') {
-                               if (!src[2]) {
-                                       /* (3) */
-                                       src += 2;
-                                       goto up_one;
-                               } else if (is_dir_sep(src[2])) {
-                                       /* (4) */
-                                       src += 3;
-                                       while (is_dir_sep(*src))
-                                               src++;
-                                       goto up_one;
-                               }
-                       }
-               }
-
-               /* copy up to the next '/', and eat all '/' */
-               while ((c = *src++) != '\0' && !is_dir_sep(c))
-                       *dst++ = c;
-               if (is_dir_sep(c)) {
-                       *dst++ = '/';
-                       while (is_dir_sep(c))
-                               c = *src++;
-                       src--;
-               } else if (!c)
-                       break;
-               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 == '/') { /* MinGW: cannot be '\\' anymore */
-                               dst += 2;
-                               break;
-                       }
-               }
-       }
-       *dst = '\0';
-       return 0;
-}
-
 const char *prefix_path(const char *prefix, int len, const char *path)
 {
        const char *orig = path;
@@ -101,7 +15,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
                        memcpy(sanitized, prefix, len);
                strcpy(sanitized + len, path);
        }
-       if (sanitary_path_copy(sanitized, sanitized))
+       if (normalize_path_copy(sanitized, sanitized))
                goto error_out;
        if (is_absolute_path(orig)) {
                const char *work_tree = get_git_work_tree();
index 8868b800cbb9259f97b8acac3c8701153118b657..032300c4c6434701e802df729cd74245e543de20 100644 (file)
@@ -689,6 +689,7 @@ void free_pack_by_name(const char *pack_name)
        while (*pp) {
                p = *pp;
                if (strcmp(pack_name, p->pack_name) == 0) {
+                       clear_delta_base_cache();
                        close_pack_windows(p);
                        if (p->pack_fd != -1)
                                close(p->pack_fd);
@@ -800,7 +801,7 @@ unsigned char* use_pack(struct packed_git *p,
        if (p->pack_fd == -1 && open_packed_git(p))
                die("packfile %s cannot be accessed", p->pack_name);
 
-       /* Since packfiles end in a hash of their content and its
+       /* Since packfiles end in a hash of their content and it's
         * pointless to ask for an offset into the middle of that
         * hash, and the in_window function above wouldn't match
         * don't allow an offset too close to the end of the file.
@@ -1663,6 +1664,13 @@ static inline void release_delta_base_cache(struct delta_base_cache_entry *ent)
        }
 }
 
+void clear_delta_base_cache(void)
+{
+       unsigned long p;
+       for (p = 0; p < MAX_DELTA_CACHE; p++)
+               release_delta_base_cache(&delta_base_cache[p]);
+}
+
 static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
        void *base, unsigned long base_size, enum object_type type)
 {
index 5d0ac0263d04d7ec72a3b7dec4aaf47aec80da5e..2f75179f4c6c1d05bdd7594b23dcf77007c26751 100644 (file)
@@ -268,16 +268,19 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
                char fullref[PATH_MAX];
                unsigned char sha1_from_ref[20];
                unsigned char *this_result;
+               int flag;
 
                this_result = refs_found ? sha1_from_ref : sha1;
                mksnpath(fullref, sizeof(fullref), *p, len, str);
-               r = resolve_ref(fullref, this_result, 1, NULL);
+               r = resolve_ref(fullref, this_result, 1, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
                        if (!warn_ambiguous_refs)
                                break;
-               }
+               } else if ((flag & REF_ISSYMREF) &&
+                          (len != 4 || strcmp(str, "HEAD")))
+                       warning("ignoring dangling symref %s.", fullref);
        }
        free(last_branch);
        return refs_found;
index ddd83c8c76112cecd5d23668aaca467601855a72..15e14cf47a586613a62970b393c269728ffbef93 100644 (file)
@@ -26,10 +26,10 @@ static int get_entry_index(const struct string_list *list, const char *string,
 }
 
 /* returns -1-index if already exists */
-static int add_entry(struct string_list *list, const char *string)
+static int add_entry(int insert_at, struct string_list *list, const char *string)
 {
-       int exact_match;
-       int index = get_entry_index(list, string, &exact_match);
+       int exact_match = 0;
+       int index = insert_at != -1 ? insert_at : get_entry_index(list, string, &exact_match);
 
        if (exact_match)
                return -1 - index;
@@ -53,7 +53,13 @@ static int add_entry(struct string_list *list, const char *string)
 
 struct string_list_item *string_list_insert(const char *string, struct string_list *list)
 {
-       int index = add_entry(list, string);
+       return string_list_insert_at_index(-1, string, list);
+}
+
+struct string_list_item *string_list_insert_at_index(int insert_at,
+                                                    const char *string, struct string_list *list)
+{
+       int index = add_entry(insert_at, list, string);
 
        if (index < 0)
                index = -1 - index;
@@ -68,6 +74,16 @@ int string_list_has_string(const struct string_list *list, const char *string)
        return exact_match;
 }
 
+int string_list_find_insert_index(const struct string_list *list, const char *string,
+                                 int negative_existing_index)
+{
+       int exact_match;
+       int index = get_entry_index(list, string, &exact_match);
+       if (exact_match)
+               index = -1 - (negative_existing_index ? index : 0);
+       return index;
+}
+
 struct string_list_item *string_list_lookup(const char *string, struct string_list *list)
 {
        int exact_match, i = get_entry_index(list, string, &exact_match);
@@ -94,6 +110,25 @@ void string_list_clear(struct string_list *list, int free_util)
        list->nr = list->alloc = 0;
 }
 
+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc)
+{
+       if (list->items) {
+               int i;
+               if (clearfunc) {
+                       for (i = 0; i < list->nr; i++)
+                               clearfunc(list->items[i].util, list->items[i].string);
+               }
+               if (list->strdup_strings) {
+                       for (i = 0; i < list->nr; i++)
+                               free(list->items[i].string);
+               }
+               free(list->items);
+       }
+       list->items = NULL;
+       list->nr = list->alloc = 0;
+}
+
+
 void print_string_list(const char *text, const struct string_list *p)
 {
        int i;
index 4d6a7051fe5bccf04a0d0c32a90e5cf9c00dba3c..d32ba05202880733dd76f5465a0ae16753d1fba6 100644 (file)
@@ -15,9 +15,18 @@ struct string_list
 void print_string_list(const char *text, const struct string_list *p);
 void string_list_clear(struct string_list *list, int free_util);
 
+/* Use this function to call a custom clear function on each util pointer */
+/* The string associated with the util pointer is passed as the second argument */
+typedef void (*string_list_clear_func_t)(void *p, const char *str);
+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc);
+
 /* Use these functions only on sorted lists: */
 int string_list_has_string(const struct string_list *list, const char *string);
+int string_list_find_insert_index(const struct string_list *list, const char *string,
+                                 int negative_existing_index);
 struct string_list_item *string_list_insert(const char *string, struct string_list *list);
+struct string_list_item *string_list_insert_at_index(int insert_at,
+                                                    const char *string, struct string_list *list);
 struct string_list_item *string_list_lookup(const char *string, struct string_list *list);
 
 /* Use these functions only on unsorted lists: */
index 3824020ca12a4c972a3e1bc8324dcd5ab6094633..86cdebc727c4964899a71366151a7782558153c7 100644 (file)
@@ -11,7 +11,21 @@ then
        exit
 fi
 
-LIB_HTTPD_PATH=${LIB_HTTPD_PATH-'/usr/sbin/apache2'}
+HTTPD_PARA=""
+
+case $(uname) in
+       Darwin)
+               DEFAULT_HTTPD_PATH='/usr/sbin/httpd'
+               DEFAULT_HTTPD_MODULE_PATH='/usr/libexec/apache2'
+               HTTPD_PARA="$HTTPD_PARA -DDarwin"
+       ;;
+       *)
+               DEFAULT_HTTPD_PATH='/usr/sbin/apache2'
+               DEFAULT_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
+       ;;
+esac
+
+LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"}
 LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'8111'}
 
 TEST_PATH="$TEST_DIRECTORY"/lib-httpd
@@ -39,14 +53,12 @@ then
                        exit
                fi
 
-               LIB_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
+               LIB_HTTPD_MODULE_PATH="$DEFAULT_HTTPD_MODULE_PATH"
        fi
 else
        error "Could not identify web server at '$LIB_HTTPD_PATH'"
 fi
 
-HTTPD_PARA=""
-
 prepare_httpd() {
        mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
 
@@ -95,5 +107,5 @@ stop_httpd() {
        trap 'die' EXIT
 
        "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
-               -f "$TEST_PATH/apache.conf" -k stop
+               -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop
 }
index fdb19a50f11c8c71c9f7addcceeab8847558cc49..af6e5e1d6ae503dfadc94feef4d9cb4b623163ec 100644 (file)
@@ -5,6 +5,12 @@ LogFormat "%h %l %u %t \"%r\" %>s %b" common
 CustomLog access.log common
 ErrorLog error.log
 
+<IfDefine Darwin>
+       LoadModule log_config_module modules/mod_log_config.so
+       LockFile accept.lock
+       PidFile httpd.pid
+</IfDefine>
+
 <IfDefine SSL>
 LoadModule ssl_module modules/mod_ssl.so
 
index 6e7501f352ee97636280357da54c50d73ceb0138..8336114f9820aa5972d38efe316de7d9e39bcc5c 100755 (executable)
@@ -8,36 +8,37 @@ test_description='Test various path utilities'
 . ./test-lib.sh
 
 norm_abs() {
-       test_expect_success "normalize absolute" \
-       "test \$(test-path-utils normalize_absolute_path '$1') = '$2'"
+       test_expect_success "normalize absolute: $1 => $2" \
+       "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'"
 }
 
 ancestor() {
-       test_expect_success "longest ancestor" \
-       "test \$(test-path-utils longest_ancestor_length '$1' '$2') = '$3'"
+       test_expect_success "longest ancestor: $1 $2 => $3" \
+       "test \"\$(test-path-utils longest_ancestor_length '$1' '$2')\" = '$3'"
 }
 
-norm_abs "" /
+norm_abs "" ""
 norm_abs / /
 norm_abs // /
 norm_abs /// /
 norm_abs /. /
 norm_abs /./ /
-norm_abs /./.. /
-norm_abs /../. /
-norm_abs /./../.// /
+norm_abs /./.. ++failed++
+norm_abs /../. ++failed++
+norm_abs /./../.// ++failed++
 norm_abs /dir/.. /
 norm_abs /dir/sub/../.. /
+norm_abs /dir/sub/../../.. ++failed++
 norm_abs /dir /dir
-norm_abs /dir// /dir
+norm_abs /dir// /dir/
 norm_abs /./dir /dir
-norm_abs /dir/. /dir
-norm_abs /dir///./ /dir
-norm_abs /dir//sub/.. /dir
-norm_abs /dir/sub/../ /dir
-norm_abs //dir/sub/../. /dir
-norm_abs /dir/s1/../s2/ /dir/s2
-norm_abs /d1/s1///s2/..//../s3/ /d1/s3
+norm_abs /dir/. /dir/
+norm_abs /dir///./ /dir/
+norm_abs /dir//sub/.. /dir/
+norm_abs /dir/sub/../ /dir/
+norm_abs //dir/sub/../. /dir/
+norm_abs /dir/s1/../s2/ /dir/s2/
+norm_abs /d1/s1///s2/..//../s3/ /d1/s3/
 norm_abs /d1/s1//../s2/../../d2 /d2
 norm_abs /d1/.../d2 /d1/.../d2
 norm_abs /d1/..././../d2 /d1/d2
@@ -84,4 +85,8 @@ ancestor /foo/bar :://foo/.:: 4
 ancestor /foo/bar //foo/./::/bar 4
 ancestor /foo/bar ::/bar -1
 
+test_expect_success 'strip_path_suffix' '
+       test c:/msysgit = $(test-path-utils strip_path_suffix \
+               c:/msysgit/libexec//git-core libexec/git-core)
+'
 test_done
diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh
new file mode 100755 (executable)
index 0000000..315b9b3
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='previous branch syntax @{-n}'
+
+. ./test-lib.sh
+
+test_expect_success 'branch -d @{-1}' '
+       test_commit A &&
+       git checkout -b junk &&
+       git checkout - &&
+       test "$(git symbolic-ref HEAD)" = refs/heads/master &&
+       git branch -d @{-1} &&
+       test_must_fail git rev-parse --verify refs/heads/junk
+'
+
+test_expect_success 'branch -d @{-12} when there is not enough switches yet' '
+       git reflog expire --expire=now &&
+       git checkout -b junk2 &&
+       git checkout - &&
+       test "$(git symbolic-ref HEAD)" = refs/heads/master &&
+       test_must_fail git branch -d @{-12} &&
+       git rev-parse --verify refs/heads/master
+'
+
+test_expect_success 'merge @{-1}' '
+       git checkout A &&
+       test_commit B &&
+       git checkout A &&
+       test_commit C &&
+       git branch -f master B &&
+       git branch -f other &&
+       git checkout other &&
+       git checkout master &&
+       git merge @{-1} &&
+       git cat-file commit HEAD | grep "Merge branch '\''other'\''"
+'
+
+test_expect_success 'merge @{-1} when there is not enough switches yet' '
+       git reflog expire --expire=now &&
+       git checkout -f master &&
+       git reset --hard B &&
+       git branch -f other C &&
+       git checkout other &&
+       git checkout master &&
+       test_must_fail git merge @{-12}
+'
+
+test_done
+
diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh
new file mode 100755 (executable)
index 0000000..7fa5f5b
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='basic symbolic-ref tests'
+. ./test-lib.sh
+
+# If the tests munging HEAD fail, they can break detection of
+# the git repo, meaning that further tests will operate on
+# the surrounding git repo instead of the trash directory.
+reset_to_sane() {
+       echo ref: refs/heads/foo >.git/HEAD
+}
+
+test_expect_success 'symbolic-ref writes HEAD' '
+       git symbolic-ref HEAD refs/heads/foo &&
+       echo ref: refs/heads/foo >expect &&
+       test_cmp expect .git/HEAD
+'
+
+test_expect_success 'symbolic-ref reads HEAD' '
+       echo refs/heads/foo >expect &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'symbolic-ref refuses non-ref for HEAD' '
+       test_must_fail git symbolic-ref HEAD foo
+'
+reset_to_sane
+
+test_expect_success 'symbolic-ref refuses bare sha1' '
+       echo content >file && git add file && git commit -m one
+       test_must_fail git symbolic-ref HEAD `git rev-parse HEAD`
+'
+reset_to_sane
+
+test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
new file mode 100755 (executable)
index 0000000..4597af0
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='git fsck random collection of tests'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_commit A fileA one &&
+       git checkout HEAD^0 &&
+       test_commit B fileB two &&
+       git tag -d A B &&
+       git reflog expire --expire=now --all
+'
+
+test_expect_success 'HEAD is part of refs' '
+       test 0 = $(git fsck | wc -l)
+'
+
+test_expect_success 'loose objects borrowed from alternate are not missing' '
+       mkdir another &&
+       (
+               cd another &&
+               git init &&
+               echo ../../../.git/objects >.git/objects/info/alternates &&
+               test_commit C fileC one &&
+               git fsck >out &&
+               ! grep "missing blob" out
+       )
+'
+
+test_done
index 85da4caa7ed1b8bcaca7b21e218f2d1839d2db82..48ee07779d64147c3eb8325a3b3f9579f8ec41d8 100755 (executable)
@@ -26,21 +26,28 @@ test_rev_parse() {
        "test '$1' = \"\$(git rev-parse --show-prefix)\""
        shift
        [ $# -eq 0 ] && return
+
+       test_expect_success "$name: git-dir" \
+       "test '$1' = \"\$(git rev-parse --git-dir)\""
+       shift
+       [ $# -eq 0 ] && return
 }
 
-# label is-bare is-inside-git is-inside-work prefix
+# label is-bare is-inside-git is-inside-work prefix git-dir
+
+ROOT=$(pwd)
 
-test_rev_parse toplevel false false true ''
+test_rev_parse toplevel false false true '' .git
 
 cd .git || exit 1
-test_rev_parse .git/ false true false ''
+test_rev_parse .git/ false true false '' .
 cd objects || exit 1
-test_rev_parse .git/objects/ false true false ''
+test_rev_parse .git/objects/ false true false '' "$ROOT/.git"
 cd ../.. || exit 1
 
 mkdir -p sub/dir || exit 1
 cd sub/dir || exit 1
-test_rev_parse subdirectory false false true sub/dir/
+test_rev_parse subdirectory false false true sub/dir/ "$ROOT/.git"
 cd ../.. || exit 1
 
 git config core.bare true
index 27dc6c55d5f50a7fd30388b60230482bad6be2d8..f6a6f839a18de4c3775ea965f164d0d20f2bbe9b 100755 (executable)
@@ -92,13 +92,6 @@ cd sub/dir || exit 1
 test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
 cd ../../../.. || exit 1
 
-test_expect_success 'detecting gitdir when cwd is in a subdir of gitdir' '
-       (expected=$(pwd)/repo.git &&
-        cd repo.git/refs &&
-        unset GIT_DIR &&
-        test "$expected" = "$(git rev-parse --git-dir)")
-'
-
 test_expect_success 'repo finds its work tree' '
        (cd repo.git &&
         : > work/sub/dir/untracked &&
index 91b704a3a4ce6771071d19bd84aa228856fe6875..e377d48902cd5fd539a0ce4cb1fb32ceb8732632 100755 (executable)
@@ -93,13 +93,13 @@ GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi"
 test_prefix subdir_ceil_at_subdi_slash "sub/dir/"
 
 
-GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub"
+GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub"
 test_fail second_of_two
 
-GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:bar"
+GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:/bar"
 test_fail first_of_two
 
-GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub:bar"
+GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub:/bar"
 test_fail second_of_three
 
 
index e42cbfe6c61951c6887a363cb668d26a7adcf20c..293dc353b1601e137f86dfe441fff57b96c92753 100755 (executable)
@@ -10,12 +10,12 @@ test_cd_to_toplevel () {
                        cd '"'$1'"' &&
                        . git-sh-setup &&
                        cd_to_toplevel &&
-                       [ "$(unset PWD; /bin/pwd)" = "$TOPLEVEL" ]
+                       [ "$(pwd -P)" = "$TOPLEVEL" ]
                )
        '
 }
 
-TOPLEVEL="$(unset PWD; /bin/pwd)/repo"
+TOPLEVEL="$(pwd -P)/repo"
 mkdir -p repo/sub/dir
 mv .git repo/
 SUBDIRECTORY_OK=1
index b7a670ef401429a50eb97e5f874c9e2c3897fd7d..be7ae5a0041a0d06b40a7152d4c5d495a842a87f 100755 (executable)
@@ -14,7 +14,8 @@ export GIT_AUTHOR_EMAIL
 
 test_expect_success \
     'prepare repository with topic branches' \
-    'echo First > A &&
+    'git config core.logAllRefUpdates true &&
+     echo First > A &&
      git update-index --add A &&
      git commit -m "Add A." &&
      git checkout -b my-topic-branch &&
@@ -47,6 +48,10 @@ test_expect_success \
     'the rebase operation should not have destroyed author information' \
     '! (git log | grep "Author:" | grep "<>")'
 
+test_expect_success 'HEAD was detached during rebase' '
+     test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1})
+'
+
 test_expect_success 'rebase after merge master' '
      git reset --hard topic &&
      git merge master &&
@@ -84,4 +89,10 @@ test_expect_success 'rebase a single mode change' '
      GIT_TRACE=1 git rebase master
 '
 
+test_expect_success 'Show verbose error when HEAD could not be detached' '
+     : > B &&
+     test_must_fail git rebase topic 2> output.err > output.out &&
+     grep "Untracked working tree file .B. would be overwritten" output.err
+'
+
 test_done
index aba53202f84b6c6c16a68f45ccfa42368e47d421..9c709022efb1b553ef53b3100d39e852117cbeda 100755 (executable)
@@ -74,6 +74,10 @@ test_expect_success setup '
        for i in 1 2; do echo $i; done >>dir/sub &&
        git update-index file0 dir/sub &&
 
+       mkdir dir3 &&
+       cp dir/sub dir3/sub &&
+       test-chmtime +1 dir3/sub &&
+
        git config log.showroot false &&
        git commit --amend &&
        git show-branch
@@ -262,6 +266,7 @@ diff --patch-with-raw -r initial..side
 diff --name-status dir2 dir
 diff --no-index --name-status dir2 dir
 diff --no-index --name-status -- dir2 dir
+diff --no-index dir dir3
 diff master master^ side
 EOF
 
diff --git a/t/t4013/diff.diff_--no-index_dir_dir3 b/t/t4013/diff.diff_--no-index_dir_dir3
new file mode 100644 (file)
index 0000000..2142c2b
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff --no-index dir dir3
+$
index 3ceb8e73c5d4e963a2c08eddf41197431ad52178..bd7f5c0f70571d0aa0f9b9c50a343a322831976f 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat --summary master -- dir/
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 43d77761f988b7f43f46a5d8fd85d406ef9caa42..14595a614c362da4515f8d5b783bb0e0f079c72b 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5187a26816723476b049c44bb3801816e8672cdf..5a4e72765d316b855a8c2beee8f417cd050de323 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat master -- dir/
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index c9640976a8f3bd724004f945b00d71f43f00c8ea..df0aaa9f2ca780ce62951a5667ed9fe3aa005e41 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --cc --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index ad050af55ffa6983bc08ef5919dd20229584fd59..c11b5f2c7f3e6846643b7d7e0e214993f4a2cf0d 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 628c6c03bc6195268c3e7e003478fd042ef36db2..5f0c98f9ce3d9d067cfcb662084b975a19c90f03 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --patch-with-stat master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5d4e0f13b59652b170c0b2498319f970d071f774..e62c368dc64dae87c6c168462b175712f197e7af 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root -c --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 217a2eb203718b91ccd79a04e0369f4c5d2905aa..b42c334439b71cdb391d832d498b90a22aad2656 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root -p master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index e17ccfc2340eadbb8c393de0aa2793f6f899ba56..e8f46159da1e5ca68524f5657010d06667a9c94b 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index f8fefef2c3869337ba048d5cd6d95169dc60cf00..bf1326dc36629096fc4e4102375c8a9c550391aa 100644 (file)
@@ -1,6 +1,6 @@
 $ git log -p master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index e9d9e7b40a08d803901ac001e4bf9f0c37ad8b15..a8f6ce5abd642e51672eb3e0ae5ea66a08aa74d2 100644 (file)
@@ -1,6 +1,6 @@
 $ git log master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 9e6e1f27105ca50262e5eb3f2ff132b46c12fe45..fb08ce0e46d16d223269754295cc4a8dc4364268 100644 (file)
@@ -1,6 +1,6 @@
 $ git show master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5facf2543db0135ceb3aafdc5ffe0395b429f44c..e96ff1fb8c11ff8c6774cb249f45744ed05a97c3 100644 (file)
@@ -1,6 +1,6 @@
 $ git whatchanged --root --cc --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 10f6767e498b0d5bdadd73067e4ef2dd5f028e89..c0aff68ef68c2d956492e7f9817b9bf5afe58005 100644 (file)
@@ -1,6 +1,6 @@
 $ git whatchanged --root -c --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index caea292f15437f50fd324496ff511971cc9d1d0c..281680d95afacb291d56d9d7f23efed1f3169e93 100755 (executable)
@@ -128,4 +128,12 @@ test_expect_success 'force diff with "diff"' '
        test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual
 '
 
+test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' '
+       echo anotherfile > file2 &&
+       git add file2 &&
+       git commit -m "added 2nd file" &&
+       echo modified >file2 &&
+       GIT_EXTERNAL_DIFF=echo git diff
+'
+
 test_done
index 796f795267dee1eaf63b10fb3e5e14ce0431bebd..5e65afa0c10d02e50c79b550a3c142d8ff1f0674 100755 (executable)
@@ -257,4 +257,37 @@ test_expect_success 'am works from file (absolute path given) in subdirectory' '
        test -z "$(git diff second)"
 '
 
+test_expect_success 'am --committer-date-is-author-date' '
+       git checkout first &&
+       test_tick &&
+       git am --committer-date-is-author-date patch1 &&
+       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       at=$(sed -ne "/^author /s/.*> //p" head1) &&
+       ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
+       test "$at" = "$ct"
+'
+
+test_expect_success 'am without --committer-date-is-author-date' '
+       git checkout first &&
+       test_tick &&
+       git am patch1 &&
+       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       at=$(sed -ne "/^author /s/.*> //p" head1) &&
+       ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
+       test "$at" != "$ct"
+'
+
+# This checks for +0000 because TZ is set to UTC and that should
+# show up when the current time is used. The date in message is set
+# by test_tick that uses -0700 timezone; if this feature does not
+# work, we will see that instead of +0000.
+test_expect_success 'am --ignore-date' '
+       git checkout first &&
+       test_tick &&
+       git am --ignore-date patch1 &&
+       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       at=$(sed -ne "/^author /s/.*> //p" head1) &&
+       echo "$at" | grep "+0000"
+'
+
 test_done
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
new file mode 100755 (executable)
index 0000000..9a7d1b4
--- /dev/null
@@ -0,0 +1,215 @@
+#!/bin/sh
+
+test_description='.mailmap configurations'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo one >one &&
+       git add one &&
+       test_tick &&
+       git commit -m initial &&
+       echo two >>one &&
+       git add one &&
+       git commit --author "nick1 <bugs@company.xx>" -m second
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'No mailmap' '
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'default .mailmap' '
+       echo "Repo Guy <author@example.com>" > .mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+# Using a mailmap file in a subdirectory of the repo here, but
+# could just as well have been a file outside of the repository
+cat >expect <<\EOF
+Internal Guy (1):
+      second
+
+Repo Guy (1):
+      initial
+
+EOF
+test_expect_success 'mailmap.file set' '
+       mkdir internal_mailmap &&
+       echo "Internal Guy <bugs@company.xx>" > internal_mailmap/.mailmap &&
+       git config mailmap.file internal_mailmap/.mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+External Guy (1):
+      initial
+
+Internal Guy (1):
+      second
+
+EOF
+test_expect_success 'mailmap.file override' '
+       echo "External Guy <author@example.com>" >> internal_mailmap/.mailmap &&
+       git config mailmap.file internal_mailmap/.mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'mailmap.file non-existant' '
+       rm internal_mailmap/.mailmap &&
+       rmdir internal_mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+test_expect_success 'No mailmap files, but configured' '
+       rm .mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+# Extended mailmap configurations should give us the following output for shortlog
+cat >expect <<\EOF
+A U Thor <author@example.com> (1):
+      initial
+
+CTO <cto@company.xx> (1):
+      seventh
+
+Other Author <other@author.xx> (2):
+      third
+      fourth
+
+Santa Claus <santa.claus@northpole.xx> (2):
+      fifth
+      sixth
+
+Some Dude <some@dude.xx> (1):
+      second
+
+EOF
+
+test_expect_success 'Shortlog output (complex mapping)' '
+       echo three >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "nick2 <bugs@company.xx>" -m third &&
+
+       echo four >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "nick2 <nick2@company.xx>" -m fourth &&
+
+       echo five >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "santa <me@company.xx>" -m fifth &&
+
+       echo six >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "claus <me@company.xx>" -m sixth &&
+
+       echo seven >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "CTO <cto@coompany.xx>" -m seventh &&
+
+       mkdir internal_mailmap &&
+       echo "Committed <committer@example.com>" > internal_mailmap/.mailmap &&
+       echo "<cto@company.xx>                       <cto@coompany.xx>" >> internal_mailmap/.mailmap &&
+       echo "Some Dude <some@dude.xx>         nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Other Author <other@author.xx>   nick2 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Other Author <other@author.xx>         <nick2@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+
+       git shortlog -e HEAD >actual &&
+       test_cmp expect actual
+
+'
+
+# git log with --pretty format which uses the name and email mailmap placemarkers
+cat >expect <<\EOF
+Author CTO <cto@coompany.xx> maps to CTO <cto@company.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author A U Thor <author@example.com> maps to A U Thor <author@example.com>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+EOF
+
+test_expect_success 'Log output (complex mapping)' '
+       git log --pretty=format:"Author %an <%ae> maps to %aN <%aE>%nCommitter %cn <%ce> maps to %cN <%cE>%n" >actual &&
+       test_cmp expect actual
+'
+
+# git blame
+cat >expect <<\EOF
+^3a2fdcb (A U Thor     2005-04-07 15:13:13 -0700 1) one
+7de6f99b (Some Dude    2005-04-07 15:13:13 -0700 2) two
+5815879d (Other Author 2005-04-07 15:14:13 -0700 3) three
+ff859d96 (Other Author 2005-04-07 15:15:13 -0700 4) four
+5ab6d4fa (Santa Claus  2005-04-07 15:16:13 -0700 5) five
+38a42d8b (Santa Claus  2005-04-07 15:17:13 -0700 6) six
+8ddc0386 (CTO          2005-04-07 15:18:13 -0700 7) seven
+EOF
+
+test_expect_success 'Blame output (complex mapping)' '
+       git blame one >actual &&
+       test_cmp expect actual
+'
+
+test_done
index c942c8be85339157e22f755d8fc94e64efaee4dd..b7e362834bd743b61c27a4fd2118a83b14828489 100755 (executable)
@@ -86,6 +86,10 @@ test_expect_success \
     'git archive vs. the same in a bare repo' \
     'test_cmp b.tar b3.tar'
 
+test_expect_success 'git archive with --output' \
+    'git archive --output=b4.tar HEAD &&
+    test_cmp b.tar b4.tar'
+
 test_expect_success \
     'validate file modification time' \
     'mkdir extract &&
@@ -172,6 +176,10 @@ test_expect_success \
     'git archive --format=zip vs. the same in a bare repo' \
     'test_cmp d.zip d1.zip'
 
+test_expect_success 'git archive --format=zip with --output' \
+    'git archive --format=zip --output=d2.zip HEAD &&
+    test_cmp d.zip d2.zip'
+
 $UNZIP -v >/dev/null 2>&1
 if [ $? -eq 127 ]; then
        echo "Skipping ZIP tests, because unzip was not found"
index 04522857abb716b8866e0f5153ec33b3ac780536..ccfc64c6eef7e0aba7bd8a8496427470e9020309 100755 (executable)
@@ -180,6 +180,23 @@ test_expect_success \
 
 unset GIT_OBJECT_DIRECTORY
 
+test_expect_success 'survive missing objects/pack directory' '
+       (
+               rm -fr missing-pack &&
+               mkdir missing-pack &&
+               cd missing-pack &&
+               git init &&
+               GOP=.git/objects/pack
+               rm -fr $GOP &&
+               git index-pack --stdin --keep=test <../test-3-${packname_3}.pack &&
+               test -f $GOP/pack-${packname_3}.pack &&
+               test_cmp $GOP/pack-${packname_3}.pack ../test-3-${packname_3}.pack &&
+               test -f $GOP/pack-${packname_3}.idx &&
+               test_cmp $GOP/pack-${packname_3}.idx ../test-3-${packname_3}.idx &&
+               test -f $GOP/pack-${packname_3}.keep
+       )
+'
+
 test_expect_success \
     'verify pack' \
     'git verify-pack   test-1-${packname_1}.idx \
index 771c0a06a4bd01cf40628ffae379b5d992802ef6..55ed7c7935c007573761842cea5978a784c865b3 100755 (executable)
@@ -112,4 +112,42 @@ test_expect_success 'prune: do not prune heads listed as an argument' '
 
 '
 
+test_expect_success 'gc --no-prune' '
+
+       before=$(git count-objects | sed "s/ .*//") &&
+       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE &&
+       test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+       git config gc.pruneExpire 2.days.ago &&
+       git gc --no-prune &&
+       test 1 = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc respects gc.pruneExpire' '
+
+       git config gc.pruneExpire 5002.days.ago &&
+       git gc &&
+       test -f $BLOB_FILE &&
+       git config gc.pruneExpire 5000.days.ago &&
+       git gc &&
+       test ! -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc --prune=<date>' '
+
+       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+       test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+       git gc --prune=5002.days.ago &&
+       test -f $BLOB_FILE &&
+       git gc --prune=5000.days.ago &&
+       test ! -f $BLOB_FILE
+
+'
+
 test_done
diff --git a/t/t5307-pack-missing-commit.sh b/t/t5307-pack-missing-commit.sh
new file mode 100755 (executable)
index 0000000..ae52a18
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='pack should notice missing commit objects'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       for i in 1 2 3 4 5
+       do
+               echo "$i" >"file$i" &&
+               git add "file$i" &&
+               test_tick &&
+               git commit -m "$i" &&
+               git tag "tag$i"
+       done &&
+       obj=$(git rev-parse --verify tag3) &&
+       fanout=$(expr "$obj" : "\(..\)") &&
+       remainder=$(expr "$obj" : "..\(.*\)") &&
+       rm -f ".git/objects/$fanout/$remainder"
+'
+
+test_expect_success 'check corruption' '
+       test_must_fail git fsck
+'
+
+test_expect_success 'rev-list notices corruption (1)' '
+       test_must_fail git rev-list HEAD
+'
+
+test_expect_success 'rev-list notices corruption (2)' '
+       test_must_fail git rev-list --objects HEAD
+'
+
+test_expect_success 'pack-objects notices corruption' '
+       echo HEAD |
+       test_must_fail git pack-objects --revs pack
+'
+
+test_done
index b21317d68527988d0c2939e7173098b08bfbb64f..f2d5581b12f7d70c9f346da75dced81e12bd4c7f 100755 (executable)
@@ -32,9 +32,7 @@ test_expect_success setup '
        done &&
        git update-ref HEAD "$commit" &&
        git clone ./. victim &&
-       cd victim &&
-       git log &&
-       cd .. &&
+       ( cd victim && git log ) &&
        git update-ref HEAD "$zero" &&
        parent=$zero &&
        i=0 &&
@@ -59,88 +57,84 @@ test_expect_success 'pack the source repository' '
 '
 
 test_expect_success 'pack the destination repository' '
+    (
        cd victim &&
        git repack -a -d &&
-       git prune &&
-       cd ..
+       git prune
+    )
 '
 
-test_expect_success \
-        'pushing rewound head should not barf but require --force' '
-       # should not fail but refuse to update.
-       if git send-pack ./victim/.git/ master
-       then
-               # now it should fail with Pasky patch
-               echo >&2 Gaah, it should have failed.
-               false
-       else
-               echo >&2 Thanks, it correctly failed.
-               true
-       fi &&
-       if cmp victim/.git/refs/heads/master .git/refs/heads/master
-       then
-               # should have been left as it was!
-               false
-       else
-               true
-       fi &&
+test_expect_success 'refuse pushing rewound head without --force' '
+       pushed_head=$(git rev-parse --verify master) &&
+       victim_orig=$(cd victim && git rev-parse --verify master) &&
+       test_must_fail git send-pack ./victim master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_head" = "$victim_orig" &&
        # this should update
-       git send-pack --force ./victim/.git/ master &&
-       cmp victim/.git/refs/heads/master .git/refs/heads/master
+       git send-pack --force ./victim master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_head" = "$pushed_head"
 '
 
 test_expect_success \
         'push can be used to delete a ref' '
-       cd victim &&
-       git branch extra master &&
-       cd .. &&
-       test -f victim/.git/refs/heads/extra &&
-       git send-pack ./victim/.git/ :extra master &&
-       ! test -f victim/.git/refs/heads/extra
+       ( cd victim && git branch extra master ) &&
+       git send-pack ./victim :extra master &&
+       ( cd victim &&
+         test_must_fail git rev-parse --verify extra )
 '
 
-unset GIT_CONFIG
-HOME=`pwd`/no-such-directory
-export HOME ;# this way we force the victim/.git/config to be used.
-
-test_expect_success \
-       'pushing a delete should be denied with denyDeletes' '
-       cd victim &&
-       git config receive.denyDeletes true &&
-       git branch extra master &&
-       cd .. &&
-       test -f victim/.git/refs/heads/extra &&
-       test_must_fail git send-pack ./victim/.git/ :extra master
+test_expect_success 'refuse deleting push with denyDeletes' '
+       (
+           cd victim &&
+           ( git branch -D extra || : ) &&
+           git config receive.denyDeletes true &&
+           git branch extra master
+       ) &&
+       test_must_fail git send-pack ./victim :extra master
 '
-rm -f victim/.git/refs/heads/extra
 
-test_expect_success \
-        'pushing with --force should be denied with denyNonFastforwards' '
-       cd victim &&
-       git config receive.denyNonFastforwards true &&
-       cd .. &&
-       git update-ref refs/heads/master master^ || return 1
-       git send-pack --force ./victim/.git/ master && return 1
-       ! test_cmp .git/refs/heads/master victim/.git/refs/heads/master
+test_expect_success 'denyNonFastforwards trumps --force' '
+       (
+           cd victim &&
+           ( git branch -D extra || : ) &&
+           git config receive.denyNonFastforwards true
+       ) &&
+       victim_orig=$(cd victim && git rev-parse --verify master) &&
+       test_must_fail git send-pack --force ./victim master^:master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_orig" = "$victim_head"
 '
 
-test_expect_success \
-       'pushing does not include non-head refs' '
-       mkdir parent && cd parent &&
-       git init && touch file && git add file && git commit -m add &&
-       cd .. &&
-       git clone parent child && cd child && git push --all &&
-       cd ../parent &&
-       git branch -a >branches && ! grep origin/master branches
+test_expect_success 'push --all excludes remote tracking hierarchy' '
+       mkdir parent &&
+       (
+           cd parent &&
+           git init && : >file && git add file && git commit -m add
+       ) &&
+       git clone parent child &&
+       (
+           cd child && git push --all
+       ) &&
+       (
+           cd parent &&
+           test -z "$(git for-each-ref refs/remotes/origin)"
+       )
 '
 
 rewound_push_setup() {
        rm -rf parent child &&
-       mkdir parent && cd parent &&
-       git init && echo one >file && git add file && git commit -m one &&
-       echo two >file && git commit -a -m two &&
-       cd .. &&
-       git clone parent child && cd child && git reset --hard HEAD^
+       mkdir parent &&
+       (
+           cd parent &&
+           git init &&
+           echo one >file && git add file && git commit -m one &&
+           echo two >file && git commit -a -m two
+       ) &&
+       git clone parent child &&
+       (
+           cd child && git reset --hard HEAD^
+       )
 }
 
 rewound_push_succeeded() {
@@ -156,30 +150,57 @@ rewound_push_failed() {
        fi
 }
 
-test_expect_success \
-       'pushing explicit refspecs respects forcing' '
+test_expect_success 'pushing explicit refspecs respects forcing' '
        rewound_push_setup &&
-       if git send-pack ../parent/.git refs/heads/master:refs/heads/master
-       then
-               false
-       else
-               true
-       fi && rewound_push_failed &&
-       git send-pack ../parent/.git +refs/heads/master:refs/heads/master &&
-       rewound_push_succeeded
+       parent_orig=$(cd parent && git rev-parse --verify master) &&
+       (
+           cd child &&
+           test_must_fail git send-pack ../parent \
+               refs/heads/master:refs/heads/master
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_orig" = "$parent_head" &&
+       (
+           cd child &&
+           git send-pack ../parent \
+               +refs/heads/master:refs/heads/master
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       child_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_head" = "$child_head"
 '
 
-test_expect_success \
-       'pushing wildcard refspecs respects forcing' '
+test_expect_success 'pushing wildcard refspecs respects forcing' '
        rewound_push_setup &&
-       if git send-pack ../parent/.git refs/heads/*:refs/heads/*
-       then
-               false
-       else
-               true
-       fi && rewound_push_failed &&
-       git send-pack ../parent/.git +refs/heads/*:refs/heads/* &&
-       rewound_push_succeeded
+       parent_orig=$(cd parent && git rev-parse --verify master) &&
+       (
+           cd child &&
+           test_must_fail git send-pack ../parent \
+               "refs/heads/*:refs/heads/*"
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_orig" = "$parent_head" &&
+       (
+           cd child &&
+           git send-pack ../parent \
+               "+refs/heads/*:refs/heads/*"
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       child_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_head" = "$child_head"
+'
+
+test_expect_success 'warn pushing to delete current branch' '
+       rewound_push_setup &&
+       (
+           cd child &&
+           git send-pack ../parent :refs/heads/master 2>errs
+       ) &&
+       grep "warning: to refuse deleting" child/errs &&
+       (
+               cd parent &&
+               test_must_fail git rev-parse --verify master
+       )
 '
 
 test_done
index 1f59960d90c31f02768666c86654a97e1fad9305..eb637184a00007c61e6d92f7b5546eed6ec5a0ae 100755 (executable)
@@ -107,6 +107,32 @@ test_expect_success 'remove remote' '
 )
 '
 
+test_expect_success 'remove remote protects non-remote branches' '
+(
+       cd test &&
+       (cat >expect1 <<EOF
+Note: A non-remote branch was not removed; to delete it, use:
+  git branch -d master
+EOF
+    cat >expect2 <<EOF
+Note: Non-remote branches were not removed; to delete them, use:
+  git branch -d foobranch
+  git branch -d master
+EOF
+) &&
+       git tag footag
+       git config --add remote.oops.fetch "+refs/*:refs/*" &&
+       git remote rm oops 2>actual1 &&
+       git branch foobranch &&
+       git config --add remote.oops.fetch "+refs/*:refs/*" &&
+       git remote rm oops 2>actual2 &&
+       git branch -d foobranch &&
+       git tag -d footag &&
+       test_cmp expect1 actual1 &&
+       test_cmp expect2 actual2
+)
+'
+
 cat > test/expect << EOF
 * remote origin
   URL: $(pwd)/one
@@ -376,4 +402,31 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' '
         test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin")
 '
 
+test_expect_success 'remote prune to cause a dangling symref' '
+       git clone one seven &&
+       (
+               cd one &&
+               git checkout side2 &&
+               git branch -D master
+       ) &&
+       (
+               cd seven &&
+               git remote prune origin
+       ) 2>err &&
+       grep "has become dangling" err &&
+
+       : And the dangling symref will not cause other annoying errors
+       (
+               cd seven &&
+               git branch -a
+       ) 2>err &&
+       ! grep "points nowhere" err
+       (
+               cd seven &&
+               test_must_fail git branch nomore origin
+       ) 2>err &&
+       grep "dangling symref" err
+'
+
 test_done
+
index 4426df9226535c55eacff80217c4ab70f77639b6..89649e7a9b2dc138bb9618d4e5770e76f62dc7e0 100755 (executable)
@@ -492,7 +492,7 @@ test_expect_success 'warn on push to HEAD of non-bare repository' '
                git checkout master &&
                git config receive.denyCurrentBranch warn) &&
        git push testrepo master 2>stderr &&
-       grep "warning.*this may cause confusion" stderr
+       grep "warning: updating the current branch" stderr
 '
 
 test_expect_success 'deny push to HEAD of non-bare repository' '
@@ -510,7 +510,7 @@ test_expect_success 'allow push to HEAD of bare repository (bare)' '
                git config receive.denyCurrentBranch true &&
                git config core.bare true) &&
        git push testrepo master 2>stderr &&
-       ! grep "warning.*this may cause confusion" stderr
+       ! grep "warning: updating the current branch" stderr
 '
 
 test_expect_success 'allow push to HEAD of non-bare repository (config)' '
@@ -520,7 +520,7 @@ test_expect_success 'allow push to HEAD of non-bare repository (config)' '
                git config receive.denyCurrentBranch false
        ) &&
        git push testrepo master 2>stderr &&
-       ! grep "warning.*this may cause confusion" stderr
+       ! grep "warning: updating the current branch" stderr
 '
 
 test_expect_success 'fetch with branches' '
index c236b5e83beed8997e8f6c3a50f0bb74f73f3f33..10e5fd0d5a5c38eaa102d88e607f2585e1157526 100755 (executable)
@@ -94,6 +94,18 @@ test_expect_success 'MKCOL sends directory names with trailing slashes' '
 
 '
 
+x1="[0-9a-f]"
+x2="$x1$x1"
+x5="$x1$x1$x1$x1$x1"
+x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
+x40="$x38$x2"
+
+test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
+       sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log |
+       grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] "
+
+'
+
 stop_httpd
 
 test_done
index fe287d31fb98237b11ccc1d1209001dec3fb0a8e..44793f2eee0e0c14802ecedfe0637cc6855f51b6 100755 (executable)
@@ -144,4 +144,19 @@ test_expect_success 'clone to an existing path' '
        test_must_fail git clone src target-5
 '
 
+test_expect_success 'clone a void' '
+       mkdir src-0 &&
+       (
+               cd src-0 && git init
+       ) &&
+       git clone src-0 target-6 &&
+       (
+               cd src-0 && test_commit A
+       ) &&
+       git clone src-0 target-7 &&
+       # There is no reason to insist they are bit-for-bit
+       # identical, but this test should suffice for now.
+       test_cmp target-6/.git/config target-7/.git/config
+'
+
 test_done
index dd7eac84ea191fb797075eca3706498edad68b32..052a6c90f5a184ddc82f2db1a2907a1b1104166c 100755 (executable)
@@ -224,6 +224,31 @@ test_expect_success 'bisect skip: cannot tell between 2 commits' '
        fi
 '
 
+# $HASH1 is good, $HASH4 is both skipped and bad, we skip $HASH3
+# and $HASH2 is good,
+# so we should not be able to tell the first bad commit
+# among $HASH3 and $HASH4
+test_expect_success 'bisect skip: with commit both bad and skipped' '
+       git bisect start &&
+       git bisect skip &&
+       git bisect bad &&
+       git bisect good $HASH1 &&
+       git bisect skip &&
+       if git bisect good > my_bisect_log.txt
+       then
+               echo Oops, should have failed.
+               false
+       else
+               test $? -eq 2 &&
+               grep "first bad commit could be any of" my_bisect_log.txt &&
+               ! grep $HASH1 my_bisect_log.txt &&
+               ! grep $HASH2 my_bisect_log.txt &&
+               grep $HASH3 my_bisect_log.txt &&
+               grep $HASH4 my_bisect_log.txt &&
+               git bisect reset
+       fi
+'
+
 # We want to automatically find the commit that
 # introduced "Another" into hello.
 test_expect_success \
index ef2e78f9df951cfacf5914dfff2a218d2e6f89f7..8fb3a56838dd476b9b0923f835ce70bd95499f2b 100755 (executable)
@@ -55,9 +55,17 @@ test_expect_success \
      git mv -k untracked1 untracked2 path0 &&
      test -f untracked1 &&
      test -f untracked2 &&
-     test ! -f path0/untracked1
+     test ! -f path0/untracked1 &&
      test ! -f path0/untracked2'
 
+test_expect_success \
+    'checking -f on untracked file with existing target' \
+    'touch path0/untracked1 &&
+     git mv -f untracked1 path0
+     test ! -f .git/index.lock &&
+     test -f untracked1 &&
+     test -f path0/untracked1'
+
 # clean up the mess in case bad things happen
 rm -f idontexist untracked1 untracked2 \
      path0/idontexist path0/untracked1 path0/untracked2 \
index 8537bf91606282161ab92b6f2f7367c9a3c016fc..329c851685b1c663ee88d45a5d21d452a293fa8e 100755 (executable)
@@ -39,13 +39,31 @@ test_expect_success 'result is really identical' '
 '
 
 test_expect_success 'rewrite bare repository identically' '
-       (git config core.bare true && cd .git && git filter-branch branch)
+       (git config core.bare true && cd .git &&
+        git filter-branch branch > filter-output 2>&1 &&
+       ! fgrep fatal filter-output)
 '
 git config core.bare false
 test_expect_success 'result is really identical' '
        test $H = $(git rev-parse HEAD)
 '
 
+TRASHDIR=$(pwd)
+test_expect_success 'correct GIT_DIR while using -d' '
+       mkdir drepo &&
+       ( cd drepo &&
+       git init &&
+       test_commit drepo &&
+       git filter-branch -d "$TRASHDIR/dfoo" \
+               --index-filter "cp \"$TRASHDIR\"/dfoo/backup-refs \"$TRASHDIR\"" \
+       ) &&
+       grep drepo "$TRASHDIR/backup-refs"
+'
+
+test_expect_success 'Fail if commit filter fails' '
+       test_must_fail git filter-branch -f --commit-filter "exit 1" HEAD
+'
+
 test_expect_success 'rewrite, renaming a specific file' '
        git filter-branch -f --tree-filter "mv d doh || :" HEAD
 '
index 2ec7ac6a510c5b83bc1ee6ce428379beb7a8b5ef..b8cb2df6670a18ebf54fecbb97a48d0c07a06e2b 100755 (executable)
@@ -234,4 +234,17 @@ test_expect_success 'gracefully add submodule with a trailing slash' '
 
 '
 
+test_expect_success 'ls-files gracefully handles trailing slash' '
+
+       test "init" = "$(git ls-files init/)"
+
+'
+
+test_expect_success 'submodule <invalid-path> warns' '
+
+       git submodule no-such-submodule 2> output.err &&
+       grep "^error: .*no-such-submodule" output.err
+
+'
+
 test_done
index 09fa5f115c9fabe1fec60a5597439b2c7f9ded6d..e768c3eb2d48a9af2fc92729a6c20126f67ec866 100755 (executable)
@@ -9,38 +9,81 @@ Testing basic merge tool invocation'
 
 . ./test-lib.sh
 
+# All the mergetool test work by checking out a temporary branch based
+# off 'branch1' and then merging in master and checking the results of
+# running mergetool
+
 test_expect_success 'setup' '
     echo master >file1 &&
-    git add file1 &&
+    mkdir subdir &&
+    echo master sub >subdir/file3 &&
+    git add file1 subdir/file3 &&
     git commit -m "added file1" &&
+
     git checkout -b branch1 master &&
     echo branch1 change >file1 &&
     echo branch1 newfile >file2 &&
-    git add file1 file2 &&
+    echo branch1 sub >subdir/file3 &&
+    git add file1 file2 subdir/file3 &&
     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"
-'
+    echo master new sub >subdir/file3 &&
+    git add file1 file2 subdir/file3 &&
+    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 config mergetool.mytool.trustExitCode true
+'
+
+test_expect_success 'custom mergetool' '
+    git checkout -b test1 branch1 &&
     test_must_fail git merge master >/dev/null 2>&1 &&
-    ( yes "" | git mergetool file1>/dev/null 2>&1 ) &&
-    ( yes "" | git mergetool file2>/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
     test "$(cat file1)" = "master updated" &&
     test "$(cat file2)" = "master new" &&
-       git commit -m "branch1 resolved with mergetool"
+    test "$(cat subdir/file3)" = "master new sub" &&
+    git commit -m "branch1 resolved with mergetool"
 '
 
+test_expect_success 'mergetool crlf' '
+    git config core.autocrlf true &&
+    git checkout -b test2 branch1
+    test_must_fail git merge master >/dev/null 2>&1 &&
+    ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
+    test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" &&
+    test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" &&
+    test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" &&
+    git commit -m "branch1 resolved with mergetool - autocrlf" &&
+    git config core.autocrlf false &&
+    git reset --hard
+'
+
+test_expect_success 'mergetool in subdir' '
+    git checkout -b test3 branch1
+    cd subdir && (
+    test_must_fail git merge master >/dev/null 2>&1 &&
+    ( yes "" | git mergetool file3 >/dev/null 2>&1 ) &&
+    test "$(cat file3)" = "master new sub" )
+'
+
+# We can't merge files from parent directories when running mergetool
+# from a subdir. Is this a bug?
+#
+#test_expect_failure 'mergetool in subdir' '
+#    cd subdir && (
+#    ( 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 - subdir" )
+#'
+
 test_done
index 704a4f8574eaeaa7256d901b09f36810b37cec57..8f35e294aa885e7afbb704186c9afe9467172570 100755 (executable)
@@ -83,8 +83,28 @@ EOF
 '
 
 test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x'
+test_expect_success 'enable broken symlink workaround' \
+  '(cd x && git config svn.brokenSymlinkWorkaround true)'
 test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar'
 test_expect_success 'get "bar" => symlink fix from svn' \
                '(cd x && git svn rebase)'
 test_expect_success '"bar" becomes a symlink' 'test -L x/bar'
+
+
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y'
+test_expect_success 'disable broken symlink workaround' \
+  '(cd y && git config svn.brokenSymlinkWorkaround false)'
+test_expect_success '"bar" is an empty file' 'test -f y/bar && ! test -s y/bar'
+test_expect_success 'get "bar" => symlink fix from svn' \
+               '(cd y && git svn rebase)'
+test_expect_success '"bar" does not become a symlink' '! test -L y/bar'
+
+# svn.brokenSymlinkWorkaround is unset
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" z'
+test_expect_success '"bar" is an empty file' 'test -f z/bar && ! test -s z/bar'
+test_expect_success 'get "bar" => symlink fix from svn' \
+               '(cd z && git svn rebase)'
+test_expect_success '"bar" does not become a symlink' '! test -L z/bar'
+
+
 test_done
diff --git a/t/t9135-git-svn-moved-branch-empty-file.sh b/t/t9135-git-svn-moved-branch-empty-file.sh
new file mode 100755 (executable)
index 0000000..03705fa
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test_description='test moved svn branch with missing empty files'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile'  '
+       svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9135/svn.dump"
+       '
+
+test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x'
+
+test_expect_success 'test that b1 exists and is empty' '
+       (cd x && test -f b1 && ! test -s b1)
+       '
+
+test_done
diff --git a/t/t9135/svn.dump b/t/t9135/svn.dump
new file mode 100644 (file)
index 0000000..b51c0cc
--- /dev/null
@@ -0,0 +1,192 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1f80e919-e9e3-4d80-a3ae-d9f21095e27b
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-02-10T19:23:16.424027Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 20
+init standard layout
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:17.195072Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 18
+branch-b off trunk
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:19.160095Z
+PROPS-END
+
+Node-path: branches/branch-b
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 3
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 17
+add empty file b1
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:20.194568Z
+PROPS-END
+
+Node-path: branches/branch-b/b1
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 8
+branch-c
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.169100Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 23
+oops, wrong branchpoint
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.253557Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-action: delete
+
+
+Revision-number: 6
+Prop-content-length: 127
+Content-length: 127
+
+K 7
+svn:log
+V 24
+branch-c off of branch-b
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.314659Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch-b
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
diff --git a/t/t9136-git-svn-recreated-branch-empty-file.sh b/t/t9136-git-svn-recreated-branch-empty-file.sh
new file mode 100755 (executable)
index 0000000..733d16e
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+test_description='test recreated svn branch with empty files'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile'  '
+       svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9136/svn.dump"
+       '
+
+test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x'
+
+test_done
diff --git a/t/t9136/svn.dump b/t/t9136/svn.dump
new file mode 100644 (file)
index 0000000..6b1ce0b
--- /dev/null
@@ -0,0 +1,192 @@
+SVN-fs-dump-format-version: 2
+
+UUID: eecae021-8f16-48da-969d-79beb8ae6ea5
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-02-22T00:50:56.292890Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 4
+init
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:57.192384Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 3
+1.0
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.124724Z
+PROPS-END
+
+Node-path: tags/1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 9
+1.0.1-bad
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.151727Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: tags/1.0
+
+
+Revision-number: 4
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 9
+Wrong tag
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.167427Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 10
+1.0-branch
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.184498Z
+PROPS-END
+
+Node-path: branches/1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: tags/1.0
+
+
+Revision-number: 6
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 10
+1.0.1-good
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.200695Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/1.0
+
+
index 99857210555288cf5ca8f4fa4d1e94bfe557fbd2..86c376088ccd04d0b0cbb14424eef7a9b89b45d3 100755 (executable)
@@ -185,8 +185,8 @@ test_expect_success 'submodule fast-export | fast-import' '
 
 '
 
-export GIT_AUTHOR_NAME='A U Thor'
-export GIT_COMMITTER_NAME='C O Mitter'
+GIT_AUTHOR_NAME='A U Thor'; export GIT_AUTHOR_NAME
+GIT_COMMITTER_NAME='C O Mitter'; export GIT_COMMITTER_NAME
 
 test_expect_success 'setup copies' '
 
index 43cd6eecbac70f1ab63f19bf972f17c1f8188d5e..6ed10d0933daa493e7a32296b38e9a32a23a725a 100755 (executable)
@@ -43,9 +43,11 @@ gitweb_run () {
        GATEWAY_INTERFACE="CGI/1.1"
        HTTP_ACCEPT="*/*"
        REQUEST_METHOD="GET"
+       SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl"
        QUERY_STRING=""$1""
        PATH_INFO=""$2""
-       export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD QUERY_STRING PATH_INFO
+       export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \
+               SCRIPT_NAME QUERY_STRING PATH_INFO
 
        GITWEB_CONFIG=$(pwd)/gitweb_config.perl
        export GITWEB_CONFIG
@@ -54,7 +56,7 @@ gitweb_run () {
        # written to web server logs, so we are not interested in that:
        # we are interested only in properly formatted errors/warnings
        rm -f gitweb.log &&
-       perl -- "$TEST_DIRECTORY/../gitweb/gitweb.perl" \
+       perl -- "$SCRIPT_NAME" \
                >/dev/null 2>gitweb.log &&
        if grep "^[[]" gitweb.log >/dev/null 2>&1; then false; else true; fi
 
@@ -659,6 +661,11 @@ cat >>gitweb_config.perl <<EOF
 \$feature{'snapshot'}{'override'} = 1;
 EOF
 
+test_expect_success \
+       'config override: tree view, features not overridden in repo config' \
+       'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
 test_expect_success \
        'config override: tree view, features disabled in repo config' \
        'git config gitweb.blame no &&
@@ -667,12 +674,23 @@ test_expect_success \
 test_debug 'cat gitweb.log'
 
 test_expect_success \
-       'config override: tree view, features enabled in repo config' \
+       'config override: tree view, features enabled in repo config (1)' \
        'git config gitweb.blame yes &&
         git config gitweb.snapshot "zip,tgz, tbz2" &&
         gitweb_run "p=.git;a=tree"'
 test_debug 'cat gitweb.log'
 
+cat >.git/config <<\EOF
+# testing noval and alternate separator
+[gitweb]
+       blame
+       snapshot = zip tgz
+EOF
+test_expect_success \
+       'config override: tree view, features enabled in repo config (2)' \
+       'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
 # ----------------------------------------------------------------------
 # non-ASCII in README.html
 
index 20214468ae56500e15977556906e62e1f62d4701..7a847ecbde018f36911ee6a1074ba169af1f350d 100644 (file)
@@ -98,7 +98,7 @@ do
        -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
                immediate=t; shift ;;
        -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
-               export GIT_TEST_LONG=t; shift ;;
+               GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;;
        -h|--h|--he|--hel|--help)
                help=t; shift ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
index 93c605594fc06683088b934273873165215ccbb5..a3f68ae3b4c2dbac2f5404a73eda3419532110cf 100755 (executable)
@@ -43,10 +43,12 @@ allowdeletetag=$(git config --bool hooks.allowdeletetag)
 
 # check for no description
 projectdesc=$(sed -e '1q' "$GIT_DIR/description")
-if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb." ]; then
+case "$projectdesc" in
+"Unnamed repository"* | "")
        echo "*** Project description file hasn't been set" >&2
        exit 1
-fi
+       ;;
+esac
 
 # --- Check types
 # if $newrev is 0000...0000, it's a commit to delete a ref.
index c6f25e80b8bcf0a21db2bea368b9e444c19bc0bf..498b267a8c7812490d6479839c5577eaaec79d62 100644 (file)
@@ -1 +1 @@
-Unnamed repository; edit this file to name it for gitweb.
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/test-dump-cache-tree.c b/test-dump-cache-tree.c
new file mode 100644 (file)
index 0000000..1f73f1e
--- /dev/null
@@ -0,0 +1,64 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+
+static void dump_one(struct cache_tree *it, const char *pfx, const char *x)
+{
+       if (it->entry_count < 0)
+               printf("%-40s %s%s (%d subtrees)\n",
+                      "invalid", x, pfx, it->subtree_nr);
+       else
+               printf("%s %s%s (%d entries, %d subtrees)\n",
+                      sha1_to_hex(it->sha1), x, pfx,
+                      it->entry_count, it->subtree_nr);
+}
+
+static int dump_cache_tree(struct cache_tree *it,
+                          struct cache_tree *ref,
+                          const char *pfx)
+{
+       int i;
+       int errs = 0;
+
+       if (!it || !ref)
+               /* missing in either */
+               return 0;
+
+       if (it->entry_count < 0) {
+               dump_one(it, pfx, "");
+               dump_one(ref, pfx, "#(ref) ");
+               if (it->subtree_nr != ref->subtree_nr)
+                       errs = 1;
+       }
+       else {
+               dump_one(it, pfx, "");
+               if (hashcmp(it->sha1, ref->sha1) ||
+                   ref->entry_count != it->entry_count ||
+                   ref->subtree_nr != it->subtree_nr) {
+                       dump_one(ref, pfx, "#(ref) ");
+                       errs = 1;
+               }
+       }
+
+       for (i = 0; i < it->subtree_nr; i++) {
+               char path[PATH_MAX];
+               struct cache_tree_sub *down = it->down[i];
+               struct cache_tree_sub *rdwn;
+
+               rdwn = cache_tree_sub(ref, down->name);
+               sprintf(path, "%s%.*s/", pfx, down->namelen, down->name);
+               if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
+                       errs = 1;
+       }
+       return errs;
+}
+
+int main(int ac, char **av)
+{
+       struct cache_tree *another = cache_tree();
+       if (read_cache() < 0)
+               die("unable to read index file");
+       cache_tree_update(another, active_cache, active_nr, 0, 1);
+       return dump_cache_tree(active_cache_tree, another, "");
+}
index 2c0f5a37e8b9051b1db80aa04e9ea763fb8d772b..d261398d6c50aeefa7450b99caae23ee5cdff0d0 100644 (file)
@@ -2,11 +2,13 @@
 
 int main(int argc, char **argv)
 {
-       if (argc == 3 && !strcmp(argv[1], "normalize_absolute_path")) {
+       if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
                char *buf = xmalloc(PATH_MAX + 1);
-               int rv = normalize_absolute_path(buf, argv[2]);
-               assert(strlen(buf) == rv);
+               int rv = normalize_path_copy(buf, argv[2]);
+               if (rv)
+                       buf = "++failed++";
                puts(buf);
+               return 0;
        }
 
        if (argc >= 2 && !strcmp(argv[1], "make_absolute_path")) {
@@ -15,12 +17,22 @@ int main(int argc, char **argv)
                        argc--;
                        argv++;
                }
+               return 0;
        }
 
        if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
                int len = longest_ancestor_length(argv[2], argv[3]);
                printf("%d\n", len);
+               return 0;
        }
 
-       return 0;
+       if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) {
+               char *prefix = strip_path_suffix(argv[2], argv[3]);
+               printf("%s\n", prefix ? prefix : "(null)");
+               return 0;
+       }
+
+       fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
+               argv[1] ? argv[1] : "(there was none)");
+       return 1;
 }
diff --git a/trace.c b/trace.c
index 4713f9165c54405d51e81c3e90847120ee907e5d..4229ae1231d69aedd9f1aa8350989ddbe2bdb845 100644 (file)
--- a/trace.c
+++ b/trace.c
@@ -50,7 +50,7 @@ static int get_trace_fd(int *need_close)
                return fd;
        }
 
-       fprintf(stderr, "What does '%s' for GIT_TRACE mean?\n", trace);
+       fprintf(stderr, "What does '%s' for GIT_TRACE mean?\n", trace);
        fprintf(stderr, "If you want to trace into a file, "
                "then please set GIT_TRACE to an absolute pathname "
                "(starting with /).\n");
diff --git a/tree.c b/tree.c
index 03e782a9cabc0a12ed5baec0ef59c99f19dbc843..25d2e29fa8b1dfb964b97a10898c77d8fe86ef78 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -110,7 +110,7 @@ int read_tree_recursive(struct tree *tree,
                case 0:
                        continue;
                case READ_TREE_RECURSIVE:
-                       break;;
+                       break;
                default:
                        return -1;
                }
@@ -131,6 +131,34 @@ int read_tree_recursive(struct tree *tree,
                        if (retval)
                                return -1;
                        continue;
+               } else if (S_ISGITLINK(entry.mode)) {
+                       int retval;
+                       struct strbuf path;
+                       unsigned int entrylen;
+                       struct commit *commit;
+
+                       entrylen = tree_entry_len(entry.path, entry.sha1);
+                       strbuf_init(&path, baselen + entrylen + 1);
+                       strbuf_add(&path, base, baselen);
+                       strbuf_add(&path, entry.path, entrylen);
+                       strbuf_addch(&path, '/');
+
+                       commit = lookup_commit(entry.sha1);
+                       if (!commit)
+                               die("Commit %s in submodule path %s not found",
+                                   sha1_to_hex(entry.sha1), path.buf);
+
+                       if (parse_commit(commit))
+                               die("Invalid commit %s in submodule path %s",
+                                   sha1_to_hex(entry.sha1), path.buf);
+
+                       retval = read_tree_recursive(commit->tree,
+                                                    path.buf, path.len,
+                                                    stage, match, fn, context);
+                       strbuf_release(&path);
+                       if (retval)
+                               return -1;
+                       continue;
                }
        }
        return 0;
index 6dd8ad02fbb05ca9124473d29132d31f0e14ede0..75cd2f1a6adf3ea773a6acc3f1e7f122e30676e5 100644 (file)
@@ -29,7 +29,7 @@ int main(int argc, char **argv)
        git_extract_argv0_path(argv[0]);
 
        if (argc != 2)
-               usage("git-unpack-file <sha1>");
+               usage("git unpack-file <sha1>");
        if (get_sha1(argv[1], sha1))
                die("Not a valid object name %s", argv[1]);
 
index 5db6f939551dff618b9e4c7a8bbf5a51bfa3032f..e15ebdc287e9ed7b91677a2bb8cb13dfc1abe639 100644 (file)
@@ -11,7 +11,7 @@
 #include "list-objects.h"
 #include "run-command.h"
 
-static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
+static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>";
 
 /* bits #0..7 in revision.h, #8..10 in commit.c */
 #define THEY_HAVE      (1u << 11)
@@ -645,7 +645,7 @@ int main(int argc, char **argv)
        dir = argv[i];
 
        if (!enter_repo(dir, strict))
-               die("'%s': unable to chdir or not a git archive", dir);
+               die("'%s' does not appear to be a git repository", dir);
        if (is_repository_shallow())
                die("attempt to fetch/clone from a shallow repository");
        if (getenv("GIT_DEBUG_SEND_PACK"))
diff --git a/utf8.c b/utf8.c
index dc3735364f85273c2a119b994ddb405c09dc395c..ddfdc5e2b88d346886f64e39a93ced85cc10a78e 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -246,6 +246,25 @@ int utf8_width(const char **start, size_t *remainder_p)
        return git_wcwidth(ch);
 }
 
+/*
+ * Returns the total number of columns required by a null-terminated
+ * string, assuming that the string is utf8.  Returns strlen() instead
+ * if the string does not look like a valid utf8 string.
+ */
+int utf8_strwidth(const char *string)
+{
+       int width = 0;
+       const char *orig = string;
+
+       while (1) {
+               if (!string)
+                       return strlen(orig);
+               if (!*string)
+                       return width;
+               width += utf8_width(&string, NULL);
+       }
+}
+
 int is_utf8(const char *text)
 {
        while (*text) {
diff --git a/utf8.h b/utf8.h
index 98cce1b038a908bec51ccd2f7e1c1f648cb429a1..2f1b14ff49ef3c73bee6f298ba396b96120c34b7 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -5,6 +5,7 @@ typedef unsigned int ucs_char_t;  /* assuming 32bit int */
 
 ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p);
 int utf8_width(const char **start, size_t *remainder_p);
+int utf8_strwidth(const char *string);
 int is_utf8(const char *text);
 int is_encoding_utf8(const char *name);
 
index 679adab6a0fccef460acaf90b116b8c6cb9bd460..e57630e983488e5c0222dc47a5e32d6efb184762 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -18,7 +18,7 @@ void walker_say(struct walker *walker, const char *fmt, const char *hex)
 static void report_missing(const struct object *obj)
 {
        char missing_hex[41];
-       strcpy(missing_hex, sha1_to_hex(obj->sha1));;
+       strcpy(missing_hex, sha1_to_hex(obj->sha1));
        fprintf(stderr, "Cannot obtain needed %s %s\n",
                obj->type ? typename(obj->type): "object", missing_hex);
        if (!is_null_sha1(current_commit_sha1))
index c85ca52ec63a679a2da7bd8980ad4e2df4e38794..d8efb1365a01104db568633fa8f6aef0c67b4cd1 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -256,3 +256,36 @@ int git_inflate(z_streamp strm, int flush)
        error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
        return ret;
 }
+
+int odb_mkstemp(char *template, size_t limit, const char *pattern)
+{
+       int fd;
+
+       snprintf(template, limit, "%s/%s",
+                get_object_directory(), pattern);
+       fd = mkstemp(template);
+       if (0 <= fd)
+               return fd;
+
+       /* slow path */
+       /* some mkstemp implementations erase template on failure */
+       snprintf(template, limit, "%s/%s",
+                get_object_directory(), pattern);
+       safe_create_leading_directories(template);
+       return xmkstemp(template);
+}
+
+int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+{
+       int fd;
+
+       snprintf(name, namesz, "%s/pack/pack-%s.keep",
+                get_object_directory(), sha1_to_hex(sha1));
+       fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+       if (0 <= fd)
+               return fd;
+
+       /* slow path */
+       safe_create_leading_directories(name);
+       return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+}