Created
June 25, 2014 14:19
-
-
Save alexsavio/ca1bd22ceebd55e427a3 to your computer and use it in GitHub Desktop.
GitHub to Gitlab repository version copier/control.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#content of the config file .python-gitlab.cfg: | |
#[global] | |
#default = local | |
#ssl_verify = true | |
#[local] | |
#url = http://*************** | |
#private_token = ************** | |
repos = [('[email protected]:numpy/numpy.git', 'v1.8.1'), | |
('[email protected]:cython/cython.git', '0.20.2'), | |
('[email protected]:scipy/scipy.git', 'v0.14.0'), | |
('[email protected]:git/git.git', 'v2.0.0'), | |
('[email protected]:gpocentek/python-gitlab.git', ''), | |
('[email protected]:gitpython-developers/GitPython.git', ''), | |
('[email protected]:python-git/python.git', ''), | |
('[email protected]:BVLC/caffe.git', ''), | |
('[email protected]:schwarty/nignore.git', ''), | |
('[email protected]:Theano/Theano.git', ''), | |
('[email protected]:akeshavan/nbpapaya.git', ''), | |
('[email protected]:nipy/nibabel.git', '1.3.0'), | |
('[email protected]:nipy/nipy.git', '0.3.0'), | |
('[email protected]:nipy/nipy-labs.git', ''), | |
('[email protected]:nipy/PySurfer.git', 'v0.5'), | |
('[email protected]:nipy/nitime.git', '0.5'), | |
('[email protected]:nipy/dipy.git', '0.7.1'), | |
('[email protected]:nipy/nipype.git', '0.9.2'), | |
('[email protected]:nipy/brainx.git', ''), | |
('[email protected]:nipy/PyLocator.git', ''), | |
('[email protected]:nipy/pbrain.git', ''), | |
('[email protected]:nipy/nidoodles.git', ''), | |
('[email protected]:FCP-INDI/C-PAC.git', ''), | |
('https://github.com/FBK-NILab/NeuroBox', ''), | |
('[email protected]:mwaskom/moss.git', ''), | |
('[email protected]:rii-mango/Papaya.git', ''), | |
('[email protected]:scikit-learn/scikit-learn.git', '0.15'), | |
('[email protected]:scikit-fuzzy/scikit-fuzzy.git', ''), | |
('[email protected]:scikit-image/scikit-image.git', ''), | |
('[email protected]:wimleers/fileconveyor.git', ''), | |
('[email protected]:danginsburg/webgl-brain-viewer.git', ''), | |
('[email protected]:andsens/homeshick.git', ''),] | |
#gitlab_cfg = dict(ssl_verify = true, | |
# url = 'http://mygitlab.somewhere.com', | |
# private_token = 'asdadsqe1420980fadmmJyqhg') | |
import os | |
import logging | |
logging.basicConfig(level=logging.INFO, #filename='repysitories.log', | |
format="%(asctime)-15s %(filename)s %(funcName)s " | |
"%(levelname)s %(message)s") | |
log = logging.getLogger(__name__) | |
try: | |
from ConfigParser import ConfigParser | |
except: | |
from configparser import ConfigParser | |
import gitlab | |
import git | |
class LoggedError(Exception): | |
def __init__(self, message): | |
Exception.__init__(self, message) | |
log.error(message) | |
class GitlabConfigError(LoggedError): | |
pass | |
class GitlabConnectionError(LoggedError): | |
pass | |
class GitlabNamespaceNotFound(LoggedError): | |
pass | |
class GitlabUserNotFound(LoggedError): | |
pass | |
class GitlabProjectNotFound(LoggedError): | |
pass | |
class GitlabProjectAlreadyExists(LoggedError): | |
pass | |
class GitlabCommandError(LoggedError): | |
pass | |
class GitCommandError(LoggedError): | |
pass | |
class GitLocalRepoNotSet(LoggedError): | |
pass | |
class PathDoesNotExist(LoggedError): | |
pass | |
class GitlabConnection(object): | |
""" | |
This class manages the configuration and connection to the Gitlab server. | |
""" | |
config_paths = ['/etc/python-gitlab.cfg', | |
os.path.expanduser('~/.python-gitlab.cfg')] | |
def __init__(self, gitlab_url=None, ssl_verify=True, gitlab_id=None): | |
self.ssl_verify = ssl_verify | |
self.gitlab_url = gitlab_url | |
self.gitlab_id = gitlab_id | |
self.verbose = False | |
self.connection = None | |
try: | |
self._from_cfgfile() | |
except: | |
pass | |
def auth(self): | |
""" | |
Creates the connection to the GitLab server | |
""" | |
try: | |
self.connection = gitlab.Gitlab(self.gitlab_url, | |
private_token=self.gitlab_token, | |
ssl_verify=self.ssl_verify) | |
self.connection.auth() | |
except: | |
raise GitlabConnectionError("Could not connect to " | |
"GitLab ({0})".format(self.gitlab_url)) | |
def get_current_user_id(self): | |
""" | |
:return: | |
""" | |
if self.connection is None: | |
self.auth() | |
return self.connection.user.id | |
def get_all_namespaces(self): | |
""" | |
:return: | |
""" | |
if self.connection is None: | |
self.auth() | |
namespaces = [] | |
namespaces.extend(self.connection.list(gitlab.User)) | |
namespaces.extend(self.connection.list(gitlab.Group)) | |
return namespaces | |
def has_namespace(self, namespace_id): | |
""" | |
Return True if the Gitlab Server has a namespace with name or id | |
namespace_id | |
:param namespace_id: int or str | |
:return: bool | |
""" | |
if self.connection is None: | |
self.auth() | |
namespace_name = None | |
if namespace_id is None: | |
namespace_id = self.get_current_user_id() | |
if isinstance(namespace_id, str): | |
namespace_name = namespace_id | |
namespace_id = self.get_namespace_id(namespace_name) | |
if namespace_name is None: | |
namespace_name = self.get_namespace_name(namespace_id) | |
for ns in self.get_all_namespaces(): | |
if namespace_name == ns.name: | |
return True | |
return False | |
def is_authenticated(self): | |
return self.connection is None | |
def logout(self): | |
""" | |
Delete the connection variable | |
:return: | |
""" | |
self.connection = None | |
def exists_project(self, name, namespace_id=None): | |
""" | |
Return True if the project named name in the namespace_id exists | |
:param name: str | |
Name of the project | |
:param namespace_id: str or int | |
Usually namespaces are treated by their numbered id. | |
If you give a string it will look for the name of the namespace. | |
:return: bool | |
""" | |
try: | |
prj = self.get_project(name, namespace_id) | |
except: | |
return None | |
return prj is not None | |
def get_project(self, name, namespace_id=None): | |
""" | |
Return the gitlab.Project named name in the namespace_id. | |
:param name: str | |
Name of the project | |
:param namespace_id: str or int | |
Usually namespaces are treated by their numbered id. | |
If you give a string it will look for the name of the namespace. | |
If empty, will try to return the project in user's namespace, if it | |
does not exist, will return the project found by name. | |
:return: gitlab.Project | |
""" | |
if self.connection is None: | |
self.auth() | |
namespace_name = None | |
if namespace_id is None: | |
namespace_id = self.get_current_user_id() | |
if isinstance(namespace_id, str): | |
namespace_name = namespace_id | |
namespace_id = self.get_namespace_id(namespace_name) | |
if namespace_name is None: | |
namespace_name = self.get_namespace_name(namespace_id) | |
prob_proj = None | |
proj_lst = self.connection.all_projects() | |
for proj in proj_lst: | |
if proj.name == name: | |
if namespace_id is None: | |
return proj | |
if proj.namespace.id == namespace_id: | |
return proj | |
#saving a probable project if the namespace doesn't match | |
prob_proj = proj | |
if prob_proj is not None: | |
return prob_proj | |
else: | |
#could not find the project | |
msg = 'Could not find project {0}/{1} in ' \ | |
'Gitlab server'.format(namespace_name, name) | |
raise GitlabProjectNotFound(msg) | |
def get_namespace_id(self, namespace_name): | |
""" | |
:param namespace_name: str | |
:return: int | |
namespace_id if found, None otherwise | |
""" | |
all_namespaces = self.get_all_namespaces() | |
for ns in all_namespaces: | |
if ns.name == namespace_name: | |
return ns.id | |
return None | |
def get_namespace_name(self, namespace_id): | |
""" | |
:param namespace_id: int | |
:return: str | |
namespace_name if found, None otherwise | |
""" | |
all_namespaces = self.get_all_namespaces() | |
for ns in all_namespaces: | |
if ns.id == namespace_id: | |
return ns.name | |
return None | |
def get_project_namespace_name(self, name, namespace_id): | |
""" | |
Return the project named name in the namespace_id. | |
:param name: str | |
Name of the project | |
:param namespace_id: int | |
Usually namespaces are treated by their numbered id. | |
:return: gitlab.Project | |
""" | |
proj = self.get_project(name, namespace_id) | |
if proj is not None: | |
return proj.name | |
else: | |
msg = 'Could not find project {0}/{1} in ' \ | |
'Gitlab server'.format(namespace_id, name) | |
raise GitlabProjectNotFound(msg) | |
def create_new_project(self, creation_args): | |
""" | |
Push an existing local repository to Gitlab | |
:param repo_path: str | |
Absolute path to the repository | |
:param creation_args: dict | |
See python-gitlab create project arguments | |
:param name: str | |
:param namespace_id: int | |
:param description: str | |
:param default_branch: str | |
:param public: bool, setting to True is the same as visibility_level: 20 | |
:param visibility_level: int, choices={'0', '10', '20'} | |
:param snippets_enabled: bool | |
:param wiki_enabled: bool | |
:param merge_requests_enabled: bool | |
:param issues_enabled: bool | |
:param wall_enabled: bool | |
""" | |
if not self.is_authenticated(): | |
self.auth() | |
try: | |
proj_name = creation_args.get('name') | |
log.info('Creating new project ' | |
'{0} on {1}.'.format(proj_name, self.connection._url)) | |
p = self.connection.Project(creation_args) | |
p.save() | |
return p | |
except Exception as exc: | |
raise LoggedError(str(exc)) | |
def _from_cfgfile(self): | |
""" | |
Parse the configuration files for self config | |
""" | |
# read the config | |
config = ConfigParser() | |
config.read(self.config_paths) | |
if self.gitlab_id is None: | |
try: | |
self.gitlab_id = config.get('global', 'default') | |
except: | |
raise GitlabConfigError("Impossible to get the gitlab id " | |
"(not specified in config file)") | |
try: | |
self.gitlab_url = config.get(self.gitlab_id, 'url') | |
self.gitlab_token = config.get(self.gitlab_id, 'private_token') | |
except: | |
raise GitlabConfigError("Impossible to get gitlab informations " | |
"from configuration (%s)" % self.gitlab_id) | |
try: | |
self.ssl_verify = config.getboolean('global', 'ssl_verify') | |
except: | |
pass | |
try: | |
self.ssl_verify = config.getboolean(self.gitlab_id, 'ssl_verify') | |
except: | |
pass | |
def from_dict(self, config): | |
""" | |
Configs self through dictionary args: | |
ssl_verify: boolean | |
url: string | |
private_token: string | |
""" | |
self.ssl_verify = config.get('ssl_verify', True) | |
self.gitlab_url = config.get('url', None) | |
self.gitlab_token = config.get('private_token', None) | |
class GitRepository(object): | |
""" | |
This class manages a git remote URL and a local copy of the repository. | |
""" | |
def __init__(self, url=None, path=None, name=None, tag=None): | |
self.giturl = None | |
self.tag = tag | |
self.path = None | |
self.local_repo = None | |
if url is not None: | |
self.giturl = GitRemoteURL(url, name) | |
if path is not None: | |
self.set_local_repo(path) | |
def clone(self, to_path, mkdir=False, checkout_tag=False): | |
""" | |
Clone the remote URL to dir_path | |
:param dir_path: string | |
:param checkout_tag: bool | |
Indicates whether to checkout the self.tag version after setup | |
:return git.Repo | |
""" | |
if not os.path.exists(to_path): | |
if mkdir: | |
os.mkdir(to_path) | |
try: | |
log.info('Clonning repo {0} in {1}.'.format(self.giturl.url, | |
to_path)) | |
self.local_repo = git.Repo.clone_from(self.giturl.url, to_path) | |
self._update(checkout_tag) | |
return self.local_repo | |
except Exception as exc: | |
raise LoggedError(exc) | |
def set_local_repo(self, path, mkdir=False, checkout_tag=False): | |
""" | |
Configure self.local_repo using the repo that is in path. | |
:param path: str | |
Full path to where the repo is. | |
:param mkdir: bool | |
If True, will create the path and initialize an empty repo there. | |
:param checkout_tag: bool | |
Indicates whether to checkout the self.tag version after setup | |
:return git.Repo | |
""" | |
if not os.path.exists(path): | |
if mkdir: | |
try: | |
os.mkdir(path) | |
self.init_local(path) | |
except: | |
raise | |
else: | |
raise PathDoesNotExist('Repository folder path {0} does ' | |
'not exist.'.format(path)) | |
else: | |
try: | |
self.local_repo = git.Repo(path) | |
except Exception as exc: | |
raise LoggedError(str(exc)) | |
self._update(checkout_tag) | |
return self.local_repo | |
def has_working_copy(self, check_head_tag=False): | |
""" | |
Return whether the repo is locally cloned in self.path with the same | |
version | |
:param check_head_tag: bool | |
If True will also check if the tag is the same as the one in git_remote. | |
:return: bool | |
""" | |
if self.local_repo is None: | |
return False | |
if self.path is None: | |
return False | |
if os.path.exists(self.path): | |
try: | |
repo = git.Repo(self.path) | |
if check_head_tag: | |
if repo.head.ref.name != self.tag: | |
return False | |
return True | |
except: | |
return False | |
return True | |
def init_local(self, path): | |
""" | |
Initialize an empty local_repo in path. | |
:param path: str | |
Full path to where the local repo will be created | |
:return git.Repo | |
""" | |
try: | |
self.local_repo = git.Repo.init(path, mkdir=True) | |
self._update(checkout=False) | |
return self.local_repo | |
except Exception as exc: | |
raise LoggedError(str(exc)) | |
@property | |
def name(self): | |
return self.giturl.name | |
@property | |
def url(self): | |
return self.giturl.url | |
def checkout(self, tag_name=None): | |
""" | |
Perform a checktout in local_repo, to checktout a specific tag. | |
:param tag_name: str | |
Name of the tag to be checked out | |
""" | |
self._check_local_repo_exists() | |
try: | |
self.local_repo.git.checkout(tag_name) | |
except GitCommandError as gce: | |
raise gce | |
def pull(self): | |
""" | |
git pull the repository | |
:returns std output | |
""" | |
self._check_local_repo_exists() | |
try: | |
return self.local_repo.git.pull() | |
except Exception as exc: | |
raise GitCommandError(str(exc)) | |
def add_remote(self, name, url): | |
""" | |
Add the remote repository to local_repo. | |
:param name: str | |
Name of the remote repository | |
:param url: str | |
URL of the remote repository | |
:return git.Remote | |
Remote reference | |
""" | |
self._check_local_repo_exists() | |
try: | |
return self.local_repo.create_remote(name, url) | |
except git.GitCommandError as gce: | |
raise GitCommandError(str(gce)) | |
def get_remote(self, name): | |
""" | |
Return git.Remote reference of local_repo remote repository named name | |
:param name: str | |
Name of the remote reference in local repo | |
:return: git.Remote | |
Remote reference | |
""" | |
self._check_local_repo_exists() | |
for remo in self.local_repo.remotes: | |
if remo.name == name: | |
return remo | |
return None | |
def has_remote(self, name): | |
""" | |
Return True if local_repo has a remote repository named name | |
:param name: str | |
Name of the remote reference in local repo | |
:return: bool | |
""" | |
return self.get_remote(name) is not None | |
def rm_remote(self, name): | |
""" | |
Return git.Remote reference of local_repo remote repository named name | |
:param name: str | |
Name of the remote reference in local repo | |
:return: git.Remote | |
Remote reference | |
""" | |
self._check_local_repo_exists() | |
try: | |
git.Remote.rm(self.local_repo, name) | |
except git.GitCommandError as gce: | |
raise GitCommandError(str(gce)) | |
def push(self, remote_name='origin', *args): | |
""" | |
Push the modifications made in local_repo to the remote named name. | |
:param remote_name: str | |
Name of the remote repository where to push. | |
:param args: Additional arguments to be passed to git-push | |
:return: IterableList(PushInfo, ...) | |
iterable list of PushInfo instances, each | |
one informing about an individual head which had been updated on the | |
remote side. | |
If the push contains rejected heads, these will have the PushInfo.ERROR | |
bit set in their flags. | |
If the operation fails completely, the length of the returned | |
IterableList will be null. | |
""" | |
self._check_local_repo_exists() | |
remote = self.get_remote(remote_name) | |
if remote is not None: | |
push_infos = remote.push(*args) | |
return push_infos | |
else: | |
log.error("Could not find remote '{0}' in " | |
"repo in {1}.".format(remote_name, self.path)) | |
def clear(self): | |
""" | |
Clears self local_repo related variables and deletes all files of the | |
local repo | |
""" | |
self.clean_files() | |
self.local_repo = None | |
self.path = None | |
def _check_local_repo_exists(self): | |
""" | |
Raises an exception if self.local_repo is None | |
""" | |
if self.local_repo is None: | |
msg = 'Local repository for {0} is not set.'.format(self.name) | |
raise GitLocalRepoNotSet(msg) | |
def _update(self, checkout=False): | |
""" | |
Update member variables related to local_repo | |
""" | |
self.path = self.local_repo.working_dir | |
if checkout: | |
self.checkout(self.tag) | |
def clean_files(self): | |
""" | |
Recursively delete repository folder | |
""" | |
if os.path.exists(self.path): | |
os.removedirs(self.path) | |
class GitRepositorySet(object): | |
def __init__(self, repo_lst=None): | |
self.repos = [] if repo_lst is None else repo_lst | |
@classmethod | |
def from_list(cls, repo_list): | |
""" | |
Creates a list of GitRepository from a list of tuples: (url, tag) | |
:param repo_list: list of 2-tuple | |
[(url, tag), ...] | |
:return | |
GitRepositorySet | |
""" | |
repos = [] | |
for repo in repo_list: | |
url = repo[0] | |
tag = repo[1] | |
if not tag: | |
tag = None | |
repos.append(GitRepository(url, tag=tag)) | |
return cls(repos) | |
@classmethod | |
def from_dict(cls, repo_dict): | |
""" | |
Creates a list of GitRepository from a dict of dict: | |
{name: {'url': url, 'tag': tag}} | |
:param repo_dict: dict | |
:return | |
GitRepositorySet | |
""" | |
repos = [] | |
for repo in repos: | |
name = repo | |
url = repos[repo]['url'] | |
tag = repos[repo].get('tag', None) | |
repos.append(GitRepository(url, name=name, tag=tag)) | |
return cls(repos) | |
def __iter__(self): | |
return self.repos.__iter__() | |
def __next__(self): | |
return self.repos.__next__() | |
def next(self): | |
return self.repos.__next__() | |
def __getitem__(self, item): | |
if hasattr(self.repos, '__getitem__'): | |
return self.repos[item] | |
else: | |
log.error('Repository set has no __getitem__ implemented.') | |
return None | |
class GitRemoteURL(object): | |
""" | |
Manages the string of a git remote URL | |
""" | |
def __init__(self, url, name=None): | |
self.url = url | |
self.name = self.name() if name is None else name | |
def name(self): | |
return self.url.split('/')[-1].replace('.git', '') | |
@property | |
def is_ssh(self): | |
if self.url.find('@') > -1: | |
return True | |
else: | |
return False | |
@property | |
def is_http(self): | |
return not self.is_ssh | |
def __repr__(self): | |
return self.url | |
class GitlabProjectManager(object): | |
""" | |
This class manages a GitRepository with local copy and | |
a GitLabConnection. | |
It can create new projects in the Gilab Server and push/update source to it | |
""" | |
def __init__(self, gitlab_conn, url=None, name=None, tag=None, | |
work_dir=None): | |
""" | |
:param gitlab_conn: GitlabConnection | |
:param url: str | |
:param name: str | |
:param tag: str | |
:param work_dir: str | |
""" | |
self.git_repo = None | |
self.work_dir = '/tmp' if work_dir is None else work_dir | |
if url is not None: | |
self.set_repository(url, name, tag) | |
self.gitlab = gitlab_conn | |
self.git_remote_name = 'gitlab' | |
def set_repository(self, url, name, tag): | |
""" | |
:param url: str | |
URL to the repository | |
:param name: str | |
Name of the repository | |
:param tag: str | |
Version or tag required for this repository | |
""" | |
self.git_repo = GitRepository(url, name, tag) | |
def set_repository_from_repo(self, repo): | |
""" | |
:param repo: GitRepository | |
""" | |
self.git_repo = repo | |
def clone(self): | |
""" | |
Clones the remote git URL to work_dir | |
""" | |
self.git_repo.clone(self.work_dir) | |
def create(self, name=None, namespace_id=None, | |
get_if_exists=False, args=None): | |
""" | |
Pushes the cloned repository to the gitlab server | |
:param name: str | |
Name of the Gitlab project | |
:param namespace_id: int or str | |
Identification int or string of the project namespace | |
:param get_if_exists: bool | |
If True will return the project if it already exists | |
:params args: dict | |
See python-gitlab create project arguments | |
:param name: str | |
:param namespace_id: int | |
:param description: str | |
:param default_branch: str | |
:param public: bool, setting to True is the same as visibility_level: 20 | |
:param visibility_level: int, choices={'0', '10', '20'} | |
:param snippets_enabled: bool | |
:param wiki_enabled: bool | |
:param merge_requests_enabled: bool | |
:param issues_enabled: bool | |
:param wall_enabled: bool | |
:return gitlab.Project | |
""" | |
if name is None: | |
name = args.get('name', self.git_repo.name) | |
if namespace_id is None: | |
try: | |
namespace_id = args.get('namespace_id', | |
self.gitlab.get_current_user_id()) | |
except Exception as exc: | |
raise GitlabUserNotFound(str(exc)) | |
#get the int namespace_id if it is a string | |
if isinstance(namespace_id, str): | |
namespace_name = namespace_id | |
namespace_id = self.get_namespace_id(namespace_name) | |
#check if the project exists in gitlab, if not, we create it | |
try: | |
ns_name = self.gitlab.get_project_namespace_name(name, | |
namespace_id) | |
except GitlabProjectNotFound as gpnf: | |
ns_name = None | |
pass | |
#if the project exists | |
if ns_name is not None: | |
msg = 'Project {0}/{1} already exists in ' \ | |
'Gitlab server.'.format(ns_name, name) | |
log.info(msg) | |
if get_if_exists: | |
return self.gitlab.get_project(name, namespace_id) | |
else: | |
raise GitlabProjectAlreadyExists(msg) | |
#the project does not exist in Gitlab, let's create it | |
args['name'] = name | |
if namespace_id: | |
args['namespace_id'] = namespace_id | |
try: | |
gl_proj = self.gitlab.create_new_project(args) | |
log.info('Project {0}/{1} created in ' | |
'Gitlab server: {2}.'.format(gl_proj.namespace.name, | |
name, | |
gl_proj.ssh_url_to_repo)) | |
return gl_proj | |
except Exception as exc: | |
raise LoggedError(str(exc)) | |
def get_namespace_id(self, namespace_name): | |
""" | |
:param namespace_name: | |
:return: | |
""" | |
if not namespace_name: | |
return self.gitlab.get_current_user_id() | |
namespace_id = self.gitlab.get_namespace_id(namespace_name) | |
if namespace_id is None: | |
msg = 'Could not find namespace {0} in ' \ | |
'Gitlab server'.format(namespace_id) | |
raise GitlabNamespaceNotFound(msg) | |
return namespace_id | |
def get_default_branch(self, proj_name, proj_namespace_id=None): | |
""" | |
:param proj_name: | |
:param proj_namespace_id: | |
:return: | |
""" | |
try: | |
proj = self.gitlab.get_project(proj_name, proj_namespace_id) | |
except GitlabProjectNotFound as exc: | |
return None | |
if proj.default_branch is None: | |
return None | |
return proj.default_branch | |
def _prepare_to_push(self, proj_name, proj_namespace_id, | |
remote_name=None): | |
""" | |
:param proj_name: | |
:param proj_namespace_id: | |
:param remote_name: | |
:return: | |
""" | |
try: | |
proj = self.gitlab.get_project(proj_name, proj_namespace_id) | |
except GitlabProjectNotFound as exc: | |
raise exc | |
if self.git_repo is None: | |
raise GitRepositorySet('Repository has not been set for project ' | |
'{0}/{1}.'.format(proj.namespace.name, | |
proj.name)) | |
if not self.git_repo.has_working_copy(): | |
#clone the repo in self.working_dir/proj_name | |
repo_path = os.path.join(self.work_dir, proj_name) | |
self.git_repo.clone(repo_path) | |
#check if the remote is available in the repository | |
if remote_name is None: | |
remote_name = self.git_remote_name | |
if self.git_repo.has_remote(remote_name): | |
self.git_repo.rm_remote(remote_name) | |
#add the gitlab remote to the local copy | |
self.git_repo.add_remote(remote_name, proj.ssh_url_to_repo) | |
return proj, self.git_repo | |
def _push(self, repo, remote_name=None, *push_args): | |
""" | |
:param repo: GitRepository | |
:param remote_name: str | |
Name of the remote to push to | |
:param push_args: Additional arguments to be passed to git-push | |
:return: IterableList(PushInfo, ...) | |
iterable list of PushInfo instances, each | |
one informing about an individual head which had been updated on the | |
remote side. | |
If the push contains rejected heads, these will have the PushInfo.ERROR | |
bit set in their flags. | |
If the operation fails completely, the length of the returned | |
IterableList will be null. | |
""" | |
#push the contents to the gitlab remote | |
if remote_name is None: | |
remote_name = self.git_remote_name | |
try: | |
infos = repo.push(remote_name, *push_args) | |
for info in infos: | |
log.info('Push info: {0} {1} {2} {3}:' | |
' {4}.'.format(info.flags, | |
info.local_ref, | |
info.remote_ref, | |
info.old_commit, | |
info.summary)) | |
return infos | |
except: | |
raise GitCommandError('Could not push from {0} ' | |
'to {1}'.format(repo.path, | |
remote_name)) | |
def push_tag(self, proj_name, proj_namespace_id, | |
remote_name=None, tag_name=None): | |
""" | |
:param proj_name: str | |
:param proj_namespace_id: str or int | |
:param remote_name: str | |
Name of the remote to push to | |
:param tag_name: str | |
Name of the tag or branch to be pushed to the project | |
:return: IterableList(PushInfo, ...) | |
iterable list of PushInfo instances, each | |
one informing about an individual head which had been updated on the | |
remote side. | |
If the push contains rejected heads, these will have the PushInfo.ERROR | |
bit set in their flags. | |
If the operation fails completely, the length of the returned | |
IterableList will be null. | |
""" | |
try: | |
proj, repo = self._prepare_to_push(proj_name, proj_namespace_id, | |
remote_name) | |
except: | |
return None | |
if tag_name is None: | |
tag_name = 'master' | |
#self.git_remote.commit(msg='First commit.') | |
log.info('Pushing {0}:{4} to {1}/{2} created in ' | |
'Gitlab server {3}.'.format(repo.path, | |
proj.namespace.name, | |
proj.name, | |
proj.ssh_url_to_repo, | |
tag_name)) | |
return self._push(repo, remote_name, tag_name) | |
def push_master(self, proj_name, proj_namespace_id, remote_name=None): | |
""" | |
:param proj_name: str | |
:param proj_namespace_id: str or int | |
:param remote_name: str | |
Name of the remote to push to | |
:return: IterableList(PushInfo, ...) | |
iterable list of PushInfo instances, each | |
one informing about an individual head which had been updated on the remote | |
side. | |
If the push contains rejected heads, these will have the PushInfo.ERROR bit set | |
in their flags. | |
If the operation fails completely, the length of the returned IterableList will | |
be null. | |
""" | |
return self.push_tag(proj_name, proj_namespace_id, | |
remote_name, 'master') | |
def does_repo_match_remote(self, repo_path): | |
""" | |
Returns True if the contents of the repo_path folder matches | |
the configuration version of self.git_remote | |
:param repo_path: str | |
Path to a local copy of a repo | |
:return: | |
""" | |
if self.git_repo is None: | |
raise GitLocalRepoNotSet('Repository not set.') | |
remote = GitRepository(self.git_repo.url, self.git_repo.name, | |
self.git_repo.tag) | |
remote.set_local_repo(repo_path) | |
return remote.has_working_copy(check_head_tag=True) | |
def exists_in_gitlab(self, name, namespace_id=None): | |
""" | |
Return True if the project with name in the namespace_id exists, | |
False otherwise. | |
:param name: str | |
:param namespace_id: str or int | |
:return bool | |
""" | |
return self.gitlab.exists_project(name, namespace_id) | |
def push_project(self, repo, namespace_id=None, creation_args=None): | |
""" | |
:param repo: | |
:return: | |
""" | |
#check if namespace exists | |
if not self.gitlab.has_namespace(namespace_id): | |
raise GitlabNamespaceNotFound('Namespace {0} does not exits in ' | |
'Gitlab server {1}.'.format(namespace_id, | |
self.gitlab.gitlab_url)) | |
#try to get the project from Gitlab Server | |
try: | |
proj = self.gitlab.get_project(repo.name, | |
namespace_id=namespace_id) | |
except: | |
pass | |
#add repo to self | |
self.set_repository_from_repo(repo) | |
#check if there is a local copy of the repository | |
#self.set_repository_from_repo(repo) | |
repo_path = os.path.join(work_dir, repo.name) | |
has_been_pulled = False | |
while os.path.exists(repo_path): | |
repo.set_local_repo(repo_path) | |
if repo.has_working_copy(): | |
try: | |
log.info('Pulling repo {0} from ' | |
'{1} to {2}'.format(repo.name, repo.url, | |
repo_path)) | |
repo.pull() | |
has_been_pulled = True | |
break | |
except: | |
log.error('Could not pull repo ' | |
'{0} in {1}'.format(repo.name, repo.path)) | |
repo_path += '+' | |
#clone the repo from the original URL | |
if not has_been_pulled: | |
log.info('Clonning repo {0} from ' | |
'{1} to {2}'.format(repo.name, repo.url, repo_path)) | |
repo.clone(repo_path, mkdir=True) | |
#check if the gitlab project | |
if not self.exists_in_gitlab(repo.name, namespace_id): | |
self.create(name=repo.name, namespace_id=namespace_id, | |
args=creation_args) | |
if self.get_default_branch(repo.name, namespace_id) is None: | |
self.push_master(proj_name=repo.name, | |
proj_namespace_id=namespace_id) | |
self.push_tag(proj_name=repo.name, | |
proj_namespace_id=namespace_id, | |
tag_name=repo.tag) | |
return self.gitlab.get_project(repo.name, namespace_id=namespace_id) | |
def clean_files(self): | |
""" | |
Removes the cloned repo files in self.work_dir | |
""" | |
self.git_repo.clear() | |
if __name__ == '__main__': | |
import socket | |
hn = socket.gethostname() | |
if hn == 'finn': | |
work_dir = os.path.expanduser('~/Software') | |
elif hn == 'corsair' or hn == 'buccaneer': | |
work_dir = os.path.expanduser('~/Desktop/repos') | |
#create a default gitlab connection through config files | |
#read python-gitlab for configuration help | |
glc = GitlabConnection() | |
gitlab_mgr = GitlabProjectManager(glc, work_dir=work_dir) | |
if isinstance(repos, set) or isinstance(repos, list): | |
repo_set = GitRepositorySet.from_list(repos) | |
elif isinstance(repos, dict): | |
repo_set = GitRepositorySet.from_dict(repos) | |
#test | |
namespace_id = 6 #bcc_library | |
creation_args = {'visibility_level': 10, | |
'snippets_enabled': True, | |
'wiki_enabled': True, | |
'merge_requests_enabled': False, | |
'issues_enabled': True, | |
'wall_enabled': False} | |
#creation_args['namespace_id'] = namespace_id | |
#creation_args['name'] = repo.name | |
for repo in repo_set: | |
try: | |
proj = gitlab_mgr.push_project(repo, namespace_id, creation_args) | |
except Exception as exc: | |
log.error(str(exc)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment