示例#1
0
 def test_authentication_error_is_raised_with_invalid_private_token(self):
     """
     Assert that a `401 Unauthorized` exception is raised provided the user
     enters invalid credentials
     """
     with self.assertRaises(GitLabServerError) as e:
         gitlab = GitLab(host='gitlab.com', private_token='foobar')
         gitlab.get_current_user()
     exception = e.exception
     self.assertEqual(exception.status_code, 401)
     self.assertEqual(exception.reason, 'Unauthorized')
    def updatePullRequests(self):
        print 'Updating pull requests from GitLab...'

        if not self.client:
            self.client = GitLab("https://gitlab.itseez.com/api/v3", userAgent=userAgent, private_token=self.gitlab_private_token, async=True)
        pullrequests = yield self.client.projects(self.project_id).merge_requests.get(state='opened', per_page=100)
        if self.client.status == 304:
            print "GitLab merge requests are not changed"
            defer.returnValue(None)
        elif self.client.status == 200:
            projects_info = {}
            prs = []
            for pullrequest in pullrequests:
                try:
                    newAPI = False
                    pr = {}
                    if not pullrequest['state'] in ['opened', 'reopened']:
                        continue
                    pr['id'] = pullrequest['iid']
                    pr['branch'] = pullrequest['target_branch']
                    pr['author'] = pullrequest['author']['username']
                    pr['assignee'] = pullrequest['assignee']['username'] if pullrequest['assignee'] else None
                    if newAPI:
                        if not projects_info.has_key(pullrequest['source_project_id']):
                            projects_info[pullrequest['source_project_id']] = yield self.client.projects(pullrequest['source_project_id']).get()
                        pr['head_user'] = projects_info[pullrequest['source_project_id']]['owner']['username']
                        pr['head_repo'] = projects_info[pullrequest['source_project_id']]['path_with_namespace']
                    else:  # Old API
                        pr['head_user'] = self.username
                        pr['head_repo'] = '%s/%s' % (self.username, self.repo)
                    pr['head_branch'] = pullrequest['source_branch']
                    if newAPI:
                        branch_info = yield self.client.projects(pullrequest['source_project_id']).repository.branches(pullrequest['source_branch']).get()
                    else:
                        branch_info = yield self.client.projects(pullrequest['project_id']).repository.branches(pullrequest['source_branch']).get()
                    pr['head_sha'] = branch_info['commit']['id']
                    pr['title'] = pullrequest['title']
                    pr['description'] = pullrequest.get('description', pullrequest['title'])
                    if pr['description'] is None:
                        pr['description'] = ''
                    prs.append(pr)
                except:
                    f = failure.Failure()
                    log.err(f, 'while adding merge request')
                    pass
            defer.returnValue(prs)
        raise Exception('invalid status', self.client.status)
class GitLabContext(pullrequest.context.Context):

    updatePullRequestsDelay = 30

    name = 'GitLab Pull Requests'
    dbname = 'pullrequests_gitlab'

    urlpath = 'pullrequests_gl'

    builders = dict(
        linux=dict(name='Linux x64', builders=['precommit_linux64'], order=100),
        windows=dict(name='Win x64', builders=['precommit_windows64'], order=200),
        win32=dict(name='Win 32', builders=['precommit_windows32'], order=250),
        macosx=dict(name='Mac', builders=['precommit_macosx'], order=300),
        android=dict(name='Android', builders=['precommit_android'], order=400),
    )

    username = '' # TODO
    repo = '' # TODO


    project_id = -1  # curl --header "PRIVATE-TOKEN: xXxXxXxXxXx" "https://gitlab.itseez.com/api/v3/projects/"
    gitlab_private_token = os.environ['GITLAB_APIKEY']  # check deploy/apikeys.sh

    client = None

    @defer.inlineCallbacks
    def updatePullRequests(self):
        print 'Updating pull requests from GitLab...'

        if not self.client:
            self.client = GitLab("https://gitlab.itseez.com/api/v3", userAgent=userAgent, private_token=self.gitlab_private_token, async=True)
        pullrequests = yield self.client.projects(self.project_id).merge_requests.get(state='opened', per_page=100)
        if self.client.status == 304:
            print "GitLab merge requests are not changed"
            defer.returnValue(None)
        elif self.client.status == 200:
            projects_info = {}
            prs = []
            for pullrequest in pullrequests:
                try:
                    newAPI = False
                    pr = {}
                    if not pullrequest['state'] in ['opened', 'reopened']:
                        continue
                    pr['id'] = pullrequest['iid']
                    pr['branch'] = pullrequest['target_branch']
                    pr['author'] = pullrequest['author']['username']
                    pr['assignee'] = pullrequest['assignee']['username'] if pullrequest['assignee'] else None
                    if newAPI:
                        if not projects_info.has_key(pullrequest['source_project_id']):
                            projects_info[pullrequest['source_project_id']] = yield self.client.projects(pullrequest['source_project_id']).get()
                        pr['head_user'] = projects_info[pullrequest['source_project_id']]['owner']['username']
                        pr['head_repo'] = projects_info[pullrequest['source_project_id']]['path_with_namespace']
                    else:  # Old API
                        pr['head_user'] = self.username
                        pr['head_repo'] = '%s/%s' % (self.username, self.repo)
                    pr['head_branch'] = pullrequest['source_branch']
                    if newAPI:
                        branch_info = yield self.client.projects(pullrequest['source_project_id']).repository.branches(pullrequest['source_branch']).get()
                    else:
                        branch_info = yield self.client.projects(pullrequest['project_id']).repository.branches(pullrequest['source_branch']).get()
                    pr['head_sha'] = branch_info['commit']['id']
                    pr['title'] = pullrequest['title']
                    pr['description'] = pullrequest.get('description', pullrequest['title'])
                    if pr['description'] is None:
                        pr['description'] = ''
                    prs.append(pr)
                except:
                    f = failure.Failure()
                    log.err(f, 'while adding merge request')
                    pass
            defer.returnValue(prs)
        raise Exception('invalid status', self.client.status)


    def getListOfAutomaticBuilders(self, pr):
        if pr.description is not None and '**WIP**' in pr.description:
            return []
        buildersList = ['linux', 'windows', 'win32', 'macosx', 'android']
        return buildersList

    def getBuildProperties(self, pr, b, properties, sourcestamps):
        prid = pr.prid

        properties.setProperty('branch', pr.branch, 'Pull request')
        properties.setProperty('head_sha', pr.head_sha, 'Pull request')
        properties.setProperty('pullrequest', prid, 'Pull request')
        if b.isPerf:
            regressionTestFilter = self.extractRegressionTestFilter(pr.description)
            if regressionTestFilter is not None:
                properties.setProperty('regression_test_filter', regressionTestFilter, 'Pull request')
            else:
                print 'ERROR: Can\'t schedule perf precommit build without regression test filter. Use check_regression parameter'
                defer.returnValue(False)

        if pr.description is None or '**WIP**' in pr.description:
            self.pushBuildProperty(properties, pr.description, 'test[s]?_filter[s]?', 'test_filter')

        sourcestamps.append(dict(
            codebase='code',
            repository='ssh://[email protected]/xxx/yyy.git',
            branch=pr.branch))

        sourcestamps.append(dict(
            codebase='code_merge',
            repository='ssh://[email protected]/xxx/yyy.git',
            branch=pr.head_branch,
            revision=pr.head_sha))

        return True

    def getWebAddressPullRequest(self, pr):
        return 'https://gitlab.itseez.com/xxx/yyy/merge_requests/%d' % (pr.prid)

    def getWebAddressPerfRegressionReport(self, pr):
        return None
示例#4
0
def main(argv=None):
    '''
    Process the command line arguments and create the JSON dump.

    :param argv: List of arguments, as if specified on the command-line.
                 If None, ``sys.argv[1:]`` is used instead.
    :type argv: list of str
    '''
    # Get command line arguments
    parser = argparse.ArgumentParser(
        description="Export all users/issues from GitLab to JIRA JSON format.",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        conflict_handler='resolve')
    parser.add_argument('gitlab_url',
                        help='The full URL to your GitLab instance.')
    parser.add_argument('-d',
                        '--date_filter',
                        help='Only include issues, notes, etc. created after\
                              the specified date. Expected format is \
                              YYYY-MM-DD',
                        type=get_datetime,
                        default='1970-01-01')
    parser.add_argument('-e',
                        '--include_empty',
                        help='Include projects in output that do not have any\
                              issues.',
                        action='store_true')
    parser.add_argument('-i',
                        '--ignore_list',
                        help='List of project names to exclude from dump.',
                        type=argparse.FileType('r'))
    parser.add_argument('-p',
                        '--password',
                        help='The password to use to authenticate if token is \
                              not specified. If password and token are both \
                              unspecified, you will be prompted to enter a \
                              password.')
    parser.add_argument('-P',
                        '--page_size',
                        help='When retrieving result from GitLab, how many \
                              results should be included in a given page?.',
                        type=int,
                        default=20)
    parser.add_argument('-s',
                        '--verify_ssl',
                        help='Enable SSL certificate verification',
                        action='store_true')
    parser.add_argument('-t',
                        '--token',
                        help='The private GitLab API token to use for \
                              authentication. Either this or username and \
                              password must be set.')
    parser.add_argument('-u',
                        '--username',
                        help='The username to use for authentication, if token\
                              is unspecified.')
    parser.add_argument('-v',
                        '--verbose',
                        help='Print more status information. For every ' +
                        'additional time this flag is specified, ' +
                        'output gets more verbose.',
                        default=0,
                        action='count')
    parser.add_argument('--version',
                        action='version',
                        version='%(prog)s {0}'.format(__version__))
    args = parser.parse_args(argv)

    args.page_size = max(100, args.page_size)

    # Convert verbose flag to actually logging level
    log_levels = [logging.WARNING, logging.INFO, logging.DEBUG]
    log_level = log_levels[min(args.verbose, 2)]
    # Make warnings from built-in warnings module get formatted more nicely
    logging.captureWarnings(True)
    logging.basicConfig(format=('%(asctime)s - %(name)s - %(levelname)s - ' +
                                '%(message)s'),
                        level=log_level)

    # Setup authenticated GitLab instance
    if args.token:
        git = GitLab(args.gitlab_url,
                     token=args.token,
                     verify_ssl=args.verify_ssl)
    else:
        if not args.username:
            print('Username: '******'').strip()
        if not args.password:
            args.password = getpass.getpass('Password: '******'Creating project entries...', end="", file=sys.stderr)
    sys.stderr.flush()
    key_set = set()
    mentioned_users = set()
    if args.ignore_list is not None:
        ignore_list = {line.strip().lower() for line in args.ignore_list}
    else:
        ignore_list = {}
    for project in gen_all_results(git.getallprojects,
                                   per_page=args.page_size):
        proj_name_lower = project['name'].lower()
        if proj_name_lower not in ignore_list and project['issues_enabled']:
            project_issues = []
            for issue in gen_all_results(git.getprojectissues,
                                         project['id'],
                                         per_page=args.page_size):
                if args.date_filter < datetime.strptime(
                        issue['updated_at'], TIME_FORMAT):
                    project_issues.append(issue)
                else:
                    for note in git.getissuewallnotes(project['id'],
                                                      issue['id']):
                        if (args.date_filter < datetime.strptime(
                                note['created_at'], TIME_FORMAT)):
                            project_issues.append(issue)
                            break

            if project_issues or args.include_empty:
                jira_project = {}
                jira_project['name'] = project['name_with_namespace']
                key = project['name']
                if key.islower():
                    key = key.title()
                key = re.sub(r'[^A-Z]', '', key)
                if len(key) < 2:
                    key = re.sub(r'[^A-Za-z]', '',
                                 project['name'])[0:2].upper()
                added = False
                suffix = 65
                while key in key_set:
                    if not added:
                        key += 'A'
                    else:
                        suffix += 1
                        key = key[:-1] + chr(suffix)
                key_set.add(key)
                jira_project['key'] = key
                jira_project['description'] = md_to_wiki(
                    project['description'])
                # jira_project['created'] = project['created_at']
                jira_project['issues'] = []
                for issue in project_issues:
                    jira_issue = {}
                    jira_issue['externalId'] = issue['iid']
                    if issue['state'] == 'closed':
                        jira_issue['status'] = 'Closed'
                        jira_issue['resolution'] = 'Resolved'
                    else:
                        jira_issue['status'] = 'Open'

                    jira_issue['description'] = md_to_wiki(
                        issue['description'])
                    jira_issue['reporter'] = issue['author']['username']
                    mentioned_users.add(jira_issue['reporter'])
                    jira_issue['labels'] = issue['labels']
                    jira_issue['summary'] = issue['title']
                    if issue['assignee']:
                        jira_issue['assignee'] = issue['assignee']['username']
                        mentioned_users.add(jira_issue['assignee'])
                    jira_issue['issueType'] = 'Bug'
                    jira_issue['comments'] = []
                    # Get all comments/notes
                    for note in git.getissuewallnotes(project['id'],
                                                      issue['id']):
                        jira_note = {}
                        jira_note['body'] = md_to_wiki(note['body'])
                        jira_note['author'] = note['author']['username']
                        mentioned_users.add(jira_note['author'])
                        jira_note['created'] = note['created_at']
                        jira_issue['comments'].append(jira_note)
                    jira_project['issues'].append(jira_issue)

                output_dict['projects'].append(jira_project)
        print('.', end="", file=sys.stderr)
        sys.stderr.flush()

    print('\nCreating user entries...', end="", file=sys.stderr)
    sys.stderr.flush()
    for user in gen_all_results(git.getusers, per_page=args.page_size):
        # Only add users who are actually referenced in issues
        if user['username'] in mentioned_users:
            jira_user = {}
            jira_user['name'] = user['username']
            jira_user['fullname'] = user['name']
            jira_user['email'] = user['email']
            jira_user['groups'] = ['gitlab-users']
            jira_user['active'] = (user['state'] == 'active')
            output_dict['users'].append(jira_user)
        print('.', end="", file=sys.stderr)
        sys.stderr.flush()

    print('\nPrinting JSON output...', file=sys.stderr)
    sys.stderr.flush()
    print(json.dumps(output_dict, indent=4))
def main(argv=None):
    '''
    Process the command line arguments and create the JSON dump.

    :param argv: List of arguments, as if specified on the command-line.
                 If None, ``sys.argv[1:]`` is used instead.
    :type argv: list of str
    '''
    # Get command line arguments
    parser = argparse.ArgumentParser(
        description="Transfer all projects/repositories from GitLab to Stash. \
                     Note: This script assumes you have your SSH key \
                     registered with both GitLab and Stash.",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        conflict_handler='resolve')
    parser.add_argument('gitlab_url',
                        help='The full URL to your GitLab instance.')
    parser.add_argument('stash_url',
                        help='The full URL to your Stash instance.')
    parser.add_argument('-p',
                        '--password',
                        help='The password to use to authenticate if token is \
                              not specified. If password and token are both \
                              unspecified, you will be prompted to enter a \
                              password.')
    parser.add_argument('-P',
                        '--page_size',
                        help='When retrieving result from GitLab, how many \
                              results should be included in a given page?.',
                        type=int,
                        default=20)
    parser.add_argument('-s',
                        '--verify_ssl',
                        help='Enable SSL certificate verification',
                        action='store_true')
    parser.add_argument('-S',
                        '--skip_existing',
                        help='Do not update existing repositories and just \
                              skip them.',
                        action='store_true')
    parser.add_argument('-t',
                        '--token',
                        help='The private GitLab API token to use for \
                              authentication. Either this or username and \
                              password must be set.')
    parser.add_argument('-u',
                        '--username',
                        help='The username to use for authentication, if token\
                              is unspecified.')
    parser.add_argument('-v',
                        '--verbose',
                        help='Print more status information. For every ' +
                        'additional time this flag is specified, ' +
                        'output gets more verbose.',
                        default=0,
                        action='count')
    parser.add_argument('--version',
                        action='version',
                        version='%(prog)s {0}'.format(__version__))
    args = parser.parse_args(argv)

    args.page_size = max(100, args.page_size)

    # Convert verbose flag to actually logging level
    log_levels = [logging.WARNING, logging.INFO, logging.DEBUG]
    log_level = log_levels[min(args.verbose, 2)]
    # Make warnings from built-in warnings module get formatted more nicely
    logging.captureWarnings(True)
    logging.basicConfig(format=('%(asctime)s - %(name)s - %(levelname)s - ' +
                                '%(message)s'),
                        level=log_level)

    # Setup authenticated GitLab and Stash instances
    if args.token:
        git = GitLab(args.gitlab_url,
                     token=args.token,
                     verify_ssl=args.verify_ssl)
    else:
        git = None
    if not args.username:
        print('Username: '******'').strip()
    if not args.password:
        args.password = getpass.getpass('Password: '******'Retrieving existing Stash projects...', end="", file=sys.stderr)
    sys.stderr.flush()
    key_set = {proj['key'] for proj in stash.projects}
    stash_project_names = {proj['name'] for proj in stash.projects}
    names_to_keys = {proj['name']: proj['key'] for proj in stash.projects}
    print('done', file=sys.stderr)
    sys.stderr.flush()
    updated_projects = set()
    repo_to_slugs = {}
    failed_to_clone = set()
    cwd = os.getcwd()
    transfer_count = 0
    skipped_count = 0
    print('Processing GitLab projects...', file=sys.stderr)
    sys.stderr.flush()
    for project in gen_all_results(git.getallprojects,
                                   per_page=args.page_size):
        print('\n' + ('=' * 80) + '\n', file=sys.stderr)
        sys.stderr.flush()
        proj_name = project['namespace']['name']
        # Create Stash project if it doesn't already exist
        if proj_name not in stash_project_names:
            # Create Stash project key
            key = proj_name
            if key.islower():
                key = key.title()
            key = re.sub(r'[^A-Z]', '', key)
            if len(key) < 2:
                key = re.sub(r'[^A-Za-z]', '', proj_name)[0:2].upper()
            added = False
            suffix = 65
            while key in key_set:
                if not added:
                    key += 'A'
                else:
                    suffix += 1
                    key = key[:-1] + chr(suffix)
            key_set.add(key)

            # Actually add the project to Stash
            print('Creating Stash project "%s" with key %s...' %
                  (proj_name, key),
                  end="",
                  file=sys.stderr)
            sys.stderr.flush()
            stash.projects.create(key, proj_name)
            names_to_keys[proj_name] = key
            stash_project_names.add(proj_name)
            print('done', file=sys.stderr)
            sys.stderr.flush()
        else:
            key = names_to_keys[proj_name]

        stash_project = stash.projects[key]

        # Initialize maping from repository names to slugs for later
        if key not in repo_to_slugs:
            repo_to_slugs[key] = {
                repo['name']: repo['slug']
                for repo in stash_project.repos
            }

        # Create Stash-compatible name for repository
        # Repository names are limited to 128 characters.
        # They must start with a letter or number and may contain spaces,
        # hyphens, underscores and periods
        repo_name = project['name']
        if not repo_name[0].isalnum():
            repo_name = 'A ' + repo_name
        repo_name = re.sub(r'[^A-Za-z0-9 _.-]', ' ', repo_name)
        if len(repo_name) > 128:
            repo_name = repo_name[0:128]

        # Add repository to Stash project if it's not already there
        if repo_name not in repo_to_slugs[key]:
            print('Creating Stash repository "%s" in project "%s"...' %
                  (repo_name, proj_name),
                  end="",
                  file=sys.stderr)
            sys.stderr.flush()
            stash_repo = stash_project.repos.create(repo_name)
            repo_to_slugs[key][repo_name] = stash_repo['slug']
            print('done', file=sys.stderr)
            sys.stderr.flush()
        elif args.skip_existing:
            print('Skipping existing Stash repository "%s" in project "%s"' %
                  (repo_name, proj_name),
                  file=sys.stderr)
            sys.stderr.flush()
            skipped_count += 1
            continue
        else:
            print('Updating existing Stash repository "%s" in project "%s"' %
                  (repo_name, proj_name),
                  file=sys.stderr)
            sys.stderr.flush()
            repo_slug = repo_to_slugs[key][repo_name]
            stash_repo = stash_project.repos[repo_slug].get()

        for clone_link in stash_repo['links']['clone']:
            if clone_link['name'] == 'ssh':
                stash_repo_url = clone_link['href']
                break

        with tempfile.TemporaryDirectory() as temp_dir:
            # Clone repository to temporary directory
            print('\nCloning GitLab repository...', file=sys.stderr)
            sys.stderr.flush()
            try:
                subprocess.check_call([
                    'git', 'clone', '--mirror', project['ssh_url_to_repo'],
                    temp_dir
                ])
            except subprocess.CalledProcessError:
                print('Failed to clone GitLab repository. This usually when ' +
                      'it does not exist.',
                      file=sys.stderr)
                failed_to_clone.add(project['name_with_namespace'])
                skipped_count += 1
                continue
            os.chdir(temp_dir)

            # Check that repository is not empty
            try:
                subprocess.check_call(['git', 'log', '--format=oneline', '-1'],
                                      stdout=subprocess.DEVNULL,
                                      stderr=subprocess.DEVNULL)
            except subprocess.CalledProcessError:
                print('Repository is empty, so skipping push to Stash.',
                      file=sys.stderr)
                skipped_count += 1
            else:
                # Change remote to Stash and push
                print('\nPushing repository to Stash...', file=sys.stderr)
                sys.stderr.flush()
                subprocess.check_call(
                    ['git', 'remote', 'set-url', 'origin', stash_repo_url])
                subprocess.check_call(['git', 'push', '--mirror'])
                transfer_count += 1

            os.chdir(cwd)

        updated_projects.add(proj_name)

    print('\n' + ('=' * 35) + 'SUMMARY' + ('=' * 35), file=sys.stderr)
    print('{} repositories transferred.\n'.format(transfer_count),
          file=sys.stderr)
    print('{} repositories skipped.\n'.format(skipped_count), file=sys.stderr)
    print('Projects created/updated:', file=sys.stderr)
    for proj in sorted(updated_projects):
        print('\t' + proj, file=sys.stderr)
    print('Repositories that we could not clone:', file=sys.stderr)
    for repo_name in sorted(failed_to_clone):
        print('\t' + repo_name, file=sys.stderr)
示例#6
0
class GitLabTestCase(unittest.TestCase):
    def setUp(self):
        self.config = ConfigParser()
        self.config.read('~/.gitlab')
        self.gitlab = GitLab(host='gitlab.com')

    def test_authentication_error_is_raised_with_invalid_private_token(self):
        """
        Assert that a `401 Unauthorized` exception is raised provided the user
        enters invalid credentials
        """
        with self.assertRaises(GitLabServerError) as e:
            gitlab = GitLab(host='gitlab.com', private_token='foobar')
            gitlab.get_current_user()
        exception = e.exception
        self.assertEqual(exception.status_code, 401)
        self.assertEqual(exception.reason, 'Unauthorized')

    def test_private_token_http_header_exists(self):
        """
        Assert that the `PRIVATE-TOKEN` header exists in the HTTP request
        header and that it is not `None`
        """
        self.assertTrue('PRIVATE-TOKEN' in self.gitlab._session.headers)
        self.assertIsNotNone(self.gitlab._session.headers['PRIVATE-TOKEN'])

    def test_get_users_endpoint_with_query_params(self):
        """
        Assert that the result set returned with query params is as expected
        """
        users = self.gitlab.get_users(per_page=1)
        self.assertEqual(len(users), 1)

    def test_get_user_endpoint_with_invalid_id(self):
        """
        Assert that `400 Bad Request` is raised when retrieving a user with
        invalid ID
        """
        with self.assertRaises(GitLabServerError) as e:
            self.gitlab.get_user(id=-1)
        exception = e.exception
        self.assertEqual(exception.status_code, 400)
        self.assertEqual(exception.reason, 'Bad Request')

    def test_get_projects_endpoint_with_query_params(self):
        """
        Assert that the result set returned with query params is as expected
        """
        projects = self.gitlab.get_projects(sort='asc')
        format_spec = '%Y-%m-%dT%H:%M:%S.%fZ'
        # Convert ISO formatted `datetime` strings into `datetime` objects
        projects = [
            datetime.strptime(project['created_at'], format_spec)
            for project in projects
        ]
        # Need to create a copy to not modify the original `projects` list
        # object
        projects_copy = list(projects)
        # Assert that the `projects` are sorted in ascending order
        self.assertEqual(projects, sorted(projects_copy))
        projects = self.gitlab.get_projects(sort='desc')
        projects = [
            datetime.strptime(project['created_at'], format_spec)
            for project in projects
        ]
        # `reversed` function returns a generator object, need to convert to a
        # `list` object
        self.assertEqual(projects, list(reversed(projects_copy)))

    def test_namespace_decorator(self):
        """
        Assert that project namespaces are URL encoded
        (e.g. / is represented by %2F)
        """
        @namespace
        def foo(id=None):
            self.assertTrue('%2F' in id)
        foo.__call__(id='foo/bar')
示例#7
0
 def setUp(self):
     self.config = ConfigParser()
     self.config.read('~/.gitlab')
     self.gitlab = GitLab(host='gitlab.com')
示例#8
0
import os
from gitlab import GitLab

client = GitLab("https://gitlab.itseez.com/api/v3", "Test",  private_token=os.environ['GITLAB_APIKEY'])
res = client.projects().get()
print res