|
|
… |
7235 unchanged lines |
| 7236 |
7236 |
|
.delegate_add(d1, crate::identity::Scope::CHANGES, None) |
| 7237 |
7237 |
|
.is_err()); |
| 7238 |
7238 |
|
} |
|
7239 |
+ |
|
|
7240 |
+ |
/// Two unrelated histories that both add the same file, joined by two |
|
7241 |
+ |
/// independent merges on two branches, then those branches merged: Git |
|
7242 |
+ |
/// merges identical additions cleanly, and the import must too (#71, |
|
7243 |
+ |
/// found in VS Code's history). |
|
7244 |
+ |
#[test] |
|
7245 |
+ |
fn git_import_joins_unrelated_histories_that_add_the_same_file() { |
|
7246 |
+ |
// Batches of one: every merge renders apart from the state, where a |
|
7247 |
+ |
// file whose graph the state had built came out empty (#71). |
|
7248 |
+ |
crate::repo::git::set_merge_batch_for_tests(Some(1)); |
|
7249 |
+ |
hermetic(); |
|
7250 |
+ |
if std::process::Command::new("git") |
|
7251 |
+ |
.arg("--version") |
|
7252 |
+ |
.output() |
|
7253 |
+ |
.is_err() |
|
7254 |
+ |
{ |
|
7255 |
+ |
eprintln!("git not installed; skipping"); |
|
7256 |
+ |
return; |
|
7257 |
+ |
} |
|
7258 |
+ |
let dir = tempfile::tempdir().unwrap(); |
|
7259 |
+ |
let root = dir.path(); |
|
7260 |
+ |
let g = root.join("src.git"); |
|
7261 |
+ |
fs::create_dir_all(&g).unwrap(); |
|
7262 |
+ |
let git = |args: &[&str]| -> String { |
|
7263 |
+ |
let out = std::process::Command::new("git") |
|
7264 |
+ |
.arg("-C") |
|
7265 |
+ |
.arg(&g) |
|
7266 |
+ |
.args([ |
|
7267 |
+ |
"-c", |
|
7268 |
+ |
"user.name=Grace", |
|
7269 |
+ |
"-c", |
|
7270 |
+ |
"[email protected]", |
|
7271 |
+ |
"-c", |
|
7272 |
+ |
"commit.gpgsign=false", |
|
7273 |
+ |
]) |
|
7274 |
+ |
.args(args) |
|
7275 |
+ |
.output() |
|
7276 |
+ |
.unwrap(); |
|
7277 |
+ |
assert!( |
|
7278 |
+ |
out.status.success(), |
|
7279 |
+ |
"git {args:?}: {}", |
|
7280 |
+ |
String::from_utf8_lossy(&out.stderr) |
|
7281 |
+ |
); |
|
7282 |
+ |
String::from_utf8_lossy(&out.stdout).trim().to_string() |
|
7283 |
+ |
}; |
|
7284 |
+ |
let text = b"/* header */\nconst a = 1;\nconst b = 2;\n"; |
|
7285 |
+ |
// History A: main. |
|
7286 |
+ |
git(&["init", "-q", "-b", "main"]); |
|
7287 |
+ |
write(&g, "gulpfile.js", text); |
|
7288 |
+ |
write(&g, "a.txt", b"a\n"); |
|
7289 |
+ |
git(&["add", "."]); |
|
7290 |
+ |
git(&["commit", "-q", "-m", "A root"]); |
|
7291 |
+ |
// History B: an unrelated root adding the same file, identical. |
|
7292 |
+ |
git(&["checkout", "-q", "--orphan", "fork"]); |
|
7293 |
+ |
git(&["rm", "-q", "-r", "--cached", "."]); |
|
7294 |
+ |
for f in ["gulpfile.js", "a.txt"] { |
|
7295 |
+ |
let _ = fs::remove_file(g.join(f)); |
|
7296 |
+ |
} |
|
7297 |
+ |
write(&g, "gulpfile.js", text); |
|
7298 |
+ |
write(&g, "b.txt", b"b\n"); |
|
7299 |
+ |
git(&["add", "."]); |
|
7300 |
+ |
git(&["commit", "-q", "-m", "B root"]); |
|
7301 |
+ |
// Two branches from main, each joining B on its own. |
|
7302 |
+ |
git(&["checkout", "-q", "-b", "one", "main"]); |
|
7303 |
+ |
git(&[ |
|
7304 |
+ |
"merge", |
|
7305 |
+ |
"-q", |
|
7306 |
+ |
"--no-ff", |
|
7307 |
+ |
"--allow-unrelated-histories", |
|
7308 |
+ |
"-m", |
|
7309 |
+ |
"join on one", |
|
7310 |
+ |
"fork", |
|
7311 |
+ |
]); |
|
7312 |
+ |
git(&["checkout", "-q", "-b", "two", "main"]); |
|
7313 |
+ |
write(&g, "a.txt", b"a\ntwo\n"); |
|
7314 |
+ |
git(&["commit", "-q", "-am", "work on two"]); |
|
7315 |
+ |
git(&[ |
|
7316 |
+ |
"merge", |
|
7317 |
+ |
"-q", |
|
7318 |
+ |
"--no-ff", |
|
7319 |
+ |
"--allow-unrelated-histories", |
|
7320 |
+ |
"-m", |
|
7321 |
+ |
"join on two", |
|
7322 |
+ |
"fork", |
|
7323 |
+ |
]); |
|
7324 |
+ |
// Then the two joins meet, and the file is reformatted after. |
|
7325 |
+ |
git(&["checkout", "-q", "main"]); |
|
7326 |
+ |
git(&["merge", "-q", "--no-ff", "-m", "merge one", "one"]); |
|
7327 |
+ |
git(&["merge", "-q", "--no-ff", "-m", "merge two", "two"]); |
|
7328 |
+ |
write( |
|
7329 |
+ |
&g, |
|
7330 |
+ |
"gulpfile.js", |
|
7331 |
+ |
b"/* header */\nconst a = 1;\nconst b = 3;\n", |
|
7332 |
+ |
); |
|
7333 |
+ |
git(&["commit", "-q", "-am", "after"]); |
|
7334 |
+ |
|
|
7335 |
+ |
let dest = root.join("dest"); |
|
7336 |
+ |
let repo = Repo::init(&dest).unwrap().with_home(root.join("home")); |
|
7337 |
+ |
repo.import_git(&g, "main").unwrap(); |
|
7338 |
+ |
let v = repo.verify_git_import(&g, "main").unwrap(); |
|
7339 |
+ |
assert!(v.mismatches.is_empty(), "{:?}", v.mismatches); |
|
7340 |
+ |
assert_eq!( |
|
7341 |
+ |
fs::read(dest.join("gulpfile.js")).unwrap(), |
|
7342 |
+ |
b"/* header */\nconst a = 1;\nconst b = 3;\n" |
|
7343 |
+ |
); |
|
7344 |
+ |
} |
|
7345 |
+ |
|
|
7346 |
+ |
/// As above, but the two joins keep different copies (each branch had a |
|
7347 |
+ |
/// different version when it joined), and later work edits each copy: |
|
7348 |
+ |
/// the shape of VS Code's c67ef57cda90 (#71). |
|
7349 |
+ |
#[test] |
|
7350 |
+ |
fn git_import_joins_where_each_join_kept_a_different_copy() { |
|
7351 |
+ |
// Batches of one: every merge renders apart from the state, where a |
|
7352 |
+ |
// file whose graph the state had built came out empty (#71). |
|
7353 |
+ |
crate::repo::git::set_merge_batch_for_tests(Some(1)); |
|
7354 |
+ |
hermetic(); |
|
7355 |
+ |
if std::process::Command::new("git") |
|
7356 |
+ |
.arg("--version") |
|
7357 |
+ |
.output() |
|
7358 |
+ |
.is_err() |
|
7359 |
+ |
{ |
|
7360 |
+ |
eprintln!("git not installed; skipping"); |
|
7361 |
+ |
return; |
|
7362 |
+ |
} |
|
7363 |
+ |
let dir = tempfile::tempdir().unwrap(); |
|
7364 |
+ |
let root = dir.path(); |
|
7365 |
+ |
let g = root.join("src.git"); |
|
7366 |
+ |
fs::create_dir_all(&g).unwrap(); |
|
7367 |
+ |
let run = |args: &[&str]| { |
|
7368 |
+ |
std::process::Command::new("git") |
|
7369 |
+ |
.arg("-C") |
|
7370 |
+ |
.arg(&g) |
|
7371 |
+ |
.args([ |
|
7372 |
+ |
"-c", |
|
7373 |
+ |
"user.name=Grace", |
|
7374 |
+ |
"-c", |
|
7375 |
+ |
"[email protected]", |
|
7376 |
+ |
"-c", |
|
7377 |
+ |
"commit.gpgsign=false", |
|
7378 |
+ |
]) |
|
7379 |
+ |
.args(args) |
|
7380 |
+ |
.output() |
|
7381 |
+ |
.unwrap() |
|
7382 |
+ |
}; |
|
7383 |
+ |
let git = |args: &[&str]| -> String { |
|
7384 |
+ |
let out = run(args); |
|
7385 |
+ |
assert!( |
|
7386 |
+ |
out.status.success(), |
|
7387 |
+ |
"git {args:?}: {}", |
|
7388 |
+ |
String::from_utf8_lossy(&out.stderr) |
|
7389 |
+ |
); |
|
7390 |
+ |
String::from_utf8_lossy(&out.stdout).trim().to_string() |
|
7391 |
+ |
}; |
|
7392 |
+ |
git(&["init", "-q", "-b", "main"]); |
|
7393 |
+ |
write( |
|
7394 |
+ |
&g, |
|
7395 |
+ |
"gulpfile.js", |
|
7396 |
+ |
b"/* header */\nconst a = 1;\nconst b = 2;\n", |
|
7397 |
+ |
); |
|
7398 |
+ |
git(&["add", "."]); |
|
7399 |
+ |
git(&["commit", "-q", "-m", "A root"]); |
|
7400 |
+ |
write( |
|
7401 |
+ |
&g, |
|
7402 |
+ |
"gulpfile.js", |
|
7403 |
+ |
b"/* header */\nconst a = 1;\n\nconst b = 2;\n", |
|
7404 |
+ |
); |
|
7405 |
+ |
git(&["commit", "-q", "-am", "whitespace"]); |
|
7406 |
+ |
git(&["checkout", "-q", "--orphan", "fork"]); |
|
7407 |
+ |
git(&["rm", "-q", "-r", "--cached", "."]); |
|
7408 |
+ |
let _ = fs::remove_file(g.join("gulpfile.js")); |
|
7409 |
+ |
write( |
|
7410 |
+ |
&g, |
|
7411 |
+ |
"gulpfile.js", |
|
7412 |
+ |
b"/* header */\nconst a = 1;\nconst b = 2;\nconst c = 3;\n", |
|
7413 |
+ |
); |
|
7414 |
+ |
git(&["add", "."]); |
|
7415 |
+ |
git(&["commit", "-q", "-m", "B root"]); |
|
7416 |
+ |
// Branch one joins B keeping main's copy; branch two keeps B's. |
|
7417 |
+ |
for (branch, keep) in [("one", "ours"), ("two", "theirs")] { |
|
7418 |
+ |
git(&["checkout", "-q", "-b", branch, "main"]); |
|
7419 |
+ |
let _ = run(&[ |
|
7420 |
+ |
"merge", |
|
7421 |
+ |
"-q", |
|
7422 |
+ |
"--no-ff", |
|
7423 |
+ |
"--allow-unrelated-histories", |
|
7424 |
+ |
"-m", |
|
7425 |
+ |
"join", |
|
7426 |
+ |
"fork", |
|
7427 |
+ |
]); |
|
7428 |
+ |
git(&["checkout", &format!("--{keep}"), "gulpfile.js"]); |
|
7429 |
+ |
git(&["add", "gulpfile.js"]); |
|
7430 |
+ |
git(&["commit", "-q", "-m", &format!("join on {branch}")]); |
|
7431 |
+ |
} |
|
7432 |
+ |
// Each branch then edits its copy. |
|
7433 |
+ |
git(&["checkout", "-q", "one"]); |
|
7434 |
+ |
write( |
|
7435 |
+ |
&g, |
|
7436 |
+ |
"gulpfile.js", |
|
7437 |
+ |
b"/* header */\nconst a = 1;\n\nconst b = 2;\n// tslint\n", |
|
7438 |
+ |
); |
|
7439 |
+ |
git(&["commit", "-q", "-am", "tslint on one"]); |
|
7440 |
+ |
git(&["checkout", "-q", "two"]); |
|
7441 |
+ |
write( |
|
7442 |
+ |
&g, |
|
7443 |
+ |
"gulpfile.js", |
|
7444 |
+ |
b"/* header */\nconst a = 1;\nconst b = 2;\nconst c = 3;\n// fmt\n", |
|
7445 |
+ |
); |
|
7446 |
+ |
write(&g, "extra.txt", b"x\n"); |
|
7447 |
+ |
write(&g, "more.txt", b"m\n"); |
|
7448 |
+ |
git(&["add", "."]); |
|
7449 |
+ |
git(&["commit", "-q", "-am", "fmt on two"]); |
|
7450 |
+ |
// They meet: resolved by hand. |
|
7451 |
+ |
git(&["checkout", "-q", "one"]); |
|
7452 |
+ |
let _ = run(&["merge", "-q", "--no-ff", "-m", "merge two", "two"]); |
|
7453 |
+ |
write( |
|
7454 |
+ |
&g, |
|
7455 |
+ |
"gulpfile.js", |
|
7456 |
+ |
b"/* header */\nconst a = 1;\nconst b = 2;\nconst c = 3;\n// tslint\n// fmt\n", |
|
7457 |
+ |
); |
|
7458 |
+ |
git(&["add", "gulpfile.js"]); |
|
7459 |
+ |
git(&["commit", "-q", "-m", "merge two into one"]); |
|
7460 |
+ |
|
|
7461 |
+ |
let dest = root.join("dest"); |
|
7462 |
+ |
let repo = Repo::init(&dest).unwrap().with_home(root.join("home")); |
|
7463 |
+ |
repo.import_git(&g, "one").unwrap(); |
|
7464 |
+ |
let v = repo.verify_git_import(&g, "one").unwrap(); |
|
7465 |
+ |
assert!(v.mismatches.is_empty(), "{:?}", v.mismatches); |
|
7466 |
+ |
} |