All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] repost: add git-nntp-post for posting git commit diffs as news messages
@ 2005-12-13 19:55 Artem Khodush
  0 siblings, 0 replies; only message in thread
From: Artem Khodush @ 2005-12-13 19:55 UTC (permalink / raw)
  To: git

Sorry for posting again, the previous post has broken long lines in the patch.

In case anyone is still interested, here is a script
that exports git repository to a nntp server,
as prompted in http://marc.theaimsgroup.com/?l=git&m=113385203614980&w=2
Tested with INN on localhost with a copy of git repository
(newsgroups must be created beforehand, and inn.conf artcutoff:
and expire.ctl /remember/: parameters better set to never).

I'm not sure if I got this one right, though:

> A merge commit would probably become a multipart with usually 2
> attachments (but N attachments for a N-way octopus), showing
> diff from each branch.

Currently for merges, it posts diff with parent[0] as first attachment,
and diff between parent[i] and git-merge-base parent[i] parent[0]
as each next attachment.




Signed-off-by: Artem Khodush <greenkaa@gmail.com>

---

 Makefile           |    3
 git-nntp-post.perl |  492 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 494 insertions(+), 1 deletions(-)
 create mode 100755 git-nntp-post.perl

676443e459c0689a8b1ec6c6d46423c4f1bbac8e
diff --git a/Makefile b/Makefile
index 00521fe..2cb2a07 100644
--- a/Makefile
+++ b/Makefile
@@ -97,7 +97,8 @@ SCRIPT_SH = \
 SCRIPT_PERL = \
  git-archimport.perl git-cvsimport.perl git-relink.perl \
  git-shortlog.perl git-fmt-merge-msg.perl \
- git-svnimport.perl git-mv.perl git-cvsexportcommit.perl
+ git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \
+ git-nntp-post.perl

 SCRIPT_PYTHON = \
  git-merge-recursive.py
diff --git a/git-nntp-post.perl b/git-nntp-post.perl
new file mode 100755
index 0000000..5a23faf
--- /dev/null
+++ b/git-nntp-post.perl
@@ -0,0 +1,492 @@
+#!/usr/bin/perl -w
+#
+# Copyright (C) 2005 Artem Khodush <greenkaa@gmail.com>
+#
+# This program contains parts from gitweb.cgi,
+# (C) 2005, Kay Sievers <kay.sievers@vrfy.org>
+# (C) 2005, Christian Gierke <ch@gierke.de>
+
+# This program is licensed under the GPL v2, or a later version
+
+use warnings;
+use strict;
+use Net::NNTP;
+use HTTP::Date;
+use MIME::Lite;
+#use MIME::Entity; # interchangeable with the above, but slower
+
+BEGIN {
+       if( $^V ge v5.8.0 ) {
+               require Encode; import Encode;
+       }else {
+               no strict "refs";
+               *{"Encode::encode"}=sub { my ($a,$s,$b)=@_; return $s; };
+       }
+}
+
+# Read commits, starting from heads given on the command line,
+# (or all repository heads), and post each commit diff
+# to a newsgroup, starting with earliest commit.
+# Commits from different branches go to different newsgroups,
+# named after the head names.
+# The order of heads on the command line is important:
+# when branching point is encountered, the branch specified
+# earlier is considered to be the trunk, continuing into the past.
+
+# For commits with more than one parent, multipart message
+# is posted with first part containing diff with parent[0], and each
+# other part containing diff between parent[i] and
+# git-merge-base parent[i] parent[0] (thus showing summary change
+# followed by what was merged from each branch)
+
+# Subset of the git-rev-list options (--max-age, --min-age and
+# commits starting with ^) is accepted to limit the number of
+# commits posted.
+# Exactly one (the latest) commit may be posted with --one option.
+
+# By default, it posts to localhost nntp port (119), this may
+# be changed by --nntp-host and --nntp-port.
+
+# The newsgroup names are formed by taking the GIT_DIR
+# without the trailing .git, replacing / with ., and appending
+# the head name. This can be overriden with --newsgroup-prefix.
+
+# Each posted message gets an id of the form
+# <commit.SHA1@kernel.org>, and the nntp server is relied upon
+# to reject duplicate posts, and to keep the posting order intact.
+# Hence, with ordinary news servers, each commit
+# can appear only in the newsgroups it was originally posted
+# (currently only one), unless someone care to implement
+# logic for rewriting posts, or a special nntp server.
+
+sub usage() {
+       print STDERR <<EOT;
+$0 [OPTIONS] [<head> ...] [^<commit> ...]
+--nntp-host <host>
+--nntp-port <port>
+--newsgroup-prefix <prefix>
+--max-age <epoch>
+--min-age <epoch>
+--one
+--print-newsgroup-names
+--print-messages
+EOT
+       exit(1);
+}
+
+my $GIT_DIR = `git rev-parse --git-dir`;
+chop $GIT_DIR;
+exit 1 if $?; # rev-parse would have given "not a git dir" message.
+chomp($GIT_DIR);
+
+my $DOMAIN_ID="kernel.org"; # sufficiently unique string for Path
+ #and Message-Id headers
+
+my $NNTP_HOST="localhost";
+my $NNTP_PORT="119";
+my $NEWSGROUP_PREFIX;
+my $MAX_AGE;
+my $MIN_AGE;
+my $ONLY_ONE;
+my %HEADS;
+my @STOP_COMMITS;
+my $PRINT_NEWSGROUP_NAMES;
+my $PRINT_MESSAGES;
+
+my $HEAD_COUNT=0;
+
+while( my $arg=shift ) {
+       if( $arg eq "-h" || $arg eq "--nntp-host" ) {
+               $NNTP_HOST=shift or usage();
+       }elsif( $arg=~/^--nntp-host=(.+)$/ ) {
+               $NNTP_HOST=$1;
+       }elsif( $arg eq "-p" || $arg eq "--nntp-port" ) {
+               $NNTP_PORT=shift or usage();
+       }elsif( $arg=~/^--nntp-port=(.+)$/ ) {
+               $NNTP_PORT=$1;
+       }elsif( $arg eq "-n" || $arg eq "--newsgroup-prefix" ) {
+               $NEWSGROUP_PREFIX=shift or usage();
+       }elsif( $arg=~/^--newsgroup-prefix=(.+)$/ ) {
+               $NEWSGROUP_PREFIX=$1;
+       }elsif( $arg eq "-a" || $arg eq "--max-age" ) {
+               $MAX_AGE=shift or usage();
+       }elsif( $arg=~/^--max-age=(.+)$/ ) {
+               $MAX_AGE=$1;
+       }elsif( $arg eq "-i" || $arg eq "--min-age" ) {
+               $MIN_AGE=shift or usage();
+       }elsif( $arg=~/^--min-age=(.+)$/ ) {
+               $MIN_AGE=$1;
+       }elsif( $arg eq "-1" || $arg eq "--one" ) {
+               $ONLY_ONE=1;
+       }elsif( $arg eq "--print-newsgroup-names" ) {
+               $PRINT_NEWSGROUP_NAMES=1;
+       }elsif( $arg eq "--print-messages" ) {
+               $PRINT_MESSAGES=1;
+       }elsif( $arg=~/^\^/ ) {
+               push @STOP_COMMITS, $arg;
+       }else {
+               if( "HEAD" eq $arg ) {
+                       (undef,$arg)=split( " ", `git-name-rev HEAD` );
+               }
+               $HEADS{$arg}->{order}=++$HEAD_COUNT;
+       }
+}
+
+&verify_heads;
+&read_commits;
+&propagate_branches;
+&post_commits;
+exit( 0 );
+
+sub verify_heads()
+{
+       my $repo=$GIT_DIR;
+       my $read_all_heads= 0==$HEAD_COUNT;
+       open my $fd, "-|", "git-peek-remote $repo"
+        or die "$0: error running git-peek-remote: $!";
+       while( my $line=<$fd> ) {
+               my ($id,$name)=split ' ', $line;
+               my $is_head=0;
+               if( "HEAD" eq $name ) {
+                       $is_head=1;
+                       (undef,$name)=split( " ", `git-name-rev HEAD` );
+               }
+               if( $name=~s/^refs\/heads\/// || $is_head ) {
+ # if there were heads given on the command line,
+ # take only those. Otherwise, with --one, take only
+ # HEAD, without --one, take all heads.
+                       if( exists( $HEADS{$name} )
+   || ($read_all_heads && ($is_head || !$ONLY_ONE)) ) {
+                               $HEADS{$name}->{id}=$id;
+                       }
+               }
+       }
+       close $fd or die "$0: git_get_type: unable to close fd: $!";
+       if( $read_all_heads ) {
+               #sort them
+               my $n=0;
+               for my $name (sort keys %HEADS) {
+                       $HEADS{$name}->{order}=++$n;
+               }
+               # make master the first
+               $HEADS{"master"}->{order}=0 if exists $HEADS{"master"};
+       }
+       if( $ONLY_ONE && scalar( keys %HEADS )!=1 ) {
+               die "$0: --one requires exactly one head, but "
+        . scalar( keys %HEADS ) . " were given\n";
+       }
+       if( $PRINT_NEWSGROUP_NAMES ) { # just print them
+               for my $head (keys %HEADS) {
+                       print &make_newsgroup_name( $head ) . "\n";
+               }
+               exit( 0 );
+       }
+}
+
+my %COMMITS;
+
+sub read_commits
+{
+       my $args="";
+       $args.=" --max-age $MAX_AGE" if $MAX_AGE;
+       $args.=" " . join( " ", keys %HEADS );
+       $args.=" " . join( " ", @STOP_COMMITS );
+       $args.=" --max-count=1" if $ONLY_ONE;
+       $/ = "\0";
+       open my $fd, "-|", "git-rev-list --header --parents $args"
+        or die "$0: error running git-rev-list: $!";
+       while( my $commit_line=<$fd> ) {
+               $commit_line =~ s/\r$//;
+               my @commit_lines = split '\n', $commit_line;
+               pop @commit_lines;
+               my %co;
+
+               my $header = shift @commit_lines;
+               if (!($header =~ m/^[0-9a-fA-F]{40}/)) {
+                       next;
+               }
+               ($co{'id'}, my @parents) = split ' ', $header;
+               $co{'parents'} = \@parents;
+               while (my $line = shift @commit_lines) {
+                       last if $line eq "\n";
+                       if ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
+                               $co{'author'} = $1;
+                               $co{'author_epoch'} = $2;
+                               $co{'author_tz'} = $3;
+                       }elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
+                               $co{'committer'} = $1;
+                               $co{'committer_epoch'} = $2;
+                               $co{'committer_tz'} = $3;
+                       }
+               }
+               $co{'title'}="";
+               $co{'title_short'}="";
+               foreach my $title (@commit_lines) {
+                       if ($title ne "") {
+                               $title=~s/^\s+//;
+                               $co{'title'}=chop_str($title, 80, 5);
+                               $co{'title_short'}=make_title_short( $title );
+                               last;
+                       }
+               }
+               # remove added spaces
+               foreach my $line (@commit_lines) {
+                       $line =~ s/^    //;
+               }
+               $co{'comment'} = \@commit_lines;
+               $COMMITS{$co{'id'}}=\%co;
+       }
+       close $fd or die "$0: git_read_commit: unable to close fd: $!";
+       $/ = "\n";
+}
+
+sub make_title_short
+{
+ my $title=shift;
+       # remove leading stuff of merges
+       # to make the interesting part visible
+       if (length($title) > 50) {
+        $title =~ s/^Automatic //;
+        $title =~ s/^merge (of|with) /Merge ... /i;
+        if (length($title) > 50) {
+        $title =~ s/(http|rsync):\/\///;
+        }
+        if (length($title) > 50) {
+        $title =~ s/(master|www|rsync)\.//;
+        }
+        if (length($title) > 50) {
+        $title =~ s/kernel.org:?//;
+        }
+        if (length($title) > 50) {
+        $title =~ s/\/pub\/scm//;
+        }
+       }
+ return chop_str($title, 50, 5)
+}
+
+sub propagate_branches
+{
+       for my $head (sort { $HEADS{$a}->{order} - $HEADS{$b}->{order} }
+        keys %HEADS) {
+               my $child_id=undef;
+               my $id=$HEADS{$head}->{id};
+               while( $id && exists $COMMITS{$id}
+            && !exists $COMMITS{$id}->{branch} ) {
+                       my $co=$COMMITS{$id};
+                       $co->{branch}=$head;
+                       $co->{child}=$child_id;
+                       $HEADS{$head}->{start_id}=$id;
+                       $child_id=$id;
+                       $id=$co->{parents}->[0];
+               }
+       }
+}
+
+sub make_message_id
+{
+       my $id=shift;
+       return "<commit.$id\@$DOMAIN_ID>",
+}
+
+sub make_commit_body
+{
+       my ($id,$other_id,$from_branch)=@_;
+       my $body="";
+       my ($author, $author_epoch, $committer, $committer_epoch);
+       my $comment="";
+       if( exists $COMMITS{$id} ) {
+        # if we have it already, spare git-cat-file
+               my $co=$COMMITS{$id};
+               $author=$co->{author};
+        $author_epoch=$co->{author_epoch};
+        $committer=$co->{committer};
+        $committer_epoch=$co->{committer_epoch};
+               $comment=join( "\n", @{$co->{comment}} );
+       }else {
+               open my $fd0, "-|", "git-cat-file commit $id"
+        or die "$0: error running git-cat-file: $!";
+               my $header=1;
+               while( <$fd0> ) {
+                       if( !$header ) {
+                               $comment.=$_;
+                       }elsif( m/^\s+$/ ) {
+                               $header=0;
+                       }elsif( m/^author (.*) ([0-9]+) (.*)$/ ) {
+                               $author=$1;
+                               $author_epoch=$2;
+                       }elsif( m/^committer (.*) ([0-9]+) (.*)$/ ) {
+                               $committer=$1;
+                               $committer_epoch=$2;
+                       }
+               }
+               close $fd0;
+       }
+       if( $from_branch ) {
+               my $merge_base=`git-merge-base $id $other_id`;
+               chop $merge_base;
+               $body.="merged from $id\n\n---\n";
+               $other_id=$merge_base if $merge_base;
+       }else {
+               my ($author_name,$author_email)=split( " ", $author );
+               my ($committer_name,$committer_email)=split( " ", $committer );
+               $body.="committer $committer ".time2str($committer_epoch)."\n"
+        unless $author_name eq $committer_name;
+               $body.=$comment;
+               $body.="\n---\n\n";
+       }
+       if( $other_id ) {
+               open my $fd1, "-|",
+ "git-diff-tree -p $other_id $id | git-apply --stat --summary"
+ or die "$0: error running git-diff-tree: $!";
+               while( <$fd1> ) {
+                       $body.=$_;
+               }
+               close $fd1;
+               $body.="\n";
+               open my $fd2, "-|", "git-diff-tree -p $other_id $id"
+        or die "$0: error running git-diff-tree again: $!";
+               while( <$fd2> ) {
+                       $body.=$_;
+               }
+               close $fd2;
+       }
+       return $body;
+}
+
+sub make_commit_message
+{
+       my ($newsgroup,$co)=@_;
+       my $n_parents=scalar( @{$co->{parents}} );
+       my $parents=join(" ", map( make_message_id( $_ ),  @{$co->{parents}}));
+       my $from=$co->{author};
+       $from=$co->{committer} unless $from;
+       $from="unknown author" unless $from;
+       my $subject=$co->{title_short};
+       $subject="undescribed patch" unless $subject;
+       my $msg=MIME::Lite->new(
+               From=>join( " ", map( Encode::encode( "MIME-Q", $_ ),
+        split( " ", $from ) ) ),
+               Subject=>join( " ", map( Encode::encode( "MIME-Q", $_ ),
+        split( " ", $subject ) ) ),
+               Date=>time2str( $co->{author_epoch} ),
+               "Message-Id"=>make_message_id( $co->{id} ),
+               ($n_parents>0 ? (References=>$parents) : ()),
+
+               ($n_parents>1 ? (Type=>"multipart/mixed")
+                       :(
+                       Type=>"text/plain; charset=utf8",
+                       Encoding=>"quoted-printable",
+                       Data=>make_commit_body( $co->{id},
+ $co->{parents}->[0],
+ 0)
+                       )
+               )
+       );
+       $msg->add( Path=>$DOMAIN_ID );
+       $msg->add( Newsgroups=>$newsgroup );
+       if( $n_parents>1 ) {
+               $msg->attach(
+                       Type=>"text/plain; charset=utf8",
+                       Encoding=>"quoted-printable",
+                       Data=>make_commit_body( $co->{id},
+        $co->{parents}->[0],
+        0 )
+               );
+               for my $n (1..$n_parents-1) {
+                       $msg->attach(
+                               Type=>"text/plain; charset=utf8",
+                               Encoding=>"quoted-printable",
+ Data=>make_commit_body( $co->{parents}->[$n],
+        $co->{parents}->[0],
+        1 )
+                       );
+               }
+       }
+       return $msg;
+}
+
+sub post_commit
+{
+       my ($nntp,$newsgroup,$co)=@_;
+       if( $PRINT_MESSAGES ) { # just print them
+               print make_commit_message( $newsgroup, $co )->as_string();
+               print "\0";
+       }else {
+               if( !$nntp->ihave( make_message_id( $co->{id} ) ) ) {
+                       my $reason=$nntp->message();
+                       unless( $reason=~m/got it/i
+     || $reason=~m/duplicate/i ) {
+                               print STDERR "unexpected responce in attempt "
+        . "to post $co->{id}: $reason"
+                       }
+               }else {
+        my$msg=make_commit_message(  $newsgroup, $co );
+                       $nntp->datasend( $msg->as_string() );
+                       if( !$nntp->dataend() ) {
+                               print STDERR "error posting $co->{id}: "
+        . $nntp->message() . "\n";
+                       }
+               }
+       }
+}
+
+sub make_newsgroup_name
+{
+       my $nhead=shift;
+       $nhead=~s/\/+$//;
+       $nhead=~s/^\/+//;
+       $nhead=~s/\./-/g;
+       $nhead=~s/\//\./g;
+       my $nprefix=$NEWSGROUP_PREFIX;
+       if( !defined( $nprefix ) ) {
+               $nprefix=$GIT_DIR;
+               $nprefix=~s/\/\.git\/?\s*$//;
+               $nprefix=~s/\/+$//;
+               $nprefix=~s/^\/+//;
+               $nprefix=~s/\./-/g;
+               $nprefix=~s/\//\./g;
+       }
+       return "$nprefix.$nhead";
+}
+
+sub post_commits
+{
+       my $nntp;
+       unless( $PRINT_MESSAGES ) {
+               $nntp=Net::NNTP->new( $NNTP_HOST,
+        Port=>$NNTP_PORT,
+ Reader=>0 );
+               die "unable to connect to nntp host "
+        ."${NNTP_HOST}:${NNTP_PORT}"
+        unless $nntp;
+       }
+       while( my ($head,$h)=each %HEADS ) {
+               my $id=$h->{start_id};
+               my $newsgroup_name=make_newsgroup_name( $head );
+               while( $id ) {
+                       my $co=$COMMITS{$id};
+                       $id=$co->{child};
+                       next if defined( $MIN_AGE )
+        && $co->{committer_epoch} > $MIN_AGE;
+                       post_commit( $nntp, $newsgroup_name, $co );
+               }
+       }
+}
+
+sub chop_str {
+       my $str = shift;
+       my $len = shift;
+       my $add_len = shift || 10;
+
+       # allow only $len chars, but don't cut a word if it would fit
+       # in $add_len. If it doesn't fit, cut it if it's still longer
+       # than the dots we would add
+       $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
+       my $body = $1;
+       my $tail = $2;
+       if (length($tail) > 4) {
+               $tail = " ...";
+       }
+       return "$body$tail";
+}
+
--
0.99.9.GIT

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2005-12-13 19:55 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-12-13 19:55 [PATCH] repost: add git-nntp-post for posting git commit diffs as news messages Artem Khodush

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.