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,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use terrors::OneOf;
|
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
|
/// 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 {
|
fn expose_secret(&self) -> &String {
|
||||||
self.0.expose_secret()
|
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 {
|
impl Display for RepoDetails {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
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 tracing::{info, warn};
|
||||||
|
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
|
@ -12,22 +13,19 @@ pub fn reset(
|
||||||
to_commit: GitRef,
|
to_commit: GitRef,
|
||||||
force: Force,
|
force: Force,
|
||||||
) -> BranchResetResult {
|
) -> BranchResetResult {
|
||||||
let user = &repo_details.forge.user;
|
let origin = repo_details.origin();
|
||||||
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 force = match force {
|
let force = match force {
|
||||||
Force::No => "".to_string(),
|
Force::No => "".to_string(),
|
||||||
Force::From(old_ref) => format!("--force-with-lease={branch_name}:{old_ref}"),
|
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'
|
// 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}");
|
let command: secrecy::Secret<String> = format!(
|
||||||
drop(origin);
|
"/usr/bin/git push {} {to_commit}:{branch_name} {force}",
|
||||||
|
origin.expose_secret()
|
||||||
|
)
|
||||||
|
.into();
|
||||||
info!("Resetting {branch_name} to {to_commit}");
|
info!("Resetting {branch_name} to {to_commit}");
|
||||||
match gix::command::prepare(command)
|
match gix::command::prepare(command.expose_secret())
|
||||||
.with_shell_allow_argument_splitting()
|
.with_shell_allow_argument_splitting()
|
||||||
.stdout(std::process::Stdio::null())
|
.stdout(std::process::Stdio::null())
|
||||||
.stderr(std::process::Stdio::null())
|
.stderr(std::process::Stdio::null())
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
mod branch;
|
mod branch;
|
||||||
mod file;
|
mod file;
|
||||||
|
mod repo;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
@ -9,7 +12,7 @@ use tracing::{error, warn};
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
actors::repo::RepoActor,
|
actors::repo::RepoActor,
|
||||||
config::{BranchName, RepoConfig, RepoDetails},
|
config::{BranchName, RepoConfig, RepoDetails},
|
||||||
gitforge,
|
gitforge::{self, RepoCloneError},
|
||||||
types::GitRef,
|
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)]
|
#[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::{
|
use crate::server::{
|
||||||
actors::repo::RepoActor,
|
actors::repo::RepoActor,
|
||||||
config::{BranchName, RepoConfig},
|
config::{BranchName, RepoConfig},
|
||||||
gitforge,
|
gitforge::{self, RepoCloneError},
|
||||||
types::GitRef,
|
types::GitRef,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,4 +53,8 @@ impl super::ForgeLike for MockForgeEnv {
|
||||||
async fn commit_status(&self, _commit: &gitforge::Commit) -> gitforge::CommitStatus {
|
async fn commit_status(&self, _commit: &gitforge::Commit) -> gitforge::CommitStatus {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn repo_clone(&self, _gitdir: PathBuf) -> Result<(), RepoCloneError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use kxio::network::Network;
|
use kxio::network::Network;
|
||||||
|
|
||||||
#[cfg(feature = "forgejo")]
|
#[cfg(feature = "forgejo")]
|
||||||
|
@ -24,17 +26,26 @@ use crate::server::{
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait ForgeLike {
|
pub trait ForgeLike {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
|
|
||||||
|
/// Returns a list of all branches in the repo.
|
||||||
async fn branches_get_all(&self) -> Result<Vec<Branch>, ForgeBranchError>;
|
async fn branches_get_all(&self) -> Result<Vec<Branch>, ForgeBranchError>;
|
||||||
|
|
||||||
|
/// Returns the contents of the file.
|
||||||
async fn file_contents_get(
|
async fn file_contents_get(
|
||||||
&self,
|
&self,
|
||||||
branch: &super::config::BranchName,
|
branch: &super::config::BranchName,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
) -> Result<String, ForgeFileError>;
|
) -> 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(
|
async fn branches_validate_positions(
|
||||||
&self,
|
&self,
|
||||||
repo_config: RepoConfig,
|
repo_config: RepoConfig,
|
||||||
addr: actix::prelude::Addr<super::actors::repo::RepoActor>,
|
addr: actix::prelude::Addr<super::actors::repo::RepoActor>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Moves a branch to a new commit.
|
||||||
fn branch_reset(
|
fn branch_reset(
|
||||||
&self,
|
&self,
|
||||||
branch_name: BranchName,
|
branch_name: BranchName,
|
||||||
|
@ -42,7 +53,11 @@ pub trait ForgeLike {
|
||||||
force: Force,
|
force: Force,
|
||||||
) -> BranchResetResult;
|
) -> BranchResetResult;
|
||||||
|
|
||||||
|
/// Checks the results of any (e.g. CI) status checks for the commit.
|
||||||
async fn commit_status(&self, commit: &Commit) -> CommitStatus;
|
async fn commit_status(&self, commit: &Commit) -> CommitStatus;
|
||||||
|
|
||||||
|
/// Clones a repo to disk.
|
||||||
|
fn repo_clone(&self, gitdir: PathBuf) -> Result<(), RepoCloneError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
Loading…
Reference in a new issue