Provide world Git identity, reflog fallback, object inspection, and which (T73)
This commit is contained in:
@@ -57,30 +57,54 @@ pub(crate) struct BranchDeleted {
|
||||
pub reference: String,
|
||||
}
|
||||
|
||||
/// Reflogs require an identity, but switching branches must not require configuring one.
|
||||
/// With no repository identity, leave reflogs untouched and report that explicitly; never invent an author.
|
||||
/// Reflog-only writes use a fallback identity when the repository has none.
|
||||
pub(crate) fn reflog_signature(repo: &gix::Repository) -> Result<gix::actor::Signature> {
|
||||
if let Some(signature) = repo
|
||||
.committer()
|
||||
.transpose()
|
||||
.map_err(|e| error("reflog identity", e))?
|
||||
{
|
||||
return signature
|
||||
.to_owned()
|
||||
.map_err(|e| error("reflog identity", e));
|
||||
}
|
||||
let config = repo.config_snapshot();
|
||||
let name = std::env::var("GIT_COMMITTER_NAME")
|
||||
.ok()
|
||||
.or_else(|| config.string("user.name").map(|s| s.to_string()))
|
||||
.or_else(|| std::env::var("USER").ok())
|
||||
.unwrap_or_else(|| "Sausage".into());
|
||||
let email = std::env::var("GIT_COMMITTER_EMAIL")
|
||||
.ok()
|
||||
.or_else(|| config.string("user.email").map(|s| s.to_string()))
|
||||
.unwrap_or_else(|| {
|
||||
format!(
|
||||
"{}@{}",
|
||||
std::env::var("USER").unwrap_or_else(|_| "sausage".into()),
|
||||
std::env::var("HOSTNAME").unwrap_or_else(|_| "localhost".into())
|
||||
)
|
||||
});
|
||||
let seconds = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
Ok(gix::actor::Signature {
|
||||
name: name.into(),
|
||||
email: email.into(),
|
||||
time: gix::date::Time::new(seconds, 0),
|
||||
})
|
||||
}
|
||||
|
||||
/// Publish references and their reflog entries without requiring a commit identity.
|
||||
pub(crate) fn edit_refs(
|
||||
repo: &gix::Repository,
|
||||
edits: Vec<gix::refs::transaction::RefEdit>,
|
||||
) -> Result<Vec<String>> {
|
||||
let committer = repo
|
||||
.committer()
|
||||
.transpose()
|
||||
.map_err(|e| error("reflog identity", e))?;
|
||||
if let Some(committer) = committer {
|
||||
repo.edit_references_as(edits, Some(committer))
|
||||
.map_err(|e| error("checkout refs", e))?;
|
||||
Ok(Vec::new())
|
||||
} else {
|
||||
let mut local = repo.clone();
|
||||
local.refs.write_reflog = gix::refs::store::WriteReflog::Disable;
|
||||
local
|
||||
.edit_references_as(edits, None)
|
||||
.map_err(|e| error("checkout refs", e))?;
|
||||
Ok(vec![
|
||||
"reflog not written: no repository committer identity configured".into(),
|
||||
])
|
||||
}
|
||||
let signature = reflog_signature(repo)?;
|
||||
let mut time = Default::default();
|
||||
repo.edit_references_as(edits, Some(signature.to_ref(&mut time)))
|
||||
.map_err(|e| error("checkout refs", e))?;
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
pub(crate) fn branch_name(name: &str) -> Result<String> {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
//! Read Git object types, sizes, contents, and tree entries without changing repository state.
|
||||
use crate::{Result, repo::error};
|
||||
use std::io::Write;
|
||||
|
||||
pub(crate) fn cat_file(
|
||||
repo: &gix::Repository,
|
||||
revision: &str,
|
||||
kind: bool,
|
||||
size: bool,
|
||||
exists: bool,
|
||||
) -> Result<()> {
|
||||
let object = repo
|
||||
.rev_parse_single(revision)
|
||||
.map_err(|e| error("cat-file", e))?
|
||||
.object()
|
||||
.map_err(|e| error("cat-file", e))?;
|
||||
if exists {
|
||||
return Ok(());
|
||||
}
|
||||
if kind {
|
||||
println!("{}", object.kind);
|
||||
} else if size {
|
||||
println!("{}", object.data.len());
|
||||
} else if object.kind == gix::objs::Kind::Tree {
|
||||
ls_tree(repo, revision, false, false, None)?;
|
||||
} else {
|
||||
std::io::stdout()
|
||||
.write_all(&object.data)
|
||||
.map_err(|e| error("cat-file", e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn ls_tree(
|
||||
repo: &gix::Repository,
|
||||
revision: &str,
|
||||
recursive: bool,
|
||||
names: bool,
|
||||
path: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let id = crate::local::tree(repo, revision)?;
|
||||
fn walk(
|
||||
repo: &gix::Repository,
|
||||
id: gix::ObjectId,
|
||||
prefix: &str,
|
||||
recursive: bool,
|
||||
names: bool,
|
||||
filter: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let tree = repo.find_tree(id).map_err(|e| error("ls-tree", e))?;
|
||||
for entry in tree.iter() {
|
||||
let entry = entry.map_err(|e| error("ls-tree", e))?;
|
||||
let filename = format!("{prefix}{}", entry.filename());
|
||||
let directory = entry.mode().is_tree();
|
||||
let descend = directory
|
||||
&& (recursive
|
||||
|| filter.is_some_and(|path| path.starts_with(&format!("{filename}/"))));
|
||||
if descend {
|
||||
walk(
|
||||
repo,
|
||||
entry.oid().to_owned(),
|
||||
&format!("{filename}/"),
|
||||
recursive,
|
||||
names,
|
||||
filter,
|
||||
)?;
|
||||
} else if filter.is_none_or(|path| {
|
||||
filename == path
|
||||
|| filename.starts_with(&format!("{}/", path.trim_end_matches('/')))
|
||||
}) {
|
||||
if names {
|
||||
println!("{filename}");
|
||||
} else {
|
||||
let kind = if directory {
|
||||
"tree"
|
||||
} else if entry.mode().is_commit() {
|
||||
"commit"
|
||||
} else {
|
||||
"blob"
|
||||
};
|
||||
println!(
|
||||
"{:06o} {kind} {}\t{filename}",
|
||||
entry.mode().kind() as u32,
|
||||
entry.oid()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
walk(repo, id, "", recursive, names, path)
|
||||
}
|
||||
+82
-5
@@ -59,6 +59,7 @@ fn identity(name: Option<String>, email: Option<String>) -> Result<Identity> {
|
||||
mod cherry_pick;
|
||||
mod history;
|
||||
mod local;
|
||||
mod inspect;
|
||||
mod plumbing;
|
||||
mod stash;
|
||||
use clap::{Parser, Subcommand};
|
||||
@@ -79,8 +80,32 @@ enum Command {
|
||||
Show {
|
||||
#[arg(long)]
|
||||
stat: bool,
|
||||
#[arg(long)]
|
||||
oneline: bool,
|
||||
#[arg(long)]
|
||||
name_only: bool,
|
||||
revision: Option<String>,
|
||||
},
|
||||
#[command(group(clap::ArgGroup::new("mode").required(true).multiple(false)))]
|
||||
CatFile {
|
||||
#[arg(short = 't', group = "mode")]
|
||||
kind: bool,
|
||||
#[arg(short = 'p', group = "mode")]
|
||||
pretty: bool,
|
||||
#[arg(short = 's', group = "mode")]
|
||||
size: bool,
|
||||
#[arg(short = 'e', group = "mode")]
|
||||
exists: bool,
|
||||
revision: String,
|
||||
},
|
||||
LsTree {
|
||||
#[arg(short = 'r')]
|
||||
recursive: bool,
|
||||
#[arg(long)]
|
||||
name_only: bool,
|
||||
revision: String,
|
||||
path: Option<String>,
|
||||
},
|
||||
Clone {
|
||||
url: String,
|
||||
path: Option<String>,
|
||||
@@ -257,6 +282,8 @@ enum Command {
|
||||
delete: bool,
|
||||
#[arg(short = 'D')]
|
||||
force_delete: bool,
|
||||
#[arg(short = 'f', long)]
|
||||
force: bool,
|
||||
#[arg(short = 'm')]
|
||||
rename: bool,
|
||||
name: Option<String>,
|
||||
@@ -1124,6 +1151,7 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
list,
|
||||
delete,
|
||||
force_delete,
|
||||
force,
|
||||
show_current,
|
||||
all,
|
||||
rename,
|
||||
@@ -1164,6 +1192,14 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
if !quiet {
|
||||
println!("Deleted branch {name} (was {}).", &id[..7]);
|
||||
}
|
||||
} else if force {
|
||||
let full = branches::branch_name(&name)?;
|
||||
worktrees::ensure_available(&repository, &full)?;
|
||||
if head_info(&repository)?.branch.as_deref() == Some(&full) {
|
||||
return Err(error("branch", "cannot force update current branch"));
|
||||
}
|
||||
let id = branches::resolve(&repository, start.as_deref().unwrap_or("HEAD"))?;
|
||||
plumbing::update_ref(&repository, &full, &id.to_string(), None)?;
|
||||
} else {
|
||||
branches::create(
|
||||
&repository,
|
||||
@@ -1488,7 +1524,35 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
)?;
|
||||
}
|
||||
},
|
||||
Command::Show { revision, stat } => {
|
||||
Command::CatFile {
|
||||
kind,
|
||||
pretty: _,
|
||||
size,
|
||||
exists,
|
||||
revision,
|
||||
} => {
|
||||
inspect::cat_file(&repository, &revision, kind, size, exists)?;
|
||||
}
|
||||
Command::LsTree {
|
||||
recursive,
|
||||
name_only,
|
||||
revision,
|
||||
path,
|
||||
} => {
|
||||
inspect::ls_tree(
|
||||
&repository,
|
||||
&revision,
|
||||
recursive,
|
||||
name_only,
|
||||
path.as_deref(),
|
||||
)?;
|
||||
}
|
||||
Command::Show {
|
||||
revision,
|
||||
stat,
|
||||
oneline,
|
||||
name_only,
|
||||
} => {
|
||||
let object = repository
|
||||
.rev_parse_single(revision.as_deref().unwrap_or("HEAD"))
|
||||
.map_err(|e| error("show", e))?
|
||||
@@ -1528,9 +1592,18 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
return Ok(129);
|
||||
}
|
||||
}
|
||||
output::commit(&commit)?;
|
||||
println!();
|
||||
if merge_commit && !stat {
|
||||
if oneline {
|
||||
let decoded = commit.decode().map_err(|e| error("show", e))?;
|
||||
println!(
|
||||
"{} {}",
|
||||
&commit.id.to_string()[..7],
|
||||
decoded.message.to_str_lossy().lines().next().unwrap_or("")
|
||||
);
|
||||
} else {
|
||||
output::commit(&commit)?;
|
||||
println!();
|
||||
}
|
||||
if merge_commit && !stat && !name_only {
|
||||
return Ok(0);
|
||||
}
|
||||
let old = match commit.parent_ids().next() {
|
||||
@@ -1548,7 +1621,11 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
path: None,
|
||||
context: None,
|
||||
})?;
|
||||
if stat {
|
||||
if name_only {
|
||||
for diff in diffs {
|
||||
println!("{}", diff.path);
|
||||
}
|
||||
} else if stat {
|
||||
output::stat(&diffs, false);
|
||||
} else {
|
||||
for diff in diffs {
|
||||
|
||||
@@ -118,7 +118,7 @@ pub(crate) fn reset(
|
||||
repo,
|
||||
Some(gix::ObjectId::from_hex(old.as_bytes()).map_err(|e| error("reset HEAD", e))?),
|
||||
id,
|
||||
&staging::signature(None, None)?,
|
||||
&branches::reflog_signature(repo)?,
|
||||
"reset",
|
||||
)?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user