8282from bloom .github import GithubException
8383from bloom .github import GitHubAuthException
8484
85+ from bloom .gitlab import Gitlab
86+ from bloom .gitlab import GitlabException
87+ from bloom .gitlab import GitlabAuthException
88+
8589from bloom .logging import debug
8690from bloom .logging import error
8791from 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+
669694def 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+
746815def 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