use crate::git::GitError;
use crate::git::repo::Repo;
pub struct Change<'a> {
pub additions: &'a [(String, String)],
pub deletions: &'a [String],
pub message: &'a str,
pub login: Option<&'a str>,
}
pub async fn blob(repo: &Repo, rev: &str, path: &str) -> Option<String> {
let spec = format!("{rev}:{path}");
let args = ["rev-parse", "--verify", "--quiet", &spec];
let out = repo.git(&args, &[], None).await;
out.ok().map(|o| o.trim().to_owned())
}
pub fn moved_on(err: &GitError) -> bool {
matches!(err, GitError::Failed { stderr, .. }
if stderr.contains("[rejected]") || stderr.contains("fetch first"))
}
async fn mode(repo: &Repo, rev: &str, path: &str) -> String {
let out = repo.git(&["ls-tree", rev, "--", path], &[], None).await;
out.ok()
.and_then(|o| o.split_whitespace().next().map(str::to_owned))
.filter(|m| m == "100755")
.unwrap_or_else(|| "100644".to_owned())
}
pub async fn make(repo: &Repo, parent: &str, change: &Change<'_>) -> Result<String, GitError> {
let index = repo
.git(&["rev-parse", "--git-path", "ghtui-edit-index"], &[], None)
.await?;
let index = repo.path.join(index.trim());
let index = index.with_file_name(format!("ghtui-edit-index-{}", std::process::id()));
let index_path = index.to_string_lossy().into_owned();
let env = [("GIT_INDEX_FILE", index_path.as_str())];
let tree = async {
repo.git(&["read-tree", parent], &env, None).await?;
for path in change.deletions {
let args = ["update-index", "--force-remove", "--", path];
repo.git(&args, &env, None).await?;
}
for (path, text) in change.additions {
let was = change
.deletions
.first()
.map_or(path.as_str(), String::as_str);
let mode = mode(repo, parent, was).await;
let blob = repo
.git(&["hash-object", "-w", "--stdin"], &[], Some(text.clone()))
.await?;
let info = format!("{mode},{},{path}", blob.trim());
repo.git(&["update-index", "--add", "--cacheinfo", &info], &env, None)
.await?;
}
repo.git(&["write-tree", "--missing-ok"], &env, None).await
}
.await;
let _ = std::fs::remove_file(&index);
let tree = tree?;
let config = |key: &'static str| async move {
let value = repo.git(&["config", key], &[], None).await.ok()?;
Some(value.trim().to_owned()).filter(|v| !v.is_empty())
};
let login = change.login.unwrap_or("ghtui");
let name = (config("user.name").await).unwrap_or_else(|| login.to_owned());
let email =
(config("user.email").await).unwrap_or_else(|| format!("{login}@users.noreply.github.com"));
let who = [
("GIT_AUTHOR_NAME", name.as_str()),
("GIT_AUTHOR_EMAIL", email.as_str()),
("GIT_COMMITTER_NAME", name.as_str()),
("GIT_COMMITTER_EMAIL", email.as_str()),
];
let args = ["commit-tree", tree.trim(), "-p", parent, "-F", "-"];
let made = repo
.git(&args, &who, Some(change.message.to_owned()))
.await?;
Ok(made.trim().to_owned())
}