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()
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()
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
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()
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
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
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)))
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))
def get_patch_user(patch_file): ph = patchheader(patch_file) return ph.user
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)))