+#---------- remote commands' implementation ----------
+
+sub cmd_remote_push_build_host {
+ my ($nrargs) = shift @ARGV;
+ my (@rargs) = @ARGV[0..$nrargs-1];
+ @ARGV = @ARGV[$nrargs..$#ARGV];
+ die unless @rargs;
+ my ($dir,$vsnwant) = @rargs;
+ # vsnwant is a comma-separated list; we report which we have
+ # chosen in our ready response (so other end can tell if they
+ # offered several)
+ $debugprefix = ' ';
+ $we_are_responder = 1;
+
+ open PI, "<&STDIN" or die $!;
+ open STDIN, "/dev/null" or die $!;
+ open PO, ">&STDOUT" or die $!;
+ autoflush PO 1;
+ open STDOUT, ">&STDERR" or die $!;
+ autoflush STDOUT 1;
+
+ $vsnwant //= 1;
+ fail "build host has dgit rpush protocol version".
+ " $rpushprotovsn but invocation host has $vsnwant"
+ unless grep { $rpushprotovsn eq $_ } split /,/, $vsnwant;
+
+ responder_send_command("dgit-remote-push-ready $rpushprotovsn");
+
+ changedir $dir;
+ &cmd_push;
+}
+
+sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
+# ... for compatibility with proto vsn.1 dgit (just so that user gets
+# a good error message)
+
+our $i_tmp;
+
+sub i_cleanup {
+ local ($@, $?);
+ my $report = i_child_report();
+ if (defined $report) {
+ printdebug "($report)\n";
+ } elsif ($i_child_pid) {
+ printdebug "(killing build host child $i_child_pid)\n";
+ kill 15, $i_child_pid;
+ }
+ if (defined $i_tmp && !defined $initiator_tempdir) {
+ changedir "/";
+ eval { rmtree $i_tmp; };
+ }
+}
+
+END { i_cleanup(); }
+
+sub i_method {
+ my ($base,$selector,@args) = @_;
+ $selector =~ s/\-/_/g;
+ { no strict qw(refs); &{"${base}_${selector}"}(@args); }
+}
+
+sub cmd_rpush {
+ my $host = nextarg;
+ my $dir;
+ if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
+ $host = $1;
+ $dir = $'; #';
+ } else {
+ $dir = nextarg;
+ }
+ $dir =~ s{^-}{./-};
+ my @rargs = ($dir,$rpushprotovsn);
+ my @rdgit;
+ push @rdgit, @dgit;
+ push @rdgit, @ropts;
+ push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
+ push @rdgit, @ARGV;
+ my @cmd = (@ssh, $host, shellquote @rdgit);
+ printcmd \*DEBUG,$debugprefix."+",@cmd;
+
+ if (defined $initiator_tempdir) {
+ rmtree $initiator_tempdir;
+ mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
+ $i_tmp = $initiator_tempdir;
+ } else {
+ $i_tmp = tempdir();
+ }
+ $i_child_pid = open2(\*RO, \*RI, @cmd);
+ changedir $i_tmp;
+ initiator_expect { m/^dgit-remote-push-ready/ };
+ for (;;) {
+ my ($icmd,$iargs) = initiator_expect {
+ m/^(\S+)(?: (.*))?$/;
+ ($1,$2);
+ };
+ i_method "i_resp", $icmd, $iargs;
+ }
+}
+
+sub i_resp_progress ($) {
+ my ($rhs) = @_;
+ my $msg = protocol_read_bytes \*RO, $rhs;
+ progress $msg;
+}
+
+sub i_resp_complete {
+ my $pid = $i_child_pid;
+ $i_child_pid = undef; # prevents killing some other process with same pid
+ printdebug "waiting for build host child $pid...\n";
+ my $got = waitpid $pid, 0;
+ die $! unless $got == $pid;
+ die "build host child failed $?" if $?;
+
+ i_cleanup();
+ printdebug "all done\n";
+ exit 0;
+}
+
+sub i_resp_file ($) {
+ my ($keyword) = @_;
+ my $localname = i_method "i_localname", $keyword;
+ my $localpath = "$i_tmp/$localname";
+ stat_exists $localpath and
+ badproto \*RO, "file $keyword ($localpath) twice";
+ protocol_receive_file \*RO, $localpath;
+ i_method "i_file", $keyword;
+}
+
+our %i_param;
+
+sub i_resp_param ($) {
+ $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
+ $i_param{$1} = $2;
+}
+
+our %i_wanted;
+
+sub i_resp_want ($) {
+ my ($keyword) = @_;
+ die "$keyword ?" if $i_wanted{$keyword}++;
+ my @localpaths = i_method "i_want", $keyword;
+ printdebug "[[ $keyword @localpaths\n";
+ foreach my $localpath (@localpaths) {
+ protocol_send_file \*RI, $localpath;
+ }
+ print RI "files-end\n" or die $!;
+}
+
+our ($i_clogp, $i_version, $i_tag, $i_dscfn, $i_changesfn);
+
+sub i_localname_parsed_changelog {
+ return "remote-changelog.822";
+}
+sub i_file_parsed_changelog {
+ ($i_clogp, $i_version, $i_tag, $i_dscfn) =
+ push_parse_changelog "$i_tmp/remote-changelog.822";
+ die if $i_dscfn =~ m#/|^\W#;
+}
+
+sub i_localname_dsc {
+ defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
+ return $i_dscfn;
+}
+sub i_file_dsc { }
+
+sub i_localname_changes {
+ defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
+ $i_changesfn = $i_dscfn;
+ $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
+ return $i_changesfn;
+}
+sub i_file_changes { }
+
+sub i_want_signed_tag {
+ printdebug Dumper(\%i_param, $i_dscfn);
+ defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
+ && defined $i_param{'csuite'}
+ or badproto \*RO, "premature desire for signed-tag";
+ my $head = $i_param{'head'};
+ die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
+
+ die unless $i_param{'csuite'} =~ m/^$suite_re$/;
+ $csuite = $&;
+ push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
+
+ my $tagobjfn =
+ push_mktag $head, $i_clogp, $i_tag,
+ $i_dscfn,
+ $i_changesfn, 'remote changes',
+ sub { "tag$_[0]"; };
+
+ return $tagobjfn;
+}
+
+sub i_want_signed_dsc_changes {
+ rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
+ sign_changes $i_changesfn;
+ return ($i_dscfn, $i_changesfn);
+}
+
+#---------- building etc. ----------
+
+our $version;
+our $sourcechanges;
+our $dscfn;
+
+our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
+
+sub build_maybe_quilt_fixup () {
+ my $format=get_source_format;
+ return unless madformat $format;
+ # sigh
+
+ # Our objective is:
+ # - honour any existing .pc in case it has any strangeness
+ # - determine the git commit corresponding to the tip of
+ # the patch stack (if there is one)
+ # - if there is such a git commit, convert each subsequent
+ # git commit into a quilt patch with dpkg-source --commit
+ # - otherwise convert all the differences in the tree into
+ # a single git commit
+ #
+ # To do this we:
+
+ # Our git tree doesn't necessarily contain .pc. (Some versions of
+ # dgit would include the .pc in the git tree.) If there isn't
+ # one, we need to generate one by unpacking the patches that we
+ # have.
+ #
+ # We first look for a .pc in the git tree. If there is one, we
+ # will use it. (This is not the normal case.)
+ #
+ # Otherwise need to regenerate .pc so that dpkg-source --commit
+ # can work. We do this as follows:
+ # 1. Collect all relevant .orig from parent directory
+ # 2. Generate a debian.tar.gz out of
+ # debian/{patches,rules,source/format}
+ # 3. Generate a fake .dsc containing just these fields:
+ # Format Source Version Files
+ # 4. Extract the fake .dsc
+ # Now the fake .dsc has a .pc directory.
+ # (In fact we do this in every case, because in future we will
+ # want to search for a good base commit for generating patches.)
+ #
+ # Then we can actually do the dpkg-source --commit
+ # 1. Make a new working tree with the same object
+ # store as our main tree and check out the main
+ # tree's HEAD.
+ # 2. Copy .pc from the fake's extraction, if necessary
+ # 3. Run dpkg-source --commit
+ # 4. If the result has changes to debian/, then
+ # - git-add them them
+ # - git-add .pc if we had a .pc in-tree
+ # - git-commit
+ # 5. If we had a .pc in-tree, delete it, and git-commit
+ # 6. Back in the main tree, fast forward to the new HEAD
+
+ my $clogp = parsechangelog();
+ my $headref = rev_parse('HEAD');
+
+ prep_ud();
+ changedir $ud;
+
+ my $upstreamversion=$version;
+ $upstreamversion =~ s/-[^-]*$//;
+
+ my $fakeversion="$upstreamversion-~~DGITFAKE";
+
+ my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
+ print $fakedsc <<END or die $!;
+Format: 3.0 (quilt)
+Source: $package
+Version: $fakeversion
+Files:
+END
+
+ my $dscaddfile=sub {
+ my ($b) = @_;
+
+ my $md = new Digest::MD5;
+
+ my $fh = new IO::File $b, '<' or die "$b $!";
+ stat $fh or die $!;
+ my $size = -s _;
+
+ $md->addfile($fh);
+ print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
+ };
+
+ foreach my $f (<../../../../*>) { #/){
+ my $b=$f; $b =~ s{.*/}{};
+ next unless is_orig_file $b, srcfn $upstreamversion,'';
+ link $f, $b or die "$b $!";
+ $dscaddfile->($b);
+ }
+
+ my @files=qw(debian/source/format debian/rules);
+ if (stat_exists '../../../debian/patches') {
+ push @files, 'debian/patches';
+ }
+
+ my $debtar= srcfn $fakeversion,'.debian.tar.gz';
+ runcmd qw(env GZIP=-1 tar -zcf), "./$debtar", qw(-C ../../..), @files;
+
+ $dscaddfile->($debtar);
+ close $fakedsc or die $!;
+
+ runcmd qw(sh -ec), 'exec dpkg-source --no-check -x fake.dsc >/dev/null';
+
+ my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
+ rename $fakexdir, "fake" or die "$fakexdir $!";
+
+ mkdir "work" or die $!;
+ changedir "work";
+ mktree_in_ud_here();
+ runcmd @git, qw(reset --hard), $headref;
+
+ my $mustdeletepc=0;
+ if (stat_exists ".pc") {
+ -d _ or die;
+ progress "Tree already contains .pc - will use it then delete it.";
+ $mustdeletepc=1;
+ } else {
+ rename '../fake/.pc','.pc' or die $!;
+ }
+
+ my $author = getfield $clogp, 'Maintainer';
+ my $time = time;
+ my $ncommits = 3;
+ my $patchname = "auto-$version-$headref-$time";
+ my $msg = cmdoutput @git, qw(log), "-n$ncommits";
+ mkpath '.git/dgit';
+ my $descfn = ".git/dgit/quilt-description.tmp";
+ open O, '>', $descfn or die "$descfn: $!";
+ $msg =~ s/\n/\n /g;
+ $msg =~ s/^\s+$/ ./mg;
+ print O <<END or die $!;
+Description: Automatically generated patch ($clogp->{Version})
+ Last (up to) $ncommits git changes, FYI:
+ .
+ $msg
+Author: $author
+
+---
+
+END
+ close O or die $!;
+ {
+ local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
+ local $ENV{'VISUAL'} = $ENV{'EDITOR'};
+ local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
+ runcmd_ordryrun_local @dpkgsource, qw(--commit .), $patchname;
+ }
+
+ if (!open P, '>>', ".pc/applied-patches") {
+ $!==&ENOENT or die $!;
+ } else {
+ close P;
+ }
+
+ commit_quilty_patch();
+
+ if ($mustdeletepc) {
+ runcmd @git, qw(rm -rq .pc);
+ commit_admin "Commit removal of .pc (quilt series tracking data)";
+ }
+
+ changedir '../../../..';
+ runcmd @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
+}
+
+sub quilt_fixup_editor () {
+ my $descfn = $ENV{$fakeeditorenv};
+ my $editing = $ARGV[$#ARGV];
+ open I1, '<', $descfn or die "$descfn: $!";
+ open I2, '<', $editing or die "$editing: $!";
+ unlink $editing or die "$editing: $!";
+ open O, '>', $editing or die "$editing: $!";
+ while (<I1>) { print O or die $!; } I1->error and die $!;
+ my $copying = 0;
+ while (<I2>) {
+ $copying ||= m/^\-\-\- /;
+ next unless $copying;
+ print O or die $!;
+ }
+ I2->error and die $!;
+ close O or die $1;
+ exit 0;
+}
+
+sub clean_tree () {
+ if ($cleanmode eq 'dpkg-source') {
+ runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
+ } elsif ($cleanmode eq 'git') {
+ runcmd_ordryrun_local @git, qw(clean -xdf);
+ } elsif ($cleanmode eq 'none') {
+ } else {
+ die "$cleanmode ?";
+ }
+}
+
+sub cmd_clean () {
+ badusage "clean takes no additional arguments" if @ARGV;
+ clean_tree();
+}
+
+sub build_prep () {
+ badusage "-p is not allowed when building" if defined $package;
+ check_not_dirty();
+ clean_tree();