diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4564f4dc0..c514077a7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,11 +2,11 @@ name: CI on: schedule: - - cron: '0 2 * * *' # run at 2 AM UTC + - cron: "0 2 * * *" # run at 2 AM UTC push: - branches: [ '*' ] + branches: ["*"] pull_request: - branches: [ master ] + branches: [master] env: CARGO_TERM_COLOR: always @@ -17,151 +17,163 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - rust: [nightly, stable, '1.50'] + rust: [nightly, stable, "1.50"] runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.rust == 'nightly' }} steps: - - uses: actions/checkout@v2 - - - name: Restore cargo cache - uses: actions/cache@v2 - env: - cache-name: ci - with: - path: | - ~/.cargo/registry - ~/.cargo/git - ~/.cargo/bin - target - key: ${{ matrix.os }}-${{ env.cache-name }}-${{ matrix.rust }}-${{ hashFiles('Cargo.lock') }} - - - name: MacOS Workaround - if: matrix.os == 'macos-latest' - run: cargo clean -p serde_derive -p thiserror - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - default: true - profile: minimal - components: clippy - - - name: New Resolver - if: matrix.rust != '1.50' - run: | - cargo install cargo-modify --force - cargo modify new-resolver - - - name: Build Debug - run: | - cargo build - - - name: Run tests - run: make test - - - name: Run clippy - run: | - make clippy - - - name: Build Release - run: make build-release - - - name: Binary Size (unix) - if: matrix.os != 'windows-latest' - run: | - ls -l ./target/release/gitui - - - name: Binary Size (win) - if: matrix.os == 'windows-latest' - run: | - ls -l ./target/release/gitui.exe - - - name: Binary dependencies (mac) - if: matrix.os == 'macos-latest' - run: | - otool -L ./target/release/gitui - - - name: Build MSI (windows) - if: matrix.os == 'windows-latest' - run: | - cargo install cargo-wix - cargo wix --no-build --nocapture --output ./target/wix/gitui.msi - ls -l ./target/wix/gitui.msi + - uses: actions/checkout@v2 + + - name: Install gpgme (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: sudo apt-get install -y --no-install-recommends libgpgme11-dev + + - name: Install gpgme (MacOS) + if: startsWith(matrix.os, 'macos') + run: brew install gpgme + + - name: Install gpgme (Windows) + if: startsWith(matrix.os, 'windows') + run: choco install gpg4win + + - name: Restore cargo cache + uses: actions/cache@v2 + env: + cache-name: ci + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ~/.cargo/bin + target + key: ${{ matrix.os }}-${{ env.cache-name }}-${{ matrix.rust }}-${{ hashFiles('Cargo.lock') }} + + - name: MacOS Workaround + if: matrix.os == 'macos-latest' + run: cargo clean -p serde_derive -p thiserror + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + default: true + profile: minimal + components: clippy + + - name: New Resolver + if: matrix.rust != '1.50' + run: | + cargo install cargo-modify --force + cargo modify new-resolver + + - name: Build Debug + run: | + cargo build + + - name: Run tests + run: make test + + - name: Run clippy + run: | + make clippy + + - name: Build Release + run: make build-release + + - name: Binary Size (unix) + if: matrix.os != 'windows-latest' + run: | + ls -l ./target/release/gitui + + - name: Binary Size (win) + if: matrix.os == 'windows-latest' + run: | + ls -l ./target/release/gitui.exe + + - name: Binary dependencies (mac) + if: matrix.os == 'macos-latest' + run: | + otool -L ./target/release/gitui + + - name: Build MSI (windows) + if: matrix.os == 'windows-latest' + run: | + cargo install cargo-wix + cargo wix --no-build --nocapture --output ./target/wix/gitui.msi + ls -l ./target/wix/gitui.msi build-linux-musl: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - rust: [nightly, stable, '1.50'] + rust: [nightly, stable, "1.50"] continue-on-error: ${{ matrix.rust == 'nightly' }} steps: - - uses: actions/checkout@master - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - profile: minimal - default: true - target: x86_64-unknown-linux-musl - - # TODO: remove once we depend on 1.51 as a msrv and resolver is supported by default - - name: New Resolver - if: matrix.rust != '1.50' - run: | - cargo install cargo-modify --force - cargo modify new-resolver - - - name: Setup MUSL - run: | - sudo apt-get -qq install musl-tools - - name: Build Debug - run: | - make build-linux-musl-debug - ./target/x86_64-unknown-linux-musl/debug/gitui --version - - name: Build Release - run: | - make build-linux-musl-release - ./target/x86_64-unknown-linux-musl/release/gitui --version - ls -l ./target/x86_64-unknown-linux-musl/release/gitui - - name: Test - run: | - make test-linux-musl + - uses: actions/checkout@master + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + profile: minimal + default: true + target: x86_64-unknown-linux-musl + + # TODO: remove once we depend on 1.51 as a msrv and resolver is supported by default + - name: New Resolver + if: matrix.rust != '1.50' + run: | + cargo install cargo-modify --force + cargo modify new-resolver + + - name: Setup MUSL + run: | + sudo apt-get -qq install musl-tools + - name: Build Debug + run: | + make build-linux-musl-debug + ./target/x86_64-unknown-linux-musl/debug/gitui --version + - name: Build Release + run: | + make build-linux-musl-release + ./target/x86_64-unknown-linux-musl/release/gitui --version + ls -l ./target/x86_64-unknown-linux-musl/release/gitui + - name: Test + run: | + make test-linux-musl rustfmt: name: Rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt - - run: cargo fmt -- --check + - uses: actions/checkout@master + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + - run: cargo fmt -- --check sec: name: Security audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/audit-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v2 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} log-test: name: Changelog Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: Extract release notes - id: extract_release_notes - uses: ffurrer2/extract-release-notes@v1 - with: - release_notes_file: ./release-notes.txt - - uses: actions/upload-artifact@v1 - with: - name: release-notes.txt - path: ./release-notes.txt \ No newline at end of file + - uses: actions/checkout@master + - name: Extract release notes + id: extract_release_notes + uses: ffurrer2/extract-release-notes@v1 + with: + release_notes_file: ./release-notes.txt + - uses: actions/upload-artifact@v1 + with: + name: release-notes.txt + path: ./release-notes.txt diff --git a/Cargo.lock b/Cargo.lock index 7ffd1f63d4..6c52ddc6f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,7 @@ dependencies = [ "crossbeam-channel", "easy-cast", "git2", + "gpgme", "invalidstring", "log", "openssl-sys", @@ -106,7 +107,7 @@ checksum = "b7815ea54e4d821e791162e078acbebfd6d8c8939cd559c9335dceb1c8ca7282" dependencies = [ "addr2line", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object", @@ -187,6 +188,12 @@ dependencies = [ "jobserver", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -217,13 +224,22 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + [[package]] name = "cpp_demangle" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea47428dc9d2237f3c6bc134472edfd63ebba0af932e783506dcfd66f10d18a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -232,7 +248,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -241,7 +257,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -251,7 +267,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] @@ -262,7 +278,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", "memoffset", @@ -275,7 +291,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] @@ -305,6 +321,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "cstr-argument" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20bd4e8067c20c7c3a4dea759ef91d4b18418ddb5bd8837ef6e2f2f93ca7ccbb" +dependencies = [ + "cfg-if 0.1.10", + "memchr", +] + [[package]] name = "ctor" version = "0.1.20" @@ -315,6 +341,12 @@ dependencies = [ "syn", ] +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + [[package]] name = "debugid" version = "0.7.2" @@ -336,7 +368,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] @@ -388,7 +420,7 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crc32fast", "libc", "miniz_oxide", @@ -416,7 +448,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -481,6 +513,7 @@ dependencies = [ "dirs-next", "easy-cast", "filetreelist", + "gpgme", "itertools", "lazy_static", "log", @@ -501,6 +534,42 @@ dependencies = [ "which", ] +[[package]] +name = "gpg-error" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7073b9ac823434ae73608715086e944d694a7ce2677371b8c5253300d1f767f1" +dependencies = [ + "libgpg-error-sys", +] + +[[package]] +name = "gpgme" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528b8b981855eb0c74696a408919b1bf5dbe7f4cd99261bb1b47097d9bf3478f" +dependencies = [ + "bitflags", + "conv", + "cstr-argument", + "gpg-error", + "gpgme-sys", + "libc", + "once_cell", + "smallvec", + "static_assertions", +] + +[[package]] +name = "gpgme-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b293be40a67cf00d4cdbefc8bc87c10d2b9581fcb9ac226084c53a556f2e31f" +dependencies = [ + "libc", + "libgpg-error-sys", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -561,7 +630,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -624,6 +693,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libgpg-error-sys" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1aedf0efc5d25fdd08eb52b0759c71d02ac77fd1879b96e95211239528897" +dependencies = [ + "libc", + "winreg", +] + [[package]] name = "libssh2-sys" version = "0.2.21" @@ -680,7 +759,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -754,7 +833,7 @@ checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" dependencies = [ "bitflags", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", ] @@ -882,7 +961,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall", @@ -1250,6 +1329,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "str_stack" version = "0.1.0" @@ -1328,7 +1413,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "rand", "redox_syscall", @@ -1557,6 +1642,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + [[package]] name = "xml-rs" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index 65d793424c..32ddd06b28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,28 +4,22 @@ version = "0.16.2" authors = ["Stephan Dilly "] description = "blazing fast terminal-ui for git" edition = "2018" -exclude = [".github/*",".vscode/*","assets/*"] +exclude = [".github/*", ".vscode/*", "assets/*"] homepage = "https://github.com/extrawurst/gitui" repository = "https://github.com/extrawurst/gitui" readme = "README.md" license = "MIT" categories = ["command-line-utilities"] -keywords = [ - "git", - "gui", - "cli", - "terminal", - "ui", -] +keywords = ["git", "gui", "cli", "terminal", "ui"] [dependencies] scopetime = { path = "./scopetime", version = "0.1" } asyncgit = { path = "./asyncgit", version = "0.16" } filetreelist = { path = "./filetreelist", version = "0.2" } -crossterm = { version = "0.19", features = [ "serde" ] } +crossterm = { version = "0.19", features = ["serde"] } clap = { version = "2.33", default-features = false } tui = { version = "0.15", default-features = false, features = ['crossterm', 'serde'] } -bytesize = { version = "1.0", default-features = false} +bytesize = { version = "1.0", default-features = false } itertools = "0.10" rayon-core = "1.9" log = "0.4" @@ -46,7 +40,8 @@ unicode-segmentation = "1.7" easy-cast = "0.4" bugreport = "0.4" lazy_static = "1.4" -syntect = { version = "4.5", default-features = false, features = ["metadata", "default-fancy"]} +syntect = { version = "4.5", default-features = false, features = ["metadata", "default-fancy"] } +gpgme = "0.9.2" [target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies] which = "4.1" @@ -62,26 +57,22 @@ pretty_assertions = "0.7" maintenance = { status = "actively-developed" } [features] -default=[] -timing=["scopetime/enabled"] +default = [] +timing = ["scopetime/enabled"] [workspace] -members=[ - "asyncgit", - "scopetime", - "filetreelist", -] +members = ["asyncgit", "scopetime", "filetreelist"] [profile.release] lto = true -opt-level = 'z' # Optimize for size. +opt-level = 'z' # Optimize for size. codegen-units = 1 -# make debug build as fast as release -# usage of utf8 encoding inside tui +# make debug build as fast as release +# usage of utf8 encoding inside tui # makes their debug profile slow [profile.dev.package."tui"] opt-level = 3 [profile.dev] -split-debuginfo = "unpacked" \ No newline at end of file +split-debuginfo = "unpacked" diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml index a45490945b..72cd58a8ef 100644 --- a/asyncgit/Cargo.toml +++ b/asyncgit/Cargo.toml @@ -8,14 +8,14 @@ homepage = "https://github.com/extrawurst/gitui" repository = "https://github.com/extrawurst/gitui" readme = "README.md" license = "MIT" -categories = ["concurrency","asynchronous"] +categories = ["concurrency", "asynchronous"] keywords = ["git"] [dependencies] scopetime = { path = "../scopetime", version = "0.1" } git2 = "0.13" # pinning to vendored openssl, using the git2 feature this gets lost with new resolver -openssl-sys = { version = '0.9', features= ["vendored"] } +openssl-sys = { version = '0.9', features = ["vendored"] } # git2 = { path = "../../github/git2-rs", features = ["vendored-openssl"]} # git2 = { git="https://github.com/extrawurst/git2-rs.git", rev="513a8c9", features = ["vendored-openssl"]} rayon-core = "1.9" @@ -25,9 +25,10 @@ thiserror = "1.0" url = "2.2" unicode-truncate = "0.2.0" easy-cast = "0.4" +gpgme = "0.9.2" [dev-dependencies] tempfile = "3.2" invalidstring = { path = "../invalidstring", version = "0.1" } serial_test = "0.5.1" -pretty_assertions = "0.7" \ No newline at end of file +pretty_assertions = "0.7" diff --git a/asyncgit/src/error.rs b/asyncgit/src/error.rs index 80c490fe31..8dcd1d1c5f 100644 --- a/asyncgit/src/error.rs +++ b/asyncgit/src/error.rs @@ -35,6 +35,9 @@ pub enum Error { #[error("git error:{0}")] Git(#[from] git2::Error), + #[error("gpgme error:{0}")] + Gpgme(#[from] gpgme::Error), + #[error("utf8 error:{0}")] Utf8Conversion(#[from] FromUtf8Error), diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs index 6b0c4fba1f..dc1ea6aeff 100644 --- a/asyncgit/src/sync/commit.rs +++ b/asyncgit/src/sync/commit.rs @@ -1,6 +1,12 @@ +use std::fs; + use super::{utils::repo, CommitId}; use crate::{error::Result, sync::utils::get_head_repo}; -use git2::{ErrorCode, ObjectType, Repository, Signature}; +use git2::{ + Buf, ErrorClass, ErrorCode, ObjectType, Oid, Repository, + Signature, +}; +use gpgme::{Context, Protocol}; use scopetime::scope_time; /// @@ -18,16 +24,43 @@ pub fn amend( let tree_id = index.write_tree()?; let tree = repo.find_tree(tree_id)?; - let new_id = commit.amend( - Some("HEAD"), - None, - None, - None, - Some(msg), - Some(&tree), - )?; + let parents = commit.parents().collect::>(); + let parents = parents.iter().collect::>(); + + if let Some(parent) = parents.first() { + repo.set_head_detached(parent.id())?; + } + + let commit_id = if sign_enabled(&repo)? { + let buffer = repo.commit_create_buffer( + &commit.author(), + &commit.committer(), + msg, + &tree, + &parents, + )?; - Ok(CommitId::new(new_id)) + let signature = sign(&repo, &buffer)?; + + repo.commit_signed( + &String::from_utf8(buffer.to_vec())?, + &signature, + None, + )? + } else { + repo.commit( + None, + &commit.author(), + &commit.committer(), + msg, + &tree, + &parents, + )? + }; + + update_head(&repo, commit_id, " (amend)")?; + + Ok(commit_id.into()) } /// Wrap `Repository::signature` to allow unknown user.name. @@ -76,16 +109,35 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { let parents = parents.iter().collect::>(); - Ok(repo - .commit( - Some("HEAD"), + let commit_id = if sign_enabled(&repo)? { + let buffer = repo.commit_create_buffer( + &signature, + &signature, + msg, + &tree, + parents.as_slice(), + )?; + let signature = sign(&repo, &buffer)?; + + repo.commit_signed( + &String::from_utf8(buffer.to_vec())?, + &signature, + None, + )? + } else { + repo.commit( + None, &signature, &signature, msg, &tree, parents.as_slice(), )? - .into()) + }; + + update_head(&repo, commit_id, "")?; + + Ok(commit_id.into()) } /// Tag a commit. @@ -109,9 +161,90 @@ pub fn tag( Ok(repo.tag(tag, &target, &signature, "", false)?.into()) } +/// Sign a commit with [`gpgme`]. +fn sign(repo: &Repository, buffer: &Buf) -> Result { + let mut context = Context::from_protocol(Protocol::OpenPgp)?; + context.set_armor(true); + + if let Ok(signing_key) = repo + .config() + .and_then(|cfg| cfg.get_string("user.signingkey")) + { + let key = context.get_secret_key(signing_key)?; + context.add_signer(&key)?; + } + + let mut signature = Vec::new(); + + context.sign_detached(buffer.as_ref(), &mut signature)?; + + String::from_utf8(signature).map_err(Into::into) +} + +/// Check whether commit signing is enabled in the Git config. This copes for the case where the +/// config entry is missing, which is not considered an error but instead counts as signing being +/// disabled. +fn sign_enabled(repo: &Repository) -> Result { + match repo.config()?.get_bool("commit.gpgsign") { + Ok(value) => Ok(value), + Err(e) + if e.class() == ErrorClass::Config + && e.code() == ErrorCode::NotFound => + { + Ok(false) + } + Err(e) => Err(e.into()), + } +} + +/// Update the current HEAD, and the reference it's pointing to, to the give commit. Based on the +/// state of the repository the current head is either: +/// +/// - Available through and **resolvable** through means of git directly. +/// - Extracted from the `.git/HEAD` file in case the current HEAD can't be resolved. +/// +/// The HEAD can usually be resolved but in case the repository is fresh, meaning it doesn't have +/// any commits so far, it can't because the reference HEAD is pointing to doesn't exist yet. +/// +/// Unfortunately, the [`git2`] crate doesn't provide a way to retrieve the current HEAD without +/// resolving it and the data must be extracted manually from the repo data. +fn update_head( + repo: &Repository, + commit_id: Oid, + commit_type: &str, +) -> Result<()> { + let head_name = match repo.head() { + Ok(r) => r.name().unwrap_or_default().to_owned(), + Err(e) + if e.class() == ErrorClass::Reference + && e.code() == ErrorCode::UnbornBranch => + { + // TODO: Check for the possible formats that can be present in the HEAD file and + // make sure we really get the typical `refs/heads/main` reference that we expect or + // fail otherwise. + let head = fs::read_to_string(repo.path().join("HEAD"))?; + head.strip_prefix("ref:") + .unwrap_or(&head) + .trim() + .to_owned() + } + Err(e) => return Err(e.into()), + }; + let reflog_msg = format!( + "commit{}: {}", + commit_type, + repo.find_commit(commit_id)?.summary().unwrap_or_default() + ); + + let new_head = + repo.reference(&head_name, commit_id, true, &reflog_msg)?; + + repo.set_head(new_head.name().unwrap_or_default()) + .map_err(Into::into) +} + #[cfg(test)] mod tests { - use crate::error::Result; use crate::sync::{ commit, get_commit_details, get_commit_files, stage_add_file,