|
@@ -7,7 +7,7 @@ use warnings;
|
|
|
|
|
|
|
|
use FindBin::libs;
|
|
use FindBin::libs;
|
|
|
|
|
|
|
|
-use List::Util qw{first any uniq};
|
|
|
|
|
|
|
+use List::Util qw{first any uniq all};
|
|
|
use HTTP::Tiny;
|
|
use HTTP::Tiny;
|
|
|
use Config::Simple;
|
|
use Config::Simple;
|
|
|
use Getopt::Long qw{GetOptionsFromArray};
|
|
use Getopt::Long qw{GetOptionsFromArray};
|
|
@@ -34,7 +34,7 @@ Alternatively, you may just want to keep your local development environment up t
|
|
|
This program facilitiates cloning your 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).
|
|
This program facilitiates cloning your 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 configure your 'origin' & 'upstream' remote to fetch from the baseurl provided, and push to it and the mirror(s) provided.
|
|
It will configure your 'origin' & 'upstream' remote to fetch from the baseurl provided, and push to it and the mirror(s) provided.
|
|
|
-Regardless, remotes for the base and mirrors will also be set up in case individual pushes must be made.
|
|
|
|
|
|
|
+Regardless, remotes for the users/orgs provided will also be set up in case individual pushes must be made.
|
|
|
|
|
|
|
|
In the event that two different users/orgs have the same named repository (e.g. forks) it
|
|
In the event that two different users/orgs have the same named repository (e.g. forks) it
|
|
|
will set up remotes named after the user/org in the event the repo is a fork, and set the 'upstream' name to be the parent repository.
|
|
will set up remotes named after the user/org in the event the repo is a fork, and set the 'upstream' name to be the parent repository.
|
|
@@ -76,7 +76,7 @@ git clone-entity --user $user1 --user $user2 --org $org1 --org $org2 --alias $us
|
|
|
|
|
|
|
|
Your username on the baseurl. Relevant to token use, what is visible, etc.
|
|
Your username on the baseurl. Relevant to token use, what is visible, etc.
|
|
|
|
|
|
|
|
-In the event your username is also having it's repos cloned, your remotes will become 'origin', otherwise the 'primary_user' or 'primary_org' will.
|
|
|
|
|
|
|
+All this users' repositories will be cloned. Additional users' repos will be added as remotes if your interests intersect.
|
|
|
|
|
|
|
|
--me tarzan
|
|
--me tarzan
|
|
|
|
|
|
|
@@ -103,7 +103,7 @@ You can omit the auth token on gogs, as we can create them automatically (we wil
|
|
|
|
|
|
|
|
=head3 primary_user, primary_org
|
|
=head3 primary_user, primary_org
|
|
|
|
|
|
|
|
-Primary entity to clone. Consider their repository to be the canonical one. One or the other must be passed. In the event both are, the org is preferred.
|
|
|
|
|
|
|
+Primary entity to clone. Defaults to --me in the event neither are passed. Considers their repository to be the canonical one. In the event both are, the org is preferred.
|
|
|
|
|
|
|
|
In most organizations, you will have the org hold the primary copy of a repo, with developers forking copies. This will become the "upstream" remote.
|
|
In most organizations, you will have the org hold the primary copy of a repo, with developers forking copies. This will become the "upstream" remote.
|
|
|
|
|
|
|
@@ -111,13 +111,13 @@ In most organizations, you will have the org hold the primary copy of a repo, wi
|
|
|
|
|
|
|
|
=head3 user
|
|
=head3 user
|
|
|
|
|
|
|
|
-Clone all of this user's repositories. May be passed multiple times.
|
|
|
|
|
|
|
+Add remotes for this user's repositories if interests intersect. May be passed multiple times.
|
|
|
|
|
|
|
|
--user fred
|
|
--user fred
|
|
|
|
|
|
|
|
=head3 org
|
|
=head3 org
|
|
|
|
|
|
|
|
-Clone all of this organization's repositories. May be passed multiple times.
|
|
|
|
|
|
|
+Add remotes for this org's repositories if interests intersect. May be passed multiple times.
|
|
|
|
|
|
|
|
--org 'Granite-Industries'
|
|
--org 'Granite-Industries'
|
|
|
|
|
|
|
@@ -247,9 +247,12 @@ sub main {
|
|
|
);
|
|
);
|
|
|
$verbose = $options{verbose};
|
|
$verbose = $options{verbose};
|
|
|
|
|
|
|
|
|
|
+ $options{primary_user} = $options{me} if !$options{primary_user} || !$options{primary_org};
|
|
|
|
|
+
|
|
|
# Tiebreaker vote in the event of conflicting forks
|
|
# Tiebreaker vote in the event of conflicting forks
|
|
|
push(@{$options{users}}, $options{primary_user}) if $options{primary_user};
|
|
push(@{$options{users}}, $options{primary_user}) if $options{primary_user};
|
|
|
push(@{$options{orgs}}, $options{primary_org}) if $options{primary_org};
|
|
push(@{$options{orgs}}, $options{primary_org}) if $options{primary_org};
|
|
|
|
|
+ push(@{$options{users}}, $options{me}) if $options{me};
|
|
|
my $prime_name = $options{primary_org} || $options{primary_user};
|
|
my $prime_name = $options{primary_org} || $options{primary_user};
|
|
|
|
|
|
|
|
return _help() if $options{help};
|
|
return _help() if $options{help};
|
|
@@ -276,6 +279,11 @@ sub main {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# Simplify code below by making the primary just another mirror to fetch
|
|
# Simplify code below by making the primary just another mirror to fetch
|
|
|
|
|
+ my @mirror_domains = map {
|
|
|
|
|
+ my $subj = $_;
|
|
|
|
|
+ my ($dom) = $subj =~ $domainRipper;
|
|
|
|
|
+ $dom
|
|
|
|
|
+ } @{$options{mirrors}};
|
|
|
unshift(@{$options{mirrors}}, $options{baseurl});
|
|
unshift(@{$options{mirrors}}, $options{baseurl});
|
|
|
|
|
|
|
|
my $field_name = $options{nossh} ? 'clone_url' : 'ssh_url';
|
|
my $field_name = $options{nossh} ? 'clone_url' : 'ssh_url';
|
|
@@ -325,84 +333,111 @@ sub main {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
my ($primary_domain) = $options{baseurl} =~ $domainRipper;
|
|
my ($primary_domain) = $options{baseurl} =~ $domainRipper;
|
|
|
- my $cloning_myself = first { $_ eq $options{me} } (@{$options{users}},@{$options{orgs}});
|
|
|
|
|
|
|
|
|
|
- my %repodata;
|
|
|
|
|
- foreach my $repo (@repos) {
|
|
|
|
|
- $repodata{$repo->{name}} //= {};
|
|
|
|
|
|
|
+ my $transform = sub {
|
|
|
|
|
+ my $repo = shift;
|
|
|
my $reversed = $alias_reverse{$repo->{domain}} // {};
|
|
my $reversed = $alias_reverse{$repo->{domain}} // {};
|
|
|
my $aliased = exists $reversed->{$repo->{owner}{login}} ? $reversed->{$repo->{owner}{login}} : $repo->{owner}{login};
|
|
my $aliased = exists $reversed->{$repo->{owner}{login}} ? $reversed->{$repo->{owner}{login}} : $repo->{owner}{login};
|
|
|
- my $repo_info = {
|
|
|
|
|
|
|
+ my $on_baseurl = $repo->{domain} eq $primary_domain;
|
|
|
|
|
+ my $is_prime = $aliased eq $prime_name;
|
|
|
|
|
+ return {
|
|
|
|
|
+ name => $repo->{name},
|
|
|
clone_uri => $repo->{$field_name},
|
|
clone_uri => $repo->{$field_name},
|
|
|
parent => $repo->{upstream_uri},
|
|
parent => $repo->{upstream_uri},
|
|
|
private => $repo->{private},
|
|
private => $repo->{private},
|
|
|
is_primary_domain => $repo->{domain} eq $primary_domain,
|
|
is_primary_domain => $repo->{domain} eq $primary_domain,
|
|
|
domain => $repo->{domain},
|
|
domain => $repo->{domain},
|
|
|
- upstream => $aliased eq $prime_name,
|
|
|
|
|
|
|
+ upstream => $on_baseurl && $is_prime,
|
|
|
owner => $aliased,
|
|
owner => $aliased,
|
|
|
- origin => $aliased eq $options{me} || ( !$cloning_myself && $aliased eq $prime_name ),
|
|
|
|
|
|
|
+ owner_noalias => $repo->{owner}{login},
|
|
|
|
|
+ origin => $repo->{owner}{login} eq $options{me},
|
|
|
|
|
+ api => $clients{$repo->{domain}},
|
|
|
};
|
|
};
|
|
|
- # Set up the "special" URIs
|
|
|
|
|
- foreach my $remote (qw{origin upstream parent}) {
|
|
|
|
|
- next unless $repo_info->{$remote};
|
|
|
|
|
- $repodata{$repo->{name}}{$remote}{fetch} = $repo_info if $repo_info->{is_primary_domain};
|
|
|
|
|
- $repodata{$repo->{name}}{$remote}{push} //= [];
|
|
|
|
|
- push(@{$repodata{$repo->{name}}{$remote}{push}}, $repo_info);
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ # get rid of everything that doesn't matter
|
|
|
|
|
+ @repos = map { $transform->($_) } @repos;
|
|
|
|
|
+ my @names = uniq map { $_->{name} } @repos;
|
|
|
|
|
+
|
|
|
|
|
+ my %proper_owner = (
|
|
|
|
|
+ origin => $options{me},
|
|
|
|
|
+ upstream => $prime_name,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ my %repodata;
|
|
|
|
|
+ foreach my $reponame (@names) {
|
|
|
|
|
+ my @matching = grep { $_->{name} eq $reponame } @repos;
|
|
|
|
|
+
|
|
|
|
|
+ # We don't care about cloning anything which do not intersect with our interests.
|
|
|
|
|
+ # This means we need at least one clone of a repo done by ourself or the prime.
|
|
|
|
|
+ next unless any { $_->{owner} eq $options{me} || $_->{owner} eq $prime_name } @matching;
|
|
|
|
|
+
|
|
|
|
|
+ # There's also no point in making local-org copies of stuff on a mirror (say github)
|
|
|
|
|
+ # Which are forks of other stuff on your users' mirror account (standard workflow).
|
|
|
|
|
+ # Just clone it and call it a day.
|
|
|
|
|
+ my $all_on_mirror = all { $_->{domain} ne $primary_domain && $_->{owner} eq $options{me} } @matching;
|
|
|
|
|
+ my $all_is_forks = all { $_->{parent} } @matching;
|
|
|
|
|
+ if ($all_on_mirror && $all_is_forks) {
|
|
|
|
|
+ # There can only be one at this point.
|
|
|
|
|
+ my $repo = $matching[0];
|
|
|
|
|
+ $repodata{$reponame}{origin}{fetch} = $repo->{clone_uri};
|
|
|
|
|
+ $repodata{$reponame}{origin}{push} //= [];
|
|
|
|
|
+ push(@{$repodata{$reponame}{origin}{push}}, $repo->{clone_uri});
|
|
|
|
|
+ $repodata{$reponame}{upstream}{fetch} = $repo->{parent};
|
|
|
|
|
+ $repodata{$reponame}{upstream}{push} //= [];
|
|
|
|
|
+ push(@{$repodata{$reponame}{upstream}{push}}, $repo->{parent});
|
|
|
|
|
+ next;
|
|
|
}
|
|
}
|
|
|
- # Set up the user's remote
|
|
|
|
|
- $repodata{$repo->{name}}{$aliased}{fetch} = $repo_info if $repo_info->{is_primary_domain};
|
|
|
|
|
- $repodata{$repo->{name}}{$aliased}{push} //= [];
|
|
|
|
|
- push(@{$repodata{$repo->{name}}{$aliased}{push}}, $repo_info);
|
|
|
|
|
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ # We only care about remote names that aren't already origin or upstream.
|
|
|
|
|
+ my @owners = grep { $_ ne $prime_name && $_ ne $options{me} } uniq map { $_->{owner} } @matching;
|
|
|
|
|
+
|
|
|
|
|
+ $repodata{$reponame} = {};
|
|
|
|
|
|
|
|
- # Remotes which we (probably) ought to have.
|
|
|
|
|
- # Also add in 'me' if it's in the list of users
|
|
|
|
|
- my $add_me = any { $_ eq $options{me} } @{$options{users}};
|
|
|
|
|
- my @known_remotes = (qw{origin upstream parent}, $prime_name);
|
|
|
|
|
- push(@known_remotes, $options{me}) if $add_me;
|
|
|
|
|
- @known_remotes = uniq @known_remotes;
|
|
|
|
|
-
|
|
|
|
|
- # TODO build a set of repos we need -- be they either mirrors needing setup or things on mirrors needing to be brought into the fold
|
|
|
|
|
- foreach my $repo (keys(%repodata)) {
|
|
|
|
|
- foreach my $remote (@known_remotes) {
|
|
|
|
|
- my $repository = $repodata{$repo}{$remote} // {};
|
|
|
|
|
- # If we only have the push URI for a mirror, then we don't have it locally
|
|
|
|
|
- if (!exists $repository->{fetch}) {
|
|
|
|
|
- LOG("$repo does not exist at $options{baseurl}");
|
|
|
|
|
- next unless $options{create};
|
|
|
|
|
- my $new_repo = _create_repo( $clients{$primary_domain}, $repo, $primary_domain);
|
|
|
|
|
- $repository->{fetch} = $new_repo;
|
|
|
|
|
- $repository->{push} //= [];
|
|
|
|
|
- push(@{$repository->{push}}, $new_repo);
|
|
|
|
|
|
|
+ # We then need to build all the remotes based on which is appropriate.
|
|
|
|
|
+ foreach my $remote (qw{origin upstream}, @owners) {
|
|
|
|
|
+ # Pick the right match. We may have something on the baseurl or on one of the mirrors.
|
|
|
|
|
+ my @repos = grep { $_->{$remote} || $_->{owner} eq $remote } @matching;
|
|
|
|
|
+ my ($baseurl_repo) = grep { $_->{domain} eq $primary_domain } @repos;
|
|
|
|
|
+
|
|
|
|
|
+ # Figure out where to make it if we have to.
|
|
|
|
|
+ my $on_baseurl = any { $remote eq $_ } qw{origin upstream};
|
|
|
|
|
+
|
|
|
|
|
+ # Supposing we don't have a match, we have to make it.
|
|
|
|
|
+ if (!$baseurl_repo && $on_baseurl) {
|
|
|
|
|
+ LOG("Missing $reponame on $options{baseurl}, going to create");
|
|
|
|
|
+ my $new_repo = _create_repo( $clients{$primary_domain}, $proper_owner{$remote}, $reponame, $primary_domain );
|
|
|
|
|
+ $baseurl_repo = $transform->($new_repo);
|
|
|
|
|
+ push(@matching, $baseurl_repo);
|
|
|
|
|
+ push(@repos, $baseurl_repo);
|
|
|
}
|
|
}
|
|
|
- # If we only have the fetch URI, then we don't have it on the mirrors
|
|
|
|
|
- foreach my $push_repo (@{$options{mirrors}}) {
|
|
|
|
|
- next if $push_repo eq $options{baseurl};
|
|
|
|
|
- my ($push_domain) = $push_repo =~ $domainRipper;
|
|
|
|
|
- next if any { $_->{domain} eq $push_domain } @{$repository->{push}};
|
|
|
|
|
- LOG("Repo $repo does not exist at $push_domain");
|
|
|
|
|
- next unless $options{create};
|
|
|
|
|
- my $new_repo = _create_repo( $clients{$push_domain}, $push_repo, $push_domain );
|
|
|
|
|
- $repository->{push} //= [];
|
|
|
|
|
- push(@{$repository->{push}}, $new_repo);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ foreach my $repo (@repos) {
|
|
|
|
|
+ # Prefer the baseurl repo if we have it, otherwise just use what you got.
|
|
|
|
|
+ $repodata{$reponame}{$remote}{fetch} = $baseurl_repo->{clone_uri} // $repo->{clone_uri};
|
|
|
|
|
+ $repodata{$reponame}{$remote}{push} //= [];
|
|
|
|
|
+ push(@{$repodata{$reponame}{$remote}{push}}, $repo->{clone_uri});
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Now that we almost certainly have a match, let's setup the mirror URIs as add'l pushes.
|
|
|
|
|
+ if ($on_baseurl) {
|
|
|
|
|
+ LOG("Looking for mirrors to $reponame:$remote");
|
|
|
|
|
+ foreach my $mirror_dom (@mirror_domains) {
|
|
|
|
|
+ my ($mirror) = grep { $_->{domain} eq $mirror_dom && $_->{owner} eq $baseurl_repo->{owner} } @matching;
|
|
|
|
|
+ if (!$mirror) {
|
|
|
|
|
+ # Create mirror.
|
|
|
|
|
+ LOG("Missing mirror on $mirror_dom for $reponame, for $baseurl_repo->{owner} creating...");
|
|
|
|
|
+ my $new_mirror = _create_repo( $clients{$mirror_dom}, $alias_map{$mirror_dom}{$baseurl_repo->{owner}}, $reponame, $mirror_dom );
|
|
|
|
|
+ $mirror = $transform->($new_mirror);
|
|
|
|
|
+ push(@matching, $mirror);
|
|
|
|
|
+ }
|
|
|
|
|
+ # Then add it.
|
|
|
|
|
+ push(@{$repodata{$reponame}{$remote}{push}}, $mirror->{clone_uri});
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- $repodata{$repo}{$remote} = $repository;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- # Now that we have everything created that we want, let's prune the info down
|
|
|
|
|
- foreach my $repo (keys(%repodata)) {
|
|
|
|
|
- foreach my $remote (keys(%{$repodata{$repo}})) {
|
|
|
|
|
- LOG("MAP $repo $remote");
|
|
|
|
|
- my $repository = $repodata{$repo}{$remote};
|
|
|
|
|
- my @push_uris = map { $_->{clone_uri} } @{$repository->{push}};
|
|
|
|
|
- $repository = {
|
|
|
|
|
- fetch => $repository->{fetch}{clone_uri},
|
|
|
|
|
- push => \@push_uris,
|
|
|
|
|
- };
|
|
|
|
|
- $repodata{$repo}{$remote} = $repository;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$cleanup->();
|
|
$cleanup->();
|
|
@@ -417,11 +452,12 @@ sub main {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
sub _create_repo {
|
|
sub _create_repo {
|
|
|
- my ($api, $repo, $domain) =@_;
|
|
|
|
|
- LOG("Creating $repo on $domain");
|
|
|
|
|
- return { 'clone_uri' => "$domain TODO" };
|
|
|
|
|
|
|
+ my ($api, $user, $repo, $domain) =@_;
|
|
|
|
|
+ # Double check if the repo actually exists, and just return that (TOCTOU)
|
|
|
|
|
+ #my $content = _fetch_repo($api, $user, { name => $repo }, 1);
|
|
|
|
|
|
|
|
- #TODO check that we haven't already created it
|
|
|
|
|
|
|
+ LOG("Creating $repo on $domain");
|
|
|
|
|
+ return { name => $repo, 'ssh_url' => "$user\@$domain:$repo TODO", owner => { login => $user }, domain => $domain };
|
|
|
|
|
|
|
|
#my $result = $api->repos->create( data => { name => $repo } );
|
|
#my $result = $api->repos->create( data => { name => $repo } );
|
|
|
#TODO handle errors
|
|
#TODO handle errors
|
|
@@ -473,18 +509,23 @@ sub _clone_repos {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+sub _fetch_repo {
|
|
|
|
|
+ my ($mirror, $muser, $repo, $nonfatal) = @_;
|
|
|
|
|
+ my $details = $mirror->repos->get( user => $muser, repo => $repo->{name});
|
|
|
|
|
+ if (!$details || !$details->response->is_success()) {
|
|
|
|
|
+ return if $nonfatal;
|
|
|
|
|
+ _help(9, "Could not fetch repository details for $repo->{name}") unless $details && $details->response->is_success();
|
|
|
|
|
+ }
|
|
|
|
|
+ return $details->content();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
sub _fetch_upstream_uri {
|
|
sub _fetch_upstream_uri {
|
|
|
my ($mirror, $field_name, $muser, $repo) = @_;
|
|
my ($mirror, $field_name, $muser, $repo) = @_;
|
|
|
|
|
+
|
|
|
my $upstream_uri;
|
|
my $upstream_uri;
|
|
|
if ($repo->{fork}) {
|
|
if ($repo->{fork}) {
|
|
|
LOG("Looking up what $repo->{name} was forked from...");
|
|
LOG("Looking up what $repo->{name} was forked from...");
|
|
|
- my $details = $mirror->repos->get( user => $muser, repo => $repo->{name});
|
|
|
|
|
- if (!$details || !$details->response->is_success()) {
|
|
|
|
|
- #LOG("Could not fetch repository details for $repo->{name}, skipping...");
|
|
|
|
|
- #next;
|
|
|
|
|
- _help(9, "Could not fetch repository details for $repo->{name}") unless $details && $details->response->is_success();
|
|
|
|
|
- }
|
|
|
|
|
- my $content = $details->content();
|
|
|
|
|
|
|
+ my $content = _fetch_repo($mirror, $muser, $repo);
|
|
|
$upstream_uri = $content->{parent}{$field_name};
|
|
$upstream_uri = $content->{parent}{$field_name};
|
|
|
_help(10, "Could not discern upstream URI for forked repo $repo->{name}!") unless $upstream_uri;
|
|
_help(10, "Could not discern upstream URI for forked repo $repo->{name}!") unless $upstream_uri;
|
|
|
}
|
|
}
|