Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
Re-License to "MIT OR Apache-2.0"
Merged fintohaps opened 5 months ago

The main dependant of radicle-git, and its crates, is heartwood. It is licensed under MIT or Apache-2.0, which has been incompatible with the GPL-3.0 license. Since the intellectual property of this code base is with the Better Internet Foundation, it was decided to re-license code in this repository to make the upstream repository compatible.

The changes required were:

  • Remove the license headers (and instructions to add them)
  • Add MIT and Apache-2.0 licenses
  • Remove the GPL-3.0 license
  • Change the license declarations in the Cargo.toml files
116 files changed +208 -5779 6aa5d631 b570fa14
modified CONTRIBUTING.md
@@ -25,13 +25,5 @@ If you did not sign-off one or more of your commits then it is not all for not.
The crowd at `src-d` have a [wonderful guide][fixing-dco] on how to remedy this
situation.

-
## License Header
-

-
As part of our license, we must include a license header at the top of each
-
source file. The template for this header can be found [here][header-template].
-
If you are creating a new file in the repository you will have to add this
-
header to the file.
-

[dco]: ./DCO
[fixing-dco]: https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md
-
[header-template]: ./.license-header
deleted LICENSE
@@ -1,639 +0,0 @@
-
radicle-git
-
Copyright © 2019-2021 The Radicle Foundation <hello@radicle.foundation>
-
Copyright © 2021-2022 The Radicle Link Contributors
-
<https://github.com/radicle-dev/radicle-link/graphs/contributors>
-
Copyright © 2022 The Radicle Git Contributors
-
<https://github.com/radicle-dev/radicle-git/graphs/contributors>
-

-
radicle-link is licensed under version 3 of the GNU General Public License
-
(GPLv3). Any contribution to radicle-link must be made subject to the
-
Developer Certificate of Origin (DCO).
-

-
By making your contribution, you are making the declaration set out in the
-
Linux Foundation’s Developer Certificate of Origin version 1.1, in which
-
the “open source licence indicated in the file” is GPLv3. A copy of the
-
DCO can be found in the file named “DCO”.
-

-
--------------------------------------------------------------------------
-

-
                      GNU GENERAL PUBLIC LICENSE
-
                       Version 3, 29 June 2007
-

-
 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
-
 Everyone is permitted to copy and distribute verbatim copies
-
 of this license document, but changing it is not allowed.
-

-
                            Preamble
-

-
  The GNU General Public License is a free, copyleft license for
-
software and other kinds of works.
-

-
  The licenses for most software and other practical works are designed
-
to take away your freedom to share and change the works.  By contrast,
-
the GNU General Public License is 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.  We, the Free Software Foundation, use the
-
GNU General Public License for most of our software; it applies also to
-
any other work released this way by its authors.  You can apply it to
-
your programs, too.
-

-
  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.
-

-
  To protect your rights, we need to prevent others from denying you
-
these rights or asking you to surrender the rights.  Therefore, you have
-
certain responsibilities if you distribute copies of the software, or if
-
you modify it: responsibilities to respect the freedom of others.
-

-
  For example, if you distribute copies of such a program, whether
-
gratis or for a fee, you must pass on to the recipients the same
-
freedoms that you received.  You must make sure that they, too, receive
-
or can get the source code.  And you must show them these terms so they
-
know their rights.
-

-
  Developers that use the GNU GPL protect your rights with two steps:
-
(1) assert copyright on the software, and (2) offer you this License
-
giving you legal permission to copy, distribute and/or modify it.
-

-
  For the developers' and authors' protection, the GPL clearly explains
-
that there is no warranty for this free software.  For both users' and
-
authors' sake, the GPL requires that modified versions be marked as
-
changed, so that their problems will not be attributed erroneously to
-
authors of previous versions.
-

-
  Some devices are designed to deny users access to install or run
-
modified versions of the software inside them, although the manufacturer
-
can do so.  This is fundamentally incompatible with the aim of
-
protecting users' freedom to change the software.  The systematic
-
pattern of such abuse occurs in the area of products for individuals to
-
use, which is precisely where it is most unacceptable.  Therefore, we
-
have designed this version of the GPL to prohibit the practice for those
-
products.  If such problems arise substantially in other domains, we
-
stand ready to extend this provision to those domains in future versions
-
of the GPL, as needed to protect the freedom of users.
-

-
  Finally, every program is threatened constantly by software patents.
-
States should not allow patents to restrict development and use of
-
software on general-purpose computers, but in those that do, we wish to
-
avoid the special danger that patents applied to a free program could
-
make it effectively proprietary.  To prevent this, the GPL assures that
-
patents cannot be used to render the program non-free.
-

-
  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 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. Use with the GNU Affero General Public License.
-

-
  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 Affero 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 special requirements of the GNU Affero General Public License,
-
section 13, concerning interaction through a network will apply to the
-
combination as such.
-

-
  14. Revised Versions of this License.
-

-
  The Free Software Foundation may publish revised and/or new versions of
-
the GNU 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 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 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 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
added LICENSE-APACHE
@@ -0,0 +1,176 @@
+
                              Apache License
+
                        Version 2.0, January 2004
+
                     http://www.apache.org/licenses/
+

+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+

+
1. Definitions.
+

+
   "License" shall mean the terms and conditions for use, reproduction,
+
   and distribution as defined by Sections 1 through 9 of this document.
+

+
   "Licensor" shall mean the copyright owner or entity authorized by
+
   the copyright owner that is granting the License.
+

+
   "Legal Entity" shall mean the union of the acting entity and all
+
   other entities that control, are controlled by, or are under common
+
   control with that entity. For the purposes of this definition,
+
   "control" means (i) the power, direct or indirect, to cause the
+
   direction or management of such entity, whether by contract or
+
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+
   outstanding shares, or (iii) beneficial ownership of such entity.
+

+
   "You" (or "Your") shall mean an individual or Legal Entity
+
   exercising permissions granted by this License.
+

+
   "Source" form shall mean the preferred form for making modifications,
+
   including but not limited to software source code, documentation
+
   source, and configuration files.
+

+
   "Object" form shall mean any form resulting from mechanical
+
   transformation or translation of a Source form, including but
+
   not limited to compiled object code, generated documentation,
+
   and conversions to other media types.
+

+
   "Work" shall mean the work of authorship, whether in Source or
+
   Object form, made available under the License, as indicated by a
+
   copyright notice that is included in or attached to the work
+
   (an example is provided in the Appendix below).
+

+
   "Derivative Works" shall mean any work, whether in Source or Object
+
   form, that is based on (or derived from) the Work and for which the
+
   editorial revisions, annotations, elaborations, or other modifications
+
   represent, as a whole, an original work of authorship. For the purposes
+
   of this License, Derivative Works shall not include works that remain
+
   separable from, or merely link (or bind by name) to the interfaces of,
+
   the Work and Derivative Works thereof.
+

+
   "Contribution" shall mean any work of authorship, including
+
   the original version of the Work and any modifications or additions
+
   to that Work or Derivative Works thereof, that is intentionally
+
   submitted to Licensor for inclusion in the Work by the copyright owner
+
   or by an individual or Legal Entity authorized to submit on behalf of
+
   the copyright owner. For the purposes of this definition, "submitted"
+
   means any form of electronic, verbal, or written communication sent
+
   to the Licensor or its representatives, including but not limited to
+
   communication on electronic mailing lists, source code control systems,
+
   and issue tracking systems that are managed by, or on behalf of, the
+
   Licensor for the purpose of discussing and improving the Work, but
+
   excluding communication that is conspicuously marked or otherwise
+
   designated in writing by the copyright owner as "Not a Contribution."
+

+
   "Contributor" shall mean Licensor and any individual or Legal Entity
+
   on behalf of whom a Contribution has been received by Licensor and
+
   subsequently incorporated within the Work.
+

+
2. Grant of Copyright License. Subject to the terms and conditions of
+
   this License, each Contributor hereby grants to You a perpetual,
+
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+
   copyright license to reproduce, prepare Derivative Works of,
+
   publicly display, publicly perform, sublicense, and distribute the
+
   Work and such Derivative Works in Source or Object form.
+

+
3. Grant of Patent License. Subject to the terms and conditions of
+
   this License, each Contributor hereby grants to You a perpetual,
+
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+
   (except as stated in this section) patent license to make, have made,
+
   use, offer to sell, sell, import, and otherwise transfer the Work,
+
   where such license applies only to those patent claims licensable
+
   by such Contributor that are necessarily infringed by their
+
   Contribution(s) alone or by combination of their Contribution(s)
+
   with the Work to which such Contribution(s) was submitted. If You
+
   institute patent litigation against any entity (including a
+
   cross-claim or counterclaim in a lawsuit) alleging that the Work
+
   or a Contribution incorporated within the Work constitutes direct
+
   or contributory patent infringement, then any patent licenses
+
   granted to You under this License for that Work shall terminate
+
   as of the date such litigation is filed.
+

+
4. Redistribution. You may reproduce and distribute copies of the
+
   Work or Derivative Works thereof in any medium, with or without
+
   modifications, and in Source or Object form, provided that You
+
   meet the following conditions:
+

+
   (a) You must give any other recipients of the Work or
+
       Derivative Works a copy of this License; and
+

+
   (b) You must cause any modified files to carry prominent notices
+
       stating that You changed the files; and
+

+
   (c) You must retain, in the Source form of any Derivative Works
+
       that You distribute, all copyright, patent, trademark, and
+
       attribution notices from the Source form of the Work,
+
       excluding those notices that do not pertain to any part of
+
       the Derivative Works; and
+

+
   (d) If the Work includes a "NOTICE" text file as part of its
+
       distribution, then any Derivative Works that You distribute must
+
       include a readable copy of the attribution notices contained
+
       within such NOTICE file, excluding those notices that do not
+
       pertain to any part of the Derivative Works, in at least one
+
       of the following places: within a NOTICE text file distributed
+
       as part of the Derivative Works; within the Source form or
+
       documentation, if provided along with the Derivative Works; or,
+
       within a display generated by the Derivative Works, if and
+
       wherever such third-party notices normally appear. The contents
+
       of the NOTICE file are for informational purposes only and
+
       do not modify the License. You may add Your own attribution
+
       notices within Derivative Works that You distribute, alongside
+
       or as an addendum to the NOTICE text from the Work, provided
+
       that such additional attribution notices cannot be construed
+
       as modifying the License.
+

+
   You may add Your own copyright statement to Your modifications and
+
   may provide additional or different license terms and conditions
+
   for use, reproduction, or distribution of Your modifications, or
+
   for any such Derivative Works as a whole, provided Your use,
+
   reproduction, and distribution of the Work otherwise complies with
+
   the conditions stated in this License.
+

+
5. Submission of Contributions. Unless You explicitly state otherwise,
+
   any Contribution intentionally submitted for inclusion in the Work
+
   by You to the Licensor shall be under the terms and conditions of
+
   this License, without any additional terms or conditions.
+
   Notwithstanding the above, nothing herein shall supersede or modify
+
   the terms of any separate license agreement you may have executed
+
   with Licensor regarding such Contributions.
+

+
6. Trademarks. This License does not grant permission to use the trade
+
   names, trademarks, service marks, or product names of the Licensor,
+
   except as required for reasonable and customary use in describing the
+
   origin of the Work and reproducing the content of the NOTICE file.
+

+
7. Disclaimer of Warranty. Unless required by applicable law or
+
   agreed to in writing, Licensor provides the Work (and each
+
   Contributor provides its Contributions) on an "AS IS" BASIS,
+
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+
   implied, including, without limitation, any warranties or conditions
+
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+
   PARTICULAR PURPOSE. You are solely responsible for determining the
+
   appropriateness of using or redistributing the Work and assume any
+
   risks associated with Your exercise of permissions under this License.
+

+
8. Limitation of Liability. In no event and under no legal theory,
+
   whether in tort (including negligence), contract, or otherwise,
+
   unless required by applicable law (such as deliberate and grossly
+
   negligent acts) or agreed to in writing, shall any Contributor be
+
   liable to You for damages, including any direct, indirect, special,
+
   incidental, or consequential damages of any character arising as a
+
   result of this License or out of the use or inability to use the
+
   Work (including but not limited to damages for loss of goodwill,
+
   work stoppage, computer failure or malfunction, or any and all
+
   other commercial damages or losses), even if such Contributor
+
   has been advised of the possibility of such damages.
+

+
9. Accepting Warranty or Additional Liability. While redistributing
+
   the Work or Derivative Works thereof, You may choose to offer,
+
   and charge a fee for, acceptance of support, warranty, indemnity,
+
   or other liability obligations and/or rights consistent with this
+
   License. However, in accepting such obligations, You may act only
+
   on Your own behalf and on Your sole responsibility, not on behalf
+
   of any other Contributor, and only if You agree to indemnify,
+
   defend, and hold each Contributor harmless for any liability
+
   incurred by, or claims asserted against, such Contributor by reason
+
   of your accepting any such warranty or additional liability.
+

+
END OF TERMS AND CONDITIONS
added LICENSE-MIT
@@ -0,0 +1,21 @@
+
MIT License
+

+
Copyright (c) 2021 The Radicle Foundation
+

+
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.
deleted archived/git-commit/Cargo.toml
@@ -1,19 +0,0 @@
-
[package]
-
name = "git-commit"
-
version = "0.3.0"
-
license = "MIT OR Apache-2.0"
-
edition = "2021"
-
authors = [
-
  "Alexis Sellier <alexis@radicle.xyz>",
-
  "Fintan Halpenny <fintan.halpenny@gmail.com>",
-
]
-
description = "A small library for parsing, displaying and creating a git commit"
-
keywords = ["git", "git-commit", "git-trailers", "radicle"]
-

-
[dependencies]
-
thiserror = "1"
-

-
[dependencies.git2]
-
version = "0.16.1"
-
default-features = false
-
features = ["vendored-libgit2"]
deleted archived/git-commit/src/author.rs
@@ -1,129 +0,0 @@
-
use std::{
-
    fmt,
-
    num::ParseIntError,
-
    str::{self, FromStr},
-
};
-

-
use thiserror::Error;
-

-
/// The data for indicating authorship of an action within a
-
/// [`super::Commit`].
-
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-
pub struct Author {
-
    /// Name corresponding to `user.name` in the git config.
-
    ///
-
    /// Note: this must not contain `<` or `>`.
-
    pub name: String,
-
    /// Email corresponding to `user.email` in the git config.
-
    ///
-
    /// Note: this must not contain `<` or `>`.
-
    pub email: String,
-
    /// The time of this author's action.
-
    pub time: Time,
-
}
-

-
/// The time of a [`Author`]'s action.
-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-
pub struct Time {
-
    seconds: i64,
-
    offset: i32,
-
}
-

-
impl Time {
-
    pub fn new(seconds: i64, offset: i32) -> Self {
-
        Self { seconds, offset }
-
    }
-

-
    /// Return the time, in seconds, since the epoch.
-
    pub fn seconds(&self) -> i64 {
-
        self.seconds
-
    }
-

-
    /// Return the timezone offset, in minutes.
-
    pub fn offset(&self) -> i32 {
-
        self.offset
-
    }
-
}
-

-
impl From<Time> for git2::Time {
-
    fn from(t: Time) -> Self {
-
        Self::new(t.seconds, t.offset)
-
    }
-
}
-

-
impl From<git2::Time> for Time {
-
    fn from(t: git2::Time) -> Self {
-
        Self::new(t.seconds(), t.offset_minutes())
-
    }
-
}
-

-
impl fmt::Display for Time {
-
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-
        let sign = if self.offset.is_negative() { '-' } else { '+' };
-
        write!(f, "{} {}{:0>4}", self.seconds, sign, self.offset.abs())
-
    }
-
}
-

-
impl fmt::Display for Author {
-
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-
        write!(f, "{} <{}> {}", self.name, self.email, self.time,)
-
    }
-
}
-

-
impl TryFrom<&Author> for git2::Signature<'_> {
-
    type Error = git2::Error;
-

-
    fn try_from(person: &Author) -> Result<Self, Self::Error> {
-
        let time = git2::Time::new(person.time.seconds, person.time.offset);
-
        git2::Signature::new(&person.name, &person.email, &time)
-
    }
-
}
-

-
impl<'a> TryFrom<&git2::Signature<'a>> for Author {
-
    type Error = str::Utf8Error;
-

-
    fn try_from(value: &git2::Signature<'a>) -> Result<Self, Self::Error> {
-
        Ok(Self {
-
            name: str::from_utf8(value.name_bytes())?.to_string(),
-
            email: str::from_utf8(value.email_bytes())?.to_string(),
-
            time: value.when().into(),
-
        })
-
    }
-
}
-

-
#[derive(Debug, Error)]
-
pub enum ParseError {
-
    #[error("missing '{0}' while parsing person signature")]
-
    Missing(&'static str),
-
    #[error("offset was incorrect format while parsing person signature")]
-
    Offset(#[source] ParseIntError),
-
    #[error("time was incorrect format while parsing person signature")]
-
    Time(#[source] ParseIntError),
-
    #[error("time offset is expected to be '+'/'-' for a person siganture")]
-
    UnknownOffset,
-
}
-

-
impl FromStr for Author {
-
    type Err = ParseError;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        let mut components = s.split(' ');
-
        let offset = match components.next_back() {
-
            None => return Err(ParseError::Missing("offset")),
-
            Some(offset) => offset.parse::<i32>().map_err(ParseError::Offset)?,
-
        };
-
        let time = match components.next_back() {
-
            None => return Err(ParseError::Missing("time")),
-
            Some(time) => time.parse::<i64>().map_err(ParseError::Time)?,
-
        };
-
        let time = Time::new(time, offset);
-

-
        let email = components
-
            .next_back()
-
            .ok_or(ParseError::Missing("email"))?
-
            .trim_matches(|c| c == '<' || c == '>')
-
            .to_owned();
-
        let name = components.collect::<Vec<_>>().join(" ");
-
        Ok(Self { name, email, time })
-
    }
-
}
deleted archived/git-commit/src/headers.rs
@@ -1,71 +0,0 @@
-
use std::borrow::Cow;
-

-
const BEGIN_SSH: &str = "-----BEGIN SSH SIGNATURE-----\n";
-
const BEGIN_PGP: &str = "-----BEGIN PGP SIGNATURE-----\n";
-

-
/// A collection of headers stored in a [`super::Commit`].
-
///
-
/// Note: these do not include `tree`, `parent`, `author`, and `committer`.
-
#[derive(Clone, Debug, Default)]
-
pub struct Headers(pub(super) Vec<(String, String)>);
-

-
/// A `gpgsig` signature stored in a [`super::Commit`].
-
pub enum Signature<'a> {
-
    /// A PGP signature, i.e. starts with `-----BEGIN PGP SIGNATURE-----`.
-
    Pgp(Cow<'a, str>),
-
    /// A SSH signature, i.e. starts with `-----BEGIN SSH SIGNATURE-----`.
-
    Ssh(Cow<'a, str>),
-
}
-

-
impl<'a> Signature<'a> {
-
    fn from_str(s: &'a str) -> Result<Self, UnknownScheme> {
-
        if s.starts_with(BEGIN_SSH) {
-
            Ok(Signature::Ssh(Cow::Borrowed(s)))
-
        } else if s.starts_with(BEGIN_PGP) {
-
            Ok(Signature::Pgp(Cow::Borrowed(s)))
-
        } else {
-
            Err(UnknownScheme)
-
        }
-
    }
-
}
-

-
pub struct UnknownScheme;
-

-
impl<'a> ToString for Signature<'a> {
-
    fn to_string(&self) -> String {
-
        match self {
-
            Signature::Pgp(pgp) => pgp.to_string(),
-
            Signature::Ssh(ssh) => ssh.to_string(),
-
        }
-
    }
-
}
-

-
impl Headers {
-
    pub fn new() -> Self {
-
        Headers(Vec::new())
-
    }
-

-
    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
-
        self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
-
    }
-

-
    pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a str> + '_ {
-
        self.iter()
-
            .filter_map(move |(k, v)| (k == name).then_some(v))
-
    }
-

-
    pub fn signatures(&self) -> impl Iterator<Item = Signature> + '_ {
-
        self.0.iter().filter_map(|(k, v)| {
-
            if k == "gpgsig" {
-
                Signature::from_str(v).ok()
-
            } else {
-
                None
-
            }
-
        })
-
    }
-

-
    /// Push a header to the end of the headers section.
-
    pub fn push(&mut self, name: &str, value: &str) {
-
        self.0.push((name.to_owned(), value.trim().to_owned()));
-
    }
-
}
deleted archived/git-commit/src/lib.rs
@@ -1,295 +0,0 @@
-
//! The `git-commit` crate provides parsing a displaying of a [git
-
//! commit][git-commit].
-
//!
-
//! The [`Commit`] data can be constructed using the `FromStr`
-
//! implementation, or by converting from a `git2::Buf`.
-
//!
-
//! The [`Headers`] can be accessed via [`Commit::headers`]. If the
-
//! signatures of the commit are of particular interest, the
-
//! [`Commit::signatures`] method can be used, which returns a series of
-
//! [`Signature`]s.
-
//!
-
//! [git-commit]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
-

-
use std::{
-
    fmt::Write as _,
-
    str::{self, FromStr},
-
};
-

-
use git2::{ObjectType, Oid};
-

-
pub mod author;
-
pub use author::Author;
-

-
pub mod headers;
-
pub use headers::{Headers, Signature};
-

-
pub mod trailers;
-
pub use trailers::{OwnedTrailer, Trailer, Trailers};
-

-
/// A git commit in its object description form, i.e. the output of
-
/// `git cat-file` for a commit object.
-
#[derive(Debug)]
-
pub struct Commit {
-
    tree: Oid,
-
    parents: Vec<Oid>,
-
    author: Author,
-
    committer: Author,
-
    headers: Headers,
-
    message: String,
-
    trailers: Vec<OwnedTrailer>,
-
}
-

-
impl Commit {
-
    pub fn new<I, T>(
-
        tree: Oid,
-
        parents: Vec<Oid>,
-
        author: Author,
-
        committer: Author,
-
        headers: Headers,
-
        message: String,
-
        trailers: I,
-
    ) -> Self
-
    where
-
        I: IntoIterator<Item = T>,
-
        OwnedTrailer: From<T>,
-
    {
-
        let trailers = trailers.into_iter().map(OwnedTrailer::from).collect();
-
        Self {
-
            tree,
-
            parents,
-
            author,
-
            committer,
-
            headers,
-
            message,
-
            trailers,
-
        }
-
    }
-

-
    /// Read the [`Commit`] from the `repo` that is expected to be found at
-
    /// `oid`.
-
    pub fn read(repo: &git2::Repository, oid: Oid) -> Result<Self, error::Read> {
-
        let odb = repo.odb()?;
-
        let object = odb.read(oid)?;
-
        Ok(Commit::try_from(object.data())?)
-
    }
-

-
    /// Write the given [`Commit`] to the `repo`. The resulting `Oid`
-
    /// is the identifier for this commit.
-
    pub fn write(&self, repo: &git2::Repository) -> Result<Oid, git2::Error> {
-
        let odb = repo.odb()?;
-
        odb.write(ObjectType::Commit, self.to_string().as_bytes())
-
    }
-

-
    /// The tree [`Oid`] this commit points to.
-
    pub fn tree(&self) -> Oid {
-
        self.tree
-
    }
-

-
    /// The parent [`Oid`]s of this commit.
-
    pub fn parents(&self) -> impl Iterator<Item = Oid> + '_ {
-
        self.parents.iter().copied()
-
    }
-

-
    /// The author of this commit, i.e. the header corresponding to `author`.
-
    pub fn author(&self) -> &Author {
-
        &self.author
-
    }
-

-
    /// The committer of this commit, i.e. the header corresponding to
-
    /// `committer`.
-
    pub fn committer(&self) -> &Author {
-
        &self.committer
-
    }
-

-
    /// The message body of this commit.
-
    pub fn message(&self) -> &str {
-
        &self.message
-
    }
-

-
    /// The [`Signature`]s found in this commit, i.e. the headers corresponding
-
    /// to `gpgsig`.
-
    pub fn signatures(&self) -> impl Iterator<Item = Signature> + '_ {
-
        self.headers.signatures()
-
    }
-

-
    /// The [`Headers`] found in this commit.
-
    ///
-
    /// Note: these do not include `tree`, `parent`, `author`, and `committer`.
-
    pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
-
        self.headers.iter()
-
    }
-

-
    /// Iterate over the [`Headers`] values that match the provided `name`.
-
    pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a str> + '_ {
-
        self.headers.values(name)
-
    }
-

-
    /// Push a header to the end of the headers section.
-
    pub fn push_header(&mut self, name: &str, value: &str) {
-
        self.headers.push(name, value.trim());
-
    }
-

-
    pub fn trailers(&self) -> impl Iterator<Item = &OwnedTrailer> {
-
        self.trailers.iter()
-
    }
-
}
-

-
pub mod error {
-
    use std::str;
-

-
    use thiserror::Error;
-

-
    use super::author;
-

-
    #[derive(Debug, Error)]
-
    pub enum Read {
-
        #[error(transparent)]
-
        Git(#[from] git2::Error),
-
        #[error(transparent)]
-
        Parse(#[from] Parse),
-
    }
-

-
    #[derive(Debug, Error)]
-
    pub enum Parse {
-
        #[error(transparent)]
-
        Author(#[from] author::ParseError),
-
        #[error("invalid '{header}'")]
-
        InvalidHeader {
-
            header: &'static str,
-
            #[source]
-
            err: git2::Error,
-
        },
-
        #[error("invalid git commit object format")]
-
        InvalidFormat,
-
        #[error("missing '{0}' while parsing commit")]
-
        Missing(&'static str),
-
        #[error("error occurred while checking for git-trailers: {0}")]
-
        Trailers(#[source] git2::Error),
-
        #[error(transparent)]
-
        Utf8(#[from] str::Utf8Error),
-
    }
-
}
-

-
impl TryFrom<git2::Buf> for Commit {
-
    type Error = error::Parse;
-

-
    fn try_from(value: git2::Buf) -> Result<Self, Self::Error> {
-
        value.as_str().ok_or(error::Parse::InvalidFormat)?.parse()
-
    }
-
}
-

-
impl TryFrom<&[u8]> for Commit {
-
    type Error = error::Parse;
-

-
    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
-
        Commit::from_str(str::from_utf8(data)?)
-
    }
-
}
-

-
impl FromStr for Commit {
-
    type Err = error::Parse;
-

-
    fn from_str(buffer: &str) -> Result<Self, Self::Err> {
-
        let (header, message) = buffer
-
            .split_once("\n\n")
-
            .ok_or(error::Parse::InvalidFormat)?;
-
        let mut lines = header.lines();
-

-
        let tree = match lines.next() {
-
            Some(tree) => tree
-
                .strip_prefix("tree ")
-
                .map(git2::Oid::from_str)
-
                .transpose()
-
                .map_err(|err| error::Parse::InvalidHeader {
-
                    header: "tree",
-
                    err,
-
                })?
-
                .ok_or(error::Parse::Missing("tree"))?,
-
            None => return Err(error::Parse::Missing("tree")),
-
        };
-

-
        let mut parents = Vec::new();
-
        let mut author: Option<Author> = None;
-
        let mut committer: Option<Author> = None;
-
        let mut headers = Headers::new();
-

-
        for line in lines {
-
            // Check if a signature is still being parsed
-
            if let Some(rest) = line.strip_prefix(' ') {
-
                let value: &mut String = headers
-
                    .0
-
                    .last_mut()
-
                    .map(|(_, v)| v)
-
                    .ok_or(error::Parse::InvalidFormat)?;
-
                value.push('\n');
-
                value.push_str(rest);
-
                continue;
-
            }
-

-
            if let Some((name, value)) = line.split_once(' ') {
-
                match name {
-
                    "parent" => parents.push(git2::Oid::from_str(value).map_err(|err| {
-
                        error::Parse::InvalidHeader {
-
                            header: "parent",
-
                            err,
-
                        }
-
                    })?),
-
                    "author" => author = Some(value.parse::<Author>()?),
-
                    "committer" => committer = Some(value.parse::<Author>()?),
-
                    _ => headers.push(name, value),
-
                }
-
                continue;
-
            }
-
        }
-

-
        let trailers = trailers::Trailers::parse(message).map_err(error::Parse::Trailers)?;
-

-
        let message = message
-
            .strip_suffix(&trailers.to_string(": "))
-
            .unwrap_or(message)
-
            .to_string();
-

-
        let trailers = trailers.iter().map(OwnedTrailer::from).collect();
-

-
        Ok(Self {
-
            tree,
-
            parents,
-
            author: author.ok_or(error::Parse::Missing("author"))?,
-
            committer: committer.ok_or(error::Parse::Missing("committer"))?,
-
            headers,
-
            message,
-
            trailers,
-
        })
-
    }
-
}
-

-
impl ToString for Commit {
-
    fn to_string(&self) -> String {
-
        let mut buf = String::new();
-

-
        writeln!(buf, "tree {}", self.tree).ok();
-

-
        for parent in &self.parents {
-
            writeln!(buf, "parent {parent}").ok();
-
        }
-

-
        writeln!(buf, "author {}", self.author).ok();
-
        writeln!(buf, "committer {}", self.committer).ok();
-

-
        for (name, value) in self.headers.iter() {
-
            writeln!(buf, "{name} {}", value.replace('\n', "\n ")).ok();
-
        }
-
        writeln!(buf).ok();
-
        write!(buf, "{}", self.message.trim()).ok();
-
        writeln!(buf).ok();
-

-
        if !self.trailers.is_empty() {
-
            writeln!(buf).ok();
-
        }
-
        for trailer in self.trailers.iter() {
-
            writeln!(buf, "{}", Trailer::from(trailer).display(": ")).ok();
-
        }
-
        buf
-
    }
-
}
deleted archived/git-commit/src/trailers.rs
@@ -1,214 +0,0 @@
-
use std::{borrow::Cow, fmt, fmt::Write, ops::Deref, str::FromStr};
-

-
use git2::{MessageTrailersStrs, MessageTrailersStrsIterator};
-

-
/// A Git commit's set of trailers that are left in the commit's
-
/// message.
-
///
-
/// Trailers are key/value pairs in the last paragraph of a message,
-
/// not including any patches or conflicts that may be present.
-
///
-
/// # Usage
-
///
-
/// To construct `Trailers`, you can use [`Trailers::parse`] or its
-
/// `FromStr` implementation.
-
///
-
/// To iterate over the trailers, you can use [`Trailers::iter`].
-
///
-
/// To render the trailers to a `String`, you can use
-
/// [`Trailers::to_string`] or its `Display` implementation (note that
-
/// it will default to using `": "` as the separator.
-
///
-
/// # Examples
-
///
-
/// ```text
-
/// Add new functionality
-
///
-
/// Making code better with new functionality.
-
///
-
/// X-Signed-Off-By: Alex Sellier
-
/// X-Co-Authored-By: Fintan Halpenny
-
/// ```
-
///
-
/// The trailers in the above example are:
-
///
-
/// ```text
-
/// X-Signed-Off-By: Alex Sellier
-
/// X-Co-Authored-By: Fintan Halpenny
-
/// ```
-
pub struct Trailers {
-
    inner: MessageTrailersStrs,
-
}
-

-
impl Trailers {
-
    pub fn parse(message: &str) -> Result<Self, git2::Error> {
-
        Ok(Self {
-
            inner: git2::message_trailers_strs(message)?,
-
        })
-
    }
-

-
    pub fn iter(&self) -> Iter<'_> {
-
        Iter {
-
            inner: self.inner.iter(),
-
        }
-
    }
-

-
    pub fn to_string<'a, S>(&self, sep: S) -> String
-
    where
-
        S: Separator<'a>,
-
    {
-
        let mut buf = String::new();
-
        for (i, trailer) in self.iter().enumerate() {
-
            if i > 0 {
-
                writeln!(buf).ok();
-
            }
-

-
            write!(buf, "{}", trailer.display(sep.sep_for(&trailer.token))).ok();
-
        }
-
        writeln!(buf).ok();
-
        buf
-
    }
-
}
-

-
impl fmt::Display for Trailers {
-
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-
        f.write_str(&self.to_string(": "))
-
    }
-
}
-

-
pub trait Separator<'a> {
-
    fn sep_for(&self, token: &Token) -> &'a str;
-
}
-

-
impl<'a> Separator<'a> for &'a str {
-
    fn sep_for(&self, _: &Token) -> &'a str {
-
        self
-
    }
-
}
-

-
impl<'a, F> Separator<'a> for F
-
where
-
    F: Fn(&Token) -> &'a str,
-
{
-
    fn sep_for(&self, token: &Token) -> &'a str {
-
        self(token)
-
    }
-
}
-

-
impl FromStr for Trailers {
-
    type Err = git2::Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Self::parse(s)
-
    }
-
}
-

-
pub struct Iter<'a> {
-
    inner: MessageTrailersStrsIterator<'a>,
-
}
-

-
impl<'a> Iterator for Iter<'a> {
-
    type Item = Trailer<'a>;
-

-
    fn next(&mut self) -> Option<Self::Item> {
-
        let (token, value) = self.inner.next()?;
-
        Some(Trailer {
-
            token: Token(token),
-
            value: Cow::Borrowed(value),
-
        })
-
    }
-
}
-

-
#[derive(Debug, Clone, Eq, PartialEq)]
-
pub struct Token<'a>(&'a str);
-

-
impl Deref for Token<'_> {
-
    type Target = str;
-

-
    fn deref(&self) -> &Self::Target {
-
        self.0
-
    }
-
}
-

-
pub struct Display<'a> {
-
    trailer: &'a Trailer<'a>,
-
    separator: &'a str,
-
}
-

-
impl<'a> fmt::Display for Display<'a> {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        write!(
-
            f,
-
            "{}{}{}",
-
            self.trailer.token.deref(),
-
            self.separator,
-
            self.trailer.value,
-
        )
-
    }
-
}
-

-
/// A trailer is a key/value pair found in the last paragraph of a Git
-
/// commit message, not including any patches or conflicts that may be
-
/// present.
-
#[derive(Debug, Clone, Eq, PartialEq)]
-
pub struct Trailer<'a> {
-
    pub token: Token<'a>,
-
    pub value: Cow<'a, str>,
-
}
-

-
impl<'a> Trailer<'a> {
-
    pub fn display(&'a self, separator: &'a str) -> Display<'a> {
-
        Display {
-
            trailer: self,
-
            separator,
-
        }
-
    }
-

-
    pub fn to_owned(&self) -> OwnedTrailer {
-
        OwnedTrailer::from(self)
-
    }
-
}
-

-
/// A version of the [`Trailer`] which owns its token and
-
/// value. Useful for when you need to carry trailers around in a long
-
/// lived data structure.
-
#[derive(Debug)]
-
pub struct OwnedTrailer {
-
    pub token: OwnedToken,
-
    pub value: String,
-
}
-

-
#[derive(Debug)]
-
pub struct OwnedToken(String);
-

-
impl Deref for OwnedToken {
-
    type Target = str;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl<'a> From<&Trailer<'a>> for OwnedTrailer {
-
    fn from(t: &Trailer<'a>) -> Self {
-
        OwnedTrailer {
-
            token: OwnedToken(t.token.0.to_string()),
-
            value: t.value.to_string(),
-
        }
-
    }
-
}
-

-
impl<'a> From<Trailer<'a>> for OwnedTrailer {
-
    fn from(t: Trailer<'a>) -> Self {
-
        (&t).into()
-
    }
-
}
-

-
impl<'a> From<&'a OwnedTrailer> for Trailer<'a> {
-
    fn from(t: &'a OwnedTrailer) -> Self {
-
        Trailer {
-
            token: Token(t.token.0.as_str()),
-
            value: Cow::from(&t.value),
-
        }
-
    }
-
}
deleted archived/git-commit/t/Cargo.toml
@@ -1,26 +0,0 @@
-
[package]
-
name = "git-commit-test"
-
version = "0.1.0"
-
license = "MIT OR Apache-2.0"
-
edition = "2021"
-

-
publish = false
-

-
[lib]
-
doctest = false
-
test = true
-
doc = false
-

-
[features]
-
test = []
-

-
[dev-dependencies.git-commit]
-
path = ".."
-

-
[dev-dependencies.git2]
-
version = "0.16.1"
-
default-features = false
-
features = ["vendored-libgit2"]
-

-
[dev-dependencies.test-helpers]
-
path = "../../test/test-helpers"

\ No newline at end of file
deleted archived/git-commit/t/src/commit.rs
@@ -1,149 +0,0 @@
-
use std::str::FromStr as _;
-

-
use git_commit::Commit;
-

-
const NO_TRAILER: &str = "\
-
tree 50d6ef440728217febf9e35716d8b0296608d7f8
-
parent 0ad95dbdfe9fdf81938ca419cf740469173e2022
-
parent a4ec9e07e1b2e6f37f7119651ae3bb63b79988b6
-
author Fintan Halpenny <fintan.halpenny@gmail.com> 1669292989 +0000
-
committer Fintan Halpenny <fintan.halpenny@gmail.com> 1669292989 +0000
-

-
Merge remote-tracking branch 'origin/surf/organise-tests'
-

-
* origin/surf/organise-tests:
-
  radicle-surf: organise tests
-
";
-

-
const SINGLE_TRAILER: &str = "\
-
tree 50d6ef440728217febf9e35716d8b0296608d7f8
-
parent 0ad95dbdfe9fdf81938ca419cf740469173e2022
-
parent a4ec9e07e1b2e6f37f7119651ae3bb63b79988b6
-
author Fintan Halpenny <fintan.halpenny@gmail.com> 1669292989 +0000
-
committer Fintan Halpenny <fintan.halpenny@gmail.com> 1669292989 +0000
-

-
Merge remote-tracking branch 'origin/surf/organise-tests'
-

-
* origin/surf/organise-tests:
-
  radicle-surf: organise tests
-

-
Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
-
";
-

-
const UNSIGNED: &str = "\
-
tree c66cc435f83ed0fba90ed4500e9b4b96e9bd001b
-
parent af06ad645133f580a87895353508053c5de60716
-
author Alexis Sellier <alexis@radicle.xyz> 1664467633 +0200
-
committer Alexis Sellier <alexis@radicle.xyz> 1664786099 -0200
-

-
Add SSH functionality with new `radicle-ssh`
-

-
We borrow code from `thrussh`, refactored to be runtime-less.
-

-
X-Signed-Off-By: Alex Sellier
-
X-Co-Authored-By: Fintan Halpenny
-
";
-

-
const SSH_SIGNATURE: &str = "\
-
-----BEGIN SSH SIGNATURE-----
-
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX
-
4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
-
AAAAQIQvhIewOgGfnXLgR5Qe1ZEr2vjekYXTdOfNWICi6ZiosgfZnIqV0enCPC4arVqQg+
-
GPp0HqxaB911OnSAr6bwU=
-
-----END SSH SIGNATURE-----";
-

-
const PGP_SIGNATURE: &str = "\
-
-----BEGIN PGP SIGNATURE-----
-
iQIzBAABCAAdFiEEHe7BWIo9taTY6TIiJVL7b2QGbLcFAmNcDhsACgkQJVL7b2QG
-
bLcc9Q//RgKf5N4enta9AuszGJZvdFhMPfIDUdw+WAZA6Z8zDPb/aAXZrPP/KIOM
-
zmX08FTqjP9B9YeWrEcFuAtxsRNqbDKrfpko9Y6bTsdrAJg3WIypBb9F8YDKJ6BO
-
CORJJqWOsLW129jW+mJDhcE0YTvPlcMiMI2qjVXKhU6Ag11W8IRZyTb9tvEaDjBR
-
YUnkPvgubv61K9BeUKexE2MakPBldaQtl0MF1Dk7/zo5btLd+KP0SOUKEhuMEu5b
-
LATHHdiYjt/2Xz7q8EcrFxXUaipxZe89dfTdi2ooJQw3ZDqjDHsGTHpDeBuzuSaJ
-
9fKVRwFz/78onfHPhmU4wfUhh+Fcl90p5/T+4dt2K6cr+7rq078e+aJYxkX2d0MG
-
PG0xGP0RN4g+X92K1kGuzoe4870xAnRTNh5nUB+X9snO8tVqQZTb0M2yI+sTsKrv
-
w/f+uiqL6e9DgIxlO5dgiNHCVoCs1QJ900jUGisrlzS4+n6GzMsG6s3c01X4yY9G
-
Ou/kGkMsn7tqejqC9RufygcchCFZqYwaHQwPkiYhfYGMarMpoCFvll0h8tSparpS
-
nnpAQXVdu8m3v1YdPUuTg5ksxSOe9HCIlVXGFhxy3iqCVRn+51FRnUI63rMTOm9/
-
LBqzvji02lDUPGqPgXfcCS0ty8FM2flBIXnwb8TDzCaPYhf53+U=
-
=6dw2
-
-----END PGP SIGNATURE-----";
-

-
const SIGNED: &str = "\
-
tree c66cc435f83ed0fba90ed4500e9b4b96e9bd001b
-
parent af06ad645133f580a87895353508053c5de60716
-
author Alexis Sellier <alexis@radicle.xyz> 1664467633 +0200
-
committer Alexis Sellier <alexis@radicle.xyz> 1664786099 -0200
-
other e6fe3c97619deb8ab4198620f9a7eb79d98363dd
-
gpgsig -----BEGIN SSH SIGNATURE-----
-
 U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX
-
 4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
-
 AAAAQIQvhIewOgGfnXLgR5Qe1ZEr2vjekYXTdOfNWICi6ZiosgfZnIqV0enCPC4arVqQg+
-
 GPp0HqxaB911OnSAr6bwU=
-
 -----END SSH SIGNATURE-----
-
gpgsig -----BEGIN PGP SIGNATURE-----
-
 iQIzBAABCAAdFiEEHe7BWIo9taTY6TIiJVL7b2QGbLcFAmNcDhsACgkQJVL7b2QG
-
 bLcc9Q//RgKf5N4enta9AuszGJZvdFhMPfIDUdw+WAZA6Z8zDPb/aAXZrPP/KIOM
-
 zmX08FTqjP9B9YeWrEcFuAtxsRNqbDKrfpko9Y6bTsdrAJg3WIypBb9F8YDKJ6BO
-
 CORJJqWOsLW129jW+mJDhcE0YTvPlcMiMI2qjVXKhU6Ag11W8IRZyTb9tvEaDjBR
-
 YUnkPvgubv61K9BeUKexE2MakPBldaQtl0MF1Dk7/zo5btLd+KP0SOUKEhuMEu5b
-
 LATHHdiYjt/2Xz7q8EcrFxXUaipxZe89dfTdi2ooJQw3ZDqjDHsGTHpDeBuzuSaJ
-
 9fKVRwFz/78onfHPhmU4wfUhh+Fcl90p5/T+4dt2K6cr+7rq078e+aJYxkX2d0MG
-
 PG0xGP0RN4g+X92K1kGuzoe4870xAnRTNh5nUB+X9snO8tVqQZTb0M2yI+sTsKrv
-
 w/f+uiqL6e9DgIxlO5dgiNHCVoCs1QJ900jUGisrlzS4+n6GzMsG6s3c01X4yY9G
-
 Ou/kGkMsn7tqejqC9RufygcchCFZqYwaHQwPkiYhfYGMarMpoCFvll0h8tSparpS
-
 nnpAQXVdu8m3v1YdPUuTg5ksxSOe9HCIlVXGFhxy3iqCVRn+51FRnUI63rMTOm9/
-
 LBqzvji02lDUPGqPgXfcCS0ty8FM2flBIXnwb8TDzCaPYhf53+U=
-
 =6dw2
-
 -----END PGP SIGNATURE-----
-

-
Add SSH functionality with new `radicle-ssh`
-

-
We borrow code from `thrussh`, refactored to be runtime-less.
-

-
X-Signed-Off-By: Alex Sellier
-
X-Co-Authored-By: Fintan Halpenny
-
";
-

-
#[test]
-
fn test_push_header() {
-
    let mut commit = Commit::from_str(UNSIGNED).unwrap();
-

-
    commit.push_header("other", "e6fe3c97619deb8ab4198620f9a7eb79d98363dd");
-
    commit.push_header("gpgsig", SSH_SIGNATURE);
-
    commit.push_header("gpgsig", PGP_SIGNATURE);
-

-
    assert_eq!(commit.to_string(), SIGNED);
-
}
-

-
#[test]
-
fn test_get_header() {
-
    let commit = Commit::from_str(SIGNED).unwrap();
-

-
    assert_eq!(
-
        commit
-
            .signatures()
-
            .map(|sig| sig.to_string())
-
            .collect::<Vec<_>>(),
-
        vec![SSH_SIGNATURE.to_owned(), PGP_SIGNATURE.to_owned()]
-
    );
-
    assert_eq!(
-
        commit.values("other").collect::<Vec<_>>(),
-
        vec![String::from("e6fe3c97619deb8ab4198620f9a7eb79d98363dd")],
-
    );
-
    assert!(commit.values("unknown").next().is_none());
-
}
-

-
#[test]
-
fn test_conversion() {
-
    assert_eq!(
-
        Commit::from_str(NO_TRAILER).unwrap().to_string(),
-
        NO_TRAILER
-
    );
-
    assert_eq!(
-
        Commit::from_str(SINGLE_TRAILER).unwrap().to_string(),
-
        SINGLE_TRAILER
-
    );
-
    assert_eq!(Commit::from_str(SIGNED).unwrap().to_string(), SIGNED);
-
    assert_eq!(Commit::from_str(UNSIGNED).unwrap().to_string(), UNSIGNED);
-
}
deleted archived/git-commit/t/src/integration.rs
@@ -1,28 +0,0 @@
-
use std::io;
-

-
use git_commit::Commit;
-
use test_helpers::tempdir::WithTmpDir;
-

-
#[test]
-
fn valid_commits() {
-
    let radicle_git = format!(
-
        "file://{}",
-
        git2::Repository::discover(".").unwrap().path().display()
-
    );
-
    let repo = WithTmpDir::new(|path| {
-
        let repo = git2::Repository::clone(&radicle_git, path)
-
            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
-
        Ok::<_, io::Error>(repo)
-
    })
-
    .unwrap();
-

-
    let mut walk = repo.revwalk().unwrap();
-
    walk.push_head().unwrap();
-

-
    // take the first 20 commits and make sure we can parse them
-
    for oid in walk.take(20) {
-
        let oid = oid.unwrap();
-
        let commit = Commit::read(&repo, oid);
-
        assert!(commit.is_ok(), "Oid: {oid}, Error: {commit:?}")
-
    }
-
}
deleted archived/git-commit/t/src/lib.rs
@@ -1,5 +0,0 @@
-
#[cfg(test)]
-
mod commit;
-

-
#[cfg(test)]
-
mod integration;
deleted archived/git-trailers/Cargo.toml
@@ -1,20 +0,0 @@
-
[package]
-
name = "git-trailers"
-
version = "0.1.0"
-
authors = [
-
  "Nuno Alexandre <hi@nunoalexandre.com>",
-
  "Kim Altintop <kim@eagain.st>",
-
  "Fintan Halpenny <fintan.halpenny@gmail.com>",
-
]
-
edition = "2018"
-
license = "GPL-3.0-or-later"
-
description = "Library to support parsing and display git trailers <https://git-scm.com/docs/git-interpret-trailers>"
-
keywords = ["git"]
-

-
[lib]
-
doctest = false
-
test = false
-

-
[dependencies]
-
nom = "7.1"
-
thiserror = "1.0"
deleted archived/git-trailers/src/lib.rs
@@ -1,300 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{borrow::Cow, convert::TryFrom, fmt, ops::Deref};
-

-
use thiserror::Error;
-

-
#[derive(Debug, Error)]
-
#[non_exhaustive]
-
pub enum Error {
-
    #[error("the trailers paragraph is missing in the given message")]
-
    MissingParagraph,
-

-
    #[error("trailing data after trailers section: '{0}")]
-
    Trailing(String),
-

-
    #[error(transparent)]
-
    Parse(#[from] nom::Err<nom::error::Error<String>>),
-
}
-

-
#[derive(Debug, Clone, Eq, PartialEq)]
-
pub struct Trailer<'a> {
-
    pub token: Token<'a>,
-
    pub values: Vec<Cow<'a, str>>,
-
}
-

-
impl<'a> Trailer<'a> {
-
    pub fn display(&'a self, separator: &'a str) -> Display<'a> {
-
        Display {
-
            trailer: self,
-
            separator,
-
        }
-
    }
-

-
    pub fn to_owned(&self) -> OwnedTrailer {
-
        OwnedTrailer::from(self)
-
    }
-
}
-

-
#[derive(Debug, Clone, Eq, PartialEq)]
-
pub struct Token<'a>(&'a str);
-

-
/// A version of the Trailer<'a> which owns it's token and values. Useful for
-
/// when you need to carry trailers around in a long lived data structure.
-
#[derive(Debug)]
-
pub struct OwnedTrailer {
-
    pub token: OwnedToken,
-
    pub values: Vec<String>,
-
}
-

-
#[derive(Debug)]
-
pub struct OwnedToken(String);
-

-
impl<'a> From<&Trailer<'a>> for OwnedTrailer {
-
    fn from(t: &Trailer<'a>) -> Self {
-
        OwnedTrailer {
-
            token: OwnedToken(t.token.0.to_string()),
-
            values: t.values.iter().map(|v| v.to_string()).collect(),
-
        }
-
    }
-
}
-

-
impl<'a> From<Trailer<'a>> for OwnedTrailer {
-
    fn from(t: Trailer<'a>) -> Self {
-
        (&t).into()
-
    }
-
}
-

-
impl<'a> From<&'a OwnedTrailer> for Trailer<'a> {
-
    fn from(t: &'a OwnedTrailer) -> Self {
-
        Trailer {
-
            token: Token(t.token.0.as_str()),
-
            values: t.values.iter().map(Cow::from).collect(),
-
        }
-
    }
-
}
-

-
impl Deref for OwnedToken {
-
    type Target = str;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
#[derive(Debug, Error)]
-
#[non_exhaustive]
-
pub enum InvalidToken {
-
    #[error("trailing characters: '{0}'")]
-
    Trailing(String),
-

-
    #[error(transparent)]
-
    Parse(#[from] nom::Err<nom::error::Error<String>>),
-
}
-

-
impl<'a> TryFrom<&'a str> for Token<'a> {
-
    type Error = InvalidToken;
-

-
    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
-
        match parser::token(s) {
-
            Ok((rest, token)) if rest.is_empty() => Ok(token),
-
            Ok((trailing, _)) => Err(InvalidToken::Trailing(trailing.to_owned())),
-
            Err(e) => Err(e.to_owned().into()),
-
        }
-
    }
-
}
-

-
impl Deref for Token<'_> {
-
    type Target = str;
-

-
    fn deref(&self) -> &Self::Target {
-
        self.0
-
    }
-
}
-

-
pub struct Display<'a> {
-
    trailer: &'a Trailer<'a>,
-
    separator: &'a str,
-
}
-

-
impl<'a> fmt::Display for Display<'a> {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        write!(
-
            f,
-
            "{}{}{}",
-
            self.trailer.token.deref(),
-
            self.separator,
-
            self.trailer.values.join("\n  ")
-
        )
-
    }
-
}
-

-
pub trait Separator<'a> {
-
    fn sep_for(&self, token: &Token) -> &'a str;
-
}
-

-
impl<'a> Separator<'a> for &'a str {
-
    fn sep_for(&self, _: &Token) -> &'a str {
-
        self
-
    }
-
}
-

-
impl<'a, F> Separator<'a> for F
-
where
-
    F: Fn(&Token) -> &'a str,
-
{
-
    fn sep_for(&self, token: &Token) -> &'a str {
-
        self(token)
-
    }
-
}
-

-
pub struct DisplayMany<'a, S> {
-
    separator: S,
-
    trailers: &'a [Trailer<'a>],
-
}
-

-
impl<'a, S> fmt::Display for DisplayMany<'a, S>
-
where
-
    S: Separator<'a>,
-
{
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        for (i, trailer) in self.trailers.iter().enumerate() {
-
            if i > 0 {
-
                writeln!(f)?
-
            }
-

-
            write!(
-
                f,
-
                "{}",
-
                trailer.display(self.separator.sep_for(&trailer.token))
-
            )?
-
        }
-

-
        Ok(())
-
    }
-
}
-

-
/// Parse the trailers of the given message. It looks up the last paragraph
-
/// of the message and attempts to parse each of its lines as a [Trailer].
-
/// Fails if no trailers paragraph is found or if at least one trailer
-
/// fails to be parsed.
-
pub fn parse<'a>(message: &'a str, separators: &'a str) -> Result<Vec<Trailer<'a>>, Error> {
-
    let trailers_paragraph =
-
        match parser::paragraphs(message.trim_end()).map(|(_, ps)| ps.last().cloned()) {
-
            Ok(None) | Err(_) => return Err(Error::MissingParagraph),
-
            Ok(Some(p)) => {
-
                if p.is_empty() {
-
                    return Err(Error::MissingParagraph);
-
                }
-
                p
-
            },
-
        };
-

-
    match parser::trailers(trailers_paragraph, separators) {
-
        Ok((rest, trailers)) if rest.is_empty() => Ok(trailers),
-
        Ok((unparseable, _)) => Err(Error::Trailing(unparseable.to_owned())),
-
        Err(e) => Err(e.to_owned().into()),
-
    }
-
}
-

-
/// Render a slice of trailers.
-
///
-
/// The `separator` can be either a string slice, or a closure which may choose
-
/// a different separator for each [`Token`] encountered. Note that multiline
-
/// trailers are rendered with a fixed indent, so the result is not
-
/// layout-preserving.
-
pub fn display<'a, S>(separator: S, trailers: &'a [Trailer<'a>]) -> DisplayMany<'a, S>
-
where
-
    S: Separator<'a>,
-
{
-
    DisplayMany {
-
        separator,
-
        trailers,
-
    }
-
}
-

-
pub mod parser {
-
    use std::borrow::Cow;
-

-
    use super::{Token, Trailer};
-
    use nom::{
-
        branch::alt,
-
        bytes::complete::{tag, take_until, take_while1},
-
        character::complete::{line_ending, not_line_ending, one_of, space0, space1},
-
        combinator::{map, rest},
-
        multi::{many0, separated_list1},
-
        sequence::{delimited, preceded, separated_pair, terminated},
-
        IResult,
-
    };
-

-
    const EMPTY_LINE: &str = "\n\n";
-

-
    pub fn paragraphs(s: &str) -> IResult<&str, Vec<&str>> {
-
        separated_list1(tag(EMPTY_LINE), paragraph)(s)
-
    }
-

-
    pub fn paragraph(s: &str) -> IResult<&str, &str> {
-
        alt((take_until(EMPTY_LINE), rest))(s)
-
    }
-

-
    /// Parse all the possible trailers.
-
    /// It stops when it can no longer parse valid trailers.
-
    pub fn trailers<'a>(s: &'a str, separators: &'a str) -> IResult<&'a str, Vec<Trailer<'a>>> {
-
        many0(|s| trailer(s, separators))(s)
-
    }
-

-
    /// Parse a trailer, which can have an inlined or multilined value.
-
    pub fn trailer<'a>(s: &'a str, separators: &'a str) -> IResult<&'a str, Trailer<'a>> {
-
        let mut parser = separated_pair(token, |s| separator(separators, s), values);
-
        let (rest, (token, values)) = parser(s)?;
-
        Ok((rest, Trailer { token, values }))
-
    }
-

-
    /// Parse a trailer token.
-
    pub(super) fn token(s: &str) -> IResult<&str, Token> {
-
        take_while1(|c: char| c.is_alphanumeric() || c == '-')(s)
-
            .map(|(i, token_str)| (i, Token(token_str)))
-
    }
-

-
    /// Parse the trailer separator, which can be delimited by spaces.
-
    fn separator<'a>(separators: &'a str, s: &'a str) -> IResult<&'a str, char> {
-
        delimited(space0, one_of(separators), space0)(s)
-
    }
-

-
    /// Parse the trailer values, which gathers the value after the separator
-
    /// (if any) and possible following multilined values, indented by a
-
    /// space.
-
    fn values(s: &str) -> IResult<&str, Vec<Cow<'_, str>>> {
-
        let (r, opt_inline_value) = until_eol_or_eof(s)?;
-
        let (r, mut values) = multiline_values(r)?;
-
        if !opt_inline_value.is_empty() {
-
            values.insert(0, opt_inline_value.into())
-
        }
-
        Ok((r, values))
-
    }
-

-
    fn multiline_values(s: &str) -> IResult<&str, Vec<Cow<'_, str>>> {
-
        many0(map(indented_line_contents, Cow::from))(s)
-
    }
-

-
    fn until_eol_or_eof(s: &str) -> IResult<&str, &str> {
-
        alt((until_eol, rest))(s)
-
    }
-

-
    /// Parse an indented line, i.e, a line that starts with a space.
-
    /// Extracts the line contents, ignoring the indentation and the
-
    /// new line character.
-
    fn indented_line_contents(s: &str) -> IResult<&str, &str> {
-
        preceded(space1, until_eol_or_eof)(s)
-
    }
-

-
    /// Consume the input until the end of the line, ignoring the new line
-
    /// character.
-
    fn until_eol(s: &str) -> IResult<&str, &str> {
-
        terminated(not_line_ending, line_ending)(s)
-
    }
-
}
deleted archived/git-trailers/t/Cargo.toml
@@ -1,22 +0,0 @@
-
[package]
-
name = "git-trailers-test"
-
version = "0.1.0"
-
edition = "2021"
-
license = "GPL-3.0-or-later"
-

-
publish = false
-

-
[lib]
-
doctest = false
-
test = true
-
doc = false
-

-
[features]
-
test = []
-

-
[dev-dependencies]
-
assert_matches = "1.5"
-
pretty_assertions = "1.1"
-

-
[dev-dependencies.git-trailers]
-
path = ".."
deleted archived/git-trailers/t/src/lib.rs
@@ -1,2 +0,0 @@
-
#[cfg(test)]
-
mod tests;
deleted archived/git-trailers/t/src/tests.rs
@@ -1,155 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{borrow::Cow, convert::TryFrom as _, ops::Deref as _};
-

-
use assert_matches::assert_matches;
-
use git_trailers::{display, parse, Error, Token, Trailer};
-
use pretty_assertions::assert_eq;
-

-
#[test]
-
fn parse_message_with_valid_trailers() {
-
    let msg = r#"Subject
-

-
A multiline
-
description.
-

-
Co-authored-by: John Doe <john.doe@test.com>
-
Ticket: #42
-
Tested-by:
-
    John <john@test.com>
-
    Jane <jane@test.com>
-
Just-a-token:
-

-
"#;
-
    assert_eq!(
-
        parse(msg, ":").unwrap(),
-
        vec![
-
            new_trailer("Co-authored-by", &["John Doe <john.doe@test.com>"]),
-
            new_trailer("Ticket", &["#42"]),
-
            new_trailer(
-
                "Tested-by",
-
                &["John <john@test.com>", "Jane <jane@test.com>"]
-
            ),
-
            new_trailer("Just-a-token", &[]),
-
        ]
-
    )
-
}
-

-
#[test]
-
fn parse_message_trailers_with_custom_separators() {
-
    let separators = ":=$";
-
    let msg = r#"Subject
-

-
A multiline
-
description.
-

-
Co-authored-by: John Doe <john.doe@test.com>
-
Ticket = #42
-
Tested-by $User <user@test.com>
-
    John <john@test.com>
-
    Jane <jane@test.com>
-
"#;
-
    assert_eq!(
-
        parse(msg, separators).unwrap(),
-
        vec![
-
            new_trailer("Co-authored-by", &["John Doe <john.doe@test.com>"]),
-
            new_trailer("Ticket", &["#42"]),
-
            new_trailer(
-
                "Tested-by",
-
                &[
-
                    "User <user@test.com>",
-
                    "John <john@test.com>",
-
                    "Jane <jane@test.com>"
-
                ]
-
            ),
-
        ]
-
    )
-
}
-

-
#[test]
-
fn parse_message_trailers_with_missing_token() {
-
    let msg = r#"Subject
-

-
Good-trailer: true
-
John Doe <john.doe@test.com> # Unparsable token due to missing token"#;
-
    assert_matches!(
-
        parse(msg, ":"),
-
        Err(Error::Trailing(s))
-
            if s == "John Doe <john.doe@test.com> # Unparsable token due to missing token"
-
    )
-
}
-

-
#[test]
-
fn parse_message_trailers_with_invalid_token() {
-
    let msg = r#"Subject
-

-
Good-trailer: true
-
&!#: John Doe <john.doe@test.com> # Unparsable token due to invalid token"#;
-
    assert_matches!(
-
        parse(msg, ":"),
-
        Err(Error::Trailing(s))
-
            if s == "&!#: John Doe <john.doe@test.com> # Unparsable token due to invalid token"
-
    )
-
}
-

-
#[test]
-
fn parse_message_with_only_trailers() {
-
    let msg = r#"Co-authored-by: John Doe <john.doe@test.com>
-
Ticket: #42
-
Tested-by: Tester <tester@test.com>
-
"#;
-
    assert_eq!(
-
        parse(msg, ":").unwrap(),
-
        vec![
-
            new_trailer("Co-authored-by", &["John Doe <john.doe@test.com>"]),
-
            new_trailer("Ticket", &["#42"]),
-
            new_trailer("Tested-by", &["Tester <tester@test.com>"]),
-
        ]
-
    )
-
}
-

-
#[test]
-
fn parse_empty_message() {
-
    let msg = "";
-
    assert_matches!(parse(msg, ":"), Err(Error::MissingParagraph))
-
}
-

-
#[test]
-
fn display_static() {
-
    let msg = r#"Tested-by: Alice
-
  Bob
-
  Carol
-
  Dylan
-
Acked-by: Eve"#;
-

-
    let parsed = parse(msg, ":").unwrap();
-
    let rendered = format!("{}", display(": ", &parsed));
-
    assert_eq!(&rendered, msg);
-
}
-

-
#[test]
-
fn display_dynamic() {
-
    let msg = r#"Co-authored-by: John Doe <john.doe@test.com>
-
Tested-by: Tester <tester@test.com>
-
Fixes #42"#;
-

-
    let parsed = parse(msg, ":#").unwrap();
-
    let rendered = format!(
-
        "{}",
-
        display(
-
            |t: &Token| if t.deref() == "Fixes" { " #" } else { ": " },
-
            &parsed
-
        )
-
    );
-
    assert_eq!(rendered, msg)
-
}
-

-
fn new_trailer<'a>(token: &'a str, values: &[&'a str]) -> Trailer<'a> {
-
    Trailer {
-
        token: Token::try_from(token).unwrap(),
-
        values: values.iter().map(|s| Cow::from(*s)).collect(),
-
    }
-
}
deleted archived/link-git/Cargo.toml
@@ -1,64 +0,0 @@
-
[package]
-
name = "link-git"
-
version = "0.1.0"
-
authors = ["Kim Altintop <kim@eagain.st>"]
-
edition = "2018"
-
license = "GPL-3.0-or-later"
-

-
description = "Core git types and functionality"
-

-
[lib]
-
doctest = false
-
test = false
-

-
[dependencies]
-
arc-swap = "1.4.0"
-
async-process = "1.1.0"
-
async-trait = "0.1"
-
blocking = "1.0.2"
-
bstr = "0.2"
-
futures-lite = "1.12.0"
-
futures-util = "0.3.15"
-
lazy_static = "1.4.0"
-
im = "15.0.0"
-
once_cell = "1.10"
-
parking_lot = "0.12"
-
pin-project = "1.0.7"
-
regex = "1.5.4"
-
rustc-hash = "1.1.0"
-
tempfile = "3.4"
-
thiserror = "1.0.30"
-
tracing = "0.1"
-
versions = "3.0.2"
-

-
# gitoxide
-
git-actor = "^0.6.0"
-
git-hash = "^0.8.0"
-
git-lock = "^1.0.1"
-
git-object = "^0.15.1"
-
git-odb = "^0.24.0"
-
git-ref = "^0.9.0"
-
git-traverse = "^0.10.0"
-

-
[dependencies.git-features]
-
version = "^0.17.0"
-
features = ["progress", "parallel", "zlib-ng-compat"]
-

-
[dependencies.git-pack]
-
version = "^0.14.0"
-
features = ["object-cache-dynamic", "pack-cache-lru-static", "pack-cache-lru-dynamic"]
-

-
[dependencies.git-packetline]
-
version = "^0.12.0"
-
features = ["async-io"]
-

-
[dependencies.git-protocol]
-
version = "^0.12.0"
-
features = ["async-client"]
-

-
# compat
-
[dependencies.git2]
-
version = "0.16.1"
-
default-features = false
-
features = ["vendored-libgit2"]
-
optional = true
deleted archived/link-git/src/lib.rs
@@ -1,20 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
#[macro_use]
-
extern crate async_trait;
-

-
pub mod odb;
-
pub mod protocol;
-
pub mod refs;
-
pub use refs::db as refdb;
-
#[cfg(feature = "git2")]
-
pub mod service;
-

-
pub use git_actor as actor;
-
pub use git_hash as hash;
-
pub use git_lock as lock;
-
pub use git_object as object;
-
pub use git_traverse as traverse;
deleted archived/link-git/src/odb.rs
@@ -1,51 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use git_hash::oid;
-
use thiserror::Error;
-

-
pub mod backend;
-
pub mod index;
-
pub mod pack;
-
pub mod window;
-

-
pub use git_pack::{cache, data::Object};
-

-
#[derive(Debug, Error)]
-
pub enum Error {
-
    #[error(transparent)]
-
    Packed(#[from] index::error::Lookup<pack::error::Data>),
-

-
    #[error(transparent)]
-
    Loose(#[from] git_odb::loose::find::Error),
-
}
-

-
pub struct Odb<I, D> {
-
    pub loose: backend::Loose,
-
    pub packed: backend::Packed<I, D>,
-
}
-

-
impl<I, D> Odb<I, D>
-
where
-
    I: index::Index,
-
    D: window::Cache,
-
{
-
    pub fn contains(&self, id: impl AsRef<oid>) -> bool {
-
        self.packed.contains(id.as_ref()) || self.loose.contains(id)
-
    }
-

-
    pub fn find<'a>(
-
        &self,
-
        id: impl AsRef<oid>,
-
        buf: &'a mut Vec<u8>,
-
        cache: &mut impl cache::DecodeEntry,
-
    ) -> Result<Option<Object<'a>>, Error> {
-
        let id = id.as_ref();
-
        if self.packed.contains(id) {
-
            return self.packed.find(id, buf, cache).map_err(Into::into);
-
        }
-
        self.loose.try_find(id, buf).map_err(Into::into)
-
    }
-
}
deleted archived/link-git/src/odb/backend.rs
@@ -1,36 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use git_hash::oid;
-
use git_pack::{cache::DecodeEntry, data::Object};
-

-
use super::{index, pack, window};
-

-
pub type Loose = git_odb::loose::Store;
-

-
pub struct Packed<I, D> {
-
    pub index: I,
-
    pub data: D,
-
}
-

-
impl<I, D> Packed<I, D>
-
where
-
    I: index::Index,
-
    D: window::Cache,
-
{
-
    pub fn contains(&self, id: impl AsRef<oid>) -> bool {
-
        self.index.contains(id)
-
    }
-

-
    pub fn find<'a>(
-
        &self,
-
        id: impl AsRef<oid>,
-
        buf: &'a mut Vec<u8>,
-
        cache: &mut impl DecodeEntry,
-
    ) -> Result<Option<Object<'a>>, index::error::Lookup<pack::error::Data>> {
-
        self.index
-
            .lookup(|info| self.data.get(info), id, buf, cache)
-
    }
-
}
deleted archived/link-git/src/odb/index.rs
@@ -1,307 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    fs,
-
    io,
-
    path::{Path, PathBuf},
-
    sync::Arc,
-
};
-

-
use arc_swap::ArcSwap;
-
use git_hash::oid;
-
use git_pack::{
-
    cache::DecodeEntry,
-
    data::{Object, ResolvedBase},
-
};
-
use parking_lot::Mutex;
-
use tracing::trace;
-

-
use super::pack;
-

-
pub use git_pack::index::File as IndexFile;
-

-
mod metrics;
-
pub use metrics::{Metrics, Stats, StatsView};
-

-
pub mod error {
-
    use super::*;
-
    use thiserror::Error;
-

-
    #[derive(Debug, Error)]
-
    pub enum Discover {
-
        #[error(transparent)]
-
        Index(#[from] pack::error::Index),
-

-
        #[error(transparent)]
-
        Io(#[from] io::Error),
-
    }
-

-
    #[derive(Debug, Error)]
-
    pub enum Lookup<E> {
-
        #[error(transparent)]
-
        Lookup(E),
-

-
        #[error(transparent)]
-
        Reload(#[from] Discover),
-

-
        #[error(transparent)]
-
        Decode(#[from] git_pack::data::decode_entry::Error),
-
    }
-
}
-

-
pub trait Index {
-
    fn contains(&self, id: impl AsRef<oid>) -> bool;
-

-
    fn lookup<'a, F, E>(
-
        &self,
-
        pack_cache: F,
-
        id: impl AsRef<oid>,
-
        buf: &'a mut Vec<u8>,
-
        cache: &mut impl DecodeEntry,
-
    ) -> Result<Option<Object<'a>>, error::Lookup<E>>
-
    where
-
        F: FnOnce(&pack::Info) -> Result<Arc<pack::Data>, E>;
-
}
-

-
/// An [`Index`] which can be shared between threads.
-
///
-
/// [`Shared`] assumes that:
-
///
-
/// * newer packs are likely to contain recent objects
-
/// * lookups tend to favour recent objects
-
/// * lookups tend to expect the object to be found (the object id is either
-
///   pointed to by a ref, or linked to by an existing object)
-
///
-
/// Thus, it:
-
///
-
/// * orders indices found in `GIT_DIR/objects/pack` by modification time, and
-
///   queries the more recent ones first
-
/// * attempts to rescan `GIT_DIR/objects/pack` when an object id was _not_
-
///   found (assuming that this is due to a compaction)
-
///
-
/// Unless a reload occurs, lookups are lock-free and mostly wait-free. Writes
-
/// ([`Shared::push`], [`Shared::reload`]) are guarded by a [`Mutex`].
-
// TODO: consecutive lookups also tend to resolve to the same pack, so we could
-
// remember the index into the `im::Vector` where we found a match and look
-
// there first. This is what libgit2 does, but the heuristic is not necessarily
-
// true when `Shared` is shared across multiple concurrent link replication
-
// tasks; per-namespace packs are independent pre-compaction.
-
pub struct Shared<M> {
-
    pack_dir: PathBuf,
-
    indices: ArcSwap<im::Vector<Arc<pack::Index>>>,
-
    write: Mutex<()>,
-
    stats: M,
-
}
-

-
impl Shared<()> {
-
    pub fn open(git_dir: impl AsRef<Path>) -> Result<Self, error::Discover> {
-
        let pack_dir = git_dir.as_ref().join("objects").join("pack");
-
        let indices = discover(&pack_dir)?;
-

-
        Ok(Self {
-
            pack_dir,
-
            indices: ArcSwap::new(Arc::new(indices)),
-
            write: Mutex::new(()),
-
            stats: (),
-
        })
-
    }
-
}
-

-
impl<M> Shared<M>
-
where
-
    M: Metrics,
-
{
-
    pub fn with_stats(self) -> Shared<Stats> {
-
        self.with_metrics(Stats::default())
-
    }
-

-
    pub fn with_metrics<N: Metrics>(self, m: N) -> Shared<N> {
-
        Shared {
-
            pack_dir: self.pack_dir,
-
            indices: self.indices,
-
            write: self.write,
-
            stats: m,
-
        }
-
    }
-

-
    pub fn stats(&self) -> M::Snapshot {
-
        self.stats.snapshot(self.len())
-
    }
-

-
    /// Add a newly discovered [`pack::Index`].
-
    ///
-
    /// This index will be considered first by subsequent lookups. Note that it
-
    /// is only guaranteed that the index will be visible to readers if it
-
    /// resides in the `git_dir` this [`Shared`] was initialised with.
-
    pub fn push(&self, idx: pack::Index) {
-
        let lock = self.write.lock();
-
        let mut new = self.indices.load_full();
-
        Arc::make_mut(&mut new).push_front(Arc::new(idx));
-
        self.indices.store(new);
-
        drop(lock);
-

-
        self.stats.record_push()
-
    }
-

-
    /// Re-scan the packs directory and replace the in-memory indices with the
-
    /// result.
-
    ///
-
    /// If the application can intercept compaction events, this method can be
-
    /// used to release memory early. Otherwise it is not required to call this
-
    /// method, as [`Shared`] manages reloads automatically.
-
    pub fn reload(&self) -> Result<(), error::Discover> {
-
        let lock = self.write.lock();
-
        let indices = discover(&self.pack_dir)?;
-
        self.indices.store(Arc::new(indices));
-
        drop(lock);
-

-
        self.stats.record_reload();
-

-
        Ok(())
-
    }
-

-
    pub fn is_empty(&self) -> bool {
-
        self.indices.load().is_empty()
-
    }
-

-
    pub fn len(&self) -> usize {
-
        self.indices.load().len()
-
    }
-

-
    fn contains(&self, id: impl AsRef<oid>) -> bool {
-
        for i in 0..2 {
-
            for idx in self.indices.load().iter() {
-
                if idx.contains(&id) {
-
                    self.stats.record_hit();
-
                    return true;
-
                }
-
            }
-

-
            if i == 0 && self.reload().is_err() {
-
                self.stats.record_miss();
-
                return false;
-
            }
-
        }
-

-
        self.stats.record_miss();
-
        false
-
    }
-

-
    fn lookup<'a, F, E>(
-
        &self,
-
        pack_cache: F,
-
        id: impl AsRef<oid>,
-
        buf: &'a mut Vec<u8>,
-
        cache: &mut impl DecodeEntry,
-
    ) -> Result<Option<Object<'a>>, error::Lookup<E>>
-
    where
-
        F: FnOnce(&pack::Info) -> Result<Arc<pack::Data>, E>,
-
    {
-
        for i in 0..2 {
-
            for idx in self.indices.load().iter() {
-
                if let Some(ofs) = idx.ofs(&id) {
-
                    self.stats.record_hit();
-
                    return load_obj(ofs, idx, pack_cache, buf, cache).map(Some);
-
                }
-
            }
-

-
            if i == 0 {
-
                self.reload()?;
-
            }
-
        }
-

-
        self.stats.record_miss();
-
        Ok(None)
-
    }
-
}
-

-
fn load_obj<'a, F, E>(
-
    ofs: u64,
-
    idx: &pack::Index,
-
    pack_cache: F,
-
    buf: &'a mut Vec<u8>,
-
    cache: &mut impl DecodeEntry,
-
) -> Result<Object<'a>, error::Lookup<E>>
-
where
-
    F: FnOnce(&pack::Info) -> Result<Arc<pack::Data>, E>,
-
{
-
    let data = pack_cache(&idx.info).map_err(error::Lookup::Lookup)?;
-
    let pack = data.file();
-
    let entry = pack.entry(ofs);
-
    let obj = pack
-
        .decode_entry(
-
            entry,
-
            buf,
-
            |id, _| idx.ofs(id).map(|ofs| ResolvedBase::InPack(pack.entry(ofs))),
-
            cache,
-
        )
-
        .map(move |out| Object {
-
            kind: out.kind,
-
            data: buf.as_slice(),
-
            pack_location: None,
-
        })?;
-

-
    Ok(obj)
-
}
-

-
fn discover(pack_dir: impl AsRef<Path>) -> Result<im::Vector<Arc<pack::Index>>, error::Discover> {
-
    let pack_dir = pack_dir.as_ref();
-
    let pack_dir_disp = pack_dir.display();
-
    trace!("discovering packs at {}", pack_dir_disp);
-
    match fs::read_dir(pack_dir) {
-
        Ok(iter) => {
-
            let mut paths = Vec::new();
-
            for entry in iter {
-
                let entry = entry?;
-
                let path = entry.path();
-
                trace!("{}", path.display());
-
                let meta = entry.metadata()?;
-
                if meta.file_type().is_file() && path.extension().unwrap_or_default() == "idx" {
-
                    let mtime = meta.modified()?;
-
                    paths.push((path, mtime));
-
                }
-
            }
-
            paths.sort_by(|(_, mtime_a), (_, mtime_b)| mtime_a.cmp(mtime_b));
-

-
            let indices = paths
-
                .into_iter()
-
                .rev()
-
                .map(|(path, _)| Ok(pack::Index::open(path).map(Arc::new)?))
-
                .collect::<Result<_, error::Discover>>()?;
-

-
            Ok(indices)
-
        },
-
        // It's not an error if the directory doesn't exist, the repository
-
        // could contain only loose objects
-
        Err(e) if e.kind() == io::ErrorKind::NotFound => {
-
            trace!("not a directory: {}", pack_dir_disp);
-
            Ok(im::Vector::new())
-
        },
-
        Err(e) => Err(e.into()),
-
    }
-
}
-

-
impl<M> Index for Shared<M>
-
where
-
    M: Metrics,
-
{
-
    fn contains(&self, id: impl AsRef<oid>) -> bool {
-
        self.contains(id)
-
    }
-

-
    fn lookup<'a, F, E>(
-
        &self,
-
        pack_cache: F,
-
        id: impl AsRef<oid>,
-
        buf: &'a mut Vec<u8>,
-
        cache: &mut impl DecodeEntry,
-
    ) -> Result<Option<Object<'a>>, error::Lookup<E>>
-
    where
-
        F: FnOnce(&pack::Info) -> Result<Arc<pack::Data>, E>,
-
    {
-
        self.lookup(pack_cache, id, buf, cache)
-
    }
-
}
deleted archived/link-git/src/odb/index/metrics.rs
@@ -1,80 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::sync::atomic::{AtomicUsize, Ordering};
-

-
pub struct StatsView {
-
    /// Total number of times a lookup was successful.
-
    pub hits: usize,
-
    /// Total number of times a lookup was unsuccessful.
-
    pub misses: usize,
-
    /// Total number of times an index was added explicitly via
-
    /// [`super::Shared::push`].
-
    pub pushes: usize,
-
    /// Total number of reloads via [`super::Shared::reload`].
-
    pub reloads: usize,
-
    /// Number of [`crate::odb::pack::Index`]es currently held.
-
    pub indices: usize,
-
}
-

-
#[derive(Default)]
-
pub struct Stats {
-
    hits: AtomicUsize,
-
    misses: AtomicUsize,
-
    pushes: AtomicUsize,
-
    reloads: AtomicUsize,
-
}
-

-
pub trait Metrics {
-
    type Snapshot;
-

-
    fn record_hit(&self);
-
    fn record_miss(&self);
-
    fn record_push(&self);
-
    fn record_reload(&self);
-

-
    fn snapshot(&self, indices: usize) -> Self::Snapshot;
-
}
-

-
impl Metrics for Stats {
-
    type Snapshot = StatsView;
-

-
    fn record_hit(&self) {
-
        self.hits.fetch_add(1, Ordering::Relaxed);
-
    }
-

-
    fn record_miss(&self) {
-
        self.misses.fetch_add(1, Ordering::Relaxed);
-
    }
-

-
    fn record_push(&self) {
-
        self.pushes.fetch_add(1, Ordering::Relaxed);
-
    }
-

-
    fn record_reload(&self) {
-
        self.reloads.fetch_add(1, Ordering::Relaxed);
-
    }
-

-
    fn snapshot(&self, indices: usize) -> Self::Snapshot {
-
        StatsView {
-
            hits: self.hits.load(Ordering::Relaxed),
-
            misses: self.misses.load(Ordering::Relaxed),
-
            pushes: self.pushes.load(Ordering::Relaxed),
-
            reloads: self.reloads.load(Ordering::Relaxed),
-
            indices,
-
        }
-
    }
-
}
-

-
impl Metrics for () {
-
    type Snapshot = ();
-

-
    fn record_hit(&self) {}
-
    fn record_miss(&self) {}
-
    fn record_push(&self) {}
-
    fn record_reload(&self) {}
-

-
    fn snapshot(&self, _: usize) -> Self::Snapshot {}
-
}
deleted archived/link-git/src/odb/pack.rs
@@ -1,138 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    path::{Path, PathBuf},
-
    sync::atomic::{AtomicUsize, Ordering},
-
};
-

-
use git_hash::{oid, ObjectId};
-
use git_pack::{data, index};
-
use rustc_hash::FxHasher;
-
use tracing::warn;
-

-
pub mod error {
-
    use super::*;
-
    use thiserror::Error;
-

-
    #[derive(Debug, Error)]
-
    #[error("failed to load pack data from {path:?}")]
-
    pub struct Data {
-
        pub path: PathBuf,
-
        pub source: data::header::decode::Error,
-
    }
-

-
    #[derive(Debug, Error)]
-
    #[error("failed to load pack index from {path:?}")]
-
    pub struct Index {
-
        pub path: PathBuf,
-
        pub source: index::init::Error,
-
    }
-
}
-

-
pub struct Data {
-
    pub hash: u64,
-
    hits: AtomicUsize,
-
    file: data::File,
-
}
-

-
impl Data {
-
    pub fn hit(&self) {
-
        self.hits.fetch_add(1, Ordering::Relaxed);
-
    }
-

-
    pub fn hits(&self) -> usize {
-
        self.hits.load(Ordering::Relaxed)
-
    }
-

-
    pub fn file(&self) -> &data::File {
-
        &self.file
-
    }
-
}
-

-
impl AsRef<data::File> for Data {
-
    fn as_ref(&self) -> &data::File {
-
        self.file()
-
    }
-
}
-

-
#[derive(Clone, PartialEq, Eq)]
-
pub struct Info {
-
    pub(super) hash: u64,
-
    pub data_path: PathBuf,
-
}
-

-
impl Info {
-
    pub fn data(&self) -> Result<Data, error::Data> {
-
        let file = data::File::at(&self.data_path).map_err(|source| error::Data {
-
            path: self.data_path.clone(),
-
            source,
-
        })?;
-
        Ok(Data {
-
            hash: self.hash,
-
            hits: AtomicUsize::new(0),
-
            file,
-
        })
-
    }
-
}
-

-
pub struct Index {
-
    pub info: Info,
-
    file: index::File,
-
}
-

-
impl Index {
-
    pub fn open(path: impl AsRef<Path>) -> Result<Self, error::Index> {
-
        let path = path.as_ref();
-
        let file = index::File::at(path).map_err(|source| error::Index {
-
            path: path.to_path_buf(),
-
            source,
-
        })?;
-
        let data_path = path.with_extension("pack");
-
        let hash = {
-
            let file_name = path
-
                .file_name()
-
                .expect("must have a file name, we opened it")
-
                .to_string_lossy();
-
            // XXX: inexplicably, gitoxide omits the "pack-" prefix
-
            let sha_hex = file_name.strip_prefix("pack-").unwrap_or(&file_name);
-
            match ObjectId::from_hex(&sha_hex.as_bytes()[..40]) {
-
                Err(e) => {
-
                    warn!(
-
                        "unconventional pack name {:?}, falling back to fxhash: {}",
-
                        path, e
-
                    );
-
                    hash(path)
-
                },
-
                Ok(oid) => {
-
                    let mut buf = [0u8; 8];
-
                    buf.copy_from_slice(&oid.sha1()[..8]);
-
                    u64::from_be_bytes(buf)
-
                },
-
            }
-
        };
-
        let info = Info { hash, data_path };
-

-
        Ok(Self { file, info })
-
    }
-

-
    pub fn contains(&self, id: impl AsRef<oid>) -> bool {
-
        self.file.lookup(id).is_some()
-
    }
-

-
    pub fn ofs(&self, id: impl AsRef<oid>) -> Option<u64> {
-
        self.file
-
            .lookup(id)
-
            .map(|idx| self.file.pack_offset_at_index(idx))
-
    }
-
}
-

-
fn hash(p: &Path) -> u64 {
-
    use std::hash::{Hash as _, Hasher as _};
-

-
    let mut hasher = FxHasher::default();
-
    p.hash(&mut hasher);
-
    hasher.finish()
-
}
deleted archived/link-git/src/odb/window.rs
@@ -1,183 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::sync::Arc;
-

-
use arc_swap::{ArcSwap, Guard};
-
use parking_lot::Mutex;
-

-
use super::pack;
-

-
mod metrics;
-
pub use metrics::{Metrics, Stats, StatsView};
-

-
/// A threadsafe, shareable cache of packfiles.
-
pub trait Cache {
-
    type Stats;
-

-
    fn stats(&self) -> Self::Stats;
-

-
    fn get(&self, info: &pack::Info) -> Result<Arc<pack::Data>, pack::error::Data>;
-
}
-

-
impl<M, const B: usize, const S: usize> Cache for Fixed<M, B, S>
-
where
-
    M: Metrics,
-
{
-
    type Stats = M::Snapshot;
-

-
    fn stats(&self) -> Self::Stats {
-
        self.stats()
-
    }
-

-
    fn get(&self, info: &pack::Info) -> Result<Arc<pack::Data>, pack::error::Data> {
-
        self.get(info)
-
    }
-
}
-

-
/// 128 open files
-
pub type Small<S> = Fixed<S, 16, 8>;
-
/// 512 open files
-
pub type Medium<S> = Fixed<S, 32, 16>;
-
/// 1024 open files
-
pub type Large<S> = Fixed<S, 64, 16>;
-
/// 2048 open files
-
pub type XLarge<S> = Fixed<S, 128, 16>;
-

-
/// A fixed-size [`Cache`].
-
///
-
/// [`Fixed`] is essentially a very simple, fixed-capacity hashtable. When a
-
/// pack (data-) file is requested via [`Cache::get`], the file is loaded
-
/// (typically `mmap`ed) from disk if it is not already in the cache. Otherwise,
-
/// a pointer to the already loaded file is returned. Old entries are replaced
-
/// on an approximate LRU basis when the cache becomes full (this means that old
-
/// entries are **not** evicted when there is still space).
-
///
-
/// The implementation is a somewhat dumbed-down version of JGit's
-
/// `WindowCache`. The main differences are that the table buckets are of fixed
-
/// size (`SLOTS`), instead of a linked list. This means that the cache does not
-
/// allow to (temporarily) commit more entries than its nominal capacity.
-
///
-
/// Reading cached values is lock-free and mostly wait-free. Modifications are
-
/// guarded by locks on individual buckets; if a cache miss occurs, multiple
-
/// threads requesting the same entry will be blocked until one of them
-
/// succeeded loading the data and updating the cache. Writers will _not_,
-
/// however, contend with readers (unlike `RwLock`).
-
///
-
/// This favours usage patterns where different threads tend to request disjoint
-
/// sets of packfiles, and of course their hashes colliding relatively
-
/// infrequently.
-
pub struct Fixed<M, const BUCKETS: usize, const SLOTS: usize> {
-
    entries: [ArcSwap<[Option<Arc<pack::Data>>; SLOTS]>; BUCKETS],
-
    locks: [Mutex<()>; BUCKETS],
-
    stats: M,
-
}
-

-
trait AssertSendSync: Send + Sync {}
-
impl<M, const B: usize, const S: usize> AssertSendSync for Fixed<M, B, S> where M: Send + Sync {}
-

-
impl<M, const B: usize, const S: usize> AsRef<Fixed<M, B, S>> for Fixed<M, B, S> {
-
    fn as_ref(&self) -> &Fixed<M, B, S> {
-
        self
-
    }
-
}
-

-
impl<const B: usize, const S: usize> Default for Fixed<(), B, S> {
-
    fn default() -> Self {
-
        Self {
-
            entries: [(); B].map(|_| ArcSwap::new(Arc::new([(); S].map(|_| None)))),
-
            locks: [(); B].map(|_| Mutex::new(())),
-
            stats: (),
-
        }
-
    }
-
}
-

-
impl<M, const B: usize, const S: usize> Fixed<M, B, S>
-
where
-
    M: Metrics,
-
{
-
    pub fn with_stats(self) -> Fixed<Stats, B, S> {
-
        self.with_metrics(Stats::default())
-
    }
-

-
    pub fn with_metrics<N: Metrics>(self, m: N) -> Fixed<N, B, S> {
-
        Fixed {
-
            entries: self.entries,
-
            locks: self.locks,
-
            stats: m,
-
        }
-
    }
-

-
    pub fn stats(&self) -> M::Snapshot {
-
        let open_files = self
-
            .entries
-
            .iter()
-
            .map(|bucket| bucket.load().iter().flatten().count())
-
            .sum();
-
        self.stats.snapshot(open_files)
-
    }
-

-
    pub fn get(&self, info: &pack::Info) -> Result<Arc<pack::Data>, pack::error::Data> {
-
        let idx = info.hash as usize % self.entries.len();
-

-
        let bucket = self.entries[idx].load();
-
        for entry in bucket.iter().flatten() {
-
            if entry.hash == info.hash {
-
                self.stats.record_hit();
-
                entry.hit();
-
                return Ok(Arc::clone(entry));
-
            }
-
        }
-
        drop(bucket);
-

-
        self.stats.record_miss();
-

-
        // Cache miss, try to load the data file
-
        let lock = self.locks[idx].lock();
-
        // Did someone else win the race for the lock?
-
        let bucket = self.entries[idx].load();
-
        for entry in bucket.iter().flatten() {
-
            if entry.hash == info.hash {
-
                self.stats.record_hit();
-
                entry.hit();
-
                return Ok(Arc::clone(entry));
-
            }
-
        }
-
        // No, proceed
-
        self.stats.record_load();
-
        let data = Arc::new(info.data()?);
-

-
        // Find an empty slot, or swap with the least popular
-
        let mut access = usize::MAX;
-
        let mut evict = 0;
-
        for (i, e) in bucket.iter().enumerate() {
-
            match e {
-
                Some(entry) => {
-
                    let hits = entry.hits();
-
                    if hits < access {
-
                        access = hits;
-
                        evict = i;
-
                    }
-
                },
-
                None => {
-
                    evict = i;
-
                    break;
-
                },
-
            }
-
        }
-
        let mut entries = Guard::into_inner(bucket);
-
        {
-
            // This costs `SLOTS` refcount increments if the slot is currently
-
            // borrowed.
-
            let mutti = Arc::make_mut(&mut entries);
-
            mutti[evict] = Some(Arc::clone(&data));
-
        }
-
        self.entries[idx].store(entries);
-
        drop(lock);
-

-
        data.hit();
-
        Ok(data)
-
    }
-
}
deleted archived/link-git/src/odb/window/metrics.rs
@@ -1,79 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::sync::atomic::{AtomicUsize, Ordering};
-

-
use tracing::trace;
-

-
pub struct StatsView {
-
    /// Total number of times the requested data was found in the cache.
-
    pub cache_hits: usize,
-
    /// Total number of times the requested data was not found in the cache.
-
    ///
-
    /// Note that a cache hit can occur after a miss if another thread was
-
    /// faster to fill in the missing entry. Thus, `cache_hits + cache_misses`
-
    /// does not necessarily sum up to the number of cache accesses.
-
    pub cache_misses: usize,
-
    /// Total number of times a pack file was attempted to be loaded from disk
-
    /// (incl. failed attempts).
-
    pub file_loads: usize,
-
    /// Total number of pack files the cache holds on to.
-
    pub open_files: usize,
-
}
-

-
#[derive(Default)]
-
pub struct Stats {
-
    hits: AtomicUsize,
-
    miss: AtomicUsize,
-
    load: AtomicUsize,
-
}
-

-
pub trait Metrics {
-
    type Snapshot;
-

-
    fn record_hit(&self);
-
    fn record_miss(&self);
-
    fn record_load(&self);
-

-
    fn snapshot(&self, open_files: usize) -> Self::Snapshot;
-
}
-

-
impl Metrics for Stats {
-
    type Snapshot = StatsView;
-

-
    fn record_hit(&self) {
-
        trace!("cache hit");
-
        self.hits.fetch_add(1, Ordering::Relaxed);
-
    }
-

-
    fn record_miss(&self) {
-
        trace!("cache miss");
-
        self.miss.fetch_add(1, Ordering::Relaxed);
-
    }
-

-
    fn record_load(&self) {
-
        trace!("pack load");
-
        self.load.fetch_add(1, Ordering::Relaxed);
-
    }
-

-
    fn snapshot(&self, open_files: usize) -> Self::Snapshot {
-
        StatsView {
-
            cache_hits: self.hits.load(Ordering::Relaxed),
-
            cache_misses: self.miss.load(Ordering::Relaxed),
-
            file_loads: self.load.load(Ordering::Relaxed),
-
            open_files,
-
        }
-
    }
-
}
-

-
impl Metrics for () {
-
    type Snapshot = ();
-

-
    fn record_hit(&self) {}
-
    fn record_miss(&self) {}
-
    fn record_load(&self) {}
-

-
    fn snapshot(&self, _: usize) -> Self::Snapshot {}
-
}
deleted archived/link-git/src/protocol.rs
@@ -1,30 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use bstr::ByteSlice as _;
-
use git_protocol::transport::client;
-
use versions::Version;
-

-
pub mod fetch;
-
pub mod ls;
-
pub mod packwriter;
-
pub mod take;
-
pub mod transport;
-
pub mod upload_pack;
-

-
pub use fetch::{fetch, Ref};
-
pub use ls::ls_refs;
-
pub use packwriter::PackWriter;
-
pub use upload_pack::upload_pack;
-

-
pub use git_hash::{oid, ObjectId};
-

-
fn remote_git_version(caps: &client::Capabilities) -> Option<Version> {
-
    let agent = caps.capability("agent").and_then(|cap| {
-
        cap.value()
-
            .and_then(|bs| bs.to_str().map(|s| s.to_owned()).ok())
-
    })?;
-
    Version::new(agent.strip_prefix("git/")?)
-
}
deleted archived/link-git/src/protocol/fetch.rs
@@ -1,290 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    future::Future,
-
    io,
-
    mem,
-
    pin::Pin,
-
    sync::{
-
        atomic::{AtomicBool, Ordering},
-
        Arc,
-
    },
-
    task::{Context, Poll},
-
};
-

-
use bstr::{BString, ByteSlice as _};
-
use futures_lite::{
-
    future,
-
    io::{AsyncBufRead, AsyncRead, AsyncWrite},
-
};
-
use git_features::progress::{self, Progress};
-
use git_protocol::{
-
    fetch::{response, Action, Arguments, Delegate, DelegateBlocking, LsRefsAction, Response},
-
    transport::client,
-
};
-
use once_cell::sync::Lazy;
-
use pin_project::{pin_project, pinned_drop};
-
use versions::Version;
-

-
pub use git_hash::ObjectId;
-
pub use git_protocol::fetch::Ref;
-

-
use super::{packwriter::PackWriter, remote_git_version, transport};
-

-
// Work around `git-upload-pack` not handling namespaces properly,
-
//
-
// cf. https://lore.kernel.org/git/CD2XNXHACAXS.13J6JTWZPO1JA@schmidt/
-
// Fixed in `git.git` 1ab13eb, which should land in 2.34
-
//
-
// Based on testing with git 2.25.1 in Ubuntu 20.04, this workaround is
-
// not needed. Hence the checked version is lowered to 2.25.0.
-
fn must_namespace_want_ref(caps: &client::Capabilities) -> bool {
-
    static FIXED_AFTER: Lazy<Version> = Lazy::new(|| Version::new("2.25.0").unwrap());
-

-
    remote_git_version(caps)
-
        .map(|version| version <= *FIXED_AFTER)
-
        .unwrap_or(false)
-
}
-

-
#[derive(Debug)]
-
pub struct Options {
-
    /// The remote (logical) repository to fetch from.
-
    ///
-
    /// Normally, this is the path to a repo on the remote side (eg.
-
    /// `/git.git`). `radicle-link` serves only a single namespaced repo, so
-
    /// this value should be the name of a namespace.
-
    pub repo: BString,
-

-
    /// [Extra Parameters][extra] to send with the initial transport header.
-
    ///
-
    /// [extra]: https://git.kernel.org/pub/scm/git/git.git/tree/Documentation/technical/pack-protocol.txt#n52
-
    pub extra_params: Vec<(String, Option<String>)>,
-

-
    /// [`ObjectId`]s to send as `want` lines.
-
    pub wants: Vec<ObjectId>,
-

-
    /// [`ObjectId`]s to send as `have` lines.
-
    pub haves: Vec<ObjectId>,
-

-
    /// Known refs to ask the server to include in the packfile.
-
    pub want_refs: Vec<BString>,
-
}
-

-
/// Result of a succesful [`fetch`].
-
#[derive(Debug)]
-
pub struct Outputs<T> {
-
    /// The `wanted-refs` as acknowledged by the server.
-
    pub wanted_refs: Vec<Ref>,
-
    /// If a packfile was received successfully, some info about it.
-
    pub pack: Option<T>,
-
}
-

-
impl<T> Default for Outputs<T> {
-
    fn default() -> Self {
-
        Self {
-
            wanted_refs: Vec::new(),
-
            pack: None,
-
        }
-
    }
-
}
-

-
/// [`Delegate`] driving the fetch end of the [pack protocol].
-
///
-
/// [pack protocol]: https://git.kernel.org/pub/scm/git/git.git/tree/Documentation/technical/pack-protocol.txt
-
pub struct Fetch<P, O> {
-
    opt: Options,
-
    pack_writer: P,
-
    out: Outputs<O>,
-
    need_namespaced_want_ref: bool,
-
}
-

-
impl<P, O> Fetch<P, O> {
-
    pub fn new(opt: Options, pack_writer: P) -> Self {
-
        Self {
-
            opt,
-
            pack_writer,
-
            out: Outputs::default(),
-
            need_namespaced_want_ref: false,
-
        }
-
    }
-

-
    pub fn outputs(&self) -> &Outputs<O> {
-
        &self.out
-
    }
-

-
    pub fn take_outputs(&mut self) -> Outputs<O> {
-
        mem::take(&mut self.out)
-
    }
-
}
-

-
impl<P: PackWriter> DelegateBlocking for Fetch<P, P::Output> {
-
    fn handshake_extra_parameters(&self) -> Vec<(String, Option<String>)> {
-
        self.opt.extra_params.clone()
-
    }
-

-
    fn prepare_ls_refs(
-
        &mut self,
-
        _: &client::Capabilities,
-
        _: &mut Vec<BString>,
-
        _: &mut Vec<(&str, Option<&str>)>,
-
    ) -> io::Result<LsRefsAction> {
-
        Ok(LsRefsAction::Skip)
-
    }
-

-
    fn prepare_fetch(
-
        &mut self,
-
        _: git_protocol::transport::Protocol,
-
        caps: &client::Capabilities,
-
        _: &mut Vec<(&str, Option<&str>)>,
-
        _: &[Ref],
-
    ) -> io::Result<Action> {
-
        if !self.opt.want_refs.is_empty() && !remote_supports_ref_in_want(caps) {
-
            return Err(io::Error::new(
-
                io::ErrorKind::Unsupported,
-
                "`want-ref`s given, but server does not support `ref-in-want`",
-
            ));
-
        }
-

-
        if self.opt.wants.is_empty() && self.opt.want_refs.is_empty() {
-
            return Err(io::Error::new(
-
                io::ErrorKind::InvalidData,
-
                "`fetch` is empty",
-
            ));
-
        }
-

-
        self.need_namespaced_want_ref = must_namespace_want_ref(caps);
-

-
        Ok(Action::Continue)
-
    }
-

-
    fn negotiate(
-
        &mut self,
-
        _: &[Ref],
-
        args: &mut Arguments,
-
        _: Option<&Response>,
-
    ) -> io::Result<Action> {
-
        for oid in &self.opt.wants {
-
            args.want(oid);
-
        }
-

-
        for oid in &self.opt.haves {
-
            args.have(oid)
-
        }
-

-
        for name in &self.opt.want_refs {
-
            if self.need_namespaced_want_ref {
-
                let want_ref = format!("refs/namespaces/{}/{}", self.opt.repo, name);
-
                args.want_ref(BString::from(want_ref).as_bstr());
-
            } else {
-
                args.want_ref(name.as_bstr());
-
            }
-
        }
-

-
        // send done, as we don't bother with further negotiation
-
        Ok(Action::Cancel)
-
    }
-
}
-

-
#[async_trait(?Send)]
-
impl<P: PackWriter> Delegate for Fetch<P, P::Output> {
-
    async fn receive_pack(
-
        &mut self,
-
        pack: impl AsyncBufRead + Unpin + 'async_trait,
-
        prog: impl Progress,
-
        _: &[Ref],
-
        resp: &Response,
-
    ) -> io::Result<()> {
-
        // Strip any namespaces leaked by the other end due to workarounds
-
        let namespace = format!("refs/namespaces/{}/", self.opt.repo);
-
        self.out.wanted_refs.extend(resp.wanted_refs().iter().map(
-
            |response::WantedRef { id, path }| {
-
                Ref::Direct {
-
                    path: path
-
                        .strip_prefix(namespace.as_bytes())
-
                        .map(BString::from)
-
                        .unwrap_or_else(|| path.clone()),
-
                    object: *id,
-
                }
-
            },
-
        ));
-
        let out = self.pack_writer.write_pack(pack, prog)?;
-
        self.out.pack = Some(out);
-

-
        Ok(())
-
    }
-
}
-

-
/// Future created by the [`fetch`] function.
-
///
-
/// Ensures that a running inner [`PackWriter`] is cancelled when the
-
/// [`Fetching`] future is dropped without also dropping the [`AsyncRead`] data
-
/// source.
-
#[pin_project(PinnedDrop)]
-
struct Fetching<T> {
-
    stop: Arc<AtomicBool>,
-
    #[pin]
-
    task: T,
-
}
-

-
#[pinned_drop]
-
impl<T> PinnedDrop for Fetching<T> {
-
    fn drop(self: Pin<&mut Self>) {
-
        self.stop.store(true, Ordering::Release)
-
    }
-
}
-

-
impl<T> Future for Fetching<T>
-
where
-
    T: Future,
-
{
-
    type Output = T::Output;
-

-
    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
-
        self.project().task.poll(cx)
-
    }
-
}
-

-
pub fn fetch<B, P, R, W>(
-
    opt: Options,
-
    build_pack_writer: B,
-
    recv: R,
-
    send: W,
-
) -> impl Future<Output = io::Result<Outputs<P::Output>>>
-
where
-
    B: FnOnce(Arc<AtomicBool>) -> P,
-
    P: PackWriter + Send + 'static,
-
    P::Output: Send + 'static,
-
    R: AsyncRead + Unpin + Send + 'static,
-
    W: AsyncWrite + Unpin + Send + 'static,
-
{
-
    let stop = Arc::new(AtomicBool::new(false));
-
    let task = blocking::unblock({
-
        let mut conn = transport::Stateless::new(opt.repo.clone(), recv, send);
-
        let pack_writer = build_pack_writer(Arc::clone(&stop));
-

-
        move || {
-
            let mut delegate = Fetch::new(opt, pack_writer);
-
            future::block_on(git_protocol::fetch(
-
                &mut conn,
-
                &mut delegate,
-
                |_| unreachable!("credentials helper requested"),
-
                progress::Discard,
-
                git_protocol::FetchConnection::AllowReuse,
-
            ))
-
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
-

-
            Ok(delegate.out)
-
        }
-
    });
-

-
    Fetching { stop, task }
-
}
-

-
fn remote_supports_ref_in_want(caps: &client::Capabilities) -> bool {
-
    caps.capability("fetch")
-
        .and_then(|cap| cap.supports("ref-in-want"))
-
        .unwrap_or(false)
-
}
deleted archived/link-git/src/protocol/ls.rs
@@ -1,151 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::io;
-

-
use bstr::{BString, ByteVec as _};
-
use futures_lite::io::{AsyncBufRead, AsyncRead, AsyncWrite};
-
use git_features::progress::{self, Progress};
-
use git_protocol::{
-
    fetch::{Action, Arguments, Delegate, DelegateBlocking, LsRefsAction, Response},
-
    transport::client,
-
};
-
use once_cell::sync::Lazy;
-
use versions::Version;
-

-
pub use git_protocol::fetch::Ref;
-

-
use super::{remote_git_version, transport};
-

-
// Work around `git-upload-pack` not handling namespaces properly
-
//
-
// cf. https://lore.kernel.org/git/pMV5dJabxOBTD8kJBaPuWK0aS6OJhRQ7YFGwfhPCeSJEbPDrIFBza36nXBCgUCeUJWGmpjPI1rlOGvZJEh71Ruz4SqljndUwOCoBUDRHRDU=@eagain.st/
-
//
-
// Based on testing with git 2.25.1 in Ubuntu 20.04, this workaround is
-
// not needed. Hence the checked version is lowered to 2.25.0.
-
fn must_namespace(caps: &client::Capabilities) -> bool {
-
    static MIN_GIT_VERSION_NAMESPACES: Lazy<Version> =
-
        Lazy::new(|| Version::new("2.25.0").unwrap());
-

-
    remote_git_version(caps)
-
        .map(|version| version < *MIN_GIT_VERSION_NAMESPACES)
-
        .unwrap_or(false)
-
}
-

-
#[derive(Debug)]
-
pub struct Options {
-
    /// The remote (logical) repository to fetch from.
-
    ///
-
    /// Normally, this is the path to a repo on the remote side (eg.
-
    /// `/git.git`). `radicle-link` serves only a single namespaced repo, so
-
    /// this value should be the name of a namespace.
-
    pub repo: BString,
-

-
    /// [Extra Parameters][extra] to send with the initial transport header.
-
    ///
-
    /// [extra]: https://git.kernel.org/pub/scm/git/git.git/tree/Documentation/technical/pack-protocol.txt#n52
-
    pub extra_params: Vec<(String, Option<String>)>,
-

-
    /// Prefixes of refs to ask the server to advertise via `ls-refs`.
-
    ///
-
    /// If the [`Vec`] is empty, the server is asked to return all refs it knows
-
    /// about. Otherwise, the server is asked to only return refs matching
-
    /// the given prefixes.
-
    pub ref_prefixes: Vec<BString>,
-
}
-

-
/// [`Delegate`] for running a stateless `ls-refs` command.
-
pub struct LsRefs {
-
    opt: Options,
-
    out: Vec<Ref>,
-
}
-

-
impl LsRefs {
-
    pub fn new(opt: Options) -> Self {
-
        Self {
-
            opt,
-
            out: Vec::new(),
-
        }
-
    }
-
}
-

-
impl DelegateBlocking for LsRefs {
-
    fn handshake_extra_parameters(&self) -> Vec<(String, Option<String>)> {
-
        self.opt.extra_params.clone()
-
    }
-

-
    fn prepare_ls_refs(
-
        &mut self,
-
        caps: &client::Capabilities,
-
        args: &mut Vec<BString>,
-
        _: &mut Vec<(&str, Option<&str>)>,
-
    ) -> io::Result<LsRefsAction> {
-
        let must_namespace = must_namespace(caps);
-
        for prefix in &self.opt.ref_prefixes {
-
            let mut arg = BString::from("ref-prefix ");
-
            if must_namespace {
-
                arg.push_str("refs/namespaces/");
-
                arg.push_str(&self.opt.repo);
-
                arg.push_char('/');
-
            }
-
            arg.push_str(prefix);
-
            args.push(arg)
-
        }
-
        Ok(LsRefsAction::Continue)
-
    }
-

-
    fn prepare_fetch(
-
        &mut self,
-
        _: git_protocol::transport::Protocol,
-
        _: &client::Capabilities,
-
        _: &mut Vec<(&str, Option<&str>)>,
-
        refs: &[Ref],
-
    ) -> io::Result<Action> {
-
        self.out.extend_from_slice(refs);
-
        Ok(Action::Cancel)
-
    }
-

-
    fn negotiate(
-
        &mut self,
-
        _: &[Ref],
-
        _: &mut Arguments,
-
        _: Option<&Response>,
-
    ) -> io::Result<Action> {
-
        unreachable!("`negotiate` called even though no `fetch` command was sent")
-
    }
-
}
-

-
#[async_trait(?Send)]
-
impl Delegate for LsRefs {
-
    async fn receive_pack(
-
        &mut self,
-
        _: impl AsyncBufRead + Unpin + 'async_trait,
-
        _: impl Progress,
-
        _: &[Ref],
-
        _: &Response,
-
    ) -> io::Result<()> {
-
        unreachable!("`receive_pack` called even though no `fetch` command was sent")
-
    }
-
}
-

-
pub async fn ls_refs<R, W>(opt: Options, recv: R, send: W) -> io::Result<Vec<Ref>>
-
where
-
    R: AsyncRead + Unpin,
-
    W: AsyncWrite + Unpin,
-
{
-
    let mut conn = transport::Stateless::new(opt.repo.clone(), recv, send);
-
    let mut delegate = LsRefs::new(opt);
-
    git_protocol::fetch(
-
        &mut conn,
-
        &mut delegate,
-
        |_| unreachable!("credentials helper requested"),
-
        progress::Discard,
-
        git_protocol::FetchConnection::AllowReuse,
-
    )
-
    .await
-
    .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
-

-
    Ok(delegate.out)
-
}
deleted archived/link-git/src/protocol/packwriter.rs
@@ -1,271 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    io,
-
    path::{Path, PathBuf},
-
    sync::{
-
        atomic::{AtomicBool, Ordering},
-
        Arc,
-
    },
-
};
-

-
use futures_lite::io::{AsyncBufRead, BlockOn};
-
use git_features::progress::Progress;
-
use git_hash::ObjectId;
-
use git_odb::{self as odb, pack};
-

-
use super::take::TryTake;
-

-
#[cfg(feature = "git2")]
-
pub use libgit::Libgit;
-

-
/// What to do with the `packfile` response.
-
///
-
/// _This is mostly the same as [`git_protocol::fetch::Delegate`], but without
-
/// incurring the [`git_protocol::fetch::DelegateBlocking`] super-trait
-
/// constraint. We can simply make [`crate::protocol::fetch::Fetch`] parametric
-
/// over the packfile sink._
-
pub trait PackWriter {
-
    type Output;
-

-
    fn write_pack(
-
        &self,
-
        pack: impl AsyncBufRead + Unpin,
-
        progress: impl Progress,
-
    ) -> io::Result<Self::Output>;
-
}
-

-
#[derive(Clone, Copy, Debug)]
-
pub struct Options {
-
    /// How many threads the packfile indexer is allowed to spawn. `None` means
-
    /// unlimited.
-
    pub max_indexer_threads: Option<usize>,
-
    /// The maximum size in bytes of the packfile.
-
    ///
-
    /// If the remote sends a larger file, the transfer will be aborted.
-
    pub max_pack_bytes: u64,
-
}
-

-
impl Default for Options {
-
    fn default() -> Self {
-
        Self {
-
            max_indexer_threads: Some(1),
-
            max_pack_bytes: u64::MAX,
-
        }
-
    }
-
}
-

-
#[cfg(feature = "git2")]
-
pub mod libgit {
-
    use super::*;
-

-
    #[derive(Clone, Copy, Debug)]
-
    pub struct PackReceived {
-
        pub objects: usize,
-
        pub local_objects: usize,
-
        pub deltas: usize,
-
    }
-

-
    impl From<git2::Progress<'_>> for PackReceived {
-
        fn from(p: git2::Progress<'_>) -> Self {
-
            Self {
-
                objects: p.indexed_objects(),
-
                local_objects: p.local_objects(),
-
                deltas: p.indexed_deltas(),
-
            }
-
        }
-
    }
-

-
    pub struct Libgit {
-
        opt: Options,
-
        repo: git2::Repository,
-
        stop: Arc<AtomicBool>,
-
    }
-

-
    impl Libgit {
-
        pub fn new(opt: Options, repo: git2::Repository, stop: Arc<AtomicBool>) -> Self {
-
            Self { opt, repo, stop }
-
        }
-

-
        fn guard_cancelled(&self) -> io::Result<()> {
-
            if self.stop.load(Ordering::Acquire) {
-
                Err(io::Error::new(io::ErrorKind::Interrupted, "cancelled"))
-
            } else {
-
                Ok(())
-
            }
-
        }
-
    }
-

-
    impl PackWriter for Libgit {
-
        type Output = Option<PackReceived>;
-

-
        fn write_pack(
-
            &self,
-
            pack: impl AsyncBufRead + Unpin,
-
            _: impl Progress,
-
        ) -> io::Result<Self::Output> {
-
            let mut out = None;
-

-
            let odb = self.repo.odb().map_err(io_error)?;
-
            let mut writer = odb.packwriter().map_err(io_error)?;
-

-
            self.guard_cancelled()?;
-
            io::copy(
-
                &mut BlockOn::new(TryTake::new(pack, self.opt.max_pack_bytes)),
-
                &mut writer,
-
            )?;
-

-
            self.guard_cancelled()?;
-
            writer
-
                .progress(|p| {
-
                    out = Some(p.to_owned());
-
                    true
-
                })
-
                .commit()
-
                .map(|_| ())
-
                .map_err(io_error)?;
-
            // Convince borrowchk that `out` can not possibly be borrowed anymore
-
            drop(writer);
-

-
            Ok(out.map(Into::into))
-
        }
-
    }
-

-
    fn io_error(e: git2::Error) -> io::Error {
-
        io::Error::new(io::ErrorKind::Other, e)
-
    }
-
}
-

-
pub type PackReceived = pack::bundle::write::Outcome;
-

-
/// A lookup function to help "thicken" thin packs by finding missing base
-
/// objects.
-
///
-
/// The impl provided for [`odb::linked::Store`] does not use any pack caching.
-
pub trait Thickener {
-
    fn find_object<'a>(&self, id: ObjectId, buf: &'a mut Vec<u8>)
-
        -> Option<pack::data::Object<'a>>;
-
}
-

-
impl Thickener for odb::linked::Store {
-
    fn find_object<'a>(
-
        &self,
-
        id: ObjectId,
-
        buf: &'a mut Vec<u8>,
-
    ) -> Option<pack::data::Object<'a>> {
-
        use git_odb::FindExt as _;
-

-
        self.find(id, buf, &mut pack::cache::Never).ok()
-
    }
-
}
-

-
/// A factory spewing out new [`Thickener`]s with static lifetimes.
-
///
-
/// `gitoxide` doesn't currently allow us to initialise thickening lazily (the
-
/// pack file may not be thin after all), but requires a static lookup function.
-
/// Instead of initialising a new [`odb::linked::Store`] for every pack stream,
-
/// users may share a pre-initialised object database provided appropriate
-
/// thread safety measures.
-
pub trait BuildThickener {
-
    type Error: std::error::Error + Send + Sync + 'static;
-
    type Thick: Thickener + 'static;
-

-
    fn build_thickener(&self) -> Result<Self::Thick, Self::Error>;
-
}
-

-
pub struct StandardThickener {
-
    git_dir: PathBuf,
-
}
-

-
impl StandardThickener {
-
    pub fn new(git_dir: impl Into<PathBuf>) -> Self {
-
        let git_dir = git_dir.into();
-
        Self { git_dir }
-
    }
-
}
-

-
impl BuildThickener for StandardThickener {
-
    type Error = odb::linked::init::Error;
-
    type Thick = odb::linked::Store;
-

-
    fn build_thickener(&self) -> Result<Self::Thick, Self::Error> {
-
        odb::linked::Store::at(self.git_dir.join("objects"))
-
    }
-
}
-

-
/// The default [`PackWriter`].
-
///
-
/// Writes the packfile into the given output directory, along with a v2
-
/// index. The packfile is verified.
-
pub struct Standard<F> {
-
    git_dir: PathBuf,
-
    opt: Options,
-
    thick: F,
-
    stop: Arc<AtomicBool>,
-
}
-

-
impl<F> Standard<F> {
-
    pub fn new(git_dir: impl AsRef<Path>, opt: Options, thick: F, stop: Arc<AtomicBool>) -> Self {
-
        Self {
-
            git_dir: git_dir.as_ref().to_owned(),
-
            opt,
-
            thick,
-
            stop,
-
        }
-
    }
-
}
-

-
impl<F> Drop for Standard<F> {
-
    fn drop(&mut self) {
-
        self.stop.store(true, Ordering::Release);
-
    }
-
}
-

-
impl<F: BuildThickener> PackWriter for Standard<F> {
-
    type Output = PackReceived;
-

-
    fn write_pack(
-
        &self,
-
        pack: impl AsyncBufRead + Unpin,
-
        prog: impl Progress,
-
    ) -> io::Result<Self::Output> {
-
        use pack::{bundle::write::Options, data::input::Mode, index::Version, Bundle};
-

-
        let opts = Options {
-
            thread_limit: self.opt.max_indexer_threads,
-
            index_kind: Version::V2,
-
            iteration_mode: Mode::Verify,
-
        };
-
        let thickener = self
-
            .thick
-
            .build_thickener()
-
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
-
        Bundle::write_to_directory(
-
            BlockOn::new(TryTake::new(pack, self.opt.max_pack_bytes)),
-
            Some(self.git_dir.join("objects").join("pack")),
-
            prog,
-
            &self.stop,
-
            Some(Box::new(move |oid, buf| thickener.find_object(oid, buf))),
-
            opts,
-
        )
-
        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
-
    }
-
}
-

-
/// No-op [`PackWriter`] which just drains the input.
-
pub struct Discard;
-

-
impl PackWriter for Discard {
-
    type Output = u64;
-

-
    fn write_pack(
-
        &self,
-
        pack: impl AsyncBufRead + Unpin,
-
        _: impl Progress,
-
    ) -> io::Result<Self::Output> {
-
        io::copy(&mut BlockOn::new(pack), &mut io::sink())
-
    }
-
}
deleted archived/link-git/src/protocol/take.rs
@@ -1,76 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    io,
-
    pin::Pin,
-
    task::{Context, Poll},
-
};
-

-
use futures_lite::io::{AsyncBufRead, AsyncRead};
-

-
/// Like [`futures_lite::io::Take`], but returns an error if and when the
-
/// `limit` is exceeded.
-
///
-
/// Note that, unlike [`futures_lite::io::Take`], if a single poll reads past
-
/// the limit, the excess bytes are _not_ discarded. Instead, an error is
-
/// returned on the next poll.
-
pub struct TryTake<R> {
-
    limit: u64,
-
    inner: R,
-
}
-

-
impl<R> TryTake<R> {
-
    pub fn new(inner: R, limit: u64) -> Self {
-
        Self { limit, inner }
-
    }
-
}
-

-
impl<R> AsyncRead for TryTake<R>
-
where
-
    R: AsyncRead + Unpin,
-
{
-
    fn poll_read(
-
        self: Pin<&mut Self>,
-
        cx: &mut Context,
-
        buf: &mut [u8],
-
    ) -> Poll<Result<usize, io::Error>> {
-
        if self.limit == 0 {
-
            return Poll::Ready(Err(io::Error::new(
-
                io::ErrorKind::Other,
-
                "max input size exceeded",
-
            )));
-
        }
-

-
        let this = self.get_mut();
-
        Pin::new(&mut this.inner).poll_read(cx, buf).map(|ready| {
-
            if let Ok(siz) = ready {
-
                this.limit = this.limit.saturating_sub(siz as u64);
-
            }
-

-
            ready
-
        })
-
    }
-
}
-

-
impl<R> AsyncBufRead for TryTake<R>
-
where
-
    R: AsyncBufRead + Unpin,
-
{
-
    fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<&[u8], io::Error>> {
-
        if self.limit == 0 {
-
            return Poll::Ready(Err(io::Error::new(
-
                io::ErrorKind::Other,
-
                "max input size exceeded",
-
            )));
-
        }
-

-
        Pin::new(&mut self.get_mut().inner).poll_fill_buf(cx)
-
    }
-

-
    fn consume(self: Pin<&mut Self>, amt: usize) {
-
        Pin::new(&mut self.get_mut().inner).consume(amt)
-
    }
-
}
deleted archived/link-git/src/protocol/transport.rs
@@ -1,84 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use bstr::BString;
-
use futures_lite::io::{AsyncRead, AsyncWrite};
-
use git_protocol::transport::{
-
    client::{
-
        self,
-
        git::{ConnectMode, Connection},
-
        SetServiceResponse,
-
        Transport,
-
        TransportWithoutIO,
-
    },
-
    Protocol,
-
    Service,
-
};
-

-
pub struct Stateless<R, W> {
-
    inner: Connection<R, W>,
-
}
-

-
impl<R, W> Stateless<R, W>
-
where
-
    R: AsyncRead + Unpin,
-
    W: AsyncWrite + Unpin,
-
{
-
    pub fn new(repo: BString, recv: R, send: W) -> Self {
-
        let url = format!("rad://{repo}");
-
        let inner = Connection::new(
-
            recv,
-
            send,
-
            Protocol::V2,
-
            repo,
-
            None::<(String, Option<u16>)>,
-
            ConnectMode::Daemon,
-
        )
-
        .custom_url(Some(url));
-

-
        Self { inner }
-
    }
-
}
-

-
impl<R, W> TransportWithoutIO for Stateless<R, W>
-
where
-
    R: AsyncRead + Unpin,
-
    W: AsyncWrite + Unpin,
-
{
-
    fn request(
-
        &mut self,
-
        write_mode: client::WriteMode,
-
        on_into_read: client::MessageKind,
-
    ) -> Result<client::RequestWriter<'_>, client::Error> {
-
        self.inner.request(write_mode, on_into_read)
-
    }
-

-
    fn to_url(&self) -> String {
-
        self.inner.to_url()
-
    }
-

-
    fn supported_protocol_versions(&self) -> &[Protocol] {
-
        &[Protocol::V2]
-
    }
-

-
    fn connection_persists_across_multiple_requests(&self) -> bool {
-
        false
-
    }
-
}
-

-
#[async_trait(?Send)]
-
impl<R, W> Transport for Stateless<R, W>
-
where
-
    R: AsyncRead + Unpin,
-
    W: AsyncWrite + Unpin,
-
{
-
    async fn handshake<'a>(
-
        &mut self,
-
        service: Service,
-
        extra_parameters: &'a [(&'a str, Option<&'a str>)],
-
    ) -> Result<SetServiceResponse<'_>, client::Error> {
-
        self.inner.handshake(service, extra_parameters).await
-
    }
-
}
deleted archived/link-git/src/protocol/upload_pack.rs
@@ -1,238 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{future::Future, io, path::Path, process::ExitStatus, str::FromStr};
-

-
use async_process::{Command, Stdio};
-
use futures_lite::io::{copy, AsyncBufReadExt as _, AsyncRead, AsyncWrite, BufReader};
-
use futures_util::try_join;
-
use git_packetline::{self as packetline, PacketLineRef};
-
use once_cell::sync::Lazy;
-
use versions::Version;
-

-
mod legacy;
-

-
#[derive(Debug, PartialEq, Eq)]
-
pub struct Header {
-
    pub path: String,
-
    pub host: Option<(String, Option<u16>)>,
-
    pub extra: Vec<(String, Option<String>)>,
-
}
-

-
impl FromStr for Header {
-
    type Err = &'static str;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        let mut parts = s
-
            .strip_prefix("git-upload-pack ")
-
            .ok_or("unsupported service")?
-
            .split_terminator('\0');
-

-
        let path = parts.next().ok_or("missing path").and_then(|path| {
-
            if path.is_empty() {
-
                Err("empty path")
-
            } else {
-
                Ok(path.to_owned())
-
            }
-
        })?;
-
        let host = match parts.next() {
-
            None | Some("") => None,
-
            Some(host) => match host.strip_prefix("host=") {
-
                None => return Err("invalid host"),
-
                Some(host) => match host.split_once(':') {
-
                    None => Some((host.to_owned(), None)),
-
                    Some((host, port)) => {
-
                        let port = port.parse::<u16>().or(Err("invalid port"))?;
-
                        Some((host.to_owned(), Some(port)))
-
                    },
-
                },
-
            },
-
        };
-
        let extra = parts
-
            .skip_while(|part| part.is_empty())
-
            .map(|part| match part.split_once('=') {
-
                None => (part.to_owned(), None),
-
                Some((k, v)) => (k.to_owned(), Some(v.to_owned())),
-
            })
-
            .collect();
-

-
        Ok(Self { path, host, extra })
-
    }
-
}
-

-
pub async fn upload_pack<R, W>(
-
    git_dir: impl AsRef<Path>,
-
    recv: R,
-
    mut send: W,
-
) -> io::Result<(Header, impl Future<Output = io::Result<ExitStatus>>)>
-
where
-
    R: AsyncRead + Unpin,
-
    W: AsyncWrite + Unpin,
-
{
-
    let mut recv = BufReader::new(recv);
-
    let header: Header = match recv.fill_buf().await?.first() {
-
        // legacy clients don't send a proper pktline header :(
-
        Some(b'g') => {
-
            let mut buf = String::with_capacity(256);
-
            recv.read_line(&mut buf).await?;
-
            buf.parse().map_err(invalid_data)?
-
        },
-
        Some(_) => {
-
            let mut pktline = packetline::StreamingPeekableIter::new(recv, &[]);
-
            let pkt = pktline
-
                .read_line()
-
                .await
-
                .ok_or_else(|| invalid_data("missing header"))?
-
                .map_err(invalid_data)?
-
                .map_err(invalid_data)?;
-
            let hdr = match pkt {
-
                PacketLineRef::Data(data) => std::str::from_utf8(data)
-
                    .map_err(invalid_data)?
-
                    .parse()
-
                    .map_err(invalid_data),
-
                _ => Err(invalid_data("not a header packet")),
-
            }?;
-
            recv = pktline.into_inner();
-

-
            hdr
-
        },
-
        None => {
-
            return Err(io::Error::new(
-
                io::ErrorKind::UnexpectedEof,
-
                "expected header",
-
            ))
-
        },
-
    };
-

-
    let namespace = header
-
        .path
-
        // legacy clients redundantly send a full URN
-
        .strip_prefix("rad:git:")
-
        .map(ToOwned::to_owned)
-
        .unwrap_or_else(|| header.path.clone());
-
    let protocol_version = header
-
        .extra
-
        .iter()
-
        .find_map(|kv| match kv {
-
            (ref k, Some(v)) if k == "version" => {
-
                let version = match v.as_str() {
-
                    "2" => 2,
-
                    "1" => 1,
-
                    _ => 0,
-
                };
-
                Some(version)
-
            },
-
            _ => None,
-
        })
-
        .unwrap_or(0);
-
    // legacy
-
    let stateless_ls = header.extra.iter().any(|(k, _)| k == "ls");
-

-
    let fut = async move {
-
        if protocol_version < 2 {
-
            if stateless_ls {
-
                return legacy::advertise_refs(git_dir, &namespace, recv, send).await;
-
            }
-
        } else {
-
            advertise_capabilities(&mut send).await?;
-
        }
-

-
        let mut child = {
-
            let mut cmd = Command::new("git");
-
            cmd.current_dir(git_dir)
-
                .env_clear()
-
                .envs(
-
                    std::env::vars()
-
                        .filter(|(key, _)| key == "PATH" || key.starts_with("GIT_TRACE")),
-
                )
-
                .env("GIT_PROTOCOL", format!("version={protocol_version}"))
-
                .env("GIT_NAMESPACE", namespace)
-
                .args([
-
                    "-c",
-
                    "uploadpack.allowanysha1inwant=true",
-
                    "-c",
-
                    "uploadpack.allowrefinwant=true",
-
                    "-c",
-
                    "lsrefs.unborn=ignore",
-
                    "upload-pack",
-
                    "--strict",
-
                    "--stateless-rpc",
-
                    ".",
-
                ])
-
                .stdout(Stdio::piped())
-
                .stdin(Stdio::piped())
-
                .stderr(Stdio::inherit())
-
                .kill_on_drop(true)
-
                .reap_on_drop(true)
-
                .spawn()?
-
        };
-

-
        let mut stdin = child.stdin.take().unwrap();
-
        let mut stdout = child.stdout.take().unwrap();
-

-
        try_join!(
-
            copy(&mut recv, &mut stdin),
-
            copy(&mut stdout, &mut send),
-
            child.status(),
-
        )
-
        .map(|(_, _, status)| status)
-
    };
-

-
    Ok((header, fut))
-
}
-

-
async fn advertise_capabilities<W>(mut send: W) -> io::Result<()>
-
where
-
    W: AsyncWrite + Unpin,
-
{
-
    // Thou shallt not upgrade your `git` installation while a link instance is
-
    // running!
-
    static GIT_VERSION: Lazy<Version> = Lazy::new(|| git_version().unwrap());
-
    static AGENT: Lazy<Vec<u8>> = Lazy::new(|| format!("agent=git/{}", *GIT_VERSION).into_bytes());
-
    static CAPABILITIES: Lazy<[&[u8]; 4]> = Lazy::new(|| {
-
        [
-
            b"version 2",
-
            AGENT.as_slice(),
-
            b"object-format=sha1",
-
            b"fetch=ref-in-want",
-
        ]
-
    });
-

-
    for cap in *CAPABILITIES {
-
        packetline::encode::text_to_write(cap, &mut send).await?;
-
    }
-
    packetline::encode::flush_to_write(&mut send).await?;
-

-
    Ok(())
-
}
-

-
fn git_version() -> io::Result<Version> {
-
    let out = std::process::Command::new("git")
-
        .arg("--version")
-
        .output()?;
-
    if !out.status.success() {
-
        return Err(io::Error::new(
-
            io::ErrorKind::Other,
-
            "failed to read `git` version",
-
        ));
-
    }
-

-
    // parse: git version 2.30.1 <other optional tokens>
-
    out.stdout
-
        .split(|x| x == &b' ')
-
        .nth(2)
-
        .and_then(|s| {
-
            let s = std::str::from_utf8(s).ok()?;
-
            Version::new(s.trim())
-
        })
-
        .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "failed to parse `git` version"))
-
}
-

-
fn invalid_data<E>(inner: E) -> io::Error
-
where
-
    E: Into<Box<dyn std::error::Error + Sync + Send>>,
-
{
-
    io::Error::new(io::ErrorKind::InvalidData, inner)
-
}
deleted archived/link-git/src/protocol/upload_pack/legacy.rs
@@ -1,102 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
// Copyright © 2021      The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{io, path::Path, process::ExitStatus};
-

-
use async_process::{Command, Stdio};
-
use futures_lite::io::{copy, AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _};
-
use futures_util::try_join;
-
use git_ref::{
-
    file::{Store as Refdb, WriteReflog},
-
    FullName,
-
    Reference,
-
};
-

-
pub(super) async fn advertise_refs<R, W>(
-
    git_dir: impl AsRef<Path>,
-
    namespace: &str,
-
    mut recv: R,
-
    mut send: W,
-
) -> io::Result<ExitStatus>
-
where
-
    R: AsyncRead + Unpin,
-
    W: AsyncWrite + Unpin,
-
{
-
    let unhide = blocking::unblock({
-
        let git_dir = git_dir.as_ref().to_path_buf();
-
        let prefix = Path::new("refs")
-
            .join("namespaces")
-
            .join(namespace)
-
            .join("refs");
-
        move || -> io::Result<Vec<FullName>> {
-
            let refdb = Refdb::at(git_dir, WriteReflog::Disable);
-
            let packed = refdb
-
                .packed_buffer()
-
                .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
-

-
            let refs = refdb
-
                .iter_prefixed(packed.as_ref(), prefix)?
-
                .filter_map(|r| r.ok().map(|Reference { name, .. }| name))
-
                .filter(|name| {
-
                    const PATTERN: &[u8] = b"rad/ids/any";
-
                    const SEPARAT: u8 = b'/';
-
                    name.as_bstr()
-
                        .rsplit(|b| b == &SEPARAT)
-
                        .zip(PATTERN.rsplit(|b| b == &SEPARAT))
-
                        .skip(1)
-
                        .all(|(a, b)| a == b)
-
                })
-
                .collect::<Vec<_>>();
-

-
            Ok(refs)
-
        }
-
    })
-
    .await?;
-

-
    let mut child = {
-
        let mut cmd = Command::new("git");
-
        cmd.current_dir(git_dir)
-
            .env_clear()
-
            .envs(std::env::vars().filter(|(key, _)| key == "PATH" || key.starts_with("GIT_TRACE")))
-
            .arg("-c")
-
            .arg("uploadpack.hiderefs=refs/")
-
            .arg("-c")
-
            .arg(format!("uploadpack.hiderefs=!refs/namespaces/{namespace}",));
-

-
        for r in unhide {
-
            cmd.arg("-c")
-
                .arg(format!("uploadpack.hiderefs=!{}", r.as_bstr()));
-
        }
-

-
        cmd.args([
-
            "upload-pack",
-
            "--strict",
-
            "--timeout=5",
-
            "--stateless-rpc",
-
            "--advertise-refs",
-
            ".",
-
        ])
-
        .stdout(Stdio::piped())
-
        .stderr(Stdio::inherit())
-
        .kill_on_drop(true)
-
        .reap_on_drop(true)
-
        .spawn()?
-
    };
-
    let mut stdout = child.stdout.take().unwrap();
-

-
    const HEADER: &[u8] = b"001e# service=git-upload-pack\n0000";
-
    send.write_all(HEADER).await?;
-
    let status = try_join!(copy(&mut stdout, &mut send), child.status()).map(|x| x.1);
-

-
    // Read one byte off the read stream to ensure it is driven to completion
-
    // (we expect EOF immediately). Failure to do so may cause resource leaks.
-
    //
-
    // Cf. 900b6cf6 (replication: Ensure git stream is closed, 2021-04-26)
-
    let mut buf = [0; 1];
-
    recv.read(&mut buf).await?;
-

-
    status
-
}
deleted archived/link-git/src/refs.rs
@@ -1,7 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
pub mod db;
-
pub use git_ref::*;
deleted archived/link-git/src/refs/db.rs
@@ -1,269 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    collections::BTreeSet,
-
    convert::TryInto,
-
    io,
-
    path::{Path, PathBuf},
-
    sync::Arc,
-
    time::{Duration, SystemTime},
-
};
-

-
use git_ref::{
-
    file::{self, iter::LooseThenPacked, Transaction, WriteReflog},
-
    packed,
-
    FullName,
-
    PartialNameRef,
-
    Reference,
-
    Target,
-
};
-
use parking_lot::RwLock;
-

-
pub mod error {
-
    use super::*;
-
    use thiserror::Error;
-

-
    #[derive(Debug, Error)]
-
    pub enum Open {
-
        #[error("failed to take a snapshot of packed-refs")]
-
        Snapshot(#[from] Snapshot),
-

-
        #[error(transparent)]
-
        Io(#[from] io::Error),
-
    }
-

-
    #[derive(Debug, Error)]
-
    pub enum Snapshot {
-
        #[error("failed to lock packed-refs")]
-
        Lock(#[from] git_lock::acquire::Error),
-

-
        #[error("failed to open packed-refs")]
-
        Open(#[from] packed::buffer::open::Error),
-

-
        #[error(transparent)]
-
        Io(#[from] io::Error),
-
    }
-

-
    #[derive(Debug, Error)]
-
    pub enum Follow {
-
        #[error("cyclic symref: {0:?}")]
-
        Cycle(FullName),
-

-
        #[error("reference {0:?} not found")]
-
        NotFound(FullName),
-

-
        #[error("max symref depth {0} exceeded")]
-
        DepthLimitExceeded(usize),
-

-
        #[error(transparent)]
-
        Find(#[from] file::find::Error),
-
    }
-
}
-

-
/// Threadsafe refdb with shareable `packed-refs` memory buffer.
-
///
-
/// Packed refs are a delicate business: they are written by an external
-
/// process, [`git-pack-refs`], _or_ when a packed ref is deleted. It may also
-
/// be that no `packed-refs` currently exist.
-
///
-
/// The only way we can be certain to operate on a consistent view of what is
-
/// committed to disk is to check if the `packed-refs` file has changed since we
-
/// last read it. This would be quite expensive to do for small operations.
-
/// Thus, the caller is responsible for determining just how much they can
-
/// afford to see possibly out-of-date data: the [`Refdb::snapshot`] method
-
/// checks if the previously loaded `packed-refs` appear to be out-of-date, and
-
/// reloads them if necessary. The resulting [`Snapshot`] contains a pointer to
-
/// an immutable memory buffer of the packed refs which can be shared between
-
/// threads, or cloned.
-
///
-
/// [`git-pack-refs`]: https://git-scm.com/docs/git-pack-refs
-
#[derive(Clone)]
-
pub struct Refdb {
-
    store: file::Store,
-
    packed: Arc<RwLock<Option<Packed>>>,
-
}
-

-
impl Refdb {
-
    pub fn open(git_dir: impl Into<PathBuf>) -> Result<Self, error::Open> {
-
        let store = file::Store::at(git_dir, WriteReflog::Normal);
-
        let packed = Arc::new(RwLock::new(Packed::open(store.packed_refs_path())?));
-
        Ok(Self { store, packed })
-
    }
-

-
    pub fn snapshot(&self) -> Result<Snapshot, error::Snapshot> {
-
        let read = self.packed.read();
-
        match &*read {
-
            None => {
-
                drop(read);
-
                // always modified, because it was None and now is Some
-
                self.reload(|_| true)
-
            },
-

-
            Some(packed) => {
-
                if packed.is_modified()? {
-
                    let mtime = packed.mtime;
-
                    drop(read);
-
                    // we don't care what the mtime is, only that we have a
-
                    // different value than before
-
                    self.reload(|packed1| packed1.mtime != mtime)
-
                } else {
-
                    Ok(Snapshot {
-
                        store: self.store.clone(),
-
                        packed: Some(packed.buf.clone()),
-
                    })
-
                }
-
            },
-
        }
-
    }
-

-
    fn reload<F>(&self, modified_while_blocked: F) -> Result<Snapshot, error::Snapshot>
-
    where
-
        F: FnOnce(&Packed) -> bool,
-
    {
-
        let mut write = self.packed.write();
-
        if let Some(packed) = &*write {
-
            if modified_while_blocked(packed) {
-
                return Ok(Snapshot {
-
                    store: self.store.clone(),
-
                    packed: Some(packed.buf.clone()),
-
                });
-
            }
-
        }
-

-
        match Packed::open(self.store.packed_refs_path())? {
-
            Some(packed) => {
-
                let buf = packed.buf.clone();
-
                *write = Some(packed);
-
                Ok(Snapshot {
-
                    store: self.store.clone(),
-
                    packed: Some(buf),
-
                })
-
            },
-

-
            None => {
-
                *write = None;
-
                Ok(Snapshot {
-
                    store: self.store.clone(),
-
                    packed: None,
-
                })
-
            },
-
        }
-
    }
-
}
-

-
#[derive(Clone)]
-
pub struct Snapshot {
-
    store: file::Store,
-
    packed: Option<Arc<packed::Buffer>>,
-
}
-

-
impl Snapshot {
-
    pub fn find<'a, N, E>(&self, name: N) -> Result<Option<Reference>, file::find::Error>
-
    where
-
        N: TryInto<PartialNameRef<'a>, Error = E>,
-
        file::find::Error: From<E>,
-
    {
-
        self.store.try_find(name, self.packed.as_deref())
-
    }
-

-
    pub fn transaction(&self) -> Transaction {
-
        self.store.transaction()
-
    }
-

-
    pub fn iter(&self, prefix: Option<impl AsRef<Path>>) -> io::Result<LooseThenPacked> {
-
        let packed = self.packed.as_deref();
-
        match prefix {
-
            None => self.store.iter(packed),
-
            Some(p) => self.store.iter_prefixed(packed, p),
-
        }
-
    }
-

-
    /// Follow a symbolic reference until a direct reference is found.
-
    ///
-
    /// If `symref` is a direct reference, a copy of it is returned. No more
-
    /// than five symbolic references will be followed, and cyclic
-
    /// references are detected. Both result in an error to be returned.
-
    ///
-
    /// Note that following is not the same as "peeling": no access to the
-
    /// object database is made, and thus no assumptions about the kind of
-
    /// object the reference ultimately points to can be made.
-
    pub fn follow(&self, symref: &Reference) -> Result<Reference, error::Follow> {
-
        match &symref.target {
-
            Target::Peeled(_) => Ok(symref.clone()),
-
            Target::Symbolic(name) => {
-
                let mut seen = BTreeSet::new();
-
                seen.insert(symref.name.clone());
-

-
                let mut next = self
-
                    .find(name.to_partial())?
-
                    .ok_or_else(|| error::Follow::NotFound(name.clone()))?;
-
                seen.insert(name.clone());
-

-
                const MAX_DEPTH: usize = 5;
-
                loop {
-
                    match next.target {
-
                        Target::Peeled(_) => return Ok(next),
-
                        Target::Symbolic(sym) => {
-
                            if seen.len() + 1 > MAX_DEPTH {
-
                                return Err(error::Follow::DepthLimitExceeded(MAX_DEPTH));
-
                            }
-

-
                            if seen.contains(&sym) {
-
                                return Err(error::Follow::Cycle(sym));
-
                            }
-
                            next = self
-
                                .find(sym.to_partial())?
-
                                .ok_or_else(|| error::Follow::NotFound(sym.clone()))?;
-
                            seen.insert(sym);
-
                        },
-
                    }
-
                }
-
            },
-
        }
-
    }
-
}
-

-
struct Packed {
-
    buf: Arc<packed::Buffer>,
-
    path: PathBuf,
-
    mtime: SystemTime,
-
}
-

-
impl Packed {
-
    fn open(path: PathBuf) -> Result<Option<Self>, error::Snapshot> {
-
        use git_lock::{acquire, Marker};
-

-
        let _lock = Marker::acquire_to_hold_resource(
-
            &path,
-
            acquire::Fail::AfterDurationWithBackoff(Duration::from_millis(500)),
-
            None,
-
        )?;
-
        match path.metadata() {
-
            // `git-lock` will happily lock a non-existent file
-
            Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
-
            Err(e) => Err(e.into()),
-

-
            Ok(meta) => {
-
                let mtime = meta.modified()?;
-
                let buf = Arc::new(packed::Buffer::open(&path, 32 * 1024)?);
-
                Ok(Some(Self { buf, path, mtime }))
-
            },
-
        }
-
    }
-

-
    fn is_modified(&self) -> io::Result<bool> {
-
        match self.path.metadata() {
-
            // it existed before, so gone is modified
-
            Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(true),
-
            Err(e) => Err(e),
-

-
            Ok(meta) => {
-
                let mtime = meta.modified()?;
-
                Ok(self.mtime == mtime)
-
            },
-
        }
-
    }
-
}
deleted archived/link-git/src/service.rs
@@ -1,126 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{fmt::Debug, ops::Deref, str::FromStr};
-

-
use git2::transport::Service as GitService;
-
use lazy_static::lazy_static;
-

-
lazy_static! {
-
    static ref SERVICE_REGEX: regex::Regex = regex::Regex::new(r"(\S+) '/?(.+)'").unwrap();
-
}
-

-
#[derive(Clone, Copy, PartialEq)]
-
pub struct Service(pub GitService);
-

-
/// A service and URN as passed to the exec_request of an SSH server by git when
-
/// talking to an SSH remote. The `FromStr` implementation for this type expects
-
/// a string of the form:
-
///
-
/// `<request type> /<path>`
-
///
-
/// Where the request type is either `upload-pack` or `receive-pack`, the
-
/// leading slash before the urn is optional, and the `path` is whatever the
-
/// `FromStr` of `Path` provides.
-
#[derive(Debug, Clone)]
-
pub struct SshService<Path> {
-
    pub service: Service,
-
    pub path: Path,
-
}
-

-
impl<Path> SshService<Path> {
-
    pub fn is_upload(&self) -> bool {
-
        match self.service.0 {
-
            GitService::UploadPackLs | GitService::UploadPack => true,
-
            GitService::ReceivePackLs | GitService::ReceivePack => false,
-
        }
-
    }
-

-
    pub fn is_receive(&self) -> bool {
-
        !self.is_upload()
-
    }
-
}
-

-
impl From<GitService> for Service {
-
    fn from(g: GitService) -> Self {
-
        Service(g)
-
    }
-
}
-

-
impl From<Service> for GitService {
-
    fn from(s: Service) -> Self {
-
        s.0
-
    }
-
}
-

-
#[derive(thiserror::Error, Debug)]
-
pub enum ParseService {
-
    #[error("the exec str must be in the form <service> <urn>")]
-
    Format,
-
    #[error(transparent)]
-
    Namespace(Box<dyn std::error::Error + Send + Sync + 'static>),
-
    #[error("unknown service {0}")]
-
    UnknownService(String),
-
}
-

-
impl Debug for Service {
-
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-
        f.debug_tuple("Service")
-
            .field(match self.0 {
-
                GitService::UploadPackLs => &"UploadPackLs",
-
                GitService::UploadPack => &"UploadPack",
-
                GitService::ReceivePackLs => &"ReceivePackLs",
-
                GitService::ReceivePack => &"ReceivePack",
-
            })
-
            .finish()
-
    }
-
}
-

-
impl std::fmt::Display for Service {
-
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-
        match self.0 {
-
            GitService::UploadPack => write!(f, "upload-pack"),
-
            GitService::UploadPackLs => write!(f, "upload-pack-ls"),
-
            GitService::ReceivePack => write!(f, "receive-pack"),
-
            GitService::ReceivePackLs => write!(f, "receive-pack-ls"),
-
        }
-
    }
-
}
-

-
impl Deref for Service {
-
    type Target = GitService;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl<Path> FromStr for SshService<Path>
-
where
-
    Path: FromStr,
-
    Path::Err: std::error::Error + Send + Sync + 'static,
-
{
-
    type Err = ParseService;
-

-
    fn from_str(exec_str: &str) -> Result<Self, Self::Err> {
-
        let cap = SERVICE_REGEX
-
            .captures_iter(exec_str)
-
            .next()
-
            .ok_or(ParseService::Format)?;
-
        debug_assert!(cap.len() == 3);
-
        let service_str: &str = &cap[1];
-
        let urn_str = &cap[2];
-

-
        let path = urn_str
-
            .parse()
-
            .map_err(|err| ParseService::Namespace(Box::new(err)))?;
-
        let service = match service_str {
-
            "git-upload-pack" => Ok(Service(GitService::UploadPack)),
-
            "git-receive-pack" => Ok(Service(GitService::ReceivePack)),
-
            other => Err(ParseService::UnknownService(other.to_string())),
-
        }?;
-
        Ok(Self { service, path })
-
    }
-
}
deleted archived/link-git/t/Cargo.toml
@@ -1,31 +0,0 @@
-
[package]
-
name = "link-git-test"
-
version = "0.1.0"
-
edition = "2021"
-
license = "GPL-3.0-or-later"
-

-
publish = false
-

-
[lib]
-
doctest = false
-
test = true
-
doc = false
-

-
[features]
-
test = []
-

-
[dev-dependencies]
-
anyhow = "1"
-
bstr = "0.2"
-
futures = "0.3"
-
futures_ringbuf = "0.3"
-
tempfile = "3.4"
-

-
[dev-dependencies.git2]
-
version = "0.16.1"
-
default-features = false
-
features = ["vendored-libgit2"]
-

-
[dev-dependencies.link-git]
-
path = ".."
-
features = ["git2"]

\ No newline at end of file
deleted archived/link-git/t/src/integration.rs
@@ -1,6 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
mod protocol;
deleted archived/link-git/t/src/integration/protocol.rs
@@ -1,383 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    collections::BTreeSet,
-
    io,
-
    path::Path,
-
    sync::{atomic::AtomicBool, Arc},
-
};
-

-
use bstr::ByteSlice as _;
-
use futures::{AsyncReadExt as _, TryFutureExt as _};
-
use link_git::protocol::{fetch, ls, packwriter, upload_pack, ObjectId, PackWriter, Ref};
-
use tempfile::{tempdir, TempDir};
-

-
fn upstream() -> TempDir {
-
    let tmp = tempdir().unwrap();
-

-
    let repo = git2::Repository::init_bare(&tmp).unwrap();
-
    let auth = git2::Signature::now("apollo", "apollo@cree.de").unwrap();
-

-
    let tree = {
-
        let empty = repo.treebuilder(None).unwrap();
-
        let oid = empty.write().unwrap();
-
        repo.find_tree(oid).unwrap()
-
    };
-
    let base = {
-
        let oid = repo
-
            .commit(
-
                Some("refs/namespaces/foo/refs/heads/main"),
-
                &auth,
-
                &auth,
-
                "initial",
-
                &tree,
-
                &[],
-
            )
-
            .unwrap();
-
        repo.find_commit(oid).unwrap()
-
    };
-
    let next = repo
-
        .commit(
-
            Some("refs/namespaces/foo/refs/heads/next"),
-
            &auth,
-
            &auth,
-
            "ng",
-
            &tree,
-
            &[&base],
-
        )
-
        .unwrap();
-
    repo.reference(
-
        "refs/namespaces/foo/refs/pulls/1/head",
-
        next,
-
        true,
-
        "pee arrr",
-
    )
-
    .unwrap();
-

-
    tmp
-
}
-

-
fn collect_refs(repo: &git2::Repository) -> Result<Vec<(String, git2::Oid)>, git2::Error> {
-
    repo.references()?
-
        .map(|x| x.map(|r| (r.name().unwrap().to_owned(), r.target().unwrap())))
-
        .collect()
-
}
-

-
fn update_tips<'a, T>(repo: &git2::Repository, tips: T) -> Result<(), anyhow::Error>
-
where
-
    T: IntoIterator<Item = &'a Ref>,
-
{
-
    for r in tips {
-
        match r {
-
            Ref::Direct { path, object } => {
-
                repo.reference(
-
                    path.to_str()?,
-
                    git2::Oid::from_bytes(object.as_slice())?,
-
                    true,
-
                    "",
-
                )?;
-
            },
-
            x => anyhow::bail!("unexpected ref variant: {:?}", x),
-
        }
-
    }
-

-
    Ok(())
-
}
-

-
fn collect_history(repo: &git2::Repository, tip: &str) -> Result<Vec<git2::Oid>, git2::Error> {
-
    let mut revwalk = repo.revwalk()?;
-
    revwalk.push_ref(tip)?;
-
    revwalk.collect()
-
}
-

-
fn run_ls_refs<R: AsRef<Path>>(remote: R, opt: ls::Options) -> io::Result<Vec<Ref>> {
-
    let (client, server) = futures_ringbuf::Endpoint::pair(256, 256);
-
    let client = async move {
-
        let (recv, send) = client.split();
-
        ls::ls_refs(opt, recv, send).await
-
    };
-
    let server = {
-
        let (recv, send) = server.split();
-
        upload_pack::upload_pack(&remote, recv, send).and_then(|(_hdr, run)| run)
-
    };
-

-
    let (client_out, server_out) =
-
        futures::executor::block_on(futures::future::try_join(client, server))?;
-
    assert!(server_out.success());
-
    Ok(client_out)
-
}
-

-
fn run_fetch<R, B, P>(
-
    remote: R,
-
    opt: fetch::Options,
-
    build_pack_writer: B,
-
) -> io::Result<fetch::Outputs<P::Output>>
-
where
-
    R: AsRef<Path>,
-
    B: FnOnce(Arc<AtomicBool>) -> P,
-
    P: PackWriter + Send + 'static,
-
    P::Output: Send + 'static,
-
{
-
    let (client, server) = futures_ringbuf::Endpoint::pair(256, 256);
-
    let client = async move {
-
        let (recv, send) = client.split();
-
        fetch::fetch(opt, build_pack_writer, recv, send).await
-
    };
-
    let server = {
-
        let (recv, send) = server.split();
-
        upload_pack::upload_pack(&remote, recv, send).and_then(|(_hdr, run)| run)
-
    };
-

-
    let (client_out, server_out) =
-
        futures::executor::block_on(futures::future::try_join(client, server))?;
-
    assert!(server_out.success());
-
    Ok(client_out)
-
}
-

-
#[test]
-
fn smoke() {
-
    let remote = upstream();
-
    let refs = run_ls_refs(
-
        &remote,
-
        ls::Options {
-
            repo: "foo".into(),
-
            extra_params: vec![],
-
            ref_prefixes: vec!["refs/heads/".into(), "refs/pulls/".into()],
-
        },
-
    )
-
    .unwrap();
-

-
    assert_eq!(
-
        refs.iter().map(|r| r.unpack().0).collect::<BTreeSet<_>>(),
-
        [
-
            "refs/heads/main".into(),
-
            "refs/heads/next".into(),
-
            "refs/pulls/1/head".into()
-
        ]
-
        .iter()
-
        .collect::<BTreeSet<_>>()
-
    );
-

-
    let out = run_fetch(
-
        &remote,
-
        fetch::Options {
-
            repo: "foo".into(),
-
            extra_params: vec![],
-
            haves: vec![],
-
            wants: vec![],
-
            want_refs: refs.iter().map(|r| r.unpack().0.clone()).collect(),
-
        },
-
        |_| packwriter::Discard,
-
    )
-
    .unwrap();
-

-
    assert!(out.pack.is_some());
-
}
-

-
#[test]
-
fn want_ref() {
-
    let remote = upstream();
-
    let out = run_fetch(
-
        &remote,
-
        fetch::Options {
-
            repo: "foo".into(),
-
            extra_params: vec![],
-
            haves: vec![],
-
            wants: vec![],
-
            want_refs: vec!["refs/heads/main".into(), "refs/pulls/1/head".into()],
-
        },
-
        |_| packwriter::Discard,
-
    )
-
    .unwrap();
-

-
    assert!(out.pack.is_some());
-
    assert_eq!(
-
        out.wanted_refs
-
            .iter()
-
            .map(|r| r.unpack().0)
-
            .collect::<BTreeSet<_>>(),
-
        ["refs/heads/main".into(), "refs/pulls/1/head".into(),]
-
            .iter()
-
            .collect::<BTreeSet<_>>()
-
    )
-
}
-

-
#[test]
-
#[should_panic(expected = "`fetch` is empty")]
-
fn empty_fetch() {
-
    let remote = upstream();
-
    run_fetch(
-
        &remote,
-
        fetch::Options {
-
            repo: "foo".into(),
-
            extra_params: vec![],
-
            haves: vec![],
-
            wants: vec![],
-
            want_refs: vec![],
-
        },
-
        |_| packwriter::Discard,
-
    )
-
    .unwrap();
-
}
-

-
fn clone_with<R, L, B, P>(remote: R, local: L, build_pack_writer: B)
-
where
-
    R: AsRef<Path>,
-
    L: AsRef<Path>,
-
    B: FnOnce(Arc<AtomicBool>) -> P,
-
    P: PackWriter + Send + 'static,
-
    P::Output: Send + 'static,
-
{
-
    let refs = run_ls_refs(
-
        &remote,
-
        ls::Options {
-
            repo: "foo".into(),
-
            extra_params: vec![],
-
            ref_prefixes: vec!["refs/heads/".into(), "refs/pulls/".into()],
-
        },
-
    )
-
    .unwrap();
-
    let out = run_fetch(
-
        &remote,
-
        fetch::Options {
-
            repo: "foo".into(),
-
            extra_params: vec![],
-
            haves: vec![],
-
            wants: vec![],
-
            want_refs: refs.iter().map(|r| r.unpack().0.clone()).collect(),
-
        },
-
        build_pack_writer,
-
    )
-
    .unwrap();
-

-
    assert!(out.pack.is_some());
-

-
    let remote_repo = git2::Repository::open(remote).unwrap();
-
    remote_repo.set_namespace("foo").unwrap();
-
    let local_repo = git2::Repository::open(&local).unwrap();
-

-
    update_tips(&local_repo, &out.wanted_refs).unwrap();
-

-
    let mut remote_refs = collect_refs(&remote_repo).unwrap();
-
    let mut local_refs = collect_refs(&local_repo).unwrap();
-

-
    remote_refs.sort();
-
    local_refs.sort();
-

-
    assert_eq!(remote_refs, local_refs);
-
}
-

-
#[test]
-
fn clone_libgit() {
-
    let remote = upstream();
-
    let local = tempdir().unwrap();
-
    let local_repo = git2::Repository::init(&local).unwrap();
-

-
    clone_with(&remote, &local, move |stop| {
-
        packwriter::Libgit::new(packwriter::Options::default(), local_repo, stop)
-
    })
-
}
-

-
#[test]
-
fn clone_gitoxide() {
-
    let remote = upstream();
-
    let local = tempdir().unwrap();
-
    let local_repo = git2::Repository::init(&local).unwrap();
-

-
    clone_with(&remote, &local, move |stop| {
-
        packwriter::Standard::new(
-
            local_repo.path(),
-
            packwriter::Options::default(),
-
            packwriter::StandardThickener::new(local_repo.path()),
-
            stop,
-
        )
-
    })
-
}
-

-
fn thin_pack_with<R, L, B, P>(remote: R, local: L, build_pack_writer: B)
-
where
-
    R: AsRef<Path>,
-
    L: AsRef<Path>,
-
    B: Fn(Arc<AtomicBool>) -> P,
-
    P: PackWriter + Send + 'static,
-
    P::Output: Send + 'static,
-
{
-
    // Clone main only
-
    {
-
        let out = run_fetch(
-
            &remote,
-
            fetch::Options {
-
                repo: "foo".into(),
-
                extra_params: vec![],
-
                haves: vec![],
-
                wants: vec![],
-
                want_refs: vec!["refs/heads/main".into()],
-
            },
-
            &build_pack_writer,
-
        )
-
        .unwrap();
-
        assert!(out.pack.is_some());
-
    }
-

-
    let remote_repo = git2::Repository::open(&remote).unwrap();
-
    remote_repo.set_namespace("foo").unwrap();
-
    let local_repo = git2::Repository::open(&local).unwrap();
-

-
    // Fetch next, which is ahead of main
-
    {
-
        let head = remote_repo.refname_to_id("refs/heads/main").unwrap();
-
        let out = run_fetch(
-
            &remote,
-
            fetch::Options {
-
                repo: "foo".into(),
-
                extra_params: vec![],
-
                haves: vec![ObjectId::from_20_bytes(head.as_bytes())],
-
                wants: vec![],
-
                want_refs: vec!["refs/heads/next".into()],
-
            },
-
            build_pack_writer,
-
        )
-
        .unwrap();
-
        assert!(out.pack.is_some());
-

-
        update_tips(&local_repo, &out.wanted_refs).unwrap();
-
    }
-

-
    let remote_history = collect_history(&remote_repo, "refs/heads/next").unwrap();
-
    let local_history = collect_history(&local_repo, "refs/heads/next").unwrap();
-

-
    assert!(!remote_history.is_empty());
-
    assert_eq!(remote_history, local_history)
-
}
-

-
#[test]
-
#[ignore]
-
fn thin_pack_libgit() {
-
    let remote = upstream();
-
    let local = tempdir().unwrap();
-

-
    thin_pack_with(&remote, &local, |stop| {
-
        let local_repo = git2::Repository::init(&local).unwrap();
-
        packwriter::Libgit::new(packwriter::Options::default(), local_repo, stop)
-
    })
-
}
-

-
#[test]
-
fn thin_pack_gitoxide() {
-
    let remote = upstream();
-
    let local = tempdir().unwrap();
-
    let local_repo = git2::Repository::init(&local).unwrap();
-
    let git_dir = local_repo.path().to_owned();
-

-
    thin_pack_with(&remote, &local, move |stop| {
-
        packwriter::Standard::new(
-
            &git_dir,
-
            packwriter::Options::default(),
-
            packwriter::StandardThickener::new(&git_dir),
-
            stop,
-
        )
-
    })
-
}
deleted archived/link-git/t/src/lib.rs
@@ -1,7 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
#[cfg(test)]
-
mod integration;
-
#[cfg(test)]
-
mod tests;
deleted archived/link-git/t/src/tests.rs
@@ -1,6 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
mod protocol;
deleted archived/link-git/t/src/tests/protocol.rs
@@ -1,7 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
mod take;
-
mod upload_pack;
deleted archived/link-git/t/src/tests/protocol/take.rs
@@ -1,47 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use futures::{executor::block_on, io::Cursor, AsyncReadExt as _};
-
use link_git::protocol::take::TryTake;
-
use std::io;
-

-
#[test]
-
fn when_within_limit() {
-
    let input = b"the world is everything that is the case";
-
    let output = block_on(async move {
-
        let mut buf = Vec::with_capacity(input.len());
-
        TryTake::new(Cursor::new(input), input.len() as u64 + 1)
-
            .read_to_end(&mut buf)
-
            .await?;
-
        Ok::<_, io::Error>(buf)
-
    })
-
    .unwrap();
-

-
    assert_eq!(input, output.as_slice())
-
}
-

-
#[test]
-
fn when_limit_exceeded() {
-
    let input = b"what is the case, the fact, is the existence of atomic facts";
-
    let output =
-
        block_on(TryTake::new(Cursor::new(input), 10).read_to_end(&mut Vec::new())).unwrap_err();
-

-
    assert_eq!(output.to_string(), "max input size exceeded")
-
}
-

-
#[test]
-
fn excess_bytes_remain() {
-
    let input = b"whereof one cannot speak, thereof one must be silent";
-
    let output = block_on(async move {
-
        let mut buf = Vec::with_capacity(input.len());
-
        let res = TryTake::new(Cursor::new(input), input.len() as u64)
-
            .read_to_end(&mut buf)
-
            .await;
-
        assert!(res.is_err());
-
        buf
-
    });
-

-
    assert_eq!(input, output.as_slice())
-
}
deleted archived/link-git/t/src/tests/protocol/upload_pack.rs
@@ -1,129 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use link_git::protocol::upload_pack;
-

-
mod header {
-
    use super::*;
-
    use std::str::FromStr as _;
-

-
    #[test]
-
    fn service_must_be_upload_pack() {
-
        assert_eq!(
-
            upload_pack::Header::from_str("git-receive-pack "),
-
            Err("unsupported service")
-
        )
-
    }
-

-
    #[test]
-
    fn no_path() {
-
        assert_eq!(
-
            upload_pack::Header::from_str("git-upload-pack "),
-
            Err("missing path")
-
        )
-
    }
-

-
    #[test]
-
    fn empty_path() {
-
        assert_eq!(
-
            upload_pack::Header::from_str("git-upload-pack \0host=lolhost:123\0"),
-
            Err("empty path")
-
        )
-
    }
-

-
    #[test]
-
    fn host_and_port() {
-
        assert_eq!(
-
            upload_pack::Header::from_str("git-upload-pack /git.git\0host=lolhost:123\0").unwrap(),
-
            upload_pack::Header {
-
                path: "/git.git".to_owned(),
-
                host: Some(("lolhost".to_owned(), Some(123))),
-
                extra: vec![]
-
            }
-
        )
-
    }
-

-
    #[test]
-
    fn host_without_port() {
-
        assert_eq!(
-
            upload_pack::Header::from_str("git-upload-pack /git.git\0host=lolhost\0").unwrap(),
-
            upload_pack::Header {
-
                path: "/git.git".to_owned(),
-
                host: Some(("lolhost".to_owned(), None)),
-
                extra: vec![]
-
            }
-
        )
-
    }
-

-
    #[test]
-
    fn no_host() {
-
        assert_eq!(
-
            upload_pack::Header::from_str("git-upload-pack /git.git\0").unwrap(),
-
            upload_pack::Header {
-
                path: "/git.git".to_owned(),
-
                host: None,
-
                extra: vec![]
-
            }
-
        )
-
    }
-

-
    #[test]
-
    fn empty_host() {
-
        assert_eq!(
-
            upload_pack::Header::from_str("git-upload-pack /git.git\0\0").unwrap(),
-
            upload_pack::Header {
-
                path: "/git.git".to_owned(),
-
                host: None,
-
                extra: vec![]
-
            }
-
        )
-
    }
-

-
    #[test]
-
    fn no_host_extra() {
-
        assert_eq!(
-
            upload_pack::Header::from_str("git-upload-pack /git.git\0\0version=42\0").unwrap(),
-
            upload_pack::Header {
-
                path: "/git.git".to_owned(),
-
                host: None,
-
                extra: vec![("version".to_owned(), Some("42".to_owned()))]
-
            }
-
        )
-
    }
-

-
    #[test]
-
    fn host_port_extra() {
-
        assert_eq!(
-
            upload_pack::Header::from_str(
-
                "git-upload-pack /git.git\0host=lolhost:123\0\0version=42\0"
-
            )
-
            .unwrap(),
-
            upload_pack::Header {
-
                path: "/git.git".to_owned(),
-
                host: Some(("lolhost".to_owned(), Some(123))),
-
                extra: vec![("version".to_owned(), Some("42".to_owned()))]
-
            }
-
        )
-
    }
-

-
    #[test]
-
    fn host_extra_extra() {
-
        assert_eq!(
-
            upload_pack::Header::from_str(
-
                "git-upload-pack /git.git\0host=lolhost\0\0version=42\0foo\0n=69\0"
-
            )
-
            .unwrap(),
-
            upload_pack::Header {
-
                path: "/git.git".to_owned(),
-
                host: Some(("lolhost".to_owned(), None)),
-
                extra: vec![
-
                    ("version".to_owned(), Some("42".to_owned())),
-
                    ("foo".to_owned(), None),
-
                    ("n".to_owned(), Some("69".to_owned()))
-
                ]
-
            }
-
        )
-
    }
-
}
modified deny.toml
@@ -33,7 +33,6 @@ ignore = [
allow = [
    "Apache-2.0",
    "BlueOak-1.0.0",
-
    "GPL-3.0",
    "MIT",
    "Unicode-3.0",
    "Unlicense",
modified radicle-git-ext/Cargo.toml
@@ -10,7 +10,7 @@ authors = [
edition = "2021"
homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md"
repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"
description = "Utilities and extensions to the git2 crate"
keywords = ["git", "radicle"]

modified radicle-git-ext/git-ref-format/Cargo.toml
@@ -8,7 +8,7 @@ authors = [
edition = "2021"
homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md"
repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"
description = "Everything you never knew you wanted for handling git ref names."
keywords = ["git", "references"]

modified radicle-git-ext/git-ref-format/core/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Kim Altintop <kim@eagain.st>"]
edition = "2021"
homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md"
repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"
description = "Core types for the git-ref-format crate"
keywords = ["git", "references"]

modified radicle-git-ext/git-ref-format/core/src/cbor.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::convert::TryFrom;

use minicbor::{
modified radicle-git-ext/git-ref-format/core/src/check.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use thiserror::Error;

pub struct Options {
modified radicle-git-ext/git-ref-format/core/src/deriv.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{
    borrow::Cow,
    fmt::{self, Display},
modified radicle-git-ext/git-ref-format/core/src/lib.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

mod check;
pub use check::{ref_format as check_ref_format, Error, Options};

modified radicle-git-ext/git-ref-format/core/src/lit.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use crate::{name, Qualified, RefStr};

/// A literal [`RefStr`].
modified radicle-git-ext/git-ref-format/core/src/name.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{
    borrow::{Borrow, Cow},
    convert::TryFrom,
modified radicle-git-ext/git-ref-format/core/src/name/iter.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{
    borrow::Cow,
    fmt::{self, Display},
modified radicle-git-ext/git-ref-format/core/src/refspec.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{
    borrow::{Borrow, Cow},
    convert::TryFrom,
modified radicle-git-ext/git-ref-format/core/src/refspec/iter.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::fmt::{self, Display};

use super::PatternStr;
modified radicle-git-ext/git-ref-format/core/src/serde.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::convert::TryFrom;

use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
modified radicle-git-ext/git-ref-format/macro/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Kim Altintop <kim@eagain.st>"]
edition = "2021"
homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md"
repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"
description = "Macros for the git-ref-format crate"
keywords = ["git", "references"]

modified radicle-git-ext/git-ref-format/macro/src/lib.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

#[macro_use]
extern crate proc_macro_error2;

modified radicle-git-ext/git-ref-format/src/lib.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

//! Everything you never knew you wanted for handling git ref names.
//!
//! # Overview
modified radicle-git-ext/git-ref-format/t/Cargo.toml
@@ -2,7 +2,7 @@
name = "git-ref-format-test"
version = "0.1.0"
edition = "2021"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"

publish = false

modified radicle-git-ext/src/blob.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{borrow::Cow, path::Path};

use radicle_std_ext::result::ResultExt as _;
modified radicle-git-ext/src/error.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{fmt::Display, io};

pub fn is_not_found_err(e: &git2::Error) -> bool {
modified radicle-git-ext/src/lib.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

//! Extensions and wrappers for `git2` types

pub mod author;
modified radicle-git-ext/src/oid.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{
    convert::TryFrom,
    fmt::{self, Display},
modified radicle-git-ext/src/revwalk.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

pub enum Start {
    Oid(git2::Oid),
    Ref(String),
modified radicle-git-ext/src/transport.rs
@@ -1,7 +1,2 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

pub const UPLOAD_PACK_HEADER: &[u8] = b"001e# service=git-upload-pack\n0000";
pub const RECEIVE_PACK_HEADER: &[u8] = b"001f# service=git-receive-pack\n0000";
modified radicle-git-ext/src/tree.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{borrow::Cow, collections::BTreeMap, iter::FromIterator};

/// A simplified representation of a git tree, intended mainly to be created
modified radicle-git-ext/t/Cargo.toml
@@ -2,7 +2,7 @@
name = "radicle-git-ext-test"
version = "0.1.0"
edition = "2021"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"

publish = false

modified radicle-git-ext/t/src/gen.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2022 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-git, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

//! Provides proptest generators

use proptest::strategy::Strategy;
modified radicle-git-ext/t/src/gen/urn.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2022 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-git, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use proptest::prelude::*;
use radicle_git_ext::Oid;

modified radicle-git-ext/t/src/git_ref_format.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

#[cfg(any(test, feature = "test"))]
pub mod gen;

modified radicle-git-ext/t/src/git_ref_format/gen.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

use proptest::prelude::*;

/// Any unicode "word" is trivially a valid refname.
modified radicle-git-ext/t/src/git_ref_format/properties.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

use proptest::prelude::*;
use radicle_git_ext::ref_format::{check_ref_format, Error, Options};

modified radicle-git-ext/t/src/git_ref_format/properties/name.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

use std::convert::TryFrom;

use proptest::prelude::*;
modified radicle-git-ext/t/src/git_ref_format/properties/pattern.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

use std::convert::TryFrom;

use proptest::prelude::*;
modified radicle-git-ext/t/src/git_ref_format/tests.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

use radicle_git_ext::ref_format::{
    component, name, qualified, refname, refspec, Error, Qualified, RefStr, RefString,
};
modified radicle-git-ext/t/src/lib.rs
@@ -1,5 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
#[cfg(test)]
#[macro_use]
extern crate assert_matches;
modified radicle-std-ext/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["The Radicle Team <dev@radicle.xyz>"]
edition = "2021"
homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md"
repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"
description = "Monkey patches of std types"
keywords = ["std", "radicle"]

modified radicle-std-ext/src/lib.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

#![cfg_attr(feature = "nightly", feature(try_trait_v2))]

pub mod ops;
modified radicle-std-ext/src/ops.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2021 The Radicle Link Contributors
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

//! Provides Try-trait for stable rust
//!
//! Probably doesn't work with `?`-desugaring. If the `nightly` feature is
modified radicle-std-ext/src/result.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

pub trait ResultExt<T, E> {
    /// Calls `f` if the result is [`Err`], **and** the predicate `pred` on the
    /// error value returns true. Otherwise returns the [`Ok`] value of
modified radicle-surf/Cargo.toml
@@ -7,7 +7,7 @@ authors = ["The Radicle Team <dev@radicle.xyz>"]
edition = "2021"
homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/radicle-surf/README.md"
repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"

include = [
    "**/*.rs",
modified radicle-surf/DEVELOPMENT.md
@@ -3,11 +3,6 @@

Thanks for wanting to contribute to `radicle-surf`!

-
# Licensing
-

-
We are [GPL-3.0-or-later](./LICENSE) licensed project. To keep in compliance with this we must
-
add a [license header](./.license-header) to any new files added. This is checked on each run of CI.
-

## Building & Testing 🏗️

We try to make development as seemless as possible so we can get down to the real work. We supply
modified radicle-surf/build.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

use std::{
    env, fs,
    fs::File,
modified radicle-surf/examples/browsing.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

//! An example of browsing a git repo using `radicle-surf`.
//!
//! How to run:
modified radicle-surf/examples/diff.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

extern crate radicle_surf;

use std::{env::Args, str::FromStr, time::Instant};
modified radicle-surf/src/blob.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

//! Represents git object type 'blob', i.e. actual file contents.
//! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details.

modified radicle-surf/src/commit.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

use std::{convert::TryFrom, str};

use radicle_git_ext::Oid;
modified radicle-surf/src/diff.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

//! Types that represent diff(s) in a Git repo.

use std::{
modified radicle-surf/src/diff/git.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

use std::convert::TryFrom;

use super::{
modified radicle-surf/src/error.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2023 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

//! Definition for a crate level error type, which wraps up module level
//! error types transparently.

modified radicle-surf/src/fs.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

//! Definition for a file system consisting of `Directory` and `File`.
//!
//! A `Directory` is expected to be a non-empty tree of directories and files.
modified radicle-surf/src/glob.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-git
-
// <https://github.com/radicle-dev/radicle-git>
-
//
-
// Copyright (C) 2022 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

use std::marker::PhantomData;

use git_ext::ref_format::{
modified radicle-surf/src/history.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-git>
-
//
-
// Copyright (C) 2022 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

use std::{
    convert::TryFrom,
    path::{Path, PathBuf},
modified radicle-surf/src/lib.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-git
-
// <https://github.com/radicle-dev/radicle-git>
-
//
-
// Copyright (C) 2019-2023 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

//! `radicle-surf` is a library to help users explore a Git repository with
//! ease. It supports browsing a repository via the concept of files and
//! directories, or via blobs and trees in a git fashion. With the additional
modified radicle-surf/src/namespace.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

use std::{
    convert::TryFrom,
    fmt,
modified radicle-surf/src/repo.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

use std::{
    collections::BTreeSet,
    convert::TryFrom,
modified radicle-surf/src/revision.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

use std::{convert::Infallible, str::FromStr};

use git_ext::{
modified radicle-surf/src/stats.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

#[cfg(feature = "serde")]
use serde::Serialize;

modified radicle-surf/src/tree.rs
@@ -1,20 +1,3 @@
-
// This file is part of radicle-surf
-
// <https://github.com/radicle-dev/radicle-surf>
-
//
-
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
-
//
-
// This program is free software: you can redistribute it and/or modify
-
// it under the terms of the GNU General Public License version 3 or
-
// later as published by the Free Software Foundation.
-
//
-
// 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 General Public License for more details.
-
//
-
// You should have received a copy of the GNU General Public License
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-

//! Represents git object type 'tree', i.e. like directory entries in Unix.
//! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details.

modified radicle-surf/t/Cargo.toml
@@ -2,7 +2,7 @@
name = "radicle-surf-test"
version = "0.1.0"
edition = "2021"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"

publish = false

modified radicle-surf/t/src/file_system.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Git Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

//! Unit tests for radicle_surf::file_system

mod directory {
modified radicle-surf/t/src/lib.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Git Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

#[cfg(test)]
const GIT_PLATINUM: &str = "../data/git-platinum";

modified test/Cargo.toml
@@ -2,7 +2,7 @@
name = "tests"
version = "0.1.0"
edition = "2021"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"

publish = false

modified test/src/lib.rs
@@ -1,4 +1 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

//! This page intentionally left blank.
modified test/test-helpers/Cargo.toml
@@ -2,7 +2,7 @@
name = "test-helpers"
version = "0.1.0"
edition = "2021"
-
license = "GPL-3.0-or-later"
+
license = "MIT OR Apache-2.0"
publish = false

description = "Test helpers"
modified test/test-helpers/src/gen.rs
@@ -1,4 +1 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPLv3-or-later
-

pub mod std_net;
modified test/test-helpers/src/gen/std_net.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPLv3-or-later
-

use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};

use proptest::prelude::*;
modified test/test-helpers/src/lib.rs
@@ -1,6 +1,3 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPLv3-or-later
-

pub mod gen;
pub mod logging;
pub mod roundtrip;
modified test/test-helpers/src/logging.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::env;

use log::{log_enabled, Level};
modified test/test-helpers/src/roundtrip.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{
    fmt::{Debug, Display},
    str::FromStr,
modified test/test-helpers/src/tempdir.rs
@@ -1,8 +1,3 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

use std::{
    io,
    ops::{Deref, DerefMut},