X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=eb263b10f188f58a0ffcfb61ee09fa8e5208cb97;hp=b17e5c129ad03470d745374257011867a1c4f872;hb=8c50bd93d6dd644c0eef242046e0f818083e206f;hpb=461d89f9163800c63a90361af414e57f576f4c20 diff --git a/dgit b/dgit index b17e5c12..eb263b10 100755 --- a/dgit +++ b/dgit @@ -42,14 +42,22 @@ our (@git) = qw(git); our (@dget) = qw(dget); our (@dput) = qw(dput); our (@debsign) = qw(debsign); -our $keyid; +our (@sbuild) = qw(sbuild); +our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git); +our (@mergechanges) = qw(mergechanges -f); -our $debug = 0; -open DEBUG, ">/dev/null" or die $!; our %opts_opt_map = ('dget' => \@dget, 'dput' => \@dput, - 'debsign' => \@debsign); + 'debsign' => \@debsign, + 'sbuild' => \@sbuild, + 'dpkg-buildpackage' => \@dpkgbuildpackage; + 'mergechanges' => \@mergechanges); + +our $keyid; + +our $debug = 0; +open DEBUG, ">/dev/null" or die $!; our $remotename = 'dgit'; our $ourdscfield = 'Vcs-Dgit-Master'; @@ -63,6 +71,8 @@ sub lrref () { return "refs/remotes/$remotename/$csuite"; } sub rrref () { return "refs/$branchprefix/$csuite"; } sub debiantag ($) { return "debian/$_[0]"; } +sub dscfn () { return "${package}_${cversion}.dsc"; } + our $us = 'dgit'; sub fail () { die "$us: @_\n"; } @@ -86,9 +96,10 @@ sub url_get { $ua = LWP::UserAgent->new(); $ua->env_proxy; } - print "downloading @_...\n"; + my $what = $_[$#_]; + print "downloading $what...\n"; my $r = $ua->get(@_) or die $!; - fail $r->status_line."; failed." unless $r->is_success; + $r->is_success or fail "failed to fetch $what: ".$r->status_line; return $r->decoded_content(); } @@ -111,9 +122,8 @@ sub printcmd { } sub failedcmd { - my $errnoval = $!; - printcmd \*STDERR, "$_[0]: failed command:", @_; - if ($errnoval) { + { local ($!); printcmd \*STDERR, "$_[0]: failed command:", @_ or die $!; }; + if ($!) { fail "failed to fork/exec: $!"; } elsif (!($? & 0xff)) { fail "subprocess failed with error exit status ".($?>>8); @@ -211,6 +221,7 @@ our %defcfg = ('dgit.default.distro' => 'debian', sub cfg { foreach my $c (@_) { + return undef if $c =~ /RETURN-UNDEF/; my @cmd = (@git, qw(config --), $c); my $v; { @@ -225,7 +236,7 @@ sub cfg { my $dv = $defcfg{$c}; return $dv if defined $dv; } - return undef; + badcfg "need value for one of: @_"; } sub access_distro () { @@ -245,7 +256,7 @@ sub access_someuserhost ($) { my ($some) = @_; my $user = access_cfg("$some-user",'username'); my $host = access_cfg("$some-host"); - return defined($user) && length($user) ? "$user\@$host" : $host; + return length($user) ? "$user\@$host" : $host; } sub access_gituserhost () { @@ -253,7 +264,7 @@ sub access_gituserhost () { } sub access_giturl () { - my $url = access_cfg('git-url'); + my $url = access_cfg('git-url','RETURN-UNDEF'); if (!defined $url) { $url = access_cfg('git-proto'). @@ -263,20 +274,32 @@ sub access_giturl () { return "$url/$package.git"; } +sub parsecontrolfh ($$@) { + my ($fh, $desc, @opts) = @_; + my $c = Dpkg::Control::Hash->new({ 'name' => $desc, @opts }); + $c->parse($fh) or die "parsing of $desc failed"; +} + sub parsecontrol { my ($file, $desc) = @_; - my $c = Dpkg::Control::Hash->new({ 'name' => $desc }); my $fh = new IO::File '<', $file or die "$file: $!"; - $c->parse($fh) or die "parsing of $desc failed"; + my $c = parsecontrolfh($fh,$desc); $fh->error and die $!; close $fh; return $c; } +sub getfield ($$) { + my ($dctrl,$field) = @_; + my $v = $dctrl->{$field}; + return $v if defined $v; + fail "missing field $field in ".$v->get_option('name'); +} + sub parsechangelog { my $c = Dpkg::Control::Hash->new(); my $p = new IO::Handle; - my @cmd = qw(dpkg-parsechangelog); + my @cmd = (qw(dpkg-parsechangelog)); open $p, '-|', @cmd or die $!; $c->parse($p); $?=0; $!=0; close $p or failedcmd @cmd; @@ -287,8 +310,8 @@ our %rmad; sub archive_query ($) { my ($method) = @_; - my $query = access_cfg('archive-query'); - if (!$query) { + my $query = access_cfg('archive-query','RETURN-UNDEF'); + if (!defined $query) { my $distro = access_distro(); if ($distro eq 'debian') { $query = "sshdakls:". @@ -324,12 +347,12 @@ sub archive_query_sshdakls ($$) { sub canonicalise_suite_sshdakls ($$) { my ($proto,$data) = @_; $data =~ m/:/ or badcfg "invalid sshdakls method string \`$data'"; - my @cmd = - access_cfg('ssh'), $`, - "set -e; cd $';". - " if test -h $isuite; then readlink $isuite; exit 0; fi;". - " if test -d $isuite; then echo $isuite; exit 0; fi;". - " exit 1"; + my @cmd = + (access_cfg('ssh'), $`, + "set -e; cd $';". + " if test -h $isuite; then readlink $isuite; exit 0; fi;". + " if test -d $isuite; then echo $isuite; exit 0; fi;". + " exit 1"); my $dakls = cmdoutput @cmd; failedcmd @cmd unless $dakls =~ m/^\w/; return $dakls; @@ -362,7 +385,7 @@ sub madison_parse ($) { sub canonicalise_suite_madison ($$) { my @r = archive_query_madison($_[0],$_[1]); @r or fail - "unable to canonialise suite using package $package". + "unable to canonicalise suite using package $package". " which does not appear to exist in suite $suite;". " --existing-package may help"; return $r[2]; @@ -384,10 +407,9 @@ sub get_archive_dsc () { $dscdata = url_get($dscurl); my $dscfh = new IO::File \$dscdata, '<' or die $!; print DEBUG Dumper($dscdata) if $debug>1; - $dsc = Dpkg::Control::Hash->new(allow_pgp=>1); - $dsc->parse($dscfh, 'dsc') or fail "parsing of $dscurl failed"; + $dsc = parsecontrolfh($dscfh,$dscurl, allow_pgp=>1); print DEBUG Dumper($dsc) if $debug>1; - my $fmt = $dsc->{Format}; + my $fmt = getfield $dsc 'Format'; fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt}; } @@ -450,11 +472,13 @@ sub mktree_in_ud_from_only_subdir () { } sub dsc_files () { + my $field = $dsc->{'Checksums-Sha256'} || $dsc->{Files}; + fail "missing both Checksums-Sha256 and Files in ".$dsc->get_option('name'); map { m/^\w+ \d+ (\S+)$/ or fail "could not parse .dsc Files/Checksums line \`$_'"; $1; - } grep m/\S/, split /\n/, ($dsc->{'Checksums-Sha256'} || $dsc->{Files}); + } grep m/\S/, split /\n/, $field; } sub is_orig_file ($) { @@ -487,33 +511,35 @@ sub generate_commit_from_dsc () { my ($tree,$dir) = mktree_in_ud_from_only_subdir(); runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp'; my $clogp = parsecontrol('../changelog.tmp',"commit's changelog"); - my $date = cmdoutput qw(date), '+%s %z', qw(-d),$clogp->{Date}; - my $author = $clogp->{Maintainer}; + my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date'); + my $author = getfield $clogp, 'Maintainer'; $author =~ s#,.*##ms; my $authline = "$author $date"; $authline =~ m/^[^<>]+ \<\S+\> \d+ [-+]\d+$/ or fail "unexpected commit author line format \`$authline'". " (was generated from changelog Maintainer field)"; + my $changes = getfield $clogp, 'Changes'; open C, ">../commit.tmp" or die $!; print C <{Changes} +$chhanges # imported from the archive END close C or die $!; my $outputhash = make_commit qw(../commit.tmp); - print "synthesised git commit from .dsc $clogp->{Version}\n"; + my $cversion = getfield $clogp, 'Version'; + print "synthesised git commit from .dsc $cversion\n"; if ($upload_hash) { runcmd @git, qw(reset --hard), $upload_hash; runcmd qw(sh -ec), 'dpkg-parsechangelog >>../changelogold.tmp'; - my $oldclogp = Dpkg::Control::Hash->new(); - $oldclogp->load('../changelogold.tmp','previous changelog'); + my $oldclogp = parsecontrol('../changelogold.tmp','previous changelog'); + my $oversion = getfield $oldclogp, 'Version'; my $vcmp = - version_compare_string($oldclogp->{Version}, $clogp->{Version}); + version_compare_string($oversion, $cversion); if ($vcmp < 0) { # git upload/ is earlier vsn than archive, use archive open C, ">../commit2.tmp" or die $!; @@ -524,25 +550,25 @@ parent $outputhash author $authline committer $authline -Record $package ($clogp->{Version}) in archive suite $csuite +Record $package ($cversion) in archive suite $csuite END $outputhash = make_commit qw(../commit2.tmp); } elsif ($vcmp > 0) { print STDERR <{Version} (older) -Last allegedly pushed/uploaded: $oldclogp->{Version} (newer or same) +Version actually in archive: $cversion (older) +Last allegedly pushed/uploaded: $oversion (newer or same) Perhaps the upload is stuck in incoming. Using the version from git. END $outputhash = $upload_hash; } elsif ($outputhash ne $upload_hash) { - fail "version in archive ($clogp->{Version})". + fail "version in archive ($cversion)". " is same as version in git". - " to-be-uploaded (upload/) branch ($oldclogp->{Version})". + " to-be-uploaded (upload/) branch ($oversion)". " but archive version hash no commit hash?!"; } } chdir '../../../..' or die $!; - runcmd @git, qw(update-ref -m),"dgit fetch import $clogp->{Version}", + runcmd @git, qw(update-ref -m),"dgit fetch import $cversion", 'DGIT_ARCHIVE', $outputhash; cmdoutput @git, qw(log -n2), $outputhash; # ... gives git a chance to complain if our commit is malformed @@ -587,7 +613,7 @@ sub fetch_from_archive () { # ensures that lrref() is what is actually in the archive, # one way or another get_archive_dsc() or return 0; - defined($dsc_hash = $dsc->{$ourdscfield}) or die; + $dsc_hash = $dsc->{$ourdscfield}; if (defined $dsc_hash) { $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'"; $dsc_hash = $&; @@ -611,6 +637,7 @@ sub fetch_from_archive () { if (defined $dsc_hash) { fail "missing git history even though dsc has hash -" " could not find commit $dsc_hash" + " (should be in ".access_giturl()."#".rref().")"; unless $upload_hash; $hash = $dsc_hash; ensure_we_have_orig(); @@ -674,7 +701,7 @@ sub pull () { } sub check_not_dirty () { - runcmd @git, qw(diff --quiet); + runcmd @git, qw(diff --quiet HEAD); } sub commit_quilty_patch ($) { @@ -700,16 +727,23 @@ sub commit_quilty_patch ($) { sub dopush () { print DEBUG "actually entering push\n"; my $clogp = parsechangelog(); - $package = $clogp->{Source}; - my $dscfn = "${package}_$clogp->{Version}.dsc"; + $package = getfield $clogp 'Source'; + my $cversion = getfield $clogp, 'Version'; + my $dscfn = dscfn(); stat "../$dscfn" or fail "looked for .dsc $dscfn, but $!;". " maybe you forgot to build"; - $dsc = parsecontrol("../$dscfn"); - print DEBUG "format $dsc->{Format}\n"; - if ($dsc->{Format} eq '3.0 (quilt)') { - print "Format \`$dsc->{Format}', urgh\n"; - commit_quilty_patch($dsc->{Version}); + $dsc = parsecontrol("../$dscfn","$dscfn"); + my $dscpackage = getfield $dsc, 'Source'; + my $format = getfield $dsc, 'Format'; + my $dversion = getfield $dsc, 'Version'; + ($dscpackage eq $package && $dversion eq $cversion) + fail "$dsc is for $dscpackage $dversion". + " but debian/changelog is for $package $cversion"; + print DEBUG "format $format\n"; + if ($format eq '3.0 (quilt)') { + print "Format \`$format', urgh\n"; + commit_quilty_patch($dversion); } check_not_dirty(); prep_ud(); @@ -737,27 +771,27 @@ sub dopush () { # (uploadbranch()); $dsc->{$ourdscfield} = rev_parse('HEAD'); $dsc->save("../$dscfn.tmp") or die $!; - if (!$dryrun) { - rename "../$dscfn.tmp","../$dscfn" or die "$dscfn $!"; - } else { - print "[new .dsc left in $dscfn.tmp]\n"; - } if (!$changesfile) { - my $pat = "${package}_$clogp->{Version}_*.changes"; + my $pat = "${package}_c$version_*.changes"; my @cs = glob "../$pat"; fail "failed to find unique changes file". " (looked for $pat in ..); perhaps you need to use dgit -C" unless @cs==1; ($changesfile) = @cs; } - my $tag = debiantag($dsc->{Version}); + my $tag = debiantag($dversion); if (!check_for_git()) { create_remote_git_repo(); } runcmd_ordryrun @git, qw(push),access_giturl(),"HEAD:".rrref(); + if (!$dryrun) { + rename "../$dscfn.tmp","../$dscfn" or die "$dscfn $!"; + } else { + print "[new .dsc left in $dscfn.tmp]\n"; + } if ($sign) { my @tag_cmd = (@git, qw(tag -s -m), - "Release $dsc->{Version} for $csuite [dgit]"); + "Release $dversion for $csuite [dgit]"); push @tag_cmd, qw(-u),$keyid if defined $keyid; push @tag_cmd, $tag; runcmd_ordryrun @tag_cmd; @@ -767,10 +801,10 @@ sub dopush () { runcmd_ordryrun @debsign_cmd; } runcmd_ordryrun @git, qw(push),access_giturl(),"refs/tags/$tag"; - my $host = access_cfg('upload-host'); + my $host = access_cfg('upload-host','RETURN-UNDEF'); my @hostarg = defined($host) ? ($host,) : (); runcmd_ordryrun @dput, @hostarg, $changesfile; - printdone "pushed and uploaded $dsc->{Version}"; + printdone "pushed and uploaded $dversion"; } sub cmd_clone { @@ -804,14 +838,14 @@ sub branchsuite () { sub fetchpullargs () { if (!defined $package) { - my $sourcep = parsecontrol('debian/control'); - $package = $sourcep->{Source}; + 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 = $clogp->{Distribution}; + $isuite = getfield $clogp, 'Distribution'; } canonicalise_suite(); print "fetching from suite $csuite\n"; @@ -840,9 +874,9 @@ sub cmd_push { badusage "-p is not allowed with dgit push" if defined $package; runcmd @git, qw(diff --quiet HEAD); my $clogp = parsechangelog(); - $package = $clogp->{Source}; + $package = getfield $clogp, 'Source'; if (@ARGV==0) { - $isuite = $clogp->{Distribution}; + $isuite = getfield $clogp, 'Distribution'; if ($new_package) { local ($package) = $existing_package; # this is a hack canonicalise_suite(); @@ -863,22 +897,47 @@ sub cmd_build { # we pass further options and args to git-buildpackage badusage "-p is not allowed with dgit build" if defined $package; my $clogp = parsechangelog(); - $isuite = $clogp->{Distribution}; - $package = $clogp->{Source}; + $isuite = getfield $clogp, 'Distribution'; + $package = getfield $clogp, 'Source'; my @cmd = (qw(git-buildpackage -us -uc --git-no-sign-tags), - '--git-builder=dpkg-buildpackage -i\.git/ -I.git'); + "--git-builder=@dpkgbuildpackage"); unless (grep { m/^--git-debian-branch/ } @ARGV) { canonicalise_suite(); push @cmd, "--git-debian-branch=".lbranch(); } runcmd_ordryrun @cmd, @ARGV; + print "build successful\n"; } +sub cmd_sbuild { + check_not_dirty(); + badusage "-p is not allowed with dgit sbuild" if defined $package; + my $clogp = parsechangelog(); + $package = getfield $clogp, 'Source'; + my $version = getfield $clogp, 'Version'; + runcmd (@dpkgbuildpackage, qw(-us -uc -S)); + chdir ".." or die $!; + my $sourcechanges = "${package}_${version}_source.changes"; + my $dscfn = dscfn(); + stat "../$dscfn" or fail "$dscfn (in parent directory): $!"; + stat "../$sourcechanges" or fail "$sourcechanges (in parent directory): $!"; + my $pat = "${package}_${version}_*.changes"; + foreach my $cf (glob $pat) { + next if $cf eq $sourcechanges; + unlink $cf or fail "remove $cf: $!"; + } + runcmd @sbuild, @ARGV, $sourcechanges; + runcmd @mergechanges, glob $pat; + my $multichanges = "${package}_${version}_multi.changes"; + stat $multichanges or fail "$multichanges: $!"; + print "build successful, results in $multichanges\n" or die $!; +} + sub cmd_quilt_fixup { badusage "incorrect arguments to dgit quilt-fixup"; my $clogp = parsechangelog(); - commit_quilty_patch($clogp->{Version}); + commit_quilty_patch((getfield $clogp, Version)); } sub parseopts () { @@ -923,7 +982,7 @@ sub parseopts () { } elsif (s/^-k(.*)//s) { $keyid=$1; } else { - badusage "unknown shorrt option \`$_'"; + badusage "unknown short option \`$_'"; } } }