Skip to contentSkip to conversation

Git::Repo API and gitweb caching

PATCH In review Lea Wiemann 7 participants 63 messages Jun 15, 2016 Kbuild

lore
Share & tools
lore
#0 lore raw

cover Git::Repo API and gitweb caching

Hi everyone,

As follow-ups to this message, I'll be sending three patches for

1) adding the Mechanize tests,
2) adding the Git::Repo API, and (the important part:)
3) making gitweb use the Git::Repo API, and adding caching to gitweb.

The patches apply on master or next: they're viewable live in action, with cache statistics temporarily enabled at the bottom of each page,
at: http://odin3.kernel.org/git-lewiemann/

Patch (3) basically makes two large changes in one patch, but it was pretty hard to separate them during development. I could try to split them up after the fact, but it would take at least an hour or two, since the changes that introduce caching are spread all over the code. I don't think that having separate commits ([a] use Git::Repo API, [b] add caching) brings enough benefit to justify the effort.

There are some other changes in (3) as well, but they fell out as part of the refactoring, so I didn't separate them either -- same thing.

The Mechanize tests succeed before and after patch (3) is applied, so I'm reasonably confident that my refactoring didn't introduce any (major) bugs.

And since you all are curious about the API thing :-), I've added some notes about why I didn't use Git.pm in the patch message of patch (2).

To all reviewers: Since the patches are quite long, I suggest that for anything but major changes that require either discussion or work on my end, you simply send a patch that applies on top of my patches. (Just sending a patch with a bunch of trivial/small changes without comment should be fine; the reasons for simple improvements are normally obvious.)

On my to-do list:

- Benchmarks. I'm planning to time a replay of kernel.org's gitweb logs on the test server, with and without caching. Nothing fancy. (The performance of the test setup on odin3.kernel.org is not representative of gitweb's actual performance under load.)

- Implement support for Last-Modified or ETags, since those basically fall out for free with the current implementation. (This will require mod_perl, since CGI doesn't allow for accessing arbitrary request headers AFAIK.) That will make the site a tad more responsive, I hope, and it will also hugely reduce the load for RSS/Atom requests, which currently make up almost half of all requests to kernel.org's gitweb and get served in full each time (i.e. "200 OK" instead of "304 Not Modified").

- Make gitweb use more parts of the Git::Repo API; in particular, the commit and tag parsing code should be ripped out, and gitweb should use the (much prettier) Git::Commit/Git::Tag API instead. Perhaps some more functions (like ls_tree) can be generalized into the API as well; I went the easy route for now and simply replaced most "open '-|'" calls with $repo->cmd_output calls.

-- Lea

#1 lore raw

PATCH v9 1/3 gitweb: add test suite with Test::WWW::Mechanize::CGI

This test uses Test::WWW::Mechanize::CGI to check gitweb's output. It also uses HTML::Lint, XML::Parser, and Archive::Tar (if present, each) to validate the HTML/XML/tgz output, and checks all links on the tested pages if --long-tests is given.

---
Last version is v8, in another thread at
<http://mid.gmane.org/[email protected]>;
major changes since v8:

- Test tag and blobdiff views.

- Test pickaxe and grep search functions.

 Makefile                    |    1 +
 t/t9503-gitweb-Mechanize.sh |  144 ++++++++++
 t/t9503/test.pl             |  607 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 752 insertions(+), 0 deletions(-)
 create mode 100755 t/t9503-gitweb-Mechanize.sh
 create mode 100755 t/t9503/test.pl
Makefile +1 -0
index e105d0d..15f210e 100644
@@ -1248,6 +1248,7 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 
 GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
 	@echo SHELL_PATH=\''$(SHELL_PATH_SQ)'\' >$@
+	@echo PERL_PATH=\''$(PERL_PATH_SQ)'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
t/t9503-gitweb-Mechanize.sh new file +144 -0

144 lines not on this page · raw message

t/t9503/test.pl new file +607 -0

607 lines not on this page · raw message

Signature · 1 line
1.5.6.2.456.g63fc0
#2 lore raw

PATCH 2/3 add new Git::Repo API

In reply to Lea Wiemann

This also adds the Git::Commit and Git::Tag classes, which are used by Git::Repo, the Git::Object base class, and the Git::RepoRoot helper factory class.
---
Here's some elaboration on why I didn't use or extend Git.pm.

Please note before starting a reply to this: This is not an argument; I'm just explaining why I implemented it the way I did. So please don't try to argue with me about what I should or should have done. I'm not going to refactor Git::Repo to use Git.pm or vice versa; it's really a much more non-trivial task than you might think at first glance.

Anyways, the following bullet points are my reasons for not extending Git.pm:

- Git.pm doesn't do what I want: It's designed to provide access to working copies. Extending it to have more repository-access functions might have resulted in a mess.

Some long-term thoughts on this issue: I don't think Git.pm's design approach is sensible: It tries to do (a) WC access, (b) repo access, and (c) frontend error handling (with sensible error messages). Those things should really be separated; e.g. one could write a Git::WC class that *has* a Git::Repo instance (since every working copy has a repository associated with it); so you can use $wc = Git::WC->new to access the working copy, and $wc->repo to access its repository. Git.pm will obviously have to stay since a few git commands use it, but I'd probably let it die a slow death, and (cleanly!) copy functionality to a Git::WC module (and perhaps a Git::Frontend module) in the long run.

- It would have needed refactoring and more features.

- For example, the Error module should be removed (and normal die and eval should be used instead). At some point when I was trying to debug it through gitweb, Git.pm would error out somewhere and I would randomly get either a blank page (usually) or an error page (rarely). I suspect that this is because of the Error module or some strange interaction with another. (Besides, Error is not in the Perl distribution, so it would be an unnecessary dependency for gitweb; the only one as far as I can see.)

- Also, I needed something like max_exit_code and a custom path to the git binary; adding max_exit_code would have been non-trivial.

Now I'm all in favor of re-using existing code, but refactoring Git.pm would have taken *much* longer than simply writing a new module. I'm working on caching for gitweb, not on implementing the next great Perl API for Git. (And Git::Repo isn't great, FTR.)

- It's buggy and untested. Neither of these is a problem by itself, but the combination is deadly. E.g. I was trying to refactor the 'repository' constructor (to be able to do instantaneous instantiation) and stumbled upon a missing semicolon that rendered the surrounding code syntactically correct but obviously buggy (line 214 on master). Adding a semicolon there seemed to cause other errors, and given that (a) I don't understand what the code does and no test or comment tells me what it should, and (b) it doesn't work (or maybe it half-works?), I lost all my confidence that I could do anything resembling a (behavior-preserving) refactoring on that code.

I might have been able to work with this particular problem, but such a problem (buggy and completely untested code) are indicative that the rest of the code might bear similar surprises.

- It's overly lengthy, and it's a lot of code for not much functionality. Git.pm has 1200 LOC, with a large stack of tiny subroutines to handle pipes, and a complicated error handling system. I can make out ~400 lines of actual Git functionality, plus a 100-lines constructor (all including documentation). The part of Git::Repo that overlaps with Git.pm (in terms of duplicate functionality) seems to be in the range of 150-200 lines, and it's mostly pipe-handling. That's not a whole lot.

- When I decided I didn't want to use Git.pm, it took me 2-3 hours to replicate the functionality in Git.pm that I needed; it would have taken me *much* longer to extend Git.pm to do what I want. Again, I'm not working on the next great Git Perl API.

So where do we go with Git.pm and Git::Repo? I would suggest that they both stay.

Thus we'd have two APIs (both of them obviously incomplete). If there's enough objection to having two competing official APIs, I'll be happy to move Git::Repo to the gitweb directory, and make it a gitweb-specific thing (though it's not gitweb-specific at all in terms of functionality); I really don't care, as my goal is to get caching for gitweb working. Again, if you're bothered by the fact that there's duplicate functionality at all, please don't complain, but send patches to reconcile the APIs; I will not take care of that myself, since it's a very much non-trivial task.

Unrelatedly, should I add copyright notices at the bottom of each perl module so they are displayed in the perldoc/man pages? I'm not generally a fan of such notices, since they tend to establish code ownership, but if it's desired I can add them.

So, here's the Git::Repo (and friends) API, plus tests of course: ;-)

 perl/Git/Commit.pm       |  163 +++++++++++++++++++
 perl/Git/Object.pm       |   81 ++++++++++
 perl/Git/Repo.pm         |  397 ++++++++++++++++++++++++++++++++++++++++++++++
 perl/Git/RepoRoot.pm     |  103 ++++++++++++
 perl/Git/Tag.pm          |  155 ++++++++++++++++++
 perl/Makefile            |    1 +
 perl/Makefile.PL         |   10 +-
 t/t9710-perl-git-repo.sh |   49 ++++++
 t/t9710/test.pl          |  165 +++++++++++++++++++
 9 files changed, 1123 insertions(+), 1 deletions(-)
 create mode 100644 perl/Git/Commit.pm
 create mode 100644 perl/Git/Object.pm
 create mode 100644 perl/Git/Repo.pm
 create mode 100644 perl/Git/RepoRoot.pm
 create mode 100644 perl/Git/Tag.pm
 create mode 100755 t/t9710-perl-git-repo.sh
 create mode 100755 t/t9710/test.pl
perl/Git/Commit.pm new file +163 -0
new file mode 100644
index 0000000..a9bc304
@@ -0,0 +1,163 @@
+=head1 NAME
+
+Git::Commit - Object-oriented interface to Git commit objects.
+
+=cut
+
+use strict;
+use warnings;
+
+
+package Git::Commit;
+
+use base qw(Git::Object);
+
+use constant _MESSAGE => 'M';
+use constant _ENCODING => 'E';
+use constant _TREE => 'T';
+use constant _PARENTS => 'P';
+use constant _AUTHOR => 'A';
+use constant _COMMITTER => 'C';
+
+
+# Keep documentation in one place to save space.
+
+=head1 METHODS
+
+=over
+
+=item $commit = Git::Commit->new($repo, $sha1)
+
+Return a new Git::Commit instance for a commit object with $sha1 in
+repository $repo.
+
+Calls to this method are free, since it does not check whether $sha1
+exists and has the right type.  However, accessing any of the commit
+object's properties will fail if $sha1 is not a valid commit object.
+
+Note that $sha1 must be the SHA1 of a commit object; tag objects are
+not dereferenced.
+
+=item $obj->repo
+
+Return the Git::Repo instance this object was instantiated with.
+
+=item $obj->sha1
+
+Return the SHA1 of this commit object.
+
+=item $commit->tree
+
+Return the tree this commit object refers to.
+
+=item $commit->parents
+
+Return a list of zero or more parent commit objects.  Note that commit
+objects stringify to their respective SHA1s, so you can alternatively
+treat this as a list of SHA1 strings.
+
+=item $commit->authors
+
+Return the author string of this commit object.
+
+=item $commit->committer
+
+Return the committer string of this commit object.
+
+=item $commit->message
+
+Return the undecoded commit message of this commit object.
+
+=item $commit->encoding
+
+Return the encoding header of the commit object.
+
+=back
+
+=cut
+
+
+sub tree {
+	my $self = shift;
+	$self->_load;
+	$self->{_TREE()};
+}
+
+sub parents {
+	my $self = shift;
+	$self->_load;
+	map { ref($self)->new($self->repo, $_) } @{$self->{_PARENTS()}};
+}
+
+sub author {
+	my $self = shift;
+	$self->_load;
+	$self->{_AUTHOR()} or '';
+}
+
+sub committer {
+	my $self = shift;
+	$self->_load;
+	$self->{_COMMITTER()} or '';
+}
+
+sub message {
+	my $self = shift;
+	$self->_load;
+	$self->{_MESSAGE()};
+}
+
+sub encoding {
+	my $self = shift;
+	$self->_load;
+	$self->{_ENCODING()};
+}
+
+# Auxiliary method to load (and parse) the commit object from the
+# repository if it hasn't already been loaded.
+
+sub _load {
+	my($self, $raw_text) = shift;
+	return if defined $self->{_MESSAGE()};  # already loaded
+
+	my $sha1 = $self->sha1;
+	if (!defined $raw_text) {
+		# Retrieve from the repository.
+		(my $type, $raw_text) = $self->repo->cat_file($sha1);
+		die "$sha1 is a $type object (expected a commit object)"
+		    unless $type eq 'commit';
+	}
+
+	(my $header, $self->{_MESSAGE()}) = split "\n\n", $raw_text, 2;
+	# Parse header.
+	for my $line (split "\n", $header) {
+		local $/ = "\n"; # for chomp
+		chomp($line);
+		my($key, $value) = split ' ', $line, 2;
+		if ($key eq 'tree') {
+			$self->{_TREE()} = $value;
+		} elsif ($key eq 'parent') {
+			push @{$self->{_PARENTS()}}, $value;
+		} elsif ($key eq 'author') {
+			$self->{_AUTHOR()} = $value;
+		} elsif ($key eq 'committer') {
+			$self->{_COMMITTER()} = $value;
+		} elsif ($key eq 'encoding') {
+			$self->{_ENCODING()} = $value;
+		} else {
+			die "unrecognized commit header $key";
+		}
+	}
+	undef;
+}
+
+
+=head1 NOTES
+
+You will usually want to call $repo->get_commit($sha1) instead of
+instantiating this class directly; see L<Git::Repo>.
+
+=cut
+
+
+1;
perl/Git/Object.pm new file +81 -0

81 lines not on this page · raw message

perl/Git/Repo.pm new file +397 -0

397 lines not on this page · raw message

perl/Git/RepoRoot.pm new file +103 -0

103 lines not on this page · raw message

perl/Git/Tag.pm new file +155 -0

155 lines not on this page · raw message

perl/Makefile +1 -0
index 5e079ad..a70e78a 100644
@@ -27,6 +27,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	echo install: >> $@
 	echo '	mkdir -p $(instdir_SQ)' >> $@
 	echo '	$(RM) $(instdir_SQ)/Git.pm; cp Git.pm $(instdir_SQ)' >> $@
+	echo '	mkdir -p $(instdir_SQ)/Git; $(RM) $(instdir_SQ)/Git/*.pm; cp Git/*.pm $(instdir_SQ)/Git' >> $@
 	echo '	$(RM) $(instdir_SQ)/Error.pm; \
 	cp private-Error.pm $(instdir_SQ)/Error.pm' >> $@
 	echo instlibdir: >> $@
perl/Makefile.PL +9 -1
index 320253e..6c62160 100644
@@ -8,7 +8,15 @@ instlibdir:
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+# Note that when changing %pm, you'll have to remove pm_to_blib rather
+# than the blib directory to .force the .pm files to be re-installed
+# to blib/lib.
+my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm',
+	  'Git/Repo.pm' => '$(INST_LIBDIR)/Git/Repo.pm',
+	  'Git/RepoRoot.pm' => '$(INST_LIBDIR)/Git/RepoRoot.pm',
+	  'Git/Commit.pm' => '$(INST_LIBDIR)/Git/Commit.pm',
+	  'Git/Tag.pm' => '$(INST_LIBDIR)/Git/Tag.pm',
+	  'Git/Object.pm' => '$(INST_LIBDIR)/Git/Object.pm');
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
t/t9710-perl-git-repo.sh new file +49 -0

49 lines not on this page · raw message

t/t9710/test.pl new file +165 -0

165 lines not on this page · raw message

Signature · 1 line
1.5.6.2.456.g63fc0
#3 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 3 lines · Lea Wiemann

This also adds the Git::Commit and Git::Tag classes, which are used by Git::Repo, the Git::Object base class, and the Git::RepoRoot helper factory class.

I think it would be perhaps better to explain relationship and purpose of each class in more detail, including Git::Repo.

No signoff - is it deliberate, or just omission?

Quoted 10 lines

---
Here's some elaboration on why I didn't use or extend Git.pm.

Please note before starting a reply to this: This is not an argument; I'm just explaining why I implemented it the way I did. So please don't try to argue with me about what I should or should have done. I'm not going to refactor Git::Repo to use Git.pm or vice versa; it's really a much more non-trivial task than you might think at first glance.

Quoted 6 lines

Anyways, the following bullet points are my reasons for not extending Git.pm:

- Git.pm doesn't do what I want: It's designed to provide access to working copies. Extending it to have more repository-access functions might have resulted in a mess.

I don't quite understand. True, Git->repository(...) has a bit strange [optional] parameters, but you should remember that IIRC it predates possibility of workdir / worktree / working copy separation. That I think is why it supports non-bare repositories (default), and bare repositories, not separating working copy from repository.

Also, from what I understand and remember, Git.pm was created to have one place, one single implementation for safe and operating system independent (which means that it works with ActiveState Perl on Windows) "pipe" and "backticks". Everything else is just convenience.

Quoted 3 lines

Some long-term thoughts on this issue: I don't think Git.pm's design approach is sensible: It tries to do (a) WC access, (b) repo access, and (c) frontend error handling (with sensible error messages).

I can see (b) and (c), but I have trouble seeing (a).

Quoted 5 lines

Those things should really be separated; e.g. one could write a Git::WC class that *has* a Git::Repo instance (since every working copy has a repository associated with it); so you can use $wc = Git::WC->new to access the working copy, and $wc->repo to access its repository.

Please remember that the opposite relation is also true. Non-bare repository has working copy / worktree associated with it, either implicitly (.git/.. is working copy), or explicitly (core.worktree).

Also even if worktree and repository are separated, there are a few files in non-bare repository which refer to worktree condition, namely $GIT_DIR/HEAD, $GIT_DIR/logs/HEAD and $GIT_DIR/index.

Quoted 2 lines

Git.pm will obviously have to stay since a few git commands use it,

The following commands use Git.pm: contrib/examples/git-remote.perl, git-add--interactive.perl (helper script), git-cvsexportcommit, git-svn, git-send-email.

The following commands are written in Perl, but *do not* use Git.pm:
git-archimport (which probably should go to contrib, and be replaced by fast-import / fast-export based Bazaar import), git-cvsimport, git-cvsserver, git-relink (rarely used now, I think).

Quoted 3 lines

but I'd probably let it die a slow death, and (cleanly!) copy functionality to a Git::WC module (and perhaps a Git::Frontend module) in the long run.

I'm not so sure if it is a way to go. Most git commands wants to just invoke other git commands safely, either creating pipeline, or reading whole input; sometimes command output is not important.

Non OO things, like ability to write

  print color('reset') . "\n";

is also important.

Quoted 8 lines

- It would have needed refactoring and more features.

- For example, the Error module should be removed (and normal die and eval should be used instead). At some point when I was trying to debug it through gitweb, Git.pm would error out somewhere and I would randomly get either a blank page (usually) or an error page (rarely). I suspect that this is because of the Error module or some strange interaction with another.

I'm not sure if using Error module and syntactic sugar in the form of try { ... } catch <class> with { ... } was a good idea for (c), frontend error handling. Or if it was a good idea (the explanation in
http://www.perl.com/pub/a/2002/11/14/exception.html is compelling) if it was implemented fully, completely, and correctly.

How would you like to catch errors from frontend in Git::Repo and friends?

Quoted 3 lines

[...] (Besides, Error is not in
the Perl distribution, so it would be an unnecessary dependency for gitweb; the only one as far as I can see.)

Git Makefile installs Error module from local copy if one is not available. perl-Git module (which would be unfortunately required to use gitweb; well, that or you could install it locally) has "perl(Error)" as one of dependencies. perl-Error module is a standard module and I think it can be found in any modern Linux distribution; if not, see first sentence in this paragraph.

Quoted 2 lines

- Also, I needed something like max_exit_code and a custom path to the git binary; adding max_exit_code would have been non-trivial.

What is max_exit_code (and why for example you couldn't just create your own derivative of Error)?

Quoted 4 lines

Now I'm all in favor of re-using existing code, but refactoring Git.pm would have taken *much* longer than simply writing a new module. I'm working on caching for gitweb, not on implementing the next great Perl API for Git. (And Git::Repo isn't great, FTR.)

Well, I can understand that.

Besides, having more than one implementation of some new feature was quite common in early stages of feature development. See for example wit and gitweb, git-applymbox and git-am, git-annotate and git-blame.

Quoted 2 lines

- It's buggy and untested. Neither of these is a problem by itself, but the combination is deadly.

Haven't you added t/t9700-perl-git.sh?

Quoted 10 lines

E.g. I was trying to refactor the
'repository' constructor (to be able to do instantaneous instantiation) and stumbled upon a missing semicolon that rendered the surrounding code syntactically correct but obviously buggy (line 214 on master). Adding a semicolon there seemed to cause other errors, and given that (a) I don't understand what the code does and no test or comment tells me what it should, and (b) it doesn't work (or maybe it half-works?), I lost all my confidence that I could do anything resembling a (behavior-preserving) refactoring on that code.

This is not that the code should not be rewritten... but git-blame (and git log -S, aka pickaxe search) can be created to find who is the author of said code, and ask him via email (probably Cc-ing git mailing list).

Quoted 3 lines

I might have been able to work with this particular problem, but such a problem (buggy and completely untested code) are indicative that the rest of the code might bear similar surprises.

I think that the pipe and command code might be tested best, as it is collation and condensation of many different "safe pipe" (etc.) code fragments.

Quoted 8 lines

- It's overly lengthy, and it's a lot of code for not much functionality. Git.pm has 1200 LOC, with a large stack of tiny subroutines to handle pipes, and a complicated error handling system. I can make out ~400 lines of actual Git functionality, plus a 100-lines constructor (all including documentation). The part of Git::Repo that overlaps with Git.pm (in terms of duplicate functionality) seems to be in the range of 150-200 lines, and it's mostly pipe-handling. That's not a whole lot.

Pipe handling is IMHO most important part. Well, other parts such as color(...) are important too, but not for gitweb.

Quoted 8 lines

- When I decided I didn't want to use Git.pm, it took me 2-3 hours to replicate the functionality in Git.pm that I needed; it would have taken me *much* longer to extend Git.pm to do what I want. Again, I'm not working on the next great Git Perl API.

So where do we go with Git.pm and Git::Repo? I would suggest that they both stay.

Quoted 9 lines

Thus we'd have two APIs (both of them obviously incomplete). If there's enough objection to having two competing official APIs, I'll be happy to move Git::Repo to the gitweb directory, and make it a gitweb-specific thing (though it's not gitweb-specific at all in terms of functionality); I really don't care, as my goal is to get caching for gitweb working. Again, if you're bothered by the fact that there's duplicate functionality at all, please don't complain, but send patches to reconcile the APIs; I will not take care of that myself, since it's a very much non-trivial task.

What I worry about is that dependence on Git.pm or Git::Repo would make gitweb installation too hard for some.

Quoted 4 lines

Unrelatedly, should I add copyright notices at the bottom of each perl module so they are displayed in the perldoc/man pages? I'm not generally a fan of such notices, since they tend to establish code ownership, but if it's desired I can add them.

Well, most manpages have information about who made them... which means who was initial author, usually, and/or who is current maintainer.

[Comments on patch itself in separate email, later]

Signature · 2 lines
Jakub Narebski
Poland
#4 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 2 lines · Jakub Narebski

I think it would be perhaps better to explain relationship and purpose of each class in more detail, including Git::Repo.

Noted, will do.

Quoted 4 lines
Quoted 2 lines

[Git.pm] tries to do (a) WC access, (b) repo access, and (c) frontend error handling (with sensible error messages).

I can see (b) and (c), but I have trouble seeing (a).

Well, Git.pm operates on working copies in the constructor (obviously), but also wc_{path,subdir,chdir} and hash_and_insert_object.

Quoted 3 lines
Quoted 1 line

every working copy has a repository associated with it

Please remember that the opposite relation is also true.

True. *nods*

Quoted 4 lines
Quoted 1 line

but I'd probably let [Git.pm] die a slow death

I'm not so sure if it is a way to go. Most git commands wants to just invoke other git commands safely,

Good point. Perhaps the command functionality of Git.pm and Git::Repo could be extracted into something like Git::Cmd.

Quoted 2 lines

Non OO things, like ability to write print color('reset') . "\n"; is also important.

Perhaps, though you might not get around some instantiation to specify the semantics of the color command: Honor color configuration in .gitconfig or .git/config? Honor non-terminal stdout? Honor command line? I suspect that in the end non-OO functions end up being wrappers around OO interfaces that simply specify a set of reasonable defaults.

Quoted 2 lines

I'm not sure if using Error module was a good idea for frontend error handling.

As a general rule, I'd try to not use program exceptions as a means to do frontend error handling, unless you're trying hard to keep the frontend minimalist. Even if you don't care about i18n, different frontends have different needs for their error reporting styles. Also, things like failed SHA1-lookups might be an error to one frontend but not an error to another frontend, so you'd have to implement an exception hierarchy to make fine-granular catching possible.

On top of that, this kind of exception handling doesn't seem very much like typical Perl style.

Quoted 2 lines

How would you like to catch errors from frontend in Git::Repo and friends?

Handle them yourself -- Git::Repo doesn't die unless a fatal (i.e. unexpected) error occurs:

($sha1, $type) = $repo->get_sha1('HEAD:/my/file');
if (! defined $sha1 || $type ne 'blob') { ... handle error ... } $contents = $repo->cat_file($sha1);
... work with contents ...

Also note how there's one well-defined (and known) error point: $sha1 being undefined, or the $type being wrong. The $repo methods *cannot* throw errors unless they're fatal, so you can for instance call cat_file and assume that everything goes right.

Quoted 1 line

What is max_exit_code

It allows you call the git binary without dying if it exits with non-zero status; see the cmd_output documentation for details.

The idea is that a non-zero exit status always indicates an internal (fatal) error, unless you specify that it's OK.

Quoted 4 lines
Quoted 2 lines

- It's buggy and untested. Neither of these is a problem by itself, but the combination is deadly.

Haven't you added t/t9700-perl-git.sh?

Yes (and it alleviated the problem), but I couldn't test the areas where the untestedness actually hits (e.g. the missing semicolon I mentioned).
IOW, t9700 is only testing the parts that are working anyway.

Quoted 2 lines

What I worry about is that dependence on Git.pm or Git::Repo would make gitweb installation too hard for some.

If I'm not mistaken you can always drop the perl/Git directory next to gitweb.cgi. (I'll add that to the installation notes.)

Quoted 5 lines
Quoted 2 lines

Unrelatedly, should I add copyright notices at the bottom of each perl module so they are displayed in the perldoc/man pages?

Well, most manpages have information about who made them... which means who was initial author, usually, and/or who is current maintainer.

I don't really care about being credited as the initial author, and I'm honestly not sure if I'll be able to maintain the modules in the long run.

Should I perhaps add some note along the lines of "Direct questions and patches to git@vger"?

#5 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Lea Wiemann

Thanks to Jakub for a nice writeup too. I will try to cover mainly design issues and do not try to dive too much into technical arguments about current Git.pm, since I agree with the general idea of designing a new API; Jakub covered most of that pretty well, I think.

Maybe I sound too mentoring at places; I tried to tone that down as much as possible, and of course what Lea should be working on is her and hers official mentor call. On the other hand, if something general gets into the Git tree, I'd like to make sure it's something we can live happily with for long time, not just a hack tailored for gitweb caching.

Quoted 3 lines · Lea Wiemann

This also adds the Git::Commit and Git::Tag classes, which are used by Git::Repo, the Git::Object base class, and the Git::RepoRoot helper factory class.

I really miss some more detailed writeup on the envisioned design here. And if we are redoing the API in a better way, we better should have some vision.

Most importantly, how is Git::Repo interacting with working copies, and how is it going to interact with them as the new OO model shapes up? You mention briefly Git::WC later, but it is not really clear how the interaction should work.

First, I don't think it's good idea at all to put the pipe-related stuff to Git::Repo - this is botched up API just like the current one. This all is independent from any repository instances, in fact it's perfectly valid to call standalone remote commands (like ls-remote or, actually, clone).

Here is an idea: Introduce Git::Command object that will have very general interface and look like

	my $c = Git::Command->new(['git', '--git-dir=.', 'cat-file', \
		'-p', 'bla'], {pipe_out=>1})
	...
	$c->close();

and a Git::CommandFactory with a nicer interface that would look like

	my $cf = Git::CommandFactory->new('git', '--git-dir=.');
	my $c = $cf->output_pipe('cat-file', '-p', 'bla');
	$c->close();

Then, Git::Repo would have a single Git::CommandFactory instance pre-initialized with the required calling convention, and returned by e.g. cmd() method. Then, from the user POV, you would just:

	my $repo = Git::Repo->new;
	$repo->cmd->output_pipe('cat-file', '-p', 'bla');

Or am I overdoing it?

Git::Repo considers only bare repositories. Now, since "working copy" by itself has nothing to do with Git and is just an ordinary directory tree, I think Git::WC does not make that much sense, but something like Git::Repo::Nonbare certainly would. This would be a Git::Repo subclass with:

* Different constructor

* Different Git::CommandFactory instance

* Git::Index object aside the existing ones (like Git::Config, Git::RefTree, ...)

* Some kind of wc_root() method to help directory navigation

And that pretty much covers it?

Another thing is clearly describing how error handling is going to work. I have not much against ditching Error.pm, but just saying "die + eval" does not cut it - how about possible sideband data? E.g. the failure mode of Git.pm's command() method includes passing the error'd command output in the exception object. How are we going to handle it? Now, it might be actually okay to say that we _aren't_ going to handle this if it is deemed unuseful, but that needs to be determined too. I don't know off the top of my head.

Quoted 9 lines

---
Here's some elaboration on why I didn't use or extend Git.pm.

Please note before starting a reply to this: This is not an argument; I'm just explaining why I implemented it the way I did. So please don't try to argue with me about what I should or should have done. I'm not going to refactor Git::Repo to use Git.pm or vice versa; it's really a much more non-trivial task than you might think at first glance.

Frankly, I'm not really happy about this attitude. "In my dark corner, I have developed this. Now behold - take it or leave it be; I'm not going to argue."

That said, I do agree that better-structured object API would be beneficial, and I think what you posted here is a good starting point. However, I'm not going to settle with seeing a completely parallel and independent code pop up alongside the existing Git.pm.

Instead, I believe the best course is to gradually translate all the Git.pm functionality to the new OO model, leaving Git.pm as a compatibility wrapper. Now, if you believe this is a non-trivial task, please tell us why.

It should be actually very easy to start with moving all the pipe functionality to Git::Command. This should let us get rid of a lot of technical code, which can be reused in the cleanly rebuilt API. Aside of the object management + error handling, the rest of Git.pm is then actually rather boring and unimportant, especially given Jakub's hopefully upcoming Git::Config.

I will be happy to send in patches factoring out the pipe handling to Git::Command as soon as we agree about the missing design details and the way of error handling. Then, making use of that in Git::Repo should get us a long way ahead.

Quoted 4 lines

Now I'm all in favor of re-using existing code, but refactoring Git.pm would have taken *much* longer than simply writing a new module. I'm working on caching for gitweb, not on implementing the next great Perl API for Git. (And Git::Repo isn't great, FTR.)

Wait, I can't make sense out of this paragraph. If Git.pm sucks, we can work on new API. But we better _make_ it great. Or someone else comes by next year and says "oh, but it's buggy and needs refactoring, let's throw it away and redesign it!"

I agree that your main objective is caching for gitweb, but that's not what everything revolves around for the rest of us. If you chose the way of caching within the Git API and introducing the API to gitweb, I think you should spend the effort to deal with the API properly now.

Quoted 16 lines
perl/Git/Commit.pm new file +10 -0
new file mode 100644
index 0000000..a9bc304
@@ -0,0 +1,163 @@
+=head1 NAME
+
+Git::Commit - Object-oriented interface to Git commit objects.
+
+=cut
+
+use strict;
+use warnings;
+
+

perl/Git/Commit.pm line 20

Quoted 10 lines
perl/Git/Commit.pm new file +10 -0
+package Git::Commit;
+
+use base qw(Git::Object);
+
+use constant _MESSAGE => 'M';
+use constant _ENCODING => 'E';
+use constant _TREE => 'T';
+use constant _PARENTS => 'P';
+use constant _AUTHOR => 'A';
+use constant _COMMITTER => 'C';

What is the idea behind this? It may make sense to create numerical constants like this and use arrays instead of hashes for "structs" in order to gain extra performance. But if you are going to use hashes anyway, why not actually key by sensible name directly?

perl/Git/Commit.pm line 29

Quoted 5 lines
perl/Git/Commit.pm new file +5 -0
+=head1 METHODS
+
+=over
+
+=item $commit = Git::Commit->new($repo, $sha1)

Are we sure we don't want hash-based arguments instead? This is badly extensible and inconsistent with the rest of the API.

perl/Git/Commit.pm line 36

Quoted 6 lines
perl/Git/Commit.pm new file +6 -0
+Return a new Git::Commit instance for a commit object with $sha1 in
+repository $repo.
+
+Calls to this method are free, since it does not check whether $sha1
+exists and has the right type.  However, accessing any of the commit
+object's properties will fail if $sha1 is not a valid commit object.

This is nice idea, but I'd also provide a well-defined way for the user to verify the object's validity at a good moment; basically, make load() a public method. The user can deal with errors then and rely on error-free behavior later.

perl/Git/Commit.pm line 39

Quoted 2 lines
perl/Git/Commit.pm new file +2 -0
+Note that $sha1 must be the SHA1 of a commit object; tag objects are
+not dereferenced.

Why not?

Quoted 9 lines
perl/Git/Commit.pm new file +9 -0
+=item $obj->repo
+
+Return the Git::Repo instance this object was instantiated with.
+
+=item $obj->sha1
+
+Return the SHA1 of this commit object.
+
+=item $commit->tree

perl/Git/Commit.pm line 59

Quoted 10 lines
perl/Git/Commit.pm new file +10 -0
+
+Return the tree this commit object refers to.
+
+=item $commit->parents
+
+Return a list of zero or more parent commit objects.  Note that commit
+objects stringify to their respective SHA1s, so you can alternatively
+treat this as a list of SHA1 strings.
+
+=item $commit->authors

author

Quoted 7 lines
perl/Git/Object.pm new file +1 -0
new file mode 100644
index 0000000..a5126f9
@@ -0,0 +1,81 @@
+=head1 NAME

perl/Git/Object.pm line 11

Quoted 10 lines
perl/Git/Object.pm new file +10 -0
+
+Git::Object - Object-oriented interface to Git objects (base class).
+
+=head1 DESCRIPTION
+
+Git::Object is a base class that provides access to commit, tag and
+(unimplemented) tree objects.  See L<Git::Commit> and L<Git::Tag>.
+
+Objects are loaded lazily, and hence instantiation is free.  Objects
+stringify to their SHA1s.

Maybe use the term 'Git database objects'? This way, it seems as if we are talking about all Git/*.pm objects.

Quoted 6 lines
perl/Git/Object.pm new file +6 -0
+=cut
+
+
+use strict;
+use warnings;
+

perl/Git/Object.pm line 28

Quoted 10 lines
perl/Git/Object.pm new file +10 -0
+
+package Git::Object;
+
+use base qw(Exporter);
+
+our @EXPORT = qw();
+our @EXPORT_OK = qw();
+
+use overload
+    '""' => \&stringify;

(For the beholders: the overload pragma comes from 5.004.)

Quoted 41 lines
perl/Git/Object.pm new file +41 -0
+# Hash indices:
+use constant _REPO => 'R';
+use constant _SHA1 => 'H';
+
+=head1 METHODS
+
+=over
+
+=item Git::Object->new($repo, $sha1)
+
+Return a new Git::Object instance for the object with $sha1 in the
+repository $repo (a Git::Repo instance).
+
+Note that this method does not check whether the object exists in the
+repository.  Trying to accessing its properties through a subclass
+will fail if the object doesn't exist, however.
+
+=cut
+
+sub new {
+	my($class, $repo, $sha1) = @_;
+	die "$repo is not a Git::Repo instance" unless $repo->isa('Git::Repo');
+	my $self = {_REPO() => $repo, _SHA1() => $sha1};
+	return bless $self, $class;
+}
+
+=item $obj->repo
+
+Return the Git::Repo instance this object was instantiated with.
+
+=cut
+
+sub repo {
+	shift->{_REPO()}
+}
+
+=item $obj->sha1
+
+Return the SHA1 of this object.
+
+=cut

perl/Git/Object.pm line 81

Quoted 10 lines
perl/Git/Object.pm new file +10 -0
+
+sub sha1 {
+	shift->{_SHA1()}
+}
+
+sub stringify {
+	shift->{_SHA1()}
+}
+
+1;

Why not overload "" directly to sha1()?

Quoted 46 lines
perl/Git/Repo.pm new file +40 -0
new file mode 100644
index 0000000..5a4c7e0
@@ -0,0 +1,397 @@
+=head1 NAME
+
+Git::Repo - Read-only access to the Git repositories.
+
+WARNING: This module is in active development -- do not use it in any
+production code as the API may change at any time.
+
+=head1 SYNOPSIS
+
+  use Git::Repo;
+
+  my $repo = Git::Repo->new(
+      directory => '/path/to/repository.git',
+      git_binary => '/usr/bin/git');
+  my ($sha1, $type, $size) = $repo->get_sha1('HEAD');
+  print "Last log message:\n\n" . $repo->get_commit($sha1)->message;
+
+=cut
+
+
+use strict;
+use warnings;
+use 5.006002;
+
+
+package Git::Repo;
+
+use Git::Tag;
+use Git::Commit;
+
+use IPC::Open2 qw(open2);
+use IO::Handle;
+
+use base qw(Exporter);
+
+our @EXPORT = qw();
+our @EXPORT_OK = qw(assert_sha1 assert_opts);
+
+# Auxiliary subroutines
+

perl/Git/Repo.pm line 50

Quoted 10 lines
perl/Git/Repo.pm new file +10 -0
+sub assert_opts {
+	die "must have an even number of arguments for named options"
+	    unless $#_ % 2;
+}
+
+sub assert_sha1 {
+	my $sha1 = shift;
+	die "'$sha1' is not a SHA1 (need to use get_sha1?)"
+	    unless $sha1 && $sha1 =~ /^[a-f0-9]{40}$/;
+}

Pretend names with underscores, since they are internal?

Quoted 9 lines
perl/Git/Repo.pm new file +9 -0
+=head1 METHODS
+
+=head2 Instantiation and basic methods
+
+=over
+
+=item $repo = Git::Repo->new(%opts)
+
+Return a new Git::Repo object.  The following options are supported:

perl/Git/Repo.pm line 71

Quoted 10 lines
perl/Git/Repo.pm new file +10 -0
+
+=over
+
+=item 'directory'
+
+The directory of the repository (mandatory).
+
+Note that this option is working-copy agnostic; you need to
+instantiate it with the working copy's .git directory as the
+'directory' option.

I don't think making this mandatory is reasonable, since all the git commands can automatically figure this out by themselves too; so can Git::Repo easily by calling git rev-parse --git-dir.

Quoted 36 lines
perl/Git/Repo.pm new file +36 -0
+=item 'git_binary'
+
+The name or full path of the git binary (default: 'git').
+
+=back
+
+Calling this method is free, since it does not check whether the
+repository exists.  Trying to access the repository through one of the
+instance methods will fail if it doesn't exist though.
+
+Examples:
+
+    $repo = Git::Repo->new(directory => '/path/to/repository.git');
+    $repo = Git::Repo->new(directory => '/path/to/working_copy/.git');
+
+=cut
+
+sub new {
+	my $class = shift;
+	assert_opts(@_);
+	my $self = {@_};
+	bless $self, $class;
+	die 'no directory given' unless $self->{directory};
+	return $self;
+}
+
+=item $repo->repo_dir
+
+Return the directory of the repository (.../.git in case of a working
+copy).
+
+=cut
+
+sub repo_dir {
+	shift->{directory}
+}

perl/Git/Repo.pm line 118

Quoted 10 lines
perl/Git/Repo.pm new file +10 -0
+
+=item $repo->git_binary
+
+Return the name of or path to the git binary (used with exec).
+
+=cut
+
+sub git_binary {
+	shift->{git_binary}
+}

--- CUT ---

Quoted 1 line
perl/Git/Repo.pm new file +1 -0
+=item $repo->version

..snip..

perl/Git/Repo.pm line 239

Quoted 6 lines
perl/Git/Repo.pm new file +6 -0
+# Return the first items of the git command line, for instance
+# qw(/usr/bin/git --git-dir=/path/to/repo.git).
+sub _get_git_cmd {
+	my $self = shift;
+	return ($self->git_binary || 'git', '--git-dir=' . $self->repo_dir);
+}

--- CUT ---

This definitely does not belong to a Git::Repo object.

BTW, I like the idea of reuse => 1 very much.

perl/Git/Repo.pm line 285

Quoted 10 lines

+=back

perl/Git/Repo.pm new file +9 -0
+
+=head2 Inspecting the Repository
+
+=over
+
+=item $repo->cat_file($sha1)
+
+Return the ($type, $content) of the object identified by $sha1, or die
+if no such object exists in the repository.

I don't think this is good combination of semantic and name. Since we don't do the same thing as plain git cat-file, we might as well call it cat_object() or even better get_object().

Quoted 2 lines
perl/Git/Repo.pm new file +2 -0
+Note that you may want to use the higher-level methods get_commit and
+get_tag instead.

..snip..

Quoted 1 line

+=item $repo->get_path($tree_sha1, $file_sha1)

Now we are quickly getting messy again. This should definitely live in Git::Tree.

Quoted 1 line

+=item $repo->get_refs

Again, the refs should be properly integrated into the object structure.

Signature · 4 lines
				Petr "Pasky" Baudis
GNU, n. An animal of South Africa, which in its domesticated state
resembles a horse, a buffalo and a stag. In its wild condition it is
something like a thunderbolt, an earthquake and a cyclone. -- A. Pierce
#6 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 9 lines · Petr Baudis
Quoted 4 lines · Lea Wiemann

This also adds the Git::Commit and Git::Tag classes, which are used by Git::Repo, the Git::Object base class, and the Git::RepoRoot helper factory class.

I really miss some more detailed writeup on the envisioned design here. And if we are redoing the API in a better way, we better should have some vision.

Once again: if you are adding some large amount of code, you'd better describe "whys" of it.

Quoted 11 lines

Most importantly, how is Git::Repo interacting with working copies, and how is it going to interact with them as the new OO model shapes up? You mention briefly Git::WC later, but it is not really clear how the interaction should work.

First, I don't think it's good idea at all to put the pipe-related stuff to Git::Repo - this is botched up API just like the current one. This all is independent from any repository instances, in fact it's perfectly valid to call standalone remote commands (like ls-remote or, actually, clone).

There are three classes of git commands:

1. standalone i.e. those that doesn't require even repository, like e.g. git-ls-remote, git-clone or git-init (or git wrapper, like for example in "git --version").
2. those that require repository (and should use --git-dir=<path>), like for example git-cat-file, git-log / git-rev-list, git-for-each-ref / git-show-refs, git-diff-tree, git-ls-tree;
3. those that require both repository and working copy (and should probably use both --git-dir=<path> and --work-tree=<path>), like git-commit, git-clean, git-ls-files (the last one can require only index).
3'. those that require both repository and working copy, and whose
behavior depends on where in working copy we (current directory) is[*1*].

*All* git commands should know path to "git" wrapper binary, or where $GIT_EXEC_PATH is.

[*1*] It is (besides pointer to Git::Repo instance) what Git::WC differs from simple directory: the pointer where we are, and wc_path(), wc_subdir(), [wc_cdup(),] and wc_chdir(<SUBDIR>) methods.

Quoted 7 lines

Here is an idea: Introduce Git::Command object that will have very general interface and look like

	my $c = Git::Command->new(['git', '--git-dir=.', 'cat-file', \
		'-p', 'bla'], {pipe_out=>1})
	...
	$c->close();

Errr... how do you read from such a pipe? <$c> I think wouldn't work, unless you would use some trickery...

Quoted 6 lines

and a Git::CommandFactory with a nicer interface that would look like

	my $cf = Git::CommandFactory->new('git', '--git-dir=.');
	my $c = $cf->output_pipe('cat-file', '-p', 'bla');
	$c->close();
Quoted 8 lines

Then, Git::Repo would have a single Git::CommandFactory instance pre-initialized with the required calling convention, and returned by e.g. cmd() method. Then, from the user POV, you would just:

	my $repo = Git::Repo->new;
	$repo->cmd->output_pipe('cat-file', '-p', 'bla');

Or am I overdoing it?

You are probably overdoing it.

I think it would be good to have the following interface

Git->output_pipe('ls-remotes', $URL, '--heads'); [...]
$r = Git::Repo->new(<git_dir>); $r->output_pipe('ls_tree', 'HEAD');
[...]
$nb = Git::Repo::NonBare->new(<git_dir>[, <working_area>]); $nb->output_pipe('ls-files');

How can it be done with minimal effort, unfortunately I don't know...

Quoted 3 lines

Git::Repo considers only bare repositories. Now, since "working copy" by itself has nothing to do with Git and is just an ordinary directory tree,

Well, it does provide also current subdir pointer, pointer to git repository it is associated with, and a few methods to examine and change both.

Quoted 6 lines

I think Git::WC does not make that much sense, but something like Git::Repo::Nonbare certainly would. This would be a Git::Repo subclass with:

* Different constructor

Quoted 8 lines

* Different Git::CommandFactory instance

* Git::Index object aside the existing ones (like Git::Config, Git::RefTree, ...)

* Some kind of wc_root() method to help directory navigation

And that pretty much covers it?

Good idea, I think.

Quoted 8 lines

Another thing is clearly describing how error handling is going to work. I have not much against ditching Error.pm, but just saying "die + eval" does not cut it - how about possible sideband data? E.g. the failure mode of Git.pm's command() method includes passing the error'd command output in the exception object. How are we going to handle it? Now, it might be actually okay to say that we _aren't_ going to handle this if it is deemed unuseful, but that needs to be determined too. I don't know off the top of my head.

I think that the solution might be some output_pipe option on how to treat command exit status, command STDERR, and errors when invoking command (for example command not found).

Mentioned http://http://www.perl.com/pub/a/2002/11/14/exception.html
explains why one might want to use Error.pm.

Quoted 8 lines
Quoted 8 lines

---

Please note before starting a reply to this: This is not an argument; I'm just explaining why I implemented it the way I did. So please don't try to argue with me about what I should or should have done. I'm not going to refactor Git::Repo to use Git.pm or vice versa; it's really a much more non-trivial task than you might think at first glance.

[...]

Quoted 4 lines

I agree that your main objective is caching for gitweb, but that's not what everything revolves around for the rest of us. If you chose the way of caching within the Git API and introducing the API to gitweb, I think you should spend the effort to deal with the API properly now.

I think the idea is that gitweb caching as it is implemented (data caching) requires some kind of Perl API, and that existing Git.pm didn't cut -- therefore Git::Repo and friends was created. But the focus is gitweb caching, not Perl API (besides Perl API having to be usable).

Signature · 2 lines
Jakub Narebski
Poland
#7 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 1 line · Jakub Narebski

Git->output_pipe('ls-remotes', $URL, '--heads');

I'd make it cmd => ['ls-remotes', $URL, '--heads'] to avoid making the interface inflexible, and perhaps add a git_binary option to specify the binary path. (Not that I'm suggesting something like this should be added right now; but perhaps it'll be needed at some point.)

Quoted 3 lines

I think that the solution might be some output_pipe option on how to treat command exit status, command STDERR, and errors when invoking command (for example command not found).

You read my design notes about error handling at the top of <[email protected]>, and you noticed there's max_exit_code in cmd_output? With the approach to error handling I described, open/exit errors can cause the method to die, and stderr can be ignored.

If you're trying to implement a more sophisticated error handling approach, be warned that you're opening a can of worms. ;) If you're aiming for something like that, it should be kept out of Git::Repo (which has very coherent error handling as-is), and preferably be discussed under a separate subject, since it's way beyond the scope of this patch.

#8 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Jakub Narebski

In order to keep this mail within sensible size, I have trimmed some of the bits - basically, the default reply is usually ranging from "Fair enough" to "Great!" :-)

Quoted 11 lines · Lea Wiemann
Quoted 2 lines

First, I don't think it's good idea at all to put the pipe-related stuff to Git::Repo - this is botched up API just like the current one.

Well, they're more like helper methods. Since they don't fit into the design goals of the Git::Repo API at all, I'd suggest we just underscore-prefix them and take them out of the man page. (The only reason why I hadn't done this is that gitweb uses $repo->cmd_output extensively, so it'd end up with a lot of underscore calls. But I suppose we can either alias _cmd_output to cmd_output in gitweb's CachedRepo subclass, or live with $repo->_cmd_output calls.) Does underscore-prefixing sound good to you?

I think this is one problematic point we keep hitting - my opinion is that we _need_ such a wrapper _publically_, because it tends to be actually the main use-case of Git.pm, and that this wrapper should be:

(i) both available standalone for commands like ls-remote

(ii) and available as part of Git::Repo instance, to have the right arguments passed to Git automagically

So you define cmd_output() and seem to argue that this command should not be called directly and is not interesting for the outside. But the experience shows that the pipe interface is actually the _most_ used part of the Git Perl API, and in fact you mentioned that as part of your gitweb migration to Git::Repo, you will temporarily introduce calls to _cmd_output(), the "internal" API. :-) Sure, it's only temporary, but many won't have the luxury to adjust the Git::Repo API to provide all the operations they need, and ultimately they will need to defer to the pipe interface.

Quoted 6 lines
Quoted 2 lines

It should be actually very easy to start with moving all the pipe functionality to Git::Command.

Creating a new (Git::Command) API is very much non-trivial, apart from the fact that I'm not convinced that we need Git::Command, and that a clean command interface neither falls out of Git.pm nor Git::Repo.

As I said, majority of Git API usage is actually the pipe API. So we should figure out how to provide it. I agree that it's not immediately within your scope, but you are introducing new Perl API and this just needs to be embedded somewhere there consistently.

Quoted 10 lines
Quoted 9 lines

perl/Git/Commit.pm line 36

Quoted 4 lines

[Git::Commit->new, Git::Tag->new:]

perl/Git/Commit.pm new file +3 -0
+Calls to this method are free, since it does not check whether $sha1
+exists and has the right type.  However, accessing any of the commit
+object's properties will fail if $sha1 is not a valid commit object.

This is nice idea, but I'd also provide a well-defined way for the user to verify the object's validity at a good moment; basically, make load() a public method. The user can deal with errors then and rely on error-free behavior later.

Quoted 6 lines

No, you should never pass in an invalid SHA1 in the first place. The above piece of documentation is just a warning that bugs will show up delayed. IOW, this is not the right place to have your error handling.

If you're getting a SHA1 through the user-interface, check its existence with get_sha1 before passing it to the constructor.

But that's an expensive operation, you need extra Git exec for this, while all the Git commands can do the checks for you, if you give them the chance.

I was doing pretty much this thing in Cogito (initially out of necessity) and it made it ungodly slow for any kind of batch operations.

Quoted 5 lines
Quoted 4 lines

perl/Git/Commit.pm line 39

Quoted 2 lines
perl/Git/Commit.pm new file +2 -0
+Note that $sha1 must be the SHA1 of a commit object; tag objects are
+not dereferenced.

Why not?

Quoted 8 lines

Because the SHA1 might resolve to an object of the wrong type, which means you have to do error handling in Git::Object objects; that's the wrong place.

If tag-resolving is really needed, we can add an optional $type parameter to get_sha1, which will cause get_sha1 to resolve the object until a $type object is found, or return undef if the object is or resolves to an object of the wrong type.

See above why I think you should reconsider requiring the explicit "resolving" step.

Quoted 5 lines

I have resolving code in gitweb's git_get_sha1_or_die (which I didn't implement in Git::Repo since it uses some customized error reporting). The resolving code could conceivably be extracted and moved to get_sha1.
I think there are a few things to ponder and maybe discuss, so I'd do
that in a separate patch (if I get around it before the end of the project).

The thing that concerns me about this is that this might show that your approach to error handling is not flexible enough for some real-world usage and this might be a design mistake - is that not so? I didn't look at the code.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

[Snipped a lot of quoting --LW]

+=item $repo->repo_dir
+=item $repo->git_binary
+=item $repo->version

+sub _get_git_cmd {

Quoted 7 lines
Quoted 1 line

This definitely does not belong to a Git::Repo object.

Which of those methods are you referring to? I think $repo->version might reasonably be removed (and the code re-added to gitweb); I'll do so unless you object. _get_git_cmd is already underscored, and repo_dir and git_binary only access attributes passed in through the constructor, so I think those three should stay.

Sorry, you're right about repo_dir and possibly git_binary. My main concern was about the command pipe handling itself, but I elaborated on that above already.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines
+=item $repo->get_refs
+=item $repo->get_refs($pattern)

Again, the refs should be properly integrated into the object structure.

Really? I think it's generally fine for get_refs to exist and to live in Git::Repo.

Quoted 6 lines

Its return value (currently an an arrayref of [$sha1, $object_type, $ref_name] arrayrefs) might need improvement though, and I find the $pattern parameter pretty suspect (in that it smells like a for-each-ref wrapper). Since get_refs is unused at the moment (gitweb ended up needing the slightly different show-ref), I'll remove it for now. (Same thing about me not being a fan of premature API design applies.)

Just a note, the thing is that you might want to add some methods for inspecting and mutating the refs, and at the same time this is not a repository-specific concept, but you can get the same structure from git ls-remote call. That's why I think it would make sense to make a separate object out of it. But that's moot point now that the API won't be there yet.

Signature · 3 lines
				Petr "Pasky" Baudis
As in certain cults it is possible to kill a process if you know
its true name.  -- Ken Thompson and Dennis M. Ritchie
#9 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 9 lines · Petr Baudis
Quoted 6 lines · Lea Wiemann

No, you should never pass in an invalid SHA1 in the first place. The above piece of documentation is just a warning that bugs will show up delayed. IOW, this is not the right place to have your error handling.

If you're getting a SHA1 through the user-interface, check its existence with get_sha1 before passing it to the constructor.

Quoted 6 lines

But that's an expensive operation, you need extra Git exec for this, while all the Git commands can do the checks for you, if you give them the chance.

I was doing pretty much this thing in Cogito (initially out of necessity) and it made it ungodly slow for any kind of batch operations.

Lea probably would point out that thanks to '--batch-check' option to git-cat-file, and "reuse => 1" option to ->get_bidi_pipe you would need only one extra Git exec... BUT it is one extra Git exec per entire Perl script (one per request, for example, for gitweb). And it wouldn't help batching Perl script commands.

Signature · 2 lines
Jakub Narebski
Poland
#10 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 14 lines · Jakub Narebski
Quoted 13 lines · Petr Baudis
Quoted 6 lines · Lea Wiemann

No, you should never pass in an invalid SHA1 in the first place. The above piece of documentation is just a warning that bugs will show up delayed. IOW, this is not the right place to have your error handling.

If you're getting a SHA1 through the user-interface, check its existence with get_sha1 before passing it to the constructor.

But that's an expensive operation, you need extra Git exec for this, while all the Git commands can do the checks for you, if you give them the chance.

Quoted 8 lines
Quoted 2 lines

I was doing pretty much this thing in Cogito (initially out of necessity) and it made it ungodly slow for any kind of batch operations.

Lea probably would point out that thanks to '--batch-check' option to git-cat-file, and "reuse => 1" option to ->get_bidi_pipe you would need only one extra Git exec... BUT it is one extra Git exec per entire Perl script (one per request, for example, for gitweb). And it wouldn't help batching Perl script commands.

Even so, I don't like this restriction simply since it makes the usage more complicated - in my commandline tool, I will be forced to write

	my $csha1 = $repo->get_sha1($ARGV[0]);
	$csha1 or die "a random inconsistent error message";
	$repo->get_commit($csha1)

instead of

	$repo->get_commit($ARGV[0]) # die if unresolved

and I don't understand why. Me and 80% of the scripts don't *care* about some more graceful error handling, and if gitweb and the other 20% (or less) do, *they* should do the extra work, not me.

Signature · 3 lines
				Petr "Pasky" Baudis
As in certain cults it is possible to kill a process if you know
its true name.  -- Ken Thompson and Dennis M. Ritchie
#11 lore raw

Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

In reply to Petr Baudis
Quoted 2 lines · Petr Baudis

But the experience shows that the pipe interface is actually the _most_ used part of the Git Perl API.

This made me curious which parts of Git.pm, the current Git Perl API, are used most.

Here is some statistics on the usage of Git Perl API among built-in and contrib commands.

Quoted 1 line

From git commands in Perl the following include "use Git" in their

sources: git-cvsexportcommit, git-send-email, git-svn, and helper script git-add--interactive. There are Perl scripts which do not use Git.pm: git-archimport (which I think should be obsoleted or moved to contrib), git-cvsimport, git-cvsserver, git-relink. This means that half of Perl scripts use Git Perl API.

The situation is worse for scripts in 'contrib/'. From those, only contrib/examples/git-remote.perl uses Git.pm; neither blameview, continuous, git-import and import-tars in fast-import, setgitperms and update-paranoid in hooks, stats, nor other Perl scripts in examples (git-rerere, git-svnimport) include "use Git".

Below there are stats on how different commands from Git.pm are used in mentioned Perl scripts:

1. git-add--interactive.perl uses Git.pm mainly to access color
configuration: 2 ->get_colorbool, 8 ->get_color, 1 ->config. It also uses once ->repository() constuctor, and once ->repo_path().

This means that future Git::Config _has_ to have support for color or colorbool valued configuration values.

2. git-cvsexportcommit.perl uses (besides ->repository() constructor) only once ->config; so I guess that current interface wrapping git-config should stay, because parsing whole config for such situation would be overkill.

What is strange that git-cvsexportcommit.perl defines its own safe_pipe_capture and xargs_safe_pipe_capture (!), and uses them, open pipeline, backticks, and system() to call git commands.

3. git-send-email.perl uses 5 config, 2 config_bool, 2 ident_person (for author and for committer), 1 version, and of course once ->repository() constructor.

Here we can see how to work around current API to: it uses Git::config(@repo, "sendemail.identity") form, where
my $repo = eval { Git->repository() };
my @repo = $repo ? ($repo) : ();
to make it work both with git repository (using repo config), and outside/without git repository, using only user and system git config.

4. git-svn.perl (which is if I checked correctly third largest scripted git command, after gitk-gui/gitk-wish and gitweb/gitweb.perl) uses:

3 x repository
28 x command_oneline
19 x command_noisy
18 x command
14 x command_output_pipe
1 x command_input_pipe
1 x git_try_cmd (and many "eval { command()/command_oneline() }") 2 x cat_blob
1 x hash_and_insert_object
1 x get_colorbool

(The above are all if I have not made mistake when counting commands)

Side note: git-svn is command which would get most out of Git::Config, as it currently uses combination of "git config -l" and "git config --get" (I guess that the code in question predates machine-parseable "git config -l -z").

Note that all those statictics doesn't count how many times some Git.pm method was called, only how many times it occurs in the code.

Signature · 2 lines
Jakub Narebski
Poland
#12 lore raw

Re: Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

Quoted 4 lines · Jakub Narebski
Quoted 2 lines · Petr Baudis

But the experience shows that the pipe interface is actually the _most_ used part of the Git Perl API.

Note that I referred both to scripts that are part of Git and
(actually more) to various third-party scripts I wrote externally.

Quoted 2 lines

Here is some statistics on the usage of Git Perl API among built-in and contrib commands.

Thanks!

Quoted 5 lines

The situation is worse for scripts in 'contrib/'. From those, only contrib/examples/git-remote.perl uses Git.pm; neither blameview, continuous, git-import and import-tars in fast-import, setgitperms and update-paranoid in hooks, stats, nor other Perl scripts in examples (git-rerere, git-svnimport) include "use Git".

I have actually once converted blameview to use Git locally, but in
the end never got around to submit it; I wonder if I have the source still around somewhere. Not so important, I guess.

Quoted 7 lines

Below there are stats on how different commands from Git.pm are used in mentioned Perl scripts:

2. git-cvsexportcommit.perl uses (besides ->repository() constructor) only once ->config; so I guess that current interface wrapping git-config should stay, because parsing whole config for such situation would be overkill.

I don't understand. Parsing whole config happen will either happen in
git-config or in Git::Config, and the performance difference is so tiny that it is not really worth the API complexity, I believe.

Quoted 11 lines

3. git-send-email.perl uses 5 config, 2 config_bool, 2 ident_person (for author and for committer), 1 version, and of course once ->repository() constructor.

Here we can see how to work around current API to: it uses Git::config(@repo, "sendemail.identity") form, where
my $repo = eval { Git->repository() };
my @repo = $repo ? ($repo) : ();
to make it work both with git repository (using repo config), and outside/without git repository, using only user and system git config.

With the envisioned model, it could use $git which would be either
a reference to a Git::Standalone singleton or Git::Repo instance.

Signature · 3 lines
				Petr "Pasky" Baudis
As in certain cults it is possible to kill a process if you know
its true name.  -- Ken Thompson and Dennis M. Ritchie
#13 lore raw

Re: Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

Quoted 14 lines · Petr Baudis
Quoted 11 lines · Jakub Narebski

3. git-send-email.perl uses 5 config, 2 config_bool, 2 ident_person (for author and for committer), 1 version, and of course once ->repository() constructor.

Here we can see how to work around current API to: it uses Git::config(@repo, "sendemail.identity") form, where
my $repo = eval { Git->repository() };
my @repo = $repo ? ($repo) : ();
to make it work both with git repository (using repo config), and outside/without git repository, using only user and system git config.

Quoted 2 lines

With the envisioned model, it could use $git which would be either
a reference to a Git::Standalone singleton or Git::Repo instance.

So that the code would look like the following, instead:

  my $git = new Git::Cmd; # or Git::Standalone, or Git::CommandFactory
  my $repo = eval { Git->repository() };
  $git = $repo if $repo;

and later use

  $git->config('sendemail.identity');

By the way, git-svn can use command(...) instead of $repo->command(...) because it sets $ENV{'GIT_DIR'} if it is unset... but I don't see where Git.pm inserts 'git' to commands list...

Signature · 2 lines
Jakub Narebski
Poland
#14 lore raw

Re: Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

Quoted 3 lines · Jakub Narebski

By the way, git-svn can use command(...) instead of $repo->command(...) because it sets $ENV{'GIT_DIR'} if it is unset... but I don't see where Git.pm inserts 'git' to commands list...

In _execv_git_cmd(), or did I misunderstand your question?

(I think that level of indirection is probably residuum of the XS interface.)

Petr "Pasky" Baudis

#15 lore raw

Re: Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

In reply to Jakub Narebski

Hi,

Quoted 7 lines · Jakub Narebski

From git commands in Perl the following include "use Git" in their
sources: git-cvsexportcommit, git-send-email, git-svn, and helper script git-add--interactive. There are Perl scripts which do not use Git.pm: git-archimport (which I think should be obsoleted or moved to contrib), git-cvsimport, git-cvsserver, git-relink. This means that half of Perl scripts use Git Perl API.

Quoted 5 lines

The situation is worse for scripts in 'contrib/'. From those, only contrib/examples/git-remote.perl uses Git.pm; neither blameview, continuous, git-import and import-tars in fast-import, setgitperms and update-paranoid in hooks, stats, nor other Perl scripts in examples (git-rerere, git-svnimport) include "use Git".

You call that "worse"? Pasky tried to convert all Perl scripts at once IIRC, and my numerous problems just _making_ the Git scripts led me to rewrite a few Perl scripts in C, so I could safely exclude the Perl scripts from my personal fork.

I guess that it was this experience which prevented more of the old scripts from being converted.

But your mention of git-add--interactive actually brings up my pet-peeve:
this script is the only Perl script needed for common operations, i.e. the only reason msysGit has to ship bloated with Perl.

Ciao,
Dscho

#16 lore raw

Re: Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

Hi,

Quoted 4 lines · Johannes Schindelin

You call that "worse"? Pasky tried to convert all Perl scripts at once IIRC, and my numerous problems just _making_ the Git scripts led me to rewrite a few Perl scripts in C, so I could safely exclude the Perl scripts from my personal fork.

I don't remember any concrete report of such problems ever reaching
me; exactly what trouble are you hitting with the Perl scripts using Git.pm? I will be glad to try to fix it.

Quoted 6 lines

I guess that it was this experience which prevented more of the old scripts from being converted.

But your mention of git-add--interactive actually brings up my pet-peeve:
this script is the only Perl script needed for common operations, i.e. the only reason msysGit has to ship bloated with Perl.

_Many_ people seem to be using git-svn, whether we like it or not. ;-)
Also, isn't git-send-mail rather commonly used? (I wouldn't know, me using stg mail.)

Signature · 3 lines
				Petr "Pasky" Baudis
As in certain cults it is possible to kill a process if you know
its true name.  -- Ken Thompson and Dennis M. Ritchie
#17 lore raw

Re: Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

Hi,

Quoted 10 lines · Petr Baudis
Quoted 4 lines · Johannes Schindelin

Pasky tried to convert all Perl scripts at once IIRC, and my numerous problems just _making_ the Git scripts led me to rewrite a few Perl scripts in C, so I could safely exclude the Perl scripts from my personal fork.

I don't remember any concrete report of such problems ever reaching me; exactly what trouble are you hitting with the Perl scripts using Git.pm? I will be glad to try to fix it.

They reached you:

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

http://thread.gmane.org/gmane.comp.version-control.git/22764/focus=22778

Yes, those are very old mails, but they _do_ explain why the old Perl scripts avoided Git.pm.

Quoted 6 lines
Quoted 4 lines

But your mention of git-add--interactive actually brings up my
pet-peeve: this script is the only Perl script needed for common operations, i.e. the only reason msysGit has to ship bloated with Perl.

_Many_ people seem to be using git-svn, whether we like it or not. ;-)

Well, they will just stop using it on msysGit, since nobody fixes git-svn on msysGit.

Quoted 2 lines

Also, isn't git-send-mail rather commonly used? (I wouldn't know, me using stg mail.)

send-mail does not work on Windows. See
http://code.google.com/p/msysgit/issues/detail?id=27

Yes, you read correctly. The issue exists since almost the birth of msysGit. Torgil seems to have stopped working on it altogether. Two other people have "starred" the issue, wanting to be notified of fixes but not wanting to work on them.

To me, it seems that either the Windows folk is a lazy bunch of bums, or they just do not care enough. Or both.

Note: I want to express very loudly here that I do not count Hannes Sixt as Windows folk. Even if he seems to like cmd.exe for some perverse reason, it is undisputable that his effort, and his effort alone, brought the MinGW port to where it is now.

To sum it up, I think we can safely remove Perl from the msysGit installer once add--interactive is a builtin. The download will be substantially smaller, and Perl on msysGit was never exactly a speed demon, so it's probably a good change from several angles.

Ciao,
Dscho

#18 lore raw

Re: Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

Hi,

Quoted 9 lines · Johannes Schindelin
Quoted 7 lines · Petr Baudis
Quoted 4 lines · Johannes Schindelin

Pasky tried to convert all Perl scripts at once IIRC, and my numerous problems just _making_ the Git scripts led me to rewrite a few Perl scripts in C, so I could safely exclude the Perl scripts from my personal fork.

Quoted 7 lines
Quoted 3 lines

I don't remember any concrete report of such problems ever reaching me; exactly what trouble are you hitting with the Perl scripts using Git.pm? I will be glad to try to fix it.

They reached you:

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

running Git in-place without correctly setting the prefix is not
supported anyway; git wrapper will still be using non-builtin commands from the prefix location instead of your fresh build.

Quoted 1 line

http://thread.gmane.org/gmane.comp.version-control.git/22764/focus=22778

It seems I fixed this right away?

Petr "Pasky" Baudis

#19 lore raw

Re: Statictics on Git.pm usage in git commands (was: [PATCH 2/3] add new Git::Repo API)

Hi,

Quoted 14 lines · Petr Baudis
Quoted 13 lines · Johannes Schindelin
Quoted 10 lines · Petr Baudis
Quoted 4 lines · Johannes Schindelin

Pasky tried to convert all Perl scripts at once IIRC, and my numerous problems just _making_ the Git scripts led me to rewrite a few Perl scripts in C, so I could safely exclude the Perl scripts from my personal fork.

I don't remember any concrete report of such problems ever reaching me; exactly what trouble are you hitting with the Perl scripts using Git.pm? I will be glad to try to fix it.

Quoted 7 lines
Quoted 3 lines

They reached you:

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

running Git in-place without correctly setting the prefix is not supported anyway; git wrapper will still be using non-builtin commands from the prefix location instead of your fresh build.

There were none. Anyway, because Git makes it _really_ hard to run in-place, I gave in. I run Git from $HOME/bin now.

Quoted 3 lines
Quoted 1 line

http://thread.gmane.org/gmane.comp.version-control.git/22764/focus=22778

It seems I fixed this right away?

That is not what I remember. I remember that I had the next issue right away. I remember that there were problems with ActiveState Perl+Cygwin. And I remember that the Git.xs was not merged in the end.

Anyway, this whole discussion is moot, methinks.

Let's concentrate back on the real issue: merging the two as-of-now incompatible Perl modules. It really would be nice to have one pony with one saddle in the end, so that the use of Lea's GSoC project is not limited to gitweb with enabled caching.

Ciao,
Dscho

#20 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Petr Baudis
Quoted 4 lines · Petr Baudis

[$repo->_cmd_output:]

we _need_ such a wrapper _publically_, because it tends to be actually the main use-case of Git.pm,

Well, sure, I happen to not be convinced, but it *may* be useful. The point I'm trying to make is that it's not part of what I'm writing here.

Quoted 5 lines

as part of your gitweb migration to Git::Repo, you will temporarily introduce calls to _cmd_output(), the "internal" API. :-) Sure, it's only temporary, but many won't have the luxury to adjust the Git::Repo API to provide all the operations they need, and ultimately they will need to defer to the pipe interface.

Yup, and I'm actually fine with that. (I'll probably alias _cmd_output to cmd_output in gitweb, just to make it clear that it is, for the purpose of gitweb, a *supported* mode of operation.) If the Git::Repo::_cmd_output API goes away, you'll have to insert a few lines of code in gitweb, but that's it. Really, no big deal.

Also, gitweb isn't using cmd_output because it needs a pipe interface, but because it needs a caching layer in between -- most applications would do just fine with open calls.

Quoted 4 lines

As I said, majority of Git API usage is actually the pipe API. So we should figure out how to provide it. I agree that it's not immediately within your scope, but you are introducing new Perl API and this just needs to be embedded somewhere there consistently.

Sure, but pleeeease not as part of this patch series! :-) Look, our conversation is going something like this:

Lea: Here's a Perl API that fell out of my gitweb development for free.
Petr: I want a pony with the API!
Lea: But I don't have a pony. Can we please just go with the Perl API as a start, even if I don't supply ponies with it?

(Cf. the very cute <http://c2.com/cgi/wiki?IwantaPony>.)

Quoted 4 lines
Quoted 2 lines

If you're getting a SHA1 through the user-interface, check its existence with get_sha1 before passing it to the constructor.

But that's an expensive operation, you need extra Git exec for this,

For the gazillionth time in this thread, there is no extra exec. It's a write to a bidirectional cat-file --batch-check pipe. It's not expensive. Really. ;-)

Quoted 5 lines
Quoted 1 line

I have resolving code in gitweb's git_get_sha1_or_die

The thing that concerns me about this is that this might show that your approach to error handling is not flexible enough for some real-world usage and this might be a design mistake - is that not so?

I don't think so; the error handling is fine. Given that I want fine-granular error reporting for gitweb, there *needs* to be a git_get_sha1_or_die function; you can't move that into the API.

-- Lea

#21 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 3 lines · Lea Wiemann

Also, gitweb isn't using cmd_output because it needs a pipe interface, but because it needs a caching layer in between -- most applications would do just fine with open calls.

One of the points of the API is to abstract these out.

Quoted 8 lines
Quoted 4 lines

As I said, majority of Git API usage is actually the pipe API. So we should figure out how to provide it. I agree that it's not immediately within your scope, but you are introducing new Perl API and this just needs to be embedded somewhere there consistently.

Sure, but pleeeease not as part of this patch series! :-) Look, our conversation is going something like this:

Quoted 6 lines

Lea: Here's a Perl API that fell out of my gitweb development for free.
Petr: I want a pony with the API!
Lea: But I don't have a pony. Can we please just go with the Perl API as a start, even if I don't supply ponies with it?

(Cf. the very cute <http://c2.com/cgi/wiki?IwantaPony>.)

I'm fine with that, as long as the version that enters into master will have a pony so that we stay with a single pony within the codebase in the end, not two ponies with differently shaped saddles.

But as I said, I'm going to work on that.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

If you're getting a SHA1 through the user-interface, check its existence with get_sha1 before passing it to the constructor.

But that's an expensive operation, you need extra Git exec for this,

For the gazillionth time in this thread, there is no extra exec. It's a write to a bidirectional cat-file --batch-check pipe. It's not expensive. Really. ;-)

But the API is still obnoxiously elaborate, as I complained in another mail.

Quoted 9 lines
Quoted 5 lines
Quoted 1 line

I have resolving code in gitweb's git_get_sha1_or_die

The thing that concerns me about this is that this might show that your approach to error handling is not flexible enough for some real-world usage and this might be a design mistake - is that not so?

I don't think so; the error handling is fine. Given that I want fine-granular error reporting for gitweb, there *needs* to be a git_get_sha1_or_die function; you can't move that into the API.

Wait, this doesn't compute here. The error handling is fine, but it is actually not fine for gitweb. Can't we make it fine for everyone?

Signature · 3 lines
				Petr "Pasky" Baudis
As in certain cults it is possible to kill a process if you know
its true name.  -- Ken Thompson and Dennis M. Ritchie
#22 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Lea Wiemann

Hi,

Quoted 4 lines · Lea Wiemann

Lea: Here's a Perl API that fell out of my gitweb development for free.
Petr: I want a pony with the API!
Lea: But I don't have a pony. Can we please just go with the Perl API as a start, even if I don't supply ponies with it?

Johannes: *mumbles* no ponies? *mumbles further* Them cowboy coders keep the ponies for themselves, eh? *mumbles and gets something to eat*

Ciao,
Dscho

#23 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Jakub Narebski
Quoted 11 lines · Jakub Narebski
Quoted 7 lines · Petr Baudis

Here is an idea: Introduce Git::Command object that will have very general interface and look like

	my $c = Git::Command->new(['git', '--git-dir=.', 'cat-file', \
		'-p', 'bla'], {pipe_out=>1})
	...
	$c->close();

Errr... how do you read from such a pipe? <$c> I think wouldn't work, unless you would use some trickery...

That's good point; it might either be done using some trickery, or $c->pipe. The idea behind having a special object for it though is to have *unified* (no matter how simple) error handling. You might not detect the command erroring out at the open time.

Is there a better approach for solving this?

Quoted 13 lines
Quoted 13 lines

and a Git::CommandFactory with a nicer interface that would look like

	my $cf = Git::CommandFactory->new('git', '--git-dir=.');
	my $c = $cf->output_pipe('cat-file', '-p', 'bla');
	$c->close();

Then, Git::Repo would have a single Git::CommandFactory instance pre-initialized with the required calling convention, and returned by e.g. cmd() method. Then, from the user POV, you would just:

	my $repo = Git::Repo->new;
	$repo->cmd->output_pipe('cat-file', '-p', 'bla');
Quoted 8 lines
Quoted 1 line

Or am I overdoing it?

You are probably overdoing it.

I think it would be good to have the following interface

Git->output_pipe('ls-remotes', $URL, '--heads');

This is problematic; I think mixing the new and old interface within a single class is very bad idea, we should have Git::Standalone or something for this. Or, just, default Git::CommandFactory. ;-)

Quoted 9 lines

[...]
$r = Git::Repo->new(<git_dir>); $r->output_pipe('ls_tree', 'HEAD');
[...]
$nb = Git::Repo::NonBare->new(<git_dir>[, <working_area>]); $nb->output_pipe('ls-files');

How can it be done with minimal effort, unfortunately I don't know...

Well, this interface is almost identical to what I delineated, except that I have the extra ->cmd-> step there. But maybe, we could go with your API and instead have Git::CommandFactory as a base of Git::Repo? The hierarchy would be

Git::CommandFactory - provides the cmd_pipe toolkit
|
Git::Repo - provides repository model
|
Git::Repo::NonBare - additional working-copy-related methods

I think I will post a sample implementation sometime over the weekend.

Quoted 9 lines
Quoted 8 lines

Another thing is clearly describing how error handling is going to work. I have not much against ditching Error.pm, but just saying "die + eval" does not cut it - how about possible sideband data? E.g. the failure mode of Git.pm's command() method includes passing the error'd command output in the exception object. How are we going to handle it? Now, it might be actually okay to say that we _aren't_ going to handle this if it is deemed unuseful, but that needs to be determined too. I don't know off the top of my head.

Quoted 6 lines

I think that the solution might be some output_pipe option on how to treat command exit status, command STDERR, and errors when invoking command (for example command not found).

Mentioned http://http://www.perl.com/pub/a/2002/11/14/exception.html
explains why one might want to use Error.pm.

The arguments against its usage that popped up over the year(s?):

(i) It is not standard practice in the Perl world

(ii) It is syntactically ambiguous, c.f. Lea's report about the missing semicolon

(iii) The usage of closures in this way has inherent memory leak issues

Signature · 3 lines
				Petr "Pasky" Baudis
As in certain cults it is possible to kill a process if you know
its true name.  -- Ken Thompson and Dennis M. Ritchie
#24 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 17 lines · Petr Baudis
Quoted 9 lines · Jakub Narebski

[...]
$r = Git::Repo->new(<git_dir>); $r->output_pipe('ls_tree', 'HEAD');
[...]
$nb = Git::Repo::NonBare->new(<git_dir>[, <working_area>]); $nb->output_pipe('ls-files');

How can it be done with minimal effort, unfortunately I don't know...

Well, this interface is almost identical to what I delineated, except that I have the extra ->cmd-> step there. But maybe, we could go with your API and instead have Git::CommandFactory as a base of Git::Repo? The hierarchy would be

Quoted 7 lines

        Git::CommandFactory - provides the cmd_pipe toolkit |
            Git::Repo       - provides repository model
                |
        Git::Repo::NonBare  - additional working-copy-related methods

I think I will post a sample implementation sometime over the weekend.

What I would want from the solution is an easy way to set up path to git binary for _all_ git command calls, and for invoking command which does not need git repository to run (like git-ls-remote).

Signature · 2 lines
Jakub Narebski
Poland
#25 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Petr Baudis
Quoted 13 lines · Petr Baudis
Quoted 11 lines · Jakub Narebski
Quoted 7 lines · Petr Baudis

Here is an idea: Introduce Git::Command object that will have very general interface and look like

	my $c = Git::Command->new(['git', '--git-dir=.', 'cat-file', \
		'-p', 'bla'], {pipe_out=>1})
	...
	$c->close();

Errr... how do you read from such a pipe? <$c> I think wouldn't work, unless you would use some trickery...

Quoted 6 lines

That's good point; it might either be done using some trickery, or $c->pipe. The idea behind having a special object for it though is to have *unified* (no matter how simple) error handling. You might not detect the command erroring out at the open time.

Is there a better approach for solving this?

I don't know if it is _better_ approach, but the _alternate_ approach would be to use:

my $c = Git::Command->new(['git', '--git-dir=.', 'cat-file', \
'-p', 'bla'], {out=>my $fh, err=>undef})

	... 	
	while (my $line = <$fh>) {
	...

$c->close();

And trickery would be to use blessed filehandle, or what? Or perhaps extending IO::Handle (but not all like using object methods for I/O handles)?

Quoted 17 lines
Quoted 17 lines
Quoted 6 lines

and a Git::CommandFactory with a nicer interface that would look like

	my $cf = Git::CommandFactory->new('git', '--git-dir=.');
	my $c = $cf->output_pipe('cat-file', '-p', 'bla');
	$c->close();
Quoted 8 lines

Then, Git::Repo would have a single Git::CommandFactory instance pre-initialized with the required calling convention, and returned by e.g. cmd() method. Then, from the user POV, you would just:

	my $repo = Git::Repo->new;
	$repo->cmd->output_pipe('cat-file', '-p', 'bla');

Or am I overdoing it?

You are probably overdoing it.

Quoted 7 lines
Quoted 3 lines

I think it would be good to have the following interface

Git->output_pipe('ls-remotes', $URL, '--heads');

This is problematic; I think mixing the new and old interface within a single class is very bad idea, we should have Git::Standalone or something for this. Or, just, default Git::CommandFactory. ;-)

I forgot that we cannot obsolete / replace old interface. Nevertheless it would be nice to be able to use for example

	Git::Cmd->output_pipe('ls-remotes', $URL, '--heads');

but also

	output_pipe('myscript.sh', <arg1>, <arg2>);

See also below for alternative interfaces to Git::Cmd->output_pipe();

Quoted 15 lines
Quoted 9 lines

[...]
$r = Git::Repo->new(<git_dir>); $r->output_pipe('ls_tree', 'HEAD');
[...]
$nb = Git::Repo::NonBare->new(<git_dir>[, <working_area>]); $nb->output_pipe('ls-files');

How can it be done with minimal effort, unfortunately I don't know...

Well, this interface is almost identical to what I delineated, except that I have the extra ->cmd-> step there. But maybe, we could go with your API and instead have Git::CommandFactory as a base of Git::Repo? The hierarchy would be

Quoted 7 lines

Git::CommandFactory - provides the cmd_pipe toolkit
|
Git::Repo - provides repository model
|
Git::Repo::NonBare - additional working-copy-related methods

I think I will post a sample implementation sometime over the weekend.

Thanks.

I think this is a very good idea. Although... you mix somewhat here relationships. Relationship between Git::CommandFactory (Git::Cmd?) is a bit different than relationship between Git::Repo and Git::Repo::NonBare. Git::Repo::NonBare is a case of Git::Repo which additionally knows where its working copy (Git::WC?) is, and where inside working copy we are (if we are inside working copy). Git::Repo uses Git::CommandFactory to route calls to git commands, and to provide default '--git-dir=<repo_path>' argument.

What I'd like to have is a way to easily set in _one_ place where git binary can be found, even if we are using different repositories, call git commands not related to git repository.

Should we use

	Git::Cmd->output_pipe('ls-remotes', $URL, '--heads');
or
	output_pipe(GIT, 'ls-remotes', $URL, '--heads');
or
	output_pipe($GIT, 'ls-remotes', $URL, '--heads');
or
	output_pipe($Git::GIT, 'ls-remotes', $URL, '--heads');

we would want to be able to set where git binary is once (and for all), for example via

	Git::Cmd->set_git('/usr/local/bin/git');

or something like that.

Signature · 2 lines
Jakub Narebski
Poland
#26 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 29 lines · Jakub Narebski
Quoted 13 lines · Petr Baudis
Quoted 11 lines · Jakub Narebski
Quoted 7 lines · Petr Baudis

Here is an idea: Introduce Git::Command object that will have very general interface and look like

	my $c = Git::Command->new(['git', '--git-dir=.', 'cat-file', \
		'-p', 'bla'], {pipe_out=>1})
	...
	$c->close();

Errr... how do you read from such a pipe? <$c> I think wouldn't work, unless you would use some trickery...

Quoted 6 lines

That's good point; it might either be done using some trickery, or $c->pipe. The idea behind having a special object for it though is to have *unified* (no matter how simple) error handling. You might not detect the command erroring out at the open time.

Is there a better approach for solving this?

I don't know if it is _better_ approach, but the _alternate_ approach would be to use:

(quoted diff) +0 -0
 	my $c = Git::Command->new(['git', '--git-dir=.', 'cat-file', \
 		'-p', 'bla'], {out=>my $fh, err=>undef})
...
	while (my $line = <$fh>) {
	...
Quoted 1 line

$c->close();

I think this is horribly ugly, you would be *much* better keeping the filehandle within $c if going this way.

Quoted 3 lines

And trickery would be to use blessed filehandle, or what? Or perhaps extending IO::Handle (but not all like using object methods for I/O handles)?

Maybe blessed filehandle is the simplest way; it seems that in case we need anything more complex later, it should be possible to replace it with an IO::Handle subclass, but that feels like overengineering now.

Quoted 8 lines

I forgot that we cannot obsolete / replace old interface. Nevertheless it would be nice to be able to use for example

	Git::Cmd->output_pipe('ls-remotes', $URL, '--heads');

but also

	output_pipe('myscript.sh', <arg1>, <arg2>);

I think exported functions should have all a git_ prefix.

Quoted 15 lines
Quoted 5 lines

Well, this interface is almost identical to what I delineated, except that I have the extra ->cmd-> step there. But maybe, we could go with your API and instead have Git::CommandFactory as a base of Git::Repo? The hierarchy would be

Quoted 7 lines

Git::CommandFactory - provides the cmd_pipe toolkit
|
Git::Repo - provides repository model
|
Git::Repo::NonBare - additional working-copy-related methods

I think I will post a sample implementation sometime over the weekend.

Thanks.

Quoted 8 lines

I think this is a very good idea. Although... you mix somewhat here relationships. Relationship between Git::CommandFactory (Git::Cmd?) is a bit different than relationship between Git::Repo and Git::Repo::NonBare. Git::Repo::NonBare is a case of Git::Repo which additionally knows where its working copy (Git::WC?) is, and where inside working copy we are (if we are inside working copy). Git::Repo uses Git::CommandFactory to route calls to git commands, and to provide default '--git-dir=<repo_path>' argument.

Yes, but that does not mean Git::Repo must not inherit from Git::CmdFactory. Think of Git::CmdFactory as maybe a kind of Java-sense interface to a degree.

Quoted 14 lines

What I'd like to have is a way to easily set in _one_ place where git binary can be found, even if we are using different repositories, call git commands not related to git repository.

Should we use

	Git::Cmd->output_pipe('ls-remotes', $URL, '--heads');
or
	output_pipe(GIT, 'ls-remotes', $URL, '--heads');
or
	output_pipe($GIT, 'ls-remotes', $URL, '--heads');
or
	output_pipe($Git::GIT, 'ls-remotes', $URL, '--heads');
Quoted 6 lines

we would want to be able to set where git binary is once (and for all), for example via

	Git::Cmd->set_git('/usr/local/bin/git');

or something like that.

Yes, that should work fine, with the Git::Cmd subclasses looking into the singleton.

BTW, I don't like Git::Cmd for the factory interface, since the methods create Git::Command objects and then the naming does not make any sense. So I'm going to use class names Git::CmdFactory and Git::Cmd for the first prototype (since "Command" _is_ too long), unless you have better but still clear names.

Signature · 3 lines
				Petr "Pasky" Baudis
As in certain cults it is possible to kill a process if you know
its true name.  -- Ken Thompson and Dennis M. Ritchie
#27 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 11 lines · Petr Baudis
Quoted 8 lines · Jakub Narebski

I think this is a very good idea.  Although... you mix somewhat here relationships.  Relationship between Git::CommandFactory (Git::Cmd?) is a bit different than relationship between Git::Repo and Git::Repo::NonBare.  Git::Repo::NonBare is a case of Git::Repo which additionally knows where its working copy (Git::WC?) is, and where inside working copy we are (if we are inside working copy).  Git::Repo uses Git::CommandFactory to route calls to git commands, and to provide default '--git-dir=<repo_path>' argument.

Quoted 3 lines

Yes, but that does not mean Git::Repo must not inherit from Git::CmdFactory. Think of Git::CmdFactory as maybe a kind of Java-sense interface to a degree.

I agree. Nevertheless one inheritance is "interface", second is "subclass"... but I don't think that matters at all. Just being nitpicky.

Signature · 2 lines
Jakub Narebski
Poland
#28 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Petr Baudis
Quoted 3 lines · Petr Baudis

Maybe I sound too mentoring at places; [OTOH] if something general gets into the Git tree, I'd like to make sure it's something we can live happily with for long time, not just a hack tailored for gitweb caching.

Yup, I agree (and you don't sound too mentoring ^^). Thanks for the review and feedback.

Quoted 2 lines

I really miss some more detailed writeup on the envisioned design here. [...] Most importantly, how is Git::Repo interacting with working copies,

Git::Repo is not interacting with working copies at all right now. Is there anything you think should be considered for its design?

Here's a write-up about the design (I'll probably move this into Git::Repo's man page):

----------
Git::Repo aims to provide low-level access to Git repositories. For instance, you can resolve object names (like "HEAD~2") to SHA1s, and inspect objects. It does not attempt to be a wrapper around the git plumbing or porcelain commands.

Error handling is simple: On a consistent repository, the Perl interface will never die. You can use the get_sha1 method to resolve arbitrary object names or check the existence of SHA1 hashes; get_sha1 will return undef if the object does not exist in the repository. Any SHA1 that is returned by get_sha1 can be safely passed to the other Git::Repo methods.
----------

Quoted 2 lines

First, I don't think it's good idea at all to put the pipe-related stuff to Git::Repo - this is botched up API just like the current one.

Well, they're more like helper methods. Since they don't fit into the design goals of the Git::Repo API at all, I'd suggest we just underscore-prefix them and take them out of the man page. (The only reason why I hadn't done this is that gitweb uses $repo->cmd_output extensively, so it'd end up with a lot of underscore calls. But I suppose we can either alias _cmd_output to cmd_output in gitweb's CachedRepo subclass, or live with $repo->_cmd_output calls.) Does underscore-prefixing sound good to you?

If someone wants to come up with a consistent nice interface for calling git commands, sure. I wasn't actually trying to do that.

Quoted 7 lines

Here is an idea: Introduce Git::Command object [and a Git::CommandFactory]

	my $c = Git::Command->new(['git', '--git-dir=.', 'cat-file', \
		'-p', 'bla'], {pipe_out=>1})    [...]
	my $cf = Git::CommandFactory->new('git', '--git-dir=.');

Or am I overdoing it?

Yes, I think so. ;-) All we're talking about here is a wrapper around "open '-|'" calls (plus some workarounds for Windows I suppose).

I don't have much of a notion of a 'command' as an object in my head; your (snipped) example makes it look like you're trying to create a IO::Handle-compatible interface, which I think is way too much effort (and error-prone) for simple pipes. Also, a CommandFactory class just to catenate lists together seems like overkill to me.

Something like a command interface *may* make sense if it's tied to repositories or working copies, in which case it could automatically set --git-dir or --work-tree, but it's beyond the scope of what I'm trying to create here, and I don't think it's even overly useful.

Quoted 4 lines

Instead, I believe the best course is to gradually translate all the Git.pm functionality to the new OO model, leaving Git.pm as a compatibility wrapper. Now, if you believe this is a non-trivial task, please tell us why.

Well, as I said, the fact that there are untested parts in Git.pm doesn't exactly make it trivial to refactor. Also, only very few parts can be cleanly moved to Git::Repo. In "grep '^sub [^_]' Git.pm" I find only cat_blob and perhaps hash_object to be eligible to be moved (though hash_object should probably live in a working-copy/non-bare-repo class, with an optional insert => 1 parameter). And even cat_blob is non-trivial to move unless you want the whole blob to be read into memory.

That's a lot of non-trivialness for very little gain. I doubt I'd even have enough time till the end of GSoC (minus vacation) to do this.

Quoted 2 lines

It should be actually very easy to start with moving all the pipe functionality to Git::Command.

Creating a new (Git::Command) API is very much non-trivial, apart from the fact that I'm not convinced that we need Git::Command, and that a clean command interface neither falls out of Git.pm nor Git::Repo.

Quoted 7 lines
Quoted 2 lines

I'm working on caching for gitweb, not on implementing the next great Perl API for Git. (And Git::Repo isn't great, FTR.)

Wait, I can't make sense out of this paragraph. If Git.pm sucks, we can work on new API. But we better _make_ it great. Or someone else comes by next year and says "oh, but it's buggy and needs refactoring, let's throw it away and redesign it!"

Sorry, I wasn't clear with my parenthesed remark: I actually think that Git::Repo is pretty good in terms of code and interface quality. It's just not *complete*, even in its limited scope, and I'm not attempting to make it complete.

I do think that someone who wants to extend Git::Repo (like Jakub with Git::Config) won't have much trouble doing so with the existing design.

Quoted 5 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+use constant _MESSAGE => 'M';
+use constant _ENCODING => 'E';   [snip]

if you are going to use hashes
anyway, why not actually key by sensible name directly?

Embarrassingly premature optimization here. ^_^ I'll fix it.

Quoted 4 lines
Quoted 1 line

$commit = Git::Commit->new($repo, $sha1)

Are we sure we don't want hash-based arguments instead? This is badly extensible and inconsistent with the rest of the API.

*ponders* Every commit needs a repo and a SHA1, so those will never get optional. We can always add hash-based options after the two mandatory arguments, but I don't even see any such possible options at the moment.
(And if I turn out to be completely wrong, we can even move to a
hash-only argument list by checking the type of the first parameter.) Really, I wouldn't worry.

Quoted 9 lines

perl/Git/Commit.pm line 36

Quoted 4 lines

[Git::Commit->new, Git::Tag->new:]

perl/Git/Commit.pm new file +3 -0
+Calls to this method are free, since it does not check whether $sha1
+exists and has the right type.  However, accessing any of the commit
+object's properties will fail if $sha1 is not a valid commit object.

This is nice idea, but I'd also provide a well-defined way for the user to verify the object's validity at a good moment; basically, make load() a public method. The user can deal with errors then and rely on error-free behavior later.

No, you should never pass in an invalid SHA1 in the first place. The above piece of documentation is just a warning that bugs will show up delayed. IOW, this is not the right place to have your error handling.

If you're getting a SHA1 through the user-interface, check its existence with get_sha1 before passing it to the constructor.

Quoted 4 lines

perl/Git/Commit.pm line 39

Quoted 2 lines
perl/Git/Commit.pm new file +2 -0
+Note that $sha1 must be the SHA1 of a commit object; tag objects are
+not dereferenced.

Why not?

Because the SHA1 might resolve to an object of the wrong type, which means you have to do error handling in Git::Object objects; that's the wrong place.

If tag-resolving is really needed, we can add an optional $type parameter to get_sha1, which will cause get_sha1 to resolve the object until a $type object is found, or return undef if the object is or resolves to an object of the wrong type.

I have resolving code in gitweb's git_get_sha1_or_die (which I didn't implement in Git::Repo since it uses some customized error reporting). The resolving code could conceivably be extracted and moved to get_sha1.
I think there are a few things to ponder and maybe discuss, so I'd do
that in a separate patch (if I get around it before the end of the project).

Quoted 3 lines
Quoted 1 line

+=item $commit->authors

author

Fixed.

Quoted 5 lines

perl/Git/Object.pm line 11

Quoted 2 lines
perl/Git/Object.pm new file +2 -0
+Objects are loaded lazily, and hence instantiation is free.  Objects
+stringify to their SHA1s.

Maybe use the term 'Git database objects'? This way, it seems as if we are talking about all Git/*.pm objects.

I've replaced it with: "Objects are loaded lazily, and hence instantiation is free. Git::Object instances stringify to their SHA1s."

Quoted 4 lines

perl/Git/Object.pm line 76

Quoted 2 lines
perl/Git/Object.pm new file +2 -0
+sub sha1 {
+sub stringify {

Why not overload "" directly to sha1()?

Done (and removed stringify).

Quoted 4 lines

perl/Git/Repo.pm line 46

Quoted 2 lines
perl/Git/Repo.pm new file +2 -0
+sub assert_opts {
+sub assert_sha1 {

Pretend names with underscores, since they are internal?

Done, and removed them from @EXPORT_OK.

Quoted 5 lines
Quoted 1 line

'directory': The directory of the repository (mandatory).

I don't think making this mandatory is reasonable, since all the git commands can automatically figure this out by themselves too; so can Git::Repo easily by calling git rev-parse --git-dir.

Sure, it can be made non-mandatory if it's needed, but there are so many possibilities for the exact time and place at which the repo directory should be resolved using rev-parse (if at all) that I'd rather leave this to the person who has an actual use-case for it. I'm not a fan of designing APIs before they are needed.

Quoted 7 lines
Quoted 5 lines

[Snipped a lot of quoting --LW]

+=item $repo->repo_dir
+=item $repo->git_binary
+=item $repo->version

+sub _get_git_cmd {

This definitely does not belong to a Git::Repo object.

Which of those methods are you referring to? I think $repo->version might reasonably be removed (and the code re-added to gitweb); I'll do so unless you object. _get_git_cmd is already underscored, and repo_dir and git_binary only access attributes passed in through the constructor, so I think those three should stay.

Quoted 5 lines
Quoted 1 line

+=item $repo->cat_file($sha1)

I don't think this is good combination of semantic and name. Since we don't do the same thing as plain git cat-file, we might as well call it cat_object() or even better get_object().

Yup; I like get_object (I think I was planning to rename it and then didn't remember doing so before sending off the patch). Will rename it.

Quoted 4 lines
Quoted 1 line

+=item $repo->get_path($tree_sha1, $file_sha1)

Now we are quickly getting messy again. This should definitely live in Git::Tree.

Yup, that's true. I'll move it into gitweb until we have Git::Tree (with a comment that it can be moved to Git::Tree once it exists).

Quoted 4 lines
Quoted 2 lines
+=item $repo->get_refs
+=item $repo->get_refs($pattern)

Again, the refs should be properly integrated into the object structure.

Really? I think it's generally fine for get_refs to exist and to live in Git::Repo.

Its return value (currently an an arrayref of [$sha1, $object_type, $ref_name] arrayrefs) might need improvement though, and I find the $pattern parameter pretty suspect (in that it smells like a for-each-ref wrapper). Since get_refs is unused at the moment (gitweb ended up needing the slightly different show-ref), I'll remove it for now. (Same thing about me not being a fan of premature API design applies.)

I keep patches of everything I remove so other people will be able to use them as starting points; I'll probably post them as "FYI"-patches to the list at the end of my project, to preserve them for posterity.

#29 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Lea Wiemann
Quoted 8 lines · Lea Wiemann

Here's some elaboration on why I didn't use or extend Git.pm.

Please note before starting a reply to this: This is not an argument; I'm just explaining why I implemented it the way I did. So please don't try to argue with me about what I should or should have done....
...
So where do we go with Git.pm and Git::Repo? I would suggest that they both stay.

This probably is something the person who mentors you as a GSoC student should tell you, and shouldn't come from me, but I am going to mention it anyway.

I thought GSoC program was about giving participants a chance to acquire skills to work better with open source community, and I firmly believe that a major component of that skillset is about working with people, probably even more so than about working with code. Of course you need have a certain basic coding skills and disciplines, but it is clear to everybody that you are beyond that threashold already. I would have preferred to see the first paragraph of yours stated more diplomaticly; it sounds rather unilateral ultimatum to me.

Having said that, I agree with your reasoning. People who care enough can help refactoring Git.pm to build on top of Git::Repo (this would include adding missing Git::WC and possibly others), but I agree that would largely be outside of the scope of gitweb caching.

This patch was not signed-off, but I assume it was simply forgotten and not deliberate?

#30 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 2 lines · Junio C Hamano

This probably is something the person who mentors you as a GSoC student should tell you, and shouldn't come from me,

No, thanks for telling me. I'm actually keeping John quite busy with the server side on kernel.org, and wouldn't currently expect him to look at the patches I'm sending.

Quoted 2 lines

I would have preferred to see the first paragraph of yours stated more diplomaticly; it sounds rather unilateral ultimatum to me.

Apologies; I was trying to hold off developers who'd see duplicate functionality and run off at me screaming "unacceptable! impure!!one". I should've phrased it more carefully.

Quoted 1 line

This patch was not signed-off, but I assume it was simply forgotten

Yup. In the unlikely case it gets applied as-is, feel free to add it.

(I'll reply to Jakub's email shortly, unless I fall asleep in the meantime. ^^)

#31 lore raw

Re: [PATCH 2/3] add new Git::Repo API

In reply to Lea Wiemann

[Here is promised patch review]

Quoted 5 lines · Lea Wiemann
 create mode 100644 perl/Git/Commit.pm
 create mode 100644 perl/Git/Object.pm
 create mode 100644 perl/Git/Repo.pm
 create mode 100644 perl/Git/RepoRoot.pm
 create mode 100644 perl/Git/Tag.pm

Does splitting into many small files is really necessary?

Quoted 2 lines
 create mode 100755 t/t9710-perl-git-repo.sh
 create mode 100755 t/t9710/test.pl

Good that you provided test suite.

Quoted 16 lines
perl/Git/Commit.pm new file +10 -0
new file mode 100644
index 0000000..a9bc304
@@ -0,0 +1,163 @@
+=head1 NAME
+
+Git::Commit - Object-oriented interface to Git commit objects.
+
+=cut
+
+use strict;
+use warnings;
+
+

perl/Git/Commit.pm line 20

Quoted 10 lines
perl/Git/Commit.pm new file +10 -0
+package Git::Commit;
+
+use base qw(Git::Object);
+
+use constant _MESSAGE => 'M';
+use constant _ENCODING => 'E';
+use constant _TREE => 'T';
+use constant _PARENTS => 'P';
+use constant _AUTHOR => 'A';
+use constant _COMMITTER => 'C';

What is this for? Why not keep content of commit headers keyed under their names? It would be much simpler code, and I don't think the above buys anything in terms of performance.

There is another thing to consider: commit object and tag object format was deliberately (I think) designed to be *extensible* (in a way). You can add new headers, and old git binary should simply ignore unknown headers. This is what allowed us to add 'encoding' header to commit object, and propose 'keyword' header to tag object (which was to be used for notes / post-factum annotations).

perl/Git/Commit.pm line 21

Quoted 1 line
perl/Git/Commit.pm new file +1 -0
+# Keep documentation in one place to save space.

Errrrr... that is a stupid idea, and even more stupid reasoning.

Embedded PODs in Perl modules serve as sort of literate programming, serving to describe code (technical/usage documentation) in addition to comments in code.

perl/Git/Commit.pm line 39

Quoted 11 lines
perl/Git/Commit.pm new file +11 -0
+=item $commit = Git::Commit->new($repo, $sha1)
+
+Return a new Git::Commit instance for a commit object with $sha1 in
+repository $repo.
+
+Calls to this method are free, since it does not check whether $sha1
+exists and has the right type.  However, accessing any of the commit
+object's properties will fail if $sha1 is not a valid commit object.
+
+Note that $sha1 must be the SHA1 of a commit object; tag objects are
+not dereferenced.

The technique you use has a name, and it is (IIUC) "lazy evaluation". By the way, wouldn't it be better to make this method internal, and use instead the following code to generate Git::Commit object

  $commit = $repo->commit($sha1);

perl/Git/Commit.pm line 34

Quoted 2 lines
perl/Git/Commit.pm new file +2 -0
+=item $obj->repo
+=item $obj->sha1

Those do not access the repository, isn't it?

perl/Git/Commit.pm line 51

Quoted 3 lines
perl/Git/Commit.pm new file +3 -0
+=item $commit->tree
+
+Return the tree this commit object refers to.

I think you should say there explicitely that it is SHA-1 of tree (tree object identifier) that is returned. Isn't it?

By the way I think it should be mentioned explicitly in documentation which methods cause "instantation" (which cause evaluation).

[The fact that documentation is separated from code means that
I cannot easily tell and write if code match documentation]

perl/Git/Commit.pm line 57

Quoted 5 lines
perl/Git/Commit.pm new file +5 -0
+=item $commit->parents
+
+Return a list of zero or more parent commit objects.  Note that commit
+objects stringify to their respective SHA1s, so you can alternatively
+treat this as a list of SHA1 strings.

Array or arrayref?

There is little inconsistency that tree object is (from the lack of Git::Tree object) returned as SHA1, and parents as objects.

NOTE that element of list of revisions has in addition to that also _effective_ parents in the event of history simplification, for example for 'history' view, or when using '--first-parent' extra option.

perl/Git/Commit.pm line 59

Quoted 1 line
perl/Git/Commit.pm new file +1 -0
+=item $commit->authors

s/authors/author/; singular.

perl/Git/Commit.pm line 65

Quoted 6 lines
perl/Git/Commit.pm new file +6 -0
+
+Return the author string of this commit object.
+
+=item $commit->committer
+
+Return the committer string of this commit object.

It returns whole value of 'author' and 'committer' headers, not something extracted from it (into name, email, epoch and timezone), isn't it?

perl/Git/Commit.pm line 69

Quoted 3 lines
perl/Git/Commit.pm new file +3 -0
+=item $commit->message
+
+Return the undecoded commit message of this commit object.

Just raw data?

NOTE that for element of list of revisions (as returned by git-rev-list or git-log) would probably have commit message decoded to UTF-8 by git.

perl/Git/Commit.pm line 73

Quoted 3 lines
perl/Git/Commit.pm new file +3 -0
+=item $commit->encoding
+
+Return the encoding header of the commit object.

Normalized?

perl/Git/Commit.pm line 90

Quoted 11 lines
perl/Git/Commit.pm new file +11 -0
+sub tree {
+	my $self = shift;
+	$self->_load;
+	$self->{_TREE()};
+}
+
+sub parents {
+	my $self = shift;
+	$self->_load;
+	map { ref($self)->new($self->repo, $_) } @{$self->{_PARENTS()}};
+}

Nice... but shouldn't ->tree return Git::Tree?

perl/Git/Commit.pm line 102

Quoted 11 lines
perl/Git/Commit.pm new file +11 -0
+sub author {
+	my $self = shift;
+	$self->_load;
+	$self->{_AUTHOR()} or '';
+}
+
+sub committer {
+	my $self = shift;
+	$self->_load;
+	$self->{_COMMITTER()} or '';
+}

Nowhere in documentation is mentioned that you use empty value for no author or no committer (isn't commit object invalid then?).

perl/Git/Commit.pm line 120

Quoted 5 lines
perl/Git/Commit.pm new file +5 -0
+# Auxiliary method to load (and parse) the commit object from the
+# repository if it hasn't already been loaded.
+
+sub _load {
+	my($self, $raw_text) = shift;

Probably should read:

+ my ($self, $raw_text) = shift;

perl/Git/Commit.pm line 83

Quoted 1 line
perl/Git/Commit.pm new file +1 -0
+	return if defined $self->{_MESSAGE()};  # already loaded

Do you ensure that 'message' (or 'body') is always defined? Why not use "exists" instead; is there some reason to chose "defined"?

perl/Git/Commit.pm line 129

Quoted 7 lines
perl/Git/Commit.pm new file +7 -0
+	my $sha1 = $self->sha1;
+	if (!defined $raw_text) {
+		# Retrieve from the repository.
+		(my $type, $raw_text) = $self->repo->cat_file($sha1);
+		die "$sha1 is a $type object (expected a commit object)"
+		    unless $type eq 'commit';
+	}

The above makes Git::Commit good solution for gitweb's 'commit' and 'commitdiff' views, but bad solution for 'log', 'shortlog', 'history' and 'rss'/'atom' views, where you would need to many command invocations, which is very bad on OS with slow fork.

perl/Git/Commit.pm line 92

Quoted 2 lines
perl/Git/Commit.pm new file +2 -0
+	(my $header, $self->{_MESSAGE()}) = split "\n\n", $raw_text, 2;
+	# Parse header.

Why not simply parse headers, then slurp rest of object into 'message' field? I'm just asking, because it is alternate (but I don't know if better) solution.

Quoted 7 lines
perl/Git/Commit.pm new file +7 -0
+	for my $line (split "\n", $header) {
+		local $/ = "\n"; # for chomp
+		chomp($line);
+		my($key, $value) = split ' ', $line, 2;
+		if ($key eq 'tree') {
+			$self->{_TREE()} = $value;
+		} elsif ($key eq 'parent') {

perl/Git/Commit.pm line 149

Quoted 10 lines
perl/Git/Commit.pm new file +10 -0
+			push @{$self->{_PARENTS()}}, $value;
+		} elsif ($key eq 'author') {
+			$self->{_AUTHOR()} = $value;
+		} elsif ($key eq 'committer') {
+			$self->{_COMMITTER()} = $value;
+		} elsif ($key eq 'encoding') {
+			$self->{_ENCODING()} = $value;
+		} else {
+			die "unrecognized commit header $key";
+		}

IMHO you should not die at unrecognized header, but simply store it under its name (and make available using ->header('<NAME>')). Please remember that commit object format is extensible.

Nevertheless I think you can check for header name validation (there are some restrictions on header names, isn't it?).

perl/Git/Commit.pm line 160

Quoted 6 lines
perl/Git/Commit.pm new file +6 -0
+=head1 NOTES
+
+You will usually want to call $repo->get_commit($sha1) instead of
+instantiating this class directly; see L<Git::Repo>.
+
+=cut

Ahhh, O.K.

Why not $repo->commit($sha1)?

Quoted 6 lines
perl/Git/Object.pm new file +0 -0
new file mode 100644
index 0000000..a5126f9

perl/Git/Object.pm line 8

Quoted 8 lines
perl/Git/Object.pm new file +8 -0
@@ -0,0 +1,81 @@
+=head1 NAME
+
+Git::Object - Object-oriented interface to Git objects (base class).
+
+=head1 DESCRIPTION
+
+Git::Object is a base class that provides access to commit, tag and
+(unimplemented) tree objects.  See L<Git::Commit> and L<Git::Tag>.

Is it base class which represents types of objects in git repository:
commits, tags, trees and blobs? Or just a class which represent headers+payload objects, i.e. commits and tags?

perl/Git/Object.pm line 11

Quoted 2 lines
perl/Git/Object.pm new file +2 -0
+Objects are loaded lazily, and hence instantiation is free.  Objects
+stringify to their SHA1s.

This should probably be repeated in documentation for Git::Commit and Git::Tree (possibly linking to Git::Object(3pm) manpage), to be more self sufficient.

perl/Git/Object.pm line 28

Quoted 2 lines
perl/Git/Object.pm new file +2 -0
+use overload
+    '""' => \&stringify;

Nice.

perl/Git/Object.pm line 32

Quoted 3 lines
perl/Git/Object.pm new file +3 -0
+# Hash indices:
+use constant _REPO => 'R';
+use constant _SHA1 => 'H';

Again: Is it necessary?

perl/Git/Object.pm line 66

Quoted 4 lines
perl/Git/Object.pm new file +4 -0
+=head1 METHODS
+=item Git::Object->new($repo, $sha1)
+=item $obj->repo
+=item $obj->sha1

Why those methods are IIRC repeated in Git::Commit, instead of being inherited?

I wonder if Git::Object should provide $obj->id alias to $obj->sha1...

perl/Git/Repo.pm line 23

Quoted 7 lines
perl/Git/Repo.pm new file +3 -0
new file mode 100644
index 0000000..5a4c7e0
 
+use strict;
+use warnings;
+use 5.006002;

Why is this "use 5.006002" for?

By the way here you (correctly) intersperse code and its documentation, contrary to some other files in this patch.

Quoted 5 lines
perl/Git/Repo.pm new file +5 -0
+our @EXPORT = qw();
+our @EXPORT_OK = qw(assert_sha1 assert_opts);
+
+# Auxiliary subroutines
+

perl/Git/Repo.pm line 50

Quoted 10 lines
perl/Git/Repo.pm new file +10 -0
+sub assert_opts {
+	die "must have an even number of arguments for named options"
+	    unless $#_ % 2;
+}
+
+sub assert_sha1 {
+	my $sha1 = shift;
+	die "'$sha1' is not a SHA1 (need to use get_sha1?)"
+	    unless $sha1 && $sha1 =~ /^[a-f0-9]{40}$/;
+}

Hmmm... exporting auxiliary subroutines?

perl/Git/Repo.pm line 71

Quoted 13 lines

+=item $repo = Git::Repo->new(%opts)

perl/Git/Repo.pm new file +12 -0
+
+Return a new Git::Repo object.  The following options are supported:
+
+=over
+
+=item 'directory'
+
+The directory of the repository (mandatory).
+
+Note that this option is working-copy agnostic; you need to
+instantiate it with the working copy's .git directory as the
+'directory' option.

Wouldn't it be better to allow the same discovery of '.git' directory as other git command do, and leave 'git_dir' to set directly path to repository itself?

perl/Git/Repo.pm line 75

Quoted 3 lines

+=item 'git_binary'

perl/Git/Repo.pm new file +2 -0
+
+The name or full path of the git binary (default: 'git').

Probably should be Git::Cmd or Git object, instead. Well, 'git_binary' could set this up indirectly, though...

Quoted 5 lines
perl/Git/Repo.pm new file +5 -0
+
+=back
+
+Calling this method is free, since it does not check whether the
+repository exists.  Trying to access the repository through one of the

perl/Git/Repo.pm line 97

Quoted 10 lines
perl/Git/Repo.pm new file +9 -0
+instance methods will fail if it doesn't exist though.
 
+sub new {
+	my $class = shift;
+	assert_opts(@_);
+	my $self = {@_};
+	bless $self, $class;
+	die 'no directory given' unless $self->{directory};
+	return $self;
+}

Not even rudimentary check: if directory exists, if it looks like git repository?

perl/Git/Repo.pm line 102

Quoted 5 lines
perl/Git/Repo.pm new file +5 -0
+
+=item $repo->repo_dir
+
+Return the directory of the repository (.../.git in case of a working
+copy).

I think $repo->git_dir (perhaps in addition to above) would be better name, as it is already established among git commands.

Quoted 4 lines

+=item $repo->version

perl/Git/Repo.pm new file +3 -0
+
+Return the output of 'git --version', with /^git version / stripped.
+

perl/Git/Repo.pm line 133

Quoted 10 lines
perl/Git/Repo.pm new file +10 -0
+This method does not require the repository to exist.
+
+=cut
+
+sub version{
+	my $self = shift;
+	chomp(my $version = $self->cmd_output(cmd => ['--version']));
+	$version =~ s/^git version //;  # be permissive if this does not match
+	return $version;
+}

We could rely instead on embedded (during build) version string... or leave _that_ to $Git::Repo::VERSION / $Git::VERSION.

perl/Git/Repo.pm line 142

Quoted 7 lines

+=back

perl/Git/Repo.pm new file +6 -0
+
+=head2 Calling the Git binary
+
+=over
+
+=item $repo->cmd_output(%opts)

Please do remember that there are git commands which do not need access to git repository, for example git-ls-remote, git-clone, git-init, even git-config. So tying calling Git binary to _repository_ instance is a bad API; as a convenience (adding '--git-dir=<path to repo>' automatically) it is a good idea.

Quoted 10 lines
perl/Git/Repo.pm new file +10 -0
+Return the output of the given git command as a string, or as a list
+of lines in array context.  Valid options are:
+
+=over
+
+=item 'cmd'
+
+An arrayref of arguments to pass to git (mandatory).
+
+=item 'max_exit_code'

perl/Git/Repo.pm line 163

Quoted 10 lines
perl/Git/Repo.pm new file +10 -0
+
+Die if the exit code of the git binary is greater than
+C<max_exit_code> (default: 0).
+
+=back
+
+Example:
+
+    $output = $repo->cmd_output(cmd => ['show', 'HEAD'])
+

I think it would be easier on users if you provide two ways of calling this command: simple and advanced (see for example CGI methods):

	$output = $repo->cmd_output('show', 'HEAD');

or

	$output = $repo->cmd_output(['show', 'HEAD'],
	                            max_exit_code=>undef);

(which I think should mean: do not die if git command dies).

perl/Git/Repo.pm line 167

Quoted 4 lines

+=cut

perl/Git/Repo.pm new file +3 -0
+
+# To do: According to Git.pm, this might not work with ActiveState
+# Perl on Win 32.  Need to check or wait for reports.

Why not copy code from Git.pm, then?

perl/Git/Repo.pm line 197

Quoted 4 lines

+=item $repo->get_bidi_pipe(%opts)

perl/Git/Repo.pm new file +3 -0
+
+Open a new bidirectional pipe and return its STDIN and STDOUT file
+handles.  Valid options are:

What about returning context, as it was done in Git.pm?

perl/Git/Repo.pm line 151

Quoted 6 lines
perl/Git/Repo.pm new file +6 -0
+
+=over
+
+=item 'cmd'
+
+An arrayref of arguments to pass to git (mandatory).

Same comment as for cmd_output.

perl/Git/Repo.pm line 208

Quoted 4 lines

+=item 'reuse'

perl/Git/Repo.pm new file +3 -0
+
+If true, reuse a previously opened pipe with the same command line and
+whose C<reuse> option was true (default: false).

What is this for? Can you show example usage of this feature? Or is it a bit of overengineering, perhaps?

You didn't provide equivalents for the rest of command related methods from Git.pm, even as simple wrappers around other methods:
command_oneline, command_output_pipe, command_input_pipe; you do error management in different way, so I think you don't need command.*close methods (note that most of errors, beside not finding git binary and such, are found on close, not on open).

Quoted 9 lines

+=back

perl/Git/Repo.pm new file +8 -0
+
+=head2 Inspecting the Repository
+
+=over
+
+=item $repo->get_sha1($extended_object_identifier)
+
+Look up the object identified by $extended_object_identifier and

perl/Git/Repo.pm line 260

Quoted 10 lines
perl/Git/Repo.pm new file +10 -0
+return its SHA1 hash in scalar context or its ($sha1, $type, $size) in
+list context, or undef or () if the lookup failed, where $type is one
+of 'tag', 'commit', 'tree', or 'blob'.
+
+See L<git-rev-parse(1)>, section "Specifying Revisions", for the
+syntax of the $extended_object_identifier string.
+
+Note that even if you pass a SHA1 hash, its existence is still
+checked, and this method returns undef or () if it doesn't exist in
+the repository.

I think I'd rather allow extended SHA1 syntax in Git::Commit and Git::Tag constructors; it is one call to git command less (I think).

Nevertheless providing equivalent of git-rev-parse (the --revs part) is, I think, a good idea.

perl/Git/Repo.pm line 269

Quoted 8 lines

+=cut

perl/Git/Repo.pm new file +7 -0
+
+sub get_sha1 {
+	my ($self, $object_id) = @_;
+	die 'no object identifier given' unless $object_id;
+	die 'object identifier must not contain newlines' if $object_id =~ /\n/;
+	my ($in, $out) = $self->get_bidi_pipe(
+		cmd => ['cat-file','--batch-check'], reuse => 1);

Ahhh... here I can see what 'reuse => 1' means, and when it is useful. But doesn't it make sense _only_ for _bi-directional pipe_? Are you sure that you wouldn't get deadlock?

perl/Git/Repo.pm line 288

Quoted 7 lines

+=item $repo->cat_file($sha1)

perl/Git/Repo.pm new file +6 -0
+
+Return the ($type, $content) of the object identified by $sha1, or die
+if no such object exists in the repository.
+
+Note that you may want to use the higher-level methods get_commit and
+get_tag instead.

Shouldn't it be $repo->get_object($sha1) instead? You don't need to repeat strange choice of git-cat-file name of command in git, which is "hysterical raisin"

perl/Git/Repo.pm line 295

Quoted 6 lines

+=cut

perl/Git/Repo.pm new file +5 -0
+
+# Possible to-do items: Add optional $file_handle parameter.  Guard
+# against getting huge blobs back when we don't expect it (for
+# instance, we could limit the size and send SIGPIPE to git if we get
+# a blob that is too large).

If I remember correctly you do implement something like that (streamed output) in gitweb patch.

perl/Git/Repo.pm line 327

Quoted 5 lines
+=item $repo->get_commit($commit_sha1)
+=item $repo->get_tag($tag_sha1)
perl/Git/Repo.pm new file +3 -0
+
+Return a new L<Git::Tag> instance referring to the tag object with SHA1
+$tag_sha1.

Probably should mention lazy evaluation, too.

By the way, for gitweb you would need (for performance and for rewritten parents) also get_log / get_commits / get_commits_list

perl/Git/Repo.pm line 341

Quoted 5 lines

+=item $repo->get_path($tree_sha1, $file_sha1)

perl/Git/Repo.pm new file +4 -0
+
+Return the path of the tree or blob identified by $file_sha1 in the
+tree identified by $tree_sha1, or undef if the tree or blob does not
+exist in the given tree.

Note that it is *not* evaluated lazily. Probably should go thru Git::Tree when it gets implemented.

perl/Git/Repo.pm line 355

Quoted 11 lines
perl/Git/Repo.pm new file +11 -0
+sub get_path {
+	my ($self, $tree, $file_sha1) = @_;
+	assert_sha1($tree, $file_sha1);
+	my @lines = split "\n", $self->cmd_output(cmd => ['ls-tree', '-r', '-t', $tree]);
+	for (@lines) {
+		if (/^[0-9]+ [a-z]+ $file_sha1\t(.+)$/) {
+			return $1;
+		}
+	}
+	return undef;
+}

You would have troubles with filename quoting! (Probably should be an option to get_path to get unquoted vs quoted filename).

Quoted 5 lines

+=item $repo->get_refs

perl/Git/Repo.pm new file +4 -0
+
+=item $repo->get_refs($pattern)
+
+Return an arrayref of [$sha1, $object_type, $ref_name] triples.  If

perl/Git/Repo.pm line 373

Quoted 10 lines
perl/Git/Repo.pm new file +9 -0
+$pattern is given, only refs matching the pattern are returned; see
+L<git-for-each-ref(1)> for details.
 
+sub get_refs {
+	my ($self, $pattern) = @_;
+
+	return [ map [ split ], split("\n", $self->cmd_output(
+			 cmd => [ 'for-each-ref',
+				  defined $pattern ? $pattern : () ])) ];
+}

I think you would need more complete interface to git-for-each-ref and/or git-show-refs.

By the way, why split("\n", ...) instead of ensuring $\ = "\n" and (en)forcing list context?

perl/Git/Repo.pm line 379

Quoted 5 lines

+=item $repo->name_rev($committish_sha1, $tags_only = 0)

perl/Git/Repo.pm new file +4 -0
+
+Return a symbolic name for the commit identified by $committish_sha1,
+or undef if no name can be found; see L<git-name-rev(1)> for details.
+If $tags_only is true, no branch names are used to name the commit.

Why name_rev, and no describe?

Quoted 9 lines
perl/Git/RepoRoot.pm new file +3 -0
new file mode 100644
index 0000000..12e1836
@@ -0,0 +1,103 @@
+=head1 NAME
+
+Git::RepoRoot - A factory class representing a root directory

perl/Git/RepoRoot.pm line 13

Quoted 10 lines
perl/Git/RepoRoot.pm new file +10 -0
+containing Git repositories.
+
+=head1 DESCRIPTION
+
+Git::RepoRoot is a factory class to create L<Git::Repo> instances that
+are located under a common root directory.  It also allows for
+specifying options that all Git::Repo instances will be created with.
+
+Using Git::RepoRoot to create Git::Repo instances is entirely
+optional, but can be more convenient than instantiating them directly.

Nice. Does Git::RepoRoot provides way to scan for repositories under commin $projectroot?

perl/Git/RepoRoot.pm line 46

Quoted 5 lines
perl/Git/RepoRoot.pm new file +5 -0
+=over
+
+=item 'directory'
+
+The directory holding all repositories.

Probably should be named 'root', or 'reporoot', or something...

perl/Git/RepoRoot.pm line 55

Quoted 4 lines
perl/Git/RepoRoot.pm new file +4 -0
+Example:
+
+    $repo_root = Git::RepoRoot->new(directory => '/pub/git',
+                                    git_binary => '/usr/bin/git'q);
                                                                   ^

It looks like typo ------------------------------------------------|

perl/Git/Tag.pm line 20

Quoted 10 lines
perl/Git/Tag.pm new file +6 -0
new file mode 100644
index 0000000..4e0549b
 
+use constant _MESSAGE => 'M';
+use constant _ENCODING => 'E';
+use constant _TAGGER => 'A';
+use constant _TAG => 'T';
+use constant _TYPE => 'Y';
+use constant _OBJECT => 'O';

Again.

Quoted 1 line

+# Keep documentation in one place to save space.

Again.

Tag object format is meant to be extensible; for example (unfortunately abandoned) git-notes implementation added 'keyword' (IIRC) header to tag object format.

perl/Git/Tag.pm line 58

Quoted 3 lines

+=item $tag->tagger

perl/Git/Tag.pm new file +2 -0
+
+Return the tagger string of this tag object.

We would probably want some way to extract name, email, epoch/date (and a way to convert epoch+timezone to RFC or ISO format), timezone.

perl/Git/Tag.pm line 66

Quoted 4 lines
+=item $tag->message
+=item $tag->encoding
perl/Git/Tag.pm new file +2 -0
+
+Return the encoding header of the tag object.

Does tag provide 'encoding' header?

Should (for completeness) Git::Tag provide $tag->validate() method?

perl/Git/Tag.pm line 150

Quoted 4 lines

+=head1 NOTES

perl/Git/Tag.pm new file +3 -0
+
+You will usually want to call $repo->get_tag($sha1) instead of
+instantiating this class directly; see L<Git::Repo>.

Good, although this could be put in description of new() instead...

Quoted 6 lines
t/t9710/test.pl new file +0 -0
new file mode 100755
index 0000000..188abba

t/t9710/test.pl line 9

Quoted 10 lines
t/t9710/test.pl new file +9 -0
@@ -0,0 +1,165 @@
+#!/usr/bin/perl
+use lib (split(/:/, $ENV{GITPERLLIB}));
+
+use warnings;
+use strict;
+
+use Test::More qw(no_plan);
 
+use Test::Exception;
+use Carp::Always;

Are those necessary? If yes, shouldn't they be checked before running this test script?

t/t9710/test.pl line 26

Quoted 8 lines
t/t9710/test.pl new file +8 -0
+our $old_stderr;
+sub discard_stderr {
+	open our $old_stderr, ">&", STDERR or die "cannot save STDERR";
+	close STDERR;
+}
+sub restore_stderr {
+	open STDERR, ">&", $old_stderr or die "cannot restore STDERR";
+}

Nice.

[I haven't examined test in detail].

Signature · 2 lines
Jakub Narebski
Poland
#32 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 1 line · Jakub Narebski

[Here is promised patch review]

Thanks. A new patch series will follow today (hopefully).

For brevity, I'm incorporating all your suggested changes unless noted otherwise.

Quoted 6 lines · Lea Wiemann
Quoted 3 lines
 create mode 100644 perl/Git/Commit.pm
 create mode 100644 perl/Git/Object.pm

[...]

Does splitting into many small files is really necessary?

I think it's better. People shouldn't get an error message if they write "use Git::Commit". Also we cannot split later if we don't split now since people would write "use Git::Repo" and then access Git::Commit; subsequently splitting the API would (might?) cause breakage, I believe.

Quoted 1 line

Good that you provided test suite.

;-)

Quoted 2 lines

You can add new headers, and old git binary should simply ignore unknown headers.

Unknown headers are now ignored.

Quoted 5 lines
Quoted 1 line

+# Keep documentation in one place to save space.

Embedded PODs in Perl modules serve as sort of literate programming, serving to describe code (technical/usage documentation) in addition to comments in code.

Yeah, but this part is only a bunch of trivial accessor methods. If the module grows and the documentation needs to be split, it can be done later. No need to be purist here. ;-) Also ...

Quoted 2 lines

[The fact that documentation is separated from code means that
I cannot easily tell and write if code match documentation]

Several of the methods actually only exist in the Git::Object base class. I still documented them in the Commit and Tag modules since having to look up methods in base class documentation can be a tad annoying, especially if the base class is never used by users of the API.

Quoted 5 lines

Review

Quoted 3 lines

+=item $commit = Git::Commit->new($repo, $sha1)

(quoted diff) +2 -0
+
+Calls to this method are free, [...]

The technique you use has a name, and it is (IIUC) "lazy evaluation".

You understand correctly; I'd call it "lazy loading" though. I've added that term for clarity.

Quoted 4 lines

By the way, wouldn't it be better to make this method internal, and use instead the following code to generate Git::Commit object

  $commit = $repo->commit($sha1);

I think the constructor shouldn't be internal (= underscore-prefixed, you mean?), since the Commit/Tag APIs are usable on their own.

Quoted 4 lines
Quoted 2 lines
+=item $obj->repo
+=item $obj->sha1

Those do not access the repository, isn't it?

No, they don't. (Clarified that in the documentation.)

Quoted 4 lines
Quoted 2 lines

+=item $commit->parents
+Return a list of zero or more parent commit objects.

Array or arrayref?

Array. I've replaced "list" with "array" in case it helps clarity.

Quoted 2 lines

There is little inconsistency that tree object is (from the lack of Git::Tree object) returned as SHA1, and parents as objects.

If we add a Git::Tree API, the Git::Tree objects will stringify to their SHA1s, so we shouldn't have compatibility issues. I've changed the documentation of $commit->tree to this:

"Return an object that stringifies to the SHA1 of the tree that this commit object refers to. (Currently this returns an actual string, but don't rely on it.)"

Quoted 3 lines

NOTE that element of list of revisions has in addition to that also _effective_ parents in the event of history simplification, for example for 'history' view, or when using '--first-parent' extra option.

Yes, but we don't actually care about those effective parents for the purpose of the Git::Commit API. IOW, the effective parent should be managed by the code that created a list of revisions, not by the Git::Commit API.

Quoted 6 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+Return the author string of this commit object. [...]
+Return the committer string of this commit object.

It returns whole value of 'author' and 'committer' headers, not something extracted from it (into name, email, epoch and timezone), isn't it?

Yup; that's why I wrote "{author,committer} *string*". ;)

Quoted 5 lines

Review

Quoted 3 lines

+=item $commit->message

(quoted diff) +2 -0
+
+Return the undecoded commit message of this commit object.

Just raw data?

Yes, just raw data. Decoding is too tricky (i.e. not guaranteed to work) to just add a simple method to the API; IOW, it needs error handling and perhaps fallback encodings.

Quoted 2 lines

NOTE that for element of list of revisions (as returned by git-rev-list or git-log) would probably have commit message decoded to UTF-8 by git.

Yes, but the API doesn't use any of those commands internally, if that's what you're worried about.

Quoted 5 lines

Review

Quoted 3 lines

+=item $commit->encoding

(quoted diff) +2 -0
+
+Return the encoding header of the commit object.

Normalized?

No. (Otherwise I'd have written that ;-).)

Quoted 5 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+sub author { [...]
+	$self->{_AUTHOR()} or ''; }

Nowhere in documentation is mentioned that you use empty value for no author or no committer (isn't commit object invalid then?).

Yes, I'd believe so. I basically wanted to make sure that those methods always return a string; do you think that this is a bad idea?

Quoted 8 lines

perl/Git/Commit.pm line 126

Quoted 3 lines
perl/Git/Commit.pm new file +3 -0
+	if (!defined $raw_text) {
+		# Retrieve from the repository.
+		(my $type, $raw_text) = $self->repo->cat_file($sha1);

The above makes Git::Commit good solution for gitweb's 'commit' and 'commitdiff' views, but bad solution for 'log', 'shortlog', 'history' and 'rss'/'atom' views, where you would need to many command invocations, which is very bad on OS with slow fork.

$repo->cat_file (now renamed to get_object) actually doesn't fork but uses a pipe (cat-file --batch); I don't think it should be a performance issue.

Quoted 4 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	(my $header, $self->{_MESSAGE()}) = split "\n\n", $raw_text, 2;

Why not simply parse headers, then slurp rest of object into 'message' field?

Because we happen to get the raw text in a single string from the Repo API. (It shouldn't be a performance/memory issue for Commit or Tag objects at all. ;-))

Quoted 1 line

IMHO you should not die at unrecognized header,

Yes, changed, but ...

Quoted 2 lines

but simply store it under its name (and make available using ->header('<NAME>')).

... let's not complicate the API unnecessarily. If a new header pops up we can immediately add it to the Commit/Tag API.

Quoted 2 lines

Nevertheless I think you can check for header name validation (there are some restrictions on header names, isn't it?).

I don't really care, and it's too much work to come up with a test case for this. ;-) If the repository is borked to the point of invalid header names, it's fine for Git::Commit to behave undefinedly.

Quoted 4 lines

perl/Git/Commit.pm line 158

Quoted 2 lines
perl/Git/Commit.pm new file +2 -0
+You will usually want to call $repo->get_commit($sha1) instead of
+instantiating this class directly; see L<Git::Repo>.

Why not $repo->commit($sha1)?

Intuition. ;-) I think I'd read $repo->commit as "the commit of the repository", akin to $commit->tree, which doesn't make sense here.

Quoted 5 lines
Quoted 1 line

+Git::Object - Object-oriented interface to Git objects (base class).

Is it base class which represents types of objects in git repository:
commits, tags, trees and blobs? Or just a class which represent headers+payload objects, i.e. commits and tags?

It could represent any object, though I don't see a need for Git::Blob right now (though it's possible that it's needed later).

Quoted 1 line

I wonder if Git::Object should provide $obj->id alias to $obj->sha1...

Why? I don't think it's necessary.

Quoted 3 lines
Quoted 1 line

+use 5.006002;

Why is this "use 5.006002" for?

It signifies that this module won't run with Perl <5.6.2. I've had to bump it to 5.008 (Perl 5.8); more about that in the message announcing the next version of the patch series.

Quoted 3 lines

Wouldn't it be better to allow the same discovery of '.git' directory as other git command do, and leave 'git_dir' to set directly path to repository itself?

I wouldn't use discovery magic here, at least for now, since it's non-trivial to get it right (and it interacts with possible future extensions of the API, like Git::WC). Such a feature can be implemented if/when it's needed.

Quoted 4 lines
Quoted 2 lines

+=item 'git_binary'
+The name or full path of the git binary (default: 'git').

Probably should be Git::Cmd or Git object, instead.

I don't think something Git::Cmd is a good idea (as I pointed out in my reply to Petr, <[email protected]>), or at least it shouldn't be implemented as part of this patch series. This method is really just supposed to return an argument for exec*p, nothing more.

Quoted 6 lines

perl/Git/Repo.pm line 81

Quoted 3 lines
perl/Git/Repo.pm new file +3 -0
+Calling this method is free, since it does not check whether the
+repository exists.  Trying to access the repository through one of the
+instance methods will fail if it doesn't exist though.

Not even rudimentary check: if directory exists, if it looks like git repository?

No. It's not helpful for error handling (which should happen in the caller), and it's not helpful for bug detection (since it will die on the first access to the repo anyway), but it causes performance penalty that can be significant for programs like gitweb.

Quoted 6 lines

perl/Git/Repo.pm line 102

Quoted 3 lines

+=item $repo->repo_dir

perl/Git/Repo.pm new file +2 -0
+Return the directory of the repository (.../.git in case of a working
+copy).

I think $repo->git_dir (perhaps in addition to above) would be better name, as it is already established among git commands.

I find repo_dir somewhat clearer (and I don't like having more than one name per method). We're not trying to mimic or wrap standard git commands here, anyway.

Quoted 3 lines
Quoted 1 line

+sub version{

We could rely instead on embedded (during build) version string...

Yup; it's been deleted anyway.

Quoted 4 lines
Quoted 1 line

+=item $repo->cmd_output(%opts)

Please do remember that there are git commands which do not need access to git repository,

As I wrote in my reply to Petr, Git::Repo is not trying to be a wrapper around git binaries, so this method really shouldn't be part of the official API -- it's just auxiliary; I'll underscore-prefix it.

Quoted 2 lines

I think it would be easier on users if you provide two ways of calling [cmd_output]: simple and advanced

It's not part of the API anyway, so no need for complicated calling conventions, IMO.

Quoted 4 lines

perl/Git/Repo.pm line 167

Quoted 2 lines
perl/Git/Repo.pm new file +2 -0
+# To do: According to Git.pm, this might not work with ActiveState
+# Perl on Win 32.  Need to check or wait for reports.

Why not copy code from Git.pm, then?

Apart from the fact that I don't do cargo-cult programming? ;-) Git.pm forks, whereas Git::Repo uses open, '-|', so it's actually different (and it's not possible to copy the code).

Quoted 6 lines

perl/Git/Repo.pm line 197

Quoted 4 lines

+=item $repo->get_bidi_pipe(%opts)

perl/Git/Repo.pm new file +3 -0
+
+Open a new bidirectional pipe and return its STDIN and STDOUT file
+handles.  Valid options are:

What about returning context, as it was done in Git.pm?

Why, what should it do? This just opens a pipe, nothing more. No need for introducing complicated concepts.

Quoted 6 lines

perl/Git/Repo.pm line 208

Quoted 4 lines

+=item 'reuse'

perl/Git/Repo.pm new file +3 -0
+
+If true, reuse a previously opened pipe with the same command line and
+whose C<reuse> option was true (default: false).

What is this for? Can you show example usage of this feature?

You found it below. :-) (If you had snipped this, I wouldn't have spent time finding and pasting an example. ;-))

Quoted 3 lines

I think I'd rather allow extended SHA1 syntax in Git::Commit and Git::Tag constructors; it is one call to git command less (I think).

I wouldn't -- see my blurb about error handling at the top of my reply to Petr (<[email protected]>). You're not supposed to pass anything that you didn't get from get_sha1 into Git::Commit or Git::Tag constructors, or your error handling is invariably broken.

Quoted 6 lines

perl/Git/Repo.pm line 269

Quoted 2 lines
perl/Git/Repo.pm new file +2 -0
+	my ($in, $out) = $self->get_bidi_pipe(
+		cmd => ['cat-file','--batch-check'], reuse => 1);

Ahhh... here I can see what 'reuse => 1' means, and when it is useful. But doesn't it make sense _only_ for _bi-directional pipe_? Are you sure that you wouldn't get deadlock?

Yes to both questions. :-)

Quoted 5 lines
Quoted 2 lines

[get_object:]
+# Possible to-do items: Add optional $file_handle parameter.

If I remember correctly you do implement something like that (streamed output) in gitweb patch.

Yes, but only for generic command calls, and with a somewhat unpleasant (cache-specific) interface. It'd need a bit of work for the API.

Quoted 2 lines

By the way, for gitweb you would need (for performance and for rewritten parents) also get_log / get_commits / get_commits_list

No. ;-) Doing fine without those.

Quoted 1 line
Quoted 1 line

+=item $repo->get_path($tree_sha1, $file_sha1)

This one has been removed as well since it would belong into Git::Tree.

Quoted 3 lines

perl/Git/Repo.pm line 348

Quoted 1 line
perl/Git/Repo.pm new file +1 -0
+	my @lines = split "\n", $self->cmd_output(cmd => ['ls-tree', '-r', '-t', $tree]);

You would have troubles with filename quoting!

Thanks. Since get_path isn't in the Git::Repo API anymore and gitweb's get_path subroutine didn't handle quoted filenames even before my patches, I'll only mark it as TODO for now. ;-)

Quoted 1 line
Quoted 1 line

+sub get_refs {

This has been removed as well, since it's not used and the interface would need work.

Quoted 3 lines
Quoted 1 line

+=item $repo->name_rev($committish_sha1, $tags_only = 0)

Why name_rev, and no describe?

Feel free to add it. ;-) (It might take some work to come up with a decent interface for that method.)

Quoted 2 lines

Does Git::RepoRoot provides way to scan for repositories under commin $projectroot?

No. If it's needed it could reasonably be extracted from gitweb though (I think).

Quoted 4 lines
Quoted 1 line

+Return the tagger string of this tag object.

We would probably want some way to extract name, email, epoch/date (and a way to convert epoch+timezone to RFC or ISO format), timezone.

Yeah. At some point. ;-)

Quoted 4 lines
Quoted 2 lines

+=item $tag->encoding
+Return the encoding header of the tag object.

Does tag provide 'encoding' header?

Sure, for the message.

Quoted 1 line

Should (for completeness) Git::Tag provide $tag->validate() method?

No, since 'validate' sounds like it would have to do error handling.

If you mean that this should check if the object exists (and has the advertised type), the user of the API should test for "defined $tag->repo->get_sha1($tag->object)" or somesuch and do error handling themselves.

-- Lea

#33 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 13 lines · Lea Wiemann
Quoted 7 lines · Jakub Narebski
Quoted 1 line · Lea Wiemann

+# Keep documentation in one place to save space.

Embedded PODs in Perl modules serve as sort of literate programming, serving to describe code (technical/usage documentation) in addition to comments in code.

Yeah, but this part is only a bunch of trivial accessor methods. If the module grows and the documentation needs to be split, it can be done later. No need to be purist here. ;-) Also ...

Quoted 8 lines
Quoted 2 lines

[The fact that documentation is separated from code means that
I cannot easily tell and write if code match documentation]

Several of the methods actually only exist in the Git::Object base class. I still documented them in the Commit and Tag modules since having to look up methods in base class documentation can be a tad annoying, especially if the base class is never used by users of the API.

I was wondering (but forgot to ask) why for some files you had continuous block of documentation, and in others documentation interspersed with documented code.

I think that providing documentation for all methods for "front-end" class, even for those that are implemented in [abstract] base class, is a very good idea. Better to have everything in one place, or at least in "porcelain" documentation.

Quoted 9 lines
Quoted 4 lines

NOTE that element of list of revisions has in addition to that also _effective_ parents in the event of history simplification, for example for 'history' view, or when using '--first-parent' extra option.

Yes, but we don't actually care about those effective parents for the purpose of the Git::Commit API. IOW, the effective parent should be managed by the code that created a list of revisions, not by the Git::Commit API.

True. Or to be more exact in a Git::Revlist (or somesuch) container class.

Quoted 8 lines
Quoted 6 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+Return the author string of this commit object. [...]
+Return the committer string of this commit object.

It returns whole value of 'author' and 'committer' headers, not something extracted from it (into name, email, epoch and timezone), isn't it?

Yup; that's why I wrote "{author,committer} *string*". ;)

We would probably want _in the future_ to return some object wrapper, which stringifies to value of author and committer headers (to author and committer string), but allows to extract (and format) parts of it, for example

$commit->author->name
$commit->author->email
$commit->author->date(format => RFC2822)

or perhaps

$commit->author{'email'}

Quoted 9 lines
Quoted 5 lines

Review

Quoted 3 lines

+=item $commit->message

(quoted diff) +2 -0
+
+Return the undecoded commit message of this commit object.

Just raw data?

Yes, just raw data. Decoding is too tricky (i.e. not guaranteed to work) to just add a simple method to the API; IOW, it needs error handling and perhaps fallback encodings.

I'd rather then have _git_ convert it to UTF=8 for us (using --encoding=<encoding> option to git-log/git-rev-list), see below on using git-log.

Quoted 6 lines
Quoted 3 lines

NOTE that for element of list of revisions (as returned by git-rev-list or git-log) would probably have commit message decoded to UTF-8 by git.

Yes, but the API doesn't use any of those commands internally, if that's what you're worried about.

I'm not worried about; I think using git-log would be better (see also below)

Quoted 8 lines
Quoted 5 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+sub author { [...]
+	$self->{_AUTHOR()} or ''; }

Nowhere in documentation is mentioned that you use empty value for no author or no committer (isn't commit object invalid then?).

Yes, I'd believe so. I basically wanted to make sure that those methods always return a string; do you think that this is a bad idea?

It don't know if it is good or bad idea, but you should have mentioned it in the documentation.

[HERE IS MAIN PART OF THIS RESPONSE]

Quoted 9 lines
Quoted 8 lines

perl/Git/Commit.pm line 126

Quoted 3 lines
perl/Git/Commit.pm new file +3 -0
+	if (!defined $raw_text) {
+		# Retrieve from the repository.
+		(my $type, $raw_text) = $self->repo->cat_file($sha1);

The above makes Git::Commit good solution for gitweb's 'commit' and 'commitdiff' views, but bad solution for 'log', 'shortlog', 'history' and 'rss'/'atom' views, where you would need to many command invocations, which is very bad on OS with slow fork.

Quoted 3 lines

$repo->cat_file (now renamed to get_object) actually doesn't fork but uses a pipe (cat-file --batch); I don't think it should be a performance issue.

It is (much) better than forking git-cat-file for each commit shown on the list; nevertheless I think that it would be better to use git-log to generate list (or Git::Revlist) of Git::Commit objects. It is one fork less, but what more important you don't have to access repository twice for the very same objects.

Let me elaborate: if I understand correctly for log-like views you propose to first run simple git-rev-list with appropriate starting point and limiters (--skip, --max-count, -- <pathname>), perhaps using '--parents' option to get parents in simplified/rewritten history, which would traverse history getting commit objects, but outputting only fragments of info, then feed list of revisions (perhaps via cache, i.e. excluding objects which are in cache) to 'git cat-file --batch' open two-directional pipeline.

What I propose instead is to provide alternate method to fully instantiate Git::Commit object (in addition to ->_load), which would fill fields by parsing git-log / git-rev-list --headers output (what gitweb currently does in parse_commits).

On the other hand... "git cat-file --batch" should have commits to be accessed in filesystem cache, which means in memory; but it is possible that they wouldn't be in cache because of I/O pressure (git-rev-list and git-cat-file are separate processes). And checking if object is in cache can be simpler... if less effective. If you generate Git::Commit objects via parsing git-log / git-rev-list output, then you can limit history further by excluding starting points from cache.

[END OF MAIN PART]

Quoted 7 lines
Quoted 3 lines
Quoted 1 line

+use 5.006002;

Why is this "use 5.006002" for?

It signifies that this module won't run with Perl <5.6.2. I've had to bump it to 5.008 (Perl 5.8); more about that in the message announcing the next version of the patch series.

I was not asking what this mean, but why do you need to set up lower bound on Perl version. What feature pre 5.6.2 Perl lacks...

Requiring 5.8 is bad. What feature pre 5.8 Perl lacks, that you absolutely cannot go without it? There will be complaints.

Quoted 10 lines
Quoted 4 lines
Quoted 2 lines

+=item 'git_binary'
+The name or full path of the git binary (default: 'git').

Probably should be Git::Cmd or Git object, instead.

I don't think something Git::Cmd is a good idea (as I pointed out in my reply to Petr, <[email protected]>), or at least it shouldn't be implemented as part of this patch series. This method is really just supposed to return an argument for exec*p, nothing more.

I think that _not using_ Git::Cmd (or somesuch) API results in botched, horrible API like (in the 3/3 patch in this series):

  our $git_version = $repo_root->repo(directory => 'dummy')->version;

Aaaaaargh! My eyes!

  out $git_version = Git->version;

(Unless it is not needed any longer, or not used any longer; if it is so, then perhaps implementing Git::Cmd as generic wrapper around git commands, hiding for example ActivePerl hack, could be left for later).

Quoted 8 lines
Quoted 4 lines
Quoted 1 line

+=item $repo->cmd_output(%opts)

Please do remember that there are git commands which do not need access to git repository,

As I wrote in my reply to Petr, Git::Repo is not trying to be a wrapper around git binaries, so this method really shouldn't be part of the official API -- it's just auxiliary; I'll underscore-prefix it.

Just a question: was this reply only to him, or to all?

I think that $repo->cmd_output(%opts) is a great shortcut for invoking Git->cmd_output with '--git-dir=<repo>' added automatically. So it should be left, but perhaps under different implementation.

Quoted 8 lines
Quoted 4 lines

perl/Git/Repo.pm line 167

Quoted 2 lines
perl/Git/Repo.pm new file +2 -0
+# To do: According to Git.pm, this might not work with ActiveState
+# Perl on Win 32.  Need to check or wait for reports.

Why not copy code from Git.pm, then?

Apart from the fact that I don't do cargo-cult programming? ;-) Git.pm forks, whereas Git::Repo uses open, '-|', so it's actually different (and it's not possible to copy the code).

Actually magic open, '-|' _does_ forks, only implicitely. So Git.pm does generate the same or almost the same code, but it work (around) with ActiveState Perl.

Quoted 8 lines
Quoted 3 lines

I think I'd rather allow extended SHA1 syntax in Git::Commit and Git::Tag constructors; it is one call to git command less (I think).

I wouldn't -- see my blurb about error handling at the top of my reply to Petr (<[email protected]>). You're not supposed to pass anything that you didn't get from get_sha1 into Git::Commit or Git::Tag constructors, or your error handling is invariably broken.

O.K.

I can understand this simpler, although less than optimal, and geared mainly towards gitweb needs.

Quoted 8 lines
Quoted 6 lines

perl/Git/Repo.pm line 269

Quoted 2 lines
perl/Git/Repo.pm new file +2 -0
+	my ($in, $out) = $self->get_bidi_pipe(
+		cmd => ['cat-file','--batch-check'], reuse => 1);

Ahhh... here I can see what 'reuse => 1' means, and when it is useful. But doesn't it make sense _only_ for _bi-directional pipe_? Are you sure that you wouldn't get deadlock?

Yes to both questions. :-)

Errr... "yes" to first question means that 'reuse' option makes sense _only_ for get_bidi_pipe? If so, why it is present in other commands?

Quoted 4 lines
Quoted 2 lines

By the way, for gitweb you would need (for performance and for rewritten parents) also get_log / get_commits / get_commits_list

No. ;-) Doing fine without those.

See above.

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

+=item $repo->name_rev($committish_sha1, $tags_only = 0)

Why name_rev, and no describe?

Feel free to add it. ;-) (It might take some work to come up with a decent interface for that method.)

Why do you _need_ name_rev, if you are not to include git-describe equivalent.

Quoted 7 lines
Quoted 5 lines
Quoted 1 line

+Return the tagger string of this tag object.

We would probably want some way to extract name, email, epoch/date (and a way to convert epoch+timezone to RFC or ISO format), timezone.

Yeah. At some point. ;-)

See above, in comment about Git::Commit.

Quoted 8 lines
Quoted 1 line

Should (for completeness) Git::Tag provide $tag->validate() method?

No, since 'validate' sounds like it would have to do error handling.

If you mean that this should check if the object exists (and has the advertised type), the user of the API should test for "defined $tag->repo->get_sha1($tag->object)" or somesuch and do error handling themselves.

I meant here equivalent of "git tag -v <tag>"

Signature · 2 lines
Jakub Narebski
Poland
#34 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 2 lines · Jakub Narebski

[$commit->author:] We would probably want _in the future_ to return some object wrapper, which stringifies to value of author and committer headers

Yup, good idea. They'll even stay strings, they'll just be blessed.

Quoted 4 lines
Quoted 1 line
Quoted 1 line
Quoted 1 line

+=item $commit->message

I'd rather then have _git_ convert it to UTF=8 for us (using --encoding=<encoding> option to git-log/git-rev-list)

Yeah, I guess the API should actually decode it. You wouldn't want to have the message in UTF-8 but in Unicode (I suggest you read man perlunitut if you haven't done so). We cannot have git do the decoding, since (apart from the fact that it doesn't smell right) it isn't guaranteed to emit valid UTF-8 (thanks Junio for the pointer):

Lea Wiemann: Does anyone know off the top of their heads how git handles
character decoding errors in commands like git log? [...]
Junio 'gitster' Hamano: silently punt and show the original unmolested. Junio 'gitster' Hamano: cf. pretty.c:pretty_print_commit()

So we're not guaranteed to be able to, in turn, decode git's output into actual characters since it might just be byte soup.

Hence, how about this fallback strategy:

1. Decode according to the encoding header.
2. Decode as UTF-8 (passing through byte soup is often equivalent to decoding UTF-8 since many terminals use UTF-8, and trying UTF-8 is reasonably safe).
3. Decode as Latin1.

(Not that the fallbacks will matter a lot in practice, I think.)

Quoted 5 lines

It is (much) better than forking git-cat-file for each commit shown on the list; nevertheless I think that it would be better to use git-log to generate list (or Git::Revlist) of Git::Commit objects. It is one fork less, but what more important you don't have to access repository twice for the very same objects.

You're confused; it's not one fork less, it's a write to a pipe less. (Pleeeease look at the code before you write something. It's there, in this very thread.) And I don't believe the "access the repository twice" thing is anywhere near an actual issue. To summarize, you're asking me to (a) write code and (presumably) (b) add something to the interface of a public API, based on some (most probably faulty) assumptions about performance? You should really read
<http://c2.com/cgi/wiki?PrematureOptimization>.

Quoted 4 lines

if I understand correctly for log-like views you propose to first run simple git-rev-list [...], then feed list of revisions (perhaps via cache, i.e. excluding objects which are in cache) to 'git cat-file --batch' open two-directional pipeline.

Yup, it's an option, though currently it's a single cached call to git log (or git rev-list).

Quoted 3 lines

What I propose instead is to provide alternate method to fully instantiate Git::Commit object (in addition to ->_load), which would fill fields by parsing git-log / git-rev-list --headers output

Yes, but this would need a method in the API, it's not an optimization that falls out for free. Cluttering an API for some obscure (= very doubtful) optimization? Bad Idea.(tm)

Quoted 3 lines

"git cat-file --batch" should have commits to be
accessed in filesystem cache, which means in memory; but it is possible that they wouldn't be in cache because of I/O pressure

No. Page cache turnover time is at least around 10 seconds (and that's under fairly artificial conditions), definitely not in the millisecond range.

Quoted 6 lines

I think that _not using_ Git::Cmd (or somesuch) API results in botched, horrible API
our $git_version = $repo_root->repo(directory => 'dummy')->version;
(Unless it is not needed any longer, or not used any longer; if it is so, then perhaps implementing Git::Cmd as generic wrapper around git commands, hiding for example ActivePerl hack, could be left for later).

It isn't used any longer -- I really suggest you read the whole thread before replying. ;-)

Quoted 3 lines
Quoted 1 line

As I wrote in my reply to Petr [...]

Just a question: was this reply only to him, or to all?

To all, otherwise I wouldn't have Cc'ed the list.

Quoted 7 lines
Quoted 4 lines

I wouldn't -- see my blurb about error handling at the top of my reply to Petr (<[email protected]>). You're not supposed to pass anything that you didn't get from get_sha1 into Git::Commit or Git::Tag constructors, or your error handling is invariably broken.

I can understand this simpler, although less than optimal, and geared mainly towards gitweb needs.

FTR, yes it is simpler, but no, it is not really geared toward gitweb needs, and it's definitely not "less than optimal" in the sense of being worse than the exception-based error handling Git.pm does. Trust on me on this one. ;-)

Quoted 2 lines

Errr... "yes" to first question means that 'reuse' option makes sense _only_ for get_bidi_pipe? If so, why it is present in other commands?

Yes, and no, it isn't present in other commands. (Hey, could you please check the code before posting? Really.)

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

Why name_rev, and no describe?

Feel free to add it. ;-) (It might take some work to come up with a decent interface for that method.)

Why do you _need_ name_rev, if you are not to include git-describe equivalent.

I needed it for gitweb. As I said, I'm not trying to create a complete API. A describe_rev (or so) method can be added later, if and when it's needed. (As I said, I don't think writing APIs without at least one use case is a good idea anyway.)

Quoted 3 lines
Quoted 1 line
Quoted 1 line

Should (for completeness) Git::Tag provide $tag->validate() method?

I meant here equivalent of "git tag -v <tag>"

I guess it could be added. As with describe_rev, I won't add it myself, in particular not as part of this patch series.

-- Lea

#35 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 10 lines · Lea Wiemann
Quoted 4 lines · Jakub Narebski
Quoted 1 line
Quoted 1 line
Quoted 1 line

+=item $commit->message

I'd rather then have _git_ convert it to UTF=8 for us (using --encoding=<encoding> option to git-log/git-rev-list)

Yeah, I guess the API should actually decode it. You wouldn't want to have the message in UTF-8 but in Unicode (I suggest you read man perlunitut if you haven't done so).

You mean perluniintro(1) here, isn't it?

Besides if decoding is done in Perl API, we can convert it simply to Perl internal form (which, IIUC, in modern Perl is UTF-8 and marked as such).

Quoted 3 lines

We cannot have git do the decoding,
since (apart from the fact that it doesn't smell right) it isn't guaranteed to emit valid UTF-8 [...]

Well, if that is the case then Perl API has to do conversion, that is the only sensible way.

Quoted 6 lines
Quoted 5 lines

It is (much) better than forking git-cat-file for each commit shown on the list; nevertheless I think that it would be better to use git-log to generate list (or Git::Revlist) of Git::Commit objects. It is one fork less, but what more important you don't have to access repository twice for the very same objects.

Quoted 8 lines

You're confused; it's not one fork less, it's a write to a pipe less. (Pleeeease look at the code before you write something. It's there, in this very thread.) And I don't believe the "access the repository twice" thing is anywhere near an actual issue. To summarize, you're asking me to (a) write code and (presumably) (b) add something to the interface of a public API, based on some (most probably faulty) assumptions about performance? You should really read
<http://c2.com/cgi/wiki?PrematureOptimization>.

Code is there, in gitweb, in parse_commits subroutine, or rather in parse_commit_text subroutine.

[cut]

But I can agree that possible (and possibly minuscule) performance improvement is not worth introducing new API and complicating (I think) gitweb code.

Quoted 9 lines
Quoted 6 lines

I think that _not using_ Git::Cmd (or somesuch) API results in botched, horrible API
our $git_version = $repo_root->repo(directory => 'dummy')->version;
(Unless it is not needed any longer, or not used any longer; if it is so, then perhaps implementing Git::Cmd as generic wrapper around git commands, hiding for example ActivePerl hack, could be left for later).

It isn't used any longer -- I really suggest you read the whole thread before replying. ;-)

O.K. Still I think that putting cmd_output and other in Git::Repo is not a good API. I'd rather route calling git commands via Git or Git::Cmd object (but Git::Repo would have Git/Git::Cmd object which automatically adds '--git-dir=<path>', and possibly also '--work-dir=<path>').

By the way, would you prefer if I commented on 3/3 patch as it is now, taking into account what I remember from discussion on this and 2/3 patch (latter only as relevant), or would you rather I wait for next round (next version) of patches?

Quoted 12 lines
Quoted 7 lines
Quoted 4 lines

I wouldn't -- see my blurb about error handling at the top of my reply to Petr (<[email protected]>). You're not supposed to pass anything that you didn't get from get_sha1 into Git::Commit or Git::Tag constructors, or your error handling is invariably broken.

I can understand this simpler, although less than optimal, and geared mainly towards gitweb needs.

FTR, yes it is simpler, but no, it is not really geared toward gitweb needs, and it's definitely not "less than optimal" in the sense of being worse than the exception-based error handling Git.pm does. Trust on me on this one. ;-)

[...]

Quoted 7 lines
Quoted 2 lines

Why do you _need_ name_rev, if you are not to include git-describe equivalent?

I needed it for gitweb. As I said, I'm not trying to create a complete API. A describe_rev (or so) method can be added later, if and when it's needed. (As I said, I don't think writing APIs without at least one use case is a good idea anyway.)

Errr... I guess I misspoke. I should not say 'geared toward gitweb needs', as perhaps it is 'created according [at least somewhat] to what gitweb would need'.

Signature · 2 lines
Jakub Narebski
Poland
#36 lore raw

Re: [PATCH 2/3] add new Git::Repo API

Quoted 3 lines · Jakub Narebski

Besides if decoding is done in Perl API, we can convert it simply to Perl internal form (which, IIUC, in modern Perl is UTF-8 and marked as such).

FWIW, Perl strings actually contain non-encoded Unicode code points. IOW, they're not byte strings.

Quoted 2 lines

Still I think that putting cmd_output and other in Git::Repo is not a good API.

Yup; that's why I'm underscore-prefixing it (and taking it out of the man page) for the next version of this patch.

Quoted 2 lines

By the way, would you prefer if I commented on 3/3 patch as it is now, [...] or would you rather I wait for next round (next version) of patches?

I suggest you wait for the next version of the patch series, which I'll post in a few hours. There are quite a few changes; I'll list them in the parent message for the series.

-- Lea

#37 lore raw

PATCH 3/3 gitweb: use new Git::Repo API, and add optional caching

In reply to Lea Wiemann

Gitweb now uses the Git::Repo API; this change is behavior-preserving, except for slightly more aggressive error handling; see below.

This patch also adds an optional caching layer for caching repository data in memory and (for larger cacheable items, like blobs, snapshots, or diffs) on disk.

Other minor changes:

- Gitweb would previously accept invalid input and either (a) display nothing, (b) display an obscure error message, or (c) proceed as normal since the parameter happens to be unused in the particular code path used. This has changed in that gitweb will check for parameter correctness more aggressively, and display meaningful error messages. This change is only relevant if you manually edit gitweb's CGI parameters, since gitweb only generates valid links.

- Empty projects:

- Only display summary link for empty projects in project list to avoid broken links (yielding 404).

- Slim down summary page for empty projects to avoid some broken links and unnecessary vertical space.

- Sort empty projects at the bottom of the project list when sorting by last change.

- Add test for empty projects to t9503 (the Mechanize test), now that there no broken links anymore.

- For HTML pages, remove the "Expires" HTTP response header, and add "Cache-Control: no-cache" instead. This is because pages can contain dynamic content (like the subject of the latest commit), so the Expires headers would be wrong.

This makes gitweb's responsiveness slightly worse, but it will get much better once If-Last-Modified is implemented. It's better to be correct than to be convenient here, since having to press the reload button makes for lousy user experience (IOW, users should be able to always trust gitweb's output).

Raw diffs and blobs still get the Expires header, where appropriate.

- Add a $page_info option to display cache stats at the bottom of each page; the option is named generically to allow for adding non-cache page info there at some point (timings perhaps?).

---
It's all documented of course :-), but for the impatient here's a snippet for gitweb_config.perl to activate caching:

use Cache::Memcached;
$cache = Cache::Memcached->new( { servers => ['localhost:11211'], compress_threshold => 1000 } ); $large_cache_root = '/home/lewiemann/gitweb-cache';
$large_cache_case_sensitive = 1;
# Invalidate cache on changes to gitweb without version number bump;

# useful for development.
$cache_key = (stat '/home/lewiemann/gitweb')[9] . (stat '/home/lewiemann/gitweb/gitweb.cgi')[9];
# Display detailed cache info at the bottom of each page.
$page_info = 2;

A live demo is here: http://odin3.kernel.org/git-lewiemann/

 Makefile                               |   27 +-
 gitweb/README                          |   14 +
 gitweb/gitweb.css                      |   16 +
 gitweb/gitweb.perl                     | 1415 +++++++++++++++++++-------------
 t/t9500-gitweb-standalone-no-errors.sh |    4 +-
 t/t9503-gitweb-Mechanize.sh            |   10 +
 t/t9503/test.pl                        |    5 +
 7 files changed, 911 insertions(+), 580 deletions(-)
Makefile +15 -12
index 15f210e..a65aa4d 100644
@@ -1099,25 +1099,28 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL
 	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
+PERL_USE_LIB_REWRITE = \
+	-e '1{' \
+	-e '	s|\#!.*perl|\#!$(PERL_PATH_SQ)|' \
+	-e '	h' \
+	-e '	s=.*=use lib (split(/:/, \$$ENV{GITPERLLIB} || \"@@INSTLIBDIR@@\"));=' \
+	-e '	H' \
+	-e '	x' \
+	-e '}' \
+	-e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+	-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g'
+
 $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
 	$(QUIET_GEN)$(RM) $@ $@+ && \
 	INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
-	sed -e '1{' \
-	    -e '	s|#!.*perl|#!$(PERL_PATH_SQ)|' \
-	    -e '	h' \
-	    -e '	s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
-	    -e '	H' \
-	    -e '	x' \
-	    -e '}' \
-	    -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
-	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-	    [email protected] >$@+ && \
+	sed $(PERL_USE_LIB_REWRITE) [email protected] >$@+ && \
 	chmod +x $@+ && \
 	mv $@+ $@
 
 gitweb/gitweb.cgi: gitweb/gitweb.perl
 	$(QUIET_GEN)$(RM) $@ $@+ && \
-	sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+	INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
+	sed $(PERL_USE_LIB_REWRITE) \
 	    -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
 	    -e 's|++GIT_BINDIR++|$(bindir)|g' \
 	    -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
@@ -1125,7 +1128,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
 	    -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
 	    -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
 	    -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
-	    -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \
+	    -e 's|++GITWEB_PROJECT_MAXDEPTH++|$(GITWEB_PROJECT_MAXDEPTH)|g' \
 	    -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
 	    -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
 	    -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
gitweb/README +14 -0
index 6908036..fb5a027 100644
@@ -214,6 +214,20 @@ not include variables usually directly set during build):
    Rename detection options for git-diff and git-diff-tree. By default
    ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or
    set it to () if you don't want to have renames detection.
+ * $cache
+   To activate caching, set this to an instance of a Cache::Cache
+   compatible cache (e.g. Cache::Memcached).
+ * $large_cache_root
+   A directory in which gitweb will store potentially large cache
+   items (like snapshots or blobs).  Cache items in this directory are
+   not deleted, so you should clean it up yourself periodically.
+   Default: undef (no caching for large items).
+ * $large_cache_case_sensitive
+   Set this to 1 if the file system on which the large cache resides
+   is case sensitive; it will result in a shorter file names.
+ * $page_info
+   Set to 1 to display cache statistics at the bottom of each page;
+   can be used to check whether your cache works.
 
 
 Projects list file format
gitweb/gitweb.css +16 -0
index aa0eeca..849bb8e 100644
@@ -71,6 +71,22 @@ div.page_footer_text {
 	font-style: italic;
 }
 
+div.page_info {
+	color: #777777;
+	padding: 4px 8px;
+	margin-top: 0.5em;
+	background-color: #ecebe8;
+}
+
+div.page_info p {
+	margin: 0px;
+	padding: 0px;
+}
+
+pre.cache_list {
+	margin: 0.5em 2em;
+}
+
 div.page_body {
 	padding: 8px;
 	font-family: monospace;
gitweb/gitweb.perl +849 -566

2,176 lines not on this page · raw message

t/t9500-gitweb-standalone-no-errors.sh +2 -2
index ae7082b..e04fb5f 100755
@@ -54,7 +54,7 @@ gitweb_run () {
 	# written to web server logs, so we are not interested in that:
 	# we are interested only in properly formatted errors/warnings
 	rm -f gitweb.log &&
-	perl -- "$(pwd)/../../gitweb/gitweb.perl" \
+	"$PERL_PATH" -- "$(pwd)/../../gitweb/gitweb.cgi" \
 		>/dev/null 2>gitweb.log &&
 	if grep -q -s "^[[]" gitweb.log >/dev/null; then false; else true; fi
 
@@ -71,7 +71,7 @@ safe_chmod () {
 
 . ./test-lib.sh
 
-perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
+"$PERL_PATH" -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
     test_expect_success 'skipping gitweb tests, perl version is too old' :
     test_done
     exit
t/t9503-gitweb-Mechanize.sh +10 -0
index 53f2a8a..c0558e5 100755
@@ -89,6 +89,16 @@ test_expect_success 'set up test repository' '
 	test_tick && git pull . b
 '
 
+# set up empty repository
+# create this as a subdirectory of trash directory; not pretty, but simple
+test_expect_success 'set up empty repository' '
+
+	mkdir empty.git &&
+	cd empty.git &&
+	git init --bare &&
+	cd ..
+'
+
 # set up gitweb configuration
 safe_pwd="$("$PERL_PATH" -MPOSIX=getcwd -e 'print quotemeta(getcwd)')"
 large_cache_root="../t9503/large_cache.tmp"
t/t9503/test.pl +5 -0
index b0a8269..2d83158 100755
@@ -274,6 +274,11 @@ if (test_page '', 'project list (implicit)') {
 		"title contains $site_name");
 	$mech->content_contains('t9503-gitweb-Mechanize test repository',
 		'lists test repository (by description)');
+	if (follow_link( { text => 'empty.git' }, 'empty git repository')) {
+		# Just check that the empty.git summary page is linked
+		# and doesn't die.
+		$mech->back;
+	}
 }
 
 
Signature · 1 line
1.5.6.2.456.g63fc0
#38 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

Quoted 2 lines · Lea Wiemann

Gitweb now uses the Git::Repo API; this change is behavior-preserving, except for slightly more aggressive error handling; see below.

Good.

It was suggested to split this into separate commit from the following change, for making it easier to test (you can check that behavior is the same, with the exception of error handling) and review (smaller patch to read and review).

Quoted 3 lines

This patch also adds an optional caching layer for caching repository data in memory and (for larger cacheable items, like blobs, snapshots, or diffs) on disk.

As it was said, if feasible it would be good idea to put this change into separate commit.

Quoted 9 lines

Other minor changes:

- Gitweb would previously accept invalid input and either (a) display nothing, (b) display an obscure error message, or (c) proceed as normal since the parameter happens to be unused in the particular code path used. This has changed in that gitweb will check for parameter correctness more aggressively, and display meaningful error messages. This change is only relevant if you manually edit gitweb's CGI parameters, since gitweb only generates valid links.

I understand that this change deals with treating invalid specifiers, which point to either object that do not exists, are ambiguous, or point to object of invalid type. Gitweb does check "syntactic" validity of input (of CGI parameters) already, even those that are not used for selected action.

BTW. such check was not feasible before implementing --batch and/or --batch-check options to git-cat-file; I think that possibly one more fork is not much price to pay for better error checking.

Quoted 5 lines

- Empty projects:

- Only display summary link for empty projects in project list to avoid broken links (yielding 404).

Quoted 8 lines

- Slim down summary page for empty projects to avoid some broken links and unnecessary vertical space.

- Sort empty projects at the bottom of the project list when sorting by last change.

- Add test for empty projects to t9503 (the Mechanize test), now that there no broken links anymore.

Good. The only thing that *might* be controversial is putting empty projects at the bottom of sorted by age (by last change) projects list, instead of at top.

Quoted 5 lines

- For HTML pages, remove the "Expires" HTTP response header, and add "Cache-Control: no-cache" instead. This is because pages can contain dynamic content (like the subject of the latest commit), so the Expires headers would be wrong.

Quoted 7 lines

This makes gitweb's responsiveness slightly worse, but it will get much better once If-Last-Modified is implemented. It's better to be correct than to be convenient here, since having to press the reload button makes for lousy user experience (IOW, users should be able to always trust gitweb's output).

Raw diffs and blobs still get the Expires header, where appropriate.

I don't think it is a good change.

Gitweb generates two types of views (pages): transient and immutable. An example of transient view (transient page/action) is for example RSS feed, or summary page. When project (repository) is updated, they can change.

The opposite are immutable pages. They are pages/actions/views where all specifiers are given by full SHA-1; to be more exact all specifiers that are needed to reconstruct object are given by SHA-1. (It is enough to have sufficient check for immutability, i.e. such that if check succeeds, then page is immutable, but it doesn't need to be true in reverse.)

Gitweb sets expires to '+1d' which is one day to pages it considers immutable, while not defining expires for other pages (which results, I think, in lack of expires header). We could have set it to "forever", which in terms of Expires: HTTP header is half a year (from what I remember).

Now I don't see *any* reason to not set long expires for immutable pages; I don't know if forbidding to cache transient pages even if in fact they are generated dynamically is a good idea... Note that if caching is enabled, you can set expires to either time-to-expire of cache entries (simpler), or time left to live to invalidation of item in cache (better, but more complicated) perhaps also setting Age:
header to appropriate value.

Sidenote: we would probably want to use Expires: for HTTP/1.0 requests, and Cache-Control: max-age=<seconds> for HTTP/1.1 requests. But that might be left as improvement for later...

Quoted 3 lines

- Add a $page_info option to display cache stats at the bottom of each page; the option is named generically to allow for adding non-cache page info there at some point (timings perhaps?).

Great idea!

Quoted 3 lines

---
It's all documented of course :-), but for the impatient here's a snippet for gitweb_config.perl to activate caching:

Nice.

Quoted 3 lines

use Cache::Memcached;
$cache = Cache::Memcached->new( { servers => ['localhost:11211'],

     compress_threshold => 1000 } );

IIRC you can use any Cache::Cache compatibile (is it explained later what it means?) cache here; IMVHO it would be nice if this info would be also in commit message.

Quoted 2 lines

$large_cache_root = '/home/lewiemann/gitweb-cache'; $large_cache_case_sensitive = 1;

Errr... I understand that it is your _private_ configuration, just copied here verbatim, but I don't think '/home/lewiemann/gitweb-cache' is a good example: '/tmp/gitweb-cache' perhaps, that I can understand.

Quoted 4 lines

# Invalidate cache on changes to gitweb without version number bump;

# useful for development.
$cache_key = (stat '/home/lewiemann/gitweb')[9] . 
     (stat '/home/lewiemann/gitweb/gitweb.cgi')[9]; 

What should be used in production? "$cache_key = $version;"?

Besides hardcoding those paths is not a good idea. You can always use $ENV{'SCRIPT_FILENAME'}, or dirname of it.

Quoted 2 lines

# Display detailed cache info at the bottom of each page. $page_info = 2;

Errr... what does "$page_info = <n>;" mean?

Quoted 1 line

A live demo is here: http://odin3.kernel.org/git-lewiemann/

Nice. Thanks.

[...]

Quoted 1 line
 gitweb/README                          |   14 +

Very good.

[Comments on patch itself in separate email, later]

Signature · 2 lines
Jakub Narebski
Poland
#39 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

Quoted 1 line · Jakub Narebski

It was suggested to split this into separate commit

Yup; I'll probably send updated patches tomorrow night (also for patch 2/3).

Quoted 5 lines
Quoted 1 line

- gitweb will check for parameter correctness more aggressively,

I understand that this change deals with treating invalid specifiers, which point to either object that do not exists, are ambiguous, or point to object of invalid type.

Yes, that's right. (I don't believe we have any point where ambiguity might come up though.)

Quoted 5 lines
Quoted 1 line

- Empty projects: [...]

Good. The only thing that *might* be controversial is putting empty projects at the bottom of sorted by age (by last change) projects list, instead of at top.

Yup; let's see if anyone objects though. If I sort the list by "Last Change", I usually want to see projects with recent activity, not dead project, at the top, which is why I changed it (since I was touching that line anyway).

Quoted 5 lines
Quoted 3 lines

- For HTML pages, remove the "Expires" HTTP response header, and add "Cache-Control: no-cache" instead. This is because pages can contain dynamic content (like the subject of the latest commit)

I don't think it is a good change.

Hm; I thought transient titles could slip in (e.g. try opening the tree of some commit and remove the hb parameter; the URL will seem cacheable, but the page contains the title of the HEAD commit), but I can't find any URL right now where mainline actually sets a wrong Expires header. I'll look into it; if you don't see me posting about it again I'll re-add the Expires header.

Quoted 3 lines

Note that if caching is enabled, you can set expires to either time-to-expire of cache entries (simpler), or time left to live to invalidation of item in cache (better, but more complicated)

Gitweb's cache is actually never out-of-date, and cache invalidation happens automatically. It uses some (long) expiry times to guard against non-standard modification of the repository, but it's nothing the HTTP client should be concerned with.

Quoted 4 lines
Quoted 1 line

$cache = Cache::Memcached->new( { servers => ['localhost:11211'],

IIRC you can use any Cache::Cache compatibile cache here; IMVHO it would be nice if this info would be also in commit message.

I'll add that.

Quoted 5 lines
Quoted 1 line

$large_cache_root = '/home/lewiemann/gitweb-cache';

Errr... I understand that it is your _private_ configuration, just copied here verbatim, but I don't think '/home/lewiemann/gitweb-cache' is a good example: '/tmp/gitweb-cache' perhaps, that I can understand.

Yup. ;-) Or /var/cache/gitweb.

Quoted 6 lines
Quoted 4 lines

# Invalidate cache on changes to gitweb without version number bump;

# useful for development.
$cache_key = (stat '/home/lewiemann/gitweb')[9] . 
     (stat '/home/lewiemann/gitweb/gitweb.cgi')[9]; 

What should be used in production? "$cache_key = $version;"?

No, nothing. $version is used automatically as a cache key; I'll add that to the documentation for $cache_key.

Quoted 1 line

You can always use $ENV{'SCRIPT_FILENAME'}, or dirname of it.

That one doesn't exist with my thttpd, or any other environment variable that'd be usable. It's just a hack anyway, so hardcoded paths are OK. :) I don't think gitweb should check its own mtime by default.

Quoted 4 lines
Quoted 2 lines

# Display detailed cache info at the bottom of each page. $page_info = 2;

Errr... what does "$page_info = <n>;" mean?

Display no (0) / short (1) / detailed (2) page (cache) info at the bottom of each page. It's documented in gitweb.perl.

Quoted 1 line

[Comments on patch itself in separate email, later]

Thanks!

#40 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

Quoted 10 lines · Lea Wiemann
Quoted 3 lines · Jakub Narebski

Note that if caching is enabled, you can set expires to either time-to-expire of cache entries (simpler), or time left to live to invalidation of item in cache (better, but more complicated)

Gitweb's cache is actually never out-of-date, and cache invalidation happens automatically.  It uses some (long) expiry times to guard against non-standard modification of the repository, but it's nothing the HTTP client should be concerned with.

Could you explain then how gitweb cache is invalidated?

The _reasoning_ behind setting Expires:/Cache-Control: max-age= header for gitweb with caching is that browser doesn't need to even try to cache-validate or access page for the time we *know* that output would not change[*1*] because it is from the cache.

[*1*] In significant way: changing relative dates/time doesn't count.

Signature · 2 lines
Jakub Narebski
Poland
#41 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

Quoted 4 lines · Jakub Narebski
Quoted 1 line · Lea Wiemann

Gitweb's cache is actually never out-of-date

Could you explain then how gitweb cache is invalidated?

Sure; from gitweb.perl:

# Transient cache entries (like get_sha1('HEAD')) are automatically

# invalidated when an mtime of either the repository's root directory
# or of the refs directory or any subdirectory changes.  This
# mechanism *should* detect changes to the repository reliably if you
# only use git or rsync to write to it,

IOW, gitweb will do a small number of (inexpensive) stat calls on those directories each time it's called, and use the most recent mtime as part of the cache key for transient entries. Hence those transient entries will automatically become invalid once the most recent mtime changes.

(If any of the relevant directories has been modified since the last time gitweb checked, gitweb will re-scan the whole tree to check for new directories, and record their mtimes as well. See get_last_modification if you're interested in more gory details.)

The punchline is, the cache never returns outdated data.

#42 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

Quoted 5 lines · Lea Wiemann
Quoted 2 lines · Jakub Narebski

Could you explain then how gitweb cache is invalidated?

Sure; from gitweb.perl:

I'll try to read and comment on patch itself soon.

Quoted 5 lines

# Transient cache entries (like get_sha1('HEAD')) are automatically

# invalidated when an mtime of either the repository's root directory
# or of the refs directory or any subdirectory changes.  This
# mechanism *should* detect changes to the repository reliably if you
# only use git or rsync to write to it,

Nice idea... for project pages. I'm not so sure about projects_list page, if wouldn't be better to have expire time for *this* page. You would have/have to stat a lot of files/directories to detect changes.

BTW. some summary of the above should be IMHO in the commit message. In short, you should write, I think, that caching mechanism uses any Cache::Cache compatible cache for caching data, that large objects can be cached on filesystem (or perhaps not, at it is a detail), that cache validity is checked by stat-ing refs area.

Quoted 11 lines

IOW, gitweb will do a small number of (inexpensive) stat calls on those directories each time it's called, and use the most recent mtime as part of the cache key for transient entries. Hence those transient entries will automatically become invalid once the most recent mtime changes.

(If any of the relevant directories has been modified since the last time gitweb checked, gitweb will re-scan the whole tree to check for new directories, and record their mtimes as well. See get_last_modification if you're interested in more gory details.)

The punchline is, the cache never returns outdated data.

In this case you don't have any basis to set expires for transient views; on the other hand situation doesn't differ much from serving static files (wrt. cache validation) so perhaps no expires but no "no-cache" would be a good solution.

P.S. is there any cache evision mechanism (to limit cache size) in gitweb cache, or just those implemented by caching backend?

Signature · 2 lines
Jakub Narebski
Poland
#43 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

Quoted 7 lines · Jakub Narebski
Quoted 2 lines · Lea Wiemann

# Transient cache entries (like get_sha1('HEAD')) are automatically

# invalidated when an mtime [...] changes.

Nice idea... for project pages. I'm not so sure about projects_list page, if wouldn't be better to have expire time for *this* page. You would have/have to stat a lot of files/directories to detect changes.

It doesn't seem to be too much of a performance issue (it takes ~500ms to generate the project list on kernel.org), and project lists aren't requested often enough to be a good optimization target.

I'll see how it performs when the OS's page cache is cold though; if it's too slow then I might revisit the issue.

Quoted 1 line

BTW. some summary of the above should be IMHO in the commit message.

*nods*

Quoted 4 lines

In this case you don't have any basis to set expires for transient views; on the other hand situation doesn't differ much from serving static files (wrt. cache validation) so perhaps no expires but no "no-cache" would be a good solution.

Gitweb provides a view on a live repository, so I don't think caching is usually what you want (e.g. static pages really tend to change much less frequently). (And yes, my Opera *does* seem to cache those pages unless you add no-cache.)

Really, I don't think the performance penalty of revalidation will be significant enough to justify risking stale data. IOW, it's pretty fast. Check out repo.or.cz's performance (and repo.or.cz doesn't even use Last-Modified).

Quoted 2 lines

P.S. is there any cache evision mechanism (to limit cache size) in gitweb cache, or just those implemented by caching backend?

For the $cache, it's in the caching backend, for the $large_cache, there is no mechanism, so you need to clean up yourself. See the patch. ;-)

#44 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

Hi,

Quoted 5 lines · Lea Wiemann
Quoted 4 lines · Jakub Narebski
Quoted 2 lines · Lea Wiemann

# Transient cache entries (like get_sha1('HEAD')) are automatically

# invalidated when an mtime [...] changes.
Quoted 7 lines
Quoted 3 lines

Nice idea... for project pages. I'm not so sure about projects_list page, if wouldn't be better to have expire time for *this* page. You would have/have to stat a lot of files/directories to detect changes.

It doesn't seem to be too much of a performance issue (it takes ~500ms to generate the project list on kernel.org), and project lists aren't requested often enough to be a good optimization target.

Wasn't the main page (i.e. the projects list) the reason why kernel.org has its own little caching mechanism in the first place?

And did Pasky not report recently that repo.or.cz got substantially less loaded with some caching of its own?

warthog? Pasky?

Ciao,
Dscho

#45 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

Quoted 13 lines · Johannes Schindelin

Hi,

Quoted 9 lines · Lea Wiemann
Quoted 7 lines · Jakub Narebski
Quoted 2 lines · Lea Wiemann

# Transient cache entries (like get_sha1('HEAD')) are automatically

# invalidated when an mtime [...] changes.

Nice idea... for project pages. I'm not so sure about projects_list page, if wouldn't be better to have expire time for *this* page. You would have/have to stat a lot of files/directories to detect changes.

Quoted 7 lines
Quoted 3 lines

It doesn't seem to be too much of a performance issue (it takes ~500ms to generate the project list on kernel.org), and project lists aren't requested often enough to be a good optimization target.

Wasn't the main page (i.e. the projects list) the reason why kernel.org has its own little caching mechanism in the first place?

Partially - it was by *far* the biggest problem we were facing, and it likely has gotten a lot better since I went on a rampage and did gitweb-caching.

That said there's a relatively small sample-set of files that gets hit more than others it seems (from stuff I was looking at a while ago) and limiting the general disk i/o on the kernel.org machines is *always* a good thing (the less we go to disk the better) so while it started that way it has been very beneficial, if it does chew up something like 15-20G of disk on those machines.)

- John 'Warthog9' Hawley

Quoted 7 lines

And did Pasky not report recently that repo.or.cz got substantially less loaded with some caching of its own?

warthog? Pasky?

Ciao,
Dscho

#46 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

In reply to Johannes Schindelin
Quoted 5 lines · Johannes Schindelin

Wasn't the main page (i.e. the projects list) the reason why kernel.org has its own little caching mechanism in the first place?

And did Pasky not report recently that repo.or.cz got substantially less loaded with some caching of its own?

Yes, you need *some* caching mechanism. Once you have that, it's fine, at least if the page cache is hot. (The x-thousand stat calls that my caching implementation issues don't actually take that much time; I suspect the ~1000 calls to memcached are the more expensive [and optimizable] part, though I'd have to benchmark that.)

Mainline vs. my caching implementation (both with hot page cache) on odin3.kernel.org:

$ time wget -qO/dev/null http://localhost/git-lewiemann/vanilla/
real    0m3.070s
$ time wget -qO/dev/null http://localhost/git-lewiemann/
real    0m0.719s
#47 lore raw

Re: [PATCH 3/3] gitweb: use new Git::Repo API, and add optional caching

To continue with that benchmarking it seems if gitweb-caching isn't hot it takes about 3 seconds, if it is I'm about 7x faster than Lea's ;-)

$ time wget -qO/dev/null http://localhost/git/
real    0m2.952s
user    0m0.001s
sys     0m0.004s
$ time wget -qO/dev/null http://localhost/git/
real    0m0.108s
user    0m0.001s
sys     0m0.001s

But regardless of who's faster - I would still argue there are two reasons to at least have some caching, even if it's crappy:

1) Prevent the thundering heard problem - many requests for the same thing all generating the same data is bad, and kills I/O

2) Relatively static data can be generated once and stick around for a bit and serve more requests more efficiently. Now I agree that invalidating the cache on a new mtime is better than my current algorithm (which is purely time based on the cache data vs. the original data, with some allowances for back-off due to load).

- John 'Warthog9' Hawley

Quoted 20 lines · Lea Wiemann
Quoted 5 lines · Johannes Schindelin

Wasn't the main page (i.e. the projects list) the reason why kernel.org has its own little caching mechanism in the first place?

And did Pasky not report recently that repo.or.cz got substantially less loaded with some caching of its own?

Yes, you need *some* caching mechanism. Once you have that, it's fine, at least if the page cache is hot. (The x-thousand stat calls that my caching implementation issues don't actually take that much time; I suspect the ~1000 calls to memcached are the more expensive [and optimizable] part, though I'd have to benchmark that.)

Mainline vs. my caching implementation (both with hot page cache) on odin3.kernel.org:

$ time wget -qO/dev/null http://localhost/git-lewiemann/vanilla/
real    0m3.070s
$ time wget -qO/dev/null http://localhost/git-lewiemann/
real    0m0.719s
#48 lore raw
In reply to Lea Wiemann
Quoted 3 lines · Lea Wiemann

As follow-ups to this message, I'll be sending three patches for

1) adding the Mechanize tests,

Somehow I didn't get Cc-ed this patch...

Quoted 3 lines

The Mechanize tests succeed before and after patch (3) is applied, so I'm reasonably confident that my refactoring didn't introduce any (major) bugs.

If I remember correctly Mechanize tests detected some bugs in gitweb (nice!), but there were swept under the rug, i.e. put as TODO.

Does that mean that those errors were corrected, or that refactoring "just" didn't break anything more?

Quoted 6 lines

On my to-do list:

- Benchmarks. I'm planning to time a replay of kernel.org's gitweb logs on the test server, with and without caching. Nothing fancy. (The performance of the test setup on odin3.kernel.org is not representative of gitweb's actual performance under load.)

Do you plan to compare other gitweb caching implementations? I mean here old kernel.org caching by J.H., and repo.or.cz caching (only for projects list page) by Pasky.

Quoted 6 lines

- Implement support for Last-Modified or ETags, since those basically fall out for free with the current implementation. (This will require mod_perl, since CGI doesn't allow for accessing arbitrary request headers AFAIK.)

From CGI(3pm):

http()
Called with no arguments returns the list of HTTP environment vari- ables, including such things as HTTP_USER_AGENT, HTTP_ACCEPT_LANGUAGE, and HTTP_ACCEPT_CHARSET, corresponding to the like-named HTTP header fields in the request. Called with the name of an HTTP header field, returns its value. Capitalization and the use of hyphens versus under- scores are not significant.

For example, all three of these examples are equivalent:

$requested_language = http('Accept-language'); $requested_language = http('Accept_language'); $requested_language = http('HTTP_ACCEPT_LANGUAGE');

Quoted 1 line

From http://hoohoo.ncsa.uiuc.edu/cgi/env.html

In addition to these, the header lines received from the client, if any, are placed into the environment with the prefix HTTP_ followed by the header name. Any '-' characters in the header name are changed to '_' characters. The server may exclude any headers which it has already processed, such as Authorization, Content-type, and Content-length. If necessary, the server may choose to exclude any or all of these headers if including them would exceed any system environment limits.

So you _can_ access 'If-Modified-Since', 'If-None-Match' (by web browsers) and 'If-Not-Modified-Since', 'If-Match' (by caches) from CGI.

Quoted 4 lines

That will make the site a tad more responsive, I hope,
and it will also hugely reduce the load for RSS/Atom requests, which currently make up almost half of all requests to kernel.org's gitweb and get served in full each time (i.e. "200 OK" instead of "304 Not Modified").

Currently only one "shortcut" is that gitweb respects HEAD request (returning only HTTP headers) for feeds, and if I remember correctly only for feeds. I don't know however how much work gitweb does before HEAD request shortcut.

Quoted 6 lines

- Make gitweb use more parts of the Git::Repo API; in particular, the commit and tag parsing code should be ripped out, and gitweb should use the (much prettier) Git::Commit/Git::Tag API instead. Perhaps some more functions (like ls_tree) can be generalized into the API as well; I went the easy route for now and simply replaced most "open '-|'" calls with $repo->cmd_output calls.

I think that ls_tree and git-ls-tree output parsing should be generalized into Git::Tree API as well.

I'll try to review the rest of patches by tomorrow...

Signature · 2 lines
Jakub Narebski
Poland
#49 lore raw
Quoted 4 lines · Jakub Narebski
Quoted 1 line · Lea Wiemann

1) adding the Mechanize tests,

Somehow I didn't get Cc-ed this patch...

Yup, nobody got Cc'ed; apologies.

Quoted 5 lines

If I remember correctly Mechanize tests detected some bugs in gitweb (nice!), but there were swept under the rug, i.e. put as TODO.

Does that mean that those errors were corrected, or that refactoring "just" didn't break anything more?

Refactoring really just didn't break anything more; it could be that something got corrected accidentally, but I'd be surprised.

Quoted 3 lines
Quoted 1 line

- Benchmarks.

Do you plan to compare other gitweb caching implementations? [k.org, repo.or.cz]

Yup, sure.

Quoted 7 lines
Quoted 2 lines

- Implementing support for Last-Modified or ETags [...] will require mod_perl, since CGI doesn't allow for accessing arbitrary request headers

              $requested_language = http('Accept-language');

the header lines received from the client, if any, are placed into the environment with the prefix HTTP_ followed by the header name.

Right, you'd think the request headers should be accessible this way, but apparently not all of them are.

If you take this script, ...

#!/usr/bin/perl
use CGI qw(http);
print "Content-type: text/plain\n";
print "Last-Modified: Thu, 03 Jul 2008 22:39:42 GMT\n\n"; print "Header: ", http('If-Last-Modified');

... then my browser (according to LiveHTTPHeaders) sends an If-Last-Modified header, but it doesn't get through to the CGI script. It does work if you test Accept-Language. (Try print env to get an idea of what gets through.) It happens on Apache and thttpd. Apparently this part somehow applies:

Quoted 1 line

'The server may exclude any headers which it has already processed,'

I honestly have no idea why the If-Last-Modified headers gets eaten (and googling didn't help), but I assume that at least it'll be possible to access all headers with mod_perl.

Quoted 1 line

'If-Not-Modified-Since', 'If-Match' (by caches)

Wait, are you sure caches would use those headers (I believe only the latter actually exists BTW), or did you fall prey to a thinko? ;)

Quoted 2 lines

one "shortcut" is that gitweb respects HEAD request (returning only HTTP headers) for feeds

Yes, and I think it does help performance-wise, but only a really small fraction of the RSS/Atom requests actually use HEAD. Most use GET.

Quoted 2 lines

I think that ls_tree and git-ls-tree output parsing should be generalized into Git::Tree API as well.

True, though I'm still not sure how to make element access work pretty and fast. I'll keep pondering it for a while.

Quoted 1 line

I'll try to review the rest of patches by tomorrow...

Thanks!

Quoted 2 lines · Johannes Schindelin

FWIW there are a few reasons why splitting up (3) might be the thing you really want to do

I've put splitting it on my list; I'm not sure though if I'll get around to doing it today. Everyone, comments on patch (3) are still appreciated in the meantime. ;-) I'll integrate any suggestions/patches when I split it up.

-- Lea

#50 lore raw
Quoted 5 lines · At 2008-07-11 16:07:26 +0200, [email protected] wrote:
Quoted 2 lines

'If-Not-Modified-Since', 'If-Match' (by caches)

Wait, are you sure caches would use those headers (I believe only the latter actually exists BTW), or did you fall prey to a thinko? ;)

If-Not-Modified-Since should really be If-Unmodified-Since.

But where's the thinko? To send If-Modified-Since or similar with a request, you would need to have a cached copy of the resource and use its Last-Modified, for example. Sure, you could do it without one, but what would be the point?

-- ams

#51 lore raw
Quoted 7 lines · Abhijit Menon-Sen
Quoted 5 lines · At 2008-07-11 16:07:26 +0200, [email protected] wrote:
Quoted 2 lines

'If-Not-Modified-Since', 'If-Match' (by caches)

Wait, are you sure caches would use those headers (I believe only the latter actually exists BTW), or did you fall prey to a thinko? ;)

Quoted 6 lines

If-Not-Modified-Since should really be If-Unmodified-Since.

But where's the thinko? To send If-Modified-Since or similar with a request, you would need to have a cached copy of the resource and use its Last-Modified, for example. Sure, you could do it without one, but what would be the point?

Actually the thinko was use of If-Unmodified-Since: and If-Match:.

Both If-Unmodified-Since and If-Match can be used and should be used when requesting *partial document transfers* (when you need to ensure that document is unchanged before you fetch next part of it). Additionally If-Match can be used for concurrenty control when you do a two-way communication using GET and PUT, in a PUT request to ensure that you are modifying correct document.

They are not used, I think, by caches to refresh cached data.

Signature · 3 lines
Jakub Narebski
Poland
ShadeHawk on #git
#52 lore raw
In reply to Lea Wiemann

Hi,

Quoted 9 lines · Lea Wiemann

Patch (3) basically makes two large changes in one patch, but it was pretty hard to separate them during development. I could try to split them up after the fact, but it would take at least an hour or two, since the changes that introduce caching are spread all over the code. I don't think that having separate commits ([a] use Git::Repo API, [b] add caching) brings enough benefit to justify the effort.

There are some other changes in (3) as well, but they fell out as part of the refactoring, so I didn't separate them either -- same thing.

FWIW there are a few reasons why splitting up (3) might be the thing you really want to do, even if it takes an hour or two:

- it makes reviewing much easier,
- it makes subsequent revisions of the patches easier to review,
- it make it easier to cherry-pick changes, should not all be equally liked,
- it makes finding bugs much easier (both spotting during review and bisecting), and
- it is good for documentation purposes, should someone read the commit log.

Now, after weighing the benefit (especially in terms of hours spared others) against the downsides, you might want to reconsider your stance.

Ciao,
Dscho

#53 lore raw
In reply to Lea Wiemann
Quoted 3 lines · Lea Wiemann

1) adding the Mechanize tests,
2) adding the Git::Repo API, and (the important part:)
3) making gitweb use the Git::Repo API, and adding caching to gitweb.

I unfortunately didn't have enough time to finish the update to the patch series before my vacation; I'll therefore be posting the next series only around Aug 12th-13th. I suggest you wait with any reviewing till I've posted the next version, since there'll be quite a few changes; also I'll post patch 3 (the gitweb patch) as (at least) two separate patches, which will hopefully help with reviewability.

Alright, off to my vacation now -- I'll be completely offline till Aug 10th.

See you then!

-- Lea

#54 lore raw
In reply to Lea Wiemann
Quoted 5 lines · Lea Wiemann

As follow-ups to this message, I'll be sending three patches for

1) adding the Mechanize tests,
2) adding the Git::Repo API, and (the important part:)
3) making gitweb use the Git::Repo API, and adding caching to gitweb.

I'll be sending the next version of the patch series as a follow-up to this message. As the GSoC pencils-down deadline is in -0.5h, I'll try to get the code out first and then I'll post some comments on a few aspects it.

I unfortunately didn't end up being able to split up the third patch (use Perl API in Gitweb, and add caching layer), since the two changes are too intricately linked to be properly separated (I actually tried splitting it two times, two different ways, and it just didn't work). The only thing that's really not caching-specific to the third patch (i.e. where using the Perl API merely prettifies the code) is where Gitweb now uses $repo->get_sha1. Those few occurrences are only a very small part of the patch, and I don't think they'd warrant ripping it apart.

Here's the list of changes since the last patch series:

Patch 1/1 (Mechanize tests):

- typo

Patch 2/3 (Perl API):

- remove get_refs, get_path, and version methods

- rename cat_file to get_object

- remove unnecessary cmd_output and get_bidi_pipe methods -- they shouldn't be part of this API (because they don't fit into the design), and keeping and underscore-prefixing them isn't worth the code complexity -- so the Git::Repo methods now use 'open' calls directly

- rename the 'directory' options of RepoRoot->new, Repo->new, and RepoRoot->repo to 'repo_dir' and 'root_dir' respectively

- ignore (rather than die on) unrecognized header lines in tag/commit objects

- do not use unnecessary hash index optimization

- add design notes

- underscore-prefix assert_opts and assert_sha1

- remove dependency of the test suite (t9710) on non-standard modules; Git::Repo should now run on vanilla Perl 5.8

- respect PERL_PATH in test suite

- add "use perl 5.008" statement (so it dies for Perl <5.8) -- it needs Perl 5.8 for Unicode support

- do not "or" author/committer/tagger/type with '' (it's unnecessary and silly)

- improved documentation, per Jakub's suggestions mostly

- author, committer, tagger, and message now all return Unicode strings per <[email protected]>

- some minor cleanups, typos

- remove warning about Git::Repo being unstable; this is a silly excuse that in theory allows you to make incompatible changes ("we told you so"), but in practice never holds up

Patch 3/3 (Gitweb):

- add installation note for the Git perl package to INSTALL

- accomodate removals/renamings in Git::Repo API

- remove two TODO markers in t9503 (gitweb tests) -- works now thanks to Git::Repo

- some minor cleanups, typos

- do not remove the Expires header (as was the case in the previous patch)

- remove option to assume case sensitive file system to unclutter the code -- the cache will now work on any file system

- elaborate on inefficient refs/ traversal code in get_last_modification method

#55 lore raw

PATCH v10 1/3 gitweb: add test suite with Test::WWW::Mechanize::CGI

This test uses Test::WWW::Mechanize::CGI to check gitweb's output. It also uses HTML::Lint, XML::Parser, and Archive::Tar (if present, each) to validate the HTML/XML/tgz output, and checks all links on the tested pages if --long-tests is given.

---
 Makefile                    |    1 +
 t/t9503-gitweb-Mechanize.sh |  144 ++++++++++
 t/t9503/test.pl             |  604 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 749 insertions(+), 0 deletions(-)
 create mode 100755 t/t9503-gitweb-Mechanize.sh
 create mode 100755 t/t9503/test.pl
Makefile +1 -0
index ca418fc..35779a7 100644
@@ -1289,6 +1289,7 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
 	@echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
 	@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
+	@echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
t/t9503-gitweb-Mechanize.sh new file +144 -0

144 lines not on this page · raw message

t/t9503/test.pl new file +604 -0

604 lines not on this page · raw message

Signature · 1 line
1.6.0.90.gf3f7b
#56 lore raw

PATCH v2 2/3 add new Perl API: Git::Repo, Git::Commit, Git::Tag, and Git::RepoRoot

The Git::Repo class provides low-level access to Git repositories. The Git::Commit and Git::Tag classes represent commit and tag objects and allow for accessing their respective properties (like 'author', 'tagger', etc.); Git::Object serves as a common base class for them. Git::RepoRoot is a helper factory class to instantiate Git::Repo objects with common properties for repositories under a common root directory.

---
 perl/Git/Commit.pm       |  179 ++++++++++++++++++++++++++++++
 perl/Git/Object.pm       |   94 ++++++++++++++++
 perl/Git/Repo.pm         |  274 ++++++++++++++++++++++++++++++++++++++++++++++
 perl/Git/RepoRoot.pm     |  103 +++++++++++++++++
 perl/Git/Tag.pm          |  169 ++++++++++++++++++++++++++++
 perl/Makefile            |    1 +
 perl/Makefile.PL         |   10 ++-
 t/t9710-perl-git-repo.sh |   49 ++++++++
 t/t9710/test.pl          |  136 +++++++++++++++++++++++
 9 files changed, 1014 insertions(+), 1 deletions(-)
 create mode 100644 perl/Git/Commit.pm
 create mode 100644 perl/Git/Object.pm
 create mode 100644 perl/Git/Repo.pm
 create mode 100644 perl/Git/RepoRoot.pm
 create mode 100644 perl/Git/Tag.pm
 create mode 100755 t/t9710-perl-git-repo.sh
 create mode 100755 t/t9710/test.pl
perl/Git/Commit.pm new file +179 -0
new file mode 100644
index 0000000..58e502f
@@ -0,0 +1,179 @@
+=head1 NAME
+
+Git::Commit - Object-oriented interface to Git commit objects.
+
+=head1 DESCRIPTION
+
+Git::Commit is a class representing a commit object in a Git
+repository.  It stringifies to the commit object's SHA1.
+
+=cut
+
+use strict;
+use warnings;
+
+
+package Git::Commit;
+
+use base qw(Git::Object);
+
+
+# Keep documentation in one place to save space.
+
+=head1 METHODS
+
+=head2 General Methods
+
+=over
+
+=item $commit = Git::Commit->new($repo, $sha1)
+
+Return a new Git::Commit instance for a commit object with $sha1 in
+repository $repo.
+
+The commit object is loaded lazily.  Hence, calls to this method are
+free, and it does not check whether $sha1 exists and has the right
+type.  However, accessing any of the commit object's properties will
+fail if $sha1 is not a valid commit object.
+
+Note that $sha1 must be the SHA1 of a commit object; tag objects are
+not dereferenced.
+
+The author, committer and message methods return Unicoded strings,
+decoded according to the "encoding" header, with UTF-8 and then
+Latin-1 as fallbacks.  (These Unicode strings can contain code points
+greater than 256 and are *not* UTF-8 strings; see man perlunitut on
+how Perl handles Unicode.)
+
+You will usually want to call $repo->get_commit($sha1) instead of
+instantiating this class directly; see L<Git::Repo>.
+
+=item $obj->repo
+
+Return the Git::Repo instance this object was instantiated with.
+
+=item $obj->sha1
+
+Return the SHA1 of this commit object, as provided at instantiation time.
+
+=back
+
+=head2 Property Methods
+
+Calling any of these methods will cause the commit object to be loaded
+from the repository, if it hasn't been loaded already.
+
+=over
+
+=item $commit->tree
+
+Return an object that stringifies to the SHA1 of the tree that this
+commit object refers to.  (Currently this returns an actual string,
+but don't rely on it.)
+
+=item $commit->parents
+
+Return an array of zero or more parent commit objects.  Note that
+commit objects stringify to their respective SHA1s, so you can
+alternatively treat this as an array of SHA1 strings.
+
+=item $commit->author
+
+Return the author of this commit object as a Unicode string.
+
+=item $commit->committer
+
+Return the committer of this commit object as a Unicode string.
+
+=item $commit->message
+
+Return the commit message of this commit object as a Unicode string.
+
+=item $commit->encoding
+
+Return the encoding header of the commit object, or undef if no
+encoding header is present; note that Git::Commit does the necessary
+decoding for you, so you should not normally need this method.
+
+=back
+
+=cut
+
+
+sub tree {
+	my $self = shift;
+	$self->_load;
+	return $self->{tree};
+}
+
+sub parents {
+	my $self = shift;
+	$self->_load;
+	return map { ref($self)->new($self->repo, $_) } @{$self->{parents}};
+}
+
+sub author {
+	my $self = shift;
+	$self->_load;
+	return $self->_decode($self->{author});
+}
+
+sub committer {
+	my $self = shift;
+	$self->_load;
+	return $self->_decode($self->{committer});
+}
+
+sub message {
+	my $self = shift;
+	$self->_load;
+	return $self->_decode($self->{message});
+}
+
+sub encoding {
+	my $self = shift;
+	$self->_load;
+	return $self->{encoding};
+}
+
+# Auxiliary method to load (and parse) the commit object from the
+# repository if it hasn't already been loaded.  Optional parameter:
+# The raw contents of the commit object; the commit object will be
+# retrieved from the repository if that parameter is not given.
+sub _load {
+	my ($self, $raw_text) = shift;
+	return if exists $self->{message};  # already loaded
+
+	my $sha1 = $self->sha1;
+	if (!defined $raw_text) {
+		# Retrieve from the repository.
+		(my $type, $raw_text) = $self->repo->get_object($sha1);
+		die "$sha1 is a $type object (expected a commit object)"
+		    unless $type eq 'commit';
+	}
+
+	(my $header, $self->{message}) = split "\n\n", $raw_text, 2;
+	# Parse header.
+	for my $line (split "\n", $header) {
+		local $/ = "\n"; # for chomp
+		chomp($line);
+		my ($key, $value) = split ' ', $line, 2;
+		if ($key eq 'tree') {
+			$self->{tree} = $value;
+		} elsif ($key eq 'parent') {
+			push @{$self->{parents}}, $value;
+		} elsif ($key eq 'author') {
+			$self->{author} = $value;
+		} elsif ($key eq 'committer') {
+			$self->{committer} = $value;
+		} elsif ($key eq 'encoding') {
+			$self->{encoding} = $value;
+		} else {
+			# Ignore unrecognized header lines.
+		}
+	}
+	undef;
+}
+
+
+1;
perl/Git/Object.pm new file +94 -0

94 lines not on this page · raw message

perl/Git/Repo.pm new file +274 -0

274 lines not on this page · raw message

perl/Git/RepoRoot.pm new file +103 -0

103 lines not on this page · raw message

perl/Git/Tag.pm new file +169 -0

169 lines not on this page · raw message

perl/Makefile +1 -0
index e3dd1a5..4572dc5 100644
@@ -31,6 +31,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	echo install: >> $@
 	echo '	mkdir -p "$(instdir_SQ)"' >> $@
 	echo '	$(RM) "$(instdir_SQ)/Git.pm"; cp Git.pm "$(instdir_SQ)"' >> $@
+	echo '	mkdir -p "$(instdir_SQ)/Git"; $(RM) "$(instdir_SQ)"/Git/*.pm; cp Git/*.pm "$(instdir_SQ)/Git"' >> $@
 	echo '	$(RM) "$(instdir_SQ)/Error.pm"' >> $@
 	'$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \
 	echo '	cp private-Error.pm "$(instdir_SQ)/Error.pm"' >> $@
perl/Makefile.PL +9 -1
index 320253e..6c62160 100644
@@ -8,7 +8,15 @@ instlibdir:
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+# Note that when changing %pm, you'll have to remove pm_to_blib rather
+# than the blib directory to .force the .pm files to be re-installed
+# to blib/lib.
+my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm',
+	  'Git/Repo.pm' => '$(INST_LIBDIR)/Git/Repo.pm',
+	  'Git/RepoRoot.pm' => '$(INST_LIBDIR)/Git/RepoRoot.pm',
+	  'Git/Commit.pm' => '$(INST_LIBDIR)/Git/Commit.pm',
+	  'Git/Tag.pm' => '$(INST_LIBDIR)/Git/Tag.pm',
+	  'Git/Object.pm' => '$(INST_LIBDIR)/Git/Object.pm');
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
t/t9710-perl-git-repo.sh new file +49 -0

49 lines not on this page · raw message

t/t9710/test.pl new file +136 -0

136 lines not on this page · raw message

Signature · 1 line
1.6.0.90.gf3f7b
#57 lore raw

PATCH v2 3/3 gitweb: use new Git::Repo API, and add optional caching

Gitweb now uses the Git::Repo API; this change is behavior-preserving, except for slightly more aggressive error handling; see below.

This patch also adds an optional caching layer for caching repository data using any Cache::Cache-compatible caching engine (typically Cache::Memcached). Larger cacheable items, like blobs, snapshots or diffs, can be cached on disk.

Other minor changes:

- Gitweb would previously accept invalid input and either (a) display nothing, (b) display an obscure error message, or (c) proceed as normal since the parameter happens to be unused in the particular code path used. This has changed in that gitweb will check for parameter correctness more aggressively, and display meaningful error messages. This change is only relevant if you manually edit gitweb's CGI parameters, since gitweb only generates valid links.

- Empty projects:

- Only display summary link for empty projects in project list to avoid broken links (yielding 404).

- Slim down summary page for empty projects to avoid some broken links and unnecessary vertical space.

- Sort empty projects at the bottom of the project list when sorting by last change.

- Add test for empty projects to t9503 (the Mechanize test), now that there no broken links anymore.

---
 Makefile                               |   25 +-
 gitweb/INSTALL                         |   11 +-
 gitweb/README                          |   11 +
 gitweb/gitweb.css                      |   16 +
 gitweb/gitweb.perl                     | 1371 +++++++++++++++++++------------
 t/t9500-gitweb-standalone-no-errors.sh |    4 +-
 t/t9503-gitweb-Mechanize.sh            |   10 +
 t/t9503/test.pl                        |   19 +-
 8 files changed, 911 insertions(+), 556 deletions(-)
Makefile +14 -11
index 35779a7..cab42d8 100644
@@ -1128,25 +1128,28 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL
 	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
+PERL_USE_LIB_REWRITE = \
+	-e '1{' \
+	-e '	s|\#!.*perl|\#!$(PERL_PATH_SQ)|' \
+	-e '	h' \
+	-e '	s=.*=use lib (split(/:/, \$$ENV{GITPERLLIB} || \"@@INSTLIBDIR@@\"));=' \
+	-e '	H' \
+	-e '	x' \
+	-e '}' \
+	-e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+	-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g'
+
 $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
 	$(QUIET_GEN)$(RM) $@ $@+ && \
 	INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
-	sed -e '1{' \
-	    -e '	s|#!.*perl|#!$(PERL_PATH_SQ)|' \
-	    -e '	h' \
-	    -e '	s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
-	    -e '	H' \
-	    -e '	x' \
-	    -e '}' \
-	    -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
-	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-	    [email protected] >$@+ && \
+	sed $(PERL_USE_LIB_REWRITE) [email protected] >$@+ && \
 	chmod +x $@+ && \
 	mv $@+ $@
 
 gitweb/gitweb.cgi: gitweb/gitweb.perl
 	$(QUIET_GEN)$(RM) $@ $@+ && \
-	sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+	INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
+	sed $(PERL_USE_LIB_REWRITE) \
 	    -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
 	    -e 's|++GIT_BINDIR++|$(bindir)|g' \
 	    -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
gitweb/INSTALL +9 -2
index 26967e2..c4ab593 100644
@@ -6,8 +6,15 @@ First you have to generate gitweb.cgi from gitweb.perl using
 gitweb.css, git-logo.png and git-favicon.png) to their destination.
 For example if git was (or is) installed with /usr prefix, you can do
 
-	$ make prefix=/usr gitweb/gitweb.cgi  ;# as yourself
-	# cp gitweb/git* /var/www/cgi-bin/    ;# as root
+	$ make prefix=/usr gitweb/gitweb.cgi           ;# as yourself
+	# cp gitweb/git* /var/www/cgi-bin/             ;# as root
+
+If the Git Perl package isn't already installed system-wide, you also
+need to copy the perl/blib/lib/Git directory to your cgi-bin
+directory:
+
+	$ make prefix=/usr -C perl                     ;# as yourself
+	# cp -r perl/blib/lib/Git /var/www/cgi-bin/    ;# as root
 
 Alternatively you can use autoconf generated ./configure script to
 set up path to git binaries (via config.mak.autogen), so you can write
gitweb/README +11 -0
index 825162a..81ec2ae 100644
@@ -214,6 +214,17 @@ not include variables usually directly set during build):
    Rename detection options for git-diff and git-diff-tree. By default
    ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or
    set it to () if you don't want to have renames detection.
+ * $cache
+   To activate caching, set this to an instance of a Cache::Cache
+   compatible cache (e.g. Cache::Memcached).
+ * $large_cache_root
+   A directory in which gitweb will store potentially large cache
+   items (like snapshots or blobs).  Cache items in this directory are
+   not deleted, so you should clean it up yourself periodically.
+   Default: undef (no caching for large items).
+ * $page_info
+   Set to 1 to display cache statistics at the bottom of each page;
+   can be used to check whether your cache works.
 
 
 Projects list file format
gitweb/gitweb.css +16 -0
index aa0eeca..849bb8e 100644
@@ -71,6 +71,22 @@ div.page_footer_text {
 	font-style: italic;
 }
 
+div.page_info {
+	color: #777777;
+	padding: 4px 8px;
+	margin-top: 0.5em;
+	background-color: #ecebe8;
+}
+
+div.page_info p {
+	margin: 0px;
+	padding: 0px;
+}
+
+pre.cache_list {
+	margin: 0.5em 2em;
+}
+
 div.page_body {
 	padding: 8px;
 	font-family: monospace;
gitweb/gitweb.perl +840 -531

2,109 lines not on this page · raw message

t/t9500-gitweb-standalone-no-errors.sh +2 -2
index 46ba19b..8edbfbd 100755
@@ -54,7 +54,7 @@ gitweb_run () {
 	# written to web server logs, so we are not interested in that:
 	# we are interested only in properly formatted errors/warnings
 	rm -f gitweb.log &&
-	perl -- "$TEST_DIRECTORY/../gitweb/gitweb.perl" \
+	"$PERL_PATH" -- "$TEST_DIRECTORY/../gitweb/gitweb.perl" \
 		>/dev/null 2>gitweb.log &&
 	if grep -q -s "^[[]" gitweb.log >/dev/null; then false; else true; fi
 
@@ -71,7 +71,7 @@ safe_chmod () {
 
 . ./test-lib.sh
 
-perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
+"$PERL_PATH" -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
     test_expect_success 'skipping gitweb tests, perl version is too old' :
     test_done
     exit
t/t9503-gitweb-Mechanize.sh +10 -0
index 53f2a8a..c0558e5 100755
@@ -89,6 +89,16 @@ test_expect_success 'set up test repository' '
 	test_tick && git pull . b
 '
 
+# set up empty repository
+# create this as a subdirectory of trash directory; not pretty, but simple
+test_expect_success 'set up empty repository' '
+
+	mkdir empty.git &&
+	cd empty.git &&
+	git init --bare &&
+	cd ..
+'
+
 # set up gitweb configuration
 safe_pwd="$("$PERL_PATH" -MPOSIX=getcwd -e 'print quotemeta(getcwd)')"
 large_cache_root="../t9503/large_cache.tmp"
t/t9503/test.pl +9 -10
index 93108e7..fca81f7 100755
@@ -271,6 +271,11 @@ if (test_page '', 'project list (implicit)') {
 		"title contains $site_name");
 	$mech->content_contains('t9503-gitweb-Mechanize test repository',
 		'lists test repository (by description)');
+	if (follow_link( { text => 'empty.git' }, 'empty git repository')) {
+		# Just check that the empty.git summary page is linked
+		# and doesn't die.
+		$mech->back;
+	}
 }
 
 
@@ -578,18 +583,12 @@ for my $action qw( blame blob blob_plain ) {
 		  "$action: look up existent file");
 	test_page("?p=.git;a=$action;f=does_not_exist;hb=HEAD",
 		  "$action: look up non-existent file", 404);
-	TODO: {
-		local $TODO = 'wrong error code (but using Git::Repo will fix this)';
-		test_page("?p=.git;a=$action;f=$directories[0];hb=HEAD",
-			  "$action: look up directory", 400);
-	}
-}
-TODO: {
-	local $TODO = 'wrong error code (but using Git::Repo will fix this)';
-	test_page("?p=.git;a=tree;f=$files[0];hb=HEAD",
-		  'tree: look up existent file', 400);
+	test_page("?p=.git;a=$action;f=$directories[0];hb=HEAD",
+		  "$action: look up directory", 400);
 }
 # Pass valid and invalid paths to tree action
+test_page("?p=.git;a=tree;f=$files[0];hb=HEAD",
+	  'tree: look up existent file', 400);
 test_page("?p=.git;a=tree;f=does_not_exist;hb=HEAD",
 	  'tree: look up non-existent file', 404);
 test_page("?p=.git;a=tree;f=$directories[0];hb=HEAD",
Signature · 1 line
1.6.0.90.gf3f7b
#58 lore raw

Re: [PATCH 2/3 v2] add new Perl API: Git::Repo, Git::Commit, Git::Tag, and Git::RepoRoot

In reply to Lea Wiemann

Since I didn't mention it: The patch series applies on next.

perl/Git/Repo.pm line 17

Quoted 8 lines · Lea Wiemann
perl/Git/Repo.pm new file +8 -0
+Git::Repo - Read-only access to the Git repositories.
+
+Error handling is simple: On a consistent repository, the Perl
+interface will never die.  You can use the get_sha1 method to resolve
+arbitrary object names or check the existence of SHA1 hashes; get_sha1
+will return undef if the object does not exist in the repository.  Any
+SHA1 that is returned by get_sha1 can be safely passed to the other
+Git::Repo methods.

Here's some elaboration on the rationale behind the error handling. As a reminder, what we do is force developers to resolve object identifiers (such as HEAD^) into SHA1s first, rather than allowing them to pass in arbitrary object identifiers into functions. Here's why:

a) There's really just one point where errors can occur: at the input boundary (like the command line). Hence, you usually need one to three get_sha1 calls to resolve your input object names, and the rest of your program will be error-handling free with regard to Git::Repo (that is, if it dies it's either a bug or an error in the repository structure).

On the other hand, if you don't have such an explicit error-checking boundary at the beginning of your program (where you resolve all identifiers), you basically allow invalid object identifiers to "creep" into your code. For instance, gitweb oftentimes would have statements like "or die 'ls-tree failed'" or "or die 'commit not found'" deep inside a function -- in some cases, I found out that these failures *could* not even happen since the objects were guaranteed to exist by earlier calls (and hence it was basically dead code); and in many cases the error messages were simply non-descript -- which brings me to the next point:

b) Error reporting is really hard to implement: For instance, if diff-tree returns non-zero, then unless you scrape its STDERR, you can't tell which of the two to-be-diffed objects didn't exist (or had the wrong type), and hence you're oftentimes stuck with a generic 'diff-tree failed' message. In other words, if a simple diff-tree call goes wrong, there are three possible causes: (1) The left object is invalid, (2) the right object is invalid, (3) something fatal happened (bug or repository breakage). Distinguishing these cases is hard, and moving the object-resolving code to the beginning of the API user's program means that diff-tree failure can only indicate case (3).

c) The error messages you could get from an API are not usually what you want anyway. So if you write

my $diff = diff_tree($obj_identifier_1, $obj_identifier_2)

and expect it to die with a descriptive error message if one of the two identifiers doesn't point to a valid tree object, the best your error message can possibly be is "git diff-tree: HEAD^:foo/bar is not a valid tree object". Which leaves users puzzled because (1) they didn't call diff-tree, and (2) the program might have constructed the "HEAD^:foo/bar" string, and they only passed in parts of it, so it's not clear to them where "HEAD^:foo/bar" comes from.

d) Last but not least, using exceptions to communicate errors is against Perl coding conventions -- I would even have to look up the syntax to check for a specific exception type after an 'eval' block, because I simply never needed it.

So I think those four reasons really prevail over the extra work of having to make 1-3 get_sha1 calls at the beginning of your program (and providing proper error messages if they fail).

#59 lore raw

Re: [PATCH 2/3 v2] add new Perl API: Git::Repo, Git::Commit, Git::Tag, and Git::RepoRoot

In reply to Lea Wiemann
Quoted 3 lines · Lea Wiemann

+=head1 DESCRIPTION
+...
+The author, committer and message methods return Unicoded strings,

Unicoded?

perl/Git/Commit.pm line 96

Quoted 4 lines
perl/Git/Commit.pm new file +4 -0
+decoded according to the "encoding" header, with UTF-8 and then
+Latin-1 as fallbacks.  (These Unicode strings can contain code points
+...
+decoding for you, so you should not normally need this method.

Anyway, nicely written description.

Quoted 69 lines

+=back

perl/Git/Commit.pm new file +68 -0
+
+=cut
+
+
+sub tree {
+	my $self = shift;
+	$self->_load;
+	return $self->{tree};
+}
+
+sub parents {
+	my $self = shift;
+	$self->_load;
+	return map { ref($self)->new($self->repo, $_) } @{$self->{parents}};
+}
+
+sub author {
+	my $self = shift;
+	$self->_load;
+	return $self->_decode($self->{author});
+}
+
+sub committer {
+	my $self = shift;
+	$self->_load;
+	return $self->_decode($self->{committer});
+}
+
+sub message {
+	my $self = shift;
+	$self->_load;
+	return $self->_decode($self->{message});
+}
+
+sub encoding {
+	my $self = shift;
+	$self->_load;
+	return $self->{encoding};
+}
+
+# Auxiliary method to load (and parse) the commit object from the
+# repository if it hasn't already been loaded.  Optional parameter:
+# The raw contents of the commit object; the commit object will be
+# retrieved from the repository if that parameter is not given.
+sub _load {
+	my ($self, $raw_text) = shift;
+	return if exists $self->{message};  # already loaded
+
+	my $sha1 = $self->sha1;
+	if (!defined $raw_text) {
+		# Retrieve from the repository.
+		(my $type, $raw_text) = $self->repo->get_object($sha1);
+		die "$sha1 is a $type object (expected a commit object)"
+		    unless $type eq 'commit';
+	}
+
+	(my $header, $self->{message}) = split "\n\n", $raw_text, 2;
+	# Parse header.
+	for my $line (split "\n", $header) {
+		local $/ = "\n"; # for chomp
+		chomp($line);
+		my ($key, $value) = split ' ', $line, 2;
+		if ($key eq 'tree') {
+			$self->{tree} = $value;
+		} elsif ($key eq 'parent') {
+			push @{$self->{parents}}, $value;
+		} elsif ($key eq 'author') {
+			$self->{author} = $value;

perl/Git/Commit.pm line 176

Quoted 10 lines
perl/Git/Commit.pm new file +10 -0
+		} elsif ($key eq 'committer') {
+			$self->{committer} = $value;
+		} elsif ($key eq 'encoding') {
+			$self->{encoding} = $value;
+		} else {
+			# Ignore unrecognized header lines.
+		}
+	}
+	undef;
+}

Aside from seeming repetitive and quite similar to Git::Tag::_load(), I have to wonder how parent rewriting and grafts come into the picture. Git::Repo::get_object is just a cat-file which means you are ignoring any grafts. As a design decision it is fine, but it needs to be documented.

Also if you run "per path history" using "rev-list $head -- $path" to collect commits that touch the named $path, you might want to get rewritten parents for each commit and use it in the presentation, but it is something you cannot cache easily (i.e. you should not be reusing the parent list rewritten with respect to other paths).

The tests looked fine, too.

#60 lore raw

Re: [PATCH 2/3 v2] add new Perl API: Git::Repo, Git::Commit, Git::Tag, and Git::RepoRoot

Quoted 4 lines · Junio C Hamano
Quoted 1 line · Lea Wiemann

+The author, committer and message methods return Unicoded strings,

Unicoded?

Fixed. :)

Quoted 5 lines

perl/Git/Commit.pm line 143

Quoted 3 lines
perl/Git/Commit.pm new file +3 -0
+# Auxiliary method to load (and parse) the commit object from the
+# repository if it hasn't already been loaded. [...]
+sub _load {

Aside from seeming repetitive and quite similar to Git::Tag::_load(),

I didn't see a clean way to extract common code since there are differences in how some headers are parsed (e.g. 'parents' is an array).

Quoted 3 lines

I have to wonder how parent rewriting and grafts come into the picture. Git::Repo::get_object is just a cat-file which means you are ignoring any grafts. As a design decision it is fine, but it needs to be documented.

I'm not sure if I understand grafts correctly, but would something like the following piece of text in the documentation for Git::Commit->parents cover it?

"Note that this method does not currently respect grafts. Hence, if you are operating on a repository that contains grafts (e.g. a shallow clone), the commit objects returned by the 'parents' method may not exist in the repository; your program will die if you call an attribute accessor method (such as 'author' or 'message') of any parent of an object with grafted parents. Thus, if you want your program to work with shallow clones, you will have to check the .git/info/grafts file yourself before accessing any parents.

You must not rely on this behavior. In the future, the 'parents' method may return the grafted parents instead of the parents recorded in the commit object."

Quoted 3 lines

Also if you run "per path history" using "rev-list $head -- $path" to collect commits that touch the named $path, you might want to get rewritten parents for each commit and use it in the presentation,

You're referring to gitweb here, right? (The perl API doesn't use rev-list.) I don't think gitweb is currently tested with grafted repositories at all, but in general it should respect rewritten parents, since the parse_commit function uses rev-list without the 'raw' format, and thus it should get the rewritten parents.

-- Lea

#61 lore raw

Re: [PATCH 1/3 v10] gitweb: add test suite with Test::WWW::Mechanize::CGI

In reply to Lea Wiemann
Quoted 7 lines · Lea Wiemann

This test uses Test::WWW::Mechanize::CGI to check gitweb's output. It also uses HTML::Lint, XML::Parser, and Archive::Tar (if present, each) to validate the HTML/XML/tgz output, and checks all links on the tested pages if --long-tests is given.

This s-o-b chain is a bit confusing; was this authored by you or Jakub?

Quoted 32 lines
Makefile +1 -0
index ca418fc..35779a7 100644
@@ -1289,6 +1289,7 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
 	@echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
 	@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
+	@echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
t/t9503-gitweb-Mechanize.sh new file +14 -0
new file mode 100755
index 0000000..53f2a8a
@@ -0,0 +1,144 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Jakub Narebski
+# Copyright (c) 2008 Lea Wiemann
+#
+
+# This test supports the --long-tests option.
+
+# This test only runs on Perl 5.8 and later versions, since
+# Test::WWW::Mechanize::CGI requires Perl 5.8.
+
+test_description='gitweb tests (using WWW::Mechanize)
+
+This test uses Test::WWW::Mechanize::CGI to test gitweb.'

t/t9503-gitweb-Mechanize.sh line 24

Quoted 10 lines
t/t9503-gitweb-Mechanize.sh new file +10 -0
+
+# helper functions
+
+safe_chmod () {
+	chmod "$1" "$2" &&
+	if [ "$(git config --get core.filemode)" = false ]
+	then
+		git update-index --chmod="$1" "$2"
+	fi
+}

You have this in t9500 as well. Perhaps it can go to test-lib?

t/t9503-gitweb-Mechanize.sh line 34

Quoted 9 lines
t/t9503-gitweb-Mechanize.sh new file +9 -0
+. ./test-lib.sh
+
+# check if test can be run
+"$PERL_PATH" -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
+	test_expect_success \
+		'skipping gitweb tests, perl version is too old' :
+	test_done
+	exit
+}

It may be helpful to say what exactly is lacking (either "Please upgrade your Perl to 5.8", or "We want decode_utf8() that can CROAK").

t/t9503-gitweb-Mechanize.sh line 41

Quoted 6 lines
t/t9503-gitweb-Mechanize.sh new file +6 -0
+"$PERL_PATH" -MTest::WWW::Mechanize::CGI -e '' >/dev/null 2>&1 || {
+	test_expect_success \
+		'skipping gitweb tests, Test::WWW::Mechanize::CGI not found' :
+	test_done
+	exit
+}

This one is better then the previous one. t3300, t4000, t5540, t9113, t9113, t9600, and t9700 use "say" (or say_color), t3902, t4016, t5000, and t7004 just use "echo", and t9200, t9400, t9401 and t9500 do this phoney "success". We should standardize these by introducing "test_stop_early $msg". Then we can lose test_done and exit from these places.

t/t9503-gitweb-Mechanize.sh line 90

Quoted 5 lines
t/t9503-gitweb-Mechanize.sh new file +4 -0
+# set up test repository
+test_expect_success 'set up test repository' '
...
+	test_tick && git pull . b
+'

That "pull . b" is somewhat old fashioned, but is Ok.

t/t9503-gitweb-Mechanize.sh line 94

Quoted 3 lines
t/t9503-gitweb-Mechanize.sh new file +3 -0
+# set up gitweb configuration
+safe_pwd="$("$PERL_PATH" -MPOSIX=getcwd -e 'print quotemeta(getcwd)')"
+large_cache_root="../t9503/large_cache.tmp"

Please use $TEST_DIRECTORY without relying on the location of "t/trash directory"; it was painful to fix all of them.

t/t9503-gitweb-Mechanize.sh line 111

Quoted 8 lines
t/t9503-gitweb-Mechanize.sh new file +7 -0
+test_expect_success 'create file cache directory' \
+	'mkdir -p "$large_cache_root"'
+cat >gitweb_config.perl <<EOF
+# gitweb configuration for tests
...
+our @stylesheets = ("file:///$safe_pwd/../../gitweb/gitweb.css");
+our \$logo = "file:///$safe_pwd/../../gitweb/git-logo.png";
+our \$favicon = "file:///$safe_pwd/../../gitweb/git-favicon.png";

These also assume "t/trash directory" not being "t/trash/t9503".

t/t9503-gitweb-Mechanize.sh line 139

Quoted 3 lines
t/t9503-gitweb-Mechanize.sh new file +3 -0
+test_external \
+	'test gitweb output' \
+	"$PERL_PATH" ../t9503/test.pl

So does this, and you have catfile('..', '..', ...) in the perl part of this test.

Quoted 20 lines
t/t9503-gitweb-Mechanize.sh new file +20 -0
+# Search form
+
+# Search commit
+if (get_summary && $mech->submit_form_ok(
+	    { form_number => 1, fields => { 's' => 'Initial' } },
+	    'submit search form (default: commit search)')) {
+	check_page;
+	$mech->content_contains('Initial commit',
+				'content contains commit we searched for');
+}
+
+# Pickaxe
+if (get_summary && $mech->submit_form_ok(
+	    { form_number => 1, fields => { 's' => 'pickaxe test string',
+					    'st' => 'pickaxe' } },
+	    'submit search form (pickaxe)')) {
+	check_page;
+	test_link( { text => 'dir1/file1' }, 'file found with pickaxe' );
+	$mech->content_contains('A U Thor', 'commit author mentioned');
+}

t/t9503-gitweb-Mechanize.sh

Quoted 10 lines
t/t9503-gitweb-Mechanize.sh new file +10 -0
+
+# Grep
+# Let's hope the pickaxe test string is still present in HEAD.
+if (get_summary && $mech->submit_form_ok(
+	    { form_number => 1, fields => { 's' => 'pickaxe test string',
+					    'st' => 'grep' } },
+	    'submit search form (grep)')) {
+	check_page;
+	test_link( { text => 'dir1/file1' }, 'file found with grep' );
+}

With these search oriented tests, making sure that you would find what you expect to find is obviously important, but shouldn't you be also making sure that irrelevant entries are not found?

It is great that there are tests for each view we care about, even though the way the individual views are tested look somewhat sketchy.

#62 lore raw

Re: [PATCH 1/3 v10] gitweb: add test suite with Test::WWW::Mechanize::CGI

Quoted 6 lines · Junio C Hamano
Quoted 2 lines · Lea Wiemann

This s-o-b chain is a bit confusing; was this authored by you or Jakub?

Jakub started it, I extended it. Should we have different SOB lines?

Quoted 9 lines

t/t9503-gitweb-Mechanize.sh line 24

Quoted 7 lines
t/t9503-gitweb-Mechanize.sh new file +7 -0
+safe_chmod () {
+	chmod "$1" "$2" &&
+	if [ "$(git config --get core.filemode)" = false ]
+	then
+		git update-index --chmod="$1" "$2"
+	fi
+}

You have this in t9500 as well. Perhaps it can go to test-lib?

Will do in the next version of this patch.

Quoted 6 lines

t/t9503-gitweb-Mechanize.sh line 31

Quoted 4 lines
t/t9503-gitweb-Mechanize.sh new file +4 -0
+# check if test can be run
+"$PERL_PATH" -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
+	test_expect_success \
+		'skipping gitweb tests, perl version is too old' :

It may be helpful to say what exactly is lacking

Right. Since Encode doesn't run on older Perl versions anyway, I'm changing it to

"$PERL_PATH" -e 'use 5.008' >/dev/null 2>&1 || {
	test_expect_success \
		'skipping gitweb tests, Perl 5.8 or newer required' :
Quoted 5 lines

t3300, t4000, t5540, t9113,
t9113, t9600, and t9700 use "say" (or say_color), t3902, t4016, t5000, and t7004 just use "echo", and t9200, t9400, t9401 and t9500 do this phoney "success". We should standardize these by introducing "test_stop_early $msg".

Yup; maybe "test_skip_all" is clearer though. I think this should be done in a separate patch.

Quoted 3 lines

t/t9503-gitweb-Mechanize.sh line 89

Quoted 1 line
t/t9503-gitweb-Mechanize.sh new file +1 -0
+	test_tick && git pull . b

That "pull . b" is somewhat old fashioned, but is Ok.

Is "git merge b" equivalent? (The test still passes with it.)

Quoted 4 lines
Quoted 1 line

+large_cache_root="../t9503/large_cache.tmp"

Please use $TEST_DIRECTORY without relying on the location of "t/trash directory"; it was painful to fix all of them.

Ok, fixed all of those. I'll also move the cache-setup code to patch 3 (gitweb caching), since it doesn't belong here as long as caching isn't implemented.

Quoted 5 lines
Quoted 1 line

+# Grep

With these search oriented tests, making sure that you would find what you expect to find is obviously important, but shouldn't you be also making sure that irrelevant entries are not found?

Technically yes, but I'm not inclined at the moment to write that test (at least while I'm not hacking the search part of gitweb). The test is basically only there to exercise the code and make sure it returns *something* sensible, which is where most breakages would occur.

Thanks for all your feedback! I'll wait with sending a new patch series until I've collected all feedback.

-- Lea