aboutsummaryrefslogtreecommitdiff
path: root/hooks/git2psyc
blob: a76678959664006f6b3ca32d2d5f2ff21249f964 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/usr/bin/env perl
#
# PSYC notification script for Git repositories, written by the lynX and improved by tg
# based on cvs2psyc and bartman's sender-side post-commit hook.
#
# it simply uses git log to find out where it stands. for more elaborate
# ways, find inspiration in contrib/hooks/post-receive-email from the git
# distribution.
#
# HOW TO INSTALL:
#
# put git2psyc.conf in the project.git/ directory and
# make a link to this script in the project.git/hooks/post-receive:
#
# cd project.git/hooks
# ln -s /path/to/git2psyc post-receive
#
# or if you want to use a different config file, call it with an argument:
#
# #!/bin/sh
# git2psyc /path/to/git2psyc.conf
#
# HOW TO DEBUG:
# set $debug to 1 in the config then run e.g.:
# echo a4ddfedb23c106fe484831d0e6df6f7f273f7db9 6a9d18cfa5d3334f446ff74710ad7cde5e0026ef refs/heads/master | ./git2psyc git2psyc.conf

use strict;
use warnings;

use Cwd qw/cwd realpath/;
use File::Basename qw/basename/;
use Socket;

chdir '.git' if -d '.git';
my $confname = 'git2psyc.conf';
my $config;
$config = $ARGV[0] if @ARGV && -f $ARGV[0];
$config = $confname if !$config && -f $confname;
$config = "../$confname" unless $config && -f $config;
$config = "../../$confname" unless $config && -f $config;
$config = "../../../$confname" unless $config && -f $config;
die "Config file not found" unless $config && -f $config;

our ($project, $target, $host, $port, $webview, $psycver, $debug);
our $pwd = cwd;
# extract the project name from the path if nothing provided
# this works for either project/ or project.git/ directories
$project = ( $pwd =~ m#/([^/]+)/?\.git\b# ) ? $1 : '-';

do $config;

# GIT handling:
my (@commits, @added, @deleted);

# received refs with old & new hashes
while (<STDIN>) { # <old-value> SP <new-value> SP <ref-name> LF
    print STDERR ">> $_" if $debug;
    next unless m,([0-9a-f]+) ([0-9a-f]+) (\S+),;
    #next if $3 ne 'refs/heads/master'; # only process master branch
    my ($old, $new, $ref) = ($1, $2, $3);

    push @added, $ref if $old =~ /^0+$/;
    push @deleted, $ref if $new =~ /^0+$/;
    next if $old =~ /^0+$/ || $new =~ /^0+$/;

    $_ = `git whatchanged --pretty=fuller --shortstat $old..`;
    print STDERR if $debug;

    # extract commit hashes & msgs from git output
    while (/commit\s+([0-9a-f]+)\n
            Author:\s+(.*?)\ <(.*?)>\n
            AuthorDate:\s+(.*?)\n
            Commit:\s+(.*?)\ <(.*?)>\n
            CommitDate:\s+(.*?)\n
            \n
            \s+(.*?)\n
            .*?
	    ^\ (?:(\d+)\ files?\ changed)?
	       (?:,\ (\d+)\ insertion\S*)?
	       (?:,\ (\d+)\ deletion\S*)?
           /gmsx) {
	# after so many years, somebody broke the output of this git command!
        push @commits, {
            ref => $ref, hash => $1, abbrev_hash => substr($1, 0, 8),
            author => $2, author_email => $3, author_date => $4,
            commit => $5, commit_email => $6, commit_date => $7,
            title => $8, stat_files_changed => $9 || '0', 
	    stat_insertions => $10 || '0', stat_deletions => $11 || '0',
        };
    }
}

if ($debug) {
    print STDERR "\n\nData::Dumper of \@commits, \@added, \@deleted:\n";
    use Data::Dumper;
    print STDERR Dumper \@commits, \@added, \@deleted;
    print STDERR "\n\n.\n";
}

die <<X unless @commits || @added || @deleted;
$0 is meant to be issued from a Git post-receive hook.
X

print <<X;
Delivering notice to $target.
X

# PSYC socket stuff:
my ($delim, $delimre);
if ($psycver >= 1.0) {
    $delim = '|';
    $delimre = qr/\|/;
} else {
    $delim = '.';
    $delimre = qr/\./;
}

if ($target =~ m#^psyc://([\w.-]+)(?::(\d+))?#i) {
    $host ||= $1;
    $port ||= $2 || 4404;
} else {
    die "target invalid: $target";
}

my $iaddr = inet_aton($host) or die "could not resolve host: $host";
my $paddr = sockaddr_in($port, $iaddr);

# there is no real reason why we aren't using UDP here...
socket(S, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or die "socket: $!";

connect(S, $paddr) or die "connect: $!";
select S; $|=1; select STDOUT;

print S <<X;
$delim
X

# wait for greeting
if (defined($_ = <S>) && !/^$delimre/) {
    die "Error while establishing circuit: invalid greeting";
}
# wait for first packet
while (defined($_ = <S>) && !/^$delimre/) {
#	print if s/^=//;
}
#print "\n";

# SEND THE MESSAGES:
# reverse them so they're in chronological order
for my $c (reverse @commits) {
    my $packet = <<X;
:_target\t$target

:_project\t$project
:_ref\t$c->{ref}
:_hash_commit_long\t$c->{hash}
:_hash_commit\t$c->{abbrev_hash}
:_page_commit\t$webview$c->{abbrev_hash}
:_name_editor\t$c->{author}
:_name_committer\t$c->{commit}
:_mailto_editor\t$c->{author_email}
:_mailto_committer\t$c->{commit_email}
:_date_editor\t$c->{author_date}
:_date_committer\t$c->{commit_date}
:_comment\t$c->{title}
:_stat_files_changed\t$c->{stat_files_changed}
:_stat_insertions\t$c->{stat_insertions}
:_stat_deletions\t$c->{stat_deletions}
_notice_update_software_git
[_project]: [_name_editor] "[_comment]" ([_stat_files_changed] files: +[_stat_insertions] -[_stat_deletions]) [_page_commit]
$delim
X
    print S $packet;
    print STDERR $packet if $debug;
    print "  \"$c->{title}\"\n" if $c->{title};
}

sub print_refs {
    my $op = shift;
    my $_list_refs = join '|', @_;
    my @refs;
    for (@_) { s,^refs/,,; push @refs, $_; }
    my $_refs = join ', ', @refs;
    my $packet = <<X;
:_target\t$target

:_project\t$project
:_refs\t$_refs
:_list_refs\t|$_list_refs
_notice_update_software_git_refs_$op
[_project]: $op refs: [_refs]
$delim
X
    print S $packet;
    print STDERR $packet if $debug;
}

print_refs('deleted', @deleted) if @deleted;
print_refs('added', @added) if @added;

print S <<X;
_request_circuit_shutdown
$delim
X
# the _request_circuit_shutdown shouldn't be necessary for a simple
# one-way message submission, but psyced needs to become easier about that

# at this point we should wait for the other side to close the socket.. argl
close (S)	or die "close: $!";
exit;