emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* For patch maintainers: A script to simplify your work
@ 2010-06-16  5:55 John Wiegley
  2010-06-16  6:07 ` John Wiegley
  0 siblings, 1 reply; 4+ messages in thread
From: John Wiegley @ 2010-06-16  5:55 UTC (permalink / raw)
  To: emacs-orgmode Mode

[-- Attachment #1: Type: text/plain, Size: 499 bytes --]

The following version of pwclient adds 'branch' command.  You use it so:

  pw branch 15

This does the following:

  1. Download patch #15.

  2. Find the last master branch commit of the night before
     that patch was submitted (best chance to apply there).

  3. Create a local topic branch named t/patch15.

  4. Apply this patch to that topic branch.

  5. Leave you in the topic branch so you can work on the patch.
     Use git commit --amend if you want to edit the commit message.

John


[-- Attachment #2: pw --]
[-- Type: application/octet-stream, Size: 16577 bytes --]

#!/usr/bin/env python
#
# Patchwork command line client
# Copyright (C) 2008 Nate Case <ncase@xes-inc.com>
#
# This file is part of the Patchwork package.
#
# Patchwork is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Patchwork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Patchwork; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import os
import sys
import xmlrpclib
import getopt
import string
import tempfile
import subprocess
import base64
import ConfigParser
import datetime
import re

# Default Patchwork remote XML-RPC server URL
# This script will check the PW_XMLRPC_URL environment variable
# for the URL to access.  If that is unspecified, it will fallback to
# the hardcoded default value specified here.
DEFAULT_URL = "http://patchwork/xmlrpc/"
CONFIG_FILES = [os.path.expanduser('~/.pwclientrc')]

class Filter:
    """Filter for selecting patches."""
    def __init__(self):
        # These fields refer to specific objects, so they are special
        # because we have to resolve them to IDs before passing the
        # filter to the server
        self.state = ""
        self.project = ""

        # The dictionary that gets passed to via XML-RPC
        self.d = {}

    def add(self, field, value):
        if field == 'state':
            self.state = value
        elif field == 'project':
            self.project = value
        else:
            # OK to add directly
            self.d[field] = value

    def resolve_ids(self, rpc):
        """Resolve State, Project, and Person IDs based on filter strings."""
        if self.state != "":
            id = state_id_by_name(rpc, self.state)
            if id == 0:
                sys.stderr.write("Note: No State found matching %s*, " \
                                 "ignoring filter\n" % self.state)
            else:
                self.d['state_id'] = id

        if self.project != "":
            id = project_id_by_name(rpc, self.project)
            if id == 0:
                sys.stderr.write("Note: No Project found matching %s, " \
                                 "ignoring filter\n" % self.project)
            else:
                self.d['project_id'] = id

    def __str__(self):
        """Return human-readable description of the filter."""
        return str(self.d)

class BasicHTTPAuthTransport(xmlrpclib.SafeTransport):

    def __init__(self, username = None, password = None, use_https = False):
        self.username = username
        self.password = password
        self.use_https = use_https
        xmlrpclib.SafeTransport.__init__(self)

    def authenticated(self):
        return self.username != None and self.password != None

    def send_host(self, connection, host):
        xmlrpclib.Transport.send_host(self, connection, host)
        if not self.authenticated():
            return
        credentials = '%s:%s' % (self.username, self.password)
        auth = 'Basic ' + base64.encodestring(credentials).strip()
        connection.putheader('Authorization', auth)

    def make_connection(self, host):
        if self.use_https:
            fn = xmlrpclib.SafeTransport.make_connection
        else:
            fn = xmlrpclib.Transport.make_connection
        return fn(self, host)

def usage():
    sys.stderr.write("Usage: %s <action> [options]\n\n" % \
                        (os.path.basename(sys.argv[0])))
    sys.stderr.write("Where <action> is one of:\n")
    sys.stderr.write(
"""        apply <ID>    : Apply a patch (in the current dir, using -p1)
        get <ID>      : Download a patch and save it locally
        projects      : List all projects
        states        : Show list of potential patch states
        list [str]    : List patches, using the optional filters specified
                        below and an optional substring to search for patches
                        by name
        search [str]  : Same as 'list'
        view <ID>     : View a patch
        update [-s state] [-c commit-ref] <ID>
                      : Update patch\n""")
    sys.stderr.write("""\nFilter options for 'list' and 'search':
        -s <state>    : Filter by patch state (e.g., 'New', 'Accepted', etc.)
        -p <project>  : Filter by project name (see 'projects' for list)
        -w <who>      : Filter by submitter (name, e-mail substring search)
        -d <who>      : Filter by delegate (name, e-mail substring search)
        -n <max #>    : Restrict number of results\n""")
    sys.stderr.write("""\nActions that take an ID argument can also be \
invoked with:
        -h <hash>     : Lookup by patch hash\n""")
    sys.exit(1)

def project_id_by_name(rpc, linkname):
    """Given a project short name, look up the Project ID."""
    if len(linkname) == 0:
        return 0
    projects = rpc.project_list(linkname, 0)
    for project in projects:
        if project['linkname'] == linkname:
            return project['id']
    return 0

def state_id_by_name(rpc, name):
    """Given a partial state name, look up the state ID."""
    if len(name) == 0:
        return 0
    states = rpc.state_list(name, 0)
    for state in states:
        if state['name'].lower().startswith(name.lower()):
            return state['id']
    return 0

def person_ids_by_name(rpc, name):
    """Given a partial name or email address, return a list of the
    person IDs that match."""
    if len(name) == 0:
        return []
    people = rpc.person_list(name, 0)
    return map(lambda x: x['id'], people)

def list_patches(patches):
    """Dump a list of patches to stdout."""
    print("%-5s %-12s %s" % ("ID", "State", "Name"))
    print("%-5s %-12s %s" % ("--", "-----", "----"))
    for patch in patches:
        print("%-5d %-12s %s" % (patch['id'], patch['state'], patch['name']))

def action_list(rpc, filter, submitter_str, delegate_str):
    filter.resolve_ids(rpc)

    if submitter_str != "":
        ids = person_ids_by_name(rpc, submitter_str)
        if len(ids) == 0:
            sys.stderr.write("Note: Nobody found matching *%s*\n", \
                             submitter_str)
        else:
            for id in ids:
                person = rpc.person_get(id)
                print "Patches submitted by %s <%s>:" % \
                        (person['name'], person['email'])
                f = filter
                f.add("submitter_id", id)
                patches = rpc.patch_list(f.d)
                list_patches(patches)
        return

    if delegate_str != "":
        ids = person_ids_by_name(rpc, delegate_str)
        if len(ids) == 0:
            sys.stderr.write("Note: Nobody found matching *%s*\n", \
                             delegate_str)
        else:
            for id in ids:
                person = rpc.person_get(id)
                print "Patches delegated to %s <%s>:" % \
                        (person['name'], person['email'])
                f = filter
                f.add("delegate_id", id)
                patches = rpc.patch_list(f.d)
                list_patches(patches)
        return

    patches = rpc.patch_list(filter.d)
    list_patches(patches)

def action_projects(rpc):
    projects = rpc.project_list("", 0)
    print("%-5s %-24s %s" % ("ID", "Name", "Description"))
    print("%-5s %-24s %s" % ("--", "----", "-----------"))
    for project in projects:
        print("%-5d %-24s %s" % (project['id'], \
                project['linkname'], \
                project['name']))

def action_states(rpc):
    states = rpc.state_list("", 0)
    print("%-5s %s" % ("ID", "Name"))
    print("%-5s %s" % ("--", "----"))
    for state in states:
        print("%-5d %s" % (state['id'], state['name']))

def action_get(rpc, patch_id):
    patch = rpc.patch_get(patch_id)
    s = rpc.patch_get_mbox(patch_id)

    if patch == {} or len(s) == 0:
        sys.stderr.write("Unable to get patch %d\n" % patch_id)
        sys.exit(1)

    base_fname = fname = os.path.basename(patch['filename'])
    i = 0
    while os.path.exists(fname):
        fname = "%s.%d" % (base_fname, i)
        i += 1

    try:
        f = open(fname, "w")
    except:
        sys.stderr.write("Unable to open %s for writing\n" % fname)
        sys.exit(1)

    try:
        f.write(unicode(s).encode("utf-8"))
        f.close()
        print "Saved patch to %s" % fname
    except:
        sys.stderr.write("Failed to write to %s\n" % fname)
        sys.exit(1)

def action_apply(rpc, patch_id):
    patch = rpc.patch_get(patch_id)
    if patch == {}:
        sys.stderr.write("Error getting information on patch ID %d\n" % \
                         patch_id)
        sys.exit(1)
    print "Applying patch #%d to current directory" % patch_id
    print "Description: %s" % patch['name']
    s = rpc.patch_get_mbox(patch_id)
    if len(s) > 0:
        proc = subprocess.Popen(['patch', '-p1'], stdin = subprocess.PIPE)
        proc.communicate(s)
    else:
        sys.stderr.write("Error: No patch content found\n")
        sys.exit(1)

def action_update_patch(rpc, patch_id, state = None, commit = None):
    patch = rpc.patch_get(patch_id)
    if patch == {}:
        sys.stderr.write("Error getting information on patch ID %d\n" % \
                         patch_id)
        sys.exit(1)

    params = {}

    if state:
        state_id = state_id_by_name(rpc, state)
        if state_id == 0:
            sys.stderr.write("Error: No State found matching %s*\n" % state)
            sys.exit(1)
        params['state'] = state_id

    if commit:
        params['commit_ref'] = commit

    success = False
    try:
        success = rpc.patch_set(patch_id, params)
    except xmlrpclib.Fault, f:
        sys.stderr.write("Error updating patch: %s\n" % f.faultString)

    if not success:
        sys.stderr.write("Patch not updated\n")

def patch_id_from_hash(rpc, project, hash):
    try:
        patch = rpc.patch_get_by_project_hash(project, hash)
    except xmlrpclib.Fault:
        # the server may not have the newer patch_get_by_project_hash function,
        # so fall back to hash-only.
        patch = rpc.patch_get_by_hash(hash)

    if patch == {}:
        return None

    return patch['id']

def branch_with(patch_id, rpc):
    s = rpc.patch_get_mbox(patch_id)
    if len(s) > 0:
        print unicode(s).encode("utf-8")

    patch = rpc.patch_get(patch_id)

    # Find the latest commit from the day before the patch
    proc = subprocess.Popen(['git', 'log', '--until=' + patch['date'],
                             '-1', '--format=%H', 'master'],
                            stdout = subprocess.PIPE)
    sha = proc.stdout.read()[:-1]

    # Create a topic branch named after this commit
    proc = subprocess.Popen(['git', 'checkout', '-b', 't/patch%s' %
                             patch_id, sha])
    sts = os.waitpid(proc.pid, 0)
    if sts[1] != 0:
        sys.stderr.write("Could not create branch for patch\n")
        return

    # Apply the patch to the branch
    fname = '/tmp/patch'
    try:
        f = open(fname, "w")
    except:
        sys.stderr.write("Unable to open %s for writing\n" % fname)
        sys.exit(1)

    try:
        f.write(unicode(s).encode("utf-8"))
        f.close()
        print "Saved patch to %s" % fname
    except:
        sys.stderr.write("Failed to write to %s\n" % fname)
        sys.exit(1)

    proc = subprocess.Popen(['git', 'am', '/tmp/patch'])
    sts = os.waitpid(proc.pid, 0)
    if sts[1] != 0:
        sys.stderr.write("Failed to apply patch to branch\n")
        proc = subprocess.Popen(['git', 'checkout', 'master'])
        os.waitpid(proc.pid, 0)
        proc = subprocess.Popen(['git', 'branch', '-D', 't/patch%s' %
                                 patch_id, sha])
        os.waitpid(proc.pid, 0)
        return

    print sha

auth_actions = ['update']

def main():
    try:
        opts, args = getopt.getopt(sys.argv[2:], 's:p:w:d:n:c:h:')
    except getopt.GetoptError, err:
        print str(err)
        usage()

    if len(sys.argv) < 2:
        usage()

    action = sys.argv[1].lower()

    # set defaults
    filt = Filter()
    submitter_str = ""
    delegate_str = ""
    project_str = ""
    commit_str = ""
    state_str = "New"
    hash_str = ""
    url = DEFAULT_URL

    config = ConfigParser.ConfigParser()
    config.read(CONFIG_FILES)

    # grab settings from config files
    if config.has_option('base', 'url'):
        url = config.get('base', 'url')

    if config.has_option('base', 'project'):
        project_str = config.get('base', 'project')

    for name, value in opts:
        if name == '-s':
            state_str = value
        elif name == '-p':
            project_str = value
        elif name == '-w':
            submitter_str = value
        elif name == '-d':
            delegate_str = value
        elif name == '-c':
            commit_str = value
        elif name == '-h':
            hash_str = value
        elif name == '-n':
            try:
                filt.add("max_count", int(value))
            except:
                sys.stderr.write("Invalid maximum count '%s'\n" % value)
                usage()
        else:
            sys.stderr.write("Unknown option '%s'\n" % name)
            usage()

    if len(args) > 1:
        sys.stderr.write("Too many arguments specified\n")
        usage()

    (username, password) = (None, None)
    transport = None
    if action in auth_actions:
        if config.has_option('auth', 'username') and \
                config.has_option('auth', 'password'):

            use_https = url.startswith('https')

            transport = BasicHTTPAuthTransport( \
                    config.get('auth', 'username'),
                    config.get('auth', 'password'),
                    use_https)

        else:
            sys.stderr.write(("The %s action requires authentication, "
                    "but no username or password\nis configured\n") % action)
            sys.exit(1)

    if project_str:
        filt.add("project", project_str)

    if state_str:
        filt.add("state", state_str)

    try:
        rpc = xmlrpclib.Server(url, transport = transport)
    except:
        sys.stderr.write("Unable to connect to %s\n" % url)
        sys.exit(1)

    patch_id = None
    if hash_str:
        patch_id = patch_id_from_hash(rpc, project_str, hash_str)
        if patch_id is None:
            sys.stderr.write("No patch has the hash provided\n")
            sys.exit(1)


    if action == 'list' or action == 'search':
        if len(args) > 0:
            filt.add("name__icontains", args[0])
        action_list(rpc, filt, submitter_str, delegate_str)

    elif action.startswith('project'):
        action_projects(rpc)

    elif action.startswith('state'):
        action_states(rpc)

    elif action == 'branch':
        try:
            patch_id = patch_id or int(args[0])
        except:
            sys.stderr.write("Invalid patch ID given\n")
            sys.exit(1)

        branch_with(patch_id, rpc)

    elif action == 'view':
        try:
            patch_id = patch_id or int(args[0])
        except:
            sys.stderr.write("Invalid patch ID given\n")
            sys.exit(1)

        s = rpc.patch_get_mbox(patch_id)
        if len(s) > 0:
            print unicode(s).encode("utf-8")

    elif action == 'get' or action == 'save':
        try:
            patch_id = patch_id or int(args[0])
        except:
            sys.stderr.write("Invalid patch ID given\n")
            sys.exit(1)

        action_get(rpc, patch_id)

    elif action == 'apply':
        try:
            patch_id = patch_id or int(args[0])
        except:
            sys.stderr.write("Invalid patch ID given\n")
            sys.exit(1)

        action_apply(rpc, patch_id)

    elif action == 'update':
        try:
            patch_id = patch_id or int(args[0])
        except:
            sys.stderr.write("Invalid patch ID given\n")
            sys.exit(1)

        action_update_patch(rpc, patch_id, state = state_str,
                commit = commit_str)

    else:
        sys.stderr.write("Unknown action '%s'\n" % action)
        usage()

if __name__ == "__main__":
    main()

[-- Attachment #3: Type: text/plain, Size: 201 bytes --]

_______________________________________________
Emacs-orgmode mailing list
Please use `Reply All' to send replies to the list.
Emacs-orgmode@gnu.org
http://lists.gnu.org/mailman/listinfo/emacs-orgmode

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: For patch maintainers: A script to simplify your work
  2010-06-16  5:55 For patch maintainers: A script to simplify your work John Wiegley
@ 2010-06-16  6:07 ` John Wiegley
  2010-06-17  6:13   ` David Maus
  2010-06-25  6:24   ` Carsten Dominik
  0 siblings, 2 replies; 4+ messages in thread
From: John Wiegley @ 2010-06-16  6:07 UTC (permalink / raw)
  To: emacs-orgmode Mode

John Wiegley <jwiegley@gmail.com> writes:

> The following version of pwclient adds 'branch' command.  You use it so:

This next version automatically rebases the topic branch to the master, which
performs the merge that will need to be done at some point.

John

<#part type="application/octet-stream" filename="~/bin/pw" disposition=attachment description=pw>
<#/part>

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: Re: For patch maintainers: A script to simplify your work
  2010-06-16  6:07 ` John Wiegley
@ 2010-06-17  6:13   ` David Maus
  2010-06-25  6:24   ` Carsten Dominik
  1 sibling, 0 replies; 4+ messages in thread
From: David Maus @ 2010-06-17  6:13 UTC (permalink / raw)
  To: John Wiegley; +Cc: emacs-orgmode Mode

Hi John,

I suppose something went wrong with this message.  The attachment is
missing, I've just got the attachment markup of mml.

>< #part type="application/octet-stream" filename="~/bin/pw" disposition=attachment description=pw>
>< #/part>

 -- David
-- 
OpenPGP... 0x99ADB83B5A4478E6
Jabber.... dmjena@jabber.org
Email..... dmaus@ictsoc.de

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: Re: For patch maintainers: A script to simplify your work
  2010-06-16  6:07 ` John Wiegley
  2010-06-17  6:13   ` David Maus
@ 2010-06-25  6:24   ` Carsten Dominik
  1 sibling, 0 replies; 4+ messages in thread
From: Carsten Dominik @ 2010-06-25  6:24 UTC (permalink / raw)
  To: John Wiegley; +Cc: emacs-orgmode Mode

Hi John,

this message did not contain an attachment.

- Carsten

On Jun 16, 2010, at 8:07 AM, John Wiegley wrote:

> John Wiegley <jwiegley@gmail.com> writes:
>
>> The following version of pwclient adds 'branch' command.  You use  
>> it so:
>
> This next version automatically rebases the topic branch to the  
> master, which
> performs the merge that will need to be done at some point.
>
> John
>
> <#part type="application/octet-stream" filename="~/bin/pw"  
> disposition=attachment description=pw>
> <#/part>
>
> _______________________________________________
> Emacs-orgmode mailing list
> Please use `Reply All' to send replies to the list.
> Emacs-orgmode@gnu.org
> http://lists.gnu.org/mailman/listinfo/emacs-orgmode

- Carsten

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2010-06-25 17:56 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-06-16  5:55 For patch maintainers: A script to simplify your work John Wiegley
2010-06-16  6:07 ` John Wiegley
2010-06-17  6:13   ` David Maus
2010-06-25  6:24   ` Carsten Dominik

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).