X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=topbloke.git;a=blobdiff_plain;f=Topbloke.pm;h=b1303e17707506748a1c3b171c207f8c097e6667;hp=8979a6ed851d41171acecdc626d5b7577a55a7e2;hb=c0f7fab5983a34c10790e39e7f48b546ca4011f7;hpb=2a6bbdd92c6d4237c78f66be47f93bee2a60e46e diff --git a/Topbloke.pm b/Topbloke.pm index 8979a6e..b1303e1 100644 --- a/Topbloke.pm +++ b/Topbloke.pm @@ -3,6 +3,9 @@ use strict; use warnings; +use POSIX; +use IO::File; + package Topbloke; BEGIN { @@ -11,21 +14,92 @@ BEGIN { $VERSION = 1.00; @ISA = qw(Exporter); - @EXPORT = qw(parse_branch_spec current_tb_branch run_git_1line); + @EXPORT = qw(debug + run_git run_git_1line run_git_check_nooutput + run_git_test_anyoutput + git_config git_dir chdir_toplevel + current_branch parse_branch_spec + setup_config check_no_unwanted_metadata + wf_start wf wf_abort wf_done wf_contents); %EXPORT_TAGS = ( ); @EXPORT_OK = qw(); } +sub debug ($) { + my ($msg) = @_; + print STDERR "DEBUG: $msg\n" or die $!; +} -sub run_git_1line { +sub run_git { + # takes optional prefix arguments: + # coderef hook to call for each line read, + # with $_ containing chomped line; if not supplied, + # output is not read + # scalarref place to store exit status; if not supplied, + # nonzero exit status is fatal + my ($estatusr,$linecallr); + while (ref $_[0]) { + my $ref = shift @_; + if (ref $ref eq 'SCALAR') { + $estatusr = $ref; + } elsif (ref $ref eq 'CODE') { + $linecallr = $ref; + } else { + die ref($ref)." @_ ?"; + } + } open GIT, "-|", 'git', @_ or die $!; - my $l = ; - $?=0; - close GIT or die "git @_ failed ($?)\n"; - chomp $l or die "@_ ?"; + if ($linecallr) { + while () { + chomp or die "$_ ?"; + $linecallr->(); + } + GIT->eof or die $!; + } + if (!close GIT) { + die "git @_ $!" if $!; + die unless $?; + die "git @_ ($?)" unless $estatusr; + $$estatusr = $?; + } else { + $$estatusr = 0 if $estatusr; + } +} + +sub run_git_1line { + my $l; + run_git(sub { $l = $_; }, @_); + die "git @_ ?" unless defined $l; return $l; } +sub run_git_check_nooutput { + my ($what) = shift @_; + run_git(sub { die "$what $_\n"; }, @_); +} + +sub run_git_test_anyoutput { + my $any = 0; + run_git(sub { $any=1; }, @_); + return $any; +} + +sub git_config ($$) { + my ($cfgvar, $default) = @_; + my ($l, $estatus); + run_git(\$estatus, sub { + die if defined $l; + $l = $_; }, + qw(config), $cfgvar); + if (defined $l) { + die "$cfgvar ($estatus)" if $estatus; + return $l; + } else { + die "$cfgvar ($estatus)" unless $estatus==0 || $estatus==256; + return $default; + } +} + sub git_dir () { our $git_dir; if (!defined $git_dir) { @@ -34,7 +108,15 @@ sub git_dir () { return $git_dir; } -sub current_tb_branch () { +sub chdir_toplevel () { + my $toplevel; + run_git(sub { $toplevel = $_; }, + qw(rev-parse --show-toplevel)); + die "not in working tree?\n" unless defined $toplevel; + chdir $toplevel or die "chdir toplevel $toplevel: $!\n"; +} + +sub current_branch () { open R, git_dir().'/HEAD' or die "open HEAD $!"; my $ref = ; defined $ref or die $!; close R; @@ -107,7 +189,7 @@ sub parse_branch_spec ($) { } } if (defined $rel_levels) { - my $branch = current_tb_branch(); + my $branch = current_branch(); if (!defined $branch->{Nick}) { die "relative branch spec \`$orig',". " but current branch not a topbloke branch\n"; @@ -122,18 +204,97 @@ sub parse_branch_spec ($) { } sub setup_config () { - my ($files) = (qw(msg deps included flags gitattributes)); + my (@files) = (qw(msg deps included flags pflags)); my $version = 1; - foreach my $file ($files) { - my $cfgname = "merge.topbloke-$file."; - my $current = run_git_1line(qw(config), "$cfgname.driver"); - next if $current =~ m/^topbloke-merge-driver --v$version /o; - debug("setting merge driver $file"); - run_git_1line(qw(config), "$cfgname.name", - "topbloke merge driver for $file"); - run_git_1line(qw(config), "$cfgname.driver", - "topbloke-merge-driver --v$version $file %O %A %B %L"); + foreach my $iteration (qw(0 1)) { + foreach my $file (@files) { + my $cfgname = "merge.topbloke-$file"; + my ($current, $current_estatus); + run_git(\$current_estatus, + sub { $current = $_; }, + qw(config), "$cfgname.driver"); + $current = "## failed $current_estatus" if $current_estatus; + next if $current =~ m/^topbloke-merge-driver --v$version /o; + die "$file $current ?" if $iteration; + debug("setting merge driver $file"); + run_git(qw(config), "$cfgname.name", + "topbloke merge driver for $file"); + run_git(qw(config), "$cfgname.driver", + "topbloke-merge-driver --v$version". + " $file %O %A %B %L"); + } + my ($newattrs, $attrsfile); + foreach my $file (@files) { + my $path = ".topbloke/$file"; + my $current = run_git_1line(qw(check-attr merge), $path); + $current =~ s#^\Q$path\E: merge: ## or die "$file $current ?"; + my $want = "topbloke-$file"; + next if $current eq $want; + die "$file $current ?" unless $current eq 'unspecified'; + die "$file $current ?" if $iteration; + if (!$newattrs) { + $attrsfile = git_dir()."/info/attributes"; + $newattrs = new IO::File "$attrsfile.tmp", 'w' + or die "$attrsfile.tmp: $!"; + if (!open OA, '<', "$attrsfile") { + die "$attrsfile $!" unless $!==&ENOENT; + } else { + while () { + print $newattrs $_ or die $!; + print "\n" or die $! unless chomp; + } + die $! if OA->error; + die $! unless close OA; + } + } + print $newattrs "$path\tmerge=$want\n" or die $!; + } + last if !$newattrs; + close $newattrs or die $!; + rename "$attrsfile.tmp", "$attrsfile" or die $!; } } +sub check_no_unwanted_metadata ($) { + # for checking foreign branches aren't contaminated + my ($gitbranch) = @_; + run_git_check_nooutput('foreign unexpectedly contains', + qw(ls-tree --name-only), + "$gitbranch:", + qw(.topbloke)); +} + +sub wf_start ($) { + my ($path) = @_; + my $fh = new IO::File "$path.tmp", '>' or die "create $path.tmp: $!\n"; + return [ $fh, $path ]; +} + +sub wf ($$) { + my ($wf, $data) = @_; + my ($fh, $path) = @$wf; + print $fh $data or die "write $path.tmp: $!\n"; +} + +sub wf_abort ($) { + my ($wf) = @_; + my ($fh, $path) = @$wf; + close $fh; + unlink "$path.tmp" or die "remove $path.tmp: $!\n"; +} + +sub wf_done ($) { + my ($wf) = @_; + my ($fh, $path) = @$wf; + close $fh or die "finish writing $path.tmp: $!\n"; + rename "$path.tmp", $path or die "install new $path: $!\n"; +} + +sub wf_contents ($$) { + my ($path,$contents) = @_; + my $wf = wf_start($path); + wf($wf, $contents); + wf_done($wf); +} + 1;