13 our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
18 run_git run_git_1line run_git_check_nooutput
19 run_git_test_anyoutput
20 git_config git_dir chdir_toplevel
21 current_branch parse_branch_spec
22 setup_config check_no_unwanted_metadata
23 wf_start wf wf_abort wf_done wf_contents);
30 print STDERR "DEBUG: $msg\n" or die $!;
34 # takes optional prefix arguments:
35 # coderef hook to call for each line read,
36 # with $_ containing chomped line; if not supplied,
38 # scalarref place to store exit status; if not supplied,
39 # nonzero exit status is fatal
40 my ($estatusr,$linecallr);
43 if (ref $ref eq 'SCALAR') {
45 } elsif (ref $ref eq 'CODE') {
48 die ref($ref)." @_ ?";
51 open GIT, "-|", 'git', @_ or die $!;
60 die "git @_ $!" if $!;
62 die "git @_ ($?)" unless $estatusr;
65 $$estatusr = 0 if $estatusr;
71 run_git(sub { $l = $_; }, @_);
72 die "git @_ ?" unless defined $l;
76 sub run_git_check_nooutput {
77 my ($what) = shift @_;
78 run_git(sub { die "$what $_\n"; }, @_);
81 sub run_git_test_anyoutput {
83 run_git(sub { $any=1; }, @_);
88 my ($cfgvar, $default) = @_;
90 run_git(\$estatus, sub {
95 die "$cfgvar ($estatus)" if $estatus;
98 die "$cfgvar ($estatus)" unless $estatus==0 || $estatus==256;
105 if (!defined $git_dir) {
106 $git_dir = run_git_1line(qw(rev-parse --git-dir));
111 sub chdir_toplevel () {
113 run_git(sub { $toplevel = $_; },
114 qw(rev-parse --show-toplevel));
115 die "not in working tree?\n" unless defined $toplevel;
116 chdir $toplevel or die "chdir toplevel $toplevel: $!\n";
119 sub current_branch () {
120 open R, git_dir().'/HEAD' or die "open HEAD $!";
121 my $ref = <R>; defined $ref or die $!;
124 if ($ref !~ s#^ref: ##) {
130 if ($ref =~ m#^refs/topbloke-(tip|base)s/([^/\@]*)\@([^/\@]*)/([^/]*)/#) {
138 DepSpec => "$2\@$3/$4/$'",
140 } elsif ($ref =~ m#^refs/heads/#) {
154 sub parse_branch_spec ($) {
157 my $spec = { }; # Email Domain DatePrefix DateNear Nick
159 my ($key,$val,$whats) = @_;
160 die "multiple $whats in branch spec\n" if exists $spec->{$key};
161 $spec->{$key} = $val;
165 if (s#([^/\@]*)\@([^/\@]*)/##) {
166 $set->('Email', $1, "email local parts") if length $1;
167 $set->('Domain', $2, "email domains") if length $1;
168 } elsif (s#([^/]*\~[^/]*)/##) {
171 open DATE, "-|", 'date','+%s','-d',$dspec or die $!;
173 close DATE or die "date parsing failed\n";
175 $set->('DateNear', $l, 'nearby dates');
176 } elsif (s#^([0-9][^/]*)/##) {
179 m/^\d{4}(?:-\d\d(?:-\d\d(?:T(?:\d\d(?:\d\d(?:\d\d(?:Z)?)?)?)?)?)?)?$/
180 or die "bad date prefix \`$dspec'\n";
181 $set->('DatePrefix', $dspec, 'date prefixes');
184 } elsif (s#^\.\./##) {
191 if (defined $rel_levels) {
192 my $branch = current_branch();
193 if (!defined $branch->{Nick}) {
194 die "relative branch spec \`$orig',".
195 " but current branch not a topbloke branch\n";
197 my ($ceaddr,$cdate,@l) = split /\//, $branch->{Nick};
199 die "relative branch spec \`$orig' has too many ../s\n";
200 $_ = (join '/', @l[0..$#l-$rel_levels]).'/'.$_;
206 sub setup_config () {
207 my (@files) = (qw(msg deps included flags pflags));
209 foreach my $iteration (qw(0 1)) {
210 foreach my $file (@files) {
211 my $cfgname = "merge.topbloke-$file";
212 my ($current, $current_estatus);
213 run_git(\$current_estatus,
214 sub { $current = $_; },
215 qw(config), "$cfgname.driver");
216 $current = "## failed $current_estatus" if $current_estatus;
217 next if $current =~ m/^topbloke-merge-driver --v$version /o;
218 die "$file $current ?" if $iteration;
219 debug("setting merge driver $file");
220 run_git(qw(config), "$cfgname.name",
221 "topbloke merge driver for $file");
222 run_git(qw(config), "$cfgname.driver",
223 "topbloke-merge-driver --v$version".
224 " $file %O %A %B %L");
226 my ($newattrs, $attrsfile);
227 foreach my $file (@files) {
228 my $path = ".topbloke/$file";
229 my $current = run_git_1line(qw(check-attr merge), $path);
230 $current =~ s#^\Q$path\E: merge: ## or die "$file $current ?";
231 my $want = "topbloke-$file";
232 next if $current eq $want;
233 die "$file $current ?" unless $current eq 'unspecified';
234 die "$file $current ?" if $iteration;
236 $attrsfile = git_dir()."/info/attributes";
237 $newattrs = new IO::File "$attrsfile.tmp", 'w'
238 or die "$attrsfile.tmp: $!";
239 if (!open OA, '<', "$attrsfile") {
240 die "$attrsfile $!" unless $!==&ENOENT;
243 print $newattrs $_ or die $!;
244 print "\n" or die $! unless chomp;
247 die $! unless close OA;
250 print $newattrs "$path\tmerge=$want\n" or die $!;
253 close $newattrs or die $!;
254 rename "$attrsfile.tmp", "$attrsfile" or die $!;
258 sub check_no_unwanted_metadata ($) {
259 # for checking foreign branches aren't contaminated
260 my ($gitbranch) = @_;
261 run_git_check_nooutput('foreign unexpectedly contains',
262 qw(ls-tree --name-only),
269 my $fh = new IO::File "$path.tmp", '>' or die "create $path.tmp: $!\n";
270 return [ $fh, $path ];
274 my ($wf, $data) = @_;
275 my ($fh, $path) = @$wf;
276 print $fh $data or die "write $path.tmp: $!\n";
281 my ($fh, $path) = @$wf;
283 unlink "$path.tmp" or die "remove $path.tmp: $!\n";
288 my ($fh, $path) = @$wf;
289 close $fh or die "finish writing $path.tmp: $!\n";
290 rename "$path.tmp", $path or die "install new $path: $!\n";
293 sub wf_contents ($$) {
294 my ($path,$contents) = @_;
295 my $wf = wf_start($path);