├── COPYING ├── .envrc ├── rust-toolchain ├── test ├── .gitignore ├── test-helpers │ ├── src │ │ ├── gen.rs │ │ ├── lib.rs │ │ ├── tempdir.rs │ │ ├── roundtrip.rs │ │ ├── gen │ │ │ └── std_net.rs │ │ └── logging.rs │ └── Cargo.toml ├── src │ └── lib.rs ├── Cargo.toml └── README.md ├── archived ├── git-trailers │ ├── t │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ └── Cargo.toml │ └── Cargo.toml ├── git-commit │ ├── t │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── integration.rs │ │ └── Cargo.toml │ ├── Cargo.toml │ └── src │ │ ├── headers.rs │ │ └── author.rs └── link-git │ ├── t │ ├── src │ │ ├── lib.rs │ │ ├── tests.rs │ │ ├── integration.rs │ │ └── tests │ │ │ ├── protocol.rs │ │ │ └── protocol │ │ │ ├── take.rs │ │ │ └── upload_pack.rs │ └── Cargo.toml │ ├── src │ ├── refs.rs │ ├── lib.rs │ ├── protocol.rs │ ├── odb │ │ ├── backend.rs │ │ ├── index │ │ │ └── metrics.rs │ │ ├── window │ │ │ └── metrics.rs │ │ └── pack.rs │ ├── odb.rs │ ├── protocol │ │ ├── take.rs │ │ ├── transport.rs │ │ └── upload_pack │ │ │ └── legacy.rs │ └── service.rs │ └── Cargo.toml ├── scripts └── ci │ ├── fmt │ ├── build │ ├── test-fast │ ├── macos-gnu │ ├── docs │ ├── advisory │ ├── run │ ├── test │ └── lint ├── radicle-surf ├── data │ ├── git-platinum.tgz │ ├── README.md │ └── mock-branches.txt ├── t │ ├── src │ │ ├── lib.rs │ │ ├── branch.rs │ │ ├── commit.rs │ │ ├── threading.rs │ │ ├── reference.rs │ │ ├── rev.rs │ │ ├── submodule.rs │ │ ├── code_browsing.rs │ │ └── namespace.rs │ └── Cargo.toml ├── CHANGELOG.md ├── README.md ├── src │ ├── stats.rs │ ├── error.rs │ ├── lib.rs │ ├── history.rs │ └── revision.rs ├── scripts │ └── update-git-platinum.sh ├── Cargo.toml ├── examples │ ├── browsing.rs │ └── diff.rs ├── build.rs └── DEVELOPMENT.md ├── .gitmodules ├── git-storage ├── t │ ├── src │ │ ├── properties.rs │ │ ├── lib.rs │ │ ├── tmp.rs │ │ ├── gen.rs │ │ └── properties │ │ │ └── odb.rs │ └── Cargo.toml ├── src │ ├── backend.rs │ ├── signature.rs │ ├── glob.rs │ ├── refdb │ │ ├── iter.rs │ │ └── read.rs │ ├── odb │ │ ├── read.rs │ │ └── write.rs │ ├── lib.rs │ ├── backend │ │ └── write │ │ │ └── error.rs │ └── refdb.rs └── Cargo.toml ├── radicle-git-ext ├── CHANGELOG.md ├── git-ref-format │ ├── macro │ │ ├── CHANGELOG.md │ │ └── Cargo.toml │ ├── t │ │ └── Cargo.toml │ ├── core │ │ ├── src │ │ │ ├── lib.rs │ │ │ ├── refspec │ │ │ │ └── iter.rs │ │ │ ├── cbor.rs │ │ │ ├── check.rs │ │ │ └── name │ │ │ │ └── iter.rs │ │ ├── Cargo.toml │ │ └── CHANGELOG.md │ └── Cargo.toml ├── t │ ├── src │ │ ├── git_ref_format.rs │ │ ├── lib.rs │ │ ├── gen.rs │ │ ├── gen │ │ │ ├── commit │ │ │ │ ├── trailers.rs │ │ │ │ ├── author.rs │ │ │ │ └── headers.rs │ │ │ ├── urn.rs │ │ │ └── commit.rs │ │ ├── git_ref_format │ │ │ ├── properties.rs │ │ │ ├── properties │ │ │ │ ├── pattern.rs │ │ │ │ └── name.rs │ │ │ └── gen.rs │ │ └── repository.rs │ └── Cargo.toml ├── src │ ├── transport.rs │ ├── lib.rs │ ├── error.rs │ ├── revwalk.rs │ ├── commit │ │ └── headers.rs │ ├── oid.rs │ └── tree.rs └── Cargo.toml ├── Cargo.toml ├── .gitignore ├── radicle-std-ext ├── src │ ├── lib.rs │ └── result.rs └── Cargo.toml ├── CONTRIBUTING.md ├── DCO └── README.md /COPYING: -------------------------------------------------------------------------------- 1 | LICENSE -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.84 2 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | proptest-regressions 2 | -------------------------------------------------------------------------------- /archived/git-trailers/t/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | -------------------------------------------------------------------------------- /scripts/ci/fmt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eoux pipefail 3 | 4 | cargo fmt -- --check 5 | -------------------------------------------------------------------------------- /archived/git-commit/t/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod commit; 3 | 4 | #[cfg(test)] 5 | mod integration; 6 | -------------------------------------------------------------------------------- /scripts/ci/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eoux pipefail 3 | 4 | cargo build --tests --workspace 5 | -------------------------------------------------------------------------------- /radicle-surf/data/git-platinum.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicle-dev/radicle-git/HEAD/radicle-surf/data/git-platinum.tgz -------------------------------------------------------------------------------- /scripts/ci/test-fast: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eoux pipefail 3 | 4 | cargo nextest run --no-fail-fast tests properties 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "git-platinum"] 2 | path = radicle-surf/data/git-platinum 3 | url = https://github.com/radicle-dev/git-platinum.git 4 | -------------------------------------------------------------------------------- /git-storage/t/src/properties.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | mod odb; 5 | -------------------------------------------------------------------------------- /scripts/ci/macos-gnu: -------------------------------------------------------------------------------- 1 | if brew ls --versions gnu-tar > /dev/null; then 2 | echo "gnu-tar is already installed" 3 | else 4 | brew install gnu-tar 5 | fi 6 | -------------------------------------------------------------------------------- /test/test-helpers/src/gen.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPLv3-or-later 3 | 4 | pub mod std_net; 5 | -------------------------------------------------------------------------------- /test/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | //! This page intentionally left blank. 5 | -------------------------------------------------------------------------------- /git-storage/src/backend.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pub mod read; 5 | pub mod write; 6 | -------------------------------------------------------------------------------- /scripts/ci/docs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eoux pipefail 3 | 4 | RUSTDOCFLAGS="-D rustdoc::broken-intra-doc-links -D warnings" \ 5 | cargo doc --no-deps --workspace --document-private-items 6 | -------------------------------------------------------------------------------- /archived/link-git/t/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | #[cfg(test)] 5 | mod integration; 6 | #[cfg(test)] 7 | mod tests; 8 | -------------------------------------------------------------------------------- /radicle-git-ext/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Version 0.2.1 4 | 5 | * Update to rust-1.67 in [this commit](https://github.com/radicle-dev/radicle-git/commit/703692371260a19ecf79abd7c38bd511c6c120dc). 6 | -------------------------------------------------------------------------------- /scripts/ci/advisory: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eoux pipefail 3 | 4 | cargo deny --version 5 | cargo deny check advisories 6 | cargo deny check licenses 7 | cargo deny check bans 8 | cargo deny check sources 9 | -------------------------------------------------------------------------------- /scripts/ci/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eou pipefail 3 | 4 | ./scripts/ci/fmt 5 | ./scripts/ci/lint 6 | ./scripts/ci/advisory 7 | ./scripts/ci/build 8 | ./scripts/ci/test 9 | ./scripts/ci/docs 10 | -------------------------------------------------------------------------------- /test/test-helpers/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPLv3-or-later 3 | 4 | pub mod gen; 5 | pub mod logging; 6 | pub mod roundtrip; 7 | pub mod tempdir; 8 | -------------------------------------------------------------------------------- /scripts/ci/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eoux pipefail 3 | 4 | RUST_LOG=error ssh-agent cargo nextest run \ 5 | --status-level all \ 6 | --failure-output immediate-final \ 7 | --no-fail-fast \ 8 | --retries 2 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "radicle-git-ext", 4 | "radicle-std-ext", 5 | "radicle-surf", 6 | "test", 7 | ] 8 | exclude = [ 9 | "git-storage", 10 | ] 11 | resolver = "2" 12 | package.version = "0.18.0" -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/macro/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Version 0.2.0 4 | 5 | * 2022-11-16 Import Qualified within macro [629191f](https://github.com/radicle-dev/radicle-git/commit/629191f55afe77c00808cfe3d6f35d4cff1f4f73) 6 | -------------------------------------------------------------------------------- /archived/link-git/t/src/tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | mod protocol; 7 | -------------------------------------------------------------------------------- /archived/link-git/t/src/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | mod protocol; 7 | -------------------------------------------------------------------------------- /scripts/ci/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eoux pipefail 3 | 4 | # Force clippy to consider all local sources 5 | # https://github.com/rust-lang/rust-clippy/issues/4612 6 | find . -name "*.rs" -not -path "./target/*" -exec touch "{}" + 7 | cargo clippy --all-targets -- -D warnings 8 | -------------------------------------------------------------------------------- /archived/link-git/src/refs.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | pub mod db; 7 | pub use git_ref::*; 8 | -------------------------------------------------------------------------------- /git-storage/t/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | #[cfg(any(test, feature = "test"))] 5 | pub mod gen; 6 | #[cfg(test)] 7 | mod properties; 8 | #[cfg(any(test, feature = "test"))] 9 | pub mod tmp; 10 | -------------------------------------------------------------------------------- /archived/link-git/t/src/tests/protocol.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | mod take; 7 | mod upload_pack; 8 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/git_ref_format.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | #[cfg(any(test, feature = "test"))] 5 | pub mod gen; 6 | 7 | #[cfg(test)] 8 | pub mod properties; 9 | 10 | #[cfg(test)] 11 | pub mod tests; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .vscode 3 | tags 4 | # These are backup files generated by rustfmt 5 | **/*.rs.bk 6 | 7 | # This is an inner git repo unpacked (if necessary) from the archive. 8 | # Git doesn't like nested repos, and we don't really want to use submodules. 9 | radicle-surf/data/git-platinum 10 | -------------------------------------------------------------------------------- /archived/git-trailers/t/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-trailers-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | 7 | publish = false 8 | 9 | [lib] 10 | doctest = false 11 | test = true 12 | doc = false 13 | 14 | [features] 15 | test = [] 16 | 17 | [dev-dependencies] 18 | assert_matches = "1.5" 19 | pretty_assertions = "1.1" 20 | 21 | [dev-dependencies.git-trailers] 22 | path = ".." 23 | -------------------------------------------------------------------------------- /radicle-git-ext/src/transport.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | pub const UPLOAD_PACK_HEADER: &[u8] = b"001e# service=git-upload-pack\n0000"; 7 | pub const RECEIVE_PACK_HEADER: &[u8] = b"001f# service=git-receive-pack\n0000"; 8 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | #[cfg(test)] 4 | #[macro_use] 5 | extern crate assert_matches; 6 | 7 | #[cfg(any(test, feature = "test"))] 8 | pub mod gen; 9 | 10 | #[cfg(test)] 11 | mod commit; 12 | 13 | #[cfg(any(test, feature = "test"))] 14 | pub mod git_ref_format; 15 | 16 | #[cfg(any(test, feature = "test"))] 17 | pub mod repository; 18 | -------------------------------------------------------------------------------- /git-storage/t/src/tmp.rs: -------------------------------------------------------------------------------- 1 | use git2::Repository; 2 | use git_storage::{signature::UserInfo, Write}; 3 | use test_helpers::tempdir::WithTmpDir; 4 | 5 | pub type TmpWriter = WithTmpDir; 6 | pub type TmpRepo = WithTmpDir; 7 | 8 | pub fn writer(user: UserInfo) -> TmpWriter { 9 | WithTmpDir::new(|path| { 10 | Write::open(path, user) 11 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) 12 | }) 13 | .unwrap() 14 | } 15 | -------------------------------------------------------------------------------- /test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | 7 | publish = false 8 | 9 | [lib] 10 | doctest = false 11 | test = true 12 | doc = false 13 | 14 | [dev-dependencies.radicle-git-ext-test] 15 | path = "../radicle-git-ext/t" 16 | features = ["test"] 17 | 18 | [dev-dependencies.radicle-surf-test] 19 | path = "../radicle-surf/t" 20 | features = ["test"] 21 | 22 | [dev-dependencies.git-storage-test] 23 | path = "../git-storage/t" 24 | features = ["test"] 25 | -------------------------------------------------------------------------------- /archived/git-commit/t/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-commit-test" 3 | version = "0.1.0" 4 | license = "MIT OR Apache-2.0" 5 | edition = "2021" 6 | 7 | publish = false 8 | 9 | [lib] 10 | doctest = false 11 | test = true 12 | doc = false 13 | 14 | [features] 15 | test = [] 16 | 17 | [dev-dependencies.git-commit] 18 | path = ".." 19 | 20 | [dev-dependencies.git2] 21 | version = "0.16.1" 22 | default-features = false 23 | features = ["vendored-libgit2"] 24 | 25 | [dev-dependencies.test-helpers] 26 | path = "../../test/test-helpers" -------------------------------------------------------------------------------- /archived/git-trailers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-trailers" 3 | version = "0.1.0" 4 | authors = [ 5 | "Nuno Alexandre ", 6 | "Kim Altintop ", 7 | "Fintan Halpenny ", 8 | ] 9 | edition = "2018" 10 | license = "GPL-3.0-or-later" 11 | description = "Library to support parsing and display git trailers " 12 | keywords = ["git"] 13 | 14 | [lib] 15 | doctest = false 16 | test = false 17 | 18 | [dependencies] 19 | nom = "7.1" 20 | thiserror = "1.0" 21 | -------------------------------------------------------------------------------- /archived/git-commit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-commit" 3 | version = "0.3.0" 4 | license = "MIT OR Apache-2.0" 5 | edition = "2021" 6 | authors = [ 7 | "Alexis Sellier ", 8 | "Fintan Halpenny ", 9 | ] 10 | description = "A small library for parsing, displaying and creating a git commit" 11 | keywords = ["git", "git-commit", "git-trailers", "radicle"] 12 | 13 | [dependencies] 14 | thiserror = "1" 15 | 16 | [dependencies.git2] 17 | version = "0.16.1" 18 | default-features = false 19 | features = ["vendored-libgit2"] 20 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/gen.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2022 The Radicle Foundation 2 | // 3 | // This file is part of radicle-git, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | //! Provides proptest generators 7 | 8 | use proptest::strategy::Strategy; 9 | 10 | pub mod commit; 11 | pub mod urn; 12 | 13 | pub fn alphanumeric() -> impl Strategy { 14 | "[a-zA-Z0-9_]+" 15 | } 16 | 17 | pub fn alpha() -> impl Strategy { 18 | "[a-zA-Z]+" 19 | } 20 | -------------------------------------------------------------------------------- /radicle-std-ext/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | #![cfg_attr(feature = "nightly", feature(try_trait_v2))] 7 | 8 | pub mod ops; 9 | pub mod result; 10 | 11 | pub type Void = std::convert::Infallible; 12 | 13 | pub mod prelude { 14 | use super::*; 15 | 16 | pub use super::Void; 17 | pub use ops::{FromResidual, Try}; 18 | pub use result::ResultExt; 19 | } 20 | -------------------------------------------------------------------------------- /radicle-std-ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "radicle-std-ext" 3 | version = "0.1.1" 4 | authors = ["The Radicle Team "] 5 | edition = "2021" 6 | homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md" 7 | repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt" 8 | license = "GPL-3.0-or-later" 9 | description = "Monkey patches of std types" 10 | keywords = ["std", "radicle"] 11 | 12 | [lib] 13 | doctest = false 14 | test = false 15 | 16 | [features] 17 | default = [] 18 | nightly = [] 19 | -------------------------------------------------------------------------------- /archived/link-git/t/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "link-git-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | 7 | publish = false 8 | 9 | [lib] 10 | doctest = false 11 | test = true 12 | doc = false 13 | 14 | [features] 15 | test = [] 16 | 17 | [dev-dependencies] 18 | anyhow = "1" 19 | bstr = "0.2" 20 | futures = "0.3" 21 | futures_ringbuf = "0.3" 22 | tempfile = "3.4" 23 | 24 | [dev-dependencies.git2] 25 | version = "0.16.1" 26 | default-features = false 27 | features = ["vendored-libgit2"] 28 | 29 | [dev-dependencies.link-git] 30 | path = ".." 31 | features = ["git2"] -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/t/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-ref-format-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | 7 | publish = false 8 | 9 | [lib] 10 | doctest = false 11 | test = true 12 | doc = false 13 | 14 | [features] 15 | test = [] 16 | 17 | [dependencies] 18 | proptest = "1" 19 | 20 | [dev-dependencies] 21 | assert_matches = "1.5" 22 | serde_json = "1" 23 | 24 | [dev-dependencies.git-ref-format] 25 | path = ".." 26 | features = ["macro", "minicbor", "serde"] 27 | 28 | [dev-dependencies.test-helpers] 29 | path = "../../test/test-helpers" 30 | -------------------------------------------------------------------------------- /archived/link-git/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | #[macro_use] 7 | extern crate async_trait; 8 | 9 | pub mod odb; 10 | pub mod protocol; 11 | pub mod refs; 12 | pub use refs::db as refdb; 13 | #[cfg(feature = "git2")] 14 | pub mod service; 15 | 16 | pub use git_actor as actor; 17 | pub use git_hash as hash; 18 | pub use git_lock as lock; 19 | pub use git_object as object; 20 | pub use git_traverse as traverse; 21 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/gen/commit/trailers.rs: -------------------------------------------------------------------------------- 1 | use proptest::{collection, strategy::Strategy}; 2 | use radicle_git_ext::commit::trailers::{OwnedTrailer, Token, Trailer}; 3 | 4 | use crate::gen; 5 | 6 | pub fn trailers(n: usize) -> impl Strategy> { 7 | collection::vec(trailer(), 0..n) 8 | } 9 | 10 | pub fn trailer() -> impl Strategy { 11 | (gen::alpha(), gen::alphanumeric()).prop_map(|(token, value)| { 12 | Trailer { 13 | token: Token::try_from(format!("X-{}", token).as_str()).unwrap(), 14 | value: value.into(), 15 | } 16 | .to_owned() 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /test/test-helpers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-helpers" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | publish = false 7 | 8 | description = "Test helpers" 9 | 10 | [lib] 11 | doctest = false 12 | test = false 13 | 14 | [dependencies] 15 | env_logger = ">= 0.9" 16 | log = ">= 0.4" 17 | pretty_assertions = "1.1" 18 | serde = "1" 19 | serde_json = "1" 20 | tempfile = "3.4" 21 | tracing = "0.1" 22 | proptest = "1" 23 | 24 | [dependencies.minicbor] 25 | version = "0.13" 26 | features = ["std"] 27 | 28 | [dependencies.tracing-subscriber] 29 | version = "0.3.16" 30 | features = ["std", "env-filter", "fmt", "json"] 31 | -------------------------------------------------------------------------------- /radicle-git-ext/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | //! Extensions and wrappers for `git2` types 7 | 8 | pub mod author; 9 | pub mod blob; 10 | pub mod commit; 11 | pub mod error; 12 | pub mod oid; 13 | pub mod revwalk; 14 | pub mod transport; 15 | pub mod tree; 16 | 17 | pub use blob::*; 18 | pub use error::*; 19 | pub use oid::*; 20 | pub use revwalk::*; 21 | pub use transport::*; 22 | pub use tree::Tree; 23 | 24 | pub use git_ref_format as ref_format; 25 | -------------------------------------------------------------------------------- /radicle-surf/t/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Git Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | #[cfg(test)] 5 | const GIT_PLATINUM: &str = "../data/git-platinum"; 6 | 7 | #[cfg(test)] 8 | mod file_system; 9 | 10 | #[cfg(test)] 11 | mod source; 12 | 13 | #[cfg(test)] 14 | mod branch; 15 | 16 | #[cfg(test)] 17 | mod code_browsing; 18 | 19 | #[cfg(test)] 20 | mod commit; 21 | 22 | #[cfg(test)] 23 | mod diff; 24 | 25 | #[cfg(test)] 26 | mod last_commit; 27 | 28 | #[cfg(test)] 29 | mod namespace; 30 | 31 | #[cfg(test)] 32 | mod reference; 33 | 34 | #[cfg(test)] 35 | mod rev; 36 | 37 | #[cfg(test)] 38 | mod submodule; 39 | 40 | #[cfg(test)] 41 | mod threading; 42 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/gen/commit/author.rs: -------------------------------------------------------------------------------- 1 | use proptest::strategy::{Just, Strategy}; 2 | use radicle_git_ext::author::{Author, Time}; 3 | 4 | use crate::gen; 5 | 6 | pub fn author() -> impl Strategy { 7 | gen::alphanumeric().prop_flat_map(move |name| { 8 | (Just(name), gen::alphanumeric()).prop_flat_map(|(name, domain)| { 9 | (Just(name), Just(domain), (0..1000i64)).prop_map(move |(name, domain, time)| { 10 | let email = format!("{name}@{domain}"); 11 | Author { 12 | name, 13 | email, 14 | time: Time::new(time, 0), 15 | } 16 | }) 17 | }) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /git-storage/src/signature.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 5 | pub struct UserInfo { 6 | /// Provided `name` of the user. 7 | pub name: String, 8 | /// Proivded `email` of the user. Note that this does not 9 | /// necessarily have to be an email, but will be used as the email 10 | /// field in the [`git2::Signature`]. 11 | pub email: String, 12 | } 13 | 14 | impl UserInfo { 15 | /// Obtain the [`git2::Signature`] for this `UserInfo`. 16 | pub fn signature(&self) -> Result { 17 | git2::Signature::now(&self.name, &self.email) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /radicle-git-ext/t/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "radicle-git-ext-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | 7 | publish = false 8 | 9 | [lib] 10 | doctest = false 11 | test = true 12 | doc = false 13 | 14 | [features] 15 | test = [] 16 | 17 | [dependencies] 18 | proptest = "1" 19 | 20 | [dev-dependencies] 21 | assert_matches = "1.5" 22 | minicbor = "0.13" 23 | serde = "1" 24 | serde_json = "1" 25 | 26 | [dependencies.git2] 27 | version = "0.19" 28 | default-features = false 29 | features = ["vendored-libgit2"] 30 | 31 | [dependencies.radicle-git-ext] 32 | path = ".." 33 | features = ["serde", "minicbor"] 34 | 35 | [dependencies.test-helpers] 36 | path = "../../test/test-helpers" 37 | -------------------------------------------------------------------------------- /git-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-storage" 3 | version = "0.1.0" 4 | authors = ["Kim Altintop ", "Fintan Halpenny "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | globset = "0.4" 10 | libc = "0.2" 11 | parking_lot = "0.12" 12 | thiserror = "1" 13 | either = "1.8.0" 14 | 15 | [dependencies.git2] 16 | version = "0.19" 17 | default-features = false 18 | features = ["vendored-libgit2"] 19 | 20 | [dependencies.libgit2-sys] 21 | version = ">= 0.14.2" 22 | default-features = false 23 | features = ["vendored"] 24 | 25 | [dependencies.radicle-git-ext] 26 | path = "../radicle-git-ext" 27 | 28 | [dependencies.radicle-std-ext] 29 | path = "../radicle-std-ext" 30 | -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | mod check; 7 | pub use check::{ref_format as check_ref_format, Error, Options}; 8 | 9 | mod deriv; 10 | pub use deriv::{Namespaced, Qualified}; 11 | 12 | pub mod lit; 13 | 14 | pub mod name; 15 | #[cfg(feature = "percent-encoding")] 16 | pub use name::PercentEncode; 17 | pub use name::{Component, RefStr, RefString}; 18 | 19 | pub mod refspec; 20 | pub use refspec::DuplicateGlob; 21 | 22 | #[cfg(feature = "minicbor")] 23 | mod cbor; 24 | #[cfg(feature = "serde")] 25 | mod serde; 26 | -------------------------------------------------------------------------------- /radicle-git-ext/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::{fmt::Display, io}; 7 | 8 | pub fn is_not_found_err(e: &git2::Error) -> bool { 9 | e.code() == git2::ErrorCode::NotFound 10 | } 11 | 12 | pub fn is_exists_err(e: &git2::Error) -> bool { 13 | e.code() == git2::ErrorCode::Exists 14 | } 15 | 16 | pub fn into_git_err(e: E) -> git2::Error { 17 | git2::Error::from_str(&e.to_string()) 18 | } 19 | 20 | pub fn into_io_err(e: git2::Error) -> io::Error { 21 | io::Error::new(io::ErrorKind::Other, e) 22 | } 23 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/git_ref_format/properties.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use proptest::prelude::*; 5 | use radicle_git_ext::ref_format::{check_ref_format, Error, Options}; 6 | 7 | use crate::git_ref_format::gen; 8 | 9 | mod name; 10 | mod pattern; 11 | 12 | proptest! { 13 | #[test] 14 | fn disallow_onelevel(input in gen::trivial(), allow_pattern in any::()) { 15 | assert_matches!( 16 | check_ref_format(Options { 17 | allow_onelevel: false, 18 | allow_pattern, 19 | }, 20 | &input 21 | ), 22 | Err(Error::OneLevel) 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-ref-format-macro" 3 | version = "0.4.0" 4 | authors = ["Kim Altintop "] 5 | edition = "2021" 6 | homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md" 7 | repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt" 8 | license = "GPL-3.0-or-later" 9 | description = "Macros for the git-ref-format crate" 10 | keywords = ["git", "references"] 11 | 12 | [lib] 13 | doctest = false 14 | proc-macro = true 15 | test = false 16 | 17 | [dependencies] 18 | proc-macro-error2 = "2" 19 | quote = "1" 20 | syn = "2" 21 | 22 | [dependencies.git-ref-format-core] 23 | version = "0.4.0" 24 | path = "../core" 25 | -------------------------------------------------------------------------------- /radicle-surf/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Version 0.9.0 2 | 3 | This release consists of a major rewrite of this crate. Its API is overall 4 | simplified and is not compatible with the previous version (v0.8.0). The main 5 | changes include: 6 | 7 | - `Browser` is removed. Its methods are implemented directly with `Repository`. 8 | - Git will be the only supported VCS. Any extension points for other VCSes were 9 | removed. 10 | - `Ref` and `RefScope` are removed. Re-use the `git-ref-format` crate and a new 11 | `Glob` type for the refspec patterns. 12 | - Added support of `Tree` and `Blob` that correspond to their definitions in 13 | Git. 14 | - Added two new traits `Revision` and `ToCommit` that make methods flexible and 15 | still simple to use. 16 | 17 | For more details, please check out the crate's documentation. 18 | -------------------------------------------------------------------------------- /radicle-surf/data/README.md: -------------------------------------------------------------------------------- 1 | # Updating [git-platinum][] 2 | 3 | 1. Push your changes to [`radicle-dev/git-platinum`][git-platinum] and/or update 4 | `surf/data/mock-branches.txt`. 5 | 2. Run `scripts/update-git-platinum.sh` from the repo root. This updates 6 | `surf/data/git-platinum.tgz`. 7 | 3. Run `cargo build` to unpack the updated repo. 8 | 4. Run the tests 9 | 5. Commit your changes. We provide a template below so that we can easily 10 | identify changes to `git-platinum`. Please fill in the details that follow a 11 | comment (`#`): 12 | ``` 13 | data/git-platinum: # short reason for updating 14 | 15 | # provide a longer reason for making changes to git-platinum 16 | # as well as what has changed. 17 | ``` 18 | 19 | 20 | 21 | [git-platinum]: https://github.com/radicle-dev/git-platinum 22 | -------------------------------------------------------------------------------- /git-storage/t/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-storage-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | 7 | publish = false 8 | 9 | [lib] 10 | doctest = false 11 | test = true 12 | doc = false 13 | 14 | [features] 15 | test = [] 16 | 17 | [dependencies] 18 | proptest = "1" 19 | 20 | [dependencies.git-storage] 21 | path = ".." 22 | 23 | [dependencies.git2] 24 | version = "0.19" 25 | default-features = false 26 | features = ["vendored-libgit2"] 27 | 28 | [dependencies.radicle-git-ext] 29 | path = "../../radicle-git-ext/" 30 | 31 | [dependencies.test-helpers] 32 | path = "../../test/test-helpers" 33 | 34 | [dev-dependencies.uuid] 35 | version = "1" 36 | features = ["v4"] 37 | 38 | [dev-dependencies.radicle-git-ext-test] 39 | path = "../../radicle-git-ext/t" 40 | features = ["test"] 41 | -------------------------------------------------------------------------------- /radicle-surf/README.md: -------------------------------------------------------------------------------- 1 | # radicle-surf 2 | 3 | A code surfing library for Git repositories 🏄‍♀️🏄‍♂️ 4 | 5 | Welcome to `radicle-surf`! 6 | 7 | `radicle-surf` is a library to describe a Git repository as a file system. It 8 | aims to provide an easy-to-use API to browse a repository via the concept of 9 | files and directories for any given revision. It also allows the user to diff 10 | any two different revisions. 11 | 12 | One of the use cases would be to create a web GUI for interacting with a Git 13 | repository (thinking GitHub, GitLab or similar systems). 14 | 15 | ## Contributing 16 | 17 | To get started on contributing you can check out our [developing guide](../DEVELOPMENT.md), and also 18 | our [LICENSE](../LICENSE) file. 19 | 20 | ## The Community 21 | 22 | Join our community disccussions at [radicle.community](https://radicle.community)! 23 | -------------------------------------------------------------------------------- /radicle-surf/t/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "radicle-surf-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | 7 | publish = false 8 | 9 | [lib] 10 | test = true 11 | 12 | [features] 13 | test = [] 14 | 15 | [dev-dependencies] 16 | nonempty = "0.5" 17 | pretty_assertions = "1.3.0" 18 | proptest = "1" 19 | serde_json = "1" 20 | url = "2.5" 21 | 22 | [dev-dependencies.git2] 23 | version = "0.19" 24 | default-features = false 25 | features = ["vendored-libgit2"] 26 | 27 | [dev-dependencies.radicle-git-ext] 28 | path = "../../radicle-git-ext" 29 | 30 | [dev-dependencies.radicle-git-ext-test] 31 | path = "../../radicle-git-ext/t" 32 | features = ["test"] 33 | 34 | [dev-dependencies.radicle-surf] 35 | path = ".." 36 | features = ["serde"] 37 | 38 | [dev-dependencies.test-helpers] 39 | path = "../../test/test-helpers" 40 | -------------------------------------------------------------------------------- /radicle-surf/data/mock-branches.txt: -------------------------------------------------------------------------------- 1 | refs/namespaces/golden/refs/heads/master,refs/heads/master 2 | refs/namespaces/golden/refs/heads/banana,refs/heads/dev 3 | refs/namespaces/golden/refs/tags/v0.1.0,refs/tags/v0.1.0 4 | refs/namespaces/golden/refs/tags/v0.2.0,refs/tags/v0.2.0 5 | refs/namespaces/golden/refs/remotes/kickflip/heads/heelflip,refs/heads/dev 6 | refs/namespaces/golden/refs/remotes/kickflip/heads/fakie/bigspin,refs/heads/dev 7 | refs/namespaces/golden/refs/remotes/kickflip/tags/v0.1.0,refs/tags/v0.1.0 8 | refs/namespaces/golden/refs/namespaces/silver/refs/heads/master,refs/heads/dev 9 | refs/remotes/banana/pineapple,refs/remotes/origin/master 10 | refs/remotes/banana/orange/pineapple,refs/remotes/origin/master 11 | refs/namespaces/me/refs/heads/feature/#1194,refs/heads/master 12 | refs/namespaces/me/refs/remotes/fein/heads/feature/#1194,refs/heads/dev 13 | -------------------------------------------------------------------------------- /archived/git-commit/t/src/integration.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use git_commit::Commit; 4 | use test_helpers::tempdir::WithTmpDir; 5 | 6 | #[test] 7 | fn valid_commits() { 8 | let radicle_git = format!( 9 | "file://{}", 10 | git2::Repository::discover(".").unwrap().path().display() 11 | ); 12 | let repo = WithTmpDir::new(|path| { 13 | let repo = git2::Repository::clone(&radicle_git, path) 14 | .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; 15 | Ok::<_, io::Error>(repo) 16 | }) 17 | .unwrap(); 18 | 19 | let mut walk = repo.revwalk().unwrap(); 20 | walk.push_head().unwrap(); 21 | 22 | // take the first 20 commits and make sure we can parse them 23 | for oid in walk.take(20) { 24 | let oid = oid.unwrap(); 25 | let commit = Commit::read(&repo, oid); 26 | assert!(commit.is_ok(), "Oid: {oid}, Error: {commit:?}") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /radicle-surf/t/src/branch.rs: -------------------------------------------------------------------------------- 1 | use proptest::prelude::*; 2 | use radicle_git_ext::ref_format::{RefStr, RefString}; 3 | use radicle_git_ext_test::git_ref_format::gen; 4 | use radicle_surf::Branch; 5 | use test_helpers::roundtrip; 6 | 7 | proptest! { 8 | #[test] 9 | fn prop_test_branch(branch in gen_branch()) { 10 | roundtrip::json(branch) 11 | } 12 | } 13 | 14 | fn gen_branch() -> impl Strategy { 15 | prop_oneof![ 16 | gen::valid().prop_map(|name| Branch::local(RefString::try_from(name).unwrap())), 17 | (gen::valid(), gen::valid()).prop_map(|(remote, name): (String, String)| { 18 | let remote = 19 | RefStr::try_from_str(&remote).expect("BUG: reference strings should be valid"); 20 | let name = RefStr::try_from_str(&name).expect("BUG: reference strings should be valid"); 21 | Branch::remote(remote.head(), name) 22 | }) 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /archived/link-git/src/protocol.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use bstr::ByteSlice as _; 7 | use git_protocol::transport::client; 8 | use versions::Version; 9 | 10 | pub mod fetch; 11 | pub mod ls; 12 | pub mod packwriter; 13 | pub mod take; 14 | pub mod transport; 15 | pub mod upload_pack; 16 | 17 | pub use fetch::{fetch, Ref}; 18 | pub use ls::ls_refs; 19 | pub use packwriter::PackWriter; 20 | pub use upload_pack::upload_pack; 21 | 22 | pub use git_hash::{oid, ObjectId}; 23 | 24 | fn remote_git_version(caps: &client::Capabilities) -> Option { 25 | let agent = caps.capability("agent").and_then(|cap| { 26 | cap.value() 27 | .and_then(|bs| bs.to_str().map(|s| s.to_owned()).ok()) 28 | })?; 29 | Version::new(agent.strip_prefix("git/")?) 30 | } 31 | -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-ref-format-core" 3 | version = "0.4.0" 4 | authors = ["Kim Altintop "] 5 | edition = "2021" 6 | homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md" 7 | repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt" 8 | license = "GPL-3.0-or-later" 9 | description = "Core types for the git-ref-format crate" 10 | keywords = ["git", "references"] 11 | 12 | [lib] 13 | doctest = false 14 | test = false 15 | 16 | [dependencies] 17 | thiserror = "1.0" 18 | 19 | [dependencies.bstr] 20 | version = "1.3" 21 | optional = true 22 | 23 | [dependencies.minicbor] 24 | version = "0.13" 25 | features = ["std"] 26 | optional = true 27 | 28 | [dependencies.percent-encoding] 29 | version = "2.1.0" 30 | optional = true 31 | 32 | [dependencies.serde] 33 | version = "1.0" 34 | features = ["derive"] 35 | optional = true 36 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/gen/urn.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2022 The Radicle Foundation 2 | // 3 | // This file is part of radicle-git, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use proptest::prelude::*; 7 | use radicle_git_ext::Oid; 8 | 9 | /// A proptest strategy that generates [`radicle_git_ext::oid::Oid`] values 10 | /// of the type indicated in parameter `kind` 11 | pub fn gen_oid(kind: git2::ObjectType) -> impl Strategy { 12 | any::>() 13 | .prop_map(move |bytes| git2::Oid::hash_object(kind, &bytes).map(Oid::from).unwrap()) 14 | } 15 | 16 | /// A proptest strategy that generates [`radicle_git_ext::oid::Oid`] values 17 | /// of the type indicated in parameter `kind` or a zeroed `Oid` with 18 | /// equal probability 19 | pub fn gen_oid_with_zero(kind: git2::ObjectType) -> impl Strategy { 20 | prop_oneof![gen_oid(kind), Just(git2::Oid::zero().into()),] 21 | } 22 | -------------------------------------------------------------------------------- /radicle-surf/t/src/commit.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use proptest::prelude::*; 4 | use radicle_git_ext::Oid; 5 | use radicle_surf::{Author, Commit, Time}; 6 | use test_helpers::roundtrip; 7 | 8 | proptest! { 9 | #[test] 10 | fn prop_test_commits(commit in commits_strategy()) { 11 | roundtrip::json(commit) 12 | } 13 | } 14 | 15 | fn commits_strategy() -> impl Strategy { 16 | ("[a-fA-F0-9]{40}", any::(), any::()).prop_map(|(id, text, time)| Commit { 17 | id: Oid::from_str(&id).unwrap(), 18 | author: Author { 19 | name: text.clone(), 20 | email: text.clone(), 21 | time: Time::new(time, 0), 22 | }, 23 | committer: Author { 24 | name: text.clone(), 25 | email: text.clone(), 26 | time: Time::new(time, 0), 27 | }, 28 | message: text.clone(), 29 | summary: text, 30 | parents: vec![Oid::from_str(&id).unwrap(), Oid::from_str(&id).unwrap()], 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /archived/link-git/src/odb/backend.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use git_hash::oid; 7 | use git_pack::{cache::DecodeEntry, data::Object}; 8 | 9 | use super::{index, pack, window}; 10 | 11 | pub type Loose = git_odb::loose::Store; 12 | 13 | pub struct Packed { 14 | pub index: I, 15 | pub data: D, 16 | } 17 | 18 | impl Packed 19 | where 20 | I: index::Index, 21 | D: window::Cache, 22 | { 23 | pub fn contains(&self, id: impl AsRef) -> bool { 24 | self.index.contains(id) 25 | } 26 | 27 | pub fn find<'a>( 28 | &self, 29 | id: impl AsRef, 30 | buf: &'a mut Vec, 31 | cache: &mut impl DecodeEntry, 32 | ) -> Result>, index::error::Lookup> { 33 | self.index 34 | .lookup(|info| self.data.get(info), id, buf, cache) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/gen/commit/headers.rs: -------------------------------------------------------------------------------- 1 | use proptest::{collection, prop_oneof, strategy::Strategy}; 2 | use radicle_git_ext::commit::headers::Headers; 3 | 4 | use crate::gen; 5 | 6 | pub fn headers() -> impl Strategy { 7 | collection::vec(prop_oneof![header(), signature()], 0..5).prop_map(|hs| { 8 | let mut headers = Headers::new(); 9 | for (k, v) in hs { 10 | headers.push(&k, &v); 11 | } 12 | headers 13 | }) 14 | } 15 | 16 | fn header() -> impl Strategy { 17 | (prop_oneof!["test", "foo", "foobar"], gen::alphanumeric()) 18 | } 19 | 20 | pub fn signature() -> impl Strategy { 21 | ("gpgsig", prop_oneof![pgp(), ssh()]) 22 | } 23 | 24 | pub fn pgp() -> impl Strategy { 25 | "-----BEGIN PGP SIGNATURE-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END PGP SIGNATURE-----" 26 | } 27 | 28 | pub fn ssh() -> impl Strategy { 29 | "-----BEGIN SSH SIGNATURE-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END SSH SIGNATURE-----" 30 | } 31 | -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-ref-format" 3 | version = "0.4.0" 4 | authors = [ 5 | "Kim Altintop ", 6 | "Fintan Halpenny ", 7 | ] 8 | edition = "2021" 9 | homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md" 10 | repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt" 11 | license = "GPL-3.0-or-later" 12 | description = "Everything you never knew you wanted for handling git ref names." 13 | keywords = ["git", "references"] 14 | 15 | [lib] 16 | doctest = false 17 | test = false 18 | 19 | [features] 20 | bstr = ["git-ref-format-core/bstr"] 21 | macro = ["git-ref-format-macro"] 22 | minicbor = ["git-ref-format-core/minicbor"] 23 | percent-encoding = ["git-ref-format-core/percent-encoding"] 24 | serde = ["git-ref-format-core/serde"] 25 | 26 | [dependencies.git-ref-format-core] 27 | version = "0.4.0" 28 | path = "./core" 29 | 30 | [dependencies.git-ref-format-macro] 31 | version = "0.4.0" 32 | path = "./macro" 33 | optional = true 34 | -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Version 0.2.0 4 | 5 | * 2022-12-16 Update to rust-1.66 [8d451e3](https://github.com/radicle-dev/radicle-git/commit/8d451e3d9d6c0121b0ed3e4ac6cbf710625e9f9d) 6 | * 2022-12-08 Fix to deserialization [db7f2d4](https://github.com/radicle-dev/radicle-git/commit/db7f2d40e32ef0fac7425054c4c49e22cb8bc70f) 7 | * 2022-11-18 Remove link-literals feature [d45d63b](https://github.com/radicle-dev/radicle-git/commit/d45d63b7ceb1d03b80f6c47749b4b3e3ebb99e70) 8 | * 2022-11-16 Add well-known refspec components [2b5a34a](https://github.com/radicle-dev/radicle-git/commit/2b5a34a703c182ff48d6c83ff3fa4256b4f37624) 9 | * 2022-11-15 DoubleEndedIterator for Components [cf1594c](https://github.com/radicle-dev/radicle-git/commit/cf1594cf43e23c9fc8f74cf69e22f81227e82f2b) 10 | * 2022-11-11 Add QualifiedPattern and NamespacedPattern [6479ba0](https://github.com/radicle-dev/radicle-git/commit/6479ba091fa3ee3b16fba61138cfd8c6ec6d985d) 11 | * 2022-11-16 Component::from_refstr [d6c7bb2](https://github.com/radicle-dev/radicle-git/commit/d6c7bb29abc0f0050e2efe8de47397eaa4564f6f) 12 | -------------------------------------------------------------------------------- /test/test-helpers/src/tempdir.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::{ 7 | io, 8 | ops::{Deref, DerefMut}, 9 | path::Path, 10 | }; 11 | 12 | use tempfile::{tempdir, TempDir}; 13 | 14 | #[derive(Debug)] 15 | pub struct WithTmpDir { 16 | _tmp: TempDir, 17 | inner: A, 18 | } 19 | 20 | impl WithTmpDir { 21 | pub fn new(mk_inner: F) -> Result 22 | where 23 | F: FnOnce(&Path) -> Result, 24 | E: From, 25 | { 26 | let tmp = tempdir()?; 27 | let inner = mk_inner(tmp.path())?; 28 | Ok(Self { _tmp: tmp, inner }) 29 | } 30 | } 31 | 32 | impl Deref for WithTmpDir { 33 | type Target = A; 34 | 35 | fn deref(&self) -> &Self::Target { 36 | &self.inner 37 | } 38 | } 39 | 40 | impl DerefMut for WithTmpDir { 41 | fn deref_mut(&mut self) -> &mut Self::Target { 42 | &mut self.inner 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /git-storage/src/glob.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::path::Path; 7 | 8 | use git_ext::ref_format::refspec::PatternString; 9 | 10 | pub trait Pattern { 11 | fn matches>(&self, path: P) -> bool; 12 | } 13 | 14 | impl Pattern for globset::GlobMatcher { 15 | fn matches>(&self, path: P) -> bool { 16 | self.is_match(path) 17 | } 18 | } 19 | 20 | impl Pattern for globset::GlobSet { 21 | fn matches>(&self, path: P) -> bool { 22 | self.is_match(path) 23 | } 24 | } 25 | 26 | #[derive(Clone, Debug)] 27 | pub struct RefspecMatcher(globset::GlobMatcher); 28 | 29 | impl From for RefspecMatcher { 30 | fn from(pat: PatternString) -> Self { 31 | Self(globset::Glob::new(pat.as_str()).unwrap().compile_matcher()) 32 | } 33 | } 34 | 35 | impl Pattern for RefspecMatcher { 36 | fn matches>(&self, path: P) -> bool { 37 | self.0.is_match(path) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /radicle-surf/src/stats.rs: -------------------------------------------------------------------------------- 1 | // This file is part of radicle-surf 2 | // 3 | // 4 | // Copyright (C) 2019-2020 The Radicle Team 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License version 3 or 8 | // later as published by the Free Software Foundation. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | #[cfg(feature = "serde")] 19 | use serde::Serialize; 20 | 21 | /// Stats for a repository 22 | #[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] 23 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 24 | pub struct Stats { 25 | /// Number of commits 26 | pub commits: usize, 27 | /// Number of local branches 28 | pub branches: usize, 29 | /// Number of contributors 30 | pub contributors: usize, 31 | } 32 | -------------------------------------------------------------------------------- /test/test-helpers/src/roundtrip.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::{ 7 | fmt::{Debug, Display}, 8 | str::FromStr, 9 | }; 10 | 11 | use pretty_assertions::assert_eq; 12 | 13 | pub fn json(a: A) 14 | where 15 | for<'de> A: Debug + PartialEq + serde::Serialize + serde::Deserialize<'de>, 16 | { 17 | assert_eq!( 18 | a, 19 | serde_json::from_str(&serde_json::to_string(&a).unwrap()).unwrap() 20 | ) 21 | } 22 | 23 | pub fn json_value(a: A) 24 | where 25 | for<'de> A: Clone + Debug + PartialEq + serde::Serialize + serde::Deserialize<'de>, 26 | { 27 | assert_eq!( 28 | a.clone(), 29 | serde_json::from_value(serde_json::to_value(a).unwrap()).unwrap() 30 | ) 31 | } 32 | 33 | pub fn cbor(a: A) 34 | where 35 | for<'de> A: Debug + PartialEq + minicbor::Encode + minicbor::Decode<'de>, 36 | { 37 | assert_eq!(a, minicbor::decode(&minicbor::to_vec(&a).unwrap()).unwrap()) 38 | } 39 | 40 | pub fn str(a: A) 41 | where 42 | A: Debug + PartialEq + Display + FromStr, 43 | ::Err: Debug, 44 | { 45 | assert_eq!(a, a.to_string().parse().unwrap()) 46 | } 47 | -------------------------------------------------------------------------------- /radicle-surf/scripts/update-git-platinum.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # Verify that the script is run from project root. 5 | BASE=$(basename "$(pwd)") 6 | 7 | if [ "${BASE}" != "radicle-surf" ] 8 | then 9 | echo "ERROR: this script should be run from the root of radicle-surf" 10 | exit 1 11 | fi 12 | 13 | TARBALL_PATH=data/git-platinum.tgz 14 | WORKDIR=.workdir 15 | PLATINUM_REPO="$WORKDIR/git-platinum" 16 | 17 | # Create the workdir if needed. 18 | mkdir -p $WORKDIR 19 | 20 | # This is here in case the last script run failed and it never cleaned up. 21 | rm -rf "$PLATINUM_REPO" 22 | 23 | # Clone an up-to-date version of git-platinum. 24 | git clone https://github.com/radicle-dev/git-platinum.git "$PLATINUM_REPO" 25 | git -C "$PLATINUM_REPO" checkout empty-branch 26 | git -C "$PLATINUM_REPO" checkout diff-test 27 | git -C "$PLATINUM_REPO" checkout dev 28 | 29 | # Add the necessary refs. 30 | input="./data/mock-branches.txt" 31 | while IFS= read -r line 32 | do 33 | IFS=, read -ra pair <<< "$line" 34 | echo "Creating branch ${pair[0]}" 35 | git -C "$PLATINUM_REPO" update-ref "${pair[0]}" "${pair[1]}" 36 | done < "$input" 37 | 38 | # Update the archive. 39 | tar -czf $WORKDIR/git-platinum.tgz -C $WORKDIR git-platinum 40 | mv $WORKDIR/git-platinum.tgz $TARBALL_PATH 41 | 42 | # Clean up. 43 | rm -rf "$PLATINUM_REPO" 44 | -------------------------------------------------------------------------------- /radicle-git-ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "radicle-git-ext" 3 | version = "0.9.0" 4 | authors = [ 5 | "Alexis Sellier ", 6 | "Kim Altintop ", 7 | "Fintan Halpenny ", 9 | ] 10 | edition = "2021" 11 | homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/README.md" 12 | repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt" 13 | license = "GPL-3.0-or-later" 14 | description = "Utilities and extensions to the git2 crate" 15 | keywords = ["git", "radicle"] 16 | 17 | [lib] 18 | doctest = false 19 | test = false 20 | 21 | [features] 22 | minicbor = ["git-ref-format/minicbor"] 23 | bstr = ["git-ref-format/bstr"] 24 | percent-encoding = ["git-ref-format/percent-encoding"] 25 | 26 | [dependencies] 27 | percent-encoding = "2" 28 | thiserror = "1" 29 | 30 | [dependencies.git2] 31 | version = "0.19" 32 | default-features = false 33 | features = ["vendored-libgit2"] 34 | 35 | [dependencies.git-ref-format] 36 | version = "0.4.0" 37 | path = "./git-ref-format" 38 | features = ["macro", "serde"] 39 | 40 | [dependencies.serde] 41 | version = "1" 42 | features = ["derive"] 43 | optional = true 44 | 45 | [dependencies.radicle-std-ext] 46 | version = "0.1.1" 47 | path = "../radicle-std-ext" 48 | -------------------------------------------------------------------------------- /radicle-git-ext/src/revwalk.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | pub enum Start { 7 | Oid(git2::Oid), 8 | Ref(String), 9 | } 10 | 11 | pub struct FirstParent<'a> { 12 | inner: git2::Revwalk<'a>, 13 | } 14 | 15 | impl<'a> FirstParent<'a> { 16 | pub fn new(repo: &'a git2::Repository, start: Start) -> Result { 17 | let mut revwalk = repo.revwalk()?; 18 | revwalk.set_sorting(git2::Sort::TOPOLOGICAL)?; 19 | revwalk.simplify_first_parent()?; 20 | 21 | match start { 22 | Start::Oid(oid) => revwalk.push(oid), 23 | Start::Ref(name) => revwalk.push_ref(&name), 24 | }?; 25 | 26 | Ok(Self { inner: revwalk }) 27 | } 28 | 29 | pub fn reverse(mut self) -> Result { 30 | let mut sort = git2::Sort::TOPOLOGICAL; 31 | sort.insert(git2::Sort::REVERSE); 32 | self.inner.set_sorting(sort)?; 33 | Ok(self) 34 | } 35 | } 36 | 37 | impl<'a> IntoIterator for FirstParent<'a> { 38 | type Item = Result; 39 | type IntoIter = git2::Revwalk<'a>; 40 | 41 | fn into_iter(self) -> Self::IntoIter { 42 | self.inner 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for your interest in contributing to this project! 2 | 3 | Before you submit your first patch, please take a moment to review the following 4 | guidelines: 5 | 6 | ## Certificate of Origin 7 | 8 | By contributing to this project, you agree to the [Developer Certificate of 9 | Origin (DCO)][dco]. This document was created by the Linux Kernel community 10 | and is a simple statement that you, as a contributor, have the legal right to 11 | make the contribution. 12 | 13 | In order to show your agreement with the DCO you should include at the end of 14 | the commit message, the following line: 15 | 16 | Signed-off-by: John Doe 17 | 18 | using your real name and email. 19 | 20 | This can be done easily using `git commit -s`. 21 | 22 | ### Fixing the DCO 23 | 24 | If you did not sign-off one or more of your commits then it is not all for not. 25 | The crowd at `src-d` have a [wonderful guide][fixing-dco] on how to remedy this 26 | situation. 27 | 28 | ## License Header 29 | 30 | As part of our license, we must include a license header at the top of each 31 | source file. The template for this header can be found [here][header-template]. 32 | If you are creating a new file in the repository you will have to add this 33 | header to the file. 34 | 35 | [dco]: ./DCO 36 | [fixing-dco]: https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md 37 | [header-template]: ./.license-header 38 | -------------------------------------------------------------------------------- /archived/link-git/src/odb.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use git_hash::oid; 7 | use thiserror::Error; 8 | 9 | pub mod backend; 10 | pub mod index; 11 | pub mod pack; 12 | pub mod window; 13 | 14 | pub use git_pack::{cache, data::Object}; 15 | 16 | #[derive(Debug, Error)] 17 | pub enum Error { 18 | #[error(transparent)] 19 | Packed(#[from] index::error::Lookup), 20 | 21 | #[error(transparent)] 22 | Loose(#[from] git_odb::loose::find::Error), 23 | } 24 | 25 | pub struct Odb { 26 | pub loose: backend::Loose, 27 | pub packed: backend::Packed, 28 | } 29 | 30 | impl Odb 31 | where 32 | I: index::Index, 33 | D: window::Cache, 34 | { 35 | pub fn contains(&self, id: impl AsRef) -> bool { 36 | self.packed.contains(id.as_ref()) || self.loose.contains(id) 37 | } 38 | 39 | pub fn find<'a>( 40 | &self, 41 | id: impl AsRef, 42 | buf: &'a mut Vec, 43 | cache: &mut impl cache::DecodeEntry, 44 | ) -> Result>, Error> { 45 | let id = id.as_ref(); 46 | if self.packed.contains(id) { 47 | return self.packed.find(id, buf, cache).map_err(Into::into); 48 | } 49 | self.loose.try_find(id, buf).map_err(Into::into) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/test-helpers/src/gen/std_net.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPLv3-or-later 3 | 4 | use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; 5 | 6 | use proptest::prelude::*; 7 | 8 | prop_compose! { 9 | pub fn gen_ipaddr_v4() 10 | (a in any::(), 11 | b in any::(), 12 | c in any::(), 13 | d in any::()) -> Ipv4Addr{ 14 | Ipv4Addr::new(a, b, c, d) 15 | } 16 | } 17 | 18 | prop_compose! { 19 | pub fn gen_ipaddr_v6() 20 | (a in any::(), 21 | b in any::(), 22 | c in any::(), 23 | d in any::(), 24 | e in any::(), 25 | f in any::(), 26 | g in any::(), 27 | h in any::()) -> Ipv6Addr 28 | { 29 | Ipv6Addr::new(a, b, c, d, e, f, g, h) 30 | } 31 | } 32 | 33 | pub fn gen_socket_v4() -> impl Strategy { 34 | any::().prop_flat_map(move |port| { 35 | gen_ipaddr_v4().prop_map(move |v4| SocketAddr::V4(SocketAddrV4::new(v4, port))) 36 | }) 37 | } 38 | 39 | pub fn gen_socket_v6() -> impl Strategy { 40 | any::().prop_flat_map(move |port| { 41 | gen_ipaddr_v6().prop_map(move |v6| SocketAddr::V6(SocketAddrV6::new(v6, port, 0, 0))) 42 | }) 43 | } 44 | 45 | pub fn gen_socket_addr() -> impl Strategy { 46 | prop_oneof![gen_socket_v4(), gen_socket_v6()] 47 | } 48 | -------------------------------------------------------------------------------- /radicle-surf/t/src/threading.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Mutex, MutexGuard}; 2 | 3 | use radicle_git_ext::ref_format::{name::component, refname}; 4 | use radicle_surf::{Branch, Error, Glob, Repository}; 5 | 6 | use super::GIT_PLATINUM; 7 | 8 | #[test] 9 | fn basic_test() -> Result<(), Error> { 10 | let shared_repo = Mutex::new(Repository::open(GIT_PLATINUM)?); 11 | let locked_repo: MutexGuard = shared_repo.lock().unwrap(); 12 | let mut branches = locked_repo 13 | .branches(Glob::all_heads().branches().and(Glob::all_remotes()))? 14 | .collect::, _>>()?; 15 | branches.sort(); 16 | 17 | let origin = component!("origin"); 18 | let banana = component!("banana"); 19 | assert_eq!( 20 | branches, 21 | vec![ 22 | Branch::local(refname!("dev")), 23 | Branch::local(refname!("diff-test")), 24 | Branch::local(refname!("empty-branch")), 25 | Branch::local(refname!("master")), 26 | Branch::remote(banana.clone(), refname!("orange/pineapple")), 27 | Branch::remote(banana, refname!("pineapple")), 28 | Branch::remote(origin.clone(), refname!("HEAD")), 29 | Branch::remote(origin.clone(), refname!("dev")), 30 | Branch::remote(origin.clone(), refname!("diff-test")), 31 | Branch::remote(origin.clone(), refname!("empty-branch")), 32 | Branch::remote(origin, refname!("master")), 33 | ] 34 | ); 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /archived/link-git/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "link-git" 3 | version = "0.1.0" 4 | authors = ["Kim Altintop "] 5 | edition = "2018" 6 | license = "GPL-3.0-or-later" 7 | 8 | description = "Core git types and functionality" 9 | 10 | [lib] 11 | doctest = false 12 | test = false 13 | 14 | [dependencies] 15 | arc-swap = "1.4.0" 16 | async-process = "1.1.0" 17 | async-trait = "0.1" 18 | blocking = "1.0.2" 19 | bstr = "0.2" 20 | futures-lite = "1.12.0" 21 | futures-util = "0.3.15" 22 | lazy_static = "1.4.0" 23 | im = "15.0.0" 24 | once_cell = "1.10" 25 | parking_lot = "0.12" 26 | pin-project = "1.0.7" 27 | regex = "1.5.4" 28 | rustc-hash = "1.1.0" 29 | tempfile = "3.4" 30 | thiserror = "1.0.30" 31 | tracing = "0.1" 32 | versions = "3.0.2" 33 | 34 | # gitoxide 35 | git-actor = "^0.6.0" 36 | git-hash = "^0.8.0" 37 | git-lock = "^1.0.1" 38 | git-object = "^0.15.1" 39 | git-odb = "^0.24.0" 40 | git-ref = "^0.9.0" 41 | git-traverse = "^0.10.0" 42 | 43 | [dependencies.git-features] 44 | version = "^0.17.0" 45 | features = ["progress", "parallel", "zlib-ng-compat"] 46 | 47 | [dependencies.git-pack] 48 | version = "^0.14.0" 49 | features = ["object-cache-dynamic", "pack-cache-lru-static", "pack-cache-lru-dynamic"] 50 | 51 | [dependencies.git-packetline] 52 | version = "^0.12.0" 53 | features = ["async-io"] 54 | 55 | [dependencies.git-protocol] 56 | version = "^0.12.0" 57 | features = ["async-client"] 58 | 59 | # compat 60 | [dependencies.git2] 61 | version = "0.16.1" 62 | default-features = false 63 | features = ["vendored-libgit2"] 64 | optional = true 65 | -------------------------------------------------------------------------------- /radicle-std-ext/src/result.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | pub trait ResultExt { 7 | /// Calls `f` if the result is [`Err`], **and** the predicate `pred` on the 8 | /// error value returns true. Otherwise returns the [`Ok`] value of 9 | /// `self`. Note that `f` may change the error type, so as long as the 10 | /// target type can be converted from the original one. 11 | /// 12 | /// # Examples 13 | /// 14 | /// ``` 15 | /// use std::io; 16 | /// use radicle_std_ext::result::ResultExt as _; 17 | /// 18 | /// let res = Err(io::Error::new(io::ErrorKind::Other, "crashbug")) 19 | /// .or_matches::(|e| matches!(e.kind(), io::ErrorKind::Other), || Ok(())) 20 | /// .unwrap(); 21 | /// 22 | /// assert_eq!((), res) 23 | /// ``` 24 | fn or_matches(self, pred: P, f: F) -> Result 25 | where 26 | E2: From, 27 | P: FnOnce(&E) -> bool, 28 | F: FnOnce() -> Result; 29 | } 30 | 31 | impl ResultExt for Result { 32 | fn or_matches(self, pred: P, f: F) -> Result 33 | where 34 | E2: From, 35 | P: FnOnce(&E) -> bool, 36 | F: FnOnce() -> Result, 37 | { 38 | self.or_else(|e| if pred(&e) { f() } else { Err(e.into()) }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /git-storage/src/refdb/iter.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | //! Iterator adaptors over [`git2::References`]. 5 | 6 | use std::fmt::Debug; 7 | 8 | use crate::glob; 9 | 10 | use super::{error, Reference}; 11 | 12 | /// Iterator for [`Reference`]s where the inner [`glob::Pattern`] supplied 13 | /// filters out any non-matching reference names. 14 | pub struct References<'a> { 15 | pub(crate) inner: ReferencesGlob<'a, glob::RefspecMatcher>, 16 | } 17 | 18 | impl<'a> Iterator for References<'a> { 19 | type Item = Result; 20 | 21 | fn next(&mut self) -> Option { 22 | self.inner.next() 23 | } 24 | } 25 | 26 | pub struct ReferencesGlob<'a, G: glob::Pattern + Debug> { 27 | pub(crate) iter: git2::References<'a>, 28 | pub(crate) glob: G, 29 | } 30 | 31 | impl<'a, G: glob::Pattern + Debug> Iterator for ReferencesGlob<'a, G> { 32 | type Item = Result; 33 | 34 | fn next(&mut self) -> Option { 35 | for reference in &mut self.iter { 36 | match reference { 37 | Ok(reference) => match reference.name() { 38 | Some(name) if self.glob.matches(name) => { 39 | return Some(Reference::try_from(reference).map_err(error::Iter::from)) 40 | } 41 | _ => continue, 42 | }, 43 | 44 | Err(e) => return Some(Err(e.into())), 45 | } 46 | } 47 | None 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /radicle-surf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "radicle-surf" 3 | description = "A code surfing library for Git repositories" 4 | readme = "README.md" 5 | version = "0.23.0" 6 | authors = ["The Radicle Team "] 7 | edition = "2021" 8 | homepage = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt/tree/radicle-surf/README.md" 9 | repository = "https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt" 10 | license = "GPL-3.0-or-later" 11 | 12 | include = [ 13 | "**/*.rs", 14 | "Cargo.toml", 15 | "data/git-platinum.tgz", 16 | ] 17 | 18 | [lib] 19 | test = false 20 | doctest = false 21 | 22 | [features] 23 | # NOTE: testing `test_submodule_failure` on GH actions 24 | # is painful since it uses this specific repo and expects 25 | # certain branches to be setup. So we use this feature flag 26 | # to ignore the test on CI. 27 | gh-actions = [] 28 | minicbor = ["radicle-git-ext/minicbor"] 29 | serde = ["dep:serde", "url/serde"] 30 | 31 | [dependencies] 32 | base64 = "0.21" 33 | log = "0.4" 34 | nonempty = "0.9" 35 | thiserror = "1.0" 36 | url = "2.5.4" 37 | 38 | [dependencies.git2] 39 | version = "0.19" 40 | default-features = false 41 | features = ["vendored-libgit2"] 42 | 43 | [dependencies.radicle-git-ext] 44 | version = "0.9.0" 45 | path = "../radicle-git-ext" 46 | features = ["serde"] 47 | 48 | [dependencies.radicle-std-ext] 49 | version = "0.1.1" 50 | path = "../radicle-std-ext" 51 | 52 | [dependencies.serde] 53 | version = "1" 54 | features = ["serde_derive"] 55 | optional = true 56 | 57 | [build-dependencies] 58 | anyhow = "1.0" 59 | flate2 = "1" 60 | tar = "0.4" 61 | -------------------------------------------------------------------------------- /archived/link-git/t/src/tests/protocol/take.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use futures::{executor::block_on, io::Cursor, AsyncReadExt as _}; 7 | use link_git::protocol::take::TryTake; 8 | use std::io; 9 | 10 | #[test] 11 | fn when_within_limit() { 12 | let input = b"the world is everything that is the case"; 13 | let output = block_on(async move { 14 | let mut buf = Vec::with_capacity(input.len()); 15 | TryTake::new(Cursor::new(input), input.len() as u64 + 1) 16 | .read_to_end(&mut buf) 17 | .await?; 18 | Ok::<_, io::Error>(buf) 19 | }) 20 | .unwrap(); 21 | 22 | assert_eq!(input, output.as_slice()) 23 | } 24 | 25 | #[test] 26 | fn when_limit_exceeded() { 27 | let input = b"what is the case, the fact, is the existence of atomic facts"; 28 | let output = 29 | block_on(TryTake::new(Cursor::new(input), 10).read_to_end(&mut Vec::new())).unwrap_err(); 30 | 31 | assert_eq!(output.to_string(), "max input size exceeded") 32 | } 33 | 34 | #[test] 35 | fn excess_bytes_remain() { 36 | let input = b"whereof one cannot speak, thereof one must be silent"; 37 | let output = block_on(async move { 38 | let mut buf = Vec::with_capacity(input.len()); 39 | let res = TryTake::new(Cursor::new(input), input.len() as u64) 40 | .read_to_end(&mut buf) 41 | .await; 42 | assert!(res.is_err()); 43 | buf 44 | }); 45 | 46 | assert_eq!(input, output.as_slice()) 47 | } 48 | -------------------------------------------------------------------------------- /git-storage/src/refdb/read.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use std::error::Error; 5 | 6 | use git_ext::{ 7 | ref_format::{refspec, RefStr}, 8 | Oid, 9 | }; 10 | 11 | use super::Reference; 12 | 13 | /// Read-only access to a git refdb. 14 | /// 15 | /// See [`crate::Read`] for an implementation of this trait. 16 | pub trait Read { 17 | /// The error type for finding a reference in the refdb. 18 | type FindRef: Error + Send + Sync + 'static; 19 | 20 | /// The error type for finding references in the refdb. 21 | type FindRefs: Error + Send + Sync + 'static; 22 | 23 | /// The error type for finding reference Oid in the refdb. 24 | type FindRefOid: Error + Send + Sync + 'static; 25 | 26 | /// Iterator for references returned by `find_references`. 27 | type References: Iterator>; 28 | 29 | /// Find the reference that corresponds to `name`. If the reference does not 30 | /// exist, then `None` is returned. 31 | fn find_reference(&self, name: Ref) -> Result, Self::FindRef> 32 | where 33 | Ref: AsRef; 34 | 35 | /// Find the references that match `pattern`. 36 | fn find_references(&self, pattern: Pat) -> Result 37 | where 38 | Pat: AsRef; 39 | 40 | /// Find the [`Oid`] for the reference that corresponds to `name`. If the 41 | /// reference does not exist, then `None` is returned. 42 | fn find_reference_oid(&self, name: Ref) -> Result, Self::FindRefOid> 43 | where 44 | Ref: AsRef; 45 | } 46 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | 3 | By making your contribution, you are making the declaration set out in the 4 | Linux Foundation’s Developer Certificate of Origin version 1.1 as set out 5 | below, in which the “open source licence indicated in the file” is GPLv3. 6 | 7 | Developer Certificate of Origin 8 | Version 1.1 9 | 10 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 11 | 12 | Everyone is permitted to copy and distribute verbatim copies of this 13 | license document, but changing it is not allowed. 14 | 15 | 16 | Developer's Certificate of Origin 1.1 17 | 18 | By making a contribution to this project, I certify that: 19 | 20 | (a) The contribution was created in whole or in part by me and I 21 | have the right to submit it under the open source license 22 | indicated in the file; or 23 | 24 | (b) The contribution is based upon previous work that, to the best 25 | of my knowledge, is covered under an appropriate open source 26 | license and I have the right under that license to submit that 27 | work with modifications, whether created in whole or in part 28 | by me, under the same open source license (unless I am 29 | permitted to submit under a different license), as indicated 30 | in the file; or 31 | 32 | (c) The contribution was provided directly to me by some other 33 | person who certified (a), (b) or (c) and I have not modified 34 | it. 35 | 36 | (d) I understand and agree that this project and the contribution 37 | are public and that a record of the contribution (including all 38 | personal information I submit with it, including my sign-off) is 39 | maintained indefinitely and may be redistributed consistent with 40 | this project or the open source license(s) involved. 41 | -------------------------------------------------------------------------------- /radicle-surf/t/src/reference.rs: -------------------------------------------------------------------------------- 1 | use radicle_git_ext::ref_format::refspec; 2 | use radicle_surf::{Glob, Repository}; 3 | 4 | use super::GIT_PLATINUM; 5 | 6 | #[test] 7 | fn test_branches() { 8 | let repo = Repository::open(GIT_PLATINUM).unwrap(); 9 | let heads = Glob::all_heads(); 10 | let branches = repo.branches(heads.clone()).unwrap(); 11 | for b in branches { 12 | println!("{}", b.unwrap().refname()); 13 | } 14 | let branches = repo 15 | .branches( 16 | heads 17 | .branches() 18 | .and(Glob::remotes(refspec::pattern!("banana/*"))), 19 | ) 20 | .unwrap(); 21 | for b in branches { 22 | println!("{}", b.unwrap().refname()); 23 | } 24 | } 25 | 26 | #[test] 27 | fn test_tag_snapshot() { 28 | let repo = Repository::open(GIT_PLATINUM).unwrap(); 29 | let tags = repo 30 | .tags(&Glob::all_tags()) 31 | .unwrap() 32 | .collect::, _>>() 33 | .unwrap(); 34 | assert_eq!(tags.len(), 6); 35 | let root_dir = repo.root_dir(&tags[0]).unwrap(); 36 | assert_eq!(root_dir.entries(&repo).unwrap().entries().count(), 1); 37 | } 38 | 39 | #[test] 40 | fn test_namespaces() { 41 | let repo = Repository::open(GIT_PLATINUM).unwrap(); 42 | 43 | let namespaces = repo.namespaces(&Glob::all_namespaces()).unwrap(); 44 | assert_eq!(namespaces.count(), 3); 45 | let namespaces = repo 46 | .namespaces(&Glob::namespaces(refspec::pattern!("golden/*"))) 47 | .unwrap(); 48 | assert_eq!(namespaces.count(), 2); 49 | let namespaces = repo 50 | .namespaces( 51 | &Glob::namespaces(refspec::pattern!("golden/*")).insert(refspec::pattern!("me/*")), 52 | ) 53 | .unwrap(); 54 | assert_eq!(namespaces.count(), 3); 55 | } 56 | -------------------------------------------------------------------------------- /git-storage/src/odb/read.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use super::*; 5 | 6 | /// Read-only access to a git refdb. 7 | /// 8 | /// See [`crate::Read`] for an implementation of this trait. 9 | pub trait Read { 10 | /// The error type for finding an object in the refdb. 11 | type FindObj: Error + Send + Sync + 'static; 12 | 13 | /// The error type for finding a blob in the refdb. 14 | type FindBlob: Error + Send + Sync + 'static; 15 | 16 | /// The error type for finding a commit in the refdb. 17 | type FindCommit: Error + Send + Sync + 'static; 18 | 19 | /// The error type for finding a tag in the refdb. 20 | type FindTag: Error + Send + Sync + 'static; 21 | 22 | /// The error type for finding a tree in the refdb. 23 | type FindTree: Error + Send + Sync + 'static; 24 | 25 | /// Find the [`Object`] corresponding to the given `oid`. 26 | /// 27 | /// Returns `None` if the [`Object`] did not exist. 28 | fn find_object(&self, oid: Oid) -> Result, Self::FindObj>; 29 | 30 | /// Find the [`Blob`] corresponding to the given `oid`. 31 | /// 32 | /// Returns `None` if the [`Blob`] did not exist. 33 | fn find_blob(&self, oid: Oid) -> Result, Self::FindBlob>; 34 | 35 | /// Find the [`Commit`] corresponding to the given `oid`. 36 | /// 37 | /// Returns `None` if the [`Commit`] did not exist. 38 | fn find_commit(&self, oid: Oid) -> Result, Self::FindCommit>; 39 | 40 | /// Find the [`Tag`] corresponding to the given `oid`. 41 | /// 42 | /// Returns `None` if the [`Tag`] did not exist. 43 | fn find_tag(&self, oid: Oid) -> Result, Self::FindTag>; 44 | 45 | /// Find the [`Object`] corresponding to the given `oid`. 46 | /// 47 | /// Returns `None` if the [`Tag`] did not exist. 48 | fn find_tree(&self, oid: Oid) -> Result, Self::FindTree>; 49 | } 50 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/git_ref_format/properties/pattern.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use std::convert::TryFrom; 5 | 6 | use proptest::prelude::*; 7 | use radicle_git_ext::ref_format::{refspec, Error}; 8 | use test_helpers::roundtrip; 9 | 10 | use crate::git_ref_format::gen; 11 | 12 | proptest! { 13 | #[test] 14 | fn valid(input in gen::with_glob()) { 15 | assert_eq!(input.as_str(), refspec::PatternStr::try_from_str(&input).unwrap().as_str()) 16 | } 17 | 18 | #[test] 19 | fn refname_is_pattern(input in gen::valid()) { 20 | assert_eq!(input.as_str(), refspec::PatternStr::try_from_str(&input).unwrap().as_str()) 21 | } 22 | 23 | #[test] 24 | fn no_more_than_one_star(input in gen::multi_glob()) { 25 | assert_matches!(refspec::PatternString::try_from(input), Err(Error::Pattern)) 26 | } 27 | 28 | #[test] 29 | fn invalid_refname_is_invalid_pattern(input in gen::invalid()) { 30 | assert_matches!(refspec::PatternString::try_from(input), Err(_)) 31 | } 32 | 33 | #[test] 34 | fn roundtrip_components(input in gen::with_glob()) { 35 | assert_eq!( 36 | input.as_str(), 37 | refspec::PatternStr::try_from_str(&input) 38 | .unwrap() 39 | .components() 40 | .collect::>() 41 | .unwrap() 42 | .as_str() 43 | ) 44 | } 45 | 46 | #[test] 47 | fn json(input in gen::with_glob()) { 48 | roundtrip::json(refspec::PatternString::try_from(input).unwrap()) 49 | } 50 | 51 | #[test] 52 | fn json_value(input in gen::with_glob()) { 53 | roundtrip::json_value(refspec::PatternString::try_from(input).unwrap()) 54 | } 55 | 56 | #[test] 57 | fn cbor(input in gen::with_glob()) { 58 | roundtrip::cbor(refspec::PatternString::try_from(input).unwrap()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # radicle-git 2 | 3 | The home for all Git utilities related to Radicle. While the majority 4 | of the utilities will attempt to be general, there may be more 5 | opinionated parts that are geared towards Radicle. 6 | 7 | ## Motivation 8 | 9 | The [git2] and [gitoxide] libraries provide low-level APIs for 10 | interacting with git repositories and the git protocol. This family of 11 | crates attempts to provide a higher-level wrapper around those to 12 | provide a more useful API for the Radicle protocol and UI clients 13 | building on top of the protocol. 14 | 15 | ## Overview 16 | 17 | The repository is defined as a series of crates: 18 | 19 | * `git-ext` -- provides higher-level types over common types found in `git2`. 20 | * `git-ref-format` -- provides a higher-level API for constructing git. 21 | reference paths. Prefer this over `git-ext`'s `RefLike` and `RefspecPattern`. 22 | * `git-trailers` -- provides a way to parse and construct git trailers. 23 | * `git-types` -- provides higher-level types, e.g. `Namespace`, 24 | `Reference`, `Remote`, etc. 25 | * `link-git` -- provides a higher-level API for git's `refdb`, `odb`, 26 | and the git protocol. 27 | * `macros` -- provides macros for the `git-ext` references types. 28 | * `std-ext` -- provides some utilities extending the standard library. 29 | * `test` -- a shim crate that refers depends on all other, individual 30 | test crates. 31 | 32 | ## Tests 33 | 34 | Please refer to [test/README.md][test] for understanding how our tests 35 | are organised. 36 | 37 | ## Contribute 38 | 39 | Please read [CONTRIBUTING.md][contrib] for a guide on contributing to 40 | this repository. 41 | 42 | ## Credits 43 | 44 | Thanks to the previous maintainers of the `radicle-link` repository, 45 | Kim, Alex, and Fintan, for providing the foundation to work upon -- as 46 | well as the [contributors][link-contributors]. 47 | 48 | [contrib]: ./CONTRIBUTING.md 49 | [git2]: https://github.com/rust-lang/git2-rs 50 | [gitoxide]: https://github.com/Byron/gitoxide 51 | [link-contributors]: https://github.com/radicle-dev/radicle-link/graphs/contributors 52 | [test]: ./test/README.md 53 | -------------------------------------------------------------------------------- /radicle-surf/src/error.rs: -------------------------------------------------------------------------------- 1 | // This file is part of radicle-surf 2 | // 3 | // 4 | // Copyright (C) 2019-2023 The Radicle Team 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License version 3 or 8 | // later as published by the Free Software Foundation. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | //! Definition for a crate level error type, which wraps up module level 19 | //! error types transparently. 20 | 21 | use crate::{commit, diff, fs, glob, namespace, refs, repo}; 22 | use thiserror::Error; 23 | 24 | /// The crate level error type that wraps up module level error types. 25 | #[derive(Debug, Error)] 26 | #[non_exhaustive] 27 | pub enum Error { 28 | #[error(transparent)] 29 | Branches(#[from] refs::error::Branch), 30 | #[error(transparent)] 31 | Categories(#[from] refs::error::Category), 32 | #[error(transparent)] 33 | Commit(#[from] commit::Error), 34 | #[error(transparent)] 35 | Diff(#[from] diff::git::error::Diff), 36 | #[error(transparent)] 37 | Directory(#[from] fs::error::Directory), 38 | #[error(transparent)] 39 | File(#[from] fs::error::File), 40 | #[error(transparent)] 41 | Git(#[from] git2::Error), 42 | #[error(transparent)] 43 | Glob(#[from] glob::Error), 44 | #[error(transparent)] 45 | Namespace(#[from] namespace::Error), 46 | #[error(transparent)] 47 | RefFormat(#[from] git_ext::ref_format::Error), 48 | #[error(transparent)] 49 | Revision(Box), 50 | #[error(transparent)] 51 | ToCommit(Box), 52 | #[error(transparent)] 53 | Tags(#[from] refs::error::Tag), 54 | #[error(transparent)] 55 | Repo(#[from] repo::error::Repo), 56 | } 57 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Test root crate 2 | 3 | Organisation of the test code for the `radicle-git` project crates deviates 4 | from the `cargo` conventions in order to work around some of the limitations of 5 | the current `cargo` / Rust testing infrastructure. 6 | 7 | Here is how: 8 | 9 | - Project crates are set to `test = false` by default, ie. no `#[cfg(test)]` / 10 | `#[test]` annotated tests are run. 11 | 12 | - Instead, project crates are tested via an accompanying `-test` crate 13 | located in a `t/` directory relative to the crate root. 14 | 15 | - This is similar to what `cargo` calls "integration tests", in that only the 16 | public API of the crate under test is available. Test crates are, however, 17 | meant to contain all kinds of tests. 18 | 19 | - Conventionally, tests are split into module hierarchies, mainly to support 20 | convenient filtering. 21 | 22 | `tests` 23 | : Unit tests. Example-based, preferably-pure. 24 | 25 | `properties` 26 | : Property tests. Randomized, preferably-pure. 27 | 28 | `integration` 29 | : Stateful tests, scenario-based. May have all kinds of effects. 30 | 31 | - Additionally, test crates may export helpers (such as mocks or fixtures) and 32 | `proptest` generators through `gen` and `helpers` modules. Test crates may 33 | depend on each other to make those types / functions available, possibly 34 | mirroring the dependency relationships of their respective "parent" crates. 35 | 36 | - `gen` and `helpers` modules are guarded behind a feature flag "test", ie. 37 | 38 | #[cfg(any(test, feature = "test"))] 39 | 40 | - Additional helpers can be found in the `test-helpers` (preferably-pure) and 41 | `it-helpers` (stateful) crates. 42 | 43 | - This crate (`test`) does not contain any code, but depends on all other test 44 | crates in the workspace (which are themselves not proper workspace members). 45 | This prevents unnecessary compilation of test crates if no test target is 46 | being built, but still makes each test crate available to be executed 47 | individually via the `-p` flag, eg. 48 | 49 | cargo test -p link-replication-test 50 | 51 | - It is recommended to use [`cargo-nextest`](https://nexte.st) instead of `cargo 52 | test` for maximising parallelism. 53 | -------------------------------------------------------------------------------- /archived/link-git/src/protocol/take.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::{ 7 | io, 8 | pin::Pin, 9 | task::{Context, Poll}, 10 | }; 11 | 12 | use futures_lite::io::{AsyncBufRead, AsyncRead}; 13 | 14 | /// Like [`futures_lite::io::Take`], but returns an error if and when the 15 | /// `limit` is exceeded. 16 | /// 17 | /// Note that, unlike [`futures_lite::io::Take`], if a single poll reads past 18 | /// the limit, the excess bytes are _not_ discarded. Instead, an error is 19 | /// returned on the next poll. 20 | pub struct TryTake { 21 | limit: u64, 22 | inner: R, 23 | } 24 | 25 | impl TryTake { 26 | pub fn new(inner: R, limit: u64) -> Self { 27 | Self { limit, inner } 28 | } 29 | } 30 | 31 | impl AsyncRead for TryTake 32 | where 33 | R: AsyncRead + Unpin, 34 | { 35 | fn poll_read( 36 | self: Pin<&mut Self>, 37 | cx: &mut Context, 38 | buf: &mut [u8], 39 | ) -> Poll> { 40 | if self.limit == 0 { 41 | return Poll::Ready(Err(io::Error::new( 42 | io::ErrorKind::Other, 43 | "max input size exceeded", 44 | ))); 45 | } 46 | 47 | let this = self.get_mut(); 48 | Pin::new(&mut this.inner).poll_read(cx, buf).map(|ready| { 49 | if let Ok(siz) = ready { 50 | this.limit = this.limit.saturating_sub(siz as u64); 51 | } 52 | 53 | ready 54 | }) 55 | } 56 | } 57 | 58 | impl AsyncBufRead for TryTake 59 | where 60 | R: AsyncBufRead + Unpin, 61 | { 62 | fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { 63 | if self.limit == 0 { 64 | return Poll::Ready(Err(io::Error::new( 65 | io::ErrorKind::Other, 66 | "max input size exceeded", 67 | ))); 68 | } 69 | 70 | Pin::new(&mut self.get_mut().inner).poll_fill_buf(cx) 71 | } 72 | 73 | fn consume(self: Pin<&mut Self>, amt: usize) { 74 | Pin::new(&mut self.get_mut().inner).consume(amt) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /archived/link-git/src/protocol/transport.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use bstr::BString; 7 | use futures_lite::io::{AsyncRead, AsyncWrite}; 8 | use git_protocol::transport::{ 9 | client::{ 10 | self, 11 | git::{ConnectMode, Connection}, 12 | SetServiceResponse, 13 | Transport, 14 | TransportWithoutIO, 15 | }, 16 | Protocol, 17 | Service, 18 | }; 19 | 20 | pub struct Stateless { 21 | inner: Connection, 22 | } 23 | 24 | impl Stateless 25 | where 26 | R: AsyncRead + Unpin, 27 | W: AsyncWrite + Unpin, 28 | { 29 | pub fn new(repo: BString, recv: R, send: W) -> Self { 30 | let url = format!("rad://{repo}"); 31 | let inner = Connection::new( 32 | recv, 33 | send, 34 | Protocol::V2, 35 | repo, 36 | None::<(String, Option)>, 37 | ConnectMode::Daemon, 38 | ) 39 | .custom_url(Some(url)); 40 | 41 | Self { inner } 42 | } 43 | } 44 | 45 | impl TransportWithoutIO for Stateless 46 | where 47 | R: AsyncRead + Unpin, 48 | W: AsyncWrite + Unpin, 49 | { 50 | fn request( 51 | &mut self, 52 | write_mode: client::WriteMode, 53 | on_into_read: client::MessageKind, 54 | ) -> Result, client::Error> { 55 | self.inner.request(write_mode, on_into_read) 56 | } 57 | 58 | fn to_url(&self) -> String { 59 | self.inner.to_url() 60 | } 61 | 62 | fn supported_protocol_versions(&self) -> &[Protocol] { 63 | &[Protocol::V2] 64 | } 65 | 66 | fn connection_persists_across_multiple_requests(&self) -> bool { 67 | false 68 | } 69 | } 70 | 71 | #[async_trait(?Send)] 72 | impl Transport for Stateless 73 | where 74 | R: AsyncRead + Unpin, 75 | W: AsyncWrite + Unpin, 76 | { 77 | async fn handshake<'a>( 78 | &mut self, 79 | service: Service, 80 | extra_parameters: &'a [(&'a str, Option<&'a str>)], 81 | ) -> Result, client::Error> { 82 | self.inner.handshake(service, extra_parameters).await 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /archived/git-commit/src/headers.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | const BEGIN_SSH: &str = "-----BEGIN SSH SIGNATURE-----\n"; 4 | const BEGIN_PGP: &str = "-----BEGIN PGP SIGNATURE-----\n"; 5 | 6 | /// A collection of headers stored in a [`super::Commit`]. 7 | /// 8 | /// Note: these do not include `tree`, `parent`, `author`, and `committer`. 9 | #[derive(Clone, Debug, Default)] 10 | pub struct Headers(pub(super) Vec<(String, String)>); 11 | 12 | /// A `gpgsig` signature stored in a [`super::Commit`]. 13 | pub enum Signature<'a> { 14 | /// A PGP signature, i.e. starts with `-----BEGIN PGP SIGNATURE-----`. 15 | Pgp(Cow<'a, str>), 16 | /// A SSH signature, i.e. starts with `-----BEGIN SSH SIGNATURE-----`. 17 | Ssh(Cow<'a, str>), 18 | } 19 | 20 | impl<'a> Signature<'a> { 21 | fn from_str(s: &'a str) -> Result { 22 | if s.starts_with(BEGIN_SSH) { 23 | Ok(Signature::Ssh(Cow::Borrowed(s))) 24 | } else if s.starts_with(BEGIN_PGP) { 25 | Ok(Signature::Pgp(Cow::Borrowed(s))) 26 | } else { 27 | Err(UnknownScheme) 28 | } 29 | } 30 | } 31 | 32 | pub struct UnknownScheme; 33 | 34 | impl<'a> ToString for Signature<'a> { 35 | fn to_string(&self) -> String { 36 | match self { 37 | Signature::Pgp(pgp) => pgp.to_string(), 38 | Signature::Ssh(ssh) => ssh.to_string(), 39 | } 40 | } 41 | } 42 | 43 | impl Headers { 44 | pub fn new() -> Self { 45 | Headers(Vec::new()) 46 | } 47 | 48 | pub fn iter(&self) -> impl Iterator { 49 | self.0.iter().map(|(k, v)| (k.as_str(), v.as_str())) 50 | } 51 | 52 | pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator + '_ { 53 | self.iter() 54 | .filter_map(move |(k, v)| (k == name).then_some(v)) 55 | } 56 | 57 | pub fn signatures(&self) -> impl Iterator + '_ { 58 | self.0.iter().filter_map(|(k, v)| { 59 | if k == "gpgsig" { 60 | Signature::from_str(v).ok() 61 | } else { 62 | None 63 | } 64 | }) 65 | } 66 | 67 | /// Push a header to the end of the headers section. 68 | pub fn push(&mut self, name: &str, value: &str) { 69 | self.0.push((name.to_owned(), value.trim().to_owned())); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /git-storage/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | //! # `git-storage` 5 | //! 6 | //! This crate provides access to git [references][refs] and [objects][objs]. 7 | //! 8 | //! To first initialise the storage use [`Write::open`]. 9 | //! 10 | //! After the storage is initialised, use [`Write::open`] or [`Read::open`] for 11 | //! read-write or read-only access to the underlying storage. These structs will 12 | //! implement the traits below depending on their access levels. 13 | //! 14 | //! ## Read-only access 15 | //! 16 | //! * [`refdb::Read`] 17 | //! * [`odb::Read`] 18 | //! 19 | //! ## Read-write access 20 | //! 21 | //! * [`refdb::Read`] 22 | //! * [`refdb::Write`] 23 | //! * [`odb::Read`] 24 | //! * [`odb::Write`] 25 | //! 26 | //! ## Concurrency 27 | //! 28 | //! 29 | //! [`Read`] and [`Write`] can be sent between threads, but it can't be shared 30 | //! between threads. _Some_ operations are safe to perform concurrently in much 31 | //! the same way two `git` processes can access the same repository. 32 | //! 33 | //! [refs]: https://git-scm.com/book/en/v2/Git-Internals-Git-References 34 | //! [objs]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects 35 | 36 | extern crate radicle_git_ext as git_ext; 37 | extern crate radicle_std_ext as std_ext; 38 | 39 | pub mod glob; 40 | 41 | pub mod refdb; 42 | pub use refdb::{Applied, Reference, SymrefTarget, Target, Update, Updated}; 43 | 44 | pub mod odb; 45 | pub use odb::{Blob, Commit, Object, Tag, Tree}; 46 | 47 | mod backend; 48 | pub use backend::{ 49 | read::{self, Read}, 50 | write::{self, Write}, 51 | }; 52 | 53 | pub mod signature; 54 | 55 | /// Initialise the git backend. 56 | /// 57 | /// **SHOULD** be called before all accesses to git functionality. 58 | pub fn init() { 59 | use libc::c_int; 60 | use libgit2_sys as raw_git; 61 | use std::sync::Once; 62 | 63 | static INIT: Once = Once::new(); 64 | 65 | unsafe { 66 | INIT.call_once(|| { 67 | let ret = 68 | raw_git::git_libgit2_opts(raw_git::GIT_OPT_SET_MWINDOW_FILE_LIMIT as c_int, 256); 69 | if ret < 0 { 70 | panic!( 71 | "error setting libgit2 option: {}", 72 | git2::Error::last_error(ret).unwrap() 73 | ) 74 | } 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /archived/link-git/src/odb/index/metrics.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | pub struct StatsView { 9 | /// Total number of times a lookup was successful. 10 | pub hits: usize, 11 | /// Total number of times a lookup was unsuccessful. 12 | pub misses: usize, 13 | /// Total number of times an index was added explicitly via 14 | /// [`super::Shared::push`]. 15 | pub pushes: usize, 16 | /// Total number of reloads via [`super::Shared::reload`]. 17 | pub reloads: usize, 18 | /// Number of [`crate::odb::pack::Index`]es currently held. 19 | pub indices: usize, 20 | } 21 | 22 | #[derive(Default)] 23 | pub struct Stats { 24 | hits: AtomicUsize, 25 | misses: AtomicUsize, 26 | pushes: AtomicUsize, 27 | reloads: AtomicUsize, 28 | } 29 | 30 | pub trait Metrics { 31 | type Snapshot; 32 | 33 | fn record_hit(&self); 34 | fn record_miss(&self); 35 | fn record_push(&self); 36 | fn record_reload(&self); 37 | 38 | fn snapshot(&self, indices: usize) -> Self::Snapshot; 39 | } 40 | 41 | impl Metrics for Stats { 42 | type Snapshot = StatsView; 43 | 44 | fn record_hit(&self) { 45 | self.hits.fetch_add(1, Ordering::Relaxed); 46 | } 47 | 48 | fn record_miss(&self) { 49 | self.misses.fetch_add(1, Ordering::Relaxed); 50 | } 51 | 52 | fn record_push(&self) { 53 | self.pushes.fetch_add(1, Ordering::Relaxed); 54 | } 55 | 56 | fn record_reload(&self) { 57 | self.reloads.fetch_add(1, Ordering::Relaxed); 58 | } 59 | 60 | fn snapshot(&self, indices: usize) -> Self::Snapshot { 61 | StatsView { 62 | hits: self.hits.load(Ordering::Relaxed), 63 | misses: self.misses.load(Ordering::Relaxed), 64 | pushes: self.pushes.load(Ordering::Relaxed), 65 | reloads: self.reloads.load(Ordering::Relaxed), 66 | indices, 67 | } 68 | } 69 | } 70 | 71 | impl Metrics for () { 72 | type Snapshot = (); 73 | 74 | fn record_hit(&self) {} 75 | fn record_miss(&self) {} 76 | fn record_push(&self) {} 77 | fn record_reload(&self) {} 78 | 79 | fn snapshot(&self, _: usize) -> Self::Snapshot {} 80 | } 81 | -------------------------------------------------------------------------------- /git-storage/src/odb/write.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use std::error::Error; 5 | 6 | use git_ext::{ref_format::RefStr, Oid}; 7 | 8 | use super::{Commit, Object, Read, Tree, TreeBuilder}; 9 | 10 | /// Read-write access to a git odb. 11 | /// 12 | /// See [`crate::Write`] for an implementation of this trait. 13 | pub trait Write: Read { 14 | /// The error type for writing a blob to the odb. 15 | type WriteBlob: Error + Send + Sync + 'static; 16 | 17 | /// The error type for writing a commit to the odb. 18 | type WriteCommit: Error + Send + Sync + 'static; 19 | 20 | /// The error type for writing a tag to the odb. 21 | type WriteTag: Error + Send + Sync + 'static; 22 | 23 | /// The error type for writing a tree to the odb. 24 | type WriteTree: Error + Send + Sync + 'static; 25 | 26 | /// Write a [`super::Blob`] containing the `data` provided. 27 | fn write_blob(&self, data: &[u8]) -> Result; 28 | 29 | /// Write a [`Commit`] that points to the given `tree` and has the provided 30 | /// `parents`. 31 | /// 32 | /// The signature of the [`Commit`] is expected to be provided by the 33 | /// implementor of the trait. 34 | /// 35 | /// The commit will not be associated with any reference. If this is 36 | /// required then you can use the [`Oid`] as the target for a 37 | /// [`crate::refdb::Update`]. 38 | fn write_commit( 39 | &self, 40 | tree: &Tree, 41 | parents: &[&Commit<'_>], 42 | message: &str, 43 | ) -> Result; 44 | 45 | /// Write a [`super::Tag`] that points to the given `target`. 46 | /// 47 | /// The signature of the [`super::Tag`] is expected to be provided by the 48 | /// implementor of the trait. 49 | /// 50 | /// No reference is created, however, the `name` is used for naming the 51 | /// [`super::Tag`] object. 52 | /// 53 | /// If a reference is required then you can use the [`Oid`] as the target 54 | /// for a [`crate::refdb::Update`]. 55 | fn write_tag(&self, name: R, target: &Object, message: &str) -> Result 56 | where 57 | R: AsRef; 58 | 59 | /// Write a [`super::Tree`] using the provided `builder`. 60 | fn write_tree(&self, builder: TreeBuilder) -> Result; 61 | } 62 | -------------------------------------------------------------------------------- /radicle-surf/examples/browsing.rs: -------------------------------------------------------------------------------- 1 | // This file is part of radicle-surf 2 | // 3 | // 4 | // Copyright (C) 2019-2020 The Radicle Team 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License version 3 or 8 | // later as published by the Free Software Foundation. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | //! An example of browsing a git repo using `radicle-surf`. 19 | //! 20 | //! How to run: 21 | //! 22 | //! cargo run --example browsing 23 | //! 24 | //! This program browses the given repo and prints out the files and 25 | //! the directories in a tree-like structure. 26 | 27 | use radicle_surf::{ 28 | fs::{self, Directory}, 29 | Repository, 30 | }; 31 | use std::{env, time::Instant}; 32 | 33 | fn main() { 34 | let repo_path = match env::args().nth(1) { 35 | Some(path) => path, 36 | None => { 37 | print_usage(); 38 | return; 39 | } 40 | }; 41 | let repo = Repository::discover(repo_path).unwrap(); 42 | let now = Instant::now(); 43 | let head = repo.head().unwrap(); 44 | let root = repo.root_dir(head).unwrap(); 45 | print_directory(&root, &repo, 0); 46 | 47 | let elapsed_millis = now.elapsed().as_millis(); 48 | println!("browse with print: {elapsed_millis} ms"); 49 | } 50 | 51 | fn print_directory(d: &Directory, repo: &Repository, indent_level: usize) { 52 | let indent = " ".repeat(indent_level * 4); 53 | println!("{}{}/", &indent, d.name()); 54 | for entry in d.entries(repo).unwrap() { 55 | match entry { 56 | fs::Entry::File(f) => println!(" {}{}", &indent, f.name()), 57 | fs::Entry::Directory(d) => print_directory(&d, repo, indent_level + 1), 58 | fs::Entry::Submodule(s) => println!(" {}{}", &indent, s.name()), 59 | } 60 | } 61 | } 62 | 63 | fn print_usage() { 64 | println!("Usage:"); 65 | println!("cargo run --example browsing "); 66 | } 67 | -------------------------------------------------------------------------------- /radicle-git-ext/src/commit/headers.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::borrow::Cow; 3 | 4 | const BEGIN_SSH: &str = "-----BEGIN SSH SIGNATURE-----\n"; 5 | const BEGIN_PGP: &str = "-----BEGIN PGP SIGNATURE-----\n"; 6 | 7 | /// A collection of headers stored in a [`crate::commit::Commit`]. 8 | /// 9 | /// Note: these do not include `tree`, `parent`, `author`, and `committer`. 10 | #[derive(Clone, Debug, Default)] 11 | pub struct Headers(pub(super) Vec<(String, String)>); 12 | 13 | /// A `gpgsig` signature stored in a [`crate::commit::Commit`]. 14 | #[derive(Debug)] 15 | pub enum Signature<'a> { 16 | /// A PGP signature, i.e. starts with `-----BEGIN PGP SIGNATURE-----`. 17 | Pgp(Cow<'a, str>), 18 | /// A SSH signature, i.e. starts with `-----BEGIN SSH SIGNATURE-----`. 19 | Ssh(Cow<'a, str>), 20 | } 21 | 22 | impl<'a> Signature<'a> { 23 | fn from_str(s: &'a str) -> Result { 24 | if s.starts_with(BEGIN_SSH) { 25 | Ok(Signature::Ssh(Cow::Borrowed(s))) 26 | } else if s.starts_with(BEGIN_PGP) { 27 | Ok(Signature::Pgp(Cow::Borrowed(s))) 28 | } else { 29 | Err(UnknownScheme) 30 | } 31 | } 32 | } 33 | 34 | impl fmt::Display for Signature<'_> { 35 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 36 | match self { 37 | Signature::Pgp(pgp) => f.write_str(pgp.as_ref()), 38 | Signature::Ssh(ssh) => f.write_str(ssh.as_ref()), 39 | } 40 | } 41 | } 42 | 43 | pub struct UnknownScheme; 44 | 45 | impl Headers { 46 | pub fn new() -> Self { 47 | Headers(Vec::new()) 48 | } 49 | 50 | pub fn iter(&self) -> impl Iterator { 51 | self.0.iter().map(|(k, v)| (k.as_str(), v.as_str())) 52 | } 53 | 54 | pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator + 'a { 55 | self.iter() 56 | .filter_map(move |(k, v)| (k == name).then_some(v)) 57 | } 58 | 59 | pub fn signatures(&self) -> impl Iterator + '_ { 60 | self.0.iter().filter_map(|(k, v)| { 61 | if k == "gpgsig" { 62 | Signature::from_str(v).ok() 63 | } else { 64 | None 65 | } 66 | }) 67 | } 68 | 69 | /// Push a header to the end of the headers section. 70 | pub fn push(&mut self, name: &str, value: &str) { 71 | self.0.push((name.to_owned(), value.trim().to_owned())); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/test-helpers/src/logging.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::env; 7 | 8 | use log::{log_enabled, Level}; 9 | use tracing::subscriber::set_global_default as set_subscriber; 10 | use tracing_subscriber::{EnvFilter, FmtSubscriber}; 11 | 12 | /// Initialise logging / tracing 13 | /// 14 | /// Note that this will capture logs, so they can be output as part of the test 15 | /// output. Use `RUST_LOG` with care, as this may create unwanted memory 16 | /// pressure. Note, however, that if `RUST_LOG` is not set, we set the level to 17 | /// `error` by default in order to surface errors on CI. 18 | /// 19 | /// The `TRACING_FMT` environment variable can be used to control the log 20 | /// formatting. Supported values: 21 | /// 22 | /// * "pretty": [`tracing_subscriber::fmt::format::Pretty`] 23 | /// * "compact": [`tracing_subscriber::fmt::format::Compact`] 24 | /// * "json": [`tracing_subscriber::fmt::format::Json`] 25 | /// 26 | /// If the variable is not set, or set to any other value, the 27 | /// [`tracing_subscriber::fmt::format::Full`] format is used. 28 | pub fn init() { 29 | if env_logger::builder().is_test(true).try_init().is_ok() { 30 | if env::var("RUST_LOG").is_err() { 31 | env::set_var("RUST_LOG", "debug"); 32 | } 33 | 34 | let mut builder = FmtSubscriber::builder() 35 | .with_env_filter(EnvFilter::from_default_env()) 36 | .with_test_writer(); 37 | if log_enabled!(target: "librad", Level::Trace) { 38 | builder = builder.with_thread_ids(true); 39 | } else if env::var("TRACING_FMT").is_err() { 40 | let default_format = if env::var("CI").is_ok() { 41 | "compact" 42 | } else { 43 | "pretty" 44 | }; 45 | env::set_var("TRACING_FMT", default_format); 46 | } 47 | 48 | match env::var("TRACING_FMT").ok().as_deref() { 49 | Some("pretty") => set_subscriber(builder.pretty().finish()), 50 | Some("compact") => set_subscriber(builder.compact().finish()), 51 | Some("json") => set_subscriber(builder.json().flatten_event(true).finish()), 52 | _ => set_subscriber(builder.finish()), 53 | } 54 | .expect("setting tracing subscriber failed") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /radicle-surf/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This file is part of radicle-git 2 | // 3 | // 4 | // Copyright (C) 2019-2023 The Radicle Team 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License version 3 or 8 | // later as published by the Free Software Foundation. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | //! `radicle-surf` is a library to help users explore a Git repository with 19 | //! ease. It supports browsing a repository via the concept of files and 20 | //! directories, or via blobs and trees in a git fashion. With the additional 21 | //! support of [`diff::Diff`] and [`History`], this library can be used to build 22 | //! an intuitive UI for any Git repository. 23 | //! 24 | //! The main entry point of the library API is [`Repository`]. 25 | //! 26 | //! Let's start surfing! 27 | //! 28 | //! ## Serialization with feature `serde` 29 | //! 30 | //! Many types in this crate support serialization using [`Serde`][serde] 31 | //! through the `serde` feature flag for this crate. 32 | //! 33 | //! [serde]: https://crates.io/crates/serde 34 | 35 | extern crate radicle_git_ext as git_ext; 36 | 37 | /// Re-exports. 38 | pub use radicle_git_ext::ref_format; 39 | 40 | /// Represents an object id in Git. Re-exported from `radicle-git-ext`. 41 | pub type Oid = radicle_git_ext::Oid; 42 | 43 | pub mod blob; 44 | pub mod diff; 45 | pub mod fs; 46 | pub mod tree; 47 | 48 | /// Private modules with their public types. 49 | mod repo; 50 | pub use repo::Repository; 51 | 52 | mod glob; 53 | pub use glob::Glob; 54 | 55 | mod history; 56 | pub use history::History; 57 | 58 | mod branch; 59 | pub use branch::{Branch, Local, Remote}; 60 | 61 | mod tag; 62 | pub use tag::Tag; 63 | 64 | mod commit; 65 | pub use commit::{Author, Commit, Time}; 66 | 67 | mod namespace; 68 | pub use namespace::Namespace; 69 | 70 | mod stats; 71 | pub use stats::Stats; 72 | 73 | mod revision; 74 | pub use revision::{Revision, Signature, ToCommit}; 75 | 76 | mod refs; 77 | 78 | mod error; 79 | pub use error::Error; 80 | -------------------------------------------------------------------------------- /archived/link-git/src/odb/window/metrics.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | use tracing::trace; 9 | 10 | pub struct StatsView { 11 | /// Total number of times the requested data was found in the cache. 12 | pub cache_hits: usize, 13 | /// Total number of times the requested data was not found in the cache. 14 | /// 15 | /// Note that a cache hit can occur after a miss if another thread was 16 | /// faster to fill in the missing entry. Thus, `cache_hits + cache_misses` 17 | /// does not necessarily sum up to the number of cache accesses. 18 | pub cache_misses: usize, 19 | /// Total number of times a pack file was attempted to be loaded from disk 20 | /// (incl. failed attempts). 21 | pub file_loads: usize, 22 | /// Total number of pack files the cache holds on to. 23 | pub open_files: usize, 24 | } 25 | 26 | #[derive(Default)] 27 | pub struct Stats { 28 | hits: AtomicUsize, 29 | miss: AtomicUsize, 30 | load: AtomicUsize, 31 | } 32 | 33 | pub trait Metrics { 34 | type Snapshot; 35 | 36 | fn record_hit(&self); 37 | fn record_miss(&self); 38 | fn record_load(&self); 39 | 40 | fn snapshot(&self, open_files: usize) -> Self::Snapshot; 41 | } 42 | 43 | impl Metrics for Stats { 44 | type Snapshot = StatsView; 45 | 46 | fn record_hit(&self) { 47 | trace!("cache hit"); 48 | self.hits.fetch_add(1, Ordering::Relaxed); 49 | } 50 | 51 | fn record_miss(&self) { 52 | trace!("cache miss"); 53 | self.miss.fetch_add(1, Ordering::Relaxed); 54 | } 55 | 56 | fn record_load(&self) { 57 | trace!("pack load"); 58 | self.load.fetch_add(1, Ordering::Relaxed); 59 | } 60 | 61 | fn snapshot(&self, open_files: usize) -> Self::Snapshot { 62 | StatsView { 63 | cache_hits: self.hits.load(Ordering::Relaxed), 64 | cache_misses: self.miss.load(Ordering::Relaxed), 65 | file_loads: self.load.load(Ordering::Relaxed), 66 | open_files, 67 | } 68 | } 69 | } 70 | 71 | impl Metrics for () { 72 | type Snapshot = (); 73 | 74 | fn record_hit(&self) {} 75 | fn record_miss(&self) {} 76 | fn record_load(&self) {} 77 | 78 | fn snapshot(&self, _: usize) -> Self::Snapshot {} 79 | } 80 | -------------------------------------------------------------------------------- /git-storage/src/backend/write/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use thiserror::Error; 7 | 8 | use super::*; 9 | 10 | #[derive(Debug, Error)] 11 | pub enum Init { 12 | #[error(transparent)] 13 | Git(#[from] git2::Error), 14 | } 15 | 16 | #[derive(Debug, Error)] 17 | pub enum Transaction { 18 | #[error("error determining if {old} is an ancestor of {new} in within {name}")] 19 | Ancestry { 20 | name: RefString, 21 | new: Oid, 22 | old: Oid, 23 | #[source] 24 | source: git2::Error, 25 | }, 26 | #[error("error committing update for storage")] 27 | Commit { 28 | #[source] 29 | source: git2::Error, 30 | }, 31 | #[error("error locking reference '{reference}' when attempting to update storage")] 32 | Lock { 33 | reference: RefString, 34 | #[source] 35 | source: git2::Error, 36 | }, 37 | #[error("error obtaining signature for '{name}' '{email}'")] 38 | Signature { 39 | name: String, 40 | email: String, 41 | #[source] 42 | source: git2::Error, 43 | }, 44 | #[error("error setting the direct reference '{reference}' to the target '{target}'")] 45 | SetDirect { 46 | reference: RefString, 47 | target: Oid, 48 | #[source] 49 | source: git2::Error, 50 | }, 51 | #[error("error setting the symbolic reference '{reference}' to the target '{target}'")] 52 | SetSymbolic { 53 | reference: RefString, 54 | target: RefString, 55 | #[source] 56 | source: git2::Error, 57 | }, 58 | #[error("error removing the reference '{reference}'")] 59 | Remove { 60 | reference: RefString, 61 | #[source] 62 | source: git2::Error, 63 | }, 64 | } 65 | 66 | #[derive(Debug, Error)] 67 | pub enum Update { 68 | #[error("non-fast-forward update of {name} (current: {cur}, new: {new})")] 69 | NonFF { name: RefString, new: Oid, cur: Oid }, 70 | #[error(transparent)] 71 | FindRef(#[from] read::error::FindRef), 72 | #[error(transparent)] 73 | Git(#[from] git2::Error), 74 | #[error("the symref target {0} is itself a symref")] 75 | TargetSymbolic(RefString), 76 | #[error(transparent)] 77 | Transaction(#[from] Transaction), 78 | #[error("rejected changing type of reference '{0}' from symbolic to direct")] 79 | TypeChange(RefString), 80 | } 81 | -------------------------------------------------------------------------------- /radicle-surf/build.rs: -------------------------------------------------------------------------------- 1 | // This file is part of radicle-surf 2 | // 3 | // 4 | // Copyright (C) 2019-2020 The Radicle Team 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License version 3 or 8 | // later as published by the Free Software Foundation. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | use std::{ 19 | env, fs, 20 | fs::File, 21 | io, 22 | path::{Path, PathBuf}, 23 | }; 24 | 25 | use anyhow::Context as _; 26 | use flate2::read::GzDecoder; 27 | use tar::Archive; 28 | 29 | enum Command { 30 | Build(PathBuf), 31 | Publish(PathBuf), 32 | } 33 | 34 | impl Command { 35 | fn new() -> io::Result { 36 | let current = env::current_dir()?; 37 | Ok(if current.ends_with("radicle-surf") { 38 | Self::Build(current) 39 | } else { 40 | Self::Publish(PathBuf::from( 41 | env::var("OUT_DIR").map_err(|err| io::Error::new(io::ErrorKind::Other, err))?, 42 | )) 43 | }) 44 | } 45 | 46 | fn target(&self) -> PathBuf { 47 | match self { 48 | Self::Build(path) => path.join("data"), 49 | Self::Publish(path) => path.join("data"), 50 | } 51 | } 52 | } 53 | 54 | fn main() { 55 | let target = Command::new() 56 | .expect("could not determine the cargo command") 57 | .target(); 58 | let git_platinum_tarball = "./data/git-platinum.tgz"; 59 | 60 | unpack(git_platinum_tarball, target).expect("Failed to unpack git-platinum"); 61 | 62 | println!("cargo:rerun-if-changed={git_platinum_tarball}"); 63 | } 64 | 65 | fn unpack(archive_path: impl AsRef, target: impl AsRef) -> anyhow::Result<()> { 66 | let content = target.as_ref().join("git-platinum"); 67 | if content.exists() { 68 | fs::remove_dir_all(content).context("attempting to remove git-platinum")?; 69 | } 70 | let archive_path = archive_path.as_ref(); 71 | let tar_gz = File::open(archive_path).context(format!( 72 | "attempting to open file: {}", 73 | archive_path.display() 74 | ))?; 75 | let tar = GzDecoder::new(tar_gz); 76 | let mut archive = Archive::new(tar); 77 | archive.unpack(target).context("attempting to unpack")?; 78 | 79 | Ok(()) 80 | } 81 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/git_ref_format/gen.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use proptest::prelude::*; 5 | 6 | /// Any unicode "word" is trivially a valid refname. 7 | pub fn trivial() -> impl Strategy { 8 | "\\w+" 9 | } 10 | 11 | pub fn valid() -> impl Strategy { 12 | prop::collection::vec(trivial(), 1..20).prop_map(|xs| xs.join("/")) 13 | } 14 | 15 | pub fn invalid_char() -> impl Strategy { 16 | prop_oneof![ 17 | Just('\0'), 18 | Just('\\'), 19 | Just('~'), 20 | Just('^'), 21 | Just(':'), 22 | Just('?'), 23 | Just('[') 24 | ] 25 | } 26 | 27 | pub fn with_invalid_char() -> impl Strategy { 28 | ("\\w*", invalid_char(), "\\w*").prop_map(|(mut pre, invalid, suf)| { 29 | pre.push(invalid); 30 | pre.push_str(&suf); 31 | pre 32 | }) 33 | } 34 | 35 | pub fn ends_with_dot_lock() -> impl Strategy { 36 | "\\w*\\.lock" 37 | } 38 | 39 | pub fn with_double_dot() -> impl Strategy { 40 | "\\w*\\.\\.\\w*" 41 | } 42 | 43 | pub fn starts_with_dot() -> impl Strategy { 44 | "\\.\\w*" 45 | } 46 | 47 | pub fn ends_with_dot() -> impl Strategy { 48 | "\\w+\\." 49 | } 50 | 51 | pub fn with_control_char() -> impl Strategy { 52 | "\\w*[\x01-\x1F\x7F]+\\w*" 53 | } 54 | 55 | pub fn with_space() -> impl Strategy { 56 | "\\w* +\\w*" 57 | } 58 | 59 | pub fn with_consecutive_slashes() -> impl Strategy { 60 | "\\w*//\\w*" 61 | } 62 | 63 | pub fn with_glob() -> impl Strategy { 64 | "\\w*\\*\\w*" 65 | } 66 | 67 | pub fn multi_glob() -> impl Strategy { 68 | ( 69 | prop::collection::vec(with_glob(), 2..5), 70 | prop::collection::vec(trivial(), 0..5), 71 | ) 72 | .prop_map(|(mut globs, mut valids)| { 73 | globs.append(&mut valids); 74 | globs 75 | }) 76 | .prop_shuffle() 77 | .prop_map(|xs| xs.join("/")) 78 | } 79 | 80 | pub fn invalid() -> impl Strategy { 81 | fn path(s: impl Strategy) -> impl Strategy { 82 | prop::collection::vec(s, 1..20).prop_map(|xs| xs.join("/")) 83 | } 84 | 85 | prop_oneof![ 86 | Just(String::from("")), 87 | Just(String::from("@")), 88 | path(with_invalid_char()), 89 | path(ends_with_dot_lock()), 90 | path(with_double_dot()), 91 | path(starts_with_dot()), 92 | path(ends_with_dot()), 93 | path(with_control_char()), 94 | path(with_space()), 95 | path(with_consecutive_slashes()), 96 | path(trivial()).prop_map(|mut p| { 97 | p.push('/'); 98 | p 99 | }), 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /radicle-surf/DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | 2 | # Radicle Surfing 🏄 3 | 4 | Thanks for wanting to contribute to `radicle-surf`! 5 | 6 | # Licensing 7 | 8 | We are [GPL-3.0-or-later](./LICENSE) licensed project. To keep in compliance with this we must 9 | add a [license header](./.license-header) to any new files added. This is checked on each run of CI. 10 | 11 | ## Building & Testing 🏗️ 12 | 13 | We try to make development as seemless as possible so we can get down to the real work. We supply 14 | the toolchain via the `rust-toolchain` file, and the formatting rules `.rustmt.toml` file. 15 | 16 | For the [Nix](https://nixos.org/) inclined there is a `default.nix` file to get all the necessary 17 | dependencies and it also uses the `rust-toolchain` file to pin to that version of Rust. 18 | 19 | You can build the project the usual way: 20 | ``` 21 | cargo build 22 | ``` 23 | 24 | To run all the tests: 25 | ``` 26 | cargo test 27 | ``` 28 | 29 | For the full list of checks that get executed in CI you can checkout the [ci/run](./ci/run) script. 30 | 31 | If any of this _isn't_ working, then let's work through it together and get it Working on Your 32 | Machine™. 33 | 34 | ## Structure 🏛️ 35 | 36 | The design of `radicle-surf` is to have an in-memory representation of a project's directory which 37 | can be generated by a VCS's backend. The directory system is modeled under `file_system`, the VCS 38 | functionality is naturally under `vcs`, and `diff` logic is held under `diff`. 39 | 40 | ``` 41 | src/ 42 | ├── diff 43 | ├── file_system 44 | └── vcs 45 | ``` 46 | 47 | ## Testing & Documentation 📚 48 | 49 | We ensure that the crate is well documented. `cargo clippy` will argue with you anytime a public 50 | facing piece of the library is undocumented. We should always provide an explanation of what 51 | something is or does, and also provide examples to allow our users to get up and running as quick 52 | and easy as possible. 53 | 54 | When writing documentation we should try provide one or two examples (if they make sense). This 55 | provides us with some simple unit tests as well as something our users can copy and paste for ease 56 | of development. 57 | 58 | If more tests are needed then we should add them under `mod tests` in the relevant module. We strive 59 | to find properties of our programs so that we can use tools like `proptest` to extensively prove our 60 | programs are correct. As well as this, we add unit tests to esnure the examples in our heads are 61 | correct, and testing out the ergonomics of our API first-hand. 62 | 63 | ## CI files 🤖 64 | 65 | Our CI infrastructure runs on Buildkite. The build process is run for every commit which is pushed 66 | to GitHub. 67 | 68 | All relevant configuration can be found here: 69 | 70 | ``` 71 | radicle-surf/.buildkite/ 72 | ├── docker 73 | │   ├── build 74 | │   │   └── Dockerfile 75 | │   └── rust-nightly 76 | │   └── Dockerfile 77 | └── pipeline.yaml 78 | ``` 79 | 80 | ## Releases 📅 81 | 82 | TODO: Once we get the API into a good shape we will keep track of releases via a `CHANGELOG.md` and 83 | tag the releases via `git tag`. 84 | -------------------------------------------------------------------------------- /radicle-surf/t/src/rev.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use radicle_git_ext::ref_format::{name::component, refname}; 4 | use radicle_surf::{Branch, Error, Oid, Repository}; 5 | 6 | use super::GIT_PLATINUM; 7 | 8 | // **FIXME**: This seems to break occasionally on 9 | // buildkite. For some reason the commit 10 | // 3873745c8f6ffb45c990eb23b491d4b4b6182f95, which is on master 11 | // (currently HEAD), is not found. It seems to load the history 12 | // with d6880352fc7fda8f521ae9b7357668b17bb5bad5 as the HEAD. 13 | // 14 | // To temporarily fix this, we need to select "New Build" from the build kite 15 | // build page that's failing. 16 | // * Under "Message" put whatever you want. 17 | // * Under "Branch" put in the branch you're working on. 18 | // * Expand "Options" and select "clean checkout". 19 | #[test] 20 | fn _master() -> Result<(), Error> { 21 | let repo = Repository::open(GIT_PLATINUM)?; 22 | let mut history = repo.history(Branch::remote(component!("origin"), refname!("master")))?; 23 | 24 | let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; 25 | assert!( 26 | history.any(|commit| commit.unwrap().id == commit1), 27 | "commit_id={}, history =\n{:#?}", 28 | commit1, 29 | &history 30 | ); 31 | 32 | let commit2 = Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?; 33 | assert!( 34 | history.any(|commit| commit.unwrap().id == commit2), 35 | "commit_id={}, history =\n{:#?}", 36 | commit2, 37 | &history 38 | ); 39 | 40 | Ok(()) 41 | } 42 | 43 | #[test] 44 | fn commit() -> Result<(), Error> { 45 | let repo = Repository::open(GIT_PLATINUM)?; 46 | let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; 47 | let mut history = repo.history(rev)?; 48 | 49 | let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; 50 | assert!(history.any(|commit| commit.unwrap().id == commit1)); 51 | 52 | Ok(()) 53 | } 54 | 55 | #[test] 56 | fn commit_parents() -> Result<(), Error> { 57 | let repo = Repository::open(GIT_PLATINUM)?; 58 | let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; 59 | let history = repo.history(rev)?; 60 | let commit = history.head(); 61 | 62 | assert_eq!( 63 | commit.parents, 64 | vec![Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?] 65 | ); 66 | 67 | Ok(()) 68 | } 69 | 70 | #[test] 71 | fn commit_short() -> Result<(), Error> { 72 | let repo = Repository::open(GIT_PLATINUM)?; 73 | let rev = repo.oid("3873745c8")?; 74 | let mut history = repo.history(rev)?; 75 | 76 | let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; 77 | assert!(history.any(|commit| commit.unwrap().id == commit1)); 78 | 79 | Ok(()) 80 | } 81 | 82 | #[test] 83 | fn tag() -> Result<(), Error> { 84 | let repo = Repository::open(GIT_PLATINUM)?; 85 | let rev = refname!("refs/tags/v0.2.0"); 86 | let history = repo.history(&rev)?; 87 | 88 | let commit1 = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?; 89 | assert_eq!(history.head().id, commit1); 90 | 91 | Ok(()) 92 | } 93 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/gen/commit.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use proptest::strategy::Strategy; 4 | use radicle_git_ext::commit::{self, CommitData}; 5 | 6 | mod author; 7 | mod headers; 8 | mod trailers; 9 | 10 | pub use author::author; 11 | pub use headers::headers; 12 | pub use trailers::{trailer, trailers}; 13 | 14 | use super::alphanumeric; 15 | 16 | pub fn commit() -> impl Strategy> { 17 | ( 18 | TreeData::gen(), 19 | author(), 20 | author(), 21 | headers(), 22 | alphanumeric(), 23 | trailers(3), 24 | ) 25 | .prop_map(|(tree, author, committer, headers, message, trailers)| { 26 | CommitData::new(tree, vec![], author, committer, headers, message, trailers) 27 | }) 28 | } 29 | 30 | pub fn write_commits( 31 | repo: &git2::Repository, 32 | linear: Vec>, 33 | ) -> Result, commit::error::Write> { 34 | let mut parent = None; 35 | let mut commits = Vec::new(); 36 | for commit in linear { 37 | let commit = commit.map_tree(|tree| tree.write(repo))?; 38 | let commit = match parent { 39 | Some(parent) => commit 40 | .map_parents::(|_| Ok(parent)) 41 | .unwrap(), 42 | None => commit 43 | .map_parents::(|_| unreachable!("no parents")) 44 | .unwrap(), 45 | }; 46 | let oid = commit.write(repo)?; 47 | commits.push(oid); 48 | parent = Some(oid); 49 | } 50 | Ok(commits) 51 | } 52 | 53 | #[derive(Clone, Debug)] 54 | pub enum TreeData { 55 | Blob { name: String, data: String }, 56 | Tree { name: String, inner: Vec }, 57 | } 58 | 59 | impl TreeData { 60 | fn gen() -> impl Strategy { 61 | let leaf = 62 | (alphanumeric(), alphanumeric()).prop_map(|(name, data)| Self::Blob { name, data }); 63 | leaf.prop_recursive(8, 16, 5, |inner| { 64 | (proptest::collection::vec(inner, 1..5), alphanumeric()) 65 | .prop_map(|(inner, name)| Self::Tree { name, inner }) 66 | }) 67 | } 68 | 69 | fn write(&self, repo: &git2::Repository) -> Result { 70 | let mut builder = repo.treebuilder(None)?; 71 | self.write_(repo, &mut builder)?; 72 | builder.write() 73 | } 74 | 75 | fn write_( 76 | &self, 77 | repo: &git2::Repository, 78 | builder: &mut git2::TreeBuilder, 79 | ) -> Result { 80 | match self { 81 | Self::Blob { name, data } => { 82 | let oid = repo.blob(data.as_bytes())?; 83 | builder.insert(name, oid, git2::FileMode::Blob.into())?; 84 | } 85 | Self::Tree { name, inner } => { 86 | for data in inner { 87 | let oid = data.write_(repo, builder)?; 88 | builder.insert(name, oid, git2::FileMode::Tree.into())?; 89 | } 90 | } 91 | } 92 | builder.write() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/repository.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::Infallible, io, path::Path}; 2 | 3 | use git2::Oid; 4 | use radicle_git_ext::{commit::CommitData, ref_format::RefString}; 5 | use test_helpers::tempdir::{self, WithTmpDir}; 6 | 7 | use crate::gen::commit::{self, TreeData}; 8 | 9 | pub struct Fixture { 10 | pub inner: WithTmpDir, 11 | pub head: Option, 12 | } 13 | 14 | /// Initialise a [`git2::Repository`] in a temporary directory. 15 | /// 16 | /// The provided `commits` will be added to the repository, and the 17 | /// head commit will be returned. 18 | pub fn fixture( 19 | refname: &RefString, 20 | commits: Vec>, 21 | ) -> io::Result { 22 | let repo = tempdir::WithTmpDir::new(|path| git2::Repository::init(path).map_err(io_other))?; 23 | let commits = commit::write_commits(&repo, commits).map_err(io_other)?; 24 | let head = commits.last().copied(); 25 | 26 | if let Some(head) = head { 27 | repo.reference(refname.as_str(), head, false, "Initialise repository") 28 | .map_err(io_other)?; 29 | } 30 | 31 | Ok(Fixture { inner: repo, head }) 32 | } 33 | 34 | pub fn bare_fixture( 35 | refname: &RefString, 36 | commits: Vec>, 37 | ) -> io::Result { 38 | let repo = 39 | tempdir::WithTmpDir::new(|path| git2::Repository::init_bare(path).map_err(io_other))?; 40 | let commits = commit::write_commits(&repo, commits).map_err(io_other)?; 41 | let head = commits.last().copied(); 42 | 43 | if let Some(head) = head { 44 | repo.reference(refname.as_str(), head, false, "Initialise repository") 45 | .map_err(io_other)?; 46 | } 47 | 48 | Ok(Fixture { inner: repo, head }) 49 | } 50 | 51 | pub fn submodule<'a>( 52 | parent: &'a git2::Repository, 53 | child: &'a git2::Repository, 54 | refname: &RefString, 55 | head: Oid, 56 | author: &git2::Signature, 57 | ) -> io::Result> { 58 | let url = format!("file://{}", child.path().canonicalize()?.display()); 59 | let mut sub = parent 60 | .submodule(url.as_str(), Path::new("submodule"), true) 61 | .map_err(io_other)?; 62 | sub.open().map_err(io_other)?; 63 | sub.clone(Some(&mut git2::SubmoduleUpdateOptions::default())) 64 | .map_err(io_other)?; 65 | sub.add_to_index(true).map_err(io_other)?; 66 | sub.add_finalize().map_err(io_other)?; 67 | { 68 | let mut ix = parent.index().map_err(io_other)?; 69 | let tree = ix.write_tree_to(parent).map_err(io_other)?; 70 | let tree = parent.find_tree(tree).map_err(io_other)?; 71 | let head = parent.find_commit(head).map_err(io_other)?; 72 | parent 73 | .commit( 74 | Some(refname.as_str()), 75 | author, 76 | author, 77 | "Commit submodule", 78 | &tree, 79 | &[&head], 80 | ) 81 | .map_err(io_other)?; 82 | } 83 | Ok(sub) 84 | } 85 | 86 | fn io_other(e: E) -> io::Error 87 | where 88 | E: std::error::Error + Send + Sync + 'static, 89 | { 90 | io::Error::new(io::ErrorKind::Other, e) 91 | } 92 | -------------------------------------------------------------------------------- /radicle-git-ext/src/oid.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::{ 7 | convert::TryFrom, 8 | fmt::{self, Display}, 9 | ops::Deref, 10 | str::FromStr, 11 | }; 12 | 13 | /// Serializable [`git2::Oid`] 14 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 15 | pub struct Oid(git2::Oid); 16 | 17 | #[cfg(feature = "serde")] 18 | mod serde_impls { 19 | use super::*; 20 | use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; 21 | 22 | impl Serialize for Oid { 23 | fn serialize(&self, serializer: S) -> Result 24 | where 25 | S: Serializer, 26 | { 27 | self.0.to_string().serialize(serializer) 28 | } 29 | } 30 | 31 | impl<'de> Deserialize<'de> for Oid { 32 | fn deserialize(deserializer: D) -> Result 33 | where 34 | D: Deserializer<'de>, 35 | { 36 | struct OidVisitor; 37 | 38 | impl Visitor<'_> for OidVisitor { 39 | type Value = Oid; 40 | 41 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 | write!(f, "a hexidecimal git2::Oid") 43 | } 44 | 45 | fn visit_str(self, s: &str) -> Result 46 | where 47 | E: serde::de::Error, 48 | { 49 | s.parse().map_err(serde::de::Error::custom) 50 | } 51 | } 52 | 53 | deserializer.deserialize_str(OidVisitor) 54 | } 55 | } 56 | } 57 | 58 | impl Deref for Oid { 59 | type Target = git2::Oid; 60 | 61 | fn deref(&self) -> &Self::Target { 62 | &self.0 63 | } 64 | } 65 | 66 | impl AsRef for Oid { 67 | fn as_ref(&self) -> &git2::Oid { 68 | self 69 | } 70 | } 71 | 72 | impl AsRef<[u8]> for Oid { 73 | fn as_ref(&self) -> &[u8] { 74 | self.as_bytes() 75 | } 76 | } 77 | 78 | impl From for Oid { 79 | fn from(oid: git2::Oid) -> Self { 80 | Self(oid) 81 | } 82 | } 83 | 84 | impl From for git2::Oid { 85 | fn from(oid: Oid) -> Self { 86 | oid.0 87 | } 88 | } 89 | 90 | impl Display for Oid { 91 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 92 | self.0.fmt(f) 93 | } 94 | } 95 | 96 | impl TryFrom<&str> for Oid { 97 | type Error = git2::Error; 98 | 99 | fn try_from(s: &str) -> Result { 100 | s.parse().map(Self) 101 | } 102 | } 103 | 104 | impl FromStr for Oid { 105 | type Err = git2::Error; 106 | 107 | fn from_str(s: &str) -> Result { 108 | Self::try_from(s) 109 | } 110 | } 111 | 112 | impl TryFrom<&[u8]> for Oid { 113 | type Error = git2::Error; 114 | 115 | fn try_from(bytes: &[u8]) -> Result { 116 | git2::Oid::from_bytes(bytes).map(Self) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/core/src/refspec/iter.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::fmt::{self, Display}; 7 | 8 | use super::PatternStr; 9 | use crate::{lit, RefStr}; 10 | 11 | pub type Iter<'a> = std::str::Split<'a, char>; 12 | 13 | pub enum Component<'a> { 14 | Glob(Option<&'a PatternStr>), 15 | Normal(&'a RefStr), 16 | } 17 | 18 | impl Component<'_> { 19 | #[inline] 20 | pub fn as_str(&self) -> &str { 21 | self.as_ref() 22 | } 23 | } 24 | 25 | impl AsRef for Component<'_> { 26 | #[inline] 27 | fn as_ref(&self) -> &str { 28 | match self { 29 | Self::Glob(None) => "*", 30 | Self::Glob(Some(x)) => x.as_str(), 31 | Self::Normal(x) => x.as_str(), 32 | } 33 | } 34 | } 35 | 36 | impl Display for Component<'_> { 37 | #[inline] 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | f.write_str(self.as_str()) 40 | } 41 | } 42 | 43 | impl From for Component<'static> { 44 | #[inline] 45 | fn from(_: T) -> Self { 46 | Self::Normal(T::NAME) 47 | } 48 | } 49 | 50 | #[must_use = "iterators are lazy and do nothing unless consumed"] 51 | #[derive(Clone)] 52 | pub struct Components<'a> { 53 | inner: Iter<'a>, 54 | } 55 | 56 | impl<'a> Iterator for Components<'a> { 57 | type Item = Component<'a>; 58 | 59 | #[inline] 60 | fn next(&mut self) -> Option { 61 | self.inner.next().map(|next| match next { 62 | "*" => Component::Glob(None), 63 | x if x.contains('*') => Component::Glob(Some(PatternStr::from_str(x))), 64 | x => Component::Normal(RefStr::from_str(x)), 65 | }) 66 | } 67 | } 68 | 69 | impl DoubleEndedIterator for Components<'_> { 70 | #[inline] 71 | fn next_back(&mut self) -> Option { 72 | self.inner.next_back().map(|next| match next { 73 | "*" => Component::Glob(None), 74 | x if x.contains('*') => Component::Glob(Some(PatternStr::from_str(x))), 75 | x => Component::Normal(RefStr::from_str(x)), 76 | }) 77 | } 78 | } 79 | 80 | impl<'a> From<&'a PatternStr> for Components<'a> { 81 | #[inline] 82 | fn from(p: &'a PatternStr) -> Self { 83 | Self { 84 | inner: p.as_str().split('/'), 85 | } 86 | } 87 | } 88 | 89 | pub mod component { 90 | use super::Component; 91 | use crate::name; 92 | 93 | pub const STAR: Component = Component::Glob(None); 94 | pub const HEADS: Component = Component::Normal(name::HEADS); 95 | pub const MAIN: Component = Component::Normal(name::MAIN); 96 | pub const MASTER: Component = Component::Normal(name::MASTER); 97 | pub const NAMESPACES: Component = Component::Normal(name::NAMESPACES); 98 | pub const NOTES: Component = Component::Normal(name::NOTES); 99 | pub const ORIGIN: Component = Component::Normal(name::ORIGIN); 100 | pub const REFS: Component = Component::Normal(name::REFS); 101 | pub const REMOTES: Component = Component::Normal(name::REMOTES); 102 | pub const TAGS: Component = Component::Normal(name::TAGS); 103 | } 104 | -------------------------------------------------------------------------------- /radicle-surf/t/src/submodule.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::Infallible, path::Path}; 2 | 3 | use proptest::{collection, proptest}; 4 | use radicle_git_ext::commit::CommitData; 5 | use radicle_git_ext::ref_format::refname; 6 | use radicle_git_ext_test::gen; 7 | use radicle_surf::tree::EntryKind; 8 | use radicle_surf::{fs, Branch, Repository}; 9 | 10 | proptest! { 11 | #[test] 12 | fn test_submodule( 13 | initial in gen::commit::commit(), 14 | commits in collection::vec(gen::commit::commit(), 1..5) 15 | ) { 16 | prop::test_submodule(initial, commits) 17 | } 18 | 19 | #[ignore = "segfault"] 20 | #[test] 21 | fn test_submodule_bare( 22 | initial in gen::commit::commit(), 23 | commits in collection::vec(gen::commit::commit(), 1..5) 24 | ) { 25 | prop::test_submodule_bare(initial, commits) 26 | } 27 | 28 | } 29 | 30 | mod prop { 31 | use radicle_git_ext_test::{gen::commit, repository}; 32 | 33 | use super::*; 34 | 35 | pub fn test_submodule( 36 | initial: CommitData, 37 | commits: Vec>, 38 | ) { 39 | let refname = refname!("refs/heads/master"); 40 | let author = git2::Signature::try_from(initial.author()).unwrap(); 41 | 42 | let submodule = repository::fixture(&refname, commits).unwrap(); 43 | let repo = repository::fixture(&refname, vec![initial]).unwrap(); 44 | 45 | let head = repo.head.expect("missing initial commit"); 46 | let sub = 47 | repository::submodule(&repo.inner, &submodule.inner, &refname, head, &author).unwrap(); 48 | 49 | let repo = Repository::open(repo.inner.path()).unwrap(); 50 | let branch = Branch::local(refname); 51 | let dir = repo.root_dir(&branch).unwrap(); 52 | 53 | let platinum = dir.find_entry(&sub.path(), &repo).unwrap(); 54 | assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some())); 55 | 56 | let root = repo.tree(&branch, &Path::new("")).unwrap(); 57 | let kind = EntryKind::from(platinum); 58 | assert!(root.entries().iter().any(|e| e.entry() == &kind)); 59 | } 60 | 61 | pub fn test_submodule_bare( 62 | initial: CommitData, 63 | commits: Vec>, 64 | ) { 65 | let refname = refname!("refs/heads/master"); 66 | let author = git2::Signature::try_from(initial.author()).unwrap(); 67 | 68 | let submodule = repository::fixture(&refname, commits).unwrap(); 69 | let repo = repository::bare_fixture(&refname, vec![initial]).unwrap(); 70 | 71 | let head = repo.head.expect("missing initial commit"); 72 | let sub = 73 | repository::submodule(&repo.inner, &submodule.inner, &refname, head, &author).unwrap(); 74 | 75 | let repo = Repository::open(repo.inner.path()).unwrap(); 76 | let branch = Branch::local(refname); 77 | let dir = repo.root_dir(&branch).unwrap(); 78 | 79 | let platinum = dir.find_entry(&sub.path(), &repo).unwrap(); 80 | assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some())); 81 | 82 | let root = repo.tree(&branch, &Path::new("")).unwrap(); 83 | let kind = EntryKind::from(platinum); 84 | assert!(root.entries().iter().any(|e| e.entry() == &kind)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /git-storage/t/src/gen.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use std::path::PathBuf; 5 | 6 | use git_storage::{ 7 | odb::{self, write::Write as _}, 8 | signature::UserInfo, 9 | Write, 10 | }; 11 | use proptest::prelude::*; 12 | use radicle_git_ext::Oid; 13 | 14 | use git2::FileMode; 15 | 16 | /// Represents a file in the git tree but without linking it to the repo yet 17 | #[derive(Clone, Debug)] 18 | pub struct File { 19 | pub path: PathBuf, 20 | pub inner: Vec, 21 | pub mode: git2::FileMode, 22 | pub oid: git2::Oid, 23 | } 24 | 25 | /// Represents a Tree to be written with the ODB writer. 26 | /// 27 | /// This Tree does not have an explicit link to a repository, linking is 28 | /// performed by writing it to the repo using the ODB writer. 29 | /// 30 | /// Used as a replacement of git2::TreeBuilder, which is more complicated to 31 | /// build since it requires a repository from the beginning. 32 | #[derive(Clone, Debug)] 33 | pub struct Tree { 34 | builder: odb::TreeBuilder, 35 | files: Vec, 36 | } 37 | 38 | impl Tree { 39 | /// Write the files to the filesystem and the repository `storage` 40 | pub fn write(self, storage: &Write) -> Result { 41 | self.write_files(storage)?; 42 | storage.write_tree(self.builder) 43 | } 44 | 45 | /// Write the files to the filesystem 46 | pub fn write_files(&self, storage: &Write) -> Result<(), git2::Error> { 47 | for file in &self.files { 48 | storage.write_blob(&file.inner)?; 49 | } 50 | Ok(()) 51 | } 52 | } 53 | 54 | /// Any valid filename 55 | pub fn trivial() -> impl Strategy { 56 | "[a-zA-Z0-9]+" 57 | } 58 | 59 | pub fn gen_signature() -> impl Strategy { 60 | trivial().prop_map(move |name| { 61 | UserInfo { 62 | name: name.clone(), 63 | // TODO: is it worth to make this more realistic? 64 | email: format!("{}@{}.com", &name, &name), 65 | } 66 | }) 67 | } 68 | 69 | pub fn gen_bytes() -> impl Strategy> { 70 | any::>() 71 | } 72 | 73 | pub fn gen_mode() -> impl Strategy { 74 | prop_oneof![Just(FileMode::Blob), Just(FileMode::BlobExecutable)] 75 | } 76 | 77 | prop_compose! { 78 | pub fn gen_file() 79 | (path in trivial(), 80 | blob in gen_bytes(), 81 | mode in gen_mode()) 82 | -> File { 83 | let oid = git2::Oid::hash_object(git2::ObjectType::Blob, &blob).unwrap(); 84 | File { 85 | path: PathBuf::from(path), 86 | inner: blob, 87 | mode, 88 | oid, 89 | } 90 | } 91 | } 92 | 93 | pub fn gen_file_set(max_size: u8) -> impl Strategy> { 94 | prop::collection::vec(gen_file(), 0..(max_size as usize)) 95 | } 96 | 97 | /// Generates a [`odb::TreeBuilder`] and a set of [`File`]s. 98 | /// 99 | /// To write the resulting `Tree`, you MUST write the [`File::oid`] to the 100 | /// repository first. 101 | pub fn gen_tree(max_size: u8) -> impl Strategy { 102 | gen_file_set(max_size).prop_map(move |files| { 103 | let builder = files.iter().fold(odb::TreeBuilder::new(), |tree, file| { 104 | tree.insert(file.path.clone(), file.oid.into(), file.mode) 105 | }); 106 | Tree { builder, files } 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /radicle-git-ext/t/src/git_ref_format/properties/name.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use std::convert::TryFrom; 5 | 6 | use proptest::prelude::*; 7 | use radicle_git_ext::ref_format::{name, refname, Error, RefStr, RefString}; 8 | use test_helpers::roundtrip; 9 | 10 | use crate::git_ref_format::gen; 11 | 12 | proptest! { 13 | #[test] 14 | fn valid(input in gen::valid()) { 15 | assert_eq!(input.as_str(), RefStr::try_from_str(&input).unwrap().as_str()) 16 | } 17 | 18 | #[test] 19 | fn invalid_char(input in gen::with_invalid_char()) { 20 | assert_matches!(RefString::try_from(input), Err(Error::InvalidChar(_))) 21 | } 22 | 23 | #[test] 24 | fn dot_lock(input in gen::ends_with_dot_lock()) { 25 | assert_matches!(RefString::try_from(input), Err(Error::DotLock)) 26 | } 27 | 28 | #[test] 29 | fn double_dot(input in gen::with_double_dot()) { 30 | assert_matches!(RefString::try_from(input), Err(Error::DotDot)) 31 | } 32 | 33 | #[test] 34 | fn starts_dot(input in gen::starts_with_dot()) { 35 | assert_matches!(RefString::try_from(input), Err(Error::StartsDot)) 36 | } 37 | 38 | #[test] 39 | fn ends_dot(input in gen::ends_with_dot()) { 40 | assert_matches!(RefString::try_from(input), Err(Error::EndsDot)) 41 | } 42 | 43 | #[test] 44 | fn control_char(input in gen::with_control_char()) { 45 | assert_matches!(RefString::try_from(input), Err(Error::Control)) 46 | } 47 | 48 | #[test] 49 | fn space(input in gen::with_space()) { 50 | assert_matches!(RefString::try_from(input), Err(Error::Space)) 51 | } 52 | 53 | #[test] 54 | fn consecutive_slashes(input in gen::with_consecutive_slashes()) { 55 | assert_matches!(RefString::try_from(input), Err(Error::Slash)) 56 | } 57 | 58 | #[test] 59 | fn glob(input in gen::with_glob()) { 60 | assert_matches!(RefString::try_from(input), Err(Error::InvalidChar('*'))) 61 | } 62 | 63 | #[test] 64 | fn invalid(input in gen::invalid()) { 65 | assert_matches!(RefString::try_from(input), Err(_)) 66 | } 67 | 68 | #[test] 69 | fn roundtrip_components(input in gen::valid()) { 70 | assert_eq!( 71 | input.as_str(), 72 | RefStr::try_from_str(&input).unwrap().components().collect::().as_str() 73 | ) 74 | } 75 | 76 | #[test] 77 | fn json(input in gen::valid()) { 78 | let input = RefString::try_from(input).unwrap(); 79 | roundtrip::json(input.clone()); 80 | let qualified = refname!("refs/heads").and(input).qualified().unwrap().into_owned(); 81 | roundtrip::json(qualified.clone()); 82 | let namespaced = qualified.with_namespace(name::component!("foo")); 83 | roundtrip::json(namespaced); 84 | } 85 | 86 | #[test] 87 | fn json_value(input in gen::valid()) { 88 | let input = RefString::try_from(input).unwrap(); 89 | roundtrip::json_value(input.clone()); 90 | let qualified = refname!("refs/heads").and(input).qualified().unwrap().into_owned(); 91 | roundtrip::json_value(qualified.clone()); 92 | let namespaced = qualified.with_namespace(name::component!("foo")); 93 | roundtrip::json_value(namespaced); 94 | } 95 | 96 | #[test] 97 | fn cbor(input in gen::valid()) { 98 | roundtrip::cbor(RefString::try_from(input).unwrap()) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/core/src/cbor.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::convert::TryFrom; 7 | 8 | use minicbor::{ 9 | decode, 10 | encode::{self, Write}, 11 | Decode, Decoder, Encode, Encoder, 12 | }; 13 | 14 | use crate::{ 15 | refspec::{PatternStr, PatternString}, 16 | Namespaced, Qualified, RefStr, RefString, 17 | }; 18 | 19 | impl<'de: 'a, 'a> Decode<'de> for &'a RefStr { 20 | #[inline] 21 | fn decode(d: &mut Decoder<'de>) -> Result { 22 | d.str() 23 | .and_then(|s| Self::try_from(s).map_err(|e| decode::Error::Custom(Box::new(e)))) 24 | } 25 | } 26 | 27 | impl Encode for &RefStr { 28 | #[inline] 29 | fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { 30 | e.str(self.as_str())?; 31 | Ok(()) 32 | } 33 | } 34 | 35 | impl<'de> Decode<'de> for RefString { 36 | #[inline] 37 | fn decode(d: &mut Decoder<'de>) -> Result { 38 | Decode::decode(d).map(|s: &RefStr| s.to_owned()) 39 | } 40 | } 41 | 42 | impl Encode for RefString { 43 | #[inline] 44 | fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { 45 | self.as_refstr().encode(e) 46 | } 47 | } 48 | 49 | impl<'de: 'a, 'a> Decode<'de> for &'a PatternStr { 50 | #[inline] 51 | fn decode(d: &mut Decoder<'de>) -> Result { 52 | d.str() 53 | .and_then(|s| Self::try_from(s).map_err(|e| decode::Error::Custom(Box::new(e)))) 54 | } 55 | } 56 | 57 | impl Encode for &PatternStr { 58 | #[inline] 59 | fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { 60 | e.str(self.as_str())?; 61 | Ok(()) 62 | } 63 | } 64 | 65 | impl<'de> Decode<'de> for PatternString { 66 | #[inline] 67 | fn decode(d: &mut Decoder<'de>) -> Result { 68 | Decode::decode(d).map(|s: &PatternStr| s.to_owned()) 69 | } 70 | } 71 | 72 | impl Encode for PatternString { 73 | #[inline] 74 | fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { 75 | self.as_pattern_str().encode(e) 76 | } 77 | } 78 | 79 | impl<'de: 'a, 'a> Decode<'de> for Qualified<'a> { 80 | #[inline] 81 | fn decode(d: &mut Decoder<'de>) -> Result { 82 | Decode::decode(d).and_then(|s: &RefStr| { 83 | s.qualified() 84 | .ok_or(decode::Error::Message("not a qualified ref")) 85 | }) 86 | } 87 | } 88 | 89 | impl Encode for Qualified<'_> { 90 | #[inline] 91 | fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { 92 | self.as_str().encode(e) 93 | } 94 | } 95 | 96 | impl<'de: 'a, 'a> Decode<'de> for Namespaced<'a> { 97 | #[inline] 98 | fn decode(d: &mut Decoder<'de>) -> Result { 99 | Decode::decode(d).and_then(|s: &RefStr| { 100 | s.to_namespaced() 101 | .ok_or(decode::Error::Message("not a namespaced ref")) 102 | }) 103 | } 104 | } 105 | 106 | impl Encode for Namespaced<'_> { 107 | #[inline] 108 | fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { 109 | self.as_str().encode(e) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /radicle-surf/t/src/code_browsing.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use radicle_git_ext::ref_format::refname; 4 | use radicle_surf::{ 5 | fs::{self, Directory}, 6 | Branch, Repository, 7 | }; 8 | 9 | use super::GIT_PLATINUM; 10 | 11 | #[test] 12 | fn iterate_root_dir_recursive() { 13 | let repo = Repository::open(GIT_PLATINUM).unwrap(); 14 | 15 | let root_dir = repo.root_dir(Branch::local(refname!("master"))).unwrap(); 16 | let count = println_dir(&root_dir, &repo); 17 | 18 | assert_eq!(count, 36); // Check total file count. 19 | 20 | /// Prints items in `dir` with `indent_level`. 21 | /// For sub-directories, will do Depth-First-Search and print 22 | /// recursively. 23 | /// Returns the number of items visited (i.e. printed) 24 | fn println_dir(dir: &Directory, repo: &Repository) -> i32 { 25 | dir.traverse::( 26 | repo, 27 | (0, 0), 28 | &mut |(count, indent_level), entry| { 29 | println!("> {}{}", " ".repeat(indent_level * 4), entry.name()); 30 | match entry { 31 | fs::Entry::File(_) => Ok((count + 1, indent_level)), 32 | fs::Entry::Directory(_) => Ok((count + 1, indent_level + 1)), 33 | fs::Entry::Submodule(_) => Ok((count + 1, indent_level)), 34 | } 35 | }, 36 | ) 37 | .unwrap() 38 | .0 39 | } 40 | } 41 | 42 | #[test] 43 | fn browse_repo_lazily() { 44 | let repo = Repository::open(GIT_PLATINUM).unwrap(); 45 | 46 | let root_dir = repo.root_dir(Branch::local(refname!("master"))).unwrap(); 47 | let count = root_dir.entries(&repo).unwrap().entries().count(); 48 | assert_eq!(count, 8); 49 | let count = traverse(&root_dir, &repo); 50 | assert_eq!(count, 36); 51 | 52 | fn traverse(dir: &Directory, repo: &Repository) -> i32 { 53 | dir.traverse::(repo, 0, &mut |count, _| Ok(count + 1)) 54 | .unwrap() 55 | } 56 | } 57 | 58 | #[test] 59 | fn test_file_history() { 60 | let repo = Repository::open(GIT_PLATINUM).unwrap(); 61 | let history = repo.history(Branch::local(refname!("dev"))).unwrap(); 62 | let path = Path::new("README.md"); 63 | let mut file_history = history.by_path(&path); 64 | let commit = file_history.next().unwrap().unwrap(); 65 | let file = repo.get_commit_file(&commit.id, &path).unwrap(); 66 | assert_eq!(file.size(), 67); 67 | } 68 | 69 | #[test] 70 | fn test_commit_history() { 71 | let repo = Repository::open(GIT_PLATINUM).unwrap(); 72 | let head = "a0dd9122d33dff2a35f564d564db127152c88e02"; 73 | 74 | // verify `&str` works. 75 | let h1 = repo.history(head).unwrap(); 76 | 77 | // verify `&String` works. 78 | let head_string = head.to_string(); 79 | let h2 = repo.history(&head_string).unwrap(); 80 | 81 | assert_eq!(h1.head().id, h2.head().id); 82 | } 83 | 84 | #[test] 85 | fn test_commit_signature() { 86 | let repo = Repository::open(GIT_PLATINUM).unwrap(); 87 | let commit_with_signature = "e24124b7538658220b5aaf3b6ef53758f0a106dc"; 88 | let signature = repo.extract_signature(commit_with_signature, None).unwrap(); 89 | assert!(signature.is_some()); 90 | 91 | let commit_without_signature = "80bacafba303bf0cdf6142921f430ff265f25095"; 92 | let signature = repo 93 | .extract_signature(commit_without_signature, None) 94 | .unwrap(); 95 | assert!(signature.is_none()); 96 | 97 | let commit_nonexist = "8080808080"; 98 | let signature = repo.extract_signature(commit_nonexist, None); 99 | assert!(signature.is_err()); 100 | } 101 | -------------------------------------------------------------------------------- /archived/link-git/src/protocol/upload_pack/legacy.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2020 The Radicle Foundation 2 | // Copyright © 2021 The Radicle Link Contributors 3 | // 4 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 5 | // Linking Exception. For full terms see the included LICENSE file. 6 | 7 | use std::{io, path::Path, process::ExitStatus}; 8 | 9 | use async_process::{Command, Stdio}; 10 | use futures_lite::io::{copy, AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _}; 11 | use futures_util::try_join; 12 | use git_ref::{ 13 | file::{Store as Refdb, WriteReflog}, 14 | FullName, 15 | Reference, 16 | }; 17 | 18 | pub(super) async fn advertise_refs( 19 | git_dir: impl AsRef, 20 | namespace: &str, 21 | mut recv: R, 22 | mut send: W, 23 | ) -> io::Result 24 | where 25 | R: AsyncRead + Unpin, 26 | W: AsyncWrite + Unpin, 27 | { 28 | let unhide = blocking::unblock({ 29 | let git_dir = git_dir.as_ref().to_path_buf(); 30 | let prefix = Path::new("refs") 31 | .join("namespaces") 32 | .join(namespace) 33 | .join("refs"); 34 | move || -> io::Result> { 35 | let refdb = Refdb::at(git_dir, WriteReflog::Disable); 36 | let packed = refdb 37 | .packed_buffer() 38 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 39 | 40 | let refs = refdb 41 | .iter_prefixed(packed.as_ref(), prefix)? 42 | .filter_map(|r| r.ok().map(|Reference { name, .. }| name)) 43 | .filter(|name| { 44 | const PATTERN: &[u8] = b"rad/ids/any"; 45 | const SEPARAT: u8 = b'/'; 46 | name.as_bstr() 47 | .rsplit(|b| b == &SEPARAT) 48 | .zip(PATTERN.rsplit(|b| b == &SEPARAT)) 49 | .skip(1) 50 | .all(|(a, b)| a == b) 51 | }) 52 | .collect::>(); 53 | 54 | Ok(refs) 55 | } 56 | }) 57 | .await?; 58 | 59 | let mut child = { 60 | let mut cmd = Command::new("git"); 61 | cmd.current_dir(git_dir) 62 | .env_clear() 63 | .envs(std::env::vars().filter(|(key, _)| key == "PATH" || key.starts_with("GIT_TRACE"))) 64 | .arg("-c") 65 | .arg("uploadpack.hiderefs=refs/") 66 | .arg("-c") 67 | .arg(format!("uploadpack.hiderefs=!refs/namespaces/{namespace}",)); 68 | 69 | for r in unhide { 70 | cmd.arg("-c") 71 | .arg(format!("uploadpack.hiderefs=!{}", r.as_bstr())); 72 | } 73 | 74 | cmd.args([ 75 | "upload-pack", 76 | "--strict", 77 | "--timeout=5", 78 | "--stateless-rpc", 79 | "--advertise-refs", 80 | ".", 81 | ]) 82 | .stdout(Stdio::piped()) 83 | .stderr(Stdio::inherit()) 84 | .kill_on_drop(true) 85 | .reap_on_drop(true) 86 | .spawn()? 87 | }; 88 | let mut stdout = child.stdout.take().unwrap(); 89 | 90 | const HEADER: &[u8] = b"001e# service=git-upload-pack\n0000"; 91 | send.write_all(HEADER).await?; 92 | let status = try_join!(copy(&mut stdout, &mut send), child.status()).map(|x| x.1); 93 | 94 | // Read one byte off the read stream to ensure it is driven to completion 95 | // (we expect EOF immediately). Failure to do so may cause resource leaks. 96 | // 97 | // Cf. 900b6cf6 (replication: Ensure git stream is closed, 2021-04-26) 98 | let mut buf = [0; 1]; 99 | recv.read(&mut buf).await?; 100 | 101 | status 102 | } 103 | -------------------------------------------------------------------------------- /radicle-git-ext/git-ref-format/core/src/check.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use thiserror::Error; 7 | 8 | pub struct Options { 9 | /// If `false`, the refname must contain at least one `/`. 10 | pub allow_onelevel: bool, 11 | /// If `true`, the refname may contain exactly one `*` character. 12 | pub allow_pattern: bool, 13 | } 14 | 15 | #[derive(Debug, PartialEq, Eq, Error)] 16 | #[non_exhaustive] 17 | pub enum Error { 18 | #[error("empty input")] 19 | Empty, 20 | #[error("lone '@' character")] 21 | LoneAt, 22 | #[error("consecutive or trailing slash")] 23 | Slash, 24 | #[error("ends with '.lock'")] 25 | DotLock, 26 | #[error("consecutive dots ('..')")] 27 | DotDot, 28 | #[error("at-open-brace ('@{{')")] 29 | AtOpenBrace, 30 | #[error("invalid character {0:?}")] 31 | InvalidChar(char), 32 | #[error("component starts with '.'")] 33 | StartsDot, 34 | #[error("component ends with '.'")] 35 | EndsDot, 36 | #[error("control character")] 37 | Control, 38 | #[error("whitespace")] 39 | Space, 40 | #[error("must contain at most one '*'")] 41 | Pattern, 42 | #[error("must contain at least one '/'")] 43 | OneLevel, 44 | } 45 | 46 | /// Validate that a string slice is a valid refname. 47 | pub fn ref_format(opts: Options, s: &str) -> Result<(), Error> { 48 | match s { 49 | "" => Err(Error::Empty), 50 | "@" => Err(Error::LoneAt), 51 | "." => Err(Error::StartsDot), 52 | _ => { 53 | let mut globs = 0usize; 54 | let mut parts = 0usize; 55 | 56 | for x in s.split('/') { 57 | if x.is_empty() { 58 | return Err(Error::Slash); 59 | } 60 | 61 | parts += 1; 62 | 63 | if x.ends_with(".lock") { 64 | return Err(Error::DotLock); 65 | } 66 | 67 | let last_char = x.chars().count() - 1; 68 | for (i, y) in x.chars().zip(x.chars().cycle().skip(1)).enumerate() { 69 | match y { 70 | ('.', '.') => return Err(Error::DotDot), 71 | ('@', '{') => return Err(Error::AtOpenBrace), 72 | 73 | ('\0', _) => return Err(Error::InvalidChar('\0')), 74 | ('\\', _) => return Err(Error::InvalidChar('\\')), 75 | ('~', _) => return Err(Error::InvalidChar('~')), 76 | ('^', _) => return Err(Error::InvalidChar('^')), 77 | (':', _) => return Err(Error::InvalidChar(':')), 78 | ('?', _) => return Err(Error::InvalidChar('?')), 79 | ('[', _) => return Err(Error::InvalidChar('[')), 80 | 81 | ('*', _) => globs += 1, 82 | 83 | ('.', _) if i == 0 => return Err(Error::StartsDot), 84 | ('.', _) if i == last_char => return Err(Error::EndsDot), 85 | 86 | (' ', _) => return Err(Error::Space), 87 | 88 | (z, _) if z.is_ascii_control() => return Err(Error::Control), 89 | 90 | _ => continue, 91 | } 92 | } 93 | } 94 | 95 | if parts < 2 && !opts.allow_onelevel { 96 | Err(Error::OneLevel) 97 | } else if globs > 1 && opts.allow_pattern { 98 | Err(Error::Pattern) 99 | } else if globs > 0 && !opts.allow_pattern { 100 | Err(Error::InvalidChar('*')) 101 | } else { 102 | Ok(()) 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /git-storage/t/src/properties/odb.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | use git2::ObjectType; 5 | 6 | use git_storage::{ 7 | odb::{Read as _, Write as _}, 8 | signature::UserInfo, 9 | }; 10 | use radicle_git_ext::ref_format::RefString; 11 | use radicle_git_ext_test::git_ref_format::gen::valid; 12 | 13 | use proptest::prelude::*; 14 | 15 | use crate::{gen, tmp}; 16 | 17 | // NOTE: It's enough to check the `Oid`s. If the contents were different the 18 | // hash would be different. 19 | pub mod prop { 20 | use super::*; 21 | 22 | pub fn roundtrip_blob(user: UserInfo, bytes: &[u8]) { 23 | let writer = tmp::writer(user); 24 | 25 | let oid = writer.write_blob(bytes).unwrap(); 26 | 27 | let readback = writer.find_blob(oid).unwrap().unwrap(); 28 | assert_eq!(oid, readback.id().into()); 29 | 30 | let readback = writer.find_object(oid).unwrap().unwrap(); 31 | assert_eq!(readback.kind(), Some(ObjectType::Blob)); 32 | } 33 | 34 | pub fn roundtrip_tree(user: UserInfo, tree: gen::Tree) { 35 | let writer = tmp::writer(user); 36 | let oid = tree.write(&writer).unwrap(); 37 | let readback = writer.find_tree(oid).unwrap().unwrap(); 38 | assert_eq!(oid, readback.id().into()); 39 | 40 | let readback = writer.find_object(oid).unwrap().unwrap(); 41 | assert_eq!(readback.kind(), Some(ObjectType::Tree)); 42 | } 43 | 44 | pub fn roundtrip_commit(user: UserInfo, tree: gen::Tree, message: &str) { 45 | let writer = tmp::writer(user); 46 | let tree_oid = tree.write(&writer).unwrap(); 47 | let tree = writer.find_tree(tree_oid).unwrap().unwrap(); 48 | let commit_oid = writer.write_commit(&tree, &[], message).unwrap(); 49 | 50 | let readback = writer.find_commit(commit_oid).unwrap().unwrap(); 51 | assert_eq!(readback.id(), commit_oid.into()); 52 | 53 | let readback = writer.find_object(commit_oid).unwrap().unwrap(); 54 | assert_eq!(readback.kind(), Some(ObjectType::Commit)); 55 | } 56 | 57 | pub fn roundtrip_tag(user: UserInfo, tree: gen::Tree, tag_name: RefString, message: &str) { 58 | let writer = tmp::writer(user); 59 | let tree_oid = tree.write(&writer).unwrap(); 60 | let tree = writer.find_tree(tree_oid).unwrap().unwrap(); 61 | let commit_oid = writer.write_commit(&tree, &[], message).unwrap(); 62 | let commit_object = writer.find_object(commit_oid).unwrap().unwrap(); 63 | 64 | let tag_oid = writer.write_tag(tag_name, &commit_object, message).unwrap(); 65 | 66 | let readback = writer.find_tag(tag_oid).unwrap().unwrap(); 67 | assert_eq!(tag_oid, readback.id().into()); 68 | 69 | let readback = writer.find_object(tag_oid).unwrap().unwrap(); 70 | assert_eq!(readback.kind(), Some(ObjectType::Tag)); 71 | } 72 | } 73 | 74 | proptest! { 75 | #[test] 76 | fn roundtrip_blob(user in gen::gen_signature(), bytes in gen::gen_bytes()) { 77 | prop::roundtrip_blob(user, &bytes) 78 | } 79 | 80 | 81 | #[test] 82 | fn roundtrip_tree( 83 | user in gen::gen_signature(), 84 | tree in gen::gen_tree(10), 85 | ) { 86 | prop::roundtrip_tree(user, tree) 87 | } 88 | 89 | #[test] 90 | fn roundtrip_commit( 91 | user in gen::gen_signature(), 92 | tree in gen::gen_tree(10), 93 | message in gen::trivial() 94 | ) { 95 | prop::roundtrip_commit(user, tree, &message) 96 | } 97 | 98 | #[test] 99 | fn roundtrip_tag( 100 | user in gen::gen_signature(), 101 | tree in gen::gen_tree(10), 102 | message in gen::trivial(), 103 | tag_name in valid() 104 | ) { 105 | let tag_name = RefString::try_from(tag_name).unwrap(); 106 | prop::roundtrip_tag(user, tree, tag_name, &message); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /archived/link-git/src/odb/pack.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::{ 7 | path::{Path, PathBuf}, 8 | sync::atomic::{AtomicUsize, Ordering}, 9 | }; 10 | 11 | use git_hash::{oid, ObjectId}; 12 | use git_pack::{data, index}; 13 | use rustc_hash::FxHasher; 14 | use tracing::warn; 15 | 16 | pub mod error { 17 | use super::*; 18 | use thiserror::Error; 19 | 20 | #[derive(Debug, Error)] 21 | #[error("failed to load pack data from {path:?}")] 22 | pub struct Data { 23 | pub path: PathBuf, 24 | pub source: data::header::decode::Error, 25 | } 26 | 27 | #[derive(Debug, Error)] 28 | #[error("failed to load pack index from {path:?}")] 29 | pub struct Index { 30 | pub path: PathBuf, 31 | pub source: index::init::Error, 32 | } 33 | } 34 | 35 | pub struct Data { 36 | pub hash: u64, 37 | hits: AtomicUsize, 38 | file: data::File, 39 | } 40 | 41 | impl Data { 42 | pub fn hit(&self) { 43 | self.hits.fetch_add(1, Ordering::Relaxed); 44 | } 45 | 46 | pub fn hits(&self) -> usize { 47 | self.hits.load(Ordering::Relaxed) 48 | } 49 | 50 | pub fn file(&self) -> &data::File { 51 | &self.file 52 | } 53 | } 54 | 55 | impl AsRef for Data { 56 | fn as_ref(&self) -> &data::File { 57 | self.file() 58 | } 59 | } 60 | 61 | #[derive(Clone, PartialEq, Eq)] 62 | pub struct Info { 63 | pub(super) hash: u64, 64 | pub data_path: PathBuf, 65 | } 66 | 67 | impl Info { 68 | pub fn data(&self) -> Result { 69 | let file = data::File::at(&self.data_path).map_err(|source| error::Data { 70 | path: self.data_path.clone(), 71 | source, 72 | })?; 73 | Ok(Data { 74 | hash: self.hash, 75 | hits: AtomicUsize::new(0), 76 | file, 77 | }) 78 | } 79 | } 80 | 81 | pub struct Index { 82 | pub info: Info, 83 | file: index::File, 84 | } 85 | 86 | impl Index { 87 | pub fn open(path: impl AsRef) -> Result { 88 | let path = path.as_ref(); 89 | let file = index::File::at(path).map_err(|source| error::Index { 90 | path: path.to_path_buf(), 91 | source, 92 | })?; 93 | let data_path = path.with_extension("pack"); 94 | let hash = { 95 | let file_name = path 96 | .file_name() 97 | .expect("must have a file name, we opened it") 98 | .to_string_lossy(); 99 | // XXX: inexplicably, gitoxide omits the "pack-" prefix 100 | let sha_hex = file_name.strip_prefix("pack-").unwrap_or(&file_name); 101 | match ObjectId::from_hex(&sha_hex.as_bytes()[..40]) { 102 | Err(e) => { 103 | warn!( 104 | "unconventional pack name {:?}, falling back to fxhash: {}", 105 | path, e 106 | ); 107 | hash(path) 108 | }, 109 | Ok(oid) => { 110 | let mut buf = [0u8; 8]; 111 | buf.copy_from_slice(&oid.sha1()[..8]); 112 | u64::from_be_bytes(buf) 113 | }, 114 | } 115 | }; 116 | let info = Info { hash, data_path }; 117 | 118 | Ok(Self { file, info }) 119 | } 120 | 121 | pub fn contains(&self, id: impl AsRef) -> bool { 122 | self.file.lookup(id).is_some() 123 | } 124 | 125 | pub fn ofs(&self, id: impl AsRef) -> Option { 126 | self.file 127 | .lookup(id) 128 | .map(|idx| self.file.pack_offset_at_index(idx)) 129 | } 130 | } 131 | 132 | fn hash(p: &Path) -> u64 { 133 | use std::hash::{Hash as _, Hasher as _}; 134 | 135 | let mut hasher = FxHasher::default(); 136 | p.hash(&mut hasher); 137 | hasher.finish() 138 | } 139 | -------------------------------------------------------------------------------- /archived/link-git/t/src/tests/protocol/upload_pack.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use link_git::protocol::upload_pack; 7 | 8 | mod header { 9 | use super::*; 10 | use std::str::FromStr as _; 11 | 12 | #[test] 13 | fn service_must_be_upload_pack() { 14 | assert_eq!( 15 | upload_pack::Header::from_str("git-receive-pack "), 16 | Err("unsupported service") 17 | ) 18 | } 19 | 20 | #[test] 21 | fn no_path() { 22 | assert_eq!( 23 | upload_pack::Header::from_str("git-upload-pack "), 24 | Err("missing path") 25 | ) 26 | } 27 | 28 | #[test] 29 | fn empty_path() { 30 | assert_eq!( 31 | upload_pack::Header::from_str("git-upload-pack \0host=lolhost:123\0"), 32 | Err("empty path") 33 | ) 34 | } 35 | 36 | #[test] 37 | fn host_and_port() { 38 | assert_eq!( 39 | upload_pack::Header::from_str("git-upload-pack /git.git\0host=lolhost:123\0").unwrap(), 40 | upload_pack::Header { 41 | path: "/git.git".to_owned(), 42 | host: Some(("lolhost".to_owned(), Some(123))), 43 | extra: vec![] 44 | } 45 | ) 46 | } 47 | 48 | #[test] 49 | fn host_without_port() { 50 | assert_eq!( 51 | upload_pack::Header::from_str("git-upload-pack /git.git\0host=lolhost\0").unwrap(), 52 | upload_pack::Header { 53 | path: "/git.git".to_owned(), 54 | host: Some(("lolhost".to_owned(), None)), 55 | extra: vec![] 56 | } 57 | ) 58 | } 59 | 60 | #[test] 61 | fn no_host() { 62 | assert_eq!( 63 | upload_pack::Header::from_str("git-upload-pack /git.git\0").unwrap(), 64 | upload_pack::Header { 65 | path: "/git.git".to_owned(), 66 | host: None, 67 | extra: vec![] 68 | } 69 | ) 70 | } 71 | 72 | #[test] 73 | fn empty_host() { 74 | assert_eq!( 75 | upload_pack::Header::from_str("git-upload-pack /git.git\0\0").unwrap(), 76 | upload_pack::Header { 77 | path: "/git.git".to_owned(), 78 | host: None, 79 | extra: vec![] 80 | } 81 | ) 82 | } 83 | 84 | #[test] 85 | fn no_host_extra() { 86 | assert_eq!( 87 | upload_pack::Header::from_str("git-upload-pack /git.git\0\0version=42\0").unwrap(), 88 | upload_pack::Header { 89 | path: "/git.git".to_owned(), 90 | host: None, 91 | extra: vec![("version".to_owned(), Some("42".to_owned()))] 92 | } 93 | ) 94 | } 95 | 96 | #[test] 97 | fn host_port_extra() { 98 | assert_eq!( 99 | upload_pack::Header::from_str( 100 | "git-upload-pack /git.git\0host=lolhost:123\0\0version=42\0" 101 | ) 102 | .unwrap(), 103 | upload_pack::Header { 104 | path: "/git.git".to_owned(), 105 | host: Some(("lolhost".to_owned(), Some(123))), 106 | extra: vec![("version".to_owned(), Some("42".to_owned()))] 107 | } 108 | ) 109 | } 110 | 111 | #[test] 112 | fn host_extra_extra() { 113 | assert_eq!( 114 | upload_pack::Header::from_str( 115 | "git-upload-pack /git.git\0host=lolhost\0\0version=42\0foo\0n=69\0" 116 | ) 117 | .unwrap(), 118 | upload_pack::Header { 119 | path: "/git.git".to_owned(), 120 | host: Some(("lolhost".to_owned(), None)), 121 | extra: vec![ 122 | ("version".to_owned(), Some("42".to_owned())), 123 | ("foo".to_owned(), None), 124 | ("n".to_owned(), Some("69".to_owned())) 125 | ] 126 | } 127 | ) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /radicle-surf/src/history.rs: -------------------------------------------------------------------------------- 1 | // This file is part of radicle-surf 2 | // 3 | // 4 | // Copyright (C) 2022 The Radicle Team 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License version 3 or 8 | // later as published by the Free Software Foundation. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | use std::{ 19 | convert::TryFrom, 20 | path::{Path, PathBuf}, 21 | }; 22 | 23 | use crate::{Commit, Error, Repository, ToCommit}; 24 | 25 | /// An iterator that produces the history of commits for a given `head`. 26 | /// 27 | /// The lifetime of this struct is attached to the underlying [`Repository`]. 28 | pub struct History<'a> { 29 | repo: &'a Repository, 30 | head: Commit, 31 | revwalk: git2::Revwalk<'a>, 32 | filter_by: Option, 33 | } 34 | 35 | /// Internal implementation, subject to refactoring. 36 | enum FilterBy { 37 | File { path: PathBuf }, 38 | } 39 | 40 | impl<'a> History<'a> { 41 | /// Creates a new history starting from `head`, in `repo`. 42 | pub(crate) fn new(repo: &'a Repository, head: C) -> Result { 43 | let head = head 44 | .to_commit(repo) 45 | .map_err(|err| Error::ToCommit(err.into()))?; 46 | let mut revwalk = repo.revwalk()?; 47 | revwalk.push(head.id.into())?; 48 | let history = Self { 49 | repo, 50 | head, 51 | revwalk, 52 | filter_by: None, 53 | }; 54 | Ok(history) 55 | } 56 | 57 | /// Returns the first commit (i.e. the head) in the history. 58 | pub fn head(&self) -> &Commit { 59 | &self.head 60 | } 61 | 62 | /// Returns a modified `History` filtered by `path`. 63 | /// 64 | /// Note that it is possible that a filtered History becomes empty, 65 | /// even though calling `.head()` still returns the original head. 66 | pub fn by_path

(mut self, path: &P) -> Self 67 | where 68 | P: AsRef, 69 | { 70 | self.filter_by = Some(FilterBy::File { 71 | path: path.as_ref().to_path_buf(), 72 | }); 73 | self 74 | } 75 | } 76 | 77 | impl Iterator for History<'_> { 78 | type Item = Result; 79 | 80 | fn next(&mut self) -> Option { 81 | // Loop through the commits with the optional filtering. 82 | while let Some(oid) = self.revwalk.next() { 83 | let found = oid 84 | .map_err(Error::Git) 85 | .and_then(|oid| { 86 | let commit = self.repo.find_commit(oid.into())?; 87 | 88 | // Handles the optional filter_by. 89 | if let Some(FilterBy::File { path }) = &self.filter_by { 90 | // Only check the commit diff if the path is not empty. 91 | if !path.as_os_str().is_empty() { 92 | let path_opt = self.repo.diff_commit_and_parents(path, &commit)?; 93 | if path_opt.is_none() { 94 | return Ok(None); // Filter out this commit. 95 | } 96 | } 97 | } 98 | 99 | let commit = Commit::try_from(commit)?; 100 | Ok(Some(commit)) 101 | }) 102 | .transpose(); 103 | if found.is_some() { 104 | return found; 105 | } 106 | } 107 | None 108 | } 109 | } 110 | 111 | impl std::fmt::Debug for History<'_> { 112 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 113 | write!(f, "History of {}", self.head.id) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /git-storage/src/refdb.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 The Radicle Link Contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | //! The `refdb` is separated into two traits: [`Read`] and [`Write`], providing 5 | //! access to [git references][refs]. 6 | //! 7 | //! The [`Read`] trait provides functions for read-only access to the refdb. 8 | //! The [`Write`] trait provides functions for read and write access to the 9 | //! refdb, thus it implies the [`Read`] trait. 10 | //! 11 | //! The reason for separating these types of actions out is that one can infer 12 | //! what kind of access a function has to the refdb by looking at which trait it 13 | //! is using. 14 | //! 15 | //! For implementations of these traits, this crate provides [`crate::Read`] and 16 | //! [`crate::Write`] structs. 17 | //! 18 | //! [refs]: https://git-scm.com/book/en/v2/Git-Internals-Git-References 19 | 20 | use std::fmt::Debug; 21 | 22 | use git_ext::{ref_format::RefString, Oid}; 23 | 24 | pub mod iter; 25 | pub use iter::{References, ReferencesGlob}; 26 | 27 | pub mod read; 28 | pub use read::Read; 29 | 30 | pub mod write; 31 | pub use write::{previous, Applied, Policy, SymrefTarget, Update, Updated, Write}; 32 | 33 | /// A read-only structure representing a git reference. 34 | /// 35 | /// References can be constructed by using the [`Read`] functions: 36 | /// * [`Read::find_reference`] 37 | /// * [`Read::find_references`] 38 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 39 | pub struct Reference { 40 | /// The name of the reference. 41 | pub name: RefString, 42 | /// The target of the reference. 43 | pub target: Target, 44 | } 45 | 46 | impl<'a> TryFrom> for Reference { 47 | type Error = error::ParseReference; 48 | 49 | fn try_from(value: git2::Reference) -> Result { 50 | use error::ParseReference; 51 | 52 | let name = value 53 | .name() 54 | .ok_or(ParseReference::InvalidUtf8) 55 | .and_then(|name| RefString::try_from(name).map_err(ParseReference::from))?; 56 | let target = match value.target() { 57 | None => { 58 | let name = value 59 | .symbolic_target() 60 | .ok_or(ParseReference::InvalidUtf8) 61 | .and_then(|name| RefString::try_from(name).map_err(ParseReference::from))?; 62 | name.into() 63 | } 64 | Some(oid) => oid.into(), 65 | }; 66 | 67 | Ok(Reference { name, target }) 68 | } 69 | } 70 | 71 | /// The target a reference points to. 72 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 73 | pub enum Target { 74 | /// The reference points directly at an `oid`. 75 | Direct { oid: Oid }, 76 | /// The reference is symbolic and points to another reference. 77 | Symbolic { name: RefString }, 78 | } 79 | 80 | impl From for Target { 81 | fn from(oid: Oid) -> Self { 82 | Self::Direct { oid } 83 | } 84 | } 85 | 86 | impl From for Target { 87 | fn from(oid: git2::Oid) -> Self { 88 | Oid::from(oid).into() 89 | } 90 | } 91 | 92 | impl From for Target { 93 | fn from(name: RefString) -> Self { 94 | Self::Symbolic { name } 95 | } 96 | } 97 | 98 | pub mod error { 99 | use thiserror::Error; 100 | 101 | #[derive(Debug, Error)] 102 | pub enum ParseReference { 103 | #[error("reference name did contain valid UTF-8 bytes")] 104 | InvalidUtf8, 105 | #[error(transparent)] 106 | RefString(#[from] radicle_git_ext::ref_format::Error), 107 | } 108 | 109 | #[derive(Debug, Error)] 110 | pub enum Iter { 111 | #[error(transparent)] 112 | Git(#[from] git2::Error), 113 | #[error(transparent)] 114 | Parse(#[from] ParseReference), 115 | } 116 | } 117 | 118 | /// Utility function for resolving a [`Reference`] to its [`Oid`] 119 | pub(crate) fn resolve(repo: &git2::Repository, r: &Reference) -> Result { 120 | match &r.target { 121 | Target::Direct { oid } => Ok(*oid), 122 | Target::Symbolic { name } => repo.refname_to_id(name.as_str()).map(Oid::from), 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /archived/link-git/src/service.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 The Radicle Link Contributors 2 | // 3 | // This file is part of radicle-link, distributed under the GPLv3 with Radicle 4 | // Linking Exception. For full terms see the included LICENSE file. 5 | 6 | use std::{fmt::Debug, ops::Deref, str::FromStr}; 7 | 8 | use git2::transport::Service as GitService; 9 | use lazy_static::lazy_static; 10 | 11 | lazy_static! { 12 | static ref SERVICE_REGEX: regex::Regex = regex::Regex::new(r"(\S+) '/?(.+)'").unwrap(); 13 | } 14 | 15 | #[derive(Clone, Copy, PartialEq)] 16 | pub struct Service(pub GitService); 17 | 18 | /// A service and URN as passed to the exec_request of an SSH server by git when 19 | /// talking to an SSH remote. The `FromStr` implementation for this type expects 20 | /// a string of the form: 21 | /// 22 | /// ` /` 23 | /// 24 | /// Where the request type is either `upload-pack` or `receive-pack`, the 25 | /// leading slash before the urn is optional, and the `path` is whatever the 26 | /// `FromStr` of `Path` provides. 27 | #[derive(Debug, Clone)] 28 | pub struct SshService { 29 | pub service: Service, 30 | pub path: Path, 31 | } 32 | 33 | impl SshService { 34 | pub fn is_upload(&self) -> bool { 35 | match self.service.0 { 36 | GitService::UploadPackLs | GitService::UploadPack => true, 37 | GitService::ReceivePackLs | GitService::ReceivePack => false, 38 | } 39 | } 40 | 41 | pub fn is_receive(&self) -> bool { 42 | !self.is_upload() 43 | } 44 | } 45 | 46 | impl From for Service { 47 | fn from(g: GitService) -> Self { 48 | Service(g) 49 | } 50 | } 51 | 52 | impl From for GitService { 53 | fn from(s: Service) -> Self { 54 | s.0 55 | } 56 | } 57 | 58 | #[derive(thiserror::Error, Debug)] 59 | pub enum ParseService { 60 | #[error("the exec str must be in the form ")] 61 | Format, 62 | #[error(transparent)] 63 | Namespace(Box), 64 | #[error("unknown service {0}")] 65 | UnknownService(String), 66 | } 67 | 68 | impl Debug for Service { 69 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 70 | f.debug_tuple("Service") 71 | .field(match self.0 { 72 | GitService::UploadPackLs => &"UploadPackLs", 73 | GitService::UploadPack => &"UploadPack", 74 | GitService::ReceivePackLs => &"ReceivePackLs", 75 | GitService::ReceivePack => &"ReceivePack", 76 | }) 77 | .finish() 78 | } 79 | } 80 | 81 | impl std::fmt::Display for Service { 82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 83 | match self.0 { 84 | GitService::UploadPack => write!(f, "upload-pack"), 85 | GitService::UploadPackLs => write!(f, "upload-pack-ls"), 86 | GitService::ReceivePack => write!(f, "receive-pack"), 87 | GitService::ReceivePackLs => write!(f, "receive-pack-ls"), 88 | } 89 | } 90 | } 91 | 92 | impl Deref for Service { 93 | type Target = GitService; 94 | 95 | fn deref(&self) -> &Self::Target { 96 | &self.0 97 | } 98 | } 99 | 100 | impl FromStr for SshService 101 | where 102 | Path: FromStr, 103 | Path::Err: std::error::Error + Send + Sync + 'static, 104 | { 105 | type Err = ParseService; 106 | 107 | fn from_str(exec_str: &str) -> Result { 108 | let cap = SERVICE_REGEX 109 | .captures_iter(exec_str) 110 | .next() 111 | .ok_or(ParseService::Format)?; 112 | debug_assert!(cap.len() == 3); 113 | let service_str: &str = &cap[1]; 114 | let urn_str = &cap[2]; 115 | 116 | let path = urn_str 117 | .parse() 118 | .map_err(|err| ParseService::Namespace(Box::new(err)))?; 119 | let service = match service_str { 120 | "git-upload-pack" => Ok(Service(GitService::UploadPack)), 121 | "git-receive-pack" => Ok(Service(GitService::ReceivePack)), 122 | other => Err(ParseService::UnknownService(other.to_string())), 123 | }?; 124 | Ok(Self { service, path }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /archived/git-commit/src/author.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | num::ParseIntError, 4 | str::{self, FromStr}, 5 | }; 6 | 7 | use thiserror::Error; 8 | 9 | /// The data for indicating authorship of an action within a 10 | /// [`super::Commit`]. 11 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 12 | pub struct Author { 13 | /// Name corresponding to `user.name` in the git config. 14 | /// 15 | /// Note: this must not contain `<` or `>`. 16 | pub name: String, 17 | /// Email corresponding to `user.email` in the git config. 18 | /// 19 | /// Note: this must not contain `<` or `>`. 20 | pub email: String, 21 | /// The time of this author's action. 22 | pub time: Time, 23 | } 24 | 25 | /// The time of a [`Author`]'s action. 26 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 27 | pub struct Time { 28 | seconds: i64, 29 | offset: i32, 30 | } 31 | 32 | impl Time { 33 | pub fn new(seconds: i64, offset: i32) -> Self { 34 | Self { seconds, offset } 35 | } 36 | 37 | /// Return the time, in seconds, since the epoch. 38 | pub fn seconds(&self) -> i64 { 39 | self.seconds 40 | } 41 | 42 | /// Return the timezone offset, in minutes. 43 | pub fn offset(&self) -> i32 { 44 | self.offset 45 | } 46 | } 47 | 48 | impl From