chiark / gitweb /
3ac042cdd3052831aa611f006f72b04a8b51c609
[dgit.git] / Debian / Dgit.pm
1 # -*- perl -*-
2 # dgit
3 # Debian::Dgit: functions common to dgit and its helpers and servers
4 #
5 # Copyright (C) 2015-2019  Ian Jackson
6 #
7 #    This program is free software; you can redistribute it and/or modify
8 #    it under the terms of the GNU General Public License as published by
9 #    the Free Software Foundation; either version 3 of the License, or
10 #    (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU General Public License for more details.
16 #
17 #    You should have received a copy of the GNU General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 package Debian::Dgit;
21
22 use strict;
23 use warnings;
24
25 use Carp;
26 use POSIX;
27 use IO::Handle;
28 use Config;
29 use Digest::SHA;
30 use Data::Dumper;
31 use IPC::Open2;
32 use File::Path qw(:DEFAULT make_path);
33 use File::Basename;
34 use Dpkg::Control::Hash;
35 use Debian::Dgit::ExitStatus;
36 use Debian::Dgit::I18n;
37
38 BEGIN {
39     use Exporter   ();
40     our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
41
42     $VERSION     = 1.00;
43     @ISA         = qw(Exporter);
44     @EXPORT      = qw(setup_sigwarn forkcheck_setup forkcheck_mainprocess
45                       dep14_version_mangle
46                       debiantags debiantag_new
47                       debiantag_maintview
48                       upstreamversion
49                       upstream_commitish_search resolve_upstream_version
50                       stripepoch source_file_leafname is_orig_file_of_p_v
51                       server_branch server_ref
52                       stat_exists link_ltarget rename_link_xf
53                       hashfile
54                       fail failmsg ensuredir ensurepath
55                       must_getcwd executable_on_path
56                       waitstatusmsg failedcmd_waitstatus
57                       failedcmd_report_cmd failedcmd
58                       runcmd shell_cmd cmdoutput cmdoutput_errok
59                       @git
60                       git_rev_parse changedir_git_toplevel git_cat_file
61                       git_get_ref git_get_symref git_for_each_ref
62                       git_for_each_tag_referring is_fast_fwd
63                       git_check_unmodified
64                       git_reflog_action_msg  git_update_ref_cmd
65                       rm_subdir_cached read_tree_subdir
66                       read_tree_debian read_tree_upstream
67                       make_commit hash_commit hash_commit_text
68                       reflog_cache_insert reflog_cache_lookup
69                       $package_re $component_re $suite_re $deliberately_re
70                       $distro_re $versiontag_re $series_filename_re
71                       $orig_f_comp_re $orig_f_sig_re $orig_f_tail_re
72                       $extra_orig_namepart_re
73                       $git_null_obj
74                       $branchprefix
75                       $ffq_refprefix $gdrlast_refprefix
76                       initdebug enabledebug enabledebuglevel
77                       printdebug debugcmd
78                       $printdebug_when_debuglevel $debugcmd_when_debuglevel
79                       $debugprefix *debuglevel *DEBUG
80                       shellquote printcmd messagequote
81                       $negate_harmful_gitattrs
82                       changedir git_slurp_config_src
83                       gdr_ffq_prev_branchinfo
84                       parsecontrolfh parsecontrol parsechangelog
85                       getfield parsechangelog_loop
86                       playtree_setup);
87     # implicitly uses $main::us
88     %EXPORT_TAGS = ( policyflags => [qw(NOFFCHECK FRESHREPO NOCOMMITCHECK)],
89                      playground => [qw(record_maindir $maindir $local_git_cfg
90                                        $maindir_gitdir $maindir_gitcommon
91                                        fresh_playground
92                                        ensure_a_playground)]);
93     @EXPORT_OK   = ( @{ $EXPORT_TAGS{policyflags} },
94                      @{ $EXPORT_TAGS{playground} } );
95 }
96
97 our @EXPORT_OK;
98
99 our $package_re = '[0-9a-z][-+.0-9a-z]*';
100 our $component_re = '[0-9a-zA-Z][-+.0-9a-zA-Z]*';
101 our $suite_re = '[-+.0-9a-z]+';
102 our $deliberately_re = "(?:TEST-)?$package_re";
103 our $distro_re = $component_re;
104 our $versiontag_re = qr{[-+.\%_0-9a-zA-Z/]+};
105 our $branchprefix = 'dgit';
106 our $series_filename_re = qr{(?:^|\.)series(?!\n)$}s;
107 our $extra_orig_namepart_re = qr{[-0-9a-zA-Z]+};
108 our $orig_f_comp_re = qr{orig(?:-$extra_orig_namepart_re)?};
109 our $orig_f_sig_re = '\\.(?:asc|gpg|pgp)';
110 our $orig_f_tail_re = "$orig_f_comp_re\\.tar(?:\\.\\w+)?(?:$orig_f_sig_re)?";
111 our $git_null_obj = '0' x 40;
112 our $ffq_refprefix = 'ffq-prev';
113 our $gdrlast_refprefix = 'debrebase-last';
114 our $printdebug_when_debuglevel = 1;
115 our $debugcmd_when_debuglevel = 1;
116
117 our (@git) = qw(git);
118
119 # these three all go together, only valid after record_maindir
120 our $maindir;
121 our $maindir_gitdir;
122 our $maindir_gitcommon;
123
124 # policy hook exit status bits
125 # see dgit-repos-server head comment for documentation
126 # 1 is reserved in case something fails with `exit 1' and to spot
127 # dynamic loader, runtime, etc., failures, which report 127 or 255
128 sub NOFFCHECK () { return 0x2; }
129 sub FRESHREPO () { return 0x4; }
130 sub NOCOMMITCHECK () { return 0x8; }
131
132 our $debugprefix;
133 our $debuglevel = 0;
134
135 our $negate_harmful_gitattrs =
136     "-text -eol -crlf -ident -filter -working-tree-encoding";
137     # ^ when updating this, alter the regexp in dgit:is_gitattrs_setup
138
139 our $forkcheck_mainprocess;
140
141 sub forkcheck_setup () {
142     $forkcheck_mainprocess = $$;
143 }
144
145 sub forkcheck_mainprocess () {
146     # You must have called forkcheck_setup or setup_sigwarn already
147     getppid != $forkcheck_mainprocess;
148 }
149
150 sub setup_sigwarn () {
151     forkcheck_setup();
152     $SIG{__WARN__} = sub { 
153         confess $_[0] if forkcheck_mainprocess;
154     };
155 }
156
157 sub initdebug ($) { 
158     ($debugprefix) = @_;
159     open DEBUG, ">/dev/null" or confess "$!";
160 }
161
162 sub enabledebug () {
163     open DEBUG, ">&STDERR" or confess "$!";
164     DEBUG->autoflush(1);
165     $debuglevel ||= 1;
166 }
167     
168 sub enabledebuglevel ($) {
169     my ($newlevel) = @_; # may be undef (eg from env var)
170     confess if $debuglevel;
171     $newlevel //= 0;
172     $newlevel += 0;
173     return unless $newlevel;
174     $debuglevel = $newlevel;
175     enabledebug();
176 }
177     
178 sub printdebug {
179     # Prints a prefix, and @_, to DEBUG.  @_ should normally contain
180     # a trailing \n.
181
182     # With no (or only empty) arguments just prints the prefix and
183     # leaves the caller to do more with DEBUG.  The caller should make
184     # sure then to call printdebug with something ending in "\n" to
185     # get the prefix right in subsequent calls.
186
187     return unless $debuglevel >= $printdebug_when_debuglevel;
188     our $printdebug_noprefix;
189     print DEBUG $debugprefix unless $printdebug_noprefix;
190     pop @_ while @_ and !length $_[-1];
191     return unless @_;
192     print DEBUG @_ or confess "$!";
193     $printdebug_noprefix = $_[-1] !~ m{\n$};
194 }
195
196 sub messagequote ($) {
197     local ($_) = @_;
198     s{\\}{\\\\}g;
199     s{\n}{\\n}g;
200     s{\x08}{\\b}g;
201     s{\t}{\\t}g;
202     s{[\000-\037\177]}{ sprintf "\\x%02x", ord $& }ge;
203     $_;
204 }
205
206 sub shellquote {
207     my @out;
208     local $_;
209     defined or confess __ 'internal error' foreach @_;
210     foreach my $a (@_) {
211         $_ = $a;
212         if (!length || m{[^-=_./:0-9a-z]}i) {
213             s{['\\]}{'\\$&'}g;
214             push @out, "'$_'";
215         } else {
216             push @out, $_;
217         }
218     }
219     return join ' ', @out;
220 }
221
222 sub printcmd {
223     my $fh = shift @_;
224     my $intro = shift @_;
225     print $fh $intro." ".(join '', shellquote @_)."\n" or confess "$!";
226 }
227
228 sub debugcmd {
229     my $extraprefix = shift @_;
230     printcmd(\*DEBUG,$debugprefix.$extraprefix,@_)
231         if $debuglevel >= $debugcmd_when_debuglevel;
232 }
233
234 sub dep14_version_mangle ($) {
235     my ($v) = @_;
236     # DEP-14 patch proposed 2016-11-09  "Version Mangling"
237     $v =~ y/~:/_%/;
238     $v =~ s/\.(?=\.|$|lock$)/.#/g;
239     return $v;
240 }
241
242 sub debiantag_new ($$) { 
243     my ($v,$distro) = @_;
244     return "archive/$distro/".dep14_version_mangle $v;
245 }
246
247 sub debiantag_maintview ($$) { 
248     my ($v,$distro) = @_;
249     return "$distro/".dep14_version_mangle $v;
250 }
251
252 sub debiantags ($$) {
253     my ($version,$distro) = @_;
254     map { $_->($version, $distro) } (\&debiantag_new, \&debiantag_maintview);
255 }
256
257 sub stripepoch ($) {
258     my ($vsn) = @_;
259     $vsn =~ s/^\d+\://;
260     return $vsn;
261 }
262
263 sub upstreamversion ($) {
264     my ($vsn) = @_;
265     $vsn =~ s/-[^-]+$//;
266     return $vsn;
267 }
268
269 sub source_file_leafname ($$$) {
270     my ($package,$vsn,$sfx) = @_;
271     return "${package}_".(stripepoch $vsn).$sfx
272 }
273
274 sub is_orig_file_of_p_v ($$$) {
275     my ($f, $package, $upstreamvsn) = @_;
276     my $base = source_file_leafname $package, $upstreamvsn, '';
277     return 0 unless $f =~ m/^\Q$base\E\.$orig_f_tail_re$/;
278     return 1;
279 }
280
281 sub server_branch ($) { return "$branchprefix/$_[0]"; }
282 sub server_ref ($) { return "refs/".server_branch($_[0]); }
283
284 sub stat_exists ($) {
285     my ($f) = @_;
286     return 1 if stat $f;
287     return 0 if $!==&ENOENT;
288     confess "stat $f: $!";
289 }
290
291 sub _us () {
292     $::us // ($0 =~ m#[^/]*$#, $&);
293 }
294
295 sub failmsg {
296     my $s = f_ "error: %s\n", "@_";
297     $s =~ s/\n\n$/\n/g;
298     my $prefix = _us().": ";
299     $s =~ s/^/$prefix/gm;
300     return "\n".$s;
301 }
302
303 sub fail {
304     die failmsg @_;
305 }
306
307 sub ensuredir ($) {
308     my ($dir) = @_; # does not create parents
309     return if mkdir $dir;
310     return if $! == EEXIST;
311     confess "mkdir $dir: $!";
312 }
313
314 sub ensurepath ($$) {
315     my ($firsttocreate, $subdir) = @_; # creates necessary bits of $subidr
316     ensuredir $firsttocreate;
317     make_path "$firsttocreate/$subdir";
318 }
319
320 sub must_getcwd () {
321     my $d = getcwd();
322     defined $d or fail f_ "getcwd failed: %s\n", $!;
323     return $d;
324 }
325
326 sub executable_on_path ($) {
327     my ($program) = @_;
328     return 1 if $program =~ m{/};
329     my @path = split /:/, ($ENV{PATH} // "/usr/local/bin:/bin:/usr/bin");
330     foreach my $pe (@path) {
331         my $here = "$pe/$program";
332         return $here if stat_exists $here && -x _;
333     }
334     return undef;
335 }
336
337 our @signames = split / /, $Config{sig_name};
338
339 sub waitstatusmsg () {
340     if (!$?) {
341         return __ "terminated, reporting successful completion";
342     } elsif (!($? & 255)) {
343         return f_ "failed with error exit status %s", WEXITSTATUS($?);
344     } elsif (WIFSIGNALED($?)) {
345         my $signum=WTERMSIG($?);
346         return f_ "died due to fatal signal %s",
347             ($signames[$signum] // "number $signum").
348             ($? & 128 ? " (core dumped)" : ""); # POSIX(3pm) has no WCOREDUMP
349     } else {
350         return f_ "failed with unknown wait status %s", $?;
351     }
352 }
353
354 sub failedcmd_report_cmd {
355     my $intro = shift @_;
356     $intro //= __ "failed command";
357     { local ($!); printcmd \*STDERR, _us().": $intro:", @_ or confess "$!"; };
358 }
359
360 sub failedcmd_waitstatus {
361     if ($? < 0) {
362         return f_ "failed to fork/exec: %s", $!;
363     } elsif ($?) {
364         return f_ "subprocess %s", waitstatusmsg();
365     } else {
366         return __ "subprocess produced invalid output";
367     }
368 }
369
370 sub failedcmd {
371     # Expects $!,$? as set by close - see below.
372     # To use with system(), set $?=-1 first.
373     #
374     # Actual behaviour of perl operations:
375     #   success              $!==0       $?==0       close of piped open
376     #   program failed       $!==0       $? >0       close of piped open
377     #   syscall failure      $! >0       $?=-1       close of piped open
378     #   failure              $! >0       unchanged   close of something else
379     #   success              trashed     $?==0       system
380     #   program failed       trashed     $? >0       system
381     #   syscall failure      $! >0       unchanged   system
382     failedcmd_report_cmd undef, @_;
383     fail failedcmd_waitstatus();
384 }
385
386 sub runcmd {
387     debugcmd "+",@_;
388     $!=0; $?=-1;
389     failedcmd @_ if system @_;
390 }
391
392 sub shell_cmd {
393     my ($first_shell, @cmd) = @_;
394     return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
395 }
396
397 sub cmdoutput_errok {
398     confess Dumper(\@_)." ?" if grep { !defined } @_;
399     local $printdebug_when_debuglevel = $debugcmd_when_debuglevel;
400     debugcmd "|",@_;
401     open P, "-|", @_ or confess "$_[0] $!";
402     my $d;
403     $!=0; $?=0;
404     { local $/ = undef; $d = <P>; }
405     confess "$!" if P->error;
406     if (!close P) { printdebug "=>!$?\n"; return undef; }
407     chomp $d;
408     if ($debuglevel > 0) {
409         $d =~ m/^.*/;
410         my $dd = $&;
411         my $more = (length $' ? '...' : ''); #');
412         $dd =~ s{[^\n -~]|\\}{ sprintf "\\x%02x", ord $& }ge;
413         printdebug "=> \`$dd'",$more,"\n";
414     }
415     return $d;
416 }
417
418 sub cmdoutput {
419     my $d = cmdoutput_errok @_;
420     defined $d or failedcmd @_;
421     return $d;
422 }
423
424 sub link_ltarget ($$) {
425     my ($old,$new) = @_;
426     lstat $old or return undef;
427     if (-l _) {
428         $old = cmdoutput qw(realpath  --), $old;
429     }
430     my $r = link $old, $new;
431     $r = symlink $old, $new if !$r && $!==EXDEV;
432     $r or fail "(sym)link $old $new: $!\n";
433 }
434
435 sub rename_link_xf ($$$) {
436     # renames/moves or links/copies $src to $dst,
437     # even if $dst is on a different fs
438     # (May use the filename "$dst.tmp".);
439     # On success, returns true.
440     # On failure, returns false and sets
441     #    $@ to a reason message
442     #    $! to an errno value, or -1 if not known
443     # having possibly printed something about mv to stderr.
444     # Not safe to use without $keeporig if $dst might be a symlink
445     # to $src, as it might delete $src leaving $dst invalid.
446     my ($keeporig,$src,$dst) = @_;
447     if ($keeporig
448         ? link   $src, $dst
449         : rename $src, $dst) {
450         return 1;
451     }
452     if ($! != EXDEV) {
453         $@ = "$!";
454         return 0;
455     }
456     if (!stat $src) {
457         $@ = f_ "stat source file: %S", $!;
458         return 0;
459     }
460     my @src_stat = (stat _)[0..1];
461
462     my @dst_stat;
463     if (stat $dst) {
464         @dst_stat = (stat _)[0..1];
465     } elsif ($! == ENOENT) {
466     } else {
467         $@ = f_ "stat destination file: %S", $!;
468         return 0;
469     }
470
471     if ("@src_stat" eq "@dst_stat") {
472         # (Symlinks to) the same file.  No need for a copy but
473         # we may need to delete the original.
474         printdebug "rename_link_xf $keeporig $src $dst EXDEV but same\n";
475     } else {
476         $!=0; $?=0;
477         my @cmd = (qw(cp --), $src, "$dst.tmp");
478         debugcmd '+',@cmd;
479         if (system @cmd) {
480             failedcmd_report_cmd undef, @cmd;
481             $@ = failedcmd_waitstatus();
482             $! = -1;
483             return 0;
484         }
485         if (!rename "$dst.tmp", $dst) {
486             $@ = f_ "finally install file after cp: %S", $!;
487             return 0;
488         }
489     }
490     if (!$keeporig) {
491         if (!unlink $src) {
492             $@ = f_ "delete old file after cp: %S", $!;
493             return 0;
494         }
495     }
496     return 1;
497 }
498
499 sub hashfile ($) {
500     my ($fn) = @_;
501     my $h = Digest::SHA->new(256);
502     $h->addfile($fn);
503     return $h->hexdigest();
504 }
505
506 sub git_rev_parse ($) {
507     return cmdoutput qw(git rev-parse), "$_[0]~0";
508 }
509
510 sub changedir_git_toplevel () {
511     my $toplevel = cmdoutput qw(git rev-parse --show-toplevel);
512     length $toplevel or fail __ <<END;
513 not in a git working tree?
514 (git rev-parse --show-toplevel produced no output)
515 END
516     chdir $toplevel or fail f_ "chdir toplevel %s: %s\n", $toplevel, $!;
517 }
518
519 sub git_cat_file ($;$) {
520     my ($objname, $etype) = @_;
521     # => ($type, $data) or ('missing', undef)
522     # in scalar context, just the data
523     # if $etype defined, dies unless type is $etype or in @$etype
524     our ($gcf_pid, $gcf_i, $gcf_o);
525     local $printdebug_when_debuglevel = $debugcmd_when_debuglevel;
526     my $chk = sub {
527         my ($gtype, $data) = @_;
528         if ($etype) {
529             $etype = [$etype] unless ref $etype;
530             confess "$objname expected @$etype but is $gtype"
531                 unless grep { $gtype eq $_ } @$etype;
532         }
533         return ($gtype, $data);
534     };
535     if (!$gcf_pid) {
536         my @cmd = qw(git cat-file --batch);
537         debugcmd "GCF|", @cmd;
538         $gcf_pid = open2 $gcf_o, $gcf_i, @cmd or confess "$!";
539     }
540     printdebug "GCF>| $objname\n";
541     print $gcf_i $objname, "\n" or confess "$!";
542     my $x = <$gcf_o>;
543     printdebug "GCF<| ", $x;
544     if ($x =~ m/ (missing)$/) { return $chk->($1, undef); }
545     my ($type, $size) = $x =~ m/^.* (\w+) (\d+)\n/ or confess "$objname ?";
546     my $data;
547     (read $gcf_o, $data, $size) == $size or confess "$objname $!";
548     $x = <$gcf_o>;
549     $x eq "\n" or confess "$objname ($_) $!";
550     return $chk->($type, $data);
551 }
552
553 sub git_get_symref (;$) {
554     my ($symref) = @_;  $symref //= 'HEAD';
555     # => undef if not a symref, otherwise refs/...
556     my @cmd = (qw(git symbolic-ref -q HEAD));
557     my $branch = cmdoutput_errok @cmd;
558     if (!defined $branch) {
559         $?==256 or failedcmd @cmd;
560     } else {
561         chomp $branch;
562     }
563     return $branch;
564 }
565
566 sub git_for_each_ref ($$;$) {
567     my ($pattern,$func,$gitdir) = @_;
568     # calls $func->($objid,$objtype,$fullrefname,$reftail);
569     # $reftail is RHS of ref after refs/[^/]+/
570     # breaks if $pattern matches any ref `refs/blah' where blah has no `/'
571     # $pattern may be an array ref to mean multiple patterns
572     $pattern = [ $pattern ] unless ref $pattern;
573     my @cmd = (qw(git for-each-ref), @$pattern);
574     if (defined $gitdir) {
575         @cmd = ('sh','-ec','cd "$1"; shift; exec "$@"','x', $gitdir, @cmd);
576     }
577     open GFER, "-|", @cmd or confess "$!";
578     debugcmd "|", @cmd;
579     while (<GFER>) {
580         chomp or confess "$_ ?";
581         printdebug "|> ", $_, "\n";
582         m#^(\w+)\s+(\w+)\s+(refs/[^/]+/(\S+))$# or confess "$_ ?";
583         $func->($1,$2,$3,$4);
584     }
585     $!=0; $?=0; close GFER or confess "$pattern $? $!";
586 }
587
588 sub git_get_ref ($) {
589     # => '' if no such ref
590     my ($refname) = @_;
591     local $_ = $refname;
592     s{^refs/}{[r]efs/} or confess "$refname $_ ?";
593     return cmdoutput qw(git for-each-ref --format=%(objectname)), $_;
594 }
595
596 sub git_for_each_tag_referring ($$) {
597     my ($objreferring, $func) = @_;
598     # calls $func->($tagobjid,$refobjid,$fullrefname,$tagname);
599     printdebug "git_for_each_tag_referring ",
600         ($objreferring // 'UNDEF'),"\n";
601     git_for_each_ref('refs/tags', sub {
602         my ($tagobjid,$objtype,$fullrefname,$tagname) = @_;
603         return unless $objtype eq 'tag';
604         my $refobjid = git_rev_parse $tagobjid;
605         return unless
606             !defined $objreferring # caller wants them all
607             or $tagobjid eq $objreferring
608             or $refobjid eq $objreferring;
609         $func->($tagobjid,$refobjid,$fullrefname,$tagname);
610     });
611 }
612
613 sub git_check_unmodified () {
614     foreach my $cached (qw(0 1)) {
615         my @cmd = qw(git diff --quiet);
616         push @cmd, qw(--cached) if $cached;
617         push @cmd, qw(HEAD);
618         debugcmd "+",@cmd;
619         $!=0; $?=-1; system @cmd;
620         return if !$?;
621         if ($?==256) {
622             fail
623                 $cached
624                 ? __ "git index contains changes (does not match HEAD)"
625                 : __ "working tree is dirty (does not match HEAD)";
626         } else {
627             failedcmd @cmd;
628         }
629     }
630 }
631
632 sub upstream_commitish_search ($$) {
633     my ($upstream_version, $tried) = @_;
634     # todo: at some point maybe use git-deborig to do this
635     foreach my $tagpfx ('', 'v', 'upstream/') {
636         my $tag = $tagpfx.(dep14_version_mangle $upstream_version);
637         my $new_upstream = git_get_ref "refs/tags/$tag";
638         push @$tried, $tag;
639         return $new_upstream if length $new_upstream;
640     }
641 }
642
643 sub resolve_upstream_version ($$) {
644     my ($new_upstream, $upstream_version) = @_;
645
646     my $used = $new_upstream;
647     my $message = __ 'using specified upstream commitish';
648     if (!defined $new_upstream) {
649         my @tried;
650         $new_upstream = upstream_commitish_search $upstream_version, \@tried;
651         if (!length $new_upstream) {
652             fail f_
653                 "Could not determine appropriate upstream commitish.\n".
654                 " (Tried these tags: %s)\n".
655                 " Check version, and specify upstream commitish explicitly.",
656                 "@tried";
657         }
658         $used = $tried[-1];
659         $message = f_ 'using upstream from git tag %s', $used;
660     } elsif ($new_upstream =~ m{^refs/tags/($versiontag_re)$}s) {
661         $message = f_ 'using upstream from git tag %s', $1;
662         $used = $1;
663     }   
664     $new_upstream = git_rev_parse $new_upstream;
665
666     return ($new_upstream, $used, $message);
667     # used is a human-readable idea of what we found
668 }
669
670 sub is_fast_fwd ($$) {
671     my ($ancestor,$child) = @_;
672     my @cmd = (qw(git merge-base), $ancestor, $child);
673     my $mb = cmdoutput_errok @cmd;
674     if (defined $mb) {
675         return git_rev_parse($mb) eq git_rev_parse($ancestor);
676     } else {
677         $?==256 or failedcmd @cmd;
678         return 0;
679     }
680 }
681
682 sub git_reflog_action_msg ($) {
683     my ($msg) = @_;
684     my $rla = $ENV{GIT_REFLOG_ACTION};
685     $msg = "$rla: $msg" if length $rla;
686     return $msg;
687 }
688
689 sub git_update_ref_cmd {
690     # returns  qw(git update-ref), qw(-m), @_
691     # except that message may be modified to honour GIT_REFLOG_ACTION
692     my $msg = shift @_;
693     $msg = git_reflog_action_msg $msg;
694     return qw(git update-ref -m), $msg, @_;
695 }
696
697 sub rm_subdir_cached ($) {
698     my ($subdir) = @_;
699     runcmd qw(git rm --quiet -rf --cached --ignore-unmatch), $subdir;
700 }
701
702 sub read_tree_subdir ($$) {
703     my ($subdir, $new_tree_object) = @_;
704     # If $new_tree_object is '', the subtree is deleted.
705     confess unless defined $new_tree_object;
706     rm_subdir_cached $subdir;
707     runcmd qw(git read-tree), "--prefix=$subdir/", $new_tree_object
708         if length $new_tree_object;
709 }
710
711 sub read_tree_debian ($) {
712     my ($treeish) = @_;
713     read_tree_subdir 'debian', "$treeish:debian";
714     rm_subdir_cached 'debian/patches';
715 }
716
717 sub read_tree_upstream ($;$$) {
718     my ($treeish, $keep_patches, $tree_with_debian) = @_;
719     # if $tree_with_debian is supplied, will use that for debian/
720     # otherwise will save and restore it.  If $tree_with_debian
721     # is '' then debian/ is deleted.
722     my $debian =
723         defined $tree_with_debian ? "$tree_with_debian:debian"
724         : cmdoutput qw(git write-tree --prefix=debian/);
725     runcmd qw(git read-tree), $treeish;
726     read_tree_subdir 'debian', $debian;
727     rm_subdir_cached 'debian/patches' unless $keep_patches;
728 }
729
730 sub changedir ($) {
731     my ($newdir) = @_;
732     printdebug "CD $newdir\n";
733     chdir $newdir or confess "chdir: $newdir: $!";
734 }
735
736 sub git_slurp_config_src ($) {
737     my ($src) = @_;
738     # returns $r such that $r->{KEY}[] = VALUE
739     my @cmd = (qw(git config -z --get-regexp), "--$src", qw(.*));
740     debugcmd "|",@cmd;
741
742     local ($debuglevel) = $debuglevel-2;
743     local $/="\0";
744
745     my $r = { };
746     open GITS, "-|", @cmd or confess "$!";
747     while (<GITS>) {
748         chomp or confess;
749         printdebug "=> ", (messagequote $_), "\n";
750         m/\n/ or confess "$_ ?";
751         push @{ $r->{$`} }, $'; #';
752     }
753     $!=0; $?=0;
754     close GITS
755         or ($!==0 && $?==256)
756         or failedcmd @cmd;
757     return $r;
758 }
759
760 sub gdr_ffq_prev_branchinfo ($) {
761     my ($symref) = @_;
762     # => ('status', "message", [$symref, $ffq_prev, $gdrlast])
763     # 'status' may be
764     #    branch         message is undef
765     #    weird-symref   } no $symref,
766     #    notbranch      }  no $ffq_prev
767     return ('detached', __ 'detached HEAD') unless defined $symref;
768     return ('weird-symref', __ 'HEAD symref is not to refs/')
769         unless $symref =~ m{^refs/};
770     my $ffq_prev = "refs/$ffq_refprefix/$'";
771     my $gdrlast = "refs/$gdrlast_refprefix/$'";
772     printdebug "ffq_prev_branchinfo branch current $symref\n";
773     return ('branch', undef, $symref, $ffq_prev, $gdrlast);
774 }
775
776 sub parsecontrolfh ($$;$) {
777     my ($fh, $desc, $allowsigned) = @_;
778     our $dpkgcontrolhash_noissigned;
779     my $c;
780     for (;;) {
781         my %opts = ('name' => $desc);
782         $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
783         $c = Dpkg::Control::Hash->new(%opts);
784         $c->parse($fh,$desc) or fail f_ "parsing of %s failed", $desc;
785         last if $allowsigned;
786         last if $dpkgcontrolhash_noissigned;
787         my $issigned= $c->get_option('is_pgp_signed');
788         if (!defined $issigned) {
789             $dpkgcontrolhash_noissigned= 1;
790             seek $fh, 0,0 or confess "seek $desc: $!";
791         } elsif ($issigned) {
792             fail f_
793                 "control file %s is (already) PGP-signed. ".
794                 " Note that dgit push needs to modify the .dsc and then".
795                 " do the signature itself",
796                 $desc;
797         } else {
798             last;
799         }
800     }
801     return $c;
802 }
803
804 sub parsecontrol {
805     my ($file, $desc, $allowsigned) = @_;
806     my $fh = new IO::Handle;
807     open $fh, '<', $file or fail f_ "open %s (%s): %s", $file, $desc, $!;
808     my $c = parsecontrolfh($fh,$desc,$allowsigned);
809     $fh->error and confess "$!";
810     close $fh;
811     return $c;
812 }
813
814 sub parsechangelog {
815     my $c = Dpkg::Control::Hash->new(name => 'parsed changelog');
816     my $p = new IO::Handle;
817     my @cmd = (qw(dpkg-parsechangelog), @_);
818     open $p, '-|', @cmd or confess "$!";
819     $c->parse($p);
820     $?=0; $!=0; close $p or failedcmd @cmd;
821     return $c;
822 }
823
824 sub getfield ($$) {
825     my ($dctrl,$field) = @_;
826     my $v = $dctrl->{$field};
827     return $v if defined $v;
828     fail f_ "missing field %s in %s", $field, $dctrl->get_option('name');
829 }
830
831 sub parsechangelog_loop ($$$) {
832     my ($clogcmd, $descbase, $fn) = @_;
833     # @$clogcmd is qw(dpkg-parsechangelog ...some...options...)
834     # calls $fn->($thisstanza, $desc);
835     debugcmd "|",@$clogcmd;
836     open CLOGS, "-|", @$clogcmd or confess "$!";
837     for (;;) {
838         my $stanzatext = do { local $/=""; <CLOGS>; };
839         printdebug "clogp stanza ".Dumper($stanzatext) if $debuglevel>1;
840         last if !defined $stanzatext;
841
842         my $desc = "$descbase, entry no.$.";
843         open my $stanzafh, "<", \$stanzatext or confess;
844         my $thisstanza = parsecontrolfh $stanzafh, $desc, 1;
845
846         $fn->($thisstanza, $desc);
847     }
848     confess "$!" if CLOGS->error;
849     close CLOGS or $?==SIGPIPE or failedcmd @$clogcmd;
850 }       
851
852 sub make_commit ($$) {
853     my ($parents, $message_paras) = @_;
854     my $tree = cmdoutput qw(git write-tree);
855     my @cmd = (qw(git commit-tree), $tree);
856     push @cmd, qw(-p), $_ foreach @$parents;
857     push @cmd, qw(-m), $_ foreach @$message_paras;
858     return cmdoutput @cmd;
859 }
860
861 sub hash_commit ($) {
862     my ($file) = @_;
863     return cmdoutput qw(git hash-object -w -t commit), $file;
864 }
865
866 sub hash_commit_text ($) {
867     my ($text) = @_;
868     my ($out, $in);
869     my @cmd = (qw(git hash-object -w -t commit --stdin));
870     debugcmd "|",@cmd;
871     print Dumper($text) if $debuglevel > 1;
872     my $child = open2($out, $in, @cmd) or confess "$!";
873     my $h;
874     eval {
875         print $in $text or confess "$!";
876         close $in or confess "$!";
877         $h = <$out>;
878         $h =~ m/^\w+$/ or confess;
879         $h = $&;
880         printdebug "=> $h\n";
881     };
882     close $out;
883     waitpid $child, 0 == $child or confess "$child $!";
884     $? and failedcmd @cmd;
885     return $h;
886 }
887
888 sub reflog_cache_insert ($$$) {
889     my ($ref, $cachekey, $value) = @_;
890     # you must call this in $maindir
891     # you must have called record_maindir
892
893     # When we no longer need to support squeeze, use --create-reflog
894     # instead of this:
895     my $parent = $ref; $parent =~ s{/[^/]+$}{};
896     ensurepath "$maindir_gitcommon/logs", "$parent";
897     my $makelogfh = new IO::File "$maindir_gitcommon/logs/$ref", '>>'
898       or confess "$!";
899
900     my $oldcache = git_get_ref $ref;
901
902     if ($oldcache eq $value) {
903         my $tree = cmdoutput qw(git rev-parse), "$value:";
904         # git update-ref doesn't always update, in this case.  *sigh*
905         my $authline = (ucfirst _us()).
906             ' <'._us().'@example.com> 1000000000 +0000';
907         my $dummy = hash_commit_text <<ENDU.(__ <<END);
908 tree $tree
909 parent $value
910 author $authline
911 committer $authline
912
913 ENDU
914 Dummy commit - do not use
915 END
916         runcmd qw(git update-ref -m), _us()." - dummy", $ref, $dummy;
917     }
918     runcmd qw(git update-ref -m), $cachekey, $ref, $value;
919 }
920
921 sub reflog_cache_lookup ($$) {
922     my ($ref, $cachekey) = @_;
923     # you may call this in $maindir or in a playtree
924     # you must have called record_maindir
925     my @cmd = (qw(git log -g), '--pretty=format:%H %gs', $ref);
926     debugcmd "|(probably)",@cmd;
927     my $child = open GC, "-|";  defined $child or confess "$!";
928     if (!$child) {
929         chdir $maindir or confess "$!";
930         if (!stat "$maindir_gitcommon/logs/$ref") {
931             $! == ENOENT or confess "$!";
932             printdebug ">(no reflog)\n";
933             finish 0;
934         }
935         exec @cmd; die f_ "exec %s: %s\n", $cmd[0], $!;
936     }
937     while (<GC>) {
938         chomp;
939         printdebug ">| ", $_, "\n" if $debuglevel > 1;
940         next unless m/^(\w+) (\S.*\S)$/ && $2 eq $cachekey;
941         close GC;
942         return $1;
943     }
944     confess "$!" if GC->error;
945     failedcmd unless close GC;
946     return undef;
947 }
948
949 # ========== playground handling ==========
950
951 # terminology:
952 #
953 #   $maindir      user's git working tree
954 #   playground    area in .git/ where we can make files, unpack, etc. etc.
955 #   playtree      git working tree sharing object store with the user's
956 #                 inside playground, or identical to it
957 #
958 # other globals
959 #
960 #   $local_git_cfg    hash of arrays of values: git config from $maindir
961 #
962 # expected calling pattern
963 #
964 #  firstly
965 #
966 #    [record_maindir]
967 #      must be run in directory containing .git
968 #      assigns to $maindir if not already set
969 #      also calls git_slurp_config_src to record git config
970 #        in $local_git_cfg, unless it's already set
971 #
972 #    fresh_playground SUBDIR_PATH_COMPONENTS
973 #      e.g fresh_playground 'dgit/unpack' ('.git/' is implied)
974 #      default SUBDIR_PATH_COMPONENTS is playground_subdir
975 #      calls record_maindir
976 #      sets up a new playground (destroying any old one)
977 #      returns playground pathname
978 #      caller may call multiple times with different subdir paths
979 #       createing different playgrounds
980 #
981 #    ensure_a_playground SUBDIR_PATH_COMPONENTS
982 #      like fresh_playground except:
983 #      merely ensures the directory exists; does not delete an existing one
984 #
985 #  then can use
986 #
987 #    changedir playground
988 #    changedir $maindir
989 #
990 #    playtree_setup
991 #            # ^ call in some (perhaps trivial) subdir of playground
992 #
993 #    rmtree playground
994
995 # ----- maindir -----
996
997 our $local_git_cfg;
998
999 sub record_maindir () {
1000     if (!defined $maindir) {
1001         $maindir = must_getcwd();
1002         if (!stat "$maindir/.git") {
1003             fail f_ "cannot stat %s/.git: %s", $maindir, $!;
1004         }
1005         if (-d _) {
1006             # we fall back to this in case we have a pre-worktree
1007             # git, which may not know git rev-parse --git-common-dir
1008             $maindir_gitdir    = "$maindir/.git";
1009             $maindir_gitcommon = "$maindir/.git";
1010         } else {
1011             $maindir_gitdir    = cmdoutput qw(git rev-parse --git-dir);
1012             $maindir_gitcommon = cmdoutput qw(git rev-parse --git-common-dir);
1013         }
1014     }
1015     $local_git_cfg //= git_slurp_config_src 'local';
1016 }
1017
1018 # ----- playgrounds -----
1019
1020 sub ensure_a_playground_parent ($) {
1021     my ($spc) = @_;
1022     record_maindir();
1023     $spc = "$maindir_gitdir/$spc";
1024     my $parent = dirname $spc;
1025     mkdir $parent or $!==EEXIST or fail f_
1026         "failed to mkdir playground parent %s: %s", $parent, $!;
1027     return $spc;
1028 }    
1029
1030 sub ensure_a_playground ($) {
1031     my ($spc) = @_;
1032     $spc = ensure_a_playground_parent $spc;
1033     mkdir $spc or $!==EEXIST or fail f_
1034         "failed to mkdir a playground %s: %s", $spc, $!;
1035     return $spc;
1036 }    
1037
1038 sub fresh_playground ($) {
1039     my ($spc) = @_;
1040     $spc = ensure_a_playground_parent $spc;
1041     rmtree $spc;
1042     mkdir $spc or fail f_
1043         "failed to mkdir the playground %s: %s", $spc, $!;
1044     return $spc;
1045 }
1046
1047 # ----- playtrees -----
1048
1049 sub playtree_setup () {
1050     # for use in the playtree
1051     # $maindir must be set, eg by calling record_maindir or fresh_playground
1052     # this is confusing: we have
1053     #   .                   playtree, not a worktree, has .git/, our cwd
1054     #   $maindir            might be a worktree so
1055     #   $maindir_gitdir     contains our main working "dgit", HEAD, etc.
1056     #   $maindir_gitcommon  the shared stuff, including .objects
1057
1058     # we need to invoke git-playtree-setup via git because
1059     # there may be config options it needs which are only available
1060     # to us, sensibly, in @git
1061
1062     # And, we look for it in @INC too.  This is a bit perverse.
1063     # We do this because in the Debian packages we want to have
1064     # a copy of this script in each binary package, rather than
1065     # making yet another .deb or tangling the dependencies.
1066     # @INC is conveniently available.
1067     my $newpath = join ':', +(grep { !m/:/ } @INC),
1068                   '/usr/share/dgit', $ENV{PATH};
1069     runcmd qw(env), "PATH=$newpath", @git, qw(playtree-setup .);
1070
1071     ensuredir '.git/info';
1072     open GA, "> .git/info/attributes" or confess "$!";
1073     print GA "* $negate_harmful_gitattrs\n" or confess "$!";
1074     close GA or confess "$!";
1075 }
1076
1077 1;