| 1124 |
1124 |
|
format!("git -C {repo} fetch --all --tags --prune")
|
| 1125 |
1125 |
|
}
|
| 1126 |
1126 |
|
|
|
1127 |
+ |
/// Probe for the one failure a tracked `Cargo.lock` hits inside Bento's
|
|
1128 |
+ |
/// worktree. Exits 0 when the crate tracks a lock AND the checkout sits under a
|
|
1129 |
+ |
/// `.cargo/config.toml` declaring `[patch]`; 1 otherwise.
|
|
1130 |
+ |
///
|
|
1131 |
+ |
/// Both halves are needed and cargo reports neither. Under a `[patch]` block
|
|
1132 |
+ |
/// cargo re-resolves and rewrites the lock's `[[patch.unused]]` entries, so
|
|
1133 |
+ |
/// `cargo publish --dry-run` refuses the tree with "1 files in the working
|
|
1134 |
+ |
/// directory contain changes that were not yet committed into git: Cargo.lock"
|
|
1135 |
+ |
/// -- naming the lock and nothing about why it moved. pter 0.2.1 lost half an
|
|
1136 |
+ |
/// hour to that message on build 325.
|
|
1137 |
+ |
///
|
|
1138 |
+ |
/// `~/Code/.bento` is under `~/Code` deliberately, so that the patch block
|
|
1139 |
+ |
/// reaches the build (see [`crate::topology::Host::worktree_root`]). The patch
|
|
1140 |
+ |
/// block is therefore not the half to remove, which is why this is worth
|
|
1141 |
+ |
/// saying rather than leaving cargo to be cryptic about it.
|
|
1142 |
+ |
///
|
|
1143 |
+ |
/// `repo` is interpolated unquoted for the `~`, matching [`git_fetch_cmd`];
|
|
1144 |
+ |
/// `pwd -P` then hands the loop an absolute path to walk up from.
|
|
1145 |
+ |
pub fn tracked_lock_under_patch_cmd(repo: &str) -> String {
|
|
1146 |
+ |
format!(
|
|
1147 |
+ |
"git -C {repo} ls-files --error-unmatch Cargo.lock >/dev/null 2>&1 || exit 1; \
|
|
1148 |
+ |
d=$(cd {repo} && pwd -P) || exit 1; \
|
|
1149 |
+ |
while [ -n \"$d\" ] && [ \"$d\" != / ]; do \
|
|
1150 |
+ |
for c in \"$d/.cargo/config.toml\" \"$d/.cargo/config\"; do \
|
|
1151 |
+ |
[ -f \"$c\" ] && grep -q '^\\[patch' \"$c\" && exit 0; \
|
|
1152 |
+ |
done; d=$(dirname \"$d\"); done; exit 1"
|
|
1153 |
+ |
)
|
|
1154 |
+ |
}
|
|
1155 |
+ |
|
|
1156 |
+ |
/// What to say when [`tracked_lock_under_patch_cmd`] answers yes. Names both
|
|
1157 |
+ |
/// facts, because the error cargo would otherwise print names neither, and
|
|
1158 |
+ |
/// closes off the two wrong fixes that are both one flag away.
|
|
1159 |
+ |
pub fn tracked_lock_under_patch_problem(repo: &str) -> String {
|
|
1160 |
+ |
format!(
|
|
1161 |
+ |
"`Cargo.lock` is tracked and {repo} sits under a `.cargo/config.toml` \
|
|
1162 |
+ |
declaring `[patch]`. Cargo re-resolves there and rewrites the lock, so \
|
|
1163 |
+ |
`cargo publish --dry-run` will refuse the tree as dirty and name only \
|
|
1164 |
+ |
the lock. Untrack it: `git rm --cached Cargo.lock`. Every other library \
|
|
1165 |
+ |
in this tree already does. Not `--allow-dirty`, which publishes a lock \
|
|
1166 |
+ |
nobody reviewed, and not committing the rewritten lock, which resolves \
|
|
1167 |
+ |
differently on the next machine and fails there instead. The build \
|
|
1168 |
+ |
worktree is under `~/Code` on purpose so the patch block applies to it; \
|
|
1169 |
+ |
that is not the half to change."
|
|
1170 |
+ |
)
|
|
1171 |
+ |
}
|
|
1172 |
+ |
|
| 1127 |
1173 |
|
/// Does `tag` resolve to a commit in this checkout? Run only when the checkout
|
| 1128 |
1174 |
|
/// has already failed, to say WHY: an absent tag is an untagged or unpushed
|
| 1129 |
1175 |
|
/// release, while a tag that resolves fine means the checkout was refused for a
|
| 1524 |
1570 |
|
})?
|
| 1525 |
1571 |
|
.0 == 0;
|
| 1526 |
1572 |
|
|
|
1573 |
+ |
// Asked of the build host rather than the daemon: the tree
|
|
1574 |
+ |
// that gets published is the worktree over there, and it is the
|
|
1575 |
+ |
// one whose `[patch]` ancestry decides this. An Err is "the
|
|
1576 |
+ |
// question could not be asked" and is not a finding -- same
|
|
1577 |
+ |
// rule as the credentials check above, for the same reason it
|
|
1578 |
+ |
// was written that way.
|
|
1579 |
+ |
let build_repo = ctx.repo_for(&ctx.build_host).to_string();
|
|
1580 |
+ |
let patched_lock =
|
|
1581 |
+ |
ctx.run(
|
|
1582 |
+ |
&ctx.build_host.clone(),
|
|
1583 |
+ |
&tracked_lock_under_patch_cmd(&build_repo),
|
|
1584 |
+ |
)
|
|
1585 |
+ |
.map_err(|e| {
|
|
1586 |
+ |
format!(
|
|
1587 |
+ |
"could not check for a tracked Cargo.lock on `{}`: {e}",
|
|
1588 |
+ |
ctx.build_host
|
|
1589 |
+ |
)
|
|
1590 |
+ |
})?
|
|
1591 |
+ |
.0 == 0;
|
|
1592 |
+ |
|
| 1527 |
1593 |
|
let published = RecipeCtx::published_versions(&meta.name);
|
| 1528 |
|
- |
let problems = crate_publish_problems(&meta, clonable, &published, creds);
|
|
1594 |
+ |
let mut problems = crate_publish_problems(&meta, clonable, &published, creds);
|
|
1595 |
+ |
if patched_lock {
|
|
1596 |
+ |
problems.push(tracked_lock_under_patch_problem(&build_repo));
|
|
1597 |
+ |
}
|
| 1529 |
1598 |
|
if !problems.is_empty() {
|
| 1530 |
1599 |
|
return Err(format!(
|
| 1531 |
1600 |
|
"{} {} is not safe to publish:\n - {}",
|
| 3601 |
3670 |
|
let silent = worktree_failure_reason("pom-v0.4.5", true, " ");
|
| 3602 |
3671 |
|
assert!(silent.contains("said nothing"), "{silent}");
|
| 3603 |
3672 |
|
}
|
|
3673 |
+ |
|
|
3674 |
+ |
/// Run the probe for real rather than asserting on its text: it is shell,
|
|
3675 |
+ |
/// and the thing worth knowing is whether `sh` agrees, not whether the
|
|
3676 |
+ |
/// string looks right.
|
|
3677 |
+ |
fn probe(repo: &std::path::Path) -> bool {
|
|
3678 |
+ |
std::process::Command::new("sh")
|
|
3679 |
+ |
.arg("-c")
|
|
3680 |
+ |
.arg(tracked_lock_under_patch_cmd(&repo.display().to_string()))
|
|
3681 |
+ |
.status()
|
|
3682 |
+ |
.unwrap()
|
|
3683 |
+ |
.success()
|
|
3684 |
+ |
}
|
|
3685 |
+ |
|
|
3686 |
+ |
/// A crate under a `[patch]` root, with and without its lock tracked.
|
|
3687 |
+ |
///
|
|
3688 |
+ |
/// pter 0.2.1 is the case: the tracked half was true, the patch half was
|
|
3689 |
+ |
/// true, and cargo reported only "Cargo.lock" (build 325). Untracking the
|
|
3690 |
+ |
/// lock in `a9969a9` is what fixed it, and this asserts the probe agrees
|
|
3691 |
+ |
/// with that fix in both directions.
|
|
3692 |
+ |
#[test]
|
|
3693 |
+ |
fn a_tracked_lock_is_only_a_problem_under_a_patch_block() {
|
|
3694 |
+ |
let root = tempfile::tempdir().unwrap();
|
|
3695 |
+ |
let repo = root.path().join("crate");
|
|
3696 |
+ |
std::fs::create_dir_all(&repo).unwrap();
|
|
3697 |
+ |
let git = |args: &[&str]| {
|
|
3698 |
+ |
std::process::Command::new("git")
|
|
3699 |
+ |
.args(args)
|
|
3700 |
+ |
.current_dir(&repo)
|
|
3701 |
+ |
.env("GIT_AUTHOR_NAME", "t")
|
|
3702 |
+ |
.env("GIT_AUTHOR_EMAIL", "t@t")
|
|
3703 |
+ |
.env("GIT_COMMITTER_NAME", "t")
|
|
3704 |
+ |
.env("GIT_COMMITTER_EMAIL", "t@t")
|
|
3705 |
+ |
.output()
|
|
3706 |
+ |
.unwrap()
|
|
3707 |
+ |
};
|
|
3708 |
+ |
git(&["init", "-q", "."]);
|
|
3709 |
+ |
std::fs::write(repo.join("Cargo.lock"), "# lock\n").unwrap();
|
|
3710 |
+ |
|
|
3711 |
+ |
// Lock present but untracked, no patch anywhere: nothing to say.
|
|
3712 |
+ |
assert!(!probe(&repo));
|
|
3713 |
+ |
|
|
3714 |
+ |
// Tracked, still no patch root. Every library that commits a lock and
|
|
3715 |
+ |
// builds outside `~/Code` lives here, and it publishes fine.
|
|
3716 |
+ |
git(&["add", "Cargo.lock"]);
|
|
3717 |
+ |
git(&["commit", "-qm", "lock"]);
|
|
3718 |
+ |
assert!(!probe(&repo));
|
|
3719 |
+ |
|
|
3720 |
+ |
// The ancestor declares `[patch]`. Both halves now hold.
|
|
3721 |
+ |
std::fs::create_dir_all(root.path().join(".cargo")).unwrap();
|
|
3722 |
+ |
std::fs::write(
|
|
3723 |
+ |
root.path().join(".cargo/config.toml"),
|
|
3724 |
+ |
"[patch.\"https://makenot.work/git/max/docengine.git\"]\ndocengine = { path = \"x\" }\n",
|
|
3725 |
+ |
)
|
|
3726 |
+ |
.unwrap();
|
|
3727 |
+ |
assert!(probe(&repo));
|
|
3728 |
+ |
|
|
3729 |
+ |
// Untracking the lock is the fix, and the probe has to agree that it is.
|
|
3730 |
+ |
git(&["rm", "-q", "--cached", "Cargo.lock"]);
|
|
3731 |
+ |
assert!(!probe(&repo));
|
|
3732 |
+ |
}
|
|
3733 |
+ |
|
|
3734 |
+ |
/// The message exists because cargo's names neither fact and both wrong
|
|
3735 |
+ |
/// fixes are one flag away. Assert it still says all four things.
|
|
3736 |
+ |
#[test]
|
|
3737 |
+ |
fn the_tracked_lock_message_names_both_facts_and_refuses_both_wrong_fixes() {
|
|
3738 |
+ |
let msg = tracked_lock_under_patch_problem("~/Code/.bento/pter/pter");
|
|
3739 |
+ |
assert!(msg.contains("Cargo.lock"), "{msg}");
|
|
3740 |
+ |
assert!(msg.contains("[patch]"), "{msg}");
|
|
3741 |
+ |
assert!(msg.contains("git rm --cached"), "{msg}");
|
|
3742 |
+ |
assert!(msg.contains("--allow-dirty"), "{msg}");
|
|
3743 |
+ |
assert!(msg.contains("~/Code/.bento/pter/pter"), "{msg}");
|
|
3744 |
+ |
}
|
| 3604 |
3745 |
|
}
|