1
0
Fork 0

Adding upstream version 0.5.5.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-05 05:16:34 +01:00
parent dde4be91ba
commit d2d6608958
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
17 changed files with 2615 additions and 0 deletions

View file

@ -0,0 +1,139 @@
use std::path::{Path, PathBuf};
#[derive(Copy, Clone)]
struct YadPrompter;
impl auth_git2::Prompter for YadPrompter {
fn prompt_username_password(&mut self, url: &str, _git_config: &git2::Config) -> Option<(String, String)> {
let mut items = yad_prompt(
"Git authentication",
&format!("Authentication required for {url}"),
&["Username", "Password:H"],
).ok()?.into_iter();
let username = items.next()?;
let password = items.next()?;
Some((username, password))
}
fn prompt_password(&mut self, username: &str, url: &str, _git_config: &git2::Config) -> Option<String> {
let mut items = yad_prompt(
"Git authentication",
&format!("Authentication required for {url}"),
&[&format!("Username: {username}:LBL"), "Password:H"],
).ok()?.into_iter();
let password = items.next()?;
Some(password)
}
fn prompt_ssh_key_passphrase(&mut self, private_key_path: &std::path::Path, _git_config: &git2::Config) -> Option<String> {
let mut items = yad_prompt(
"Git authentication",
&format!("Passphrase required for {}", private_key_path.display()),
&["Passphrase:H"],
).ok()?.into_iter();
let passphrase = items.next()?;
Some(passphrase)
}
}
fn yad_prompt(title: &str, text: &str, fields: &[&str]) -> Result<Vec<String>, ()> {
let mut command = std::process::Command::new("yad");
command
.arg("--title")
.arg(title)
.arg("--text")
.arg(text)
.arg("--form")
.arg("--separator=\n");
for field in fields {
command.arg("--field");
command.arg(field);
}
let output = command
.stderr(std::process::Stdio::inherit())
.output()
.map_err(|e| log::error!("Failed to run `yad`: {e}"))?;
if !output.status.success() {
log::debug!("yad exited with {}", output.status);
return Err(());
}
let output = String::from_utf8(output.stdout)
.map_err(|_| log::warn!("Invalid UTF-8 in response from yad"))?;
let mut items: Vec<_> = output.splitn(fields.len() + 1, '\n')
.take(fields.len())
.map(|x| x.to_owned())
.collect();
if let Some(last) = items.pop() {
if !last.is_empty() {
items.push(last)
}
}
if items.len() != fields.len() {
log::error!("asked yad for {} values but got only {}", fields.len(), items.len());
Err(())
} else {
Ok(items)
}
}
#[derive(clap::Parser)]
struct Options {
/// Show more verbose statement.
#[clap(long, short)]
#[clap(global = true)]
#[clap(action = clap::ArgAction::Count)]
verbose: u8,
/// The URL of the repository to clone.
#[clap(value_name = "URL")]
repo: String,
/// The path where to clone the repository.
#[clap(value_name = "PATH")]
local_path: Option<PathBuf>,
}
fn main() {
if let Err(()) = do_main(clap::Parser::parse()) {
std::process::exit(1);
}
}
fn log_level(verbose: u8) -> log::LevelFilter {
match verbose {
0 => log::LevelFilter::Info,
1 => log::LevelFilter::Debug,
2.. => log::LevelFilter::Trace,
}
}
fn do_main(options: Options) -> Result<(), ()> {
let log_level = log_level(options.verbose);
env_logger::builder()
.parse_default_env()
.filter_module(module_path!(), log_level)
.filter_module("auth_git2", log_level)
.init();
let local_path = options.local_path.as_deref()
.unwrap_or_else(|| Path::new(repo_name_from_url(&options.repo)));
log::info!("Cloning {} into {}", options.repo, local_path.display());
let auth = auth_git2::GitAuthenticator::default()
.set_prompter(YadPrompter);
auth.clone_repo(&options.repo, local_path)
.map_err(|e| log::error!("Failed to clone {}: {}", options.repo, e))?;
Ok(())
}
fn repo_name_from_url(url: &str) -> &str {
url.rsplit_once('/')
.map(|(_head, tail)| tail)
.unwrap_or(url)
}

146
examples/git.rs Normal file
View file

@ -0,0 +1,146 @@
use std::path::{Path, PathBuf};
#[derive(clap::Parser)]
struct Options {
/// Show more verbose statement.
#[clap(long, short)]
#[clap(global = true)]
#[clap(action = clap::ArgAction::Count)]
verbose: u8,
/// The subcommand.
#[clap(subcommand)]
command: Command,
}
#[derive(clap::Subcommand)]
enum Command {
Clone(CloneCommand),
Fetch(FetchCommand),
Push(PushCommand),
}
/// Clone a repository.
#[derive(clap::Parser)]
struct CloneCommand {
/// The URL of the repository to clone.
#[clap(value_name = "URL")]
repo: String,
/// The path where to clone the repository.
#[clap(value_name = "PATH")]
local_path: Option<PathBuf>,
}
/// Fetch from a remote.
#[derive(clap::Parser)]
struct FetchCommand {
/// The repository to operate on.
#[clap(value_name = "PATH")]
#[clap(short = 'C', long)]
repo: PathBuf,
/// The repository to operate on.
#[clap(value_name = "REMOTE")]
remote: String,
/// The refs to fetch.
#[clap(trailing_var_arg = true)]
#[clap(required = true)]
refspec: Vec<String>,
}
/// Push to a remote.
#[derive(clap::Parser)]
struct PushCommand {
/// The repository to operate on.
#[clap(value_name = "PATH")]
#[clap(short = 'C', long)]
#[clap(default_value = ".")]
repo: PathBuf,
/// The repository to operate on.
#[clap(value_name = "REMOTE")]
remote: String,
/// The refs to fetch.
#[clap(trailing_var_arg = true)]
#[clap(required = true)]
refspec: Vec<String>,
}
fn main() {
if let Err(()) = do_main(clap::Parser::parse()) {
std::process::exit(1);
}
}
fn log_level(verbose: u8) -> log::LevelFilter {
match verbose {
0 => log::LevelFilter::Info,
1 => log::LevelFilter::Debug,
2.. => log::LevelFilter::Trace,
}
}
fn do_main(options: Options) -> Result<(), ()> {
let log_level = log_level(options.verbose);
env_logger::builder()
.parse_default_env()
.filter_module(module_path!(), log_level)
.filter_module("auth_git2", log_level)
.init();
match options.command {
Command::Clone(command) => clone(command),
Command::Fetch(command) => fetch(command),
Command::Push(command) => push(command),
}
}
fn clone(command: CloneCommand) -> Result<(), ()> {
let local_path = command.local_path.as_deref()
.unwrap_or_else(|| Path::new(repo_name_from_url(&command.repo)));
log::info!("Cloning {} into {}", command.repo, local_path.display());
let auth = auth_git2::GitAuthenticator::default();
auth.clone_repo(&command.repo, local_path)
.map_err(|e| log::error!("Failed to clone {}: {}", command.repo, e))?;
Ok(())
}
fn fetch(command: FetchCommand) -> Result<(), ()> {
let repo = git2::Repository::open(&command.repo)
.map_err(|e| log::error!("Failed to open git repo at {}: {e}", command.repo.display()))?;
let refspecs: Vec<_> = command.refspec.iter().map(|x| x.as_str()).collect();
let auth = auth_git2::GitAuthenticator::default();
let mut remote = repo.find_remote(&command.remote)
.map_err(|e| log::error!("Failed to find remote {:?}: {e}", command.remote))?;
auth.fetch(&repo, &mut remote, &refspecs, None)
.map_err(|e| log::error!("Failed to fetch from remote {:?}: {e}", command.remote))?;
Ok(())
}
fn push(command: PushCommand) -> Result<(), ()> {
let repo = git2::Repository::open(&command.repo)
.map_err(|e| log::error!("Failed to open git repo at {}: {e}", command.repo.display()))?;
log::info!("Fetching {:?} from remote {:?}", command.refspec, command.remote);
let refspecs: Vec<_> = command.refspec.iter().map(|x| x.as_str()).collect();
let auth = auth_git2::GitAuthenticator::default();
let mut remote = repo.find_remote(&command.remote)
.map_err(|e| log::error!("Failed to find remote {:?}: {e}", command.remote))?;
auth.push(&repo, &mut remote, &refspecs,)
.map_err(|e| log::error!("Failed to push to remote {:?}: {e}", command.remote))?;
Ok(())
}
fn repo_name_from_url(url: &str) -> &str {
url.rsplit_once('/')
.map(|(_head, tail)| tail)
.unwrap_or(url)
}