summaryrefslogtreecommitdiffstats
path: root/src/commands/mirror
blob: b22ec2aa49f1692432e8635f586dbcb77b496dc7 (plain)
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
#!/usr/bin/perl
use strict;
use warnings;

my $tid;

BEGIN {
    $tid = $ENV{GL_TID} || 0;
    delete $ENV{GL_TID};
}

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;

=for usage
Usage 1:    gitolite mirror push <copy> <repo>
            gitolite mirror status <copy> <repo>
            gitolite mirror status all <repo>
            gitolite mirror status all all
Usage 2:    ssh git@master-server mirror push <copy> <repo>
            ssh git@master-server mirror status <copy> <repo>

Forces a push of one repo to one copy.

Usage 1 is directly on the master server.  Nothing is checked; if the copy
accepts it, the push happens, even if the copy is not in any copies
option.  This is how you do delayed or lagged pushes to servers that do not
need real-time updates or have bandwidth/connectivity issues.

Usage 2 can be initiated by *any* user who has *any* gitolite access to the
master server, but it checks that the copy is in one of the copies options
before doing the push.

MIRROR STATUS: The usage examples above show what can be done.  The 'status
all <repo>' usage checks the status of all the copies defined for the given
repo.  The 'status all all' usage is special, in that it only prints a list of
repos that have *some* error, instead of dumping all the error info itself.

SERVER LIST: 'gitolite mirror list master <reponame>' and 'gitolite mirror
list copies <reponame>' will show you the name of the master server, and list
the copy servers, for the repo.  They only work on the server command line
(any server), but not remotely (from a normal user).
=cut

usage() if not @ARGV or $ARGV[0] eq '-h';

_die "HOSTNAME not set" if not $rc{HOSTNAME};

my ( $cmd, $host, $repo ) = @ARGV;
$host = 'copies' if $host eq 'slaves';
$repo =~ s/\.git$//;
usage() if not $repo;

if ( $cmd eq 'push' ) {
    valid_copy( $host, $repo ) if exists $ENV{GL_USER};
    # will die if host not in copies for repo

    trace( 1, "TID=$tid host=$host repo=$repo", "gitolite mirror push started" );
    _chdir( $rc{GL_REPO_BASE} );
    _chdir("$repo.git");

    if ( -f "gl-creator" ) {
        # try to propagate the wild repo, including creator name and gl-perms
        my $creator = `cat gl-creator`; chomp($creator);
        trace( 1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\' 2>/dev/null` );
    }

    my $errors = 0;
    my $glss = '';
    for (`git push --mirror $host:$repo 2>&1`) {
        $errors = 1 if $?;
        print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
        $glss .= $_;
        chomp;
        if (/FATAL/) {
            $errors = 1;
            gl_log( 'mirror', $_ );
        } else {
            trace( 1, "mirror: $_" );
        }
    }
    # save the mirror push status for this copy if the word 'fatal' is found,
    # else remove the status file.  We don't store "success" output messages;
    # you can always get those from the log files if you really need them.
    if ( $glss =~ /fatal/i ) {
        my $glss_prefix = Gitolite::Common::gen_ts() . "\t$ENV{GL_TID}\t";
        $glss =~ s/^/$glss_prefix/gm;
        _print("gl-copy-$host.status", $glss);
    } else {
        unlink "gl-copy-$host.status";
    }

    exit $errors;
} elsif ($cmd eq 'status') {
    if (not exists $ENV{GL_USER} and $repo eq 'all') {
        # this means 'gitolite mirror status all all'; in this case we only
        # return a list of repos that *have* status files (indicating some
        # problem).  It's upto you what you do with that list.  This is not
        # allowed to be run remotely; far too wide ranging, sorry.
        _chdir( $rc{GL_REPO_BASE} );
        my $phy_repos = list_phy_repos(1);
        for my $repo ( @{$phy_repos} ) {
            my @x = glob("$rc{GL_REPO_BASE}/$repo.git/gl-copy-*.status");
            print "$repo\n" if @x;
        }
        exit 0;
    }

    valid_copy( $host, $repo ) if exists $ENV{GL_USER};
    # will die if host not in copies for repo

    _chdir( $rc{GL_REPO_BASE} );
    _chdir("$repo.git");

    $host = '*' if $host eq 'all';
    map { print_status($repo, $_) } sort glob("gl-copy-$host.status");
} else {
    # strictly speaking, we could allow some of the possible commands remotely
    # also, at least for admins.  However, these commands are mainly intended
    # for server-side scripting so I don't care.
    usage() if $ENV{GL_USER};

    server_side_commands(@ARGV);
}

# ----------------------------------------------------------------------

sub valid_copy {
    my ( $host, $repo ) = @_;
    _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;

    my %list = repo_copies($repo);
    _die "'$host' not a valid copy for '$repo'" unless $list{$host};
}

sub repo_copies {
    my $repo = shift;

    my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies.*" );
    my %list = map { $_ => 1 } map { split } values %$ref;

    return %list;
}

sub repo_master {
    my $repo = shift;

    my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.master\$" );
    my @list = map { split } values %$ref;
    _die "'$repo' seems to have more than one master" if @list > 1;

    return $list[0] || '';
}

sub print_status {
    my $repo = shift;
    my $file = shift;
    return unless -f $file;
    my $copy = $1 if $file =~ /^gl-copy-(.+)\.status$/;
    print "----------\n";
    print "WARNING: previous mirror push of repo '$repo' to host '$copy' failed, status is:\n";
    print slurp($file);
    print "----------\n";
}

# ----------------------------------------------------------------------
# server side commands.  Very little error checking.
#   gitolite mirror list master <repo>
#   gitolite mirror list copies <repo>

sub server_side_commands {
    if ( $cmd eq 'list' ) {
        if ( $host eq 'master' ) {
            say repo_master($repo);
        } elsif ( $host eq 'copies' ) {
            my %list = repo_copies($repo);
            say join( " ", sort keys %list );
        } else {
            _die "gitolite mirror list master|copies <reponame>";
        }
    } else {
        _die "invalid command";
    }
}