from __future__ import absolute_import import sys is_python_3 = sys.version_info[0] >= 3 if is_python_3: def arc_items(dict): return dict.items() else: def arc_items(dict): return dict.iteritems() import os import json from mercurial import ( cmdutil, bookmarks, bundlerepo, error, hg, i18n, node, ) _ = i18n._ cmdtable = {} # Older veresions of Mercurial (~4.7) moved the command function and the # remoteopts object to different modules. Using try/except here to attempt # allowing this module to load properly, despite whether individual commands # will work properly on older versions of Mercurial or not. # https://phab.mercurial-scm.org/rHG46ba2cdda476ac53a8a8f50e4d9435d88267db60 # https://phab.mercurial-scm.org/rHG04baab18d60a5c833ab3190506147e01b3c6d12c try: from mercurial import registrar command = registrar.command(cmdtable) except: command = cmdutil.command(cmdtable) try: remoteopts = cmdutil.remoteopts except: from mercurial import commands remoteopts = commands.remoteopts try: parseurl = hg.parseurl except: from mercurial import utils parseurl = utils.urlutil.parseurl @command( b'arc-amend', [ (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')), (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')), (b'u', b'user', b'', _(b'record the specified user as committer'), _(b'USER')), (b'd', b'date', b'', _(b'record the specified date as commit date'), _(b'DATE')), (b'A', b'addremove', False, _(b'mark new/missing files as added/removed before committing')), (b'n', b'note', b'', _(b'store a note on amend'), _(b'TEXT')), ], _(b'[OPTION]')) def amend(ui, repo, source=None, **opts): """amend Uses Mercurial internal API to amend changes to a non-head commit. (This is an Arcanist extension to Mercurial.) Returns 0 if amending succeeds, 1 otherwise. """ # The option keys seem to come in as 'str' type but the cmdutil.amend() code # expects them as binary. To account for both Python 2 and Python 3 # compatibility, insert the value under both 'str' and binary type. newopts = {} for key in opts: val = opts.get(key) newopts[key] = val if isinstance(key, str): newkey = key.encode('UTF-8') newopts[newkey] = val orig = repo[b'.'] extra = {} pats = [] cmdutil.amend(ui, repo, orig, extra, pats, newopts) """ # This will allow running amend on older versions of Mercurial, ~3.5, however # the behavior on those versions will squash child commits of the working # directory into the amended commit which is undesired. try: cmdutil.amend(ui, repo, orig, extra, pats, newopts) except: def commitfunc(ui, repo, message, match, opts): return repo.commit( message, opts.get('user') or orig.user(), opts.get('date') or orig.date(), match, extra=extra) cmdutil.amend(ui, repo, commitfunc, orig, extra, pats, newopts) """ return 0 @command( b'arc-ls-markers', [ (b'', b'output', b'', _(b'file to output refs to'), _(b'FILE')), ] + remoteopts, _(b'[--output FILENAME] [SOURCE]')) def lsmarkers(ui, repo, source=None, **opts): """list markers Show the current branch heads and bookmarks in the local working copy, or a specified path/URL. Markers are printed to stdout in JSON. (This is an Arcanist extension to Mercurial.) Returns 0 if listing the markers succeeds, 1 otherwise. """ if source is None: markers = localmarkers(ui, repo) else: markers = remotemarkers(ui, repo, source, opts) for m in markers: if m['name'] != None: m['name'] = m['name'].decode('utf-8') if m['node'] != None: m['node'] = m['node'].decode('utf-8') if m['description'] != None: m['description'] = m['description'].decode('utf-8') json_opts = { 'indent': 2, 'sort_keys': True, } output_file = opts.get('output') if output_file: if os.path.exists(output_file): raise error.Abort(_('File "%s" already exists.' % output_file)) with open(output_file, 'w+') as f: json.dump(markers, f, **json_opts) else: json_data = json.dumps(markers, **json_opts) print(json_data) return 0 def localmarkers(ui, repo): markers = [] active_node = repo[b'.'].node() all_heads = set(repo.heads()) current_name = repo.dirstate.branch() branch_list = repo.branchmap().iterbranches() for branch_name, branch_heads, tip_node, is_closed in branch_list: for head_node in branch_heads: is_active = False if branch_name == current_name: if head_node == active_node: is_active = True is_tip = (head_node == tip_node) if is_closed: head_closed = True else: head_closed = bool(head_node not in all_heads) description = repo[head_node].description() markers.append({ 'type': 'branch', 'name': branch_name, 'node': node.hex(head_node), 'isActive': is_active, 'isClosed': head_closed, 'isTip': is_tip, 'description': description, }) bookmarks = repo._bookmarks active_bookmark = repo._activebookmark for bookmark_name, bookmark_node in arc_items(bookmarks): is_active = (active_bookmark == bookmark_name) description = repo[bookmark_node].description() markers.append({ 'type': 'bookmark', 'name': bookmark_name, 'node': node.hex(bookmark_node), 'isActive': is_active, 'description': description, }) # Add virtual markers for the current commit state and current branch state # so callers can figure out exactly where we are. # Common cases where this matters include: # You run "hg update 123" to update to an older revision. Your working # copy commit will not be a branch head or a bookmark. # You run "hg branch X" to create a new branch, but have not made any commits # yet. Your working copy branch will not be reflected in any commits. markers.append({ 'type': 'branch-state', 'name': current_name, 'node': None, 'isActive': True, 'isClosed': False, 'isTip': False, 'description': None, }) markers.append({ 'type': 'commit-state', 'name': None, 'node': node.hex(active_node), 'isActive': True, 'isClosed': False, 'isTip': False, 'description': repo[b'.'].description(), }) return markers def remotemarkers(ui, repo, source, opts): # Disable status output from fetching a remote. ui.quiet = True markers = [] # Determine the remote to use based on the default path configured in # [ui.paths] for this local repository. # # The expandpath function in the ui module was deprecated in 5.8 and removed # in 6.4. NOTE: There is also an expandpath function in mercurial.util (not # plural mercurial.utils...) however that function behaves differently from # the old ui.expandpath. Reviewing the source comments for the old # ui.expandpath function points to using urilutils.get_ functions. try: remote_path, branches = parseurl(ui.expandpath(source)) except: from mercurial import utils origsource, remote_path, branch = utils.urlutil.get_clone_path(ui, source) remote = hg.peer(repo, opts, remote_path) with remote.commandexecutor() as e: branchmap = e.callcommand(b'branchmap', {}).result() for branch_name in branchmap: for branch_node in branchmap[branch_name]: markers.append({ 'type': 'branch', 'name': branch_name, 'node': node.hex(branch_node), 'description': None, }) with remote.commandexecutor() as e: remotemarks = bookmarks.unhexlifybookmarks(e.callcommand(b'listkeys', { b'namespace': b'bookmarks', }).result()) for mark in remotemarks: markers.append({ 'type': 'bookmark', 'name': mark, 'node': node.hex(remotemarks[mark]), 'description': None, }) return markers