diff --git a/docs/design.md b/docs/design.md index ea2e979..8dab5c9 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,221 +1,237 @@ # Software Heritage virtual filesystem (SwhFS) --- Design notes ```{warning} this document describes design notes for the Software Heritage virtual filesystem (SwhFS), which is still under active development and hence **not yet available** for general use. ``` The [Software Heritage](https://www.softwareheritage.org/) {ref}`data model ` is a [Direct Acyclic Graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) (DAG) with nodes of different types that correspond to source code artifacts such as directories, commits, etc. Using this [FUSE](https://en.wikipedia.org/wiki/Filesystem_in_Userspace) module (*SwhFS* for short) you can locally mount, and then navigate as a (virtual) file system, parts of the archive identified by {ref}`Software Heritage identifiers ` (SWHIDs). To retrieve information about the source code artifacts, SwhFS interacts over the network with the Software Heritage archive via its {ref}`Web API `. ## Command-line interface $ swh fs mount [SWHID]... will mount the Software Heritage archive at the local ``, the *SwhFS mount point*. From there, the user will be able to lazily load and navigate the archive using SWHID at entry points. If one or more SWHIDs are also specified, the corresponding objects will be pre- fetched from the archive at mount-time and available at `/archive/`. For more details see the {ref}`CLI documentation `. ## Mount point The SwhFS mount point contain: - `archive/`: initially empty, this directory is lazily populated with one entry per accessed SWHID, having actual SWHIDs as names. - `meta/`: initially empty, this directory contains one `.json` file for each `` entry under `archive/`. The JSON file contain all available meta information about the given SWHID, as returned by the Software Heritage Web API for that object. Note that, in case of pagination (e.g., snapshot objects with many branches) the JSON file will contain a complete version with all pages merged together. ```{todo} Consider sharding ``/`.json` files under `ab/cd/` dirs to avoid exploding the number of dir entries under `archive/` and `meta/` (cf. [T2694](https://forge.softwareheritage.org/T2694)) ``` ## File system representation SWHID are represented differently on the file-system depending on the associated node types in the Software Heritage graph. Details are given below, for each node type. ### `cnt` nodes (blobs) Content leaves (AKA blobs) are represented on disks as regular files, containing the corresponding bytes, as archived. Note that permissions are associated to blobs only in the context of directories. Hence, when accessing blobs from the top-level `archive/` directory, the permissions of the `archive/SWHID` file will be arbitrary and not meaningful (e.g., `0x644`). ### `dir` nodes (directories) Directory nodes are represented as directories on the file-system, containing one entry for each entry of the archived directory. Entry names and other metadata, including permissions, will correspond to the archived entry metadata. Note that SwhFS is mounted read-only, no matter what the permissions say. So it is possible that, in the context of a directory, a file is presented as writable, whereas actually writing to it will fail with `EPERM`. ### `rev` nodes (commits) Revision (AKA commit) nodes are represented on the file-system as directories with the following entries: - `root`: source tree at the time of the commit, as a symlink pointing into `archive/`, to a SWHID of type `dir` - `parents/` (note the plural): a virtual directory containing entries named `1`, `2`, `3`, etc., one for each parent commit. Each of these entry is a symlink pointing into `archive/`, to the SWHID file for the given parent commit - `parent` (note the singular): present if and only if the current commit has a single parent commit (which is the most common case). When present it is a symlink pointing into `archive/` to the SWHID for the sole parent commit +- `history`: a virtual directory containing all the parents commit until the +root commit. Entries are listed as symlinks with the SWHID as directory name, +pointing into `archive/SWHID`, and are returned in a topological ordering +similar to `git log` ordering. - `meta.json`: metadata for the current node, as a symlink pointing to the relevant `meta/.json` file ### `rel` nodes (releases) Release nodes are represented on the file-system as directories with the following entries: - `target`: target node, as a symlink to `archive/` - `target_type`: type of the target SWHID, as a 3-letter code - `root`: present if and only if the release points to something that (transitively) resolves to a directory. When present it is a symlink pointing into `archive/` to the SWHID of the given directory - `meta.json`: metadata for the current node, as a symlink pointing to the relevant `meta/.json` file ### `snp` nodes (snapshots) Snapshot nodes are represented on the file-system as directories with on entry for each branch in the snapshot. Branch names are mangled by replacing... ```{todo} decide how to do branch name escaping and describe it here ``` Each entry is a symlink pointing into `archive/` to the branch target SWHID. ## Caching SwhFS retrieves both metadata and file contents from the Software Heritage archive via the network. In order to obtain reasonable performances several caches are used to minimize network transfer. Caches are stored on disk in SQLite DB(s) located under `$XDG_CACHE_HOME/swh/fuse/`. ```{todo} - potential improvement: store blobs larger than a threshold on disk as files rather than in SQLite, e.g., under `$XDG_CACHE_HOME/swh/fuse/objects/` ``` All caches are persistent (i.e., they survive the restart of the SwhFS process) and global (i.e., they are shared by concurrent SwhFS processes). We assume that no cache *invalidation* is necessary, due to intrinsic properties of the Software Heritage archive, such as integrity verification and append-only archive changes. To clean the caches one can just remove the corresponding files from disk. ### Metadata cache SWHID → JSON metadata The metadata cache map each SWHID to the complete metadata of the referenced object. This is analogous to what is available in `meta/.json` file (and generally used as data source for returning the content of those files). Cache location on-disk: `$XDG_CACHE_HOME/swh/fuse/metadata.sqlite` ### Blob cache cnt SWHID → bytes The blob cache map SWHIDs of type `cnt` to the bytes of their archived content. In general, each SWHID that has an entry in the blob cache also has a matching entry in the metadata cache for other blob attributes (e.g., checksums, size, etc.). The blob cache entry for a given content object is populated, at the latest, the first time the object is `open()`-d. It might be populated earlier on due to prefetching, e.g., when a directory pointing to the given content is listed for the first time. Cache location on-disk: `$XDG_CACHE_HOME/swh/fuse/blob.sqlite` ### Dentry cache dir SWHID → directory entries The dentry (directory entry) cache map SWHIDs of type `dir` to the directory entries they contain. Each entry comes with its name as well as file attributes (i.e., all its needed to perform a detailed directory listing). Additional attributes of each directory entry should be looked up on a entry by entry basis, possibly hitting the metadata cache. The dentry cache for a given dir is populated, at the latest, when the content of the directory is listed. More aggressive prefetching might happen. For instance, when first opening a dir a recursive listing of it can be retrieved from the remote backend and used to recursively populate the dentry cache for all (transitive) sub-directories. ### Parents cache rev SWHID → parent SWHIDs The parents cache map SWHIDs of type `rev` to the list of their parent commits. The parents cache for a given rev is populated, at the latest, when the content of the revision virtual directory is listed. More aggressive prefetching might happen. For instance, when first opening a rev virtual directory a recursive listing of all its ancestor can be retrieved from the remote backend and used to recursively populate the parents cache for all ancestors. + + +### History cache + + rev SWHID → ancestor SWHIDs + +The history cache map SWHIDs of type `rev` to a list of `rev` SWHIDs +corresponding to all its revision ancestors, sorted in reverse topological +order. As the parents cache, the history cache is lazily populated and can be +prefetched. To efficiently store the ancestor lists, the history cache +represents ancestors as graph edges (a pair of two SWHID nodes), meaning the +history cache is shared amongst all revisions parents. diff --git a/swh/fuse/cache.py b/swh/fuse/cache.py index 9a948d2..87dcd71 100644 --- a/swh/fuse/cache.py +++ b/swh/fuse/cache.py @@ -1,145 +1,213 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from abc import ABC import json +import logging from pathlib import Path -from typing import Any, AsyncGenerator, Dict, Optional +from typing import Any, AsyncGenerator, Dict, List, Optional import aiosqlite +from swh.model.exceptions import ValidationError from swh.model.identifiers import SWHID, parse_swhid from swh.web.client.client import typify_json class FuseCache: """SwhFS retrieves both metadata and file contents from the Software Heritage archive via the network. In order to obtain reasonable performances several caches are used to minimize network transfer. Caches are stored on disk in SQLite databases located at `$XDG_CACHE_HOME/swh/fuse/`. All caches are persistent (i.e., they survive the restart of the SwhFS process) and global (i.e., they are shared by concurrent SwhFS processes). We assume that no cache *invalidation* is necessary, due to intrinsic properties of the Software Heritage archive, such as integrity verification and append-only archive changes. To clean the caches one can just remove the corresponding files from disk. """ def __init__(self, cache_conf: Dict[str, Any]): self.cache_conf = cache_conf async def __aenter__(self): self.metadata = MetadataCache(self.cache_conf["metadata"]) self.blob = BlobCache(self.cache_conf["blob"]) + self.history = HistoryCache(self.cache_conf["history"]) await self.metadata.__aenter__() await self.blob.__aenter__() + await self.history.__aenter__() return self async def __aexit__(self, type=None, val=None, tb=None) -> None: await self.metadata.__aexit__() await self.blob.__aexit__() + await self.history.__aexit__() async def get_cached_swhids(self) -> AsyncGenerator[SWHID, None]: """ Return a list of all previously cached SWHID """ # Use the metadata db since it should always contain all accessed SWHIDs metadata_cursor = await self.metadata.conn.execute( "select swhid from metadata_cache" ) swhids = await metadata_cursor.fetchall() for raw_swhid in swhids: yield parse_swhid(raw_swhid[0]) class AbstractCache(ABC): """ Abstract cache implementation to share common behavior between cache types (such as: YAML config parsing, SQLite context manager) """ def __init__(self, conf: Dict[str, Any]): self.conf = conf async def __aenter__(self): # In-memory (thus temporary) caching is useful for testing purposes if self.conf.get("in-memory", False): path = ":memory:" else: path = Path(self.conf["path"]) path.parent.mkdir(parents=True, exist_ok=True) self.conn = await aiosqlite.connect(path) return self async def __aexit__(self, type=None, val=None, tb=None) -> None: await self.conn.close() class MetadataCache(AbstractCache): """ The metadata cache map each SWHID to the complete metadata of the referenced object. This is analogous to what is available in `meta/.json` file (and generally used as data source for returning the content of those files). """ async def __aenter__(self): await super().__aenter__() await self.conn.execute( "create table if not exists metadata_cache (swhid, metadata)" ) await self.conn.commit() return self async def get(self, swhid: SWHID, typify: bool = True) -> Any: cursor = await self.conn.execute( "select metadata from metadata_cache where swhid=?", (str(swhid),) ) cache = await cursor.fetchone() if cache: metadata = json.loads(cache[0]) return typify_json(metadata, swhid.object_type) if typify else metadata else: return None async def set(self, swhid: SWHID, metadata: Any) -> None: await self.conn.execute( "insert into metadata_cache values (?, ?)", (str(swhid), json.dumps(metadata)), ) await self.conn.commit() class BlobCache(AbstractCache): """ The blob cache map SWHIDs of type `cnt` to the bytes of their archived content. The blob cache entry for a given content object is populated, at the latest, the first time the object is `read()`-d. It might be populated earlier on due to prefetching, e.g., when a directory pointing to the given content is listed for the first time. """ async def __aenter__(self): await super().__aenter__() await self.conn.execute("create table if not exists blob_cache (swhid, blob)") await self.conn.commit() return self async def get(self, swhid: SWHID) -> Optional[bytes]: cursor = await self.conn.execute( "select blob from blob_cache where swhid=?", (str(swhid),) ) cache = await cursor.fetchone() if cache: blob = cache[0] return blob else: return None async def set(self, swhid: SWHID, blob: bytes) -> None: await self.conn.execute( "insert into blob_cache values (?, ?)", (str(swhid), blob) ) await self.conn.commit() + + +class HistoryCache(AbstractCache): + """ The history cache map SWHIDs of type `rev` to a list of `rev` SWHIDs + corresponding to all its revision ancestors, sorted in reverse topological + order. As the parents cache, the history cache is lazily populated and can + be prefetched. To efficiently store the ancestor lists, the history cache + represents ancestors as graph edges (a pair of two SWHID nodes), meaning the + history cache is shared amongst all revisions parents. """ + + async def __aenter__(self): + await super().__aenter__() + await self.conn.execute( + """ + create table if not exists history_graph ( + src text not null, + dst text not null, + unique(src, dst) + ) + """ + ) + await self.conn.execute( + "create index if not exists index_history_graph on history_graph(src)" + ) + await self.conn.commit() + return self + + async def get(self, swhid: SWHID) -> Optional[List[SWHID]]: + cursor = await self.conn.execute( + """ + with recursive + dfs(node) AS ( + values(?) + union + select history_graph.dst + from history_graph + join dfs on history_graph.src = dfs.node + ) + -- Do not keep the root node since it is not an ancestor + select * from dfs limit -1 offset 1 + """, + (str(swhid),), + ) + cache = await cursor.fetchall() + if not cache: + return None + + history = [] + for parent in cache: + parent = parent[0] + try: + history.append(parse_swhid(parent)) + except ValidationError: + logging.warning(f"Cannot parse object from history cache: {parent}") + return history + + async def set(self, history: str) -> None: + history = history.strip() + edges = [edge.split(" ") for edge in history.split("\n")] + await self.conn.executemany( + "insert or ignore into history_graph values (?, ?)", edges + ) + await self.conn.commit() diff --git a/swh/fuse/cli.py b/swh/fuse/cli.py index 326d4ba..f19d30c 100644 --- a/swh/fuse/cli.py +++ b/swh/fuse/cli.py @@ -1,198 +1,199 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information # WARNING: do not import unnecessary things here to keep cli startup time under # control import os from pathlib import Path from typing import Any, Dict import click from swh.core.cli import CONTEXT_SETTINGS from swh.core.cli import swh as swh_cli_group from swh.model.cli import SWHIDParamType # All generic config code should reside in swh.core.config DEFAULT_CONFIG_PATH = os.environ.get( "SWH_CONFIG_FILE", os.path.join(click.get_app_dir("swh"), "global.yml") ) CACHE_HOME_DIR: Path = ( Path(os.environ["XDG_CACHE_HOME"]) if "XDG_CACHE_HOME" in os.environ else Path.home() / ".cache" ) DEFAULT_CONFIG: Dict[str, Any] = { "cache": { "metadata": {"path": CACHE_HOME_DIR / "swh/fuse/metadata.sqlite"}, "blob": {"path": CACHE_HOME_DIR / "swh/fuse/blob.sqlite"}, + "history": {"path": CACHE_HOME_DIR / "swh/fuse/history.sqlite"}, }, "web-api": { "url": "https://archive.softwareheritage.org/api/1", "auth-token": None, }, } @swh_cli_group.group(name="fs", context_settings=CONTEXT_SETTINGS) @click.option( "-C", "--config-file", default=None, type=click.Path(exists=True, dir_okay=False, path_type=str), help=f"Configuration file (default: {DEFAULT_CONFIG_PATH})", ) @click.pass_context def fuse(ctx, config_file): """Software Heritage virtual file system""" import logging import pprint from swh.core import config if not config_file: config_file = DEFAULT_CONFIG_PATH try: logging.info(f"Loading configuration from: {config_file}") conf = config.read_raw_config(config.config_basepath(config_file)) if not conf: raise ValueError(f"Cannot parse configuration file: {config_file}") if config_file == DEFAULT_CONFIG_PATH: try: conf = conf["swh"]["fuse"] except KeyError: pass # recursive merge not done by config.read conf = config.merge_configs(DEFAULT_CONFIG, conf) except Exception as err: logging.warning(f"Using default configuration (cannot load custom one: {err})") conf = DEFAULT_CONFIG logging.info(f"Read configuration: \n{pprint.pformat(conf)}") ctx.ensure_object(dict) ctx.obj["config"] = conf @fuse.command(name="mount") @click.argument( "path", required=True, metavar="PATH", type=click.Path(exists=True, dir_okay=True, file_okay=False), ) @click.argument("swhids", nargs=-1, metavar="[SWHID]...", type=SWHIDParamType()) @click.option( "-f/-d", "--foreground/--daemon", default=False, help="whether to run FUSE attached to the console (foreground) " "or daemonized in the background (default: daemon)", ) @click.pass_context def mount(ctx, swhids, path, foreground): """Mount the Software Heritage virtual file system at PATH. If specified, objects referenced by the given SWHIDs will be prefetched and used to populate the virtual file system (VFS). Otherwise the VFS will be populated on-demand, when accessing its content. \b Example: \b $ mkdir swhfs $ swh fs mount swhfs/ $ grep printf swhfs/archive/swh:1:cnt:c839dea9e8e6f0528b468214348fee8669b305b2 printf("Hello, World!"); $ """ import asyncio from contextlib import ExitStack import logging from daemon import DaemonContext from swh.fuse import fuse # TODO: set default logging settings when --log-config is not passed # DEFAULT_LOG_PATH = Path(".local/swh/fuse/mount.log") with ExitStack() as stack: if not foreground: # TODO: temporary fix until swh.core has the proper logging utilities # Disable logging config before daemonizing, and reset it once # daemonized to be sure to not close file handlers logging.shutdown() # Stay in the current working directory when spawning daemon cwd = os.getcwd() stack.enter_context(DaemonContext(working_directory=cwd)) logging.config.dictConfig( { "version": 1, "handlers": { "syslog": { "class": "logging.handlers.SysLogHandler", "address": "/dev/log", }, }, "root": {"level": ctx.obj["log_level"], "handlers": ["syslog"],}, } ) conf = ctx.obj["config"] asyncio.run(fuse.main(swhids, path, conf)) @fuse.command() @click.argument( "path", required=True, metavar="PATH", type=click.Path(exists=True, dir_okay=True, file_okay=False), ) @click.pass_context def umount(ctx, path): """Unmount a mounted virtual file system. Note: this is equivalent to ``fusermount -u PATH``, which can be used to unmount any FUSE-based virtual file system. See ``man fusermount3``. """ import logging import subprocess try: subprocess.run(["fusermount", "-u", path], check=True) except subprocess.CalledProcessError as err: logging.error( f"cannot unmount virtual file system: " f"\"{' '.join(err.cmd)}\" returned exit status {err.returncode}" ) ctx.exit(1) @fuse.command() @click.pass_context def clean(ctx): """Clean on-disk cache(s). """ def rm_cache(conf, cache_name): try: conf["cache"][cache_name]["path"].unlink(missing_ok=True) except KeyError: pass conf = ctx.obj["config"] - for cache_name in ["blob", "metadata"]: + for cache_name in ["blob", "metadata", "history"]: rm_cache(conf, cache_name) diff --git a/swh/fuse/fs/artifact.py b/swh/fuse/fs/artifact.py index 7d8f878..16874ca 100644 --- a/swh/fuse/fs/artifact.py +++ b/swh/fuse/fs/artifact.py @@ -1,302 +1,329 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from dataclasses import dataclass from pathlib import Path from typing import Any, AsyncIterator, List import urllib.parse from swh.fuse.fs.entry import ( EntryMode, FuseDirEntry, FuseEntry, FuseFileEntry, FuseSymlinkEntry, ) from swh.model.from_disk import DentryPerms from swh.model.identifiers import CONTENT, DIRECTORY, RELEASE, REVISION, SNAPSHOT, SWHID @dataclass class Content(FuseFileEntry): """ Software Heritage content artifact. Attributes: swhid: Software Heritage persistent identifier prefetch: optional prefetched metadata used to set entry attributes Content leaves (AKA blobs) are represented on disks as regular files, containing the corresponding bytes, as archived. Note that permissions are associated to blobs only in the context of directories. Hence, when accessing blobs from the top-level `archive/` directory, the permissions of the `archive/SWHID` file will be arbitrary and not meaningful (e.g., `0x644`). """ swhid: SWHID prefetch: Any = None async def get_content(self) -> bytes: data = await self.fuse.get_blob(self.swhid) if not self.prefetch: self.prefetch = {"length": len(data)} return data async def size(self) -> int: if self.prefetch: return self.prefetch["length"] else: return len(await self.get_content()) @dataclass class Directory(FuseDirEntry): """ Software Heritage directory artifact. Attributes: swhid: Software Heritage persistent identifier Directory nodes are represented as directories on the file-system, containing one entry for each entry of the archived directory. Entry names and other metadata, including permissions, will correspond to the archived entry metadata. Note that the FUSE mount is read-only, no matter what the permissions say. So it is possible that, in the context of a directory, a file is presented as writable, whereas actually writing to it will fail with `EPERM`. """ swhid: SWHID async def __aiter__(self) -> AsyncIterator[FuseEntry]: metadata = await self.fuse.get_metadata(self.swhid) for entry in metadata: name = entry["name"] swhid = entry["target"] mode = ( # Archived permissions for directories are always set to # 0o040000 so use a read-only permission instead int(EntryMode.RDONLY_DIR) if swhid.object_type == DIRECTORY else entry["perms"] ) # 1. Regular file if swhid.object_type == CONTENT: yield self.create_child( Content, name=name, mode=mode, swhid=swhid, # The directory API has extra info we can use to set # attributes without additional Software Heritage API call prefetch=entry, ) # 2. Regular directory elif swhid.object_type == DIRECTORY: yield self.create_child( Directory, name=name, mode=mode, swhid=swhid, ) # 3. Symlink elif mode == DentryPerms.symlink: yield self.create_child( FuseSymlinkEntry, name=name, # Symlink target is stored in the blob content target=await self.fuse.get_blob(swhid), ) # 4. Submodule elif swhid.object_type == REVISION: # Make sure the revision metadata is fetched and create a # symlink to distinguish it with regular directories await self.fuse.get_metadata(swhid) yield self.create_child( FuseSymlinkEntry, name=name, target=Path(self.get_relative_root_path(), f"archive/{swhid}"), ) else: raise ValueError("Unknown directory entry type: {swhid.object_type}") @dataclass class Revision(FuseDirEntry): """ Software Heritage revision artifact. Attributes: swhid: Software Heritage persistent identifier Revision (AKA commit) nodes are represented on the file-system as directories with the following entries: - `root`: source tree at the time of the commit, as a symlink pointing into `archive/`, to a SWHID of type `dir` - `parents/` (note the plural): a virtual directory containing entries named `1`, `2`, `3`, etc., one for each parent commit. Each of these entry is a symlink pointing into `archive/`, to the SWHID file for the given parent commit - `parent` (note the singular): present if and only if the current commit has at least one parent commit (which is the most common case). When present it is a symlink pointing into `parents/1/` + - `history`: a virtual directory listing all its revision ancestors, sorted + in reverse topological order. Each entry is a symlink pointing into + `archive/SWHID`. - `meta.json`: metadata for the current node, as a symlink pointing to the relevant `meta/.json` file """ swhid: SWHID async def __aiter__(self) -> AsyncIterator[FuseEntry]: metadata = await self.fuse.get_metadata(self.swhid) directory = metadata["directory"] parents = metadata["parents"] root_path = self.get_relative_root_path() yield self.create_child( FuseSymlinkEntry, name="root", target=Path(root_path, f"archive/{directory}"), ) yield self.create_child( FuseSymlinkEntry, name="meta.json", target=Path(root_path, f"meta/{self.swhid}.json"), ) yield self.create_child( RevisionParents, name="parents", mode=int(EntryMode.RDONLY_DIR), parents=[x["id"] for x in parents], ) if len(parents) >= 1: yield self.create_child( FuseSymlinkEntry, name="parent", target="parents/1/", ) + yield self.create_child( + RevisionHistory, + name="history", + mode=int(EntryMode.RDONLY_DIR), + swhid=self.swhid, + ) + @dataclass class RevisionParents(FuseDirEntry): """ Revision virtual `parents/` directory """ parents: List[SWHID] async def __aiter__(self) -> AsyncIterator[FuseEntry]: root_path = self.get_relative_root_path() for i, parent in enumerate(self.parents): yield self.create_child( FuseSymlinkEntry, name=str(i + 1), target=Path(root_path, f"archive/{parent}"), ) +@dataclass +class RevisionHistory(FuseDirEntry): + """ Revision virtual `history/` directory """ + + swhid: SWHID + + async def __aiter__(self) -> AsyncIterator[FuseEntry]: + history = await self.fuse.get_history(self.swhid) + root_path = self.get_relative_root_path() + for swhid in history: + yield self.create_child( + FuseSymlinkEntry, + name=str(swhid), + target=Path(root_path, f"archive/{swhid}"), + ) + + @dataclass class Release(FuseDirEntry): """ Software Heritage release artifact. Attributes: swhid: Software Heritage persistent identifier Release nodes are represented on the file-system as directories with the following entries: - `target`: target node, as a symlink to `archive/` - `target_type`: regular file containing the type of the target SWHID - `root`: present if and only if the release points to something that (transitively) resolves to a directory. When present it is a symlink pointing into `archive/` to the SWHID of the given directory - `meta.json`: metadata for the current node, as a symlink pointing to the relevant `meta/.json` file """ swhid: SWHID async def find_root_directory(self, swhid: SWHID) -> SWHID: if swhid.object_type == RELEASE: metadata = await self.fuse.get_metadata(swhid) return await self.find_root_directory(metadata["target"]) elif swhid.object_type == REVISION: metadata = await self.fuse.get_metadata(swhid) return metadata["directory"] elif swhid.object_type == DIRECTORY: return swhid else: return None async def __aiter__(self) -> AsyncIterator[FuseEntry]: metadata = await self.fuse.get_metadata(self.swhid) root_path = self.get_relative_root_path() yield self.create_child( FuseSymlinkEntry, name="meta.json", target=Path(root_path, f"meta/{self.swhid}.json"), ) target = metadata["target"] yield self.create_child( FuseSymlinkEntry, name="target", target=Path(root_path, f"archive/{target}") ) yield self.create_child( ReleaseType, name="target_type", mode=int(EntryMode.RDONLY_FILE), target_type=target.object_type, ) target_dir = await self.find_root_directory(target) if target_dir is not None: yield self.create_child( FuseSymlinkEntry, name="root", target=Path(root_path, f"archive/{target_dir}"), ) @dataclass class ReleaseType(FuseFileEntry): """ Release type virtual file """ target_type: str async def get_content(self) -> bytes: return str.encode(self.target_type + "\n") async def size(self) -> int: return len(await self.get_content()) @dataclass class Snapshot(FuseDirEntry): """ Software Heritage snapshot artifact. Attributes: swhid: Software Heritage persistent identifier Snapshot nodes are represented on the file-system as directories with one entry for each branch in the snapshot. Each entry is a symlink pointing into `archive/` to the branch target SWHID. Branch names are URL encoded (hence '/' are replaced with '%2F'). """ swhid: SWHID async def __aiter__(self) -> AsyncIterator[FuseEntry]: metadata = await self.fuse.get_metadata(self.swhid) root_path = self.get_relative_root_path() for branch_name, branch_meta in metadata.items(): # Mangle branch name to create a valid UNIX filename name = urllib.parse.quote_plus(branch_name) yield self.create_child( FuseSymlinkEntry, name=name, target=Path(root_path, f"archive/{branch_meta['target']}"), ) OBJTYPE_GETTERS = { CONTENT: Content, DIRECTORY: Directory, REVISION: Revision, RELEASE: Release, SNAPSHOT: Snapshot, } diff --git a/swh/fuse/fuse.py b/swh/fuse/fuse.py index 609e9fc..b21b891 100644 --- a/swh/fuse/fuse.py +++ b/swh/fuse/fuse.py @@ -1,247 +1,270 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import asyncio import errno import logging import os from pathlib import Path import time from typing import Any, Dict, List import pyfuse3 import pyfuse3_asyncio import requests from swh.fuse.cache import FuseCache from swh.fuse.fs.entry import FuseDirEntry, FuseEntry, FuseFileEntry, FuseSymlinkEntry from swh.fuse.fs.mountpoint import Root -from swh.model.identifiers import CONTENT, SWHID +from swh.model.identifiers import CONTENT, REVISION, SWHID from swh.web.client.client import WebAPIClient class Fuse(pyfuse3.Operations): """ Software Heritage Filesystem in Userspace (FUSE). Locally mount parts of the archive and navigate it as a virtual file system. """ def __init__( self, root_path: Path, cache: FuseCache, conf: Dict[str, Any], ): super(Fuse, self).__init__() self._next_inode: int = pyfuse3.ROOT_INODE self._inode2entry: Dict[int, FuseEntry] = {} self.root = Root(fuse=self) self.time_ns: int = time.time_ns() # start time, used as timestamp self.gid = os.getgid() self.uid = os.getuid() self.web_api = WebAPIClient( conf["web-api"]["url"], conf["web-api"]["auth-token"] ) self.cache = cache def shutdown(self) -> None: pass def _alloc_inode(self, entry: FuseEntry) -> int: """ Return a unique inode integer for a given entry """ inode = self._next_inode self._next_inode += 1 self._inode2entry[inode] = entry # TODO add inode recycling with invocation to invalidate_inode when # the dicts get too big return inode def inode2entry(self, inode: int) -> FuseEntry: """ Return the entry matching a given inode """ try: return self._inode2entry[inode] except KeyError: raise pyfuse3.FUSEError(errno.ENOENT) async def get_metadata(self, swhid: SWHID) -> Any: """ Retrieve metadata for a given SWHID using Software Heritage API """ cache = await self.cache.metadata.get(swhid) if cache: return cache try: - # TODO: swh-graph API typify = False # Get the raw JSON from the API # TODO: async web API loop = asyncio.get_event_loop() metadata = await loop.run_in_executor(None, self.web_api.get, swhid, typify) await self.cache.metadata.set(swhid, metadata) # Retrieve it from cache so it is correctly typed return await self.cache.metadata.get(swhid) except requests.HTTPError as err: logging.error(f"Cannot fetch metadata for object {swhid}: {err}") raise async def get_blob(self, swhid: SWHID) -> bytes: """ Retrieve the blob bytes for a given content SWHID using Software Heritage API """ if swhid.object_type != CONTENT: raise pyfuse3.FUSEError(errno.EINVAL) # Make sure the metadata cache is also populated with the given SWHID await self.get_metadata(swhid) cache = await self.cache.blob.get(swhid) if cache: return cache try: loop = asyncio.get_event_loop() resp = await loop.run_in_executor(None, self.web_api.content_raw, swhid) blob = b"".join(list(resp)) await self.cache.blob.set(swhid, blob) return blob except requests.HTTPError as err: logging.error(f"Cannot fetch blob for object {swhid}: {err}") raise + async def get_history(self, swhid: SWHID) -> List[SWHID]: + if swhid.object_type != REVISION: + raise pyfuse3.FUSEError(errno.EINVAL) + + cache = await self.cache.history.get(swhid) + if cache: + return cache + + try: + # Use the swh-graph API to retrieve the full history very fast + call = f"graph/visit/edges/{swhid}?edges=rev:rev" + loop = asyncio.get_event_loop() + history = await loop.run_in_executor(None, self.web_api._call, call) + await self.cache.history.set(history.text) + # Retrieve it from cache so it is correctly typed + return await self.cache.history.get(swhid) + except requests.HTTPError as err: + logging.error(f"Cannot fetch history for object {swhid}: {err}") + # Ignore exception since swh-graph does not necessarily contain the + # most recent artifacts from the archive. Computing the full history + # from the Web API is too computationally intensive so simply return + # an empty list. + return [] + async def get_attrs(self, entry: FuseEntry) -> pyfuse3.EntryAttributes: """ Return entry attributes """ attrs = pyfuse3.EntryAttributes() attrs.st_size = 0 attrs.st_atime_ns = self.time_ns attrs.st_ctime_ns = self.time_ns attrs.st_mtime_ns = self.time_ns attrs.st_gid = self.gid attrs.st_uid = self.uid attrs.st_ino = entry.inode attrs.st_mode = entry.mode attrs.st_size = await entry.size() return attrs async def getattr( self, inode: int, _ctx: pyfuse3.RequestContext ) -> pyfuse3.EntryAttributes: """ Get attributes for a given inode """ entry = self.inode2entry(inode) return await self.get_attrs(entry) async def opendir(self, inode: int, _ctx: pyfuse3.RequestContext) -> int: """ Open a directory referred by a given inode """ # Re-use inode as directory handle return inode async def readdir(self, fh: int, offset: int, token: pyfuse3.ReaddirToken) -> None: """ Read entries in an open directory """ # opendir() uses inode as directory handle inode = fh # TODO: add cache on direntry list? direntry = self.inode2entry(inode) assert isinstance(direntry, FuseDirEntry) next_id = offset + 1 i = 0 try: async for entry in direntry: if i < offset: i += 1 continue name = os.fsencode(entry.name) attrs = await self.get_attrs(entry) if not pyfuse3.readdir_reply(token, name, attrs, next_id): break next_id += 1 self._inode2entry[attrs.st_ino] = entry except Exception as err: logging.error(f"Cannot readdir: {err}") raise pyfuse3.FUSEError(errno.ENOENT) async def open( self, inode: int, _flags: int, _ctx: pyfuse3.RequestContext ) -> pyfuse3.FileInfo: """ Open an inode and return a unique file handle """ # Re-use inode as file handle return pyfuse3.FileInfo(fh=inode, keep_cache=True) async def read(self, fh: int, offset: int, length: int) -> bytes: """ Read `length` bytes from file handle `fh` at position `offset` """ # open() uses inode as file handle inode = fh entry = self.inode2entry(inode) assert isinstance(entry, FuseFileEntry) try: data = await entry.get_content() return data[offset : offset + length] except Exception as err: logging.error(f"Cannot read: {err}") raise pyfuse3.FUSEError(errno.ENOENT) async def lookup( self, parent_inode: int, name: str, _ctx: pyfuse3.RequestContext ) -> pyfuse3.EntryAttributes: """ Look up a directory entry by name and get its attributes """ name = os.fsdecode(name) parent_entry = self.inode2entry(parent_inode) assert isinstance(parent_entry, FuseDirEntry) try: lookup_entry = await parent_entry.lookup(name) if lookup_entry: return await self.get_attrs(lookup_entry) else: raise ValueError(f"unknown name: {name}") except Exception as err: logging.error(f"Cannot lookup: {err}") raise pyfuse3.FUSEError(errno.ENOENT) async def readlink(self, inode: int, _ctx: pyfuse3.RequestContext) -> bytes: entry = self.inode2entry(inode) assert isinstance(entry, FuseSymlinkEntry) return os.fsencode(entry.get_target()) async def main(swhids: List[SWHID], root_path: Path, conf: Dict[str, Any]) -> None: """ swh-fuse CLI entry-point """ # Use pyfuse3 asyncio layer to match the rest of Software Heritage codebase pyfuse3_asyncio.enable() async with FuseCache(conf["cache"]) as cache: fs = Fuse(root_path, cache, conf) # Initially populate the cache for swhid in swhids: try: await fs.get_metadata(swhid) except Exception as err: logging.error(f"Cannot prefetch object {swhid}: {err}") fuse_options = set(pyfuse3.default_options) fuse_options.add("fsname=swhfs") if logging.root.level <= logging.DEBUG: fuse_options.add("debug") try: pyfuse3.init(fs, root_path, fuse_options) await pyfuse3.main() except Exception as err: logging.error(f"Error running FUSE: {err}") finally: fs.shutdown() pyfuse3.close(unmount=True) diff --git a/swh/fuse/tests/conftest.py b/swh/fuse/tests/conftest.py index b81f1f2..f6c8ac3 100644 --- a/swh/fuse/tests/conftest.py +++ b/swh/fuse/tests/conftest.py @@ -1,76 +1,80 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import json from multiprocessing import Process import os from pathlib import Path import subprocess from tempfile import NamedTemporaryFile, TemporaryDirectory import time from click.testing import CliRunner import pytest import yaml import swh.fuse.cli as cli from swh.fuse.tests.data.api_data import API_URL, MOCK_ARCHIVE @pytest.fixture def web_api_mock(requests_mock): for api_call, data in MOCK_ARCHIVE.items(): # Convert Python dict JSON into a string (only for non-raw API call) - if not api_call.endswith("raw/"): + if not api_call.endswith("raw/") and not api_call.startswith("graph/"): data = json.dumps(data) requests_mock.get(f"{API_URL}/{api_call}", text=data) return requests_mock @pytest.fixture def fuse_mntdir(web_api_mock): tmpdir = TemporaryDirectory(suffix=".swh-fuse-test") tmpfile = NamedTemporaryFile(suffix=".swh-fuse-test.yml") config = { - "cache": {"metadata": {"in-memory": True}, "blob": {"in-memory": True}}, + "cache": { + "metadata": {"in-memory": True}, + "blob": {"in-memory": True}, + "history": {"in-memory": True}, + }, "web-api": {"url": API_URL, "auth-token": None}, } # Run FUSE in foreground mode but in a separate process, so it does not # block execution and remains easy to kill during teardown def fuse_process(tmpdir, tmpfile): with tmpdir as mntdir, tmpfile as config_path: config_path = Path(config_path.name) config_path.write_text(yaml.dump(config)) CliRunner().invoke( cli.fuse, args=[ "--config-file", str(config_path), "mount", mntdir, "--foreground", ], ) fuse = Process(target=fuse_process, args=[tmpdir, tmpfile]) fuse.start() # Wait max 3 seconds for the FUSE to correctly mount for i in range(30): try: root = os.listdir(tmpdir.name) if root: break except FileNotFoundError: pass time.sleep(0.1) else: raise FileNotFoundError(f"Could not mount FUSE in {tmpdir.name}") yield Path(tmpdir.name) subprocess.run(["fusermount", "-u", tmpdir.name], check=True) fuse.join() diff --git a/swh/fuse/tests/data/api_data.py b/swh/fuse/tests/data/api_data.py index 07e4edc..db2ddab 100644 --- a/swh/fuse/tests/data/api_data.py +++ b/swh/fuse/tests/data/api_data.py @@ -1,1534 +1,1536 @@ # GENERATED FILE, DO NOT EDIT. # Run './gen-api-data.py > api_data.py' instead. # flake8: noqa from typing import Any, Dict API_URL = "https://invalid-test-only.archive.softwareheritage.org/api/1" SWHID2URL: Dict[str, str] = { "swh:1:cnt:61d3c9e1157203f0c4ed5165608d92294eaca808": "content/sha1_git:61d3c9e1157203f0c4ed5165608d92294eaca808/", "swh:1:dir:c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578": "directory/c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578/", "swh:1:dir:80ae84abc6122c47aae597fde99645f8663d1aba": "directory/80ae84abc6122c47aae597fde99645f8663d1aba/", "swh:1:rev:b8cedc00407a4c56a3bda1ed605c6fc166655447": "revision/b8cedc00407a4c56a3bda1ed605c6fc166655447/", "swh:1:rev:87dd6843678575f8dda962f239d14ef4be14b352": "revision/87dd6843678575f8dda962f239d14ef4be14b352/", "swh:1:rev:1a2390247ad6d08160e0dd74f40a01a9578659c2": "revision/1a2390247ad6d08160e0dd74f40a01a9578659c2/", "swh:1:rev:4d78994915af1bde9a95c04a8c27d8dca066232a": "revision/4d78994915af1bde9a95c04a8c27d8dca066232a/", "swh:1:rev:3e6e1001dc6e095dbd5c88005e80969f60e384e1": "revision/3e6e1001dc6e095dbd5c88005e80969f60e384e1/", "swh:1:rev:11e893fc1357bc688418ddf1087c2b7aa25d154d": "revision/11e893fc1357bc688418ddf1087c2b7aa25d154d/", "swh:1:rev:1c2bd024d13f8011307e13386cf1fea2180352b5": "revision/1c2bd024d13f8011307e13386cf1fea2180352b5/", "swh:1:rev:92baf7293dd2d418d2ac4b141b0faa822075d9f7": "revision/92baf7293dd2d418d2ac4b141b0faa822075d9f7/", "swh:1:rel:874f7cbe352033cac5a8bc889847da2fe1d13e9f": "release/874f7cbe352033cac5a8bc889847da2fe1d13e9f/", "swh:1:rel:da5f9898d6248ab26277116f54aca855338401d2": "release/da5f9898d6248ab26277116f54aca855338401d2/", "swh:1:cnt:be5effea679c057aec2bb020f0241b1d1d660840": "content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/", "swh:1:rel:3a7b2dfffed2945d2933ba4ebc063adba35ddb2e": "release/3a7b2dfffed2945d2933ba4ebc063adba35ddb2e/", "swh:1:dir:b24d39c928b9c3f440f8e2ec06c78f43d28d87d6": "directory/b24d39c928b9c3f440f8e2ec06c78f43d28d87d6/", "swh:1:snp:02db117fef22434f1658b833a756775ca6effed0": "snapshot/02db117fef22434f1658b833a756775ca6effed0/", "swh:1:rev:430a9fd4c797c50cea26157141b2408073b2ed91": "revision/430a9fd4c797c50cea26157141b2408073b2ed91/", } MOCK_ARCHIVE: Dict[str, Any] = { "content/sha1_git:61d3c9e1157203f0c4ed5165608d92294eaca808/": { "length": 10084, "status": "visible", "checksums": { - "sha256": "7152be0097b003d148f773ce0be0a695219c636f4f20073993335758c810166c", - "sha1": "0d84ad5f5167010347a13cf0be95f47a3cb99dfa", "blake2s256": "fe43b0ad08a9bf943a912f67b5e7d98e58fbd4ff318dcd9a1edaceceefe9ebca", + "sha1": "0d84ad5f5167010347a13cf0be95f47a3cb99dfa", + "sha256": "7152be0097b003d148f773ce0be0a695219c636f4f20073993335758c810166c", "sha1_git": "61d3c9e1157203f0c4ed5165608d92294eaca808", }, "data_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:61d3c9e1157203f0c4ed5165608d92294eaca808/raw/", "filetype_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:61d3c9e1157203f0c4ed5165608d92294eaca808/filetype/", "language_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:61d3c9e1157203f0c4ed5165608d92294eaca808/language/", "license_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:61d3c9e1157203f0c4ed5165608d92294eaca808/license/", }, "content/sha1_git:61d3c9e1157203f0c4ed5165608d92294eaca808/raw/": "# The Rust Programming Language\n\nThis is the main source code repository for [Rust]. It contains the compiler,\nstandard library, and documentation.\n\n[Rust]: https://www.rust-lang.org\n\n## Quick Start\n\nRead [\"Installation\"] from [The Book].\n\n[\"Installation\"]: https://doc.rust-lang.org/book/ch01-01-installation.html\n[The Book]: https://doc.rust-lang.org/book/index.html\n\n## Installing from Source\n\n_Note: If you wish to contribute to the compiler, you should read [this\nchapter][rustcguidebuild] of the rustc-guide instead of this section._\n\nThe Rust build system has a Python script called `x.py` to bootstrap building\nthe compiler. More information about it may be found by running `./x.py --help`\nor reading the [rustc guide][rustcguidebuild].\n\n[rustcguidebuild]: https://rust-lang.github.io/rustc-guide/building/how-to-build-and-run.html\n\n### Building on *nix\n1. Make sure you have installed the dependencies:\n\n * `g++` 5.1 or later or `clang++` 3.5 or later\n * `python` 2.7 (but not 3.x)\n * GNU `make` 3.81 or later\n * `cmake` 3.4.3 or later\n * `curl`\n * `git`\n * `ssl` which comes in `libssl-dev` or `openssl-devel`\n * `pkg-config` if you are compiling on Linux and targeting Linux\n\n2. Clone the [source] with `git`:\n\n ```sh\n $ git clone https://github.com/rust-lang/rust.git\n $ cd rust\n ```\n\n[source]: https://github.com/rust-lang/rust\n\n3. Configure the build settings:\n\n The Rust build system uses a file named `config.toml` in the root of the\n source tree to determine various configuration settings for the build.\n Copy the default `config.toml.example` to `config.toml` to get started.\n\n ```sh\n $ cp config.toml.example config.toml\n ```\n\n It is recommended that if you plan to use the Rust build system to create\n an installation (using `./x.py install`) that you set the `prefix` value\n in the `[install]` section to a directory that you have write permissions.\n\n Create install directory if you are not installing in default directory\n\n4. Build and install:\n\n ```sh\n $ ./x.py build && ./x.py install\n ```\n\n When complete, `./x.py install` will place several programs into\n `$PREFIX/bin`: `rustc`, the Rust compiler, and `rustdoc`, the\n API-documentation tool. This install does not include [Cargo],\n Rust's package manager. To build and install Cargo, you may\n run `./x.py install cargo` or set the `build.extended` key in\n `config.toml` to `true` to build and install all tools.\n\n[Cargo]: https://github.com/rust-lang/cargo\n\n### Building on Windows\n\nThere are two prominent ABIs in use on Windows: the native (MSVC) ABI used by\nVisual Studio, and the GNU ABI used by the GCC toolchain. Which version of Rust\nyou need depends largely on what C/C++ libraries you want to interoperate with:\nfor interop with software produced by Visual Studio use the MSVC build of Rust;\nfor interop with GNU software built using the MinGW/MSYS2 toolchain use the GNU\nbuild.\n\n#### MinGW\n\n[MSYS2][msys2] can be used to easily build Rust on Windows:\n\n[msys2]: https://msys2.github.io/\n\n1. Grab the latest [MSYS2 installer][msys2] and go through the installer.\n\n2. Run `mingw32_shell.bat` or `mingw64_shell.bat` from wherever you installed\n MSYS2 (i.e. `C:\\msys64`), depending on whether you want 32-bit or 64-bit\n Rust. (As of the latest version of MSYS2 you have to run `msys2_shell.cmd\n -mingw32` or `msys2_shell.cmd -mingw64` from the command line instead)\n\n3. From this terminal, install the required tools:\n\n ```sh\n # Update package mirrors (may be needed if you have a fresh install of MSYS2)\n $ pacman -Sy pacman-mirrors\n\n # Install build tools needed for Rust. If you're building a 32-bit compiler,\n # then replace \"x86_64\" below with \"i686\". If you've already got git, python,\n # or CMake installed and in PATH you can remove them from this list. Note\n # that it is important that you do **not** use the 'python2' and 'cmake'\n # packages from the 'msys2' subsystem. The build has historically been known\n # to fail with these packages.\n $ pacman -S git \\\n make \\\n diffutils \\\n tar \\\n mingw-w64-x86_64-python2 \\\n mingw-w64-x86_64-cmake \\\n mingw-w64-x86_64-gcc\n ```\n\n4. Navigate to Rust's source code (or clone it), then build it:\n\n ```sh\n $ ./x.py build && ./x.py install\n ```\n\n#### MSVC\n\nMSVC builds of Rust additionally require an installation of Visual Studio 2017\n(or later) so `rustc` can use its linker. The simplest way is to get the\n[Visual Studio], check the “C++ build tools” and “Windows 10 SDK” workload.\n\n[Visual Studio]: https://visualstudio.microsoft.com/downloads/\n\n(If you're installing cmake yourself, be careful that “C++ CMake tools for\nWindows” doesn't get included under “Individual components”.)\n\nWith these dependencies installed, you can build the compiler in a `cmd.exe`\nshell with:\n\n```sh\n> python x.py build\n```\n\nCurrently, building Rust only works with some known versions of Visual Studio. If\nyou have a more recent version installed the build system doesn't understand\nthen you may need to force rustbuild to use an older version. This can be done\nby manually calling the appropriate vcvars file before running the bootstrap.\n\n```batch\n> CALL \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat\"\n> python x.py build\n```\n\n### Building rustc with older host toolchains\nIt is still possible to build Rust with the older toolchain versions listed below, but only if the\nLLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN option is set to true in the config.toml file.\n\n* Clang 3.1\n* Apple Clang 3.1\n* GCC 4.8\n* Visual Studio 2015 (Update 3)\n\nToolchain versions older than what is listed above cannot be used to build rustc.\n\n#### Specifying an ABI\n\nEach specific ABI can also be used from either environment (for example, using\nthe GNU ABI in PowerShell) by using an explicit build triple. The available\nWindows build triples are:\n- GNU ABI (using GCC)\n - `i686-pc-windows-gnu`\n - `x86_64-pc-windows-gnu`\n- The MSVC ABI\n - `i686-pc-windows-msvc`\n - `x86_64-pc-windows-msvc`\n\nThe build triple can be specified by either specifying `--build=` when\ninvoking `x.py` commands, or by copying the `config.toml` file (as described\nin [Installing From Source](#installing-from-source)), and modifying the\n`build` option under the `[build]` section.\n\n### Configure and Make\n\nWhile it's not the recommended build system, this project also provides a\nconfigure script and makefile (the latter of which just invokes `x.py`).\n\n```sh\n$ ./configure\n$ make && sudo make install\n```\n\nWhen using the configure script, the generated `config.mk` file may override the\n`config.toml` file. To go back to the `config.toml` file, delete the generated\n`config.mk` file.\n\n## Building Documentation\n\nIf you’d like to build the documentation, it’s almost the same:\n\n```sh\n$ ./x.py doc\n```\n\nThe generated documentation will appear under `doc` in the `build` directory for\nthe ABI used. I.e., if the ABI was `x86_64-pc-windows-msvc`, the directory will be\n`build\\x86_64-pc-windows-msvc\\doc`.\n\n## Notes\n\nSince the Rust compiler is written in Rust, it must be built by a\nprecompiled \"snapshot\" version of itself (made in an earlier stage of\ndevelopment). As such, source builds require a connection to the Internet, to\nfetch snapshots, and an OS that can execute the available snapshot binaries.\n\nSnapshot binaries are currently built and tested on several platforms:\n\n| Platform / Architecture | x86 | x86_64 |\n|----------------------------|-----|--------|\n| Windows (7, 8, 10, ...) | ✓ | ✓ |\n| Linux (2.6.18 or later) | ✓ | ✓ |\n| macOS (10.7 Lion or later) | ✓ | ✓ |\n\nYou may find that other platforms work, but these are our officially\nsupported build environments that are most likely to work.\n\nThere is more advice about hacking on Rust in [CONTRIBUTING.md].\n\n[CONTRIBUTING.md]: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md\n\n## Getting Help\n\nThe Rust community congregates in a few places:\n\n* [Stack Overflow] - Direct questions about using the language.\n* [users.rust-lang.org] - General discussion and broader questions.\n* [/r/rust] - News and general discussion.\n\n[Stack Overflow]: https://stackoverflow.com/questions/tagged/rust\n[/r/rust]: https://reddit.com/r/rust\n[users.rust-lang.org]: https://users.rust-lang.org/\n\n## Contributing\n\nTo contribute to Rust, please see [CONTRIBUTING](CONTRIBUTING.md).\n\nMost real-time collaboration happens in a variety of channels on the\n[Rust Discord server][rust-discord], with channels dedicated for getting help,\ncommunity, documentation, and all major contribution areas in the Rust ecosystem.\nA good place to ask for help would be the #help channel.\n\nThe [rustc guide] might be a good place to start if you want to find out how\nvarious parts of the compiler work.\n\nAlso, you may find the [rustdocs for the compiler itself][rustdocs] useful.\n\n[rust-discord]: https://discord.gg/rust-lang\n[rustc guide]: https://rust-lang.github.io/rustc-guide/about-this-guide.html\n[rustdocs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/\n\n## License\n\nRust is primarily distributed under the terms of both the MIT license\nand the Apache License (Version 2.0), with portions covered by various\nBSD-like licenses.\n\nSee [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT), and\n[COPYRIGHT](COPYRIGHT) for details.\n\n## Trademark\n\nThe Rust programming language is an open source, community project governed\nby a core team. It is also sponsored by the Mozilla Foundation (“Mozilla”),\nwhich owns and protects the Rust and Cargo trademarks and logos\n(the “Rust Trademarks”).\n\nIf you want to use these names or brands, please read the [media guide][media-guide].\n\nThird-party logos may be subject to third-party copyrights and trademarks. See\n[Licenses][policies-licenses] for details.\n\n[media-guide]: https://www.rust-lang.org/policies/media-guide\n[policies-licenses]: https://www.rust-lang.org/policies/licenses\n", "directory/c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578/": [ { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "a7de7ce85593c140267bd3bafa3812859d8f259f", "name": ".gitattributes", "perms": 33188, "status": "visible", "length": 357, "checksums": { - "sha256": "59a397e1ac39dd858750476ebd621ad0b468e511c3bea56fb8b507849409bdde", "sha1": "ce9cce2dad1505a8d35c229943925046d3a4cbc8", + "sha256": "59a397e1ac39dd858750476ebd621ad0b468e511c3bea56fb8b507849409bdde", "sha1_git": "a7de7ce85593c140267bd3bafa3812859d8f259f", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:a7de7ce85593c140267bd3bafa3812859d8f259f/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "d9761ce40927ce92d29daa23b4496e04b9e97e4f", "name": ".gitignore", "perms": 33188, "status": "visible", "length": 1054, "checksums": { - "sha256": "d0d262bc2f18bda49a434222ff508f8fe43da72b31b06138cb81b9d8fc6c471a", "sha1": "874e94541299f36e7d45d5e60252cc360421d921", + "sha256": "d0d262bc2f18bda49a434222ff508f8fe43da72b31b06138cb81b9d8fc6c471a", "sha1_git": "d9761ce40927ce92d29daa23b4496e04b9e97e4f", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:d9761ce40927ce92d29daa23b4496e04b9e97e4f/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "003e50d0788e4c6efb0d4315556a8c1ce0cf73ef", "name": ".gitmodules", "perms": 33188, "status": "visible", "length": 1638, "checksums": { - "sha256": "429734af1b42ca1d4e7b8112a9fbcb0d13ec8a89cc92864bddd6a4036a68ead9", "sha1": "2728096a9234a05c2e246dbf437cc99969a7ed73", + "sha256": "429734af1b42ca1d4e7b8112a9fbcb0d13ec8a89cc92864bddd6a4036a68ead9", "sha1_git": "003e50d0788e4c6efb0d4315556a8c1ce0cf73ef", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:003e50d0788e4c6efb0d4315556a8c1ce0cf73ef/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "6ab6be26cf101388162fbec2a942d5352ecea49a", "name": ".mailmap", "perms": 33188, "status": "visible", "length": 16168, "checksums": { - "sha256": "8b0443f512c8540b2942bfad7b2057bf05d3718c8d00e4e04099575a1b3cba1d", "sha1": "272e26eb45fbe4d57d2b3ef771b728aa37d45f04", + "sha256": "8b0443f512c8540b2942bfad7b2057bf05d3718c8d00e4e04099575a1b3cba1d", "sha1_git": "6ab6be26cf101388162fbec2a942d5352ecea49a", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:6ab6be26cf101388162fbec2a942d5352ecea49a/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "e3708bc485399fd42b32c6a1c24491771afa1a04", "name": "CODE_OF_CONDUCT.md", "perms": 33188, "status": "visible", "length": 131, "checksums": { - "sha256": "3c4d1c4de2e6991695f5dc495f7530ecb188dfafdb1f47a1323ce7159987accd", "sha1": "8242335087079c2fafb18c1f6f89bcdb8f6ba647", + "sha256": "3c4d1c4de2e6991695f5dc495f7530ecb188dfafdb1f47a1323ce7159987accd", "sha1_git": "e3708bc485399fd42b32c6a1c24491771afa1a04", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:e3708bc485399fd42b32c6a1c24491771afa1a04/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "fc8ca5d07b21280c575477457b8e4e3e953b26b4", "name": "CONTRIBUTING.md", "perms": 33188, "status": "visible", "length": 21302, "checksums": { - "sha256": "0ce1302f56e93ac9cee754253690d5400f907e80d63d175e603ef26a537c5131", "sha1": "3439b8ccdf39af7b95a646c6ae3fa18fe86e6420", + "sha256": "0ce1302f56e93ac9cee754253690d5400f907e80d63d175e603ef26a537c5131", "sha1_git": "fc8ca5d07b21280c575477457b8e4e3e953b26b4", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:fc8ca5d07b21280c575477457b8e4e3e953b26b4/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "dc9abf84b8e5a4d3b6ab5472883f0997fa0454cc", "name": "COPYRIGHT", "perms": 33188, "status": "visible", "length": 9322, "checksums": { - "sha256": "401266ab45019fe25d501eb10f11b85140ecf54a739fc1e3d26800ed276f899a", "sha1": "9fa123623c5ecf1fa171c3a211c41dc1b4767fe8", + "sha256": "401266ab45019fe25d501eb10f11b85140ecf54a739fc1e3d26800ed276f899a", "sha1_git": "dc9abf84b8e5a4d3b6ab5472883f0997fa0454cc", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:dc9abf84b8e5a4d3b6ab5472883f0997fa0454cc/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "80c90243e5db7130efa10f96e19fb65a9cbcf140", "name": "Cargo.lock", "perms": 33188, "status": "visible", "length": 116575, "checksums": { - "sha256": "8a0f9f9557435540a797032ae005fa40cae933cf906730bf95cf1d12e850e0a0", "sha1": "cb30c3049af7a34291af1fff80ecb91f8728879a", + "sha256": "8a0f9f9557435540a797032ae005fa40cae933cf906730bf95cf1d12e850e0a0", "sha1_git": "80c90243e5db7130efa10f96e19fb65a9cbcf140", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:80c90243e5db7130efa10f96e19fb65a9cbcf140/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "9d5c27b96df5d435daaded1ece44d1c8b6b613c1", "name": "Cargo.toml", "perms": 33188, "status": "visible", "length": 2436, "checksums": { - "sha256": "5eefbe2e4fad05f80b63450a764b646dd3c691376cbe8aaf7ef68b9911ea5704", "sha1": "037cc780fa9836ec344fe02b47ab5c3642fe26b1", + "sha256": "5eefbe2e4fad05f80b63450a764b646dd3c691376cbe8aaf7ef68b9911ea5704", "sha1_git": "9d5c27b96df5d435daaded1ece44d1c8b6b613c1", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:9d5c27b96df5d435daaded1ece44d1c8b6b613c1/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "1b5ec8b78e237b5c3b3d812a7c0a6589d0f7161d", "name": "LICENSE-APACHE", "perms": 33188, "status": "visible", "length": 9723, "checksums": { - "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", "sha1": "6e5c4711bcae04967d7f5b5e01cf56ae03bebe7a", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", "sha1_git": "1b5ec8b78e237b5c3b3d812a7c0a6589d0f7161d", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:1b5ec8b78e237b5c3b3d812a7c0a6589d0f7161d/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "31aa79387f27e730e33d871925e152e35e428031", "name": "LICENSE-MIT", "perms": 33188, "status": "visible", "length": 1023, "checksums": { - "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", "sha1": "ce3a2603094e799f42ce99c40941544dfcc5c4a5", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", "sha1_git": "31aa79387f27e730e33d871925e152e35e428031", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:31aa79387f27e730e33d871925e152e35e428031/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "61d3c9e1157203f0c4ed5165608d92294eaca808", "name": "README.md", "perms": 33188, "status": "visible", "length": 10084, "checksums": { - "sha256": "7152be0097b003d148f773ce0be0a695219c636f4f20073993335758c810166c", "sha1": "0d84ad5f5167010347a13cf0be95f47a3cb99dfa", + "sha256": "7152be0097b003d148f773ce0be0a695219c636f4f20073993335758c810166c", "sha1_git": "61d3c9e1157203f0c4ed5165608d92294eaca808", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:61d3c9e1157203f0c4ed5165608d92294eaca808/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "77d0bbe57912bed5a90c2f1d3628eb7bdcab0dd8", "name": "RELEASES.md", "perms": 33188, "status": "visible", "length": 436110, "checksums": { - "sha256": "9efd0b82142e37f24948d185a359c84d57c8894ef32480a98e963c5076400f7f", "sha1": "971916d3e574a3b1caa332e144d3cd85d396aa39", + "sha256": "9efd0b82142e37f24948d185a359c84d57c8894ef32480a98e963c5076400f7f", "sha1_git": "77d0bbe57912bed5a90c2f1d3628eb7bdcab0dd8", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:77d0bbe57912bed5a90c2f1d3628eb7bdcab0dd8/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "c9e17337ee23f801857093ec1237bbb833ae17b3", "name": "config.toml.example", "perms": 33188, "status": "visible", "length": 22148, "checksums": { - "sha256": "f77840688189e2a3fb1f7921886e763382d7c65b7b044bb4d92f21957c7773e2", "sha1": "604c62c6a08002c18795f0e3c70bdc454ad8889c", + "sha256": "f77840688189e2a3fb1f7921886e763382d7c65b7b044bb4d92f21957c7773e2", "sha1_git": "c9e17337ee23f801857093ec1237bbb833ae17b3", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:c9e17337ee23f801857093ec1237bbb833ae17b3/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "eeb8d081d34549f5ca2b19f703bbb4e547264e46", "name": "configure", "perms": 33261, "status": "visible", "length": 275, "checksums": { - "sha256": "5f6e26a0f2993b96749fce11791631e8b0085f344f8c135b710e182c4d6dd420", "sha1": "f6a766df481855359c1dac80c0262a5e6c3f3aab", + "sha256": "5f6e26a0f2993b96749fce11791631e8b0085f344f8c135b710e182c4d6dd420", "sha1_git": "eeb8d081d34549f5ca2b19f703bbb4e547264e46", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:eeb8d081d34549f5ca2b19f703bbb4e547264e46/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "73f8cc1ff68c68bbbbfe6216f4b0f00626701672", "name": "rustfmt.toml", "perms": 33188, "status": "visible", "length": 1014, "checksums": { - "sha256": "37bcf3d674319038e17f9d607a5df81b93ea2b96408db43ba9920c6bbafad47a", "sha1": "b778b5d9c139074d0eba57486419d2513af537ec", + "sha256": "37bcf3d674319038e17f9d607a5df81b93ea2b96408db43ba9920c6bbafad47a", "sha1_git": "73f8cc1ff68c68bbbbfe6216f4b0f00626701672", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:73f8cc1ff68c68bbbbfe6216f4b0f00626701672/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "dir", "target": "64df732293e27dee84e495363040af15a5b3a54b", "name": "src", "perms": 16384, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/directory/64df732293e27dee84e495363040af15a5b3a54b/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "7ece7f977ce2a39b76c002105aacb1598885a36a", "name": "triagebot.toml", "perms": 33188, "status": "visible", "length": 971, "checksums": { - "sha256": "f405f6325384e99729cc883ff871512f1be4829259059d2d59a7b47e3062ef90", "sha1": "07d3df8565a55d2ddf1502b7fb0b173d128e3fda", + "sha256": "f405f6325384e99729cc883ff871512f1be4829259059d2d59a7b47e3062ef90", "sha1_git": "7ece7f977ce2a39b76c002105aacb1598885a36a", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:7ece7f977ce2a39b76c002105aacb1598885a36a/", }, { "dir_id": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "type": "file", "target": "7973730ef177cd600aaf4baebe1b40a81ed1b085", "name": "x.py", "perms": 33261, "status": "visible", "length": 270, "checksums": { - "sha256": "3573a0e5a4def372ad5800b3f76aa4163b60bce4596fac892de737e409380baf", "sha1": "6eb663ee7ac8d3849139dca2e60c00f2935915eb", + "sha256": "3573a0e5a4def372ad5800b3f76aa4163b60bce4596fac892de737e409380baf", "sha1_git": "7973730ef177cd600aaf4baebe1b40a81ed1b085", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:7973730ef177cd600aaf4baebe1b40a81ed1b085/", }, ], "directory/80ae84abc6122c47aae597fde99645f8663d1aba/": [ { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "rev", "target": "87dd6843678575f8dda962f239d14ef4be14b352", "name": "book", "perms": 57344, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/revision/87dd6843678575f8dda962f239d14ef4be14b352/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "f4898dc676530356e86b287c42018a2ad4cd5699", "name": "complement-design-faq.md", "perms": 33188, "status": "visible", "length": 92, "checksums": { - "sha256": "3cfb6483c2ff498754aa2cf9ef41347cc5fe41c7753bc74c1db5f3160d07d0b4", "sha1": "a5f982a0831d5c563610de8d3f82ab3a574e6f97", + "sha256": "3cfb6483c2ff498754aa2cf9ef41347cc5fe41c7753bc74c1db5f3160d07d0b4", "sha1_git": "f4898dc676530356e86b287c42018a2ad4cd5699", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:f4898dc676530356e86b287c42018a2ad4cd5699/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "920c6edc389fe8aafdd17f582df8af6aed80cf2e", "name": "complement-lang-faq.md", "perms": 33188, "status": "visible", "length": 94, "checksums": { - "sha256": "10e8220d761c9ff87954417effad9c6c381739732e12e7975129c845beda6721", "sha1": "609a26fbd8a91083d7fb551d5e1096ed7e95987d", + "sha256": "10e8220d761c9ff87954417effad9c6c381739732e12e7975129c845beda6721", "sha1_git": "920c6edc389fe8aafdd17f582df8af6aed80cf2e", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:920c6edc389fe8aafdd17f582df8af6aed80cf2e/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "b44de8e2cb32d3cd72213bcb4228870f1edcf0dc", "name": "complement-project-faq.md", "perms": 33188, "status": "visible", "length": 93, "checksums": { - "sha256": "0d402aa08c59e2f134f0bc6696c4d81cbda379772a8b4a4f959270ef1713ed42", "sha1": "dd14bfdaf0b97f433c6a107942b4bfb3f9080a86", + "sha256": "0d402aa08c59e2f134f0bc6696c4d81cbda379772a8b4a4f959270ef1713ed42", "sha1_git": "b44de8e2cb32d3cd72213bcb4228870f1edcf0dc", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:b44de8e2cb32d3cd72213bcb4228870f1edcf0dc/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "rev", "target": "1a2390247ad6d08160e0dd74f40a01a9578659c2", "name": "edition-guide", "perms": 57344, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/revision/1a2390247ad6d08160e0dd74f40a01a9578659c2/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "rev", "target": "4d78994915af1bde9a95c04a8c27d8dca066232a", "name": "embedded-book", "perms": 57344, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/revision/4d78994915af1bde9a95c04a8c27d8dca066232a/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "8f881657bdc1a1965140ab4941436a06bee2f3ba", "name": "favicon.inc", "perms": 33188, "status": "visible", "length": 72, "checksums": { - "sha256": "6ce1630d627e002c01e21bb1c14994cf814ebe00fab6ca6e97d4c051a9fccc83", "sha1": "f054d111eeef94a0337a06e2d2b81b9a276cdab3", + "sha256": "6ce1630d627e002c01e21bb1c14994cf814ebe00fab6ca6e97d4c051a9fccc83", "sha1_git": "8f881657bdc1a1965140ab4941436a06bee2f3ba", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:8f881657bdc1a1965140ab4941436a06bee2f3ba/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "77e151235e822d4281d365d6908d13bf8073a231", "name": "footer.inc", "perms": 33188, "status": "visible", "length": 362, "checksums": { - "sha256": "93aa2c5f3a3a890581870a66d6233b5fdb181901694c8f95c8155ed621ada30c", "sha1": "c255bb1015b2da689f615cd4b8dd0a8c04eab4fd", + "sha256": "93aa2c5f3a3a890581870a66d6233b5fdb181901694c8f95c8155ed621ada30c", "sha1_git": "77e151235e822d4281d365d6908d13bf8073a231", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:77e151235e822d4281d365d6908d13bf8073a231/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "604a65dc8a9b98cd797e6555d1802a49c6067cef", "name": "full-toc.inc", "perms": 33188, "status": "visible", "length": 265, "checksums": { - "sha256": "7fea658f27efcb8eea07748e98b2fbc80523ff5c2aadd39556f65c7ccd9da4bc", "sha1": "1e450161ad277053fe76c03a209de22b59a4b534", + "sha256": "7fea658f27efcb8eea07748e98b2fbc80523ff5c2aadd39556f65c7ccd9da4bc", "sha1_git": "604a65dc8a9b98cd797e6555d1802a49c6067cef", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:604a65dc8a9b98cd797e6555d1802a49c6067cef/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "4501d74073e900846f0bcf13196bfca39f6e9484", "name": "grammar.md", "perms": 33188, "status": "visible", "length": 267, "checksums": { - "sha256": "da97f73a003c3a658500547e8a97be80b0481c5aa753681f8391e9fd24a28349", "sha1": "26b33551387b7defe83d0ec2f69e70bc2df5a4df", + "sha256": "da97f73a003c3a658500547e8a97be80b0481c5aa753681f8391e9fd24a28349", "sha1_git": "4501d74073e900846f0bcf13196bfca39f6e9484", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:4501d74073e900846f0bcf13196bfca39f6e9484/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "85badc11d64f03de8b2fd9262b1fb2cf0470cbcb", "name": "guide-crates.md", "perms": 33188, "status": "visible", "length": 139, "checksums": { - "sha256": "5aa7054e3c2238dc093c46547604beece9b91e186364b1fe2bd5029a9676643b", "sha1": "a5481e1cb75eed8d90663e33d3ac6d9c4ac47c56", + "sha256": "5aa7054e3c2238dc093c46547604beece9b91e186364b1fe2bd5029a9676643b", "sha1_git": "85badc11d64f03de8b2fd9262b1fb2cf0470cbcb", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:85badc11d64f03de8b2fd9262b1fb2cf0470cbcb/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "fd71d3e3c8e79e1030039dc17f587cda6018df3a", "name": "guide-error-handling.md", "perms": 33188, "status": "visible", "length": 126, "checksums": { - "sha256": "17b521b83aef7183c2c22841ebf2ac1e2f5a42712de7467e859dc4c7b752fbb1", "sha1": "0212ebfaed13a7847a49588c6197d02f7198efcc", + "sha256": "17b521b83aef7183c2c22841ebf2ac1e2f5a42712de7467e859dc4c7b752fbb1", "sha1_git": "fd71d3e3c8e79e1030039dc17f587cda6018df3a", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:fd71d3e3c8e79e1030039dc17f587cda6018df3a/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "1130a10bd1c5540c1f3f5be2a0da56fda1acb444", "name": "guide-ffi.md", "perms": 33188, "status": "visible", "length": 132, "checksums": { - "sha256": "aade247c7f69aba4248450f5f1a8be77ae87c94fc73e597d1edfe134df911214", "sha1": "88e47f1c32dd2df8d338ccfa378e056ac5979dfd", + "sha256": "aade247c7f69aba4248450f5f1a8be77ae87c94fc73e597d1edfe134df911214", "sha1_git": "1130a10bd1c5540c1f3f5be2a0da56fda1acb444", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:1130a10bd1c5540c1f3f5be2a0da56fda1acb444/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "228cb3c624f8958abc5526886745f03dbf912fac", "name": "guide-macros.md", "perms": 33188, "status": "visible", "length": 115, "checksums": { - "sha256": "194a44f13a9806027e6f39fdd3cf2d32cea9591ebf8eed88eac76bfd70a76c17", "sha1": "9ba9912b177cb33b2a42651780fdb597e1ded091", + "sha256": "194a44f13a9806027e6f39fdd3cf2d32cea9591ebf8eed88eac76bfd70a76c17", "sha1_git": "228cb3c624f8958abc5526886745f03dbf912fac", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:228cb3c624f8958abc5526886745f03dbf912fac/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "767dafc5baf9208e3927680947fe3da83c493201", "name": "guide-ownership.md", "perms": 33188, "status": "visible", "length": 143, "checksums": { - "sha256": "df1ea1cff3fe6082222840754dbb440980cd9cf04d85e5287d9f23d5db5ea863", "sha1": "3c1ec7447489a516cd4d9e1389073e1862d5ff22", + "sha256": "df1ea1cff3fe6082222840754dbb440980cd9cf04d85e5287d9f23d5db5ea863", "sha1_git": "767dafc5baf9208e3927680947fe3da83c493201", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:767dafc5baf9208e3927680947fe3da83c493201/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "6c511548789b73656a9303f5dbf153274d62f4de", "name": "guide-plugins.md", "perms": 33188, "status": "visible", "length": 135, "checksums": { - "sha256": "b8b132edf8d80694638dbb85f84ca8a098103618fc92ca1a2c2f06f45cdbd955", "sha1": "7a5ecf2d63691280e4fb433ed6dc2c9335d3b917", + "sha256": "b8b132edf8d80694638dbb85f84ca8a098103618fc92ca1a2c2f06f45cdbd955", "sha1_git": "6c511548789b73656a9303f5dbf153274d62f4de", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:6c511548789b73656a9303f5dbf153274d62f4de/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "bafdb2fe0bbc3790867d1e8a117226f033c298ae", "name": "guide-pointers.md", "perms": 33188, "status": "visible", "length": 293, "checksums": { - "sha256": "9eb9ba201ac0a4c1347db17e89cdbdfdf7e682cf9ecb26dc5aa7d86454facfd2", "sha1": "6abd35d1edcb1569ce8fa82c87b71ecef5ba49e2", + "sha256": "9eb9ba201ac0a4c1347db17e89cdbdfdf7e682cf9ecb26dc5aa7d86454facfd2", "sha1_git": "bafdb2fe0bbc3790867d1e8a117226f033c298ae", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:bafdb2fe0bbc3790867d1e8a117226f033c298ae/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "d030614489bccfd3ad87f3619003fe43ed65a7f6", "name": "guide-strings.md", "perms": 33188, "status": "visible", "length": 120, "checksums": { - "sha256": "4a9a99fe8de30d497d3995b2767ded900449a56153da144bc5d78eec30262d79", "sha1": "d70b2391cc30674d1b0d32f69a02f87ce59d961f", + "sha256": "4a9a99fe8de30d497d3995b2767ded900449a56153da144bc5d78eec30262d79", "sha1_git": "d030614489bccfd3ad87f3619003fe43ed65a7f6", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:d030614489bccfd3ad87f3619003fe43ed65a7f6/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "21217bf54d7693e50a4777601536f2fca935edc0", "name": "guide-tasks.md", "perms": 33188, "status": "visible", "length": 139, "checksums": { - "sha256": "cacfb85fcf58614e40a91b112ae7e6d7a3132a4d5d43c24d2163a03a183b7eb1", "sha1": "0c649ef00ae61445fde8f6509b82b1b3998e14cf", + "sha256": "cacfb85fcf58614e40a91b112ae7e6d7a3132a4d5d43c24d2163a03a183b7eb1", "sha1_git": "21217bf54d7693e50a4777601536f2fca935edc0", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:21217bf54d7693e50a4777601536f2fca935edc0/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "28d9fb48b73e74485f6a8b930428a7fbb6db81ef", "name": "guide-testing.md", "perms": 33188, "status": "visible", "length": 125, "checksums": { - "sha256": "61af0dc860ae011b5a4e8eba990d3bc581ace146736247b8e450f4c663f664cd", "sha1": "d85d294a9ed8d11d31e2e25b6272e8e6e3058e00", + "sha256": "61af0dc860ae011b5a4e8eba990d3bc581ace146736247b8e450f4c663f664cd", "sha1_git": "28d9fb48b73e74485f6a8b930428a7fbb6db81ef", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:28d9fb48b73e74485f6a8b930428a7fbb6db81ef/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "3c1a82d0174494f9c276eca6c20801fc2fdb1a6b", "name": "guide-unsafe.md", "perms": 33188, "status": "visible", "length": 134, "checksums": { - "sha256": "4a5e663d343fcbd8fb69bbbd4a1b0566862cea079c7517cafdb261b775e6195a", "sha1": "5122f12a66652ebc5d2357a25695590eb6a60b4f", + "sha256": "4a5e663d343fcbd8fb69bbbd4a1b0566862cea079c7517cafdb261b775e6195a", "sha1_git": "3c1a82d0174494f9c276eca6c20801fc2fdb1a6b", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:3c1a82d0174494f9c276eca6c20801fc2fdb1a6b/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "b9e70e7cfd7e06f27160657e9bb509011b5bf89a", "name": "guide.md", "perms": 33188, "status": "visible", "length": 108, "checksums": { - "sha256": "b25c83b21ca63b3c896ba37452d5b1f1b6d159b4458ec53f72972d56ab19de3f", "sha1": "cb4ff4bf5f73aabeb72b51fae345a355d047381c", + "sha256": "b25c83b21ca63b3c896ba37452d5b1f1b6d159b4458ec53f72972d56ab19de3f", "sha1_git": "b9e70e7cfd7e06f27160657e9bb509011b5bf89a", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:b9e70e7cfd7e06f27160657e9bb509011b5bf89a/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "0a2a80e8fd6e2b4d62dcf9a93f2dc5983b0da249", "name": "index.md", "perms": 33188, "status": "visible", "length": 4366, "checksums": { - "sha256": "353459533ed0b76facfb75b57f690d754360b9cd8d7bce8195191d3e5d40ac1b", "sha1": "cede6ea0203a41976e3a729426e94c769d8af9ea", + "sha256": "353459533ed0b76facfb75b57f690d754360b9cd8d7bce8195191d3e5d40ac1b", "sha1_git": "0a2a80e8fd6e2b4d62dcf9a93f2dc5983b0da249", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:0a2a80e8fd6e2b4d62dcf9a93f2dc5983b0da249/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "48712d8d49b55f3f70c5134247dfc54ce46744e2", "name": "intro.md", "perms": 33188, "status": "visible", "length": 150, "checksums": { - "sha256": "0d48afe88e4ff6e88b7b09b35f02f368ca29a80a8a823be9051746e8dc279059", "sha1": "e51eb22fb0afc96a6cda88204bd37ef363518d4c", + "sha256": "0d48afe88e4ff6e88b7b09b35f02f368ca29a80a8a823be9051746e8dc279059", "sha1_git": "48712d8d49b55f3f70c5134247dfc54ce46744e2", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:48712d8d49b55f3f70c5134247dfc54ce46744e2/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "dir", "target": "75ac1666fbdce03ae0dd511cdcc75dc7e097863a", "name": "man", "perms": 16384, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/directory/75ac1666fbdce03ae0dd511cdcc75dc7e097863a/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "rev", "target": "3e6e1001dc6e095dbd5c88005e80969f60e384e1", "name": "nomicon", "perms": 57344, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/revision/3e6e1001dc6e095dbd5c88005e80969f60e384e1/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "d26fcfc0168d7676138a74ac2ef336b115268b30", "name": "not_found.md", "perms": 33188, "status": "visible", "length": 2639, "checksums": { - "sha256": "3e12811e42249800a98e69e31d1b9ed7941749981f77f618f97c707a5c24b7e8", "sha1": "9656218548d47dda0b43ad16f617914a283ae804", + "sha256": "3e12811e42249800a98e69e31d1b9ed7941749981f77f618f97c707a5c24b7e8", "sha1_git": "d26fcfc0168d7676138a74ac2ef336b115268b30", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:d26fcfc0168d7676138a74ac2ef336b115268b30/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "33e3860c2a4340ba428e789a980bafeeb7982b02", "name": "redirect.inc", "perms": 33188, "status": "visible", "length": 118, "checksums": { - "sha256": "254be837de875bb8bf0e650ad1c94090eebf52c754e174e9c3fade6867263a88", "sha1": "05a24674274e70062bdc0088a31f3126a0f898fc", + "sha256": "254be837de875bb8bf0e650ad1c94090eebf52c754e174e9c3fade6867263a88", "sha1_git": "33e3860c2a4340ba428e789a980bafeeb7982b02", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:33e3860c2a4340ba428e789a980bafeeb7982b02/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "rev", "target": "11e893fc1357bc688418ddf1087c2b7aa25d154d", "name": "reference", "perms": 57344, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/revision/11e893fc1357bc688418ddf1087c2b7aa25d154d/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "fdeea17ed1124bde8b8453bd4c6b0a6035079074", "name": "reference.md", "perms": 33188, "status": "visible", "length": 137, "checksums": { - "sha256": "f7e15476ea21caf0a81e74a7e2389e88eaa8e177268924b04b7619dc9ef92f0f", "sha1": "4b7736799c39dc3636fe0c8d4494bfef774f8e79", + "sha256": "f7e15476ea21caf0a81e74a7e2389e88eaa8e177268924b04b7619dc9ef92f0f", "sha1_git": "fdeea17ed1124bde8b8453bd4c6b0a6035079074", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:fdeea17ed1124bde8b8453bd4c6b0a6035079074/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "61ee12739fb37426603b65e857060f03aefb3434", "name": "robots.txt", "perms": 33188, "status": "visible", "length": 762, "checksums": { - "sha256": "1d5fc8b3d3dc393ba1e67b4b0b267ec4b14357fb6c3990ace2e0f03c4aa7c719", "sha1": "0590cfcec734441c5d9f1ea46f445c80becd27c6", + "sha256": "1d5fc8b3d3dc393ba1e67b4b0b267ec4b14357fb6c3990ace2e0f03c4aa7c719", "sha1_git": "61ee12739fb37426603b65e857060f03aefb3434", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:61ee12739fb37426603b65e857060f03aefb3434/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "rev", "target": "1c2bd024d13f8011307e13386cf1fea2180352b5", "name": "rust-by-example", "perms": 57344, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/revision/1c2bd024d13f8011307e13386cf1fea2180352b5/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "a92d4ff54db837a6e64dd260d66b3bc5e2e60f43", "name": "rust.css", "perms": 33188, "status": "visible", "length": 7604, "checksums": { - "sha256": "029da15998da9bf0bd9c6ea190cdfbf3a3563ae18bfe50674efd99eca62a3a85", "sha1": "6e86ec1077ef0c268a373b42ecf173de45e4891f", + "sha256": "029da15998da9bf0bd9c6ea190cdfbf3a3563ae18bfe50674efd99eca62a3a85", "sha1_git": "a92d4ff54db837a6e64dd260d66b3bc5e2e60f43", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:a92d4ff54db837a6e64dd260d66b3bc5e2e60f43/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "5008b228c5c85fe04df289f4180a83fd4f5ce7b9", "name": "rust.md", "perms": 33188, "status": "visible", "length": 108, "checksums": { - "sha256": "c474313aabfbc668f205eaf146c31bc47470fb322d1fd96008ac8ddbb585273f", "sha1": "014d60b4041b6135c2e2c6a2c47d99334de473f6", + "sha256": "c474313aabfbc668f205eaf146c31bc47470fb322d1fd96008ac8ddbb585273f", "sha1_git": "5008b228c5c85fe04df289f4180a83fd4f5ce7b9", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:5008b228c5c85fe04df289f4180a83fd4f5ce7b9/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "dir", "target": "afcf954dde68fd80b42f374902722daef93ef300", "name": "rustc", "perms": 16384, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/directory/afcf954dde68fd80b42f374902722daef93ef300/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "rev", "target": "92baf7293dd2d418d2ac4b141b0faa822075d9f7", "name": "rustc-guide", "perms": 57344, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/revision/92baf7293dd2d418d2ac4b141b0faa822075d9f7/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "dfd8e9db3c5c9cacb236c7d1cacc84791649920f", "name": "rustc-ux-guidelines.md", "perms": 33188, "status": "visible", "length": 5192, "checksums": { - "sha256": "0898345d1f94b6b15f324a1c9afa4df85fc45fb05960879ac61e9a9ac0b47835", "sha1": "e584054db3a809e089d84b1680d78cadb7eacb1f", + "sha256": "0898345d1f94b6b15f324a1c9afa4df85fc45fb05960879ac61e9a9ac0b47835", "sha1_git": "dfd8e9db3c5c9cacb236c7d1cacc84791649920f", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:dfd8e9db3c5c9cacb236c7d1cacc84791649920f/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "dir", "target": "0886ac8aa41122ec1068a1cb49d8e2fbb69bfbc8", "name": "rustdoc", "perms": 16384, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/directory/0886ac8aa41122ec1068a1cb49d8e2fbb69bfbc8/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "d4a25efec17f6895baeed32178ca69baad319159", "name": "rustdoc.md", "perms": 33188, "status": "visible", "length": 84, "checksums": { - "sha256": "5d5ebec01e6606b7f0f8ff2fac793a8b870847b715d069c1ff88b81ddb1ecdc1", "sha1": "85f5f5a92b0904bcfcf0eb4b54dae893b2b17d52", + "sha256": "5d5ebec01e6606b7f0f8ff2fac793a8b870847b715d069c1ff88b81ddb1ecdc1", "sha1_git": "d4a25efec17f6895baeed32178ca69baad319159", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:d4a25efec17f6895baeed32178ca69baad319159/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "320283f31b51feb3e3fd24a632195fcb42a5181a", "name": "tutorial.md", "perms": 33188, "status": "visible", "length": 177, "checksums": { - "sha256": "6d2a400c36e46a97acadeecac8103ef603cd1bf5533df453cf519d4a4a769193", "sha1": "98bbf3d1c8e78e1ab161de9a5385499668d516f1", + "sha256": "6d2a400c36e46a97acadeecac8103ef603cd1bf5533df453cf519d4a4a769193", "sha1_git": "320283f31b51feb3e3fd24a632195fcb42a5181a", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:320283f31b51feb3e3fd24a632195fcb42a5181a/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "dir", "target": "4a1887e5658c16ce237ef0253703ddc5841afcfb", "name": "unstable-book", "perms": 16384, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/directory/4a1887e5658c16ce237ef0253703ddc5841afcfb/", }, { "dir_id": "80ae84abc6122c47aae597fde99645f8663d1aba", "type": "file", "target": "7215e4f13c9bb4d914032eda4192430e69c51a41", "name": "version_info.html.template", "perms": 33188, "status": "visible", "length": 342, "checksums": { - "sha256": "5c43d83bf45d7a0e8e10f2f66730d8e8a737c77668e1f7a522444d46e613efcf", "sha1": "4b4cfe57c5e1aab39eac235699a14dded2c565b0", + "sha256": "5c43d83bf45d7a0e8e10f2f66730d8e8a737c77668e1f7a522444d46e613efcf", "sha1_git": "7215e4f13c9bb4d914032eda4192430e69c51a41", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:7215e4f13c9bb4d914032eda4192430e69c51a41/", }, ], "revision/b8cedc00407a4c56a3bda1ed605c6fc166655447/": { "message": "Auto merge of #69854 - pietroalbini:stable-next, r=Centril\n\n[stable] Release 1.42.0\n\nThis PR prepares the release artifacts of Rust 1.42.0, and cherry-picks the following PRs:\n\n* https://github.com/rust-lang/rust/pull/69754: Update deprecation version to 1.42 for Error::description\n* https://github.com/rust-lang/rust/pull/69753: Do not ICE when matching an uninhabited enum's field\n* https://github.com/rust-lang/rust/pull/69522 / https://github.com/rust-lang/rust/pull/69853: error_derive_forbidden_on_non_adt: be more graceful\n* https://github.com/rust-lang/rust/pull/68598: Fix null synthetic_implementors error\n\nIn addition, the release notes are updated to include the remaining compatibility notes.\n\nr? @Centril\n", "author": { "fullname": "bors ", "name": "bors", "email": "bors@rust-lang.org", }, "committer": { "fullname": "bors ", "name": "bors", "email": "bors@rust-lang.org", }, "date": "2020-03-09T22:09:51+00:00", "committer_date": "2020-03-09T22:09:51+00:00", "type": "git", "directory": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", "synthetic": False, "metadata": {}, "parents": [ { "id": "b08d07143d2b61777d341f8658281adc0f2ac809", "url": "https://archive.softwareheritage.org/api/1/revision/b08d07143d2b61777d341f8658281adc0f2ac809/", }, { "id": "133f659766c60ff7a33288ae6f33b0c272792f57", "url": "https://archive.softwareheritage.org/api/1/revision/133f659766c60ff7a33288ae6f33b0c272792f57/", }, ], "id": "b8cedc00407a4c56a3bda1ed605c6fc166655447", "extra_headers": [], "merge": True, "url": "https://archive.softwareheritage.org/api/1/revision/b8cedc00407a4c56a3bda1ed605c6fc166655447/", "history_url": "https://archive.softwareheritage.org/api/1/revision/b8cedc00407a4c56a3bda1ed605c6fc166655447/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578/", }, "revision/87dd6843678575f8dda962f239d14ef4be14b352/": { "message": "Fix listing numbers (#2227)\n\nFix listing numbers", "author": { "fullname": "Carol (Nichols || Goulding) <193874+carols10cents@users.noreply.github.com>", "name": "Carol (Nichols || Goulding)", "email": "193874+carols10cents@users.noreply.github.com", }, "committer": { "fullname": "GitHub ", "name": "GitHub", "email": "noreply@github.com", }, "date": "2020-01-20T15:20:40-05:00", "committer_date": "2020-01-20T15:20:40-05:00", "type": "git", "directory": "4c1b991bc9997e885308de8a87b05bbd9956a4fb", "synthetic": False, "metadata": {}, "parents": [ { "id": "6f38be000b6307428c3cee7cd71fb898ccb43d78", "url": "https://archive.softwareheritage.org/api/1/revision/6f38be000b6307428c3cee7cd71fb898ccb43d78/", }, { "id": "6fe276d7126cd95d82603e1b50805ca30e587972", "url": "https://archive.softwareheritage.org/api/1/revision/6fe276d7126cd95d82603e1b50805ca30e587972/", }, ], "id": "87dd6843678575f8dda962f239d14ef4be14b352", "extra_headers": [ [ "gpgsig", "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJeJguYCRBK7hj4Ov3rIwAAdHIIAA1yxHVdkDlW8PCPJMrK89Ge\niLDgnWwPusKJ0j1MNG/CpWJE0a6ZXEsK2uEBWPZ1RGLK1tsMl4SqaaHwjX32lsUf\nJzDFUKVauE5pZxUQlB5SjIbGdKFwuEP7ROK+JQmeUYXz9Qn3Z73C7SBVNSgGcFbK\nz5EX2anhbEtYGsw4jBpANV3t5qMaVnjUOTsoBmpgUVIniW8r4Jn58mrDWS2ccAu8\nlkjbgSqx6xT9mNZ0VCIX3NGcvUByOl+wonedn1ta1gKFFQT4wQWwukPP3GzoBIxD\nn544eucLpvi0Bnleifw0ZbQ/4QqrEomD7RkPC9YxoUi+sKxCoewQUGQI9ML+v1s=\n=xEhG\n-----END PGP SIGNATURE-----\n", ] ], "merge": True, "url": "https://archive.softwareheritage.org/api/1/revision/87dd6843678575f8dda962f239d14ef4be14b352/", "history_url": "https://archive.softwareheritage.org/api/1/revision/87dd6843678575f8dda962f239d14ef4be14b352/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/4c1b991bc9997e885308de8a87b05bbd9956a4fb/", }, "revision/1a2390247ad6d08160e0dd74f40a01a9578659c2/": { "message": "Merge pull request #194 from zakaluka/rustup2148\n\nUpdated install and uninstall instructions", "author": { "fullname": "Eric Huss ", "name": "Eric Huss", "email": "eric@huss.org", }, "committer": { "fullname": "GitHub ", "name": "GitHub", "email": "noreply@github.com", }, "date": "2019-12-29T10:40:55-08:00", "committer_date": "2019-12-29T10:40:55-08:00", "type": "git", "directory": "5f461af28f89e3de5fa7aadbe8e601e61e0318de", "synthetic": False, "metadata": {}, "parents": [ { "id": "6601cab4666596494a569f94aa63b7b3230e9769", "url": "https://archive.softwareheritage.org/api/1/revision/6601cab4666596494a569f94aa63b7b3230e9769/", }, { "id": "7570e6effbb37e2bf855febeb2182b33d14298e1", "url": "https://archive.softwareheritage.org/api/1/revision/7570e6effbb37e2bf855febeb2182b33d14298e1/", }, ], "id": "1a2390247ad6d08160e0dd74f40a01a9578659c2", "extra_headers": [ [ "gpgsig", "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJeCPM3CRBK7hj4Ov3rIwAAdHIIACrp857RJofE1m6rROTafhnQ\n6gs/+we/s6JV3mbvYv4VJFWhulhqyA/CMAsAN/Bk3BLS7APCJsD+G0KzzrcXnqr8\n8MjWHlgYygZaPoxLYFzpm945Dtm54uYD2cp7EIHtoHrtcukbCAdgycAkobm2upmy\nQwZnq63+zTVysoZiD3xuCpgh/EFcvVL+dw0FxpuLMBn71NMp1TNxzdW6bSRO4FMX\niD7K096pl9lcQ3D85Y+wkXAa/+1S0n2xm+WZRqbBl7BnfuPfLCvXc870w2OI0GLu\nlEwklX/zaRBkpKoNRIesMHVaB5lqwMSon/vnM6GlGDYfx5+6g4CpCQ3WQe0wQB4=\n=sRoH\n-----END PGP SIGNATURE-----\n", ] ], "merge": True, "url": "https://archive.softwareheritage.org/api/1/revision/1a2390247ad6d08160e0dd74f40a01a9578659c2/", "history_url": "https://archive.softwareheritage.org/api/1/revision/1a2390247ad6d08160e0dd74f40a01a9578659c2/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/5f461af28f89e3de5fa7aadbe8e601e61e0318de/", }, "revision/4d78994915af1bde9a95c04a8c27d8dca066232a/": { "message": "Merge #221\n\n221: Update .gitattributes r=therealprof a=jethrogb\n\nSee https://github.com/rust-lang/rust/pull/57858\n\nCo-authored-by: jethrogb \n", "author": { "fullname": "bors[bot] <26634292+bors[bot]@users.noreply.github.com>", "name": "bors[bot]", "email": "26634292+bors[bot]@users.noreply.github.com", }, "committer": { "fullname": "GitHub ", "name": "GitHub", "email": "noreply@github.com", }, "date": "2020-01-14T08:25:25+00:00", "committer_date": "2020-01-14T08:25:25+00:00", "type": "git", "directory": "aedfd5f87a2bb6f48d748a3d6e11ce755a5fc531", "synthetic": False, "metadata": {}, "parents": [ { "id": "9493b7d4dc97eda439bd8780f05ad7b234cd1cd7", "url": "https://archive.softwareheritage.org/api/1/revision/9493b7d4dc97eda439bd8780f05ad7b234cd1cd7/", }, { "id": "dde3bbc0315e8640668b72c6d8ecb367c53c00e3", "url": "https://archive.softwareheritage.org/api/1/revision/dde3bbc0315e8640668b72c6d8ecb367c53c00e3/", }, ], "id": "4d78994915af1bde9a95c04a8c27d8dca066232a", "extra_headers": [ [ "gpgsig", "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJeHXr1CRBK7hj4Ov3rIwAAdHIIAG5OD0KTHIIaabwpLeRwEjsw\nlTFeVXSZmQ6oQBIlq+SsvjsRAyKo/0F6fosiEpPGdemlBcm2r2XgkqAewTYWaRCC\naLbZY4hDj96wvo7bKCxubzJ4nrerxQivuoZww831feQHaHHph8xfT9WfH60OAAci\nFOxCFHbY/t37/utqSGr6ondnsXWv1AZxgMYjZ5I3jFdnciLNGCHCIpmmvARjbo4L\ny/lqTUxbq0c6yBrxONOqoJOReggAA2DLqpS+vS3Qdly693aJiBwY6O44Bbhqr89w\nCnaMruJXYgFTORrghkyVczxZMlqQEOkMH8QRZUTYKBCmt2luGV6cxHk0Lvq4+ao=\n=CpRt\n-----END PGP SIGNATURE-----\n", ] ], "merge": True, "url": "https://archive.softwareheritage.org/api/1/revision/4d78994915af1bde9a95c04a8c27d8dca066232a/", "history_url": "https://archive.softwareheritage.org/api/1/revision/4d78994915af1bde9a95c04a8c27d8dca066232a/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/aedfd5f87a2bb6f48d748a3d6e11ce755a5fc531/", }, "revision/3e6e1001dc6e095dbd5c88005e80969f60e384e1/": { "message": "Merge pull request #177 from petertodd/2019-12-long-live-contravariance\n\nRemove mention of contravariance possibly getting scrapped", "author": { "fullname": "matthewjasper ", "name": "matthewjasper", "email": "mjjasper1@gmail.com", }, "committer": { "fullname": "GitHub ", "name": "GitHub", "email": "noreply@github.com", }, "date": "2019-12-14T22:08:52+00:00", "committer_date": "2019-12-14T22:08:52+00:00", "type": "git", "directory": "2c4e09410c52ffd98d771d370948037d192f6178", "synthetic": False, "metadata": {}, "parents": [ { "id": "8be35b201f9cf0a4c3fcc96c83ac21671dcf3112", "url": "https://archive.softwareheritage.org/api/1/revision/8be35b201f9cf0a4c3fcc96c83ac21671dcf3112/", }, { "id": "ce08025be28869805e0a0b7c643a9655f548c1b5", "url": "https://archive.softwareheritage.org/api/1/revision/ce08025be28869805e0a0b7c643a9655f548c1b5/", }, ], "id": "3e6e1001dc6e095dbd5c88005e80969f60e384e1", "extra_headers": [ [ "gpgsig", "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJd9V10CRBK7hj4Ov3rIwAAdHIIAA3WxB9bSxzO2PWCPMYDdlKv\nhVXjUWcz/ItFmIgFav0SjC3nTTdznfEvKwaAu7H29IJzNex3m9jZ+TExslXP/6/a\nVLTnXvgrqrQ45WO96W1KYoDnFwZW1/NI4VDZIhecrvWgiV73kUHUa5JCBRzxl/Ft\nT2FPJ5n+EeBiIddqviy9hJ75vfO6tnDbAn2zJV/tFoadIxSoua5Ax6+YRc8pPRhs\nw14KPvywCbZMyJU/IIE01fIjkMN6CH8R2PoJ+sCGZOotN25o3cJ+waDEcgK4k2A8\n92GvsdE04WZyNhSziZQ5Ey7QSlL0ttEwnd25fM6C7RQgRzfV3Tx1wrcBRn3W7fs=\n=cOHT\n-----END PGP SIGNATURE-----\n", ] ], "merge": True, "url": "https://archive.softwareheritage.org/api/1/revision/3e6e1001dc6e095dbd5c88005e80969f60e384e1/", "history_url": "https://archive.softwareheritage.org/api/1/revision/3e6e1001dc6e095dbd5c88005e80969f60e384e1/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/2c4e09410c52ffd98d771d370948037d192f6178/", }, "revision/11e893fc1357bc688418ddf1087c2b7aa25d154d/": { "message": "Merge pull request #726 from phansch/small_pointer_improvement\n\nSmall improvements to types/pointer.md", "author": { "fullname": "Mazdak Farrokhzad ", "name": "Mazdak Farrokhzad", "email": "twingoow@gmail.com", }, "committer": { "fullname": "GitHub ", "name": "GitHub", "email": "noreply@github.com", }, "date": "2020-01-18T21:24:08+01:00", "committer_date": "2020-01-18T21:24:08+01:00", "type": "git", "directory": "2892c88f2d5fc8c16adef8dd7e7c649bc194b672", "synthetic": False, "metadata": {}, "parents": [ { "id": "a94b0efafc6198cbe62f9116b6c75c48d32dd80e", "url": "https://archive.softwareheritage.org/api/1/revision/a94b0efafc6198cbe62f9116b6c75c48d32dd80e/", }, { "id": "4f647729f75a753d708876ab250a981c3c2a5185", "url": "https://archive.softwareheritage.org/api/1/revision/4f647729f75a753d708876ab250a981c3c2a5185/", }, ], "id": "11e893fc1357bc688418ddf1087c2b7aa25d154d", "extra_headers": [ [ "gpgsig", "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJeI2loCRBK7hj4Ov3rIwAAdHIIALClmx/QX58u6E2A4obZgGQt\nxWuvHaqjN9iucIxJStL7Sw476VrUuYiXrLPyrNp3HG3TIKZEx2viSnCMj/Kw/ZQI\nAltpk6wQMYUiG517mhOqD3LCea1LvBJPgC/+/5KDsLzKg5suTmgRWcQmPf3JQXLy\nUKuKUbaGwg3V/4U2bPlIySvYz93Rz3p21oOVwQwsZWIAbInYeIh6JuYqk2uVW4pS\nPOv1mXU1SYfYabnjjtjT/eDL5iAroW+qHEccnEuNGB6JuLOvolaVWUft9JO2L2lT\nmpuoMX2/iG3b46oxLHjRN3kG84OaFnmqtBoUQqaPJUZsuSuVhuqAspkxQfQTJ6Q=\n=1VUt\n-----END PGP SIGNATURE-----\n", ] ], "merge": True, "url": "https://archive.softwareheritage.org/api/1/revision/11e893fc1357bc688418ddf1087c2b7aa25d154d/", "history_url": "https://archive.softwareheritage.org/api/1/revision/11e893fc1357bc688418ddf1087c2b7aa25d154d/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/2892c88f2d5fc8c16adef8dd7e7c649bc194b672/", }, "revision/1c2bd024d13f8011307e13386cf1fea2180352b5/": { "message": "Merge pull request #1302 from Cawibo/patch-1\n\nCamelCase -> UpperCamelCase", "author": { "fullname": "Mario Idival ", "name": "Mario Idival", "email": "marioidival@gmail.com", }, "committer": { "fullname": "GitHub ", "name": "GitHub", "email": "noreply@github.com", }, "date": "2020-01-20T12:18:36-03:00", "committer_date": "2020-01-20T12:18:36-03:00", "type": "git", "directory": "07720bc1cae5641b300fadc2aa076b9a5de71d2b", "synthetic": False, "metadata": {}, "parents": [ { "id": "1d59403cb5269c190cc52a95584ecc280345495a", "url": "https://archive.softwareheritage.org/api/1/revision/1d59403cb5269c190cc52a95584ecc280345495a/", }, { "id": "2d39e2894830331fb02b77980a6190e972ad3d68", "url": "https://archive.softwareheritage.org/api/1/revision/2d39e2894830331fb02b77980a6190e972ad3d68/", }, ], "id": "1c2bd024d13f8011307e13386cf1fea2180352b5", "extra_headers": [ [ "gpgsig", "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJeJcTMCRBK7hj4Ov3rIwAAdHIIAI8ZEhlu05Dxk0mFl22ch9BW\nKhRcv/YvB3UgLc7nHa5jE/foPnWkUYrFGY+g2M/eB7Tin/TZKVnlEpVbq4hs7cU0\nscfnineThRmClYnSiZIOBFS4jV2smvPJb5U33mOYNa9GJ3q9Tb8jTeHHjaUK0Msa\nXOgB1GYfnJ84mQSZbIx8xNAQz33+Nx+B1ZTM9OH70poHiI1W3cSOlKf3MtAbkXyg\nUjbTFUwvxEPeu0M+0tfH6DyPN6zqmg52YJtNPq/IXWpDTSOhs1d4BMFGXtj5WOlg\n6I5w3KbIVLmTTD95nL4sy/9sRsX0Qxhi36DbCaD2GChGV4AtA6N1BmMhQCU5YUE=\n=mndN\n-----END PGP SIGNATURE-----\n", ] ], "merge": True, "url": "https://archive.softwareheritage.org/api/1/revision/1c2bd024d13f8011307e13386cf1fea2180352b5/", "history_url": "https://archive.softwareheritage.org/api/1/revision/1c2bd024d13f8011307e13386cf1fea2180352b5/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/07720bc1cae5641b300fadc2aa076b9a5de71d2b/", }, "revision/92baf7293dd2d418d2ac4b141b0faa822075d9f7/": { "message": "Fix link\n", "author": { "fullname": "Yuki Okushi ", "name": "Yuki Okushi", "email": "huyuumi.dev@gmail.com", }, "committer": { "fullname": "Who? Me?! ", "name": "Who? Me?!", "email": "mark-i-m@users.noreply.github.com", }, "date": "2020-01-14T13:47:41+09:00", "committer_date": "2020-01-13T22:58:05-06:00", "type": "git", "directory": "d5b7e02dd66666e7f16066162d3cc9bbc3a2c3d3", "synthetic": False, "metadata": {}, "parents": [ { "id": "cf6447aff01e4bcb1fdbc89d6f754451a157589e", "url": "https://archive.softwareheritage.org/api/1/revision/cf6447aff01e4bcb1fdbc89d6f754451a157589e/", } ], "id": "92baf7293dd2d418d2ac4b141b0faa822075d9f7", "extra_headers": [], "merge": False, "url": "https://archive.softwareheritage.org/api/1/revision/92baf7293dd2d418d2ac4b141b0faa822075d9f7/", "history_url": "https://archive.softwareheritage.org/api/1/revision/92baf7293dd2d418d2ac4b141b0faa822075d9f7/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/d5b7e02dd66666e7f16066162d3cc9bbc3a2c3d3/", }, "release/874f7cbe352033cac5a8bc889847da2fe1d13e9f/": { "name": "1.42.0", "message": "1.42.0 release\n", "target": "b8cedc00407a4c56a3bda1ed605c6fc166655447", "target_type": "revision", "synthetic": False, "author": { "fullname": "Pietro Albini ", "name": "Pietro Albini", "email": "pietro@pietroalbini.org", }, "date": "2020-03-12T15:05:24+01:00", "id": "874f7cbe352033cac5a8bc889847da2fe1d13e9f", "target_url": "https://archive.softwareheritage.org/api/1/revision/b8cedc00407a4c56a3bda1ed605c6fc166655447/", }, "release/da5f9898d6248ab26277116f54aca855338401d2/": { "name": "maintainer-gpg-key", "message": "GPG public key of SimCav\n-----BEGIN PGP SIGNATURE-----\n\niQIcBAABAgAGBQJaPZzzAAoJEBXKttsl/fq6/y4P+wVtI9WpXeR5E1OSdJtXiomY\nh1Htc+d0mRS5PDT6h9R80VdfAl9Bvts+xiHqy1kqptAfxRqFZJIorbwq6MGFn42i\nSEVA/Y6yWvgxNUhMdJAywlzJ6ql4D2Awa3AqM+nwtHtvDJ0FQe9tE+mYjah1fL51\nf41HJi9iaFbfEmMMwENPsbbOZtQDRsMimPCQnlQU0O+DTrhvQA/1dpVdhWg0azC6\nc3NPoEbq8dzYPbYUPJeotb9wIxPxeX+XFCwtc9aIoNP+LLtXwztYQTt5AqBhSf4T\nFYZmYkT9X+0uBru4AyJbeiHBt1ssh9ri3e6kfxcjE49btCQz5HoLPUnRxUWr0FFW\nyxEdyljt4Tzl7DcImkI4crQmMzym5c4h1KkK+O9dv205kCwya8aLQyRLEzMcGFBp\n7SsbHdVMf3K6nXBIDnf/AxErO76/PbjvYtCRVlfMRlMKXLciJu0N4/GTEYrK7qxc\nU3UFvdmxq33VE0YjcorzwSSkb5GTqwc6qwjsnnYl3tO35Ev/1+c+uryEwlk+P00n\neVnyq8zzgEANcxAyTxchFbd73sJ2JsWrBLsDBOQRk5Qo5tXTw+pDJzl3dmOytv4S\nAcFlryzMt4ShKdLUUUN6tvmuziCGkfiwWWj1LG+G+DEr5bGQH49Q1l9IMxgAvgPP\ncJPmfbi315UGs8k48xfT\n=JiPC\n-----END PGP SIGNATURE-----\n", "target": "be5effea679c057aec2bb020f0241b1d1d660840", "target_type": "content", "synthetic": False, "author": { "fullname": "SimCav ", "name": "SimCav", "email": "simcav@protonmail.com", }, "date": "2017-12-23T01:01:25+01:00", "id": "da5f9898d6248ab26277116f54aca855338401d2", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/", }, "content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/": { "length": 5279, "status": "visible", "checksums": { - "sha256": "d3923bc07a944321af5eb781c1ae7b86b1f8c07385dce3adad1eee052f2cda47", - "sha1": "c640e23feb6f93b02878de5b02d70e87388a2bd2", "blake2s256": "6f515bb07318b5730f7c2d0aa4dbe24fe1b65ed4f38cf3500a8ffbdbb1ea3cfe", + "sha1": "c640e23feb6f93b02878de5b02d70e87388a2bd2", + "sha256": "d3923bc07a944321af5eb781c1ae7b86b1f8c07385dce3adad1eee052f2cda47", "sha1_git": "be5effea679c057aec2bb020f0241b1d1d660840", }, "data_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/raw/", "filetype_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/filetype/", "language_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/language/", "license_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/license/", }, "content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/raw/": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n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\n=kZK9\n-----END PGP PUBLIC KEY BLOCK-----\n", "release/3a7b2dfffed2945d2933ba4ebc063adba35ddb2e/": { "name": "luvit/lit/v1.2.10", "message": '{"license":"MIT","version":"1.2.10","luvi":{"version":"2.0.9","flavor":"regular"},"homepage":"https://github.com/luvit/lit","author":{"name":"Tim Caswell"},"dependencies":["luvit/require@1.2.0","luvit/pretty-print@1.0.2","luvit/http-codec@1.0.0","luvit/json@2.5.0","creationix/coro-fs@1.2.3","creationix/coro-tcp@1.0.5","creationix/coro-http@1.0.7","creationix/coro-tls@1.2.0","creationix/coro-wrapper@1.0.0","creationix/hex-bin@1.0.0","creationix/semver@1.0.2","creationix/git@1.0.1","creationix/prompt@1.0.3","creationix/ssh-rsa@1.0.0","creationix/websocket-codec@1.0.2"],"tags":["lit","meta"],"name":"luvit/lit","description":"The Luvit Invention Toolkit is a luvi app that handles dependencies and luvi builds."}\n-----BEGIN RSA SIGNATURE-----\nFormat: sha256-ssh-rsa\nFingerprint: 89:08:c2:48:49:f6:f8:dd:df:e3:fc:1c:a0:7f:3a:ec\n\nIwalmldr0L9ZBHcKMhq9bgwFnCIjQoAhXjZd+hPOcrzTNNI50LWJxwr2d4yOEckC\nk0n1HcYDMhJpOEOo91A61Trts6hu27VW3FaQMzqtlqlwlL5X/px92RLb06CsBaOR\n4B+x0FXnL9RB4Cdw0JZFNWcC/jkMoKXS+tiCwYueIAb8VIH1CXcKUkJgmWPldkxX\nOPm1u6ITcJ/eBU8EzCfI+03beIqIbDA1FBhPegsyGLY/uaCOQKx/Ofqfuxxiz/kf\nsRKIQuGZzQYXgOdU9rjg7gkEw0FV8rqAavD1crWahlx+RohmUceISnKvC8cd0fn8\nI6rcW6jxHIOjqA/m7wwHbA==\n-----END RSA SIGNATURE-----\n', "target": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", "target_type": "directory", "synthetic": False, "author": { "fullname": "Tim Caswell ", "name": "Tim Caswell", "email": "tim@creationix.com", }, "date": "2015-05-27T12:03:12-05:00", "id": "3a7b2dfffed2945d2933ba4ebc063adba35ddb2e", "target_url": "https://archive.softwareheritage.org/api/1/directory/b24d39c928b9c3f440f8e2ec06c78f43d28d87d6/", }, "directory/b24d39c928b9c3f440f8e2ec06c78f43d28d87d6/": [ { "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", "type": "dir", "target": "7aac5c4e25bd03690b47db72425ada565c58cac6", "name": "commands", "perms": 16384, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/directory/7aac5c4e25bd03690b47db72425ada565c58cac6/", }, { "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", "type": "file", "target": "53ea710b37aef348b3e09478b18e2bfd180efb43", "name": "init.lua", "perms": 33188, "status": "visible", "length": 27, "checksums": { - "sha256": "28d6e007e8ba8de537247c2e4dce5ea081919da9eabd2a1cd580afd02425275b", "sha1": "e757103bdac5b2be6e8f28b47595862dd3d36b2b", + "sha256": "28d6e007e8ba8de537247c2e4dce5ea081919da9eabd2a1cd580afd02425275b", "sha1_git": "53ea710b37aef348b3e09478b18e2bfd180efb43", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:53ea710b37aef348b3e09478b18e2bfd180efb43/", }, { "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", "type": "dir", "target": "8535df65f0a259879040f7922438de2c4272e48f", "name": "libs", "perms": 16384, "length": None, "target_url": "https://archive.softwareheritage.org/api/1/directory/8535df65f0a259879040f7922438de2c4272e48f/", }, { "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", "type": "file", "target": "5ddf82d3f5330bad8c830ac6b21bab2e912bee6e", "name": "main.lua", "perms": 33188, "status": "visible", "length": 1216, "checksums": { - "sha256": "e6ab5dc18e4ca7612439c28a991d5a5c09ed2006a5efa2e9034ced6ee995cf1e", "sha1": "37a14e4c123ae1d5006665ef867f84bc23ca2fe8", + "sha256": "e6ab5dc18e4ca7612439c28a991d5a5c09ed2006a5efa2e9034ced6ee995cf1e", "sha1_git": "5ddf82d3f5330bad8c830ac6b21bab2e912bee6e", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:5ddf82d3f5330bad8c830ac6b21bab2e912bee6e/", }, { "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", "type": "file", "target": "d8d2804032211fec42ec197827b049d5dea40ea7", "name": "package.lua", "perms": 33188, "status": "visible", "length": 915, "checksums": { - "sha256": "c63c6cbe41d8fc6fcc3401f0d4d993e42a7ae873dd97fda9dc4cfc2132d61c03", "sha1": "bf83eda0827a970c3cccc9d3ba681c497b1108e9", + "sha256": "c63c6cbe41d8fc6fcc3401f0d4d993e42a7ae873dd97fda9dc4cfc2132d61c03", "sha1_git": "d8d2804032211fec42ec197827b049d5dea40ea7", }, "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:d8d2804032211fec42ec197827b049d5dea40ea7/", }, ], "snapshot/02db117fef22434f1658b833a756775ca6effed0/": { "id": "02db117fef22434f1658b833a756775ca6effed0", "branches": { "refs/heads/auto": { "target": "a0eb7a2c6da31b44718188002ac0cec12a3c86ee", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/a0eb7a2c6da31b44718188002ac0cec12a3c86ee/", }, "refs/heads/beta": { "target": "c980aba9a88704717229da3c1ec02685333c0db2", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/c980aba9a88704717229da3c1ec02685333c0db2/", }, "refs/heads/grammer": { "target": "d7b93216cdeb477e1af813f898096af867550338", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/d7b93216cdeb477e1af813f898096af867550338/", }, "refs/heads/master": { "target": "430a9fd4c797c50cea26157141b2408073b2ed91", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/430a9fd4c797c50cea26157141b2408073b2ed91/", }, "refs/heads/snap-stage3": { "target": "a5c12f4e39d32af3c951b66bd2839bc0b5a1125b", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/a5c12f4e39d32af3c951b66bd2839bc0b5a1125b/", }, "refs/heads/stable": { "target": "082e4763615bdbe7b4dd3dfd6fc2210b7773edf5", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/082e4763615bdbe7b4dd3dfd6fc2210b7773edf5/", }, "refs/heads/tmp": { "target": "a0eb7a2c6da31b44718188002ac0cec12a3c86ee", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/a0eb7a2c6da31b44718188002ac0cec12a3c86ee/", }, "refs/heads/try": { "target": "b53c0f93eedcdedd4fd89bccc5a3a09d1c5cd23e", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/b53c0f93eedcdedd4fd89bccc5a3a09d1c5cd23e/", }, "refs/tags/0.1": { "target": "16e4369fe3b5f00aa3cdc584a4e41c51c0d3ca8a", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/16e4369fe3b5f00aa3cdc584a4e41c51c0d3ca8a/", }, "refs/tags/0.10": { "target": "46867cc3e4ddcbb1d359a315805de00094dacaf9", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/46867cc3e4ddcbb1d359a315805de00094dacaf9/", }, "refs/tags/0.11.0": { "target": "aa1163b92de7717eb7c5eba002b4012e0574a7fe", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/aa1163b92de7717eb7c5eba002b4012e0574a7fe/", }, "refs/tags/0.12.0": { "target": "ba4081a5a8573875fed17545846f6f6902c8ba8d", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/ba4081a5a8573875fed17545846f6f6902c8ba8d/", }, "refs/tags/0.2": { "target": "0622a74c48a708aec28d25964f4e9b4489580bc7", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/0622a74c48a708aec28d25964f4e9b4489580bc7/", }, "refs/tags/0.3": { "target": "2f32a1581f522e524009138b33b1c7049ced668d", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/2f32a1581f522e524009138b33b1c7049ced668d/", }, "refs/tags/0.4": { "target": "39c0d3591e0326874b7263a621ce09ecd64f0eb2", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/39c0d3591e0326874b7263a621ce09ecd64f0eb2/", }, "refs/tags/0.5": { "target": "8b98e5a296d95c5e832db0756828e5bec31c6f50", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/8b98e5a296d95c5e832db0756828e5bec31c6f50/", }, "refs/tags/0.6": { "target": "00dbbd01c2aee72982b3e0f9511ae1d4428c3ba9", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/00dbbd01c2aee72982b3e0f9511ae1d4428c3ba9/", }, "refs/tags/0.7": { "target": "a2db7c15ce9f586164cabb15d83fb3f6bbeb3cf5", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/a2db7c15ce9f586164cabb15d83fb3f6bbeb3cf5/", }, "refs/tags/0.8": { "target": "8a4f0fa6c518eb634687abe9659601d9d2a61899", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/8a4f0fa6c518eb634687abe9659601d9d2a61899/", }, "refs/tags/0.9": { "target": "7613b15fdbbb9bf770a2c731f4135886b0ff3cf0", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/7613b15fdbbb9bf770a2c731f4135886b0ff3cf0/", }, "refs/tags/1.0.0": { "target": "a59de37e99060162a2674e3ff45409ac73595c0e", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/a59de37e99060162a2674e3ff45409ac73595c0e/", }, "refs/tags/1.0.0-alpha": { "target": "44a287e6eb22ec3c2a687fc156813577464017f7", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/44a287e6eb22ec3c2a687fc156813577464017f7/", }, "refs/tags/1.0.0-alpha.2": { "target": "522d09dfecbeca1595f25ac58c6d0178bbd21d7d", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/522d09dfecbeca1595f25ac58c6d0178bbd21d7d/", }, "refs/tags/1.0.0-beta": { "target": "9854143cba679834bc4ef932858cd5303f015a0e", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/9854143cba679834bc4ef932858cd5303f015a0e/", }, "refs/tags/1.1.0": { "target": "35ceea3997c79a3b7562e89b462ab76af5b86b22", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/35ceea3997c79a3b7562e89b462ab76af5b86b22/", }, "refs/tags/homu-tmp": { "target": "1fe32ca12c51afcd761d9962f51a74ff0d07a591", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/1fe32ca12c51afcd761d9962f51a74ff0d07a591/", }, "refs/tags/release-0.1": { "target": "16e4369fe3b5f00aa3cdc584a4e41c51c0d3ca8a", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/16e4369fe3b5f00aa3cdc584a4e41c51c0d3ca8a/", }, "refs/tags/release-0.2": { "target": "0622a74c48a708aec28d25964f4e9b4489580bc7", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/0622a74c48a708aec28d25964f4e9b4489580bc7/", }, "refs/tags/release-0.3": { "target": "2f32a1581f522e524009138b33b1c7049ced668d", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/2f32a1581f522e524009138b33b1c7049ced668d/", }, "refs/tags/release-0.3.1": { "target": "33a055638c637d2f63d1d7a18b235c93c08d10b8", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/33a055638c637d2f63d1d7a18b235c93c08d10b8/", }, "refs/tags/release-0.4": { "target": "39c0d3591e0326874b7263a621ce09ecd64f0eb2", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/39c0d3591e0326874b7263a621ce09ecd64f0eb2/", }, "refs/tags/release-0.5": { "target": "8b98e5a296d95c5e832db0756828e5bec31c6f50", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/8b98e5a296d95c5e832db0756828e5bec31c6f50/", }, "refs/tags/release-0.6": { "target": "00dbbd01c2aee72982b3e0f9511ae1d4428c3ba9", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/00dbbd01c2aee72982b3e0f9511ae1d4428c3ba9/", }, "refs/tags/release-0.7": { "target": "a2db7c15ce9f586164cabb15d83fb3f6bbeb3cf5", "target_type": "revision", "target_url": "https://archive.softwareheritage.org/api/1/revision/a2db7c15ce9f586164cabb15d83fb3f6bbeb3cf5/", }, }, "next_branch": None, }, "revision/430a9fd4c797c50cea26157141b2408073b2ed91/": { "message": 'Auto merge of #27534 - alexcrichton:revert-adding-gdb-pp-tests, r=michaelwoerister\n\n… are not actually broken."\r\n\r\nThis reverts commit 354cf4b56b8e2af67cc68965eb816deec0e79e4b.\r\n\r\n\r\nUnfortunately these [tests are failing](http://buildbot.rust-lang.org/builders/nightly-dist-rustc-linux/builds/224/steps/distcheck/logs/stdio) on the snapshot/nightly bots with the [same message](https://gist.github.com/alexcrichton/611705ded07b0d73ded9) found in #27514\r\n', "author": { "fullname": "bors ", "name": "bors", "email": "bors@rust-lang.org", }, "committer": { "fullname": "bors ", "name": "bors", "email": "bors@rust-lang.org", }, "date": "2015-08-05T18:48:53+00:00", "committer_date": "2015-08-05T18:48:53+00:00", "type": "git", "directory": "1ac29db0e7280af41064676569a96d1f88ccfa96", "synthetic": False, "metadata": {}, "parents": [ { "id": "d03456183e85fe7bd465bbe7c8f67885a2528444", "url": "https://archive.softwareheritage.org/api/1/revision/d03456183e85fe7bd465bbe7c8f67885a2528444/", }, { "id": "3430532a8ee8fd5a7f47647c8c29403384647095", "url": "https://archive.softwareheritage.org/api/1/revision/3430532a8ee8fd5a7f47647c8c29403384647095/", }, ], "id": "430a9fd4c797c50cea26157141b2408073b2ed91", "extra_headers": [], "merge": True, "url": "https://archive.softwareheritage.org/api/1/revision/430a9fd4c797c50cea26157141b2408073b2ed91/", "history_url": "https://archive.softwareheritage.org/api/1/revision/430a9fd4c797c50cea26157141b2408073b2ed91/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/1ac29db0e7280af41064676569a96d1f88ccfa96/", }, + "graph/visit/nodes/swh:1:rev:b8cedc00407a4c56a3bda1ed605c6fc166655447": "swh:1:rev:b08d07143d2b61777d341f8658281adc0f2ac809\nswh:1:rev:133f659766c60ff7a33288ae6f33b0c272792f57", + "graph/visit/nodes/swh:1:rev:430a9fd4c797c50cea26157141b2408073b2ed91": "swh:1:rev:d03456183e85fe7bd465bbe7c8f67885a2528444\nswh:1:rev:3430532a8ee8fd5a7f47647c8c29403384647095", } diff --git a/swh/fuse/tests/data/gen-api-data.py b/swh/fuse/tests/data/gen-api-data.py index ea2b220..360a7a9 100755 --- a/swh/fuse/tests/data/gen-api-data.py +++ b/swh/fuse/tests/data/gen-api-data.py @@ -1,100 +1,112 @@ #!/usr/bin/env python3 # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import json from typing import Any, Dict import requests -from swh.fuse.tests.data.config import ALL_ENTRIES +from swh.fuse.tests.data.config import ALL_ENTRIES, ROOT_REV, ROOT_SNP_MASTER_BRANCH from swh.model.identifiers import ( CONTENT, DIRECTORY, RELEASE, REVISION, SNAPSHOT, SWHID, parse_swhid, ) API_URL_real = "https://archive.softwareheritage.org/api/1" API_URL_test = "https://invalid-test-only.archive.softwareheritage.org/api/1" SWHID2URL: Dict[str, str] = {} MOCK_ARCHIVE: Dict[str, Any] = {} # Temporary map (swhid -> metadata) to ease data generation METADATA: Dict[SWHID, Any] = {} def swhid2url(swhid: SWHID) -> str: prefix = { CONTENT: "content/sha1_git:", DIRECTORY: "directory/", REVISION: "revision/", RELEASE: "release/", SNAPSHOT: "snapshot/", } return f"{prefix[swhid.object_type]}{swhid.object_id}/" def get_short_type(object_type: str) -> str: short_type = { CONTENT: "cnt", DIRECTORY: "dir", REVISION: "rev", RELEASE: "rel", SNAPSHOT: "snp", } return short_type[object_type] def generate_archive_data( swhid: SWHID, raw: bool = False, recursive: bool = False ) -> None: # Already in mock archive if swhid in METADATA and not raw: return url = swhid2url(swhid) SWHID2URL[str(swhid)] = url if raw: url += "raw/" data = requests.get(f"{API_URL_real}/{url}").text else: data = requests.get(f"{API_URL_real}/{url}").text data = json.loads(data) MOCK_ARCHIVE[url] = data METADATA[swhid] = data # Retrieve additional needed data for different artifacts (eg: content's # blob data, release target, etc.) if recursive: if swhid.object_type == CONTENT: generate_archive_data(swhid, raw=True) elif swhid.object_type == RELEASE: target_type = METADATA[swhid]["target_type"] target_id = METADATA[swhid]["target"] target = parse_swhid(f"swh:1:{get_short_type(target_type)}:{target_id}") generate_archive_data(target, recursive=True) for entry in ALL_ENTRIES: swhid = parse_swhid(entry) generate_archive_data(swhid, recursive=True) +# XXX: temporary fix, this should be retrieved from the public archive but at +# the moment the /graph API is only for authorized accounts +# TODO: pick a revision with very few parents and put all history +MOCK_ARCHIVE[ + f"graph/visit/nodes/{ROOT_REV}" +] = """swh:1:rev:b08d07143d2b61777d341f8658281adc0f2ac809 +swh:1:rev:133f659766c60ff7a33288ae6f33b0c272792f57""" +MOCK_ARCHIVE[ + f"graph/visit/nodes/{ROOT_SNP_MASTER_BRANCH}" +] = """swh:1:rev:d03456183e85fe7bd465bbe7c8f67885a2528444 +swh:1:rev:3430532a8ee8fd5a7f47647c8c29403384647095""" + print("# GENERATED FILE, DO NOT EDIT.") print("# Run './gen-api-data.py > api_data.py' instead.") print("# flake8: noqa") print("from typing import Any, Dict") print("") print(f"API_URL = '{API_URL_test}'\n") print(f"SWHID2URL: Dict[str, str] = {SWHID2URL}\n") print(f"MOCK_ARCHIVE: Dict[str, Any] = {MOCK_ARCHIVE}") diff --git a/swh/fuse/tests/test_release.py b/swh/fuse/tests/test_release.py index 50d1f59..1ac96b2 100644 --- a/swh/fuse/tests/test_release.py +++ b/swh/fuse/tests/test_release.py @@ -1,46 +1,46 @@ import json import os from swh.fuse.tests.common import check_dir_name_entries, get_data_from_archive from swh.fuse.tests.data.config import ( REL_TARGET_CNT, REL_TARGET_DIR, ROOT_DIR, ROOT_REL, TARGET_CNT, TARGET_DIR, ) def test_access_meta(fuse_mntdir): file_path = fuse_mntdir / "archive" / ROOT_REL / "meta.json" expected = json.dumps(get_data_from_archive(ROOT_REL)) assert file_path.read_text() == expected def test_access_rev_target(fuse_mntdir): target_path = fuse_mntdir / "archive" / ROOT_REL / "target" - expected = ["meta.json", "root", "parent", "parents"] + expected = ["meta.json", "root", "parent", "parents", "history"] actual = os.listdir(target_path) assert set(actual) == set(expected) def test_access_dir_target(fuse_mntdir): target_path = fuse_mntdir / "archive" / REL_TARGET_DIR / "target" check_dir_name_entries(target_path, TARGET_DIR) def test_access_cnt_target(fuse_mntdir): target_path = fuse_mntdir / "archive" / REL_TARGET_CNT / "target" expected = get_data_from_archive(TARGET_CNT, raw=True) assert target_path.read_text() == expected def test_target_type(fuse_mntdir): file_path = fuse_mntdir / "archive" / ROOT_REL / "target_type" assert file_path.read_text() == "revision\n" def test_access_root(fuse_mntdir): dir_path = fuse_mntdir / "archive" / ROOT_REL / "root" check_dir_name_entries(dir_path, ROOT_DIR) diff --git a/swh/fuse/tests/test_snapshot.py b/swh/fuse/tests/test_snapshot.py index 3435ab6..f43a542 100644 --- a/swh/fuse/tests/test_snapshot.py +++ b/swh/fuse/tests/test_snapshot.py @@ -1,22 +1,22 @@ import os import urllib.parse from swh.fuse.tests.common import get_data_from_archive from swh.fuse.tests.data.config import ROOT_SNP def test_list_branches(fuse_mntdir): snp_meta = get_data_from_archive(ROOT_SNP) expected = snp_meta.keys() # Mangle branch name to create a valid UNIX filename expected = [urllib.parse.quote_plus(x) for x in expected] actual = os.listdir(fuse_mntdir / "archive" / ROOT_SNP) assert set(actual) == set(expected) def test_access_rev_target(fuse_mntdir): branch_name = urllib.parse.quote_plus("refs/heads/master") dir_path = fuse_mntdir / "archive" / ROOT_SNP / branch_name - expected = ["meta.json", "root", "parent", "parents"] + expected = ["meta.json", "root", "parent", "parents", "history"] actual = os.listdir(dir_path) assert set(actual) == set(expected)