diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index 4c5f595..33c24d6 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -89,6 +89,7 @@ class APIERROR(enum.Enum): ENOTMAINADMIN = 'Only the main admin can set the main admin of a project' EMODIFYPROJECTNOTALLOWED = 'You are not allowed to modify this project' EINVALIDPERPAGEVALUE = 'The per_page value must be between 1 and 100' + EGITERROR = 'An error occured during a git operation' def get_authorized_api_project(SESSION, repo, user=None, namespace=None): @@ -439,6 +440,7 @@ def api(): api_modify_project_doc = load_doc(project.api_modify_project) api_fork_project_doc = load_doc(project.api_fork_project) api_generate_acls_doc = load_doc(project.api_generate_acls) + api_new_branch_doc = load_doc(project.api_new_branch) issues = [] if pagure.APP.config.get('ENABLE_TICKETS', True): @@ -503,7 +505,8 @@ def api(): api_project_watchers_doc, api_git_branches_doc, api_fork_project_doc, - api_generate_acls_doc + api_generate_acls_doc, + api_new_branch_doc ], issues=issues, requests=[ diff --git a/pagure/api/project.py b/pagure/api/project.py index 17f0c7d..5b69215 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -11,6 +11,8 @@ import flask from sqlalchemy.exc import SQLAlchemyError +from six import string_types +from pygit2 import GitError import pagure import pagure.exceptions @@ -1152,3 +1154,84 @@ def api_generate_acls(repo, username=None, namespace=None): jsonout = flask.jsonify(output) return jsonout + + +@API.route('//git/branch', methods=['POST']) +@API.route('///git/branch', methods=['POST']) +@API.route('/fork///git/branch', methods=['POST']) +@API.route('/fork////git/branch', + methods=['POST']) +@api_login_required(acls=['modify_project']) +@api_method +def api_new_branch(repo, username=None, namespace=None): + """ + Create a new git branch on a project + ------------------------------------ + Create a new git branch on a project + + :: + + POST /api/0/rpms/python-requests/git/branch + + + Input + ^^^^^ + + +------------------+---------+--------------+---------------------------+ + | Key | Type | Optionality | Description | + +==================+=========+==============+===========================+ + | ``branch`` | string | Mandatory | | A string of the branch | + | | | | to create. | + +------------------+---------+--------------+---------------------------+ + | ``from_branch`` | string | Optional | | A string of the branch | + | | | | to branch off of. This | + | | | | defaults to "master". | + +------------------+---------+--------------+---------------------------+ + + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + 'message': 'Project branch was created' + } + + """ + project = get_authorized_api_project(SESSION, repo, namespace=namespace) + if not project: + raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) + + # Check if it's JSON or form data + if flask.request.headers.get('Content-Type') == 'application/json': + # Set force to True to ignore the mimetype. Set silent so that None is + # returned if it's invalid JSON. + args = flask.request.get_json(force=True, silent=True) or {} + else: + args = flask.request.form + + branch = args.get('branch') + from_branch = args.get('from_branch') + from_commit = args.get('from_commit') + + if from_branch and from_commit: + raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) + + if not branch or not isinstance(branch, string_types) or \ + (from_branch and not isinstance(from_branch, string_types)) or \ + (from_commit and not isinstance(from_commit, string_types)): + raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) + + try: + pagure.lib.git.new_git_branch(project, branch, from_branch=from_branch, + from_commit=from_commit) + except GitError: # pragma: no cover + raise pagure.exceptions.APIError(400, error_code=APIERROR.EGITERROR) + except pagure.exceptions.PagureException as error: + raise pagure.exceptions.APIError( + 400, error_code=APIERROR.ENOCODE, error=str(error)) + + output = {'message': 'Project branch was created'} + jsonout = flask.jsonify(output) + return jsonout diff --git a/pagure/lib/git.py b/pagure/lib/git.py index 13f3e92..85a6dc3 100644 --- a/pagure/lib/git.py +++ b/pagure/lib/git.py @@ -1657,3 +1657,32 @@ def get_git_branches(project): repo_path = pagure.get_repo_path(project) repo_obj = pygit2.Repository(repo_path) return repo_obj.listall_branches() + + +def new_git_branch(project, branch, from_branch=None, from_commit=None): + ''' Create a new git branch on the project + :arg project: The Project instance to get the branches for + :arg from_branch: The branch to branch off of + ''' + if not from_branch and not from_commit: + from_branch = 'master' + repo_path = pagure.get_repo_path(project) + repo_obj = pygit2.Repository(repo_path) + branches = repo_obj.listall_branches() + + if from_branch: + if from_branch not in branches: + raise pagure.exceptions.PagureException( + 'The "{0}" branch does not exist'.format(from_branch)) + parent = get_branch_ref(repo_obj, from_branch).get_object() + else: + if from_commit not in repo_obj: + raise pagure.exceptions.PagureException( + 'The commit "{0}" does not exist'.format(from_commit)) + parent = repo_obj[from_commit] + + if branch not in branches: + repo_obj.create_branch(branch, parent) + else: + raise pagure.exceptions.PagureException( + 'The branch "{0}" already exists'.format(branch)) diff --git a/tests/test_pagure_flask_api_project.py b/tests/test_pagure_flask_api_project.py index 12dd514..302772e 100644 --- a/tests/test_pagure_flask_api_project.py +++ b/tests/test_pagure_flask_api_project.py @@ -2279,5 +2279,124 @@ class PagureFlaskApiProjecttests(tests.Modeltests): } self.assertEqual(data, expected_output) + def test_api_new_git_branch(self): + """ Test the api_new_branch method of the flask api """ + tests.create_projects(self.session) + repo_path = os.path.join(self.path, 'repos') + tests.create_projects_git(repo_path, bare=True) + tests.add_content_git_repo(os.path.join(repo_path, 'test.git')) + tests.create_tokens(self.session, project_id=None) + tests.create_tokens_acl( + self.session, 'aaabbbcccddd', 'modify_project') + headers = {'Authorization': 'token aaabbbcccddd'} + args = {'branch': 'test123'} + output = self.app.post('/api/0/test/git/branch', headers=headers, + data=args) + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + expected_output = { + 'message': 'Project branch was created', + } + self.assertEqual(data, expected_output) + git_path = os.path.join(self.path, 'repos', 'test.git') + repo_obj = pygit2.Repository(git_path) + self.assertIn('test123', repo_obj.listall_branches()) + + + def test_api_new_git_branch_json(self): + """ Test the api_new_branch method of the flask api """ + tests.create_projects(self.session) + repo_path = os.path.join(self.path, 'repos') + tests.create_projects_git(repo_path, bare=True) + tests.add_content_git_repo(os.path.join(repo_path, 'test.git')) + tests.create_tokens(self.session, project_id=None) + tests.create_tokens_acl( + self.session, 'aaabbbcccddd', 'modify_project') + headers = {'Authorization': 'token aaabbbcccddd', + 'Content-Type': 'application/json'} + args = {'branch': 'test123'} + output = self.app.post('/api/0/test/git/branch', headers=headers, + data=json.dumps(args)) + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + expected_output = { + 'message': 'Project branch was created', + } + self.assertEqual(data, expected_output) + git_path = os.path.join(self.path, 'repos', 'test.git') + repo_obj = pygit2.Repository(git_path) + self.assertIn('test123', repo_obj.listall_branches()) + + def test_api_new_git_branch_from_branch(self): + """ Test the api_new_branch method of the flask api """ + tests.create_projects(self.session) + repo_path = os.path.join(self.path, 'repos') + tests.create_projects_git(repo_path, bare=True) + tests.add_content_git_repo(os.path.join(repo_path, 'test.git')) + tests.create_tokens(self.session, project_id=None) + tests.create_tokens_acl( + self.session, 'aaabbbcccddd', 'modify_project') + git_path = os.path.join(self.path, 'repos', 'test.git') + repo_obj = pygit2.Repository(git_path) + parent = pagure.lib.git.get_branch_ref(repo_obj, 'master').get_object() + repo_obj.create_branch('dev123', parent) + headers = {'Authorization': 'token aaabbbcccddd'} + args = {'branch': 'test123', 'from_branch': 'dev123'} + output = self.app.post('/api/0/test/git/branch', headers=headers, + data=args) + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + expected_output = { + 'message': 'Project branch was created', + } + self.assertEqual(data, expected_output) + self.assertIn('test123', repo_obj.listall_branches()) + + def test_api_new_git_branch_already_exists(self): + """ Test the api_new_branch method of the flask api when branch already + exists """ + tests.create_projects(self.session) + repo_path = os.path.join(self.path, 'repos') + tests.create_projects_git(repo_path, bare=True) + tests.add_content_git_repo(os.path.join(repo_path, 'test.git')) + tests.create_tokens(self.session, project_id=None) + tests.create_tokens_acl( + self.session, 'aaabbbcccddd', 'modify_project') + headers = {'Authorization': 'token aaabbbcccddd'} + args = {'branch': 'master'} + output = self.app.post('/api/0/test/git/branch', headers=headers, + data=args) + self.assertEqual(output.status_code, 400) + data = json.loads(output.data) + expected_output = { + 'error': 'The branch "master" already exists', + 'error_code': 'ENOCODE' + } + self.assertEqual(data, expected_output) + + def test_api_new_git_branch_from_commit(self): + """ Test the api_new_branch method of the flask api """ + tests.create_projects(self.session) + repos_path = os.path.join(self.path, 'repos') + tests.create_projects_git(repos_path, bare=True) + git_path = os.path.join(repos_path, 'test.git') + tests.add_content_git_repo(git_path) + tests.create_tokens(self.session, project_id=None) + tests.create_tokens_acl( + self.session, 'aaabbbcccddd', 'modify_project') + repo_obj = pygit2.Repository(git_path) + from_commit = repo_obj.revparse_single('HEAD').oid.hex + headers = {'Authorization': 'token aaabbbcccddd'} + args = {'branch': 'test123', 'from_commit': from_commit} + output = self.app.post('/api/0/test/git/branch', headers=headers, + data=args) + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + expected_output = { + 'message': 'Project branch was created', + } + self.assertEqual(data, expected_output) + self.assertIn('test123', repo_obj.listall_branches()) + if __name__ == '__main__': unittest.main(verbosity=2)