George Baugh 1 anno fa
parent
commit
18928cf38a
1 ha cambiato i file con 119 aggiunte e 78 eliminazioni
  1. 119 78
      bin/git-clone-entity

+ 119 - 78
bin/git-clone-entity

@@ -7,7 +7,7 @@ use warnings;
 
 use FindBin::libs;
 
-use List::Util qw{first any uniq};
+use List::Util qw{first any uniq all};
 use HTTP::Tiny;
 use Config::Simple;
 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).
 
 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
 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.
 
-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
 
@@ -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
 
-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.
 
@@ -111,13 +111,13 @@ In most organizations, you will have the org hold the primary copy of a repo, wi
 
 =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
 
 =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'
 
@@ -247,9 +247,12 @@ sub main {
     );
 	$verbose = $options{verbose};
 
+	$options{primary_user} = $options{me} if !$options{primary_user} || !$options{primary_org};
+
 	# Tiebreaker vote in the event of conflicting forks
 	push(@{$options{users}}, $options{primary_user}) if $options{primary_user};
 	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};
 
     return _help() if $options{help};
@@ -276,6 +279,11 @@ sub main {
     }
 
 	# 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});
 
 	my $field_name = $options{nossh} ? 'clone_url' : 'ssh_url';
@@ -325,84 +333,111 @@ sub main {
     }
 
     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 $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},
 			parent              => $repo->{upstream_uri},
 			private             => $repo->{private},
 			is_primary_domain   => $repo->{domain} eq $primary_domain,
 			domain              => $repo->{domain},
-			upstream            => $aliased eq $prime_name,
+			upstream            => $on_baseurl && $is_prime,
 			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->();
@@ -417,11 +452,12 @@ sub main {
 }
 
 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 } );
 	#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 {
 	my ($mirror, $field_name, $muser, $repo) = @_;
+
 	my $upstream_uri;
 	if ($repo->{fork}) {
 		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};
 		_help(10, "Could not discern upstream URI for forked repo $repo->{name}!") unless $upstream_uri;
 	}