Example #1
0
    def __init__(self, patchpath, repo, pf=None, rev=None):
        """ Read patch context from file
        :param pf: currently ignored
            The provided handle is used to read the patch and
            the patchpath contains the name of the patch.
            The handle is NOT closed.
        """
        self._path = patchpath
        if rev:
            assert isinstance(rev, str)
            self._patchname = rev
        else:
            self._patchname = os.path.basename(patchpath)
        self._repo = repo
        self._rev = rev or 'patch'
        self._status = [[], [], []]
        self._fileorder = []
        self._user = ''
        self._desc = ''
        self._branch = ''
        self._node = node.nullid
        self._mtime = None
        self._fsize = 0
        self._parseerror = None
        self._phase = 'draft'

        try:
            self._mtime = os.path.getmtime(patchpath)
            self._fsize = os.path.getsize(patchpath)
            ph = mq.patchheader(self._path)
            self._ph = ph
        except EnvironmentError:
            self._date = util.makedate()
            return

        try:
            self._branch = ph.branch or ''
            self._node = binascii.unhexlify(ph.nodeid)
            if self._repo.ui.configbool('mq', 'secret'):
                self._phase = 'secret'
        except TypeError:
            pass
        except AttributeError:
            # hacks to try to deal with older versions of mq.py
            self._branch = ''
            ph.diffstartline = len(ph.comments)
            if ph.message:
                ph.diffstartline += 1
        except error.ConfigError:
            pass

        self._user = ph.user or ''
        self._desc = ph.message and '\n'.join(ph.message).strip() or ''
        try:
            self._date = ph.date and util.parsedate(ph.date) or util.makedate()
        except error.Abort:
            self._date = util.makedate()
Example #2
0
    def __init__(self, patchpath, repo, pf=None, rev=None):
        """ Read patch context from file
        :param pf: currently ignored
            The provided handle is used to read the patch and
            the patchpath contains the name of the patch.
            The handle is NOT closed.
        """
        self._path = patchpath
        self._patchname = os.path.basename(patchpath)
        self._repo = repo
        self._rev = rev or 'patch'
        self._status = [[], [], []]
        self._fileorder = []
        self._user = ''
        self._desc = ''
        self._branch = ''
        self._node = node.nullid
        self._identity = node.nullid
        self._mtime = None
        self._fsize = 0
        self._parseerror = None
        self._phase = 'draft'

        try:
            self._mtime = os.path.getmtime(patchpath)
            self._fsize = os.path.getsize(patchpath)
            ph = mq.patchheader(self._path)
            self._ph = ph
            hash = util.sha1(self._path)
            hash.update(str(self._mtime))
            self._identity = hash.digest()
        except EnvironmentError:
            self._date = util.makedate()
            return

        try:
            self._branch = ph.branch or ''
            self._node = binascii.unhexlify(ph.nodeid)
            if self._repo.ui.configbool('mq', 'secret'):
                self._phase = 'secret'
        except TypeError:
            pass
        except AttributeError:
            # hacks to try to deal with older versions of mq.py
            self._branch = ''
            ph.diffstartline = len(ph.comments)
            if ph.message:
                ph.diffstartline += 1
        except error.ConfigError:
            pass

        self._user = ph.user or ''
        self._desc = ph.message and '\n'.join(ph.message).strip() or ''
        try:
            self._date = ph.date and util.parsedate(ph.date) or util.makedate()
        except error.Abort:
            self._date = util.makedate()
Example #3
0
 def thgmqoriginalparent(self):
     '''The revisionid of the original patch parent'''
     if not self.thgmqunappliedpatch() and not self.thgmqappliedpatch():
         return ''
     try:
         patchpath = self._repo.mq.join(self.thgmqpatchname())
         mqoriginalparent = mq.patchheader(patchpath).parent
     except EnvironmentError:
         return ''
     return mqoriginalparent
Example #4
0
 def thgmqoriginalparent(self):
     '''The revisionid of the original patch parent'''
     if not self.thgmqunappliedpatch() and not self.thgmqappliedpatch():
         return ''
     try:
         patchpath = self._repo.mq.join(self.thgmqpatchname())
         mqoriginalparent = mq.patchheader(patchpath).parent
     except EnvironmentError:
         return ''
     return mqoriginalparent
Example #5
0
    def refresh(self):
        """
        Refresh the list of patches.
        This operation will try to keep selection state.
        """
        if not self.mqloaded:
            return

        # store selected patch name
        selname = None
        model, paths = self.list.get_selection().get_selected_rows()
        if len(paths) > 0:
            selname = model[paths[0]][MQ_NAME]

        # clear model data
        self.model.clear()

        # insert 'qparent' row
        top = None
        if self.get_property('show-qparent'):
            top = self.model.append((INDEX_QPARENT, None, None, None, None))

        # add patches
        from hgext import mq
        q = self.repo.mq
        q.parse_series()
        applied = set([p.name for p in q.applied])
        for index, patchname in enumerate(q.series):
            stat = patchname in applied and 'A' or 'U'
            try:
                msg = hglib.toutf(mq.patchheader(q.join(patchname)).message[0])
                msg_esc = gtklib.markup_escape_text(msg)
            except IndexError:
                msg = msg_esc = None
            iter = self.model.append((index, stat, patchname, msg, msg_esc))
            if stat == 'A':
                top = iter

        # insert separator
        if top:
            row = self.model.insert_after(top, (INDEX_SEPARATOR, None, None, None, None))
            self.separator_pos = self.model.get_path(row)[0]

        # restore patch selection
        if selname:
            iter = self.get_iter_by_patchname(selname)
            if iter:
                self.list.get_selection().select_iter(iter)

        # update UI sensitives
        self.update_sensitives()
Example #6
0
def update_patch(ui, repo, rev, bug, update_patch, rename_patch, interactive):
    q = repo.mq
    try:
        rev = q.lookup(rev)
    except util.error.Abort:
        # If the patch is not coming from mq, don't complain that the name is not found
        update_patch = False
        rename_patch = False

    todo = []
    if rename_patch:
        todo.append("name")
    if update_patch:
        todo.append("description")
    if todo:
        if interactive and ui.prompt("Update patch " + " and ".join(todo) +
                                     " (y/n)?") != 'y':
            ui.write(_("Exiting without updating patch\n"))
            return

    if rename_patch:
        newname = str("bug-%s-%s" % (bug, re.sub(r'^bug-\d+-', '', rev)))
        if newname != rev:
            try:
                mq.rename(ui, repo, rev, newname)
            except:
                # mq.rename has a tendency to leave things in an inconsistent
                # state. Fix things up.
                q.invalidate()
                if os.path.exists(
                        q.join(newname)) and newname not in q.fullseries:
                    os.rename(q.join(newname), q.join(rev))
                raise
            rev = newname

    if update_patch:
        # Add "Bug nnnn - " to the beginning of the description
        ph = mq.patchheader(q.join(rev), q.plainmode)
        msg = [s.decode('utf-8') for s in ph.message]
        if not msg:
            msg = ["Bug %s patch" % bug]
        elif not BUG_RE.search(msg[0]):
            msg[0] = "Bug %s - %s" % (bug, msg[0])
        opts = {
            'git': True,
            'message': '\n'.join(msg).encode('utf-8'),
            'include': ["re:."]
        }
        mq.refresh(ui, repo, **opts)

    return rev
Example #7
0
    def __init__(self, patchpath, repo, pf=None, rev=None):
        """ Read patch context from file
        :param pf: currently ignored
            The provided handle is used to read the patch and
            the patchpath contains the name of the patch.
            The handle is NOT closed.
        """
        self._path = patchpath
        self._patchname = os.path.basename(patchpath)
        self._repo = repo
        self._rev = rev or 'patch'
        self._status = [[], [], []]
        self._fileorder = []
        self._user = ''
        self._desc = ''
        self._branch = ''
        self._node = node.nullid
        self._identity = node.nullid
        self._mtime = None
        self._fsize = 0
        self._parseerror = None

        try:
            self._mtime = os.path.getmtime(patchpath)
            self._fsize = os.path.getsize(patchpath)
            ph = mq.patchheader(self._path)
            self._ph = ph
            hash = util.sha1(self._path)
            hash.update(str(self._mtime))
            self._identity = hash.digest()
        except EnvironmentError:
            self._date = util.makedate()
            return

        try:
            self._branch = ph.branch or ''
            self._node = binascii.unhexlify(ph.nodeid)
        except TypeError:
            pass
        except AttributeError:
            # hacks to try to deal with older versions of mq.py
            self._branch = ''
            ph.diffstartline = len(ph.comments)
            if ph.message:
                ph.diffstartline += 1
        self._user = ph.user or ''
        self._date = ph.date and util.parsedate(ph.date) or util.makedate()
        self._desc = ph.message and '\n'.join(ph.message).strip() or ''
def update_patch(ui, repo, rev, bug, update_patch, rename_patch, interactive):
    q = repo.mq
    try:
        rev = q.lookup(rev)
    except util.error.Abort:
        # If the patch is not coming from mq, don't complain that the name is not found
        update_patch = False
        rename_patch = False

    todo = []
    if rename_patch:
        todo.append("name")
    if update_patch:
        todo.append("description")
    if todo:
        if interactive and ui.prompt("Update patch " + " and ".join(todo) + " (y/n)?") != 'y':
            ui.write(_("Exiting without updating patch\n"))
            return

    if rename_patch:
        newname = str("bug-%s-%s" % (bug, re.sub(r'^bug-\d+-', '', rev)))
        if newname != rev:
            try:
                mq.rename(ui, repo, rev, newname)
            except:
                # mq.rename has a tendency to leave things in an inconsistent
                # state. Fix things up.
                q.invalidate()
                if os.path.exists(q.join(newname)) and newname not in q.fullseries:
                    os.rename(q.join(newname), q.join(rev))
                raise
            rev = newname

    if update_patch:
        # Add "Bug nnnn - " to the beginning of the description
        ph = mq.patchheader(q.join(rev), q.plainmode)
        msg = [s.decode('utf-8') for s in ph.message]
        if not msg:
            msg = ["Bug %s patch" % bug]
        elif not BUG_RE.search(msg[0]):
            msg[0] = "Bug %s - %s" % (bug, msg[0])
        opts = {'git': True, 'message': '\n'.join(msg).encode('utf-8'), 'include': ["re:."]}
        mq.refresh(ui, repo, **opts)

    return rev
Example #9
0
    def __init__(self, patchpath, repo, patchHandle=None):
        """ Read patch context from file
        :param patchHandle: If set, then the patch is a temporary.
            The provided handle is used to read the patch and
            the patchpath contains the name of the patch. 
            The handle is NOT closed.
        """
        self._path = patchpath
        self._patchname = os.path.basename(patchpath)
        self._repo = repo
        if patchHandle:
            pf = patchHandle
            pf_start_pos = pf.tell()
        else:
            pf = open(patchpath)
        try:
            data = patch.extract(self._repo.ui, pf)
            tmpfile, msg, user, date, branch, node, p1, p2 = data
            if tmpfile:
                os.unlink(tmpfile)
        finally:
            if patchHandle:
                pf.seek(pf_start_pos)
            else:
                pf.close()
        if not msg and hasattr(repo, "mq"):
            # attempt to get commit message
            from hgext import mq

            msg = mq.patchheader(repo.mq.join(self._patchname)).message
            if msg:
                msg = "\n".join(msg)
        self._node = node
        self._user = user and hglib.toutf(user) or ""
        self._date = date and util.parsedate(date) or None
        self._desc = msg and msg or ""
        self._branch = branch and hglib.toutf(branch) or ""
        self._parents = []
        for p in (p1, p2):
            if not p:
                continue
            try:
                self._parents.append(repo[p])
            except (error.LookupError, error.RepoLookupError, error.RepoError):
                self._parents.append(p)
def bzexport(ui, repo, *args, **opts):
    """
    Export changesets to bugzilla attachments.

    The -e option may be used to bring up an editor that will allow editing all
    fields of the attachment and bug (if creating one).

    The --new option may be used to create a new bug rather than using an
    existing bug. See the newbug command for details.

    The -u (--update) option is equivalent to setting both 'update-patch'
    and 'rename-patch' to True in the [bzexport] section of your config file.
    """
    auth, api_server, bugzilla = bugzilla_info(ui, opts.get('ffprofile'))

    rev, bug = infer_arguments(ui, repo, args, opts)

    if not opts['new']:
        for o in ('cc', 'depends', 'blocks'):
            if opts[o]:
                ui.write("Warning: ignoring --%s option when not creating a bug\n" % o)

    contents = StringIO()
    diffopts = patch.diffopts(ui, opts)
    context = ui.config("bzexport", "unified", ui.config("diff", "unified", None))
    if context:
        diffopts.context = int(context)
    if rev in repo:
        description_from_patch = repo[rev].description().decode('utf-8')
        if hasattr(cmdutil, "export"):
            cmdutil.export(repo, [rev], fp=contents, opts=diffopts)
        else:
            # Support older hg versions
            patch.export(repo, [rev], fp=contents, opts=diffopts)
    else:
        q = repo.mq
        contents = q.opener(q.lookup(rev), "r")
        description_from_patch = '\n'.join(mq.patchheader(q.join(rev), q.plainmode).message)

    # Just always use the rev name as the patch name. Doesn't matter much,
    # unless you want to avoid obsoleting existing patches when uploading a
    # version that doesn't include whitespace changes.
    filename = rev
    if opts['ignore_all_space']:
        filename += "_ws"

    patch_comment = None
    reviewers = []
    orig_desc = opts['description'] or description_from_patch
    if not orig_desc or orig_desc.startswith('[mq]'):
        desc = '<required>'
    else:
        # Lightly reformat changeset messages into attachment descriptions.
        # Only use the first line of the provided description for our actual
        # description - use the rest for the patch/bug comment.
        parts = orig_desc.split('\n', 1)
        firstline = parts[0]
        if len(parts) == 2:
            patch_comment = parts[1].strip()

        # Attempt to split the firstline into a bug number, and strip()ed
        # description with that bug number string removed.
        desc_bug_number, desc = extract_bug_num_and_desc(firstline)

        # Failing that try looking in the commit description for a bug number,
        # since orig_desc could have come from the command line instead.
        if not desc_bug_number:
            commit_firstline = description_from_patch.split('\n', 1)[0]
            desc_bug_number, __ = extract_bug_num_and_desc(commit_firstline)

        if desc_bug_number:
            if bug and bug != desc_bug_number:
                ui.warn("Warning: Bug number %s from commandline doesn't match "
                        "bug number %s from changeset description\n"
                        % (bug, desc_bug_number))
            else:
                bug = desc_bug_number

        # Strip any remaining leading separator and whitespace,
        # if the original was something like "bug NNN - "
        if desc[0] in ['-', ':', '.']:
            desc = desc[1:].lstrip()

        # Next strip off review and approval annotations, grabbing the
        # reviewers from the patch comments only if -r auto was given
        def grab_reviewer(m):
            if opts['review'] == 'auto':
                reviewers.append(m.group(1))
            return ''
        desc = review_re.sub(grab_reviewer, desc).rstrip()

        # Strip any trailing separators, if the original was something like:
        # "Desc; r=foo" or "Desc. r=foo"
        if desc and desc[-1] in (';', '.'):
            desc = desc[:-1].rstrip()

        if len(reviewers) > 0:
            opts['review'] = ''

    attachment_comment = opts['comment']
    bug_comment = opts['bug_description']

    if not attachment_comment:
        # New bugs get first shot at the patch comment
        if not opts['new'] or bug_comment:
            attachment_comment = patch_comment

    if not bug_comment and opts['new']:
        bug_comment = patch_comment

    if opts["review"]:
        search_strings = opts["review"].split(",")
        valid_users = validate_users(ui, api_server, auth, search_strings, multi_user_prompt, 'reviewer')
        reviewers = select_users(valid_users, search_strings)
    elif len(reviewers) > 0:
        # Pulled reviewers out of commit message
        valid_users = validate_users(ui, api_server, auth, reviewers, multi_user_prompt, 'reviewer')
        reviewers = select_users(valid_users, reviewers)

    if reviewers is None:
        raise util.Abort(_("Invalid reviewers"))

    feedback = []
    if opts["feedback"]:
        search_strings = opts["feedback"].split(",")
        valid_users = validate_users(ui, api_server, auth, search_strings, multi_user_prompt, 'feedback from')
        feedback = select_users(valid_users, search_strings)

    values = {'BUGNUM': bug,
              'ATTACHMENT_FILENAME': filename,
              'ATTACHMENT_DESCRIPTION': desc,
              'ATTACHCOMMENT': attachment_comment,
              'REVIEWERS': reviewers,
              'FEEDBACK': feedback,
              }

    cc = []
    depends = opts["depends"].split(",")
    blocks = opts["blocks"].split(",")
    if opts['new']:
        if opts["cc"]:
            search_strings = opts["cc"].split(",")
            valid_users = validate_users(ui, api_server, auth, search_strings, multi_user_prompt, 'CC')
            cc = select_users(valid_users, search_strings)

        values['BUGTITLE'] = opts['title'] or desc
        values['PRODUCT'] = opts.get('product', '') or ui.config("bzexport", "product", '<choose-from-menu>')
        values['COMPONENT'] = opts.get('component', '') or ui.config("bzexport", "component", '<choose-from-menu>')
        values['PRODVERSION'] = opts.get('prodversion', '') or ui.config("bzexport", "prodversion", '<default>')
        values['BUGCOMMENT0'] = bug_comment
        values['CC'] = cc
        values['BLOCKS'] = blocks
        values['DEPENDS'] = depends

    values = fill_values(values, ui, api_server, finalize=False)

    if opts['edit']:
        if opts['new']:
            values = edit_form(ui, repo, values, 'new_both_template')
        else:
            values = edit_form(ui, repo, values, 'existing_bug_template')
            bug = values['BUGNUM']

        search_strings = []
        for key in ('REVIEWERS', 'CC', 'FEEDBACK'):
            # TODO: Handle <choose-from-menu>
            search_strings.extend(values.get(key, []))
        users = validate_users(ui, api_server, auth, search_strings, multi_user_prompt, 'reviewer')
        if users is None:
            raise util.Abort("Invalid users")

        if 'REVIEWERS' in values:  # Always true
            reviewers = select_users(users, values['REVIEWERS'])
        if 'CC' in values:         # Only when opts['new']
            cc = select_users(users, values['CC'])
        if 'BLOCKS' in values:     # Only when opts['new']
            blocks = values['BLOCKS']
        if 'DEPENDS' in values:    # Only when opts['new']
            depends = values['DEPENDS']
        if 'FEEDBACK' in values:   # Always true
            feedback = select_users(users, values['FEEDBACK'])
        if 'ATTACHMENT_FILENAME' in values:
            filename = values['ATTACHMENT_FILENAME']

    values = fill_values(values, ui, api_server, finalize=True)

    if opts["new"]:
        if bug is not None:
            raise util.Abort("Bug %s given but creation of new bug requested!" % bug)

        if opts['interactive'] and ui.prompt(_("Create bug in '%s' :: '%s' (y/n)?") %
                                             (values['PRODUCT'], values['COMPONENT'])) != 'y':
            ui.write(_("Exiting without creating bug\n"))
            return

        try:
            create_opts = {}
            if not opts['no_take_bug']:
                create_opts['assign_to'] = auth.username(api_server)
            result = bz.create_bug(auth,
                                product=values['PRODUCT'],
                                component=values['COMPONENT'],
                                version=values['PRODVERSION'],
                                title=values['BUGTITLE'],
                                description=values['BUGCOMMENT0'],
                                cc=cc,
                                depends=depends,
                                blocks=blocks,
                                **create_opts)
            bug = result['id']
            ui.write("Created bug %s at %sshow_bug.cgi?id=%s\n" % (bug, bugzilla, bug))
        except Exception, e:
            raise util.Abort(_("Error creating bug: %s\n" % str(e)))
Example #11
0
 def showSummary(self, item):
     patchname = hglib.fromunicode(item.text())
     txt = '\n'.join(mq.patchheader(self.repo.mq.join(patchname)).message)
     self.summ.setText(hglib.tounicode(txt))
Example #12
0
def get_patch_user(patch_file):
    ph = patchheader(patch_file)
    return ph.user
Example #13
0
 def showSummary(self, item):
     patchname = hglib.fromunicode(item.text())
     txt = '\n'.join(mq.patchheader(self.repo.mq.join(patchname)).message)
     self.summ.setText(hglib.tounicode(txt))
Example #14
0
def bzexport(ui, repo, *args, **opts):
    """
    Export changesets to bugzilla attachments.

    The -e option may be used to bring up an editor that will allow editing all
    fields of the attachment and bug (if creating one).

    The --new option may be used to create a new bug rather than using an
    existing bug. See the newbug command for details.

    The -u (--update) option is equivalent to setting both 'update-patch'
    and 'rename-patch' to True in the [bzexport] section of your config file.
    """
    auth, api_server, bugzilla = bugzilla_info(ui, opts.get('ffprofile'))

    rev, bug = infer_arguments(ui, repo, args, opts)

    if not opts['new']:
        for o in ('cc', 'depends', 'blocks'):
            if opts[o]:
                ui.write(
                    "Warning: ignoring --%s option when not creating a bug\n" %
                    o)

    contents = StringIO()
    diffopts = patch.diffopts(ui, opts)
    context = ui.config("bzexport", "unified",
                        ui.config("diff", "unified", None))
    if context:
        diffopts.context = int(context)
    if rev in repo:
        description_from_patch = repo[rev].description().decode('utf-8')
        if hasattr(cmdutil, "export"):
            cmdutil.export(repo, [rev], fp=contents, opts=diffopts)
        else:
            # Support older hg versions
            patch.export(repo, [rev], fp=contents, opts=diffopts)
    else:
        q = repo.mq
        contents = q.opener(q.lookup(rev), "r")
        description_from_patch = '\n'.join(
            mq.patchheader(q.join(rev), q.plainmode).message)

    # Just always use the rev name as the patch name. Doesn't matter much,
    # unless you want to avoid obsoleting existing patches when uploading a
    # version that doesn't include whitespace changes.
    filename = rev
    if opts['ignore_all_space']:
        filename += "_ws"

    patch_comment = None
    reviewers = []
    orig_desc = opts['description'] or description_from_patch
    if not orig_desc or orig_desc.startswith('[mq]'):
        desc = '<required>'
    else:
        # Lightly reformat changeset messages into attachment descriptions.
        # Only use the first line of the provided description for our actual
        # description - use the rest for the patch/bug comment.
        parts = orig_desc.split('\n', 1)
        firstline = parts[0]
        if len(parts) == 2:
            patch_comment = parts[1].strip()

        # Attempt to split the firstline into a bug number, and strip()ed
        # description with that bug number string removed.
        desc_bug_number, desc = extract_bug_num_and_desc(firstline)

        # Failing that try looking in the commit description for a bug number,
        # since orig_desc could have come from the command line instead.
        if not desc_bug_number:
            commit_firstline = description_from_patch.split('\n', 1)[0]
            desc_bug_number, __ = extract_bug_num_and_desc(commit_firstline)

        if desc_bug_number:
            if bug and bug != desc_bug_number:
                ui.warn(
                    "Warning: Bug number %s from commandline doesn't match "
                    "bug number %s from changeset description\n" %
                    (bug, desc_bug_number))
            else:
                bug = desc_bug_number

        # Strip any remaining leading separator and whitespace,
        # if the original was something like "bug NNN - "
        if desc[0] in ['-', ':', '.']:
            desc = desc[1:].lstrip()

        # Next strip off review and approval annotations, grabbing the
        # reviewers from the patch comments only if -r auto was given
        def grab_reviewer(m):
            if opts['review'] == 'auto':
                reviewers.append(m.group(1))
            return ''

        desc = review_re.sub(grab_reviewer, desc).rstrip()

        # Strip any trailing separators, if the original was something like:
        # "Desc; r=foo" or "Desc. r=foo"
        if desc and desc[-1] in (';', '.'):
            desc = desc[:-1].rstrip()

        if len(reviewers) > 0:
            opts['review'] = ''

    attachment_comment = opts['comment']
    bug_comment = opts['bug_description']

    if not attachment_comment:
        # New bugs get first shot at the patch comment
        if not opts['new'] or bug_comment:
            attachment_comment = patch_comment

    if not bug_comment and opts['new']:
        bug_comment = patch_comment

    if opts["review"]:
        search_strings = opts["review"].split(",")
        valid_users = validate_users(ui, api_server, auth, search_strings,
                                     multi_user_prompt, 'reviewer')
        reviewers = select_users(valid_users, search_strings)
    elif len(reviewers) > 0:
        # Pulled reviewers out of commit message
        valid_users = validate_users(ui, api_server, auth, reviewers,
                                     multi_user_prompt, 'reviewer')
        reviewers = select_users(valid_users, reviewers)

    if reviewers is None:
        raise util.Abort(_("Invalid reviewers"))

    feedback = []
    if opts["feedback"]:
        search_strings = opts["feedback"].split(",")
        valid_users = validate_users(ui, api_server, auth, search_strings,
                                     multi_user_prompt, 'feedback from')
        feedback = select_users(valid_users, search_strings)

    values = {
        'BUGNUM': bug,
        'ATTACHMENT_FILENAME': filename,
        'ATTACHMENT_DESCRIPTION': desc,
        'ATTACHCOMMENT': attachment_comment,
        'REVIEWERS': reviewers,
        'FEEDBACK': feedback,
    }

    cc = []
    depends = opts["depends"].split(",")
    blocks = opts["blocks"].split(",")
    if opts['new']:
        if opts["cc"]:
            search_strings = opts["cc"].split(",")
            valid_users = validate_users(ui, api_server, auth, search_strings,
                                         multi_user_prompt, 'CC')
            cc = select_users(valid_users, search_strings)

        values['BUGTITLE'] = opts['title'] or desc
        values['PRODUCT'] = opts.get('product', '') or ui.config(
            "bzexport", "product", '<choose-from-menu>')
        values['COMPONENT'] = opts.get('component', '') or ui.config(
            "bzexport", "component", '<choose-from-menu>')
        values['PRODVERSION'] = opts.get('prodversion', '') or ui.config(
            "bzexport", "prodversion", '<default>')
        values['BUGCOMMENT0'] = bug_comment
        values['CC'] = cc
        values['BLOCKS'] = blocks
        values['DEPENDS'] = depends

    values = fill_values(values, ui, api_server, finalize=False)

    if opts['edit']:
        if opts['new']:
            values = edit_form(ui, repo, values, 'new_both_template')
        else:
            values = edit_form(ui, repo, values, 'existing_bug_template')
            bug = values['BUGNUM']

        search_strings = []
        for key in ('REVIEWERS', 'CC', 'FEEDBACK'):
            # TODO: Handle <choose-from-menu>
            search_strings.extend(values.get(key, []))
        users = validate_users(ui, api_server, auth, search_strings,
                               multi_user_prompt, 'reviewer')
        if users is None:
            raise util.Abort("Invalid users")

        if 'REVIEWERS' in values:  # Always true
            reviewers = select_users(users, values['REVIEWERS'])
        if 'CC' in values:  # Only when opts['new']
            cc = select_users(users, values['CC'])
        if 'BLOCKS' in values:  # Only when opts['new']
            blocks = values['BLOCKS']
        if 'DEPENDS' in values:  # Only when opts['new']
            depends = values['DEPENDS']
        if 'FEEDBACK' in values:  # Always true
            feedback = select_users(users, values['FEEDBACK'])
        if 'ATTACHMENT_FILENAME' in values:
            filename = values['ATTACHMENT_FILENAME']

    values = fill_values(values, ui, api_server, finalize=True)

    if opts["new"]:
        if bug is not None:
            raise util.Abort(
                "Bug %s given but creation of new bug requested!" % bug)

        if opts['interactive'] and ui.prompt(
                _("Create bug in '%s' :: '%s' (y/n)?") %
            (values['PRODUCT'], values['COMPONENT'])) != 'y':
            ui.write(_("Exiting without creating bug\n"))
            return

        try:
            create_opts = {}
            if not opts['no_take_bug']:
                create_opts['assign_to'] = auth.username(api_server)
            result = bz.create_bug(auth,
                                   product=values['PRODUCT'],
                                   component=values['COMPONENT'],
                                   version=values['PRODVERSION'],
                                   title=values['BUGTITLE'],
                                   description=values['BUGCOMMENT0'],
                                   cc=cc,
                                   depends=depends,
                                   blocks=blocks,
                                   **create_opts)
            bug = result['id']
            ui.write("Created bug %s at %sshow_bug.cgi?id=%s\n" %
                     (bug, bugzilla, bug))
        except Exception, e:
            raise util.Abort(_("Error creating bug: %s\n" % str(e)))