X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=d936acdfecb30da0aea88a5f808d4f594855acde;hp=2056818baa4cb07fa6a4e57b4253151d85cd5c10;hb=e0673a9afdec58cc24eef17bc8c017aa6b30f11b;hpb=3f747fd7e5b6d982d0b8a46532149e20d600a6c7 diff --git a/dgit b/dgit index 2056818b..d936acdf 100755 --- a/dgit +++ b/dgit @@ -48,11 +48,12 @@ our $changesfile; our $buildproductsdir = '..'; our $new_package = 0; our $ignoredirty = 0; -our $noquilt = 0; our $rmonerror = 1; our $existing_package = 'dpkg'; our $cleanmode = 'dpkg-source'; our $changes_since_version; +our $quilt_mode; +our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck'; our $we_are_responder; our $initiator_tempdir; @@ -1412,6 +1413,7 @@ sub clone ($) { } fetch_from_archive() or no_such_package; my $vcsgiturl = $dsc->{'Vcs-Git'}; + $vcsgiturl =~ s/\s+-b\s+\S+//g; if (length $vcsgiturl) { runcmd @git, qw(remote add vcs-git), $vcsgiturl; } @@ -1485,7 +1487,7 @@ sub get_source_format () { sub madformat ($) { my ($format) = @_; return 0 unless $format eq '3.0 (quilt)'; - if ($noquilt) { + if ($quilt_mode eq 'nocheck') { progress "Not doing any fixup of \`$format' due to --no-quilt-fixup"; return 0; } @@ -2037,32 +2039,29 @@ our $version; our $sourcechanges; our $dscfn; +#----- `3.0 (quilt)' handling ----- + our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT'; -sub quiltify ($$) { - my ($clogp,$headref) = @_; +sub quiltify_dpkg_commit ($$$;$) { + my ($patchname,$author,$msg, $xinfo) = @_; + $xinfo //= ''; - my $author = getfield $clogp, 'Maintainer'; - my $time = time; - my $ncommits = 3; - my $patchname = "auto-$version-$headref-$time"; - my $msg = cmdoutput @git, qw(log), "-n$ncommits"; mkpath '.git/dgit'; my $descfn = ".git/dgit/quilt-description.tmp"; open O, '>', $descfn or die "$descfn: $!"; + $msg =~ s/\s+$//g; $msg =~ s/\n/\n /g; $msg =~ s/^\s+$/ ./mg; print O <{Version}) - Last (up to) $ncommits git changes, FYI: - . - $msg +Description: $msg Author: $author - +$xinfo --- END close O or die $!; + { local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0; local $ENV{'VISUAL'} = $ENV{'EDITOR'}; @@ -2071,6 +2070,245 @@ END } } +sub quiltify_trees_differ ($$) { + my ($x,$y) = @_; + # returns 1 iff the two tree objects differ other than in debian/ + local $/=undef; + my @cmd = (@git, qw(diff-tree --name-only -z), $x, $y); + my $diffs= cmdoutput @cmd; + foreach my $f (split /\0/, $diffs) { + next if $f eq 'debian'; + return 1; + } + return 0; +} + +sub quiltify_tree_sentinelfiles ($) { + # lists the `sentinel' files present in the tree + my ($x) = @_; + my $r = cmdoutput @git, qw(ls-tree --name-only), $x, + qw(-- debian/rules debian/control); + $r =~ s/\n/,/g; + return $r; +} + +sub quiltify ($$) { + my ($clogp,$target) = @_; + + # Quilt patchification algorithm + # + # We search backwards through the history of the main tree's HEAD + # (T) looking for a start commit S whose tree object is identical + # to to the patch tip tree (ie the tree corresponding to the + # current dpkg-committed patch series). For these purposes + # `identical' disregards anything in debian/ - this wrinkle is + # necessary because dpkg-source treates debian/ specially. + # + # We can only traverse edges where at most one of the ancestors' + # trees differs (in changes outside in debian/). And we cannot + # handle edges which change .pc/ or debian/patches. To avoid + # going down a rathole we avoid traversing edges which introduce + # debian/rules or debian/control. And we set a limit on the + # number of edges we are willing to look at. + # + # If we succeed, we walk forwards again. For each traversed edge + # PC (with P parent, C child) (starting with P=S and ending with + # C=T) to we do this: + # - git checkout C + # - dpkg-source --commit with a patch name and message derived from C + # After traversing PT, we git commit the changes which + # should be contained within debian/patches. + + changedir '../fake'; + 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: + # $p = { + # Commit => $git_commit_id, + # Child => $c, # or undef if P=T + # Whynot => $reason_edge_PC_unsuitable, # in @nots only + # Nontrivial => true iff $p..$c has relevant changes + # }; + + my @todo; + my @nots; + my $sref_S; + my $max_work=100; + my %considered; # saves being exponential on some weird graphs + + my $t_sentinels = quiltify_tree_sentinelfiles $target; + + my $not = sub { + my ($search,$whynot) = @_; + printdebug " search NOT $search->{Commit} $whynot\n"; + $search->{Whynot} = $whynot; + push @nots, $search; + no warnings qw(exiting); + next; + }; + + push @todo, { + Commit => $target, + }; + + while (@todo) { + my $c = shift @todo; + next if $considered{$c->{Commit}}++; + + $not->($c, "maximum search space exceeded") if --$max_work <= 0; + + printdebug "quiltify investigate $c->{Commit}\n"; + + # are we done? + if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) { + printdebug " search finished hooray!\n"; + $sref_S = $c; + last; + } + + if ($quilt_mode eq 'nofix') { + fail "quilt fixup required but quilt mode is \`nofix'\n". + "HEAD commit $c->{Commit} differs from tree implied by ". + " debian/patches (tree object $oldtiptree)"; + } + if ($quilt_mode eq 'smash') { + printdebug " search quitting smash\n"; + last; + } + + my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit}; + $not->($c, "has $c_sentinels not $t_sentinels") + if $c_sentinels ne $t_sentinels; + + my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit}; + $commitdata =~ m/\n\n/; + $commitdata =~ $`; + my @parents = ($commitdata =~ m/^parent (\w+)$/gm); + @parents = map { { Commit => $_, Child => $c } } @parents; + + $not->($c, "root commit") if !@parents; + + foreach my $p (@parents) { + $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit}; + } + my $ndiffers = grep { $_->{Nontrivial} } @parents; + $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1; + + foreach my $p (@parents) { + printdebug "considering C=$c->{Commit} P=$p->{Commit}\n"; + + my @cmd= (@git, qw(diff-tree -r --name-only), + $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc)); + my $patchstackchange = cmdoutput @cmd; + if (length $patchstackchange) { + $patchstackchange =~ s/\n/,/g; + $not->($p, "changed $patchstackchange"); + } + + printdebug " search queue P=$p->{Commit} ", + ($p->{Nontrivial} ? "NT" : "triv"),"\n"; + push @todo, $p; + } + } + + if (!$sref_S) { + printdebug "quiltify want to smash\n"; + + my $abbrev = sub { + my $x = $_[0]{Commit}; + $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/; + return $; + }; + my $reportnot = sub { + my ($notp) = @_; + my $s = $abbrev->($notp); + my $c = $notp->{Child}; + $s .= "..".$abbrev->($c) if $c; + $s .= ": ".$c->{Whynot}; + return $s; + }; + if ($quilt_mode eq 'linear') { + print STDERR "$us: quilt fixup cannot be linear. Stopped at:\n"; + foreach my $notp (@nots) { + print STDERR "$us: ", $reportnot->($notp), "\n"; + } + fail "quilt fixup naive history linearisation failed.\n". + "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch"; + } elsif ($quilt_mode eq 'smash') { + } elsif ($quilt_mode eq 'auto') { + progress "quilt fixup cannot be linear, smashing..."; + } else { + die "$quilt_mode ?"; + } + + my $time = time; + my $ncommits = 3; + my $msg = cmdoutput @git, qw(log), "-n$ncommits"; + + quiltify_dpkg_commit "auto-$version-$target-$time", + (getfield $clogp, 'Maintainer'), + "Automatically generated patch ($clogp->{Version})\n". + "Last (up to) $ncommits git changes, FYI:\n\n". $msg; + return; + } + + progress "quiltify linearisation planning successful, executing..."; + + for (my $p = $sref_S; + my $c = $p->{Child}; + $p = $p->{Child}) { + printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n"; + next unless $p->{Nontrivial}; + + my $cc = $c->{Commit}; + + my $commitdata = cmdoutput @git, qw(cat-file commit), $cc; + $commitdata =~ m/\n\n/ or die "$c ?"; + $commitdata = $`; + my $msg = $'; #'; + $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?"; + my $author = $1; + + $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?"; + + my $title = $1; + my $patchname = $title; + $patchname =~ s/[.:]$//; + $patchname =~ y/ A-Z/-a-z/; + $patchname =~ y/-a-z0-9_.+=~//cd; + $patchname =~ s/^\W/x-$&/; + $patchname = substr($patchname,0,40); + my $index; + for ($index=''; + stat "debian/patches/$patchname$index"; + $index++) { } + $!==ENOENT or die "$patchname$index $!"; + + runcmd @git, qw(checkout -q), $cc; + + # We use the tip's changelog so that dpkg-source doesn't + # produce complaining messages from dpkg-parsechangelog. None + # of the information dpkg-source gets from the changelog is + # actually relevant - it gets put into the original message + # which dpkg-source provides our stunt editor, and then + # overwritten. + runcmd @git, qw(checkout -q), $target, qw(debian/changelog); + + quiltify_dpkg_commit "$patchname$index", $author, $msg, + "X-Dgit-Generated: $clogp->{Version} $cc\n"; + + runcmd @git, qw(checkout -q), $cc, qw(debian/changelog); + } + + runcmd @git, qw(checkout -q master); +} + sub build_maybe_quilt_fixup () { my $format=get_source_format; return unless madformat $format; @@ -2227,6 +2465,8 @@ sub quilt_fixup_editor () { exit 0; } +#----- other building ----- + sub clean_tree () { if ($cleanmode eq 'dpkg-source') { runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean); @@ -2433,12 +2673,17 @@ sub parseopts () { $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; } elsif (m/^--no-quilt-fixup$/s) { push @ropts, $_; - $noquilt = 1; + $quilt_mode = 'nocheck'; } elsif (m/^--no-rm-on-error$/s) { push @ropts, $_; $rmonerror = 0; @@ -2516,6 +2761,15 @@ if (!@ARGV) { my $cmd = shift @ARGV; $cmd =~ y/-/_/; +if (!defined $quilt_mode) { + $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF') + // access_cfg('quilt-mode', 'RETURN-UNDEF') + // 'linear'; + $quilt_mode =~ m/^($quilt_modes_re)$/ + or badcfg "unknown quilt-mode \`$quilt_mode'"; + $quilt_mode = $1; +} + my $fn = ${*::}{"cmd_$cmd"}; $fn or badusage "unknown operation $cmd"; $fn->();