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.
14
15use crate::common::create_commit_with_files;
16use crate::common::CommandOutput;
17use crate::common::TestEnvironment;
18use crate::common::TestWorkDir;
19
20#[must_use]
21fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
22 work_dir.run_jj(["log", "-T", "bookmarks"])
23}
24
25#[test]
26fn test_chmod_regular_conflict() {
27 let test_env = TestEnvironment::default();
28 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
29 let work_dir = test_env.work_dir("repo");
30
31 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
32 create_commit_with_files(&work_dir, "n", &["base"], &[("file", "n\n")]);
33 create_commit_with_files(&work_dir, "x", &["base"], &[("file", "x\n")]);
34 // Test chmodding a file. The effect will be visible in the conflict below.
35 work_dir
36 .run_jj(["file", "chmod", "x", "file", "-r=x"])
37 .success();
38 create_commit_with_files(&work_dir, "conflict", &["x", "n"], &[]);
39
40 // Test the setup
41 insta::assert_snapshot!(get_log_output(&work_dir), @r"
42 @ conflict
43 ├─╮
44 │ ○ n
45 ○ │ x
46 ├─╯
47 ○ base
48 ◆
49 [EOF]
50 ");
51 let output = work_dir.run_jj(["debug", "tree"]);
52 insta::assert_snapshot!(output, @r#"
53 file: Ok(Conflicted([Some(File { id: FileId("587be6b4c3f93f93c489c0111bba5596147a26cb"), executable: true }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("8ba3a16384aacc37d01564b28401755ce8053f51"), executable: false })]))
54 [EOF]
55 "#);
56 let output = work_dir.run_jj(["file", "show", "file"]);
57 insta::assert_snapshot!(output, @r"
58 <<<<<<< Conflict 1 of 1
59 %%%%%%% Changes from base to side #1
60 -base
61 +x
62 +++++++ Contents of side #2
63 n
64 >>>>>>> Conflict 1 of 1 ends
65 [EOF]
66 ");
67
68 // Test chmodding a conflict
69 work_dir.run_jj(["file", "chmod", "x", "file"]).success();
70 let output = work_dir.run_jj(["debug", "tree"]);
71 insta::assert_snapshot!(output, @r#"
72 file: Ok(Conflicted([Some(File { id: FileId("587be6b4c3f93f93c489c0111bba5596147a26cb"), executable: true }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: true }), Some(File { id: FileId("8ba3a16384aacc37d01564b28401755ce8053f51"), executable: true })]))
73 [EOF]
74 "#);
75 let output = work_dir.run_jj(["file", "show", "file"]);
76 insta::assert_snapshot!(output, @r"
77 <<<<<<< Conflict 1 of 1
78 %%%%%%% Changes from base to side #1
79 -base
80 +x
81 +++++++ Contents of side #2
82 n
83 >>>>>>> Conflict 1 of 1 ends
84 [EOF]
85 ");
86 work_dir.run_jj(["file", "chmod", "n", "file"]).success();
87 let output = work_dir.run_jj(["debug", "tree"]);
88 insta::assert_snapshot!(output, @r#"
89 file: Ok(Conflicted([Some(File { id: FileId("587be6b4c3f93f93c489c0111bba5596147a26cb"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("8ba3a16384aacc37d01564b28401755ce8053f51"), executable: false })]))
90 [EOF]
91 "#);
92 let output = work_dir.run_jj(["file", "show", "file"]);
93 insta::assert_snapshot!(output, @r"
94 <<<<<<< Conflict 1 of 1
95 %%%%%%% Changes from base to side #1
96 -base
97 +x
98 +++++++ Contents of side #2
99 n
100 >>>>>>> Conflict 1 of 1 ends
101 [EOF]
102 ");
103
104 // Unmatched paths should generate warnings
105 let output = work_dir.run_jj(["file", "chmod", "x", "nonexistent", "file"]);
106 insta::assert_snapshot!(output, @r"
107 ------- stderr -------
108 Warning: No matching entries for paths: nonexistent
109 Working copy (@) now at: yostqsxw 2b11d002 conflict | (conflict) conflict
110 Parent commit (@-) : royxmykx 427fbd2f x | x
111 Parent commit (@-) : zsuskuln 3f83a26d n | n
112 Added 0 files, modified 1 files, removed 0 files
113 Warning: There are unresolved conflicts at these paths:
114 file 2-sided conflict including an executable
115 [EOF]
116 ");
117}
118
119// TODO: Test demonstrating that conflicts whose *base* is not a file are
120// chmod-dable
121
122#[test]
123fn test_chmod_file_dir_deletion_conflicts() {
124 let test_env = TestEnvironment::default();
125 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
126 let work_dir = test_env.work_dir("repo");
127
128 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
129 create_commit_with_files(&work_dir, "file", &["base"], &[("file", "a\n")]);
130
131 create_commit_with_files(&work_dir, "deletion", &["base"], &[]);
132 work_dir.remove_file("file");
133
134 create_commit_with_files(&work_dir, "dir", &["base"], &[]);
135 work_dir.remove_file("file");
136 work_dir.create_dir("file");
137 // Without a placeholder file, `jj` ignores an empty directory
138 work_dir.write_file("file/placeholder", "");
139
140 // Create a file-dir conflict and a file-deletion conflict
141 create_commit_with_files(&work_dir, "file_dir", &["file", "dir"], &[]);
142 create_commit_with_files(&work_dir, "file_deletion", &["file", "deletion"], &[]);
143 insta::assert_snapshot!(get_log_output(&work_dir), @r"
144 @ file_deletion
145 ├─╮
146 │ ○ deletion
147 │ │ × file_dir
148 ╭───┤
149 │ │ ○ dir
150 │ ├─╯
151 ○ │ file
152 ├─╯
153 ○ base
154 ◆
155 [EOF]
156 ");
157
158 // The file-dir conflict cannot be chmod-ed
159 let output = work_dir.run_jj(["debug", "tree", "-r=file_dir"]);
160 insta::assert_snapshot!(output, @r#"
161 file: Ok(Conflicted([Some(File { id: FileId("78981922613b2afb6025042ff6bd878ac1994e85"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(Tree(TreeId("133bb38fc4e4bf6b551f1f04db7e48f04cac2877")))]))
162 [EOF]
163 "#);
164 let output = work_dir.run_jj(["file", "show", "-r=file_dir", "file"]);
165 insta::assert_snapshot!(output, @r"
166 Conflict:
167 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55
168 Adding file with id 78981922613b2afb6025042ff6bd878ac1994e85
169 Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877
170 [EOF]
171 ");
172 let output = work_dir.run_jj(["file", "chmod", "x", "file", "-r=file_dir"]);
173 insta::assert_snapshot!(output, @r"
174 ------- stderr -------
175 Error: Some of the sides of the conflict are not files at 'file'.
176 [EOF]
177 [exit status: 1]
178 ");
179
180 // The file_deletion conflict can be chmod-ed
181 let output = work_dir.run_jj(["debug", "tree", "-r=file_deletion"]);
182 insta::assert_snapshot!(output, @r#"
183 file: Ok(Conflicted([Some(File { id: FileId("78981922613b2afb6025042ff6bd878ac1994e85"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), None]))
184 [EOF]
185 "#);
186 let output = work_dir.run_jj(["file", "show", "-r=file_deletion", "file"]);
187 insta::assert_snapshot!(output, @r"
188 <<<<<<< Conflict 1 of 1
189 +++++++ Contents of side #1
190 a
191 %%%%%%% Changes from base to side #2
192 -base
193 >>>>>>> Conflict 1 of 1 ends
194 [EOF]
195 ");
196 let output = work_dir.run_jj(["file", "chmod", "x", "file", "-r=file_deletion"]);
197 insta::assert_snapshot!(output, @r"
198 ------- stderr -------
199 Working copy (@) now at: kmkuslsw 139dee15 file_deletion | (conflict) file_deletion
200 Parent commit (@-) : zsuskuln c51c9c55 file | file
201 Parent commit (@-) : royxmykx 6b18b3c1 deletion | deletion
202 Added 0 files, modified 1 files, removed 0 files
203 Warning: There are unresolved conflicts at these paths:
204 file 2-sided conflict including 1 deletion and an executable
205 New conflicts appeared in 1 commits:
206 kmkuslsw 139dee15 file_deletion | (conflict) file_deletion
207 Hint: To resolve the conflicts, start by updating to it:
208 jj new kmkuslsw
209 Then use `jj resolve`, or edit the conflict markers in the file directly.
210 Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
211 Then run `jj squash` to move the resolution into the conflicted commit.
212 [EOF]
213 ");
214 let output = work_dir.run_jj(["debug", "tree", "-r=file_deletion"]);
215 insta::assert_snapshot!(output, @r#"
216 file: Ok(Conflicted([Some(File { id: FileId("78981922613b2afb6025042ff6bd878ac1994e85"), executable: true }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: true }), None]))
217 [EOF]
218 "#);
219 let output = work_dir.run_jj(["file", "show", "-r=file_deletion", "file"]);
220 insta::assert_snapshot!(output, @r"
221 <<<<<<< Conflict 1 of 1
222 +++++++ Contents of side #1
223 a
224 %%%%%%% Changes from base to side #2
225 -base
226 >>>>>>> Conflict 1 of 1 ends
227 [EOF]
228 ");
229}