From f4b44a4cf5445b52986de88ed4e2f9252da766c0 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Apr 15 2016 15:02:14 +0000 Subject: [PATCH 1/8] Adjust README, mostly for testing. --- diff --git a/README.rst b/README.rst index d214646..348757f 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,9 @@ pag === -Command line tool for interacting with https://pagure.io +`pag` helps you win at `pagure.io `_. + +Intended to mimic the `hub `_ cli tool for `github.com `_. Usage ----- From d5988d6fc627b6980ca2f092dbfeeb9786330c5c Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Apr 15 2016 15:02:58 +0000 Subject: [PATCH 2/8] Adjust syntax. --- diff --git a/README.rst b/README.rst index 348757f..e8eb0cc 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ pag === -`pag` helps you win at `pagure.io `_. +``pag`` helps you win at `pagure.io `_. Intended to mimic the `hub `_ cli tool for `github.com `_. From 2d4a414060d8a308d04b6057228af21dfc6fb0dd Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Apr 15 2016 18:24:08 +0000 Subject: [PATCH 3/8] Add missing import. --- diff --git a/pag/client.py b/pag/client.py index 1136c10..4e6a55a 100644 --- a/pag/client.py +++ b/pag/client.py @@ -2,6 +2,7 @@ import bs4 import fedora.client +from pag.utils import repo_url class PagureException(Exception): pass From 3658e2c66305292c2c56b2aa209d03eff2a77317 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Apr 15 2016 18:25:27 +0000 Subject: [PATCH 4/8] Update the run() function. Fixes #1. --- diff --git a/pag/commands/clone.py b/pag/commands/clone.py index 42515c3..dd29097 100644 --- a/pag/commands/clone.py +++ b/pag/commands/clone.py @@ -10,4 +10,4 @@ from pag.utils import ( @click.argument('name') def clone(name): url = repo_url(name, ssh=True, git=True) - run('git clone %s %s' % (url, name.split('/')[-1])) + run(['git', 'clone', url, name.split('/')[-1]]) diff --git a/pag/commands/create.py b/pag/commands/create.py index 401b8f8..a68b2b5 100644 --- a/pag/commands/create.py +++ b/pag/commands/create.py @@ -29,9 +29,9 @@ def create(conf, name, description): local_repo = in_git_repo() if local_repo is None or local_repo != name: url = repo_url(name, ssh=True, git=True) - run('git clone %s %s' % (url, name.split('/')[-1])) + run(['git', 'clone', url, name.split('/')[-1]]) else: url = repo_url(name, ssh=True, git=True) name = name.split('/')[0] - run('git remote add %s %s' % (name, url)) - run('git remote add %s %s' % ('origin', url)) + run(['git', 'remote', 'add', name, url]) + run(['git', 'remote', 'add', 'origin', url]) diff --git a/pag/commands/fork.py b/pag/commands/fork.py index eb762b6..2f021bd 100644 --- a/pag/commands/fork.py +++ b/pag/commands/fork.py @@ -30,4 +30,4 @@ def fork(conf): name = username + '/' + name url = repo_url(name, ssh=True, git=True) name = name.split('/')[0] - run('git remote add %s %s' % (name, url)) + run(['git', 'remote', 'add', name, url]) diff --git a/pag/commands/remote.py b/pag/commands/remote.py index 39a390f..8a920b6 100644 --- a/pag/commands/remote.py +++ b/pag/commands/remote.py @@ -28,4 +28,4 @@ def add(name): repo = in_git_repo() url = repo_url(name + '/' + repo, ssh=True, git=True) name = name.split('/')[0] - return run('git remote add %s %s' % (name, url)) + return run(['git', 'remote', 'add', name, url]) diff --git a/pag/utils.py b/pag/utils.py index 3b2de14..c55ae42 100644 --- a/pag/utils.py +++ b/pag/utils.py @@ -1,5 +1,6 @@ import functools import os +import subprocess as sp import sys import click @@ -8,9 +9,17 @@ import yaml CONF_FILE = os.path.expanduser('~/.config/pag') -def run(cmd): - click.echo(' $ ' + cmd) - return os.system(cmd) + +def run(cmd, echo=True, graceful=True): + click.echo(' $ ' + " ".join(cmd)) + proc = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.STDOUT) + output, _ = proc.communicate() + output = stdout.decode('utf-8') + if echo: + click.echo(output) + if not graceful and proc.returncode != 0: + sys.exit(1) + return proc.returncode, output def die(msg, code=1): From 38983df0fd6665d3b23fd9efc76919c6591d8f19 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Apr 15 2016 18:26:12 +0000 Subject: [PATCH 5/8] Add a pull-request command. --- diff --git a/pag/app.py b/pag/app.py index a2834b8..b6c2ab9 100644 --- a/pag/app.py +++ b/pag/app.py @@ -14,6 +14,7 @@ from .commands import create from .commands import clone from .commands import fork from .commands import remote +from .commands import pullrequest if __name__ == '__main__': diff --git a/pag/client.py b/pag/client.py index 4e6a55a..b5504f5 100644 --- a/pag/client.py +++ b/pag/client.py @@ -75,8 +75,35 @@ class Pagure(fedora.client.OpenIdBaseClient): if not bool(response): del data['csrf_token'] raise PagureException('Bad status code from pagure when ' - 'creating project: %r. Sent %r' % ( + 'forking project: %r. Sent %r' % ( response, data)) return repo_url(name) + def submit_pull_request(self, name, base, head, title, comment): + url = 'https://pagure.io/{name}/diff/{base}..{head}' + url = url.format(name=name, base=base, head=head) + + response = self._session.get(url) + if not bool(response): + raise PagureException("Couldn't get form to get " + "csrf token %r" % response) + + soup = bs4.BeautifulSoup(response.text, "html.parser") + data = dict( + csrf_token=soup.find(id='csrf_token').attrs['value'], + branch_to=base, + title=title, + initial_comment=comment, + ) + response = self._session.post(url, data=data) + + if not bool(response): + del data['csrf_token'] + raise PagureException('Bad status code from pagure when ' + 'creating pull request: %r. Sent %r' % ( + response, data)) + + return response.url + + client = Pagure() diff --git a/pag/commands/pullrequest.py b/pag/commands/pullrequest.py new file mode 100644 index 0000000..e944060 --- /dev/null +++ b/pag/commands/pullrequest.py @@ -0,0 +1,62 @@ +import getpass +import sys + +import click + +from pag.app import app +from pag.utils import ( + configured, + assert_local_repo, + in_git_repo, + get_default_upstream_branch, + get_current_local_branch, + run, +) +from pag.client import client + +HEADER = "Pull request title goes here." +MARKER = "# All lines below this marker are ignored." + + +@app.command('pull-request') +@assert_local_repo +@click.option('-b', '--base') +@click.option('-h', '--head') +@configured +def pullrequest(conf, base, head): + + name = in_git_repo() + + if base is None: + try: + base = get_default_upstream_branch(name) + except Exception: + click.echo("Failed to find default upstream branch for %r" % name) + click.echo("Please specify a base branch explicitly.") + sys.exit(1) + + if head is None: + head = get_current_local_branch() + + cmd = ['git', 'log', '{base}..{head}'.format(base=base, head=head)] + _, log = run(cmd, echo=False) + + def modify(line): + if not line: + return line + if line[0].isspace(): + return line.strip() + return '# ' + line + + log = '\n'.join([modify(line) for line in log.split('\n')]) + edited = click.edit("\n\n".join([HEADER, MARKER, log])) + title, comment = edited.split('\n', 1) + comment = comment.split(MARKER)[0] + comment = comment.strip() + + username = conf['username'] + if not client.is_logged_in: + password = getpass.getpass("FAS password for %r" % username) + client.login(username=username, password=password) + url = client.submit_pull_request(name, base, head, title, comment) + click.echo(url) diff --git a/pag/utils.py b/pag/utils.py index c55ae42..a68ffab 100644 --- a/pag/utils.py +++ b/pag/utils.py @@ -4,6 +4,7 @@ import subprocess as sp import sys import click +import requests import yaml @@ -43,6 +44,31 @@ def assert_local_repo(func): return inner +def get_default_upstream_branch(name): + url = 'https://pagure.io/api/0/projects' + response = requests.get(url, params=dict(pattern=name, fork=False)) + if not bool(response): + raise IOError("Failed to talk to %r %r", (url, response)) + data = response.json() + projects = data['projects'] + if not projects: + raise ValueError("No such project %r" % name) + if len(projects) > 1: + raise ValueError("More than one project called %r found " + "(%i of them, in fact)." % (name, len(projects))) + project = projects[0] + return project['default_branch'] + + +def get_current_local_branch(): + code, stdout, stderr = run(['git', 'branch', '--contains']) + if code != 0: + raise ValueError("Unable to determine branch." + "\nstdout: %s\nstderr: %s" % (stdout, stderr)) + branch = stdout.split(maxsplit=1)[1].strip().decode('utf-8') + return branch + + def repo_url(name, ssh=False, git=False, domain='pagure.io'): if ssh: prefix = 'ssh://git@' From f94ad05de9f6cf19c71ec3fc6ec3a85e59757474 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Apr 15 2016 18:27:46 +0000 Subject: [PATCH 6/8] Fix another ref to run(). --- diff --git a/pag/utils.py b/pag/utils.py index a68ffab..f9c8fb2 100644 --- a/pag/utils.py +++ b/pag/utils.py @@ -15,7 +15,7 @@ def run(cmd, echo=True, graceful=True): click.echo(' $ ' + " ".join(cmd)) proc = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.STDOUT) output, _ = proc.communicate() - output = stdout.decode('utf-8') + output = output.decode('utf-8') if echo: click.echo(output) if not graceful and proc.returncode != 0: @@ -61,12 +61,8 @@ def get_default_upstream_branch(name): def get_current_local_branch(): - code, stdout, stderr = run(['git', 'branch', '--contains']) - if code != 0: - raise ValueError("Unable to determine branch." - "\nstdout: %s\nstderr: %s" % (stdout, stderr)) - branch = stdout.split(maxsplit=1)[1].strip().decode('utf-8') - return branch + code, stdout = run(['git', 'branch', '--contains']) + return stdout.split(maxsplit=1)[1].strip() def repo_url(name, ssh=False, git=False, domain='pagure.io'): From 96187505ecf737857208686b6e4c2f1e3ec2500a Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Apr 15 2016 18:34:18 +0000 Subject: [PATCH 7/8] Bail if title is empty. --- diff --git a/pag/commands/pullrequest.py b/pag/commands/pullrequest.py index e944060..ec1e85d 100644 --- a/pag/commands/pullrequest.py +++ b/pag/commands/pullrequest.py @@ -51,6 +51,9 @@ def pullrequest(conf, base, head): log = '\n'.join([modify(line) for line in log.split('\n')]) edited = click.edit("\n\n".join([HEADER, MARKER, log])) title, comment = edited.split('\n', 1) + if not title: + click.echo("Aborting due to empty pull request message.") + sys.exit(1) comment = comment.split(MARKER)[0] comment = comment.strip() From 8aaf3025c2314739bf94b94fd6091c738d1c8b2b Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Apr 15 2016 20:23:42 +0000 Subject: [PATCH 8/8] Use git-style vim highlighting for PR messages. --- diff --git a/pag/commands/pullrequest.py b/pag/commands/pullrequest.py index ec1e85d..aaf8295 100644 --- a/pag/commands/pullrequest.py +++ b/pag/commands/pullrequest.py @@ -49,7 +49,10 @@ def pullrequest(conf, base, head): return '# ' + line log = '\n'.join([modify(line) for line in log.split('\n')]) - edited = click.edit("\n\n".join([HEADER, MARKER, log])) + edited = click.edit( + "\n\n".join([HEADER, MARKER, log]), + env=dict(VIMINIT='set filetype="gitcommit"'), + ) title, comment = edited.split('\n', 1) if not title: click.echo("Aborting due to empty pull request message.")