chiark / gitweb /
Switch to new production git repositories for reading. (this can no longer divert...
[dgit.git] / dgit
diff --git a/dgit b/dgit
index a9fab5e..ac394f0 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -2,7 +2,7 @@
 # dgit
 # Integration between git and Debian-style archives
 #
-# Copyright (C)2013 Ian Jackson
+# Copyright (C)2013-2015 Ian Jackson
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -111,7 +111,7 @@ sub lref () { return "refs/heads/".lbranch(); }
 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
 sub rrref () { return server_ref($csuite); }
 
-sub lrfetchrefs () { return "refs/dgit-fetch/$isuite"; }
+sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
 
 sub stripepoch ($) {
     my ($vsn) = @_;
@@ -444,15 +444,29 @@ our %defcfg = ('dgit.default.distro' => 'debian',
               'dgit.default.archive-query' => 'madison:',
               'dgit.default.sshpsql-dbname' => 'service=projectb',
               'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
-              'dgit-distro.debian.git-host' => 'dgit-git.debian.net',
-              'dgit-distro.debian.git-user-force' => 'dgit',
-              'dgit-distro.debian.git-proto' => 'git+ssh://',
-              'dgit-distro.debian.git-path' => '/dgit/debian/repos',
-              'dgit-distro.debian.git-check' => 'ssh-cmd',
+              'dgit-distro.debian.git-check' => 'url',
+              'dgit-distro.debian.git-check-suffix' => '/info/refs',
+              'dgit-distro.debian/push.git-url' => '',
+              'dgit-distro.debian/push.git-host' => 'dgit-git.debian.net',
+              'dgit-distro.debian/push.git-user-force' => 'dgit',
+              'dgit-distro.debian/push.git-proto' => 'git+ssh://',
+              'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
+              'dgit-distro.debian/push.git-create' => 'true',
+              'dgit-distro.debian/push.git-check' => 'ssh-cmd',
  'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
- 'dgit-distro.debian.archive-query-tls-key',
-    '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
+# 'dgit-distro.debian.archive-query-tls-key',
+#    '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
+# ^ this does not work because curl is broken nowadays
+# Fixing #790093 properly will involve providing providing the key
+# in some pacagke and maybe updating these paths.
+#
+# 'dgit-distro.debian.archive-query-tls-curl-args',
+#   '--ca-path=/etc/ssl/ca-debian',
+# ^ this is a workaround but works (only) on DSA-administered machines
               'dgit-distro.debian.diverts.alioth' => '/alioth',
+              'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
+              'dgit-distro.debian.git-url-suffix' => '',
+              'dgit-distro.debian/push.diverts.alioth' => '/alioth',
               'dgit-distro.debian/alioth.git-host' => 'git.debian.org',
               'dgit-distro.debian/alioth.git-user-force' => '',
               'dgit-distro.debian/alioth.git-proto' => 'git+ssh://',
@@ -477,20 +491,35 @@ our %defcfg = ('dgit.default.distro' => 'debian',
               'dgit-distro.test-dummy.upload-host' => 'test-dummy',
                );
 
+sub git_get_config ($) {
+    my ($c) = @_;
+
+    our %git_get_config_memo;
+    if (exists $git_get_config_memo{$c}) {
+       return $git_get_config_memo{$c};
+    }
+
+    my $v;
+    my @cmd = (@git, qw(config --), $c);
+    {
+       local ($debuglevel) = $debuglevel-2;
+       $v = cmdoutput_errok @cmd;
+    };
+    if ($?==0) {
+    } elsif ($?==256) {
+       $v = undef;
+    } else {
+       failedcmd @cmd;
+    }
+    $git_get_config_memo{$c} = $v;
+    return $v;
+}
+
 sub cfg {
     foreach my $c (@_) {
        return undef if $c =~ /RETURN-UNDEF/;
-       my @cmd = (@git, qw(config --), $c);
-       my $v;
-       {
-           local ($debuglevel) = $debuglevel-2;
-           $v = cmdoutput_errok @cmd;
-       };
-       if ($?==0) {
-           return $v;
-       } elsif ($?!=256) {
-           failedcmd @cmd;
-       }
+       my $v = git_get_config($c);
+       return $v if defined $v;
        my $dv = $defcfg{$c};
        return $dv if defined $dv;
     }
@@ -525,6 +554,12 @@ sub access_quirk () {
     return ('none',undef);
 }
 
+our $access_pushing = 0;
+
+sub pushing () {
+    $access_pushing = 1;
+}
+
 sub access_distros () {
     # Returns list of distros to try, in order
     #
@@ -538,7 +573,12 @@ sub access_distros () {
     my (undef,$quirkdistro) = access_quirk();
     unshift @l, $quirkdistro;
     unshift @l, $instead_distro;
-    return grep { defined } @l;
+    @l = grep { defined } @l;
+
+    if ($access_pushing) {
+       @l = map { ("$_/push", $_) } @l;
+    }
+    @l;
 }
 
 sub access_cfg (@) {
@@ -611,6 +651,7 @@ sub access_gituserhost () {
 sub access_giturl (;$) {
     my ($optional) = @_;
     my $url = access_cfg('git-url','RETURN-UNDEF');
+    my $suffix;
     if (!defined $url) {
        my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
        return undef unless defined $proto;
@@ -618,8 +659,11 @@ sub access_giturl (;$) {
            $proto.
            access_gituserhost().
            access_cfg('git-path');
+    } else {
+       $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
     }
-    return "$url/$package.git";
+    $suffix //= '.git';
+    return "$url/$package$suffix";
 }             
 
 sub parsecontrolfh ($$;$) {
@@ -706,16 +750,25 @@ sub archive_api_query_cmd ($) {
     my $url = access_cfg('archive-query-url');
     if ($url =~ m#^https://([-.0-9a-z]+)/#) {
        my $host = $1;
-       my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF');
+       my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
        foreach my $key (split /\:/, $keys) {
            $key =~ s/\%HOST\%/$host/g;
            if (!stat $key) {
                fail "for $url: stat $key: $!" unless $!==ENOENT;
                next;
            }
-           push @cmd, "--ca-certificate=$key", "--ca-directory=/dev/enoent";
+           fail "config requested specific TLS key but do not know".
+               " how to get curl to use exactly that EE key ($key)";
+#          push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
+#           # Sadly the above line does not work because of changes
+#           # to gnutls.   The real fix for #790093 may involve
+#           # new curl options.
            last;
        }
+       # Fixing #790093 properly will involve providing a value
+       # for this on clients.
+       my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
+       push @cmd, split / /, $kargs if defined $kargs;
     }
     push @cmd, $url.$subpath;
     return @cmd;
@@ -1002,13 +1055,32 @@ sub check_for_git () {
        if ($r =~ m/^divert (\w+)$/) {
            my $divert=$1;
            my ($usedistro,) = access_distros();
+           # NB that if we are pushing, $usedistro will be $distro/push
            $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
            $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
-           printdebug "diverting $divert so using distro $instead_distro\n";
+           progress "diverting to $divert (using config for $instead_distro)";
            return check_for_git();
        }
        failedcmd @cmd unless $r =~ m/^[01]$/;
        return $r+0;
+    } elsif ($how eq 'url') {
+       my $prefix = access_cfg('git-check-url','git-url');
+       my $suffix = access_cfg('git-check-suffix','git-suffix',
+                               'RETURN-UNDEF') // '.git';
+       my $url = "$prefix/$package$suffix";
+       my @cmd = (qw(curl -sS -I), $url);
+       my $result = cmdoutput @cmd;
+       $result =~ m/^\S+ (404|200) /s or
+           fail "unexpected results from git check query - ".
+               Dumper($prefix, $result);
+       my $code = $1;
+       if ($code eq '404') {
+           return 0;
+       } elsif ($code eq '200') {
+           return 1;
+       } else {
+           die;
+       }
     } elsif ($how eq 'true') {
        return 1;
     } elsif ($how eq 'false') {
@@ -1129,6 +1201,69 @@ sub clogp_authline ($) {
     return $authline;
 }
 
+sub vendor_patches_distro ($$) {
+    my ($checkdistro, $what) = @_;
+    return unless defined $checkdistro;
+
+    my $series = "debian/patches/\L$checkdistro\E.series";
+    printdebug "checking for vendor-specific $series ($what)\n";
+
+    if (!open SERIES, "<", $series) {
+       die "$series $!" unless $!==ENOENT;
+       return;
+    }
+    while (<SERIES>) {
+       next unless m/\S/;
+       next if m/^\s+\#/;
+
+       print STDERR <<END;
+
+Unfortunately, this source package uses a feature of dpkg-source where
+the same source package unpacks to different source code on different
+distros.  dgit cannot safely operate on such packages on affected
+distros, because the meaning of source packages is not stable.
+
+Please ask the distro/maintainer to remove the distro-specific series
+files and use a different technique (if necessary, uploading actually
+different packages, if different distros are supposed to have
+different code).
+
+END
+       fail "Found active distro-specific series file for".
+           " $checkdistro ($what): $series, cannot continue";
+    }
+    die "$series $!" if SERIES->error;
+    close SERIES;
+}
+
+sub check_for_vendor_patches () {
+    # This dpkg-source feature doesn't seem to be documented anywhere!
+    # But it can be found in the changelog (reformatted):
+
+    #   commit  4fa01b70df1dc4458daee306cfa1f987b69da58c
+    #   Author: Raphael Hertzog <hertzog@debian.org>
+    #   Date: Sun  Oct  3  09:36:48  2010 +0200
+
+    #   dpkg-source: correctly create .pc/.quilt_series with alternate
+    #   series files
+    #   
+    #   If you have debian/patches/ubuntu.series and you were
+    #   unpacking the source package on ubuntu, quilt was still
+    #   directed to debian/patches/series instead of
+    #   debian/patches/ubuntu.series.
+    #   
+    #   debian/changelog                        |    3 +++
+    #   scripts/Dpkg/Source/Package/V3/quilt.pm |    4 +++-
+    #   2 files changed, 6 insertions(+), 1 deletion(-)
+
+    use Dpkg::Vendor;
+    vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
+    vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
+                        "Dpkg::Vendor \`current vendor'");
+    vendor_patches_distro(access_basedistro(),
+                         "distro being accessed");
+}
+
 sub generate_commit_from_dsc () {
     prep_ud();
     changedir $ud;
@@ -1161,6 +1296,7 @@ sub generate_commit_from_dsc () {
     runcmd @cmd;
 
     my ($tree,$dir) = mktree_in_ud_from_only_subdir();
+    check_for_vendor_patches() if madformat($dsc->{format});
     runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp';
     my $clogp = parsecontrol('../changelog.tmp',"commit's changelog");
     my $authline = clogp_authline $clogp;
@@ -1263,12 +1399,33 @@ sub ensure_we_have_orig () {
 }
 
 sub git_fetch_us () {
-    runcmd_ordryrun_local @git, qw(fetch),access_giturl(),fetchspec();
-    if (deliberately_not_fast_forward) {
-       runcmd_ordryrun_local @git, qw(fetch -p), access_giturl(),
-           map { "+refs/$_/*:".lrfetchrefs."/$_/*" }
-           qw(tags heads);
-    }
+    my @specs = (fetchspec());
+    push @specs,
+        map { "+refs/$_/*:".lrfetchrefs."/$_/*" }
+        qw(tags heads);
+    runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(), @specs;
+
+    my %here;
+    my $tagpat = debiantag('*',access_basedistro);
+
+    git_for_each_ref("refs/tags/".$tagpat, sub {
+       my ($objid,$objtype,$fullrefname,$reftail) = @_;
+       printdebug "currently $fullrefname=$objid\n";
+       $here{$fullrefname} = $objid;
+    });
+    git_for_each_ref(lrfetchrefs."/tags/".$tagpat, sub {
+       my ($objid,$objtype,$fullrefname,$reftail) = @_;
+       my $lref = "refs".substr($fullrefname, length lrfetchrefs);
+       printdebug "offered $lref=$objid\n";
+       if (!defined $here{$lref}) {
+           my @upd = (@git, qw(update-ref), $lref, $objid, '');
+           runcmd_ordryrun_local @upd;
+       } elsif ($here{$lref} eq $objid) {
+       } else {
+           print STDERR \
+               "Not updateting $lref from $here{$lref} to $objid.\n";
+       }
+    });
 }
 
 sub fetch_from_archive () {
@@ -1375,6 +1532,38 @@ END
     return 1;
 }
 
+sub set_local_git_config ($$) {
+    my ($k, $v) = @_;
+    runcmd @git, qw(config), $k, $v;
+}
+
+sub setup_mergechangelogs () {
+    my $driver = 'dpkg-mergechangelogs';
+    my $cb = "merge.$driver";
+    my $attrs = '.git/info/attributes';
+    ensuredir '.git/info';
+
+    open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
+    if (!open ATTRS, "<", $attrs) {
+       $!==ENOENT or die "$attrs: $!";
+    } else {
+       while (<ATTRS>) {
+           chomp;
+           next if m{^debian/changelog\s};
+           print NATTRS $_, "\n" or die $!;
+       }
+       ATTRS->error and die $!;
+       close ATTRS;
+    }
+    print NATTRS "debian/changelog merge=$driver\n" or die $!;
+    close NATTRS;
+
+    set_local_git_config "$cb.name", 'debian/changelog merge driver';
+    set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
+
+    rename "$attrs.new", "$attrs" or die "$attrs: $!";
+}
+
 sub clone ($) {
     my ($dstdir) = @_;
     canonicalise_suite();
@@ -1385,7 +1574,7 @@ sub clone ($) {
     runcmd @git, qw(init -q);
     my $giturl = access_giturl(1);
     if (defined $giturl) {
-       runcmd @git, qw(config), "remote.$remotename.fetch", fetchspec();
+       set_local_git_config "remote.$remotename.fetch", fetchspec();
        open H, "> .git/HEAD" or die $!;
        print H "ref: ".lref()."\n" or die $!;
        close H or die $!;
@@ -1404,6 +1593,7 @@ sub clone ($) {
        $vcsgiturl =~ s/\s+-b\s+\S+//g;
        runcmd @git, qw(remote add vcs-git), $vcsgiturl;
     }
+    setup_mergechangelogs();
     runcmd @git, qw(reset --hard), lrref();
     printdone "ready for work in $dstdir";
 }
@@ -1490,7 +1680,7 @@ sub push_parse_changelog ($) {
 
     $package = getfield $clogp, 'Source';
     my $cversion = getfield $clogp, 'Version';
-    my $tag = debiantag($cversion);
+    my $tag = debiantag($cversion, access_basedistro);
     runcmd @git, qw(check-ref-format), $tag;
 
     my $dscfn = dscfn($cversion);
@@ -1616,6 +1806,7 @@ sub dopush ($) {
     runcmd qw(dpkg-source -x --),
         $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
     my ($tree,$dir) = mktree_in_ud_from_only_subdir();
+    check_for_vendor_patches() if madformat($dsc->{format});
     changedir '../../../..';
     my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
     my @diffcmd = (@git, qw(diff), $diffopt, $tree);
@@ -1661,7 +1852,7 @@ sub dopush ($) {
     responder_send_command("param head $head");
     responder_send_command("param csuite $csuite");
 
-    if ($forceflag) {
+    if (deliberately_not_fast_forward) {
        git_for_each_ref(lrfetchrefs, sub {
            my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
            my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
@@ -1687,13 +1878,12 @@ sub dopush ($) {
     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(),
-        $forceflag."HEAD:".rrref(), "refs/tags/$tag";
+        $forceflag."HEAD:".rrref(), $forceflag."refs/tags/$tag";
     runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
 
     if ($we_are_responder) {
@@ -1800,6 +1990,7 @@ sub cmd_pull {
 }
 
 sub cmd_push {
+    pushing();
     parseopts();
     badusage "-p is not allowed with dgit push" if defined $package;
     check_not_dirty();
@@ -1816,10 +2007,12 @@ sub cmd_push {
     if ($new_package) {
        local ($package) = $existing_package; # this is a hack
        canonicalise_suite();
-    }
-    if (defined $specsuite && $specsuite ne $isuite) {
+    } else {
        canonicalise_suite();
-       $csuite eq $specsuite or
+    }
+    if (defined $specsuite &&
+       $specsuite ne $isuite &&
+       $specsuite ne $csuite) {
            fail "dgit push: changelog specifies $isuite ($csuite)".
                " but command line specifies $specsuite";
     }
@@ -1851,6 +2044,7 @@ sub cmd_push {
 #---------- remote commands' implementation ----------
 
 sub cmd_remote_push_build_host {
+    pushing();
     my ($nrargs) = shift @ARGV;
     my (@rargs) = @ARGV[0..$nrargs-1];
     @ARGV = @ARGV[$nrargs..$#ARGV];
@@ -1911,6 +2105,7 @@ sub i_method {
 }
 
 sub cmd_rpush {
+    pushing();
     my $host = nextarg;
     my $dir;
     if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
@@ -2254,7 +2449,7 @@ sub quiltify ($$) {
            my $s = $abbrev->($notp);
            my $c = $notp->{Child};
            $s .= "..".$abbrev->($c) if $c;
-           $s .= ": ".$c->{Whynot};
+           $s .= ": ".$notp->{Whynot};
            return $s;
        };
        if ($quilt_mode eq 'linear') {
@@ -2338,6 +2533,8 @@ sub build_maybe_quilt_fixup () {
     return unless madformat $format;
     # sigh
 
+    check_for_vendor_patches();
+
     # Our objective is:
     #  - honour any existing .pc in case it has any strangeness
     #  - determine the git commit corresponding to the tip of
@@ -2462,7 +2659,7 @@ END
     commit_quilty_patch();
 
     if ($mustdeletepc) {
-        runcmd @git, qw(rm -rq .pc);
+        runcmd @git, qw(rm -rqf .pc);
         commit_admin "Commit removal of .pc (quilt series tracking data)";
     }
 
@@ -2494,8 +2691,18 @@ sub quilt_fixup_editor () {
 sub clean_tree () {
     if ($cleanmode eq 'dpkg-source') {
        runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
+    } elsif ($cleanmode eq 'dpkg-source-d') {
+       runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
     } elsif ($cleanmode eq 'git') {
        runcmd_ordryrun_local @git, qw(clean -xdf);
+    } elsif ($cleanmode eq 'git-ff') {
+       runcmd_ordryrun_local @git, qw(clean -xdff);
+    } elsif ($cleanmode eq 'check') {
+       my $leftovers = cmdoutput @git, qw(clean -xdn);
+       if (length $leftovers) {
+           print STDERR $leftovers, "\n" or die $!;
+           fail "tree contains uncommitted files and --clean=check specified";
+       }
     } elsif ($cleanmode eq 'none') {
     } else {
        die "$cleanmode ?";
@@ -2545,17 +2752,35 @@ sub changesopts () {
     return @opts;
 }
 
+sub massage_dbp_args ($) {
+    my ($cmd) = @_;
+    return unless $cleanmode =~ m/git|none/;
+    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;
+    push @newcmd, @$cmd;
+    @$cmd = @newcmd;
+}
+
 sub cmd_build {
     build_prep();
-    runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc), changesopts(), @ARGV;
+    my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts(), @ARGV);
+    massage_dbp_args \@dbp;
+    runcmd_ordryrun_local @dbp;
     printdone "build successful\n";
 }
 
 sub cmd_git_build {
     build_prep();
+    my @dbp = @dpkgbuildpackage;
+    massage_dbp_args \@dbp;
     my @cmd =
        (qw(git-buildpackage -us -uc --git-no-sign-tags),
-        "--git-builder=@dpkgbuildpackage");
+        "--git-builder=@dbp");
     unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
        canonicalise_suite();
        push @cmd, "--git-debian-branch=".lbranch();
@@ -2572,6 +2797,9 @@ sub build_source {
     if ($cleanmode eq 'dpkg-source') {
        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();
     } else {
        my $pwd = must_getcwd();
        my $leafdir = basename $pwd;
@@ -2596,7 +2824,7 @@ sub cmd_sbuild {
     changedir "..";
     my $pat = "${package}_".(stripepoch $version)."_*.changes";
     if (act_local()) {
-       stat_exist $dscfn or fail "$dscfn (in parent directory): $!";
+       stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
        stat_exists $sourcechanges
            or fail "$sourcechanges (in parent directory): $!";
        foreach my $cf (glob $pat) {
@@ -2636,6 +2864,20 @@ sub cmd_archive_api_query {
     exec @cmd or fail "exec curl: $!\n";
 }
 
+sub cmd_clone_dgit_repos_server {
+    badusage "need destination argument" unless @ARGV==1;
+    my ($destdir) = @ARGV;
+    $package = '_dgit-repos-server';
+    my @cmd = (@git, qw(clone), access_giturl(), $destdir);
+    debugcmd ">",@cmd;
+    exec @cmd or fail "exec git clone: $!\n";
+}
+
+sub cmd_setup_mergechangelogs {
+    badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
+    setup_mergechangelogs();
+}
+
 #---------- argument parsing and main program ----------
 
 sub cmd_version {
@@ -2700,7 +2942,7 @@ sub parseopts () {
            } elsif (m/^--build-products-dir=(.*)/s) {
                push @ropts, $_;
                $buildproductsdir = $1;
-           } elsif (m/^--clean=(dpkg-source|git|none)$/s) {
+           } elsif (m/^--clean=(dpkg-source(?:-d)?|git|git-ff|check|none)$/s) {
                push @ropts, $_;
                $cleanmode = $1;
            } elsif (m/^--clean=(.*)$/s) {
@@ -2772,9 +3014,18 @@ sub parseopts () {
                } elsif (s/^-wg$//s) {
                    push @ropts, $&;
                    $cleanmode = 'git';
+               } elsif (s/^-wgf$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'git-ff';
                } elsif (s/^-wd$//s) {
                    push @ropts, $&;
                    $cleanmode = 'dpkg-source';
+               } elsif (s/^-wdd$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'dpkg-source-d';
+               } elsif (s/^-wc$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'check';
                } else {
                    badusage "unknown short option \`$_'";
                }