just playing with tangled
at main 415 lines 15 kB view raw
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}