chiark / gitweb /
wip
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 22 Jan 2012 15:28:46 +0000 (15:28 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 22 Jan 2012 15:28:46 +0000 (15:28 +0000)
FORMAT
Topbloke.pm
tb-create.pl
topbloke-merge-driver

diff --git a/FORMAT b/FORMAT
index 258e073..447a3fb 100644 (file)
--- a/FORMAT
+++ b/FORMAT
@@ -7,6 +7,9 @@ Topbloke branch is:
 
 In-tree, there are metadata files in .topbloke
 
+    Files which are per-branch and do not inherit any contents
+    or changes from dependencies:
+
        msg             brach "commit message"
 
        deps            direct dependencies, one per line
@@ -14,15 +17,23 @@ In-tree, there are metadata files in .topbloke
                                topbloke branch name
                                /refs/heads/<something>
 
-       included        actual included branches, one per line
-                               topbloke branch name
-
        flags           flags that apply to this branch, one per line
-                        unknown flags starting with [a-z] are ok;
+                        Unknown flags starting with [a-z] are ok;
                         otherwise fatal.  Currently defined flags:
                                Deleted         branch is deleted
 
 
+    Files which not inherit contents and changes from dependencies:
+
+       included        actual included branches, one per line
+                               topbloke branch name
+
+       pflags          flags that apply to this branch and all its
+                       dependencies (ie, flags that propagate)
+                        Unknown flags starting with [a-z] are ok;
+                        otherwise fatal.  No currently defined flags
+
+
 <full-name> has the format:
        <email>@<domain.name>/<yyyy>-<mm>-<dd>T<hh><mm><ss>Z/<nickname-path>
 where nickname-path's first component must not start with a digit 
index 2c9abe4..f1c0615 100644 (file)
@@ -25,31 +25,74 @@ sub debug ($) {
     print STDERR "DEBUG: $msg\n" or die $!;
 }
 
-sub run_git_1line {
+sub run_git {
+    # takes optional prefix arguments:
+    #    coderef    hook to call for each line read,
+    #                with $_ containing chomped line; if not supplied,
+    #                output is not read
+    #    scalarref  place to store exit status; if not supplied,
+    #                nonzero exit status is fatal
+    my ($estatusr,$linecallr);
+    while (ref $_[0]) {
+       my $ref = shift @_;
+       if (ref $ref eq 'SCALAR') {
+           $estatusr = $ref;
+       } elsif (ref $ref eq 'CODE') {
+           $linecallr = $ref;
+       } else {
+           die ref($ref)." @_ ?";
+       }
+    }
     open GIT, "-|", 'git', @_ or die $!;
-    my $l = <GIT>;
-    $?=0;
-    close GIT or die "git @_ failed ($?)\n";
-    chomp $l or die "@_ ?";
+    if ($linecallr) {
+       while (<GIT>) {
+           chomp or die "$_ ?";
+           $linecallr->();
+       }
+       GIT->eof or die $!;
+    }
+    if (!close GIT) {
+       die "git @_ $!" if $!;
+       die unless $?;
+       die "git @_ ($?)" unless $estatusr;
+       $$estatusr = $?;
+    } else {
+       $$estatusr = 0 if $estatusr;
+    }
+}
+
+sub run_git_1line {
+    my $l;
+    run_git(sub { $l = $_; }, @_);
+    die "git @_ ?" unless defined $l;
     return $l;
 }
 
-sub run_git_1line_estatus {
-    open GIT, "-|", 'git', @_ or die $!;
-    my $l = <GIT>;
-    $?=0;
-    if (close GIT) {
-       chomp $l or die "@_ ?";
-       return (0,$l);
-    } else {
-       die unless $?;
-       return ($?,undef);
-    }
+sub run_git_check_nooutput {
+    my ($what) = shift @_;
+    run_git(sub { die "$what $_\n"; }, @_);
 }
 
-sub run_git_nooutput {
-    my $rc = system('git', @_);
-    die "git @_ failed ($rc)" if $rc;
+sub run_git_test_anyoutput {
+    my $any = 0;
+    run_git(sub { $any=1; }, @_);
+    return $any;
+}
+
+sub git_config ($$) {
+    my ($cfgvar, $default) = @_;
+    my ($l, $estatus);
+    run_git(\$estatus, sub { 
+       die if defined $l; 
+       $l = $_; },
+           qw(config), $cfgvar);
+    if (defined $l) {
+       die "$cfgvar ($estatus)" if $estatus;
+       return $l;
+    } else {
+       die "$cfgvar ($estatus)" unless $estatus==0 || $estatus==256;
+       return $default;
+    }
 }
 
 sub git_dir () {
@@ -148,22 +191,23 @@ sub parse_branch_spec ($) {
 }
 
 sub setup_config () {
-    my (@files) = (qw(msg deps included flags));
+    my (@files) = (qw(msg deps included flags pflags));
     my $version = 1;
     foreach my $iteration (qw(0 1)) {
        foreach my $file (@files) {
            my $cfgname = "merge.topbloke-$file";
-           my ($current_estatus, $current) =
-               run_git_1line_estatus(qw(config), "$cfgname.driver");
+           my $current_estatus;
+           my $current = run_git_1line(\$current_estatus,
+                                       qw(config), "$cfgname.driver");
            $current = "## failed $current_estatus" if $current_estatus;
            next if $current =~ m/^topbloke-merge-driver --v$version /o;
            die "$file $current ?" if $iteration;
            debug("setting merge driver $file");
-           run_git_nooutput(qw(config), "$cfgname.name",
-                            "topbloke merge driver for $file");
-           run_git_nooutput(qw(config), "$cfgname.driver",
-                            "topbloke-merge-driver --v$version".
-                            " $file %O %A %B %L");
+           run_git(qw(config), "$cfgname.name",
+                   "topbloke merge driver for $file");
+           run_git(qw(config), "$cfgname.driver",
+                   "topbloke-merge-driver --v$version".
+                   " $file %O %A %B %L");
        }
        my ($newattrs, $attrsfile);
        foreach my $file (@files) {
@@ -198,16 +242,12 @@ sub setup_config () {
 }
 
 sub check_no_unwanted_metadata ($) {
+    # for checking foreign branches aren't contaminated
     my ($gitbranch) = @_;
-    open GIT, "-|", 'git', qw(ls-tree --name-status),
-        "$gitbranch:", qw(.topbloke/included .topbloke/flags)
-           or die $!;
-    while (<GIT>) {
-       chomp or die;
-       die "foreign unexpectedly contains $_\n";
-    }
-    GIT->error and die $!;
-    close GIT or die $!;
+    run_git_check_nooutput('foreign unexpectedly contains',
+                          qw(ls-tree --name-only),
+                          "$gitbranch:",
+                          qw(.topbloke));
 }
 
 1;
index 79d4274..591a72e 100755 (executable)
@@ -44,6 +44,13 @@ length($spec->{Date})==18 or die "partial date specified, not supported\n";
 check_no_unwanted_metadata('HEAD')
     if $current->{Kind} ne 'tip';
 
+# For the metadata files in .topbloke, we hope that the user
+# doesn't modify them.  If they do then they get to keep all the pieces.
+#
+# For .topbloke/msg, if it's modified by the user (ie, if working
+# version differs from HEAD) we keep that, and we stage it unless
+# the cached version differs from the HEAD.
+
 my $newbranch = "$spec->{Email}\@$spec->{Domain}/$spec->{Date}/$spec->{Nick}";
 
 $newbranch = run_git_1line(qw(check-ref-format --print), $newbranch);
@@ -52,3 +59,34 @@ printf "creating %s\n", $newbranch;
 
 setup_config();
 
+if (!run_git_test_anyoutput(qw(diff --name-only HEAD -- .topbloke/msg)) {
+    open NM, '>', ".topbloke/msg.tmp" or die $!;
+    my $author = run_git_1line(qw(var GIT_AUTHOR_IDENT));
+    $author =~ s/ \d+ [-+]\d+$//;
+    print NM "From: $author\n" or die $!;
+    foreach my $h (qw(To CC BCC)) {
+       my $estatus;
+       run_git(\$estatus, sub { print NM "$h: $_" or die $!; },
+               qw(config), "topbloke.".lc $h);
+       die "$h $estatus" unless $estatus==0 || $estatus==256;
+    }
+    $subjprefix = git_config('topbloke.subjectprefix', '');
+    print NM <END or die $!;
+Subject: [${subprefix}PATCH] $spec->{Nick}
+
+<patch description>
+
+Signed-off-by: $author
+END
+    run_git(qw(add .topbloke/msg));
+    print " created and staged new .topbloke/msg\n";
+} else {
+    if (!run_git_test_anyoutput(qw(diff --cached --name-only HEAD --
+                                  .topblokemsg))) {
+       print " staged your modified .topbloke/msg\n";
+       run_git(qw(add .topbloke/msg));
+    } else {
+       print " left your (partially staged?) .topbloke/msg\n";
+    }
+}
+
index c90680e..72c749d 100755 (executable)
@@ -4,8 +4,9 @@ set -e
 fail () { echo >&2 "$0: $*"; exit 127; }
 
 case "$1" in
---v1) on=tip ;;
---base-v1) on=base ;;
+--v1) how=tip ;;
+--v1-base) how=base ;;
+--v1-dep) how=dep ;;
 *) fail "bad usage" ;;
 esac
 
@@ -16,21 +17,24 @@ other=$4
 markersize=$5
 
 case $on.$whichfile in
-tip.msg)
-       exec git-merge-file --marker-size=$markersize \
-               "$current" "$ancestor" "$other"
+dep.msg|dep.deps|dep.flags)
+       touch "$current"
+       exit 0
        ;;
 base.msg|base.deps)
        echo '# not applicable' >"$current"
        exit 0
        ;;
-*.included|tip.deps|tip.flags)
-       exec topbloke-merge-lists "$current" "$ancestor" "$other"
-       ;;
 base.flags)
-       exec topbloke-merge-lists -UDeleted \
+       exec topbloke-merge-lists -UDeleted "$current" "$ancestor" "$other"
+       ;;
+tip.msg)
+       exec git-merge-file --marker-size=$markersize \
                "$current" "$ancestor" "$other"
        ;;
+tip.deps|tip.flags|*.included|*.pflags)
+       exec topbloke-merge-lists "$current" "$ancestor" "$other"
+       ;;
 *)
        fail "huh $on $whichfile ?"
        ;;