just playing with tangled
1// Copyright 2020 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 futures::StreamExt as _;
16use indoc::indoc;
17use itertools::Itertools as _;
18use jj_lib::backend::ChangeId;
19use jj_lib::backend::MillisSinceEpoch;
20use jj_lib::backend::Signature;
21use jj_lib::backend::Timestamp;
22use jj_lib::config::ConfigLayer;
23use jj_lib::config::ConfigSource;
24use jj_lib::config::StackedConfig;
25use jj_lib::matchers::EverythingMatcher;
26use jj_lib::merged_tree::MergedTree;
27use jj_lib::repo::Repo as _;
28use jj_lib::repo_path::RepoPath;
29use jj_lib::repo_path::RepoPathBuf;
30use jj_lib::rewrite::RebaseOptions;
31use jj_lib::settings::UserSettings;
32use pollster::FutureExt as _;
33use test_case::test_case;
34use testutils::assert_rebased_onto;
35use testutils::create_tree;
36use testutils::rebase_descendants_with_options_return_map;
37use testutils::CommitGraphBuilder;
38use testutils::TestRepo;
39use testutils::TestRepoBackend;
40
41fn config_with_commit_timestamp(timestamp: &str) -> StackedConfig {
42 let mut config = testutils::base_user_config();
43 let mut layer = ConfigLayer::empty(ConfigSource::User);
44 layer
45 .set_value("debug.commit-timestamp", timestamp)
46 .unwrap();
47 config.add_layer(layer);
48 config
49}
50
51fn diff_paths(from_tree: &MergedTree, to_tree: &MergedTree) -> Vec<RepoPathBuf> {
52 from_tree
53 .diff_stream(to_tree, &EverythingMatcher)
54 .map(|diff| {
55 let _ = diff.values.unwrap();
56 diff.path
57 })
58 .collect()
59 .block_on()
60}
61
62fn to_owned_path_vec(paths: &[&RepoPath]) -> Vec<RepoPathBuf> {
63 paths.iter().map(|&path| path.to_owned()).collect()
64}
65
66#[test_case(TestRepoBackend::Simple ; "simple backend")]
67#[test_case(TestRepoBackend::Git ; "git backend")]
68fn test_initial(backend: TestRepoBackend) {
69 let test_repo = TestRepo::init_with_backend(backend);
70 let repo = &test_repo.repo;
71 let store = repo.store();
72
73 let root_file_path = RepoPath::from_internal_string("file");
74 let dir_file_path = RepoPath::from_internal_string("dir/file");
75 let tree = create_tree(
76 repo,
77 &[
78 (root_file_path, "file contents"),
79 (dir_file_path, "dir/file contents"),
80 ],
81 );
82
83 let mut tx = repo.start_transaction();
84 let author_signature = Signature {
85 name: "author name".to_string(),
86 email: "author email".to_string(),
87 timestamp: Timestamp {
88 timestamp: MillisSinceEpoch(1000),
89 tz_offset: 60,
90 },
91 };
92 let committer_signature = Signature {
93 name: "committer name".to_string(),
94 email: "committer email".to_string(),
95 timestamp: Timestamp {
96 timestamp: MillisSinceEpoch(2000),
97 tz_offset: -60,
98 },
99 };
100 let change_id = ChangeId::new(vec![100u8; 16]);
101 let builder = tx
102 .repo_mut()
103 .new_commit(vec![store.root_commit_id().clone()], tree.id())
104 .set_change_id(change_id.clone())
105 .set_description("description")
106 .set_author(author_signature.clone())
107 .set_committer(committer_signature.clone());
108 assert_eq!(builder.parents(), &[store.root_commit_id().clone()]);
109 assert_eq!(builder.predecessors(), &[]);
110 assert_eq!(builder.tree_id(), &tree.id());
111 assert_eq!(builder.change_id(), &change_id);
112 assert_eq!(builder.author(), &author_signature);
113 assert_eq!(builder.committer(), &committer_signature);
114 let commit = builder.write().unwrap();
115 tx.commit("test").unwrap();
116
117 let parents: Vec<_> = commit.parents().try_collect().unwrap();
118 assert_eq!(parents, vec![store.root_commit()]);
119 assert!(commit.predecessors().next().is_none());
120 assert_eq!(commit.description(), "description");
121 assert_eq!(commit.author(), &author_signature);
122 assert_eq!(commit.committer(), &committer_signature);
123 assert_eq!(
124 diff_paths(
125 &store.root_commit().tree().unwrap(),
126 &commit.tree().unwrap(),
127 ),
128 to_owned_path_vec(&[dir_file_path, root_file_path]),
129 );
130}
131
132#[test_case(TestRepoBackend::Simple ; "simple backend")]
133#[test_case(TestRepoBackend::Git ; "git backend")]
134fn test_rewrite(backend: TestRepoBackend) {
135 let settings = testutils::user_settings();
136 let test_repo = TestRepo::init_with_backend_and_settings(backend, &settings);
137 let test_env = &test_repo.env;
138 let repo = &test_repo.repo;
139 let store = repo.store();
140
141 let root_file_path = RepoPath::from_internal_string("file");
142 let dir_file_path = RepoPath::from_internal_string("dir/file");
143 let initial_tree = create_tree(
144 repo,
145 &[
146 (root_file_path, "file contents"),
147 (dir_file_path, "dir/file contents"),
148 ],
149 );
150
151 let mut tx = repo.start_transaction();
152 let initial_commit = tx
153 .repo_mut()
154 .new_commit(vec![store.root_commit_id().clone()], initial_tree.id())
155 .write()
156 .unwrap();
157 let repo = tx.commit("test").unwrap();
158
159 let rewritten_tree = create_tree(
160 &repo,
161 &[
162 (root_file_path, "file contents"),
163 (dir_file_path, "updated dir/file contents"),
164 ],
165 );
166
167 let mut config = StackedConfig::with_defaults();
168 config.add_layer(
169 ConfigLayer::parse(
170 ConfigSource::User,
171 indoc! {"
172 user.name = 'Rewrite User'
173 user.email = 'rewrite.user@example.com'
174 "},
175 )
176 .unwrap(),
177 );
178 let rewrite_settings = UserSettings::from_config(config).unwrap();
179 let repo = test_env.load_repo_at_head(&rewrite_settings, test_repo.repo_path());
180 let store = repo.store();
181 let initial_commit = store.get_commit(initial_commit.id()).unwrap();
182 let mut tx = repo.start_transaction();
183 let rewritten_commit = tx
184 .repo_mut()
185 .rewrite_commit(&initial_commit)
186 .set_tree_id(rewritten_tree.id().clone())
187 .write()
188 .unwrap();
189 tx.repo_mut().rebase_descendants().unwrap();
190 tx.commit("test").unwrap();
191 let parents: Vec<_> = rewritten_commit.parents().try_collect().unwrap();
192 assert_eq!(parents, vec![store.root_commit()]);
193 let predecessors: Vec<_> = rewritten_commit.predecessors().try_collect().unwrap();
194 assert_eq!(predecessors, vec![initial_commit.clone()]);
195 assert_eq!(rewritten_commit.author().name, settings.user_name());
196 assert_eq!(rewritten_commit.author().email, settings.user_email());
197 assert_eq!(
198 rewritten_commit.committer().name,
199 rewrite_settings.user_name()
200 );
201 assert_eq!(
202 rewritten_commit.committer().email,
203 rewrite_settings.user_email()
204 );
205 assert_eq!(
206 diff_paths(
207 &store.root_commit().tree().unwrap(),
208 &rewritten_commit.tree().unwrap(),
209 ),
210 to_owned_path_vec(&[dir_file_path, root_file_path]),
211 );
212 assert_eq!(
213 diff_paths(
214 &initial_commit.tree().unwrap(),
215 &rewritten_commit.tree().unwrap(),
216 ),
217 to_owned_path_vec(&[dir_file_path]),
218 );
219}
220
221// An author field with an empty name/email should get filled in on rewrite
222#[test_case(TestRepoBackend::Simple ; "simple backend")]
223#[test_case(TestRepoBackend::Git ; "git backend")]
224fn test_rewrite_update_missing_user(backend: TestRepoBackend) {
225 let missing_user_settings = UserSettings::from_config(StackedConfig::with_defaults()).unwrap();
226 let test_repo = TestRepo::init_with_backend_and_settings(backend, &missing_user_settings);
227 let test_env = &test_repo.env;
228 let repo = &test_repo.repo;
229
230 let mut tx = repo.start_transaction();
231 let initial_commit = tx
232 .repo_mut()
233 .new_commit(
234 vec![repo.store().root_commit_id().clone()],
235 repo.store().empty_merged_tree_id(),
236 )
237 .write()
238 .unwrap();
239 assert_eq!(initial_commit.author().name, "");
240 assert_eq!(initial_commit.author().email, "");
241 assert_eq!(initial_commit.committer().name, "");
242 assert_eq!(initial_commit.committer().email, "");
243 tx.commit("test").unwrap();
244
245 let mut config = StackedConfig::with_defaults();
246 config.add_layer(
247 ConfigLayer::parse(
248 ConfigSource::User,
249 indoc! {"
250 user.name = 'Configured User'
251 user.email = 'configured.user@example.com'
252 "},
253 )
254 .unwrap(),
255 );
256 let settings = UserSettings::from_config(config).unwrap();
257 let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path());
258 let initial_commit = repo.store().get_commit(initial_commit.id()).unwrap();
259 let mut tx = repo.start_transaction();
260 let rewritten_commit = tx
261 .repo_mut()
262 .rewrite_commit(&initial_commit)
263 .write()
264 .unwrap();
265
266 assert_eq!(rewritten_commit.author().name, "Configured User");
267 assert_eq!(
268 rewritten_commit.author().email,
269 "configured.user@example.com"
270 );
271 assert_eq!(rewritten_commit.committer().name, "Configured User");
272 assert_eq!(
273 rewritten_commit.committer().email,
274 "configured.user@example.com"
275 );
276}
277
278#[test_case(TestRepoBackend::Simple ; "simple backend")]
279#[test_case(TestRepoBackend::Git ; "git backend")]
280fn test_rewrite_resets_author_timestamp(backend: TestRepoBackend) {
281 let test_repo = TestRepo::init_with_backend(backend);
282 let test_env = &test_repo.env;
283
284 // Create discardable commit
285 let initial_timestamp = "2001-02-03T04:05:06+07:00";
286 let settings =
287 UserSettings::from_config(config_with_commit_timestamp(initial_timestamp)).unwrap();
288 let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path());
289 let mut tx = repo.start_transaction();
290 let initial_commit = tx
291 .repo_mut()
292 .new_commit(
293 vec![repo.store().root_commit_id().clone()],
294 repo.store().empty_merged_tree_id(),
295 )
296 .write()
297 .unwrap();
298 tx.commit("test").unwrap();
299
300 let initial_timestamp =
301 Timestamp::from_datetime(chrono::DateTime::parse_from_rfc3339(initial_timestamp).unwrap());
302 assert_eq!(initial_commit.author().timestamp, initial_timestamp);
303 assert_eq!(initial_commit.committer().timestamp, initial_timestamp);
304
305 // Rewrite discardable commit to no longer be discardable
306 let new_timestamp_1 = "2002-03-04T05:06:07+08:00";
307 let settings =
308 UserSettings::from_config(config_with_commit_timestamp(new_timestamp_1)).unwrap();
309 let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path());
310 let initial_commit = repo.store().get_commit(initial_commit.id()).unwrap();
311 let mut tx = repo.start_transaction();
312 let rewritten_commit_1 = tx
313 .repo_mut()
314 .rewrite_commit(&initial_commit)
315 .set_description("No longer discardable")
316 .write()
317 .unwrap();
318 tx.repo_mut().rebase_descendants().unwrap();
319 tx.commit("test").unwrap();
320
321 let new_timestamp_1 =
322 Timestamp::from_datetime(chrono::DateTime::parse_from_rfc3339(new_timestamp_1).unwrap());
323 assert_ne!(new_timestamp_1, initial_timestamp);
324
325 assert_eq!(rewritten_commit_1.author().timestamp, new_timestamp_1);
326 assert_eq!(rewritten_commit_1.committer().timestamp, new_timestamp_1);
327 assert_eq!(rewritten_commit_1.author(), rewritten_commit_1.committer());
328
329 // Rewrite non-discardable commit
330 let new_timestamp_2 = "2003-04-05T06:07:08+09:00";
331 let settings =
332 UserSettings::from_config(config_with_commit_timestamp(new_timestamp_2)).unwrap();
333 let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path());
334 let rewritten_commit_1 = repo.store().get_commit(rewritten_commit_1.id()).unwrap();
335 let mut tx = repo.start_transaction();
336 let rewritten_commit_2 = tx
337 .repo_mut()
338 .rewrite_commit(&rewritten_commit_1)
339 .set_description("New description")
340 .write()
341 .unwrap();
342 tx.repo_mut().rebase_descendants().unwrap();
343 tx.commit("test").unwrap();
344
345 let new_timestamp_2 =
346 Timestamp::from_datetime(chrono::DateTime::parse_from_rfc3339(new_timestamp_2).unwrap());
347 assert_ne!(new_timestamp_2, new_timestamp_1);
348
349 assert_eq!(rewritten_commit_2.author().timestamp, new_timestamp_1);
350 assert_eq!(rewritten_commit_2.committer().timestamp, new_timestamp_2);
351}
352
353#[test_case(TestRepoBackend::Simple ; "simple backend")]
354// #[test_case(TestRepoBackend::Git ; "git backend")]
355fn test_commit_builder_descendants(backend: TestRepoBackend) {
356 let test_repo = TestRepo::init_with_backend(backend);
357 let repo = &test_repo.repo;
358 let store = repo.store().clone();
359
360 let mut tx = repo.start_transaction();
361 let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut());
362 let commit1 = graph_builder.initial_commit();
363 let commit2 = graph_builder.commit_with_parents(&[&commit1]);
364 let commit3 = graph_builder.commit_with_parents(&[&commit2]);
365 let repo = tx.commit("test").unwrap();
366
367 // Test with for_new_commit()
368 let mut tx = repo.start_transaction();
369 tx.repo_mut()
370 .new_commit(
371 vec![store.root_commit_id().clone()],
372 store.empty_merged_tree_id(),
373 )
374 .write()
375 .unwrap();
376 let rebase_map =
377 rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default());
378 assert_eq!(rebase_map.len(), 0);
379
380 // Test with for_rewrite_from()
381 let mut tx = repo.start_transaction();
382 let commit4 = tx.repo_mut().rewrite_commit(&commit2).write().unwrap();
383 let rebase_map =
384 rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default());
385 assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit3, &[commit4.id()]);
386 assert_eq!(rebase_map.len(), 1);
387
388 // Test with for_rewrite_from() but new change id
389 let mut tx = repo.start_transaction();
390 tx.repo_mut()
391 .rewrite_commit(&commit2)
392 .generate_new_change_id()
393 .write()
394 .unwrap();
395 let rebase_map =
396 rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default());
397 assert!(rebase_map.is_empty());
398}