Git development
 help / color / mirror / Atom feed
* Re: git pull a subtree, embedded trees
From: Jakub Narebski @ 2006-09-18  5:58 UTC (permalink / raw)
  To: git
In-Reply-To: <450E3399.5070601@sgi.com>

Timothy Shimmin wrote:

>>> * Are there any tools for dumping out the contents of the
>>> git objects in the .git/objects directory.
>>> By dumping out, I mean an ascii representation of the data
>>> fields for the commit and tree objects in particular.
>>> I've written a simple small program to dump out the index
>>> entries (cache entries).
>> 
>> git-cat-file -p
>> 
> Excellent, thanks. (looks like the option is undocumented - secret option:)

It looks not:

usage: git-cat-file [-t|-s|-e|-p|<type>] <sha1>

       -p     Pretty-print the contents of <object> based on its type.

> So I added this to a script which walks over the objects directory,
> to work out what all the object ids are so I can apply git-cat-file
> to all the objects on my test directory.
> I guess this will fall down if the objects are stored in a pack :)
> I'll have to look and see how to extract all the object ids using
> some command.

git-rev-parse and/or git-rev-list (the latter with --objects option) is your
friend. And there is git-ls-tree command which list sha1 of blobs (files)
and trees (subdirectories) for specific revision (specified tree).
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply

* Re: git pull a subtree, embedded trees
From: Timothy Shimmin @ 2006-09-18  5:57 UTC (permalink / raw)
  To: Shawn Pearce; +Cc: git
In-Reply-To: <20060913150028.GB29608@spearce.org>

Hi Shawn,

Shawn Pearce wrote:
> Tim Shimmin <tes@sgi.com> wrote:
>> I've written a simple small program to dump out the index
>> entries (cache entries).
> 
> `git-ls-files --stage` also dumps a number of those details, though
> it doesn't dump every available field.
> 
Thanks, that's handy.
However, when going through the core-tutorial (copying repository section)
and populating the index from the .git objects using git-read-tree,
it was nice to see all the stat fields using my program.
These fields were empty at that stage, of course, (it was nice to see it)
until I had populated the workarea using git-checkout-index.
But generally, git-ls-files --stage would be fine.

>> I just want to see what is exactly stored in the .git
>> binary files and how they change when I do various git
>> operations.
> 
> You may want to review some of the material in
> Documentation/core-tutorial.txt and Documentation/technical.
> These documents try to describe some of the formats but reviewing
> them now it looks like there's still some additional information
> that could be written down.
> 
Thanks. I've been going through the core-tutorial.
It's great.

Cheers,
Tim.

^ permalink raw reply

* Re: What's in git.git
From: Junio C Hamano @ 2006-09-18  5:50 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <eelbd2$56s$1@sea.gmane.org>

Jakub Narebski <jnareb@gmail.com> writes:

> Junio C Hamano wrote:
>
>>   - An experimental git-for-each-ref command to help language
>>     bindings to get information on many refs at once.  Hopefully
>>     Jakub can teach gitweb to use it to speed things up.
>
> I use 'origin' (or 'next') version of gitweb, while using _released_
> version of git (git-core-1.4.2.1-1.i386.rpm). So at least for now 
> I wouldn't be able to _test_ the git-for-each-ref.

That's not a good excuse, though.  It means you cannot propose
new core-side support that only gitweb would benefit from
initially, since we will not add new stuff to the core that does
not have real users, and new stuff in the core must be cooked in
"next" before it is proven to be useful and correct.

^ permalink raw reply

* Re: git pull a subtree, embedded trees
From: Timothy Shimmin @ 2006-09-18  5:50 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <ee945j$h3u$1@sea.gmane.org>

Hi Jakub,

Jakub Narebski wrote:
> Tim Shimmin wrote:
> 
>> I'm new to git and have a couple of novice questions.
>>
>> * Is it possible to only pull in a subtree from
>> a repository.
> 
> I assume that by pull you mean checkout...
> 
> I think it is possible (try git-read-tree with --prefix option, 
> and select subtree by giving either it's sha1, or e.g.
> HEAD:<path> form), but not easy to do. Git revisions are 
> snapshots of the whole project (the revisions are states of
> the whole project).
> 
I'm not sure if that was what I was wanting.

I'm just starting to understand git better (I think:).
It seems like it is about having object snapshots.
We have snapshots of files (blobs) and snapshots of a directory,
tree objects which reference other trees and blob snapshots,
and then we link the snapshots in time using commit objects.
So every time we do a "git-update-index file" we create a new blob
in the object directory and every time we do a "git-write-tree" we
create tree objects in the database (.git/object/xx/xxxx....).
So at these snapshot points, do we just keep adding more and more objects?
I'm used to rcs and sccs, where we just keep diffs for file history,
we don't do that here do we?; we keep the whole snapshot but in compressed
form. (And then we have a packed form too.)

So trying to understand your suggestion and the command:
Given a tree object in our object database, we can update our
index with the tree objects but they will be stored in the index
with entry names which have prepended to them the given "prefix/".
We can then use git-checkout-index to populate our workarea
with the prefix/ files and dirs.
So how do I get the foreign tree objects into the database;
just copy them in?
And this works with prefix/ dir not already existing in workarea.
Hmmmmm....


>> Moreover, is it possible to have a subtree based on another
>> repository.
> 
> It is possible. For example, make empty directory <subproject>
> somewhere, add this directory, or just all the files in it
> either to .gitignore or .git/info/excludes file, then clone
> the other project (subproject) to this place. You would have
> the following directory structure
> 
>   /
>   dir1
>   dir2
>   dir2/subdir
>   subproject
>   subproject/.git
>   subproject/subprojectsubdir
>   ...
> 
This could be handy.
Looks like by using .gitignore, I can check the file in.
(So the ingore/excludes are used by git scripts which call
git-ls-files --others.)


>> * Are there any tools for dumping out the contents of the
>> git objects in the .git/objects directory.
>> By dumping out, I mean an ascii representation of the data
>> fields for the commit and tree objects in particular.
>> I've written a simple small program to dump out the index
>> entries (cache entries).
> 
> git-cat-file -p
> 
Excellent, thanks. (looks like the option is undocumented - secret option:)
So I added this to a script which walks over the objects directory,
to work out what all the object ids are so I can apply git-cat-file
to all the objects on my test directory.
I guess this will fall down if the objects are stored in a pack :)
I'll have to look and see how to extract all the object ids using
some command.


What I have is an existing full tree with a subproject directory.
And then I have a separate tree just for the subproject.
The development happens in the subproject tree.
At certain points we want to update the existing full tree's subproject
directory with the work we have done in the subproject tree.
At these points I'd like to effectively copy over the new tree objects
and blobs to the full tree, but I guess I'd need new commits
(which are based on the new commits of the subproject tree which would
include their commit messages)
which refer to new higher level tree objects (which refer down
to my subproject tree objects).
This presupposes that no other outside changes happened to subproject
in the full tree - since I'm just copying over objects.
Probably should be merging, just in case.
Does this sound too confusing and awkward? :)

--Tim

^ permalink raw reply

* Re: [PATCH] Remove branch by putting a null sha1 into the ref file.
From: Junio C Hamano @ 2006-09-18  5:47 UTC (permalink / raw)
  To: Christian Couder; +Cc: git
In-Reply-To: <20060918065429.6f4de06e.chriscool@tuxfamily.org>

Christian Couder <chriscool@tuxfamily.org> writes:

> @@ -43,7 +46,8 @@ If you are sure you want to delete it, r
>  	    ;;
>  	esac
>  	rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
> -	rm -f "$GIT_DIR/refs/heads/$branch_name"
> +	echo $NULL_SHA1 > "$GIT_DIR/refs/heads/$branch_name" || \
> +	    die "Failed to delete branch '$branch_name' !"

Don't you need mkdir -p somewhere?

> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
> index 5b04efc..150dfdc 100755
> --- a/t/t3200-branch.sh
> +++ b/t/t3200-branch.sh
> @@ -47,7 +47,7 @@ test_expect_success \
>  test_expect_success \
>      'git branch -d d/e/f should delete a branch and a log' \
>  	'git-branch -d d/e/f &&
> -	 test ! -f .git/refs/heads/d/e/f &&
> +	 ! git-show-ref --verify --quiet -- "refs/heads/d/e/f" &&
>  	 test ! -f .git/logs/refs/heads/d/e/f'

I am old-fashioned and it makes me think twice when I see people
do "! command" in shell.  Bash and dash has support for it, and
opengroup has it in its base specification, so probably it is
Ok.

As usual, Solaris /bin/sh does not grok it ;-).

^ permalink raw reply

* Re: What's in git.git
From: Jakub Narebski @ 2006-09-18  5:48 UTC (permalink / raw)
  To: git
In-Reply-To: <7vu035u4c3.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano wrote:

>     We really need some test suites for gitweb.

Could we use the git.git repository itself for testing gitweb?
At least checking if there are any errors or warnings?

The problem with test suite is that you really need _two_ tests;
first if there are any errors or warnings, then if page looks like
it should. The first can be done by simply running gitweb with
at least the following enviromental variables set:
  export GATEWAY_INTERFACE="CGI/1.1"
  export HTTP_ACCEPT="*/*"
  export REQUEST_METHOD="GET"
  export QUERY_STRING=""$1""
The second should be done by looking at gitweb output.
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply

* Re: What's in git.git
From: Jakub Narebski @ 2006-09-18  5:39 UTC (permalink / raw)
  To: git
In-Reply-To: <7vu035u4c3.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano wrote:

>   - An experimental git-for-each-ref command to help language
>     bindings to get information on many refs at once.  Hopefully
>     Jakub can teach gitweb to use it to speed things up.

I use 'origin' (or 'next') version of gitweb, while using _released_
version of git (git-core-1.4.2.1-1.i386.rpm). So at least for now 
I wouldn't be able to _test_ the git-for-each-ref.

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply

* What's in git.git
From: Junio C Hamano @ 2006-09-18  5:33 UTC (permalink / raw)
  To: git
In-Reply-To: <7vk64bnnxl.fsf@assigned-by-dhcp.cox.net>

* The 'maint' branch has this since the last announcement (v1.4.2.1).

   - Liu Yubao fixed duplicate xmalloc in builtin-add.

   - "git-am --skip" incorrectly insisted that its standard
     input to be connected to a tty.  Fixed.


* The 'master' branch has these since the last announcement.

  - http-fetch from a repository that uses alternates to borrow
    from neighbouring repositories were quite broken for some
    time now.  This has been fixed (this fix is also in
    v1.4.2.1).

  - Andy Whitcroft taught send-pack to use git-rev-list --stdin
    so that we can deal with repositories with massive number
    of refs more efficiently.

  - A handful clean-ups, fixes and documentation updates by
    Christian Couder, Dmitry V. Levin, Jonas Fonseca and Linus.

  - Franck Bui-Huu and Rene Scharfe added 'git-archive' command,
    that will eventually supersede 'git-tar-tree' and
    'git-zip-tree'.

    I think zip-tree can be deprecated without hurting too many
    users, judging from its short existence, but I suspect that
    deprecating tar-tree needs to be done very carefully.
    Perhaps we should drop "tar-tree --remote" and "upload-tar",
    but keep tar-tree but make it internally a synonym for
    "archive --format=tar".  We should also update our toplevel
    Makefile to use git-archive.

  - Jakub Narebski continues improving gitweb with help from 
    Martin Waitz, and Matthias Lederhofer.

    We really need some test suites for gitweb.

  - Jeff King rewrote run_status() shell function used in
    git-commit and git-status in C, and made it colorful while
    he was at it.  Johannes Schindelin taught it --untracked.

  - unpack-objects with "-r" now makes the best effort to
    recover objects from a corrupt packfile.

  - apply does not need --binary anymore to take a binary patch.

  - diff --binary does not produce full 40-byte index lines
    unless necessary.

  - pack-objects learned --revs option, which lets it not to
    rely on rev-list.  Instead of taking the list of objects to
    pack from the standard input, it can read the list of rev
    parameters and run rev-list logic internally.

  - rev-list learned --unpacked=<existing pack> option.

  - Linus taught git-grep "-h" option to suppress filename
    output.

  - "git-am --skip" incorrectly insisted that its standard
    input to be connected to a tty.  Fixed.

  - "git-apply" learned to handle --unified=0 patches more
    gracefully by allowing some sanity checks that cannot be
    done with such patches to be disabled.

  - Sasha Khapyorsky noticed that http-fetch commit walker can
    almost deal with ftp:// transport already, and added
    minimum updates to support it.


* The 'next' branch, in addition, has these.

  - Git.pm is on hold, waiting for stripping out Git.xs part before
    going forward.

  - Linus introduced packed refs and taught the core about
    them.  Christian Couder taught git-branch about them and
    Jeff King taught wt-status about it.

    There are still some things that are broken which need to
    be addressed before this series is pushed out to "master".
    I offhand know of these two but there probably are others:

    - "git branch -d" does not work.
    - "git ls-remote rsync://" does not work.

  - An experimental git-for-each-ref command to help language
    bindings to get information on many refs at once.  Hopefully
    Jakub can teach gitweb to use it to speed things up.


* The 'pu' branch, in addition, has these.

  - Jon Loeliger's git-daemon virtual hosting patch; this will be
    dropped and replaced with his updated version.

  - "git log --author=foo", "git log --grep=pattern" support.

  - I haven't started cleaning up the para-walk changes yet; they
    are still in the form of a messy 10-series patchset.  When I
    find time I'd like to rewrite diff-index with it and see how
    well it performs.

^ permalink raw reply

* [PATCH] Remove branch by putting a null sha1 into the ref file.
From: Christian Couder @ 2006-09-18  4:54 UTC (permalink / raw)
  To: Junio Hamano; +Cc: git

With the new packed ref file format from Linus, this should be
the new way to remove a branch.

"refs.c" is fixed so that a null sha1 for a deleted branch does
not result in "refs/head/deleted does not point to a valid
commit object!" messages.

"t/t3200-branch.sh" is fixed so that it uses git-show-ref
instead of checking that the ref does not exist when a branch
is deleted.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 git-branch.sh     |    6 +++++-
 refs.c            |    2 ++
 t/t3200-branch.sh |    2 +-
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/git-branch.sh b/git-branch.sh
index 2600e9c..cb55880 100755
--- a/git-branch.sh
+++ b/git-branch.sh
@@ -10,6 +10,9 @@ SUBDIRECTORY_OK='Yes'
 
 headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
 
+# Fourty 0s.
+NULL_SHA1="0000000000000000000000000000000000000000" 
+
 delete_branch () {
     option="$1"
     shift
@@ -43,7 +46,8 @@ If you are sure you want to delete it, r
 	    ;;
 	esac
 	rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
-	rm -f "$GIT_DIR/refs/heads/$branch_name"
+	echo $NULL_SHA1 > "$GIT_DIR/refs/heads/$branch_name" || \
+	    die "Failed to delete branch '$branch_name' !"
 	echo "Deleted branch $branch_name."
     done
     exit 0
diff --git a/refs.c b/refs.c
index 5e65314..76d8d0e 100644
--- a/refs.c
+++ b/refs.c
@@ -162,6 +162,8 @@ static int do_for_each_ref(const char *b
 				error("%s points nowhere!", path);
 				continue;
 			}
+			if (is_null_sha1(sha1))
+				continue; /* Ignore deleted refs. */
 			if (!has_sha1_file(sha1)) {
 				error("%s does not point to a valid "
 				      "commit object!", path);
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 5b04efc..150dfdc 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -47,7 +47,7 @@ test_expect_success \
 test_expect_success \
     'git branch -d d/e/f should delete a branch and a log' \
 	'git-branch -d d/e/f &&
-	 test ! -f .git/refs/heads/d/e/f &&
+	 ! git-show-ref --verify --quiet -- "refs/heads/d/e/f" &&
 	 test ! -f .git/logs/refs/heads/d/e/f'
 
 cat >expect <<EOF
-- 
1.4.2.1.g4251-dirty

^ permalink raw reply related

* Re: git-repack: Outof memory
From: Junio C Hamano @ 2006-09-18  2:47 UTC (permalink / raw)
  To: Shawn Pearce; +Cc: git, Dongsheng Song
In-Reply-To: <20060918002357.GA19727@spearce.org>

Shawn Pearce <spearce@spearce.org> writes:

> ...  I fear that
> you are going to bump up against address space limitations soon
> on 32 bit systems.  Then you will bump up against the 4 GiB pack
> file size limit.  Which means you will need to use several packs
> and avoid the '-a' flag when calling git-repack.

I presume that you are hinting that we would need to update
git-repack so that it is still useful without --all.

Which means "pack-objects --unpacked=active-pack" would need to
be pushed out so that git-repack can be updated to do the
"archived ones and repacking the active pack" we talked about
earlier.

I think pack-objects --unpacked=active-pack is ready, so I'll
push it out.

^ permalink raw reply

* Re: [PATCH] Contributed bash completion support for core Git tools.
From: Shawn Pearce @ 2006-09-18  1:18 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vodtex9xm.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano <junkio@cox.net> wrote:
> Shawn Pearce <spearce@spearce.org> writes:
> 
> > Since these are completion routines only for tools shipped with
> > core Git and since bash is a popular shell on many of the native
> > core Git platforms (Linux, Mac OS X, Solaris, BSD) including these
> > routines as part of the stock package would probably be convienent
> > for many users.
> >
> > Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
> >
> >  contrib/bash-git-completion.sh |  330 +++++++++++++++...
> >  1 files changed, 330 insertions(+), 0 deletions(-)
> 
> Hmph.  I tried this and found that I like "git pull ."
> completion quite a bit.  Having said that:

I got hooked very fast on the completion for git pull, git checkout
and git fetch, especially on my local network where setting up
the SSH connection for a remote git-ls-remote isn't that much
of a performance hit.  Trying to use ref completion on the Git
repository itself on kernel.org was quite horrible.  But it does
(sort of) beat doing an ls-remote first.
 
>  * If many people like it (like me), this may deserve to be
>    outside contrib/
> 
>  * Otherwise, it would probably be better to place it in either
>    contrib/bash/git-completion.sh (with potentially other
>    bash-related things not just completion, but I do not know
>    offhand what other kind of hooks would be useful) or
>    contrib/completion/git-completion.bash (possibly with
>    completion for other shells).

I'm not sure there are too many other things to hook into bash in
addition to completion so contrib/completion/git-completion.bash may
be the better location, assuming it doesn't graduate out of contrib/.

-- 
Shawn.

^ permalink raw reply

* Re: [PATCH] Contributed bash completion support for core Git tools.
From: Junio C Hamano @ 2006-09-18  1:03 UTC (permalink / raw)
  To: Shawn Pearce; +Cc: git
In-Reply-To: <20060918004831.GA19851@spearce.org>

Shawn Pearce <spearce@spearce.org> writes:

> Since these are completion routines only for tools shipped with
> core Git and since bash is a popular shell on many of the native
> core Git platforms (Linux, Mac OS X, Solaris, BSD) including these
> routines as part of the stock package would probably be convienent
> for many users.
>
> Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
>
>  contrib/bash-git-completion.sh |  330 +++++++++++++++...
>  1 files changed, 330 insertions(+), 0 deletions(-)

Hmph.  I tried this and found that I like "git pull ."
completion quite a bit.  Having said that:

 * If many people like it (like me), this may deserve to be
   outside contrib/

 * Otherwise, it would probably be better to place it in either
   contrib/bash/git-completion.sh (with potentially other
   bash-related things not just completion, but I do not know
   offhand what other kind of hooks would be useful) or
   contrib/completion/git-completion.bash (possibly with
   completion for other shells).

^ permalink raw reply

* [PATCH] Contributed bash completion support for core Git tools.
From: Shawn Pearce @ 2006-09-18  0:48 UTC (permalink / raw)
  To: git

This is a set of bash completion routines for many of the
popular core Git tools.  I wrote these routines from scratch
after reading the git-compl and git-compl-lib routines available
from the gitcompletion package at http://gitweb.hawaga.org.uk/
and found those to be lacking in functionality for some commands.
Consequently there may be some similarities but many differences.

Since these are completion routines only for tools shipped with
core Git and since bash is a popular shell on many of the native
core Git platforms (Linux, Mac OS X, Solaris, BSD) including these
routines as part of the stock package would probably be convienent
for many users.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 I put this together rather quickly today so although I find it
 to work well for me and be useful, others might not.  User be
 warned.

 In particular I have found the completion of remote refs and
 paths within 'ref:foo/path' style arguments to be very useful,
 even if there is some (mild) latency involved.

 contrib/bash-git-completion.sh |  330 ++++++++++++++++++++++++++++++++++++++++
 1 files changed, 330 insertions(+), 0 deletions(-)

diff --git a/contrib/bash-git-completion.sh b/contrib/bash-git-completion.sh
new file mode 100644
index 0000000..4800185
--- /dev/null
+++ b/contrib/bash-git-completion.sh
@@ -0,0 +1,330 @@
+#
+# bash completion support for core Git.
+#
+# Copyright (C) 2006 Shawn Pearce
+# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/).
+#
+# The contained completion routines provide support for completing:
+#
+#    *) local and remote branch names
+#    *) local and remote tag names
+#    *) .git/remotes file names
+#    *) git 'subcommands'
+#    *) tree paths within 'ref:path/to/file' expressions
+# 
+# To use these routines:
+#
+#    1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
+#    2) Added the following line to your .bashrc:
+#        source ~/.git-completion.sh
+#
+
+__git_refs ()
+{
+	local cmd i is_hash=y
+	if [ -d "$1" ]; then
+		cmd=git-peek-remote
+	else
+		cmd=git-ls-remote
+	fi
+	for i in $($cmd "$1" 2>/dev/null); do
+		case "$is_hash,$i" in
+		y,*) is_hash=n ;;
+		n,*^{}) is_hash=y ;;
+		n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
+		n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+		n,*) is_hash=y; echo "$i" ;;
+		esac
+	done
+}
+
+__git_refs2 ()
+{
+	local cmd i is_hash=y
+	if [ -d "$1" ]; then
+		cmd=git-peek-remote
+	else
+		cmd=git-ls-remote
+	fi
+	for i in $($cmd "$1" 2>/dev/null); do
+		case "$is_hash,$i" in
+		y,*) is_hash=n ;;
+		n,*^{}) is_hash=y ;;
+		n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;;
+		n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;;
+		n,*) is_hash=y; echo "$i:$i" ;;
+		esac
+	done
+}
+
+__git_remotes ()
+{
+	local i REVERTGLOB=$(shopt -p nullglob)
+	shopt -s nullglob
+	for i in .git/remotes/*; do
+		echo ${i#.git/remotes/}
+	done
+	$REVERTGLOB
+}
+
+__git_complete_file ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	case "$cur" in
+	?*:*)
+		local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')"
+		cur="$(echo "$cur" | sed 's,^.*:,,')"
+		case "$cur" in
+		?*/*)
+			pfx="$(echo "$cur" | sed 's,/[^/]*$,,')"
+			cur="$(echo "$cur" | sed 's,^.*/,,')"
+			ls="$ref:$pfx"
+			pfx="$pfx/"
+			;;
+		*)
+			ls="$ref"
+			;;
+	    esac	    
+		COMPREPLY=($(compgen -P "$pfx" \
+			-W "$(git-ls-tree "$ls" \
+				| sed '/^100... blob /s,^.*	,,
+				       /^040000 tree /{
+				           s,^.*	,,
+				           s,$,/,
+				       }
+				       s/^.*	//')" \
+			-- "$cur"))
+		;;
+	*)
+		COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+		;;
+	esac
+}
+
+_git_branch ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur"))
+}
+
+_git_cat_file ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	case "${COMP_WORDS[0]},$COMP_CWORD" in
+	git-cat-file*,1)
+		COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+		;;
+	git,2)
+		COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+		;;
+	*)
+		__git_complete_file
+		;;
+	esac
+}
+
+_git_checkout ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur"))
+}
+
+_git_diff ()
+{
+	__git_complete_file
+}
+
+_git_diff_tree ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur"))
+}
+
+_git_fetch ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+
+	case "${COMP_WORDS[0]},$COMP_CWORD" in
+	git-fetch*,1)
+		COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+		;;
+	git,2)
+		COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+		;;
+	*)
+		case "$cur" in
+		*:*)
+	        cur=$(echo "$cur" | sed 's/^.*://')
+			COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+			;;
+		*)
+			local remote
+			case "${COMP_WORDS[0]}" in
+			git-fetch) remote="${COMP_WORDS[1]}" ;;
+			git)       remote="${COMP_WORDS[2]}" ;;
+			esac
+			COMPREPLY=($(compgen -W "$(__git_refs2 "$remote")" -- "$cur"))
+			;;
+		esac
+		;;
+	esac
+}
+
+_git_ls_remote ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+}
+
+_git_ls_tree ()
+{
+	__git_complete_file
+}
+
+_git_log ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	case "$cur" in
+	*..*)
+		local pfx=$(echo "$cur" | sed 's/\.\..*$/../')
+		cur=$(echo "$cur" | sed 's/^.*\.\.//')
+		COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur"))
+		;;
+	*)
+		COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+		;;
+	esac
+}
+
+_git_merge_base ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+}
+
+_git_pull ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+
+	case "${COMP_WORDS[0]},$COMP_CWORD" in
+	git-pull*,1)
+		COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+		;;
+	git,2)
+		COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+		;;
+	*)
+		local remote
+		case "${COMP_WORDS[0]}" in
+		git-pull)  remote="${COMP_WORDS[1]}" ;;
+		git)       remote="${COMP_WORDS[2]}" ;;
+		esac
+		COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+		;;
+	esac
+}
+
+_git_push ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+
+	case "${COMP_WORDS[0]},$COMP_CWORD" in
+	git-push*,1)
+		COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+		;;
+	git,2)
+		COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+		;;
+	*)
+		case "$cur" in
+		*:*)
+			local remote
+			case "${COMP_WORDS[0]}" in
+			git-push)  remote="${COMP_WORDS[1]}" ;;
+			git)       remote="${COMP_WORDS[2]}" ;;
+			esac
+	        cur=$(echo "$cur" | sed 's/^.*://')
+			COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+			;;
+		*)
+			COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur"))
+			;;
+		esac
+		;;
+	esac
+}
+
+_git_whatchanged ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	case "$cur" in
+	*..*)
+		local pfx=$(echo "$cur" | sed 's/\.\..*$/../')
+		cur=$(echo "$cur" | sed 's/^.*\.\.//')
+		COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur"))
+		;;
+	*)
+		COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+		;;
+	esac
+}
+
+_git ()
+{
+	if [ $COMP_CWORD = 1 ]; then
+		COMPREPLY=($(compgen \
+			-W "--version $(git help -a|egrep '^ ')" \
+			-- "${COMP_WORDS[COMP_CWORD]}"))
+	else
+		case "${COMP_WORDS[1]}" in
+		branch)      _git_branch ;;
+		cat-file)    _git_cat_file ;;
+		checkout)    _git_checkout ;;
+		diff)        _git_diff ;;
+		diff-tree)   _git_diff_tree ;;
+		fetch)       _git_fetch ;;
+		log)         _git_log ;;
+		ls-remote)   _git_ls_remote ;;
+		ls-tree)     _git_ls_tree ;;
+		pull)        _git_pull ;;
+		push)        _git_push ;;
+		whatchanged) _git_whatchanged ;;
+		*)           COMPREPLY=() ;;
+		esac
+	fi
+}
+
+_gitk ()
+{
+	local cur="${COMP_WORDS[COMP_CWORD]}"
+	COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur"))
+}
+
+complete -o default -o nospace -F _git git
+complete -o default            -F _gitk gitk
+complete -o default            -F _git_branch git-branch
+complete -o default -o nospace -F _git_cat_file git-cat-file
+complete -o default            -F _git_checkout git-checkout
+complete -o default -o nospace -F _git_diff git-diff
+complete -o default            -F _git_diff_tree git-diff-tree
+complete -o default -o nospace -F _git_fetch git-fetch
+complete -o default -o nospace -F _git_log git-log
+complete -o default            -F _git_ls_remote git-ls-remote
+complete -o default -o nospace -F _git_ls_tree git-ls-tree
+complete -o default            -F _git_merge_base git-merge-base
+complete -o default -o nospace -F _git_pull git-pull
+complete -o default -o nospace -F _git_push git-push
+complete -o default -o nospace -F _git_whatchanged git-whatchanged
+
+# The following are necessary only for Cygwin, and only are needed
+# when the user has tab-completed the executable name and consequently
+# included the '.exe' suffix.
+#
+complete -o default -o nospace -F _git_cat_file git-cat-file.exe
+complete -o default -o nospace -F _git_diff git-diff.exe
+complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
+complete -o default -o nospace -F _git_log git-log.exe
+complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
+complete -o default            -F _git_merge_base git-merge-base.exe
+complete -o default -o nospace -F _git_push git-push.exe
+complete -o default -o nospace -F _git_whatchanged git-whatchanged.exe
-- 
1.4.2.1.ga817

^ permalink raw reply related

* [PATCH 3/3] revision traversal: --author, --committer, and --grep.
From: Junio C Hamano @ 2006-09-18  0:42 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git, Kai Blin, Jeff King

This adds three options to setup_revisions(), which lets you
filter resulting commits by the author name, the committer name
and the log message with regexp.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 Documentation/git-rev-list.txt |   11 ++++++
 revision.c                     |   76 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 86 insertions(+), 1 deletions(-)

diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 28966ad..00a95e2 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -20,6 +20,7 @@ SYNOPSIS
 	     [ \--stdin ]
 	     [ \--topo-order ]
 	     [ \--parents ]
+	     [ \--(author|committer|grep)=<pattern> ]
 	     [ [\--objects | \--objects-edge] [ \--unpacked ] ]
 	     [ \--pretty | \--header ]
 	     [ \--bisect ]
@@ -154,6 +155,16 @@ limiting may be applied.
 
 	Limit the commits output to specified time range.
 
+--author='pattern', --committer='pattern'::
+
+	Limit the commits output to ones with author/committer
+	header lines that match the specified pattern.
+
+--grep='pattern'::
+
+	Limit the commits output to ones with log message that
+	matches the specified pattern.
+
 --remove-empty::
 
 	Stop when a given path disappears from the tree.
diff --git a/revision.c b/revision.c
index 8fda20d..19f7272 100644
--- a/revision.c
+++ b/revision.c
@@ -673,6 +673,40 @@ int handle_revision_arg(const char *arg,
 	return 0;
 }
 
+static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)
+{
+	char *pat;
+	int patlen, fldlen;
+
+	if (!revs->header_filter) {
+		struct grep_opt *opt = xcalloc(1, sizeof(*opt));
+		opt->status_only = 1;
+		opt->pattern_tail = &(opt->pattern_list);
+		opt->regflags = REG_NEWLINE;
+		revs->header_filter = opt;
+	}
+
+	fldlen = strlen(field);
+	patlen = strlen(pattern);
+	pat = xmalloc(patlen + fldlen + 3);
+	sprintf(pat, "^%s %s", field, pattern);
+	append_grep_pattern(revs->header_filter, pat,
+			    "command line", 0, GREP_PATTERN);
+}
+
+static void add_message_grep(struct rev_info *revs, const char *pattern)
+{
+	if (!revs->message_filter) {
+		struct grep_opt *opt = xcalloc(1, sizeof(*opt));
+		opt->status_only = 1;
+		opt->pattern_tail = &(opt->pattern_list);
+		opt->regflags = REG_NEWLINE;
+		revs->message_filter = opt;
+	}
+	append_grep_pattern(revs->message_filter, pattern,
+			    "command line", 0, GREP_PATTERN);
+}
+
 /*
  * Parse revision information, filling in the "rev_info" structure,
  * and removing the used arguments from the argument list.
@@ -896,6 +930,18 @@ int setup_revisions(int argc, const char
 				revs->relative_date = 1;
 				continue;
 			}
+			if (!strncmp(arg, "--author=", 9)) {
+				add_header_grep(revs, "author", arg+9);
+				continue;
+			}
+			if (!strncmp(arg, "--committer=", 12)) {
+				add_header_grep(revs, "committer", arg+12);
+				continue;
+			}
+			if (!strncmp(arg, "--grep=", 7)) {
+				add_message_grep(revs, arg+7);
+				continue;
+			}
 			opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
 			if (opts > 0) {
 				revs->diff = 1;
@@ -956,6 +1002,11 @@ int setup_revisions(int argc, const char
 	if (diff_setup_done(&revs->diffopt) < 0)
 		die("diff_setup_done failed");
 
+	if (revs->header_filter)
+		compile_grep_patterns(revs->header_filter);
+	if (revs->message_filter)
+		compile_grep_patterns(revs->message_filter);
+
 	return left;
 }
 
@@ -1030,10 +1081,33 @@ static void mark_boundary_to_show(struct
 
 static int commit_match(struct commit *commit, struct rev_info *opt)
 {
+	char *header, *message;
+	unsigned long header_len, message_len;
+
 	if (!opt->header_filter && !opt->message_filter)
 		return 1;
 
-	/* match it here */
+	header = commit->buffer;
+	message = strstr(header, "\n\n");
+	if (message) {
+		message += 2;
+		header_len = message - header - 1;
+		message_len = strlen(message);
+	}
+	else {
+		header_len = strlen(header);
+		message = header;
+		message_len = 0;
+	}
+
+	if (opt->header_filter &&
+	    !grep_buffer(opt->header_filter, NULL, header, header_len))
+		return 0;
+
+	if (opt->message_filter &&
+	    !grep_buffer(opt->message_filter, NULL, message, message_len))
+		return 0;
+
 	return 1;
 }
 
-- 
1.4.2.1.g414e5

^ permalink raw reply related

* [PATCH 2/3] revision traversal: prepare for commit log match.
From: Junio C Hamano @ 2006-09-18  0:42 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git, Kai Blin, Jeff King

This is from a suggestion by Linus, just to mark the locations where we
need to modify to actually implement the filtering.

We do not have any actual filtering code yet.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 revision.c |   13 +++++++++++++
 revision.h |    4 ++++
 2 files changed, 17 insertions(+), 0 deletions(-)

diff --git a/revision.c b/revision.c
index db01682..8fda20d 100644
--- a/revision.c
+++ b/revision.c
@@ -6,6 +6,8 @@ #include "commit.h"
 #include "diff.h"
 #include "refs.h"
 #include "revision.h"
+#include <regex.h>
+#include "grep.h"
 
 static char *path_name(struct name_path *path, const char *name)
 {
@@ -1026,6 +1028,15 @@ static void mark_boundary_to_show(struct
 	}
 }
 
+static int commit_match(struct commit *commit, struct rev_info *opt)
+{
+	if (!opt->header_filter && !opt->message_filter)
+		return 1;
+
+	/* match it here */
+	return 1;
+}
+
 struct commit *get_revision(struct rev_info *revs)
 {
 	struct commit_list *list = revs->commits;
@@ -1085,6 +1096,8 @@ struct commit *get_revision(struct rev_i
 		if (revs->no_merges &&
 		    commit->parents && commit->parents->next)
 			continue;
+		if (!commit_match(commit, revs))
+			continue;
 		if (revs->prune_fn && revs->dense) {
 			/* Commit without changes? */
 			if (!(commit->object.flags & TREECHANGE)) {
diff --git a/revision.h b/revision.h
index c1f71af..35a1db4 100644
--- a/revision.h
+++ b/revision.h
@@ -67,6 +67,10 @@ struct rev_info {
 	const char	*add_signoff;
 	const char	*extra_headers;
 
+	/* Filter by commit log message */
+	struct grep_opt	*header_filter;
+	struct grep_opt	*message_filter;
+
 	/* special limits */
 	int max_count;
 	unsigned long max_age;
-- 
1.4.2.1.g414e5

^ permalink raw reply related

* [PATCH 1/3] builtin-grep: make pieces of it available as library.
From: Junio C Hamano @ 2006-09-18  0:42 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git, Kai Blin, Jeff King

This makes three functions and associated option structures from
builtin-grep available from other parts of the system.

 * options to drive built-in grep engine is stored in struct
   grep_opt;

 * pattern strings and extended grep expressions are added to
   struct grep_opt with append_grep_pattern();

 * when finished calling append_grep_pattern(), call
   compile_grep_patterns() to prepare for execution;

 * call grep_buffer() to find matches in the in-core buffer.

This also adds an internal option "status_only" to grep_opt,
which suppresses any output from grep_buffer().  Callers of the
function as library can use it to check if there is a match
without producing any output.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 Makefile       |    2 
 builtin-grep.c |  522 ++------------------------------------------------------
 grep.c         |  440 +++++++++++++++++++++++++++++++++++++++++++++++
 grep.h         |   71 ++++++++
 4 files changed, 530 insertions(+), 505 deletions(-)

diff --git a/Makefile b/Makefile
index b987450..b8e679e 100644
--- a/Makefile
+++ b/Makefile
@@ -252,7 +252,7 @@ LIB_OBJS = \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
 	tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
 	fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
-	write_or_die.o trace.o \
+	write_or_die.o trace.o grep.o \
 	alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS)
 
 BUILTIN_OBJS = \
diff --git a/builtin-grep.c b/builtin-grep.c
index ed87a55..6718788 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -11,6 +11,7 @@ #include "tag.h"
 #include "tree-walk.h"
 #include "builtin.h"
 #include <regex.h>
+#include "grep.h"
 #include <fnmatch.h>
 #include <sys/wait.h>
 
@@ -82,498 +83,6 @@ static int pathspec_matches(const char *
 	return 0;
 }
 
-enum grep_pat_token {
-	GREP_PATTERN,
-	GREP_AND,
-	GREP_OPEN_PAREN,
-	GREP_CLOSE_PAREN,
-	GREP_NOT,
-	GREP_OR,
-};
-
-struct grep_pat {
-	struct grep_pat *next;
-	const char *origin;
-	int no;
-	enum grep_pat_token token;
-	const char *pattern;
-	regex_t regexp;
-};
-
-enum grep_expr_node {
-	GREP_NODE_ATOM,
-	GREP_NODE_NOT,
-	GREP_NODE_AND,
-	GREP_NODE_OR,
-};
-
-struct grep_expr {
-	enum grep_expr_node node;
-	union {
-		struct grep_pat *atom;
-		struct grep_expr *unary;
-		struct {
-			struct grep_expr *left;
-			struct grep_expr *right;
-		} binary;
-	} u;
-};
-
-struct grep_opt {
-	struct grep_pat *pattern_list;
-	struct grep_pat **pattern_tail;
-	struct grep_expr *pattern_expression;
-	int prefix_length;
-	regex_t regexp;
-	unsigned linenum:1;
-	unsigned invert:1;
-	unsigned name_only:1;
-	unsigned unmatch_name_only:1;
-	unsigned count:1;
-	unsigned word_regexp:1;
-	unsigned fixed:1;
-#define GREP_BINARY_DEFAULT	0
-#define GREP_BINARY_NOMATCH	1
-#define GREP_BINARY_TEXT	2
-	unsigned binary:2;
-	unsigned extended:1;
-	unsigned relative:1;
-	unsigned pathname:1;
-	int regflags;
-	unsigned pre_context;
-	unsigned post_context;
-};
-
-static void add_pattern(struct grep_opt *opt, const char *pat,
-			const char *origin, int no, enum grep_pat_token t)
-{
-	struct grep_pat *p = xcalloc(1, sizeof(*p));
-	p->pattern = pat;
-	p->origin = origin;
-	p->no = no;
-	p->token = t;
-	*opt->pattern_tail = p;
-	opt->pattern_tail = &p->next;
-	p->next = NULL;
-}
-
-static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
-{
-	int err = regcomp(&p->regexp, p->pattern, opt->regflags);
-	if (err) {
-		char errbuf[1024];
-		char where[1024];
-		if (p->no)
-			sprintf(where, "In '%s' at %d, ",
-				p->origin, p->no);
-		else if (p->origin)
-			sprintf(where, "%s, ", p->origin);
-		else
-			where[0] = 0;
-		regerror(err, &p->regexp, errbuf, 1024);
-		regfree(&p->regexp);
-		die("%s'%s': %s", where, p->pattern, errbuf);
-	}
-}
-
-static struct grep_expr *compile_pattern_expr(struct grep_pat **);
-static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
-{
-	struct grep_pat *p;
-	struct grep_expr *x;
-
-	p = *list;
-	switch (p->token) {
-	case GREP_PATTERN: /* atom */
-		x = xcalloc(1, sizeof (struct grep_expr));
-		x->node = GREP_NODE_ATOM;
-		x->u.atom = p;
-		*list = p->next;
-		return x;
-	case GREP_OPEN_PAREN:
-		*list = p->next;
-		x = compile_pattern_expr(list);
-		if (!x)
-			return NULL;
-		if (!*list || (*list)->token != GREP_CLOSE_PAREN)
-			die("unmatched parenthesis");
-		*list = (*list)->next;
-		return x;
-	default:
-		return NULL;
-	}
-}
-
-static struct grep_expr *compile_pattern_not(struct grep_pat **list)
-{
-	struct grep_pat *p;
-	struct grep_expr *x;
-
-	p = *list;
-	switch (p->token) {
-	case GREP_NOT:
-		if (!p->next)
-			die("--not not followed by pattern expression");
-		*list = p->next;
-		x = xcalloc(1, sizeof (struct grep_expr));
-		x->node = GREP_NODE_NOT;
-		x->u.unary = compile_pattern_not(list);
-		if (!x->u.unary)
-			die("--not followed by non pattern expression");
-		return x;
-	default:
-		return compile_pattern_atom(list);
-	}
-}
-
-static struct grep_expr *compile_pattern_and(struct grep_pat **list)
-{
-	struct grep_pat *p;
-	struct grep_expr *x, *y, *z;
-
-	x = compile_pattern_not(list);
-	p = *list;
-	if (p && p->token == GREP_AND) {
-		if (!p->next)
-			die("--and not followed by pattern expression");
-		*list = p->next;
-		y = compile_pattern_and(list);
-		if (!y)
-			die("--and not followed by pattern expression");
-		z = xcalloc(1, sizeof (struct grep_expr));
-		z->node = GREP_NODE_AND;
-		z->u.binary.left = x;
-		z->u.binary.right = y;
-		return z;
-	}
-	return x;
-}
-
-static struct grep_expr *compile_pattern_or(struct grep_pat **list)
-{
-	struct grep_pat *p;
-	struct grep_expr *x, *y, *z;
-
-	x = compile_pattern_and(list);
-	p = *list;
-	if (x && p && p->token != GREP_CLOSE_PAREN) {
-		y = compile_pattern_or(list);
-		if (!y)
-			die("not a pattern expression %s", p->pattern);
-		z = xcalloc(1, sizeof (struct grep_expr));
-		z->node = GREP_NODE_OR;
-		z->u.binary.left = x;
-		z->u.binary.right = y;
-		return z;
-	}
-	return x;
-}
-
-static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
-{
-	return compile_pattern_or(list);
-}
-
-static void compile_patterns(struct grep_opt *opt)
-{
-	struct grep_pat *p;
-
-	/* First compile regexps */
-	for (p = opt->pattern_list; p; p = p->next) {
-		if (p->token == GREP_PATTERN)
-			compile_regexp(p, opt);
-		else
-			opt->extended = 1;
-	}
-
-	if (!opt->extended)
-		return;
-
-	/* Then bundle them up in an expression.
-	 * A classic recursive descent parser would do.
-	 */
-	p = opt->pattern_list;
-	opt->pattern_expression = compile_pattern_expr(&p);
-	if (p)
-		die("incomplete pattern expression: %s", p->pattern);
-}
-
-static char *end_of_line(char *cp, unsigned long *left)
-{
-	unsigned long l = *left;
-	while (l && *cp != '\n') {
-		l--;
-		cp++;
-	}
-	*left = l;
-	return cp;
-}
-
-static int word_char(char ch)
-{
-	return isalnum(ch) || ch == '_';
-}
-
-static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
-		      const char *name, unsigned lno, char sign)
-{
-	if (opt->pathname)
-		printf("%s%c", name, sign);
-	if (opt->linenum)
-		printf("%d%c", lno, sign);
-	printf("%.*s\n", (int)(eol-bol), bol);
-}
-
-/*
- * NEEDSWORK: share code with diff.c
- */
-#define FIRST_FEW_BYTES 8000
-static int buffer_is_binary(const char *ptr, unsigned long size)
-{
-	if (FIRST_FEW_BYTES < size)
-		size = FIRST_FEW_BYTES;
-	return !!memchr(ptr, 0, size);
-}
-
-static int fixmatch(const char *pattern, char *line, regmatch_t *match)
-{
-	char *hit = strstr(line, pattern);
-	if (!hit) {
-		match->rm_so = match->rm_eo = -1;
-		return REG_NOMATCH;
-	}
-	else {
-		match->rm_so = hit - line;
-		match->rm_eo = match->rm_so + strlen(pattern);
-		return 0;
-	}
-}
-
-static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol)
-{
-	int hit = 0;
-	int at_true_bol = 1;
-	regmatch_t pmatch[10];
-
- again:
-	if (!opt->fixed) {
-		regex_t *exp = &p->regexp;
-		hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
-			       pmatch, 0);
-	}
-	else {
-		hit = !fixmatch(p->pattern, bol, pmatch);
-	}
-
-	if (hit && opt->word_regexp) {
-		if ((pmatch[0].rm_so < 0) ||
-		    (eol - bol) <= pmatch[0].rm_so ||
-		    (pmatch[0].rm_eo < 0) ||
-		    (eol - bol) < pmatch[0].rm_eo)
-			die("regexp returned nonsense");
-
-		/* Match beginning must be either beginning of the
-		 * line, or at word boundary (i.e. the last char must
-		 * not be a word char).  Similarly, match end must be
-		 * either end of the line, or at word boundary
-		 * (i.e. the next char must not be a word char).
-		 */
-		if ( ((pmatch[0].rm_so == 0 && at_true_bol) ||
-		      !word_char(bol[pmatch[0].rm_so-1])) &&
-		     ((pmatch[0].rm_eo == (eol-bol)) ||
-		      !word_char(bol[pmatch[0].rm_eo])) )
-			;
-		else
-			hit = 0;
-
-		if (!hit && pmatch[0].rm_so + bol + 1 < eol) {
-			/* There could be more than one match on the
-			 * line, and the first match might not be
-			 * strict word match.  But later ones could be!
-			 */
-			bol = pmatch[0].rm_so + bol + 1;
-			at_true_bol = 0;
-			goto again;
-		}
-	}
-	return hit;
-}
-
-static int match_expr_eval(struct grep_opt *opt,
-			   struct grep_expr *x,
-			   char *bol, char *eol)
-{
-	switch (x->node) {
-	case GREP_NODE_ATOM:
-		return match_one_pattern(opt, x->u.atom, bol, eol);
-		break;
-	case GREP_NODE_NOT:
-		return !match_expr_eval(opt, x->u.unary, bol, eol);
-	case GREP_NODE_AND:
-		return (match_expr_eval(opt, x->u.binary.left, bol, eol) &&
-			match_expr_eval(opt, x->u.binary.right, bol, eol));
-	case GREP_NODE_OR:
-		return (match_expr_eval(opt, x->u.binary.left, bol, eol) ||
-			match_expr_eval(opt, x->u.binary.right, bol, eol));
-	}
-	die("Unexpected node type (internal error) %d\n", x->node);
-}
-
-static int match_expr(struct grep_opt *opt, char *bol, char *eol)
-{
-	struct grep_expr *x = opt->pattern_expression;
-	return match_expr_eval(opt, x, bol, eol);
-}
-
-static int match_line(struct grep_opt *opt, char *bol, char *eol)
-{
-	struct grep_pat *p;
-	if (opt->extended)
-		return match_expr(opt, bol, eol);
-	for (p = opt->pattern_list; p; p = p->next) {
-		if (match_one_pattern(opt, p, bol, eol))
-			return 1;
-	}
-	return 0;
-}
-
-static int grep_buffer(struct grep_opt *opt, const char *name,
-		       char *buf, unsigned long size)
-{
-	char *bol = buf;
-	unsigned long left = size;
-	unsigned lno = 1;
-	struct pre_context_line {
-		char *bol;
-		char *eol;
-	} *prev = NULL, *pcl;
-	unsigned last_hit = 0;
-	unsigned last_shown = 0;
-	int binary_match_only = 0;
-	const char *hunk_mark = "";
-	unsigned count = 0;
-
-	if (buffer_is_binary(buf, size)) {
-		switch (opt->binary) {
-		case GREP_BINARY_DEFAULT:
-			binary_match_only = 1;
-			break;
-		case GREP_BINARY_NOMATCH:
-			return 0; /* Assume unmatch */
-			break;
-		default:
-			break;
-		}
-	}
-
-	if (opt->pre_context)
-		prev = xcalloc(opt->pre_context, sizeof(*prev));
-	if (opt->pre_context || opt->post_context)
-		hunk_mark = "--\n";
-
-	while (left) {
-		char *eol, ch;
-		int hit = 0;
-
-		eol = end_of_line(bol, &left);
-		ch = *eol;
-		*eol = 0;
-
-		hit = match_line(opt, bol, eol);
-
-		/* "grep -v -e foo -e bla" should list lines
-		 * that do not have either, so inversion should
-		 * be done outside.
-		 */
-		if (opt->invert)
-			hit = !hit;
-		if (opt->unmatch_name_only) {
-			if (hit)
-				return 0;
-			goto next_line;
-		}
-		if (hit) {
-			count++;
-			if (binary_match_only) {
-				printf("Binary file %s matches\n", name);
-				return 1;
-			}
-			if (opt->name_only) {
-				printf("%s\n", name);
-				return 1;
-			}
-			/* Hit at this line.  If we haven't shown the
-			 * pre-context lines, we would need to show them.
-			 * When asked to do "count", this still show
-			 * the context which is nonsense, but the user
-			 * deserves to get that ;-).
-			 */
-			if (opt->pre_context) {
-				unsigned from;
-				if (opt->pre_context < lno)
-					from = lno - opt->pre_context;
-				else
-					from = 1;
-				if (from <= last_shown)
-					from = last_shown + 1;
-				if (last_shown && from != last_shown + 1)
-					printf(hunk_mark);
-				while (from < lno) {
-					pcl = &prev[lno-from-1];
-					show_line(opt, pcl->bol, pcl->eol,
-						  name, from, '-');
-					from++;
-				}
-				last_shown = lno-1;
-			}
-			if (last_shown && lno != last_shown + 1)
-				printf(hunk_mark);
-			if (!opt->count)
-				show_line(opt, bol, eol, name, lno, ':');
-			last_shown = last_hit = lno;
-		}
-		else if (last_hit &&
-			 lno <= last_hit + opt->post_context) {
-			/* If the last hit is within the post context,
-			 * we need to show this line.
-			 */
-			if (last_shown && lno != last_shown + 1)
-				printf(hunk_mark);
-			show_line(opt, bol, eol, name, lno, '-');
-			last_shown = lno;
-		}
-		if (opt->pre_context) {
-			memmove(prev+1, prev,
-				(opt->pre_context-1) * sizeof(*prev));
-			prev->bol = bol;
-			prev->eol = eol;
-		}
-
-	next_line:
-		*eol = ch;
-		bol = eol + 1;
-		if (!left)
-			break;
-		left--;
-		lno++;
-	}
-
-	if (opt->unmatch_name_only) {
-		/* We did not see any hit, so we want to show this */
-		printf("%s\n", name);
-		return 1;
-	}
-
-	/* NEEDSWORK:
-	 * The real "grep -c foo *.c" gives many "bar.c:0" lines,
-	 * which feels mostly useless but sometimes useful.  Maybe
-	 * make it another option?  For now suppress them.
-	 */
-	if (opt->count && count)
-		printf("%s:%u\n", name, count);
-	return !!last_hit;
-}
-
 static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len)
 {
 	unsigned long size;
@@ -1055,8 +564,9 @@ int cmd_grep(int argc, const char **argv
 				/* ignore empty line like grep does */
 				if (!buf[0])
 					continue;
-				add_pattern(&opt, xstrdup(buf), argv[1], ++lno,
-					    GREP_PATTERN);
+				append_grep_pattern(&opt, xstrdup(buf),
+						    argv[1], ++lno,
+						    GREP_PATTERN);
 			}
 			fclose(patterns);
 			argv++;
@@ -1064,27 +574,32 @@ int cmd_grep(int argc, const char **argv
 			continue;
 		}
 		if (!strcmp("--not", arg)) {
-			add_pattern(&opt, arg, "command line", 0, GREP_NOT);
+			append_grep_pattern(&opt, arg, "command line", 0,
+					    GREP_NOT);
 			continue;
 		}
 		if (!strcmp("--and", arg)) {
-			add_pattern(&opt, arg, "command line", 0, GREP_AND);
+			append_grep_pattern(&opt, arg, "command line", 0,
+					    GREP_AND);
 			continue;
 		}
 		if (!strcmp("--or", arg))
 			continue; /* no-op */
 		if (!strcmp("(", arg)) {
-			add_pattern(&opt, arg, "command line", 0, GREP_OPEN_PAREN);
+			append_grep_pattern(&opt, arg, "command line", 0,
+					    GREP_OPEN_PAREN);
 			continue;
 		}
 		if (!strcmp(")", arg)) {
-			add_pattern(&opt, arg, "command line", 0, GREP_CLOSE_PAREN);
+			append_grep_pattern(&opt, arg, "command line", 0,
+					    GREP_CLOSE_PAREN);
 			continue;
 		}
 		if (!strcmp("-e", arg)) {
 			if (1 < argc) {
-				add_pattern(&opt, argv[1], "-e option", 0,
-					    GREP_PATTERN);
+				append_grep_pattern(&opt, argv[1],
+						    "-e option", 0,
+						    GREP_PATTERN);
 				argv++;
 				argc--;
 				continue;
@@ -1106,8 +621,8 @@ int cmd_grep(int argc, const char **argv
 
 		/* First unrecognized non-option token */
 		if (!opt.pattern_list) {
-			add_pattern(&opt, arg, "command line", 0,
-				    GREP_PATTERN);
+			append_grep_pattern(&opt, arg, "command line", 0,
+					    GREP_PATTERN);
 			break;
 		}
 		else {
@@ -1124,8 +639,7 @@ int cmd_grep(int argc, const char **argv
 		die("no pattern given.");
 	if ((opt.regflags != REG_NEWLINE) && opt.fixed)
 		die("cannot mix --fixed-strings and regexp");
-	if (!opt.fixed)
-		compile_patterns(&opt);
+	compile_grep_patterns(&opt);
 
 	/* Check revs and then paths */
 	for (i = 1; i < argc; i++) {
diff --git a/grep.c b/grep.c
new file mode 100644
index 0000000..61db6e1
--- /dev/null
+++ b/grep.c
@@ -0,0 +1,440 @@
+#include "cache.h"
+#include <regex.h>
+#include "grep.h"
+
+void append_grep_pattern(struct grep_opt *opt, const char *pat,
+			 const char *origin, int no, enum grep_pat_token t)
+{
+	struct grep_pat *p = xcalloc(1, sizeof(*p));
+	p->pattern = pat;
+	p->origin = origin;
+	p->no = no;
+	p->token = t;
+	*opt->pattern_tail = p;
+	opt->pattern_tail = &p->next;
+	p->next = NULL;
+}
+
+static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
+{
+	int err = regcomp(&p->regexp, p->pattern, opt->regflags);
+	if (err) {
+		char errbuf[1024];
+		char where[1024];
+		if (p->no)
+			sprintf(where, "In '%s' at %d, ",
+				p->origin, p->no);
+		else if (p->origin)
+			sprintf(where, "%s, ", p->origin);
+		else
+			where[0] = 0;
+		regerror(err, &p->regexp, errbuf, 1024);
+		regfree(&p->regexp);
+		die("%s'%s': %s", where, p->pattern, errbuf);
+	}
+}
+
+static struct grep_expr *compile_pattern_expr(struct grep_pat **);
+static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
+{
+	struct grep_pat *p;
+	struct grep_expr *x;
+
+	p = *list;
+	switch (p->token) {
+	case GREP_PATTERN: /* atom */
+		x = xcalloc(1, sizeof (struct grep_expr));
+		x->node = GREP_NODE_ATOM;
+		x->u.atom = p;
+		*list = p->next;
+		return x;
+	case GREP_OPEN_PAREN:
+		*list = p->next;
+		x = compile_pattern_expr(list);
+		if (!x)
+			return NULL;
+		if (!*list || (*list)->token != GREP_CLOSE_PAREN)
+			die("unmatched parenthesis");
+		*list = (*list)->next;
+		return x;
+	default:
+		return NULL;
+	}
+}
+
+static struct grep_expr *compile_pattern_not(struct grep_pat **list)
+{
+	struct grep_pat *p;
+	struct grep_expr *x;
+
+	p = *list;
+	switch (p->token) {
+	case GREP_NOT:
+		if (!p->next)
+			die("--not not followed by pattern expression");
+		*list = p->next;
+		x = xcalloc(1, sizeof (struct grep_expr));
+		x->node = GREP_NODE_NOT;
+		x->u.unary = compile_pattern_not(list);
+		if (!x->u.unary)
+			die("--not followed by non pattern expression");
+		return x;
+	default:
+		return compile_pattern_atom(list);
+	}
+}
+
+static struct grep_expr *compile_pattern_and(struct grep_pat **list)
+{
+	struct grep_pat *p;
+	struct grep_expr *x, *y, *z;
+
+	x = compile_pattern_not(list);
+	p = *list;
+	if (p && p->token == GREP_AND) {
+		if (!p->next)
+			die("--and not followed by pattern expression");
+		*list = p->next;
+		y = compile_pattern_and(list);
+		if (!y)
+			die("--and not followed by pattern expression");
+		z = xcalloc(1, sizeof (struct grep_expr));
+		z->node = GREP_NODE_AND;
+		z->u.binary.left = x;
+		z->u.binary.right = y;
+		return z;
+	}
+	return x;
+}
+
+static struct grep_expr *compile_pattern_or(struct grep_pat **list)
+{
+	struct grep_pat *p;
+	struct grep_expr *x, *y, *z;
+
+	x = compile_pattern_and(list);
+	p = *list;
+	if (x && p && p->token != GREP_CLOSE_PAREN) {
+		y = compile_pattern_or(list);
+		if (!y)
+			die("not a pattern expression %s", p->pattern);
+		z = xcalloc(1, sizeof (struct grep_expr));
+		z->node = GREP_NODE_OR;
+		z->u.binary.left = x;
+		z->u.binary.right = y;
+		return z;
+	}
+	return x;
+}
+
+static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
+{
+	return compile_pattern_or(list);
+}
+
+void compile_grep_patterns(struct grep_opt *opt)
+{
+	struct grep_pat *p;
+
+	if (opt->fixed)
+		return;
+
+	/* First compile regexps */
+	for (p = opt->pattern_list; p; p = p->next) {
+		if (p->token == GREP_PATTERN)
+			compile_regexp(p, opt);
+		else
+			opt->extended = 1;
+	}
+
+	if (!opt->extended)
+		return;
+
+	/* Then bundle them up in an expression.
+	 * A classic recursive descent parser would do.
+	 */
+	p = opt->pattern_list;
+	opt->pattern_expression = compile_pattern_expr(&p);
+	if (p)
+		die("incomplete pattern expression: %s", p->pattern);
+}
+
+static char *end_of_line(char *cp, unsigned long *left)
+{
+	unsigned long l = *left;
+	while (l && *cp != '\n') {
+		l--;
+		cp++;
+	}
+	*left = l;
+	return cp;
+}
+
+static int word_char(char ch)
+{
+	return isalnum(ch) || ch == '_';
+}
+
+static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
+		      const char *name, unsigned lno, char sign)
+{
+	if (opt->pathname)
+		printf("%s%c", name, sign);
+	if (opt->linenum)
+		printf("%d%c", lno, sign);
+	printf("%.*s\n", (int)(eol-bol), bol);
+}
+
+/*
+ * NEEDSWORK: share code with diff.c
+ */
+#define FIRST_FEW_BYTES 8000
+static int buffer_is_binary(const char *ptr, unsigned long size)
+{
+	if (FIRST_FEW_BYTES < size)
+		size = FIRST_FEW_BYTES;
+	return !!memchr(ptr, 0, size);
+}
+
+static int fixmatch(const char *pattern, char *line, regmatch_t *match)
+{
+	char *hit = strstr(line, pattern);
+	if (!hit) {
+		match->rm_so = match->rm_eo = -1;
+		return REG_NOMATCH;
+	}
+	else {
+		match->rm_so = hit - line;
+		match->rm_eo = match->rm_so + strlen(pattern);
+		return 0;
+	}
+}
+
+static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol)
+{
+	int hit = 0;
+	int at_true_bol = 1;
+	regmatch_t pmatch[10];
+
+ again:
+	if (!opt->fixed) {
+		regex_t *exp = &p->regexp;
+		hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
+			       pmatch, 0);
+	}
+	else {
+		hit = !fixmatch(p->pattern, bol, pmatch);
+	}
+
+	if (hit && opt->word_regexp) {
+		if ((pmatch[0].rm_so < 0) ||
+		    (eol - bol) <= pmatch[0].rm_so ||
+		    (pmatch[0].rm_eo < 0) ||
+		    (eol - bol) < pmatch[0].rm_eo)
+			die("regexp returned nonsense");
+
+		/* Match beginning must be either beginning of the
+		 * line, or at word boundary (i.e. the last char must
+		 * not be a word char).  Similarly, match end must be
+		 * either end of the line, or at word boundary
+		 * (i.e. the next char must not be a word char).
+		 */
+		if ( ((pmatch[0].rm_so == 0 && at_true_bol) ||
+		      !word_char(bol[pmatch[0].rm_so-1])) &&
+		     ((pmatch[0].rm_eo == (eol-bol)) ||
+		      !word_char(bol[pmatch[0].rm_eo])) )
+			;
+		else
+			hit = 0;
+
+		if (!hit && pmatch[0].rm_so + bol + 1 < eol) {
+			/* There could be more than one match on the
+			 * line, and the first match might not be
+			 * strict word match.  But later ones could be!
+			 */
+			bol = pmatch[0].rm_so + bol + 1;
+			at_true_bol = 0;
+			goto again;
+		}
+	}
+	return hit;
+}
+
+static int match_expr_eval(struct grep_opt *opt,
+			   struct grep_expr *x,
+			   char *bol, char *eol)
+{
+	switch (x->node) {
+	case GREP_NODE_ATOM:
+		return match_one_pattern(opt, x->u.atom, bol, eol);
+		break;
+	case GREP_NODE_NOT:
+		return !match_expr_eval(opt, x->u.unary, bol, eol);
+	case GREP_NODE_AND:
+		return (match_expr_eval(opt, x->u.binary.left, bol, eol) &&
+			match_expr_eval(opt, x->u.binary.right, bol, eol));
+	case GREP_NODE_OR:
+		return (match_expr_eval(opt, x->u.binary.left, bol, eol) ||
+			match_expr_eval(opt, x->u.binary.right, bol, eol));
+	}
+	die("Unexpected node type (internal error) %d\n", x->node);
+}
+
+static int match_expr(struct grep_opt *opt, char *bol, char *eol)
+{
+	struct grep_expr *x = opt->pattern_expression;
+	return match_expr_eval(opt, x, bol, eol);
+}
+
+static int match_line(struct grep_opt *opt, char *bol, char *eol)
+{
+	struct grep_pat *p;
+	if (opt->extended)
+		return match_expr(opt, bol, eol);
+	for (p = opt->pattern_list; p; p = p->next) {
+		if (match_one_pattern(opt, p, bol, eol))
+			return 1;
+	}
+	return 0;
+}
+
+int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+{
+	char *bol = buf;
+	unsigned long left = size;
+	unsigned lno = 1;
+	struct pre_context_line {
+		char *bol;
+		char *eol;
+	} *prev = NULL, *pcl;
+	unsigned last_hit = 0;
+	unsigned last_shown = 0;
+	int binary_match_only = 0;
+	const char *hunk_mark = "";
+	unsigned count = 0;
+
+	if (buffer_is_binary(buf, size)) {
+		switch (opt->binary) {
+		case GREP_BINARY_DEFAULT:
+			binary_match_only = 1;
+			break;
+		case GREP_BINARY_NOMATCH:
+			return 0; /* Assume unmatch */
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (opt->pre_context)
+		prev = xcalloc(opt->pre_context, sizeof(*prev));
+	if (opt->pre_context || opt->post_context)
+		hunk_mark = "--\n";
+
+	while (left) {
+		char *eol, ch;
+		int hit = 0;
+
+		eol = end_of_line(bol, &left);
+		ch = *eol;
+		*eol = 0;
+
+		hit = match_line(opt, bol, eol);
+		*eol = ch;
+
+		/* "grep -v -e foo -e bla" should list lines
+		 * that do not have either, so inversion should
+		 * be done outside.
+		 */
+		if (opt->invert)
+			hit = !hit;
+		if (opt->unmatch_name_only) {
+			if (hit)
+				return 0;
+			goto next_line;
+		}
+		if (hit) {
+			count++;
+			if (opt->status_only)
+				return 1;
+			if (binary_match_only) {
+				printf("Binary file %s matches\n", name);
+				return 1;
+			}
+			if (opt->name_only) {
+				printf("%s\n", name);
+				return 1;
+			}
+			/* Hit at this line.  If we haven't shown the
+			 * pre-context lines, we would need to show them.
+			 * When asked to do "count", this still show
+			 * the context which is nonsense, but the user
+			 * deserves to get that ;-).
+			 */
+			if (opt->pre_context) {
+				unsigned from;
+				if (opt->pre_context < lno)
+					from = lno - opt->pre_context;
+				else
+					from = 1;
+				if (from <= last_shown)
+					from = last_shown + 1;
+				if (last_shown && from != last_shown + 1)
+					printf(hunk_mark);
+				while (from < lno) {
+					pcl = &prev[lno-from-1];
+					show_line(opt, pcl->bol, pcl->eol,
+						  name, from, '-');
+					from++;
+				}
+				last_shown = lno-1;
+			}
+			if (last_shown && lno != last_shown + 1)
+				printf(hunk_mark);
+			if (!opt->count)
+				show_line(opt, bol, eol, name, lno, ':');
+			last_shown = last_hit = lno;
+		}
+		else if (last_hit &&
+			 lno <= last_hit + opt->post_context) {
+			/* If the last hit is within the post context,
+			 * we need to show this line.
+			 */
+			if (last_shown && lno != last_shown + 1)
+				printf(hunk_mark);
+			show_line(opt, bol, eol, name, lno, '-');
+			last_shown = lno;
+		}
+		if (opt->pre_context) {
+			memmove(prev+1, prev,
+				(opt->pre_context-1) * sizeof(*prev));
+			prev->bol = bol;
+			prev->eol = eol;
+		}
+
+	next_line:
+		bol = eol + 1;
+		if (!left)
+			break;
+		left--;
+		lno++;
+	}
+
+	if (opt->status_only)
+		return 0;
+	if (opt->unmatch_name_only) {
+		/* We did not see any hit, so we want to show this */
+		printf("%s\n", name);
+		return 1;
+	}
+
+	/* NEEDSWORK:
+	 * The real "grep -c foo *.c" gives many "bar.c:0" lines,
+	 * which feels mostly useless but sometimes useful.  Maybe
+	 * make it another option?  For now suppress them.
+	 */
+	if (opt->count && count)
+		printf("%s:%u\n", name, count);
+	return !!last_hit;
+}
+
diff --git a/grep.h b/grep.h
new file mode 100644
index 0000000..80122b0
--- /dev/null
+++ b/grep.h
@@ -0,0 +1,71 @@
+#ifndef GREP_H
+#define GREP_H
+
+enum grep_pat_token {
+	GREP_PATTERN,
+	GREP_AND,
+	GREP_OPEN_PAREN,
+	GREP_CLOSE_PAREN,
+	GREP_NOT,
+	GREP_OR,
+};
+
+struct grep_pat {
+	struct grep_pat *next;
+	const char *origin;
+	int no;
+	enum grep_pat_token token;
+	const char *pattern;
+	regex_t regexp;
+};
+
+enum grep_expr_node {
+	GREP_NODE_ATOM,
+	GREP_NODE_NOT,
+	GREP_NODE_AND,
+	GREP_NODE_OR,
+};
+
+struct grep_expr {
+	enum grep_expr_node node;
+	union {
+		struct grep_pat *atom;
+		struct grep_expr *unary;
+		struct {
+			struct grep_expr *left;
+			struct grep_expr *right;
+		} binary;
+	} u;
+};
+
+struct grep_opt {
+	struct grep_pat *pattern_list;
+	struct grep_pat **pattern_tail;
+	struct grep_expr *pattern_expression;
+	int prefix_length;
+	regex_t regexp;
+	unsigned linenum:1;
+	unsigned invert:1;
+	unsigned status_only:1;
+	unsigned name_only:1;
+	unsigned unmatch_name_only:1;
+	unsigned count:1;
+	unsigned word_regexp:1;
+	unsigned fixed:1;
+#define GREP_BINARY_DEFAULT	0
+#define GREP_BINARY_NOMATCH	1
+#define GREP_BINARY_TEXT	2
+	unsigned binary:2;
+	unsigned extended:1;
+	unsigned relative:1;
+	unsigned pathname:1;
+	int regflags;
+	unsigned pre_context;
+	unsigned post_context;
+};
+
+extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
+extern void compile_grep_patterns(struct grep_opt *opt);
+extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
+
+#endif
-- 
1.4.2.1.g414e5

^ permalink raw reply related

* git log --author='Jeff King'
From: Junio C Hamano @ 2006-09-18  0:40 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git, Kai Blin, Jeff King
In-Reply-To: <Pine.LNX.4.64.0608281147420.27779@g5.osdl.org>

I have three patch series that makes a part of git-grep
available and use it for log filtering:

 [PATCH] builtin-grep: make pieces of it available as library.
 [PATCH] revision traversal: prepare for commit log match.
 [PATCH] revision traversal: --author, --committer, and --grep.

I didn't implement the boolean combination of patterns like
git-grep does, but it should be pretty straightforward to do so
in setup_revisions().  The syntax probably would be something
like:

    git log --grep-( rev-list gitweb --and --not --author=Jakub --grep-)

to find logs that:

 * talk about rev-list, or
 * talk about gitweb but not by Jakub

^ permalink raw reply

* Re: git-repack: Outof memory
From: Shawn Pearce @ 2006-09-18  0:23 UTC (permalink / raw)
  To: Dongsheng Song; +Cc: git
In-Reply-To: <4b3406f0609170543p68d96b9x9ba0c5a74d9e89e8@mail.gmail.com>

Dongsheng Song <dongsheng.song@gmail.com> wrote:
> finished, thanks a lot.
> 
> $ ls -l .git/objects/pack/
> total 1675466
> -rw-r--r-- 1 www-data www-data    2964992 Sep 17 19:17
> pack-b09c57c25e4459f6365b5d27139abfd93bf1c86f.idx
> -rw-r--r-- 1 www-data www-data 1711037142 Sep 17 19:17
> pack-b09c57c25e4459f6365b5d27139abfd93bf1c86f.pack
> 
> If the pack files larger than 2.5g,  how can  I repack it on i686 ?

*youch* A 1.5 GiB pack file?

Your files apparently do not delta compress very well.  I fear that
you are going to bump up against address space limitations soon
on 32 bit systems.  Then you will bump up against the 4 GiB pack
file size limit.  Which means you will need to use several packs
and avoid the '-a' flag when calling git-repack.

Nico was suggesting using the default window size (rather than
--window=64) as larger windows requires more memory during repack.
But you may also need the mmap window code I'm working on.  I better
hurry up and get that into Junio's testing branches.  :-)

-- 
Shawn.

^ permalink raw reply

* Re: [PATCH] gitweb: fix warnings in PATH_INFO code and add export_ok/strict_export
From: Junio C Hamano @ 2006-09-17 23:10 UTC (permalink / raw)
  To: git; +Cc: jnareb
In-Reply-To: <eekj5b$rlc$1@sea.gmane.org>

Jakub Narebski <jnareb@gmail.com> writes:

> Junio C Hamano wrote:
>
>> Matthias Lederhofer <matled@gmx.net> writes:
>> 
>>> This patch replaces the other two warning fixes by Jakub and me.  I've
>>> put the whole thing in a sub-routine to keep the indentation level
>>> low.
>> 
>> Looks much cleaner with this extra sub.
>> 
>> This does not seem have the "colon as a cue that the URL is also
>> talking about a branch", Jakub's summary of the discussion
>> between you two on #git channel.  I think that proposal makes
>> sense as well.
>
> Well, that is because this patch _predates_ the discussion 
> (and proposal/conclusions). 

Yup, I know, I was not complaining.

I was saying that I do not oppose to a follow-up patch to add
that "colon" stuff.

^ permalink raw reply

* Re: [PATCH] gitweb: fix warnings in PATH_INFO code and add export_ok/strict_export
From: Jakub Narebski @ 2006-09-17 22:45 UTC (permalink / raw)
  To: git
In-Reply-To: <7v4pv62lni.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano wrote:

> Matthias Lederhofer <matled@gmx.net> writes:
> 
>> This patch replaces the other two warning fixes by Jakub and me.  I've
>> put the whole thing in a sub-routine to keep the indentation level
>> low.
> 
> Looks much cleaner with this extra sub.
> 
> This does not seem have the "colon as a cue that the URL is also
> talking about a branch", Jakub's summary of the discussion
> between you two on #git channel.  I think that proposal makes
> sense as well.

Well, that is because this patch _predates_ the discussion 
(and proposal/conclusions). 

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply

* Re: [PATCH] gitweb: fix warnings in PATH_INFO code and add export_ok/strict_export
From: Junio C Hamano @ 2006-09-17 22:06 UTC (permalink / raw)
  To: Matthias Lederhofer; +Cc: git, Jakub Narebski
In-Reply-To: <20060917132948.GA976@moooo.ath.cx>

Matthias Lederhofer <matled@gmx.net> writes:

> This patch replaces the other two warning fixes by Jakub and me.  I've
> put the whole thing in a sub-routine to keep the indentation level
> low.

Looks much cleaner with this extra sub.

This does not seem have the "colon as a cue that the URL is also
talking about a branch", Jakub's summary of the discussion
between you two on #git channel.  I think that proposal makes
sense as well.

^ permalink raw reply

* Re: [PATCH] gitweb fix validating pg (page) parameter
From: Junio C Hamano @ 2006-09-17 21:45 UTC (permalink / raw)
  To: Matthias Lederhofer; +Cc: git
In-Reply-To: <20060917213838.GA12782@moooo.ath.cx>

Matthias Lederhofer <matled@gmx.net> writes:

>> > +	if ($page =~ m/[^0-9]/) {
>...
> The first page seems to have number 0.  After removing the 1 <= $page
> this code should do the same as my patch, apply whatever you think is
> easier to read (I prefer if over unless most of the time).

Ah, I did not notice we call the first page 0; barfing on any
non-digit in the string like you do sounds quite sane.

Thanks.

^ permalink raw reply

* Re: [PATCH] gitweb fix validating pg (page) parameter
From: Matthias Lederhofer @ 2006-09-17 21:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vlkoi2nqr.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano <junkio@cox.net> wrote:
> Matthias Lederhofer <matled@gmx.net> writes:
> 
> > Currently it is possible to give any string ending with a number as
> > page.  -1 for example is quite bad (error log shows probably 100
> > warnings).
> >
> > @@ -259,7 +259,7 @@ if (defined $hash_parent_base) {
> >  
> >  our $page = $cgi->param('pg');
> >  if (defined $page) {
> > -	if ($page =~ m/[^0-9]$/) {
> > +	if ($page =~ m/[^0-9]/) {
> >  		die_error(undef, "Invalid page parameter");
> >  	}
> >  }
> 
> Are we complaining if $page is not a validly formatted number
> here?  If so why not
> 
> 	unless ($page =~ /^\d+$/ && 1 <= $page) {
>         	barf(...);
> 	}
The first page seems to have number 0.  After removing the 1 <= $page
this code should do the same as my patch, apply whatever you think is
easier to read (I prefer if over unless most of the time).

^ permalink raw reply

* Re: [PATCH] gitweb: fix warnings from dd70235f5a81e (PATH_INFO)
From: Junio C Hamano @ 2006-09-17 21:34 UTC (permalink / raw)
  To: Martin Waitz; +Cc: git
In-Reply-To: <20060917121408.GA19860@moooo.ath.cx>

Matthias Lederhofer <matled@gmx.net> writes:

> We really need a gitweb test target.

Yes, really.  Any takers who cares truly about gitweb?

> Something else I noted:
>> +	while ($project && !-e "$projectroot/$project/HEAD") {
> Evaluating $project boolean value leads to problems if a repository is
> named "0" (I dunno if there are other strings than "" and "0" which
> evaluate to false in perl).  There are multiple places where this is
> used so I did not change it in this patch (even added one more).
> Should this be changed?

Yes, there are tons of places that says if ($foo) and/or while
($bar) and fail miserably when $foo or $bar _can_ be "0".  For
project names "0" is probably not something people would want to
use but there is no inherent reason to forbid it.

How many places, like this, that we should say "defined()"
instead in the current code I wonder?

> -	} elsif ($path_info =~ m,^$project/([^/]+)$,) {
> +	} elsif ($project && $path_info =~ m,^$project/([^/]+)$,) {

^ permalink raw reply

* Re: [PATCH] gitweb fix validating pg (page) parameter
From: Junio C Hamano @ 2006-09-17 21:21 UTC (permalink / raw)
  To: Matthias Lederhofer; +Cc: git
In-Reply-To: <20060917115245.GA15658@moooo.ath.cx>

Matthias Lederhofer <matled@gmx.net> writes:

> Currently it is possible to give any string ending with a number as
> page.  -1 for example is quite bad (error log shows probably 100
> warnings).
>
> @@ -259,7 +259,7 @@ if (defined $hash_parent_base) {
>  
>  our $page = $cgi->param('pg');
>  if (defined $page) {
> -	if ($page =~ m/[^0-9]$/) {
> +	if ($page =~ m/[^0-9]/) {
>  		die_error(undef, "Invalid page parameter");
>  	}
>  }

Are we complaining if $page is not a validly formatted number
here?  If so why not

	unless ($page =~ /^\d+$/ && 1 <= $page) {
        	barf(...);
	}

???

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox