just playing with tangled
at gvimdiff 398 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 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}