feat(gitforge): Add ability to clone a repo
All checks were successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/cron/push-next Pipeline was successful
ci/woodpecker/cron/tag-created Pipeline was successful
ci/woodpecker/cron/cron-docker-builder Pipeline was successful
All checks were successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/cron/push-next Pipeline was successful
ci/woodpecker/cron/tag-created Pipeline was successful
ci/woodpecker/cron/cron-docker-builder Pipeline was successful
Closes kemitix/git-next#56
This commit is contained in:
parent
16dc823f58
commit
91870055b0
8 changed files with 110 additions and 14 deletions
|
@ -7,7 +7,6 @@ use std::{
|
|||
path::PathBuf,
|
||||
};
|
||||
|
||||
use secrecy::ExposeSecret;
|
||||
use serde::Deserialize;
|
||||
use terrors::OneOf;
|
||||
|
||||
|
@ -251,7 +250,7 @@ impl From<String> for ApiToken {
|
|||
}
|
||||
}
|
||||
/// The API Token is in effect a password, so it must be explicitly exposed to access its value
|
||||
impl ExposeSecret<String> for ApiToken {
|
||||
impl secrecy::ExposeSecret<String> for ApiToken {
|
||||
fn expose_secret(&self) -> &String {
|
||||
self.0.expose_secret()
|
||||
}
|
||||
|
@ -347,6 +346,17 @@ impl RepoDetails {
|
|||
},
|
||||
}
|
||||
}
|
||||
pub fn origin(&self) -> secrecy::Secret<String> {
|
||||
let repo_details = self;
|
||||
let user = &repo_details.forge.user;
|
||||
let hostname = &repo_details.forge.hostname;
|
||||
let path = &repo_details.repo;
|
||||
use secrecy::ExposeSecret;
|
||||
let expose_secret = &repo_details.forge.token;
|
||||
let token = expose_secret.expose_secret();
|
||||
let origin = format!("https://{user}:{token}@{hostname}/{path}.git");
|
||||
origin.into()
|
||||
}
|
||||
}
|
||||
impl Display for RepoDetails {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
|
|
|
@ -39,3 +39,20 @@ impl std::fmt::Display for ForgeBranchError {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RepoCloneError {
|
||||
InvalidGitDir(std::path::PathBuf),
|
||||
Wait(std::io::Error),
|
||||
Spawn(std::io::Error),
|
||||
}
|
||||
impl std::error::Error for RepoCloneError {}
|
||||
impl std::fmt::Display for RepoCloneError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidGitDir(gitdir) => write!(f, "Invalid Git dir: {:?}", gitdir),
|
||||
Self::Wait(err) => write!(f, "Waiting for command: {:?}", err),
|
||||
Self::Spawn(err) => write!(f, "Spawning comming: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use secrecy::ExposeSecret;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::server::{
|
||||
|
@ -12,22 +13,19 @@ pub fn reset(
|
|||
to_commit: GitRef,
|
||||
force: Force,
|
||||
) -> BranchResetResult {
|
||||
let user = &repo_details.forge.user;
|
||||
let hostname = &repo_details.forge.hostname;
|
||||
let path = &repo_details.repo;
|
||||
use secrecy::ExposeSecret;
|
||||
let expose_secret = &repo_details.forge.token;
|
||||
let token = expose_secret.expose_secret();
|
||||
let origin = format!("https://{user}:{token}@{hostname}/{path}.git");
|
||||
let origin = repo_details.origin();
|
||||
let force = match force {
|
||||
Force::No => "".to_string(),
|
||||
Force::From(old_ref) => format!("--force-with-lease={branch_name}:{old_ref}"),
|
||||
};
|
||||
// INFO: never log the command as it contains the API token within the 'origin'
|
||||
let command = format!("/usr/bin/git push {origin} {to_commit}:{branch_name} {force}");
|
||||
drop(origin);
|
||||
let command: secrecy::Secret<String> = format!(
|
||||
"/usr/bin/git push {} {to_commit}:{branch_name} {force}",
|
||||
origin.expose_secret()
|
||||
)
|
||||
.into();
|
||||
info!("Resetting {branch_name} to {to_commit}");
|
||||
match gix::command::prepare(command)
|
||||
match gix::command::prepare(command.expose_secret())
|
||||
.with_shell_allow_argument_splitting()
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
mod branch;
|
||||
mod file;
|
||||
mod repo;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use actix::prelude::*;
|
||||
|
||||
|
@ -9,7 +12,7 @@ use tracing::{error, warn};
|
|||
use crate::server::{
|
||||
actors::repo::RepoActor,
|
||||
config::{BranchName, RepoConfig, RepoDetails},
|
||||
gitforge,
|
||||
gitforge::{self, RepoCloneError},
|
||||
types::GitRef,
|
||||
};
|
||||
|
||||
|
@ -98,6 +101,10 @@ impl super::ForgeLike for ForgeJoEnv {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn repo_clone(&self, gitdir: PathBuf) -> Result<(), RepoCloneError> {
|
||||
repo::clone(&self.repo_details, gitdir)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
|
|
40
src/server/gitforge/forgejo/repo/clone.rs
Normal file
40
src/server/gitforge/forgejo/repo/clone.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::server::{config::RepoDetails, gitforge::RepoCloneError};
|
||||
|
||||
pub fn clone(repo_details: &RepoDetails, gitdir: PathBuf) -> Result<(), RepoCloneError> {
|
||||
let Some(gitdir) = gitdir.to_str() else {
|
||||
return Err(RepoCloneError::InvalidGitDir(gitdir));
|
||||
};
|
||||
let origin = repo_details.origin();
|
||||
// INFO: never log the command as it contains the API token within the 'origin'
|
||||
use secrecy::ExposeSecret;
|
||||
let command: secrecy::Secret<String> = format!(
|
||||
"/usr/bin/git clone --bare -- {} {:?}",
|
||||
origin.expose_secret(),
|
||||
gitdir
|
||||
)
|
||||
.into();
|
||||
let repo_name = &repo_details.name;
|
||||
info!("Cloning {repo_name} to {gitdir}");
|
||||
match gix::command::prepare(command.expose_secret())
|
||||
.with_shell_allow_argument_splitting()
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()
|
||||
{
|
||||
Ok(mut child) => match child.wait() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
warn!(?err, "Failed (wait)");
|
||||
Err(RepoCloneError::Wait(err))
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(?err, "Failed (spawn)");
|
||||
Err(RepoCloneError::Spawn(err))
|
||||
}
|
||||
}
|
||||
}
|
3
src/server/gitforge/forgejo/repo/mod.rs
Normal file
3
src/server/gitforge/forgejo/repo/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod clone;
|
||||
|
||||
pub use clone::clone;
|
|
@ -1,7 +1,9 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::server::{
|
||||
actors::repo::RepoActor,
|
||||
config::{BranchName, RepoConfig},
|
||||
gitforge,
|
||||
gitforge::{self, RepoCloneError},
|
||||
types::GitRef,
|
||||
};
|
||||
|
||||
|
@ -51,4 +53,8 @@ impl super::ForgeLike for MockForgeEnv {
|
|||
async fn commit_status(&self, _commit: &gitforge::Commit) -> gitforge::CommitStatus {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn repo_clone(&self, _gitdir: PathBuf) -> Result<(), RepoCloneError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use kxio::network::Network;
|
||||
|
||||
#[cfg(feature = "forgejo")]
|
||||
|
@ -24,17 +26,26 @@ use crate::server::{
|
|||
#[async_trait::async_trait]
|
||||
pub trait ForgeLike {
|
||||
fn name(&self) -> String;
|
||||
|
||||
/// Returns a list of all branches in the repo.
|
||||
async fn branches_get_all(&self) -> Result<Vec<Branch>, ForgeBranchError>;
|
||||
|
||||
/// Returns the contents of the file.
|
||||
async fn file_contents_get(
|
||||
&self,
|
||||
branch: &super::config::BranchName,
|
||||
file_path: &str,
|
||||
) -> Result<String, ForgeFileError>;
|
||||
|
||||
/// Assesses the relative positions of the main, next and dev branch and updates their
|
||||
/// positions as needed.
|
||||
async fn branches_validate_positions(
|
||||
&self,
|
||||
repo_config: RepoConfig,
|
||||
addr: actix::prelude::Addr<super::actors::repo::RepoActor>,
|
||||
);
|
||||
|
||||
/// Moves a branch to a new commit.
|
||||
fn branch_reset(
|
||||
&self,
|
||||
branch_name: BranchName,
|
||||
|
@ -42,7 +53,11 @@ pub trait ForgeLike {
|
|||
force: Force,
|
||||
) -> BranchResetResult;
|
||||
|
||||
/// Checks the results of any (e.g. CI) status checks for the commit.
|
||||
async fn commit_status(&self, commit: &Commit) -> CommitStatus;
|
||||
|
||||
/// Clones a repo to disk.
|
||||
fn repo_clone(&self, gitdir: PathBuf) -> Result<(), RepoCloneError>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
Loading…
Reference in a new issue