just playing with tangled
1// Copyright 2023 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14use std::path::Path;
15
16use crate::common::TestEnvironment;
17
18/// Creates a remote Git repo containing a branch with the same name
19fn init_git_remote(test_env: &TestEnvironment, remote: &str) {
20 let git_repo_path = test_env.env_root().join(remote);
21 let git_repo = git2::Repository::init(git_repo_path).unwrap();
22 let signature =
23 git2::Signature::new("Some One", "some.one@example.com", &git2::Time::new(0, 0)).unwrap();
24 let mut tree_builder = git_repo.treebuilder(None).unwrap();
25 let file_oid = git_repo.blob(remote.as_bytes()).unwrap();
26 tree_builder
27 .insert("file", file_oid, git2::FileMode::Blob.into())
28 .unwrap();
29 let tree_oid = tree_builder.write().unwrap();
30 let tree = git_repo.find_tree(tree_oid).unwrap();
31 git_repo
32 .commit(
33 Some(&format!("refs/heads/{remote}")),
34 &signature,
35 &signature,
36 "message",
37 &tree,
38 &[],
39 )
40 .unwrap();
41}
42
43/// Add a remote containing a branch with the same name
44fn add_git_remote(test_env: &TestEnvironment, repo_path: &Path, remote: &str) {
45 init_git_remote(test_env, remote);
46 test_env.jj_cmd_ok(
47 repo_path,
48 &["git", "remote", "add", remote, &format!("../{remote}")],
49 );
50}
51
52fn get_branch_output(test_env: &TestEnvironment, repo_path: &Path) -> String {
53 test_env.jj_cmd_success(repo_path, &["branch", "list", "--all-remotes"])
54}
55
56fn create_commit(test_env: &TestEnvironment, repo_path: &Path, name: &str, parents: &[&str]) {
57 let descr = format!("descr_for_{name}");
58 if parents.is_empty() {
59 test_env.jj_cmd_ok(repo_path, &["new", "root()", "-m", &descr]);
60 } else {
61 let mut args = vec!["new", "-m", &descr];
62 args.extend(parents);
63 test_env.jj_cmd_ok(repo_path, &args);
64 }
65 std::fs::write(repo_path.join(name), format!("{name}\n")).unwrap();
66 test_env.jj_cmd_ok(repo_path, &["branch", "create", name]);
67}
68
69fn get_log_output(test_env: &TestEnvironment, workspace_root: &Path) -> String {
70 let template = r#"commit_id.short() ++ " " ++ description.first_line() ++ " " ++ branches"#;
71 test_env.jj_cmd_success(workspace_root, &["log", "-T", template, "-r", "all()"])
72}
73
74#[test]
75fn test_git_fetch_with_default_config() {
76 let test_env = TestEnvironment::default();
77 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
78 let repo_path = test_env.env_root().join("repo");
79 add_git_remote(&test_env, &repo_path, "origin");
80
81 test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
82 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
83 origin@origin: oputwtnw ffecd2d6 message
84 "###);
85}
86
87#[test]
88fn test_git_fetch_default_remote() {
89 let test_env = TestEnvironment::default();
90 test_env.add_config("git.auto-local-branch = true");
91 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
92 let repo_path = test_env.env_root().join("repo");
93 add_git_remote(&test_env, &repo_path, "origin");
94
95 test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
96 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
97 origin: oputwtnw ffecd2d6 message
98 @origin: oputwtnw ffecd2d6 message
99 "###);
100}
101
102#[test]
103fn test_git_fetch_single_remote() {
104 let test_env = TestEnvironment::default();
105 test_env.add_config("git.auto-local-branch = true");
106 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
107 let repo_path = test_env.env_root().join("repo");
108 add_git_remote(&test_env, &repo_path, "rem1");
109
110 let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
111 insta::assert_snapshot!(stderr, @r###"
112 Hint: Fetching from the only existing remote: rem1
113 branch: rem1@rem1 [new] tracked
114 "###);
115 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
116 rem1: qxosxrvv 6a211027 message
117 @rem1: qxosxrvv 6a211027 message
118 "###);
119}
120
121#[test]
122fn test_git_fetch_single_remote_all_remotes_flag() {
123 let test_env = TestEnvironment::default();
124 test_env.add_config("git.auto-local-branch = true");
125 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
126 let repo_path = test_env.env_root().join("repo");
127 add_git_remote(&test_env, &repo_path, "rem1");
128
129 test_env
130 .jj_cmd(&repo_path, &["git", "fetch", "--all-remotes"])
131 .assert()
132 .success();
133 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
134 rem1: qxosxrvv 6a211027 message
135 @rem1: qxosxrvv 6a211027 message
136 "###);
137}
138
139#[test]
140fn test_git_fetch_single_remote_from_arg() {
141 let test_env = TestEnvironment::default();
142 test_env.add_config("git.auto-local-branch = true");
143 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
144 let repo_path = test_env.env_root().join("repo");
145 add_git_remote(&test_env, &repo_path, "rem1");
146
147 test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote", "rem1"]);
148 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
149 rem1: qxosxrvv 6a211027 message
150 @rem1: qxosxrvv 6a211027 message
151 "###);
152}
153
154#[test]
155fn test_git_fetch_single_remote_from_config() {
156 let test_env = TestEnvironment::default();
157 test_env.add_config("git.auto-local-branch = true");
158 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
159 let repo_path = test_env.env_root().join("repo");
160 add_git_remote(&test_env, &repo_path, "rem1");
161 test_env.add_config(r#"git.fetch = "rem1""#);
162
163 test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
164 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
165 rem1: qxosxrvv 6a211027 message
166 @rem1: qxosxrvv 6a211027 message
167 "###);
168}
169
170#[test]
171fn test_git_fetch_multiple_remotes() {
172 let test_env = TestEnvironment::default();
173 test_env.add_config("git.auto-local-branch = true");
174 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
175 let repo_path = test_env.env_root().join("repo");
176 add_git_remote(&test_env, &repo_path, "rem1");
177 add_git_remote(&test_env, &repo_path, "rem2");
178
179 test_env.jj_cmd_ok(
180 &repo_path,
181 &["git", "fetch", "--remote", "rem1", "--remote", "rem2"],
182 );
183 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
184 rem1: qxosxrvv 6a211027 message
185 @rem1: qxosxrvv 6a211027 message
186 rem2: yszkquru 2497a8a0 message
187 @rem2: yszkquru 2497a8a0 message
188 "###);
189}
190
191#[test]
192fn test_git_fetch_all_remotes() {
193 let test_env = TestEnvironment::default();
194 test_env.add_config("git.auto-local-branch = true");
195 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
196 let repo_path = test_env.env_root().join("repo");
197 add_git_remote(&test_env, &repo_path, "rem1");
198 add_git_remote(&test_env, &repo_path, "rem2");
199
200 test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--all-remotes"]);
201 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
202 rem1: qxosxrvv 6a211027 message
203 @rem1: qxosxrvv 6a211027 message
204 rem2: yszkquru 2497a8a0 message
205 @rem2: yszkquru 2497a8a0 message
206 "###);
207}
208
209#[test]
210fn test_git_fetch_multiple_remotes_from_config() {
211 let test_env = TestEnvironment::default();
212 test_env.add_config("git.auto-local-branch = true");
213 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
214 let repo_path = test_env.env_root().join("repo");
215 add_git_remote(&test_env, &repo_path, "rem1");
216 add_git_remote(&test_env, &repo_path, "rem2");
217 test_env.add_config(r#"git.fetch = ["rem1", "rem2"]"#);
218
219 test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
220 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
221 rem1: qxosxrvv 6a211027 message
222 @rem1: qxosxrvv 6a211027 message
223 rem2: yszkquru 2497a8a0 message
224 @rem2: yszkquru 2497a8a0 message
225 "###);
226}
227
228#[test]
229fn test_git_fetch_nonexistent_remote() {
230 let test_env = TestEnvironment::default();
231 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
232 let repo_path = test_env.env_root().join("repo");
233 add_git_remote(&test_env, &repo_path, "rem1");
234
235 let stderr = &test_env.jj_cmd_failure(
236 &repo_path,
237 &["git", "fetch", "--remote", "rem1", "--remote", "rem2"],
238 );
239 insta::assert_snapshot!(stderr, @r###"
240 branch: rem1@rem1 [new] untracked
241 Error: No git remote named 'rem2'
242 "###);
243 // No remote should have been fetched as part of the failing transaction
244 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
245}
246
247#[test]
248fn test_git_fetch_nonexistent_remote_from_config() {
249 let test_env = TestEnvironment::default();
250 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
251 let repo_path = test_env.env_root().join("repo");
252 add_git_remote(&test_env, &repo_path, "rem1");
253 test_env.add_config(r#"git.fetch = ["rem1", "rem2"]"#);
254
255 let stderr = &test_env.jj_cmd_failure(&repo_path, &["git", "fetch"]);
256 insta::assert_snapshot!(stderr, @r###"
257 branch: rem1@rem1 [new] untracked
258 Error: No git remote named 'rem2'
259 "###);
260 // No remote should have been fetched as part of the failing transaction
261 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
262}
263
264#[test]
265fn test_git_fetch_from_remote_named_git() {
266 let test_env = TestEnvironment::default();
267 test_env.add_config("git.auto-local-branch = true");
268 let repo_path = test_env.env_root().join("repo");
269 init_git_remote(&test_env, "git");
270 let git_repo = git2::Repository::init(&repo_path).unwrap();
271 git_repo.remote("git", "../git").unwrap();
272
273 // Existing remote named 'git' shouldn't block the repo initialization.
274 test_env.jj_cmd_ok(&repo_path, &["init", "--git-repo=."]);
275
276 // Try fetching from the remote named 'git'.
277 let stderr = &test_env.jj_cmd_failure(&repo_path, &["git", "fetch", "--remote=git"]);
278 insta::assert_snapshot!(stderr, @r###"
279 Error: Failed to import refs from underlying Git repo
280 Caused by: Git remote named 'git' is reserved for local Git repository
281 Hint: Run `jj git remote rename` to give different name.
282 "###);
283
284 // Implicit import shouldn't fail because of the remote ref.
285 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "list", "--all-remotes"]);
286 insta::assert_snapshot!(stdout, @"");
287 insta::assert_snapshot!(stderr, @"");
288
289 // Explicit import is an error.
290 // (This could be warning if we add mechanism to report ignored refs.)
291 insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["git", "import"]), @r###"
292 Error: Failed to import refs from underlying Git repo
293 Caused by: Git remote named 'git' is reserved for local Git repository
294 Hint: Run `jj git remote rename` to give different name.
295 "###);
296
297 // The remote can be renamed, and the ref can be imported.
298 test_env.jj_cmd_ok(&repo_path, &["git", "remote", "rename", "git", "bar"]);
299 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "list", "--all-remotes"]);
300 insta::assert_snapshot!(stdout, @r###"
301 git: mrylzrtu 76fc7466 message
302 @bar: mrylzrtu 76fc7466 message
303 @git: mrylzrtu 76fc7466 message
304 "###);
305 insta::assert_snapshot!(stderr, @r###"
306 Done importing changes from the underlying Git repo.
307 "###);
308}
309
310#[test]
311fn test_git_fetch_prune_before_updating_tips() {
312 let test_env = TestEnvironment::default();
313 test_env.add_config("git.auto-local-branch = true");
314 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
315 let repo_path = test_env.env_root().join("repo");
316 add_git_remote(&test_env, &repo_path, "origin");
317 test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
318 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
319 origin: oputwtnw ffecd2d6 message
320 @origin: oputwtnw ffecd2d6 message
321 "###);
322
323 // Remove origin branch in git repo and create origin/subname
324 let git_repo = git2::Repository::open(test_env.env_root().join("origin")).unwrap();
325 git_repo
326 .find_branch("origin", git2::BranchType::Local)
327 .unwrap()
328 .rename("origin/subname", false)
329 .unwrap();
330
331 test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
332 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
333 origin/subname: oputwtnw ffecd2d6 message
334 @origin: oputwtnw ffecd2d6 message
335 "###);
336}
337
338#[test]
339fn test_git_fetch_conflicting_branches() {
340 let test_env = TestEnvironment::default();
341 test_env.add_config("git.auto-local-branch = true");
342 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
343 let repo_path = test_env.env_root().join("repo");
344 add_git_remote(&test_env, &repo_path, "rem1");
345
346 // Create a rem1 branch locally
347 test_env.jj_cmd_ok(&repo_path, &["new", "root()"]);
348 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "rem1"]);
349 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
350 rem1: kkmpptxz fcdbbd73 (empty) (no description set)
351 "###);
352
353 test_env.jj_cmd_ok(
354 &repo_path,
355 &["git", "fetch", "--remote", "rem1", "--branch", "glob:*"],
356 );
357 // This should result in a CONFLICTED branch
358 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
359 rem1 (conflicted):
360 + kkmpptxz fcdbbd73 (empty) (no description set)
361 + qxosxrvv 6a211027 message
362 @rem1 (behind by 1 commits): qxosxrvv 6a211027 message
363 "###);
364}
365
366#[test]
367fn test_git_fetch_conflicting_branches_colocated() {
368 let test_env = TestEnvironment::default();
369 test_env.add_config("git.auto-local-branch = true");
370 let repo_path = test_env.env_root().join("repo");
371 let _git_repo = git2::Repository::init(&repo_path).unwrap();
372 // create_colocated_repo_and_branches_from_trunk1(&test_env, &repo_path);
373 test_env.jj_cmd_ok(&repo_path, &["init", "--git-repo", "."]);
374 add_git_remote(&test_env, &repo_path, "rem1");
375 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
376
377 // Create a rem1 branch locally
378 test_env.jj_cmd_ok(&repo_path, &["new", "root()"]);
379 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "rem1"]);
380 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
381 rem1: zsuskuln f652c321 (empty) (no description set)
382 @git: zsuskuln f652c321 (empty) (no description set)
383 "###);
384
385 test_env.jj_cmd_ok(
386 &repo_path,
387 &["git", "fetch", "--remote", "rem1", "--branch", "rem1"],
388 );
389 // This should result in a CONFLICTED branch
390 // See https://github.com/martinvonz/jj/pull/1146#discussion_r1112372340 for the bug this tests for.
391 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
392 rem1 (conflicted):
393 + zsuskuln f652c321 (empty) (no description set)
394 + qxosxrvv 6a211027 message
395 @git (behind by 1 commits): zsuskuln f652c321 (empty) (no description set)
396 @rem1 (behind by 1 commits): qxosxrvv 6a211027 message
397 "###);
398}
399
400// Helper functions to test obtaining multiple branches at once and changed
401// branches
402fn create_colocated_repo_and_branches_from_trunk1(
403 test_env: &TestEnvironment,
404 repo_path: &Path,
405) -> String {
406 // Create a colocated repo in `source` to populate it more easily
407 test_env.jj_cmd_ok(repo_path, &["init", "--git-repo", "."]);
408 create_commit(test_env, repo_path, "trunk1", &[]);
409 create_commit(test_env, repo_path, "a1", &["trunk1"]);
410 create_commit(test_env, repo_path, "a2", &["trunk1"]);
411 create_commit(test_env, repo_path, "b", &["trunk1"]);
412 format!(
413 " ===== Source git repo contents =====\n{}",
414 get_log_output(test_env, repo_path)
415 )
416}
417
418fn create_trunk2_and_rebase_branches(test_env: &TestEnvironment, repo_path: &Path) -> String {
419 create_commit(test_env, repo_path, "trunk2", &["trunk1"]);
420 for br in ["a1", "a2", "b"] {
421 test_env.jj_cmd_ok(repo_path, &["rebase", "-b", br, "-d", "trunk2"]);
422 }
423 format!(
424 " ===== Source git repo contents =====\n{}",
425 get_log_output(test_env, repo_path)
426 )
427}
428
429#[test]
430fn test_git_fetch_all() {
431 let test_env = TestEnvironment::default();
432 test_env.add_config("git.auto-local-branch = true");
433 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
434 let source_git_repo_path = test_env.env_root().join("source");
435 let _git_repo = git2::Repository::init(source_git_repo_path.clone()).unwrap();
436
437 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
438 let (stdout, stderr) =
439 test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "target"]);
440 insta::assert_snapshot!(stdout, @"");
441 insta::assert_snapshot!(stderr, @r###"
442 Fetching into new repo in "$TEST_ENV/target"
443 Nothing changed.
444 "###);
445 let target_jj_repo_path = test_env.env_root().join("target");
446
447 let source_log =
448 create_colocated_repo_and_branches_from_trunk1(&test_env, &source_git_repo_path);
449 insta::assert_snapshot!(source_log, @r###"
450 ===== Source git repo contents =====
451 @ c7d4bdcbc215 descr_for_b b
452 │ ◉ decaa3966c83 descr_for_a2 a2
453 ├─╯
454 │ ◉ 359a9a02457d descr_for_a1 a1
455 ├─╯
456 ◉ ff36dc55760e descr_for_trunk1 trunk1
457 ◉ 000000000000
458 "###);
459
460 // Nothing in our repo before the fetch
461 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
462 @ 230dd059e1b0
463 ◉ 000000000000
464 "###);
465 insta::assert_snapshot!(get_branch_output(&test_env, &target_jj_repo_path), @"");
466 let (stdout, stderr) = test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch"]);
467 insta::assert_snapshot!(stdout, @"");
468 insta::assert_snapshot!(stderr, @r###"
469 branch: a1@origin [new] tracked
470 branch: a2@origin [new] tracked
471 branch: b@origin [new] tracked
472 branch: trunk1@origin [new] tracked
473 "###);
474 insta::assert_snapshot!(get_branch_output(&test_env, &target_jj_repo_path), @r###"
475 a1: nknoxmzm 359a9a02 descr_for_a1
476 @origin: nknoxmzm 359a9a02 descr_for_a1
477 a2: qkvnknrk decaa396 descr_for_a2
478 @origin: qkvnknrk decaa396 descr_for_a2
479 b: vpupmnsl c7d4bdcb descr_for_b
480 @origin: vpupmnsl c7d4bdcb descr_for_b
481 trunk1: zowqyktl ff36dc55 descr_for_trunk1
482 @origin: zowqyktl ff36dc55 descr_for_trunk1
483 "###);
484 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
485 ◉ c7d4bdcbc215 descr_for_b b
486 │ ◉ decaa3966c83 descr_for_a2 a2
487 ├─╯
488 │ ◉ 359a9a02457d descr_for_a1 a1
489 ├─╯
490 ◉ ff36dc55760e descr_for_trunk1 trunk1
491 │ @ 230dd059e1b0
492 ├─╯
493 ◉ 000000000000
494 "###);
495
496 // ==== Change both repos ====
497 // First, change the target repo:
498 let source_log = create_trunk2_and_rebase_branches(&test_env, &source_git_repo_path);
499 insta::assert_snapshot!(source_log, @r###"
500 ===== Source git repo contents =====
501 ◉ babc49226c14 descr_for_b b
502 │ ◉ 91e46b4b2653 descr_for_a2 a2
503 ├─╯
504 │ ◉ 0424f6dfc1ff descr_for_a1 a1
505 ├─╯
506 @ 8f1f14fbbf42 descr_for_trunk2 trunk2
507 ◉ ff36dc55760e descr_for_trunk1 trunk1
508 ◉ 000000000000
509 "###);
510 // Change a branch in the source repo as well, so that it becomes conflicted.
511 test_env.jj_cmd_ok(
512 &target_jj_repo_path,
513 &["describe", "b", "-m=new_descr_for_b_to_create_conflict"],
514 );
515
516 // Our repo before and after fetch
517 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
518 ◉ 061eddbb43ab new_descr_for_b_to_create_conflict b*
519 │ ◉ decaa3966c83 descr_for_a2 a2
520 ├─╯
521 │ ◉ 359a9a02457d descr_for_a1 a1
522 ├─╯
523 ◉ ff36dc55760e descr_for_trunk1 trunk1
524 │ @ 230dd059e1b0
525 ├─╯
526 ◉ 000000000000
527 "###);
528 insta::assert_snapshot!(get_branch_output(&test_env, &target_jj_repo_path), @r###"
529 a1: nknoxmzm 359a9a02 descr_for_a1
530 @origin: nknoxmzm 359a9a02 descr_for_a1
531 a2: qkvnknrk decaa396 descr_for_a2
532 @origin: qkvnknrk decaa396 descr_for_a2
533 b: vpupmnsl 061eddbb new_descr_for_b_to_create_conflict
534 @origin (ahead by 1 commits, behind by 1 commits): vpupmnsl hidden c7d4bdcb descr_for_b
535 trunk1: zowqyktl ff36dc55 descr_for_trunk1
536 @origin: zowqyktl ff36dc55 descr_for_trunk1
537 "###);
538 let (stdout, stderr) = test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch"]);
539 insta::assert_snapshot!(stdout, @"");
540 insta::assert_snapshot!(stderr, @r###"
541 branch: a1@origin [updated] tracked
542 branch: a2@origin [updated] tracked
543 branch: b@origin [updated] tracked
544 branch: trunk2@origin [new] tracked
545 Abandoned 2 commits that are no longer reachable.
546 "###);
547 insta::assert_snapshot!(get_branch_output(&test_env, &target_jj_repo_path), @r###"
548 a1: quxllqov 0424f6df descr_for_a1
549 @origin: quxllqov 0424f6df descr_for_a1
550 a2: osusxwst 91e46b4b descr_for_a2
551 @origin: osusxwst 91e46b4b descr_for_a2
552 b (conflicted):
553 - vpupmnsl hidden c7d4bdcb descr_for_b
554 + vpupmnsl 061eddbb new_descr_for_b_to_create_conflict
555 + vktnwlsu babc4922 descr_for_b
556 @origin (behind by 1 commits): vktnwlsu babc4922 descr_for_b
557 trunk1: zowqyktl ff36dc55 descr_for_trunk1
558 @origin: zowqyktl ff36dc55 descr_for_trunk1
559 trunk2: umznmzko 8f1f14fb descr_for_trunk2
560 @origin: umznmzko 8f1f14fb descr_for_trunk2
561 "###);
562 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
563 ◉ babc49226c14 descr_for_b b?? b@origin
564 │ ◉ 91e46b4b2653 descr_for_a2 a2
565 ├─╯
566 │ ◉ 0424f6dfc1ff descr_for_a1 a1
567 ├─╯
568 ◉ 8f1f14fbbf42 descr_for_trunk2 trunk2
569 │ ◉ 061eddbb43ab new_descr_for_b_to_create_conflict b??
570 ├─╯
571 ◉ ff36dc55760e descr_for_trunk1 trunk1
572 │ @ 230dd059e1b0
573 ├─╯
574 ◉ 000000000000
575 "###);
576}
577
578#[test]
579fn test_git_fetch_some_of_many_branches() {
580 let test_env = TestEnvironment::default();
581 test_env.add_config("git.auto-local-branch = true");
582 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
583 let source_git_repo_path = test_env.env_root().join("source");
584 let _git_repo = git2::Repository::init(source_git_repo_path.clone()).unwrap();
585
586 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
587 let (stdout, stderr) =
588 test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "target"]);
589 insta::assert_snapshot!(stdout, @"");
590 insta::assert_snapshot!(stderr, @r###"
591 Fetching into new repo in "$TEST_ENV/target"
592 Nothing changed.
593 "###);
594 let target_jj_repo_path = test_env.env_root().join("target");
595
596 let source_log =
597 create_colocated_repo_and_branches_from_trunk1(&test_env, &source_git_repo_path);
598 insta::assert_snapshot!(source_log, @r###"
599 ===== Source git repo contents =====
600 @ c7d4bdcbc215 descr_for_b b
601 │ ◉ decaa3966c83 descr_for_a2 a2
602 ├─╯
603 │ ◉ 359a9a02457d descr_for_a1 a1
604 ├─╯
605 ◉ ff36dc55760e descr_for_trunk1 trunk1
606 ◉ 000000000000
607 "###);
608
609 // Test an error message
610 let stderr = test_env.jj_cmd_failure(
611 &target_jj_repo_path,
612 &["git", "fetch", "--branch", "glob:^:a*"],
613 );
614 insta::assert_snapshot!(stderr, @r###"
615 Error: Invalid branch pattern provided. Patterns may not contain the characters `:`, `^`, `?`, `[`, `]`
616 "###);
617 let stderr = test_env.jj_cmd_failure(&target_jj_repo_path, &["git", "fetch", "--branch", "a*"]);
618 insta::assert_snapshot!(stderr, @r###"
619 Error: Invalid branch pattern provided. Patterns may not contain the characters `:`, `^`, `?`, `[`, `]`
620 Hint: Prefix the pattern with `glob:` to expand `*` as a glob
621 "###);
622
623 // Nothing in our repo before the fetch
624 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
625 @ 230dd059e1b0
626 ◉ 000000000000
627 "###);
628 // Fetch one branch...
629 let (stdout, stderr) =
630 test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch", "--branch", "b"]);
631 insta::assert_snapshot!(stdout, @"");
632 insta::assert_snapshot!(stderr, @r###"
633 branch: b@origin [new] tracked
634 "###);
635 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
636 ◉ c7d4bdcbc215 descr_for_b b
637 ◉ ff36dc55760e descr_for_trunk1
638 │ @ 230dd059e1b0
639 ├─╯
640 ◉ 000000000000
641 "###);
642 // ...check what the intermediate state looks like...
643 insta::assert_snapshot!(get_branch_output(&test_env, &target_jj_repo_path), @r###"
644 b: vpupmnsl c7d4bdcb descr_for_b
645 @origin: vpupmnsl c7d4bdcb descr_for_b
646 "###);
647 // ...then fetch two others with a glob.
648 let (stdout, stderr) = test_env.jj_cmd_ok(
649 &target_jj_repo_path,
650 &["git", "fetch", "--branch", "glob:a*"],
651 );
652 insta::assert_snapshot!(stdout, @"");
653 insta::assert_snapshot!(stderr, @r###"
654 branch: a1@origin [new] tracked
655 branch: a2@origin [new] tracked
656 "###);
657 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
658 ◉ decaa3966c83 descr_for_a2 a2
659 │ ◉ 359a9a02457d descr_for_a1 a1
660 ├─╯
661 │ ◉ c7d4bdcbc215 descr_for_b b
662 ├─╯
663 ◉ ff36dc55760e descr_for_trunk1
664 │ @ 230dd059e1b0
665 ├─╯
666 ◉ 000000000000
667 "###);
668 // Fetching the same branch again
669 let (stdout, stderr) =
670 test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch", "--branch", "a1"]);
671 insta::assert_snapshot!(stdout, @"");
672 insta::assert_snapshot!(stderr, @r###"
673 Nothing changed.
674 "###);
675 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
676 ◉ decaa3966c83 descr_for_a2 a2
677 │ ◉ 359a9a02457d descr_for_a1 a1
678 ├─╯
679 │ ◉ c7d4bdcbc215 descr_for_b b
680 ├─╯
681 ◉ ff36dc55760e descr_for_trunk1
682 │ @ 230dd059e1b0
683 ├─╯
684 ◉ 000000000000
685 "###);
686
687 // ==== Change both repos ====
688 // First, change the target repo:
689 let source_log = create_trunk2_and_rebase_branches(&test_env, &source_git_repo_path);
690 insta::assert_snapshot!(source_log, @r###"
691 ===== Source git repo contents =====
692 ◉ 01d115196c39 descr_for_b b
693 │ ◉ 31c7d94b1f29 descr_for_a2 a2
694 ├─╯
695 │ ◉ 6df2d34cf0da descr_for_a1 a1
696 ├─╯
697 @ 2bb3ebd2bba3 descr_for_trunk2 trunk2
698 ◉ ff36dc55760e descr_for_trunk1 trunk1
699 ◉ 000000000000
700 "###);
701 // Change a branch in the source repo as well, so that it becomes conflicted.
702 test_env.jj_cmd_ok(
703 &target_jj_repo_path,
704 &["describe", "b", "-m=new_descr_for_b_to_create_conflict"],
705 );
706
707 // Our repo before and after fetch of two branches
708 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
709 ◉ 6ebd41dc4f13 new_descr_for_b_to_create_conflict b*
710 │ ◉ decaa3966c83 descr_for_a2 a2
711 ├─╯
712 │ ◉ 359a9a02457d descr_for_a1 a1
713 ├─╯
714 ◉ ff36dc55760e descr_for_trunk1
715 │ @ 230dd059e1b0
716 ├─╯
717 ◉ 000000000000
718 "###);
719 let (stdout, stderr) = test_env.jj_cmd_ok(
720 &target_jj_repo_path,
721 &["git", "fetch", "--branch", "b", "--branch", "a1"],
722 );
723 insta::assert_snapshot!(stdout, @"");
724 insta::assert_snapshot!(stderr, @r###"
725 branch: a1@origin [updated] tracked
726 branch: b@origin [updated] tracked
727 Abandoned 1 commits that are no longer reachable.
728 "###);
729 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
730 ◉ 01d115196c39 descr_for_b b?? b@origin
731 │ ◉ 6df2d34cf0da descr_for_a1 a1
732 ├─╯
733 ◉ 2bb3ebd2bba3 descr_for_trunk2
734 │ ◉ 6ebd41dc4f13 new_descr_for_b_to_create_conflict b??
735 ├─╯
736 │ ◉ decaa3966c83 descr_for_a2 a2
737 ├─╯
738 ◉ ff36dc55760e descr_for_trunk1
739 │ @ 230dd059e1b0
740 ├─╯
741 ◉ 000000000000
742 "###);
743
744 // We left a2 where it was before, let's see how `jj branch list` sees this.
745 insta::assert_snapshot!(get_branch_output(&test_env, &target_jj_repo_path), @r###"
746 a1: ypowunwp 6df2d34c descr_for_a1
747 @origin: ypowunwp 6df2d34c descr_for_a1
748 a2: qkvnknrk decaa396 descr_for_a2
749 @origin: qkvnknrk decaa396 descr_for_a2
750 b (conflicted):
751 - vpupmnsl hidden c7d4bdcb descr_for_b
752 + vpupmnsl 6ebd41dc new_descr_for_b_to_create_conflict
753 + nxrpswuq 01d11519 descr_for_b
754 @origin (behind by 1 commits): nxrpswuq 01d11519 descr_for_b
755 "###);
756 // Now, let's fetch a2 and double-check that fetching a1 and b again doesn't do
757 // anything.
758 let (stdout, stderr) = test_env.jj_cmd_ok(
759 &target_jj_repo_path,
760 &["git", "fetch", "--branch", "b", "--branch", "glob:a*"],
761 );
762 insta::assert_snapshot!(stdout, @"");
763 insta::assert_snapshot!(stderr, @r###"
764 branch: a2@origin [updated] tracked
765 Abandoned 1 commits that are no longer reachable.
766 "###);
767 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
768 ◉ 31c7d94b1f29 descr_for_a2 a2
769 │ ◉ 01d115196c39 descr_for_b b?? b@origin
770 ├─╯
771 │ ◉ 6df2d34cf0da descr_for_a1 a1
772 ├─╯
773 ◉ 2bb3ebd2bba3 descr_for_trunk2
774 │ ◉ 6ebd41dc4f13 new_descr_for_b_to_create_conflict b??
775 ├─╯
776 ◉ ff36dc55760e descr_for_trunk1
777 │ @ 230dd059e1b0
778 ├─╯
779 ◉ 000000000000
780 "###);
781 insta::assert_snapshot!(get_branch_output(&test_env, &target_jj_repo_path), @r###"
782 a1: ypowunwp 6df2d34c descr_for_a1
783 @origin: ypowunwp 6df2d34c descr_for_a1
784 a2: qrmzolkr 31c7d94b descr_for_a2
785 @origin: qrmzolkr 31c7d94b descr_for_a2
786 b (conflicted):
787 - vpupmnsl hidden c7d4bdcb descr_for_b
788 + vpupmnsl 6ebd41dc new_descr_for_b_to_create_conflict
789 + nxrpswuq 01d11519 descr_for_b
790 @origin (behind by 1 commits): nxrpswuq 01d11519 descr_for_b
791 "###);
792}
793
794// See `test_undo_restore_commands.rs` for fetch-undo-push and fetch-undo-fetch
795// of the same branches for various kinds of undo.
796#[test]
797fn test_git_fetch_undo() {
798 let test_env = TestEnvironment::default();
799 test_env.add_config("git.auto-local-branch = true");
800 let source_git_repo_path = test_env.env_root().join("source");
801 let _git_repo = git2::Repository::init(source_git_repo_path.clone()).unwrap();
802
803 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
804 let (stdout, stderr) =
805 test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "target"]);
806 insta::assert_snapshot!(stdout, @"");
807 insta::assert_snapshot!(stderr, @r###"
808 Fetching into new repo in "$TEST_ENV/target"
809 Nothing changed.
810 "###);
811 let target_jj_repo_path = test_env.env_root().join("target");
812
813 let source_log =
814 create_colocated_repo_and_branches_from_trunk1(&test_env, &source_git_repo_path);
815 insta::assert_snapshot!(source_log, @r###"
816 ===== Source git repo contents =====
817 @ c7d4bdcbc215 descr_for_b b
818 │ ◉ decaa3966c83 descr_for_a2 a2
819 ├─╯
820 │ ◉ 359a9a02457d descr_for_a1 a1
821 ├─╯
822 ◉ ff36dc55760e descr_for_trunk1 trunk1
823 ◉ 000000000000
824 "###);
825
826 // Fetch 2 branches
827 let (stdout, stderr) = test_env.jj_cmd_ok(
828 &target_jj_repo_path,
829 &["git", "fetch", "--branch", "b", "--branch", "a1"],
830 );
831 insta::assert_snapshot!(stdout, @"");
832 insta::assert_snapshot!(stderr, @r###"
833 branch: a1@origin [new] tracked
834 branch: b@origin [new] tracked
835 "###);
836 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
837 ◉ c7d4bdcbc215 descr_for_b b
838 │ ◉ 359a9a02457d descr_for_a1 a1
839 ├─╯
840 ◉ ff36dc55760e descr_for_trunk1
841 │ @ 230dd059e1b0
842 ├─╯
843 ◉ 000000000000
844 "###);
845 let (stdout, stderr) = test_env.jj_cmd_ok(&target_jj_repo_path, &["undo"]);
846 insta::assert_snapshot!(stdout, @"");
847 insta::assert_snapshot!(stderr, @"");
848 // The undo works as expected
849 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
850 @ 230dd059e1b0
851 ◉ 000000000000
852 "###);
853 // Now try to fetch just one branch
854 let (stdout, stderr) =
855 test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch", "--branch", "b"]);
856 insta::assert_snapshot!(stdout, @"");
857 insta::assert_snapshot!(stderr, @r###"
858 branch: b@origin [new] tracked
859 "###);
860 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
861 ◉ c7d4bdcbc215 descr_for_b b
862 ◉ ff36dc55760e descr_for_trunk1
863 │ @ 230dd059e1b0
864 ├─╯
865 ◉ 000000000000
866 "###);
867}
868
869// Compare to `test_git_import_undo` in test_git_import_export
870// TODO: Explain why these behaviors are useful
871#[test]
872fn test_fetch_undo_what() {
873 let test_env = TestEnvironment::default();
874 test_env.add_config("git.auto-local-branch = true");
875 let source_git_repo_path = test_env.env_root().join("source");
876 let _git_repo = git2::Repository::init(source_git_repo_path.clone()).unwrap();
877
878 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
879 let (stdout, stderr) =
880 test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "target"]);
881 insta::assert_snapshot!(stdout, @"");
882 insta::assert_snapshot!(stderr, @r###"
883 Fetching into new repo in "$TEST_ENV/target"
884 Nothing changed.
885 "###);
886 let repo_path = test_env.env_root().join("target");
887
888 let source_log =
889 create_colocated_repo_and_branches_from_trunk1(&test_env, &source_git_repo_path);
890 insta::assert_snapshot!(source_log, @r###"
891 ===== Source git repo contents =====
892 @ c7d4bdcbc215 descr_for_b b
893 │ ◉ decaa3966c83 descr_for_a2 a2
894 ├─╯
895 │ ◉ 359a9a02457d descr_for_a1 a1
896 ├─╯
897 ◉ ff36dc55760e descr_for_trunk1 trunk1
898 ◉ 000000000000
899 "###);
900
901 // Initial state we will try to return to after `op restore`. There are no
902 // branches.
903 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
904 let base_operation_id = test_env.current_operation_id(&repo_path);
905
906 // Fetch a branch
907 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--branch", "b"]);
908 insta::assert_snapshot!(stdout, @"");
909 insta::assert_snapshot!(stderr, @r###"
910 branch: b@origin [new] tracked
911 "###);
912 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
913 ◉ c7d4bdcbc215 descr_for_b b
914 ◉ ff36dc55760e descr_for_trunk1
915 │ @ 230dd059e1b0
916 ├─╯
917 ◉ 000000000000
918 "###);
919 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
920 b: vpupmnsl c7d4bdcb descr_for_b
921 @origin: vpupmnsl c7d4bdcb descr_for_b
922 "###);
923
924 // We can undo the change in the repo without moving the remote-tracking branch
925 let (stdout, stderr) = test_env.jj_cmd_ok(
926 &repo_path,
927 &["op", "restore", "--what", "repo", &base_operation_id],
928 );
929 insta::assert_snapshot!(stdout, @"");
930 insta::assert_snapshot!(stderr, @"");
931 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
932 b (deleted)
933 @origin: vpupmnsl hidden c7d4bdcb descr_for_b
934 (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this)
935 "###);
936
937 // Now, let's demo restoring just the remote-tracking branch. First, let's
938 // change our local repo state...
939 test_env.jj_cmd_ok(&repo_path, &["branch", "c", "newbranch"]);
940 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
941 b (deleted)
942 @origin: vpupmnsl hidden c7d4bdcb descr_for_b
943 (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this)
944 newbranch: qpvuntsm 230dd059 (empty) (no description set)
945 "###);
946 // Restoring just the remote-tracking state will not affect `newbranch`, but
947 // will eliminate `b@origin`.
948 let (stdout, stderr) = test_env.jj_cmd_ok(
949 &repo_path,
950 &[
951 "op",
952 "restore",
953 "--what",
954 "remote-tracking",
955 &base_operation_id,
956 ],
957 );
958 insta::assert_snapshot!(stdout, @"");
959 insta::assert_snapshot!(stderr, @"");
960 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
961 newbranch: qpvuntsm 230dd059 (empty) (no description set)
962 "###);
963}
964
965#[test]
966fn test_git_fetch_remove_fetch() {
967 let test_env = TestEnvironment::default();
968 test_env.add_config("git.auto-local-branch = true");
969 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
970 let repo_path = test_env.env_root().join("repo");
971 add_git_remote(&test_env, &repo_path, "origin");
972
973 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "origin"]);
974 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
975 origin: qpvuntsm 230dd059 (empty) (no description set)
976 "###);
977
978 test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
979 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
980 origin (conflicted):
981 + qpvuntsm 230dd059 (empty) (no description set)
982 + oputwtnw ffecd2d6 message
983 @origin (behind by 1 commits): oputwtnw ffecd2d6 message
984 "###);
985
986 test_env.jj_cmd_ok(&repo_path, &["git", "remote", "remove", "origin"]);
987 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
988 origin (conflicted):
989 + qpvuntsm 230dd059 (empty) (no description set)
990 + oputwtnw ffecd2d6 message
991 "###);
992
993 test_env.jj_cmd_ok(&repo_path, &["git", "remote", "add", "origin", "../origin"]);
994
995 // Check that origin@origin is properly recreated
996 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
997 insta::assert_snapshot!(stdout, @"");
998 insta::assert_snapshot!(stderr, @r###"
999 branch: origin@origin [new] tracked
1000 "###);
1001 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
1002 origin (conflicted):
1003 + qpvuntsm 230dd059 (empty) (no description set)
1004 + oputwtnw ffecd2d6 message
1005 @origin (behind by 1 commits): oputwtnw ffecd2d6 message
1006 "###);
1007}
1008
1009#[test]
1010fn test_git_fetch_rename_fetch() {
1011 let test_env = TestEnvironment::default();
1012 test_env.add_config("git.auto-local-branch = true");
1013 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
1014 let repo_path = test_env.env_root().join("repo");
1015 add_git_remote(&test_env, &repo_path, "origin");
1016
1017 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "origin"]);
1018 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
1019 origin: qpvuntsm 230dd059 (empty) (no description set)
1020 "###);
1021
1022 test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
1023 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
1024 origin (conflicted):
1025 + qpvuntsm 230dd059 (empty) (no description set)
1026 + oputwtnw ffecd2d6 message
1027 @origin (behind by 1 commits): oputwtnw ffecd2d6 message
1028 "###);
1029
1030 test_env.jj_cmd_ok(
1031 &repo_path,
1032 &["git", "remote", "rename", "origin", "upstream"],
1033 );
1034 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
1035 origin (conflicted):
1036 + qpvuntsm 230dd059 (empty) (no description set)
1037 + oputwtnw ffecd2d6 message
1038 @upstream (behind by 1 commits): oputwtnw ffecd2d6 message
1039 "###);
1040
1041 // Check that jj indicates that nothing has changed
1042 let (stdout, stderr) =
1043 test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote", "upstream"]);
1044 insta::assert_snapshot!(stdout, @"");
1045 insta::assert_snapshot!(stderr, @r###"
1046 Nothing changed.
1047 "###);
1048}
1049
1050#[test]
1051fn test_git_fetch_removed_branch() {
1052 let test_env = TestEnvironment::default();
1053 test_env.add_config("git.auto-local-branch = true");
1054 let source_git_repo_path = test_env.env_root().join("source");
1055 let _git_repo = git2::Repository::init(source_git_repo_path.clone()).unwrap();
1056
1057 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
1058 let (stdout, stderr) =
1059 test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "target"]);
1060 insta::assert_snapshot!(stdout, @"");
1061 insta::assert_snapshot!(stderr, @r###"
1062 Fetching into new repo in "$TEST_ENV/target"
1063 Nothing changed.
1064 "###);
1065 let target_jj_repo_path = test_env.env_root().join("target");
1066
1067 let source_log =
1068 create_colocated_repo_and_branches_from_trunk1(&test_env, &source_git_repo_path);
1069 insta::assert_snapshot!(source_log, @r###"
1070 ===== Source git repo contents =====
1071 @ c7d4bdcbc215 descr_for_b b
1072 │ ◉ decaa3966c83 descr_for_a2 a2
1073 ├─╯
1074 │ ◉ 359a9a02457d descr_for_a1 a1
1075 ├─╯
1076 ◉ ff36dc55760e descr_for_trunk1 trunk1
1077 ◉ 000000000000
1078 "###);
1079
1080 // Fetch all branches
1081 let (stdout, stderr) = test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch"]);
1082 insta::assert_snapshot!(stdout, @"");
1083 insta::assert_snapshot!(stderr, @r###"
1084 branch: a1@origin [new] tracked
1085 branch: a2@origin [new] tracked
1086 branch: b@origin [new] tracked
1087 branch: trunk1@origin [new] tracked
1088 "###);
1089 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
1090 ◉ c7d4bdcbc215 descr_for_b b
1091 │ ◉ decaa3966c83 descr_for_a2 a2
1092 ├─╯
1093 │ ◉ 359a9a02457d descr_for_a1 a1
1094 ├─╯
1095 ◉ ff36dc55760e descr_for_trunk1 trunk1
1096 │ @ 230dd059e1b0
1097 ├─╯
1098 ◉ 000000000000
1099 "###);
1100
1101 // Remove a2 branch in origin
1102 test_env.jj_cmd_ok(&source_git_repo_path, &["branch", "forget", "a2"]);
1103
1104 // Fetch branch a1 from origin and check that a2 is still there
1105 let (stdout, stderr) =
1106 test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch", "--branch", "a1"]);
1107 insta::assert_snapshot!(stdout, @"");
1108 insta::assert_snapshot!(stderr, @r###"
1109 Nothing changed.
1110 "###);
1111 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
1112 ◉ c7d4bdcbc215 descr_for_b b
1113 │ ◉ decaa3966c83 descr_for_a2 a2
1114 ├─╯
1115 │ ◉ 359a9a02457d descr_for_a1 a1
1116 ├─╯
1117 ◉ ff36dc55760e descr_for_trunk1 trunk1
1118 │ @ 230dd059e1b0
1119 ├─╯
1120 ◉ 000000000000
1121 "###);
1122
1123 // Fetch branches a2 from origin, and check that it has been removed locally
1124 let (stdout, stderr) =
1125 test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch", "--branch", "a2"]);
1126 insta::assert_snapshot!(stdout, @"");
1127 insta::assert_snapshot!(stderr, @r###"
1128 branch: a2@origin [deleted] untracked
1129 Abandoned 1 commits that are no longer reachable.
1130 "###);
1131 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
1132 ◉ c7d4bdcbc215 descr_for_b b
1133 │ ◉ 359a9a02457d descr_for_a1 a1
1134 ├─╯
1135 ◉ ff36dc55760e descr_for_trunk1 trunk1
1136 │ @ 230dd059e1b0
1137 ├─╯
1138 ◉ 000000000000
1139 "###);
1140}
1141
1142#[test]
1143fn test_git_fetch_removed_parent_branch() {
1144 let test_env = TestEnvironment::default();
1145 test_env.add_config("git.auto-local-branch = true");
1146 let source_git_repo_path = test_env.env_root().join("source");
1147 let _git_repo = git2::Repository::init(source_git_repo_path.clone()).unwrap();
1148
1149 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated
1150 let (stdout, stderr) =
1151 test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "target"]);
1152 insta::assert_snapshot!(stdout, @"");
1153 insta::assert_snapshot!(stderr, @r###"
1154 Fetching into new repo in "$TEST_ENV/target"
1155 Nothing changed.
1156 "###);
1157 let target_jj_repo_path = test_env.env_root().join("target");
1158
1159 let source_log =
1160 create_colocated_repo_and_branches_from_trunk1(&test_env, &source_git_repo_path);
1161 insta::assert_snapshot!(source_log, @r###"
1162 ===== Source git repo contents =====
1163 @ c7d4bdcbc215 descr_for_b b
1164 │ ◉ decaa3966c83 descr_for_a2 a2
1165 ├─╯
1166 │ ◉ 359a9a02457d descr_for_a1 a1
1167 ├─╯
1168 ◉ ff36dc55760e descr_for_trunk1 trunk1
1169 ◉ 000000000000
1170 "###);
1171
1172 // Fetch all branches
1173 let (stdout, stderr) = test_env.jj_cmd_ok(&target_jj_repo_path, &["git", "fetch"]);
1174 insta::assert_snapshot!(stdout, @"");
1175 insta::assert_snapshot!(stderr, @r###"
1176 branch: a1@origin [new] tracked
1177 branch: a2@origin [new] tracked
1178 branch: b@origin [new] tracked
1179 branch: trunk1@origin [new] tracked
1180 "###);
1181 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
1182 ◉ c7d4bdcbc215 descr_for_b b
1183 │ ◉ decaa3966c83 descr_for_a2 a2
1184 ├─╯
1185 │ ◉ 359a9a02457d descr_for_a1 a1
1186 ├─╯
1187 ◉ ff36dc55760e descr_for_trunk1 trunk1
1188 │ @ 230dd059e1b0
1189 ├─╯
1190 ◉ 000000000000
1191 "###);
1192
1193 // Remove all branches in origin.
1194 test_env.jj_cmd_ok(&source_git_repo_path, &["branch", "forget", "--glob", "*"]);
1195
1196 // Fetch branches master, trunk1 and a1 from origin and check that only those
1197 // branches have been removed and that others were not rebased because of
1198 // abandoned commits.
1199 let (stdout, stderr) = test_env.jj_cmd_ok(
1200 &target_jj_repo_path,
1201 &[
1202 "git", "fetch", "--branch", "master", "--branch", "trunk1", "--branch", "a1",
1203 ],
1204 );
1205 insta::assert_snapshot!(stdout, @"");
1206 insta::assert_snapshot!(stderr, @r###"
1207 branch: a1@origin [deleted] untracked
1208 branch: trunk1@origin [deleted] untracked
1209 Abandoned 1 commits that are no longer reachable.
1210 "###);
1211 insta::assert_snapshot!(get_log_output(&test_env, &target_jj_repo_path), @r###"
1212 ◉ c7d4bdcbc215 descr_for_b b
1213 │ ◉ decaa3966c83 descr_for_a2 a2
1214 ├─╯
1215 ◉ ff36dc55760e descr_for_trunk1
1216 │ @ 230dd059e1b0
1217 ├─╯
1218 ◉ 000000000000
1219 "###);
1220}
1221
1222#[test]
1223fn test_git_fetch_remote_only_branch() {
1224 let test_env = TestEnvironment::default();
1225 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
1226 let repo_path = test_env.env_root().join("repo");
1227
1228 // Create non-empty git repo to add as a remote
1229 let git_repo_path = test_env.env_root().join("git-repo");
1230 let git_repo = git2::Repository::init(git_repo_path).unwrap();
1231 let signature =
1232 git2::Signature::new("Some One", "some.one@example.com", &git2::Time::new(0, 0)).unwrap();
1233 let mut tree_builder = git_repo.treebuilder(None).unwrap();
1234 let file_oid = git_repo.blob(b"content").unwrap();
1235 tree_builder
1236 .insert("file", file_oid, git2::FileMode::Blob.into())
1237 .unwrap();
1238 let tree_oid = tree_builder.write().unwrap();
1239 let tree = git_repo.find_tree(tree_oid).unwrap();
1240 test_env.jj_cmd_ok(
1241 &repo_path,
1242 &["git", "remote", "add", "origin", "../git-repo"],
1243 );
1244 // Create a commit and a branch in the git repo
1245 git_repo
1246 .commit(
1247 Some("refs/heads/feature1"),
1248 &signature,
1249 &signature,
1250 "message",
1251 &tree,
1252 &[],
1253 )
1254 .unwrap();
1255
1256 // Fetch using git.auto_local_branch = true
1257 test_env.add_config("git.auto-local-branch = true");
1258 test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
1259 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
1260 feature1: mzyxwzks 9f01a0e0 message
1261 @origin: mzyxwzks 9f01a0e0 message
1262 "###);
1263
1264 git_repo
1265 .commit(
1266 Some("refs/heads/feature2"),
1267 &signature,
1268 &signature,
1269 "message",
1270 &tree,
1271 &[],
1272 )
1273 .unwrap();
1274
1275 // Fetch using git.auto_local_branch = false
1276 test_env.add_config("git.auto-local-branch = false");
1277 test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
1278 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
1279 ◉ 9f01a0e04879 message feature1 feature2@origin
1280 │ @ 230dd059e1b0
1281 ├─╯
1282 ◉ 000000000000
1283 "###);
1284 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
1285 feature1: mzyxwzks 9f01a0e0 message
1286 @origin: mzyxwzks 9f01a0e0 message
1287 feature2@origin: mzyxwzks 9f01a0e0 message
1288 "###);
1289}