just playing with tangled
1// Copyright 2022 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.
14
15use itertools::Itertools as _;
16use jj_lib::backend::CommitId;
17use testutils::git;
18
19use crate::common::CommandOutput;
20use crate::common::TestEnvironment;
21use crate::common::TestWorkDir;
22
23#[test]
24fn test_resolution_of_git_tracking_bookmarks() {
25 let test_env = TestEnvironment::default();
26 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
27 let work_dir = test_env.work_dir("repo");
28 work_dir
29 .run_jj(["bookmark", "create", "-r@", "main"])
30 .success();
31 work_dir
32 .run_jj(["describe", "-r", "main", "-m", "old_message"])
33 .success();
34
35 // Create local-git tracking bookmark
36 let output = work_dir.run_jj(["git", "export"]);
37 insta::assert_snapshot!(output, @"");
38 // Move the local bookmark somewhere else
39 work_dir
40 .run_jj(["describe", "-r", "main", "-m", "new_message"])
41 .success();
42 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
43 main: qpvuntsm b61d21b6 (empty) new_message
44 @git (ahead by 1 commits, behind by 1 commits): qpvuntsm hidden 03757d22 (empty) old_message
45 [EOF]
46 ");
47
48 // Test that we can address both revisions
49 let query = |expr| {
50 let template = r#"commit_id ++ " " ++ description"#;
51 work_dir.run_jj(["log", "-r", expr, "-T", template, "--no-graph"])
52 };
53 insta::assert_snapshot!(query("main"), @r"
54 b61d21b660c17a7191f3f73873bfe7d3f7938628 new_message
55 [EOF]
56 ");
57 insta::assert_snapshot!(query("main@git"), @r"
58 03757d2212d89990ec158e97795b612a38446652 old_message
59 [EOF]
60 ");
61 // Can't be selected by remote_bookmarks()
62 insta::assert_snapshot!(query(r#"remote_bookmarks(exact:"main", exact:"git")"#), @"");
63}
64
65#[test]
66fn test_git_export_conflicting_git_refs() {
67 let test_env = TestEnvironment::default();
68 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
69 let work_dir = test_env.work_dir("repo");
70
71 work_dir
72 .run_jj(["bookmark", "create", "-r@", "main"])
73 .success();
74 work_dir
75 .run_jj(["bookmark", "create", "-r@", "main/sub"])
76 .success();
77 let output = work_dir.run_jj(["git", "export"]);
78 insta::with_settings!({filters => vec![("Failed to set: .*", "Failed to set: ...")]}, {
79 insta::assert_snapshot!(output, @r#"
80 ------- stderr -------
81 Warning: Failed to export some bookmarks:
82 main/sub@git: Failed to set: ...
83 Hint: Git doesn't allow a branch name that looks like a parent directory of
84 another (e.g. `foo` and `foo/bar`). Try to rename the bookmarks that failed to
85 export or their "parent" bookmarks.
86 [EOF]
87 "#);
88 });
89}
90
91#[test]
92fn test_git_export_undo() {
93 let test_env = TestEnvironment::default();
94 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
95 let work_dir = test_env.work_dir("repo");
96 let git_repo = git::open(work_dir.root().join(".jj/repo/store/git"));
97
98 work_dir
99 .run_jj(["bookmark", "create", "-r@", "a"])
100 .success();
101 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
102 a: qpvuntsm 230dd059 (empty) (no description set)
103 [EOF]
104 ");
105 let output = work_dir.run_jj(["git", "export"]);
106 insta::assert_snapshot!(output, @"");
107 insta::assert_snapshot!(work_dir.run_jj(["log", "-ra@git"]), @r"
108 @ qpvuntsm test.user@example.com 2001-02-03 08:05:07 a 230dd059
109 │ (empty) (no description set)
110 ~
111 [EOF]
112 ");
113
114 // Exported refs won't be removed by undoing the export, but the git-tracking
115 // bookmark is. This is the same as remote-tracking bookmarks.
116 let output = work_dir.run_jj(["op", "undo"]);
117 insta::assert_snapshot!(output, @r"
118 ------- stderr -------
119 Undid operation: edb40232c741 (2001-02-03 08:05:10) export git refs
120 [EOF]
121 ");
122 insta::assert_debug_snapshot!(get_git_repo_refs(&git_repo), @r#"
123 [
124 (
125 "refs/heads/a",
126 CommitId(
127 "230dd059e1b059aefc0da06a2e5a7dbf22362f22",
128 ),
129 ),
130 ]
131 "#);
132 insta::assert_snapshot!(work_dir.run_jj(["log", "-ra@git"]), @r"
133 ------- stderr -------
134 Error: Revision `a@git` doesn't exist
135 Hint: Did you mean `a`?
136 [EOF]
137 [exit status: 1]
138 ");
139
140 // This would re-export bookmark "a" and create git-tracking bookmark.
141 let output = work_dir.run_jj(["git", "export"]);
142 insta::assert_snapshot!(output, @"");
143 insta::assert_snapshot!(work_dir.run_jj(["log", "-ra@git"]), @r"
144 @ qpvuntsm test.user@example.com 2001-02-03 08:05:07 a 230dd059
145 │ (empty) (no description set)
146 ~
147 [EOF]
148 ");
149}
150
151#[test]
152fn test_git_import_undo() {
153 let test_env = TestEnvironment::default();
154 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
155 let work_dir = test_env.work_dir("repo");
156 let git_repo = git::open(work_dir.root().join(".jj/repo/store/git"));
157
158 // Create bookmark "a" in git repo
159 let commit_id = work_dir
160 .run_jj(&["log", "-Tcommit_id", "--no-graph", "-r@"])
161 .success()
162 .stdout
163 .into_raw();
164 let commit_id = gix::ObjectId::from_hex(commit_id.as_bytes()).unwrap();
165 git_repo
166 .reference(
167 "refs/heads/a",
168 commit_id,
169 gix::refs::transaction::PreviousValue::Any,
170 "",
171 )
172 .unwrap();
173
174 // Initial state we will return to after `undo`. There are no bookmarks.
175 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
176 let base_operation_id = work_dir.current_operation_id();
177
178 let output = work_dir.run_jj(["git", "import"]);
179 insta::assert_snapshot!(output, @r"
180 ------- stderr -------
181 bookmark: a@git [new] tracked
182 [EOF]
183 ");
184 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
185 a: qpvuntsm 230dd059 (empty) (no description set)
186 @git: qpvuntsm 230dd059 (empty) (no description set)
187 [EOF]
188 ");
189
190 // "git import" can be undone by default.
191 let output = work_dir.run_jj(["op", "restore", &base_operation_id]);
192 insta::assert_snapshot!(output, @r"
193 ------- stderr -------
194 Restored to operation: eac759b9ab75 (2001-02-03 08:05:07) add workspace 'default'
195 [EOF]
196 ");
197 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
198 // Try "git import" again, which should re-import the bookmark "a".
199 let output = work_dir.run_jj(["git", "import"]);
200 insta::assert_snapshot!(output, @r"
201 ------- stderr -------
202 bookmark: a@git [new] tracked
203 [EOF]
204 ");
205 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
206 a: qpvuntsm 230dd059 (empty) (no description set)
207 @git: qpvuntsm 230dd059 (empty) (no description set)
208 [EOF]
209 ");
210}
211
212#[test]
213fn test_git_import_move_export_with_default_undo() {
214 let test_env = TestEnvironment::default();
215 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
216 let work_dir = test_env.work_dir("repo");
217 let git_repo = git::open(work_dir.root().join(".jj/repo/store/git"));
218
219 // Create bookmark "a" in git repo
220 let commit_id = work_dir
221 .run_jj(&["log", "-Tcommit_id", "--no-graph", "-r@"])
222 .success()
223 .stdout
224 .into_raw();
225 let commit_id = gix::ObjectId::from_hex(commit_id.as_bytes()).unwrap();
226 git_repo
227 .reference(
228 "refs/heads/a",
229 commit_id,
230 gix::refs::transaction::PreviousValue::Any,
231 "",
232 )
233 .unwrap();
234
235 // Initial state we will try to return to after `op restore`. There are no
236 // bookmarks.
237 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
238 let base_operation_id = work_dir.current_operation_id();
239
240 let output = work_dir.run_jj(["git", "import"]);
241 insta::assert_snapshot!(output, @r"
242 ------- stderr -------
243 bookmark: a@git [new] tracked
244 [EOF]
245 ");
246 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
247 a: qpvuntsm 230dd059 (empty) (no description set)
248 @git: qpvuntsm 230dd059 (empty) (no description set)
249 [EOF]
250 ");
251
252 // Move bookmark "a" and export to git repo
253 work_dir.run_jj(["new"]).success();
254 work_dir
255 .run_jj(["bookmark", "set", "a", "--to=@"])
256 .success();
257 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
258 a: yqosqzyt 096dc80d (empty) (no description set)
259 @git (behind by 1 commits): qpvuntsm 230dd059 (empty) (no description set)
260 [EOF]
261 ");
262 let output = work_dir.run_jj(["git", "export"]);
263 insta::assert_snapshot!(output, @"");
264 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
265 a: yqosqzyt 096dc80d (empty) (no description set)
266 @git: yqosqzyt 096dc80d (empty) (no description set)
267 [EOF]
268 ");
269
270 // "git import" can be undone with the default `restore` behavior, as shown in
271 // the previous test. However, "git export" can't: the bookmarks in the git
272 // repo stay where they were.
273 let output = work_dir.run_jj(["op", "restore", &base_operation_id]);
274 insta::assert_snapshot!(output, @r"
275 ------- stderr -------
276 Restored to operation: eac759b9ab75 (2001-02-03 08:05:07) add workspace 'default'
277 Working copy (@) now at: qpvuntsm 230dd059 (empty) (no description set)
278 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
279 [EOF]
280 ");
281 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
282 insta::assert_debug_snapshot!(get_git_repo_refs(&git_repo), @r#"
283 [
284 (
285 "refs/heads/a",
286 CommitId(
287 "096dc80da67094fbaa6683e2a205dddffa31f9a8",
288 ),
289 ),
290 ]
291 "#);
292
293 // The last bookmark "a" state is imported from git. No idea what's the most
294 // intuitive result here.
295 let output = work_dir.run_jj(["git", "import"]);
296 insta::assert_snapshot!(output, @r"
297 ------- stderr -------
298 bookmark: a@git [new] tracked
299 [EOF]
300 ");
301 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
302 a: yqosqzyt 096dc80d (empty) (no description set)
303 @git: yqosqzyt 096dc80d (empty) (no description set)
304 [EOF]
305 ");
306}
307
308#[must_use]
309fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput {
310 work_dir.run_jj(["bookmark", "list", "--all-remotes"])
311}
312
313fn get_git_repo_refs(git_repo: &gix::Repository) -> Vec<(bstr::BString, CommitId)> {
314 let mut refs: Vec<_> = git_repo
315 .references()
316 .unwrap()
317 .all()
318 .unwrap()
319 .filter_ok(|git_ref| {
320 matches!(
321 git_ref.name().category(),
322 Some(gix::reference::Category::Tag)
323 | Some(gix::reference::Category::LocalBranch)
324 | Some(gix::reference::Category::RemoteBranch),
325 )
326 })
327 .filter_map_ok(|mut git_ref| {
328 let full_name = git_ref.name().as_bstr().to_owned();
329 let git_commit = git_ref.peel_to_commit().ok()?;
330 let commit_id = CommitId::from_bytes(git_commit.id().as_bytes());
331 Some((full_name, commit_id))
332 })
333 .try_collect()
334 .unwrap();
335 refs.sort();
336 refs
337}