summaryrefslogtreecommitdiff
path: root/bin/git-push-all
blob: 99e7a88c141ffe6cf2d2cd97066bc9b041b37b76 (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
#!/usr/bin/perl

# git-push-all -- intelligently push most branches

# Copyright (C) 2016, 2019 Sean Whitton
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Prerequisites:

# The Git::Wrapper, Array::Utils, Config::GitLike, and List::MoreUtils
# perl libraries.  On a Debian system,
#     apt-get install libgit-wrapper-perl libconfig-gitlike-perl \
#         liblist-moreutils-perl libarray-utils-perl

# Description:

# This script will try to push all your branches to the places they
# should be pushed, with --follow-tags.  Specifically, for each branch,
#
# 1. If branch.pushRemote is set, push it there
#
# 2. Otherwise, if remote.pushDefault is set, push it there
#
# 3. Otherwise, if it is tracking a remote branch, push it there
#
# 4. Otherwise, exit non-zero.
#
# If a branch is tracking a remote that you cannot push to, be sure to
# set at least one of branch.pushRemote and remote.pushDefault.

# TODO --confirm to show what will be pushed to where and ask for confirmation  'branch -> remote#target'

use strict;
use warnings;
no warnings "experimental::smartmatch";

use Array::Utils qw{ array_minus };
use Git::Wrapper;
use Config::GitLike;
use List::MoreUtils qw{ uniq apply };

my $git = Git::Wrapper->new(".");
my $config = Config::GitLike->new( confname => 'config' );
$config->load_file('.git/config');

my @branches = apply { s/[ \*]//g } $git->branch;
my @allBranches = apply { s/[ \*]//g } $git->branch({ all => 1 });
my $pushDefault = $config->get( key => "remote.pushDefault" );

my %pushes;

foreach my $branch ( @branches ) {
    my $pushRemote = $config->get( key => "branch.$branch.pushRemote" );
    my $tracking = $config->get( key => "branch.$branch.remote" );

    if ( defined $pushRemote ) {
        # print "I: pushing $branch to $pushRemote (its pushRemote)\n";
        push @{ $pushes{$pushRemote} }, $branch;
    # don't push unless it already exists on the remote: this script
    # avoids creating branches
    } elsif ( defined $pushDefault
              && "remotes/$pushDefault/$branch" ~~ @allBranches ) {
        # print "I: pushing $branch to $pushDefault (the remote.pushDefault)\n";
        push @{ $pushes{$pushDefault} }, $branch;
    } elsif ( !defined $pushDefault && defined $tracking
              && "remotes/$tracking/$branch" ~~ @allBranches ) {
        # print "I: pushing $branch to $tracking (probably to its tracking branch)\n";
        push @{ $pushes{$tracking} }, $branch;
    } else {
        print "E: couldn't find anywhere to push $branch\n";
        print "I: maybe you want to \`git branchmove\` it to a remote\n";
        exit 1;
    }
}

foreach my $remote ( keys %pushes ) {
    # TODO if $remote eq $pushDefault, consider s/follow-// below (and
    # pushRemote of master branch, if that exists?)
    # I almost certainly want all tags on that remote (e.g. an alioth repo)
    my @branches = @{ $pushes{$remote} };
    if ( "--no-verify" ~~ @ARGV ) {
        system "git push --follow-tags --no-verify $remote @branches";
    } else {
        system "git push --follow-tags $remote @branches";
    }
    exit 1 if ( $? != 0 );
}

# Now find any tags that have not been pushed to any remote.
# --follow-tags should avoid this, but sometimes tags fall through the
# gaps.  It will also catch unannotated tags, since --follow-tags
# ignores those, and I probably don't want them

# TODO if this turns out to be slow, split out into a script run
# weekly as part of ~/bin/sysmaint

# TODO definitely split out because should be run as a safety catch by
# src-unregistered

unless ( "--no-tags" ~~ @ARGV ) {
    my @tags = grep { !(m|archive/debian/\S+|) } $git->tag;
    my @remotes = $git->remote;
    my @pushed_tags;

    foreach my $remote ( @remotes ) {
        unless ( $remote eq "dgit" ) {
            my @this_remote_tags = apply { s|\^\{\}$||; s|[a-f0-9]+\trefs/tags/|| }
              $git->ls_remote( { tags => 1 }, $remote );
            push @pushed_tags, @this_remote_tags;
        }
    }

    @pushed_tags = uniq @pushed_tags;
    my @unpushed_tags = array_minus ( @tags, @pushed_tags );

    if ( scalar @unpushed_tags > 0 ) {
        print "E: the following tags have not been pushed to any remote:\n";
        print join(", ", @unpushed_tags);
        print "\n";
        exit 1;
    }
}