mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
Merge branch 'mauro' into docs-mw
This series improve the output at process/maintainers: instead of a
pure enriched text, the maintainer's file content is now converted
to a table, and has gained a javascript to allow filtering entries.
The initial patches change the logic to split parsing from
output generation. Then, everything is stored into a dict at
the parsing phase, and ona header description variable.
This way, it is easier to adjust the output handler to produce
a more structured document. Right now, the entries are sorted
alphabetically, per subsystem's name(*).
(*) Currently, MAINTAINERS file has several entries not sorted.
One has to run:
scripts/parse-maintainers.pl --input MAINTAINERS --output MAINTAINERS.new
to sort it.
This commit is contained in:
commit
5677cae653
|
|
@ -1,30 +1,25 @@
|
|||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# -*- coding: utf-8; mode: python -*-
|
||||
# pylint: disable=R0903, C0330, R0914, R0912, E0401
|
||||
# pylint: disable=C0209, C0301, E0401, R0022, R0902, R0903, R0912, R0914
|
||||
|
||||
"""
|
||||
maintainers-include
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
Implementation of the ``maintainers-include`` reST-directive.
|
||||
|
||||
Implementation of the ``maintainers-include`` reST-directive.
|
||||
:copyright: Copyright (C) 2019 Kees Cook <keescook@chromium.org>
|
||||
:license: GPL Version 2, June 1991 see linux/COPYING for details.
|
||||
|
||||
:copyright: Copyright (C) 2019 Kees Cook <keescook@chromium.org>
|
||||
:license: GPL Version 2, June 1991 see linux/COPYING for details.
|
||||
|
||||
The ``maintainers-include`` reST-directive performs extensive parsing
|
||||
specific to the Linux kernel's standard "MAINTAINERS" file, in an
|
||||
effort to avoid needing to heavily mark up the original plain text.
|
||||
The ``maintainers-include`` reST-directive performs extensive parsing
|
||||
specific to the Linux kernel's standard "MAINTAINERS" file, in an
|
||||
effort to avoid needing to heavily mark up the original plain text.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from glob import glob
|
||||
|
||||
from docutils import statemachine
|
||||
from docutils.parsers.rst import Directive
|
||||
from docutils.parsers.rst.directives.misc import Include
|
||||
|
||||
#
|
||||
|
|
@ -32,193 +27,274 @@ from docutils.parsers.rst.directives.misc import Include
|
|||
#
|
||||
KERNELDOC_URL = "https://docs.kernel.org/"
|
||||
|
||||
def ErrorString(exc): # Shamelessly stolen from docutils
|
||||
return f'{exc.__class__.__name}: {exc}'
|
||||
__version__ = "1.0"
|
||||
|
||||
__version__ = '1.0'
|
||||
maint_parser = None # pylint: disable=C0103
|
||||
|
||||
maint_parser = None
|
||||
JS_FILTER = """
|
||||
(function() {
|
||||
function filterTable(table) {
|
||||
const filter = document.getElementById("filter-table").value.trim();
|
||||
const rows = table.querySelectorAll("tbody tr");
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const tds = rows[i].getElementsByTagName("td");
|
||||
let match = false;
|
||||
for (let j = 0; j < tds.length; j++) {
|
||||
const cellText = (tds[j].textContent || tds[j].innerText);
|
||||
if (cellText.includes(filter)) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rows[i].style.display = match ? "table-row" : "none";
|
||||
}
|
||||
}
|
||||
function addInput() {
|
||||
const table = document.getElementById("maintainers-table");
|
||||
if (!table) return;
|
||||
let input = document.getElementById("filter-table");
|
||||
if (!input) {
|
||||
const filt_div = document.createElement('div');
|
||||
filt_div.innerHTML = `
|
||||
<p>Filter:
|
||||
<input type="search" id="filter-table" placeholder="search string"/>
|
||||
subsystem or property (case-sensitive)
|
||||
</p>
|
||||
`;
|
||||
table.parentNode.insertBefore(filt_div, table);
|
||||
const input = document.getElementById("filter-table")
|
||||
input.addEventListener('input', () => filterTable(table));
|
||||
}
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', addInput);
|
||||
} else {
|
||||
addInput();
|
||||
}
|
||||
})();
|
||||
"""
|
||||
|
||||
|
||||
# Shamelessly stolen from docutils
|
||||
def ErrorString(exc): # pylint: disable=C0103, C0116
|
||||
return f"{exc.__class__.__name}: {exc}" # pylint: disable=W0212
|
||||
|
||||
class MaintainersParser:
|
||||
"""Parse MAINTAINERS file(s) content"""
|
||||
|
||||
def __init__(self, app_dir, path):
|
||||
def __init__(self, base_dir, app_dir, path):
|
||||
self.path = path
|
||||
self.profile_toc = set()
|
||||
self.profile_entries = {}
|
||||
|
||||
result = list()
|
||||
result.append(".. _maintainers:")
|
||||
result.append("")
|
||||
|
||||
# Poor man's state machine.
|
||||
descriptions = False
|
||||
maintainers = False
|
||||
subsystems = False
|
||||
self.descriptions = False
|
||||
self.maintainers = False
|
||||
self.subsystems = False
|
||||
|
||||
# Field letter to field name mapping.
|
||||
field_letter = None
|
||||
fields = dict()
|
||||
self.subsystem_name = None
|
||||
|
||||
self.base_dir = base_dir
|
||||
self.app_dir = app_dir
|
||||
|
||||
self.re_doc = re.compile(r'(Documentation/(\S*)\.rst)')
|
||||
|
||||
#
|
||||
# Output variables with maintainers content to be stored
|
||||
#
|
||||
self.profile_toc = set()
|
||||
self.profile_entries = {}
|
||||
self.header = ""
|
||||
self.maint_entries = {}
|
||||
self.fields = {}
|
||||
|
||||
prev = None
|
||||
field_prev = ""
|
||||
field_content = ""
|
||||
subsystem_name = None
|
||||
|
||||
base_dir, doc_dir, sphinx_dir = app_dir.partition("Documentation")
|
||||
|
||||
for line in open(path):
|
||||
# Have we reached the end of the preformatted Descriptions text?
|
||||
if descriptions and line.startswith('Maintainers'):
|
||||
descriptions = False
|
||||
# Ensure a blank line following the last "|"-prefixed line.
|
||||
result.append("")
|
||||
|
||||
# Start subsystem processing? This is to skip processing the text
|
||||
# between the Maintainers heading and the first subsystem name.
|
||||
if maintainers and not subsystems:
|
||||
if re.search('^[A-Z0-9]', line):
|
||||
subsystems = True
|
||||
|
||||
# Drop needless input whitespace.
|
||||
line = line.rstrip()
|
||||
|
||||
#
|
||||
# Handle profile entries - either as files or as https refs
|
||||
#
|
||||
match = re.match(rf"P:\s*({doc_dir})(/\S+)\.rst", line)
|
||||
if match:
|
||||
name = "".join(match.groups())
|
||||
entry = os.path.relpath(base_dir + name, app_dir)
|
||||
|
||||
full_name = os.path.join(base_dir, name)
|
||||
path = os.path.relpath(full_name, app_dir)
|
||||
#
|
||||
# When SPHINXDIRS is used, it will try to reference files
|
||||
# outside srctree, causing warnings. To avoid that, point
|
||||
# to the latest official documentation
|
||||
#
|
||||
if path.startswith("../"):
|
||||
entry = KERNELDOC_URL + match.group(2) + ".html"
|
||||
with open(path, "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
if self.descriptions:
|
||||
self.parse_descriptions(line)
|
||||
elif self.maintainers and not self.subsystems:
|
||||
if re.search('^[A-Z0-9]', line):
|
||||
self.subsystems = True
|
||||
self.parse_subsystems(line)
|
||||
else:
|
||||
self.header += line
|
||||
elif self.subsystems:
|
||||
self.parse_subsystems(line)
|
||||
else:
|
||||
entry = "/" + entry
|
||||
self.header += line
|
||||
|
||||
if "*" in entry:
|
||||
for e in glob(entry):
|
||||
# Update the state machine when we find heading separators.
|
||||
if line.startswith("----------"):
|
||||
if prev.startswith("Descriptions"):
|
||||
self.descriptions = True
|
||||
if prev.startswith("Maintainers"):
|
||||
self.maintainers = True
|
||||
|
||||
# Retain previous line for state machine transitions.
|
||||
prev = line
|
||||
|
||||
def get_entries(self, text):
|
||||
"""Generate refs to ReST files in Documentation/"""
|
||||
|
||||
if "Documentation/" not in text:
|
||||
return None
|
||||
|
||||
if "*" in text or "?" in text:
|
||||
m = self.re_doc.search(text)
|
||||
if not m:
|
||||
return None
|
||||
|
||||
doc_list = glob(m.group(1), root_dir=self.base_dir)
|
||||
else:
|
||||
doc_list = [text]
|
||||
|
||||
entries = {}
|
||||
for doc in doc_list:
|
||||
m = self.re_doc.search(doc)
|
||||
if m:
|
||||
fname = m.group(1)
|
||||
ename = m.group(2)
|
||||
|
||||
entry = os.path.relpath(self.base_dir + fname, self.app_dir)
|
||||
entry = entry.removesuffix(".rst")
|
||||
|
||||
if entry.startswith("../"):
|
||||
html = KERNELDOC_URL + ename + ".html"
|
||||
entries[entry] = f'`{ename} <{html}>`_'
|
||||
else:
|
||||
entries[entry] = f':doc:`{ename} </{entry}>`'
|
||||
|
||||
return entries
|
||||
|
||||
def linkify(self, text):
|
||||
"""Return a list of doc files converted to cross-references"""
|
||||
|
||||
entries = self.get_entries(text)
|
||||
if not entries:
|
||||
return text
|
||||
|
||||
return self.re_doc.sub(", ".join(entries.values()), text)
|
||||
|
||||
def parse_descriptions(self, line):
|
||||
"""Handle contents of the descriptions section."""
|
||||
|
||||
# Have we reached the end of the preformatted Descriptions text?
|
||||
if line.startswith("Maintainers"):
|
||||
self.descriptions = False
|
||||
self.header += "\n" + line
|
||||
return
|
||||
|
||||
# Look for and record field letter to field name mappings:
|
||||
# R: Designated *reviewer*: FullName <address@domain>
|
||||
m = re.match(r"\s+(\S):\s+(\S+)", line)
|
||||
if m:
|
||||
field = m.group(1)
|
||||
details = m.group(2)
|
||||
|
||||
if field not in self.fields:
|
||||
m = re.search(r"\*([^\*]+)\*", line)
|
||||
if m:
|
||||
self.fields[field] = m.group(1)
|
||||
elif field in ['F', 'N', 'X', 'K']:
|
||||
line = line.replace(details, f'``{details}``')
|
||||
|
||||
self.header += "| " + self.linkify(line)
|
||||
|
||||
|
||||
def parse_subsystems(self, line):
|
||||
"""Handle contents of the per-subsystem sections."""
|
||||
|
||||
# Drop needless input whitespace.
|
||||
line = line.rstrip()
|
||||
|
||||
# Skip empty lines: subsystem parser adds them as needed.
|
||||
if not line:
|
||||
return
|
||||
|
||||
if line[1] != ':':
|
||||
self.subsystem_name = re.sub(r"\s+", " ", self.linkify(line))
|
||||
return
|
||||
|
||||
# Render a subsystem field as:
|
||||
# :Field: entry
|
||||
# entry...
|
||||
field, details = line.split(":", 1)
|
||||
details = details.strip()
|
||||
|
||||
#
|
||||
# Handle profile entries - either as files or as https refs
|
||||
#
|
||||
if field == "P":
|
||||
entries = self.get_entries(details)
|
||||
if entries:
|
||||
for e, link in entries.items():
|
||||
if "html" not in link:
|
||||
self.profile_toc.add(e)
|
||||
self.profile_entries[subsystem_name] = e
|
||||
else:
|
||||
self.profile_toc.add(entry)
|
||||
self.profile_entries[subsystem_name] = entry
|
||||
|
||||
self.profile_entries[self.subsystem_name] = link
|
||||
|
||||
details = ", ".join(entries.values())
|
||||
else:
|
||||
match = re.match(r"P:\s*(https?://.*)", line)
|
||||
match = re.match(r"(https?://.*)", details)
|
||||
if match:
|
||||
entry = match.group(1).strip()
|
||||
self.profile_entries[subsystem_name] = entry
|
||||
|
||||
# Linkify all non-wildcard refs to ReST files in Documentation/.
|
||||
pat = r'(Documentation/([^\s\?\*]*)\.rst)'
|
||||
m = re.search(pat, line)
|
||||
if m:
|
||||
# maintainers.rst is in a subdirectory, so include "../".
|
||||
line = re.sub(pat, ':doc:`%s <../%s>`' % (m.group(2), m.group(2)), line)
|
||||
|
||||
# Check state machine for output rendering behavior.
|
||||
output = None
|
||||
if descriptions:
|
||||
# Escape the escapes in preformatted text.
|
||||
output = "| %s" % (line.replace("\\", "\\\\"))
|
||||
# Look for and record field letter to field name mappings:
|
||||
# R: Designated *reviewer*: FullName <address@domain>
|
||||
m = re.search(r"\s(\S):\s", line)
|
||||
if m:
|
||||
field_letter = m.group(1)
|
||||
if field_letter and not field_letter in fields:
|
||||
m = re.search(r"\*([^\*]+)\*", line)
|
||||
if m:
|
||||
fields[field_letter] = m.group(1)
|
||||
elif subsystems:
|
||||
# Skip empty lines: subsystem parser adds them as needed.
|
||||
if len(line) == 0:
|
||||
continue
|
||||
# Subsystem fields are batched into "field_content"
|
||||
if line[1] != ':':
|
||||
# Render a subsystem entry as:
|
||||
# SUBSYSTEM NAME
|
||||
# ~~~~~~~~~~~~~~
|
||||
|
||||
# Flush pending field content.
|
||||
output = field_content + "\n\n"
|
||||
field_content = ""
|
||||
|
||||
subsystem_name = line.title()
|
||||
|
||||
# Collapse whitespace in subsystem name.
|
||||
heading = re.sub(r"\s+", " ", line)
|
||||
output = output + "%s\n%s" % (heading, "~" * len(heading))
|
||||
field_prev = ""
|
||||
self.profile_entries[self.subsystem_name] = entry
|
||||
else:
|
||||
# Render a subsystem field as:
|
||||
# :Field: entry
|
||||
# entry...
|
||||
field, details = line.split(':', 1)
|
||||
details = details.strip()
|
||||
self.profile_entries[self.subsystem_name] = f"``{details}``"
|
||||
|
||||
# Mark paths (and regexes) as literal text for improved
|
||||
# readability and to escape any escapes.
|
||||
if field in ['F', 'N', 'X', 'K']:
|
||||
# But only if not already marked :)
|
||||
if not ':doc:' in details:
|
||||
details = '``%s``' % (details)
|
||||
details = self.linkify(details)
|
||||
else:
|
||||
details = self.linkify(details)
|
||||
|
||||
# Comma separate email field continuations.
|
||||
if field == field_prev and field_prev in ['M', 'R', 'L']:
|
||||
field_content = field_content + ","
|
||||
#
|
||||
# Mark paths (and regexes) as literal text for improved
|
||||
# readability and to escape any escapes.
|
||||
#
|
||||
if field in ['F', 'N', 'X', 'K']:
|
||||
# But only if not already marked :)
|
||||
if ':doc:' not in details and "http" not in details:
|
||||
details = '``%s``' % (details)
|
||||
|
||||
# Do not repeat field names, so that field entries
|
||||
# will be collapsed together.
|
||||
if field != field_prev:
|
||||
output = field_content + "\n"
|
||||
field_content = ":%s:" % (fields.get(field, field))
|
||||
field_content = field_content + "\n\t%s" % (details)
|
||||
field_prev = field
|
||||
else:
|
||||
output = line
|
||||
if self.subsystem_name not in self.maint_entries:
|
||||
self.maint_entries[self.subsystem_name] = {}
|
||||
|
||||
# Re-split on any added newlines in any above parsing.
|
||||
if output != None:
|
||||
for separated in output.split('\n'):
|
||||
result.append(separated)
|
||||
if field not in self.maint_entries[self.subsystem_name]:
|
||||
self.maint_entries[self.subsystem_name][field] = []
|
||||
|
||||
# Update the state machine when we find heading separators.
|
||||
if line.startswith('----------'):
|
||||
if prev.startswith('Descriptions'):
|
||||
descriptions = True
|
||||
if prev.startswith('Maintainers'):
|
||||
maintainers = True
|
||||
self.maint_entries[self.subsystem_name][field].append(details)
|
||||
|
||||
# Retain previous line for state machine transitions.
|
||||
prev = line
|
||||
self.field_prev = field
|
||||
|
||||
# Flush pending field contents.
|
||||
if field_content != "":
|
||||
for separated in field_content.split('\n'):
|
||||
result.append(separated)
|
||||
|
||||
self.output = "\n".join(result)
|
||||
|
||||
# Create a TOC class
|
||||
|
||||
class MaintainersInclude(Include):
|
||||
"""MaintainersInclude (``maintainers-include``) directive"""
|
||||
|
||||
required_arguments = 0
|
||||
|
||||
def emit(self):
|
||||
"""Parse all the MAINTAINERS lines into ReST for human-readability"""
|
||||
global maint_parser
|
||||
|
||||
path = maint_parser.path
|
||||
output = maint_parser.output
|
||||
output = ".. _maintainers:\n\n"
|
||||
output += maint_parser.header
|
||||
|
||||
output += ".. _maintainers_table:\n\n"
|
||||
output += ".. flat-table::\n"
|
||||
output += " :header-rows: 1\n\n"
|
||||
output += " * - Subsystem\n"
|
||||
output += " - Properties\n\n"
|
||||
|
||||
self.state.document['maintainers_included'] = True
|
||||
|
||||
for name, fields in sorted(maint_parser.maint_entries.items()):
|
||||
output += f" * - {name}\n"
|
||||
tag = "-"
|
||||
for field, lines in fields.items():
|
||||
field_name = maint_parser.fields.get(field, field)
|
||||
|
||||
output += f" {tag} :{field_name}:\n "
|
||||
output += ",\n ".join(lines) + "\n"
|
||||
tag = " "
|
||||
|
||||
output += "\n"
|
||||
|
||||
# For debugging the pre-rendered results...
|
||||
#print(output, file=open("/tmp/MAINTAINERS.rst", "w"))
|
||||
|
|
@ -232,20 +308,23 @@ class MaintainersInclude(Include):
|
|||
raise self.warning('"%s" directive disabled.' % self.name)
|
||||
|
||||
try:
|
||||
lines = self.emit()
|
||||
self.emit()
|
||||
except IOError as error:
|
||||
raise self.severe('Problems with "%s" directive path:\n%s.' %
|
||||
(self.name, ErrorString(error)))
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class MaintainersProfile(Include):
|
||||
"""Generate a list with all maintainer's profiles"""
|
||||
|
||||
required_arguments = 0
|
||||
|
||||
def emit(self):
|
||||
"""Parse all the MAINTAINERS lines looking for profile entries"""
|
||||
global maint_parser
|
||||
|
||||
env = self.state.document.settings.env
|
||||
docdir = os.path.dirname(os.path.join(env.srcdir, env.docname))
|
||||
path = maint_parser.path
|
||||
|
||||
#
|
||||
|
|
@ -253,10 +332,15 @@ class MaintainersProfile(Include):
|
|||
#
|
||||
output = ""
|
||||
for profile, entry in sorted(maint_parser.profile_entries.items()):
|
||||
name = profile.title()
|
||||
|
||||
if entry.startswith("http"):
|
||||
output += f"- `{profile} <{entry}>`_\n"
|
||||
output += f"- `{name} <{entry}>`_\n"
|
||||
elif entry.startswith("`"):
|
||||
output += f"- {name}: {entry}\n"
|
||||
self.warning(f"{profile}: Invalid 'P' tag: {entry}\n")
|
||||
else:
|
||||
output += f"- :doc:`{profile} <{entry}>`\n"
|
||||
output += f"- {entry}\n"
|
||||
|
||||
#
|
||||
# Create a hidden TOC table with all profiles. That allows adding
|
||||
|
|
@ -265,11 +349,16 @@ class MaintainersProfile(Include):
|
|||
output += "\n.. toctree::\n"
|
||||
output += " :hidden:\n\n"
|
||||
|
||||
for fname in maint_parser.profile_toc:
|
||||
for f in sorted(maint_parser.profile_toc):
|
||||
fname = os.path.join(maint_parser.base_dir, "Documentation", f)
|
||||
fname = os.path.relpath(fname, docdir)
|
||||
output += f" {fname}\n"
|
||||
|
||||
output += "\n"
|
||||
|
||||
# For debugging the pre-rendered results...
|
||||
#print(output, file=open("/tmp/profiles.rst", "w"))
|
||||
|
||||
self.state.document.settings.record_dependencies.add(path)
|
||||
self.state_machine.insert_input(statemachine.string2lines(output), path)
|
||||
|
||||
|
|
@ -279,30 +368,43 @@ class MaintainersProfile(Include):
|
|||
raise self.warning('"%s" directive disabled.' % self.name)
|
||||
|
||||
try:
|
||||
lines = self.emit()
|
||||
self.emit()
|
||||
except IOError as error:
|
||||
raise self.severe('Problems with "%s" directive path:\n%s.' %
|
||||
(self.name, ErrorString(error)))
|
||||
|
||||
return []
|
||||
|
||||
|
||||
# pylint: disable=W0613
|
||||
def add_filter_script(app, pagename, templatename, context, doctree):
|
||||
"""Add Filter javascript only to maintainers page"""
|
||||
|
||||
if doctree and doctree.get('maintainers_included'):
|
||||
app.add_js_file(None, body=JS_FILTER)
|
||||
|
||||
|
||||
def setup(app):
|
||||
global maint_parser
|
||||
"""Setup Sphinx extension"""
|
||||
global maint_parser # pylint: disable=W0603
|
||||
|
||||
#
|
||||
# NOTE: we're using os.fspath() here because of a Sphinx warning:
|
||||
# RemovedInSphinx90Warning: Sphinx 9 will drop support for representing paths as strings. Use "pathlib.Path" or "os.fspath" instead.
|
||||
#
|
||||
app_dir = os.fspath(app.srcdir)
|
||||
srctree = os.path.abspath(os.environ["srctree"])
|
||||
path = os.path.join(srctree, "MAINTAINERS")
|
||||
app_dir = os.path.abspath(app.srcdir)
|
||||
match = re.match(r"(.*/)Documentation", app_dir)
|
||||
if not match:
|
||||
raise ValueError('Documentation directory not found.')
|
||||
|
||||
maint_parser = MaintainersParser(app_dir, path)
|
||||
base_dir = match.group(1)
|
||||
path = os.path.join(base_dir, "MAINTAINERS")
|
||||
|
||||
maint_parser = MaintainersParser(base_dir, app_dir, path)
|
||||
|
||||
app.add_directive("maintainers-include", MaintainersInclude)
|
||||
app.add_directive("maintainers-profile-toc", MaintainersProfile)
|
||||
return dict(
|
||||
version = __version__,
|
||||
parallel_read_safe = True,
|
||||
parallel_write_safe = True
|
||||
)
|
||||
|
||||
app.connect("html-page-context", add_filter_script)
|
||||
|
||||
return {
|
||||
"version": __version__,
|
||||
"parallel_read_safe": True,
|
||||
"parallel_write_safe": True,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ Descriptions of section entries and preferred order
|
|||
C: URI for *chat* protocol, server and channel where developers
|
||||
usually hang out, for example irc://server/channel.
|
||||
P: *Subsystem Profile* document for more details submitting
|
||||
patches to the given subsystem. This is either an in-tree file,
|
||||
or a URI. See Documentation/maintainer/maintainer-entry-profile.rst
|
||||
for details.
|
||||
patches to the given subsystem. This is either an in-tree .rst file
|
||||
inside Documentation/, or a URI.
|
||||
See Documentation/maintainer/maintainer-entry-profile.rst for details.
|
||||
T: *SCM* tree type and location.
|
||||
Type is one of: git, hg, quilt, stgit, topgit
|
||||
F: *Files* and directories wildcard patterns.
|
||||
|
|
@ -23402,7 +23402,7 @@ S: Maintained
|
|||
W: https://rust-for-linux.com/pin-init
|
||||
B: https://github.com/Rust-for-Linux/pin-init/issues
|
||||
C: zulip://rust-for-linux.zulipchat.com
|
||||
P: rust/pin-init/CONTRIBUTING.md
|
||||
P: https://github.com/Rust-for-Linux/pin-init/blob/main/CONTRIBUTING.md
|
||||
T: git https://github.com/Rust-for-Linux/linux.git pin-init-next
|
||||
F: rust/kernel/init.rs
|
||||
F: rust/pin-init/
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user