Merge branch 'jc/fsck-reflog'
authorJunio C Hamano <junkio@cox.net>
Wed, 27 Dec 2006 07:47:40 +0000 (23:47 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 27 Dec 2006 07:47:40 +0000 (23:47 -0800)
* jc/fsck-reflog:
Add git-reflog to .gitignore
reflog expire: do not punt on tags that point at non commits.
reflog expire: prune commits that are not incomplete
Don't crash during repack of a reflog with pruned commits.
git reflog expire
Move in_merge_bases() to commit.c
reflog: fix warning message.
Teach git-repack to preserve objects referred to by reflog entries.
Protect commits recorded in reflog from pruning.
add for_each_reflog_ent() iterator

49 files changed:
.gitignore
.mailmap [new file with mode: 0644]
Documentation/Makefile
Documentation/diff-options.txt
Documentation/everyday.txt
Documentation/git-add.txt
Documentation/git-apply.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-rm.txt
Documentation/git-show-branch.txt
Documentation/install-doc-quick.sh [new file with mode: 0755]
GIT-VERSION-GEN
Makefile
builtin-add.c
builtin-commit-tree.c
builtin-merge-file.c
builtin-rerere.c [new file with mode: 0644]
builtin-rm.c
builtin-show-branch.c
builtin.h
compat/mmap.c
configure.ac
contrib/emacs/vc-git.el
dir.c
dir.h
git-add--interactive.perl [new file with mode: 0755]
git-checkout.sh
git-compat-util.h
git-merge.sh
git-parse-remote.sh
git-rebase.sh
git-svn.perl
git-tag.sh
git.c
gitweb/gitweb.perl
merge-recursive.c
revision.c
revision.h
t/t3600-rm.sh
t/t4200-rerere.sh [new file with mode: 0755]
t/t6005-rev-list-count.sh [new file with mode: 0755]
t/t6024-recursive-merge.sh
t/t9100-git-svn-basic.sh
templates/hooks--commit-msg
utf8.c [new file with mode: 0644]
utf8.h [new file with mode: 0644]
xdiff-interface.c
xdiff-interface.h
index 255789a8b62e58ff548287ee53f2e7a3db3c854a..60e5002bd55bfc5565cfc5f77084bfc900ae8f4e 100644 (file)
@@ -2,6 +2,7 @@ GIT-CFLAGS
 GIT-VERSION-FILE
 git
 git-add
+git-add--interactive
 git-am
 git-annotate
 git-apply
@@ -10,6 +11,7 @@ git-applypatch
 git-archimport
 git-archive
 git-bisect
+git-blame
 git-branch
 git-cat-file
 git-check-ref-format
@@ -153,4 +155,3 @@ config.status
 config.mak.autogen
 config.mak.append
 configure
-git-blame
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..2c658f4
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,37 @@
+#
+# This list is used by git-shortlog to fix a few botched name translations
+# in the git archive, either because the author's full name was messed up
+# and/or not always written the same way, making contributions from the
+# same person appearing not to be so.
+#
+
+Aneesh Kumar K.V <aneesh.kumar@gmail.com>
+Chris Shoemaker <c.shoemaker@cox.net>
+Daniel Barkalow <barkalow@iabervon.org>
+David Kågedal <davidk@lysator.liu.se>
+Fredrik Kuivinen <freku045@student.liu.se>
+H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
+H. Peter Anvin <hpa@tazenda.sc.orionmulti.com>
+H. Peter Anvin <hpa@trantor.hos.anvin.org>
+Horst H. von Brand <vonbrand@inf.utfsm.cl>
+Joachim Berdal Haga <cjhaga@fys.uio.no>
+Jon Loeliger <jdl@freescale.com>
+Jon Seymour <jon@blackcubes.dyndns.org>
+Karl Hasselström <kha@treskal.com>
+Kent Engstrom <kent@lysator.liu.se>
+Lars Doelle <lars.doelle@on-line.de>
+Lars Doelle <lars.doelle@on-line ! de>
+Lukas Sandström <lukass@etek.chalmers.se>
+Martin Langhoff <martin@catalyst.net.nz>
+Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
+Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk>
+René Scharfe <rene.scharfe@lsrfire.ath.cx>
+Robert Fitzsimons <robfitz@273k.net>
+Santi Béjar <sbejar@gmail.com>
+Sean Estabrooks <seanlkml@sympatico.ca>
+Shawn O. Pearce <spearce@spearce.org>
+Tony Luck <tony.luck@intel.com>
+Ville Skyttä <scop@xemacs.org>
+YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+anonymous <linux@horizon.com>
+anonymous <linux@horizon.net>
index d68bc4a788743412719b3590487ec6dc7efee3e1..93c7024b481157ca44dc40e844fa279616a03b3d 100644 (file)
@@ -32,6 +32,7 @@ man7dir=$(mandir)/man7
 # DESTDIR=
 
 INSTALL?=install
+DOC_REF = origin/man
 
 -include ../config.mak.autogen
 
@@ -112,3 +113,6 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
 
 install-webdoc : html
        sh ./install-webdoc.sh $(WEBDOC_DEST)
+
+quick-install:
+       sh ./install-doc-quick.sh $(DOC_REF) $(mandir)
index f12082e134871c37c92a06c68430de2ef2a0413b..da1cc60e970027bec50c484e1db5717ec90eb150 100644 (file)
@@ -19,7 +19,9 @@
 --numstat::
        Similar to \--stat, but shows number of added and
        deleted lines in decimal notation and pathname without
-       abbreviation, to make it more machine friendly.
+       abbreviation, to make it more machine friendly.  For
+       binary files, outputs two `-` instead of saying
+       `0 0`.
 
 --shortstat::
        Output only the last line of the --stat format containing total
index 9677671892700ae246dcfe36dc806a548fafe2d2..5d17ace721d1ec4c031a4a1f96374559002a93c9 100644 (file)
@@ -47,11 +47,11 @@ $ git repack <3>
 $ git prune <4>
 ------------
 +
-<1> running without "--full" is usually cheap and assures the
+<1> running without `\--full` is usually cheap and assures the
 repository health reasonably well.
 <2> check how many loose objects there are and how much
 disk space is wasted by not repacking.
-<3> without "-a" repacks incrementally.  repacking every 4-5MB
+<3> without `-a` repacks incrementally.  repacking every 4-5MB
 of loose objects accumulation may be a good rule of thumb.
 <4> after repack, prune removes the duplicate loose objects.
 
@@ -80,8 +80,7 @@ following commands.
   * gitlink:git-checkout[1] and gitlink:git-branch[1] to switch
     branches.
 
-  * gitlink:git-add[1] and gitlink:git-update-index[1] to manage
-    the index file.
+  * gitlink:git-add[1] to manage the index file.
 
   * gitlink:git-diff[1] and gitlink:git-status[1] to see what
     you are in the middle of doing.
@@ -91,8 +90,7 @@ following commands.
   * gitlink:git-reset[1] and gitlink:git-checkout[1] (with
     pathname parameters) to undo changes.
 
-  * gitlink:git-pull[1] with "." as the remote to merge between
-    local branches.
+  * gitlink:git-merge[1] to merge between local branches.
 
   * gitlink:git-rebase[1] to maintain topic branches.
 
@@ -101,7 +99,7 @@ following commands.
 Examples
 ~~~~~~~~
 
-Use a tarball as a starting point for a new repository:
+Use a tarball as a starting point for a new repository.::
 +
 ------------
 $ tar zxf frotz.tar.gz
@@ -123,7 +121,7 @@ $ edit/compile/test
 $ git checkout -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
 $ edit/compile/test
-$ git diff <4>
+$ git diff HEAD <4>
 $ git commit -a -s <5>
 $ edit/compile/test
 $ git reset --soft HEAD^ <6>
@@ -131,15 +129,15 @@ $ edit/compile/test
 $ git diff ORIG_HEAD <7>
 $ git commit -a -c ORIG_HEAD <8>
 $ git checkout master <9>
-$ git pull . alsa-audio <10>
+$ git merge alsa-audio <10>
 $ git log --since='3 days ago' <11>
 $ git log v2.43.. curses/ <12>
 ------------
 +
 <1> create a new topic branch.
-<2> revert your botched changes in "curses/ux_audio_oss.c".
+<2> revert your botched changes in `curses/ux_audio_oss.c`.
 <3> you need to tell git if you added a new file; removal and
-modification will be caught if you do "commit -a" later.
+modification will be caught if you do `git commit -a` later.
 <4> to see what changes you are committing.
 <5> commit everything as you have tested, with your sign-off.
 <6> take the last commit back, keeping what is in the working tree.
@@ -147,11 +145,13 @@ modification will be caught if you do "commit -a" later.
 <8> redo the commit undone in the previous step, using the message
 you originally wrote.
 <9> switch to the master branch.
-<10> merge a topic branch into your master branch
+<10> merge a topic branch into your master branch.  You can also use
+`git pull . alsa-audio`, i.e. pull from the local repository.
 <11> review commit logs; other forms to limit output can be
-combined and include --max-count=10 (show 10 commits), --until='2005-12-10'.
-<12> view only the changes that touch what's in curses/
-directory, since v2.43 tag.
+combined and include `\--max-count=10` (show 10 commits),
+`\--until=2005-12-10`, etc.
+<12> view only the changes that touch what's in `curses/`
+directory, since `v2.43` tag.
 
 
 Individual Developer (Participant)[[Individual Developer (Participant)]]
@@ -193,7 +193,7 @@ $ git fetch --tags <8>
 +
 <1> repeat as needed.
 <2> extract patches from your branch for e-mail submission.
-<3> "pull" fetches from "origin" by default and merges into the
+<3> `git pull` fetches from `origin` by default and merges into the
 current branch.
 <4> immediately after pulling, look at the changes done upstream
 since last time we checked, only in the
@@ -201,37 +201,41 @@ area we are interested in.
 <5> fetch from a specific branch from a specific repository and merge.
 <6> revert the pull.
 <7> garbage collect leftover objects from reverted pull.
-<8> from time to time, obtain official tags from the "origin"
-and store them under .git/refs/tags/.
+<8> from time to time, obtain official tags from the `origin`
+and store them under `.git/refs/tags/`.
 
 
 Push into another repository.::
 +
 ------------
-satellite$ git clone mothership:frotz/.git frotz <1>
+satellite$ git clone mothership:frotz frotz <1>
 satellite$ cd frotz
-satellite$ cat .git/remotes/origin <2>
-URL: mothership:frotz/.git
-Pull: master:origin
-satellite$ echo 'Push: master:satellite' >>.git/remotes/origin <3>
+satellite$ git repo-config --get-regexp '^(remote|branch)\.' <2>
+remote.origin.url mothership:frotz
+remote.origin.fetch refs/heads/*:refs/remotes/origin/*
+branch.master.remote origin
+branch.master.merge refs/heads/master
+satellite$ git repo-config remote.origin.push \
+           master:refs/remotes/satellite/master <3>
 satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
 mothership$ git checkout master
-mothership$ git pull . satellite <5>
+mothership$ git merge satellite/master <5>
 ------------
 +
 <1> mothership machine has a frotz repository under your home
 directory; clone from it to start a repository on the satellite
 machine.
-<2> clone creates this file by default.  It arranges "git pull"
-to fetch and store the master branch head of mothership machine
-to local "origin" branch.
-<3> arrange "git push" to push local "master" branch to
-"satellite" branch of the mothership machine.
-<4> push will stash our work away on "satellite" branch on the
-mothership machine.  You could use this as a back-up method.
+<2> clone sets these configuration variables by default.
+It arranges `git pull` to fetch and store the branches of mothership
+machine to local `remotes/origin/*` tracking branches.
+<3> arrange `git push` to push local `master` branch to
+`remotes/satellite/master` branch of the mothership machine.
+<4> push will stash our work away on `remotes/satellite/master`
+tracking branch on the mothership machine.  You could use this as
+a back-up method.
 <5> on mothership machine, merge the work done on the satellite
 machine into the master branch.
 
@@ -247,7 +251,7 @@ $ git format-patch -k -m --stdout v2.6.14..private2.6.14 |
 +
 <1> create a private branch based on a well known (but somewhat behind)
 tag.
-<2> forward port all changes in private2.6.14 branch to master branch
+<2> forward port all changes in `private2.6.14` branch to `master` branch
 without a formal "merging".
 
 
@@ -284,13 +288,13 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout master
+$ git checkout -b topic/one master
 $ git am -3 -i -s -u ./+to-apply <4>
 $ compile/test
 $ git checkout -b hold/linus && git am -3 -i -s -u ./+hold-linus <5>
 $ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard master <7>
-$ git pull . topic/one topic/two && git pull . hold/linus <8>
+$ git checkout pu && git reset --hard next <7>
+$ git merge topic/one topic/two && git merge hold/linus <8>
 $ git checkout maint
 $ git cherry-pick master~4 <9>
 $ compile/test
@@ -307,29 +311,32 @@ they are.
 that are not quite ready.
 <4> apply them, interactively, with my sign-offs.
 <5> create topic branch as needed and apply, again with my
-sign-offs. 
+sign-offs.
 <6> rebase internal topic branch that has not been merged to the
 master, nor exposed as a part of a stable branch.
-<7> restart "pu" every time from the master.
+<7> restart `pu` every time from the next.
 <8> and bundle topic branches still cooking.
 <9> backport a critical fix.
 <10> create a signed tag.
 <11> make sure I did not accidentally rewind master beyond what I
-already pushed out.  "ko" shorthand points at the repository I have
+already pushed out.  `ko` shorthand points at the repository I have
 at kernel.org, and looks like this:
 +
 ------------
 $ cat .git/remotes/ko
 URL: kernel.org:/pub/scm/git/git.git
 Pull: master:refs/tags/ko-master
+Pull: next:refs/tags/ko-next
 Pull: maint:refs/tags/ko-maint
 Push: master
+Push: next
 Push: +pu
 Push: maint
 ------------
 +
-In the output from "git show-branch", "master" should have
-everything "ko-master" has.
+In the output from `git show-branch`, `master` should have
+everything `ko-master` has, and `next` should have
+everything `ko-next` has.
 
 <12> push out the bleeding edge.
 <13> push the tag out, too.
@@ -406,7 +413,7 @@ $ grep git /etc/shells <2>
 ------------
 +
 <1> log-in shell is set to /usr/bin/git-shell, which does not
-allow anything but "git push" and "git pull".  The users should
+allow anything but `git push` and `git pull`.  The users should
 get an ssh access to the machine.
 <2> in many distributions /etc/shells needs to list what is used
 as the login shell.
index d86c0e7f19ee67556b48bb7f94ab9039a0195ea6..95bea66374a6054ee6823b760dbc73d5b84cd5a0 100644 (file)
@@ -7,7 +7,7 @@ git-add - Add file contents to the changeset to be committed next
 
 SYNOPSIS
 --------
-'git-add' [-n] [-v] [--] <file>...
+'git-add' [-n] [-v] [-f] [--interactive] [--] <file>...
 
 DESCRIPTION
 -----------
@@ -25,8 +25,10 @@ the commit.
 The 'git status' command can be used to obtain a summary of what is included
 for the next commit.
 
-This command only adds non-ignored files, to add ignored files use
-"git update-index --add".
+This command can be used to add ignored files with `-f` (force)
+option, but they have to be
+explicitly and exactly specified from the command line.  File globbing
+and recursive behaviour do not add ignored files.
 
 Please see gitlink:git-commit[1] for alternative ways to add content to a
 commit.
@@ -35,7 +37,11 @@ commit.
 OPTIONS
 -------
 <file>...::
-       Files to add content from.
+       Files to add content from.  Fileglobs (e.g. `*.c`) can
+       be given to add all matching files.  Also a
+       leading directory name (e.g. `dir` to add `dir/file1`
+       and `dir/file2`) can be given to add all files in the
+       directory, recursively.
 
 -n::
         Don't actually add the file(s), just show if they exist.
@@ -43,6 +49,13 @@ OPTIONS
 -v::
         Be verbose.
 
+-f::
+       Allow adding otherwise ignored files.
+
+\--interactive::
+       Add modified contents in the working tree interactively to
+       the index.
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
@@ -67,6 +80,119 @@ git-add git-*.sh::
        (i.e. you are listing the files explicitly), it does not
        consider `subdir/git-foo.sh`.
 
+Interactive mode
+----------------
+When the command enters the interactive mode, it shows the
+output of the 'status' subcommand, and then goes into ints
+interactive command loop.
+
+The command loop shows the list of subcommands available, and
+gives a prompt "What now> ".  In general, when the prompt ends
+with a single '>', you can pick only one of the choices given
+and type return, like this:
+
+------------
+    *** Commands ***
+      1: status       2: update       3: revert       4: add untracked
+      5: patch        6: diff         7: quit         8: help
+    What now> 1
+------------
+
+You also could say "s" or "sta" or "status" above as long as the
+choice is unique.
+
+The main command loop has 6 subcommands (plus help and quit).
+
+status::
+
+   This shows the change between HEAD and index (i.e. what will be
+   committed if you say "git commit"), and between index and
+   working tree files (i.e. what you could stage further before
+   "git commit" using "git-add") for each path.  A sample output
+   looks like this:
++
+------------
+              staged     unstaged path
+     1:       binary      nothing foo.png
+     2:     +403/-35        +1/-1 git-add--interactive.perl
+------------
++
+It shows that foo.png has differences from HEAD (but that is
+binary so line count cannot be shown) and there is no
+difference between indexed copy and the working tree
+version (if the working tree version were also different,
+'binary' would have been shown in place of 'nothing').  The
+other file, git-add--interactive.perl, has 403 lines added
+and 35 lines deleted if you commit what is in the index, but
+working tree file has further modifications (one addition and
+one deletion).
+
+update::
+
+   This shows the status information and gives prompt
+   "Update>>".  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.  You can say '*' to choose
+   everything.
++
+What you chose are then highlighted with '*',
+like this:
++
+------------
+           staged     unstaged path
+  1:       binary      nothing foo.png
+* 2:     +403/-35        +1/-1 git-add--interactive.perl
+------------
++
+To remove selection, prefix the input with `-`
+like this:
++
+------------
+Update>> -2
+------------
++
+After making the selection, answer with an empty line to stage the
+contents of working tree files for selected paths in the index.
+
+revert::
+
+  This has a very similar UI to 'update', and the staged
+  information for selected paths are reverted to that of the
+  HEAD version.  Reverting new paths makes them untracked.
+
+add untracked::
+
+  This has a very similar UI to 'update' and
+  'revert', and lets you add untracked paths to the index.
+
+patch::
+
+  This lets you choose one path out of 'status' like selection.
+  After choosing the path, it presents 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:
+
+       y - add the change from that hunk to index
+       n - do not add the change from that hunk to index
+       a - add the change from that hunk and all the rest to index
+       d - do not the change from that hunk nor any of the rest to index
+       j - do not decide on this hunk now, and view the next
+           undecided hunk
+       J - do not decide on this hunk now, and view the next hunk
+       k - do not decide on this hunk now, and view the previous
+           undecided hunk
+       K - do not decide on this hunk now, and view the previous hunk
++
+After deciding the fate for all hunks, if there is any hunk
+that was chosen, the index is updated with the selected hunks.
+
+diff::
+
+  This lets you review what will be committed (i.e. between
+  HEAD and index).
+
+
 See Also
 --------
 gitlink:git-status[1]
index 2cc32d1c5efa0ba9c69ce59d481ca852045096c0..33b93db508062529fe956b79e0618e67848b19de 100644 (file)
@@ -33,8 +33,9 @@ OPTIONS
 --numstat::
        Similar to \--stat, but shows number of added and
        deleted lines in decimal notation and pathname without
-       abbreviation, to make it more machine friendly.  Turns
-       off "apply".
+       abbreviation, to make it more machine friendly.  For
+       binary files, outputs two `-` instead of saying
+       `0 0`.  Turns off "apply".
 
 --summary::
        Instead of applying the patch, output a condensed
index 4a4ceb62012d7dbd56085a87d62aa4dec928d800..4f424782ebbd90572793a86bce862f3b856f56e0 100644 (file)
@@ -7,7 +7,9 @@ git-reset - Reset current HEAD to the specified state
 
 SYNOPSIS
 --------
-'git-reset' [--mixed | --soft | --hard] [<commit-ish>]
+[verse]
+'git-reset' [--mixed | --soft | --hard] [<commit>]
+'git-reset' [--mixed] <commit> [--] <paths>...
 
 DESCRIPTION
 -----------
@@ -21,6 +23,10 @@ the undo in the history.
 If you want to undo a commit other than the latest on a branch,
 gitlink:git-revert[1] is your friend.
 
+The second form with 'paths' is used to revert selected paths in
+the index from a given commit, without moving HEAD.
+
+
 OPTIONS
 -------
 --mixed::
@@ -37,9 +43,9 @@ OPTIONS
 --hard::
        Matches the working tree and index to that of the tree being
        switched to. Any changes to tracked files in the working tree
-       since <commit-ish> are lost.
+       since <commit> are lost.
 
-<commit-ish>::
+<commit>::
        Commit to make the current HEAD.
 
 Examples
index ec43c0b3a8ca87c2bd0116e1a20061eda3ca3f21..9e0dcf8d3fef2495da4940ce662a46cc8a2bf621 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-rev-list' [ \--max-count=number ]
+            [ \--skip=number ]
             [ \--max-age=timestamp ]
             [ \--min-age=timestamp ]
             [ \--sparse ]
@@ -139,6 +140,10 @@ limiting may be applied.
 
        Limit the number of commits output.
 
+--skip='number'::
+
+       Skip 'number' commits before starting to show the commit output.
+
 --since='date', --after='date'::
 
        Show commits more recent than a specific date.
index 66fc478f5736957d56de10ddc69c5c0fd9aad9c6..3a8f279e1a5b699f23f908540f828a87a3a4d779 100644 (file)
@@ -7,51 +7,54 @@ git-rm - Remove files from the working tree and from the index
 
 SYNOPSIS
 --------
-'git-rm' [-f] [-n] [-v] [--] <file>...
+'git-rm' [-f] [-n] [-r] [--cached] [--] <file>...
 
 DESCRIPTION
 -----------
-A convenience wrapper for git-update-index --remove. For those coming
-from cvs, git-rm provides an operation similar to "cvs rm" or "cvs
-remove".
+Remove files from the working tree and from the index.  The
+files have to be identical to the tip of the branch, and no
+updates to its contents must have been placed in the staging
+area (aka index).
 
 
 OPTIONS
 -------
 <file>...::
-       Files to remove from the index and optionally, from the
-       working tree as well.
+       Files to remove.  Fileglobs (e.g. `*.c`) can be given to
+       remove all matching files.  Also a leading directory name
+       (e.g. `dir` to add `dir/file1` and `dir/file2`) can be
+       given to remove all files in the directory, recursively,
+       but this requires `-r` option to be given for safety.
 
 -f::
-       Remove files from the working tree as well as from the index.
+       Override the up-to-date check.
 
 -n::
         Don't actually remove the file(s), just show if they exist in
         the index.
 
--v::
-        Be verbose.
+-r::
+        Allow recursive removal when a leading directory name is
+        given.
 
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
        for command-line options).
 
+\--cached::
+       This option can be used to tell the command to remove
+       the paths only from the index, leaving working tree
+       files.
+
 
 DISCUSSION
 ----------
 
-The list of <file> given to the command is fed to `git-ls-files`
-command to list files that are registered in the index and
-are not ignored/excluded by `$GIT_DIR/info/exclude` file or
-`.gitignore` file in each directory.  This means two things:
-
-. You can put the name of a directory on the command line, and the
-  command will remove all files in it and its subdirectories (the
-  directories themselves are never removed from the working tree);
-
-. Giving the name of a file that is not in the index does not
-  remove that file.
+The list of <file> given to the command can be exact pathnames,
+file glob patterns, or leading directory name.  The command
+removes only the paths that is known to git.  Giving the name of
+a file that you have not told git about does not remove that file.
 
 
 EXAMPLES
@@ -69,10 +72,10 @@ subdirectories of `Documentation/` directory.
 git-rm -f git-*.sh::
 
        Remove all git-*.sh scripts that are in the index. The files
-       are removed from the index, and (because of the -f option),
-       from the working tree as well. Because this example lets the
-       shell expand the asterisk (i.e. you are listing the files
-       explicitly), it does not remove `subdir/git-foo.sh`.
+       are removed from the index, and from the working
+       tree. Because this example lets the shell expand the
+       asterisk (i.e. you are listing the files explicitly), it
+       does not remove `subdir/git-foo.sh`.
 
 See Also
 --------
index 948ff10e6c83b8842de453e798ff7916ac98d911..912e15bcba54d28d7e4f71ffd3f9da923f2a6385 100644 (file)
@@ -8,9 +8,10 @@ git-show-branch - Show branches and their commits
 SYNOPSIS
 --------
 [verse]
-'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current]
+'git-show-branch' [--all] [--remotes] [--topo-order] [--current]
                [--more=<n> | --list | --independent | --merge-base]
                [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...
+'git-show-branch' --reflog[=<n>] <ref>
 
 DESCRIPTION
 -----------
@@ -37,9 +38,11 @@ OPTIONS
        branches under $GIT_DIR/refs/heads/topic, giving
        `topic/*` would show all of them.
 
---all --heads --tags::
-       Show all refs under $GIT_DIR/refs, $GIT_DIR/refs/heads,
-       and $GIT_DIR/refs/tags, respectively.
+-r|--remotes::
+       Show the remote-tracking branches.
+
+-a|--all::
+       Show both remote-tracking branches and local branches.
 
 --current::
        With this option, the command includes the current
@@ -94,6 +97,10 @@ OPTIONS
        will show the revisions given by "git rev-list {caret}master
        topic1 topic2"
 
+--reflog[=<n>] <ref>::
+       Shows <n> most recent ref-log entries for the given ref.
+
+
 Note that --more, --list, --independent and --merge-base options
 are mutually exclusive.
 
diff --git a/Documentation/install-doc-quick.sh b/Documentation/install-doc-quick.sh
new file mode 100755 (executable)
index 0000000..a640549
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+# This requires a branch named in $head
+# (usually 'man' or 'html', provided by the git.git repository)
+set -e
+head="$1"
+mandir="$2"
+SUBDIRECTORY_OK=t
+USAGE='<refname> <target directory>'
+. git-sh-setup
+export GIT_DIR
+
+test -z "$mandir" && usage
+if ! git-rev-parse --verify "$head^0" >/dev/null; then
+       echo >&2 "head: $head does not exist in the current repository"
+       usage
+fi
+
+GIT_INDEX_FILE=`pwd`/.quick-doc.index
+export GIT_INDEX_FILE
+rm -f "$GIT_INDEX_FILE"
+git-read-tree $head
+git-checkout-index -a -f --prefix="$mandir"/
+
+if test -n "$GZ"; then
+       cd "$mandir"
+       for i in `git-ls-tree -r --name-only $head`
+       do
+               gzip < $i > $i.gz && rm $i
+       done
+fi
+rm -f "$GIT_INDEX_FILE"
index eca1ff2175c9c62ce51fe6606d615f99925e7734..21ff949ea2be33aa3a51becff87f4d97d871459c 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.4.4.GIT
+DEF_VER=v1.4.5-rc0.GIT
 
 LF='
 '
index d4d8590b6e4458e2cd49d75981b1637b4b4a1ce7..52d4a3a86a214a99bd3cf32053539e18f36778a7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -79,9 +79,6 @@ all:
 #
 # Define NO_ICONV if your libc does not properly support iconv.
 #
-# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
-# a missing newline at the end of the file.
-#
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
 # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
@@ -173,8 +170,8 @@ SCRIPT_SH = \
        git-lost-found.sh git-quiltimport.sh
 
 SCRIPT_PERL = \
+       git-add--interactive.perl \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-rerere.perl \
        git-cvsserver.perl \
        git-svnimport.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
@@ -233,7 +230,8 @@ LIB_H = \
        archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \
        diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
-       tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h
+       tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
+       utf8.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -252,7 +250,8 @@ LIB_OBJS = \
        revision.o pager.o tree-walk.o xdiff-interface.o \
        write_or_die.o trace.o list-objects.o grep.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
-       color.o wt-status.o archive-zip.o archive-tar.o
+       color.o wt-status.o archive-zip.o archive-tar.o \
+       utf8.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
@@ -290,6 +289,7 @@ BUILTIN_OBJS = \
        builtin-read-tree.o \
        builtin-reflog.o \
        builtin-repo-config.o \
+       builtin-rerere.o \
        builtin-rev-list.o \
        builtin-rev-parse.o \
        builtin-rm.o \
@@ -550,9 +550,6 @@ else
 endif
 endif
 endif
-ifdef NO_ACCURATE_DIFF
-       BASIC_CFLAGS += -DNO_ACCURATE_DIFF
-endif
 ifdef NO_PERL_MAKEMAKER
        export NO_PERL_MAKEMAKER
 endif
@@ -831,6 +828,8 @@ install: all
 install-doc:
        $(MAKE) -C Documentation install
 
+quick-install-doc:
+       $(MAKE) -C Documentation quick-install
 
 
 
index f306f82b16c3c3c76416610cd4cabf0a307924ed..8ed4a6a9f32b9cd0f71289af2296cacf79864448 100644 (file)
@@ -6,10 +6,11 @@
 #include "cache.h"
 #include "builtin.h"
 #include "dir.h"
+#include "exec_cmd.h"
 #include "cache-tree.h"
 
 static const char builtin_add_usage[] =
-"git-add [-n] [-v] <filepattern>...";
+"git-add [-n] [-v] [-f] [--interactive] [--] <filepattern>...";
 
 static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
 {
@@ -25,7 +26,14 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
        i = dir->nr;
        while (--i >= 0) {
                struct dir_entry *entry = *src++;
-               if (!match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) {
+               int how = match_pathspec(pathspec, entry->name, entry->len,
+                                        prefix, seen);
+               /*
+                * ignored entries can be added with exact match,
+                * but not with glob nor recursive.
+                */
+               if (!how ||
+                   (entry->ignored_entry && how != MATCHED_EXACTLY)) {
                        free(entry);
                        continue;
                }
@@ -54,6 +62,8 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec)
 
        /* Set up the default git porcelain excludes */
        memset(dir, 0, sizeof(*dir));
+       if (pathspec)
+               dir->show_both = 1;
        dir->exclude_per_dir = ".gitignore";
        path = git_path("info/exclude");
        if (!access(path, R_OK))
@@ -81,12 +91,29 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec)
 
 static struct lock_file lock_file;
 
+static const char ignore_warning[] =
+"The following paths are ignored by one of your .gitignore files:\n";
+
 int cmd_add(int argc, const char **argv, const char *prefix)
 {
        int i, newfd;
-       int verbose = 0, show_only = 0;
+       int verbose = 0, show_only = 0, ignored_too = 0;
        const char **pathspec;
        struct dir_struct dir;
+       int add_interactive = 0;
+
+       for (i = 1; i < argc; i++) {
+               if (!strcmp("--interactive", argv[i]))
+                       add_interactive++;
+       }
+       if (add_interactive) {
+               const char *args[] = { "add--interactive", NULL };
+
+               if (add_interactive != 1 || argc != 2)
+                       die("add --interactive does not take any parameters");
+               execv_git_cmd(args);
+               exit(1);
+       }
 
        git_config(git_default_config);
 
@@ -105,6 +132,10 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        show_only = 1;
                        continue;
                }
+               if (!strcmp(arg, "-f")) {
+                       ignored_too = 1;
+                       continue;
+               }
                if (!strcmp(arg, "-v")) {
                        verbose = 1;
                        continue;
@@ -123,6 +154,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        if (show_only) {
                const char *sep = "", *eof = "";
                for (i = 0; i < dir.nr; i++) {
+                       if (!ignored_too && dir.entries[i]->ignored_entry)
+                               continue;
                        printf("%s%s", sep, dir.entries[i]->name);
                        sep = " ";
                        eof = "\n";
@@ -134,6 +167,24 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        if (read_cache() < 0)
                die("index file corrupt");
 
+       if (!ignored_too) {
+               int has_ignored = -1;
+               for (i = 0; has_ignored < 0 && i < dir.nr; i++)
+                       if (dir.entries[i]->ignored_entry)
+                               has_ignored = i;
+               if (0 <= has_ignored) {
+                       fprintf(stderr, ignore_warning);
+                       for (i = has_ignored; i < dir.nr; i++) {
+                               if (!dir.entries[i]->ignored_entry)
+                                       continue;
+                               fprintf(stderr, "%s\n", dir.entries[i]->name);
+                       }
+                       fprintf(stderr,
+                               "Use -f if you really want to add them.\n");
+                       exit(1);
+               }
+       }
+
        for (i = 0; i < dir.nr; i++)
                add_file_to_index(dir.entries[i]->name, verbose);
 
index 856f3cd841818bdad4446b3f7b75d5ab22959788..f641787988e197209f097cbc9d1b260a2cb6d9d8 100644 (file)
@@ -7,6 +7,7 @@
 #include "commit.h"
 #include "tree.h"
 #include "builtin.h"
+#include "utf8.h"
 
 #define BLOCKING (1ul << 14)
 
@@ -32,7 +33,7 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
        len = vsnprintf(one_line, sizeof(one_line), fmt, args);
        va_end(args);
        size = *sizep;
-       newsize = size + len;
+       newsize = size + len + 1;
        alloc = (size + 32767) & ~32767;
        buf = *bufp;
        if (newsize > alloc) {
@@ -40,7 +41,7 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
                buf = xrealloc(buf, alloc);
                *bufp = buf;
        }
-       *sizep = newsize;
+       *sizep = newsize - 1;
        memcpy(buf + size, one_line, len);
 }
 
@@ -77,6 +78,11 @@ static int new_parent(int idx)
        return 1;
 }
 
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
        int i;
@@ -101,6 +107,9 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
                a = argv[i]; b = argv[i+1];
                if (!b || strcmp(a, "-p"))
                        usage(commit_tree_usage);
+
+               if (parents >= MAXPARENT)
+                       die("Too many parents (%d max)", MAXPARENT);
                if (get_sha1(b, parent_sha1[parents]))
                        die("Not a valid object name %s", b);
                check_valid(parent_sha1[parents], commit_type);
@@ -127,6 +136,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
        while (fgets(comment, sizeof(comment), stdin) != NULL)
                add_buffer(&buffer, &size, "%s", comment);
 
+       /* And check the encoding */
+       buffer[size] = '\0';
+       if (!strcmp(git_commit_encoding, "utf-8") && !is_utf8(buffer))
+               fprintf(stderr, commit_utf8_warn);
+
        if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) {
                printf("%s\n", sha1_to_hex(commit_sha1));
                return 0;
index 6c4c3a351333b94ab62b77793b540af5c9bcaa3a..913577390862a847857a650516740059072b60ad 100644 (file)
@@ -1,26 +1,10 @@
 #include "cache.h"
 #include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
 
 static const char merge_file_usage[] =
 "git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
 
-static int read_file(mmfile_t *ptr, const char *filename)
-{
-       struct stat st;
-       FILE *f;
-
-       if (stat(filename, &st))
-               return error("Could not stat %s", filename);
-       if ((f = fopen(filename, "rb")) == NULL)
-               return error("Could not open %s", filename);
-       ptr->ptr = xmalloc(st.st_size);
-       if (fread(ptr->ptr, st.st_size, 1, f) != 1)
-               return error("Could not read %s", filename);
-       fclose(f);
-       ptr->size = st.st_size;
-       return 0;
-}
-
 int cmd_merge_file(int argc, char **argv, char **envp)
 {
        char *names[3];
@@ -53,7 +37,7 @@ int cmd_merge_file(int argc, char **argv, char **envp)
                names[i] = argv[i + 1];
 
        for (i = 0; i < 3; i++)
-               if (read_file(mmfs + i, argv[i + 1]))
+               if (read_mmfile(mmfs + i, argv[i + 1]))
                        return -1;
 
        ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
diff --git a/builtin-rerere.c b/builtin-rerere.c
new file mode 100644 (file)
index 0000000..d064bd8
--- /dev/null
@@ -0,0 +1,406 @@
+#include "cache.h"
+#include "path-list.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+
+#include <time.h>
+
+static const char git_rerere_usage[] =
+"git-rerere [clear | status | diff | gc]";
+
+/* these values are days */
+static int cutoff_noresolve = 15;
+static int cutoff_resolve = 60;
+
+static char *merge_rr_path;
+
+static const char *rr_path(const char *name, const char *file)
+{
+       return git_path("rr-cache/%s/%s", name, file);
+}
+
+static void read_rr(struct path_list *rr)
+{
+       unsigned char sha1[20];
+       char buf[PATH_MAX];
+       FILE *in = fopen(merge_rr_path, "r");
+       if (!in)
+               return;
+       while (fread(buf, 40, 1, in) == 1) {
+               int i;
+               char *name;
+               if (get_sha1_hex(buf, sha1))
+                       die("corrupt MERGE_RR");
+               buf[40] = '\0';
+               name = xstrdup(buf);
+               if (fgetc(in) != '\t')
+                       die("corrupt MERGE_RR");
+               for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
+                       ; /* do nothing */
+               if (i == sizeof(buf))
+                       die("filename too long");
+               path_list_insert(buf, rr)->util = xstrdup(name);
+       }
+       fclose(in);
+}
+
+static struct lock_file write_lock;
+
+static int write_rr(struct path_list *rr, int out_fd)
+{
+       int i;
+       for (i = 0; i < rr->nr; i++) {
+               const char *path = rr->items[i].path;
+               write(out_fd, rr->items[i].util, 40);
+               write(out_fd, "\t", 1);
+               write(out_fd, path, strlen(path) + 1);
+       }
+       close(out_fd);
+       return commit_lock_file(&write_lock);
+}
+
+struct buffer {
+       char *ptr;
+       int nr, alloc;
+};
+
+static void append_line(struct buffer *buffer, const char *line)
+{
+       int len = strlen(line);
+
+       if (buffer->nr + len > buffer->alloc) {
+               buffer->alloc = alloc_nr(buffer->nr + len);
+               buffer->ptr = xrealloc(buffer->ptr, buffer->alloc);
+       }
+       memcpy(buffer->ptr + buffer->nr, line, len);
+       buffer->nr += len;
+}
+
+static int handle_file(const char *path,
+        unsigned char *sha1, const char *output)
+{
+       SHA_CTX ctx;
+       char buf[1024];
+       int hunk = 0, hunk_no = 0;
+       struct buffer minus = { NULL, 0, 0 }, plus = { NULL, 0, 0 };
+       struct buffer *one = &minus, *two = &plus;
+       FILE *f = fopen(path, "r");
+       FILE *out;
+
+       if (!f)
+               return error("Could not open %s", path);
+
+       if (output) {
+               out = fopen(output, "w");
+               if (!out) {
+                       fclose(f);
+                       return error("Could not write %s", output);
+               }
+       } else
+               out = NULL;
+
+       if (sha1)
+               SHA1_Init(&ctx);
+
+       while (fgets(buf, sizeof(buf), f)) {
+               if (!strncmp("<<<<<<< ", buf, 8))
+                       hunk = 1;
+               else if (!strncmp("=======", buf, 7))
+                       hunk = 2;
+               else if (!strncmp(">>>>>>> ", buf, 8)) {
+                       hunk_no++;
+                       hunk = 0;
+                       if (memcmp(one->ptr, two->ptr, one->nr < two->nr ?
+                                               one->nr : two->nr) > 0) {
+                               struct buffer *swap = one;
+                               one = two;
+                               two = swap;
+                       }
+                       if (out) {
+                               fputs("<<<<<<<\n", out);
+                               fwrite(one->ptr, one->nr, 1, out);
+                               fputs("=======\n", out);
+                               fwrite(two->ptr, two->nr, 1, out);
+                               fputs(">>>>>>>\n", out);
+                       }
+                       if (sha1) {
+                               SHA1_Update(&ctx, one->ptr, one->nr);
+                               SHA1_Update(&ctx, "\0", 1);
+                               SHA1_Update(&ctx, two->ptr, two->nr);
+                               SHA1_Update(&ctx, "\0", 1);
+                       }
+               } else if (hunk == 1)
+                       append_line(one, buf);
+               else if (hunk == 2)
+                       append_line(two, buf);
+               else if (out)
+                       fputs(buf, out);
+       }
+
+       fclose(f);
+       if (out)
+               fclose(out);
+       if (sha1)
+               SHA1_Final(sha1, &ctx);
+       return hunk_no;
+}
+
+static int find_conflict(struct path_list *conflict)
+{
+       int i;
+       if (read_cache() < 0)
+               return error("Could not read index");
+       for (i = 0; i + 2 < active_nr; i++) {
+               struct cache_entry *e1 = active_cache[i];
+               struct cache_entry *e2 = active_cache[i + 1];
+               struct cache_entry *e3 = active_cache[i + 2];
+               if (ce_stage(e1) == 1 && ce_stage(e2) == 2 &&
+                               ce_stage(e3) == 3 && ce_same_name(e1, e2) &&
+                               ce_same_name(e1, e3)) {
+                       path_list_insert((const char *)e1->name, conflict);
+                       i += 3;
+               }
+       }
+       return 0;
+}
+
+static int merge(const char *name, const char *path)
+{
+       int ret;
+       mmfile_t cur, base, other;
+       mmbuffer_t result = {NULL, 0};
+       xpparam_t xpp = {XDF_NEED_MINIMAL};
+
+       if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
+               return 1;
+
+       if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
+                       read_mmfile(&base, rr_path(name, "preimage")) ||
+                       read_mmfile(&other, rr_path(name, "postimage")))
+               return 1;
+       ret = xdl_merge(&base, &cur, "", &other, "",
+                       &xpp, XDL_MERGE_ZEALOUS, &result);
+       if (!ret) {
+               FILE *f = fopen(path, "w");
+               if (!f)
+                       return error("Could not write to %s", path);
+               fwrite(result.ptr, result.size, 1, f);
+               fclose(f);
+       }
+
+       free(cur.ptr);
+       free(base.ptr);
+       free(other.ptr);
+       free(result.ptr);
+
+       return ret;
+}
+
+static void unlink_rr_item(const char *name)
+{
+       unlink(rr_path(name, "thisimage"));
+       unlink(rr_path(name, "preimage"));
+       unlink(rr_path(name, "postimage"));
+       rmdir(git_path("rr-cache/%s", name));
+}
+
+static void garbage_collect(struct path_list *rr)
+{
+       struct path_list to_remove = { NULL, 0, 0, 1 };
+       char buf[1024];
+       DIR *dir;
+       struct dirent *e;
+       int len, i, cutoff;
+       time_t now = time(NULL), then;
+
+       strlcpy(buf, git_path("rr-cache"), sizeof(buf));
+       len = strlen(buf);
+       dir = opendir(buf);
+       strcpy(buf + len++, "/");
+       while ((e = readdir(dir))) {
+               const char *name = e->d_name;
+               struct stat st;
+               if (name[0] == '.' && (name[1] == '\0' ||
+                                       (name[1] == '.' && name[2] == '\0')))
+                       continue;
+               i = snprintf(buf + len, sizeof(buf) - len, "%s", name);
+               strlcpy(buf + len + i, "/preimage", sizeof(buf) - len - i);
+               if (stat(buf, &st))
+                       continue;
+               then = st.st_mtime;
+               strlcpy(buf + len + i, "/postimage", sizeof(buf) - len - i);
+               cutoff = stat(buf, &st) ? cutoff_noresolve : cutoff_resolve;
+               if (then < now - cutoff * 86400) {
+                       buf[len + i] = '\0';
+                       path_list_insert(xstrdup(name), &to_remove);
+               }
+       }
+       for (i = 0; i < to_remove.nr; i++)
+               unlink_rr_item(to_remove.items[i].path);
+       path_list_clear(&to_remove, 0);
+}
+
+static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
+{
+       int i;
+       for (i = 0; i < nbuf; i++)
+               write(1, ptr[i].ptr, ptr[i].size);
+       return 0;
+}
+
+static int diff_two(const char *file1, const char *label1,
+               const char *file2, const char *label2)
+{
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+       mmfile_t minus, plus;
+
+       if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
+               return 1;
+
+       printf("--- a/%s\n+++ b/%s\n", label1, label2);
+       fflush(stdout);
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 3;
+       xecfg.flags = 0;
+       ecb.outf = outf;
+       xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+
+       free(minus.ptr);
+       free(plus.ptr);
+       return 0;
+}
+
+static int copy_file(const char *src, const char *dest)
+{
+       FILE *in, *out;
+       char buffer[32768];
+       int count;
+
+       if (!(in = fopen(src, "r")))
+               return error("Could not open %s", src);
+       if (!(out = fopen(dest, "w")))
+               return error("Could not open %s", dest);
+       while ((count = fread(buffer, 1, sizeof(buffer), in)))
+               fwrite(buffer, 1, count, out);
+       fclose(in);
+       fclose(out);
+       return 0;
+}
+
+static int do_plain_rerere(struct path_list *rr, int fd)
+{
+       struct path_list conflict = { NULL, 0, 0, 1 };
+       int i;
+
+       find_conflict(&conflict);
+
+       /*
+        * MERGE_RR records paths with conflicts immediately after merge
+        * failed.  Some of the conflicted paths might have been hand resolved
+        * in the working tree since then, but the initial run would catch all
+        * and register their preimages.
+        */
+
+       for (i = 0; i < conflict.nr; i++) {
+               const char *path = conflict.items[i].path;
+               if (!path_list_has_path(rr, path)) {
+                       unsigned char sha1[20];
+                       char *hex;
+                       int ret;
+                       ret = handle_file(path, sha1, NULL);
+                       if (ret < 1)
+                               continue;
+                       hex = xstrdup(sha1_to_hex(sha1));
+                       path_list_insert(path, rr)->util = hex;
+                       if (mkdir(git_path("rr-cache/%s", hex), 0755))
+                               continue;;
+                       handle_file(path, NULL, rr_path(hex, "preimage"));
+                       fprintf(stderr, "Recorded preimage for '%s'\n", path);
+               }
+       }
+
+       /*
+        * Now some of the paths that had conflicts earlier might have been
+        * hand resolved.  Others may be similar to a conflict already that
+        * was resolved before.
+        */
+
+       for (i = 0; i < rr->nr; i++) {
+               struct stat st;
+               int ret;
+               const char *path = rr->items[i].path;
+               const char *name = (const char *)rr->items[i].util;
+
+               if (!stat(rr_path(name, "preimage"), &st) &&
+                               !stat(rr_path(name, "postimage"), &st)) {
+                       if (!merge(name, path)) {
+                               fprintf(stderr, "Resolved '%s' using "
+                                               "previous resolution.\n", path);
+                               goto tail_optimization;
+                       }
+               }
+
+               /* Let's see if we have resolved it. */
+               ret = handle_file(path, NULL, NULL);
+               if (ret)
+                       continue;
+
+               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+               copy_file(path, rr_path(name, "postimage"));
+tail_optimization:
+               if (i < rr->nr - 1) {
+                       memmove(rr->items + i,
+                                       rr->items + i + 1,
+                                       rr->nr - i - 1);
+               }
+               rr->nr--;
+               i--;
+       }
+
+       return write_rr(rr, fd);
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+       struct path_list merge_rr = { NULL, 0, 0, 1 };
+       int i, fd = -1;
+       struct stat st;
+
+       if (stat(git_path("rr-cache"), &st) || !S_ISDIR(st.st_mode))
+               return 0;
+
+       merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
+       fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
+       read_rr(&merge_rr);
+
+       if (argc < 2)
+               return do_plain_rerere(&merge_rr, fd);
+       else if (!strcmp(argv[1], "clear")) {
+               for (i = 0; i < merge_rr.nr; i++) {
+                       const char *name = (const char *)merge_rr.items[i].util;
+                       if (!stat(git_path("rr-cache/%s", name), &st) &&
+                                       S_ISDIR(st.st_mode) &&
+                                       stat(rr_path(name, "postimage"), &st))
+                               unlink_rr_item(name);
+               }
+               unlink(merge_rr_path);
+       } else if (!strcmp(argv[1], "gc"))
+               garbage_collect(&merge_rr);
+       else if (!strcmp(argv[1], "status"))
+               for (i = 0; i < merge_rr.nr; i++)
+                       printf("%s\n", merge_rr.items[i].path);
+       else if (!strcmp(argv[1], "diff"))
+               for (i = 0; i < merge_rr.nr; i++) {
+                       const char *path = merge_rr.items[i].path;
+                       const char *name = (const char *)merge_rr.items[i].util;
+                       diff_two(rr_path(name, "preimage"), path, path, path);
+               }
+       else
+               usage(git_rerere_usage);
+
+       path_list_clear(&merge_rr, 1);
+       return 0;
+}
+
index 33d04bd015e43965a1bc44bb281908298f152f6c..5b078c41943c9ce0ff1983896e8ad6ae38705f60 100644 (file)
@@ -7,9 +7,10 @@
 #include "builtin.h"
 #include "dir.h"
 #include "cache-tree.h"
+#include "tree-walk.h"
 
 static const char builtin_rm_usage[] =
-"git-rm [-n] [-v] [-f] <filepattern>...";
+"git-rm [-n] [-f] [--cached] <filepattern>...";
 
 static struct {
        int nr, alloc;
@@ -41,12 +42,75 @@ static int remove_file(const char *name)
        return ret;
 }
 
+static int check_local_mod(unsigned char *head)
+{
+       /* items in list are already sorted in the cache order,
+        * so we could do this a lot more efficiently by using
+        * tree_desc based traversal if we wanted to, but I am
+        * lazy, and who cares if removal of files is a tad
+        * slower than the theoretical maximum speed?
+        */
+       int i, no_head;
+       int errs = 0;
+
+       no_head = is_null_sha1(head);
+       for (i = 0; i < list.nr; i++) {
+               struct stat st;
+               int pos;
+               struct cache_entry *ce;
+               const char *name = list.name[i];
+               unsigned char sha1[20];
+               unsigned mode;
+
+               pos = cache_name_pos(name, strlen(name));
+               if (pos < 0)
+                       continue; /* removing unmerged entry */
+               ce = active_cache[pos];
+
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno != ENOENT)
+                               fprintf(stderr, "warning: '%s': %s",
+                                       ce->name, strerror(errno));
+                       /* It already vanished from the working tree */
+                       continue;
+               }
+               else if (S_ISDIR(st.st_mode)) {
+                       /* if a file was removed and it is now a
+                        * directory, that is the same as ENOENT as
+                        * far as git is concerned; we do not track
+                        * directories.
+                        */
+                       continue;
+               }
+               if (ce_match_stat(ce, &st, 0))
+                       errs = error("'%s' has local modifications "
+                                    "(hint: try -f)", ce->name);
+               if (no_head)
+                       continue;
+               /*
+                * It is Ok to remove a newly added path, as long as
+                * it is cache-clean.
+                */
+               if (get_tree_entry(head, name, sha1, &mode))
+                       continue;
+               /*
+                * Otherwise make sure the version from the HEAD
+                * matches the index.
+                */
+               if (ce->ce_mode != create_ce_mode(mode) ||
+                   hashcmp(ce->sha1, sha1))
+                       errs = error("'%s' has changes staged in the index "
+                                    "(hint: try -f)", name);
+       }
+       return errs;
+}
+
 static struct lock_file lock_file;
 
 int cmd_rm(int argc, const char **argv, const char *prefix)
 {
        int i, newfd;
-       int verbose = 0, show_only = 0, force = 0;
+       int show_only = 0, force = 0, index_only = 0, recursive = 0;
        const char **pathspec;
        char *seen;
 
@@ -62,23 +126,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
                if (*arg != '-')
                        break;
-               if (!strcmp(arg, "--")) {
+               else if (!strcmp(arg, "--")) {
                        i++;
                        break;
                }
-               if (!strcmp(arg, "-n")) {
+               else if (!strcmp(arg, "-n"))
                        show_only = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-v")) {
-                       verbose = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-f")) {
+               else if (!strcmp(arg, "--cached"))
+                       index_only = 1;
+               else if (!strcmp(arg, "-f"))
                        force = 1;
-                       continue;
-               }
-               usage(builtin_rm_usage);
+               else if (!strcmp(arg, "-r"))
+                       recursive = 1;
+               else
+                       usage(builtin_rm_usage);
        }
        if (argc <= i)
                usage(builtin_rm_usage);
@@ -99,14 +160,36 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
        if (pathspec) {
                const char *match;
                for (i = 0; (match = pathspec[i]) != NULL ; i++) {
-                       if (*match && !seen[i])
-                               die("pathspec '%s' did not match any files", match);
+                       if (!seen[i])
+                               die("pathspec '%s' did not match any files",
+                                   match);
+                       if (!recursive && seen[i] == MATCHED_RECURSIVELY)
+                               die("not removing '%s' recursively without -r",
+                                   *match ? match : ".");
                }
        }
 
+       /*
+        * If not forced, the file, the index and the HEAD (if exists)
+        * must match; but the file can already been removed, since
+        * this sequence is a natural "novice" way:
+        *
+        *      rm F; git fm F
+        *
+        * Further, if HEAD commit exists, "diff-index --cached" must
+        * report no changes unless forced.
+        */
+       if (!force) {
+               unsigned char sha1[20];
+               if (get_sha1("HEAD", sha1))
+                       hashclr(sha1);
+               if (check_local_mod(sha1))
+                       exit(1);
+       }
+
        /*
         * First remove the names from the index: we won't commit
-        * the index unless all of them succeed
+        * the index unless all of them succeed.
         */
        for (i = 0; i < list.nr; i++) {
                const char *path = list.name[i];
@@ -121,14 +204,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                return 0;
 
        /*
-        * Then, if we used "-f", remove the filenames from the
-        * workspace. If we fail to remove the first one, we
+        * Then, unless we used "--cache", remove the filenames from
+        * the workspace. If we fail to remove the first one, we
         * abort the "git rm" (but once we've successfully removed
         * any file at all, we'll go ahead and commit to it all:
         * by then we've already committed ourselves and can't fail
         * in the middle)
         */
-       if (force) {
+       if (!index_only) {
                int removed = 0;
                for (i = 0; i < list.nr; i++) {
                        const char *path = list.name[i];
index b9d9781d4d6a12860d377607369a4dea95a3c7be..c67f2fa2fe2203b5308eb156d502b75ce44751d5 100644 (file)
@@ -4,7 +4,7 @@
 #include "builtin.h"
 
 static const char show_branch_usage[] =
-"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n] <branch>";
+"git-show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n] <branch>";
 
 static int default_num;
 static int default_alloc;
@@ -383,6 +383,20 @@ static int append_head_ref(const char *refname, const unsigned char *sha1, int f
        return append_ref(refname + ofs, sha1, flag, cb_data);
 }
 
+static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       unsigned char tmp[20];
+       int ofs = 13;
+       if (strncmp(refname, "refs/remotes/", ofs))
+               return 0;
+       /* If both heads/foo and tags/foo exists, get_sha1 would
+        * get confused.
+        */
+       if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+               ofs = 5;
+       return append_ref(refname + ofs, sha1, flag, cb_data);
+}
+
 static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        if (strncmp(refname, "refs/tags/", 10))
@@ -423,16 +437,16 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i
        return append_ref(refname, sha1, flag, cb_data);
 }
 
-static void snarf_refs(int head, int tag)
+static void snarf_refs(int head, int remotes)
 {
        if (head) {
                int orig_cnt = ref_name_cnt;
                for_each_ref(append_head_ref, NULL);
                sort_ref_range(orig_cnt, ref_name_cnt);
        }
-       if (tag) {
+       if (remotes) {
                int orig_cnt = ref_name_cnt;
-               for_each_ref(append_tag_ref, NULL);
+               for_each_ref(append_remote_ref, NULL);
                sort_ref_range(orig_cnt, ref_name_cnt);
        }
 }
@@ -554,7 +568,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        struct commit_list *list = NULL, *seen = NULL;
        unsigned int rev_mask[MAX_REVS];
        int num_rev, i, extra = 0;
-       int all_heads = 0, all_tags = 0;
+       int all_heads = 0, all_remotes = 0;
        int all_mask, all_revs;
        int lifo = 1;
        char head[128];
@@ -586,12 +600,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        ac--; av++;
                        break;
                }
-               else if (!strcmp(arg, "--all"))
-                       all_heads = all_tags = 1;
-               else if (!strcmp(arg, "--heads"))
-                       all_heads = 1;
-               else if (!strcmp(arg, "--tags"))
-                       all_tags = 1;
+               else if (!strcmp(arg, "--all") || !strcmp(arg, "-a"))
+                       all_heads = all_remotes = 1;
+               else if (!strcmp(arg, "--remotes") || !strcmp(arg, "-r"))
+                       all_remotes = 1;
                else if (!strcmp(arg, "--more"))
                        extra = 1;
                else if (!strcmp(arg, "--list"))
@@ -636,11 +648,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                usage(show_branch_usage);
 
        /* If nothing is specified, show all branches by default */
-       if (ac + all_heads + all_tags == 0)
+       if (ac + all_heads + all_remotes == 0)
                all_heads = 1;
 
-       if (all_heads + all_tags)
-               snarf_refs(all_heads, all_tags);
+       if (all_heads + all_remotes)
+               snarf_refs(all_heads, all_remotes);
        if (reflog) {
                int reflen;
                if (!ac)
index fdc0907eca4d056ce52da65f655e36ab52fff52e..df72d09447d0edd17d07eb97a9b3b36fa4b57531 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -53,6 +53,7 @@ extern int cmd_push(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_reflog(int argc, const char **argv, const char *prefix);
 extern int cmd_repo_config(int argc, const char **argv, const char *prefix);
+extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
index 0fd46e793d088e6567b20c7e81a5cde7023bdb58..4cfaee31361d98d2c8f68d250609b09130335baa 100644 (file)
@@ -1,17 +1,11 @@
 #include "../git-compat-util.h"
 
-void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
+void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
 {
-       int n = 0;
-       off_t current_offset = lseek(fd, 0, SEEK_CUR);
+       size_t n = 0;
 
        if (start != NULL || !(flags & MAP_PRIVATE))
-               die("Invalid usage of gitfakemmap.");
-
-       if (lseek(fd, offset, SEEK_SET) < 0) {
-               errno = EINVAL;
-               return MAP_FAILED;
-       }
+               die("Invalid usage of mmap when built with NO_MMAP");
 
        start = xmalloc(length);
        if (start == NULL) {
@@ -20,14 +14,16 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_
        }
 
        while (n < length) {
-               int count = read(fd, start+n, length-n);
+               ssize_t count = pread(fd, (char *)start + n, length - n, offset + n);
 
                if (count == 0) {
-                       memset(start+n, 0, length-n);
+                       memset((char *)start+n, 0, length-n);
                        break;
                }
 
                if (count < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
                        free(start);
                        errno = EACCES;
                        return MAP_FAILED;
@@ -36,15 +32,10 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_
                n += count;
        }
 
-       if (current_offset != lseek(fd, current_offset, SEEK_SET)) {
-               errno = EINVAL;
-               return MAP_FAILED;
-       }
-
        return start;
 }
 
-int gitfakemunmap(void *start, size_t length)
+int git_munmap(void *start, size_t length)
 {
        free(start);
        return 0;
index e153d5382364def42fa062a39148a696e2e49785..7cfb3a0666ef88dca43598ebe879f53787b4efab 100644 (file)
@@ -235,9 +235,6 @@ AC_SUBST(NO_SETENV)
 #
 # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
 # Enable it on Windows.  By default, symrefs are still used.
-#
-# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
-# a missing newline at the end of the file.
 
 ## Site configuration (override autodetection)
 ## --with-PACKAGE[=ARG] and --without-PACKAGE
index 8b6361922fd6e6a2fcd9acb20fd54f5b645b36f0..3eb4bd19e921f293f1ed1ab1fd1bcd9f78763c4c 100644 (file)
@@ -58,8 +58,9 @@
   (with-temp-buffer
     (let* ((dir (file-name-directory file))
            (name (file-relative-name file dir)))
-      (when dir (cd dir))
-      (and (ignore-errors (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)))
+      (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"))))))))
diff --git a/dir.c b/dir.c
index 16401d8017c5d96dc17ac9d52ab77eabbc4e9270..dd188a8c56c02e1bbe2c8fc0cfb9ce67cdeb948d 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -40,6 +40,18 @@ int common_prefix(const char **pathspec)
        return prefix;
 }
 
+/*
+ * Does 'match' matches the given name?
+ * A match is found if
+ *
+ * (1) the 'match' string is leading directory of 'name', or
+ * (2) the 'match' string is a wildcard and matches 'name', or
+ * (3) the 'match' string is exactly the same as 'name'.
+ *
+ * and the return value tells which case it was.
+ *
+ * It returns 0 when there is no match.
+ */
 static int match_one(const char *match, const char *name, int namelen)
 {
        int matchlen;
@@ -47,27 +59,30 @@ static int match_one(const char *match, const char *name, int namelen)
        /* If the match was just the prefix, we matched */
        matchlen = strlen(match);
        if (!matchlen)
-               return 1;
+               return MATCHED_RECURSIVELY;
 
        /*
         * If we don't match the matchstring exactly,
         * we need to match by fnmatch
         */
        if (strncmp(match, name, matchlen))
-               return !fnmatch(match, name, 0);
+               return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
 
-       /*
-        * If we did match the string exactly, we still
-        * need to make sure that it happened on a path
-        * component boundary (ie either the last character
-        * of the match was '/', or the next character of
-        * the name was '/' or the terminating NUL.
-        */
-       return  match[matchlen-1] == '/' ||
-               name[matchlen] == '/' ||
-               !name[matchlen];
+       if (!name[matchlen])
+               return MATCHED_EXACTLY;
+       if (match[matchlen-1] == '/' || name[matchlen] == '/')
+               return MATCHED_RECURSIVELY;
+       return 0;
 }
 
+/*
+ * Given a name and a list of pathspecs, see if the name matches
+ * any of the pathspecs.  The caller is also interested in seeing
+ * all pathspec matches some names it calls this function with
+ * (otherwise the user could have mistyped the unmatched pathspec),
+ * and a mark is left in seen[] array for pathspec element that
+ * actually matched anything.
+ */
 int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
 {
        int retval;
@@ -77,12 +92,16 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre
        namelen -= prefix;
 
        for (retval = 0; (match = *pathspec++) != NULL; seen++) {
-               if (retval & *seen)
+               int how;
+               if (retval && *seen == MATCHED_EXACTLY)
                        continue;
                match += prefix;
-               if (match_one(match, name, namelen)) {
-                       retval = 1;
-                       *seen = 1;
+               how = match_one(match, name, namelen);
+               if (how) {
+                       if (retval < how)
+                               retval = how;
+                       if (*seen < how)
+                               *seen = how;
                }
        }
        return retval;
@@ -241,7 +260,8 @@ int excluded(struct dir_struct *dir, const char *pathname)
        return 0;
 }
 
-static void add_name(struct dir_struct *dir, const char *pathname, int len)
+static void add_name(struct dir_struct *dir, const char *pathname, int len,
+                    int ignored_entry)
 {
        struct dir_entry *ent;
 
@@ -254,6 +274,7 @@ static void add_name(struct dir_struct *dir, const char *pathname, int len)
                dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
        }
        ent = xmalloc(sizeof(*ent) + len + 1);
+       ent->ignored_entry = ignored_entry;
        ent->len = len;
        memcpy(ent->name, pathname, len);
        ent->name[len] = 0;
@@ -295,6 +316,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
 
                while ((de = readdir(fdir)) != NULL) {
                        int len;
+                       int ignored_entry;
 
                        if ((de->d_name[0] == '.') &&
                            (de->d_name[1] == 0 ||
@@ -303,11 +325,12 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                                continue;
                        len = strlen(de->d_name);
                        memcpy(fullname + baselen, de->d_name, len+1);
-                       if (excluded(dir, fullname) != dir->show_ignored) {
-                               if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
-                                       continue;
-                               }
-                       }
+                       ignored_entry = excluded(dir, fullname);
+
+                       if (!dir->show_both &&
+                           (ignored_entry != dir->show_ignored) &&
+                           (!dir->show_ignored || DTYPE(de) != DT_DIR))
+                               continue;
 
                        switch (DTYPE(de)) {
                        struct stat st;
@@ -345,7 +368,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        if (check_only)
                                goto exit_early;
                        else
-                               add_name(dir, fullname, baselen + len);
+                               add_name(dir, fullname, baselen + len,
+                                        ignored_entry);
                }
 exit_early:
                closedir(fdir);
diff --git a/dir.h b/dir.h
index 550551ab25372c1898ba790e973808ccf0450924..08c634547229fc1353fd092fb226293ed07368c3 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -13,7 +13,8 @@
 
 
 struct dir_entry {
-       int len;
+       unsigned ignored_entry : 1;
+       unsigned int len : 15;
        char name[FLEX_ARRAY]; /* more */
 };
 
@@ -29,7 +30,8 @@ struct exclude_list {
 
 struct dir_struct {
        int nr, alloc;
-       unsigned int show_ignored:1,
+       unsigned int show_both: 1,
+                    show_ignored:1,
                     show_other_directories:1,
                     hide_empty_directories:1;
        struct dir_entry **entries;
@@ -40,6 +42,10 @@ struct dir_struct {
 };
 
 extern int common_prefix(const char **pathspec);
+
+#define MATCHED_RECURSIVELY 1
+#define MATCHED_FNMATCH 2
+#define MATCHED_EXACTLY 3
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 
 extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen);
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
new file mode 100755 (executable)
index 0000000..0057f86
--- /dev/null
@@ -0,0 +1,804 @@
+#!/usr/bin/perl -w
+
+
+use strict;
+
+sub run_cmd_pipe {
+       my $fh = undef;
+       open($fh, '-|', @_) or die;
+       return <$fh>;
+}
+
+my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
+
+if (!defined $GIT_DIR) {
+       exit(1); # rev-parse would have already said "not a git repo"
+}
+chomp($GIT_DIR);
+
+sub refresh {
+       my $fh;
+       open $fh, '-|', qw(git update-index --refresh)
+           or die;
+       while (<$fh>) {
+               ;# ignore 'needs update'
+       }
+       close $fh;
+}
+
+sub list_untracked {
+       map {
+               chomp $_;
+               $_;
+       }
+       run_cmd_pipe(qw(git ls-files --others
+                       --exclude-per-directory=.gitignore),
+                    "--exclude-from=$GIT_DIR/info/exclude",
+                    '--', @_);
+}
+
+my $status_fmt = '%12s %12s %s';
+my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
+
+# Returns list of hashes, contents of each of which are:
+# PRINT:       print message
+# VALUE:       pathname
+# BINARY:      is a binary path
+# INDEX:       is index different from HEAD?
+# FILE:                is file different from index?
+# INDEX_ADDDEL:        is it add/delete between HEAD and index?
+# FILE_ADDDEL: is it add/delete between index and file?
+
+sub list_modified {
+       my ($only) = @_;
+       my (%data, @return);
+       my ($add, $del, $adddel, $file);
+
+       for (run_cmd_pipe(qw(git diff-index --cached
+                            --numstat --summary HEAD))) {
+               if (($add, $del, $file) =
+                   /^([-\d]+)  ([-\d]+)        (.*)/) {
+                       my ($change, $bin);
+                       if ($add eq '-' && $del eq '-') {
+                               $change = 'binary';
+                               $bin = 1;
+                       }
+                       else {
+                               $change = "+$add/-$del";
+                       }
+                       $data{$file} = {
+                               INDEX => $change,
+                               BINARY => $bin,
+                               FILE => 'nothing',
+                       }
+               }
+               elsif (($adddel, $file) =
+                      /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $data{$file}{INDEX_ADDDEL} = $adddel;
+               }
+       }
+
+       for (run_cmd_pipe(qw(git diff-files --numstat --summary))) {
+               if (($add, $del, $file) =
+                   /^([-\d]+)  ([-\d]+)        (.*)/) {
+                       if (!exists $data{$file}) {
+                               $data{$file} = +{
+                                       INDEX => 'unchanged',
+                                       BINARY => 0,
+                               };
+                       }
+                       my ($change, $bin);
+                       if ($add eq '-' && $del eq '-') {
+                               $change = 'binary';
+                               $bin = 1;
+                       }
+                       else {
+                               $change = "+$add/-$del";
+                       }
+                       $data{$file}{FILE} = $change;
+                       if ($bin) {
+                               $data{$file}{BINARY} = 1;
+                       }
+               }
+               elsif (($adddel, $file) =
+                      /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $data{$file}{FILE_ADDDEL} = $adddel;
+               }
+       }
+
+       for (sort keys %data) {
+               my $it = $data{$_};
+
+               if ($only) {
+                       if ($only eq 'index-only') {
+                               next if ($it->{INDEX} eq 'unchanged');
+                       }
+                       if ($only eq 'file-only') {
+                               next if ($it->{FILE} eq 'nothing');
+                       }
+               }
+               push @return, +{
+                       VALUE => $_,
+                       PRINT => (sprintf $status_fmt,
+                                 $it->{INDEX}, $it->{FILE}, $_),
+                       %$it,
+               };
+       }
+       return @return;
+}
+
+sub find_unique {
+       my ($string, @stuff) = @_;
+       my $found = undef;
+       for (my $i = 0; $i < @stuff; $i++) {
+               my $it = $stuff[$i];
+               my $hit = undef;
+               if (ref $it) {
+                       if ((ref $it) eq 'ARRAY') {
+                               $it = $it->[0];
+                       }
+                       else {
+                               $it = $it->{VALUE};
+                       }
+               }
+               eval {
+                       if ($it =~ /^$string/) {
+                               $hit = 1;
+                       };
+               };
+               if (defined $hit && defined $found) {
+                       return undef;
+               }
+               if ($hit) {
+                       $found = $i + 1;
+               }
+       }
+       return $found;
+}
+
+sub list_and_choose {
+       my ($opts, @stuff) = @_;
+       my (@chosen, @return);
+       my $i;
+
+      TOPLOOP:
+       while (1) {
+               my $last_lf = 0;
+
+               if ($opts->{HEADER}) {
+                       if (!$opts->{LIST_FLAT}) {
+                               print "     ";
+                       }
+                       print "$opts->{HEADER}\n";
+               }
+               for ($i = 0; $i < @stuff; $i++) {
+                       my $chosen = $chosen[$i] ? '*' : ' ';
+                       my $print = $stuff[$i];
+                       if (ref $print) {
+                               if ((ref $print) eq 'ARRAY') {
+                                       $print = $print->[0];
+                               }
+                               else {
+                                       $print = $print->{PRINT};
+                               }
+                       }
+                       printf("%s%2d: %s", $chosen, $i+1, $print);
+                       if (($opts->{LIST_FLAT}) &&
+                           (($i + 1) % ($opts->{LIST_FLAT}))) {
+                               print "\t";
+                               $last_lf = 0;
+                       }
+                       else {
+                               print "\n";
+                               $last_lf = 1;
+                       }
+               }
+               if (!$last_lf) {
+                       print "\n";
+               }
+
+               return if ($opts->{LIST_ONLY});
+
+               print $opts->{PROMPT};
+               if ($opts->{SINGLETON}) {
+                       print "> ";
+               }
+               else {
+                       print ">> ";
+               }
+               my $line = <STDIN>;
+               last if (!$line);
+               chomp $line;
+               my $donesomething = 0;
+               for my $choice (split(/[\s,]+/, $line)) {
+                       my $choose = 1;
+                       my ($bottom, $top);
+
+                       # Input that begins with '-'; unchoose
+                       if ($choice =~ s/^-//) {
+                               $choose = 0;
+                       }
+                       # A range can be specified like 5-7
+                       if ($choice =~ /^(\d+)-(\d+)$/) {
+                               ($bottom, $top) = ($1, $2);
+                       }
+                       elsif ($choice =~ /^\d+$/) {
+                               $bottom = $top = $choice;
+                       }
+                       elsif ($choice eq '*') {
+                               $bottom = 1;
+                               $top = 1 + @stuff;
+                       }
+                       else {
+                               $bottom = $top = find_unique($choice, @stuff);
+                               if (!defined $bottom) {
+                                       print "Huh ($choice)?\n";
+                                       next TOPLOOP;
+                               }
+                       }
+                       if ($opts->{SINGLETON} && $bottom != $top) {
+                               print "Huh ($choice)?\n";
+                               next TOPLOOP;
+                       }
+                       for ($i = $bottom-1; $i <= $top-1; $i++) {
+                               next if (@stuff <= $i);
+                               $chosen[$i] = $choose;
+                               $donesomething++;
+                       }
+               }
+               last if (!$donesomething || $opts->{IMMEDIATE});
+       }
+       for ($i = 0; $i < @stuff; $i++) {
+               if ($chosen[$i]) {
+                       push @return, $stuff[$i];
+               }
+       }
+       return @return;
+}
+
+sub status_cmd {
+       list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
+                       list_modified());
+       print "\n";
+}
+
+sub say_n_paths {
+       my $did = shift @_;
+       my $cnt = scalar @_;
+       print "$did ";
+       if (1 < $cnt) {
+               print "$cnt paths\n";
+       }
+       else {
+               print "one path\n";
+       }
+}
+
+sub update_cmd {
+       my @mods = list_modified('file-only');
+       return if (!@mods);
+
+       my @update = list_and_choose({ PROMPT => 'Update',
+                                      HEADER => $status_head, },
+                                    @mods);
+       if (@update) {
+               system(qw(git update-index --add --),
+                      map { $_->{VALUE} } @update);
+               say_n_paths('updated', @update);
+       }
+       print "\n";
+}
+
+sub revert_cmd {
+       my @update = list_and_choose({ PROMPT => 'Revert',
+                                      HEADER => $status_head, },
+                                    list_modified());
+       if (@update) {
+               my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
+                                        map { $_->{VALUE} } @update);
+               my $fh;
+               open $fh, '|-', qw(git update-index --index-info)
+                   or die;
+               for (@lines) {
+                       print $fh $_;
+               }
+               close($fh);
+               for (@update) {
+                       if ($_->{INDEX_ADDDEL} &&
+                           $_->{INDEX_ADDDEL} eq 'create') {
+                               system(qw(git update-index --force-remove --),
+                                      $_->{VALUE});
+                               print "note: $_->{VALUE} is untracked now.\n";
+                       }
+               }
+               refresh();
+               say_n_paths('reverted', @update);
+       }
+       print "\n";
+}
+
+sub add_untracked_cmd {
+       my @add = list_and_choose({ PROMPT => 'Add untracked' },
+                                 list_untracked());
+       if (@add) {
+               system(qw(git update-index --add --), @add);
+               say_n_paths('added', @add);
+       }
+       print "\n";
+}
+
+sub parse_diff {
+       my ($path) = @_;
+       my @diff = run_cmd_pipe(qw(git diff-files -p --), $path);
+       my (@hunk) = { TEXT => [] };
+
+       for (@diff) {
+               if (/^@@ /) {
+                       push @hunk, { TEXT => [] };
+               }
+               push @{$hunk[-1]{TEXT}}, $_;
+       }
+       return @hunk;
+}
+
+sub hunk_splittable {
+       my ($text) = @_;
+
+       my @s = split_hunk($text);
+       return (1 < @s);
+}
+
+sub parse_hunk_header {
+       my ($line) = @_;
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+           $line =~ /^@@ -(\d+)(?:,(\d+)) \+(\d+)(?:,(\d+)) @@/;
+       return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
+}
+
+sub split_hunk {
+       my ($text) = @_;
+       my @split = ();
+
+       # If there are context lines in the middle of a hunk,
+       # it can be split, but we would need to take care of
+       # overlaps later.
+
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = parse_hunk_header($text->[0]);
+       my $hunk_start = 1;
+       my $next_hunk_start;
+
+      OUTER:
+       while (1) {
+               my $next_hunk_start = undef;
+               my $i = $hunk_start - 1;
+               my $this = +{
+                       TEXT => [],
+                       OLD => $o_ofs,
+                       NEW => $n_ofs,
+                       OCNT => 0,
+                       NCNT => 0,
+                       ADDDEL => 0,
+                       POSTCTX => 0,
+               };
+
+               while (++$i < @$text) {
+                       my $line = $text->[$i];
+                       if ($line =~ /^ /) {
+                               if ($this->{ADDDEL} &&
+                                   !defined $next_hunk_start) {
+                                       # We have seen leading context and
+                                       # adds/dels and then here is another
+                                       # context, which is trailing for this
+                                       # split hunk and leading for the next
+                                       # one.
+                                       $next_hunk_start = $i;
+                               }
+                               push @{$this->{TEXT}}, $line;
+                               $this->{OCNT}++;
+                               $this->{NCNT}++;
+                               if (defined $next_hunk_start) {
+                                       $this->{POSTCTX}++;
+                               }
+                               next;
+                       }
+
+                       # add/del
+                       if (defined $next_hunk_start) {
+                               # We are done with the current hunk and
+                               # this is the first real change for the
+                               # next split one.
+                               $hunk_start = $next_hunk_start;
+                               $o_ofs = $this->{OLD} + $this->{OCNT};
+                               $n_ofs = $this->{NEW} + $this->{NCNT};
+                               $o_ofs -= $this->{POSTCTX};
+                               $n_ofs -= $this->{POSTCTX};
+                               push @split, $this;
+                               redo OUTER;
+                       }
+                       push @{$this->{TEXT}}, $line;
+                       $this->{ADDDEL}++;
+                       if ($line =~ /^-/) {
+                               $this->{OCNT}++;
+                       }
+                       else {
+                               $this->{NCNT}++;
+                       }
+               }
+
+               push @split, $this;
+               last;
+       }
+
+       for my $hunk (@split) {
+               $o_ofs = $hunk->{OLD};
+               $n_ofs = $hunk->{NEW};
+               $o_cnt = $hunk->{OCNT};
+               $n_cnt = $hunk->{NCNT};
+
+               my $head = ("@@ -$o_ofs" .
+                           (($o_cnt != 1) ? ",$o_cnt" : '') .
+                           " +$n_ofs" .
+                           (($n_cnt != 1) ? ",$n_cnt" : '') .
+                           " @@\n");
+               unshift @{$hunk->{TEXT}}, $head;
+       }
+       return map { $_->{TEXT} } @split;
+}
+
+sub find_last_o_ctx {
+       my ($it) = @_;
+       my $text = $it->{TEXT};
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = parse_hunk_header($text->[0]);
+       my $i = @{$text};
+       my $last_o_ctx = $o_ofs + $o_cnt;
+       while (0 < --$i) {
+               my $line = $text->[$i];
+               if ($line =~ /^ /) {
+                       $last_o_ctx--;
+                       next;
+               }
+               last;
+       }
+       return $last_o_ctx;
+}
+
+sub merge_hunk {
+       my ($prev, $this) = @_;
+       my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
+           parse_hunk_header($prev->{TEXT}[0]);
+       my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
+           parse_hunk_header($this->{TEXT}[0]);
+
+       my (@line, $i, $ofs, $o_cnt, $n_cnt);
+       $ofs = $o0_ofs;
+       $o_cnt = $n_cnt = 0;
+       for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
+               my $line = $prev->{TEXT}[$i];
+               if ($line =~ /^\+/) {
+                       $n_cnt++;
+                       push @line, $line;
+                       next;
+               }
+
+               last if ($o1_ofs <= $ofs);
+
+               $o_cnt++;
+               $ofs++;
+               if ($line =~ /^ /) {
+                       $n_cnt++;
+               }
+               push @line, $line;
+       }
+
+       for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
+               my $line = $this->{TEXT}[$i];
+               if ($line =~ /^\+/) {
+                       $n_cnt++;
+                       push @line, $line;
+                       next;
+               }
+               $ofs++;
+               $o_cnt++;
+               if ($line =~ /^ /) {
+                       $n_cnt++;
+               }
+               push @line, $line;
+       }
+       my $head = ("@@ -$o0_ofs" .
+                   (($o_cnt != 1) ? ",$o_cnt" : '') .
+                   " +$n0_ofs" .
+                   (($n_cnt != 1) ? ",$n_cnt" : '') .
+                   " @@\n");
+       @{$prev->{TEXT}} = ($head, @line);
+}
+
+sub coalesce_overlapping_hunks {
+       my (@in) = @_;
+       my @out = ();
+
+       my ($last_o_ctx);
+
+       for (grep { $_->{USE} } @in) {
+               my $text = $_->{TEXT};
+               my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+                   parse_hunk_header($text->[0]);
+               if (defined $last_o_ctx &&
+                   $o_ofs <= $last_o_ctx) {
+                       merge_hunk($out[-1], $_);
+               }
+               else {
+                       push @out, $_;
+               }
+               $last_o_ctx = find_last_o_ctx($out[-1]);
+       }
+       return @out;
+}
+
+sub help_patch_cmd {
+       print <<\EOF ;
+y - stage this hunk
+n - do not stage this hunk
+a - stage this and all the remaining hunks
+d - do not stage this hunk nor any of the remaining hunks
+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
+K - leave this hunk undecided, see previous hunk
+s - split the current hunk into smaller hunks
+EOF
+}
+
+sub patch_update_cmd {
+       my @mods = list_modified('file-only');
+       @mods = grep { !($_->{BINARY}) } @mods;
+       return if (!@mods);
+
+       my ($it) = list_and_choose({ PROMPT => 'Patch update',
+                                    SINGLETON => 1,
+                                    IMMEDIATE => 1,
+                                    HEADER => $status_head, },
+                                  @mods);
+       return if (!$it);
+
+       my ($ix, $num);
+       my $path = $it->{VALUE};
+       my ($head, @hunk) = parse_diff($path);
+       for (@{$head->{TEXT}}) {
+               print;
+       }
+       $num = scalar @hunk;
+       $ix = 0;
+
+       while (1) {
+               my ($prev, $next, $other, $undecided, $i);
+               $other = '';
+
+               if ($num <= $ix) {
+                       $ix = 0;
+               }
+               for ($i = 0; $i < $ix; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $prev = 1;
+                               $other .= '/k';
+                               last;
+                       }
+               }
+               if ($ix) {
+                       $other .= '/K';
+               }
+               for ($i = $ix + 1; $i < $num; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $next = 1;
+                               $other .= '/j';
+                               last;
+                       }
+               }
+               if ($ix < $num - 1) {
+                       $other .= '/J';
+               }
+               for ($i = 0; $i < $num; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $undecided = 1;
+                               last;
+                       }
+               }
+               last if (!$undecided);
+
+               if (hunk_splittable($hunk[$ix]{TEXT})) {
+                       $other .= '/s';
+               }
+               for (@{$hunk[$ix]{TEXT}}) {
+                       print;
+               }
+               print "Stage this hunk [y/n/a/d$other/?]? ";
+               my $line = <STDIN>;
+               if ($line) {
+                       if ($line =~ /^y/i) {
+                               $hunk[$ix]{USE} = 1;
+                       }
+                       elsif ($line =~ /^n/i) {
+                               $hunk[$ix]{USE} = 0;
+                       }
+                       elsif ($line =~ /^a/i) {
+                               while ($ix < $num) {
+                                       if (!defined $hunk[$ix]{USE}) {
+                                               $hunk[$ix]{USE} = 1;
+                                       }
+                                       $ix++;
+                               }
+                               next;
+                       }
+                       elsif ($line =~ /^d/i) {
+                               while ($ix < $num) {
+                                       if (!defined $hunk[$ix]{USE}) {
+                                               $hunk[$ix]{USE} = 0;
+                                       }
+                                       $ix++;
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /K/ && $line =~ /^K/) {
+                               $ix--;
+                               next;
+                       }
+                       elsif ($other =~ /J/ && $line =~ /^J/) {
+                               $ix++;
+                               next;
+                       }
+                       elsif ($other =~ /k/ && $line =~ /^k/) {
+                               while (1) {
+                                       $ix--;
+                                       last if (!$ix ||
+                                                !defined $hunk[$ix]{USE});
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /j/ && $line =~ /^j/) {
+                               while (1) {
+                                       $ix++;
+                                       last if ($ix >= $num ||
+                                                !defined $hunk[$ix]{USE});
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /s/ && $line =~ /^s/) {
+                               my @split = split_hunk($hunk[$ix]{TEXT});
+                               if (1 < @split) {
+                                       print "Split into ",
+                                       scalar(@split), " hunks.\n";
+                               }
+                               splice(@hunk, $ix, 1,
+                                      map { +{ TEXT => $_, USE => undef } }
+                                      @split);
+                               $num = scalar @hunk;
+                               next;
+                       }
+                       else {
+                               help_patch_cmd($other);
+                               next;
+                       }
+                       # soft increment
+                       while (1) {
+                               $ix++;
+                               last if ($ix >= $num ||
+                                        !defined $hunk[$ix]{USE});
+                       }
+               }
+       }
+
+       @hunk = coalesce_overlapping_hunks(@hunk);
+
+       my ($o_lofs, $n_lofs) = (0, 0);
+       my @result = ();
+       for (@hunk) {
+               my $text = $_->{TEXT};
+               my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+                   parse_hunk_header($text->[0]);
+
+               if (!$_->{USE}) {
+                       if (!defined $o_cnt) { $o_cnt = 1; }
+                       if (!defined $n_cnt) { $n_cnt = 1; }
+
+                       # We would have added ($n_cnt - $o_cnt) lines
+                       # to the postimage if we were to use this hunk,
+                       # but we didn't.  So the line number that the next
+                       # hunk starts at would be shifted by that much.
+                       $n_lofs -= ($n_cnt - $o_cnt);
+                       next;
+               }
+               else {
+                       if ($n_lofs) {
+                               $n_ofs += $n_lofs;
+                               $text->[0] = ("@@ -$o_ofs" .
+                                             ((defined $o_cnt)
+                                              ? ",$o_cnt" : '') .
+                                             " +$n_ofs" .
+                                             ((defined $n_cnt)
+                                              ? ",$n_cnt" : '') .
+                                             " @@\n");
+                       }
+                       for (@$text) {
+                               push @result, $_;
+                       }
+               }
+       }
+
+       if (@result) {
+               my $fh;
+
+               open $fh, '|-', qw(git apply --cached);
+               for (@{$head->{TEXT}}, @result) {
+                       print $fh $_;
+               }
+               if (!close $fh) {
+                       for (@{$head->{TEXT}}, @result) {
+                               print STDERR $_;
+                       }
+               }
+               refresh();
+       }
+
+       print "\n";
+}
+
+sub diff_cmd {
+       my @mods = list_modified('index-only');
+       @mods = grep { !($_->{BINARY}) } @mods;
+       return if (!@mods);
+       my (@them) = list_and_choose({ PROMPT => 'Review diff',
+                                    IMMEDIATE => 1,
+                                    HEADER => $status_head, },
+                                  @mods);
+       return if (!@them);
+       system(qw(git diff-index -p --cached HEAD --),
+              map { $_->{VALUE} } @them);
+}
+
+sub quit_cmd {
+       print "Bye.\n";
+       exit(0);
+}
+
+sub help_cmd {
+       print <<\EOF ;
+status        - show paths with changes
+update        - add working tree state to the staged set of changes
+revert        - revert staged set of changes back to the HEAD version
+patch         - pick hunks and update selectively
+diff         - view diff between HEAD and index
+add untracked - add contents of untracked files to the staged set of changes
+EOF
+}
+
+sub main_loop {
+       my @cmd = ([ 'status', \&status_cmd, ],
+                  [ 'update', \&update_cmd, ],
+                  [ 'revert', \&revert_cmd, ],
+                  [ 'add untracked', \&add_untracked_cmd, ],
+                  [ 'patch', \&patch_update_cmd, ],
+                  [ 'diff', \&diff_cmd, ],
+                  [ 'quit', \&quit_cmd, ],
+                  [ 'help', \&help_cmd, ],
+       );
+       while (1) {
+               my ($it) = list_and_choose({ PROMPT => 'What now',
+                                            SINGLETON => 1,
+                                            LIST_FLAT => 4,
+                                            HEADER => '*** Commands ***',
+                                            IMMEDIATE => 1 }, @cmd);
+               if ($it) {
+                       eval {
+                               $it->[1]->();
+                       };
+                       if ($@) {
+                               print "$@";
+                       }
+               }
+       }
+}
+
+my @z;
+
+refresh();
+status_cmd();
+main_loop();
index 4192a99fec1e137599b250a4f65c1b1e4201c601..92ec069a3acacc2d12c2c709311969f3d0aa4153 100755 (executable)
@@ -146,8 +146,11 @@ fi
 
 [ -z "$branch$newbranch" ] &&
        [ "$new" != "$old" ] &&
-       die "git checkout: to checkout the requested commit you need to specify 
-              a name for a new branch which is created and switched to"
+       die "git checkout: provided reference cannot be checked out directly
+
+  You need -b to associate a new branch with the wanted checkout. Example:
+  git checkout -b <new_branch_name> $arg
+"
 
 if [ "X$old" = X ]
 then
index a55b923894196b5c4a5af32cd773b24824f14cec..5d9eb2615b2e21979f547199121828aafbf02135 100644 (file)
@@ -11,7 +11,7 @@
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
-#if !defined(__APPLE__) && !defined(__FreeBSD)
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
 #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
 #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
 #endif
@@ -87,10 +87,10 @@ extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
 #define MAP_FAILED ((void*)-1)
 #endif
 
-#define mmap gitfakemmap
-#define munmap gitfakemunmap
-extern void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
-extern int gitfakemunmap(void *start, size_t length);
+#define mmap git_mmap
+#define munmap git_munmap
+extern void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+extern int git_munmap(void *start, size_t length);
 
 #else /* NO_MMAP */
 
index 4ebfcf65d99f7743048df901b38969c28cc11edb..7dd0a112368a8b3672a98fdfcdb5290146cefeba 100755 (executable)
@@ -32,7 +32,7 @@ savestate() {
 restorestate() {
         if test -f "$GIT_DIR/MERGE_SAVE"
        then
-               git reset --hard $head
+               git reset --hard $head >/dev/null
                cpio -iuv <"$GIT_DIR/MERGE_SAVE"
                git-update-index --refresh >/dev/null
        fi
@@ -221,6 +221,8 @@ do
        remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
            die "$remote - not something we can merge"
        remoteheads="${remoteheads}$remotehead "
+       eval GITHEAD_$remotehead='"$remote"'
+       export GITHEAD_$remotehead
 done
 set x $remoteheads ; shift
 
index f163821d033cc0dc81dcd7ce0af13a7e75986db1..144f1701553a1e2f4204a2df15e66437e0670123 100755 (executable)
@@ -7,18 +7,7 @@ GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null) || :;
 get_data_source () {
        case "$1" in
        */*)
-               # Not so fast.  This could be the partial URL shorthand...
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               if test "$(git-repo-config --get "remote.$token.url")"
-               then
-                       echo config-partial
-               elif test -f "$GIT_DIR/branches/$token"
-               then
-                       echo branches-partial
-               else
-                       echo ''
-               fi
+               echo ''
                ;;
        *)
                if test "$(git-repo-config --get "remote.$1.url")"
@@ -40,12 +29,7 @@ get_remote_url () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
        '')
-               echo "$1" ;;
-       config-partial)
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               url=$(git-repo-config --get "remote.$token.url")
-               echo "$url/$remainder"
+               echo "$1"
                ;;
        config)
                git-repo-config --get "remote.$1.url"
@@ -54,14 +38,10 @@ get_remote_url () {
                sed -ne '/^URL: */{
                        s///p
                        q
-               }' "$GIT_DIR/remotes/$1" ;;
+               }' "$GIT_DIR/remotes/$1"
+               ;;
        branches)
-               sed -e 's/#.*//' "$GIT_DIR/branches/$1" ;;
-       branches-partial)
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               url=$(sed -e 's/#.*//' "$GIT_DIR/branches/$token")
-               echo "$url/$remainder"
+               sed -e 's/#.*//' "$GIT_DIR/branches/$1"
                ;;
        *)
                die "internal error: get-remote-url $1" ;;
@@ -77,7 +57,7 @@ get_default_remote () {
 get_remote_default_refs_for_push () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
-       '' | config-partial | branches | branches-partial)
+       '' | branches)
                ;; # no default push mapping, just send matching refs.
        config)
                git-repo-config --get-all "remote.$1.push" ;;
@@ -145,13 +125,6 @@ canon_refs_list_for_fetch () {
                        merge_branches=$(git-repo-config \
                            --get-all "branch.${curr_branch}.merge")
                fi
-               # If we are fetching only one branch, then first branch
-               # is the only thing that makes sense to merge anyway,
-               # so there is no point refusing that traditional rule.
-               if test $# != 1 && test "z$merge_branches" = z
-               then
-                       merge_branches=..this..would..never..match..
-               fi
        fi
        for ref
        do
@@ -173,8 +146,12 @@ canon_refs_list_for_fetch () {
                else
                        for merge_branch in $merge_branches
                        do
-                           [ "$remote" = "$merge_branch" ] &&
-                           dot_prefix= && break
+                           if  test "$remote" = "$merge_branch" ||
+                               test "$local" = "$merge_branch"
+                           then
+                                   dot_prefix=
+                                   break
+                           fi
                        done
                fi
                case "$remote" in
@@ -203,7 +180,7 @@ canon_refs_list_for_fetch () {
 get_remote_default_refs_for_fetch () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
-       '' | config-partial | branches-partial)
+       '')
                echo "HEAD:" ;;
        config)
                canon_refs_list_for_fetch -d "$1" \
index 2b4f3477fa941afe4f6450f74f6f8cf43a7567c0..ece31425d08a7fc2758b769afac65c37956d20cb 100755 (executable)
@@ -292,6 +292,7 @@ then
 fi
 
 # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
+echo "First, rewinding head to replay your work on top of it..."
 git-reset --hard "$onto"
 
 # If the $onto is a proper descendant of the tip of the branch, then
index 07748bc3e3f4091613c8e78647a471ac21ee46d2..c2cdceb1d1c15eb5e4d2698ceca14bac3e9d3eeb 100755 (executable)
                        { 'merge|m|M' => \$_merge,
                          'strategy|s=s' => \$_strategy,
                          'dry-run|n' => \$_dry_run,
-                       %cmt_opts } ],
+                       %cmt_opts, %fc_opts } ],
        'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish",
                        {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
@@ -512,15 +512,15 @@ sub dcommit {
        }
        return if $_dry_run;
        fetch();
-       my @diff = command('diff-tree', $head, $gs, '--');
+       my @diff = command('diff-tree', 'HEAD', $gs, '--');
        my @finish;
        if (@diff) {
                @finish = qw/rebase/;
                push @finish, qw/--merge/ if $_merge;
                push @finish, "--strategy=$_strategy" if $_strategy;
-               print STDERR "W: $head and $gs differ, using @finish:\n", @diff;
+               print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff;
        } else {
-               print "No changes between current $head and $gs\n",
+               print "No changes between current HEAD and $gs\n",
                      "Resetting to the latest $gs\n";
                @finish = qw/reset --mixed/;
        }
index 36cd6aa256db765aa741099b8d3c63b50f58048f..e1bfa82f1ea1213fad8ff8c4b6ad2b17e6ca1f7e 100755 (executable)
@@ -40,7 +40,6 @@ do
        message="$1"
        if test "$#" = "0"; then
            die "error: option -m needs an argument"
-           exit 2
        else
            message_given=1
        fi
@@ -50,7 +49,6 @@ do
        shift
        if test "$#" = "0"; then
            die "error: option -F needs an argument"
-           exit 2
        else
            message="$(cat "$1")"
            message_given=1
diff --git a/git.c b/git.c
index 5822296e6e6ef79e6c334d7f3f2426ce8f81ff96..50ebd869ad47cb2803a5a1e581442d7e72842034 100644 (file)
--- a/git.c
+++ b/git.c
@@ -59,8 +59,10 @@ static int handle_options(const char*** argv, int* argc)
                } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
                        setup_pager();
                } else if (!strcmp(cmd, "--git-dir")) {
-                       if (*argc < 1)
-                               return -1;
+                       if (*argc < 2) {
+                               fprintf(stderr, "No directory given for --git-dir.\n" );
+                               usage(git_usage_string);
+                       }
                        setenv("GIT_DIR", (*argv)[1], 1);
                        (*argv)++;
                        (*argc)--;
@@ -246,6 +248,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "read-tree", cmd_read_tree, RUN_SETUP },
                { "reflog", cmd_reflog, RUN_SETUP },
                { "repo-config", cmd_repo_config },
+               { "rerere", cmd_rerere, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
                { "rm", cmd_rm, RUN_SETUP },
index ebbc397ee8dce807bd9700aa72d33c80b13f02bf..65fcdb0f289c21b0824546884d826b48586ca712 100755 (executable)
                #         => [content-encoding, suffix, program]
                'default' => ['x-gzip', 'gz', 'gzip']},
 
+       # Enable text search, which will list the commits which match author,
+       # committer or commit text to a given string.  Enabled by default.
+       'search' => {
+               'override' => 0,
+               'default' => [1]},
+
        # Enable the pickaxe search, which will list the commits that modified
        # a given string in a file. This can be practical and quite faster
        # alternative to 'blame', but still potentially CPU-intensive.
@@ -351,6 +357,9 @@ sub check_export_ok {
        if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
                die_error(undef, "Invalid search parameter");
        }
+       if (length($searchtext) < 2) {
+               die_error(undef, "At least two characters are required for search parameter");
+       }
        $searchtext = quotemeta $searchtext;
 }
 
@@ -1139,8 +1148,9 @@ sub git_get_last_activity {
 
        $git_dir = "$projectroot/$path";
        open($fd, "-|", git_cmd(), 'for-each-ref',
-            '--format=%(refname) %(committer)',
+            '--format=%(committer)',
             '--sort=-committerdate',
+            '--count=1',
             'refs/heads') or return;
        my $most_recent = <$fd>;
        close $fd or return;
@@ -1260,36 +1270,25 @@ sub parse_tag {
        return %tag
 }
 
-sub parse_commit {
-       my $commit_id = shift;
-       my $commit_text = shift;
-
-       my @commit_lines;
+sub parse_commit_text {
+       my ($commit_text) = @_;
+       my @commit_lines = split '\n', $commit_text;
        my %co;
 
-       if (defined $commit_text) {
-               @commit_lines = @$commit_text;
-       } else {
-               local $/ = "\0";
-               open my $fd, "-|", git_cmd(), "rev-list",
-                       "--header", "--parents", "--max-count=1",
-                       $commit_id, "--"
-                       or return;
-               @commit_lines = split '\n', <$fd>;
-               close $fd or return;
-               pop @commit_lines;
-       }
+       pop @commit_lines; # Remove '\0'
+
        my $header = shift @commit_lines;
        if (!($header =~ m/^[0-9a-fA-F]{40}/)) {
                return;
        }
-       ($co{'id'}, my @parents) = split ' ', $header;
-       $co{'parents'} = \@parents;
-       $co{'parent'} = $parents[0];
+       $co{'id'} = $header;
+       my @parents;
        while (my $line = shift @commit_lines) {
                last if $line eq "\n";
                if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
                        $co{'tree'} = $1;
+               } elsif ($line =~ m/^parent ([0-9a-fA-F]{40})$/) {
+                       push @parents, $1;
                } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
                        $co{'author'} = $1;
                        $co{'author_epoch'} = $2;
@@ -1316,6 +1315,8 @@ sub parse_commit {
        if (!defined $co{'tree'}) {
                return;
        };
+       $co{'parents'} = \@parents;
+       $co{'parent'} = $parents[0];
 
        foreach my $title (@commit_lines) {
                $title =~ s/^    //;
@@ -1365,6 +1366,51 @@ sub parse_commit {
        return %co;
 }
 
+sub parse_commit {
+       my ($commit_id) = @_;
+       my %co;
+
+       local $/ = "\0";
+
+       open my $fd, "-|", git_cmd(), "rev-list",
+               "--header",
+               "--max-count=1",
+               $commit_id,
+               "--",
+               or die_error(undef, "Open git-rev-list failed");
+       %co = parse_commit_text(<$fd>);
+       close $fd;
+
+       return %co;
+}
+
+sub parse_commits {
+       my ($commit_id, $maxcount, $skip, $arg, $filename) = @_;
+       my @cos;
+
+       $maxcount ||= 1;
+       $skip ||= 0;
+
+       local $/ = "\0";
+
+       open my $fd, "-|", git_cmd(), "rev-list",
+               "--header",
+               ($arg ? ($arg) : ()),
+               ("--max-count=" . $maxcount),
+               ("--skip=" . $skip),
+               $commit_id,
+               "--",
+               ($filename ? ($filename) : ())
+               or die_error(undef, "Open git-rev-list failed");
+       while (my $line = <$fd>) {
+               my %co = parse_commit_text($line);
+               push @cos, \%co;
+       }
+       close $fd;
+
+       return wantarray ? @cos : \@cos;
+}
+
 # parse ref from ref_file, given by ref_id, with given type
 sub parse_ref {
        my $ref_file = shift;
@@ -1726,6 +1772,9 @@ sub git_header_html {
                        print " / $action";
                }
                print "\n";
+       }
+       my ($have_search) = gitweb_check_feature('search');
+       if ((defined $project) && ($have_search)) {
                if (!defined $searchtext) {
                        $searchtext = "";
                }
@@ -2633,18 +2682,19 @@ sub git_project_list_body {
 
 sub git_shortlog_body {
        # uses global variable $project
-       my ($revlist, $from, $to, $refs, $extra) = @_;
+       my ($commitlist, $from, $to, $refs, $extra) = @_;
+
+       my $have_snapshot = gitweb_have_snapshot();
 
        $from = 0 unless defined $from;
-       $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
+       $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
 
        print "<table class=\"shortlog\" cellspacing=\"0\">\n";
        my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
-               my $commit = $revlist->[$i];
-               #my $ref = defined $refs ? format_ref_marker($refs, $commit) : '';
+               my %co = %{$commitlist->[$i]};
+               my $commit = $co{'id'};
                my $ref = format_ref_marker($refs, $commit);
-               my %co = parse_commit($commit);
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
                } else {
@@ -2662,7 +2712,7 @@ sub git_shortlog_body {
                      $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
-               if (gitweb_have_snapshot()) {
+               if ($have_snapshot) {
                        print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
                }
                print "</td>\n" .
@@ -2678,23 +2728,19 @@ sub git_shortlog_body {
 
 sub git_history_body {
        # Warning: assumes constant type (blob or tree) during history
-       my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
+       my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
 
        $from = 0 unless defined $from;
-       $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist});
+       $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
 
        print "<table class=\"history\" cellspacing=\"0\">\n";
        my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
-               if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) {
-                       next;
-               }
-
-               my $commit = $1;
-               my %co = parse_commit($commit);
+               my %co = %{$commitlist->[$i]};
                if (!%co) {
                        next;
                }
+               my $commit = $co{'id'};
 
                my $ref = format_ref_marker($refs, $commit);
 
@@ -2837,6 +2883,58 @@ sub git_heads_body {
        print "</table>\n";
 }
 
+sub git_search_grep_body {
+       my ($commitlist, $from, $to, $extra) = @_;
+       $from = 0 unless defined $from;
+       $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
+
+       print "<table class=\"grep\" cellspacing=\"0\">\n";
+       my $alternate = 1;
+       for (my $i = $from; $i <= $to; $i++) {
+               my %co = %{$commitlist->[$i]};
+               if (!%co) {
+                       next;
+               }
+               my $commit = $co{'id'};
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+                     "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+                     "<td>" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
+                              esc_html(chop_str($co{'title'}, 50)) . "<br/>");
+               my $comment = $co{'comment'};
+               foreach my $line (@$comment) {
+                       if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
+                               my $lead = esc_html($1) || "";
+                               $lead = chop_str($lead, 30, 10);
+                               my $match = esc_html($2) || "";
+                               my $trail = esc_html($3) || "";
+                               $trail = chop_str($trail, 30, 10);
+                               my $text = "$lead<span class=\"match\">$match</span>$trail";
+                               print chop_str($text, 80, 5) . "<br/>\n";
+                       }
+               }
+               print "</td>\n" .
+                     "<td class=\"link\">" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
+                     " | " .
+                     $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
+               print "</td>\n" .
+                     "</tr>\n";
+       }
+       if (defined $extra) {
+               print "<tr>\n" .
+                     "<td colspan=\"3\">$extra</td>\n" .
+                     "</tr>\n";
+       }
+       print "</table>\n";
+}
+
 ## ======================================================================
 ## ======================================================================
 ## actions
@@ -2908,9 +3006,9 @@ sub git_project_index {
 
 sub git_summary {
        my $descr = git_get_project_description($project) || "none";
-       my $head = git_get_head_hash($project);
-       my %co = parse_commit($head);
+       my %co = parse_commit("HEAD");
        my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+       my $head = $co{'id'};
 
        my $owner = git_get_project_owner($project);
 
@@ -2956,14 +3054,10 @@ sub git_summary {
 
        # we need to request one more than 16 (0..15) to check if
        # those 16 are all
-       open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
-               git_get_head_hash($project), "--"
-               or die_error(undef, "Open git-rev-list failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd;
+       my @commitlist = parse_commits($head, 17);
        git_print_header_div('shortlog');
-       git_shortlog_body(\@revlist, 0, 15, $refs,
-                         $#revlist <=  15 ? undef :
+       git_shortlog_body(\@commitlist, 0, 15, $refs,
+                         $#commitlist <=  15 ? undef :
                          $cgi->a({-href => href(action=>"shortlog")}, "..."));
 
        if (@taglist) {
@@ -2983,6 +3077,7 @@ sub git_summary {
        if (@forklist) {
                git_print_header_div('forks');
                git_project_list_body(\@forklist, undef, 0, 15,
+                                     $#forklist <= 15 ? undef :
                                      $cgi->a({-href => href(action=>"forks")}, "..."),
                                      'noheader');
        }
@@ -3524,28 +3619,25 @@ sub git_log {
        }
        my $refs = git_get_references();
 
-       my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-       open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
-               or die_error(undef, "Open git-rev-list failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd;
+       my @commitlist = parse_commits($hash, 101, (100 * $page));
 
-       my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist);
+       my $paging_nav = format_paging_nav('log', $hash, $head, $page, (100 * ($page+1)));
 
        git_header_html();
        git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
 
-       if (!@revlist) {
+       if (!@commitlist) {
                my %co = parse_commit($hash);
 
                git_print_header_div('summary', $project);
                print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
        }
-       for (my $i = ($page * 100); $i <= $#revlist; $i++) {
-               my $commit = $revlist[$i];
-               my $ref = format_ref_marker($refs, $commit);
-               my %co = parse_commit($commit);
+       my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
+       for (my $i = 0; $i <= $to; $i++) {
+               my %co = %{$commitlist[$i]};
                next if !%co;
+               my $commit = $co{'id'};
+               my $ref = format_ref_marker($refs, $commit);
                my %ad = parse_date($co{'author_epoch'});
                git_print_header_div('commit',
                               "<span class=\"age\">$co{'age_string'}</span>" .
@@ -3567,6 +3659,12 @@ sub git_log {
                git_print_log($co{'comment'}, -final_empty_line=> 1);
                print "</div>\n";
        }
+       if ($#commitlist >= 100) {
+               print "<div class=\"page_nav\">\n";
+               print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1),
+                              -accesskey => "n", -title => "Alt-n"}, "next");
+               print "</div>\n";
+       }
        git_footer_html();
 }
 
@@ -4095,12 +4193,7 @@ sub git_history {
                $ftype = git_get_type($hash);
        }
 
-       open my $fd, "-|",
-               git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name
-                       or die_error(undef, "Open git-rev-list-failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd
-               or die_error(undef, "Reading git-rev-list failed");
+       my @commitlist = parse_commits($hash_base, 101, (100 * $page), "--full-history", $file_name);
 
        my $paging_nav = '';
        if ($page > 0) {
@@ -4116,7 +4209,7 @@ sub git_history {
                $paging_nav .= "first";
                $paging_nav .= " &sdot; prev";
        }
-       if ($#revlist >= (100 * ($page+1)-1)) {
+       if ($#commitlist >= 100) {
                $paging_nav .= " &sdot; " .
                        $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
                                               file_name=>$file_name, page=>$page+1),
@@ -4125,11 +4218,11 @@ sub git_history {
                $paging_nav .= " &sdot; next";
        }
        my $next_link = '';
-       if ($#revlist >= (100 * ($page+1)-1)) {
+       if ($#commitlist >= 100) {
                $next_link =
                        $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
                                               file_name=>$file_name, page=>$page+1),
-                                -title => "Alt-n"}, "next");
+                                -accesskey => "n", -title => "Alt-n"}, "next");
        }
 
        git_header_html();
@@ -4137,13 +4230,17 @@ sub git_history {
        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
        git_print_page_path($file_name, $ftype, $hash_base);
 
-       git_history_body(\@revlist, ($page * 100), $#revlist,
+       git_history_body(\@commitlist, 0, 99,
                         $refs, $hash_base, $ftype, $next_link);
 
        git_footer_html();
 }
 
 sub git_search {
+       my ($have_search) = gitweb_check_feature('search');
+       if (!$have_search) {
+               die_error('403 Permission denied', "Permission denied");
+       }
        if (!defined $searchtext) {
                die_error(undef, "Text field empty");
        }
@@ -4154,6 +4251,9 @@ sub git_search {
        if (!%co) {
                die_error(undef, "Unknown commit object");
        }
+       if (!defined $page) {
+               $page = 0;
+       }
 
        $searchtype ||= 'commit';
        if ($searchtype eq 'pickaxe') {
@@ -4166,66 +4266,63 @@ sub git_search {
        }
 
        git_header_html();
-       git_print_page_nav('','', $hash,$co{'tree'},$hash);
-       git_print_header_div('commit', esc_html($co{'title'}), $hash);
 
-       print "<table cellspacing=\"0\">\n";
-       my $alternate = 1;
        if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
-               $/ = "\0";
-               open my $fd, "-|", git_cmd(), "rev-list",
-                       "--header", "--parents", $hash, "--"
-                       or next;
-               while (my $commit_text = <$fd>) {
-                       if (!grep m/$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       my @commit_lines = split "\n", $commit_text;
-                       my %co = parse_commit(undef, \@commit_lines);
-                       if (!%co) {
-                               next;
-                       }
-                       if ($alternate) {
-                               print "<tr class=\"dark\">\n";
-                       } else {
-                               print "<tr class=\"light\">\n";
-                       }
-                       $alternate ^= 1;
-                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                             "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
-                             "<td>" .
-                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
-                                      esc_html(chop_str($co{'title'}, 50)) . "<br/>");
-                       my $comment = $co{'comment'};
-                       foreach my $line (@$comment) {
-                               if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
-                                       my $lead = esc_html($1) || "";
-                                       $lead = chop_str($lead, 30, 10);
-                                       my $match = esc_html($2) || "";
-                                       my $trail = esc_html($3) || "";
-                                       $trail = chop_str($trail, 30, 10);
-                                       my $text = "$lead<span class=\"match\">$match</span>$trail";
-                                       print chop_str($text, 80, 5) . "<br/>\n";
-                               }
-                       }
-                       print "</td>\n" .
-                             "<td class=\"link\">" .
-                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
-                             " | " .
-                             $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
-                       print "</td>\n" .
-                             "</tr>\n";
+               my $greptype;
+               if ($searchtype eq 'commit') {
+                       $greptype = "--grep=";
+               } elsif ($searchtype eq 'author') {
+                       $greptype = "--author=";
+               } elsif ($searchtype eq 'committer') {
+                       $greptype = "--committer=";
                }
-               close $fd;
+               $greptype .= $searchtext;
+               my @commitlist = parse_commits($hash, 101, (100 * $page), $greptype);
+
+               my $paging_nav = '';
+               if ($page > 0) {
+                       $paging_nav .=
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype)},
+                                       "first");
+                       $paging_nav .= " &sdot; " .
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page-1),
+                                        -accesskey => "p", -title => "Alt-p"}, "prev");
+               } else {
+                       $paging_nav .= "first";
+                       $paging_nav .= " &sdot; prev";
+               }
+               if ($#commitlist >= 100) {
+                       $paging_nav .= " &sdot; " .
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
+               } else {
+                       $paging_nav .= " &sdot; next";
+               }
+               my $next_link = '';
+               if ($#commitlist >= 100) {
+                       $next_link =
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
+               }
+
+               git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
+               git_print_header_div('commit', esc_html($co{'title'}), $hash);
+               git_search_grep_body(\@commitlist, 0, 99, $next_link);
        }
 
        if ($searchtype eq 'pickaxe') {
+               git_print_page_nav('','', $hash,$co{'tree'},$hash);
+               git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+               print "<table cellspacing=\"0\">\n";
+               my $alternate = 1;
                $/ = "\n";
                my $git_command = git_cmd_str();
                open my $fd, "-|", "$git_command rev-list $hash | " .
@@ -4280,8 +4377,9 @@ sub git_search {
                        }
                }
                close $fd;
+
+               print "</table>\n";
        }
-       print "</table>\n";
        git_footer_html();
 }
 
@@ -4320,26 +4418,21 @@ sub git_shortlog {
        }
        my $refs = git_get_references();
 
-       my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-       open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
-               or die_error(undef, "Open git-rev-list failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd;
+       my @commitlist = parse_commits($head, 101, (100 * $page));
 
-       my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist);
+       my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, (100 * ($page+1)));
        my $next_link = '';
-       if ($#revlist >= (100 * ($page+1)-1)) {
+       if ($#commitlist >= 100) {
                $next_link =
                        $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1),
-                                -title => "Alt-n"}, "next");
+                                -accesskey => "n", -title => "Alt-n"}, "next");
        }
 
-
        git_header_html();
        git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
        git_print_header_div('summary', $project);
 
-       git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link);
+       git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
 
        git_footer_html();
 }
@@ -4359,11 +4452,7 @@ sub git_feed {
 
        # log/feed of current (HEAD) branch, log of given branch, history of file/directory
        my $head = $hash || 'HEAD';
-       open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
-               $head, "--", (defined $file_name ? $file_name : ())
-               or die_error(undef, "Open git-rev-list failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading git-rev-list failed");
+       my @commitlist = parse_commits($head, 150);
 
        my %latest_commit;
        my %latest_date;
@@ -4373,8 +4462,8 @@ sub git_feed {
                # browser (feed reader) prefers text/xml
                $content_type = 'text/xml';
        }
-       if (defined($revlist[0])) {
-               %latest_commit = parse_commit($revlist[0]);
+       if (defined($commitlist[0])) {
+               %latest_commit = %{$commitlist[0]};
                %latest_date   = parse_date($latest_commit{'author_epoch'});
                print $cgi->header(
                        -type => $content_type,
@@ -4464,9 +4553,9 @@ sub git_feed {
        }
 
        # contents
-       for (my $i = 0; $i <= $#revlist; $i++) {
-               my $commit = $revlist[$i];
-               my %co = parse_commit($commit);
+       for (my $i = 0; $i <= $#commitlist; $i++) {
+               my %co = %{$commitlist[$i]};
+               my $commit = $co{'id'};
                # we read 150, we always show 30 and the ones more recent than 48 hours
                if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
                        last;
@@ -4474,7 +4563,7 @@ sub git_feed {
                my %cd = parse_date($co{'author_epoch'});
 
                # get list of changed files
-               open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+               open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
                        $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
                        or next;
                my @difftree = map { chomp; $_ } <$fd>;
index ae7ae4cd2a9ce392945ad8ed27e4289c27ee24bd..ca4f19e34d4216fed7c8110ba8f75585c69f92c7 100644 (file)
@@ -649,8 +649,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
                        char *name1, *name2;
                        int merge_status;
 
-                       name1 = xstrdup(mkpath("%s/%s", branch1, a->path));
-                       name2 = xstrdup(mkpath("%s/%s", branch2, b->path));
+                       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+                       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
 
                        fill_mm(o->sha1, &orig);
                        fill_mm(a->sha1, &src1);
@@ -1263,6 +1263,18 @@ static struct commit *get_ref(const char *ref)
        return (struct commit *)object;
 }
 
+static const char *better_branch_name(const char *branch)
+{
+       static char githead_env[8 + 40 + 1];
+       char *name;
+
+       if (strlen(branch) != 40)
+               return branch;
+       sprintf(githead_env, "GITHEAD_%s", branch);
+       name = getenv(githead_env);
+       return name ? name : branch;
+}
+
 int main(int argc, char *argv[])
 {
        static const char *bases[2];
@@ -1293,11 +1305,14 @@ int main(int argc, char *argv[])
 
        branch1 = argv[++i];
        branch2 = argv[++i];
-       printf("Merging %s with %s\n", branch1, branch2);
 
        h1 = get_ref(branch1);
        h2 = get_ref(branch2);
 
+       branch1 = better_branch_name(branch1);
+       branch2 = better_branch_name(branch2);
+       printf("Merging %s with %s\n", branch1, branch2);
+
        if (bases_count == 1) {
                struct commit *ancestor = get_ref(bases[0]);
                clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result);
index 4f6de2dfdd51c7a0fe39b8dfef10a1a3cb11d20a..af9f87418c6ed342e0a3d751b8f8e59fe5e8aeed 100644 (file)
@@ -574,6 +574,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->prefix = prefix;
        revs->max_age = -1;
        revs->min_age = -1;
+       revs->skip_count = -1;
        revs->max_count = -1;
 
        revs->prune_fn = NULL;
@@ -810,6 +811,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->max_count = atoi(arg + 12);
                                continue;
                        }
+                       if (!strncmp(arg, "--skip=", 7)) {
+                               revs->skip_count = atoi(arg + 7);
+                               continue;
+                       }
                        /* accept -<digit>, like traditional "head" */
                        if ((*arg == '-') && isdigit(arg[1])) {
                                revs->max_count = atoi(arg + 1);
@@ -1181,23 +1186,11 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
                           commit->buffer, strlen(commit->buffer));
 }
 
-struct commit *get_revision(struct rev_info *revs)
+static struct commit *get_revision_1(struct rev_info *revs)
 {
-       struct commit_list *list = revs->commits;
-
-       if (!list)
+       if (!revs->commits)
                return NULL;
 
-       /* Check the max_count ... */
-       switch (revs->max_count) {
-       case -1:
-               break;
-       case 0:
-               return NULL;
-       default:
-               revs->max_count--;
-       }
-
        do {
                struct commit_list *entry = revs->commits;
                struct commit *commit = entry->item;
@@ -1264,3 +1257,28 @@ struct commit *get_revision(struct rev_info *revs)
        } while (revs->commits);
        return NULL;
 }
+
+struct commit *get_revision(struct rev_info *revs)
+{
+       struct commit *c = NULL;
+
+       if (0 < revs->skip_count) {
+               while ((c = get_revision_1(revs)) != NULL) {
+                       if (revs->skip_count-- <= 0)
+                               break;
+               }
+       }
+
+       /* Check the max_count ... */
+       switch (revs->max_count) {
+       case -1:
+               break;
+       case 0:
+               return NULL;
+       default:
+               revs->max_count--;
+       }
+       if (c)
+               return c;
+       return get_revision_1(revs);
+}
index 4585463a44c9faecdcdf4454b735b1e4ba1335aa..ec991e5c57039a57af7c63db483e5b108a25ad16 100644 (file)
@@ -77,6 +77,7 @@ struct rev_info {
        struct grep_opt *grep_filter;
 
        /* special limits */
+       int skip_count;
        int max_count;
        unsigned long max_age;
        unsigned long min_age;
index 201d1642da672492d9975df3ee3c78d7011e4321..e31cf93a00ab377355734b3d88d536d36fe734e1 100755 (executable)
@@ -43,19 +43,19 @@ test_expect_success \
 
 test_expect_success \
     'Test that git-rm foo succeeds' \
-    'git-rm foo'
+    'git-rm --cached foo'
 
 test_expect_success \
     'Post-check that foo exists but is not in index after git-rm foo' \
     '[ -f foo ] && ! git-ls-files --error-unmatch foo'
 
 test_expect_success \
-    'Pre-check that bar exists and is in index before "git-rm -f bar"' \
+    'Pre-check that bar exists and is in index before "git-rm bar"' \
     '[ -f bar ] && git-ls-files --error-unmatch bar'
 
 test_expect_success \
-    'Test that "git-rm -f bar" succeeds' \
-    'git-rm -f bar'
+    'Test that "git-rm bar" succeeds' \
+    'git-rm bar'
 
 test_expect_success \
     'Post-check that bar does not exist and is not in index after "git-rm -f bar"' \
@@ -84,4 +84,74 @@ test_expect_success \
     'When the rm in "git-rm -f" fails, it should not remove the file from the index' \
     'git-ls-files --error-unmatch baz'
 
+# Now, failure cases.
+test_expect_success 'Re-add foo and baz' '
+       git add foo baz &&
+       git ls-files --error-unmatch foo baz
+'
+
+test_expect_success 'Modify foo -- rm should refuse' '
+       echo >>foo &&
+       ! git rm foo baz &&
+       test -f foo &&
+       test -f baz &&
+       git ls-files --error-unmatch foo baz
+'
+
+test_expect_success 'Modified foo -- rm -f should work' '
+       git rm -f foo baz &&
+       test ! -f foo &&
+       test ! -f baz &&
+       ! git ls-files --error-unmatch foo &&
+       ! git ls-files --error-unmatch bar
+'
+
+test_expect_success 'Re-add foo and baz for HEAD tests' '
+       echo frotz >foo &&
+       git checkout HEAD -- baz &&
+       git add foo baz &&
+       git ls-files --error-unmatch foo baz
+'
+
+test_expect_success 'foo is different in index from HEAD -- rm should refuse' '
+       ! git rm foo baz &&
+       test -f foo &&
+       test -f baz &&
+       git ls-files --error-unmatch foo baz
+'
+
+test_expect_success 'but with -f it should work.' '
+       git rm -f foo baz &&
+       test ! -f foo &&
+       test ! -f baz &&
+       ! git ls-files --error-unmatch foo
+       ! git ls-files --error-unmatch baz
+'
+
+test_expect_success 'Recursive test setup' '
+       mkdir -p frotz &&
+       echo qfwfq >frotz/nitfol &&
+       git add frotz &&
+       git commit -m "subdir test"
+'
+
+test_expect_success 'Recursive without -r fails' '
+       ! git rm frotz &&
+       test -d frotz &&
+       test -f frotz/nitfol
+'
+
+test_expect_success 'Recursive with -r but dirty' '
+       echo qfwfq >>frotz/nitfol
+       ! git rm -r frotz &&
+       test -d frotz &&
+       test -f frotz/nitfol
+'
+
+test_expect_success 'Recursive with -r -f' '
+       git rm -f -r frotz &&
+       ! test -f frotz/nitfol &&
+       ! test -d frotz
+'
+
 test_done
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
new file mode 100755 (executable)
index 0000000..5ee5b23
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+#
+
+test_description='git-rerere
+'
+
+. ./test-lib.sh
+
+cat > a1 << EOF
+Whether 'tis nobler in the mind to suffer
+The slings and arrows of outrageous fortune,
+Or to take arms against a sea of troubles,
+And by opposing end them? To die: to sleep;
+No more; and by a sleep to say we end
+The heart-ache and the thousand natural shocks
+That flesh is heir to, 'tis a consummation
+Devoutly to be wish'd.
+EOF
+
+git add a1
+git commit -q -a -m initial
+
+git checkout -b first
+cat >> a1 << EOF
+To die, to sleep;
+To sleep: perchance to dream: ay, there's the rub;
+For in that sleep of death what dreams may come
+When we have shuffled off this mortal coil,
+Must give us pause: there's the respect
+That makes calamity of so long life;
+EOF
+git commit -q -a -m first
+
+git checkout -b second master
+git show first:a1 | sed 's/To die, t/To die! T/' > a1
+git commit -q -a -m second
+
+# activate rerere
+mkdir .git/rr-cache
+
+test_expect_failure 'conflicting merge' 'git pull . first'
+
+sha1=4f58849a60b4f969a2848966b6d02893b783e8fb
+rr=.git/rr-cache/$sha1
+test_expect_success 'recorded preimage' "grep ======= $rr/preimage"
+
+test_expect_success 'no postimage or thisimage yet' \
+       "test ! -f $rr/postimage -a ! -f $rr/thisimage"
+
+git show first:a1 > a1
+
+cat > expect << EOF
+--- a/a1
++++ b/a1
+@@ -6,11 +6,7 @@
+ The heart-ache and the thousand natural shocks
+ That flesh is heir to, 'tis a consummation
+ Devoutly to be wish'd.
+-<<<<<<<
+-To die! To sleep;
+-=======
+ To die, to sleep;
+->>>>>>>
+ To sleep: perchance to dream: ay, there's the rub;
+ For in that sleep of death what dreams may come
+ When we have shuffled off this mortal coil,
+EOF
+
+git rerere diff > out
+
+test_expect_success 'rerere diff' 'diff -u expect out'
+
+cat > expect << EOF
+a1
+EOF
+
+git rerere status > out
+
+test_expect_success 'rerere status' 'diff -u expect out'
+
+test_expect_success 'commit succeeds' \
+       "git commit -q -a -m 'prefer first over second'"
+
+test_expect_success 'recorded postimage' "test -f $rr/postimage"
+
+git checkout -b third master
+git show second^:a1 | sed 's/To die: t/To die! T/' > a1
+git commit -q -a -m third
+
+test_expect_failure 'another conflicting merge' 'git pull . first'
+
+git show first:a1 | sed 's/To die: t/To die! T/' > expect
+test_expect_success 'rerere kicked in' "! grep ======= a1"
+
+test_expect_success 'rerere prefers first change' 'diff -u a1 expect'
+
+rm $rr/postimage
+echo "$sha1    a1" | tr '\012' '\0' > .git/rr-cache/MERGE_RR
+
+test_expect_success 'rerere clear' 'git rerere clear'
+
+test_expect_success 'clear removed the directory' "test ! -d $rr"
+
+mkdir $rr
+echo Hello > $rr/preimage
+echo World > $rr/postimage
+
+sha2=4000000000000000000000000000000000000000
+rr2=.git/rr-cache/$sha2
+mkdir $rr2
+echo Hello > $rr2/preimage
+
+case "$(date -d @11111111 +%s 2>/dev/null)" in
+[1-9]*)
+       # it is a recent GNU date. good.
+       now=$(date +%s)
+       almost_15_days_ago=$(($now+60-15*86400))
+       just_over_15_days_ago=$(($now-1-15*86400))
+       almost_60_days_ago=$(($now+60-60*86400))
+       just_over_60_days_ago=$(($now-1-60*86400))
+       predate1="$(date -d "@$almost_60_days_ago" +%c)"
+       predate2="$(date -d "@$almost_15_days_ago" +%c)"
+       postdate1="$(date -d "@$just_over_60_days_ago" +%c)"
+       postdate2="$(date -d "@$just_over_15_days_ago" +%c)"
+       ;;
+*)
+       # it is not GNU date. oh, well.
+       predate1="$(date)"
+       predate2="$(date)"
+       postdate1='1 Oct 2006 00:00:00'
+       postdate2='1 Dec 2006 00:00:00'
+esac
+
+touch -m -d "$predate1" $rr/preimage
+touch -m -d "$predate2" $rr2/preimage
+
+test_expect_success 'garbage collection (part1)' 'git rerere gc'
+
+test_expect_success 'young records still live' \
+       "test -f $rr/preimage -a -f $rr2/preimage"
+
+touch -m -d "$postdate1" $rr/preimage
+touch -m -d "$postdate2" $rr2/preimage
+
+test_expect_success 'garbage collection (part2)' 'git rerere gc'
+
+test_expect_success 'old records rest in peace' \
+       "test ! -f $rr/preimage -a ! -f $rr2/preimage"
+
+test_done
+
+
diff --git a/t/t6005-rev-list-count.sh b/t/t6005-rev-list-count.sh
new file mode 100755 (executable)
index 0000000..334fccf
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description='git-rev-list --max-count and --skip test'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+    for n in 1 2 3 4 5 ; do \
+        echo $n > a ; \
+        git add a ; \
+        git commit -m "$n" ; \
+    done
+'
+
+test_expect_success 'no options' '
+    test $(git-rev-list HEAD | wc -l) = 5
+'
+
+test_expect_success '--max-count' '
+    test $(git-rev-list HEAD --max-count=0 | wc -l) = 0 &&
+    test $(git-rev-list HEAD --max-count=3 | wc -l) = 3 &&
+    test $(git-rev-list HEAD --max-count=5 | wc -l) = 5 &&
+    test $(git-rev-list HEAD --max-count=10 | wc -l) = 5
+'
+
+test_expect_success '--max-count all forms' '
+    test $(git-rev-list HEAD --max-count=1 | wc -l) = 1 &&
+    test $(git-rev-list HEAD -1 | wc -l) = 1 &&
+    test $(git-rev-list HEAD -n1 | wc -l) = 1 &&
+    test $(git-rev-list HEAD -n 1 | wc -l) = 1
+'
+
+test_expect_success '--skip' '
+    test $(git-rev-list HEAD --skip=0 | wc -l) = 5 &&
+    test $(git-rev-list HEAD --skip=3 | wc -l) = 2 &&
+    test $(git-rev-list HEAD --skip=5 | wc -l) = 0 &&
+    test $(git-rev-list HEAD --skip=10 | wc -l) = 0
+'
+
+test_expect_success '--skip --max-count' '
+    test $(git-rev-list HEAD --skip=0 --max-count=0 | wc -l) = 0 &&
+    test $(git-rev-list HEAD --skip=0 --max-count=10 | wc -l) = 5 &&
+    test $(git-rev-list HEAD --skip=3 --max-count=0 | wc -l) = 0 &&
+    test $(git-rev-list HEAD --skip=3 --max-count=1 | wc -l) = 1 &&
+    test $(git-rev-list HEAD --skip=3 --max-count=2 | wc -l) = 2 &&
+    test $(git-rev-list HEAD --skip=3 --max-count=10 | wc -l) = 2 &&
+    test $(git-rev-list HEAD --skip=5 --max-count=10 | wc -l) = 0 &&
+    test $(git-rev-list HEAD --skip=10 --max-count=10 | wc -l) = 0
+'
+
+test_done
index 964010e764a1f6340ae4a5cd300ef67cd6babfc4..69b18f7d8160d0acb2bf222a9478674cfc21634f 100644 (file)
@@ -59,18 +59,18 @@ GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
 test_expect_failure "combined merge conflicts" "git merge -m final G"
 
 cat > expect << EOF
-<<<<<<< HEAD/a1
+<<<<<<< HEAD:a1
 F
 =======
 G
->>>>>>> 26f86b677eb03d4d956dbe108b29cb77061c1e73/a1
+>>>>>>> G:a1
 EOF
 
 test_expect_success "result contains a conflict" "diff -u expect a1"
 
 git ls-files --stage > out
 cat > expect << EOF
-100644 f16f906ab60483c100d1241dfc39868de9ec9fcb 1      a1
+100644 da056ce14a2241509897fa68bb2b3b6e6194ef9e 1      a1
 100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2      a1
 100644 fd7923529855d0b274795ae3349c5e0438333979 3      a1
 EOF
index 0edf19e48d20bf03dc567a3be73a03892f143632..c22fe47213a8fccbb954aef4b175d2ea1a920e16 100755 (executable)
@@ -19,180 +19,176 @@ esac
 
 echo 'define NO_SVN_TESTS to skip git-svn tests'
 
-mkdir import
-cd import
-
-echo foo > foo
-if test -z "$NO_SYMLINK"
-then
-       ln -s foo foo.link
-fi
-mkdir -p dir/a/b/c/d/e
-echo 'deep dir' > dir/a/b/c/d/e/file
-mkdir -p bar
-echo 'zzz' > bar/zzz
-echo '#!/bin/sh' > exec.sh
-chmod +x exec.sh
-svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
-
-cd ..
-rm -rf import
-
 test_expect_success \
-    'initialize git-svn' \
-    "git-svn init $svnrepo"
+    'initialize git-svn' "
+       mkdir import &&
+       cd import &&
+       echo foo > foo &&
+       if test -z '$NO_SYMLINK'
+       then
+               ln -s foo foo.link
+       fi
+       mkdir -p dir/a/b/c/d/e &&
+       echo 'deep dir' > dir/a/b/c/d/e/file &&
+       mkdir bar &&
+       echo 'zzz' > bar/zzz &&
+       echo '#!/bin/sh' > exec.sh &&
+       chmod +x exec.sh &&
+       svn import -m 'import for git-svn' . $svnrepo >/dev/null &&
+       cd .. &&
+       rm -rf import &&
+       git-svn init $svnrepo"
 
 test_expect_success \
     'import an SVN revision into git' \
     'git-svn fetch'
 
-test_expect_success "checkout from svn" "svn co $svnrepo $SVN_TREE"
+test_expect_success "checkout from svn" "svn co $svnrepo '$SVN_TREE'"
 
 name='try a deep --rmdir with a commit'
-git checkout -f -b mybranch remotes/git-svn
-mv dir/a/b/c/d/e/file dir/file
-cp dir/file file
-git update-index --add --remove dir/a/b/c/d/e/file dir/file file
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch &&
-     svn up $SVN_TREE &&
-     test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
+test_expect_success "$name" "
+       git checkout -f -b mybranch remotes/git-svn &&
+       mv dir/a/b/c/d/e/file dir/file &&
+       cp dir/file file &&
+       git update-index --add --remove dir/a/b/c/d/e/file dir/file file &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch &&
+       svn up '$SVN_TREE' &&
+       test -d '$SVN_TREE'/dir && test ! -d '$SVN_TREE'/dir/a"
 
 
 name='detect node change from file to directory #1'
-mkdir dir/new_file
-mv dir/file dir/new_file/file
-mv dir/new_file dir/file
-git update-index --remove dir/file
-git update-index --add dir/file/file
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch' \
-    || true
+test_expect_failure "$name" "
+       mkdir dir/new_file &&
+       mv dir/file dir/new_file/file &&
+       mv dir/new_file dir/file &&
+       git update-index --remove dir/file &&
+       git update-index --add dir/file/file &&
+       git commit -m '$name'  &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch" || true
 
 
 name='detect node change from directory to file #1'
-rm -rf dir $GIT_DIR/index
-git checkout -f -b mybranch2 remotes/git-svn
-mv bar/zzz zzz
-rm -rf bar
-mv zzz bar
-git update-index --remove -- bar/zzz
-git update-index --add -- bar
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
-    || true
+test_expect_failure "$name" "
+       rm -rf dir '$GIT_DIR'/index &&
+       git checkout -f -b mybranch2 remotes/git-svn &&
+       mv bar/zzz zzz &&
+       rm -rf bar &&
+       mv zzz bar &&
+       git update-index --remove -- bar/zzz &&
+       git update-index --add -- bar &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch2" || true
 
 
 name='detect node change from file to directory #2'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch3 remotes/git-svn
-rm bar/zzz
-git-update-index --remove bar/zzz
-mkdir bar/zzz
-echo yyy > bar/zzz/yyy
-git-update-index --add bar/zzz/yyy
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
-    || true
+test_expect_failure "$name" "
+       rm -f '$GIT_DIR'/index &&
+       git checkout -f -b mybranch3 remotes/git-svn &&
+       rm bar/zzz &&
+       git-update-index --remove bar/zzz &&
+       mkdir bar/zzz &&
+       echo yyy > bar/zzz/yyy &&
+       git-update-index --add bar/zzz/yyy &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch3" || true
 
 
 name='detect node change from directory to file #2'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch4 remotes/git-svn
-rm -rf dir
-git update-index --remove -- dir/file
-touch dir
-echo asdf > dir
-git update-index --add -- dir
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
-    || true
+test_expect_failure "$name" "
+       rm -f '$GIT_DIR'/index &&
+       git checkout -f -b mybranch4 remotes/git-svn &&
+       rm -rf dir &&
+       git update-index --remove -- dir/file &&
+       touch dir &&
+       echo asdf > dir &&
+       git update-index --add -- dir &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch4" || true
 
 
 name='remove executable bit from a file'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch5 remotes/git-svn
-chmod -x exec.sh
-git update-index exec.sh
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-     svn up $SVN_TREE &&
-     test ! -x $SVN_TREE/exec.sh"
+test_expect_success "$name" "
+       rm -f '$GIT_DIR'/index &&
+       git checkout -f -b mybranch5 remotes/git-svn &&
+       chmod -x exec.sh &&
+       git update-index exec.sh &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch5 &&
+       svn up '$SVN_TREE' &&
+       test ! -x '$SVN_TREE'/exec.sh"
 
 
 name='add executable bit back file'
-chmod +x exec.sh
-git update-index exec.sh
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-     svn up $SVN_TREE &&
-     test -x $SVN_TREE/exec.sh"
-
+test_expect_success "$name" "
+       chmod +x exec.sh &&
+       git update-index exec.sh &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch5 &&
+       svn up '$SVN_TREE' &&
+       test -x '$SVN_TREE'/exec.sh"
 
 
 if test -z "$NO_SYMLINK"
 then
        name='executable file becomes a symlink to bar/zzz (file)'
-       rm exec.sh
-       ln -s bar/zzz exec.sh
-       git update-index exec.sh
-       git commit -m "$name"
 
-       test_expect_success "$name" \
-           "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -L $SVN_TREE/exec.sh"
+       test_expect_success "$name" "
+               rm exec.sh &&
+               ln -s bar/zzz exec.sh &&
+               git update-index exec.sh &&
+               git commit -m '$name' &&
+               git-svn set-tree --find-copies-harder --rmdir \
+                       remotes/git-svn..mybranch5 &&
+               svn up '$SVN_TREE' &&
+               test -L '$SVN_TREE'/exec.sh"
 
        name='new symlink is added to a file that was also just made executable'
-       chmod +x bar/zzz
-       ln -s bar/zzz exec-2.sh
-       git update-index --add bar/zzz exec-2.sh
-       git commit -m "$name"
 
-       test_expect_success "$name" \
-           "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -x $SVN_TREE/bar/zzz &&
-            test -L $SVN_TREE/exec-2.sh"
+       test_expect_success "$name" "
+               chmod +x bar/zzz &&
+               ln -s bar/zzz exec-2.sh &&
+               git update-index --add bar/zzz exec-2.sh &&
+               git commit -m '$name' &&
+               git-svn set-tree --find-copies-harder --rmdir \
+                       remotes/git-svn..mybranch5 &&
+               svn up '$SVN_TREE' &&
+               test -x '$SVN_TREE'/bar/zzz &&
+               test -L '$SVN_TREE'/exec-2.sh"
 
        name='modify a symlink to become a file'
-       echo git help > help || true
-       rm exec-2.sh
-       cp help exec-2.sh
-       git update-index exec-2.sh
-       git commit -m "$name"
-
-       test_expect_success "$name" \
-           "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -f $SVN_TREE/exec-2.sh &&
-            test ! -L $SVN_TREE/exec-2.sh &&
-            diff -u help $SVN_TREE/exec-2.sh"
+       test_expect_success "$name" "
+               echo git help > help || true &&
+               rm exec-2.sh &&
+               cp help exec-2.sh &&
+               git update-index exec-2.sh &&
+               git commit -m '$name' &&
+               git-svn set-tree --find-copies-harder --rmdir \
+                       remotes/git-svn..mybranch5 &&
+               svn up '$SVN_TREE' &&
+               test -f '$SVN_TREE'/exec-2.sh &&
+               test ! -L '$SVN_TREE'/exec-2.sh &&
+               diff -u help $SVN_TREE/exec-2.sh"
 fi
 
 
 if test "$have_utf8" = t
 then
        name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
-       echo '# hello' >> exec-2.sh
-       git update-index exec-2.sh
-       git commit -m 'éï∏'
-       export LC_ALL="$GIT_SVN_LC_ALL"
-       test_expect_success "$name" "git-svn set-tree HEAD"
+       LC_ALL="$GIT_SVN_LC_ALL"
+       export LC_ALL
+       test_expect_success "$name" "
+               echo '# hello' >> exec-2.sh &&
+               git update-index exec-2.sh &&
+               git commit -m 'éï∏' &&
+               git-svn set-tree HEAD"
        unset LC_ALL
 else
        echo "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)"
index 0b906caa98f3d4deced75d196d969d7216d31961..9b04f2d69c6dc054e8e5403062fb7ef5d2117328 100644 (file)
@@ -8,6 +8,10 @@
 #
 # To enable this hook, make this file executable.
 
+# Uncomment the below to add a Signed-off-by line to the message.
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
 # This example catches duplicate Signed-off-by lines.
 
 test "" = "$(grep '^Signed-off-by: ' "$1" |
diff --git a/utf8.c b/utf8.c
new file mode 100644 (file)
index 0000000..8fa6257
--- /dev/null
+++ b/utf8.c
@@ -0,0 +1,278 @@
+#include "git-compat-util.h"
+#include "utf8.h"
+
+/* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */
+
+struct interval {
+  int first;
+  int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(wchar_t ucs, const struct interval *table, int max) {
+       int min = 0;
+       int mid;
+
+       if (ucs < table[0].first || ucs > table[max].last)
+               return 0;
+       while (max >= min) {
+               mid = (min + max) / 2;
+               if (ucs > table[mid].last)
+                       min = mid + 1;
+               else if (ucs < table[mid].first)
+                       max = mid - 1;
+               else
+                       return 1;
+       }
+
+       return 0;
+}
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ *    - The null character (U+0000) has a column width of 0.
+ *
+ *    - Other C0/C1 control characters and DEL will lead to a return
+ *      value of -1.
+ *
+ *    - Non-spacing and enclosing combining characters (general
+ *      category code Mn or Me in the Unicode database) have a
+ *      column width of 0.
+ *
+ *    - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ *    - Other format characters (general category code Cf in the Unicode
+ *      database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ *    - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ *      have a column width of 0.
+ *
+ *    - Spacing characters in the East Asian Wide (W) or East Asian
+ *      Full-width (F) category as defined in Unicode Technical
+ *      Report #11 have a column width of 2.
+ *
+ *    - All remaining characters (including all printable
+ *      ISO 8859-1 and WGL4 characters, Unicode control characters,
+ *      etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+static int wcwidth(wchar_t ch)
+{
+       /*
+        * Sorted list of non-overlapping intervals of non-spacing characters,
+        * generated by
+        *   "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c".
+        */
+       static const struct interval combining[] = {
+               { 0x0300, 0x0357 }, { 0x035D, 0x036F }, { 0x0483, 0x0486 },
+               { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 },
+               { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+               { 0x05C4, 0x05C4 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 },
+               { 0x064B, 0x0658 }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 },
+               { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F },
+               { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
+               { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
+               { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 },
+               { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 },
+               { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 },
+               { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 },
+               { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 },
+               { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 },
+               { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 },
+               { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 },
+               { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 },
+               { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 },
+               { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 },
+               { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 },
+               { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+               { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+               { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+               { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+               { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+               { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+               { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+               { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+               { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+               { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x1712, 0x1714 },
+               { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 },
+               { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 },
+               { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D },
+               { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 },
+               { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x200B, 0x200F },
+               { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F },
+               { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A },
+               { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 },
+               { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 },
+               { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B },
+               { 0x1D1AA, 0x1D1AD }, { 0xE0001, 0xE0001 },
+               { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF }
+       };
+
+       /* test for 8-bit control characters */
+       if (ch == 0)
+               return 0;
+       if (ch < 32 || (ch >= 0x7f && ch < 0xa0))
+               return -1;
+
+       /* binary search in table of non-spacing characters */
+       if (bisearch(ch, combining, sizeof(combining)
+                               / sizeof(struct interval) - 1))
+               return 0;
+
+       /*
+        * If we arrive here, ch is neither a combining nor a C0/C1
+        * control character.
+        */
+
+       return 1 +
+               (ch >= 0x1100 &&
+                    /* Hangul Jamo init. consonants */
+                (ch <= 0x115f ||
+                 ch == 0x2329 || ch == 0x232a ||
+                  /* CJK ... Yi */
+                 (ch >= 0x2e80 && ch <= 0xa4cf &&
+                  ch != 0x303f) ||
+                 /* Hangul Syllables */
+                 (ch >= 0xac00 && ch <= 0xd7a3) ||
+                 /* CJK Compatibility Ideographs */
+                 (ch >= 0xf900 && ch <= 0xfaff) ||
+                 /* CJK Compatibility Forms */
+                 (ch >= 0xfe30 && ch <= 0xfe6f) ||
+                 /* Fullwidth Forms */
+                 (ch >= 0xff00 && ch <= 0xff60) ||
+                 (ch >= 0xffe0 && ch <= 0xffe6) ||
+                 (ch >= 0x20000 && ch <= 0x2fffd) ||
+                 (ch >= 0x30000 && ch <= 0x3fffd)));
+}
+
+/*
+ * This function returns the number of columns occupied by the character
+ * pointed to by the variable start. The pointer is updated to point at
+ * the next character. If it was not valid UTF-8, the pointer is set to NULL.
+ */
+int utf8_width(const char **start)
+{
+       unsigned char *s = (unsigned char *)*start;
+       wchar_t ch;
+
+       if (*s < 0x80) {
+               /* 0xxxxxxx */
+               ch = *s;
+               *start += 1;
+       } else if ((s[0] & 0xe0) == 0xc0) {
+               /* 110XXXXx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] & 0xfe) == 0xc0)
+                       goto invalid;
+               ch = ((s[0] & 0x1f) << 6) | (s[1] & 0x3f);
+               *start += 2;
+       } else if ((s[0] & 0xf0) == 0xe0) {
+               /* 1110XXXX 10Xxxxxx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               (s[2] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) ||
+                               /* surrogate? */
+                               (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) ||
+                               /* U+FFFE or U+FFFF? */
+                               (s[0] == 0xef && s[1] == 0xbf &&
+                                (s[2] & 0xfe) == 0xbe))
+                       goto invalid;
+               ch = ((s[0] & 0x0f) << 12) |
+                       ((s[1] & 0x3f) << 6) | (s[2] & 0x3f);
+               *start += 3;
+       } else if ((s[0] & 0xf8) == 0xf0) {
+               /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               (s[2] & 0xc0) != 0x80 ||
+                               (s[3] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) ||
+                               /* > U+10FFFF? */
+                               (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4)
+                       goto invalid;
+               ch = ((s[0] & 0x07) << 18) | ((s[1] & 0x3f) << 12) |
+                       ((s[2] & 0x3f) << 6) | (s[3] & 0x3f);
+               *start += 4;
+       } else {
+invalid:
+               *start = NULL;
+               return 0;
+       }
+
+       return wcwidth(ch);
+}
+
+int is_utf8(const char *text)
+{
+       while (*text) {
+               if (*text == '\n' || *text == '\t' || *text == '\r') {
+                       text++;
+                       continue;
+               }
+               utf8_width(&text);
+               if (!text)
+                       return 0;
+       }
+       return 1;
+}
+
+static void print_spaces(int count)
+{
+       static const char s[] = "                    ";
+       while (count >= sizeof(s)) {
+               fwrite(s, sizeof(s) - 1, 1, stdout);
+               count -= sizeof(s) - 1;
+       }
+       fwrite(s, count, 1, stdout);
+}
+
+/*
+ * Wrap the text, if necessary. The variable indent is the indent for the
+ * first line, indent2 is the indent for all other lines.
+ */
+void print_wrapped_text(const char *text, int indent, int indent2, int width)
+{
+       int w = indent, assume_utf8 = is_utf8(text);
+       const char *bol = text, *space = NULL;
+
+       for (;;) {
+               char c = *text;
+               if (!c || isspace(c)) {
+                       if (w < width || !space) {
+                               const char *start = bol;
+                               if (space)
+                                       start = space;
+                               else
+                                       print_spaces(indent);
+                               fwrite(start, text - start, 1, stdout);
+                               if (!c) {
+                                       putchar('\n');
+                                       return;
+                               } else if (c == '\t')
+                                       w |= 0x07;
+                               space = text;
+                               w++;
+                               text++;
+                       }
+                       else {
+                               putchar('\n');
+                               text = bol = space + 1;
+                               space = NULL;
+                               w = indent = indent2;
+                       }
+                       continue;
+               }
+               if (assume_utf8)
+                       w += utf8_width(&text);
+               else {
+                       w++;
+                       text++;
+               }
+       }
+}
diff --git a/utf8.h b/utf8.h
new file mode 100644 (file)
index 0000000..a0d7f59
--- /dev/null
+++ b/utf8.h
@@ -0,0 +1,8 @@
+#ifndef GIT_UTF8_H
+#define GIT_UTF8_H
+
+int utf8_width(const char **start);
+int is_utf8(const char *text);
+void print_wrapped_text(const char *text, int indent, int indent2, int len);
+
+#endif
index 08602f522183dc43787616f37cba9b8af4e3dade..6c1f99b149f0800a9c9ab1b45033bc1401a84766 100644 (file)
@@ -102,3 +102,22 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
        }
        return 0;
 }
+
+int read_mmfile(mmfile_t *ptr, const char *filename)
+{
+       struct stat st;
+       FILE *f;
+
+       if (stat(filename, &st))
+               return error("Could not stat %s", filename);
+       if ((f = fopen(filename, "rb")) == NULL)
+               return error("Could not open %s", filename);
+       ptr->ptr = xmalloc(st.st_size);
+       if (fread(ptr->ptr, st.st_size, 1, f) != 1)
+               return error("Could not read %s", filename);
+       fclose(f);
+       ptr->size = st.st_size;
+       return 0;
+}
+
+
index 1346908bea31319aabeabdfd955e2ea9aab37456..1918808081c00daf20a16a1592c0ed9be6d79be4 100644 (file)
@@ -17,5 +17,6 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf);
 int parse_hunk_header(char *line, int len,
                      int *ob, int *on,
                      int *nb, int *nn);
+int read_mmfile(mmfile_t *ptr, const char *filename);
 
 #endif