Initial commit
This commit is contained in:
commit
6f9ccf054b
19
.editorconfig
Normal file
19
.editorconfig
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = spaces
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whilespace = true
|
||||||
|
|
||||||
|
[README]
|
||||||
|
indent_style = tabs
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = spaces
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
/target
|
2377
Cargo.lock
generated
Normal file
2377
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
2
Cargo.lock.license
Normal file
2
Cargo.lock.license
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
|
SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
37
Cargo.toml
Normal file
37
Cargo.toml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "pr-tracker"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Alyssa Ross <hi@alyssa.is>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "AGPL-3.0-or-later WITH GPL-3.0-linking-exception"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
pkg-config = "0.3.19"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
http-types = "*"
|
||||||
|
once_cell = "1.5"
|
||||||
|
regex = "1.4"
|
||||||
|
surf = "2.1"
|
||||||
|
serde_json = "1.0"
|
||||||
|
graphql_client = "0.8.0"
|
||||||
|
serde = "1.0"
|
||||||
|
askama = "0.10.5"
|
||||||
|
structopt = "0.3.21"
|
||||||
|
futures-util = "0.3.12"
|
||||||
|
|
||||||
|
[dependencies.async-std]
|
||||||
|
version = "*" # Use whatever tide uses.
|
||||||
|
features = ["attributes"]
|
||||||
|
|
||||||
|
[dependencies.tide]
|
||||||
|
version = "0.16.0"
|
||||||
|
default-features = false
|
||||||
|
features = ["h1-server", "logger"]
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
# https://github.com/djc/askama/pull/447
|
||||||
|
askama = { git = "https://github.com/djc/askama", branch = "main" }
|
603
LICENSES/AGPL-3.0-or-later.txt
Normal file
603
LICENSES/AGPL-3.0-or-later.txt
Normal file
|
@ -0,0 +1,603 @@
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||||
|
document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for software
|
||||||
|
and other kinds of works, specifically designed to ensure cooperation with
|
||||||
|
the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed to take
|
||||||
|
away your freedom to share and change the works. By contrast, our General
|
||||||
|
Public Licenses are intended to guarantee your freedom to share and change
|
||||||
|
all versions of a program--to make sure it remains free software for all its
|
||||||
|
users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not price. Our
|
||||||
|
General Public Licenses are designed to make sure that you have the freedom
|
||||||
|
to distribute copies of free software (and charge for them if you wish), that
|
||||||
|
you receive source code or can get it if you want it, that you can change
|
||||||
|
the software or use pieces of it in new free programs, and that you know you
|
||||||
|
can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights with two
|
||||||
|
steps: (1) assert copyright on the software, and (2) offer you this License
|
||||||
|
which gives you legal permission to copy, distribute and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that improvements made
|
||||||
|
in alternate versions of the program, if they receive widespread use, become
|
||||||
|
available for other developers to incorporate. Many developers of free software
|
||||||
|
are heartened and encouraged by the resulting cooperation. However, in the
|
||||||
|
case of software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and letting
|
||||||
|
the public access it on a server without ever releasing its source code to
|
||||||
|
the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to ensure that,
|
||||||
|
in such cases, the modified source code becomes available to the community.
|
||||||
|
It requires the operator of a network server to provide the source code of
|
||||||
|
the modified version running there to the users of that server. Therefore,
|
||||||
|
public use of a modified version, on a publicly accessible server, gives the
|
||||||
|
public access to the source code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and published by
|
||||||
|
Affero, was designed to accomplish similar goals. This is a different license,
|
||||||
|
not a version of the Affero GPL, but Affero has released a new version of
|
||||||
|
the Affero GPL which permits relicensing under this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and modification
|
||||||
|
follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of works,
|
||||||
|
such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this License.
|
||||||
|
Each licensee is addressed as "you". "Licensees" and "recipients" may be
|
||||||
|
individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work in
|
||||||
|
a fashion requiring copyright permission, other than the making of an exact
|
||||||
|
copy. The resulting work is called a "modified version" of the earlier work
|
||||||
|
or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based on the
|
||||||
|
Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without permission,
|
||||||
|
would make you directly or secondarily liable for infringement under applicable
|
||||||
|
copyright law, except executing it on a computer or modifying a private copy.
|
||||||
|
Propagation includes copying, distribution (with or without modification),
|
||||||
|
making available to the public, and in some countries other activities as
|
||||||
|
well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other parties
|
||||||
|
to make or receive copies. Mere interaction with a user through a computer
|
||||||
|
network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices" to the
|
||||||
|
extent that it includes a convenient and prominently visible feature that
|
||||||
|
(1) displays an appropriate copyright notice, and (2) tells the user that
|
||||||
|
there is no warranty for the work (except to the extent that warranties are
|
||||||
|
provided), that licensees may convey the work under this License, and how
|
||||||
|
to view a copy of this License. If the interface presents a list of user
|
||||||
|
commands or options, such as a menu, a prominent item in the list meets this
|
||||||
|
criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
The "source code" for a work means the preferred form of the work for making
|
||||||
|
modifications to it. "Object code" means any non-source form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official standard
|
||||||
|
defined by a recognized standards body, or, in the case of interfaces specified
|
||||||
|
for a particular programming language, one that is widely used among developers
|
||||||
|
working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other than
|
||||||
|
the work as a whole, that (a) is included in the normal form of packaging
|
||||||
|
a Major Component, but which is not part of that Major Component, and (b)
|
||||||
|
serves only to enable use of the work with that Major Component, or to implement
|
||||||
|
a Standard Interface for which an implementation is available to the public
|
||||||
|
in source code form. A "Major Component", in this context, means a major
|
||||||
|
essential component (kernel, window system, and so on) of the specific operating
|
||||||
|
system (if any) on which the executable work runs, or a compiler used to produce
|
||||||
|
the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all the source
|
||||||
|
code needed to generate, install, and (for an executable work) run the object
|
||||||
|
code and to modify the work, including scripts to control those activities.
|
||||||
|
However, it does not include the work's System Libraries, or general-purpose
|
||||||
|
tools or generally available free programs which are used unmodified in performing
|
||||||
|
those activities but which are not part of the work. For example, Corresponding
|
||||||
|
Source includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically linked
|
||||||
|
subprograms that the work is specifically designed to require, such as by
|
||||||
|
intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users can regenerate
|
||||||
|
automatically from other parts of the Corresponding Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
All rights granted under this License are granted for the term of copyright
|
||||||
|
on the Program, and are irrevocable provided the stated conditions are met.
|
||||||
|
This License explicitly affirms your unlimited permission to run the unmodified
|
||||||
|
Program. The output from running a covered work is covered by this License
|
||||||
|
only if the output, given its content, constitutes a covered work. This License
|
||||||
|
acknowledges your rights of fair use or other equivalent, as provided by copyright
|
||||||
|
law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not convey, without
|
||||||
|
conditions so long as your license otherwise remains in force. You may convey
|
||||||
|
covered works to others for the sole purpose of having them make modifications
|
||||||
|
exclusively for you, or provide you with facilities for running those works,
|
||||||
|
provided that you comply with the terms of this License in conveying all material
|
||||||
|
for which you do not control copyright. Those thus making or running the
|
||||||
|
covered works for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of your copyrighted
|
||||||
|
material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under the conditions
|
||||||
|
stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
No covered work shall be deemed part of an effective technological measure
|
||||||
|
under any applicable law fulfilling obligations under article 11 of the WIPO
|
||||||
|
copyright treaty adopted on 20 December 1996, or similar laws prohibiting
|
||||||
|
or restricting circumvention of such measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid circumvention
|
||||||
|
of technological measures to the extent such circumvention is effected by
|
||||||
|
exercising rights under this License with respect to the covered work, and
|
||||||
|
you disclaim any intention to limit operation or modification of the work
|
||||||
|
as a means of enforcing, against the work's users, your or third parties'
|
||||||
|
legal rights to forbid circumvention of technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
You may convey verbatim copies of the Program's source code as you receive
|
||||||
|
it, in any medium, provided that you conspicuously and appropriately publish
|
||||||
|
on each copy an appropriate copyright notice; keep intact all notices stating
|
||||||
|
that this License and any non-permissive terms added in accord with section
|
||||||
|
7 apply to the code; keep intact all notices of the absence of any warranty;
|
||||||
|
and give all recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey, and you
|
||||||
|
may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
You may convey a work based on the Program, or the modifications to produce
|
||||||
|
it from the Program, in the form of source code under the terms of section
|
||||||
|
4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified it, and
|
||||||
|
giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is released under
|
||||||
|
this License and any conditions added under section 7. This requirement modifies
|
||||||
|
the requirement in section 4 to "keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this License to anyone
|
||||||
|
who comes into possession of a copy. This License will therefore apply, along
|
||||||
|
with any applicable section 7 additional terms, to the whole of the work,
|
||||||
|
and all its parts, regardless of how they are packaged. This License gives
|
||||||
|
no permission to license the work in any other way, but it does not invalidate
|
||||||
|
such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display Appropriate
|
||||||
|
Legal Notices; however, if the Program has interactive interfaces that do
|
||||||
|
not display Appropriate Legal Notices, your work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent works,
|
||||||
|
which are not by their nature extensions of the covered work, and which are
|
||||||
|
not combined with it such as to form a larger program, in or on a volume of
|
||||||
|
a storage or distribution medium, is called an "aggregate" if the compilation
|
||||||
|
and its resulting copyright are not used to limit the access or legal rights
|
||||||
|
of the compilation's users beyond what the individual works permit. Inclusion
|
||||||
|
of a covered work in an aggregate does not cause this License to apply to
|
||||||
|
the other parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
You may convey a covered work in object code form under the terms of sections
|
||||||
|
4 and 5, provided that you also convey the machine-readable Corresponding
|
||||||
|
Source under the terms of this License, in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product (including
|
||||||
|
a physical distribution medium), accompanied by the Corresponding Source fixed
|
||||||
|
on a durable physical medium customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product (including
|
||||||
|
a physical distribution medium), accompanied by a written offer, valid for
|
||||||
|
at least three years and valid for as long as you offer spare parts or customer
|
||||||
|
support for that product model, to give anyone who possesses the object code
|
||||||
|
either (1) a copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical medium customarily
|
||||||
|
used for software interchange, for a price no more than your reasonable cost
|
||||||
|
of physically performing this conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the written
|
||||||
|
offer to provide the Corresponding Source. This alternative is allowed only
|
||||||
|
occasionally and noncommercially, and only if you received the object code
|
||||||
|
with such an offer, in accord with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated place (gratis
|
||||||
|
or for a charge), and offer equivalent access to the Corresponding Source
|
||||||
|
in the same way through the same place at no further charge. You need not
|
||||||
|
require recipients to copy the Corresponding Source along with the object
|
||||||
|
code. If the place to copy the object code is a network server, the Corresponding
|
||||||
|
Source may be on a different server (operated by you or a third party) that
|
||||||
|
supports equivalent copying facilities, provided you maintain clear directions
|
||||||
|
next to the object code saying where to find the Corresponding Source. Regardless
|
||||||
|
of what server hosts the Corresponding Source, you remain obligated to ensure
|
||||||
|
that it is available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided you inform
|
||||||
|
other peers where the object code and Corresponding Source of the work are
|
||||||
|
being offered to the general public at no charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded from
|
||||||
|
the Corresponding Source as a System Library, need not be included in conveying
|
||||||
|
the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any tangible
|
||||||
|
personal property which is normally used for personal, family, or household
|
||||||
|
purposes, or (2) anything designed or sold for incorporation into a dwelling.
|
||||||
|
In determining whether a product is a consumer product, doubtful cases shall
|
||||||
|
be resolved in favor of coverage. For a particular product received by a
|
||||||
|
particular user, "normally used" refers to a typical or common use of that
|
||||||
|
class of product, regardless of the status of the particular user or of the
|
||||||
|
way in which the particular user actually uses, or expects or is expected
|
||||||
|
to use, the product. A product is a consumer product regardless of whether
|
||||||
|
the product has substantial commercial, industrial or non-consumer uses, unless
|
||||||
|
such uses represent the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods, procedures,
|
||||||
|
authorization keys, or other information required to install and execute modified
|
||||||
|
versions of a covered work in that User Product from a modified version of
|
||||||
|
its Corresponding Source. The information must suffice to ensure that the
|
||||||
|
continued functioning of the modified object code is in no case prevented
|
||||||
|
or interfered with solely because modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or specifically
|
||||||
|
for use in, a User Product, and the conveying occurs as part of a transaction
|
||||||
|
in which the right of possession and use of the User Product is transferred
|
||||||
|
to the recipient in perpetuity or for a fixed term (regardless of how the
|
||||||
|
transaction is characterized), the Corresponding Source conveyed under this
|
||||||
|
section must be accompanied by the Installation Information. But this requirement
|
||||||
|
does not apply if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has been installed
|
||||||
|
in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a requirement
|
||||||
|
to continue to provide support service, warranty, or updates for a work that
|
||||||
|
has been modified or installed by the recipient, or for the User Product in
|
||||||
|
which it has been modified or installed. Access to a network may be denied
|
||||||
|
when the modification itself materially and adversely affects the operation
|
||||||
|
of the network or violates the rules and protocols for communication across
|
||||||
|
the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided, in accord
|
||||||
|
with this section must be in a format that is publicly documented (and with
|
||||||
|
an implementation available to the public in source code form), and must require
|
||||||
|
no special password or key for unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
"Additional permissions" are terms that supplement the terms of this License
|
||||||
|
by making exceptions from one or more of its conditions. Additional permissions
|
||||||
|
that are applicable to the entire Program shall be treated as though they
|
||||||
|
were included in this License, to the extent that they are valid under applicable
|
||||||
|
law. If additional permissions apply only to part of the Program, that part
|
||||||
|
may be used separately under those permissions, but the entire Program remains
|
||||||
|
governed by this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option remove any
|
||||||
|
additional permissions from that copy, or from any part of it. (Additional
|
||||||
|
permissions may be written to require their own removal in certain cases when
|
||||||
|
you modify the work.) You may place additional permissions on material, added
|
||||||
|
by you to a covered work, for which you have or can give appropriate copyright
|
||||||
|
permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you add
|
||||||
|
to a covered work, you may (if authorized by the copyright holders of that
|
||||||
|
material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the terms of
|
||||||
|
sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or author
|
||||||
|
attributions in that material or in the Appropriate Legal Notices displayed
|
||||||
|
by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or requiring
|
||||||
|
that modified versions of such material be marked in reasonable ways as different
|
||||||
|
from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or authors
|
||||||
|
of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some trade names,
|
||||||
|
trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that material by
|
||||||
|
anyone who conveys the material (or modified versions of it) with contractual
|
||||||
|
assumptions of liability to the recipient, for any liability that these contractual
|
||||||
|
assumptions directly impose on those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further restrictions"
|
||||||
|
within the meaning of section 10. If the Program as you received it, or any
|
||||||
|
part of it, contains a notice stating that it is governed by this License
|
||||||
|
along with a term that is a further restriction, you may remove that term.
|
||||||
|
If a license document contains a further restriction but permits relicensing
|
||||||
|
or conveying under this License, you may add to a covered work material governed
|
||||||
|
by the terms of that license document, provided that the further restriction
|
||||||
|
does not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you must place,
|
||||||
|
in the relevant source files, a statement of the additional terms that apply
|
||||||
|
to those files, or a notice indicating where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the form
|
||||||
|
of a separately written license, or stated as exceptions; the above requirements
|
||||||
|
apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly provided
|
||||||
|
under this License. Any attempt otherwise to propagate or modify it is void,
|
||||||
|
and will automatically terminate your rights under this License (including
|
||||||
|
any patent licenses granted under the third paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your license from
|
||||||
|
a particular copyright holder is reinstated (a) provisionally, unless and
|
||||||
|
until the copyright holder explicitly and finally terminates your license,
|
||||||
|
and (b) permanently, if the copyright holder fails to notify you of the violation
|
||||||
|
by some reasonable means prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is reinstated permanently
|
||||||
|
if the copyright holder notifies you of the violation by some reasonable means,
|
||||||
|
this is the first time you have received notice of violation of this License
|
||||||
|
(for any work) from that copyright holder, and you cure the violation prior
|
||||||
|
to 30 days after your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the licenses
|
||||||
|
of parties who have received copies or rights from you under this License.
|
||||||
|
If your rights have been terminated and not permanently reinstated, you do
|
||||||
|
not qualify to receive new licenses for the same material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or run a copy
|
||||||
|
of the Program. Ancillary propagation of a covered work occurring solely
|
||||||
|
as a consequence of using peer-to-peer transmission to receive a copy likewise
|
||||||
|
does not require acceptance. However, nothing other than this License grants
|
||||||
|
you permission to propagate or modify any covered work. These actions infringe
|
||||||
|
copyright if you do not accept this License. Therefore, by modifying or propagating
|
||||||
|
a covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically receives
|
||||||
|
a license from the original licensors, to run, modify and propagate that work,
|
||||||
|
subject to this License. You are not responsible for enforcing compliance
|
||||||
|
by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an organization,
|
||||||
|
or substantially all assets of one, or subdividing an organization, or merging
|
||||||
|
organizations. If propagation of a covered work results from an entity transaction,
|
||||||
|
each party to that transaction who receives a copy of the work also receives
|
||||||
|
whatever licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the Corresponding
|
||||||
|
Source of the work from the predecessor in interest, if the predecessor has
|
||||||
|
it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the rights
|
||||||
|
granted or affirmed under this License. For example, you may not impose a
|
||||||
|
license fee, royalty, or other charge for exercise of rights granted under
|
||||||
|
this License, and you may not initiate litigation (including a cross-claim
|
||||||
|
or counterclaim in a lawsuit) alleging that any patent claim is infringed
|
||||||
|
by making, using, selling, offering for sale, or importing the Program or
|
||||||
|
any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this License
|
||||||
|
of the Program or a work on which the Program is based. The work thus licensed
|
||||||
|
is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims owned or controlled
|
||||||
|
by the contributor, whether already acquired or hereafter acquired, that would
|
||||||
|
be infringed by some manner, permitted by this License, of making, using,
|
||||||
|
or selling its contributor version, but do not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of the contributor
|
||||||
|
version. For purposes of this definition, "control" includes the right to
|
||||||
|
grant patent sublicenses in a manner consistent with the requirements of this
|
||||||
|
License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free patent
|
||||||
|
license under the contributor's essential patent claims, to make, use, sell,
|
||||||
|
offer for sale, import and otherwise run, modify and propagate the contents
|
||||||
|
of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express agreement
|
||||||
|
or commitment, however denominated, not to enforce a patent (such as an express
|
||||||
|
permission to practice a patent or covenant not to sue for patent infringement).
|
||||||
|
To "grant" such a patent license to a party means to make such an agreement
|
||||||
|
or commitment not to enforce a patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license, and the
|
||||||
|
Corresponding Source of the work is not available for anyone to copy, free
|
||||||
|
of charge and under the terms of this License, through a publicly available
|
||||||
|
network server or other readily accessible means, then you must either (1)
|
||||||
|
cause the Corresponding Source to be so available, or (2) arrange to deprive
|
||||||
|
yourself of the benefit of the patent license for this particular work, or
|
||||||
|
(3) arrange, in a manner consistent with the requirements of this License,
|
||||||
|
to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have actual
|
||||||
|
knowledge that, but for the patent license, your conveying the covered work
|
||||||
|
in a country, or your recipient's use of the covered work in a country, would
|
||||||
|
infringe one or more identifiable patents in that country that you have reason
|
||||||
|
to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or arrangement,
|
||||||
|
you convey, or propagate by procuring conveyance of, a covered work, and grant
|
||||||
|
a patent license to some of the parties receiving the covered work authorizing
|
||||||
|
them to use, propagate, modify or convey a specific copy of the covered work,
|
||||||
|
then the patent license you grant is automatically extended to all recipients
|
||||||
|
of the covered work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within the scope
|
||||||
|
of its coverage, prohibits the exercise of, or is conditioned on the non-exercise
|
||||||
|
of one or more of the rights that are specifically granted under this License.
|
||||||
|
You may not convey a covered work if you are a party to an arrangement with
|
||||||
|
a third party that is in the business of distributing software, under which
|
||||||
|
you make payment to the third party based on the extent of your activity of
|
||||||
|
conveying the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory patent
|
||||||
|
license (a) in connection with copies of the covered work conveyed by you
|
||||||
|
(or copies made from those copies), or (b) primarily for and in connection
|
||||||
|
with specific products or compilations that contain the covered work, unless
|
||||||
|
you entered into that arrangement, or that patent license was granted, prior
|
||||||
|
to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting any implied
|
||||||
|
license or other defenses to infringement that may otherwise be available
|
||||||
|
to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or otherwise)
|
||||||
|
that contradict the conditions of this License, they do not excuse you from
|
||||||
|
the conditions of this License. If you cannot convey a covered work so as
|
||||||
|
to satisfy simultaneously your obligations under this License and any other
|
||||||
|
pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey the
|
||||||
|
Program, the only way you could satisfy both those terms and this License
|
||||||
|
would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the Program,
|
||||||
|
your modified version must prominently offer all users interacting with it
|
||||||
|
remotely through a computer network (if your version supports such interaction)
|
||||||
|
an opportunity to receive the Corresponding Source of your version by providing
|
||||||
|
access to the Corresponding Source from a network server at no charge, through
|
||||||
|
some standard or customary means of facilitating copying of software. This
|
||||||
|
Corresponding Source shall include the Corresponding Source for any work covered
|
||||||
|
by version 3 of the GNU General Public License that is incorporated pursuant
|
||||||
|
to the following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have permission to
|
||||||
|
link or combine any covered work with a work licensed under version 3 of the
|
||||||
|
GNU General Public License into a single combined work, and to convey the
|
||||||
|
resulting work. The terms of this License will continue to apply to the part
|
||||||
|
which is the covered work, but the work with which it is combined will remain
|
||||||
|
governed by version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of the
|
||||||
|
GNU Affero General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to address
|
||||||
|
new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program specifies
|
||||||
|
that a certain numbered version of the GNU Affero General Public License "or
|
||||||
|
any later version" applies to it, you have the option of following the terms
|
||||||
|
and conditions either of that numbered version or of any later version published
|
||||||
|
by the Free Software Foundation. If the Program does not specify a version
|
||||||
|
number of the GNU Affero General Public License, you may choose any version
|
||||||
|
ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future versions of
|
||||||
|
the GNU Affero General Public License can be used, that proxy's public statement
|
||||||
|
of acceptance of a version permanently authorizes you to choose that version
|
||||||
|
for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different permissions.
|
||||||
|
However, no additional obligations are imposed on any author or copyright
|
||||||
|
holder as a result of your choosing to follow a later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
|
||||||
|
LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||||
|
OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||||
|
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
|
||||||
|
AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR
|
||||||
|
OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
|
||||||
|
ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM
|
||||||
|
AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
|
||||||
|
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO
|
||||||
|
USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
|
||||||
|
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
|
||||||
|
PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||||
|
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided above cannot
|
||||||
|
be given local legal effect according to their terms, reviewing courts shall
|
||||||
|
apply local law that most closely approximates an absolute waiver of all civil
|
||||||
|
liability in connection with the Program, unless a warranty or assumption
|
||||||
|
of liability accompanies a copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest possible
|
||||||
|
use to the public, the best way to achieve this is to make it free software
|
||||||
|
which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest to attach
|
||||||
|
them to the start of each source file to most effectively state the exclusion
|
||||||
|
of warranty; and each file should have at least the "copyright" line and a
|
||||||
|
pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License along
|
||||||
|
with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer network,
|
||||||
|
you should also make sure that it provides a way for users to get its source.
|
||||||
|
For example, if your program is a web application, its interface could display
|
||||||
|
a "Source" link that leads users to an archive of the code. There are many
|
||||||
|
ways you could offer source, and different solutions will be better for different
|
||||||
|
programs; see section 13 for the specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
|
more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
|
121
LICENSES/CC0-1.0.txt
Normal file
121
LICENSES/CC0-1.0.txt
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
CC0 1.0 Universal
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
|
Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||||
|
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
|
authorship and/or a database (each, a "Work").
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for
|
||||||
|
the purpose of contributing to a commons of creative, cultural and
|
||||||
|
scientific works ("Commons") that the public can reliably and without fear
|
||||||
|
of later claims of infringement build upon, modify, incorporate in other
|
||||||
|
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||||
|
and for any purposes, including without limitation commercial purposes.
|
||||||
|
These owners may contribute to the Commons to promote the ideal of a free
|
||||||
|
culture and the further production of creative, cultural and scientific
|
||||||
|
works, or to gain reputation or greater distribution for their Work in
|
||||||
|
part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any
|
||||||
|
expectation of additional consideration or compensation, the person
|
||||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||||
|
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||||
|
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not
|
||||||
|
limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
|
likeness depicted in a Work;
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(a), below;
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation
|
||||||
|
thereof, including any amended or successor version of such
|
||||||
|
directive); and
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
|
world based on applicable law or treaty, and any national
|
||||||
|
implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||||
|
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||||
|
of action, whether now known or unknown (including existing as well as
|
||||||
|
future claims and causes of action), in the Work (i) in all territories
|
||||||
|
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||||
|
treaty (including future time extensions), (iii) in any current or future
|
||||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||||
|
member of the public at large and to the detriment of Affirmer's heirs and
|
||||||
|
successors, fully intending that such Waiver shall not be subject to
|
||||||
|
revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||||
|
be judged legally invalid or ineffective under applicable law, then the
|
||||||
|
Waiver shall be preserved to the maximum extent permitted taking into
|
||||||
|
account Affirmer's express Statement of Purpose. In addition, to the
|
||||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||||
|
maximum duration provided by applicable law or treaty (including future
|
||||||
|
time extensions), (iii) in any current or future medium and for any number
|
||||||
|
of copies, and (iv) for any purpose whatsoever, including without
|
||||||
|
limitation commercial, advertising or promotional purposes (the
|
||||||
|
"License"). The License shall be deemed effective as of the date CC0 was
|
||||||
|
applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||||
|
of the License, and in such case Affirmer hereby affirms that he or she
|
||||||
|
will not (i) exercise any of his or her remaining Copyright and Related
|
||||||
|
Rights in the Work or (ii) assert any associated claims and causes of
|
||||||
|
action with respect to the Work, in either case contrary to Affirmer's
|
||||||
|
express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied,
|
||||||
|
statutory or otherwise, including without limitation warranties of
|
||||||
|
title, merchantability, fitness for a particular purpose, non
|
||||||
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
|
the greatest extent permissible under applicable law.
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the
|
||||||
|
Work.
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
3
LICENSES/GPL-3.0-linking-exception.txt
Normal file
3
LICENSES/GPL-3.0-linking-exception.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Additional permission under GNU GPL version 3 section 7
|
||||||
|
|
||||||
|
If you modify this Program, or any covered work, by linking or combining it with [name of library] (or a modified version of that library), containing parts covered by the terms of [name of library's license], the licensors of this Program grant you additional permission to convey the resulting work.
|
20
LICENSES/MIT.txt
Normal file
20
LICENSES/MIT.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) <year> <copyright holders>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
41
Makefile
Normal file
41
Makefile
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
CARGO = cargo
|
||||||
|
INSTALL = install
|
||||||
|
INSTALL_PROGRAM = $(INSTALL)
|
||||||
|
MKDIR_P = mkdir -p
|
||||||
|
PROFILE = release
|
||||||
|
|
||||||
|
prefix = /usr/local
|
||||||
|
exec_prefix = $(prefix)
|
||||||
|
bindir = $(exec_prefix)/bin
|
||||||
|
|
||||||
|
all: release
|
||||||
|
.PHONY: all
|
||||||
|
|
||||||
|
cargo-deps: vendor/github_schema.graphql src/merge_commit.graphql
|
||||||
|
.PHONY: cargo-deps
|
||||||
|
|
||||||
|
target/release/pr-tracker: cargo-deps
|
||||||
|
$(CARGO) build --release
|
||||||
|
|
||||||
|
target/debug/pr-tracker: cargo-deps
|
||||||
|
$(CARGO) build
|
||||||
|
|
||||||
|
check: cargo-deps
|
||||||
|
$(CARGO) test
|
||||||
|
.PHONY: check
|
||||||
|
|
||||||
|
install-dirs:
|
||||||
|
$(MKDIR_P) $(DESTDIR)$(bindir)
|
||||||
|
.PHONY: install-dirs
|
||||||
|
|
||||||
|
install: install-dirs target/$(PROFILE)/pr-tracker
|
||||||
|
$(INSTALL_PROGRAM) target/$(PROFILE)/pr-tracker \
|
||||||
|
$(DESTDIR)$(bindir)/pr-tracker
|
||||||
|
.PHONY: install
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f $(DESTDIR)$(bindir)/pr-tracker
|
||||||
|
.PHONY: uninstall
|
112
README
Normal file
112
README
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
pr-tracker
|
||||||
|
==========
|
||||||
|
|
||||||
|
Run a web server that displays the path a Nixpkgs pull request will
|
||||||
|
take through the various release channels.
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Build and runtime dependencies:
|
||||||
|
- libsystemd
|
||||||
|
- OpenSSL
|
||||||
|
|
||||||
|
Other build dependencies:
|
||||||
|
- Cargo
|
||||||
|
- rustc
|
||||||
|
- pkg-config
|
||||||
|
|
||||||
|
Other runtime dependencies:
|
||||||
|
- Git
|
||||||
|
|
||||||
|
In most cases, installation should be as simple as
|
||||||
|
|
||||||
|
make install
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The program must be supplied with a local checkout of the monitored
|
||||||
|
git repository, the remote name in the repository corresponding to
|
||||||
|
upstream Nixpkgs, a User-Agent string to use when contacting the
|
||||||
|
GitHub API, and a URL where users can download the program's source
|
||||||
|
code. Optionally, a "mount" path can be specified, which will be
|
||||||
|
prefixed to all of the server's routes, so that it can be served at a
|
||||||
|
non-root HTTP path.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
pr-tracker \
|
||||||
|
--path /var/lib/nixpkgs.git \
|
||||||
|
--remote nixpkgs \
|
||||||
|
--user-agent 'pr-tracker (alyssais)' \
|
||||||
|
--source-url https://example.com/pr-tracker.tar.gz \
|
||||||
|
--mount pr-tracker
|
||||||
|
|
||||||
|
Additionally, a GitHub API token should be supplied on pr-tracker's
|
||||||
|
standard input.
|
||||||
|
|
||||||
|
pr-tracker expects the socket(s) for it to listen on to be set up for
|
||||||
|
it by a service supervisor, using the systemd socket activation
|
||||||
|
protocol. It does not support binding its own sockets, but it can
|
||||||
|
still be run outside of systemd using by implementing the same
|
||||||
|
interface using utility programs, such as in this example that makes
|
||||||
|
use of the s6-networking[1] and execline[2] packages (example is
|
||||||
|
written in POSIX shell, not execline):
|
||||||
|
|
||||||
|
s6-tcpserver 0.0.0.0 8000 \
|
||||||
|
fdmove 3 0 \
|
||||||
|
env LISTEN_FDS=1 \
|
||||||
|
getpid LISTEN_PID
|
||||||
|
redirfd -r 0 /var/lib/pr-tracker/token \
|
||||||
|
pr-tracker [...]
|
||||||
|
|
||||||
|
Further information on available command line arguments can be
|
||||||
|
obtained with
|
||||||
|
|
||||||
|
pr-tracker --help
|
||||||
|
|
||||||
|
[1]: https://skarnet.org/software/s6-networking/
|
||||||
|
[2]: https://skarnet.org/software/execline/
|
||||||
|
|
||||||
|
Development
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The upstream git repository for pr-tracker is available at
|
||||||
|
<https://git.qyliss.net/pr-tracker/>.
|
||||||
|
|
||||||
|
Bugs and patches can be sent to the author,
|
||||||
|
Alyssa Ross <hi@alyssa.is>.
|
||||||
|
|
||||||
|
For information about how to use git to send a patch email, see
|
||||||
|
<https://git-send-email.io/>.
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Copyright 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation; either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public
|
||||||
|
License along with this program; if not, see
|
||||||
|
<https://www.gnu.org/licenses>.
|
||||||
|
|
||||||
|
Additional permission under GNU AGPL version 3 section 7
|
||||||
|
|
||||||
|
If you modify this Program, or any covered work, by linking or
|
||||||
|
combining it with OpenSSL (or a modified version of that library),
|
||||||
|
containing parts covered by the terms of the OpenSSL License, or the
|
||||||
|
Original SSLeay License, the licensors of this Program grant you
|
||||||
|
additional permission to convey the resulting work.
|
2
README.license
Normal file
2
README.license
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
|
SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
6
build.rs
Normal file
6
build.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rustc-link-lib=systemd")
|
||||||
|
}
|
62
src/branches.rs
Normal file
62
src/branches.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::{Regex, RegexSet};
|
||||||
|
|
||||||
|
const NEXT_BRANCH_TABLE: [(&str, &str); 8] = [
|
||||||
|
(r"\Astaging(-[\d.]+)?\z", "staging-next$1"),
|
||||||
|
(r"\Astaging-next\z", "master"),
|
||||||
|
(r"\Amaster\z", "nixpkgs-unstable"),
|
||||||
|
(r"\Amaster\z", "nixos-unstable-small"),
|
||||||
|
(r"\Anixos-(.*)-small\z", "nixos-$1"),
|
||||||
|
(r"\Arelease-([\d.]+)\z", "nixpkgs-$1-darwin"),
|
||||||
|
(r"\Arelease-([\d.]+)\z", "nixos-$1-small"),
|
||||||
|
(r"\Astaging-next-([\d.]*)\z", "release-$1"),
|
||||||
|
];
|
||||||
|
|
||||||
|
static BRANCH_NEXTS: Lazy<BTreeMap<&str, Vec<&str>>> = Lazy::new(|| {
|
||||||
|
NEXT_BRANCH_TABLE
|
||||||
|
.iter()
|
||||||
|
.fold(BTreeMap::new(), |mut map, (pattern, next)| {
|
||||||
|
map.entry(pattern).or_insert_with(Vec::new).push(next);
|
||||||
|
map
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
static BRANCH_NEXTS_BY_INDEX: Lazy<Vec<&Vec<&str>>> = Lazy::new(|| BRANCH_NEXTS.values().collect());
|
||||||
|
|
||||||
|
static BRANCH_PATTERNS: Lazy<Vec<Regex>> = Lazy::new(|| {
|
||||||
|
BRANCH_NEXTS
|
||||||
|
.keys()
|
||||||
|
.copied()
|
||||||
|
.map(Regex::new)
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
static BRANCH_REGEXES: Lazy<RegexSet> = Lazy::new(|| RegexSet::new(BRANCH_NEXTS.keys()).unwrap());
|
||||||
|
|
||||||
|
pub fn next_branches(branch: &str) -> Vec<Cow<str>> {
|
||||||
|
BRANCH_REGEXES
|
||||||
|
.matches(branch)
|
||||||
|
.iter()
|
||||||
|
.flat_map(|index| {
|
||||||
|
let regex = BRANCH_PATTERNS.get(index).unwrap();
|
||||||
|
BRANCH_NEXTS_BY_INDEX
|
||||||
|
.get(index)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(move |next| regex.replace(branch, *next))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_next_branches() {
|
||||||
|
let res = next_branches("release-20.09");
|
||||||
|
assert_eq!(res, vec!["nixpkgs-20.09-darwin", "nixos-20.09-small"])
|
||||||
|
}
|
139
src/github.rs
Normal file
139
src/github.rs
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use surf::http::headers::HeaderValue;
|
||||||
|
use surf::StatusCode;
|
||||||
|
|
||||||
|
type GitObjectID = String;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
NotFound,
|
||||||
|
Serialization(serde_json::Error),
|
||||||
|
Request(surf::Error),
|
||||||
|
Response(StatusCode),
|
||||||
|
Deserialization(http_types::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
use Error::*;
|
||||||
|
match self {
|
||||||
|
NotFound => write!(f, "Not found"),
|
||||||
|
Serialization(e) => write!(f, "Serialization error: {}", e),
|
||||||
|
Request(e) => write!(f, "Request error: {}", e),
|
||||||
|
Response(s) => write!(f, "Unexpected response status: {}", s),
|
||||||
|
Deserialization(e) => write!(f, "Deserialization error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "vendor/github_schema.graphql",
|
||||||
|
query_path = "src/merge_commit.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
struct MergeCommitQuery;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct GitHubGraphQLResponse<D> {
|
||||||
|
data: D,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PullRequestStatus {
|
||||||
|
Open,
|
||||||
|
Closed,
|
||||||
|
Merged {
|
||||||
|
/// This field is optional because GitHub doesn't provide us with this information
|
||||||
|
/// for PRs merged before around March 2016.
|
||||||
|
merge_commit_oid: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MergeInfo {
|
||||||
|
pub branch: String,
|
||||||
|
pub status: PullRequestStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GitHub<'a> {
|
||||||
|
token: &'a OsStr,
|
||||||
|
user_agent: &'a OsStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GitHub<'a> {
|
||||||
|
pub fn new(token: &'a OsStr, user_agent: &'a OsStr) -> Self {
|
||||||
|
Self { token, user_agent }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authorization_header(&self) -> Result<HeaderValue, surf::Error> {
|
||||||
|
let mut value = b"bearer ".to_vec();
|
||||||
|
value.extend_from_slice(self.token.as_bytes());
|
||||||
|
Ok(HeaderValue::from_bytes(value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn merge_info_for_nixpkgs_pr(&self, pr: i64) -> Result<MergeInfo, Error> {
|
||||||
|
let query = MergeCommitQuery::build_query(merge_commit_query::Variables {
|
||||||
|
owner: "NixOS".to_string(),
|
||||||
|
repo: "nixpkgs".to_string(),
|
||||||
|
number: pr,
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = surf::post("https://api.github.com/graphql")
|
||||||
|
.header("Accept", "application/vnd.github.merge-info-preview+json")
|
||||||
|
.header(
|
||||||
|
"User-Agent",
|
||||||
|
HeaderValue::from_bytes(self.user_agent.as_bytes().to_vec())
|
||||||
|
.map_err(Error::Request)?,
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
"Authorization",
|
||||||
|
self.authorization_header().map_err(Error::Request)?,
|
||||||
|
)
|
||||||
|
.body(serde_json::to_vec(&query).map_err(Error::Serialization)?)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Request)?;
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
if status == StatusCode::NotFound || status == StatusCode::Gone {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
} else if !status.is_success() {
|
||||||
|
return Err(Error::Response(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: GitHubGraphQLResponse<merge_commit_query::ResponseData> = dbg!(response)
|
||||||
|
.body_json()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Deserialization)?;
|
||||||
|
|
||||||
|
let pr = data
|
||||||
|
.data
|
||||||
|
.repository
|
||||||
|
.and_then(|repo| repo.pull_request)
|
||||||
|
.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
|
Ok(MergeInfo {
|
||||||
|
branch: pr.base_ref_name,
|
||||||
|
status: if pr.merged {
|
||||||
|
PullRequestStatus::Merged {
|
||||||
|
merge_commit_oid: pr.merge_commit.map(|commit| commit.oid),
|
||||||
|
}
|
||||||
|
} else if pr.closed {
|
||||||
|
PullRequestStatus::Closed
|
||||||
|
} else {
|
||||||
|
PullRequestStatus::Open
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
211
src/main.rs
Normal file
211
src/main.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
mod branches;
|
||||||
|
mod github;
|
||||||
|
mod nixpkgs;
|
||||||
|
mod systemd;
|
||||||
|
mod tree;
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use askama::Template;
|
||||||
|
use async_std::io;
|
||||||
|
use async_std::net::TcpListener;
|
||||||
|
use async_std::os::unix::io::FromRawFd;
|
||||||
|
use async_std::os::unix::net::UnixListener;
|
||||||
|
use async_std::pin::Pin;
|
||||||
|
use async_std::prelude::*;
|
||||||
|
use async_std::process::exit;
|
||||||
|
use futures_util::future::join_all;
|
||||||
|
use http_types::mime;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
use tide::{Request, Response};
|
||||||
|
|
||||||
|
use github::{GitHub, PullRequestStatus};
|
||||||
|
use nixpkgs::Nixpkgs;
|
||||||
|
use systemd::{is_socket_inet, is_socket_unix, listen_fds};
|
||||||
|
use tree::Tree;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
struct Config {
|
||||||
|
#[structopt(long, parse(from_os_str))]
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
#[structopt(long, parse(from_os_str))]
|
||||||
|
remote: PathBuf,
|
||||||
|
|
||||||
|
#[structopt(long, parse(from_os_str))]
|
||||||
|
user_agent: OsString,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
source_url: String,
|
||||||
|
|
||||||
|
#[structopt(long, default_value = "/")]
|
||||||
|
mount: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
static CONFIG: Lazy<Config> = Lazy::new(Config::from_args);
|
||||||
|
|
||||||
|
static GITHUB_TOKEN: Lazy<OsString> = Lazy::new(|| {
|
||||||
|
use std::io::{stdin, BufRead, BufReader};
|
||||||
|
use std::os::unix::prelude::*;
|
||||||
|
|
||||||
|
let mut bytes = Vec::with_capacity(41);
|
||||||
|
if let Err(e) = BufReader::new(stdin()).read_until(b'\n', &mut bytes) {
|
||||||
|
eprintln!("pr-tracker: read: {}", e);
|
||||||
|
exit(74)
|
||||||
|
}
|
||||||
|
if bytes.last() == Some(&b'\n') {
|
||||||
|
bytes.pop();
|
||||||
|
}
|
||||||
|
OsString::from_vec(bytes)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Template)]
|
||||||
|
#[template(path = "page.html")]
|
||||||
|
struct PageTemplate {
|
||||||
|
error: Option<String>,
|
||||||
|
pr_number: Option<String>,
|
||||||
|
closed: bool,
|
||||||
|
tree: Option<Tree>,
|
||||||
|
source_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Query {
|
||||||
|
pr: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn track_pr(pr_number: Option<String>, status: &mut u16, page: &mut PageTemplate) {
|
||||||
|
let pr_number = match pr_number {
|
||||||
|
Some(pr_number) => pr_number,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pr_number_i64 = match pr_number.parse() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_) => {
|
||||||
|
*status = 400;
|
||||||
|
page.error = Some(format!("Invalid PR number: {}", pr_number));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let github = GitHub::new(&GITHUB_TOKEN, &CONFIG.user_agent);
|
||||||
|
|
||||||
|
let merge_info = match github.merge_info_for_nixpkgs_pr(pr_number_i64).await {
|
||||||
|
Err(github::Error::NotFound) => {
|
||||||
|
*status = 404;
|
||||||
|
page.error = Some(format!("No such nixpkgs PR #{}.", pr_number_i64));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
*status = 500;
|
||||||
|
page.error = Some(e.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(info) => info,
|
||||||
|
};
|
||||||
|
|
||||||
|
page.pr_number = Some(pr_number);
|
||||||
|
|
||||||
|
if matches!(merge_info.status, PullRequestStatus::Closed) {
|
||||||
|
page.closed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nixpkgs = Nixpkgs::new(&CONFIG.path, &CONFIG.remote);
|
||||||
|
let tree = Tree::make(merge_info.branch.to_string(), &merge_info.status, &nixpkgs).await;
|
||||||
|
|
||||||
|
if let github::PullRequestStatus::Merged {
|
||||||
|
merge_commit_oid, ..
|
||||||
|
} = merge_info.status
|
||||||
|
{
|
||||||
|
if merge_commit_oid.is_none() {
|
||||||
|
page.error = Some("For older PRs, GitHub doesn't tell us the merge commit, so we're unable to track this PR past being merged.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page.tree = Some(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_request<S>(request: Request<S>) -> http_types::Result<Response> {
|
||||||
|
let mut status = 200;
|
||||||
|
let mut page = PageTemplate {
|
||||||
|
source_url: CONFIG.source_url.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pr_number = request.query::<Query>()?.pr;
|
||||||
|
|
||||||
|
track_pr(pr_number, &mut status, &mut page).await;
|
||||||
|
|
||||||
|
Ok(Response::builder(status)
|
||||||
|
.content_type(mime::HTML)
|
||||||
|
.body(page.render()?)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::main]
|
||||||
|
async fn main() {
|
||||||
|
fn handle_error<T, E>(result: Result<T, E>, code: i32, message: impl AsRef<str>) -> T
|
||||||
|
where
|
||||||
|
E: std::error::Error,
|
||||||
|
{
|
||||||
|
match result {
|
||||||
|
Ok(v) => return v,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("pr-tracker: {}: {}", message.as_ref(), e);
|
||||||
|
exit(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure arguments are parsed before starting server.
|
||||||
|
let _ = *CONFIG;
|
||||||
|
let _ = *GITHUB_TOKEN;
|
||||||
|
|
||||||
|
let mut server = tide::new();
|
||||||
|
let mut root = server.at(&CONFIG.mount);
|
||||||
|
|
||||||
|
root.at("/").get(handle_request);
|
||||||
|
|
||||||
|
let fd_count = handle_error(listen_fds(true), 71, "sd_listen_fds");
|
||||||
|
|
||||||
|
if fd_count == 0 {
|
||||||
|
eprintln!("pr-tracker: No listen file descriptors given");
|
||||||
|
exit(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut listeners: Vec<Pin<Box<dyn Future<Output = _>>>> = Vec::new();
|
||||||
|
|
||||||
|
for fd in (3..).into_iter().take(fd_count as usize) {
|
||||||
|
let s = server.clone();
|
||||||
|
if handle_error(is_socket_inet(fd), 74, "sd_is_socket_inet") {
|
||||||
|
listeners.push(Box::pin(s.listen(unsafe { TcpListener::from_raw_fd(fd) })));
|
||||||
|
} else if handle_error(is_socket_unix(fd), 74, "sd_is_socket_unix") {
|
||||||
|
listeners.push(Box::pin(s.listen(unsafe { UnixListener::from_raw_fd(fd) })));
|
||||||
|
} else {
|
||||||
|
eprintln!("pr-tracker: file descriptor {} is not a socket", fd);
|
||||||
|
exit(64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let errors: Vec<_> = join_all(listeners)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(io::Result::err)
|
||||||
|
.collect();
|
||||||
|
for error in errors.iter() {
|
||||||
|
eprintln!("pr-tracker: listen: {}", error);
|
||||||
|
}
|
||||||
|
if !errors.is_empty() {
|
||||||
|
exit(74);
|
||||||
|
}
|
||||||
|
}
|
15
src/merge_commit.graphql
Normal file
15
src/merge_commit.graphql
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
query MergeCommitQuery($owner: String!, $repo: String!, $number: Int!) {
|
||||||
|
repository(owner: $owner, name: $repo) {
|
||||||
|
pullRequest(number: $number) {
|
||||||
|
baseRefName
|
||||||
|
mergeCommit {
|
||||||
|
oid
|
||||||
|
}
|
||||||
|
merged
|
||||||
|
closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
src/nixpkgs.rs
Normal file
127
src/nixpkgs.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
use std::os::unix::prelude::*;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::ExitStatus;
|
||||||
|
|
||||||
|
use async_std::io;
|
||||||
|
use async_std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Io(io::Error),
|
||||||
|
ExitFailure(ExitStatus),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
use Error::*;
|
||||||
|
match self {
|
||||||
|
Io(e) => write!(f, "git: {}", e),
|
||||||
|
ExitFailure(e) => match e.code() {
|
||||||
|
Some(code) => write!(f, "git exited {}", code),
|
||||||
|
None => write!(f, "git killed by signal {}", e.signal().unwrap()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
fn check_status(status: ExitStatus) -> Result<()> {
|
||||||
|
if status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::ExitFailure(status))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Nixpkgs<'a> {
|
||||||
|
path: &'a Path,
|
||||||
|
remote_name: &'a Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Nixpkgs<'a> {
|
||||||
|
pub fn new(path: &'a Path, remote_name: &'a Path) -> Self {
|
||||||
|
Self { path, remote_name }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn git_command(&self, subcommand: impl AsRef<OsStr>) -> Command {
|
||||||
|
let mut command = Command::new("git");
|
||||||
|
command.arg("-C");
|
||||||
|
command.arg(&self.path);
|
||||||
|
command.arg(subcommand);
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn git_branch_contains(&self, commit: &str) -> Result<Vec<u8>> {
|
||||||
|
let output = self
|
||||||
|
.git_command("branch")
|
||||||
|
.args(&["-r", "--format=%(refname)", "--contains"])
|
||||||
|
.arg(commit)
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Io)?;
|
||||||
|
|
||||||
|
check_status(output.status)?;
|
||||||
|
|
||||||
|
Ok(output.stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn git_fetch_nixpkgs(&self) -> Result<()> {
|
||||||
|
// TODO: add refspecs
|
||||||
|
self.git_command("fetch")
|
||||||
|
.arg(&self.remote_name)
|
||||||
|
.status()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Io)
|
||||||
|
.and_then(check_status)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn branches_containing_commit(
|
||||||
|
&self,
|
||||||
|
commit: &str,
|
||||||
|
out: &mut BTreeSet<OsString>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let output = match self.git_branch_contains(commit).await {
|
||||||
|
Err(Error::ExitFailure(status)) if status.code().is_some() => {
|
||||||
|
eprintln!("pr-tracker: git branch --contains failed; updating branches");
|
||||||
|
|
||||||
|
if let Err(e) = self.git_fetch_nixpkgs().await {
|
||||||
|
eprintln!("pr-tracker: fetching nixpkgs: {}", e);
|
||||||
|
// Carry on, because it might have fetched what we
|
||||||
|
// need before dying.
|
||||||
|
}
|
||||||
|
|
||||||
|
self.git_branch_contains(commit).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut prefix = PathBuf::from("refs/remotes/");
|
||||||
|
prefix.push(&self.remote_name);
|
||||||
|
|
||||||
|
for branch_name in output
|
||||||
|
.split(|byte| *byte == b'\n')
|
||||||
|
.filter(|b| !b.is_empty())
|
||||||
|
.map(OsStr::from_bytes)
|
||||||
|
.map(Path::new)
|
||||||
|
.filter_map(|r| r.strip_prefix(&prefix).ok())
|
||||||
|
.map(Into::into)
|
||||||
|
{
|
||||||
|
out.insert(branch_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
49
src/systemd.rs
Normal file
49
src/systemd.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::os::raw::{c_char, c_int, c_uint};
|
||||||
|
use std::os::unix::prelude::*;
|
||||||
|
use std::ptr::null;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn sd_listen_fds(unset_environment: c_int) -> c_int;
|
||||||
|
fn sd_is_socket_inet(
|
||||||
|
fd: c_int,
|
||||||
|
family: c_int,
|
||||||
|
type_: c_int,
|
||||||
|
listening: c_int,
|
||||||
|
port: u16,
|
||||||
|
) -> c_int;
|
||||||
|
fn sd_is_socket_unix(
|
||||||
|
fd: c_int,
|
||||||
|
type_: c_int,
|
||||||
|
listening: c_int,
|
||||||
|
path: *const c_char,
|
||||||
|
length: usize,
|
||||||
|
) -> c_int;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen_fds(unset_environment: bool) -> io::Result<c_uint> {
|
||||||
|
let r = unsafe { sd_listen_fds(if unset_environment { 1 } else { 0 }) };
|
||||||
|
if r < 0 {
|
||||||
|
return Err(io::Error::from_raw_os_error(-r));
|
||||||
|
}
|
||||||
|
Ok(r as c_uint)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_socket_inet(fd: RawFd) -> io::Result<bool> {
|
||||||
|
let r = unsafe { sd_is_socket_inet(fd, 0, 0, -1, 0) };
|
||||||
|
if r < 0 {
|
||||||
|
return Err(io::Error::from_raw_os_error(-r));
|
||||||
|
}
|
||||||
|
Ok(r != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_socket_unix(fd: RawFd) -> io::Result<bool> {
|
||||||
|
let r = unsafe { sd_is_socket_unix(fd, 0, -1, null(), 0) };
|
||||||
|
if r < 0 {
|
||||||
|
return Err(io::Error::from_raw_os_error(-r));
|
||||||
|
}
|
||||||
|
Ok(r != 0)
|
||||||
|
}
|
91
src/tree.rs
Normal file
91
src/tree.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
|
||||||
|
use askama::Template;
|
||||||
|
|
||||||
|
use crate::branches::next_branches;
|
||||||
|
use crate::github;
|
||||||
|
use crate::nixpkgs::Nixpkgs;
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "tree.html")]
|
||||||
|
pub struct Tree {
|
||||||
|
branch_name: String,
|
||||||
|
accepted: Option<bool>,
|
||||||
|
children: Vec<Tree>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tree {
|
||||||
|
fn generate(branch: String, found_branches: &mut BTreeSet<OsString>) -> Tree {
|
||||||
|
found_branches.insert((&branch).into());
|
||||||
|
|
||||||
|
let nexts = next_branches(&branch)
|
||||||
|
.into_iter()
|
||||||
|
.map(|b| Self::generate(b.to_string(), found_branches))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Tree {
|
||||||
|
accepted: None,
|
||||||
|
branch_name: branch,
|
||||||
|
children: nexts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_accepted(&mut self, branches: &BTreeSet<OsString>, missing_means_absent: bool) {
|
||||||
|
self.accepted = match branches.contains(OsStr::new(&self.branch_name)) {
|
||||||
|
true => Some(true),
|
||||||
|
false if missing_means_absent => Some(false),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
for child in self.children.iter_mut() {
|
||||||
|
child.fill_accepted(branches, missing_means_absent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn make(base_branch: String, merge_status: &github::PullRequestStatus, nixpkgs: &Nixpkgs<'_>) -> Tree {
|
||||||
|
let mut missing_means_absent = true;
|
||||||
|
let mut branches = BTreeSet::new();
|
||||||
|
|
||||||
|
let mut tree = Self::generate(base_branch.clone(), &mut branches);
|
||||||
|
|
||||||
|
if let github::PullRequestStatus::Merged {
|
||||||
|
merge_commit_oid, ..
|
||||||
|
} = merge_status
|
||||||
|
{
|
||||||
|
if let Some(merge_commit) = merge_commit_oid {
|
||||||
|
let mut containing_commits = BTreeSet::new();
|
||||||
|
|
||||||
|
if let Err(e) =
|
||||||
|
nixpkgs.branches_containing_commit(&merge_commit, &mut containing_commits)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
eprintln!("pr-tracker: branches_containing_commit: {}", e);
|
||||||
|
missing_means_absent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
branches = branches
|
||||||
|
.intersection(&containing_commits)
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
} else {
|
||||||
|
branches.clear();
|
||||||
|
missing_means_absent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even if something goes wrong with our local Git repo,
|
||||||
|
// or GitHub didn't tell us the merge commit, we know that
|
||||||
|
// the base branch of the PR must contain the commit,
|
||||||
|
// because GitHub told us it was merged into it.
|
||||||
|
branches.insert(base_branch.into());
|
||||||
|
} else {
|
||||||
|
branches.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.fill_accepted(&branches, missing_means_absent);
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
}
|
203
templates/page.html
Normal file
203
templates/page.html
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception -->
|
||||||
|
<!-- SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is> -->
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{% match pr_number %}
|
||||||
|
{% when Some with (pr_number) %}
|
||||||
|
<title>Nixpkgs PR #{{ pr_number }} progress</title>
|
||||||
|
{% else %}
|
||||||
|
<title>Nixpkgs PR progress tracker</title>
|
||||||
|
{% endmatch %}
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
line-height: 1;
|
||||||
|
font-family: sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > header {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr {
|
||||||
|
width: 6ch;
|
||||||
|
box-sizing: content-box;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > section {
|
||||||
|
background: #c4b0b0;
|
||||||
|
padding: 0 1em;
|
||||||
|
margin: 1em auto;
|
||||||
|
display: flex;
|
||||||
|
max-width: 50ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > main {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > main > ol {
|
||||||
|
text-align: left;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul > li {
|
||||||
|
margin-left: 2em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul > li:last-child {
|
||||||
|
margin-left: 0;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 1em 0;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: transparent;
|
||||||
|
position: relative;
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
span::after {
|
||||||
|
content: "";
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: block;
|
||||||
|
border: .3em solid #7A877D;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.state-pending::after {
|
||||||
|
background: #C2C9C2;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.state-unknown::after {
|
||||||
|
background: #C4A500;
|
||||||
|
content: "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
span.state-accepted::after {
|
||||||
|
background: #00C42D;
|
||||||
|
content: "✔";
|
||||||
|
}
|
||||||
|
|
||||||
|
span.state-rejected::after {
|
||||||
|
background: #c40000;
|
||||||
|
content: "❌︎";
|
||||||
|
}
|
||||||
|
|
||||||
|
ul span::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 42.5%;
|
||||||
|
bottom: 42.5%;
|
||||||
|
right: .5em;
|
||||||
|
left: -1em;
|
||||||
|
display: block;
|
||||||
|
background: #7A877D;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul > li:last-child > span::before {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol::before, ul::before {
|
||||||
|
background: #7A877D;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
left: .85em;
|
||||||
|
top: 0.5em;
|
||||||
|
bottom: 1em;
|
||||||
|
width: .3em;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Nixpkgs Pull Request Tracker</h1>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<label for="pr">PR number: </label>
|
||||||
|
<input id="pr" name="pr" type="text" pattern="[1-9][0-9]*"
|
||||||
|
value="{%- match pr_number -%}
|
||||||
|
{%- when Some with (pr_number) -%}
|
||||||
|
{{- pr_number -}}
|
||||||
|
{%- else -%}
|
||||||
|
{%- endmatch -%}">
|
||||||
|
<button type="submit">Track</button>
|
||||||
|
</form>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{% match error %}
|
||||||
|
{% when Some with (error) %}
|
||||||
|
<section>
|
||||||
|
<p>{{ error }}</p>
|
||||||
|
</section>
|
||||||
|
{% else %}
|
||||||
|
{% endmatch %}
|
||||||
|
|
||||||
|
{% match pr_number %}
|
||||||
|
{% when Some with (pr_number) %}
|
||||||
|
<main>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
{% if closed %}
|
||||||
|
<span class="state-rejected">❌</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="state-accepted">✅</span>
|
||||||
|
{% endif %}
|
||||||
|
PR <a href="https://github.com/NixOS/nixpkgs/pull/{{ pr_number }}">#{{ pr_number }}</a>
|
||||||
|
{% if closed %}
|
||||||
|
closed
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% match tree %}
|
||||||
|
{% when Some with (tree) %}
|
||||||
|
{{ tree|safe }}
|
||||||
|
{% else %}
|
||||||
|
{% endmatch %}
|
||||||
|
</ol>
|
||||||
|
</main>
|
||||||
|
{% else %}
|
||||||
|
{% endmatch %}
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>By <a href="https://alyssa.is/">Alyssa Ross</a></p>
|
||||||
|
|
||||||
|
<p><a href="{{ source_url }}">Source code</a></p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
23
templates/tree.html
Normal file
23
templates/tree.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception -->
|
||||||
|
<!-- SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is> -->
|
||||||
|
|
||||||
|
<li>
|
||||||
|
{% match accepted %}
|
||||||
|
{% when Some with (true) %}
|
||||||
|
<span class="state-accepted">✅</span>
|
||||||
|
{% when Some with (false) %}
|
||||||
|
<span class="state-pending">⚪</span>
|
||||||
|
{% when None %}
|
||||||
|
<span class="state-unknown">❓</span>
|
||||||
|
{% endmatch %}
|
||||||
|
|
||||||
|
{{ branch_name }}
|
||||||
|
|
||||||
|
{% if !children.is_empty() %}
|
||||||
|
<ul>
|
||||||
|
{% for child in children %}
|
||||||
|
{{ child|safe }}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
39245
vendor/github_schema.graphql
vendored
Normal file
39245
vendor/github_schema.graphql
vendored
Normal file
File diff suppressed because it is too large
Load diff
2
vendor/github_schema.graphql.license
vendored
Normal file
2
vendor/github_schema.graphql.license
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
SPDX-FileCopyrightText: 2017 Gregor Martynus
|
Loading…
Reference in a new issue