Git development
 help / color / mirror / Atom feed
* Re: [PATCH 1/2] init: support --import to add all files and commit right after init
From: Jeff King @ 2009-03-27  5:08 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Markus Heidelberg, Santi Béjar,
	Nguyễn Thái Ngọc, git
In-Reply-To: <20090327050626.GA23512@coredump.intra.peff.net>

On Fri, Mar 27, 2009 at 01:06:26AM -0400, Jeff King wrote:

> Another option would be a patch on top of the original to allow
> 
>   git config --global init.importmessage 'Commit inicial'
> 
> or
> 
>   git config --global init.importeditor true
> 
> I have no interest in writing such a patch, but I don't see a reason to
> reject it.

Actually, there is one possible reason to reject it: scripts could not
rely on the behavior of "--import" without it. But I think it is OK to
make a conscious decision that this is a feature for _humans_, and that
scripts can use "init && add && commit" (or they can be happy with
dealing with the human's choice of editor or not).

-Peff

^ permalink raw reply

* Re: [PATCH 1/2] init: support --import to add all files and commit right after init
From: Jeff King @ 2009-03-27  5:06 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Markus Heidelberg, Santi Béjar,
	Nguyễn Thái Ngọc, git
In-Reply-To: <alpine.DEB.1.00.0903270259470.10279@pacific.mpi-cbg.de>

On Fri, Mar 27, 2009 at 03:03:07AM +0100, Johannes Schindelin wrote:

> _Again_, as Peff pointed out, you are welcome to use the current method of 
> git init && git add . && git commit, which _does_ launch an editor.
> 
> The fact that you want to spend much time (anyway) doing your initial 
> commit does not allow you to inconvenience others.

Another option would be a patch on top of the original to allow

  git config --global init.importmessage 'Commit inicial'

or

  git config --global init.importeditor true

I have no interest in writing such a patch, but I don't see a reason to
reject it.

-Peff

^ permalink raw reply

* Problem Creating Commit Messages
From: bggy @ 2009-03-27  4:44 UTC (permalink / raw)
  To: git


Hello, I'm new to git and I haven't been able to create commit messages in
either vim or textmate(changed core.editor to mate).  I can create commit
messages when I use the -m option, but I like creating multiple-line
messages and it's easier to do it with a fresh line-break in the terminal.

Using textmate I saved and closed the textmate file that pops up after $git
commit -a, but git responds that the commit was terminated due to an empty
message.

I am not familiar with VIM, can change modes, but none of the key combos I
tried worked.

Thank you!,
John
-- 
View this message in context: http://www.nabble.com/Problem-Creating-Commit-Messages-tp22735968p22735968.html
Sent from the git mailing list archive at Nabble.com.

^ permalink raw reply

* Re: [PATCH] log-tree: fix patch filename computation in "git  format-patch"
From: Stephen Boyd @ 2009-03-27  4:31 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Couder, git
In-Reply-To: <7v3acziot0.fsf@gitster.siamese.dyndns.org>

On Thu, Mar 26, 2009 at 9:15 PM, Junio C Hamano <gitster@pobox.com> wrote:
>
> Shouldn't it be just:
>
>        size_t max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len;
>        if (max_len < buf->len)
>                strbuf_setlen(buf, max_len);
>        strbuf_addstr(buf, suffix);

Yes, this is good.

>
> The caller must make sure that suffix_len is sufficiently shorter than
> FORMAT_PATCH_NAME_MAX; I do not know if the current code does that,
> though.
>

The original code never did this. What should happen in this case?

I am away on travel this week, so I won't be able to update this until Monday.

Thanks,
Stephen

^ permalink raw reply

* Re: [PATCH] log-tree: fix patch filename computation in "git format-patch"
From: Junio C Hamano @ 2009-03-27  4:15 UTC (permalink / raw)
  To: Christian Couder; +Cc: Stephen Boyd, git
In-Reply-To: <20090327011301.a5185805.chriscool@tuxfamily.org>

Christian Couder <chriscool@tuxfamily.org> writes:

> When using "git format-patch", "get_patch_filename" in
> "log-tree.c" calls "strbuf_splice" that could die with
> the following message:
>
> "`pos + len' is too far after the end of the buffer"
>
> if you have:
>
> 	buf->len < start_len + FORMAT_PATCH_NAME_MAX
>
> but:
>
> 	buf->len + suffix_len > start_len + FORMAT_PATCH_NAME_MAX
>
> This patch tries to get rid of that bug.

hmm, tries to?

> diff --git a/log-tree.c b/log-tree.c
> index 56a3488..ade79ab 100644
> --- a/log-tree.c
> +++ b/log-tree.c
> @@ -187,16 +187,17 @@ void get_patch_filename(struct commit *commit, int nr, const char *suffix,
>  
>  	strbuf_addf(buf, commit ? "%04d-" : "%d", nr);
>  	if (commit) {
> +		int max_len = start_len + FORMAT_PATCH_NAME_MAX;
>  		format_commit_message(commit, "%f", buf, DATE_NORMAL);
>  		/*
>  		 * Replace characters at the end with the suffix if the
>  		 * filename is too long
>  		 */
> +		if (buf->len + suffix_len > max_len) {
> +			int base = (max_len > buf->len) ? buf->len : max_len;
> +			strbuf_splice(buf, base - suffix_len, suffix_len,
> +				      suffix, suffix_len);
> +		} else
>  			strbuf_addstr(buf, suffix);

Your third argument to splice does not look right; if the existing length
is very very long, you would need to remove a lot, and if the existing
length is slightly long, you would need to remove just a little bit, but
you always seem to remove the fixed amount, to splice the suffix in.

In any case, why does this have to be so complex?

In your buffer, you originally have start_len, and would want to end up
with "%f" expansion, plus the suffix, but you are not allowed to exceed
FORMAT_PATCH_NAME_MAX to store what you add, and are only allowed to chop
the "%f" expansion if you are short of room.

Shouldn't it be just:

	size_t max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len;
        if (max_len < buf->len)
                strbuf_setlen(buf, max_len);
	strbuf_addstr(buf, suffix);

The caller must make sure that suffix_len is sufficiently shorter than
FORMAT_PATCH_NAME_MAX; I do not know if the current code does that,
though.

^ permalink raw reply

* Re: [PATCH] builtin-send-pack.c: avoid empty structure initialization
From: Junio C Hamano @ 2009-03-27  4:02 UTC (permalink / raw)
  To: Brandon Casey; +Cc: git
In-Reply-To: <5dTED0fgBNPRxaQcq2Yx362_Ykx8_d294JWsQ989AjNLfL5kjzWRBnuVa1D9jLmvQzvhAD5NPfY@cipher.nrlssc.navy.mil>

Brandon Casey <casey@nrlssc.navy.mil> writes:

> The IRIX6.5 MIPSpro Compiler doesn't like it.
>
> Signed-off-by: Brandon Casey <casey@nrlssc.navy.mil>
> ...
> diff --git a/builtin-send-pack.c b/builtin-send-pack.c
> index 91c3651..d5a1c48 100644
> --- a/builtin-send-pack.c
> +++ b/builtin-send-pack.c
> @@ -10,8 +10,7 @@ static const char send_pack_usage[] =
>  "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
>  "  --all and explicit <ref> specification are mutually exclusive.";
>  
> -static struct send_pack_args args = {
> -};
> +static struct send_pack_args args;
>  
>  static int feed_object(const unsigned char *sha1, int fd, int negative)
>  {

Thanks.

^ permalink raw reply

* [PATCH] builtin-send-pack.c: avoid empty structure initialization
From: Brandon Casey @ 2009-03-27  2:37 UTC (permalink / raw)
  To: gitster; +Cc: git
In-Reply-To: <vllV2KFOw2fPeT48N4ZHBW8HPcTVqhWLjdDJnF8chb2m9lcTJFlnjPZc-WBSwqdoNvvZc5cQ0A4@cipher.nrlssc.navy.mil>

The IRIX6.5 MIPSpro Compiler doesn't like it.

Signed-off-by: Brandon Casey <casey@nrlssc.navy.mil>
---


Signed version, sorry.


 builtin-send-pack.c |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)

diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 91c3651..d5a1c48 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -10,8 +10,7 @@ static const char send_pack_usage[] =
 "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
 "  --all and explicit <ref> specification are mutually exclusive.";
 
-static struct send_pack_args args = {
-};
+static struct send_pack_args args;
 
 static int feed_object(const unsigned char *sha1, int fd, int negative)
 {
-- 
1.6.2.16.geb16e

^ permalink raw reply related

* [PATCH] builtin-send-pack.c: avoid empty structure initialization
From: Brandon Casey @ 2009-03-27  2:33 UTC (permalink / raw)
  To: gitster; +Cc: git

The IRIX6.5 MIPSpro Compiler doesn't like it.
---
 builtin-send-pack.c |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)

diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 91c3651..d5a1c48 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -10,8 +10,7 @@ static const char send_pack_usage[] =
 "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
 "  --all and explicit <ref> specification are mutually exclusive.";
 
-static struct send_pack_args args = {
-};
+static struct send_pack_args args;
 
 static int feed_object(const unsigned char *sha1, int fd, int negative)
 {
-- 
1.6.2.16.geb16e

^ permalink raw reply related

* Re: [PATCH] avoid possible overflow in delta size filtering computation
From: Nicolas Pitre @ 2009-03-27  2:23 UTC (permalink / raw)
  To: Kjetil Barvik; +Cc: Junio C Hamano, git
In-Reply-To: <86y6usah1p.fsf@broadpark.no>

On Thu, 26 Mar 2009, Kjetil Barvik wrote:

> Nicolas Pitre <nico@cam.org> writes:
> 
> > Here you go.  If you want a perfectly deterministic repacking, you'll 
> > have to force the pack.threads config option to 1.
> 
>   OK, have rerun the test again with pack.config set to 1, and got the
>   exact same result(1) as above this time also!  :-)

Good.

>   Give me a hint if you want some debug info from the 2 pack/idx files.

I think there is nothing else to debug.  Having some differences between 
successive threaded repacks is expected.


Nicolas

^ permalink raw reply

* Re: Implementing stat() with FindFirstFile()
From: Johannes Schindelin @ 2009-03-27  2:25 UTC (permalink / raw)
  To: Magnus Bäck, Johannes Sixt; +Cc: git
In-Reply-To: <20090326213907.GC27249@jeeves.jpl.local>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 2024 bytes --]

Hi,

Magnus, it is the official policy to reply-to-all on this list.  This has 
been mentioned in the past quite often, and it will be mentioned in the 
future, too.

You actually forced me to manually look up and re-add Hannes' address.  I 
do not appreciate having to waste my time like that.

If that sounds negative, please understand that I am used to the ways of 
this list, and when I am annoyed by somebody not fitting in, then it is 
not totally _my_ mistake.

On Thu, 26 Mar 2009, Magnus Bäck wrote:

> On Thursday, March 26, 2009 at 08:15 CET,
>      Johannes Sixt <j.sixt@viscovery.net> wrote:
> 
> > Magnus Bäck schrieb:
> >
> > > From what I gather the problematic conversion takes place in the 
> > > Win32 layer, in which case we might be able to call the 
> > > ZwQueryDirectoryFile() kernel routine directly via ntdll.dll to 
> > > obtain the file times straight from the file system. Has anyone 
> > > explored that path, and would it be acceptable to make such a 
> > > change?
> >
> > It depends.
> >
> > The disadvantages are that this function is only available on Windows 
> > XP and later and that it is not present in the header files of MinGW 
> > gcc.
> 
> I'd be very surprised if ZwQueryDirectoryFile() hasn't always been 
> around (I just verified ntdll.dll from NT 4.0), so that's not a worry. 
> Don't know why MSDN reports it as introduced in XP.

As the current maintainer of msysGit, I refuse to have something in the 
installer I ship that relies on not-at-all guaranteed interfaces.

> > It's on you to prove that there are advantages that clearly outweigh 
> > these disadvantages.
> 
> All right, I'll see if I can find time to take a look at this. I just 
> wanted to check that it wasn't a project policy or whatever to bypass 
> Win32.

You can do whatever you want... This is Open Source.

However, I will try to stay with the officially supported functionality, 
even if that makes msysGit slower -- Windows will never reach the 
performance levels of Linux anyway.

Ciao,
Dscho

^ permalink raw reply

* Re: [PATCH 01/10] refs: add "for_each_bisect_ref" function
From: Johannes Schindelin @ 2009-03-27  2:08 UTC (permalink / raw)
  To: Christian Couder
  Cc: Michael J Gruber, Sverre Rabbelier, Junio C Hamano, git,
	John Tapsell
In-Reply-To: <200903270141.57426.chriscool@tuxfamily.org>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 2615 bytes --]

Hi,

On Fri, 27 Mar 2009, Christian Couder wrote:

> Le jeudi 26 mars 2009, Johannes Schindelin a écrit :
>
> > On Thu, 26 Mar 2009, Michael J Gruber wrote:
> > > Christian Couder venit, vidit, dixit 26.03.2009 08:48:
> > > > Le jeudi 26 mars 2009, Sverre Rabbelier a écrit :
> > > >> A 10 patches series with no cover letter?
> > > >
> > > > I am not a big fan of cover letters. Usually I prefer adding 
> > > > comments in the patches.
> > >
> > > I'm sorry I have to say that, but your individual preferences don't 
> > > matter. Many of us would do things differently, each in their own 
> > > way, but people adjust to the list's preferences. It's a matter of 
> > > attitude. So, please...
> >
> > Actually, a better way to ask for a cover letter would have been to 
> > convince Christian.  So I'll try that.
> 
> Thanks.
> 
> As you know, I have been sending patches since nearly 3 years ago to 
> this list. And it's only since a few weeks ago that I am asked to send 
> cover letters...

Heh, I have the feeling that your patch series were much shorter, and did 
not have many revisions, until a few weeks ago ;-)

> > From the patch series' titles (especially when they are cropped due to 
> > the text window being too small to fit the indented thread), it is not 
> > all that obvious what you want to achieve with those 10 patches.
> >
> > From recent discussions, I seem to remember that you wanted to have 
> > some cute way to mark commits as non-testable during a bisect, and I 
> > further seem to remember that Junio said that very method should be 
> > usable outside of bisect, too.
> 
> Well, we want to move "git bisect skip" code from shell (in 
> "git-bisect.sh") to C. So this patch series does that by creating a new 
> "git bisect--helper" command in C that contains the new code and using 
> that new command in "git-bisect.sh".

Oh?  I _completely_ missed that.  And that's being one of the original 
Cc:ed persons...

> > Unfortunately, that does not reveal to me, quickly, what is the 
> > current state of affairs, and what you changed since the last time.
> 
> Yeah, I should have at least put something in the comment section of my 
> first patch in this series.

No.  I would still have missed it.

The cover letter is outside of any patch, because it describes the purpose 
of the _whole_ patch series, not just one patch.

So, it would have been nice to get a heads-up that this is not your 
bisect-skip-a-whole-bunch-of-commits series, but a new animal.

This way, I decided I do not have time for something I do not need, and 
deleted it without having a look.

Ciao,
Dscho

^ permalink raw reply

* Re: [PATCH 1/2] init: support --import to add all files and commit right after init
From: Johannes Schindelin @ 2009-03-27  2:03 UTC (permalink / raw)
  To: Markus Heidelberg
  Cc: Santi Béjar, Jeff King, Nguyễn Thái Ngọc,
	git
In-Reply-To: <200903262223.28546.markus.heidelberg@web.de>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 1112 bytes --]

Hi,

On Thu, 26 Mar 2009, Markus Heidelberg wrote:

> Santi Béjar, 25.03.2009:
> > 2009/3/25 Jeff King <peff@peff.net>:
> > > On Wed, Mar 25, 2009 at 01:38:30PM +0100, Johannes Schindelin wrote:
> > >
> > >> > > +If no message is given, "Initial commit" will be used.
> > >> >
> > >> > Why a default message and not running the editor?
> > >>
> > >> Because I would say "Initial commit" anyway.
> > 
> > And I would say "Commit inicial".
> 
> And I would describe the current state in a few words.
> 
> Invoking an editor is more universal and I don't think the majority
> would be contented with "Initial commit".

_Again_, as Peff pointed out, you are welcome to use the current method of 
git init && git add . && git commit, which _does_ launch an editor.

The fact that you want to spend much time (anyway) doing your initial 
commit does not allow you to inconvenience others.

Others who want to have a quick way to work safely with something they 
might need to change, and might then want to use the full power of Git to 
see what they changed.  Without any need for a "nice" first commit.

Ciao,
Dscho

^ permalink raw reply

* Re: Test that every revision builds before pushing changes?
From: Daniel Pittman @ 2009-03-27  1:30 UTC (permalink / raw)
  To: git
In-Reply-To: <49CB4ED8.4060205@op5.se>

Andreas Ericsson <ae@op5.se> writes:
> Daniel Pittman wrote:
>> Andreas Ericsson <ae@op5.se> writes:
>>> Daniel Pittman wrote:
>>>> I would like to ensure that my commits are fully bisectable before I
>>>> commit them to an upstream repository, at least to the limits of an
>>>> automatic tool for testing them.

[...]

>>> The manual step comes at merge-time; Someone has to be responsible for
>>> merging all the topics that are to be included in the release branch
>>> and make sure it builds and passes all tests after each merge.
>>
>> Ah.  You have not quite grasped what I was looking for: I was after a
>> tool to help automate that step, rather than a workflow around it.
>
> Oh right. Sorry, I'm stuck in continuous-integration land where people
> tend to want the server to take care of such things.

We have that also; I am primarily motivated by avoiding trivial breakage
in the CI server, as well as bisection.

>> For example, the responsible person for that testing could use the
>> hypothetical (until someone tells me where to find it):
>>
>>     git test public..test make test

[...]

> Something like this?
> --%<--%<--
> #!/bin/sh
>
> git stash
> revspec="$1"
> shift
> for rev in $(git rev-list "$revspec"); do
> 	git checkout $rev
> 	"$@" || break
> done
> --%<--%<--
>
> Run it as such:
> ./git-test.sh public..test make test

Thank you, that points me in the right direction, and I can obviously
season the rest of it to taste — reverse that revision list, for
example.

Thank you also to Wincent Colaiuta, who provided a similar script albiet
with significantly more detail.

[...]

> It doesn't handle merges very nicely, btw, but I guess this should be
> run prior to merging anyways.

Once I have the framework I can quietly work my way through fixing nasty
issues one way or another.  Thanks.

Regards,
        Daniel

^ permalink raw reply

* sending patch sets (was: Re: [PATCH 01/10] refs: add "for_each_bisect_ref" function)
From: Julian Phillips @ 2009-03-27  1:32 UTC (permalink / raw)
  To: Christian Couder
  Cc: Johannes Schindelin, Michael J Gruber, Sverre Rabbelier,
	Junio C Hamano, git, John Tapsell
In-Reply-To: <200903270141.57426.chriscool@tuxfamily.org>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 806 bytes --]

On Fri, 27 Mar 2009, Christian Couder wrote:

> If someone knows some other tools that can easily send a threaded patch
> series, I will try to see if I can use them...

I long ago gave up on send-email, as it seemed to cumbersome for what I 
wanted, and my perl had got so rusty I really couldn't face trying to 
improve it.

So I wrote a replacement in Python (attached), which I have subsequently 
used for all patches I've sent.  It calls format-patch, passing through 
arguments (and you can use -- to let it pass options too).

(the only setting it reads from git config atm is mail-commit.to)

I find it much easier to use than send-email, but as usual YMMV ...

-- 
Julian

  ---
Have you seen the latest Japanese camera?  Apparently it is so fast it can
photograph an American with his mouth shut!

[-- Attachment #2: git-mail-commits --]
[-- Type: TEXT/PLAIN, Size: 11904 bytes --]

#!/usr/bin/python

import optparse
import os
import random
import re
import smtplib
import socket
import sys
import tempfile
import time

from cStringIO import StringIO
from email.Generator import Generator
from email.Message import Message
from email.Parser import FeedParser, Parser
from email.Utils import parseaddr, parsedate, formatdate, \
                        getaddresses, formataddr

my_version = "0.1"
my_name = "git-mail-commits"
this_script = "%s v%s" % (my_name, my_version)

gci_re = re.compile("^(?P<email>(.*) <(.*)>) (\d+ [+-]\d{4})$")

smtp_server = "neutron"

# -----------------------------------------------------------------------------
def get_message_text(message):
    text_msg = StringIO()
    gen = Generator(text_msg, mangle_from_=False)
    gen.flatten(message)
    return text_msg.getvalue()
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
def send_message(msg):
    sent = (None, 'No destination address given')

    (a, fromaddr) = parseaddr(msg.get('From'))
    toaddr_list = msg.get_all('To', [])
    ccaddr_list = msg.get_all('CC', [])
    bccaddr_list = msg.get_all('BCC', [])
    all_recips = getaddresses(toaddr_list + ccaddr_list + bccaddr_list)
    to_list = [ email for (name, email) in all_recips ]

    if len(to_list) > 0:
        server = smtplib.SMTP(smtp_server)
        try:
            errors = server.sendmail(fromaddr, to_list, get_message_text(msg))
            sent = (errors, "Failed to send to one or more recipients")
        except smtplib.SMTPRecipientsRefused, rr:
            sent = (rr.recipients, "Failed to send to all recipients")
        server.quit()

    return sent
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
def get_msgid(idstring=None, idhost=socket.getfqdn(), email=None):
    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:

    <20020201195627.33539.96671@nightshade.la.mastaler.com>

    Optional idstring if given is a string used to strengthen the
    uniqueness of the message id.

    Based on email.Utils.make_msgid
    """
    timeval = time.time()
    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
    pid = os.getpid()
    randint = random.randrange(100000)
    if email is not None:
        ids = ".%s" % (email)
    else:
        if idstring is None:
            idstring = ''
        else:
            idstring = '.' + idstring
        ids = "%s@%s" % (idstring, idhost)
    msgid = '<%s.%s.%s%s>' % (utcdate, pid, randint, ids)
    return msgid
# -----------------------------------------------------------------------------

def get_patches(args, numbered=True, signoff=True):
    patches = []
    cur_patch = None
#    print args
    opts = ['-M']
    if numbered:
        opts.append("-n")
    if signoff:
        opts.append("-s")
    fp = os.popen("git format-patch --stdout %s %s" % (' '.join(opts),
                                                       ' '.join(args)))
    for line in fp.readlines():
        if line[:5] == "From ":
            if cur_patch is not None:
                patches.append(cur_patch.close())
            cur_patch = FeedParser()
        cur_patch.feed(line)
    fp.close()
    if cur_patch is not None:
        patches.append(cur_patch.close())
    return patches

def format_patches(patches, to, initial_msg_id=None,
                   addr=None, cc_list=[]):
    refs = []
    if initial_msg_id is not None:
        refs.append(initial_msg_id)
    for patch in patches:
        if addr is not None:
            patch.replace_header("From", addr)
        if len(cc_list) > 0:
            cc = [x.strip() for x in patch.get("CC", "").split(",")]
            if len(cc) == 1 and cc[0] == "":
                cc = cc_list
            else:
                cc.extend(cc_list)
            del patch['CC']
            print cc
            patch.add_header("CC", ",".join(cc))
        subject = patch.get("Subject")
        (name, email) = parseaddr(patch.get("From"))
        sha1 = patch.get_unixfrom()[5:46]
        msg_id = get_msgid(email=email)
        patch.add_header("To", to)
        del patch['Message-Id']
        patch.add_header("Message-Id", msg_id)
        patch.add_header("X-git-sha1", sha1)
        del patch['X-Mailer']
        patch.add_header("X-Mailer", this_script)
        if len(refs) > 0:
            del patch['In-Reply-To']
            del patch['References']
            patch.add_header("In-Reply-To", refs[-1])
            patch.add_header("References", ' '.join(refs))
        refs.append(msg_id)
#        print "%s - %s" %(sha1[0:7], subject)
#        print patch

def get_git_committer_email():
    gv = os.popen("git var GIT_COMMITTER_IDENT")
    gci = gv.readline()
    gv.close()
#    print gci
    gci_m = gci_re.search(gci)
    if gci_m:
        return gci_m.group('email')
    else:
        print "Unable to get/parse GIT_COMMITTER_IDENT"
        sys.exit(-1)

def get_intro_msg(to, frm_addr, count, filename=None):
    if filename is None:
        if 'EDITOR' not in os.environ:
            print "$EDITOR not set, please set."
            sys.exit(-1)
        (fd, fname) = tempfile.mkstemp()
        f = os.fdopen(fd)
        ret = os.system("%s %s" % (os.environ['EDITOR'], fname))
        if not (os.WIFEXITED(ret) and os.WEXITSTATUS(ret) == 0):
            print "Failed to edit intro message."
            sys.exit(-1)
    else:
        f = open(filename)
    slist = []
    blist = []
    cur = slist
    for line in f.readlines():
        if line == "\n":
            cur = blist
            continue
        cur.append(line)
    f.close()
    if filename is None:
        os.remove(fname)
    subject = ''.join(slist).replace('\n', ' ')
    body = ''.join(blist)
    if subject == "":
        print "No subject for intro message, aborting."
        sys.exit(-1)
    msg = Message()
    msg.add_header('From', frm_addr)
    msg.add_header('To', to)
    (name, email) = parseaddr(msg.get("From"))
    msg.add_header('Message-Id', get_msgid(email=email))
    msg.set_payload(body)
    msg.add_header('Subject', "[PATCH 0/%d] %s" % (count, subject))
    msg.add_header("X-Mailer", this_script)
    return msg

def reply_to(fname):
    p = Parser()
    f = open(fname)
    msg = p.parse(f)
    f.close()
    cc = [x.strip() for x in msg['CC'].split(',')]
    return (msg['From'], msg['Message-ID'], cc)

def main():
    description = "send the given commits as patch emails to the specified " \
                  "address, all non-option arguments are passed to " \
                  "git-format-patch (\"--\" can be used to indicate the end " \
                  "of the options for this script)."
    
    parser = optparse.OptionParser(description=description)
    parser.disable_interspersed_args()

    parser.add_option("", "--to", action="store", default=None,
                      help="the address to send the mails to")

    parser.add_option("", "--cc", action="append", default=[],
                      help="copy the mails to this address")

    parser.add_option("", "--reply-to", action="store", default=None,
                      help="reply to the given mail (rather than use an intro)")

    parser.add_option("-n", "--numbered", action="store_true", default=False,
                      help="send numbered patches (adds -n to the "
                      "git-format-patch options)")

    parser.add_option("-f", "--from", dest="frm_addr", action="store",
                      default=None,
                      help="set FROM as the from address, otherwise use "
                      "GIT_COMMITTER_IDENT")

    parser.add_option("-i", "--intro", dest="intro", action="store_true",
                      help="start with a 0/n intro message using $EDITOR to "
                      "write the message (implies -n).")
    parser.add_option("-I", "--intro-file", dest="intro", action="store",
                      help="start with a 0/n intro message read from INTRO "
                      "(implies -n).")
    parser.set_defaults(intro=None)

    parser.add_option("-e", "--edit", action="store_true", default=False,
                      help="edit the patches before sending")

    parser.add_option("-S", "--no-signoff", dest="signoff",
                      action="store_false", default=True,
                      help="Don't signoff the patches")

    (options, args) = parser.parse_args()

    if len(args) < 1:
        print "You must specify at least one commit to send ..."
        sys.exit(-1)

    if options.to is None:
        gc = os.popen("git config mail-commits.to")
        to = gc.read()
        gc.close()
        if to == "":
            print "you must specify the destination using --to"
            sys.exit(-1)
        else:
            options.to = to.strip()

    print "Sending to: %s\n" % options.to

    if options.frm_addr is not None:
        frm_addr = options.frm_addr
    else:        
        frm_addr = get_git_committer_email()

    if options.intro is not None:
        options.numbered = True

    patches = get_patches(args, options.numbered, signoff=options.signoff)

    intro_msg = None
    intro_msg_id = None
    if options.intro is not None:
        fname = None
        if options.intro is not True:
            fname = options.intro
        intro_msg = get_intro_msg(options.to, frm_addr, len(patches),
                                  filename=fname)
        intro_msg_id = intro_msg.get('Message-Id')

    if options.reply_to is not None:
        (options.to, intro_msg_id, cc) = reply_to(options.reply_to)
        options.cc.extend(cc)

#    print intro_msg

    format_patches(patches, to=options.to, cc_list=options.cc,
                   initial_msg_id=intro_msg_id,
                   addr=frm_addr)

    if options.edit:
        new_patches = []
        for patch in patches:
            (fd, fname) = tempfile.mkstemp()
            f = os.fdopen(fd, "w+")
            f.write(get_message_text(patch))
            f.close()
            if 'EDITOR' not in os.environ:
                print "$EDITOR not set, please set."
                sys.exit(-1)
            ret = os.system("%s %s" % (os.environ['EDITOR'], fname))
            if not (os.WIFEXITED(ret) and os.WEXITSTATUS(ret) == 0):
                print "Failed to edit patch (%d)." % ret
                sys.exit(-1)
            p = FeedParser()
            f = open(fname)
            for line in f:
                p.feed(line)
            m = p.close()
            if m:
                new_patches.append(m)
            f.close()
            os.remove(fname)
        patches = new_patches

    if intro_msg:
        print intro_msg['Subject']
    for patch in patches:
        print patch['Subject']
    print
    print "Press [Enter] to send patches, Ctrl-C to cancel."
    try:
        raw_input()
    except KeyboardInterrupt:
        print "Not sending patches."
        sys.exit(-1)

    print "Sending patches ...\n"

    msgs=[intro_msg]
    msgs.extend(patches)
    for msg in msgs:
        if msg is None:
            continue
        (errors, errmsg) = send_message(msg)
        if len(errors) == 0:
            print "sent %s" % msg['Subject']
        else:
            print "error sending %s" % msg['Subject']
            for (name, (ecode, emsg)) in errors.items():
                print "  %s: %s %s" % (name, ecode, emsg)

if __name__ == "__main__":
    main()

^ permalink raw reply

* Re: Git for collaborative web development
From: Giuseppe Bilotta @ 2009-03-27  0:57 UTC (permalink / raw)
  To: git
In-Reply-To: <loom.20090326T184207-345@post.gmane.org>

On Thursday 26 March 2009 19:51, Carlo wrote:

> Hi,
> I'm going to work on a web project with a friend. We will host our code on a
> server and my idea was to set up a git repository (maybe using gitosis) so that
> we could work on our machines and push the changes to the server.
> 
> He said that it would be complicated, because if he's going to try some changes
> to show me on the fly he just can't. I mean, he would like to work on the code
> directly on the server, so that he can change the code, save and I can just
> refresh the page from my browser and see what he did.
> 
> Using git he should save, commit, add, push... so it's a bit longer.
> 
> Is there a nice compromise? Or a better way to use git for such a task or web
> development in general?

You could try using gitweb to expose the local repository directly.

If the web pages don't rely on hard-coded paths (i.e. all links are
relative) you should actually be able to navigate the tentative website
in plain blob view mode, for any given commit or branch.


-- 
Giuseppe "Oblomov" Bilotta

^ permalink raw reply

* Re: [PATCH 01/10] refs: add "for_each_bisect_ref" function
From: Christian Couder @ 2009-03-27  0:41 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Michael J Gruber, Sverre Rabbelier, Junio C Hamano, git,
	John Tapsell
In-Reply-To: <alpine.DEB.1.00.0903261748280.12753@intel-tinevez-2-302>

Le jeudi 26 mars 2009, Johannes Schindelin a écrit :
> Hi,
>
> On Thu, 26 Mar 2009, Michael J Gruber wrote:
> > Christian Couder venit, vidit, dixit 26.03.2009 08:48:
> > > Le jeudi 26 mars 2009, Sverre Rabbelier a écrit :
> > >> A 10 patches series with no cover letter?
> > >
> > > I am not a big fan of cover letters. Usually I prefer adding comments
> > > in the patches.
> >
> > I'm sorry I have to say that, but your individual preferences don't
> > matter. Many of us would do things differently, each in their own way,
> > but people adjust to the list's preferences. It's a matter of attitude.
> > So, please...
>
> Actually, a better way to ask for a cover letter would have been to
> convince Christian.  So I'll try that.

Thanks.

As you know, I have been sending patches since nearly 3 years ago to this 
list. And it's only since a few weeks ago that I am asked to send cover 
letters...

> From the patch series' titles (especially when they are cropped due to
> the text window being too small to fit the indented thread), it is not
> all that obvious what you want to achieve with those 10 patches.
>
> From recent discussions, I seem to remember that you wanted to have some
> cute way to mark commits as non-testable during a bisect, and I further
> seem to remember that Junio said that very method should be usable
> outside of bisect, too.

Well, we want to move "git bisect skip" code from shell (in "git-bisect.sh") 
to C. So this patch series does that by creating a new "git bisect--helper" 
command in C that contains the new code and using that new command 
in "git-bisect.sh".

> Unfortunately, that does not reveal to me, quickly, what is the current
> state of affairs, and what you changed since the last time.

Yeah, I should have at least put something in the comment section of my 
first patch in this series.

And I try to improve, you know, I even tried to use "git send-email" again 
this morning to see if perhaps I could use it to send my patch series.

I did:

$ git send-email --compose --dry-run bh15/*
Can't call method "repo_path" on an undefined value 
at /home/christian/libexec/git-core//git-send-email line 160.

and then I gave up, because I don't like spending a lot of my free time to 
fight with tools I don't like.

If someone knows some other tools that can easily send a threaded patch 
series, I will try to see if I can use them...

Thanks in advance,
Christian.

^ permalink raw reply

* Re: [PATCH 09/10] bisect: implement "read_bisect_paths" to read paths in "$GIT_DIR/BISECT_NAMES"
From: Christian Couder @ 2009-03-27  0:18 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, John Tapsell, Johannes Schindelin
In-Reply-To: <7vwsacrd6x.fsf@gitster.siamese.dyndns.org>

Le jeudi 26 mars 2009, Junio C Hamano a écrit :
> Christian Couder <chriscool@tuxfamily.org> writes:
> > This is needed because  "git bisect--helper" must read bisect paths
> > in "$GIT_DIR/BISECT_NAMES", so that a bisection can be performed only
> > on commits that touches paths in this file.
> >
> > Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
>
> Again, very nice.

Thanks, but I found a big bug in this patch.
It doesn't work when there is more than one path in "$GIT_DIR/BISECT_NAMES"
because they are all on the same line, not on different lines.
I will try to fix it soon and provide some test cases.

Best regards,
Christian.

^ permalink raw reply

* [PATCH] log-tree: fix patch filename computation in "git format-patch"
From: Christian Couder @ 2009-03-27  0:13 UTC (permalink / raw)
  To: Junio C Hamano, Stephen Boyd; +Cc: git

When using "git format-patch", "get_patch_filename" in
"log-tree.c" calls "strbuf_splice" that could die with
the following message:

"`pos + len' is too far after the end of the buffer"

if you have:

	buf->len < start_len + FORMAT_PATCH_NAME_MAX

but:

	buf->len + suffix_len > start_len + FORMAT_PATCH_NAME_MAX

This patch tries to get rid of that bug.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 log-tree.c |   11 ++++++-----
 1 files changed, 6 insertions(+), 5 deletions(-)

This bug happens on "pu".

diff --git a/log-tree.c b/log-tree.c
index 56a3488..ade79ab 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -187,16 +187,17 @@ void get_patch_filename(struct commit *commit, int nr, const char *suffix,
 
 	strbuf_addf(buf, commit ? "%04d-" : "%d", nr);
 	if (commit) {
+		int max_len = start_len + FORMAT_PATCH_NAME_MAX;
 		format_commit_message(commit, "%f", buf, DATE_NORMAL);
 		/*
 		 * Replace characters at the end with the suffix if the
 		 * filename is too long
 		 */
-		if (buf->len + suffix_len > FORMAT_PATCH_NAME_MAX + start_len)
-			strbuf_splice(buf,
-				start_len + FORMAT_PATCH_NAME_MAX - suffix_len,
-				suffix_len, suffix, suffix_len);
-		else
+		if (buf->len + suffix_len > max_len) {
+			int base = (max_len > buf->len) ? buf->len : max_len;
+			strbuf_splice(buf, base - suffix_len, suffix_len,
+				      suffix, suffix_len);
+		} else
 			strbuf_addstr(buf, suffix);
 	}
 }
-- 
1.6.2.1.506.g7aa09

^ permalink raw reply related

* How to merge by subtree while preserving history?
From: David Reitter @ 2009-03-26 22:59 UTC (permalink / raw)
  To: git

I have two separately developed projects (foo, bar) which I'd like to  
merge; the contents of foo should, initially, go in a subdirectory of  
bar.

I'm aware of two methods:  moving (renaming) everything within foo  
into foo-dir, and then just pulling foo into bar.

This works beautifully, except that the big rename causes havoc w.r.t.  
to the files histories, i.e. git-log needs a "--follow" argument now,  
and "diff-tree" can't track changes when given the new file name.  No  
good.

I've also tried the method described in [1], but it seems that all  
history is lost here (the text could point this out..)
I've tried to "git pull -s subtree foo master" directly as well, but  
then it put foo into strange places (and lost the history).


So, I'm at a loss.  Suggestions much appreciated.




[1] http://www.kernel.org/pub/software/scm/git/docs/howto/using-merge-subtree.html

^ permalink raw reply

* Re: Improve tags
From: Nanako Shiraishi @ 2009-03-26 21:53 UTC (permalink / raw)
  To: Etienne Vallette d'Osia; +Cc: git
In-Reply-To: <49CB798B.4090107@gmail.com>

Quoting "Etienne Vallette d'Osia" <dohzya@gmail.com>:

> In addition, branches are a way to specify streams,
> not a way to specify an aim for a commit.
> (like in ruby a class is a method container, not a type)
> So branch names are often like next, pu, dev, test, stupid-idea, etc.
> They are totally useless for tracking aims.

Why should that be?  'next' clearly states the aim (it is to serve as an
integration testing area for the possible new features for the next
release).

Quoting http://article.gmane.org/gmane.comp.version-control.git/113812

 (1) Name your (eh, "my") branch just like you name your function.

     You probably learned in programming 101 course the importance of
     giving a good name to your functions.  The same principle applies.
     When I see kb/checkout-optim branch, I know it is about optimizing
     the checkout command, and it came from Kjetil Barvik.  I can tell
     that jc/maint-1.6.0-read-tree-overlay is about the bugfix to the
     "overlay" feature of read-tree command, and the fix would apply as
     far back as the 1.6.0.X series, not just the current maintenance.


-- 
Nanako Shiraishi
http://ivory.ap.teacup.com/nanako3/

^ permalink raw reply

* Re: Implementing stat() with FindFirstFile()
From: Magnus Bäck @ 2009-03-26 21:39 UTC (permalink / raw)
  To: git
In-Reply-To: <49CB2BA5.1070100@viscovery.net>

On Thursday, March 26, 2009 at 08:15 CET,
     Johannes Sixt <j.sixt@viscovery.net> wrote:

> Magnus Bäck schrieb:
>
> > From what I gather the problematic conversion takes place in
> > the Win32 layer, in which case we might be able to call the
> > ZwQueryDirectoryFile() kernel routine directly via ntdll.dll
> > to obtain the file times straight from the file system. Has
> > anyone explored that path, and would it be acceptable to make
> > such a change?
>
> It depends.
>
> The disadvantages are that this function is only available on
> Windows XP and later and that it is not present in the header
> files of MinGW gcc.

I'd be very surprised if ZwQueryDirectoryFile() hasn't always
been around (I just verified ntdll.dll from NT 4.0), so that's
not a worry. Don't know why MSDN reports it as introduced in XP.

> It's on you to prove that there are advantages that clearly
> outweigh these disadvantages.

All right, I'll see if I can find time to take a look at this.
I just wanted to check that it wasn't a project policy or whatever
to bypass Win32.

-- 
Magnus Bäck
baeck@swipnet.se

^ permalink raw reply

* Re: [PATCH 1/2] Add feature release instructions to MaintNotes addendum
From: Raman Gupta @ 2009-03-26 21:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Nanako Shiraishi, git
In-Reply-To: <7vprg4m3k9.fsf@gitster.siamese.dyndns.org>

Junio C Hamano wrote:
> Raman Gupta <rocketraman@fastmail.fm> writes:
> 
>> Nanako Shiraishi wrote:
>>> Quoting rocketraman@fastmail.fm:
>>>
>>>> + - The 'maint' branch is updated to the new release.
>>>> +
>>>> +     $ git checkout maint
>>>> +     $ git merge master
>>>> +
>>>> +   This is equivalent to deleting maint and recreating it from
>>>> +   master, but it preserves the maint reflog.
>>> After giving a recipe that is better than an alternative, what's
>>> the point of describing an inferior alternative as "equivalent",
>>> when it is obviously not "equivalent"?
>> Is this better:
>>
>> The resulting maint tree is equivalent to deleting maint and
>> recreating it from the tip of master, but merging from master
>> preserves the maint reflog.
> 
> It is unclear what you are trying to explain with these two (in your
> original) or three (your rewrite) lines.  As an explanation for the two
> command sequence, I would expect to see:
> 
>     "This merges the tip of the master into maint".
> 
> But that is literally what the command sequence does, so it goes without
> saying.

Let me see if I can explain why I think the extra verbiage, at least
in some form, is useful...

It is my understanding that the _goal_ in this case is for the maint
tree to match the master tree (so that the maint tree matches the new
feature release). The "obvious" way to do that, at least for less
experienced folks, is to delete maint, and recreate it from the tip of
master (or from the feature release tag which should be the same commit).

In this particular case, because and only because of the semantics of
the maint and master branch i.e. we know that master already contains
everything that maint does, merging from master to maint makes the
trees equivalent, while *also* maintaining the reflog. However,
someone less familiar with the semantics of the maint and master
branches may not draw this conclusion automatically.

BTW, would:

git branch -f maint master

be another way of doing this?

> If there is anything that needs to be said further, I think it is not how
> delete-then-recreate is inappropriate (I do not think it is even worth
> teaching).  But you may want to explain the reason _why_ maint gets this
> update from master.  I thought the explanation "... is updated to the new
> release" already covers that motivation, but if you want to make the
> description really novice-friendly, you _could_ say something like:
> 
>     Now a new release X.Y.Z is out, the 'maint' branch will be used to
>     manage the fixes to it.  The branch used to be used for managing the
>     fixes to X.Y.(Z-1), and does not have any feature development that
>     happened between X.Y.(Z-1) and X.Y.Z.  Because these changes are
>     contained in the 'master' branch, we can merge 'master' to 'maint' to
>     have the latter have them, which prepares it to be used for managing
>     the fixes to X.Y.Z.
> 
> I personally would not want to see somebody who needs the above to be
> explained to take over git maintenance after I get hit by a wayward bus,
> by the way ;-)

:) Very true, but there are lots of people out there who are trying to
understand and use git, and when they come across documentation like
this they rightfully think "hey if this works for the git.git guys, it
would probably be a pretty good starting point for me as well!". I
know I did. So a bit of explanation may be appropriate, even though
its not relevant for your intended audience. On the other hand, maybe
the newbie-level explanation can be skipped here, and instead be put
into gitworkflows(7). For my next patch iteration, I'll assume that's
what you want unless you tell me otherwise.

Cheers,
Raman

^ permalink raw reply

* [EGIT] [PATCH RFC v1 1/5] Build up the ignore patterns cache upon workspace startup.
From: Ferry Huberts @ 2009-03-26 21:34 UTC (permalink / raw)
  To: git; +Cc: Shawn O. Pearce, Robin Rosenberg, Ferry Huberts
In-Reply-To: <cover.1238102327.git.ferry.huberts@pelagic.nl>

Read in all relevant ignore files: all .gitignore files in the projects,
all .gitignore files between the checkout directory and the project
directories, the info/exclude ignore files of the repositories, the
repository core.excludesfile ignore files and the global
core.excludesfile ignore file.

Signed-off-by: Ferry Huberts <ferry.huberts@pelagic.nl>
---
 .../src/org/spearce/egit/core/ignores/Exclude.java |  158 ++++++
 .../spearce/egit/core/ignores/GitIgnoreData.java   |  139 +++++
 .../org/spearce/egit/core/ignores/IgnoreFile.java  |   82 +++
 .../egit/core/ignores/IgnoreFileOutside.java       |  543 ++++++++++++++++++++
 .../egit/core/ignores/IgnoreProjectCache.java      |  201 ++++++++
 .../egit/core/ignores/IgnoreRepositoryCache.java   |  308 +++++++++++
 .../spearce/egit/core/project/GitProjectData.java  |    5 +
 7 files changed, 1436 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFile.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFileOutside.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
new file mode 100644
index 0000000..c4c48e9
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Ferry Huberts <ferry.huberts@pelagic.nl>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.ignores;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class describes an ignore pattern in the same way as git does, with some
+ * extra information to support Eclipse specific functionality.
+ * 
+ * The git definition can be found in the source file dir.h, within the
+ * exclude_list structure definition. The code can be found in the source file
+ * dir.c:excluded_1
+ */
+class Exclude {
+	/** the pattern to match */
+	private String pattern = null;
+
+	/**
+	 * the directory in which the pattern is anchored, relative to the checkout
+	 * directory and with a trailing slash (except when in the checkout
+	 * directory, in which case it will be an empty string). Slashes are in Unix
+	 * format: forward slashes
+	 */
+	private String base = null;
+
+	/**
+	 * true when the resource must be excluded when matched, false in case of a
+	 * negative pattern: when it must be included
+	 */
+	private boolean to_exclude = true;
+
+	/** true when the resource must be a directory */
+	private boolean mustBeDir = false;
+
+	/** true when the pattern does not contain directories */
+	private boolean noDir = false;
+
+	/** true when the resource must end with pattern.substring(1) */
+	private boolean endsWith = false;
+
+	/** true when the pattern has no wildcards */
+	private boolean noWildcard = false;
+
+	/*
+	 * Extra Information
+	 */
+
+	/**
+	 * the full path name of the ignore file. Stored so that a user can ask
+	 * 'which pattern in which ignore file makes this resource be ignored?'
+	 */
+	private String ignoreFileAbsolutePath = null;
+
+	/**
+	 * the line number of the pattern in the ignore file. Stored for the same
+	 * reason as the ignoreFileFullPath field
+	 */
+	private int lineNumber = 0;
+
+	/**
+	 * Constructor. See the git source file dir.c, method add_exclude
+	 * 
+	 * @param pattern
+	 *            the pattern to match
+	 * @param base
+	 *            the directory in which the pattern is anchored, relative to
+	 *            the checkout directory and with a trailing slash (except when
+	 *            in the checkout directory, in which case it will be an empty
+	 *            string). Slashes are in Unix format: forward slashes
+	 * @param ignoreFileAbsolutePath
+	 *            the full path name of the ignore file. Stored so that a user
+	 *            can ask 'which pattern in which ignore file makes this
+	 *            resource be ignored?'
+	 * @param lineNumber
+	 *            the line number of the pattern in the ignore file. Stored for
+	 *            the same reason as the ignoreFileFullPath field
+	 */
+	Exclude(final String pattern, final String base,
+			final String ignoreFileAbsolutePath, final int lineNumber) {
+		this.pattern = pattern;
+		this.base = base;
+
+		this.to_exclude = !this.pattern.startsWith("!");
+		if (!this.to_exclude) {
+			this.pattern = this.pattern.substring(1);
+		}
+
+		this.mustBeDir = this.pattern.endsWith("/");
+		if (this.mustBeDir) {
+			this.pattern = this.pattern.substring(0, this.pattern.length() - 1);
+		}
+		this.noDir = !this.pattern.contains("/");
+		this.noWildcard = no_wildcard(this.pattern);
+		this.endsWith = ((this.pattern.charAt(0) == '*') && no_wildcard(this.pattern
+				.substring(1)));
+
+		this.ignoreFileAbsolutePath = ignoreFileAbsolutePath;
+		this.lineNumber = lineNumber;
+	}
+
+	/*
+	 * Private Methods
+	 */
+
+	private static Pattern wildcardPattern = Pattern
+			.compile("^.*[\\*\\?\\[\\{].*$");
+
+	/* dir.c::no_wildcard */
+	private boolean no_wildcard(final String string) {
+		return !wildcardPattern.matcher(string).matches();
+	}
+
+	/*
+	 * Getters / Setters
+	 */
+
+	public String getIgnoreFileAbsolutePath() {
+		return ignoreFileAbsolutePath;
+	}
+
+	public int getLineNumber() {
+		return lineNumber;
+	}
+
+	/**
+	 * @return the base
+	 */
+	public String getBase() {
+		return base;
+	}
+
+	/**
+	 * @return the noDir
+	 */
+	public boolean isNoDir() {
+		return noDir;
+	}
+
+	/**
+	 * @return the endsWith
+	 */
+	public boolean isEndsWith() {
+		return endsWith;
+	}
+
+	/**
+	 * @return the noWildcard
+	 */
+	public boolean isNoWildcard() {
+		return noWildcard;
+	}
+}
\ No newline at end of file
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
new file mode 100644
index 0000000..401a378
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Ferry Huberts <ferry.huberts@pelagic.nl>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.ignores;
+
+import java.util.HashMap;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.spearce.egit.core.project.RepositoryMapping;
+import org.spearce.jgit.lib.Repository;
+
+/**
+ * This class provides management of ignore data. It deals with .gitignore
+ * files, the .git/info/exclude file, and core.excludefile settings. It also
+ * deals with with changes to those files.
+ * 
+ * The git code for ignores can be found in its files dir.{h,c}.
+ * 
+ * See the file Documentation/gitignore.txt in the git repository for a
+ * description of how ignores work.
+ */
+public class GitIgnoreData {
+
+	/*
+	 * Ignore Data Cache
+	 */
+
+	private static HashMap<Repository, IgnoreRepositoryCache> repositories = new HashMap<Repository, IgnoreRepositoryCache>();
+
+	/**
+	 * Retrieve a repository mapping from the repositories cache. When the
+	 * repository is not yet in the cache then create a new mapping for it and
+	 * store it in the cache first.
+	 * 
+	 * @param repository
+	 *            the repository to retrieve from the repositories cache
+	 * @return the repository mapping in the cache
+	 */
+	private static IgnoreRepositoryCache getRepositoryFromCache(
+			final Repository repository) {
+		IgnoreRepositoryCache cache = repositories.get(repository);
+		if (cache == null) {
+			cache = new IgnoreRepositoryCache(repository);
+			repositories.put(repository, cache);
+		}
+		return cache;
+	}
+
+	/*
+	 * Public Methods
+	 */
+
+	/**
+	 * This method must be invoked upon shutdown of the plugin. It empties the
+	 * ignore cache.
+	 */
+	public synchronized static void clear() {
+		for (final IgnoreRepositoryCache projectCache : repositories.values()) {
+			projectCache.clear();
+		}
+		repositories.clear();
+	}
+
+	/**
+	 * @param project
+	 *            the project to remove the ignores for
+	 */
+	public synchronized static void uncacheProject(final IResource project) {
+		if ((project == null) || (!(project instanceof IProject))) {
+			return;
+		}
+
+		final RepositoryMapping mapping = RepositoryMapping.getMapping(project);
+		if (mapping == null) {
+			return;
+		}
+
+		final Repository repository = mapping.getRepository();
+		final IgnoreRepositoryCache ignoreData = repositories.get(repository);
+		if (ignoreData == null) {
+			return;
+		}
+
+		ignoreData.uncacheProject((IProject) project);
+	}
+
+	/**
+	 * This method must be invoked upon startup. It goes through all files in
+	 * the workspace and picks up all .gitignore files, parses them so that the
+	 * plugin knows what to ignore. It also goes up the directory tree from the
+	 * project root directories to look for .gitignore files.
+	 */
+	public synchronized static void importWorkspaceIgnores() {
+		final IWorkspace workSpace = ResourcesPlugin.getWorkspace();
+		final IProject[] projects = workSpace.getRoot().getProjects();
+		for (final IProject project : projects) {
+			final RepositoryMapping mapping = RepositoryMapping
+					.getMapping(project);
+			if (mapping != null) {
+				try {
+					final IResource[] projectChildren = project.members();
+					final IgnoreRepositoryCache ignoreRepositoryCache = getRepositoryFromCache(mapping
+							.getRepository());
+					ignoreRepositoryCache.importProjectIgnores(project,
+							projectChildren);
+					ignoreRepositoryCache.importProjectIgnoresOutside(project);
+				} catch (final CoreException e) {
+					/* swallow */
+				}
+			}
+		}
+
+		for (final IgnoreRepositoryCache repositoryCache : repositories
+				.values()) {
+			repositoryCache.importRepositoryInfoExclude();
+			repositoryCache.importRepositoryCoreExclude();
+		}
+
+		/*
+		 * FIXME: also do the file `git config --global --get
+		 * core.excludesfile`, is this already covered? RepositoryConfig
+		 * globalConfig = new RepositoryConfig(null, new File(FS.userHome(),
+		 * ".gitconfig"));
+		 */
+
+		/*
+		 * System.out.println("== GitIgnoreData.repositories ==\n" +
+		 * repositories.toString());
+		 */
+	}
+}
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFile.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFile.java
new file mode 100644
index 0000000..78dd71d
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFile.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Ferry Huberts <ferry.huberts@pelagic.nl>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.ignores;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * This class implements ignore file helpers.
+ */
+class IgnoreFile {
+	/**
+	 * This method parses an ignore file.
+	 * 
+	 * @param ignoreFileBaseDir
+	 *            the directory of the ignore file, relative to the checkout
+	 *            directory and with a trailing slash. Slashes are in Unix
+	 *            format: forward slashes
+	 * @param ignoreFile
+	 *            the .gitignore file
+	 * @return returns a set of Excludes that reflects the ignore patterns.
+	 */
+	static LinkedList<Exclude> parseIgnoreFile(final String ignoreFileBaseDir,
+			final IFile ignoreFile) {
+		final LinkedList<Exclude> excludes = new LinkedList<Exclude>();
+
+		/* make sure that the resource is synchronized */
+		try {
+			if (!ignoreFile.isSynchronized(IResource.DEPTH_ZERO)) {
+				ignoreFile.refreshLocal(IResource.DEPTH_ZERO, null);
+			}
+		} catch (final Exception e) {
+			return excludes;
+		}
+
+		String base = ignoreFileBaseDir;
+		if (base.equals("/")) {
+			base = "";
+		}
+		final String ignoreFileName = ignoreFile.getLocation().toOSString();
+		BufferedReader txtIn = null;
+		int lineNumber = 0;
+		try {
+			txtIn = new BufferedReader(new InputStreamReader(ignoreFile
+					.getContents()));
+			String line;
+			while ((line = txtIn.readLine()) != null) {
+				lineNumber++;
+				line = line.trim();
+				if (!line.startsWith("#") && (line.length() > 0)) {
+					excludes.add(new Exclude(line, base, ignoreFileName,
+							lineNumber));
+				}
+			}
+		} catch (final CoreException e) {
+			/* swallow */
+		} catch (final IOException e) {
+			/* swallow */
+		} finally {
+			try {
+				if (txtIn != null) {
+					txtIn.close();
+					txtIn = null;
+				}
+			} catch (final IOException e1) {
+				/* swallow */
+			}
+		}
+		return excludes;
+	}
+}
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFileOutside.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFileOutside.java
new file mode 100644
index 0000000..8ad5a48
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFileOutside.java
@@ -0,0 +1,543 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Ferry Huberts <ferry.huberts@pelagic.nl>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.ignores;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URI;
+import java.util.Map;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFileState;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResourceProxy;
+import org.eclipse.core.resources.IResourceProxyVisitor;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourceAttributes;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.spearce.jgit.lib.Repository;
+
+/**
+ * This class is only used to be able to store the .gitignore files in the
+ * ignore cache that are outside the projects (up in the checkout directory tree
+ * from the project root directory)
+ */
+class IgnoreFileOutside implements IFile {
+	private String relativeDir = null;
+
+	private String relativePath = null;
+
+	private String resourceBaseName = null;
+
+	private String absoluteDir = null;
+
+	private String fullPath = null;
+
+	private File fullPathFile = null;
+
+	private long lastModificationTime = 0L;
+
+	/**
+	 * Constructor
+	 * 
+	 * @param repository
+	 *            the repository. when null then the relativeDir parameter is
+	 *            taken to be an absolute path
+	 * @param directory
+	 *            the directory in which the pattern is anchored, relative to
+	 *            the checkout directory and with a trailing slash (except when
+	 *            in the checkout directory, in which case it will be an empty
+	 *            string). Slashes are in Unix format: forward slashes. When
+	 *            repository is null then this is taken to be an absolute
+	 *            directory with slashes in platform format.
+	 * @param resourceBasename
+	 *            the name of the ignore file.
+	 */
+	IgnoreFileOutside(final Repository repository, final String directory,
+			final String resourceBasename) {
+		if ((directory == null) || (resourceBasename == null)) {
+			throw new IllegalArgumentException("Can not handle NULL values: "
+					+ directory + ", " + resourceBasename);
+		}
+
+		this.relativeDir = directory.replaceAll("/", File.separator);
+		this.resourceBaseName = resourceBasename
+				.replaceAll("/", File.separator);
+		this.relativePath = this.relativeDir + this.resourceBaseName;
+
+		String repoRoot = "";
+		if (repository != null) {
+			repoRoot = repository.getWorkDir().getAbsolutePath()
+					+ File.separator;
+		}
+		this.absoluteDir = repoRoot + this.relativeDir;
+		this.fullPath = this.absoluteDir + this.resourceBaseName;
+		this.fullPathFile = new File(this.fullPath);
+	}
+
+	/* used interface methods */
+
+	public boolean exists() {
+		return this.fullPathFile.exists();
+	}
+
+	public InputStream getContents() throws CoreException {
+		try {
+			return new FileInputStream(fullPath);
+		} catch (final FileNotFoundException e) {
+			/* FIXME: use actual plugin id */
+			throw new CoreException(new Status(IStatus.WARNING, "git plugin", e
+					.getLocalizedMessage()));
+		}
+	}
+
+	public String getName() {
+		return resourceBaseName;
+	}
+
+	public IPath getProjectRelativePath() {
+		return new Path(relativePath);
+	}
+
+	public boolean isSynchronized(final int depth) {
+		return (lastModificationTime == this.fullPathFile.lastModified());
+	}
+
+	public void refreshLocal(final int depth, final IProgressMonitor monitor)
+			throws CoreException {
+		lastModificationTime = this.fullPathFile.lastModified();
+		return;
+	}
+
+	public IPath getLocation() {
+		return new Path(fullPath);
+	}
+
+	/*
+	 * Overridden Methods
+	 */
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (!(obj instanceof IgnoreFileOutside)) {
+			throw new IllegalArgumentException("Wrong type");
+		}
+		return ((IgnoreFileOutside) obj).fullPath.equals(this.fullPath);
+	}
+
+	@Override
+	public int hashCode() {
+		return fullPath.hashCode();
+	}
+
+	@Override
+	public String toString() {
+		return fullPath;
+	}
+
+	/*
+	 * Unused Interface Methods
+	 */
+
+	public void appendContents(final InputStream source, final int updateFlags,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void appendContents(final InputStream source, final boolean force,
+			final boolean keepHistory, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void create(final InputStream source, final boolean force,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void create(final InputStream source, final int updateFlags,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void createLink(final IPath localLocation, final int updateFlags,
+			final IProgressMonitor monitor) throws CoreException {
+
+		/** not used */
+	}
+
+	public void createLink(final URI location, final int updateFlags,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void delete(final boolean force, final boolean keepHistory,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public String getCharset() throws CoreException {
+		return null;
+	}
+
+	public String getCharset(final boolean checkImplicit) throws CoreException {
+		return null;
+	}
+
+	public String getCharsetFor(final Reader reader) throws CoreException {
+		return null;
+	}
+
+	public IContentDescription getContentDescription() throws CoreException {
+		return null;
+	}
+
+	public InputStream getContents(final boolean force) throws CoreException {
+		return null;
+	}
+
+	public int getEncoding() throws CoreException {
+		return 0;
+	}
+
+	public IPath getFullPath() {
+		return null;
+	}
+
+	public IFileState[] getHistory(final IProgressMonitor monitor)
+			throws CoreException {
+		return null;
+	}
+
+	public boolean isReadOnly() {
+		return false;
+	}
+
+	public void move(final IPath destination, final boolean force,
+			final boolean keepHistory, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void setCharset(final String newCharset) throws CoreException {
+		/** not used */
+	}
+
+	public void setCharset(final String newCharset,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void setContents(final InputStream source, final int updateFlags,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void setContents(final IFileState source, final int updateFlags,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void setContents(final InputStream source, final boolean force,
+			final boolean keepHistory, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void setContents(final IFileState source, final boolean force,
+			final boolean keepHistory, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void accept(final IResourceVisitor visitor) throws CoreException {
+		/** not used */
+	}
+
+	public void accept(final IResourceProxyVisitor visitor,
+			final int memberFlags) throws CoreException {
+		/** not used */
+	}
+
+	public void accept(final IResourceVisitor visitor, final int depth,
+			final boolean includePhantoms) throws CoreException {
+		/** not used */
+	}
+
+	public void accept(final IResourceVisitor visitor, final int depth,
+			final int memberFlags) throws CoreException {
+		/** not used */
+	}
+
+	public void clearHistory(final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void copy(final IPath destination, final boolean force,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void copy(final IPath destination, final int updateFlags,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void copy(final IProjectDescription description,
+			final boolean force, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void copy(final IProjectDescription description,
+			final int updateFlags, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public IMarker createMarker(final String type) throws CoreException {
+		return null;
+	}
+
+	public IResourceProxy createProxy() {
+		return null;
+	}
+
+	public void delete(final boolean force, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void delete(final int updateFlags, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void deleteMarkers(final String type, final boolean includeSubtypes,
+			final int depth) throws CoreException {
+		/** not used */
+	}
+
+	public IMarker findMarker(final long id) throws CoreException {
+		return null;
+	}
+
+	public IMarker[] findMarkers(final String type,
+			final boolean includeSubtypes, final int depth)
+			throws CoreException {
+		return null;
+	}
+
+	public int findMaxProblemSeverity(final String type,
+			final boolean includeSubtypes, final int depth)
+			throws CoreException {
+		return 0;
+	}
+
+	public String getFileExtension() {
+		return null;
+	}
+
+	public long getLocalTimeStamp() {
+		return 0;
+	}
+
+	public URI getLocationURI() {
+		return null;
+	}
+
+	public IMarker getMarker(final long id) {
+		return null;
+	}
+
+	public long getModificationStamp() {
+		return 0;
+	}
+
+	public IContainer getParent() {
+		return null;
+	}
+
+	public Map getPersistentProperties() throws CoreException {
+		return null;
+	}
+
+	public String getPersistentProperty(final QualifiedName key)
+			throws CoreException {
+		return null;
+	}
+
+	public IProject getProject() {
+		return null;
+	}
+
+	public IPath getRawLocation() {
+		return null;
+	}
+
+	public URI getRawLocationURI() {
+		return null;
+	}
+
+	public ResourceAttributes getResourceAttributes() {
+		return null;
+	}
+
+	public Map getSessionProperties() throws CoreException {
+		return null;
+	}
+
+	public Object getSessionProperty(final QualifiedName key)
+			throws CoreException {
+		return null;
+	}
+
+	public int getType() {
+		return 0;
+	}
+
+	public IWorkspace getWorkspace() {
+		return null;
+	}
+
+	public boolean isAccessible() {
+		return false;
+	}
+
+	public boolean isDerived() {
+		return false;
+	}
+
+	public boolean isDerived(final int options) {
+		return false;
+	}
+
+	public boolean isHidden() {
+		return false;
+	}
+
+	public boolean isLinked() {
+		return false;
+	}
+
+	public boolean isLinked(final int options) {
+		return false;
+	}
+
+	public boolean isLocal(final int depth) {
+		return false;
+	}
+
+	public boolean isPhantom() {
+		return false;
+	}
+
+	public boolean isTeamPrivateMember() {
+		return false;
+	}
+
+	public void move(final IPath destination, final boolean force,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void move(final IPath destination, final int updateFlags,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void move(final IProjectDescription description,
+			final int updateFlags, final IProgressMonitor monitor)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void move(final IProjectDescription description,
+			final boolean force, final boolean keepHistory,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public void revertModificationStamp(final long value) throws CoreException {
+		/** not used */
+	}
+
+	public void setDerived(final boolean isDerived) throws CoreException {
+		/** not used */
+	}
+
+	public void setHidden(final boolean isHidden) throws CoreException {
+		/** not used */
+	}
+
+	public void setLocal(final boolean flag, final int depth,
+			final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public long setLocalTimeStamp(final long value) throws CoreException {
+		return 0;
+	}
+
+	public void setPersistentProperty(final QualifiedName key,
+			final String value) throws CoreException {
+		/** not used */
+	}
+
+	public void setReadOnly(final boolean readOnly) {
+		/** not used */
+	}
+
+	public void setResourceAttributes(final ResourceAttributes attributes)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void setSessionProperty(final QualifiedName key, final Object value)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void setTeamPrivateMember(final boolean isTeamPrivate)
+			throws CoreException {
+		/** not used */
+	}
+
+	public void touch(final IProgressMonitor monitor) throws CoreException {
+		/** not used */
+	}
+
+	public Object getAdapter(final Class adapter) {
+		return null;
+	}
+
+	public boolean contains(final ISchedulingRule rule) {
+		return false;
+	}
+
+	public boolean isConflicting(final ISchedulingRule rule) {
+		return false;
+	}
+
+}
\ No newline at end of file
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
new file mode 100644
index 0000000..fe2f529
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Ferry Huberts <ferry.huberts@pelagic.nl>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.ignores;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * This class implements a cache of ignore patterns for an Eclipse project: it
+ * holds a list of ignore patterns, stored in 'ignoreFiles' against the ignore
+ * file handle. We also keep an ignoreFilesIndex of the ignore file handle
+ * stored against the directory names (relative to the 'checkoutDir') of the
+ * ignore ignoreFiles in 'ignoreFilesIndex'. We use this to access the cache and
+ * retrieve the list of ignore patterns. This is because when trying to
+ * determine whether a resource is ignored we must first try the ignore file in
+ * the directory of the resource, and if it doesn't match, then in the directory
+ * up from that, and so on, al the way up to the checkout directory.
+ */
+class IgnoreProjectCache {
+	/**
+	 * the directory of the project, relative to the checkout, with a trailing
+	 * slash, except when in the checkout directory in which case it will be
+	 * empty
+	 */
+	private String projectDirInCheckout = null;
+
+	/**
+	 * Map used to find .gitignore ignore files in ignoreFiles. key=directory
+	 * path of the .gitignore file, relative to the checkout directory,
+	 * value=IFile to use to ignoreFilesIndex ignoreFiles
+	 */
+	private final HashMap<String, IFile> ignoreFilesIndex = new HashMap<String, IFile>();
+
+	/**
+	 * Map with .gitignore ignore files and their exclude patterns.
+	 * key=.gitignore file handle, value=list with its ignore patterns
+	 */
+	private final HashMap<IFile, LinkedList<Exclude>> ignoreFiles = new HashMap<IFile, LinkedList<Exclude>>();
+
+	/*
+	 * Constructors
+	 */
+
+	/**
+	 * Constructor
+	 * 
+	 * @param projectDirInCheckout
+	 *            the directory of the project, relative to the checkout
+	 */
+	IgnoreProjectCache(final String projectDirInCheckout) {
+		if (projectDirInCheckout == null) {
+			throw new ExceptionInInitializerError(
+					"NULL is not a valid project directory");
+		}
+
+		this.projectDirInCheckout = projectDirInCheckout;
+		if (!this.projectDirInCheckout.isEmpty()
+				&& !this.projectDirInCheckout.endsWith("/")) {
+			this.projectDirInCheckout = this.projectDirInCheckout.concat("/");
+		}
+	}
+
+	/*
+	 * Methods
+	 */
+
+	synchronized void clear() {
+		ignoreFilesIndex.clear();
+
+		for (final LinkedList<Exclude> excludeList : ignoreFiles.values()) {
+			excludeList.clear();
+		}
+		ignoreFiles.clear();
+	}
+
+	synchronized void importProjectIgnores(final IResource[] projectChildren) {
+		if (projectChildren == null) {
+			return;
+		}
+
+		for (final IResource projectChild : projectChildren) {
+			if (projectChild != null) {
+				if (projectChild instanceof IFile) {
+					if (projectChild.getName().equals(".gitignore")) {
+						String projectRelativeDir = projectChild
+								.getProjectRelativePath().removeLastSegments(1)
+								.toString();
+						if (!projectRelativeDir.isEmpty()) {
+							projectRelativeDir = projectRelativeDir + "/";
+						}
+						final String ignoreFileBaseDir = projectDirInCheckout
+								+ projectRelativeDir;
+						processIgnoreFile((IFile) projectChild,
+								ignoreFileBaseDir, IResourceDelta.ADDED,
+								IResourceDelta.CONTENT);
+					}
+				} else if (projectChild instanceof IFolder) {
+					try {
+						importProjectIgnores(((IFolder) projectChild).members());
+					} catch (final CoreException e) {
+						/* swallow */
+					}
+				} else {
+					/* FIXME: signal an error */
+					System.out.println("Unhandled resource type in"
+							+ " processResourceForIgnoreChild: "
+							+ projectChild.getClass().getName());
+				}
+			}
+		}
+	}
+
+	/**
+	 * This method parses a .gitignore file and stores the patterns for use by
+	 * the plugin. It is shared between startup of the workspace and changes to
+	 * the workspace.
+	 * 
+	 * @param ignoreFile
+	 *            the .gitignore file
+	 * @param ignoreFileBaseDir
+	 *            the directory of the ignore file, relative to the checkout
+	 *            directory and with a trailing slash (except when in the
+	 *            checkout directory, in which case it will be an empty string).
+	 *            Slashes are in Unix format: forward slashes
+	 * @param changeKind
+	 *            the kind of the change
+	 * @param changeFlags
+	 *            further information on the change
+	 */
+	synchronized void processIgnoreFile(final IFile ignoreFile,
+			final String ignoreFileBaseDir, final int changeKind,
+			final int changeFlags) {
+		if ((ignoreFile == null) || (changeKind == IResourceDelta.NO_CHANGE)) {
+			return;
+		}
+
+		if (((changeKind & IResourceDelta.ADDED) == IResourceDelta.ADDED)
+				|| ((changeKind & IResourceDelta.ADDED_PHANTOM) == IResourceDelta.ADDED_PHANTOM)
+				|| (((changeKind & IResourceDelta.CHANGED) == IResourceDelta.CHANGED) && ((changeFlags & IResourceDelta.CONTENT) == IResourceDelta.CONTENT))) {
+			ignoreFiles.put(ignoreFile, IgnoreFile.parseIgnoreFile(
+					ignoreFileBaseDir, ignoreFile));
+			ignoreFilesIndex.put(ignoreFileBaseDir, ignoreFile);
+		} else if (((changeKind & IResourceDelta.REMOVED) == IResourceDelta.REMOVED)
+				|| ((changeKind & IResourceDelta.REMOVED_PHANTOM) == IResourceDelta.REMOVED_PHANTOM)) {
+			ignoreFiles.remove(ignoreFile);
+			ignoreFilesIndex.remove(ignoreFileBaseDir);
+		} else {
+			System.out.println("Unhandled change combination kind/flags: "
+					+ changeKind + "/" + changeFlags);
+		}
+
+		// int changeKind = projectChild.getKind();
+		// int changeFlags = projectChild.getFlags();
+		// switch (changeKind) {
+		// case IResourceDelta.ADDED:
+		// case IResourceDelta.ADDED_PHANTOM:
+		// if ((changeFlags & IResourceDelta.MOVED_FROM) ==
+		// IResourceDelta.MOVED_FROM) {
+		// /*
+		// * The resource has moved: getMovedToPath will
+		// * return the path of where it was moved to.
+		// */
+		// break;
+		// }
+		//
+		// /* simply parse the content and process it */
+		// break;
+		//
+		// case IResourceDelta.REMOVED:
+		// case IResourceDelta.REMOVED_PHANTOM:
+		// /* remove the patterns from the file */
+		// break;
+		//
+		// case IResourceDelta.CHANGED:
+		// /* this one is more involved, also have to deal with moved
+		// ignoreFiles */
+		// if ((changeFlags & IResourceDelta.REPLACED) ==
+		// IResourceDelta.REPLACED) {
+		// /*
+		// * The resource has moved: getMovedToPath will
+		// * return the path of where it was moved to.
+		// */
+		// }
+		// break;
+		//
+		// default:
+		// break;
+		// }
+	}
+}
\ No newline at end of file
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java
new file mode 100644
index 0000000..4b6bf61
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java
@@ -0,0 +1,308 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Ferry Huberts <ferry.huberts@pelagic.nl>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.ignores;
+
+import java.io.File;
+import java.util.HashMap;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.spearce.egit.core.project.RepositoryMapping;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.RepositoryConfig;
+
+/**
+ * This class manages the ignore data for a single repository (and its
+ * checkout). It implements the model that a single repository can contain
+ * multiple projects.
+ * 
+ * It contains a cache that holds ignore data on a per-project basis and it also
+ * contains a cache that holds ignore data that does not belong to any project
+ * but still belongs to the repository. The latter cache we need because Eclipse
+ * will not send change events for those files; we have to re-read these files
+ * every time there is a change.
+ */
+class IgnoreRepositoryCache {
+	/** the repository */
+	private Repository repository = null;
+
+	/** the checkout directory for the repository */
+	private String checkoutDir = null;
+
+	/** the cache that holds ignore data on a per-project basis */
+	private final HashMap<IProject, IgnoreProjectCache> projects = new HashMap<IProject, IgnoreProjectCache>();
+
+	/** cache that holds ignore data that does not belong to any project */
+	private final IgnoreProjectCache outside = new IgnoreProjectCache("");
+
+	/** cache that holds ignore data of the .git/info/exclude files */
+	private final IgnoreProjectCache infoExclude = new IgnoreProjectCache("");
+
+	/** cache that holds ignore data of the core exclude setting */
+	private final IgnoreProjectCache coreExcludes = new IgnoreProjectCache("");
+
+	/** the .git/info/exclude file */
+	private IgnoreFileOutside infoExcludesFile = null;
+
+	/** the core.excludes file setting from the config */
+	private IgnoreFileOutside coreExcludesFile = null;
+
+	/** the core.excludesfile setting */
+	private String coreExcludesSetting = null;
+
+	/**
+	 * Retrieve a project mapping from the projects cache. When the project is
+	 * not yet in the cache then create a new mapping for it and store it in the
+	 * cache first.
+	 * 
+	 * @param project
+	 *            the project to retrieve from the projects cache
+	 * @return the project mapping in the cache
+	 */
+	private IgnoreProjectCache getProjectFromCache(final IProject project) {
+		IgnoreProjectCache cache = projects.get(project);
+		if (cache == null) {
+			cache = new IgnoreProjectCache(RepositoryMapping
+					.getMapping(project).getRepoRelativePath(project));
+			projects.put(project, cache);
+		}
+		return cache;
+	}
+
+	/*
+	 * Constructors
+	 */
+
+	/**
+	 * Constructor
+	 * 
+	 * @param repository
+	 *            the repository
+	 */
+	IgnoreRepositoryCache(final Repository repository) {
+		if (repository == null) {
+			throw new ExceptionInInitializerError(
+					"NULL is not a valid repository");
+		}
+		this.repository = repository;
+		this.checkoutDir = repository.getWorkDir().getAbsolutePath();
+	}
+
+	/*
+	 * Methods
+	 */
+
+	synchronized void clear() {
+		for (final IgnoreProjectCache projectCache : projects.values()) {
+			projectCache.clear();
+		}
+		projects.clear();
+		outside.clear();
+		infoExclude.clear();
+		coreExcludes.clear();
+	}
+
+	/**
+	 * @param project
+	 *            the project for which to remove the ignore data
+	 */
+	synchronized void uncacheProject(final IProject project) {
+		if (project == null) {
+			return;
+		}
+
+		final IgnoreProjectCache cache = projects.get(project);
+		if (cache == null) {
+			return;
+		}
+
+		/*
+		 * FIXME: remove everything that is not 'outside' to remaining projects
+		 */
+
+		cache.clear();
+
+		projects.remove(project);
+	}
+
+	/**
+	 * Process all project children: look for .gitignore files and read in the
+	 * ignore patterns.
+	 * 
+	 * @param project
+	 *            the project
+	 * @param projectChildren
+	 *            the project children
+	 */
+	synchronized void importProjectIgnores(final IProject project,
+			final IResource[] projectChildren) {
+		if ((project == null) || (projectChildren == null)) {
+			return;
+		}
+
+		final IgnoreProjectCache projectCache = getProjectFromCache(project);
+		projectCache.importProjectIgnores(projectChildren);
+	}
+
+	/*
+	 * walk directory tree up looking for .gitignore files until in the checkout
+	 * directory
+	 */
+	synchronized boolean importProjectIgnoresOutside(final IProject project) {
+		boolean changes = false;
+
+		String projectDirectory = RepositoryMapping.getMapping(project)
+				.getRepoRelativePath(project);
+		while (!projectDirectory.isEmpty()) {
+			final int pos = projectDirectory.lastIndexOf('/');
+			if (pos < 0) {
+				projectDirectory = "";
+			} else {
+				projectDirectory = projectDirectory.substring(0, pos) + "/";
+			}
+
+			final IgnoreFileOutside ignoreFile = new IgnoreFileOutside(
+					repository, projectDirectory, ".gitignore");
+			if (!ignoreFile.isSynchronized(0)) {
+				changes = true;
+				String projectRelativeDir = ignoreFile.getProjectRelativePath()
+						.removeLastSegments(1).toString();
+				if (!projectRelativeDir.isEmpty()) {
+					projectRelativeDir = projectRelativeDir + "/";
+				}
+				final String ignoreFileBaseDir = projectDirectory
+						+ projectRelativeDir;
+				outside.processIgnoreFile(ignoreFile, ignoreFileBaseDir,
+						IResourceDelta.ADDED, IResourceDelta.CONTENT);
+			}
+		}
+
+		return changes;
+	}
+
+	synchronized boolean importRepositoryInfoExclude() {
+		readRepositoryInfoExcludesFile();
+
+		if ((infoExcludesFile != null) && !infoExcludesFile.isSynchronized(0)) {
+			try {
+				infoExcludesFile.refreshLocal(IResource.DEPTH_ZERO, null);
+			} catch (final CoreException e) {
+				/* swallow */
+			}
+			infoExclude.clear();
+			infoExclude.processIgnoreFile(infoExcludesFile, "",
+					IResourceDelta.ADDED, IResourceDelta.CONTENT);
+			return true;
+		}
+		return false;
+	}
+
+	synchronized boolean importRepositoryCoreExclude() {
+		readRepositoryCoreExcludesSetting();
+		if ((coreExcludesFile != null) && !coreExcludesFile.isSynchronized(0)) {
+			try {
+				coreExcludesFile.refreshLocal(IResource.DEPTH_ZERO, null);
+			} catch (final CoreException e) {
+				/* swallow */
+			}
+			coreExcludes.clear();
+			coreExcludes.processIgnoreFile(coreExcludesFile, "",
+					IResourceDelta.ADDED, IResourceDelta.CONTENT);
+			return true;
+		}
+		return false;
+	}
+
+	/*
+	 * Private Methods
+	 */
+
+	private boolean readRepositoryInfoExcludesFile() {
+		boolean changes = false;
+
+		if (infoExcludesFile == null) {
+			infoExcludesFile = new IgnoreFileOutside(this.repository, "",
+					".git/info/exclude");
+			changes = true;
+			if (!infoExcludesFile.exists()) {
+				infoExcludesFile = null;
+				changes = false;
+			}
+		} else {
+			if (!infoExcludesFile.exists()) {
+				infoExcludesFile = null;
+				changes = true;
+			}
+		}
+
+		return changes;
+	}
+
+	private boolean readRepositoryCoreExcludesSetting() {
+		final RepositoryConfig config = repository.getConfig();
+		if (config == null) {
+			return false;
+		}
+
+		boolean changes = false;
+		String newCoreExcludesSetting = config.getString("core", null,
+				"excludesfile");
+		if (newCoreExcludesSetting != null) {
+			/* FIXME check this! both for per-repo and global */
+			if (!newCoreExcludesSetting.equals(coreExcludesSetting)) {
+				changes = true;
+
+				final IPath newCoreExcludesSettingPath = new Path(
+						newCoreExcludesSetting);
+				newCoreExcludesSetting = newCoreExcludesSettingPath
+						.toOSString();
+				if (!newCoreExcludesSettingPath.isAbsolute()) {
+					newCoreExcludesSetting = repository.getWorkDir()
+							.getAbsolutePath().toString()
+							+ File.separator + newCoreExcludesSetting;
+				}
+
+				if (coreExcludesFile != null) {
+					coreExcludesFile = null;
+				}
+
+				final int pos = newCoreExcludesSetting
+						.lastIndexOf(File.separatorChar);
+				final String directory = newCoreExcludesSetting.substring(0,
+						pos);
+				final String resourceBasename = newCoreExcludesSetting
+						.substring(pos + 1);
+
+				coreExcludes.clear();
+				coreExcludesSetting = newCoreExcludesSetting;
+				coreExcludesFile = new IgnoreFileOutside(null, directory,
+						resourceBasename);
+			}
+		} else {
+			changes = (coreExcludesSetting != null);
+			coreExcludesSetting = null;
+			coreExcludesFile = null;
+		}
+		return changes;
+	}
+	
+	/*
+	 * Getters / Setters
+	 */
+
+	/**
+	 * @return the checkoutDir
+	 */
+	public String getCheckoutDir() {
+		return checkoutDir;
+	}
+}
\ No newline at end of file
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java b/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
index 31d5483..414bd83 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
@@ -40,6 +40,7 @@
 import org.spearce.egit.core.CoreText;
 import org.spearce.egit.core.GitCorePreferences;
 import org.spearce.egit.core.GitProvider;
+import org.spearce.egit.core.ignores.GitIgnoreData;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.lib.WindowCache;
 import org.spearce.jgit.lib.WindowCacheConfig;
@@ -64,9 +65,11 @@ public void resourceChanged(final IResourceChangeEvent event) {
 			switch (event.getType()) {
 			case IResourceChangeEvent.PRE_CLOSE:
 				uncache((IProject) event.getResource());
+				GitIgnoreData.uncacheProject(event.getResource());
 				break;
 			case IResourceChangeEvent.PRE_DELETE:
 				delete((IProject) event.getResource());
+				GitIgnoreData.uncacheProject(event.getResource());
 				break;
 			default:
 				break;
@@ -84,6 +87,7 @@ public void resourceChanged(final IResourceChangeEvent event) {
 	 */
 	public static void attachToWorkspace(final boolean includeChange) {
 		trace("attachToWorkspace - addResourceChangeListener");
+		GitIgnoreData.importWorkspaceIgnores();
 		ResourcesPlugin.getWorkspace().addResourceChangeListener(
 				rcl,
 				(includeChange ? IResourceChangeEvent.POST_CHANGE : 0)
@@ -97,6 +101,7 @@ public static void attachToWorkspace(final boolean includeChange) {
 	public static void detachFromWorkspace() {
 		trace("detachFromWorkspace - removeResourceChangeListener");
 		ResourcesPlugin.getWorkspace().removeResourceChangeListener(rcl);
+		GitIgnoreData.clear();
 	}
 
 	/**
-- 
1.6.0.6

^ permalink raw reply related

* [EGIT] [PATCH RFC v1 5/5] Use the ignore patterns cache to determine ignores
From: Ferry Huberts @ 2009-03-26 21:34 UTC (permalink / raw)
  To: git; +Cc: Shawn O. Pearce, Robin Rosenberg, Ferry Huberts
In-Reply-To: <cover.1238102327.git.ferry.huberts@pelagic.nl>

Signed-off-by: Ferry Huberts <ferry.huberts@pelagic.nl>
---
 .../src/org/spearce/egit/core/ignores/DType.java   |   44 ++++++
 .../src/org/spearce/egit/core/ignores/Exclude.java |  143 ++++++++++++++++----
 .../spearce/egit/core/ignores/GitIgnoreData.java   |   43 ++++++-
 .../egit/core/ignores/IgnoreProjectCache.java      |   44 ++++++
 .../egit/core/ignores/IgnoreRepositoryCache.java   |   68 ++++++++--
 .../spearce/egit/core/project/GitProjectData.java  |    3 +
 org.spearce.jgit/META-INF/MANIFEST.MF              |    1 +
 7 files changed, 307 insertions(+), 39 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/DType.java

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/DType.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/DType.java
new file mode 100644
index 0000000..5661dca
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/DType.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Ferry Huberts <ferry.huberts@pelagic.nl>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.ignores;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+
+/**
+ * This class describes a file type in the same way as git does.
+ * 
+ * The git definition can be found in the source file cache.h. The code can be
+ * found in the source file dir.c:get_dtype
+ */
+enum DType {
+	DT_UNKNOWN, DT_DIR, DT_REG, DT_LINK;
+
+	/*
+	 * Interface Methods
+	 */
+
+	static DType get(final IResource resource) {
+		if (resource == null) {
+			return DT_UNKNOWN;
+		}
+		if (resource instanceof IFile) {
+			return DT_REG;
+		}
+		if ((resource instanceof IFolder) || (resource instanceof IProject)) {
+			return DT_DIR;
+		}
+		if (resource.isLinked()) {
+			return DT_LINK;
+		}
+
+		return DT_UNKNOWN;
+	}
+}
\ No newline at end of file
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
index c4c48e9..eaddd17 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
@@ -9,6 +9,9 @@
 
 import java.util.regex.Pattern;
 
+import org.spearce.jgit.errors.InvalidPatternException;
+import org.spearce.jgit.fnmatch.FileNameMatcher;
+
 /**
  * This class describes an ignore pattern in the same way as git does, with some
  * extra information to support Eclipse specific functionality.
@@ -105,6 +108,110 @@ Exclude(final String pattern, final String base,
 	}
 
 	/*
+	 * Interface Methods
+	 */
+
+	/**
+	 * Tries to match a given resource to the Exclude
+	 * 
+	 * @param pathName
+	 *            the full path of the resource, relative to the checkout
+	 *            directory
+	 * @param baseName
+	 *            the baseName of the resource
+	 * @param resourceType
+	 *            the type of the resource
+	 * @return true when the resource matches this Exclude, false otherwise
+	 * 
+	 */
+	boolean isMatch(final String pathName, final String baseName,
+			final DType resourceType) {
+		/* this is needed to make the exact same match as git does */
+		String xbase = pathName;
+		final int pos = xbase.lastIndexOf('/');
+		if (pos < 0) {
+			xbase = "";
+		} else {
+			xbase = xbase.substring(0, pos + 1);
+		}
+
+		if (mustBeDir && (resourceType != DType.DT_DIR)) {
+			return false;
+		}
+
+		if (noDir) {
+			/* pattern does not contain directories. dir.c: match basename */
+			if (noWildcard) {
+				/* pattern does not contain directories and has no wildcards */
+				if (baseName.equals(pattern)) {
+					return to_exclude;
+				}
+			} else if (endsWith) {
+				/*
+				 * pattern does not contain directories and resource must end
+				 * with pattern.substring(1)
+				 */
+				if (baseName.endsWith(pattern.substring(1))) {
+					return to_exclude;
+				}
+			} else {
+				/*
+				 * pattern does not contain directories, has wildcards, and does
+				 * not end with pattern.substring(1)
+				 */
+				try {
+					final FileNameMatcher matcher = new FileNameMatcher(
+							pattern, null);
+					matcher.append(baseName);
+					if (matcher.isMatch()) {
+						return to_exclude;
+					}
+				} catch (final InvalidPatternException e) {
+					return false;
+				}
+			}
+		} else {
+			/*
+			 * pattern contains directories. dir.c: match with FNM_PATHNAME:
+			 * exclude (e.g. 'this.pattern') has base (baselen long) implicitly
+			 * in front of it.
+			 */
+			final int baselen = base.length();
+			String matchPattern = this.pattern;
+			if (matchPattern.startsWith("/")) {
+				matchPattern = matchPattern.substring(1);
+			}
+
+			if ((pathName.length() < baselen)
+					|| ((baselen > 0) && (pathName.charAt(baselen - 1) != '/'))
+					|| !pathName.substring(0, baselen).equals(xbase)) {
+				return false;
+			}
+
+			final String remainingResourceName = pathName.substring(baselen);
+			if (noWildcard) {
+				/* pattern contains directories and has no wildcards */
+				if (remainingResourceName.equals(matchPattern)) {
+					return to_exclude;
+				}
+			} else {
+				/* pattern contains directories and has wildcards */
+				try {
+					final FileNameMatcher matcher = new FileNameMatcher(
+							matchPattern, Character.valueOf('/'));
+					matcher.append(remainingResourceName);
+					if (matcher.isMatch()) {
+						return to_exclude;
+					}
+				} catch (final InvalidPatternException e) {
+					return false;
+				}
+			}
+		}
+		return false;
+	}
+
+	/*
 	 * Private Methods
 	 */
 
@@ -119,40 +226,18 @@ private boolean no_wildcard(final String string) {
 	/*
 	 * Getters / Setters
 	 */
-
-	public String getIgnoreFileAbsolutePath() {
-		return ignoreFileAbsolutePath;
-	}
-
-	public int getLineNumber() {
-		return lineNumber;
-	}
-
-	/**
-	 * @return the base
-	 */
-	public String getBase() {
-		return base;
-	}
-
-	/**
-	 * @return the noDir
-	 */
-	public boolean isNoDir() {
-		return noDir;
-	}
-
+	
 	/**
-	 * @return the endsWith
+	 * @return the ignoreFileAbsolutePath
 	 */
-	public boolean isEndsWith() {
-		return endsWith;
+	public String getIgnoreFileAbsolutePath() {
+		return ignoreFileAbsolutePath;
 	}
 
 	/**
-	 * @return the noWildcard
+	 * @return the lineNumber
 	 */
-	public boolean isNoWildcard() {
-		return noWildcard;
+	public int getLineNumber() {
+		return lineNumber;
 	}
 }
\ No newline at end of file
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
index 401a378..48cf454 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
@@ -14,6 +14,7 @@
 import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.team.core.Team;
 import org.spearce.egit.core.project.RepositoryMapping;
 import org.spearce.jgit.lib.Repository;
 
@@ -128,7 +129,7 @@ public synchronized static void importWorkspaceIgnores() {
 		 * FIXME: also do the file `git config --global --get
 		 * core.excludesfile`, is this already covered? RepositoryConfig
 		 * globalConfig = new RepositoryConfig(null, new File(FS.userHome(),
-		 * ".gitconfig"));
+		 * ".gitconfig")); RepositoryConfig.openUserConfig
 		 */
 
 		/*
@@ -136,4 +137,44 @@ public synchronized static void importWorkspaceIgnores() {
 		 * repositories.toString());
 		 */
 	}
+
+	/**
+	 * @param resource
+	 *            the resource to check
+	 * @return null when not matched, the matching Exclude otherwise the
+	 *         resource
+	 */
+	synchronized static Exclude isResourceExcluded(final IResource resource) {
+		if (resource == null) {
+			return null;
+		}
+
+		final RepositoryMapping mapping = RepositoryMapping
+				.getMapping(resource);
+		if (mapping == null) {
+			return null;
+		}
+
+		final IgnoreRepositoryCache cache = repositories.get(mapping
+				.getRepository());
+		if (cache == null) {
+			return null;
+		}
+
+		/* FIXME: also check global core.excludesfile, is this already covered? */
+
+		return cache.isIgnored(resource, mapping);
+	}
+
+	/**
+	 * @param resource
+	 * @return true when the resource is ignored
+	 */
+	public synchronized static boolean isIgnored(final IResource resource) {
+		if (isResourceExcluded(resource) != null) {
+			return true;
+		}
+
+		return Team.isIgnoredHint(resource);
+	}
 }
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
index fe2f529..7f35240 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
@@ -15,6 +15,7 @@
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceDelta;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
 
 /**
  * This class implements a cache of ignore patterns for an Eclipse project: it
@@ -198,4 +199,47 @@ synchronized void processIgnoreFile(final IFile ignoreFile,
 		// break;
 		// }
 	}
+
+	synchronized Exclude isIgnored(final String pathName,
+			final String baseName, final DType dType,
+			final IPath deepestDirectory) {
+		IPath searchDir = deepestDirectory;
+		String lookupKey = (searchDir.isEmpty() ? searchDir.toString()
+				: searchDir.toString() + "/");
+		final boolean result = false;
+		searcher: while (!result) {
+			/* look for the first ignore file up in the tree */
+			while (!ignoreFilesIndex.containsKey(lookupKey)) {
+				searchDir = searchDir.removeLastSegments(1);
+				if (searchDir.isEmpty()) {
+					break searcher;
+				}
+				lookupKey = (searchDir.isEmpty() ? searchDir.toString()
+						: searchDir.toString() + "/");
+			}
+			final IFile ignoreFile = ignoreFilesIndex.get(lookupKey);
+
+			/* when found then try to match the resource to those patterns */
+			if (ignoreFile != null) {
+				final LinkedList<Exclude> excludeList = ignoreFiles
+						.get(ignoreFile);
+				for (int i = excludeList.size() - 1; i >= 0; i--) {
+					final Exclude x = excludeList.get(i);
+					if (x.isMatch(pathName, baseName, dType)) {
+						return x;
+					}
+				}
+			}
+
+			if (searchDir.isEmpty()) {
+				break searcher;
+			}
+			searchDir = searchDir.removeLastSegments(1);
+			lookupKey = (searchDir.isEmpty() ? searchDir.toString() : searchDir
+					.toString()
+					+ "/");
+		}
+
+		return null;
+	}
 }
\ No newline at end of file
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java
index 4b6bf61..9bfb197 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java
@@ -35,7 +35,7 @@
 	/** the repository */
 	private Repository repository = null;
 
-	/** the checkout directory for the repository */
+	/** the checkout directory for the repository, full path, platform specific */
 	private String checkoutDir = null;
 
 	/** the cache that holds ignore data on a per-project basis */
@@ -59,6 +59,9 @@
 	/** the core.excludesfile setting */
 	private String coreExcludesSetting = null;
 
+	/** the exclude for the repository itself */
+	private Exclude repositoryExclude = null;
+
 	/**
 	 * Retrieve a project mapping from the projects cache. When the project is
 	 * not yet in the cache then create a new mapping for it and store it in the
@@ -95,6 +98,8 @@ IgnoreRepositoryCache(final Repository repository) {
 		}
 		this.repository = repository;
 		this.checkoutDir = repository.getWorkDir().getAbsolutePath();
+		this.repositoryExclude = new Exclude("/.git/", "", checkoutDir
+				+ "/.git/config", 1);
 	}
 
 	/*
@@ -295,14 +300,59 @@ private boolean readRepositoryCoreExcludesSetting() {
 		return changes;
 	}
 	
-	/*
-	 * Getters / Setters
-	 */
+	synchronized Exclude isIgnored(final IResource resource,
+			final RepositoryMapping mapping) {
+		final String pathName = mapping.getRepoRelativePath(resource);
+		IPath deepestDirectory = new Path(pathName).removeLastSegments(1);
+		final String baseName = resource.getName();
+		final DType dType = DType.get(resource);
+
+		/* the repository directory is always ignored */
+		if (this.repositoryExclude.isMatch(pathName, resource.getName(), DType
+				.get(resource))) {
+			return this.repositoryExclude;
+		}
 
-	/**
-	 * @return the checkoutDir
-	 */
-	public String getCheckoutDir() {
-		return checkoutDir;
+		Exclude exclude = null;
+
+		/* check project excludes */
+		final IProject project = resource.getProject();
+		if (project != null) {
+			final IgnoreProjectCache cache = projects.get(project);
+			if (cache != null) {
+				exclude = cache.isIgnored(pathName, baseName, dType,
+						deepestDirectory);
+				if (exclude != null) {
+					return exclude;
+				}
+			}
+			deepestDirectory = new Path(mapping.getRepoRelativePath(project))
+					.removeLastSegments(1);
+		} else {
+			deepestDirectory = new Path("");
+		}
+		
+		/* check excludes outside projects */
+		exclude = outside
+				.isIgnored(pathName, baseName, dType, deepestDirectory);
+		if (exclude != null) {
+			return exclude;
+		}
+		
+		/* also check info/exclude file per repo */
+		exclude = infoExclude.isIgnored(baseName, baseName, dType,
+				deepestDirectory);
+		if (exclude != null) {
+			return exclude;
+		}
+		
+		/* also check core.excludesfile per repo */
+		exclude = coreExcludes.isIgnored(baseName, baseName, dType,
+				deepestDirectory);
+		if (exclude != null) {
+			return exclude;
+		}
+
+		return null;
 	}
 }
\ No newline at end of file
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java b/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
index 414bd83..09766b6 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
@@ -63,6 +63,9 @@
 		@SuppressWarnings("synthetic-access")
 		public void resourceChanged(final IResourceChangeEvent event) {
 			switch (event.getType()) {
+			case IResourceChangeEvent.POST_CHANGE:
+				// GitIgnoreData.processChangesForIgnores(event);
+				break;
 			case IResourceChangeEvent.PRE_CLOSE:
 				uncache((IProject) event.getResource());
 				GitIgnoreData.uncacheProject(event.getResource());
diff --git a/org.spearce.jgit/META-INF/MANIFEST.MF b/org.spearce.jgit/META-INF/MANIFEST.MF
index 3344c3c..e5f6478 100644
--- a/org.spearce.jgit/META-INF/MANIFEST.MF
+++ b/org.spearce.jgit/META-INF/MANIFEST.MF
@@ -7,6 +7,7 @@ Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Export-Package: org.spearce.jgit.dircache,
  org.spearce.jgit.errors;uses:="org.spearce.jgit.lib",
+ org.spearce.jgit.fnmatch,
  org.spearce.jgit.lib,
  org.spearce.jgit.revplot,
  org.spearce.jgit.revwalk,
-- 
1.6.0.6

^ permalink raw reply related

* [EGIT] [PATCH RFC v1 0/5] Add (static) ignore functionality to EGit
From: Ferry Huberts @ 2009-03-26 21:34 UTC (permalink / raw)
  To: git; +Cc: Shawn O. Pearce, Robin Rosenberg, Ferry Huberts

This is the first - early - code that adds ignore functionality to EGit.
Currently it reads in all ignore patterns upon workspace startup into an
ignore cache. From this cache the ignore state of a resource is evaluated
in the same fashion as git does.

The code does not yet react to changes in ignore files but I'm planning to add
that soon and I can share a lot of code for that.

I send this code to receive feedback and to give you insight into what I'm
doing with it. I'm new both to EGit programming and Eclipse programming so
there might be things that could be done more elegantly :-)

A few notes:
- The patches are rebased on the current master (e3440623)
- The order of the patches must be re-arranged, but that is rather easy. The
  correct order - once finished - would be:
    Build up the ignore patterns cache upon workspace startup.
    Use the ignore patterns cache to determine ignores
    Enable the ignore handling of the plugin
    Optimise ignore evaluation
    Do not set .git as a Team ignore pattern
- The core.excludesfile code is currently untested, the other code seems to be
  in a good state.
- There are a few FIXMEs in the code with questions and tasks. It's a work in
  progress and these will disappear.

Ferry Huberts (5):
  Build up the ignore patterns cache upon workspace startup.
  Enable the ignore handling of the plugin
  Optimise ignore evaluation
  Do not set .git as a Team ignore pattern
  Use the ignore patterns cache to determine ignores

 org.spearce.egit.core/META-INF/MANIFEST.MF         |    1 +
 org.spearce.egit.core/plugin.xml                   |    6 -
 .../src/org/spearce/egit/core/ignores/DType.java   |   44 ++
 .../src/org/spearce/egit/core/ignores/Exclude.java |  243 +++++++++
 .../spearce/egit/core/ignores/GitIgnoreData.java   |  180 +++++++
 .../org/spearce/egit/core/ignores/IgnoreFile.java  |   82 +++
 .../egit/core/ignores/IgnoreFileOutside.java       |  543 ++++++++++++++++++++
 .../egit/core/ignores/IgnoreProjectCache.java      |  245 +++++++++
 .../egit/core/ignores/IgnoreRepositoryCache.java   |  358 +++++++++++++
 .../org/spearce/egit/core/op/TrackOperation.java   |    7 +-
 .../spearce/egit/core/project/GitProjectData.java  |    8 +
 .../decorators/DecoratableResourceAdapter.java     |   11 +-
 org.spearce.jgit/META-INF/MANIFEST.MF              |    1 +
 13 files changed, 1712 insertions(+), 17 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/DType.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFile.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFileOutside.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java

^ 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