use POSIX;
use IO::File;
+use IPC::Open2;
package Topbloke;
@ISA = qw(Exporter);
@EXPORT = qw(debug
run_git run_git_1line run_git_check_nooutput
- run_git_test_anyoutput
+ run_git_test_anyoutput git_get_object
git_config git_dir chdir_toplevel
- current_branch parse_patch_spec
+ current_branch parse_patch_spec parse_patch_name
setup_config check_no_unwanted_metadata
+ patch_matches_spec
+ foreach_patch
flagsfile_add_flag
- wf_start wf wf_abort wf_done wf_contents);
+ wf_start wf wf_abort wf_done wf_contents
+ closeout);
%EXPORT_TAGS = ( );
@EXPORT_OK = qw();
}
+our $git_command = 'git';
+
sub debug ($) {
my ($msg) = @_;
print STDERR "DEBUG: $msg\n" or die $!;
die ref($ref)." @_ ?";
}
}
- open GIT, "-|", 'git', @_ or die $!;
+ open GIT, "-|", $git_command, @_ or die $!;
if ($linecallr) {
while (<GIT>) {
- chomp or die "$_ ?";
+ chomp or die "$git_command @_ gave $_ ?";
$linecallr->();
}
GIT->eof or die $!;
}
if (!close GIT) {
- die "git @_ $!" if $!;
+ die "$git_command @_ $!" if $!;
die unless $?;
- die "git @_ ($?)" unless $estatusr;
+ die "$git_command @_ ($?)" unless $estatusr;
$$estatusr = $?;
} else {
$$estatusr = 0 if $estatusr;
return $any;
}
+sub git_get_object ($) {
+ my ($objname) = @_;
+ our ($gro_pid, $gro_out, $gro_in);
+ if (!$gro_pid) {
+ $gro_pid = IPC::Open2::open2($gro_out, $gro_in,
+ $git_command, qw(cat-file --batch))
+ or die $!;
+ }
+ #debug("git_get_object $objname");
+ $SIG{'PIPE'} = 'IGN';
+ print $gro_in $objname,"\n" or die $!;
+ $gro_in->flush or die "$objname $!";
+ $SIG{'PIPE'} = 'DFL';
+ my $l = <$gro_out>;
+ chomp $l or die "$objname $l ?";
+ #debug("git_get_object $objname => $l");
+ if ($l =~ m/ missing$/) {
+ return 'missing';
+ } elsif (my ($type,$bytes) = $l =~ m/^\S+ (\w+) (\d+)$/) {
+ my $data = '';
+ if ($bytes) {
+ (read $gro_out, $data, $bytes) == $bytes or die "$objname $!";
+ }
+ my $nl;
+ (read $gro_out, $nl, 1) == 1 or die "$objname $!";
+ $nl eq "\n" or die "$objname ?";
+ return ($type, $data);
+ } else {
+ die "$objname $l";
+ }
+}
+
sub git_config ($$) {
my ($cfgvar, $default) = @_;
my ($l, $estatus);
};
}
if ($ref =~ m#^refs/topbloke-(tip|base)s/([^/\@]*)\@([^/\@]*)/([^/]*)/#) {
+ my $fullname = "$2\@$3/$4/$'";
return {
Kind => $1,
Email => $2,
Date => $4,
Nick => $', #',
Ref => $ref,
- DepSpec => "$2\@$3/$4/$'",
+ DepSpec => $fullname,
+ Fullname => $fullname,
};
} elsif ($ref =~ m#^refs/heads/#) {
return {
}
}
+sub parse_patch_name ($) {
+ my ($patch) = @_;
+ my ($eaddr, $date, $nick) = split /\//, $patch, 3;
+ defined $nick && length $nick or die "$patch ?";
+ my ($email, $domain) = $eaddr =~ m/^(.*)\@([^\@]+)$/
+ or die "$patch eaddr ?";
+ return {
+ Email => $email,
+ Domain => $domain,
+ Date => $date,
+ Nick => $nick,
+ Kind => 'tip',
+ DepSpec => $patch,
+ Fullname => $patch,
+ Ref => "refs/topbloke-tips/$patch",
+ };
+}
+
sub parse_patch_spec ($) {
my ($orig) = @_;
local $_ = $orig;
qw(.topbloke));
}
+sub patch_matches_spec ($$) {
+ my ($parsedname, $spec) = @_;
+ foreach my $k (qw(Email Domain Nick)) {
+ debug("patch_matches_spec mismatch $k"), return 0
+ if defined $spec->{$k} &&
+ $parsedname->{$k} ne $spec->{$k};
+ }
+ debug("patch_matches_spec mismatch DatePrefix"), return 0
+ if defined $spec->{DatePrefix} &&
+ substr($parsedname->{Date}, 0, length $spec->{DatePrefix})
+ ne $spec->{DatePrefix};
+ debug("patch_matches_spec match"), return 1;
+}
+
+sub foreach_patch ($$$$) {
+ my ($spec, $deleted_ok, $want, $body) = @_;
+ # runs $body->($patch, $parsedname, \%flags, \%deps, \%pflags, \%included)
+ # $want->[0] 1 2 3
+ # where $deps->{$fullname} etc. are 1 for true or nonexistent for false
+ # and if $want->[$item] is not true, the corresponding item may be undef
+ # and $parsedname is only valid if $spec is not undef
+ # (say $spec { } if you want the name parsed but no restrictions)
+ run_git(sub {
+ debug("foreach_patch considering $_");
+ m/ / or die "$_ ?";
+ my $objname = $`;
+ my @out;
+ my $patch = substr($',19); #');
+ $want->[0] ||= !$deleted_ok;
+ foreach my $file (qw(flags deps pflags included)) {
+
+ if ($file eq 'deps') {
+ # do this check after checking for deleted patches,
+ # so we don't parse deleted patches' names
+ # right, check the spec next
+ if ($spec) {
+ my $have = parse_patch_name($patch);
+ debug("foreach_patch mismatch"), return
+ unless patch_matches_spec($have, $spec);
+ unshift @out, $have;
+ } else {
+ unshift @out, undef;
+ }
+ }
+
+ if (!shift @$want) {
+ push @out, undef;
+ next;
+ }
+
+ my ($got, $data) = git_get_object("$objname:.topbloke/$file");
+ die "$patch $file ?" unless defined $data;
+ my %data;
+ $data{$_}=1 foreach split /\n/, $data;
+
+ if ($file eq 'flags') {
+ debug("foreach_patch Deleted"), return
+ if !$deleted_ok && $data{Deleted};
+ }
+
+ push @out, \%data;
+ }
+ debug("foreach_patch YES @out"), return
+ $body->($patch, @out);
+ },
+ qw(for-each-ref --format), '%(objectname) %(refname)',
+ qw(refs/topbloke-tips));
+}
+
sub flagsfile_add_flag ($$) {
# works on "deps" too
my ($flagsfile, $flag) = @_;
wf_done($wf);
}
+sub closeout () {
+ STDOUT->error and die $!;
+ close STDOUT or die $!;
+}
+
1;