+ if (check_for_git()) {
+ git_fetch_us();
+ }
+ fetch_from_archive() or no_such_package();
+ printdone "fetched into ".lrref();
+}
+
+sub pull () {
+ fetch();
+ runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
+ lrref();
+ printdone "fetched to ".lrref()." and merged into HEAD";
+}
+
+sub check_not_dirty () {
+ return if $ignoredirty;
+ my @cmd = (@git, qw(diff --quiet HEAD));
+ printcmd(\*DEBUG,$debugprefix."+",@cmd) if $debug>0;
+ $!=0; $?=0; system @cmd;
+ return if !$! && !$?;
+ if (!$! && $?==256) {
+ fail "working tree is dirty (does not match HEAD)";
+ } else {
+ failedcmd @cmd;
+ }
+}
+
+sub commit_quilty_patch () {
+ my $output = cmdoutput @git, qw(status --porcelain);
+ my %adds;
+ foreach my $l (split /\n/, $output) {
+ next unless $l =~ m/\S/;
+ if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
+ $adds{$1}++;
+ }
+ }
+ if (!%adds) {
+ progress "nothing quilty to commit, ok.";
+ return;
+ }
+ runcmd_ordryrun_local @git, qw(add), sort keys %adds;
+ my $m = "Commit Debian 3.0 (quilt) metadata";
+ progress "$m";
+ runcmd_ordryrun_local @git, qw(commit -m), $m;
+}
+
+sub madformat ($) {
+ my ($format) = @_;
+ return 0 unless $format eq '3.0 (quilt)';
+ progress "Format \`$format', urgh";
+ if ($noquilt) {
+ progress "Not doing any fixup of \`$format' due to --no-quilt-fixup";
+ return 0;
+ }
+ return 1;
+}
+
+sub push_parse_changelog ($) {
+ my ($clogpfn) = @_;
+
+ my $clogp = Dpkg::Control::Hash->new();
+ $clogp->load($clogpfn) or die;
+
+ $package = getfield $clogp, 'Source';
+ my $cversion = getfield $clogp, 'Version';
+ my $tag = debiantag($cversion);
+ runcmd @git, qw(check-ref-format), $tag;
+
+ my $dscfn = dscfn($cversion);
+
+ return ($clogp, $cversion, $tag, $dscfn);
+}
+
+sub push_parse_dsc ($$$) {
+ my ($dscfn,$dscfnwhat, $cversion) = @_;
+ $dsc = parsecontrol($dscfn,$dscfnwhat);
+ my $dversion = getfield $dsc, 'Version';
+ my $dscpackage = getfield $dsc, 'Source';
+ ($dscpackage eq $package && $dversion eq $cversion) or
+ fail "$dscfn is for $dscpackage $dversion".
+ " but debian/changelog is for $package $cversion";
+}
+
+sub push_mktag ($$$$$$$) {
+ my ($head,$clogp,$tag,
+ $dscfn,
+ $changesfile,$changesfilewhat,
+ $tfn) = @_;
+
+ $dsc->{$ourdscfield[0]} = $head;
+ $dsc->save("$dscfn.tmp") or die $!;
+
+ my $changes = parsecontrol($changesfile,$changesfilewhat);
+ foreach my $field (qw(Source Distribution Version)) {
+ $changes->{$field} eq $clogp->{$field} or
+ fail "changes field $field \`$changes->{$field}'".
+ " does not match changelog \`$clogp->{$field}'";
+ }
+
+ my $cversion = getfield $clogp, 'Version';
+ my $clogsuite = getfield $clogp, 'Distribution';
+
+ # We make the git tag by hand because (a) that makes it easier
+ # to control the "tagger" (b) we can do remote signing
+ my $authline = clogp_authline $clogp;
+ open TO, '>', $tfn->('.tmp') or die $!;
+ print TO <<END or die $!;
+object $head
+type commit
+tag $tag
+tagger $authline
+
+$package release $cversion for $clogsuite ($csuite) [dgit]
+END
+ close TO or die $!;
+
+ my $tagobjfn = $tfn->('.tmp');
+ if ($sign) {
+ if (!defined $keyid) {
+ $keyid = access_cfg('keyid','RETURN-UNDEF');
+ }
+ unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
+ my @sign_cmd = (@gpg, qw(--detach-sign --armor));
+ push @sign_cmd, qw(-u),$keyid if defined $keyid;
+ push @sign_cmd, $tfn->('.tmp');
+ runcmd_ordryrun @sign_cmd;
+ if (act_scary()) {
+ $tagobjfn = $tfn->('.signed.tmp');
+ runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
+ $tfn->('.tmp'), $tfn->('.tmp.asc');
+ }
+ }
+
+ return ($tagobjfn);
+}
+
+sub sign_changes ($) {
+ my ($changesfile) = @_;
+ if ($sign) {
+ my @debsign_cmd = @debsign;
+ push @debsign_cmd, "-k$keyid" if defined $keyid;
+ push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
+ push @debsign_cmd, $changesfile;
+ runcmd_ordryrun @debsign_cmd;
+ }
+}
+
+sub dopush () {
+ printdebug "actually entering push\n";
+ prep_ud();
+
+ access_giturl(); # check that success is vaguely likely
+
+ my $clogpfn = ".git/dgit/changelog.822.tmp";
+ runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
+
+ responder_send_file('parsed-changelog', $clogpfn);
+
+ my ($clogp, $cversion, $tag, $dscfn) =
+ push_parse_changelog("$clogpfn");
+
+ my $dscpath = "$buildproductsdir/$dscfn";
+ stat $dscpath or
+ fail "looked for .dsc $dscfn, but $!;".
+ " maybe you forgot to build";
+
+ responder_send_file('dsc', $dscpath);
+
+ push_parse_dsc($dscpath, $dscfn, $cversion);
+
+ my $format = getfield $dsc, 'Format';
+ printdebug "format $format\n";
+ if (madformat($format)) {
+ commit_quilty_patch();
+ }
+ check_not_dirty();
+ changedir $ud;
+ progress "checking that $dscfn corresponds to HEAD";
+ runcmd qw(dpkg-source -x --),
+ $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
+ my ($tree,$dir) = mktree_in_ud_from_only_subdir();
+ changedir '../../../..';
+ my $diffopt = $debug>0 ? '--exit-code' : '--quiet';
+ my @diffcmd = (@git, qw(diff), $diffopt, $tree);
+ printcmd \*DEBUG,$debugprefix."+",@diffcmd;
+ $!=0; $?=0;
+ my $r = system @diffcmd;
+ if ($r) {
+ if ($r==256) {
+ fail "$dscfn specifies a different tree to your HEAD commit;".
+ " perhaps you forgot to build".
+ ($diffopt eq '--exit-code' ? "" :
+ " (run with -D to see full diff output)");
+ } else {
+ failedcmd @diffcmd;
+ }
+ }
+#fetch from alioth
+#do fast forward check and maybe fake merge
+# if (!is_fast_fwd(mainbranch
+# runcmd @git, qw(fetch -p ), "$alioth_git/$package.git",
+# map { lref($_).":".rref($_) }
+# (uploadbranch());
+ my $head = rev_parse('HEAD');
+ if (!$changesfile) {
+ my $multi = "$buildproductsdir/".
+ "${package}_".(stripepoch $cversion)."_multi.changes";
+ if (stat "$multi") {
+ $changesfile = $multi;
+ } else {
+ $!==&ENOENT or die "$multi: $!";
+ my $pat = "${package}_".(stripepoch $cversion)."_*.changes";
+ my @cs = glob "$buildproductsdir/$pat";
+ fail "failed to find unique changes file".
+ " (looked for $pat in $buildproductsdir, or $multi);".
+ " perhaps you need to use dgit -C"
+ unless @cs==1;
+ ($changesfile) = @cs;
+ }
+ } else {
+ $changesfile = "$buildproductsdir/$changesfile";
+ }
+
+ responder_send_file('changes',$changesfile);
+ responder_send_command("param head $head");
+ responder_send_command("param csuite $csuite");
+
+ my $tfn = sub { ".git/dgit/tag$_[0]"; };
+ my $tagobjfn;
+
+ if ($we_are_responder) {
+ $tagobjfn = $tfn->('.signed.tmp');
+ responder_receive_files('signed-tag', $tagobjfn);
+ } else {
+ $tagobjfn =
+ push_mktag($head,$clogp,$tag,
+ $dscpath,
+ $changesfile,$changesfile,
+ $tfn);
+ }
+
+ my $tag_obj_hash = cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
+ runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
+ runcmd_ordryrun_local @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
+ runcmd_ordryrun @git, qw(tag -v --), $tag;
+
+ if (!check_for_git()) {
+ create_remote_git_repo();
+ }
+ runcmd_ordryrun @git, qw(push),access_giturl(),
+ "HEAD:".rrref(), "refs/tags/$tag";
+ runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
+
+ if ($we_are_responder) {
+ my $dryrunsuffix = act_local() ? "" : ".tmp";
+ responder_receive_files('signed-dsc-changes',
+ "$dscpath$dryrunsuffix",
+ "$changesfile$dryrunsuffix");
+ } else {
+ if (act_local()) {
+ rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
+ } else {
+ progress "[new .dsc left in $dscpath.tmp]";
+ }
+ sign_changes $changesfile;
+ }
+
+ my $host = access_cfg('upload-host','RETURN-UNDEF');
+ my @hostarg = defined($host) ? ($host,) : ();
+ runcmd_ordryrun @dput, @hostarg, $changesfile;
+ printdone "pushed and uploaded $cversion";
+
+ responder_send_command("complete");
+}
+
+sub cmd_clone {
+ parseopts();
+ my $dstdir;
+ badusage "-p is not allowed with clone; specify as argument instead"
+ if defined $package;
+ if (@ARGV==1) {
+ ($package) = @ARGV;
+ } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
+ ($package,$isuite) = @ARGV;
+ } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
+ ($package,$dstdir) = @ARGV;
+ } elsif (@ARGV==3) {
+ ($package,$isuite,$dstdir) = @ARGV;
+ } else {
+ badusage "incorrect arguments to dgit clone";
+ }
+ $dstdir ||= "$package";
+
+ if (stat $dstdir) {
+ fail "$dstdir already exists";
+ } elsif ($! != &ENOENT) {
+ die "$dstdir: $!";
+ }
+
+ my $cwd_remove;
+ if ($rmonerror && !$dryrun_level) {
+ $cwd_remove= getcwd();
+ unshift @end, sub {
+ return unless defined $cwd_remove;
+ if (!chdir "$cwd_remove") {
+ return if $!==&ENOENT;
+ die "chdir $cwd_remove: $!";
+ }
+ rmtree($dstdir) or die "remove $dstdir: $!\n";
+ };
+ }
+
+ clone($dstdir);
+ $cwd_remove = undef;
+}
+
+sub branchsuite () {
+ my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
+ if ($branch =~ m#$lbranch_re#o) {
+ return $1;
+ } else {
+ return undef;
+ }
+}
+
+sub fetchpullargs () {
+ if (!defined $package) {
+ my $sourcep = parsecontrol('debian/control','debian/control');
+ $package = getfield $sourcep, 'Source';
+ }
+ if (@ARGV==0) {
+# $isuite = branchsuite(); # this doesn't work because dak hates canons
+ if (!$isuite) {
+ my $clogp = parsechangelog();
+ $isuite = getfield $clogp, 'Distribution';
+ }
+ canonicalise_suite();
+ progress "fetching from suite $csuite";
+ } elsif (@ARGV==1) {
+ ($isuite) = @ARGV;
+ canonicalise_suite();
+ } else {
+ badusage "incorrect arguments to dgit fetch or dgit pull";
+ }
+}
+
+sub cmd_fetch {
+ parseopts();
+ fetchpullargs();
+ fetch();
+}
+
+sub cmd_pull {
+ parseopts();
+ fetchpullargs();
+ pull();
+}
+
+sub cmd_push {
+ parseopts();
+ badusage "-p is not allowed with dgit push" if defined $package;
+ check_not_dirty();
+ my $clogp = parsechangelog();
+ $package = getfield $clogp, 'Source';
+ my $specsuite;
+ if (@ARGV==0) {
+ } elsif (@ARGV==1) {
+ ($specsuite) = (@ARGV);
+ } else {
+ badusage "incorrect arguments to dgit push";
+ }
+ $isuite = getfield $clogp, 'Distribution';
+ if ($new_package) {
+ local ($package) = $existing_package; # this is a hack
+ canonicalise_suite();
+ }
+ if (defined $specsuite && $specsuite ne $isuite) {
+ canonicalise_suite();
+ $csuite eq $specsuite or
+ fail "dgit push: changelog specifies $isuite ($csuite)".
+ " but command line specifies $specsuite";
+ }
+ if (check_for_git()) {
+ git_fetch_us();
+ }
+ if (fetch_from_archive()) {
+ is_fast_fwd(lrref(), 'HEAD') or
+ fail "dgit push: HEAD is not a descendant".
+ " of the archive's version.\n".
+ "$us: To overwrite it, use git merge -s ours ".lrref().".";
+ } else {
+ $new_package or
+ fail "package appears to be new in this suite;".
+ " if this is intentional, use --new";
+ }
+ dopush();
+}
+
+#---------- remote commands' implementation ----------
+
+sub cmd_remote_push_responder {
+ my ($nrargs) = shift @ARGV;
+ my (@rargs) = @ARGV[0..$nrargs-1];
+ @ARGV = @ARGV[$nrargs..$#ARGV];
+ die unless @rargs;
+ my ($dir) = @rargs;
+ $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;
+
+ responder_send_command("dgit-remote-push-ready");
+
+ changedir $dir;
+ &cmd_push;
+}
+
+our $i_tmp;
+our $i_child_pid;
+
+sub i_cleanup {
+ local ($@);
+ if ($i_child_pid) {
+ printdebug "(killing remote 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);
+ my @rdgit;
+ push @rdgit, @dgit;
+ push @rdgit, @ropts;
+ push @rdgit, qw(remote-push-responder), (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 $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 () {
+ if (!open F, "debian/source/format") {
+ die $! unless $!==&ENOENT;
+ return;
+ }
+ $_ = <F>;
+ F->error and die $!;
+ chomp;
+ return unless madformat($_);
+ # sigh