X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=0c8183dc2d614abe2cd27f91188d205752ad1bcd;hp=21cbd56f67d26cc1cd8ab11052461f4c37d6ef5c;hb=71350fe30f7449b24dc2884b42cc5ff50a2bf84e;hpb=348b995ead9fbcab890da5d222af36402f33d68c diff --git a/dgit b/dgit index 21cbd56f..0c8183dc 100755 --- a/dgit +++ b/dgit @@ -57,8 +57,9 @@ our $rmonerror = 1; our @deliberatelies; our %previously; our $existing_package = 'dpkg'; -our $cleanmode = 'dpkg-source'; +our $cleanmode; our $changes_since_version; +our $rmchanges; our $quilt_mode; our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck'; our $we_are_responder; @@ -67,6 +68,7 @@ our $initiator_tempdir; our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)"); our $suite_re = '[-+.0-9a-z]+'; +our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none'; our (@git) = qw(git); our (@dget) = qw(dget); @@ -74,7 +76,7 @@ our (@curl) = qw(curl -f); our (@dput) = qw(dput); our (@debsign) = qw(debsign); our (@gpg) = qw(gpg); -our (@sbuild) = qw(sbuild -A); +our (@sbuild) = qw(sbuild); our (@ssh) = 'ssh'; our (@dgit) = qw(dgit); our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git); @@ -111,6 +113,7 @@ our $keyid; autoflush STDOUT 1; our $supplementary_message = ''; +our $need_split_build_invocation = 0; END { local ($@, $?); @@ -146,6 +149,11 @@ sub dscfn ($) { return srcfn($vsn,".dsc"); } +sub changespat ($;$) { + my ($vsn, $arch) = @_; + return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes"; +} + our $us = 'dgit'; initdebug(''); @@ -154,7 +162,7 @@ END { local ($?); foreach my $f (@end) { eval { $f->(); }; - warn "$us: cleanup: $@" if length $@; + print STDERR "$us: cleanup: $@" if length $@; } }; @@ -1201,10 +1209,12 @@ our ($dsc_hash,$lastpush_hash); our $ud = '.git/dgit/unpack'; -sub prep_ud () { - rmtree($ud); +sub prep_ud (;$) { + my ($d) = @_; + $d //= $ud; + rmtree($d); mkpath '.git/dgit'; - mkdir $ud or die $!; + mkdir $d or die $!; } sub mktree_in_ud_here () { @@ -1245,7 +1255,7 @@ sub mktree_in_ud_from_only_subdir () { remove_stray_gits(); mktree_in_ud_here(); - my $format=get_source_format(); + my ($format, $fopts) = get_source_format(); if (madformat($format)) { rmtree '.pc'; } @@ -1381,11 +1391,12 @@ sub generate_commit_from_dsc () { my $f = $fi->{Filename}; die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#; - link "../../../$f", $f + link_ltarget "../../../$f", $f or $!==&ENOENT or die "$f $!"; - complete_file_from_dsc('.', $fi); + complete_file_from_dsc('.', $fi) + or next; if (is_orig_file($f)) { link $f, "../../../../$f" @@ -1480,10 +1491,10 @@ sub complete_file_from_dsc ($$) { my $furl = $dscurl; $furl =~ s{/[^/]+$}{}; $furl .= "/$f"; - die "$f ?" unless $f =~ m/^${package}_/; + die "$f ?" unless $f =~ m/^\Q${package}\E_/; die "$f ?" if $f =~ m#/#; runcmd_ordryrun_local @curl,qw(-o),$tf,'--',"$furl"; - next if !act_local(); + return 0 if !act_local(); $downloaded = 1; } @@ -1497,13 +1508,16 @@ sub complete_file_from_dsc ($$) { " demands hash $fi->{Hash} ". ($downloaded ? "(got wrong file from archive!)" : "(perhaps you should delete this file?)"); + + return 1; } sub ensure_we_have_orig () { foreach my $fi (dsc_files_info()) { my $f = $fi->{Filename}; next unless is_orig_file($f); - complete_file_from_dsc('..', $fi); + complete_file_from_dsc('..', $fi) + or next; } } @@ -1701,7 +1715,7 @@ sub clone ($) { canonicalise_suite(); badusage "dry run makes no sense with clone" unless act_local(); my $hasgit = check_for_git(); - mkdir $dstdir or die "$dstdir $!"; + mkdir $dstdir or fail "create \`$dstdir': $!"; changedir $dstdir; runcmd @git, qw(init -q); my $giturl = access_giturl(1); @@ -1746,7 +1760,14 @@ sub pull () { } sub check_not_dirty () { + foreach my $f (qw(local-options local-patch-header)) { + if (stat_exists "debian/source/$f") { + fail "git tree contains debian/source/$f"; + } + } + return if $ignoredirty; + my @cmd = (@git, qw(diff --quiet HEAD)); debugcmd "+",@cmd; $!=0; $?=0; system @cmd; @@ -1778,11 +1799,32 @@ sub commit_quilty_patch () { progress "nothing quilty to commit, ok."; return; } - runcmd_ordryrun_local @git, qw(add), sort keys %adds; + my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds; + runcmd_ordryrun_local @git, qw(add -f), @adds; commit_admin "Commit Debian 3.0 (quilt) metadata"; } sub get_source_format () { + my %options; + if (open F, "debian/source/options") { + while () { + next if m/^\s*\#/; + next unless m/\S/; + s/\s+$//; # ignore missing final newline + if (m/\s*\#\s*/) { + my ($k, $v) = ($`, $'); #'); + $v =~ s/^"(.*)"$/$1/; + $options{$k} = $v; + } else { + $options{$_} = 1; + } + } + F->error and die $!; + close F; + } else { + die $! unless $!==&ENOENT; + } + if (!open F, "debian/source/format") { die $! unless $!==&ENOENT; return ''; @@ -1790,7 +1832,7 @@ sub get_source_format () { $_ = ; F->error and die $!; chomp; - return $_; + return ($_, \%options); } sub madformat ($) { @@ -1937,6 +1979,7 @@ END my $format = getfield $dsc, 'Format'; printdebug "format $format\n"; if (madformat($format)) { + # user might have not used dgit build, so maybe do this now: commit_quilty_patch(); } check_not_dirty(); @@ -1964,19 +2007,13 @@ END } my $head = git_rev_parse('HEAD'); if (!$changesfile) { - my $multi = "$buildproductsdir/". - "${package}_".(stripepoch $cversion)."_multi.changes"; - if (stat_exists "$multi") { - $changesfile = $multi; - } else { - 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; - } + my $pat = changespat $cversion; + my @cs = glob "$buildproductsdir/$pat"; + fail "failed to find unique changes file". + " (looked for $pat in $buildproductsdir);". + " perhaps you need to use dgit -C" + unless @cs==1; + ($changesfile) = @cs; } else { $changesfile = "$buildproductsdir/$changesfile"; } @@ -2050,7 +2087,7 @@ END sign_changes $changesfile; } - supplementary_message(<<'END'); + supplementary_message(< $r\n"; + return $r; } sub quiltify_tree_sentinelfiles ($) { @@ -2489,8 +2538,15 @@ sub quiltify_tree_sentinelfiles ($) { return $r; } -sub quiltify ($$) { - my ($clogp,$target) = @_; +sub quilt_could_gbp ($$$) { + my ($userhead,$unapplied,$applied) = @_; + return + !(quiltify_trees_differ($userhead,$unapplied,1) & 01) && + (quiltify_trees_differ($userhead,$applied,1) & 01); +} + +sub quiltify ($$$$) { + my ($clogp,$target,$unapplied,$oldtiptree) = @_; # Quilt patchification algorithm # @@ -2516,14 +2572,6 @@ sub quiltify ($$) { # After traversing PT, we git commit the changes which # should be contained within debian/patches. - changedir '../fake'; - remove_stray_gits(); - mktree_in_ud_here(); - rmtree '.pc'; - runcmd @git, 'add', '.'; - my $oldtiptree=git_write_tree(); - changedir '../work'; - # The search for the path S..T is breadth-first. We maintain a # todo list containing search nodes. A search node identifies a # commit, and looks something like this: @@ -2621,7 +2669,7 @@ sub quiltify ($$) { my $abbrev = sub { my $x = $_[0]{Commit}; $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/; - return $; + return $x; }; my $reportnot = sub { my ($notp) = @_; @@ -2636,6 +2684,12 @@ sub quiltify ($$) { foreach my $notp (@nots) { print STDERR "$us: ", $reportnot->($notp), "\n"; } + if (quilt_could_gbp($target,$unapplied,$oldtiptree)) { + print STDERR <{'single-debian-patch'}) { + quilt_fixup_singlepatch($clogp, $headref, $upstreamversion); + } else { + quilt_fixup_multipatch($clogp, $headref, $upstreamversion); + } + + changedir '../../../..'; + runcmd_ordryrun_local + @git, qw(pull --ff-only -q .git/dgit/unpack/work master); +} + +sub quilt_fixup_mkwork ($) { + my ($headref) = @_; + + mkdir "work" or die $!; + changedir "work"; + mktree_in_ud_here(); + runcmd @git, qw(reset -q --hard), $headref; +} + +sub quilt_fixup_linkorigs ($$) { + my ($upstreamversion, $fn) = @_; + # calls $fn->($leafname); + + foreach my $f (<../../../../*>) { #/){ + my $b=$f; $b =~ s{.*/}{}; + { + local ($debuglevel) = $debuglevel-1; + printdebug "QF linkorigs $b, $f ?\n"; + } + next unless is_orig_file $b, srcfn $upstreamversion,''; + printdebug "QF linkorigs $b, $f Y\n"; + link_ltarget $f, $b or die "$b $!"; + $fn->($b); + } +} + +sub quilt_fixup_delete_pc () { + runcmd @git, qw(rm -rqf .pc); + commit_admin "Commit removal of .pc (quilt series tracking data)"; +} + +sub quilt_fixup_singlepatch ($$$) { + my ($clogp, $headref, $upstreamversion) = @_; + + progress "starting quiltify (single-debian-patch)"; + + # dpkg-source --commit generates new patches even if + # single-debian-patch is in debian/source/options. In order to + # get it to generate debian/patches/debian-changes, it is + # necessary to build the source package. + + quilt_fixup_linkorigs($upstreamversion, sub { }); + quilt_fixup_mkwork($headref); + + rmtree("debian/patches"); + + runcmd @dpkgsource, qw(-b .); + chdir ".."; + runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc"); + rename srcfn("$upstreamversion", "/debian/patches"), + "work/debian/patches"; + + chdir "work"; + commit_quilty_patch(); + + +} + +sub quilt_fixup_multipatch ($$$) { + my ($clogp, $headref, $upstreamversion) = @_; + + progress "starting quiltify (multiple patches, $quilt_mode mode)"; + # Our objective is: # - honour any existing .pc in case it has any strangeness # - determine the git commit corresponding to the tip of @@ -2737,7 +2874,7 @@ sub build_maybe_quilt_fixup () { # 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} + # debian/{patches,rules,source/format,source/options} # 3. Generate a fake .dsc containing just these fields: # Format Source Version Files # 4. Extract the fake .dsc @@ -2758,14 +2895,30 @@ sub build_maybe_quilt_fixup () { # 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 = git_rev_parse('HEAD'); - - prep_ud(); - changedir $ud; - - my $upstreamversion=$version; - $upstreamversion =~ s/-[^-]*$//; + # Another situation we may have to cope with is gbp-style + # patches-unapplied trees. + # + # We would want to detect these, so we know to escape into + # quilt_fixup_gbp. However, this is in general not possible. + # Consider a package with a one patch which the dgit user reverts + # (with git-revert or the moral equivalent). + # + # That is indistinguishable in contents from a patches-unapplied + # tree. And looking at the history to distinguish them is not + # useful because the user might have made a confusing-looking git + # history structure (which ought to produce an error if dgit can't + # cope, not a silent reintroduction of an unwanted patch). + # + # So gbp users will have to pass an option. But we can usually + # detect their failure to do so: if the tree is not a clean + # patches-applied tree, quilt linearisation fails, but the tree + # _is_ a clean patches-unapplied tree, we can suggest that maybe + # they want --quilt=unapplied. + # + # To help detect this, when we are extracting the fake dsc, we + # first extract it with --skip-patches, and then apply the patches + # afterwards with dpkg-source --before-build. That lets us save a + # tree object corresponding to .origs. my $fakeversion="$upstreamversion-~~DGITFAKE"; @@ -2790,16 +2943,14 @@ END 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); - } + quilt_fixup_linkorigs($upstreamversion, $dscaddfile); - my @files=qw(debian/source/format debian/rules); - if (stat_exists '../../../debian/patches') { - push @files, 'debian/patches'; + my @files=qw(debian/source/format debian/rules + debian/control debian/changelog); + foreach my $maybe (qw(debian/patches debian/source/options + debian/tests/control)) { + next unless stat_exists "../../../$maybe"; + push @files, $maybe; } my $debtar= srcfn $fakeversion,'.debian.tar.gz'; @@ -2808,15 +2959,31 @@ END $dscaddfile->($debtar); close $fakedsc or die $!; - runcmd qw(sh -ec), 'exec dpkg-source --no-check -x fake.dsc >/dev/null'; + runcmd qw(sh -ec), + 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null'; my $fakexdir= $package.'-'.(stripepoch $upstreamversion); rename $fakexdir, "fake" or die "$fakexdir $!"; - mkdir "work" or die $!; - changedir "work"; + changedir 'fake'; + + remove_stray_gits(); mktree_in_ud_here(); - runcmd @git, qw(reset --hard), $headref; + + rmtree '.pc'; + + runcmd @git, qw(add -Af .); + my $unapplied=git_write_tree(); + printdebug "fake orig tree object $unapplied\n"; + + ensuredir '.pc'; + + runcmd qw(sh -ec), + 'exec dpkg-source --before-build . >/dev/null'; + + changedir '..'; + + quilt_fixup_mkwork($headref); my $mustdeletepc=0; if (stat_exists ".pc") { @@ -2827,7 +2994,13 @@ END rename '../fake/.pc','.pc' or die $!; } - quiltify($clogp,$headref); + changedir '../fake'; + rmtree '.pc'; + runcmd @git, qw(add -Af .); + my $oldtiptree=git_write_tree(); + changedir '../work'; + + quiltify($clogp,$headref,$unapplied,$oldtiptree); if (!open P, '>>', ".pc/applied-patches") { $!==&ENOENT or die $!; @@ -2838,12 +3011,8 @@ END commit_quilty_patch(); if ($mustdeletepc) { - runcmd @git, qw(rm -rqf .pc); - commit_admin "Commit removal of .pc (quilt series tracking data)"; + quilt_fixup_delete_pc(); } - - changedir '../../../..'; - runcmd @git, qw(pull --ff-only -q .git/dgit/unpack/work master); } sub quilt_fixup_editor () { @@ -2907,6 +3076,16 @@ sub build_prep () { $package = getfield $clogp, 'Source'; $version = getfield $clogp, 'Version'; build_maybe_quilt_fixup(); + if ($rmchanges) { + my $pat = changespat $version; + foreach my $f (glob "$buildproductsdir/$pat") { + if (act_local()) { + unlink $f or fail "remove old changes file $f: $!"; + } else { + progress "would remove $f"; + } + } + } } sub changesopts_initial () { @@ -2946,48 +3125,98 @@ sub changesopts () { sub massage_dbp_args ($;$) { my ($cmd,$xargs) = @_; - if ($cleanmode eq 'dpkg-source') { + # We need to: + # + # - if we're going to split the source build out so we can + # do strange things to it, massage the arguments to dpkg-buildpackage + # so that the main build doessn't build source (or add an argument + # to stop it building source by default). + # + # - add -nc to stop dpkg-source cleaning the source tree, + # unless we're not doing a split build and want dpkg-source + # as cleanmode, in which case we can do nothing + # + # return values: + # 0 - source will NOT need to be built separately by caller + # +1 - source will need to be built separately by caller + # +2 - source will need to be built separately by caller AND + # dpkg-buildpackage should not in fact be run at all! + debugcmd '#massaging#', @$cmd if $debuglevel>1; +#print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation); + if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) { $suppress_clean = 1; - return; + return 0; } - debugcmd '#massaging#', @$cmd if $debuglevel>1; - my @newcmd = shift @$cmd; # -nc has the side effect of specifying -b if nothing else specified - push @newcmd, '-nc'; # and some combinations of -S, -b, et al, are errors, rather than - # later simply overriding earlier - push @newcmd, '-F' unless grep { m/^-[bBASF]$/ } (@$cmd, @$xargs); - push @newcmd, @$cmd; - @$cmd = @newcmd; + # later simply overriding earlie. So we need to: + # - search the command line for these options + # - pick the last one + # - perhaps add our own as a default + # - perhaps adjust it to the corresponding non-source-building version + my $dmode = '-F'; + foreach my $l ($cmd, $xargs) { + next unless $l; + @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l; + } + push @$cmd, '-nc'; +#print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode); + my $r = 0; + if ($need_split_build_invocation) { + $r = $dmode =~ m/[S]/ ? +2 : + $dmode =~ y/gGF/ABb/ ? +1 : + $dmode =~ m/[ABb]/ ? 0 : + die "$dmode ?"; + } + push @$cmd, $dmode; +#print STDERR "MASS2 ",Dumper($cmd, $xargs, $r); + return $r; } sub cmd_build { my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV); - massage_dbp_args \@dbp; - build_prep(); - push @dbp, changesopts_version(); - runcmd_ordryrun_local @dbp; + my $wantsrc = massage_dbp_args \@dbp; + if ($wantsrc > 0) { + build_source(); + } else { + build_prep(); + } + if ($wantsrc < 2) { + push @dbp, changesopts_version(); + runcmd_ordryrun_local @dbp; + } printdone "build successful\n"; } sub cmd_gbp_build { my @dbp = @dpkgbuildpackage; - massage_dbp_args \@dbp, \@ARGV; - my @cmd = - (qw(git-buildpackage -us -uc --git-no-sign-tags), - "--git-builder=@dbp"); - if ($cleanmode eq 'dpkg-source') { - $suppress_clean = 1; + + my $wantsrc = massage_dbp_args \@dbp, \@ARGV; + + my @cmd; + if (length executable_on_path('git-buildpackage')) { + @cmd = qw(git-buildpackage); } else { - push @cmd, '--git-cleaner=true'; + @cmd = qw(gbp buildpackage); } - build_prep(); - unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) { - canonicalise_suite(); - push @cmd, "--git-debian-branch=".lbranch(); + push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp"); + + if ($wantsrc > 0) { + build_source(); + } else { + if (!$suppress_clean) { + push @cmd, '--git-cleaner=true'; + } + build_prep(); + } + if ($wantsrc < 2) { + unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) { + canonicalise_suite(); + push @cmd, "--git-debian-branch=".lbranch(); + } + push @cmd, changesopts(); + runcmd_ordryrun_local @cmd, @ARGV; } - push @cmd, changesopts(); - runcmd_ordryrun_local @cmd, @ARGV; printdone "build successful\n"; } sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0 @@ -2998,14 +3227,18 @@ sub build_source { $suppress_clean = 1; } build_prep(); - $sourcechanges = "${package}_".(stripepoch $version)."_source.changes"; + $sourcechanges = changespat $version,'source'; + if (act_local()) { + unlink "../$sourcechanges" or $!==ENOENT + or fail "remove $sourcechanges: $!"; + } $dscfn = dscfn($version); if ($cleanmode eq 'dpkg-source') { - runcmd_ordryrun_local (@dpkgbuildpackage, qw(-us -uc -S)), - changesopts(); + runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S), + changesopts(); } elsif ($cleanmode eq 'dpkg-source-d') { - runcmd_ordryrun_local (@dpkgbuildpackage, qw(-us -uc -S -d)), - changesopts(); + runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d), + changesopts(); } else { my $pwd = must_getcwd(); my $leafdir = basename $pwd; @@ -3027,29 +3260,42 @@ sub cmd_build_source { sub cmd_sbuild { build_source(); + my $pat = changespat $version; + if (!$rmchanges) { + my @unwanted = map { s#^\.\./##; $_; } glob "../$pat"; + @unwanted = grep { $_ ne changespat $version,'source' } @unwanted; + fail "changes files other than source matching $pat". + " already present (@unwanted);". + " building would result in ambiguity about the intended results" + if @unwanted; + } changedir ".."; - my $pat = "${package}_".(stripepoch $version)."_*.changes"; if (act_local()) { stat_exists $dscfn or fail "$dscfn (in parent directory): $!"; stat_exists $sourcechanges or fail "$sourcechanges (in parent directory): $!"; - foreach my $cf (glob $pat) { - next if $cf eq $sourcechanges; - unlink $cf or fail "remove $cf: $!"; - } } - runcmd_ordryrun_local @sbuild, @ARGV, qw(-d), $isuite, $dscfn; + runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn; my @changesfiles = glob $pat; @changesfiles = sort { ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/) or $a cmp $b } @changesfiles; fail "wrong number of different changes files (@changesfiles)" - unless @changesfiles; + unless @changesfiles==2; + my $binchanges = parsecontrol($changesfiles[1], "binary changes file"); + foreach my $l (split /\n/, getfield $binchanges, 'Files') { + fail "$l found in binaries changes file $binchanges" + if $l =~ m/\.dsc$/; + } runcmd_ordryrun_local @mergechanges, @changesfiles; - my $multichanges = "${package}_".(stripepoch $version)."_multi.changes"; + my $multichanges = changespat $version,'multi'; if (act_local()) { stat_exists $multichanges or fail "$multichanges: $!"; + foreach my $cf (glob $pat) { + next if $cf eq $multichanges; + rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!"; + } } printdone "build successful, results in $multichanges\n" or die $!; } @@ -3059,6 +3305,8 @@ sub cmd_quilt_fixup { my $clogp = parsechangelog(); $version = getfield $clogp, 'Version'; $package = getfield $clogp, 'Source'; + check_not_dirty(); + clean_tree(); build_maybe_quilt_fixup(); } @@ -3101,6 +3349,44 @@ sub cmd_version { exit 0; } +our (%valopts_long, %valopts_short); +our @rvalopts; + +sub defvalopt ($$$$) { + my ($long,$short,$val_re,$how) = @_; + my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how }; + $valopts_long{$long} = $oi; + $valopts_short{$short} = $oi; + # $how subref should: + # do whatever assignemnt or thing it likes with $_[0] + # if the option should not be passed on to remote, @rvalopts=() + # or $how can be a scalar ref, meaning simply assign the value +} + +defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version; +defvalopt '--distro', '-d', '.+', \$idistro; +defvalopt '', '-k', '.+', \$keyid; +defvalopt '--existing-package','', '.*', \$existing_package; +defvalopt '--build-products-dir','','.*', \$buildproductsdir; +defvalopt '--clean', '', $cleanmode_re, \$cleanmode; +defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode; + +defvalopt '', '-c', '.*=.*', sub { push @git, '-c', @_; }; + +defvalopt '', '-C', '.+', sub { + ($changesfile) = (@_); + if ($changesfile =~ s#^(.*)/##) { + $buildproductsdir = $1; + } +}; + +defvalopt '--initiator-tempdir','','.*', sub { + ($initiator_tempdir) = (@_); + $initiator_tempdir =~ m#^/# or + badusage "--initiator-tempdir must be used specify an". + " absolute, not relative, directory." +}; + sub parseopts () { my $om; @@ -3110,6 +3396,27 @@ sub parseopts () { @ssh = ($ENV{'GIT_SSH'}); } + my $oi; + my $val; + my $valopt = sub { + my ($what) = @_; + @rvalopts = ($_); + if (!defined $val) { + badusage "$what needs a value" unless @ARGV; + $val = shift @ARGV; + push @rvalopts, $val; + } + badusage "bad value \`$val' for $what" unless + $val =~ m/^$oi->{Re}$(?!\n)/s; + my $how = $oi->{How}; + if (ref($how) eq 'SCALAR') { + $$how = $val; + } else { + $how->($val); + } + push @ropts, @rvalopts; + }; + while (@ARGV) { last unless $ARGV[0] =~ m/^-/; $_ = shift @ARGV; @@ -3131,9 +3438,6 @@ sub parseopts () { } elsif (m/^--new$/) { push @ropts, $_; $new_package=1; - } elsif (m/^--since-version=([^_]+|_)$/) { - push @ropts, $_; - $changes_since_version = $1; } elsif (m/^--([-0-9a-z]+)=(.+)/s && ($om = $opts_opt_map{$1}) && length $om->[0]) { @@ -3144,30 +3448,6 @@ sub parseopts () { ($om = $opts_opt_map{$1})) { push @ropts, $_; push @$om, $2; - } elsif (m/^--existing-package=(.*)/s) { - push @ropts, $_; - $existing_package = $1; - } elsif (m/^--initiator-tempdir=(.*)/s) { - $initiator_tempdir = $1; - $initiator_tempdir =~ m#^/# or - badusage "--initiator-tempdir must be used specify an". - " absolute, not relative, directory." - } elsif (m/^--distro=(.*)/s) { - push @ropts, $_; - $idistro = $1; - } elsif (m/^--build-products-dir=(.*)/s) { - push @ropts, $_; - $buildproductsdir = $1; - } elsif (m/^--clean=(dpkg-source(?:-d)?|git|git-ff|check|none)$/s) { - push @ropts, $_; - $cleanmode = $1; - } elsif (m/^--clean=(.*)$/s) { - badusage "unknown cleaning mode \`$1'"; - } elsif (m/^--quilt=($quilt_modes_re)$/s) { - push @ropts, $_; - $quilt_mode = $1; - } elsif (m/^--quilt=(.*)$/s) { - badusage "unknown quilt fixup mode \`$1'"; } elsif (m/^--ignore-dirty$/s) { push @ropts, $_; $ignoredirty = 1; @@ -3177,9 +3457,19 @@ sub parseopts () { } elsif (m/^--no-rm-on-error$/s) { push @ropts, $_; $rmonerror = 0; + } elsif (m/^--(no-)?rm-old-changes$/s) { + push @ropts, $_; + $rmchanges = !$1; } elsif (m/^--deliberately-($deliberately_re)$/s) { push @ropts, $_; push @deliberatelies, $&; + } elsif (m/^--always-split-source-build$/s) { + # undocumented, for testing + push @ropts, $_; + $need_split_build_invocation = 1; + } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) { + $val = $2 ? $' : undef; #'; + $valopt->($oi->{Long}); } else { badusage "unknown long option \`$_'"; } @@ -3200,30 +3490,10 @@ sub parseopts () { } elsif (s/^-N/-/) { push @ropts, $&; $new_package=1; - } elsif (s/^-v([^_]+|_)$//s) { - push @ropts, $&; - $changes_since_version = $1; } elsif (m/^-m/) { push @ropts, $&; push @changesopts, $_; $_ = ''; - } elsif (s/^-c(.*=.*)//s) { - push @ropts, $&; - push @git, '-c', $1; - } elsif (s/^-d(.+)//s) { - push @ropts, $&; - $idistro = $1; - } elsif (s/^-C(.+)//s) { - push @ropts, $&; - $changesfile = $1; - if ($changesfile =~ s#^(.*)/##) { - $buildproductsdir = $1; - } - } elsif (s/^-k(.+)//s) { - $keyid=$1; - } elsif (m/^-[vdCk]$/) { - badusage - "option \`$_' requires an argument (and no space before the argument)"; } elsif (s/^-wn$//s) { push @ropts, $&; $cleanmode = 'none'; @@ -3242,6 +3512,11 @@ sub parseopts () { } elsif (s/^-wc$//s) { push @ropts, $&; $cleanmode = 'check'; + } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) { + $val = $'; #'; + $val = undef unless length $val; + $valopt->($oi->{Short}); + $_ = ''; } else { badusage "unknown short option \`$_'"; } @@ -3295,6 +3570,11 @@ if (!@ARGV) { my $cmd = shift @ARGV; $cmd =~ y/-/_/; +if (!defined $rmchanges) { + local $access_forpush; + $rmchanges = access_cfg_bool(0, 'rm-old-changes'); +} + if (!defined $quilt_mode) { local $access_forpush; $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF') @@ -3305,6 +3585,15 @@ if (!defined $quilt_mode) { $quilt_mode = $1; } +if (!defined $cleanmode) { + local $access_forpush; + $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF'); + $cleanmode //= 'dpkg-source'; + + badcfg "unknown clean-mode \`$cleanmode'" unless + $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s; +} + my $fn = ${*::}{"cmd_$cmd"}; $fn or badusage "unknown operation $cmd"; $fn->();