| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- #!/usr/bin/env perl
- package Git::CloneEntity;
- use strict;
- use warnings;
- use FindBin::libs;
- use Getopt::Long qw{GetOptionsFromArray};
- use Pod::Usage;
- use Pithub;
- use Gogs;
- use Term::ReadKey();
- use IO::Interactive::Tiny();
- =head1 DESCRIPTION
- It is a common pattern in organizations to have their own git resources, but mirror everything public on one of the big platforms with network effect.
- Currently (AD 2024), and for the forseeable future, github.com will be such a platform.
- It is also a common pattern to need to clone basically everything for a given user/org when new development environments are instantiated.
- Alternatively, you may just want to keep your local development environment up to date for said users/projects.
- This program facilitiates cloning your (public) repositories for given users/orgs from either a local gogs/github instance and configuring pushurls for both it and github, or any other github-api compatible mirror(s).
- It will warn you whenever a repository is missing from either, so you can make it go whirr appropriately.
- Using this you can easily migrate an organization from being entirely on github to using private resources or vice versa.
- =head1
- =head1 USAGE
- git clone-entity --user $user1 --user $user2 --org $org1 --org $org2 --alias $user1:$mirror_domain:$mirrorUser1 --baseurl=https://my.local.install/ [--gogs] [--mirror https://github.com] [--help]
- =cut
- sub _help {
- my ($code, $msg) = @_;
- $code //= 0;
- $msg //= "";
- return Pod::Usage::pod2usage( -message => $msg, -exitval => $code);
- }
- my $domainRipper = qr{m/^\w+://([\w|\.]+)};
- sub main {
- my @args = @_;
- my $help;
- my ($users, $orgs, $aliases, $tokens, $mirrors, $baseurl, $gogs, $me) = ([],[],[],[],[],"", 0, "");
- GetOptionsFromArray(\@args,
- 'me=s' => \$me,
- 'user=s@' => \$users,
- 'alias=s@' => \$aliases,
- 'token=s@' => \$tokens,
- 'org=s@' => \$orgs,
- 'baseurl=s' => \$baseurl,
- 'mirror=s@' => \$mirrors,
- 'gogs' => \$gogs,
- 'help' => \$help,
- );
- return _help() if $help;
- return _help(1, "Must pass at least one user or organization") unless (@$users + @$orgs);
- return _help(2, "Must pass baseurl") unless $baseurl;
- return _help(3, "Must pass your username") unless $me;
- # Parse Alias mappings
- my %alias_map;
- if (@$aliases) {
- foreach my $arg (@$aliases) {
- my ($actual, $domain, $alias) = split(/:/, $arg);
- return _help(3, "aliases must be of the form user:domain:alias") unless $actual && $domain && $alias;
- $alias_map{$domain}{$actual} = $alias;
- }
- }
- my ($primary_domain) = $baseurl =~ $domainRipper;
- my %tokens;
- foreach my $tok (@$tokens) {
- my ($domain, $token) = split(/:/, $tok);
- return _help(4, "tokens must be of the form domain:token") unless $domain && $token;
- $tokens{$domain} = $token;
- }
- $primary_token = $tokens{$primary_domain};
- my %args = (
- user => $me,
- api_uri => $baseurl,
- token => $primary_token,
- );
- # It's important which is the primary, because we can have only one pull url, and many push urls.
- my $local = $gogs ? Gogs->new(%args) : Pithub->new( %args );
- # If the primary is gogs and we have no token passed, let's make one.
- if (!$primary_token && $gogs) {
- _help(5, "Program must be run interactively to auto-create keys on Gogs installs.") unless IO::Interactive::Tiny::is_interactive();
- $primary_token = $local->get_token(
- name => "git-clone-entity",
- password => _prompt("Please type in the password for $local->user:"),
- );
- }
- my @repos_local = _fetch_all($local, $users, $orgs);
- my %repos_mirror;
- foreach my $mirror_url (@$mirrors) {
- my ($mirror_domain) = $mirror_url =~ $domainRipper;
- my $muser = $me;
- $muser = $alias_map->{$domain}{$me} if exists $alias_map->{$domain}{$me};
- my %margs = (
- user => $muser,
- api_uri => $mirror_url,
- token => $tokens{$mirror_domain},
- );
- my $mirror = Pithub->new( api_uri => $mirror_url );
- $repos_mirror{$mirror_url} = _fetch_all($mirror, $users, $orgs, \%alias_map);
- }
- use Data::Dumper;
- die Dumper(\%repos_mirror, \@repos_local, \%alias_map);
- }
- sub _prompt {
- my ( $prompt ) = @_;
- $prompt ||= "";
- my $input = "";
- print $prompt;
- # We are readin a password
- Term::ReadKey::ReadMode('noecho');
- {
- local $SIG{'INT'} = sub { Term::ReadKey::ReadMode(0); exit 130; };
- $input = <STDIN>;
- chomp($input) if $input;
- }
- Term::ReadKey::ReadMode(0);
- print "\n";
- return $input;
- }
- sub _fetch_all {
- my ($api, $users, $orgs, $alias_map) = @_;
- my ($domain) = $api->api_uri =~ $domainRipper;
- my @repos;
- foreach my $user (@$users) {
- $user = $alias_map->{$domain}{$user} if exists $alias_map->{$domain}{$user};
- my $result = $api->repos->list( user => $user );
- push(@repos, $result) if $result;
- }
- foreach my $org (@$orgs) {
- $org = $alias_map->{$domain}{$org} if exists $alias_map->{$domain}{$org};
- my $result = $api->repos->list( org => $org );
- push(@repos, $result) if $result;
- }
- return @repos;
- }
- exit main(@ARGV) unless caller;
- 1;
|