Skip to content

Commit bd5cc2e

Browse files
committed
Gitlab interfacing
1 parent a7bc25e commit bd5cc2e

File tree

2 files changed

+296
-104
lines changed

2 files changed

+296
-104
lines changed

bloom/commands/release.py

Lines changed: 206 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@
8282
from bloom.github import GithubException
8383
from bloom.github import GitHubAuthException
8484

85+
from bloom.gitlab import Gitlab
86+
from bloom.gitlab import GitlabException
87+
from bloom.gitlab import GitlabAuthException
88+
8589
from bloom.logging import debug
8690
from bloom.logging import error
8791
from bloom.logging import fmt
@@ -663,9 +667,30 @@ def get_gh_info(url):
663667
return url_paths[1], url_paths[2], url_paths[3], '/'.join(url_paths[4:])
664668

665669

670+
def get_gl_info(url):
671+
# returns base_org, base_repo, base_branch, base_path
672+
o = urlparse(url)
673+
if 'gitlab' not in o.netloc:
674+
return None, None, None, None, None
675+
server = '{}://{}'.format(o.scheme, o.netloc)
676+
url_paths = o.path.split('/')
677+
if len(url_paths) < 6:
678+
return None, None, None, None, None
679+
return server, url_paths[1], url_paths[2], url_paths[4], '/'.join(url_paths[5:])
680+
681+
666682
_gh = None
683+
_gl = None
667684

668685

686+
def get_bloom_config_and_path():
687+
oauth_config_path = os.path.join(os.path.expanduser('~'), '.config', 'bloom')
688+
config = {}
689+
if os.path.exists(oauth_config_path):
690+
with open(oauth_config_path, 'r') as f:
691+
config = json.loads(f.read())
692+
return config, oauth_config_path
693+
669694
def get_github_interface(quiet=False):
670695
def mfa_prompt(oauth_config_path, username):
671696
"""Explain how to create a token for users with Multi-Factor Authentication configured."""
@@ -684,15 +709,13 @@ def mfa_prompt(oauth_config_path, username):
684709
if _gh is not None:
685710
return _gh
686711
# First check to see if the oauth token is stored
687-
oauth_config_path = os.path.join(os.path.expanduser('~'), '.config', 'bloom')
688-
config = {}
689-
if os.path.exists(oauth_config_path):
690-
with open(oauth_config_path, 'r') as f:
691-
config = json.loads(f.read())
692-
token = config.get('oauth_token', None)
693-
username = config.get('github_user', None)
694-
if token and username:
695-
return Github(username, auth=auth_header_from_oauth_token(token), token=token)
712+
config, oauth_config_path = get_bloom_config_and_path()
713+
token = config.get('oauth_token', None)
714+
username = config.get('github_user', None)
715+
716+
if token and username:
717+
return Github(username, auth=auth_header_from_oauth_token(token), token=token)
718+
696719
if not os.path.isdir(os.path.dirname(oauth_config_path)):
697720
os.makedirs(os.path.dirname(oauth_config_path))
698721
if quiet:
@@ -723,7 +746,7 @@ def mfa_prompt(oauth_config_path, username):
723746
gh = Github(username, auth=auth_header_from_basic_auth(username, password))
724747
try:
725748
token = gh.create_new_bloom_authorization(update_auth=True)
726-
with open(oauth_config_path, 'a') as f:
749+
with open(oauth_config_path, 'w') as f:
727750
config.update({'oauth_token': token, 'github_user': username})
728751
f.write(json.dumps(config))
729752
info("The token '{token}' was created and stored in the bloom config file: '{oauth_config_path}'"
@@ -743,6 +766,52 @@ def mfa_prompt(oauth_config_path, username):
743766
return gh
744767

745768

769+
def get_gitlab_interface(server, quiet=False):
770+
global _gl
771+
if _gl is not None:
772+
return _gl
773+
774+
config, oauth_config_path = get_bloom_config_and_path()
775+
if 'gitlab' in config:
776+
_gl = Gitlab(server, token=config['gitlab'])
777+
return _gl
778+
779+
if quiet:
780+
return None
781+
782+
info("")
783+
warning("Looks like bloom doesn't have a gitlab token for you yet.")
784+
warning("Go to http://{}/profile/personal_access_tokens to create one.".format(server))
785+
warning("Make sure you give it API access.")
786+
warning("The token will be stored in `~/.config/bloom`.")
787+
warning("You can delete the token from that file to have a new token generated.")
788+
warning("Guard this token like a password, because it allows someone/something to act on your behalf.")
789+
info("")
790+
if not maybe_continue('y', "Would you like to input a token now"):
791+
return None
792+
token = None
793+
while token is None:
794+
try:
795+
token = safe_input("Gitlab Token: ")
796+
except (KeyboardInterrupt, EOFError):
797+
return None
798+
try:
799+
gl = Gitlab(server, token=token)
800+
gl.auth()
801+
with open(oauth_config_path, 'w') as f:
802+
config.update({'gitlab': token})
803+
f.write(json.dumps(config))
804+
info("The token was stored in the bloom config file")
805+
_gl = gl
806+
break
807+
except GitlabAuthException:
808+
error("Failed to authenticate your token.")
809+
if not maybe_continue():
810+
return None
811+
812+
return _gl
813+
814+
746815
def get_changelog_summary(release_tag):
747816
summary = u""
748817
packages = dict([(p.name, p) for p in get_packages().values()])
@@ -792,60 +861,23 @@ def open_pull_request(track, repository, distro, interactive, override_release_r
792861
return None
793862
version = updated_distribution_file.repositories[repository].release_repository.version
794863
updated_distro_file_yaml = yaml_from_distribution_file(updated_distribution_file)
795-
# Determine if the distro file is hosted on github...
796-
base_org, base_repo, base_branch, base_path = get_gh_info(get_distribution_file_url(distro))
797-
if None in [base_org, base_repo, base_branch, base_path]:
798-
warning("Automated pull request only available via github.com")
799-
return
864+
865+
# Determine where the distro file is hosted...
866+
distro_url = get_distribution_file_url(distro)
867+
base_org, base_repo, base_branch, base_path = get_gh_info(distro_url)
868+
if None not in [base_org, base_repo, base_branch, base_path]:
869+
server = 'http://github.com'
870+
else:
871+
server, base_org, base_repo, base_branch, base_path = get_gl_info(distro_url)
872+
if None in [server, base_org, base_repo, base_branch, base_path]:
873+
warning("Automated pull request only available via github.com or gitlab")
874+
return
875+
800876
# If we did replace the branch in the url with a commit, restore that now
801877
if _rosdistro_index_original_branch is not None:
802878
base_branch = _rosdistro_index_original_branch
803-
# Get the github interface
804-
gh = get_github_interface()
805-
if gh is None:
806-
return None
807-
# Determine the head org/repo for the pull request
808-
head_org = gh.username # The head org will always be gh user
809-
head_repo = None
810-
# Check if the github user and the base org are the same
811-
if gh.username == base_org:
812-
# If it is, then a fork is not necessary
813-
head_repo = gh.get_repo(base_org, base_repo)
814-
else:
815-
info(fmt("@{bf}@!==> @|@!Checking on GitHub for a fork to make the pull request from..."))
816-
# It is not, so a fork will be required
817-
# Check if a fork already exists on the user's account
818879

819-
try:
820-
repo_forks = gh.list_forks(base_org, base_repo)
821-
user_forks = [r for r in repo_forks if r.get('owner', {}).get('login', '') == gh.username]
822-
# github allows only 1 fork per org as far as I know. We just take the first one.
823-
head_repo = user_forks[0] if user_forks else None
824-
825-
except GithubException as exc:
826-
debug("Received GithubException while checking for fork: {exc}".format(**locals()))
827-
pass # 404 or unauthorized, but unauthorized should have been caught above
828-
829-
# If not head_repo still, a fork does not exist and must be created
830-
if head_repo is None:
831-
warning("Could not find a fork of {base_org}/{base_repo} on the {gh.username} GitHub account."
832-
.format(**locals()))
833-
warning("Would you like to create one now?")
834-
if not maybe_continue():
835-
warning("Skipping the pull request...")
836-
return
837-
# Create a fork
838-
try:
839-
head_repo = gh.create_fork(base_org, base_repo) # Will raise if not successful
840-
except GithubException as exc:
841-
error("Aborting pull request: {0}".format(exc))
842-
return
843-
head_repo = head_repo.get('name', '')
844-
info(fmt("@{bf}@!==> @|@!" +
845-
"Using this fork to make a pull request from: {head_org}/{head_repo}".format(**locals())))
846-
# Clone the fork
847-
info(fmt("@{bf}@!==> @|@!" + "Cloning {0}/{1}...".format(head_org, head_repo)))
848-
new_branch = None
880+
# Create content for PR
849881
title = "{0}: {1} in '{2}' [bloom]".format(repository, version, base_path)
850882
track_dict = get_tracks_dict_raw()['tracks'][track]
851883
body = u"""\
@@ -866,50 +898,120 @@ def open_pull_request(track, repository, distro, interactive, override_release_r
866898
release_repo=updated_distribution_file.repositories[repository].release_repository.url,
867899
)
868900
body += get_changelog_summary(generate_release_tag(distro))
869-
with temporary_directory() as temp_dir:
870-
def _my_run(cmd, msg=None):
871-
if msg:
872-
info(fmt("@{bf}@!==> @|@!" + sanitize(msg)))
873-
else:
874-
info(fmt("@{bf}@!==> @|@!" + sanitize(str(cmd))))
875-
from subprocess import check_call
876-
check_call(cmd, shell=True)
877-
# Use the oauth token to clone
878-
rosdistro_url = 'https://{gh.token}:[email protected]/{base_org}/{base_repo}.git'.format(**locals())
879-
rosdistro_fork_url = 'https://{gh.token}:[email protected]/{head_org}/{head_repo}.git'.format(**locals())
880-
_my_run('mkdir -p {base_repo}'.format(**locals()))
881-
with change_directory(base_repo):
882-
_my_run('git init')
883-
branches = [x['name'] for x in gh.list_branches(head_org, head_repo)]
884-
new_branch = 'bloom-{repository}-{count}'
885-
count = 0
886-
while new_branch.format(repository=repository, count=count) in branches:
887-
count += 1
888-
new_branch = new_branch.format(repository=repository, count=count)
889-
# Final check
890-
info(fmt("@{cf}Pull Request Title: @{yf}" + sanitize(title)))
891-
info(fmt("@{cf}Pull Request Body : \n@{yf}" + sanitize(body)))
892-
msg = fmt("@!Open a @|@{cf}pull request@| @!@{kf}from@| @!'@|@!@{bf}" +
893-
"{head_org}/{head_repo}:{new_branch}".format(**locals()) +
894-
"@|@!' @!@{kf}into@| @!'@|@!@{bf}" +
895-
"{base_org}/{base_repo}:{base_branch}".format(**locals()) +
896-
"@|@!'?")
897-
info(msg)
898-
if interactive and not maybe_continue():
899-
warning("Skipping the pull request...")
900-
return
901-
_my_run('git checkout -b {new_branch}'.format(**locals()))
902-
_my_run('git pull {rosdistro_url} {base_branch}'.format(**locals()), "Pulling latest rosdistro branch")
903-
if _rosdistro_index_commit is not None:
904-
_my_run('git reset --hard {_rosdistro_index_commit}'.format(**globals()))
905-
with open('{0}'.format(base_path), 'w') as f:
906-
info(fmt("@{bf}@!==> @|@!Writing new distribution file: ") + str(base_path))
907-
f.write(updated_distro_file_yaml)
908-
_my_run('git add {0}'.format(base_path))
909-
_my_run('git commit -m "{0}"'.format(title))
910-
_my_run('git push {rosdistro_fork_url} {new_branch}'.format(**locals()), "Pushing changes to fork")
911-
# Open the pull request
912-
return gh.create_pull_request(base_org, base_repo, base_branch, head_org, new_branch, title, body)
901+
902+
if server == 'http://github.com':
903+
# Get the github interface
904+
gh = get_github_interface()
905+
if gh is None:
906+
return None
907+
# Determine the head org/repo for the pull request
908+
head_org = gh.username # The head org will always be gh user
909+
head_repo = None
910+
# Check if the github user and the base org are the same
911+
if gh.username == base_org:
912+
# If it is, then a fork is not necessary
913+
head_repo = gh.get_repo(base_org, base_repo)
914+
else:
915+
info(fmt("@{bf}@!==> @|@!Checking on GitHub for a fork to make the pull request from..."))
916+
# It is not, so a fork will be required
917+
# Check if a fork already exists on the user's account
918+
919+
try:
920+
repo_forks = gh.list_forks(base_org, base_repo)
921+
user_forks = [r for r in repo_forks if r.get('owner', {}).get('login', '') == gh.username]
922+
# github allows only 1 fork per org as far as I know. We just take the first one.
923+
head_repo = user_forks[0] if user_forks else None
924+
925+
except GithubException as exc:
926+
debug("Received GithubException while checking for fork: {exc}".format(**locals()))
927+
pass # 404 or unauthorized, but unauthorized should have been caught above
928+
929+
# If not head_repo still, a fork does not exist and must be created
930+
if head_repo is None:
931+
warning("Could not find a fork of {base_org}/{base_repo} on the {gh.username} GitHub account."
932+
.format(**locals()))
933+
warning("Would you like to create one now?")
934+
if not maybe_continue():
935+
warning("Skipping the pull request...")
936+
return
937+
# Create a fork
938+
try:
939+
head_repo = gh.create_fork(base_org, base_repo) # Will raise if not successful
940+
except GithubException as exc:
941+
error("Aborting pull request: {0}".format(exc))
942+
return
943+
head_repo = head_repo.get('name', '')
944+
info(fmt("@{bf}@!==> @|@!" +
945+
"Using this fork to make a pull request from: {head_org}/{head_repo}".format(**locals())))
946+
# Clone the fork
947+
info(fmt("@{bf}@!==> @|@!" + "Cloning {0}/{1}...".format(head_org, head_repo)))
948+
new_branch = None
949+
950+
with temporary_directory() as temp_dir:
951+
def _my_run(cmd, msg=None):
952+
if msg:
953+
info(fmt("@{bf}@!==> @|@!" + sanitize(msg)))
954+
else:
955+
info(fmt("@{bf}@!==> @|@!" + sanitize(str(cmd))))
956+
from subprocess import check_call
957+
check_call(cmd, shell=True)
958+
# Use the oauth token to clone
959+
rosdistro_url = 'https://{gh.token}:[email protected]/{base_org}/{base_repo}.git'.format(**locals())
960+
rosdistro_fork_url = 'https://{gh.token}:[email protected]/{head_org}/{head_repo}.git'.format(**locals())
961+
_my_run('mkdir -p {base_repo}'.format(**locals()))
962+
with change_directory(base_repo):
963+
_my_run('git init')
964+
branches = [x['name'] for x in gh.list_branches(head_org, head_repo)]
965+
new_branch = 'bloom-{repository}-{count}'
966+
count = 0
967+
while new_branch.format(repository=repository, count=count) in branches:
968+
count += 1
969+
new_branch = new_branch.format(repository=repository, count=count)
970+
# Final check
971+
info(fmt("@{cf}Pull Request Title: @{yf}" + sanitize(title)))
972+
info(fmt("@{cf}Pull Request Body : \n@{yf}" + sanitize(body)))
973+
msg = fmt("@!Open a @|@{cf}pull request@| @!@{kf}from@| @!'@|@!@{bf}" +
974+
"{head_org}/{head_repo}:{new_branch}".format(**locals()) +
975+
"@|@!' @!@{kf}into@| @!'@|@!@{bf}" +
976+
"{base_org}/{base_repo}:{base_branch}".format(**locals()) +
977+
"@|@!'?")
978+
info(msg)
979+
if interactive and not maybe_continue():
980+
warning("Skipping the pull request...")
981+
return
982+
_my_run('git checkout -b {new_branch}'.format(**locals()))
983+
_my_run('git pull {rosdistro_url} {base_branch}'.format(**locals()), "Pulling latest rosdistro branch")
984+
if _rosdistro_index_commit is not None:
985+
_my_run('git reset --hard {_rosdistro_index_commit}'.format(**globals()))
986+
with open('{0}'.format(base_path), 'w') as f:
987+
info(fmt("@{bf}@!==> @|@!Writing new distribution file: ") + str(base_path))
988+
f.write(updated_distro_file_yaml)
989+
_my_run('git add {0}'.format(base_path))
990+
_my_run('git commit -m "{0}"'.format(title))
991+
_my_run('git push {rosdistro_fork_url} {new_branch}'.format(**locals()), "Pushing changes to fork")
992+
# Open the pull request
993+
return gh.create_pull_request(base_org, base_repo, base_branch, head_org, new_branch, title, body)
994+
else:
995+
gl = get_gitlab_interface(server)
996+
if gl is None:
997+
return None
998+
999+
repo_obj = gl.get_repo(base_org, base_repo)
1000+
1001+
# Determine New Branch Name
1002+
branches = gl.list_branches(repo_obj)
1003+
new_branch = 'bloom-{repository}-{count}'
1004+
count = 0
1005+
while new_branch.format(repository=repository, count=count) in branches:
1006+
count += 1
1007+
new_branch = new_branch.format(repository=repository, count=count)
1008+
1009+
gl.create_branch(repo_obj, new_branch, base_branch)
1010+
gl.update_file(repo_obj, new_branch, title, base_path, updated_distro_file_yaml)
1011+
1012+
mr = gl.create_pull_request(repo_obj, new_branch, base_branch, title, body)
1013+
return mr['web_url']
1014+
9131015

9141016
_original_version = None
9151017

0 commit comments

Comments
 (0)