def rage(ui, repo, *pats, **opts): """collect troubleshooting diagnostics The rage command collects useful diagnostic information. By default, the information will be uploaded to Phabricator and instructions about how to ask for help will be printed. After submitting to Phabricator, it prints configerable advice:: [rage] advice = Please see our FAQ guide: https://... """ with progress.spinner(ui, "collecting information"): msg = _makerage(ui, repo, **opts) if opts.get("preview"): ui.pager("rage") ui.write("%s\n" % msg) return with progress.spinner(ui, "saving paste"): try: p = subprocess.Popen( ["pastry", "--lang", "hgrage", "--title", "hgrage"], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, shell=pycompat.iswindows, ) out, err = p.communicate(input=msg + "\n") ret = p.returncode except OSError: ui.write(_("Failed calling pastry. (is it in your PATH?)\n")) ret = 1 if ret: fd, tmpname = tempfile.mkstemp(prefix="hg-rage-") with util.fdopen(fd, r"w") as tmpfp: tmpfp.write(msg) ui.write( _( "Failed to post the diagnostic paste to Phabricator, " "but its contents have been written to:\n\n" ) ) ui.write(_(" %s\n") % tmpname, label="rage.link") ui.write( _("\nPlease include this file in the %s.\n") % ui.config("ui", "supportcontact") ) else: ui.write( _("Please post in %s with the following link:\n\n") % (ui.config("ui", "supportcontact")) ) ui.write(" " + out + "\n", label="rage.link") ui.write(ui.config("rage", "advice", "") + "\n")
def render(self): ui = self.ui ui.pushbuffer() ui.status(_("Interactive Smartlog History\n\n")) if opts.get("all"): limit = 0 else: limit = 2 * 604800 # two weeks if self.index == len(self.versions): self.index = -1 if self.index == -2: self.index = len(self.versions) - 1 if self.index == -1: with progress.spinner(ui, _("fetching")): firstpublic, revdag = serv.getsmartlog( reponame, workspacename, repo, limit) ui.status(_("Current Smartlog:\n\n")) else: with progress.spinner(ui, _("fetching")): firstpublic, revdag, slversion, sltimestamp = serv.getsmartlogbyversion( reponame, workspacename, repo, None, self.versions[self.index]["version_number"], limit, ) formatteddate = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(sltimestamp)) ui.status( _("Smartlog version %d \nsynced at %s\n\n") % (slversion, formatteddate)) template = "sl_cloud" smartlogstyle = ui.config("templatealias", template) if smartlogstyle: opts["template"] = "{%s}" % smartlogstyle else: ui.debug( _("style %s is not defined, skipping") % smartlogstyle, component="commitcloud", ) displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True) if ui.config("experimental", "graph.renderer") == "legacy": cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges) else: cmdutil.rustdisplaygraph(ui, repo, revdag, displayer, reserved=firstpublic) repo.ui.status( _("<-: newer " "->: older " "q: abort \n" "a: 1 day forward d: 1 day back \n")) return ui.popbuffer()
def pendingchanges(self, match=None, listignored=False): # type: (Optional[Callable[[str], bool]], bool) -> Iterable[Tuple[str, bool]] def bail(reason): self._ui.debug("fsmonitor: fallback to core status, %s\n" % reason) return super(fsmonitorfilesystem, self).pendingchanges( match, listignored=listignored ) if self._fsmonitordisable: return bail("fsmonitor disabled") if listignored: return bail("listing ignored files") if not self._watchmanclient.available(): return bail("client unavailable") with progress.spinner(self._ui, "scanning working copy"), self._detectrace( match ): try: # Ideally we'd return the result incrementally, but we need to # be able to fall back if watchman fails. So let's consume the # whole pendingchanges list upfront. return list(self._fspendingchanges(match)) except fsmonitorfallback as ex: return bail(str(ex))
def command(self, *args, **kwargs): ignoreerrors = kwargs.get("ignoreerrors", False) with progress.spinner(self._ui, "querying watchman"): try: try: return self._command(*args) except pywatchman.UseAfterFork: # Ideally we wouldn't let this happen, but if it does happen, # record it in the log and retry the command. blackbox.log( { "debug": { "value": "fork detected. re-connect to watchman socket" } } ) self._watchmanclient = None return self._command(*args) except WatchmanNoRoot: # this 'watch' command can also raise a WatchmanNoRoot if # watchman refuses to accept this root self._command("watch") return self._command(*args) except Unavailable: # this is in an outer scope to catch Unavailable form any of the # above _command calls if not ignoreerrors: self._watchmanclient = None raise
def loadoldversion(self, versionindex): versionnumber = self.versions[versionindex]["version_number"] with self.servlock, progress.spinner( self.ui, _("fetching version %s") % versionnumber): limit = self.limit if limit > 0: # Increase the limit by how long ago the smartlog was # backed-up. This gives a rolling window, so viewing # versions more than the limit in age will still show # commits. timestamp = self.versions[versionindex]["timestamp"] limit += max(0, int(time.time() - timestamp)) slinfo = self.serv.getsmartlogbyversion( self.reponame, self.workspacename, self.repo, None, versionnumber, limit, self.flags, ) formatteddate = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(slinfo.timestamp)) title = "Smartlog version %d synced at %s:" % ( slinfo.version, formatteddate, ) return (title, slinfo)
def __init__(self, ui, repo, reponame, workspacename, **opts): self.ui = ui self.repo = repo self.reponame = reponame self.workspacename = workspacename self.opts = opts self.serv = service.get(ui, tokenmod.TokenLocator(ui).token) self.servlock = threading.Lock() self.renderevent = threading.Event() self.running = True self.cache = {} with progress.spinner(ui, _("fetching cloud smartlog history")): self.versions = sorted( self.serv.gethistoricalversions(reponame, workspacename), key=lambda version: version["version_number"], ) smartlogstyle = ui.config("templatealias", template) if smartlogstyle: self.opts["template"] = "{%s}" % smartlogstyle self.cur_index = len(self.versions) if opts.get("all"): self.limit = 0 else: self.limit = 2 * 7 * 24 * 60 * 60 # two weeks self.flags = []
def _cleanupoldpacks(ui, packpath, limit): """Enforce a size limit on the cache. Packfiles will be removed oldest first, with the asumption that old packfiles contains less useful data than new ones. """ with progress.spinner(ui, _("cleaning old packs")): def _mtime(f): stat = util.lstat(f) return stat.st_mtime def _listpackfiles(path): packs = [] try: for f in os.listdir(path): _, ext = os.path.splitext(f) if ext.endswith("pack"): packs.append(os.path.join(packpath, f)) except OSError as ex: if ex.errno != errno.ENOENT: raise return packs files = sorted(_listpackfiles(packpath), key=_mtime, reverse=True) cachesize = 0 for f in files: stat = os.lstat(f) cachesize += stat.st_size while cachesize > limit: f = files.pop() stat = util.lstat(f) # Dont't remove files that are newer than 10 minutes. This will # avoid a race condition where mercurial downloads files from the # network and expect these to be present on disk. If the 'limit' is # properly set, we should have removed enough files that this # condition won't matter. if time.gmtime(stat.st_mtime + 10 * 60) > time.gmtime(): return root, ext = os.path.splitext(f) try: if ext == datapack.PACKSUFFIX: util.unlink(root + datapack.INDEXSUFFIX) else: util.unlink(root + historypack.INDEXSUFFIX) except OSError as ex: if ex.errno != errno.ENOENT: raise try: util.unlink(f) except OSError as ex: if ex.errno != errno.ENOENT: raise cachesize -= stat.st_size
def loadcurrentversion(self): with self.servlock: with progress.spinner(self.ui, _("fetching latest version")): slinfo = self.serv.getsmartlog( self.reponame, self.workspacename, self.repo, self.limit, self.flags, ) title = "Current cloud smartlog" return (title, slinfo)
def _cleanuptemppacks(ui, packpath): """In some situations, temporary pack files are left around unecessarily using disk space. We've even seen cases where some users had 170GB+ worth of these. Let's remove these. """ extensions = [ datapack.PACKSUFFIX, datapack.INDEXSUFFIX, historypack.PACKSUFFIX, historypack.INDEXSUFFIX, ] def _shouldhold(f): """Newish files shouldn't be removed as they could be used by another running command. """ if os.path.isdir(f) or os.path.basename(f) == "repacklock": return True try: stat = os.lstat(f) except OSError: # If we can't access the file, it's either being removed, or we # don't have access to it, either way there is nothing we can do # about it, ignore them. return True return time.gmtime(stat.st_atime + 24 * 3600) > time.gmtime() with progress.spinner(ui, _("cleaning old temporary files")): try: for f in os.listdir(packpath): f = os.path.join(packpath, f) if _shouldhold(f): continue __, ext = os.path.splitext(f) if ext not in extensions: try: util.unlink(f) except Exception: pass except OSError as ex: if ex.errno != errno.ENOENT: raise
def rendercontents(self, versionindex): if versionindex in self.cache: contents = self.cache[versionindex] else: if versionindex == len(self.versions): (title, slinfo) = self.loadcurrentversion() else: (title, slinfo) = self.loadoldversion(versionindex) contents = [ ui.label("Commit Cloud Smartlog History", "bold cyan underline").encode(), ui.label( "Use [ and ] to navigate to earlier or later versions", "cyan").encode(), ui.label( "Note: version dates may be off by one due to a server bug", "cyan", ).encode(), b"", title.encode(), b"", ] firstpublic, revdag = self.serv.makedagwalker( slinfo, self.repo) displayer = cmdutil.show_changeset(self.ui, self.repo, self.opts, buffered=True) def out(row): contents.extend(row.rstrip().encode().split(b"\n")) with progress.spinner(ui, _("loading commit information")): cmdutil.displaygraph( self.ui, self.repo, revdag, displayer, reserved=firstpublic, out=out, ) self.cache[versionindex] = contents return contents
def latencytest(count): # Use the upload endpoint for the latency test. We will time how long it # takes for the server to return the "upload complete" response for a # single byte upload. latencies = [] with progress.spinner(ui, "testing connection latency"): for i in range(count): pipeo.write(b"upload 1\n") pipeo.flush() l = pipei.readline() if l != b"upload bytes 1\n": raise error.Abort("invalid response from server: %r" % l) starttime = util.timer() pipeo.write(b"\n") pipeo.flush() l = pipei.readline() endtime = util.timer() if l != b"upload complete\n": raise error.Abort("invalid response from server: %r" % l) latencies.append(endtime - starttime) return latencies
def cloudhide(ui, repo, *revs, **opts): """remove commits or bookmarks from the cloud workspace""" reponame = ccutil.getreponame(repo) workspacename = workspace.parseworkspace(ui, opts) if workspacename is None: workspacename = workspace.currentworkspace(repo) if workspacename is None: workspacename = workspace.defaultworkspace(ui) with progress.spinner(ui, _("fetching commit cloud workspace")): serv = service.get(ui, tokenmod.TokenLocator(ui).token) slinfo = serv.getsmartlog(reponame, workspacename, repo, 0) firstpublic, revdag = serv.makedagwalker(slinfo, repo) cloudrefs = serv.getreferences(reponame, workspacename, 0) nodeinfos = slinfo.nodeinfos dag = slinfo.dag drafts = set(slinfo.draft) removenodes = set() for rev in list(revs) + opts.get("rev", []): rev = pycompat.encodeutf8(rev) if rev in drafts: removenodes.add(rev) else: candidate = None for draft in drafts: if draft.startswith(rev): if candidate is None: candidate = draft else: raise error.Abort( _("ambiguous commit hash prefix: %s") % rev) if candidate is None: raise error.Abort(_("commit not in workspace: %s") % rev) removenodes.add(candidate) # Find the bookmarks we need to remove removebookmarks = set() for bookmark in opts.get("bookmark", []): kind, pattern, matcher = util.stringmatcher(bookmark) if kind == "literal": if pattern not in cloudrefs.bookmarks: raise error.Abort(_("bookmark not in workspace: %s") % pattern) removebookmarks.add(pattern) else: for bookmark in cloudrefs.bookmarks: if matcher(bookmark): removebookmarks.add(bookmark) # Find the remote bookmarks we need to remove removeremotes = set() for remote in opts.get("remotebookmark", []): kind, pattern, matcher = util.stringmatcher(remote) if kind == "literal": if pattern not in cloudrefs.remotebookmarks: raise error.Abort( _("remote bookmark not in workspace: %s") % pattern) removeremotes.add(remote) else: for remote in cloudrefs.remotebookmarks: if matcher(remote): removeremotes.add(remote) # Find the heads and bookmarks we need to remove allremovenodes = dag.descendants(removenodes) removeheads = set(allremovenodes & map(pycompat.encodeutf8, cloudrefs.heads)) for node in allremovenodes: removebookmarks.update(nodeinfos[node].bookmarks) # Find the heads we need to remove because we are removing the last bookmark # to it. remainingheads = set(map(pycompat.encodeutf8, cloudrefs.heads)) - removeheads for bookmark in removebookmarks: nodeutf8 = cloudrefs.bookmarks[bookmark] node = pycompat.encodeutf8(nodeutf8) info = nodeinfos.get(node) if node in remainingheads and info: if removebookmarks.issuperset(set(info.bookmarks)): remainingheads.discard(node) removeheads.add(node) # Find the heads we need to add to keep other commits visible addheads = (dag.parents(removenodes) - allremovenodes - dag.ancestors(remainingheads)) & drafts if removeheads: ui.status(_("removing heads:\n")) for head in sorted(removeheads): headutf8 = pycompat.decodeutf8(head) ui.status(" %s %s\n" % (headutf8[:12], templatefilters.firstline(nodeinfos[head].message))) if addheads: ui.status(_("adding heads:\n")) for head in sorted(addheads): headutf8 = pycompat.decodeutf8(head) ui.status(" %s %s\n" % (headutf8[:12], templatefilters.firstline(nodeinfos[head].message))) if removebookmarks: ui.status(_("removing bookmarks:\n")) for bookmark in sorted(removebookmarks): ui.status(" %s: %s\n" % (bookmark, cloudrefs.bookmarks[bookmark][:12])) if removeremotes: ui.status(_("removing remote bookmarks:\n")) for remote in sorted(removeremotes): ui.status(" %s: %s\n" % (remote, cloudrefs.remotebookmarks[remote][:12])) # Normalize back to strings. (The DAG wants bytes, the cloudrefs wants str) removeheads = list(map(pycompat.decodeutf8, removeheads)) addheads = list(map(pycompat.decodeutf8, addheads)) if removeheads or addheads or removebookmarks or removeremotes: if opts.get("dry_run"): ui.status(_("not updating cloud workspace: --dry-run specified\n")) return 0 with progress.spinner(ui, _("updating commit cloud workspace")): serv.updatereferences( reponame, workspacename, cloudrefs.version, oldheads=list(removeheads), newheads=list(addheads), oldbookmarks=list(removebookmarks), oldremotebookmarks=list(removeremotes), ) else: ui.status(_("nothing to change\n"))
def showhistory(ui, repo, reponame, workspacename, **opts): """Shows an interactive view for historical versions of smartlogs""" serv = service.get(ui, tokenmod.TokenLocator(ui).token) with progress.spinner(ui, _("fetching")): versions = sorted( serv.gethistoricalversions(reponame, workspacename), key=lambda version: version["version_number"], reverse=True, ) class smartlogview(interactiveui.viewframe): def __init__(self, ui, repo, versions): interactiveui.viewframe.__init__(self, ui, repo, -1) self.versions = versions self.flags = [] if opts.get("force_original_backend"): self.flags.append("USE_ORIGINAL_BACKEND") def render(self): ui = self.ui ui.pushbuffer() ui.status(_("Interactive Smartlog History\n\n")) if opts.get("all"): limit = 0 else: limit = 2 * 604800 # two weeks if self.index == len(self.versions): self.index = -1 if self.index == -2: self.index = len(self.versions) - 1 if self.index == -1: with progress.spinner(ui, _("fetching")): slinfo = serv.getsmartlog(reponame, workspacename, repo, limit, self.flags) ui.status(_("Current Smartlog:\n\n")) else: with progress.spinner(ui, _("fetching")): slinfo = serv.getsmartlogbyversion( reponame, workspacename, repo, None, self.versions[self.index]["version_number"], limit, self.flags, ) formatteddate = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(slinfo.timestamp)) ui.status( _("Smartlog version %d \nsynced at %s\n\n") % (slinfo.version, formatteddate)) template = "sl_cloud" smartlogstyle = ui.config("templatealias", template) if smartlogstyle: opts["template"] = "{%s}" % smartlogstyle else: ui.debug( _("style %s is not defined, skipping") % smartlogstyle, component="commitcloud", ) firstpublic, revdag = serv.makedagwalker(slinfo, repo) displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True) cmdutil.rustdisplaygraph(ui, repo, revdag, displayer, reserved=firstpublic) repo.ui.status( _("<-: newer " "->: older " "q: abort \n" "a: 1 day forward d: 1 day back \n")) return ui.popbuffer() def rightarrow(self): self.index += 1 def leftarrow(self): self.index -= 1 def apress(self): if self.index == -1: return else: mintimestamp = self.versions[self.index]["timestamp"] + 86400 while True: self.index -= 1 if self.index <= -1: break if self.versions[self.index]["timestamp"] >= mintimestamp: break def dpress(self): if self.index == -1: maxtimestamp = int(time.time()) - 86400 else: maxtimestamp = self.versions[self.index]["timestamp"] - 86400 while True: self.index += 1 if (self.index == len(self.versions) or self.versions[self.index]["timestamp"] <= maxtimestamp): break def enter(self): return viewobj = smartlogview(ui, repo, versions) interactiveui.view(viewobj)
def __init__(self, ui, repo, reponame, workspacename, **opts): self.ui = ui self.repo = repo self.reponame = reponame self.workspacename = workspacename self.opts = opts self.serv = service.get(ui, tokenmod.TokenLocator(ui).token) self.servlock = threading.Lock() self.renderevent = threading.Event() self.running = True self.cache = {} with progress.spinner(ui, _("fetching cloud smartlog history")): self.versions = sorted( self.serv.gethistoricalversions(reponame, workspacename), key=lambda version: version["version_number"], ) initversion = opts.get("workspace_version") date = opts.get("date") inittime = int(util.parsedate(date)[0]) if date else None if initversion and inittime: raise error.Abort( "'--workspace-version' and '--date' options can't be both provided" ) if inittime: timestamps = sorted( self.versions, key=lambda version: version["timestamp"], ) for index, version in enumerate(timestamps): if version["timestamp"] >= inittime: initversion = version["version_number"] break if index == len(timestamps) - 1: raise error.Abort( "You have no recorded history at or after this date" ) smartlogstyle = ui.config("templatealias", template) if smartlogstyle: self.opts["template"] = "{%s}" % smartlogstyle if initversion: initversion = int(initversion) for index, version in enumerate(self.versions): if version["version_number"] == initversion: self.cur_index = index break else: versionrange = [ version["version_number"] for version in self.versions ] raise error.Abort( "workspace version %s is not available (%s to %s are available)" % (initversion, min(versionrange), max(versionrange))) else: self.cur_index = len(self.versions) if opts.get("all"): self.limit = 0 else: self.limit = 12 * 7 * 24 * 60 * 60 # 12 weeks self.flags = [] if ui.configbool("commitcloud", "sl_showremotebookmarks"): self.flags.append("ADD_REMOTE_BOOKMARKS") if ui.configbool("commitcloud", "sl_showallbookmarks"): self.flags.append("ADD_ALL_BOOKMARKS") if opts.get("force_original_backend"): self.flags.append("USE_ORIGINAL_BACKEND")
def _makerage(ui, repo, **opts): # Make graphlog shorter. configoverrides = {("experimental", "graphshorten"): "1"} def hgcmd(cmdname, *args, **additional_opts): cmd, opts = cmdutil.getcmdanddefaultopts(cmdname, commands.table) opts.update(additional_opts) _repo = repo if "_repo" in opts: _repo = opts["_repo"] del opts["_repo"] # If we failed to popbuffer for some reason, do not mess up with the # main `ui` object. newui = ui.copy() newui.pushbuffer(error=True) try: with ui.configoverride(configoverrides, "rage"): if cmd.norepo: cmd(newui, *args, **opts) else: cmd(newui, _repo, *args, **opts) finally: return newui.popbuffer() basic = [ ("date", lambda: time.ctime()), ("unixname", lambda: encoding.environ.get("LOGNAME")), ("hostname", lambda: socket.gethostname()), ("repo location", lambda: repo.root), ("cwd", lambda: pycompat.getcwd()), ("fstype", lambda: util.getfstype(repo.root)), ("active bookmark", lambda: bookmarks._readactive(repo, repo._bookmarks)), ( "hg version", lambda: __import__( "edenscm.mercurial.__version__" ).mercurial.__version__.version, ), ("obsstore size", lambda: str(repo.svfs.stat("obsstore").st_size)), ] oldcolormode = ui._colormode ui._colormode = None detailed = [ ("df -h", lambda: shcmd("df -h", check=False)), # smartlog as the user sees it ("hg sl", lambda: hgcmd("smartlog", template="{sl_debug}")), # unfiltered smartlog for recent hidden changesets, including full # node identity ( "hg sl --master='interestingmaster()' -r 'predecessors(draft())'", lambda: hgcmd( "smartlog", master="interestingmaster()", rev=["predecessors(draft())"], _repo=repo.unfiltered(), template='{sub("\\n", " ", "{node} {sl_debug}")}', ), ), ( 'first 20 lines of "hg status"', lambda: "\n".join(hgcmd("status").splitlines()[:20]), ), ( "hg blackbox", lambda: "\n".join( hgcmd("blackbox", pattern=BLACKBOX_PATTERN).splitlines()[-500:] ), ), ("hg summary", lambda: hgcmd("summary")), ("hg cloud status", lambda: hgcmd("cloud status")), ("hg debugprocesstree", lambda: hgcmd("debugprocesstree")), ("hg config (local)", lambda: "\n".join(localconfig(ui))), ("hg sparse show", lambda: hgcmd("sparse show")), ("hg debuginstall", lambda: hgcmd("debuginstall")), ("usechg", (usechginfo)), ( "uptime", lambda: shcmd( "wmic path Win32_OperatingSystem get LastBootUpTime" if pycompat.iswindows else "uptime" ), ), ("rpm info", (partial(rpminfo, ui))), ("klist", lambda: shcmd("klist", check=False)), ("ifconfig", lambda: shcmd("ipconfig" if pycompat.iswindows else "ifconfig")), ( "airport", lambda: shcmd( "/System/Library/PrivateFrameworks/Apple80211." + "framework/Versions/Current/Resources/airport " + "--getinfo", check=False, ), ), ( 'last 100 lines of "hg debugobsolete"', lambda: "\n".join(hgcmd("debugobsolete").splitlines()[-100:]), ), ("infinitepush backup state", lambda: readinfinitepushbackupstate(repo)), ("commit cloud workspace sync state", lambda: readcommitcloudstate(repo)), ( "infinitepush / commitcloud backup logs", lambda: infinitepushbackuplogs(ui, repo), ), ("scm daemon logs", lambda: scmdaemonlog(ui, repo)), ("debugstatus", lambda: hgcmd("debugstatus")), ("debugtree", lambda: hgcmd("debugtree")), ("hg config (overrides)", lambda: "\n".join(overriddenconfig(ui))), ("edenfs rage", lambda: shcmd("edenfsctl rage --stdout")), ( "environment variables", lambda: "\n".join( sorted(["{}={}".format(k, v) for k, v in encoding.environ.items()]) ), ), ("ssh config", lambda: shcmd("ssh -G hg.vip.facebook.com", check=False)), ] msg = "" if util.safehasattr(repo, "name"): # Add the contents of both local and shared pack directories. packlocs = { "local": lambda category: shallowutil.getlocalpackpath( repo.svfs.vfs.base, category ), "shared": lambda category: shallowutil.getcachepackpath(repo, category), } for loc, getpath in packlocs.iteritems(): for category in constants.ALL_CATEGORIES: path = getpath(category) detailed.append( ( "%s packs (%s)" % (loc, constants.getunits(category)), lambda path=path: "%s:\n%s" % ( path, shcmd( "dir /o-s %s" % os.path.normpath(path) if pycompat.iswindows else "ls -lhS %s" % path ), ), ) ) footnotes = [] timeout = opts.get("timeout") or 20 def _failsafe(gen, timeout=timeout): class TimedOut(RuntimeError): pass def target(result, gen): try: result.append(gen()) except TimedOut: return except Exception as ex: index = len(footnotes) + 1 footnotes.append( "[%d]: %s\n%s\n\n" % (index, str(ex), traceback.format_exc()) ) result.append("(Failed. See footnote [%d])" % index) result = [] thread = threading.Thread(target=target, args=(result, gen)) thread.daemon = True thread.start() thread.join(timeout) if result: value = result[0] return value else: if thread.is_alive(): # Attempt to stop the thread, since hg is not thread safe. # There is no pure Python API to interrupt a thread. # But CPython C API can do that. ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(thread.ident), ctypes.py_object(TimedOut) ) return ( "(Did not complete in %s seconds, rerun with a larger --timeout to collect this)" % timeout ) msg = [] profile = [] allstart = time.time() for name, gen in basic: msg.append("%s: %s\n\n" % (name, _failsafe(gen))) profile.append((time.time() - allstart, "basic info", None)) for name, gen in detailed: start = time.time() with progress.spinner(ui, "collecting %r" % name): value = _failsafe(gen) finish = time.time() msg.append( "%s: (%.2f s)\n---------------------------\n%s\n\n" % (name, finish - start, value) ) profile.append((finish - start, name, value.count("\n"))) allfinish = time.time() profile.append((allfinish - allstart, "total time", None)) msg.append("hg rage profile:\n") width = max([len(name) for _t, name, _l in profile]) for timetaken, name, lines in reversed(sorted(profile)): m = " %-*s %8.2f s" % (width + 1, name + ":", timetaken) if lines is not None: msg.append("%s for %4d lines\n" % (m, lines)) else: msg.append("%s\n" % m) msg.append("\n") msg.extend(footnotes) msg = "".join(msg) ui._colormode = oldcolormode return msg
def _prefetch(self, revs, base=None, pats=None, opts=None, matcher=None): fallbackpath = self.fallbackpath if fallbackpath: # If we know a rev is on the server, we should fetch the server # version of those files, since our local file versions might # become obsolete if the local commits are stripped. with progress.spinner(self.ui, _("finding outgoing revisions")): localrevs = self.revs("outgoing(%s)", fallbackpath) if base is not None and base != nullrev: serverbase = list( self.revs("first(reverse(::%s) - %ld)", base, localrevs)) if serverbase: base = serverbase[0] else: localrevs = self mfl = self.manifestlog if base is not None: mfdict = mfl[self[base].manifestnode()].read() skip = set(mfdict.iteritems()) else: skip = set() # Copy the skip set to start large and avoid constant resizing, # and since it's likely to be very similar to the prefetch set. files = skip.copy() serverfiles = skip.copy() visited = set() visited.add(nullid) with progress.bar(self.ui, _("prefetching"), total=len(revs)) as prog: for rev in sorted(revs): ctx = self[rev] if pats: m = scmutil.match(ctx, pats, opts) if matcher is None: matcher = self.maybesparsematch(rev) mfnode = ctx.manifestnode() mfctx = mfl[mfnode] # Decompressing manifests is expensive. # When possible, only read the deltas. p1, p2 = mfctx.parents if p1 in visited and p2 in visited: mfdict = mfctx.readnew() else: mfdict = mfctx.read() diff = mfdict.iteritems() if pats: diff = (pf for pf in diff if m(pf[0])) if matcher: diff = (pf for pf in diff if matcher(pf[0])) if rev not in localrevs: serverfiles.update(diff) else: files.update(diff) visited.add(mfctx.node()) prog.value += 1 files.difference_update(skip) serverfiles.difference_update(skip) # Fetch files known to be on the server if serverfiles: results = [(path, hex(fnode)) for (path, fnode) in serverfiles] self.fileservice.prefetch(results, force=True) # Fetch files that may or may not be on the server if files: results = [(path, hex(fnode)) for (path, fnode) in files] self.fileservice.prefetch(results)
def cloudsmartlog(ui, repo, template="sl_cloud", **opts): """get smartlog view for the default workspace of the given user If the requested template is not defined in the config the command provides a simple view as a list of draft commits. """ reponame = ccutil.getreponame(repo) workspacename = workspace.parseworkspace(ui, opts) if workspacename is None: workspacename = workspace.currentworkspace(repo) if workspacename is None: workspacename = workspace.defaultworkspace(ui) if opts.get("history"): interactivehistory.showhistory(ui, repo, reponame, workspacename, **opts) return date = opts.get("date") version = opts.get("workspace_version") if date: parseddate = util.parsedate(date) else: parseddate = None ui.status( _("searching draft commits for the '%s' workspace for the '%s' repo\n") % (workspacename, reponame), component="commitcloud", ) serv = service.get(ui, tokenmod.TokenLocator(ui).token) if parseddate is None and not version: with progress.spinner(ui, _("fetching")): firstpublic, revdag = serv.getsmartlog(reponame, workspacename, repo, 0) else: with progress.spinner(ui, _("fetching")): firstpublic, revdag, slversion, sltimestamp = serv.getsmartlogbyversion( reponame, workspacename, repo, parseddate, version, 0) if parseddate or version: formatteddate = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(sltimestamp)) ui.status( _("Smartlog version %d \nsynced at %s\n\n") % (slversion, formatteddate)) else: ui.status(_("Smartlog:\n\n")) # set up pager ui.pager("smartlog") smartlogstyle = ui.config("templatealias", template) # if style is defined in templatealias section of config apply that style if smartlogstyle: opts["template"] = "{%s}" % smartlogstyle else: ui.debug( _("style %s is not defined, skipping") % smartlogstyle, component="commitcloud", ) # show all the nodes displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True) if ui.config("experimental", "graph.renderer") == "legacy": cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges) else: cmdutil.rustdisplaygraph(ui, repo, revdag, displayer, reserved=firstpublic)
def _makerage(ui, repo, **opts): configoverrides = { # Make graphlog shorter. ("experimental", "graphshorten"): "1", # Force use of lines-square renderer, as the user's configuration may # not render properly in a text file. ("experimental", "graph.renderer"): "lines-square", # Reduce the amount of data used for debugnetwork speed tests to # increase the chance they complete within 20s. ("debugnetwork", "speed-test-download-size"): "4M", ("debugnetwork", "speed-test-upload-size"): "1M", } # Override the encoding to "UTF-8" to generate the rage in UTF-8. oldencoding = encoding.encoding oldencodingmode = encoding.encodingmode encoding.encoding = "UTF-8" encoding.encodingmode = "replace" def hgcmd(cmdname, *args, **additional_opts): cmd, opts = cmdutil.getcmdanddefaultopts(cmdname, commands.table) opts.update(additional_opts) _repo = repo if "_repo" in opts: _repo = opts["_repo"] del opts["_repo"] # If we failed to popbuffer for some reason, do not mess up with the # main `ui` object. newui = ui.copy() newui.pushbuffer(error=True, subproc=True) newui._colormode = None def remoteui(orig, src, opts): rui = orig(src, opts) rui._outputui = newui return rui try: with newui.configoverride(configoverrides, "rage"), extensions.wrappedfunction( hg, "remoteui", remoteui): if cmd.norepo: cmd(newui, *args, **opts) else: cmd(newui, _repo, *args, **opts) finally: return newui.popbuffer() basic = [ ("date", lambda: time.ctime()), ("unixname", lambda: encoding.environ.get("LOGNAME")), ("hostname", lambda: socket.gethostname()), ("repo location", lambda: repo.root), ("cwd", lambda: pycompat.getcwd()), ("fstype", lambda: util.getfstype(repo.root)), ("active bookmark", lambda: bookmarks._readactive(repo, repo._bookmarks)), ( "hg version", lambda: __import__("edenscm.mercurial.__version__").mercurial. __version__.version, ), ] def _edenfs_rage(): ragecmd = "edenfsctl rage --stdout" if opts.get("preview"): return shcmd(ragecmd + " --dry-run") return shcmd(ragecmd) detailed = [ ( "disk space usage", lambda: shcmd( "wmic LogicalDisk Where DriveType=3 Get DeviceId,FileSystem,FreeSpace,Size" if pycompat.iswindows else "df -h", check=False, ), ), # smartlog as the user sees it ("hg sl", lambda: hgcmd("smartlog", template="{sl_debug}")), ( "hg debugmetalog -t 'since 2d ago'", lambda: hgcmd("debugmetalog", time_range=["since 2d ago"]), ), ( 'first 20 lines of "hg status"', lambda: "\n".join(hgcmd("status").splitlines()[:20]), ), ( "hg debugmutation -r 'draft() & date(-4)' -t 'since 4d ago'", lambda: hgcmd("debugmutation", rev=["draft() & date(-4)"], time_range=["since 4d ago"]), ), ( "hg bookmarks --list-subscriptions", lambda: hgcmd("bookmarks", list_subscriptions=True), ), ("sigtrace", lambda: readsigtraces(repo)), ( "hg blackbox", lambda: "\n".join( hgcmd("blackbox", pattern=BLACKBOX_PATTERN).splitlines()[-500:] ), ), ("hg summary", lambda: hgcmd("summary")), ("hg cloud status", lambda: hgcmd("cloud status")), ("hg debugprocesstree", lambda: hgcmd("debugprocesstree")), ("hg config (local)", lambda: "\n".join(localconfig(ui))), ("hg sparse", lambda: hgcmd("sparse")), ("hg debugchangelog", lambda: hgcmd("debugchangelog")), ("hg debugexpandpaths", lambda: hgcmd("debugexpandpaths")), ("hg debuginstall", lambda: hgcmd("debuginstall")), ("hg debugdetectissues", lambda: hgcmd("debugdetectissues")), ("usechg", usechginfo), ( "uptime", lambda: shcmd("wmic path Win32_OperatingSystem get LastBootUpTime" if pycompat.iswindows else "uptime"), ), ("rpm info", (partial(rpminfo, ui))), ("klist", lambda: shcmd("klist", check=False)), ("ifconfig", lambda: shcmd("ipconfig" if pycompat.iswindows else "ifconfig")), ( "airport", lambda: shcmd( "/System/Library/PrivateFrameworks/Apple80211." + "framework/Versions/Current/Resources/airport " + "--getinfo", check=False, ), ), ("hg debugnetwork", lambda: hgcmd("debugnetwork")), ("infinitepush backup state", lambda: readinfinitepushbackupstate(repo)), ("commit cloud workspace sync state", lambda: readcommitcloudstate(repo)), ( "infinitepush / commitcloud backup logs", lambda: infinitepushbackuplogs(ui, repo), ), ("scm daemon logs", lambda: scmdaemonlog(ui, repo)), ("debugstatus", lambda: hgcmd("debugstatus")), ("debugtree", lambda: hgcmd("debugtree")), ("hg config (all)", lambda: "\n".join(allconfig(ui))), ("edenfs rage", _edenfs_rage), ( "environment variables", lambda: "\n".join( sorted([ "{}={}".format(k, v) for k, v in encoding.environ.items() ])), ), ("ssh config", lambda: shcmd("ssh -G hg.vip.facebook.com", check=False)), ("debuglocks", lambda: hgcmd("debuglocks")), ("x2pagentd info", lambda: checkproxyagentstate(ui)), ] msg = "" if util.safehasattr(repo, "name"): # Add the contents of both local and shared pack directories. packlocs = { "local": lambda category: shallowutil.getlocalpackpath( repo.svfs.vfs.base, category), "shared": lambda category: shallowutil.getcachepackpath(repo, category), } for loc, getpath in pycompat.iteritems(packlocs): for category in constants.ALL_CATEGORIES: path = getpath(category) detailed.append(( "%s packs (%s)" % (loc, constants.getunits(category)), lambda path=path: "%s:\n%s" % ( path, shcmd("dir /o-s %s" % os.path.normpath(path) if pycompat.iswindows else "ls -lhS %s" % path), ), )) footnotes = [] timeout = opts.get("timeout") or 20 def _failsafe(gen, timeout=timeout): class TimedOut(RuntimeError): pass def target(result, gen): try: result.append(gen()) except TimedOut: return except Exception as ex: index = len(footnotes) + 1 footnotes.append("[%d]: %s\n%s\n\n" % (index, str(ex), traceback.format_exc())) result.append("(Failed. See footnote [%d])" % index) result = [] thread = threading.Thread(target=target, args=(result, gen)) thread.daemon = True thread.start() thread.join(timeout) if result: value = result[0] return value else: if thread.is_alive(): # Attempt to stop the thread, since hg is not thread safe. # There is no pure Python API to interrupt a thread. # But CPython C API can do that. ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(thread.ident), ctypes.py_object(TimedOut)) return ( "(Did not complete in %s seconds, rerun with a larger --timeout to collect this)" % timeout) msg = [] profile = [] allstart = time.time() for name, gen in basic: msg.append("%s: %s\n\n" % (name, _failsafe(gen))) profile.append((time.time() - allstart, "basic info", None)) for name, gen in detailed: start = time.time() with progress.spinner(ui, "collecting %r" % name): value = _failsafe(gen) finish = time.time() msg.append("%s: (%.2f s)\n---------------------------\n%s\n\n" % (name, finish - start, value)) profile.append((finish - start, name, value.count("\n"))) allfinish = time.time() profile.append((allfinish - allstart, "total time", None)) msg.append("hg rage profile:\n") width = max([len(name) for _t, name, _l in profile]) for timetaken, name, lines in reversed(sorted(profile)): m = " %-*s %8.2f s" % (width + 1, name + ":", timetaken) if lines is not None: msg.append("%s for %4d lines\n" % (m, lines)) else: msg.append("%s\n" % m) msg.append("\n") msg.extend(footnotes) msg = "".join(msg) encoding.encoding = oldencoding encoding.encodingmode = oldencodingmode return msg
def _innerwalk(self, match, event, span): state = self._fsmonitorstate clock, ignorehash, notefiles = state.get() if not clock: if state.walk_on_invalidate: raise fsmonitorfallback("no clock") # Initial NULL clock value, see # https://facebook.github.io/watchman/docs/clockspec.html clock = "c:0:0" notefiles = [] ignore = self.dirstate._ignore # experimental config: experimental.fsmonitor.skipignore if not self._ui.configbool("experimental", "fsmonitor.skipignore"): if ignorehash and _hashignore( ignore) != ignorehash and clock != "c:0:0": # ignore list changed -- can't rely on Watchman state any more if state.walk_on_invalidate: raise fsmonitorfallback("ignore rules changed") notefiles = [] clock = "c:0:0" matchfn = match.matchfn matchalways = match.always() dmap = self.dirstate._map if util.safehasattr(dmap, "_map"): # for better performance, directly access the inner dirstate map if the # standard dirstate implementation is in use. dmap = dmap._map if "treestate" in self._repo.requirements: # treestate has a fast path to filter out ignored directories. ignorevisitdir = self.dirstate._ignore.visitdir def dirfilter(path): result = ignorevisitdir(path.rstrip("/")) return result == "all" nonnormalset = self.dirstate._map.nonnormalsetfiltered(dirfilter) else: nonnormalset = self.dirstate._map.nonnormalset event["old_clock"] = clock event["old_files"] = blackbox.shortlist(sorted(nonnormalset)) span.record(oldclock=clock, oldfileslen=len(nonnormalset)) state.setlastnonnormalfilecount(len(nonnormalset)) copymap = self.dirstate._map.copymap getkind = stat.S_IFMT dirkind = stat.S_IFDIR regkind = stat.S_IFREG lnkkind = stat.S_IFLNK join = self.dirstate._join normcase = util.normcase fresh_instance = False exact = False if match.isexact(): # match.exact exact = True if not exact and self.dirstate._checkcase: # note that even though we could receive directory entries, we're only # interested in checking if a file with the same name exists. So only # normalize files if possible. normalize = self.dirstate._normalizefile else: normalize = None # step 2: query Watchman with progress.spinner(self._ui, "watchman query"): try: # Use the user-configured timeout for the query. # Add a little slack over the top of the user query to allow for # overheads while transferring the data excludes = [ "anyof", ["dirname", ".hg"], ["name", ".hg", "wholename"] ] # Exclude submodules. if git.isgitformat(self._repo): submods = git.parsesubmodules(self._repo[None]) excludes += [["dirname", s.path] for s in submods] self._watchmanclient.settimeout(state.timeout + 0.1) result = self._watchmanclient.command( "query", { "fields": ["mode", "mtime", "size", "exists", "name"], "since": clock, "expression": ["not", excludes], "sync_timeout": int(state.timeout * 1000), "empty_on_fresh_instance": state.walk_on_invalidate, }, ) except Exception as ex: event["is_error"] = True span.record(error=ex) _handleunavailable(self._ui, state, ex) self._watchmanclient.clearconnection() # XXX: Legacy scuba logging. Remove this once the source of truth # is moved to the Rust Event. self._ui.log("fsmonitor_status", fsmonitor_status="exception") if self._ui.configbool("fsmonitor", "fallback-on-watchman-exception"): raise fsmonitorfallback("exception during run") else: raise ex else: # We need to propagate the last observed clock up so that we # can use it for our next query event["new_clock"] = result["clock"] event["is_fresh"] = result["is_fresh_instance"] span.record(newclock=result["clock"], isfresh=result["is_fresh_instance"]) state.setlastclock(result["clock"]) state.setlastisfresh(result["is_fresh_instance"]) files = list( filter(lambda x: _isutf8(self._ui, x["name"]), result["files"])) self._ui.metrics.gauge("watchmanfilecount", len(files)) # Ideally we'd just track a bool for fresh_instance or not, but there # could be multiple queries during a command, so let's use a counter. self._ui.metrics.gauge( "watchmanfreshinstances", 1 if result["is_fresh_instance"] else 0, ) if result["is_fresh_instance"]: if not self._ui.plain() and self._ui.configbool( "fsmonitor", "warn-fresh-instance"): oldpid = _watchmanpid(event["old_clock"]) newpid = _watchmanpid(event["new_clock"]) if oldpid is not None and newpid is not None and oldpid != newpid: self._ui.warn( _("warning: watchman has recently restarted (old pid %s, new pid %s) - operation will be slower than usual\n" ) % (oldpid, newpid)) elif oldpid is None and newpid is not None: self._ui.warn( _("warning: watchman has recently started (pid %s) - operation will be slower than usual\n" ) % (newpid, )) else: self._ui.warn( _("warning: watchman failed to catch up with file change events and requires a full scan - operation will be slower than usual\n" )) if state.walk_on_invalidate: state.invalidate(reason="fresh_instance") raise fsmonitorfallback("fresh instance") fresh_instance = True # Ignore any prior noteable files from the state info notefiles = [] else: count = len(files) state.setwatchmanchangedfilecount(count) event["new_files"] = blackbox.shortlist( sorted(e["name"] for e in files), count) span.record(newfileslen=len(files)) # XXX: Legacy scuba logging. Remove this once the source of truth # is moved to the Rust Event. if event["is_fresh"]: self._ui.log("fsmonitor_status", fsmonitor_status="fresh") else: self._ui.log("fsmonitor_status", fsmonitor_status="normal") results = {} # for file paths which require normalization and we encounter a case # collision, we store our own foldmap if normalize: foldmap = dict((normcase(k), k) for k in results) switch_slashes = pycompat.ossep == "\\" # The order of the results is, strictly speaking, undefined. # For case changes on a case insensitive filesystem we may receive # two entries, one with exists=True and another with exists=False. # The exists=True entries in the same response should be interpreted # as being happens-after the exists=False entries due to the way that # Watchman tracks files. We use this property to reconcile deletes # for name case changes. ignorelist = [] ignorelistappend = ignorelist.append with progress.bar(self.ui, _("Watchman results"), _("files"), len(files)) as prog: for entry in files: prog.value += 1 fname = entry["name"] if _fixencoding: fname = _watchmantofsencoding(fname) if switch_slashes: fname = fname.replace("\\", "/") if normalize: normed = normcase(fname) fname = normalize(fname, True, True) foldmap[normed] = fname fmode = entry["mode"] fexists = entry["exists"] kind = getkind(fmode) if not fexists: # if marked as deleted and we don't already have a change # record, mark it as deleted. If we already have an entry # for fname then it was either part of walkexplicit or was # an earlier result that was a case change if (fname not in results and fname in dmap and (matchalways or matchfn(fname))): results[fname] = None elif kind == dirkind: if fname in dmap and (matchalways or matchfn(fname)): results[fname] = None elif kind == regkind or kind == lnkkind: if fname in dmap: if matchalways or matchfn(fname): results[fname] = entry else: ignored = ignore(fname) if ignored: ignorelistappend(fname) if (matchalways or matchfn(fname)) and not ignored: results[fname] = entry elif fname in dmap and (matchalways or matchfn(fname)): results[fname] = None elif fname in match.files(): match.bad(fname, filesystem.badtype(kind)) # step 3: query notable files we don't already know about # XXX try not to iterate over the entire dmap if normalize: # any notable files that have changed case will already be handled # above, so just check membership in the foldmap notefiles = set((normalize(f, True, True) for f in notefiles if normcase(f) not in foldmap)) visit = set((f for f in notefiles if ( f not in results and matchfn(f) and (f in dmap or not ignore(f))))) if not fresh_instance: if matchalways: visit.update(f for f in nonnormalset if f not in results) visit.update(f for f in copymap if f not in results) else: visit.update(f for f in nonnormalset if f not in results and matchfn(f)) visit.update(f for f in copymap if f not in results and matchfn(f)) else: if matchalways: visit.update(f for f in dmap if f not in results) visit.update(f for f in copymap if f not in results) else: visit.update(f for f in dmap if f not in results and matchfn(f)) visit.update(f for f in copymap if f not in results and matchfn(f)) # audit returns False for paths with one of its parent directories being a # symlink. audit = pathutil.pathauditor(self.dirstate._root, cached=True).check with progress.bar(self.ui, _("Auditing paths"), _("files"), len(visit)) as prog: auditpass = [] for f in visit: prog.value += 1 if audit(f): auditpass.append(f) auditpass.sort() auditfail = visit.difference(auditpass) droplist = [] droplistappend = droplist.append for f in auditfail: # For auditfail paths, they should be treated as not existed in working # copy. filestate = dmap.get(f, ("?", 0, 0, 0))[0] if filestate in ("?", ): # do not exist in working parents, remove them from treestate and # avoid walking through them. droplistappend(f) results.pop(f, None) else: # tracked, mark as deleted results[f] = None auditpassiter = iter(auditpass) def nf(): return next(auditpassiter) with progress.bar(self.ui, _("Getting metadata"), _("files"), len(auditpass)) as prog: # Break it into chunks so we get some progress information for i in range(0, len(auditpass), 5000): chunk = auditpass[i:i + 5000] for st in util.statfiles([join(f) for f in chunk]): prog.value += 1 f = nf() if (st and not ignore(f)) or f in dmap: results[f] = st elif not st: # '?' (untracked) file was deleted from the filesystem - remove it # from treestate. # # We can only update the dirstate (and treestate) while holding the # wlock. That happens inside poststatus.__call__ -> state.set. So # buffer what files to "drop" so state.set can clean them up. entry = dmap.get(f, None) if entry and entry[0] == "?": droplistappend(f) # The droplist and ignorelist need to match setlastclock() state.setdroplist(droplist) state.setignorelist(ignorelist) results.pop(".hg", None) return pycompat.iteritems(results)
def _makerage(ui, repo, **opts): configoverrides = { # Make graphlog shorter. ("experimental", "graphshorten"): "1", # Force use of lines-square renderer, as the user's configuration may # not render properly in a text file. ("experimental", "graph.renderer"): "lines-square", # Reduce the amount of data used for debugnetwork speed tests to # increase the chance they complete within 20s. ("debugnetwork", "speed-test-download-size"): "4M", ("debugnetwork", "speed-test-upload-size"): "1M", } # Override the encoding to "UTF-8" to generate the rage in UTF-8. oldencoding = encoding.encoding oldencodingmode = encoding.encodingmode encoding.encoding = "UTF-8" encoding.encodingmode = "replace" def hgcmd(cmdname, *args, **additional_opts): cmdargs = ["hg", *cmdname.split(), *args] for flagname, flagvalue in additional_opts.items(): flagname = flagname.replace("_", "-") if isinstance(flagvalue, list): cmdargs += [f"--{flagname}={v}" for v in flagvalue] else: cmdargs += [f"--{flagname}={flagvalue}"] fin = util.stringio() fout = ferr = util.stringio() status = bindings.commands.run(cmdargs, fin, fout, ferr) output = fout.getvalue().decode() if status != 0: output += f"[{status}]\n" return output basic = [ ("date", lambda: time.ctime()), ("unixname", lambda: encoding.environ.get("LOGNAME")), ("hostname", lambda: socket.gethostname()), ("repo location", lambda: repo.root), ("cwd", lambda: pycompat.getcwd()), ("fstype", lambda: util.getfstype(repo.root)), ("active bookmark", lambda: bookmarks._readactive(repo, repo._bookmarks)), ( "hg version", lambda: __import__( "edenscm.mercurial.__version__" ).mercurial.__version__.version, ), ] def _edenfs_rage(): ragecmd = "edenfsctl rage --stdout" if opts.get("preview"): return shcmd(ragecmd + " --dry-run") return shcmd(ragecmd) detailed = [ ( "disk space usage", lambda: shcmd( "wmic LogicalDisk Where DriveType=3 Get DeviceId,FileSystem,FreeSpace,Size" if pycompat.iswindows else "df -h", check=False, ), ), # smartlog as the user sees it ("hg sl", lambda: hgcmd("smartlog", template="{sl_debug}")), ( "hg debugmetalog -t 'since 2d ago'", lambda: hgcmd("debugmetalog", time_range=["since 2d ago"]), ), ( 'first 20 lines of "hg status"', lambda: "\n".join(hgcmd("status").splitlines()[:20]), ), ( "hg debugmutation -r 'draft() & date(-4)' -t 'since 4d ago'", lambda: hgcmd( "debugmutation", rev=["draft() & date(-4)"], time_range=["since 4d ago"] ), ), ( "hg bookmarks --list-subscriptions", lambda: hgcmd("bookmarks", list_subscriptions=True), ), ("sigtrace", lambda: readsigtraces(repo)), ( "hg blackbox", lambda: "\n".join( hgcmd("blackbox", pattern=BLACKBOX_PATTERN).splitlines()[-500:] ), ), ("hg summary", lambda: hgcmd("summary")), ("hg cloud status", lambda: hgcmd("cloud status")), ("hg debugprocesstree", lambda: hgcmd("debugprocesstree")), ("hg config (local)", lambda: "\n".join(localconfig(ui))), ("hg sparse", lambda: hgcmd("sparse")), ("hg debugchangelog", lambda: hgcmd("debugchangelog")), ("hg debugexpandpaths", lambda: hgcmd("debugexpandpaths")), ("hg debuginstall", lambda: hgcmd("debuginstall")), ("hg debugdetectissues", lambda: hgcmd("debugdetectissues")), ("usechg", usechginfo), ( "uptime", lambda: shcmd( "wmic path Win32_OperatingSystem get LastBootUpTime" if pycompat.iswindows else "uptime" ), ), ("rpm info", (partial(rpminfo, ui))), ("klist", lambda: shcmd("klist", check=False)), ("ifconfig", lambda: shcmd("ipconfig" if pycompat.iswindows else "ifconfig")), ( "airport", lambda: shcmd( "/System/Library/PrivateFrameworks/Apple80211." + "framework/Versions/Current/Resources/airport " + "--getinfo", check=False, ), ), ("hg debugnetwork", lambda: hgcmd("debugnetwork")), ("hg debugnetworkdoctor", lambda: hgcmd("debugnetworkdoctor")), ("infinitepush backup state", lambda: readinfinitepushbackupstate(repo)), ("commit cloud workspace sync state", lambda: readcommitcloudstate(repo)), ( "infinitepush / commitcloud backup logs", lambda: infinitepushbackuplogs(ui, repo), ), ("scm daemon logs", lambda: scmdaemonlog(ui, repo)), ("debugstatus", lambda: hgcmd("debugstatus")), ("debugtree", lambda: hgcmd("debugtree")), ("hg config (all)", lambda: "\n".join(allconfig(ui))), ("edenfs rage", _edenfs_rage), ( "environment variables", lambda: "\n".join( sorted(["{}={}".format(k, v) for k, v in encoding.environ.items()]) ), ), ("ssh config", lambda: shcmd("ssh -G hg.vip.facebook.com", check=False)), ("debuglocks", lambda: hgcmd("debuglocks")), ("x2pagentd info", lambda: checkproxyagentstate(ui)), ("sks-agent rage", lambda: sksagentrage(ui)), ] msg = "" footnotes = [] timeout = opts.get("timeout") or 20 def _failsafe(gen, timeout=timeout): class TimedOut(RuntimeError): pass def target(result, gen): try: result.append(gen()) except TimedOut: return except Exception as ex: index = len(footnotes) + 1 footnotes.append( "[%d]: %s\n%s\n\n" % (index, str(ex), traceback.format_exc()) ) result.append("(Failed. See footnote [%d])" % index) result = [] thread = threading.Thread(target=target, args=(result, gen)) thread.daemon = True thread.start() thread.join(timeout) if result: value = result[0] return value else: if thread.is_alive(): # Attempt to stop the thread, since hg is not thread safe. # There is no pure Python API to interrupt a thread. # But CPython C API can do that. ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(thread.ident), ctypes.py_object(TimedOut) ) return ( "(Did not complete in %s seconds, rerun with a larger --timeout to collect this)" % timeout ) msg = [] profile = [] allstart = time.time() for name, gen in basic: msg.append("%s: %s\n\n" % (name, _failsafe(gen))) profile.append((time.time() - allstart, "basic info", None)) for name, gen in detailed: start = time.time() with progress.spinner(ui, name): value = _failsafe(gen) finish = time.time() msg.append( "%s: (%.2f s)\n---------------------------\n%s\n\n" % (name, finish - start, value) ) profile.append((finish - start, name, value.count("\n"))) allfinish = time.time() profile.append((allfinish - allstart, "total time", None)) msg.append("hg rage profile:\n") width = max([len(name) for _t, name, _l in profile]) for timetaken, name, lines in reversed(sorted(profile)): m = " %-*s %8.2f s" % (width + 1, name + ":", timetaken) if lines is not None: msg.append("%s for %4d lines\n" % (m, lines)) else: msg.append("%s\n" % m) msg.append("\n") msg.extend(footnotes) msg = "".join(msg) encoding.encoding = oldencoding encoding.encodingmode = oldencodingmode return msg