use File::Temp qw(tempdir);
use File::Basename;
use Dpkg::Version;
+use Dpkg::Compression;
+use Dpkg::Compression::Process;
use POSIX;
use IPC::Open2;
use Digest::SHA;
our (@git) = qw(git);
our (@dget) = qw(dget);
-our (@curl) = qw(curl);
+our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
our (@dput) = qw(dput);
our (@debsign) = qw(debsign);
our (@gpg) = qw(gpg);
# > param tagformat old|new
# > param maint-view MAINT-VIEW-HEAD
#
+# > param buildinfo-filename P_V_X.buildinfo # zero or more times
+# > file buildinfo # for buildinfos to sign
+#
# > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
# # goes into tag, for replay prevention
#
# [etc]
# < data-block NBYTES [transfer of signed changes]
# [etc]
+# < data-block NBYTES [transfer of each signed buildinfo
+# [etc] same number and order as "file buildinfo"]
+# ...
# < files-end
#
# > complete
return scalar shift @ARGV;
}
+sub pre_help () {
+ no_local_git_cfg();
+}
sub cmd_help () {
print $helpmsg or die $!;
exit 0;
my ($c) = @_;
foreach my $src (@gitcfgsources) {
my $l = $gitcfgs{$src}{$c};
- croak "$l $c" if $l && !ref $l;
+ confess "internal error ($l $c)" if $l && !ref $l;
printdebug"C $c ".(defined $l ?
join " ", map { messagequote "'$_'" } @$l :
"undef")."\n"
"$us: distro or suite appears not to be (properly) supported";
}
+sub no_local_git_cfg () {
+ # needs to be called from pre_*
+ @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
+}
+
sub access_basedistro__noalias () {
if (defined $idistro) {
return $idistro;
return [ (getfield $pre_dsc, 'Version'), $uri ];
}
+sub file_in_archive_aptget () { return undef; }
+
#---------- `dummyapicat' archive query method ----------
sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
sub mktree_in_ud_here () {
runcmd qw(git init -q);
runcmd qw(git config gc.auto 0);
+ foreach my $copy (qw(user.email user.name user.useConfigOnly
+ core.sharedRepository
+ core.compression core.looseCompression
+ core.bigFileThreshold core.fsyncObjectFiles)) {
+ my $v = $gitcfgs{local}{$copy};
+ next unless $v;
+ runcmd qw(git config), $copy, $_ foreach @$v;
+ }
rmtree('.git/objects');
symlink '../../../../objects','.git/objects' or die $!;
setup_gitattrs(1);
sub clogp_authline ($) {
my ($clogp) = @_;
my $author = getfield $clogp, 'Maintainer';
- $author =~ s#,.*##ms;
+ if ($author =~ m/^[^"\@]+\,/) {
+ # single entry Maintainer field with unquoted comma
+ $author = ($& =~ y/,//rd).$'; # strip the comma
+ }
+ # git wants a single author; any remaining commas in $author
+ # are by now preceded by @ (or "). It seems safer to punt on
+ # "..." for now rather than attempting to dequote or something.
+ $author =~ s#,.*##ms unless $author =~ m/"/;
my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
my $authline = "$author $date";
$authline =~ m/$git_authline_re/o or
if defined $compr_ext && !defined $cname;
my $compr_proc =
new Dpkg::Compression::Process compression => $cname;
- my @compr_cmd = $compr_proc->get_uncompress_cmdline();
+ @compr_cmd = $compr_proc->get_uncompress_cmdline();
my $compr_fh = new IO::Handle;
my $compr_pid = open $compr_fh, "-|" // die $!;
if (!$compr_pid) {
!$? or failedcmd @tarcmd;
close $input or
- (@compr_cmd ? failedcmd @compr_cmd
+ (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
: die $!);
# finally, we have the results in "tarball", but maybe
# with the wrong permissions
my $authline = clogp_authline $clogp;
my $changes = getfield $clogp, 'Changes';
+ $changes =~ s/^\n//; # Changes: \n
my $cversion = getfield $clogp, 'Version';
if (@tartrees) {
$fi->{Digester}->reset();
$fi->{Digester}->addfile(*F);
F->error and die $!;
- my $got = $fi->{Digester}->hexdigest();
+ $got = $fi->{Digester}->hexdigest();
return $got eq $fi->{Hash};
};
}
sub git_lrfetch_sane {
- my ($supplementary, @specs) = @_;
+ my ($url, $supplementary, @specs) = @_;
# Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
# at least as regards @specs. Also leave the results in
# %lrfetchrefs_f, and arrange for lrfetchref_used to be
# git fetch to try to generate it. If we don't manage to generate
# the target state, we try again.
- my $url = access_giturl();
-
printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
my $specre = join '|', map {
push @specs, $rewritemap;
push @specs, qw(heads/*) if deliberately_not_fast_forward;
- git_lrfetch_sane 0, @specs;
+ my $url = access_giturl();
+ git_lrfetch_sane $url, 0, @specs;
my %here;
my @tagpats = debiantags('*',access_nomdistro);
or fail <<END;
.dsc Dgit metadata is in context of distro $dsc_distro
for which we have no configured url;
-.dsc provices hinted url with protocol $proto which is unsafe.
+.dsc provides hinted url with protocol $proto which is unsafe.
(can be overridden by config - consult documentation)
END
$url = $dsc_hint_url;
}
- git_lrfetch_sane 1, @fetch;
+ git_lrfetch_sane $url, 1, @fetch;
return $lrf;
};
return;
}
my $af = ".git/info/attributes";
+ ensuredir '.git/info';
open GAO, "> $af.new" or die $!;
print GAO <<END or die $!;
* dgit-defuse-attrs
$before_fetch_merge->();
foreach my $tsuite (@suites[1..$#suites]) {
+ $tsuite =~ s/^-/$cbasesuite-/;
my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
sub {
@end = ();
}
sub clone ($) {
+ # in multisuite, returns twice!
+ # once in parent after first suite fetched,
+ # and then again in child after everything is finished
my ($dstdir) = @_;
badusage "dry run makes no sense with clone" unless act_local();
printdebug "multi clone after fetch merge\n";
clone_set_head();
clone_finish($dstdir);
- exit 0;
+ return;
}
printdebug "clone main body\n";
} else {
my $v = $i_arch_v->[0];
progress "Checking package changelog for archive version $v ...";
+ my $cd;
eval {
my @xa = ("-f$v", "-t$v");
my $vclogp = parsechangelog @xa;
- my $cv = [ (getfield $vclogp, 'Version'),
- "Version field from dpkg-parsechangelog @xa" ];
+ my $gf = sub {
+ my ($fn) = @_;
+ [ (getfield $vclogp, $fn),
+ "$fn field from dpkg-parsechangelog @xa" ];
+ };
+ my $cv = $gf->('Version');
infopair_cond_equal($i_arch_v, $cv);
+ $cd = $gf->('Distribution');
};
if ($@) {
$@ =~ s/^dgit: //gm;
fail "$@".
"Perhaps debian/changelog does not mention $v ?";
}
+ fail <<END if $cd->[0] =~ m/UNRELEASED/;
+$cd->[1] is $cd->[0]
+Your tree seems to based on earlier (not uploaded) $v.
+END
}
}
responder_send_command("param maint-view $maintviewhead");
}
+ # Perhaps send buildinfo(s) for signing
+ my $changes_files = getfield $changes, 'Files';
+ my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
+ foreach my $bi (@buildinfos) {
+ responder_send_command("param buildinfo-filename $bi");
+ responder_send_file('buildinfo', "$buildproductsdir/$bi");
+ }
+
if (deliberately_not_fast_forward) {
git_for_each_ref(lrfetchrefs, sub {
my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
END
if ($we_are_responder) {
my $dryrunsuffix = act_local() ? "" : ".tmp";
+ my @rfiles = ($dscpath, $changesfile);
+ push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
responder_receive_files('signed-dsc-changes',
- "$dscpath$dryrunsuffix",
- "$changesfile$dryrunsuffix");
+ map { "$_$dryrunsuffix" } @rfiles);
} else {
if (act_local()) {
rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
responder_send_command("complete");
}
+sub pre_clone () {
+ no_local_git_cfg();
+}
sub cmd_clone {
parseopts();
my $dstdir;
}
sub branchsuite () {
- my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
+ my @cmd = (@git, qw(symbolic-ref -q HEAD));
+ my $branch = cmdoutput_errok @cmd;
+ if (!defined $branch) {
+ $?==256 or failedcmd @cmd;
+ return undef;
+ }
if ($branch =~ m#$lbranch_re#o) {
return $1;
} else {
#---------- remote commands' implementation ----------
-sub cmd_remote_push_build_host {
+sub pre_remote_push_build_host {
my ($nrargs) = shift @ARGV;
my (@rargs) = @ARGV[0..$nrargs-1];
@ARGV = @ARGV[$nrargs..$#ARGV];
" but invocation host has $vsnwant"
unless defined $protovsn;
- responder_send_command("dgit-remote-push-ready $protovsn");
changedir $dir;
+}
+sub cmd_remote_push_build_host {
+ responder_send_command("dgit-remote-push-ready $protovsn");
&cmd_push;
}
+sub pre_remote_push_responder { pre_remote_push_build_host(); }
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)
{ no strict qw(refs); &{"${base}_${selector}"}(@args); }
}
+sub pre_rpush () {
+ no_local_git_cfg();
+}
sub cmd_rpush {
my $host = nextarg;
my $dir;
print RI "files-end\n" or die $!;
}
-our ($i_clogp, $i_version, $i_dscfn, $i_changesfn);
+our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
sub i_localname_parsed_changelog {
return "remote-changelog.822";
}
sub i_file_dsc { }
+sub i_localname_buildinfo ($) {
+ my $bi = $i_param{'buildinfo-filename'};
+ defined $bi or badproto \*RO, "buildinfo before filename";
+ defined $i_changesfn or badproto \*RO, "buildinfo before changes";
+ $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
+ or badproto \*RO, "improper buildinfo filename";
+ return $&;
+}
+sub i_file_buildinfo {
+ my $bi = $i_param{'buildinfo-filename'};
+ my $bd = parsecontrol "$i_tmp/$bi", $bi;
+ my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
+ if (!forceing [qw(buildinfo-changes-mismatch)]) {
+ files_compare_inputs($bd, $ch);
+ (getfield $bd, $_) eq (getfield $ch, $_) or
+ fail "buildinfo mismatch $_"
+ foreach qw(Source Version);
+ !defined $bd->{$_} or
+ fail "buildinfo contains $_"
+ foreach qw(Changes Changed-by Distribution);
+ }
+ push @i_buildinfos, $bi;
+ delete $i_param{'buildinfo-filename'};
+}
+
sub i_localname_changes {
defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
$i_changesfn = $i_dscfn;
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);
+ return ($i_dscfn, $i_changesfn, @i_buildinfos);
}
#---------- building etc. ----------
# a list of unrepresentable changes (removals of upstream files
# (as messages)
local $/=undef;
- my @cmd = (@git, qw(diff-tree -z));
+ my @cmd = (@git, qw(diff-tree -z --no-renames));
push @cmd, qw(--name-only) unless $unrepres;
push @cmd, qw(-r) if $finegrained || $unrepres;
push @cmd, $x, $y;
if ($unrepres) {
eval {
- die "not a plain file\n"
- unless $newmode =~ m/^10\d{4}$/ ||
- $oldmode =~ m/^10\d{4}$/;
+ die "not a plain file or symlink\n"
+ unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
+ $oldmode =~ m/^(?:10|12)\d{4}$/;
if ($oldmode =~ m/[^0]/ &&
$newmode =~ m/[^0]/) {
- die "mode changed\n" if $oldmode ne $newmode;
+ # both old and new files exist
+ die "mode or type changed\n" if $oldmode ne $newmode;
+ die "modified symlink\n" unless $newmode =~ m/^10/;
+ } elsif ($oldmode =~ m/[^0]/) {
+ # deletion
+ die "deletion of symlink\n"
+ unless $oldmode =~ m/^10/;
} else {
- die "non-default mode\n"
- unless $newmode =~ m/^100644$/ ||
- $oldmode =~ m/^100644$/;
+ # creation
+ die "creation with non-default mode\n"
+ unless $newmode =~ m/^100644$/ or
+ $newmode =~ m/^120000$/;
}
};
if ($@) {
die "contains unexpected slashes\n" if m{//} || m{/$};
die "contains leading punctuation\n" if m{^\W} || m{/\W};
die "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
+ die "is series file\n" if m{$series_filename_re}o;
die "too long" if length > 200;
};
return $_ unless $@;
$patchname =~ y/-a-z0-9_.+=~//cd;
$patchname =~ s/^\W/x-$&/;
$patchname = substr($patchname,0,40);
+ $patchname .= ".patch";
}
if (!defined $patchdir) {
$patchdir = '';
foreach my $fi (@dfi) {
my $f = $fi->{Filename};
my $here = "../$f";
- next if lstat $here;
+ if (lstat $here) {
+ next if stat $here;
+ fail "lstat $here works but stat gives $! !";
+ }
fail "stat $here: $!" unless $! == ENOENT;
my $there = $dscfn;
if ($dscfn =~ m#^(?:\./+)?\.\./+#) {
fail "cannot import $dscfn which seems to be inside working tree!";
}
$there =~ s#/+[^/]+$## or
- fail "cannot import $dscfn which seems to not have a basename";
+ fail "import $dscfn requires ../$f, but it does not exist";
$there .= "/$f";
+ my $test = $there =~ m{^/} ? $there : "../$there";
+ stat $test or fail "import $dscfn requires $test, but: $!";
symlink $there, $here or fail "symlink $there to $here: $!";
progress "made symlink $here -> $there";
# print STDERR Dumper($fi);
"results are in in git ref $dstbranch";
}
+sub pre_archive_api_query () {
+ no_local_git_cfg();
+}
sub cmd_archive_api_query {
badusage "need only 1 subpath argument" unless @ARGV==1;
my ($subpath) = @ARGV;
exec @cmd or fail "exec curl: $!\n";
}
+sub repos_server_url () {
+ $package = '_dgit-repos-server';
+ local $access_forpush = 1;
+ local $isuite = 'DGIT-REPOS-SERVER';
+ my $url = access_giturl();
+}
+
+sub pre_clone_dgit_repos_server () {
+ no_local_git_cfg();
+}
sub cmd_clone_dgit_repos_server {
badusage "need destination argument" unless @ARGV==1;
my ($destdir) = @ARGV;
- $package = '_dgit-repos-server';
- local $access_forpush = 0;
- my @cmd = (@git, qw(clone), access_giturl(), $destdir);
+ my $url = repos_server_url();
+ my @cmd = (@git, qw(clone), $url, $destdir);
debugcmd ">",@cmd;
exec @cmd or fail "exec git clone: $!\n";
}
+sub pre_print_dgit_repos_server_source_url () {
+ no_local_git_cfg();
+}
sub cmd_print_dgit_repos_server_source_url {
badusage "no arguments allowed to dgit print-dgit-repos-server-source-url"
if @ARGV;
- $package = '_dgit-repos-server';
- local $access_forpush = 0;
- my $url = access_giturl();
+ my $url = repos_server_url();
print $url, "\n" or die $!;
}
sub cmd_setup_mergechangelogs {
badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
+ local $isuite = 'DGIT-SETUP-TREE';
setup_mergechangelogs(1);
}
sub cmd_setup_useremail {
badusage "no arguments allowed to dgit setup-useremail" if @ARGV;
+ local $isuite = 'DGIT-SETUP-TREE';
setup_useremail(1);
}
sub cmd_setup_gitattributes {
badusage "no arguments allowed to dgit setup-useremail" if @ARGV;
+ local $isuite = 'DGIT-SETUP-TREE';
setup_gitattrs(1);
}
sub cmd_setup_new_tree {
badusage "no arguments allowed to dgit setup-tree" if @ARGV;
+ local $isuite = 'DGIT-SETUP-TREE';
setup_new_tree();
}
parseopts();
check_env_sanity();
-git_slurp_config();
print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
my $pre_fn = ${*::}{"pre_$cmd"};
$pre_fn->() if $pre_fn;
+git_slurp_config();
+
my $fn = ${*::}{"cmd_$cmd"};
$fn or badusage "unknown operation $cmd";
$fn->();