~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

A moved pull merges with an older git

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:fix/merge-from-old-git
.changeset/merge-from-old-git.mdnew+7−0
@@ -0,0 +1,7 @@
---
default: patch
---
# A moved pull lands with an older git
Merging or queueing a pull moved off a landed pull no longer needs git 2.40, and git failing during a merge is no longer reported as a conflict.
crates/knotless/src/forge.rs+75−11
@@ -3224,21 +3224,38 @@ pub async fn merge_tree_from(
base: &str,
head: &str,
) -> Result<Result<String, Vec<String>>> {
let from = since.map(|s| format!("--merge-base={s}"));
let mut args = vec!["merge-tree", "--write-tree", "--name-only"];
args.extend(from.as_deref());
args.extend([base, head]);
let ours = match since {
Some(since) => on_start(git, since, base).await?,
None => base.to_string(),
};
let args = ["merge-tree", "--write-tree", "--name-only", &ours, head];
let out = git.output_of(&args).await?;
let text = String::from_utf8_lossy(&out.stdout);
let mut lines = text.lines();
let tree = lines.next().unwrap_or_default().to_string();
Ok(match out.status.success() {
true => Ok(tree),
false => Err(lines
// It exits 1 for a conflict, having written a tree: git failing writes
// none, whatever it exits with, and must not read as a conflict.
match out.status.code() {
Some(0) => Ok(Ok(tree)),
Some(1) if !tree.is_empty() => Ok(Err(lines
.take_while(|l| !l.is_empty())
.map(str::to_string)
.collect()),
})
.collect())),
_ => bail!(
"git merge-tree: {}",
String::from_utf8_lossy(&out.stderr).trim()
),
}
}
/// A commit of `base`'s tree whose only parent is `since`, so a merge of it
/// with a head built on `since` takes `since` as their merge base. Git has
/// `merge-tree --merge-base` for this only from 2.40, and Debian's stable
/// has an older one.
async fn on_start(git: &Git, since: &str, base: &str) -> Result<String> {
let tree = format!("{base}^{{tree}}");
let args = ["commit-tree", &tree, "-p", since, "-m", "Start"];
nobody(git).text(&args, None).await
}
/// The commit a pull is checked at: what merging `head` into `base` would
@@ -3264,6 +3281,13 @@ pub async fn merge_result(
/// The merge commit of `head` into `base` at `tree` that a pull is checked
/// at, the same whoever makes it.
pub(crate) async fn checked_merge(git: &Git, tree: &str, base: &str, head: &str) -> Result<String> {
let args = ["commit-tree", tree, "-p", base, "-p", head, "-m", "Merge"];
nobody(git).text(&args, None).await
}
/// `git`, committing as nobody at no time, so that the same commit made
/// twice is the same one.
fn nobody(git: &Git) -> Git {
let mut git = git.clone();
for (var, value) in [
("GIT_AUTHOR_NAME", "knotless"),
@@ -3275,8 +3299,7 @@ pub(crate) async fn checked_merge(git: &Git, tree: &str, base: &str, head: &str)
] {
git.envs.push((var.into(), value.into()));
}
let args = ["commit-tree", tree, "-p", base, "-p", head, "-m", "Merge"];
git.text(&args, None).await
git
}
/// `git`, committing as `me`.
@@ -3491,6 +3514,47 @@ mod tests {
use super::*;
use serde_json::json;
#[tokio::test]
async fn merging_from_a_start_leaves_out_what_is_under_it() {
let dir = tempfile::tempdir().unwrap();
let bare = Git::init_bare(&dir.path().join("git")).await.unwrap();
let mut git = nobody(&bare);
let work = dir.path().join("work");
std::fs::create_dir(&work).unwrap();
git.envs
.push(("GIT_WORK_TREE".into(), work.display().to_string()));
let commit = async |file: &str, text: &str| {
std::fs::write(work.join(file), text).unwrap();
git.text(&["add", "-A"], None).await.unwrap();
git.text(&["commit", "-q", "-m", file], None).await.unwrap();
git.text(&["rev-parse", "HEAD"], None).await.unwrap()
};
let first = commit("a", "a\n").await;
// A pull adds f, one above it adds g.
let low = commit("f", "low\n").await;
let high = commit("g", "high\n").await;
// The branch takes it squashed, and goes on to change f.
git.text(&["reset", "-q", "--hard", &first], None)
.await
.unwrap();
commit("f", "changed\n").await;
let base = git.text(&["rev-parse", "HEAD"], None).await.unwrap();
let plain = merge_tree_from(&git, None, &base, &high).await.unwrap();
assert_eq!(plain, Err(vec!["f".to_string()]));
let tree = merge_tree_from(&git, Some(&low), &base, &high).await;
let tree = tree.unwrap().unwrap();
let read = format!("{tree}:f");
assert_eq!(git.text(&["show", &read], None).await.unwrap(), "changed");
let read = format!("{tree}:g");
assert_eq!(git.text(&["show", &read], None).await.unwrap(), "high");
// Git failing is not a conflict.
let gone = "0123456789012345678901234567890123456789";
let failed = merge_tree_from(&git, None, &base, gone).await.unwrap_err();
assert!(failed.to_string().contains("git merge-tree"), "{failed}");
}
fn blob(cid: &str) -> BlobRef {
BlobRef {
kind: "blob".into(),