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