def diffpatch(ui, repo, node1, node2, tmproot, matcher, cmdline): template = b'hg-%h.patch' # write patches to temporary files with formatter.nullformatter(ui, b'extdiff', {}) as fm: cmdutil.export( repo, [repo[node1].rev(), repo[node2].rev()], fm, fntemplate=repo.vfs.reljoin(tmproot, template), match=matcher, ) label1 = cmdutil.makefilename(repo[node1], template) label2 = cmdutil.makefilename(repo[node2], template) file1 = repo.vfs.reljoin(tmproot, label1) file2 = repo.vfs.reljoin(tmproot, label2) cmdline = formatcmdline( cmdline, repo.root, # no 3way while comparing patches do3way=False, parent1=file1, plabel1=label1, # while comparing patches, there is no second parent parent2=None, plabel2=None, child=file2, clabel=label2, ) ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)) ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff') return 1
def qshow(ui, repo, patchspec=None, **opts): '''display a patch If no patch is given, the top of the applied stack is shown.''' q = repo.mq patchf = None if patchspec is None: p = q.lookup("qtip") patchf = q.opener(p, "r") else: try: p = q.lookup(patchspec) patchf = q.opener(p, "r") except util.Abort, e: try: patchf = file(patchspec, "r") except Exception, e: # commands.diff has a bad error message if patchspec not in repo: raise util.Abort(_("Unknown patch '%s'") % patchspec) # the built-in export command does not label the diff for color # output, and the patch header generation is not reusable # independently def empty_diff(*args, **kwargs): return [] temp = patch.diff try: patch.diff = empty_diff cmdutil.export(repo, [ patchspec ], fp=ui) finally: patch.diff = temp return commands.diff(ui, repo, change=patchspec, date=None)
def getpatches(revs): prev = repo['.'].rev() for r in scmutil.revrange(repo, revs): if r == prev and (repo[None].files() or repo[None].deleted()): ui.warn( _('warning: working directory has ' 'uncommitted changes\n')) output = cStringIO.StringIO() cmdutil.export(repo, [r], fp=output, opts=patch.diffopts(ui, opts)) yield output.getvalue().split('\n')
def getpatches(revs): prev = repo['.'].rev() for r in scmutil.revrange(repo, revs): if r == prev and (repo[None].files() or repo[None].deleted()): ui.warn(_('warning: working directory has ' 'uncommitted changes\n')) output = cStringIO.StringIO() cmdutil.export(repo, [r], fp=output, opts=patch.diffopts(ui, opts)) yield output.getvalue().split('\n')
def _shelvecreatedcommit(ui, repo, node, name, tr): shelvedfile(repo, name, 'oshelve').writeobsshelveinfo({ 'node': nodemod.hex(node) }) if util.safehasattr(cmdutil, 'exportfile'): # Mercurial 4.6 and later cmdutil.exportfile(repo.unfiltered(), [node], shelvedfile(repo, name, patchextension).opener('wb'), opts=mdiff.diffopts(git=True)) else: # Mercurial 4.5 and earlier cmdutil.export(repo.unfiltered(), [node], fp=shelvedfile(repo, name, patchextension).opener('wb'), opts=mdiff.diffopts(git=True))
def diffstat(self): class patchbuf(object): def __init__(self): self.lines = [] # diffstat is stupid self.name = 'cia' def write(self, data): self.lines.append(data) def close(self): pass n = self.ctx.node() pbuf = patchbuf() cmdutil.export(self.cia.repo, [n], fp=pbuf) return patch.diffstat(pbuf.lines) or ''
def _getpatches(repo, revs, **opts): """return a list of patches for a list of revisions Each patch in the list is itself a list of lines. """ ui = repo.ui prev = repo['.'].rev() for r in revs: if r == prev and (repo[None].files() or repo[None].deleted()): ui.warn(_('warning: working directory has ' 'uncommitted changes\n')) output = cStringIO.StringIO() cmdutil.export(repo, [r], fp=output, opts=patch.difffeatureopts(ui, opts, git=True)) yield output.getvalue().split('\n')
def _getpatches(repo, revs, **opts): """return a list of patches for a list of revisions Each patch in the list is itself a list of lines. """ ui = repo.ui prev = repo['.'].rev() for r in scmutil.revrange(repo, revs): if r == prev and (repo[None].files() or repo[None].deleted()): ui.warn(_('warning: working directory has ' 'uncommitted changes\n')) output = cStringIO.StringIO() cmdutil.export(repo, [r], fp=output, opts=patch.difffeatureopts(ui, opts, git=True)) yield output.getvalue().split('\n')
def diffstat(self): class patchbuf(object): def __init__(self): self.lines = [] # diffstat is stupid self.name = "cia" def write(self, data): self.lines += data.splitlines(True) def close(self): pass n = self.ctx.node() pbuf = patchbuf() cmdutil.export(self.cia.repo, [n], fp=pbuf) return patch.diffstat(pbuf.lines) or ""
def qshow(ui, repo, patchspec=None, **opts): '''display a patch If no patch is given, the top of the applied stack is shown.''' patchf = resolve_patchfile(ui, repo, patchspec) if patchf is None: # commands.diff has a bad error message if patchspec is None: patchspec = '.' if patchspec not in repo and not repo.revs(patchspec).first(): raise util.Abort(_("Unknown patch '%s'") % patchspec) # the built-in export command does not label the diff for color # output, and the patch header generation is not reusable # independently def empty_diff(*args, **kwargs): return [] temp = patch.diff try: patch.diff = empty_diff cmdutil.export(repo, repo.revs(patchspec), fp=ui) finally: patch.diff = temp return commands.diff(ui, repo, change=patchspec, date=None, **opts) if opts['stat']: del opts['stat'] lines = patch.diffstatui(patchf, **opts) else: def singlefile(*a, **b): return patchf lines = patch.difflabel(singlefile, **opts) for chunk, label in lines: ui.write(chunk, label=label) patchf.close()
def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False, opts=None): ''' export changesets as hg patches. Mercurial moved patch.export to cmdutil.export after version 1.5 (change e764f24a45ee in mercurial). ''' try: return cmdutil.export(repo, revs, template, fp, switch_parent, opts) except AttributeError: from mercurial import patch return patch.export(repo, revs, template, fp, switch_parent, opts)
def qshow(ui, repo, patchspec=None, **opts): '''display a patch If no patch is given, the top of the applied stack is shown.''' q = repo.mq patchf = None if patchspec is None: p = q.lookup("qtip") patchf = q.opener(p, "r") else: try: p = q.lookup(patchspec) patchf = q.opener(p, "r") except util.Abort, e: try: patchf = file(patchspec, "r") except Exception, e: # commands.diff has a bad error message if patchspec not in repo: raise util.Abort(_("Unknown patch '%s'") % patchspec) # the built-in export command does not label the diff for color # output, and the patch header generation is not reusable # independently def empty_diff(*args, **kwargs): return [] temp = patch.diff try: patch.diff = empty_diff cmdutil.export(repo, [patchspec], fp=ui) finally: patch.diff = temp return commands.diff(ui, repo, change=patchspec, date=None)
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 dodiff(ui, repo, cmdline, pats, opts): '''Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir ''' revs = opts.get('rev') change = opts.get('change') do3way = '$parent2' in cmdline if revs and change: msg = _('cannot specify --rev and --change at the same time') raise error.Abort(msg) elif change: node2 = scmutil.revsingle(repo, change, None).node() node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = scmutil.revpair(repo, revs) if not revs: node1b = repo.dirstate.p2() else: node1b = nullid # Disable 3-way merge if there is only one parent if do3way: if node1b == nullid: do3way = False subrepos = opts.get('subrepos') matcher = scmutil.match(repo[node2], pats, opts) if opts.get('patch'): if subrepos: raise error.Abort(_('--patch cannot be used with --subrepos')) if node2 is None: raise error.Abort(_('--patch requires two revisions')) else: mod_a, add_a, rem_a = list( map(set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3])) if do3way: mod_b, add_b, rem_b = list( map( set, repo.status(node1b, node2, matcher, listsubrepos=subrepos)[:3])) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b common = modadd | rem_a | rem_b if not common: return 0 tmproot = tempfile.mkdtemp(prefix='extdiff.') try: if not opts.get('patch'): # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] rev1a = '@%d' % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0] rev1b = '@%d' % repo[node1b].rev() else: dir1b = None rev1b = '' fnsandstat = [] # If node2 in not the wc or there is >1 change, copy it dir2root = '' rev2 = '' if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] rev2 = '@%d' % repo[node2].rev() elif len(common) > 1: #we only actually need to get the files to copy back to #the working dir in this case (because the other cases #are: diffing 2 revisions or single file -- in which case #the file is already directly passed to the diff tool). dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos) else: # This lets the diff tool open the changed file directly dir2 = '' dir2root = repo.root label1a = rev1a label1b = rev1b label2 = rev2 # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 else: template = 'hg-%h.patch' cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()], fntemplate=repo.vfs.reljoin(tmproot, template), match=matcher) label1a = cmdutil.makefilename(repo, template, node1a) label2 = cmdutil.makefilename(repo, template, node2) dir1a = repo.vfs.reljoin(tmproot, label1a) dir2 = repo.vfs.reljoin(tmproot, label2) dir1b = None label1b = None fnsandstat = [] # Function to quote file/dir names in the argument string. # When not operating in 3-way mode, an empty string is # returned for parent2 replace = { 'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b, 'plabel1': label1a, 'plabel2': label1b, 'clabel': label2, 'child': dir2, 'root': repo.root } def quote(match): pre = match.group(2) key = match.group(3) if not do3way and key == 'parent2': return pre return pre + util.shellquote(replace[key]) # Match parent2 first, so 'parent1?' will match both parent1 and parent regex = (r'''(['"]?)([^\s'"$]*)''' r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') if not do3way and not re.search(regex, cmdline): cmdline += ' $parent1 $child' cmdline = re.sub(regex, quote, cmdline) ui.debug('running %r in %s\n' % (cmdline, tmproot)) ui.system(cmdline, cwd=tmproot, blockedtag='extdiff') for copy_fn, working_fn, st in fnsandstat: cpstat = os.lstat(copy_fn) # Some tools copy the file and attributes, so mtime may not detect # all changes. A size check will detect more cases, but not all. # The only certain way to detect every case is to diff all files, # which could be expensive. # copyfile() carries over the permission, so the mode check could # be in an 'elif' branch, but for the case where the file has # changed without affecting mtime or size. if (cpstat.st_mtime != st.st_mtime or cpstat.st_size != st.st_size or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)): ui.debug('file changed while diffing. ' 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) return 1 finally: ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot)
def dodiff(ui, repo, cmdline, pats, opts): '''Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir ''' revs = opts.get('rev') change = opts.get('change') do3way = '$parent2' in cmdline if revs and change: msg = _('cannot specify --rev and --change at the same time') raise error.Abort(msg) elif change: node2 = scmutil.revsingle(repo, change, None).node() node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = scmutil.revpair(repo, revs) if not revs: node1b = repo.dirstate.p2() else: node1b = nullid # Disable 3-way merge if there is only one parent if do3way: if node1b == nullid: do3way = False subrepos=opts.get('subrepos') matcher = scmutil.match(repo[node2], pats, opts) if opts.get('patch'): if subrepos: raise error.Abort(_('--patch cannot be used with --subrepos')) if node2 is None: raise error.Abort(_('--patch requires two revisions')) else: mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3]) if do3way: mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher, listsubrepos=subrepos)[:3]) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b common = modadd | rem_a | rem_b if not common: return 0 tmproot = tempfile.mkdtemp(prefix='extdiff.') try: if not opts.get('patch'): # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] rev1a = '@%d' % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0] rev1b = '@%d' % repo[node1b].rev() else: dir1b = None rev1b = '' fns_and_mtime = [] # If node2 in not the wc or there is >1 change, copy it dir2root = '' rev2 = '' if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] rev2 = '@%d' % repo[node2].rev() elif len(common) > 1: #we only actually need to get the files to copy back to #the working dir in this case (because the other cases #are: diffing 2 revisions or single file -- in which case #the file is already directly passed to the diff tool). dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot, subrepos) else: # This lets the diff tool open the changed file directly dir2 = '' dir2root = repo.root label1a = rev1a label1b = rev1b label2 = rev2 # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 else: template = 'hg-%h.patch' cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()], template=repo.vfs.reljoin(tmproot, template), match=matcher) label1a = cmdutil.makefilename(repo, template, node1a) label2 = cmdutil.makefilename(repo, template, node2) dir1a = repo.vfs.reljoin(tmproot, label1a) dir2 = repo.vfs.reljoin(tmproot, label2) dir1b = None label1b = None fns_and_mtime = [] # Function to quote file/dir names in the argument string. # When not operating in 3-way mode, an empty string is # returned for parent2 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b, 'plabel1': label1a, 'plabel2': label1b, 'clabel': label2, 'child': dir2, 'root': repo.root} def quote(match): pre = match.group(2) key = match.group(3) if not do3way and key == 'parent2': return pre return pre + util.shellquote(replace[key]) # Match parent2 first, so 'parent1?' will match both parent1 and parent regex = (r'''(['"]?)([^\s'"$]*)''' r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') if not do3way and not re.search(regex, cmdline): cmdline += ' $parent1 $child' cmdline = re.sub(regex, quote, cmdline) ui.debug('running %r in %s\n' % (cmdline, tmproot)) ui.system(cmdline, cwd=tmproot) for copy_fn, working_fn, mtime in fns_and_mtime: if os.lstat(copy_fn).st_mtime != mtime: ui.debug('file changed while diffing. ' 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) return 1 finally: ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot)
def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" def publicancestors(ctx): """Compute the public ancestors of a commit. Much faster than the revset ancestors(ctx) & draft()""" seen = set([nullrev]) visit = util.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise util.Abort(_('cannot shelve while merging')) parent = parents[0] # we never need the user, so we use a generic user for all shelve operations user = '******' label = repo._bookmarkcurrent or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') def gennames(): yield label for i in xrange(1, 100): yield '%s-%02d' % (label, i) shelvedfiles = [] def commitfunc(ui, repo, message, match, opts): # check modified, added, removed, deleted only for flist in repo.status(match=match)[:4]: shelvedfiles.extend(flist) hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False try: return repo.commit(message, user, opts.get('date'), match, editor=cmdutil.getcommiteditor(**opts)) finally: if hasmq: repo.mq.checkapplied = saved if parent.node() != nullid: desc = "changes to '%s'" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts['message']: opts['message'] = desc name = opts['name'] wlock = lock = tr = bms = None try: wlock = repo.wlock() lock = repo.lock() bms = repo._bookmarks.copy() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) if name: if shelvedfile(repo, name, 'hg').exists(): raise util.Abort(_("a shelved change named '%s' already exists") % name) else: for n in gennames(): if not shelvedfile(repo, n, 'hg').exists(): name = n break else: raise util.Abort(_("too many shelved changes named '%s'") % label) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise util.Abort(_('shelved change names may not contain slashes')) if name.startswith('.'): raise util.Abort(_("shelved change names may not start with '.'")) node = cmdutil.commit(ui, repo, commitfunc, pats, opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat[3]: ui.status(_("nothing changed (%d missing files, see " "'hg status')\n") % len(stat[3])) else: ui.status(_("nothing changed\n")) return 1 phases.retractboundary(repo, phases.secret, [node]) fp = shelvedfile(repo, name, 'files').opener('wb') fp.write('\0'.join(shelvedfiles)) bases = list(publicancestors(repo[node])) cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve') shelvedfile(repo, name, 'hg').writebundle(cg) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, 'patch').opener('wb'), opts=mdiff.diffopts(git=True)) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) finally: if bms: # restore old bookmarks repo._bookmarks.update(bms) repo._bookmarks.write() if tr: tr.abort() lockmod.release(lock, wlock)
def _shelvecreatedcommit(repo, node, name): bases = list(mutableancestors(repo[node])) shelvedfile(repo, name, 'hg').writebundle(bases, node) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, patchextension).opener('wb'), opts=mdiff.diffopts(git=True))
def _docreatecmd(ui, repo, pats, opts): def mutableancestors(ctx): """return all mutable ancestors for ctx (included) Much faster than the revset ancestors(ctx) & draft()""" seen = set([nodemod.nullrev]) visit = collections.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise error.Abort(_('cannot shelve while merging')) parent = parents[0] origbranch = wctx.branch() # we never need the user, so we use a generic user for all shelve operations user = '******' label = repo._activebookmark or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') def gennames(): yield label for i in xrange(1, 100): yield '%s-%02d' % (label, i) if parent.node() != nodemod.nullid: desc = "changes to: %s" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts.get('message'): opts['message'] = desc name = opts.get('name') lock = tr = None try: lock = repo.lock() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) if name: if shelvedfile(repo, name, 'hg').exists(): raise error.Abort( _("a shelved change named '%s' already exists") % name) else: for n in gennames(): if not shelvedfile(repo, n, 'hg').exists(): name = n break else: raise error.Abort( _("too many shelved changes named '%s'") % label) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise error.Abort( _('shelved change names may not contain slashes')) if name.startswith('.'): raise error.Abort(_("shelved change names may not start with '.'")) interactive = opts.get('interactive', False) includeunknown = (opts.get('unknown', False) and not opts.get('addremove', False)) extra = {} if includeunknown: s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True) if s.unknown: extra['shelve_unknown'] = '\0'.join(s.unknown) repo[None].add(s.unknown) if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): # In non-bare shelve we don't store newly created branch # at bundled commit repo.dirstate.setbranch(repo['.'].branch()) def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False backup = repo.ui.backupconfig('phases', 'new-commit') try: repo.ui.setconfig('phases', 'new-commit', phases.secret) editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts) return repo.commit(message, user, opts.get('date'), match, editor=editor, extra=extra) finally: repo.ui.restoreconfig(backup) if hasmq: repo.mq.checkapplied = saved def interactivecommitfunc(ui, repo, *pats, **opts): match = scmutil.match(repo['.'], pats, {}) message = opts['message'] return commitfunc(ui, repo, message, match, opts) if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: node = cmdutil.dorecord(ui, repo, interactivecommitfunc, None, False, cmdutil.recordfilter, *pats, **opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat.deleted: ui.status( _("nothing changed (%d missing files, see " "'hg status')\n") % len(stat.deleted)) else: ui.status(_("nothing changed\n")) return 1 bases = list(mutableancestors(repo[node])) shelvedfile(repo, name, 'hg').writebundle(bases, node) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, 'patch').opener('wb'), opts=mdiff.diffopts(git=True)) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts): repo.dirstate.setbranch(origbranch) _aborttransaction(repo) finally: lockmod.release(tr, lock)
def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" def publicancestors(ctx): """Compute the public ancestors of a commit. Much faster than the revset ancestors(ctx) & draft()""" seen = set([nullrev]) visit = util.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise util.Abort(_('cannot shelve while merging')) parent = parents[0] # we never need the user, so we use a generic user for all shelve operations user = '******' label = repo._bookmarkcurrent or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') def gennames(): yield label for i in xrange(1, 100): yield '%s-%02d' % (label, i) shelvedfiles = [] def commitfunc(ui, repo, message, match, opts): # check modified, added, removed, deleted only for flist in repo.status(match=match)[:4]: shelvedfiles.extend(flist) hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False try: return repo.commit(message, user, opts.get('date'), match, editor=cmdutil.getcommiteditor(**opts)) finally: if hasmq: repo.mq.checkapplied = saved if parent.node() != nullid: desc = "changes to '%s'" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts['message']: opts['message'] = desc name = opts['name'] wlock = lock = tr = bms = None try: wlock = repo.wlock() lock = repo.lock() bms = repo._bookmarks.copy() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) if name: if shelvedfile(repo, name, 'hg').exists(): raise util.Abort( _("a shelved change named '%s' already exists") % name) else: for n in gennames(): if not shelvedfile(repo, n, 'hg').exists(): name = n break else: raise util.Abort( _("too many shelved changes named '%s'") % label) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise util.Abort(_('shelved change names may not contain slashes')) if name.startswith('.'): raise util.Abort(_("shelved change names may not start with '.'")) node = cmdutil.commit(ui, repo, commitfunc, pats, opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat[3]: ui.status( _("nothing changed (%d missing files, see " "'hg status')\n") % len(stat[3])) else: ui.status(_("nothing changed\n")) return 1 phases.retractboundary(repo, phases.secret, [node]) fp = shelvedfile(repo, name, 'files').opener('wb') fp.write('\0'.join(shelvedfiles)) bases = list(publicancestors(repo[node])) cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve') shelvedfile(repo, name, 'hg').writebundle(cg) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, 'patch').opener('wb'), opts=mdiff.diffopts(git=True)) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) finally: if bms: # restore old bookmarks repo._bookmarks.update(bms) repo._bookmarks.write() if tr: tr.abort() lockmod.release(lock, wlock)
def getpatches(revs): for r in scmutil.revrange(repo, revs): output = cStringIO.StringIO() cmdutil.export(repo, [r], fp=output, opts=patch.diffopts(ui, opts)) yield output.getvalue().split('\n')
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 dodiff(ui, repo, cmdline, pats, opts, guitool=False): '''Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir ''' revs = opts.get(b'rev') change = opts.get(b'change') do3way = b'$parent2' in cmdline if revs and change: msg = _(b'cannot specify --rev and --change at the same time') raise error.Abort(msg) elif change: ctx2 = scmutil.revsingle(repo, change, None) ctx1a, ctx1b = ctx2.p1(), ctx2.p2() else: ctx1a, ctx2 = scmutil.revpair(repo, revs) if not revs: ctx1b = repo[None].p2() else: ctx1b = repo[nullid] perfile = opts.get(b'per_file') confirm = opts.get(b'confirm') node1a = ctx1a.node() node1b = ctx1b.node() node2 = ctx2.node() # Disable 3-way merge if there is only one parent if do3way: if node1b == nullid: do3way = False subrepos = opts.get(b'subrepos') matcher = scmutil.match(repo[node2], pats, opts) if opts.get(b'patch'): if subrepos: raise error.Abort(_(b'--patch cannot be used with --subrepos')) if perfile: raise error.Abort(_(b'--patch cannot be used with --per-file')) if node2 is None: raise error.Abort(_(b'--patch requires two revisions')) else: mod_a, add_a, rem_a = map( set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3]) if do3way: mod_b, add_b, rem_b = map( set, repo.status(node1b, node2, matcher, listsubrepos=subrepos)[:3], ) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b common = modadd | rem_a | rem_b if not common: return 0 tmproot = pycompat.mkdtemp(prefix=b'extdiff.') try: if not opts.get(b'patch'): # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] rev1a = b'@%d' % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0] rev1b = b'@%d' % repo[node1b].rev() else: dir1b = None rev1b = b'' fnsandstat = [] # If node2 in not the wc or there is >1 change, copy it dir2root = b'' rev2 = b'' if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] rev2 = b'@%d' % repo[node2].rev() elif len(common) > 1: # we only actually need to get the files to copy back to # the working dir in this case (because the other cases # are: diffing 2 revisions or single file -- in which case # the file is already directly passed to the diff tool). dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos) else: # This lets the diff tool open the changed file directly dir2 = b'' dir2root = repo.root label1a = rev1a label1b = rev1b label2 = rev2 # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 else: template = b'hg-%h.patch' with formatter.nullformatter(ui, b'extdiff', {}) as fm: cmdutil.export( repo, [repo[node1a].rev(), repo[node2].rev()], fm, fntemplate=repo.vfs.reljoin(tmproot, template), match=matcher, ) label1a = cmdutil.makefilename(repo[node1a], template) label2 = cmdutil.makefilename(repo[node2], template) dir1a = repo.vfs.reljoin(tmproot, label1a) dir2 = repo.vfs.reljoin(tmproot, label2) dir1b = None label1b = None fnsandstat = [] if not perfile: # Run the external tool on the 2 temp directories or the patches cmdline = formatcmdline( cmdline, repo.root, do3way=do3way, parent1=dir1a, plabel1=label1a, parent2=dir1b, plabel2=label1b, child=dir2, clabel=label2, ) ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)) ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff') else: # Run the external tool once for each pair of files _runperfilediff( cmdline, repo.root, ui, guitool=guitool, do3way=do3way, confirm=confirm, commonfiles=common, tmproot=tmproot, dir1a=dir1a, dir1b=dir1b, dir2root=dir2root, dir2=dir2, rev1a=rev1a, rev1b=rev1b, rev2=rev2, ) for copy_fn, working_fn, st in fnsandstat: cpstat = os.lstat(copy_fn) # Some tools copy the file and attributes, so mtime may not detect # all changes. A size check will detect more cases, but not all. # The only certain way to detect every case is to diff all files, # which could be expensive. # copyfile() carries over the permission, so the mode check could # be in an 'elif' branch, but for the case where the file has # changed without affecting mtime or size. if (cpstat[stat.ST_MTIME] != st[stat.ST_MTIME] or cpstat.st_size != st.st_size or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)): ui.debug(b'file changed while diffing. ' b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) return 1 finally: ui.note(_(b'cleaning up temp directory\n')) shutil.rmtree(tmproot)
def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" def mutableancestors(ctx): """return all mutable ancestors for ctx (included) Much faster than the revset ancestors(ctx) & draft()""" seen = set([nullrev]) visit = collections.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise error.Abort(_('cannot shelve while merging')) parent = parents[0] # we never need the user, so we use a generic user for all shelve operations user = '******' label = repo._activebookmark or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') def gennames(): yield label for i in xrange(1, 100): yield '%s-%02d' % (label, i) def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False backup = repo.ui.backupconfig('phases', 'new-commit') try: repo.ui. setconfig('phases', 'new-commit', phases.secret) editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts) return repo.commit(message, user, opts.get('date'), match, editor=editor) finally: repo.ui.restoreconfig(backup) if hasmq: repo.mq.checkapplied = saved if parent.node() != nullid: desc = "changes to '%s'" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts['message']: opts['message'] = desc name = opts['name'] wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) if name: if shelvedfile(repo, name, 'hg').exists(): raise error.Abort(_("a shelved change named '%s' already exists" ) % name) else: for n in gennames(): if not shelvedfile(repo, n, 'hg').exists(): name = n break else: raise error.Abort(_("too many shelved changes named '%s'") % label) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise error.Abort(_('shelved change names may not contain slashes')) if name.startswith('.'): raise error.Abort(_("shelved change names may not start with '.'")) interactive = opts.get('interactive', False) def interactivecommitfunc(ui, repo, *pats, **opts): match = scmutil.match(repo['.'], pats, {}) message = opts['message'] return commitfunc(ui, repo, message, match, opts) if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: node = cmdutil.dorecord(ui, repo, interactivecommitfunc, None, False, cmdutil.recordfilter, *pats, **opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat.deleted: ui.status(_("nothing changed (%d missing files, see " "'hg status')\n") % len(stat.deleted)) else: ui.status(_("nothing changed\n")) return 1 bases = list(mutableancestors(repo[node])) shelvedfile(repo, name, 'hg').writebundle(bases, node) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, 'patch').opener('wb'), opts=mdiff.diffopts(git=True)) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) _aborttransaction(repo) finally: lockmod.release(tr, lock, wlock)