t/Makefile: clean: Delete "tmpdir"
[gitspread.git] / gitspreadd
blobb5d31d0edc571d81aa78ba2129e44b6a31d8c025
1 #!/usr/bin/perl
3 #=======================================================================
4 # gitspreadd
5 # File ID: 6c4d0b82-6111-11e0-8250-e509f64f5c24
7 # Push received commits and tags to other remotes to save bandwith
9 # Character set: UTF-8
10 # ©opyleft 2011– Øyvind A. Holm <sunny@sunbase.org>
11 # License: GNU General Public License version 2 or later, see end of
12 # file for legal stuff.
13 #=======================================================================
15 use strict;
16 use warnings;
17 use Getopt::Long;
19 use Cwd qw{ abs_path };
20 use Fcntl ':flock';
21 use IO::Handle;
22 use POSIX 'setsid';
24 local $| = 1;
26 my %Std = (
28 'repodir' => "$ENV{'HOME'}/Git-spread",
32 our %Opt = (
34 'help' => 0,
35 'quiet' => 0,
36 'repodir' => '',
37 'run-once' => 0,
38 'verbose' => 0,
39 'version' => 0,
43 our $progname = $0;
44 $progname =~ s/^.*\/(.*?)$/$1/;
45 our $VERSION = '0.11.0+git';
47 Getopt::Long::Configure('bundling');
48 GetOptions(
50 'help|h' => \$Opt{'help'},
51 'quiet|q+' => \$Opt{'quiet'},
52 'repodir|r=s' => \$Opt{'repodir'},
53 'run-once|1' => \$Opt{'run-once'},
54 'verbose|v+' => \$Opt{'verbose'},
55 'version' => \$Opt{'version'},
57 ) || die("$progname: Option error. Use -h for help.\n");
59 $Opt{'verbose'} -= $Opt{'quiet'};
60 $Opt{'help'} && usage(0);
61 if ($Opt{'version'}) {
62 print_version();
63 exit(0);
66 my $CMD_GIT = defined($ENV{'GITSPREAD_GIT'}) ? $ENV{'GITSPREAD_GIT'} : 'git';
67 my $repodir = abs_path($Std{'repodir'});
68 defined($ENV{'GITSPREAD_REPODIR'}) &&
69 length($ENV{'GITSPREAD_REPODIR'}) &&
70 ($repodir = abs_path($ENV{'GITSPREAD_REPODIR'}));
71 length($Opt{'repodir'}) && ($repodir = abs_path($Opt{'repodir'}));
73 my $spooldir = abs_path("$repodir/spool");
74 my $logfile = abs_path("$repodir/$progname.log");
75 my $pidfile = abs_path("$repodir/pid");
76 my $stopfile = abs_path("$repodir/stop");
77 my $logfh;
79 exit(main(%Opt));
81 sub main {
82 # {{{
83 my %Opt = @_;
84 my $Retval = 0;
86 if (! -d "$repodir/.") {
87 warn("$progname: $repodir: Missing repository top directory\n");
88 exit(1);
91 if (! -d "$spooldir/.") {
92 mkdir($spooldir) or die("$progname: $spooldir: Cannot create spool directory: $!\n");
95 open($logfh, '>>', $logfile)
96 or die("$progname: $logfile: Cannot open logfile: $!\n");
97 $logfh->autoflush(1);
98 flock($logfh, LOCK_EX | LOCK_NB)
99 or die("$progname: $logfile: Cannot lock file: $!.\n" .
100 "Is $progname already running?\n");
102 my $do_loop = $Opt{'run-once'} ? 0 : 1;
104 defined(my $pid = fork) or die("$progname: Cannot fork: $!");
106 if ($pid) {
107 my $start_str = "Starting $progname $VERSION, PID = $pid";
108 print("$start_str\n");
109 logmsg($start_str);
110 if (open(my $pidfh, '>', $pidfile)) {
111 print($pidfh "$pid\n");
112 close($pidfh);
116 exit if $pid;
117 setsid or die("$progname: Cannot start a new session: $!\n");
119 do {
120 if (-e $stopfile) {
121 logmsg("$stopfile exists, terminating properly");
122 unlink($pidfile) || logmsg("WARNING: Cannot remove $pidfile: $!");
123 unlink($stopfile) || logmsg("WARNING: Cannot remove $stopfile: $!");
124 exit(0);
126 chdir($spooldir) || (logmsg("$spooldir: Cannot chdir to spool directory: $!. Aborting."), exit(1));
127 my @repos = glob('*');
128 if (scalar @repos) {
129 logmsg('Found new commits: ' . join(' ', @repos));
130 for my $repo (@repos) {
131 process_repo($repo);
134 sleep(1);
135 } while($do_loop);
137 return($Retval);
138 # }}}
139 } # main()
141 sub process_repo {
142 # {{{
143 my $repo = shift;
144 logmsg("Processing $repo");
145 my $repogit = "$repodir/$repo.git";
146 unless (chdir($repogit)) {
147 logmsg("$repogit: Cannot chdir: $!. Ignoring repository.");
148 goto endfunc;
150 my @deleted_branches = ();
151 if (open(my $fh, '<', "$spooldir/$repo")) {
152 while (<$fh>) {
153 chomp(my $line = $_);
154 if ($line =~ /^[0-9a-f]{40} 0{40} refs\/heads\/(.*)$/) {
155 my $deleted_branch = $1;
156 push(@deleted_branches, $deleted_branch);
159 close($fh);
160 } else {
161 logmsg("$spooldir/$repo: Cannot open file for read: $!. Ignoring repository $repo.");
162 goto endfunc;
165 my $force = git_config_value('gitspread.forcepush');
166 if ($force !~ /^(|false|true)$/) {
167 logmsg("WARNING: $repo: gitspread.forcepush contains invalid value \"$force\". " .
168 'Using "false".');
169 $force = 'false';
171 my $force_opt = $force eq 'true' ? '-f' : '';
172 for my $remote (git_remotes()) {
173 if (scalar(@deleted_branches)) {
174 for my $branch (@deleted_branches) {
175 exec_command($CMD_GIT, 'push', $remote, ":$branch");
178 exec_command($CMD_GIT, 'push', $force_opt, '--all', $remote);
179 exec_command($CMD_GIT, 'push', $force_opt, '--tags', $remote);
182 endfunc:
183 unlink("$spooldir/$repo") || logmsg("WARNING: $spooldir/$repo: Cannot delete file: $!");
184 return;
185 # }}}
186 } # process_repo()
188 sub exec_command {
189 # {{{
190 my @args = @_;
191 logmsg("Executing '" . join(' ', @args) . "'");
192 system(join(' ', @args, ">>$logfile", '2>&1'));
193 return;
194 # }}}
195 } # exec_command()
197 sub git_config_value {
198 # {{{
199 my $option = shift;
200 my $retval = '';
201 if (open(my $pipefh, "$CMD_GIT config --get $option |")) {
202 local $/ = undef;
203 $retval = <$pipefh>;
204 close($pipefh);
205 } else {
206 logmsg("FATAL: git_config_value(): " .
207 "Cannot open \"$CMD_GIT config --get\" pipe: $!. Aborting.");
208 exit(1);
210 chomp($retval);
211 return($retval);
212 # }}}
213 } # git_config_value()
215 sub git_remotes {
216 # {{{
217 my @retval = ();
218 if (open(my $pipefh, "$CMD_GIT remote |")) {
219 while (<$pipefh>) {
220 chomp;
221 length($_) && push(@retval, $_);
223 close($pipefh) or
224 logmsg("WARNING: Could not close \$pipefh in git_remotes(): $!");
225 } else {
226 logmsg("FATAL: git_remotes(): " .
227 "Cannot open \"$CMD_GIT remote\" pipe: $!. Aborting.");
228 exit(1);
230 return(@retval);
231 # }}}
232 } # git_remotes()
234 sub logmsg {
235 # {{{
236 my @txt = @_;
237 my ($sec, $min, $hour, $day, $mon, $year, $wday, $yday, $is_dst) =
238 gmtime(time);
239 my $timestamp = sprintf('%04u-%02u-%02u %02u:%02u:%02uZ',
240 $year+1900, $mon+1, $day, $hour, $min, $sec);
241 print($logfh "$timestamp - ", @txt, "\n");
242 return;
243 # }}}
244 } # logmsg()
246 sub print_version {
247 # Print program version {{{
248 printf("%s$VERSION\n", $Opt{'verbose'} >= 0 ? "$progname " : "");
249 return;
250 # }}}
251 } # print_version()
253 sub usage {
254 # Send the help message to stdout {{{
255 my $Retval = shift;
257 if ($Opt{'verbose'} > 0) {
258 print("\n");
259 print_version();
261 print(<<"END");
263 Usage: $progname [options]
266 if ($Opt{'verbose'} >= 0) {
267 print(<<END);
268 Options:
270 -h, --help
271 Show this help.
272 -r X, --repodir X
273 Use X as directory to store bare repos.
274 Default: "$Std{'repodir'}"
275 -1, --run-once
276 Just pass through the program once instead of looping. Used for
277 testing purposes.
278 -q, --quiet
279 Be more quiet. Can be repeated to increase silence.
280 -v, --verbose
281 Increase level of verbosity. Can be repeated.
282 --version
283 Print version information. "Semantic versioning" is used, described
284 at <http://semver.org>.
286 To use an alternative version of git, set the \$GITSPREAD_GIT
287 environment variable to the git executable to use. For example:
289 export GITSPREAD_GIT=/usr/local/bin/git
290 ./gitspreadd
294 exit($Retval);
295 # }}}
296 } # usage()
298 sub msg {
299 # Print a status message to stderr based on verbosity level {{{
300 my ($verbose_level, $Txt) = @_;
302 if ($Opt{'verbose'} >= $verbose_level) {
303 print(STDERR "$progname: $Txt\n");
305 return;
306 # }}}
307 } # msg()
309 __END__
311 # This program is free software: you can redistribute it and/or modify
312 # it under the terms of the GNU General Public License as published by
313 # the Free Software Foundation, either version 2 of the License, or (at
314 # your option) any later version.
316 # This program is distributed in the hope that it will be useful, but
317 # WITHOUT ANY WARRANTY; without even the implied warranty of
318 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
319 # See the GNU General Public License in the file COPYING for more
320 # details.
322 # You should have received a copy of the GNU General Public License
323 # along with this program.
324 # If not, see L<http://www.gnu.org/licenses/gpl-2.0.txt>.
326 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :