Git development
 help / color / mirror / Atom feed
* Re: cvs import
From: Oswald Buddenhagen @ 2006-09-16  6:04 UTC (permalink / raw)
  To: Shawn Pearce
  Cc: Markus Schiltknecht, Michael Haggerty, Jon Smirl, Martin Langhoff,
	Git Mailing List, monotone-devel, dev
In-Reply-To: <20060916033917.GA24269@spearce.org>

On Fri, Sep 15, 2006 at 11:39:18PM -0400, Shawn Pearce wrote:
> On the other hand from what I understand of Monotone it needs
> the revisions in oldest->newest order, as does SVN.
> 
> Doing both orderings in cvs2noncvs is probably ugly.
>
don't worry, as i know mike, he'll come up with an abstract, outright
beautiful interface that makes you want to implement middle->oldnewest
just for the sake of doing it. :)

-- 
Hi! I'm a .signature virus! Copy me into your ~/.signature, please!
--
Chaos, panic, and disorder - my work here is done.

^ permalink raw reply

* Re: [PATCH] Skip t3403 selftests if stdin is not a terminal
From: Junio C Hamano @ 2006-09-16  5:39 UTC (permalink / raw)
  To: Gerrit Pape; +Cc: git
In-Reply-To: <20060915125910.10514.qmail@26499ea44f2ee3.315fe32.mid.smarden.org>

Gerrit Pape <pape@smarden.org> writes:

> sh t3403-rebase-skip.sh </dev/null fails because stdin is not connected
> to a terminal, as in the Debian autobuild environment.  This disbales
> the test 3 and 7 in this case.

Disabling these tests somehow feels as if you are shooting the
messenger who reports breakage of the commands they try to test.

Is it expected that the git Porcelainish commands involved in
these particular tests not to work without terminal?  If not
maybe we should fix them, not the test.

^ permalink raw reply

* Re: [PATCH] Build on Debian GNU/Hurd
From: Junio C Hamano @ 2006-09-16  5:35 UTC (permalink / raw)
  To: Gerrit Pape; +Cc: git
In-Reply-To: <20060915125846.10469.qmail@ccc765dd61a02d.315fe32.mid.smarden.org>

Gerrit Pape <pape@smarden.org> writes:

> Patch from Cyril Brulebois to make the build process detect and support the
> Debian GNU/Hurd architecture, see
>  http://bugs.debian.org/379841
>
> Signed-off-by: Gerrit Pape <pape@smarden.org>
>
> +ifeq ($(uname_S),GNU)
> +       # GNU stands for GNU/Hurd
> +       NO_STRLCPY = YesPlease
> +       ALL_CFLAGS += -DPATH_MAX=4096
> +endif

Two questions come to mind. (1) Does GNU stand for GNU/Hurd and
nobody else? (2) Does everybody else have PATH_MAX?

Adding NO_STRLCPY I do not have much problems with, but
something like the attached may be cleaner to deal with PATH_MAX;
of course now there is an issue of what the appropriate value
for that symbol should be.

Would including git-compat-util.h in builtin.h break somebody?

-- >8 --
Define fallback PATH_MAX on systems that do not define one in <limits.h>

Notably GNU/Hurd, as reported by Gerrit Pape.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
diff --git a/git-compat-util.h b/git-compat-util.h
index 552b8ec..0272d04 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -26,6 +26,13 @@ #include <netinet/in.h>
 #include <sys/types.h>
 #include <dirent.h>
 
+/* On most systems <limits.h> would have given us this, but
+ * not on some systems (e.g. GNU/Hurd).
+ */
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
 #ifdef __GNUC__
 #define NORETURN __attribute__((__noreturn__))
 #else
diff --git a/builtin.h b/builtin.h
index 34ed7b9..1d41f83 100644
--- a/builtin.h
+++ b/builtin.h
@@ -1,8 +1,7 @@
 #ifndef BUILTIN_H
 #define BUILTIN_H
 
-#include <stdio.h>
-#include <limits.h>
+#include "git-compat-util.h"
 
 extern const char git_version_string[];
 extern const char git_usage_string[];

^ permalink raw reply related

* Re: cvs import
From: Shawn Pearce @ 2006-09-16  3:39 UTC (permalink / raw)
  To: Markus Schiltknecht
  Cc: Michael Haggerty, Jon Smirl, Martin Langhoff, Git Mailing List,
	monotone-devel, dev
In-Reply-To: <450A581E.2050509@bluegap.ch>

Markus Schiltknecht <markus@bluegap.ch> wrote:
> Shawn Pearce wrote:
> >I don't know how the Monotone guys feel about it but I think Git
> >is happy with the data in any order, just so long as the dependency
> >chains aren't fed out of order.  Which I think nearly all changeset
> >based SCMs would have an issue with.  So we should be just fine
> >with the current chronological order produced by cvs2svn.
> 
> I'd vote for splitting into file data (and delta / patches) import and 
> metadata import (author, changelog, DAG).
> 
> Monotone would be happiest if the file data were sent one file after 
> another and (inside each file) in the order of each file's single 
> history. That guarantees good import performance for monotone. I imagine 
> it's about the same for git. And if you have to somehow cache the files 
> anyway, subversion will benefit, too. (Well, at least the cache will 
> thank us with good performance).
>
> After all file data has been delivered, the metadata can be delivered. 
> As neigther monotone nor git care much if they are chronological across 
> branches, I'd vote for doing it that way.

Right.  I think that one of the cvs2svn guys had the right idea
here.  Provide two hooks: one early during the RCS file parse which
supplies a backend each full text file revision and another during
the very last stage which includes the "file" in the metadata stream
for commit.

This would give Git and Monotone a way to grab the full text for each
file and stream them out up front, then include only a "token" in the
metadata stream which identifies the specific revision.  Meanwhile
SVN can either cache the file revision during the early part or
ignore it, then dump out the full content during the metadata.


As it happens Git doesn't care what order the file revisions come in.
If we don't repack the imported data we would prefer to get the
revisions in newest->oldest order so we can delta the older versions
against the newer versions (like RCS).  This is also happens to be
the fastest way to extract the revision data from RCS.

On the other hand from what I understand of Monotone it needs
the revisions in oldest->newest order, as does SVN.

Doing both orderings in cvs2noncvs is probably ugly.  Doing just
oldest->newest (since 2/3 backends want that) would be acceptable
but would slow down Git imports as the RCS parsing overhead would
be much higher.

-- 
Shawn.

^ permalink raw reply

* Re: [PATCH] Trivial support for cloning and fetching via ftp://.
From: Sasha Khapyorsky @ 2006-09-16  2:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vk6475408.fsf@assigned-by-dhcp.cox.net>

On 23:57 Wed 13 Sep     , Junio C Hamano wrote:
> Sasha Khapyorsky <sashak@voltaire.com> writes:
> 
> > This adds trivial support for cloning and fetching via ftp://.
> 
> Interesting.
> 
> I was wondering myself if our use of curl libraries in
> http-fetch allows us to do this when I was looking at the
> alternates breakage yesterday.
> 
> At a few places we do look at http error code that is returned
> from the curl library, and change our behaviour based on that.
> But it appears the difference between error code from ftp and
> http has no bad effect on us.  In an empty repository, we can
> run this:
> 
> 	$ git-http-fetch -a -v heads/merge \
> 	  ftp://ftp.kernel.org/pub/scm/linux/kernel/git/paulus/powerpc.git
> 
> (of course, this should normally be with http://www.kernel.org).
> We notice that we get an error from a request for one object,
> and switch to pack & alternates transfer.  The only difference
> between http://www and ftp://ftp is that for the former we know
> error code 404 and supress the error message but for the latter
> we do not treat error 550 from RETR response any specially and
> show an error message.  We still fall back to retrieve packs,
> hoping that the missing object is in a pack.
> 
> I'd take this patch as is, but we might want to add some error
> message supression logic just like we do for http.

Something like this?

With this change I'm able to clone
ftp://ftp.kernel.org/pub/scm/linux/kernel/git/paulus/powerpc.git


diff --git a/http-fetch.c b/http-fetch.c
index a113bb8..46d6029 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -324,7 +324,9 @@ static void process_object_response(void
 
 	/* Use alternates if necessary */
 	if (obj_req->http_code == 404 ||
-	    obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+	    obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE ||
+	    (obj_req->http_code == 550 &&
+	     obj_req->curl_result == CURLE_FTP_COULDNT_RETR_FILE)) {
 		fetch_alternates(alt->base);
 		if (obj_req->repo->next != NULL) {
 			obj_req->repo =
@@ -538,7 +540,9 @@ static void process_alternates_response(
 		}
 	} else if (slot->curl_result != CURLE_OK) {
 		if (slot->http_code != 404 &&
-		    slot->curl_result != CURLE_FILE_COULDNT_READ_FILE) {
+		    slot->curl_result != CURLE_FILE_COULDNT_READ_FILE &&
+		    (slot->http_code != 550 &&
+		     slot->curl_result != CURLE_FTP_COULDNT_RETR_FILE)) {
 			got_alternates = -1;
 			return;
 		}
@@ -942,7 +946,9 @@ #endif
 		run_active_slot(slot);
 		if (results.curl_result != CURLE_OK) {
 			if (results.http_code == 404 ||
-			    results.curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+			    results.curl_result == CURLE_FILE_COULDNT_READ_FILE ||
+			    (results.http_code == 550 &&
+			     results.curl_result == CURLE_FTP_COULDNT_RETR_FILE)) {
 				repo->got_indices = 1;
 				free(buffer.buffer);
 				return 0;
@@ -1124,7 +1130,9 @@ #endif
 	} else if (obj_req->curl_result != CURLE_OK &&
 		   obj_req->http_code != 416) {
 		if (obj_req->http_code == 404 ||
-		    obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE)
+		    obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE ||
+		    (obj_req->http_code == 550 &&
+		     obj_req->curl_result == CURLE_FTP_COULDNT_RETR_FILE))
 			ret = -1; /* Be silent, it is probably in a pack. */
 		else
 			ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",

^ permalink raw reply related

* Re: Add "git show-ref" builtin command
From: Junio C Hamano @ 2006-09-15 22:54 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <eef5m8$euj$1@sea.gmane.org>

Jakub Narebski <jnareb@gmail.com> writes:

> Wouldn't it be better to be able to use (or be able to enable, like echo -e
> option) interpretation of the  backslash-escaped characters, like 
> \t, \n, \0?

I've been thinking about letting the --format to specify
embedding arbitrary byte value in the output.

This option however is mostly to help Porcelain written in
languages other than C (and that is where the language specific
quoting styles come in) to allow a template of a scriptlet to be
specified, as you have probably seen in the examples in the
documentation page, so I think it is more user friendly to leave
backslash as just a literal character.

My current thinking is to allow you to say %XX (a per-cent
followed by exactly two hexadecimal digits) to do embed a
literal byte value.  Then a Porcelain written in Perl that does
not want to eval output can do something like this:

	my $fmt = 'r%(refname)%00o%(objectname)%00%00';
	open R, '-|', 'git-for-each-ref', "--format=$fmt";
	my $all = join('', <R>);
	close R;        	        
        for (split(/\0\0/, $all)) {
		/r(.*?)\0o(.*)/ &&
                print "ref = $1, obj = $2\n";
        }

Another thing is that originally I picked %(name) syntax because
I thought we might want to do fancier "%20(column)d" like Python
does with its string formatting operator.  But I now think it
makes more sense to output whatever is asked as string literals
and have host language worry about formatting.  So in that
sense, using %() as our formatting specifier will get in the way
for people who writes in Python.  Maybe I should change it to
something like %{name} instead (not ${name} -- that would
interfere with the shell and Perl).

Anyhow, on top of the previous one, this will let you say %00 to
embed a NUL in your string.

diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
index f064e7e..698618b 100644
--- a/builtin-for-each-ref.c
+++ b/builtin-for-each-ref.c
@@ -710,12 +710,39 @@ static void print_value(struct refinfo *
 	}
 }
 
+static int hex1(char ch)
+{
+	if ('0' <= ch && ch <= '9')
+		return ch - '0';
+	else if ('a' <= ch && ch <= 'f')
+		return ch - 'a' + 10;
+	else if ('A' <= ch && ch <= 'F')
+		return ch - 'A' + 10;
+	return -1;
+}
+static int hex2(const char *cp)
+{
+	if (cp[0] && cp[1])
+		return (hex1(cp[0]) << 4) | hex1(cp[1]);
+	else
+		return -1;
+}
+
 static void emit(const char *cp, const char *ep)
 {
 	while (*cp && (!ep || cp < ep)) {
-		if (*cp == '%')
+		if (*cp == '%') {
 			if (cp[1] == '%')
 				cp++;
+			else {
+				int ch = hex2(cp + 1);
+				if (0 <= ch) {
+					putchar(ch);
+					cp += 3;
+					continue;
+				}
+			}
+		}
 		putchar(*cp);
 		cp++;
 	}
@@ -731,8 +758,10 @@ static void show_ref(struct refinfo *inf
 			emit(cp, sp);
 		print_value(info, parse_atom(sp + 2, ep), quote_style);
 	}
-	if (*cp)
-		fputs(cp, stdout);
+	if (*cp) {
+		sp = cp + strlen(cp);
+		emit(cp, sp);
+	}
 	putchar('\n');
 }
 

^ permalink raw reply related

* Re: Teach "git checkout" to use git-show-ref
From: Jakub Narebski @ 2006-09-15 22:11 UTC (permalink / raw)
  To: git
In-Reply-To: <Pine.LNX.4.64.0609151455150.4388@g5.osdl.org>

Linus Torvalds wrote:

> @@ -51,7 +51,7 @@ while [ "$#" != "0" ]; do
>                         fi
>                         new="$rev"
>                         new_name="$arg^0"
> -                       if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
> +                       if git-show-ref --verify --quiet -- "refs/heads/$arg" ]; then
  +                       if git-show-ref --verify --quiet -- "refs/heads/$arg"; then
>                                 branch="$arg"
>                         fi
>                 elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)

There is leftover ] before ; in if.

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply

* Teach "git checkout" to use git-show-ref
From: Linus Torvalds @ 2006-09-15 21:56 UTC (permalink / raw)
  To: Junio C Hamano, Git Mailing List
In-Reply-To: <Pine.LNX.4.64.0609151108560.4388@g5.osdl.org>


That way, it doesn't care how the refs are stored any more

Signed-off-by: Linus Torvalds <torvalds@osdl.org>
---

Just as an example of how to use git-show-ref rather than knowing about 
"$GIT_DIR/refs/..." paths.

This basically replaces my much hackier version that is in the "lt/refs" 
branch, but can go into the master branch independently of the 
refs-packing work.

diff --git a/git-checkout.sh b/git-checkout.sh
index 580a9e8..6e4c535 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do
 		shift
 		[ -z "$newbranch" ] &&
 			die "git checkout: -b needs a branch name"
-		[ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
+		git-show-ref --verify --quiet -- "refs/heads/$newbranch" &&
 			die "git checkout: branch $newbranch already exists"
 		git-check-ref-format "heads/$newbranch" ||
 			die "git checkout: we do not like '$newbranch' as a branch name."
@@ -51,7 +51,7 @@ while [ "$#" != "0" ]; do
 			fi
 			new="$rev"
 			new_name="$arg^0"
-			if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+			if git-show-ref --verify --quiet -- "refs/heads/$arg" ]; then
 				branch="$arg"
 			fi
 		elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)

^ permalink raw reply related

* Re: stg pop/push -a nitpick: not handling deleted files
From: Catalin Marinas @ 2006-09-15 21:41 UTC (permalink / raw)
  To: Auke Kok; +Cc: git
In-Reply-To: <450AE0AC.7060806@intel.com>

Auke Kok <auke-jan.h.kok@intel.com> wrote:
> minor nitpick when working with a patch that deletes a file: stg does
> not handle the 'abandoned' file when popping all patches off -
> essentially it would have to put the file back over an abandoned file.
[...]
> $ stg pop -a
> Popping "all_git_versions.patch" - "all_copyright_update.patch"
> patches...fatal: Untracked working tree file
> 'drivers/net/e1000/LICENSE' would be overwritten by merge.
> stg pop: git-read-tree failed (local changes maybe?)

I don't think it's an StGIT-specific issue, git-read-tree failed to
switch the working tree from one commit to another because a file
would be overwritten. It is probably safer to fail in this situation.

-- 
Catalin

^ permalink raw reply

* Re: Add "git show-ref" builtin command
From: Jakub Narebski @ 2006-09-15 21:24 UTC (permalink / raw)
  To: git
In-Reply-To: <7vmz90g80m.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano wrote:

> +<format>::
> +       A string that interpolates `%(fieldname)` from the
> +       object pointed at by a ref being shown.  If `fieldname`
> +       is prefixed with an asterisk (`*`) and the ref points
> +       at a tag object, the value for the field in the object
> +       tag refers is used.  When unspecified, defaults to
> +       `%(refname)`.

Wouldn't it be better to be able to use (or be able to enable, like echo -e
option) interpretation of the  backslash-escaped characters, like 
\t, \n, \0?

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply

* Re: Add "git show-ref" builtin command
From: Junio C Hamano @ 2006-09-15 21:00 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git, Jakub Narebski
In-Reply-To: <Pine.LNX.4.64.0609151108560.4388@g5.osdl.org>

Linus Torvalds <torvalds@osdl.org> writes:

> It's kind of like "git peek-remote", but works only locally (and thus 
> avoids the whole overhead of git_connect()) and has some extra 
> verification features.
>...
> NOTE! This is against the current master branch, and does _not_ depend on 
> my other ref-work. In fact, I'd argue that this is much better to merge 
> first and independently of the packed references branch, because it allows 
> us to much more easily update the scripts etc to be totally unaware of how 
> the refs are actually stored.

Looks sane.  Thanks.

Having said that....

Gaaah.  I had been doing a "show-refs" that does something quite
different from this X-<.

I'll rename it; I think the new name makes more sense anyway.

-- >8 --
From: Junio C Hamano <junkio@cox.net>
Date: Fri, 15 Sep 2006 13:30:02 -0700
Subject: [PATCH] Add git-for-each-ref: helper for language bindings

This adds a new command, git-for-each-ref.  You can have it iterate
over refs and have it output various aspects of the objects they
refer to.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 .gitignore                         |    1 
 Documentation/git-for-each-ref.txt |  164 +++++++
 Makefile                           |    1 
 builtin-for-each-ref.c             |  845 ++++++++++++++++++++++++++++++++++++
 builtin.h                          |    3 
 git.c                              |    1 
 object.c                           |   68 ++-
 object.h                           |    6 
 quote.c                            |   38 ++
 quote.h                            |    4 
 10 files changed, 1103 insertions(+), 28 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0d608fe..0b08f37 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@ git-fetch
 git-fetch-pack
 git-findtags
 git-fmt-merge-msg
+git-for-each-ref
 git-format-patch
 git-fsck-objects
 git-get-tar-commit-id
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
new file mode 100644
index 0000000..74dd735
--- /dev/null
+++ b/Documentation/git-for-each-ref.txt
@@ -0,0 +1,164 @@
+git-for-each-ref(1)
+===================
+
+NAME
+----
+git-for-each-ref - Output information on each ref
+
+SYNOPSIS
+--------
+'git-for-each-ref' [--count=<count>]* [--shell|--perl|--python] [--sort=<key>]* [--format=<format>] [<pattern>]
+
+DESCRIPTION
+-----------
+
+Iterate over all refs that match `<pattern>` and show them
+according to the given `<format>`, after sorting them according
+to the given `<sortkeys>`.  If `<max>` is given, stop after
+showing that many refs.  The interporated values in `<format>`
+can optionally be quoted as string literals in the specified
+host language.
+
+OPTIONS
+-------
+<count>::
+	By default the command shows all refs that match
+	`<pattern>`.  This option makes it stop after showing
+	that many refs.
+
+<key>::
+	A field name to sort on.  Prefix `-` to sort in
+	descending order of the value.  When unspecified,
+	`refname` is used.  More than one sort keys can be
+	given.
+
+<format>::
+	A string that interpolates `%(fieldname)` from the
+	object pointed at by a ref being shown.  If `fieldname`
+	is prefixed with an asterisk (`*`) and the ref points
+	at a tag object, the value for the field in the object
+	tag refers is used.  When unspecified, defaults to
+	`%(refname)`.
+
+<pattern>::
+	If given, the name of the ref is matched against this
+	using fnmatch(3).  Refs that do not match the pattern
+	are not shown.
+
+--shell, --perl, --python::
+	If given, strings that substitute `%(fieldname)`
+	placeholders are quoted as string literals suitable for
+	the specified host language.  This is meant to produce
+	a scriptlet that can directly be `eval`ed.
+
+
+FIELD NAMES
+-----------
+
+Various values from structured fields in referenced objects can
+be used to interpolate into the resulting output, or as sort
+keys.
+
+For all objects, the following names can be used:
+
+refname::
+	The name of the ref (the part after $GIT_DIR/refs/).
+
+objecttype::
+	The type of the object (`blob`, `tree`, `commit`, `tag`).
+
+objectsize::
+	The size of the object (the same as `git-cat-file -s` reports).
+
+objectname::
+	The object name (aka SHA-1).
+
+In addition to the above, for commit and tag objects, the header
+field names (`tree`, `parent`, `object`, `type`, and `tag`) can
+be used to specify the value in the header field.
+
+Fields that have name-email-date tuple as its value (`author`,
+`committer`, and `tagger`) can be suffixed with `name`, `email`,
+and `date` to extract the named component.
+
+The first line of the message in a commit and tag object is
+`subject`, the remaining lines are `body`.  The whole message
+is `contents`.
+
+For sorting purposes, fields with numeric values sort in numeric
+order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
+All other fields are used to sort in their byte-value order.
+
+In any case, a field name that refers to a field inapplicable to
+the object referred by the ref does not cause an error.  It
+returns an empty string instead.
+
+
+EXAMPLES
+--------
+
+Show the most recent 3 tagged commits::
+
+------------
+#!/bin/sh
+
+git-for-each-ref --count=3 --sort='-*authordate' \
+--format='From: %(*authorname) %(*authoremail)
+Subject: %(*subject)
+Date: %(*authordate)
+Ref: %(*refname)
+
+%(*body)
+' 'refs/tags'
+------------
+
+A bit more elaborate report on tags::
+------------
+#!/bin/sh
+
+fmt='
+	r=%(refname)
+	t=%(*objecttype)
+	T=${r#refs/tags/}
+
+	o=%(*objectname)
+	n=%(*authorname)
+	e=%(*authoremail)
+	s=%(*subject)
+	d=%(*authordate)
+	b=%(*body)
+
+	kind=Tag
+	if test "z$t" = z
+	then
+		# could be a lightweight tag
+		t=%(objecttype)
+		kind="Lightweight tag"
+		o=%(objectname)
+		n=%(authorname)
+		e=%(authoremail)
+		s=%(subject)
+		d=%(authordate)
+		b=%(body)
+	fi
+	echo "$kind $T points at a $t object $o"
+	if test "z$t" = zcommit
+	then
+		echo "The commit was authored by $n $e
+at $d, and titled
+
+    $s
+
+Its message reads as:
+"
+		echo "$b" | sed -e "s/^/    /"
+		echo
+	fi
+'
+
+eval=`git-for-each-ref -s --format="$fmt" \
+	--sort='*objecttype' \
+	--sort=-taggerdate \
+	refs/tags`
+eval "$eval"
+------------
diff --git a/Makefile b/Makefile
index 7b3114f..f0e2e51 100644
--- a/Makefile
+++ b/Makefile
@@ -267,6 +267,7 @@ BUILTIN_OBJS = \
 	builtin-diff-stages.o \
 	builtin-diff-tree.o \
 	builtin-fmt-merge-msg.o \
+	builtin-for-each-ref.o \
 	builtin-grep.o \
 	builtin-init-db.o \
 	builtin-log.o \
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
new file mode 100644
index 0000000..f064e7e
--- /dev/null
+++ b/builtin-for-each-ref.c
@@ -0,0 +1,845 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "quote.h"
+#include <fnmatch.h>
+
+/* Quoting styles */
+#define QUOTE_NONE 0
+#define QUOTE_SHELL 1
+#define QUOTE_PERL 2
+#define QUOTE_PYTHON 3
+
+typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+struct atom_value {
+	const char *s;
+	unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
+struct ref_sort {
+	struct ref_sort *next;
+	int atom; /* index into used_atom array */
+	unsigned reverse : 1;
+};
+
+struct refinfo {
+	char *refname;
+	unsigned char objectname[20];
+	struct atom_value *value;
+};
+
+static struct {
+	const char *name;
+	cmp_type cmp_type;
+} valid_atom[] = {
+	{ "refname" },
+	{ "objecttype" },
+	{ "objectsize", FIELD_ULONG },
+	{ "objectname" },
+	{ "tree" },
+	{ "parent" }, /* NEEDSWORK: how to address 2nd and later parents? */
+	{ "numparent", FIELD_ULONG },
+	{ "object" },
+	{ "type" },
+	{ "tag" },
+	{ "author" },
+	{ "authorname" },
+	{ "authoremail" },
+	{ "authordate", FIELD_TIME },
+	{ "committer" },
+	{ "committername" },
+	{ "committeremail" },
+	{ "committerdate", FIELD_TIME },
+	{ "tagger" },
+	{ "taggername" },
+	{ "taggeremail" },
+	{ "taggerdate", FIELD_TIME },
+	{ "subject" },
+	{ "body" },
+	{ "contents" },
+};
+
+/*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects.  refinfo
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+static const char **used_atom;
+static cmp_type *used_atom_type;
+static int used_atom_cnt, sort_atom_limit, need_tagged;
+
+/*
+ * Used to parse format string and sort specifiers
+ */
+static int parse_atom(const char *atom, const char *ep)
+{
+	const char *sp;
+	char *n;
+	int i, at;
+
+	sp = atom;
+	if (*sp == '*' && sp < ep)
+		sp++; /* deref */
+	if (ep <= sp)
+		die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+	/* Do we have the atom already used elsewhere? */
+	for (i = 0; i < used_atom_cnt; i++) {
+		int len = strlen(used_atom[i]);
+		if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+			return i;
+	}
+
+	/* Is the atom a valid one? */
+	for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+		int len = strlen(valid_atom[i].name);
+		if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len))
+			break;
+	}
+
+	if (ARRAY_SIZE(valid_atom) <= i)
+		die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+	/* Add it in, including the deref prefix */
+	at = used_atom_cnt;
+	used_atom_cnt++;
+	used_atom = xrealloc(used_atom,
+			     (sizeof *used_atom) * used_atom_cnt);
+	used_atom_type = xrealloc(used_atom_type,
+				  (sizeof(*used_atom_type) * used_atom_cnt));
+	n = xmalloc(ep - atom + 1);
+	memcpy(n, atom, ep - atom);
+	n[ep-atom] = 0;
+	used_atom[at] = n;
+	used_atom_type[at] = valid_atom[i].cmp_type;
+	return at;
+}
+
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static const char *find_next(const char *cp)
+{
+	while (*cp) {
+		if (*cp == '%') {
+			/* %( is the start of an atom;
+			 * %% is a quoteed per-cent.
+			 */
+			if (cp[1] == '(')
+				return cp;
+			else if (cp[1] == '%')
+				cp++; /* skip over two % */
+			/* otherwise this is a singleton, literal % */
+		}
+		cp++;
+	}
+	return NULL;
+}
+
+/*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+static void verify_format(const char *format)
+{
+	const char *cp, *sp;
+	for (cp = format; *cp && (sp = find_next(cp)); ) {
+		const char *ep = strchr(sp, ')');
+		if (!ep)
+			die("malformatted format string %s", sp);
+		/* sp points at "%(" and ep points at the closing ")" */
+		parse_atom(sp + 2, ep);
+		cp = ep + 1;
+	}
+}
+
+/*
+ * Given an object name, read the object data and size, and return a
+ * "struct object".  If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+{
+	char type[20];
+	void *buf = read_sha1_file(sha1, type, sz);
+
+	if (buf)
+		*obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+	else
+		*obj = NULL;
+	return buf;
+}
+
+/* See grab_values */
+static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+	int i;
+
+	for (i = 0; i < used_atom_cnt; i++) {
+		const char *name = used_atom[i];
+		struct atom_value *v = &val[i];
+		if (!!deref != (*name == '*'))
+			continue;
+		if (deref)
+			name++;
+		if (!strcmp(name, "objecttype"))
+			v->s = type_names[obj->type];
+		else if (!strcmp(name, "objectsize")) {
+			char *s = xmalloc(40);
+			sprintf(s, "%lu", sz);
+			v->ul = sz;
+			v->s = s;
+		}
+		else if (!strcmp(name, "objectname")) {
+			char *s = xmalloc(41);
+			strcpy(s, sha1_to_hex(obj->sha1));
+			v->s = s;
+		}
+	}
+}
+
+/* See grab_values */
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+	int i;
+	struct tag *tag = (struct tag *) obj;
+
+	for (i = 0; i < used_atom_cnt; i++) {
+		const char *name = used_atom[i];
+		struct atom_value *v = &val[i];
+		if (!!deref != (*name == '*'))
+			continue;
+		if (deref)
+			name++;
+		if (!strcmp(name, "tag"))
+			v->s = tag->tag;
+	}
+}
+
+static int num_parents(struct commit *commit)
+{
+	struct commit_list *parents;
+	int i;
+
+	for (i = 0, parents = commit->parents;
+	     parents;
+	     parents = parents->next)
+		i++;
+	return i;
+}
+
+/* See grab_values */
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+	int i;
+	struct commit *commit = (struct commit *) obj;
+
+	for (i = 0; i < used_atom_cnt; i++) {
+		const char *name = used_atom[i];
+		struct atom_value *v = &val[i];
+		if (!!deref != (*name == '*'))
+			continue;
+		if (deref)
+			name++;
+		if (!strcmp(name, "tree")) {
+			char *s = xmalloc(41);
+			strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+			v->s = s;
+		}
+		if (!strcmp(name, "numparent")) {
+			char *s = xmalloc(40);
+			sprintf(s, "%lu", v->ul);
+			v->s = s;
+			v->ul = num_parents(commit);
+		}
+		else if (!strcmp(name, "parent")) {
+			int num = num_parents(commit);
+			int i;
+			struct commit_list *parents;
+			char *s = xmalloc(42 * num);
+			v->s = s;
+			for (i = 0, parents = commit->parents;
+			     parents;
+			     parents = parents->next, i = i + 42) {
+				struct commit *parent = parents->item;
+				strcpy(s+i, sha1_to_hex(parent->object.sha1));
+				if (parents->next)
+					s[i+40] = ' ';
+			}
+		}
+	}
+}
+
+static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+{
+	const char *eol;
+	while (*buf) {
+		if (!strncmp(buf, who, wholen) &&
+		    buf[wholen] == ' ')
+			return buf + wholen + 1;
+		eol = strchr(buf, '\n');
+		if (!eol)
+			return "";
+		eol++;
+		if (eol[1] == '\n')
+			return ""; /* end of header */
+		buf = eol;
+	}
+	return "";
+}
+
+static char *copy_line(const char *buf)
+{
+	const char *eol = strchr(buf, '\n');
+	char *line;
+	int len;
+	if (!eol)
+		return "";
+	len = eol - buf;
+	line = xmalloc(len + 1);
+	memcpy(line, buf, len);
+	line[len] = 0;
+	return line;
+}
+
+static char *copy_name(const char *buf)
+{
+	const char *eol = strchr(buf, '\n');
+	const char *eoname = strstr(buf, " <");
+	char *line;
+	int len;
+	if (!(eoname && eol && eoname < eol))
+		return "";
+	len = eoname - buf;
+	line = xmalloc(len + 1);
+	memcpy(line, buf, len);
+	line[len] = 0;
+	return line;
+}
+
+static char *copy_email(const char *buf)
+{
+	const char *email = strchr(buf, '<');
+	const char *eoemail = strchr(email, '>');
+	char *line;
+	int len;
+	if (!email || !eoemail)
+		return "";
+	eoemail++;
+	len = eoemail - email;
+	line = xmalloc(len + 1);
+	memcpy(line, email, len);
+	line[len] = 0;
+	return line;
+}
+
+static void grab_date(const char *buf, struct atom_value *v)
+{
+	const char *eoemail = strstr(buf, "> ");
+	char *zone;
+	unsigned long timestamp;
+	long tz;
+
+	if (!eoemail)
+		goto bad;
+	timestamp = strtoul(eoemail + 2, &zone, 10);
+	if (timestamp == ULONG_MAX)
+		goto bad;
+	tz = strtol(zone, NULL, 10);
+	if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+		goto bad;
+	v->s = xstrdup(show_date(timestamp, tz, 0));
+	v->ul = timestamp;
+	return;
+ bad:
+	v->s = "";
+	v->ul = 0;
+}
+
+/* See grab_values */
+static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+	int i;
+	int wholen = strlen(who);
+	const char *wholine = NULL;
+
+	for (i = 0; i < used_atom_cnt; i++) {
+		const char *name = used_atom[i];
+		struct atom_value *v = &val[i];
+		if (!!deref != (*name == '*'))
+			continue;
+		if (deref)
+			name++;
+		if (strncmp(who, name, wholen))
+			continue;
+		if (name[wholen] != 0 &&
+		    strcmp(name + wholen, "name") &&
+		    strcmp(name + wholen, "email") &&
+		    strcmp(name + wholen, "date"))
+			continue;
+		if (!wholine)
+			wholine = find_wholine(who, wholen, buf, sz);
+		if (!wholine)
+			return; /* no point looking for it */
+		if (name[wholen] == 0)
+			v->s = copy_line(wholine);
+		else if (!strcmp(name + wholen, "name"))
+			v->s = copy_name(wholine);
+		else if (!strcmp(name + wholen, "email"))
+			v->s = copy_email(wholine);
+		else if (!strcmp(name + wholen, "date"))
+			grab_date(wholine, v);
+	}
+}
+
+static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+{
+	while (*buf) {
+		const char *eol = strchr(buf, '\n');
+		if (!eol)
+			return;
+		if (eol[1] == '\n') {
+			buf = eol + 1;
+			break; /* found end of header */
+		}
+		buf = eol + 1;
+	}
+	while (*buf == '\n')
+		buf++;
+	if (!*buf)
+		return;
+	*sub = buf; /* first non-empty line */
+	buf = strchr(buf, '\n');
+	if (!buf)
+		return; /* no body */
+	while (*buf == '\n')
+		buf++; /* skip blank between subject and body */
+	*body = buf;
+}
+
+/* See grab_values */
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+	int i;
+	const char *subpos = NULL, *bodypos = NULL;
+
+	for (i = 0; i < used_atom_cnt; i++) {
+		const char *name = used_atom[i];
+		struct atom_value *v = &val[i];
+		if (!!deref != (*name == '*'))
+			continue;
+		if (deref)
+			name++;
+		if (strcmp(name, "subject") &&
+		    strcmp(name, "body") &&
+		    strcmp(name, "contents"))
+			continue;
+		if (!subpos)
+			find_subpos(buf, sz, &subpos, &bodypos);
+		if (!subpos)
+			return;
+
+		if (!strcmp(name, "subject"))
+			v->s = copy_line(subpos);
+		else if (!strcmp(name, "body"))
+			v->s = bodypos;
+		else if (!strcmp(name, "contents"))
+			v->s = subpos;
+	}
+}
+
+/* We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+static void fill_missing_values(struct atom_value *val)
+{
+	int i;
+	for (i = 0; i < used_atom_cnt; i++) {
+		struct atom_value *v = &val[i];
+		if (v->s == NULL)
+			v->s = "";
+	}
+}
+
+/*
+ * val is a list of atom_value to hold returned values.  Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+	grab_common_values(val, deref, obj, buf, sz);
+	switch (obj->type) {
+	case OBJ_TAG:
+		grab_tag_values(val, deref, obj, buf, sz);
+		grab_sub_body_contents(val, deref, obj, buf, sz);
+		grab_person("tagger", val, deref, obj, buf, sz);
+		break;
+	case OBJ_COMMIT:
+		grab_commit_values(val, deref, obj, buf, sz);
+		grab_sub_body_contents(val, deref, obj, buf, sz);
+		grab_person("author", val, deref, obj, buf, sz);
+		grab_person("committer", val, deref, obj, buf, sz);
+		break;
+	case OBJ_TREE:
+		// grab_tree_values(val, deref, obj, buf, sz);
+		break;
+	case OBJ_BLOB:
+		// grab_blob_values(val, deref, obj, buf, sz);
+		break;
+	default:
+		die("Eh?  Object of type %d?", obj->type);
+	}
+}
+
+/*
+ * Parse the object referred by ref, and grab needed value.
+ */
+static void populate_value(struct refinfo *ref)
+{
+	void *buf;
+	struct object *obj;
+	int eaten, i;
+	unsigned long size;
+	const unsigned char *tagged;
+
+	ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+
+	buf = get_obj(ref->objectname, &obj, &size, &eaten);
+	if (!buf)
+		die("missing object %s for %s",
+		    sha1_to_hex(ref->objectname), ref->refname);
+	if (!obj)
+		die("parse_object_buffer failed on %s for %s",
+		    sha1_to_hex(ref->objectname), ref->refname);
+
+	/* Fill in specials first */
+	for (i = 0; i < used_atom_cnt; i++) {
+		const char *name = used_atom[i];
+		struct atom_value *v = &ref->value[i];
+		if (!strcmp(name, "refname"))
+			v->s = ref->refname;
+		else if (!strcmp(name, "*refname")) {
+			int len = strlen(ref->refname);
+			char *s = xmalloc(len + 4);
+			sprintf(s, "%s^{}", ref->refname);
+			v->s = s;
+		}
+	}
+
+	grab_values(ref->value, 0, obj, buf, size);
+	if (!eaten)
+		free(buf);
+
+	/* If there is no atom that wants to know about tagged
+	 * object, we are done.
+	 */
+	if (!need_tagged || (obj->type != OBJ_TAG))
+		return;
+
+	/* If it is a tag object, see if we use a value that derefs
+	 * the object, and if we do grab the object it refers to.
+	 */
+	tagged = ((struct tag *)obj)->tagged->sha1;
+
+	/* NEEDSWORK: This derefs tag only once, which
+	 * is good to deal with chains of trust, but
+	 * is not consistent with what deref_tag() does
+	 * which peels the onion to the core.
+	 */
+	buf = get_obj(tagged, &obj, &size, &eaten);
+	if (!buf)
+		die("missing object %s for %s",
+		    sha1_to_hex(tagged), ref->refname);
+	if (!obj)
+		die("parse_object_buffer failed on %s for %s",
+		    sha1_to_hex(tagged), ref->refname);
+	grab_values(ref->value, 1, obj, buf, size);
+	if (!eaten)
+		free(buf);
+}
+
+/*
+ * Given a ref, return the value for the atom.  This lazily gets value
+ * out of the object by calling populate value.
+ */
+static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
+{
+	if (!ref->value) {
+		populate_value(ref);
+		fill_missing_values(ref->value);
+	}
+	*v = &ref->value[atom];
+}
+
+static struct refinfo **grab_array;
+static const char **grab_pattern;
+static int *grab_cnt;
+
+/*
+ * A call-back given to for_each_ref().  It is unfortunate that we
+ * need to use global variables to pass extra information to this
+ * function.
+ */
+static int grab_single_ref(const char *refname, const unsigned char *sha1)
+{
+	struct refinfo *ref;
+	int cnt;
+
+	if (*grab_pattern) {
+		const char **pattern;
+		int namelen = strlen(refname);
+		for (pattern = grab_pattern; *pattern; pattern++) {
+			const char *p = *pattern;
+			int plen = strlen(p);
+
+			if ((plen <= namelen) &&
+			    !strncmp(refname, p, plen) &&
+			    (refname[plen] == '\0' ||
+			     refname[plen] == '/'))
+				break;
+			if (!fnmatch(p, refname, FNM_PATHNAME))
+				break;
+		}
+		if (!*pattern)
+			return 0;
+	}
+
+	/* We do not open the object yet; sort may only need refname
+	 * to do its job and the resulting list may yet to be pruned
+	 * by maxcount logic.
+	 */
+	ref = xcalloc(1, sizeof(*ref));
+	ref->refname = xstrdup(refname);
+	hashcpy(ref->objectname, sha1);
+
+	cnt = *grab_cnt;
+	grab_array = xrealloc(grab_array, sizeof(*grab_array) * (cnt + 1));
+	grab_array[cnt++] = ref;
+	*grab_cnt = cnt;
+	return 0;
+}
+
+static struct refinfo **grab_refs(const char **pattern, int *cnt)
+{
+	/* Sheesh, we really should make for-each-ref to take
+	 * callback data.
+	 */
+	*cnt = 0;
+	grab_pattern = pattern;
+	grab_cnt = cnt;
+	for_each_ref(grab_single_ref);
+	return grab_array;
+}
+
+static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
+{
+	struct atom_value *va, *vb;
+	int cmp;
+	cmp_type cmp_type = used_atom_type[s->atom];
+
+	get_value(a, s->atom, &va);
+	get_value(b, s->atom, &vb);
+	switch (cmp_type) {
+	case FIELD_STR:
+		cmp = strcmp(va->s, vb->s);
+		break;
+	default:
+		if (va->ul < vb->ul)
+			cmp = -1;
+		else if (va->ul == vb->ul)
+			cmp = 0;
+		else
+			cmp = 1;
+		break;
+	}
+	return (s->reverse) ? -cmp : cmp;
+}
+
+static struct ref_sort *ref_sort;
+static int compare_refs(const void *a_, const void *b_)
+{
+	struct refinfo *a = *((struct refinfo **)a_);
+	struct refinfo *b = *((struct refinfo **)b_);
+	struct ref_sort *s;
+
+	for (s = ref_sort; s; s = s->next) {
+		int cmp = cmp_ref_sort(s, a, b);
+		if (cmp)
+			return cmp;
+	}
+	return 0;
+}
+
+static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
+{
+	ref_sort = sort;
+	qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
+}
+
+static void print_value(struct refinfo *ref, int atom, int quote_style)
+{
+	struct atom_value *v;
+	get_value(ref, atom, &v);
+	switch (quote_style) {
+	case QUOTE_NONE:
+		fputs(v->s, stdout);
+		break;
+	case QUOTE_SHELL:
+		sq_quote_print(stdout, v->s);
+		break;
+	case QUOTE_PERL:
+		perl_quote_print(stdout, v->s);
+		break;
+	case QUOTE_PYTHON:
+		python_quote_print(stdout, v->s);
+		break;
+	}
+}
+
+static void emit(const char *cp, const char *ep)
+{
+	while (*cp && (!ep || cp < ep)) {
+		if (*cp == '%')
+			if (cp[1] == '%')
+				cp++;
+		putchar(*cp);
+		cp++;
+	}
+}
+
+static void show_ref(struct refinfo *info, const char *format, int quote_style)
+{
+	const char *cp, *sp, *ep;
+
+	for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+		ep = strchr(sp, ')');
+		if (cp < sp)
+			emit(cp, sp);
+		print_value(info, parse_atom(sp + 2, ep), quote_style);
+	}
+	if (*cp)
+		fputs(cp, stdout);
+	putchar('\n');
+}
+
+static struct ref_sort *default_sort(void)
+{
+	static const char cstr_name[] = "refname";
+
+	struct ref_sort *sort = xcalloc(1, sizeof(*sort));
+
+	sort->next = NULL;
+	sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
+	return sort;
+}
+
+int cmd_for_each_ref(int ac, const char **av, char *prefix)
+{
+	int i, num_refs;
+	const char *format = NULL;
+	struct ref_sort *sort = NULL, **sort_tail = &sort;
+	int maxcount = 0;
+	int quote_style = -1; /* unspecified yet */
+	struct refinfo **refs;
+
+	for (i = 1; i < ac; i++) {
+		const char *arg = av[i];
+		if (arg[0] != '-')
+			break;
+		if (!strcmp(arg, "--")) {
+			i++;
+			break;
+		}
+		if (!strncmp(arg, "--format=", 9)) {
+			if (format)
+				die("more than one --format?");
+			format = arg + 9;
+			continue;
+		}
+		if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) {
+			if (0 <= quote_style)
+				die("more than one quoting style?");
+			quote_style = QUOTE_SHELL;
+			continue;
+		}
+		if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) {
+			if (0 <= quote_style)
+				die("more than one quoting style?");
+			quote_style = QUOTE_PERL;
+			continue;
+		}
+		if (!strcmp(arg, "--python") ) {
+			if (0 <= quote_style)
+				die("more than one quoting style?");
+			quote_style = QUOTE_PYTHON;
+			continue;
+		}
+		if (!strncmp(arg, "--count=", 8)) {
+			if (maxcount)
+				die("more than one --count?");
+			maxcount = atoi(arg + 8);
+			if (maxcount <= 0)
+				die("The number %s did not parse", arg);
+			continue;
+		}
+		if (!strncmp(arg, "--sort=", 7)) {
+			struct ref_sort *s = xcalloc(1, sizeof(*s));
+			int len;
+
+			s->next = NULL;
+			*sort_tail = s;
+			sort_tail = &s->next;
+
+			arg += 7;
+			if (*arg == '-') {
+				s->reverse = 1;
+				arg++;
+			}
+			len = strlen(arg);
+			sort->atom = parse_atom(arg, arg+len);
+			continue;
+		}
+		break;
+	}
+	if (quote_style < 0)
+		quote_style = QUOTE_NONE;
+
+	if (!sort)
+		sort = default_sort();
+	sort_atom_limit = used_atom_cnt;
+	if (!format)
+		format = "%(objectname) %(objecttype)\t%(refname)";
+
+	verify_format(format);
+
+	refs = grab_refs(av + i, &num_refs);
+
+	for (i = 0; i < used_atom_cnt; i++) {
+		if (used_atom[i][0] == '*') {
+			need_tagged = 1;
+			break;
+		}
+	}
+
+	sort_refs(sort, refs, num_refs);
+
+	if (!maxcount || num_refs < maxcount)
+		maxcount = num_refs;
+	for (i = 0; i < maxcount; i++)
+		show_ref(refs[i], format, quote_style);
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 25431d7..87d3326 100644
--- a/builtin.h
+++ b/builtin.h
@@ -26,6 +26,7 @@ extern int cmd_diff(int argc, const char
 extern int cmd_diff_stages(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
+extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_format_patch(int argc, const char **argv, const char *prefix);
 extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 extern int cmd_grep(int argc, const char **argv, const char *prefix);
@@ -47,8 +48,8 @@ extern int cmd_repo_config(int argc, con
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
-extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
+extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/git.c b/git.c
index 8c182a5..10db27b 100644
--- a/git.c
+++ b/git.c
@@ -231,6 +231,7 @@ static void handle_internal_command(int 
 		{ "diff-stages", cmd_diff_stages, RUN_SETUP },
 		{ "diff-tree", cmd_diff_tree, RUN_SETUP },
 		{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
+		{ "for-each-ref", cmd_for_each_ref, RUN_SETUP },
 		{ "format-patch", cmd_format_patch, RUN_SETUP },
 		{ "get-tar-commit-id", cmd_get_tar_commit_id },
 		{ "grep", cmd_grep, RUN_SETUP },
diff --git a/object.c b/object.c
index 9281300..de244e2 100644
--- a/object.c
+++ b/object.c
@@ -138,42 +138,56 @@ struct object *lookup_unknown_object(con
 	return obj;
 }
 
+struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p)
+{
+	struct object *obj;
+	int eaten = 0;
+
+	if (!strcmp(type, blob_type)) {
+		struct blob *blob = lookup_blob(sha1);
+		parse_blob_buffer(blob, buffer, size);
+		obj = &blob->object;
+	} else if (!strcmp(type, tree_type)) {
+		struct tree *tree = lookup_tree(sha1);
+		obj = &tree->object;
+		if (!tree->object.parsed) {
+			parse_tree_buffer(tree, buffer, size);
+			eaten = 1;
+		}
+	} else if (!strcmp(type, commit_type)) {
+		struct commit *commit = lookup_commit(sha1);
+		parse_commit_buffer(commit, buffer, size);
+		if (!commit->buffer) {
+			commit->buffer = buffer;
+			eaten = 1;
+		}
+		obj = &commit->object;
+	} else if (!strcmp(type, tag_type)) {
+		struct tag *tag = lookup_tag(sha1);
+		parse_tag_buffer(tag, buffer, size);
+		obj = &tag->object;
+	} else {
+		obj = NULL;
+	}
+	*eaten_p = eaten;
+	return obj;
+}
+
 struct object *parse_object(const unsigned char *sha1)
 {
 	unsigned long size;
 	char type[20];
+	int eaten;
 	void *buffer = read_sha1_file(sha1, type, &size);
+
 	if (buffer) {
 		struct object *obj;
 		if (check_sha1_signature(sha1, buffer, size, type) < 0)
 			printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
-		if (!strcmp(type, blob_type)) {
-			struct blob *blob = lookup_blob(sha1);
-			parse_blob_buffer(blob, buffer, size);
-			obj = &blob->object;
-		} else if (!strcmp(type, tree_type)) {
-			struct tree *tree = lookup_tree(sha1);
-			obj = &tree->object;
-			if (!tree->object.parsed) {
-				parse_tree_buffer(tree, buffer, size);
-				buffer = NULL;
-			}
-		} else if (!strcmp(type, commit_type)) {
-			struct commit *commit = lookup_commit(sha1);
-			parse_commit_buffer(commit, buffer, size);
-			if (!commit->buffer) {
-				commit->buffer = buffer;
-				buffer = NULL;
-			}
-			obj = &commit->object;
-		} else if (!strcmp(type, tag_type)) {
-			struct tag *tag = lookup_tag(sha1);
-			parse_tag_buffer(tag, buffer, size);
-			obj = &tag->object;
-		} else {
-			obj = NULL;
-		}
-		free(buffer);
+
+		obj = parse_object_buffer(sha1, type, size, buffer, &eaten);
+		if (!eaten)
+			free(buffer);
 		return obj;
 	}
 	return NULL;
diff --git a/object.h b/object.h
index 3d4ff46..caee733 100644
--- a/object.h
+++ b/object.h
@@ -59,6 +59,12 @@ void created_object(const unsigned char 
 /** Returns the object, having parsed it to find out what it is. **/
 struct object *parse_object(const unsigned char *sha1);
 
+/* Given the result of read_sha1_file(), returns the object after
+ * parsing it.  eaten_p indicates if the object has a borrowed copy
+ * of buffer and the caller should not free() it.
+ */
+struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p);
+
 /** Returns the object, with potentially excess memory allocated. **/
 struct object *lookup_unknown_object(const unsigned  char *sha1);
 
diff --git a/quote.c b/quote.c
index e3a4d4a..ee7d62c 100644
--- a/quote.c
+++ b/quote.c
@@ -349,3 +349,41 @@ void write_name_quoted(const char *prefi
 	else
 		goto no_quote;
 }
+
+/* quoting as a string literal for other languages */
+
+void perl_quote_print(FILE *stream, const char *src)
+{
+	const char sq = '\'';
+	const char bq = '\\';
+	char c;
+
+	fputc(sq, stream);
+	while ((c = *src++)) {
+		if (c == sq || c == bq)
+			fputc(bq, stream);
+		fputc(c, stream);
+	}
+	fputc(sq, stream);
+}
+
+void python_quote_print(FILE *stream, const char *src)
+{
+	const char sq = '\'';
+	const char bq = '\\';
+	const char nl = '\n';
+	char c;
+
+	fputc(sq, stream);
+	while ((c = *src++)) {
+		if (c == nl) {
+			fputc(bq, stream);
+			fputc('n', stream);
+			continue;
+		}
+		if (c == sq || c == bq)
+			fputc(bq, stream);
+		fputc(c, stream);
+	}
+	fputc(sq, stream);
+}
diff --git a/quote.h b/quote.h
index 1a29e79..b55e699 100644
--- a/quote.h
+++ b/quote.h
@@ -52,4 +52,8 @@ extern char *unquote_c_style(const char 
 extern void write_name_quoted(const char *prefix, int prefix_len,
 			      const char *name, int quote, FILE *out);
 
+/* quoting as a string literal for other languages */
+extern void perl_quote_print(FILE *stream, const char *src);
+extern void python_quote_print(FILE *stream, const char *src);
+
 #endif
-- 
1.4.2.1.gcd6f1

^ permalink raw reply related

* Re: bare repositories: packing and fetching
From: Junio C Hamano @ 2006-09-15 20:44 UTC (permalink / raw)
  To: Andy Whitcroft; +Cc: git
In-Reply-To: <450AEB37.3090909@shadowen.org>

Andy Whitcroft <apw@shadowen.org> writes:

> I was trying to make a bare repo to stage linus' main tree.  As this was
> only to be a local tree for others to pull from I thought that I could
> clone his tree 'bare' and then fetch into that on a regular basis.  That
> does not appear to be the case?  Both git fetch and git repack say 'not
> a git repository .git' and bail.
>
> Is this expected behaviour.  Do I have to have all the checked out files?

cd $that_repository && GIT_DIR=. git fetch

^ permalink raw reply

* Re: bare repositories: packing and fetching
From: Johannes Schindelin @ 2006-09-15 18:50 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <eeeqbv$8na$1@sea.gmane.org>

Hi,

On Fri, 15 Sep 2006, Jakub Narebski wrote:

> Andy Whitcroft wrote:
> 
> > I was trying to make a bare repo to stage linus' main tree.  As this was
> > only to be a local tree for others to pull from I thought that I could
> > clone his tree 'bare' and then fetch into that on a regular basis.  That
> > does not appear to be the case?  Both git fetch and git repack say 'not
> > a git repository .git' and bail.
> > 
> > Is this expected behaviour.  Do I have to have all the checked out files?
> 
> No, but perhaps git doesn't detect _where_ is a git repository. With full
> repository git tries to find .git somewhere in the directory, or it's
> parents, if I understand correctly.

Exactly. Since there is no .git directory, git does not know where it 
should look for the repository.

Try "git --bare fetch".

Hth,
Dscho

^ permalink raw reply

* Add "git show-ref" builtin command
From: Linus Torvalds @ 2006-09-15 18:19 UTC (permalink / raw)
  To: Junio C Hamano, Git Mailing List


It's kind of like "git peek-remote", but works only locally (and thus 
avoids the whole overhead of git_connect()) and has some extra 
verification features.

For example, it allows you to filter the results, and to choose whether 
you want the tag dereferencing or not. You can also use it to just test 
whether a particular ref exists.

For example:

	git show-ref master

will show all references called "master", whether tags or heads or 
anything else, and regardless of how deep in the reference naming 
hierarchy they are (so it would show "refs/heads/master" but also 
"refs/remote/other-repo/master").

When using the "--verify" flag, the command requires an exact ref path:

	git show-ref --verify refs/heads/master

will only match the exact branch called "master".

If nothing matches, show-ref will return an error code of 1, and in the 
case of verification, it will show an error message.

For scripting, you can ask it to be quiet with the "--quiet" flag, which 
allows you to do things like

	git-show-ref --quiet --verify -- "refs/heads/$headname" ||
		echo "$headname is not a valid branch"

to check whether a particular branch exists or not (notice how we don't 
actually want to show any results, and we want to use the full refname for 
it in order to not trigger the problem with ambiguous partial matches).

To show only tags, or only proper branch heads, use "--tags" and/or 
"--heads" respectively (using both means that it shows tags _and_ heads, 
but not other random references under the refs/ subdirectory).

To do automatic tag object dereferencing, use the "-d" or "--dereference" 
flag, so you can do

	git show-ref --tags --dereference

to get a listing of all tags together with what they dereference.

Signed-off-by: Linus Torvalds <torvalds@osdl.org>
---

NOTE! This is against the current master branch, and does _not_ depend on 
my other ref-work. In fact, I'd argue that this is much better to merge 
first and independently of the packed references branch, because it allows 
us to much more easily update the scripts etc to be totally unaware of how 
the refs are actually stored.

I've given it some basic testing, and it all seems obvious enough.

diff --git a/Makefile b/Makefile
index 7b3114f..c365138 100644
--- a/Makefile
+++ b/Makefile
@@ -295,7 +295,8 @@ BUILTIN_OBJS = \
 	builtin-upload-tar.o \
 	builtin-verify-pack.o \
 	builtin-write-tree.o \
-	builtin-zip-tree.o
+	builtin-zip-tree.o \
+	builtin-show-ref.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
new file mode 100644
index 0000000..161b236
--- /dev/null
+++ b/builtin-show-ref.c
@@ -0,0 +1,112 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+
+static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--deref] [--tags] [--heads] [--] [pattern*]";
+
+static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, found_match = 0, verify = 0, quiet = 0;
+static const char **pattern;
+
+static int show_ref(const char *refname, const unsigned char *sha1)
+{
+	struct object *obj;
+
+	if (tags_only || heads_only) {
+		int match;
+
+		match = heads_only && !strncmp(refname, "refs/heads/", 11);
+		match |= tags_only && !strncmp(refname, "refs/tags/", 10);
+		if (!match)
+			return 0;
+	}
+	if (pattern) {
+		int reflen = strlen(refname);
+		const char **p = pattern, *m;
+		while ((m = *p++) != NULL) {
+			int len = strlen(m);
+			if (len > reflen)
+				continue;
+			if (memcmp(m, refname + reflen - len, len))
+				continue;
+			if (len == reflen)
+				goto match;
+			/* "--verify" requires an exact match */
+			if (verify)
+				continue;
+			if (refname[reflen - len - 1] == '/')
+				goto match;
+		}
+		return 0;
+	}
+
+match:
+	found_match++;
+	obj = parse_object(sha1);
+	if (!obj) {
+		if (quiet)
+			return 0;
+		die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1));
+	}
+	if (quiet)
+		return 0;
+	printf("%s %s\n", sha1_to_hex(sha1), refname);
+	if (deref_tags && obj->type == OBJ_TAG) {
+		obj = deref_tag(obj, refname, 0);
+		printf("%s %s^{}\n", sha1_to_hex(obj->sha1), refname);
+	}
+	return 0;
+}
+
+int cmd_show_ref(int argc, const char **argv, const char *prefix)
+{
+	int i;
+
+	for (i = 1; i < argc; i++) {
+		const char *arg = argv[i];
+		if (*arg != '-') {
+			pattern = argv + i;
+			break;
+		}
+		if (!strcmp(arg, "--")) {
+			pattern = argv + i + 1;
+			if (!*pattern)
+				pattern = NULL;
+			break;
+		}
+		if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
+			quiet = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-h") || !strcmp(arg, "--head")) {
+			show_head = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-d") || !strcmp(arg, "--dereference")) {
+			deref_tags = 1;
+			continue;
+		}
+		if (!strcmp(arg, "--verify")) {
+			verify = 1;
+			continue;
+		}
+		if (!strcmp(arg, "--tags")) {
+			tags_only = 1;
+			continue;
+		}
+		if (!strcmp(arg, "--heads")) {
+			heads_only = 1;
+			continue;
+		}
+		usage(show_ref_usage);
+	}
+	if (show_head)
+		head_ref(show_ref);
+	for_each_ref(show_ref);
+	if (!found_match) {
+		if (verify && !quiet)
+			die("No match");
+		return 1;
+	}
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 25431d7..bca5f79 100644
--- a/builtin.h
+++ b/builtin.h
@@ -61,5 +61,6 @@ extern int cmd_version(int argc, const c
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
 extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
+extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
 
 #endif
diff --git a/git.c b/git.c
index 8c182a5..fedd536 100644
--- a/git.c
+++ b/git.c
@@ -266,6 +266,7 @@ static void handle_internal_command(int 
 		{ "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
 		{ "write-tree", cmd_write_tree, RUN_SETUP },
 		{ "verify-pack", cmd_verify_pack },
+		{ "show-ref", cmd_show_ref, RUN_SETUP },
 	};
 	int i;
 

^ permalink raw reply related

* Re: Notes on supporting Git operations in/on partial Working Directories
From: A Large Angry SCM @ 2006-09-15 18:15 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7v8xkl26kb.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano wrote:
...
> 
> Having said that, I do not necessarily agree that highly modular
> projects would want to put everything in one git repository and
> track everything as a whole unit.

And yet that's exactly how a lot of developers use CVS. You can argue 
that some other way is better but when they move from CVS they're 
looking for continuity of productivity which often means not radically 
changing how they work. At least in the short term.

> The primary audience of git, the kernel project, is reasonably
> modular (although Andrew seems to be suffering from subsystem

I no longer believe that the Linux kernel developers are the "primary 
audience". They are certainly an important and influential set of Git 
users but there are also a lot of non kernel projects using Git. If not 
now, there will soon be more non kernel Git users than kernel Git users.

[Nice description of how to work with the Linux kernel code base.]

[Nice description of one way a hypothetical project with dependencies on 
libraries under active development could work.]

> I think what truly huge but highly modular projects need is a
> good support to lay-out check-outs from multiple subprojects,
> each of which is managed in its own repository but has loose
> (looser than the level of individual commits) version
> dependency.  That would need to solve three issues: (1) the
> right versions from many repositories need to be checked out in
> correct locations for a build, (2) after building and testing to
> make sure they work together as a whole, these specific versions
> from the subcomponent repositories need to be tagged to mark a
> release, and (3) maybe a single large tarball that contains all
> subprojects' checkout can be made easily.
 >
> So the issue may not be partial repository support, but support
> for managing multiple projects.

There's no question that that may be better for some projects. But I 
believe that the project members (or owners) should decide how they use 
their tools.

^ permalink raw reply

* Re: bare repositories: packing and fetching
From: Jakub Narebski @ 2006-09-15 18:11 UTC (permalink / raw)
  To: git
In-Reply-To: <450AEB37.3090909@shadowen.org>

Andy Whitcroft wrote:

> I was trying to make a bare repo to stage linus' main tree.  As this was
> only to be a local tree for others to pull from I thought that I could
> clone his tree 'bare' and then fetch into that on a regular basis.  That
> does not appear to be the case?  Both git fetch and git repack say 'not
> a git repository .git' and bail.
> 
> Is this expected behaviour.  Do I have to have all the checked out files?

No, but perhaps git doesn't detect _where_ is a git repository. With full
repository git tries to find .git somewhere in the directory, or it's
parents, if I understand correctly.

If running git commands from the inside of bare repository doesn't work, try
setting enviromental variable GIT_DIR (e.g. GIT_DIR=git.git git fetch
origin), or with git 1.4.2 or newer with --git-dir option (e.g. git
--git-dir=git fetch origin).
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply

* bare repositories: packing and fetching
From: Andy Whitcroft @ 2006-09-15 18:04 UTC (permalink / raw)
  To: Git Mailing List

I was trying to make a bare repo to stage linus' main tree.  As this was
only to be a local tree for others to pull from I thought that I could
clone his tree 'bare' and then fetch into that on a regular basis.  That
does not appear to be the case?  Both git fetch and git repack say 'not
a git repository .git' and bail.

Is this expected behaviour.  Do I have to have all the checked out files?

-apw

^ permalink raw reply

* [PATCH 2/3 (take 2)] gitweb: Allow for href() to be used for links without project param
From: Jakub Narebski @ 2006-09-15 17:30 UTC (permalink / raw)
  To: git; +Cc: Junio Hamano
In-Reply-To: <200609150453.42231.jnareb@gmail.com>

Make it possible use href() subroutine to generate link with
query string which does not include project ('p') parameter.
href() used to add project=$project to its parameters, if it
was not set (to be more exact if $params{'project'} was false).
Now you can pass "project => undef" if you don't want for href()
to add project parameter to query string in the generated link.

Links to "project_list", "project_index" and "opml" (all related
to list of all projects/all git repositories) doesn't need project
parameter. Moreover "project_list" is default view (action) if
project ('p') parameter is not set, just like "summary" is default
view (action) if project is set; project list served as a kind
of "home" page for gitweb instalation, and links to "project_list"
view were done without specyfying it as an action.

Convert remaining links (except $home_link and anchor links)
to use href(); this required adding 'order => "o"' to @mapping
in href(). This finishes consolidation of URL generation.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
Only commit message has changed.

 gitweb/gitweb.perl |   17 ++++++++++-------
 1 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 7dbcb88..e900713 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -326,11 +326,12 @@ sub href(%) {
 		hash_base => "hb",
 		hash_parent_base => "hpb",
 		page => "pg",
+		order => "o",
 		searchtext => "s",
 	);
 	my %mapping = @mapping;
 
-	$params{"project"} ||= $project;
+	$params{'project'} = $project unless exists $params{'project'};
 
 	my @result = ();
 	for (my $i = 0; $i < @mapping; $i += 2) {
@@ -1304,9 +1305,11 @@ sub git_footer_html {
 		if (defined $descr) {
 			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
 		}
-		print $cgi->a({-href => href(action=>"rss"), -class => "rss_logo"}, "RSS") . "\n";
+		print $cgi->a({-href => href(action=>"rss"),
+		              -class => "rss_logo"}, "RSS") . "\n";
 	} else {
-		print $cgi->a({-href => href(action=>"opml"), -class => "rss_logo"}, "OPML") . "\n";
+		print $cgi->a({-href => href(project=>undef, action=>"opml"),
+		              -class => "rss_logo"}, "OPML") . "\n";
 	}
 	print "</div>\n" .
 	      "</body>\n" .
@@ -2153,7 +2156,7 @@ sub git_project_list {
 		print "<th>Project</th>\n";
 	} else {
 		print "<th>" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("o=project"),
+		      $cgi->a({-href => href(project=>undef, order=>'project'),
 		               -class => "header"}, "Project") .
 		      "</th>\n";
 	}
@@ -2162,7 +2165,7 @@ sub git_project_list {
 		print "<th>Description</th>\n";
 	} else {
 		print "<th>" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("o=descr"),
+		      $cgi->a({-href => href(project=>undef, order=>'descr'),
 		               -class => "header"}, "Description") .
 		      "</th>\n";
 	}
@@ -2171,7 +2174,7 @@ sub git_project_list {
 		print "<th>Owner</th>\n";
 	} else {
 		print "<th>" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("o=owner"),
+		      $cgi->a({-href => href(project=>undef, order=>'owner'),
 		               -class => "header"}, "Owner") .
 		      "</th>\n";
 	}
@@ -2180,7 +2183,7 @@ sub git_project_list {
 		print "<th>Last Change</th>\n";
 	} else {
 		print "<th>" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("o=age"),
+		      $cgi->a({-href => href(project=>undef, order=>'age'),
 		               -class => "header"}, "Last Change") .
 		      "</th>\n";
 	}
-- 
1.4.2

^ permalink raw reply related

* stg pop/push -a nitpick: not handling deleted files
From: Auke Kok @ 2006-09-15 17:19 UTC (permalink / raw)
  To: git


Hi,

minor nitpick when working with a patch that deletes a file: stg does not 
handle the 'abandoned' file when popping all patches off - essentially it would 
have to put the file back over an abandoned file.

I'm not sure what the resolution of this would be, but this problem could very 
well be anticipated (after all I just pushed/imported a patch removing the file 
to the stack).

Cheers,

Auke



$ stg pop -a
Popping "all_git_versions.patch" - "all_copyright_update.patch" 
patches...fatal: Untracked working tree file 'drivers/net/e1000/LICENSE' would 
be overwritten by merge.
stg pop: git-read-tree failed (local changes maybe?)

^ permalink raw reply

* Re: Historical kernel repository size
From: Nicolas Pitre @ 2006-09-15 16:45 UTC (permalink / raw)
  To: Olivier Galibert
  Cc: Thomas Gleixner, Linus Torvalds, Petr Baudis, Git Mailing List
In-Reply-To: <20060915090305.GC75256@dspnet.fr.eu.org>

On Fri, 15 Sep 2006, Olivier Galibert wrote:

> On Thu, Sep 14, 2006 at 09:19:04PM -0400, Nicolas Pitre wrote:
> > Erm... Both incantations work fine fine here.
> > 
> > > -rw-rw-r-- 1 tglx ftpadmin  13600376 Sep 14 22:16 pack-4d27038611fe7755938efd4a2745d5d5d35de1c1.idx
> > > -rw-rw-r-- 1 tglx ftpadmin 158679705 Sep 14 22:16 pack-4d27038611fe7755938efd4a2745d5d5d35de1c1.pack
> > 
> > And I get the same result as well.
> 
> For the curious, a 100/100 parameter gives a size of 154261771.

Right.   And then the runtime cost of extracting objects out of such a 
pack increases due to the deeper delta chain.

The average runtime cost is probably linear with the delta depth, 
something like f(x) = a*x + k.

But the size reduction follows f(x) = a/x + k.

So to say that infinite delta length does not provide infinite packing 
size reduction. Anything larger than 50 is probably not worth the 
small reduction gain.


Nicolas

^ permalink raw reply

* Re: [PATCH] Build on Debian GNU/Hurd
From: Martin Waitz @ 2006-09-15 15:47 UTC (permalink / raw)
  To: git
In-Reply-To: <20060915125846.10469.qmail@ccc765dd61a02d.315fe32.mid.smarden.org>

[-- Attachment #1: Type: text/plain, Size: 316 bytes --]

hoi :)

On Fri, Sep 15, 2006 at 12:58:46PM +0000, Gerrit Pape wrote:
> +ifeq ($(uname_S),GNU)
> +       # GNU stands for GNU/Hurd
> +       NO_STRLCPY = YesPlease
> +       ALL_CFLAGS += -DPATH_MAX=4096
> +endif

I guess this arbitrary limit will upset all the Hurd enthusiasts... ;-)

-- 
Martin Waitz

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* Re: stgit: cannot push a patch - Python trace dump
From: Catalin Marinas @ 2006-09-15 14:33 UTC (permalink / raw)
  To: Auke Kok; +Cc: git
In-Reply-To: <450AB81F.2090305@intel.com>

On 15/09/06, Auke Kok <auke-jan.h.kok@intel.com> wrote:
> Catalin Marinas wrote:
> > Thanks, I fixed StGIT to cope with this and I'll update the repository
> > and snapshot tonight (which, BTW, will contain the initial support for
> > patch history tracking).
>
> awesome and awesome, allthough I have no idea what that (patch history
> tracking) is about :)

It keeps the history of all the patch modifications (like refresh,
push) so that you can get previous versions if something went wrong.
It's like having many small commits reflecting the changes to a patch.
These commits are totally separate from the main patch commit (so that
an StGIT repository is still compatible with GIT) but you can access
them with "stg log [--graphical] <patch>". The --graphical option will
automatically invoke gitk.

-- 
Catalin

^ permalink raw reply

* Re: stgit: cannot push a patch - Python trace dump
From: Auke Kok @ 2006-09-15 14:26 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <tnxwt855wvr.fsf@arm.com>

Catalin Marinas wrote:
> Auke Kok <auke-jan.h.kok@intel.com> wrote:
>> I'm preparing patches for upstream and am haunted by an apparent
>> patch breaking stg. The problem appears to be one or more of my
>> patches breaking a push:
> [...]
>> I've been abusing stg by leaving the commit messages empty so I
>> assume that that's the cause here, or related to the problem.
> 
> Thanks, I fixed StGIT to cope with this and I'll update the repository
> and snapshot tonight (which, BTW, will contain the initial support for
> patch history tracking).

awesome and awesome, allthough I have no idea what that (patch history 
tracking) is about :)

Auke

^ permalink raw reply

* cg-commit -p -m ignores -p
From: Gerrit Pape @ 2006-09-15 13:23 UTC (permalink / raw)
  To: git, 387515

Hi, please see http:/bugs.debian.org/387515 or below.  I agree that
ideally cg-commit should behave differently.

Thanks, Gerrit.

----- Forwarded message from Andrew Suffield <asuffield@suffields.me.uk> -----

cg-commit -p -m ignores the -p argument and silently commits. Yes,
this is documented, but it's still stupid behaviour; if the user had
meant that, they would have just used -m alone.

At the very least, it should abort with an error (on the basis that
the command makes no sense); more sensibly, it should create a log
message with the given string and then spawn an editor (and then since
the user will probably exit without changing the file, the "Abort or
commit?" message would appear as per usual, which seems to me to be
appropriate behaviour).


----- End forwarded message -----

^ permalink raw reply

* git-apply fails to apply some patches
From: Gerrit Pape @ 2006-09-15 13:22 UTC (permalink / raw)
  To: git, 386495

Hi, please see http://bugs.debian.org/386495 or below.  I can reproduce
the problem with 1.4.2.1.

Thanks, Gerrit.

----- Forwarded message from Matthew Wilcox <matthew@wil.cx> -----

Trying to apply a patch that was created with interdiff fails ...
Here's the first patch (which works)

--- pciutils-2.1.11.orig/debian/dirs
+++ pciutils-2.1.11/debian/dirs
@@ -0,0 +1,6 @@
+usr/share/misc
+usr/share/man
+usr/bin
+usr/lib
+usr/share/lintian/overrides
+bin

On top of that, I try to apply this interdiff generated patch:

diff -u pciutils-2.1.11/debian/dirs pciutils-2.1.11/debian/dirs
--- pciutils-2.1.11/debian/dirs
+++ pciutils-2.1.11/debian/dirs
@@ -6,0 +7 @@
+var/lib/pciutils

and git-apply says:

error: debian/dirs: already exists in working directory

I suspect it's confused by the '-x,0' thinking that means "file does not
exist" rather than "we have 0 context for this diff".


----- End forwarded message -----

^ 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