##// END OF EJS Templates
copies-rust: use the entry API to overwrite deleted entry...
marmoute -
r46767:e166e8a0 default
parent child Browse files
Show More
@@ -1,751 +1,748
1 1 use crate::utils::hg_path::HgPath;
2 2 use crate::utils::hg_path::HgPathBuf;
3 3 use crate::Revision;
4 4 use crate::NULL_REVISION;
5 5
6 6 use im_rc::ordmap::DiffItem;
7 7 use im_rc::ordmap::OrdMap;
8 8
9 9 use std::cmp::Ordering;
10 10 use std::collections::HashMap;
11 11 use std::convert::TryInto;
12 12
13 13 pub type PathCopies = HashMap<HgPathBuf, HgPathBuf>;
14 14
15 15 type PathToken = usize;
16 16
17 17 #[derive(Clone, Debug, PartialEq, Copy)]
18 18 struct TimeStampedPathCopy {
19 19 /// revision at which the copy information was added
20 20 rev: Revision,
21 21 /// the copy source, (Set to None in case of deletion of the associated
22 22 /// key)
23 23 path: Option<PathToken>,
24 24 }
25 25
26 26 /// maps CopyDestination to Copy Source (+ a "timestamp" for the operation)
27 27 type TimeStampedPathCopies = OrdMap<PathToken, TimeStampedPathCopy>;
28 28
29 29 /// hold parent 1, parent 2 and relevant files actions.
30 30 pub type RevInfo<'a> = (Revision, Revision, ChangedFiles<'a>);
31 31
32 32 /// represent the files affected by a changesets
33 33 ///
34 34 /// This hold a subset of mercurial.metadata.ChangingFiles as we do not need
35 35 /// all the data categories tracked by it.
36 36 /// This hold a subset of mercurial.metadata.ChangingFiles as we do not need
37 37 /// all the data categories tracked by it.
38 38 pub struct ChangedFiles<'a> {
39 39 nb_items: u32,
40 40 index: &'a [u8],
41 41 data: &'a [u8],
42 42 }
43 43
44 44 /// Represent active changes that affect the copy tracing.
45 45 enum Action<'a> {
46 46 /// The parent ? children edge is removing a file
47 47 ///
48 48 /// (actually, this could be the edge from the other parent, but it does
49 49 /// not matters)
50 50 Removed(&'a HgPath),
51 51 /// The parent ? children edge introduce copy information between (dest,
52 52 /// source)
53 53 Copied(&'a HgPath, &'a HgPath),
54 54 }
55 55
56 56 /// This express the possible "special" case we can get in a merge
57 57 ///
58 58 /// See mercurial/metadata.py for details on these values.
59 59 #[derive(PartialEq)]
60 60 enum MergeCase {
61 61 /// Merged: file had history on both side that needed to be merged
62 62 Merged,
63 63 /// Salvaged: file was candidate for deletion, but survived the merge
64 64 Salvaged,
65 65 /// Normal: Not one of the two cases above
66 66 Normal,
67 67 }
68 68
69 69 type FileChange<'a> = (u8, &'a HgPath, &'a HgPath);
70 70
71 71 const EMPTY: &[u8] = b"";
72 72 const COPY_MASK: u8 = 3;
73 73 const P1_COPY: u8 = 2;
74 74 const P2_COPY: u8 = 3;
75 75 const ACTION_MASK: u8 = 28;
76 76 const REMOVED: u8 = 12;
77 77 const MERGED: u8 = 8;
78 78 const SALVAGED: u8 = 16;
79 79
80 80 impl<'a> ChangedFiles<'a> {
81 81 const INDEX_START: usize = 4;
82 82 const ENTRY_SIZE: u32 = 9;
83 83 const FILENAME_START: u32 = 1;
84 84 const COPY_SOURCE_START: u32 = 5;
85 85
86 86 pub fn new(data: &'a [u8]) -> Self {
87 87 assert!(
88 88 data.len() >= 4,
89 89 "data size ({}) is too small to contain the header (4)",
90 90 data.len()
91 91 );
92 92 let nb_items_raw: [u8; 4] = (&data[0..=3])
93 93 .try_into()
94 94 .expect("failed to turn 4 bytes into 4 bytes");
95 95 let nb_items = u32::from_be_bytes(nb_items_raw);
96 96
97 97 let index_size = (nb_items * Self::ENTRY_SIZE) as usize;
98 98 let index_end = Self::INDEX_START + index_size;
99 99
100 100 assert!(
101 101 data.len() >= index_end,
102 102 "data size ({}) is too small to fit the index_data ({})",
103 103 data.len(),
104 104 index_end
105 105 );
106 106
107 107 let ret = ChangedFiles {
108 108 nb_items,
109 109 index: &data[Self::INDEX_START..index_end],
110 110 data: &data[index_end..],
111 111 };
112 112 let max_data = ret.filename_end(nb_items - 1) as usize;
113 113 assert!(
114 114 ret.data.len() >= max_data,
115 115 "data size ({}) is too small to fit all data ({})",
116 116 data.len(),
117 117 index_end + max_data
118 118 );
119 119 ret
120 120 }
121 121
122 122 pub fn new_empty() -> Self {
123 123 ChangedFiles {
124 124 nb_items: 0,
125 125 index: EMPTY,
126 126 data: EMPTY,
127 127 }
128 128 }
129 129
130 130 /// internal function to return an individual entry at a given index
131 131 fn entry(&'a self, idx: u32) -> FileChange<'a> {
132 132 if idx >= self.nb_items {
133 133 panic!(
134 134 "index for entry is higher that the number of file {} >= {}",
135 135 idx, self.nb_items
136 136 )
137 137 }
138 138 let flags = self.flags(idx);
139 139 let filename = self.filename(idx);
140 140 let copy_idx = self.copy_idx(idx);
141 141 let copy_source = self.filename(copy_idx);
142 142 (flags, filename, copy_source)
143 143 }
144 144
145 145 /// internal function to return the filename of the entry at a given index
146 146 fn filename(&self, idx: u32) -> &HgPath {
147 147 let filename_start;
148 148 if idx == 0 {
149 149 filename_start = 0;
150 150 } else {
151 151 filename_start = self.filename_end(idx - 1)
152 152 }
153 153 let filename_end = self.filename_end(idx);
154 154 let filename_start = filename_start as usize;
155 155 let filename_end = filename_end as usize;
156 156 HgPath::new(&self.data[filename_start..filename_end])
157 157 }
158 158
159 159 /// internal function to return the flag field of the entry at a given
160 160 /// index
161 161 fn flags(&self, idx: u32) -> u8 {
162 162 let idx = idx as usize;
163 163 self.index[idx * (Self::ENTRY_SIZE as usize)]
164 164 }
165 165
166 166 /// internal function to return the end of a filename part at a given index
167 167 fn filename_end(&self, idx: u32) -> u32 {
168 168 let start = (idx * Self::ENTRY_SIZE) + Self::FILENAME_START;
169 169 let end = (idx * Self::ENTRY_SIZE) + Self::COPY_SOURCE_START;
170 170 let start = start as usize;
171 171 let end = end as usize;
172 172 let raw = (&self.index[start..end])
173 173 .try_into()
174 174 .expect("failed to turn 4 bytes into 4 bytes");
175 175 u32::from_be_bytes(raw)
176 176 }
177 177
178 178 /// internal function to return index of the copy source of the entry at a
179 179 /// given index
180 180 fn copy_idx(&self, idx: u32) -> u32 {
181 181 let start = (idx * Self::ENTRY_SIZE) + Self::COPY_SOURCE_START;
182 182 let end = (idx + 1) * Self::ENTRY_SIZE;
183 183 let start = start as usize;
184 184 let end = end as usize;
185 185 let raw = (&self.index[start..end])
186 186 .try_into()
187 187 .expect("failed to turn 4 bytes into 4 bytes");
188 188 u32::from_be_bytes(raw)
189 189 }
190 190
191 191 /// Return an iterator over all the `Action` in this instance.
192 192 fn iter_actions(&self, parent: Parent) -> ActionsIterator {
193 193 ActionsIterator {
194 194 changes: &self,
195 195 parent: parent,
196 196 current: 0,
197 197 }
198 198 }
199 199
200 200 /// return the MergeCase value associated with a filename
201 201 fn get_merge_case(&self, path: &HgPath) -> MergeCase {
202 202 if self.nb_items == 0 {
203 203 return MergeCase::Normal;
204 204 }
205 205 let mut low_part = 0;
206 206 let mut high_part = self.nb_items;
207 207
208 208 while low_part < high_part {
209 209 let cursor = (low_part + high_part - 1) / 2;
210 210 let (flags, filename, _source) = self.entry(cursor);
211 211 match path.cmp(filename) {
212 212 Ordering::Less => low_part = cursor + 1,
213 213 Ordering::Greater => high_part = cursor,
214 214 Ordering::Equal => {
215 215 return match flags & ACTION_MASK {
216 216 MERGED => MergeCase::Merged,
217 217 SALVAGED => MergeCase::Salvaged,
218 218 _ => MergeCase::Normal,
219 219 };
220 220 }
221 221 }
222 222 }
223 223 MergeCase::Normal
224 224 }
225 225 }
226 226
227 227 /// A struct responsible for answering "is X ancestors of Y" quickly
228 228 ///
229 229 /// The structure will delegate ancestors call to a callback, and cache the
230 230 /// result.
231 231 #[derive(Debug)]
232 232 struct AncestorOracle<'a, A: Fn(Revision, Revision) -> bool> {
233 233 inner: &'a A,
234 234 pairs: HashMap<(Revision, Revision), bool>,
235 235 }
236 236
237 237 impl<'a, A: Fn(Revision, Revision) -> bool> AncestorOracle<'a, A> {
238 238 fn new(func: &'a A) -> Self {
239 239 Self {
240 240 inner: func,
241 241 pairs: HashMap::default(),
242 242 }
243 243 }
244 244
245 245 /// returns `true` if `anc` is an ancestors of `desc`, `false` otherwise
246 246 fn is_ancestor(&mut self, anc: Revision, desc: Revision) -> bool {
247 247 if anc > desc {
248 248 false
249 249 } else if anc == desc {
250 250 true
251 251 } else {
252 252 if let Some(b) = self.pairs.get(&(anc, desc)) {
253 253 *b
254 254 } else {
255 255 let b = (self.inner)(anc, desc);
256 256 self.pairs.insert((anc, desc), b);
257 257 b
258 258 }
259 259 }
260 260 }
261 261 }
262 262
263 263 struct ActionsIterator<'a> {
264 264 changes: &'a ChangedFiles<'a>,
265 265 parent: Parent,
266 266 current: u32,
267 267 }
268 268
269 269 impl<'a> Iterator for ActionsIterator<'a> {
270 270 type Item = Action<'a>;
271 271
272 272 fn next(&mut self) -> Option<Action<'a>> {
273 273 let copy_flag = match self.parent {
274 274 Parent::FirstParent => P1_COPY,
275 275 Parent::SecondParent => P2_COPY,
276 276 };
277 277 while self.current < self.changes.nb_items {
278 278 let (flags, file, source) = self.changes.entry(self.current);
279 279 self.current += 1;
280 280 if (flags & ACTION_MASK) == REMOVED {
281 281 return Some(Action::Removed(file));
282 282 }
283 283 let copy = flags & COPY_MASK;
284 284 if copy == copy_flag {
285 285 return Some(Action::Copied(file, source));
286 286 }
287 287 }
288 288 return None;
289 289 }
290 290 }
291 291
292 292 /// A small struct whose purpose is to ensure lifetime of bytes referenced in
293 293 /// ChangedFiles
294 294 ///
295 295 /// It is passed to the RevInfoMaker callback who can assign any necessary
296 296 /// content to the `data` attribute. The copy tracing code is responsible for
297 297 /// keeping the DataHolder alive at least as long as the ChangedFiles object.
298 298 pub struct DataHolder<D> {
299 299 /// RevInfoMaker callback should assign data referenced by the
300 300 /// ChangedFiles struct it return to this attribute. The DataHolder
301 301 /// lifetime will be at least as long as the ChangedFiles one.
302 302 pub data: Option<D>,
303 303 }
304 304
305 305 pub type RevInfoMaker<'a, D> =
306 306 Box<dyn for<'r> Fn(Revision, &'r mut DataHolder<D>) -> RevInfo<'r> + 'a>;
307 307
308 308 /// enum used to carry information about the parent β†’ child currently processed
309 309 #[derive(Copy, Clone, Debug)]
310 310 enum Parent {
311 311 /// The `p1(x) β†’ x` edge
312 312 FirstParent,
313 313 /// The `p2(x) β†’ x` edge
314 314 SecondParent,
315 315 }
316 316
317 317 /// A small "tokenizer" responsible of turning full HgPath into lighter
318 318 /// PathToken
319 319 ///
320 320 /// Dealing with small object, like integer is much faster, so HgPath input are
321 321 /// turned into integer "PathToken" and converted back in the end.
322 322 #[derive(Clone, Debug, Default)]
323 323 struct TwoWayPathMap {
324 324 token: HashMap<HgPathBuf, PathToken>,
325 325 path: Vec<HgPathBuf>,
326 326 }
327 327
328 328 impl TwoWayPathMap {
329 329 fn tokenize(&mut self, path: &HgPath) -> PathToken {
330 330 match self.token.get(path) {
331 331 Some(a) => *a,
332 332 None => {
333 333 let a = self.token.len();
334 334 let buf = path.to_owned();
335 335 self.path.push(buf.clone());
336 336 self.token.insert(buf, a);
337 337 a
338 338 }
339 339 }
340 340 }
341 341
342 342 fn untokenize(&self, token: PathToken) -> &HgPathBuf {
343 343 assert!(token < self.path.len(), format!("Unknown token: {}", token));
344 344 &self.path[token]
345 345 }
346 346 }
347 347
348 348 /// Same as mercurial.copies._combine_changeset_copies, but in Rust.
349 349 ///
350 350 /// Arguments are:
351 351 ///
352 352 /// revs: all revisions to be considered
353 353 /// children: a {parent ? [childrens]} mapping
354 354 /// target_rev: the final revision we are combining copies to
355 355 /// rev_info(rev): callback to get revision information:
356 356 /// * first parent
357 357 /// * second parent
358 358 /// * ChangedFiles
359 359 /// isancestors(low_rev, high_rev): callback to check if a revision is an
360 360 /// ancestor of another
361 361 pub fn combine_changeset_copies<A: Fn(Revision, Revision) -> bool, D>(
362 362 revs: Vec<Revision>,
363 363 mut children_count: HashMap<Revision, usize>,
364 364 target_rev: Revision,
365 365 rev_info: RevInfoMaker<D>,
366 366 is_ancestor: &A,
367 367 ) -> PathCopies {
368 368 let mut all_copies = HashMap::new();
369 369 let mut oracle = AncestorOracle::new(is_ancestor);
370 370
371 371 let mut path_map = TwoWayPathMap::default();
372 372
373 373 for rev in revs {
374 374 let mut d: DataHolder<D> = DataHolder { data: None };
375 375 let (p1, p2, changes) = rev_info(rev, &mut d);
376 376
377 377 // We will chain the copies information accumulated for the parent with
378 378 // the individual copies information the curent revision. Creating a
379 379 // new TimeStampedPath for each `rev` β†’ `children` vertex.
380 380 let mut copies: Option<TimeStampedPathCopies> = None;
381 381 if p1 != NULL_REVISION {
382 382 // Retrieve data computed in a previous iteration
383 383 let parent_copies = get_and_clean_parent_copies(
384 384 &mut all_copies,
385 385 &mut children_count,
386 386 p1,
387 387 );
388 388 if let Some(parent_copies) = parent_copies {
389 389 // combine it with data for that revision
390 390 let vertex_copies = add_from_changes(
391 391 &mut path_map,
392 392 &parent_copies,
393 393 &changes,
394 394 Parent::FirstParent,
395 395 rev,
396 396 );
397 397 // keep that data around for potential later combination
398 398 copies = Some(vertex_copies);
399 399 }
400 400 }
401 401 if p2 != NULL_REVISION {
402 402 // Retrieve data computed in a previous iteration
403 403 let parent_copies = get_and_clean_parent_copies(
404 404 &mut all_copies,
405 405 &mut children_count,
406 406 p2,
407 407 );
408 408 if let Some(parent_copies) = parent_copies {
409 409 // combine it with data for that revision
410 410 let vertex_copies = add_from_changes(
411 411 &mut path_map,
412 412 &parent_copies,
413 413 &changes,
414 414 Parent::SecondParent,
415 415 rev,
416 416 );
417 417
418 418 copies = match copies {
419 419 None => Some(vertex_copies),
420 420 // Merge has two parents needs to combines their copy
421 421 // information.
422 422 //
423 423 // If we got data from both parents, We need to combine
424 424 // them.
425 425 Some(copies) => Some(merge_copies_dict(
426 426 &path_map,
427 427 vertex_copies,
428 428 copies,
429 429 &changes,
430 430 &mut oracle,
431 431 )),
432 432 };
433 433 }
434 434 }
435 435 match copies {
436 436 Some(copies) => {
437 437 all_copies.insert(rev, copies);
438 438 }
439 439 _ => {}
440 440 }
441 441 }
442 442
443 443 // Drop internal information (like the timestamp) and return the final
444 444 // mapping.
445 445 let tt_result = all_copies
446 446 .remove(&target_rev)
447 447 .expect("target revision was not processed");
448 448 let mut result = PathCopies::default();
449 449 for (dest, tt_source) in tt_result {
450 450 if let Some(path) = tt_source.path {
451 451 let path_dest = path_map.untokenize(dest).to_owned();
452 452 let path_path = path_map.untokenize(path).to_owned();
453 453 result.insert(path_dest, path_path);
454 454 }
455 455 }
456 456 result
457 457 }
458 458
459 459 /// fetch previous computed information
460 460 ///
461 461 /// If no other children are expected to need this information, we drop it from
462 462 /// the cache.
463 463 ///
464 464 /// If parent is not part of the set we are expected to walk, return None.
465 465 fn get_and_clean_parent_copies(
466 466 all_copies: &mut HashMap<Revision, TimeStampedPathCopies>,
467 467 children_count: &mut HashMap<Revision, usize>,
468 468 parent_rev: Revision,
469 469 ) -> Option<TimeStampedPathCopies> {
470 470 let count = children_count.get_mut(&parent_rev)?;
471 471 *count -= 1;
472 472 if *count == 0 {
473 473 match all_copies.remove(&parent_rev) {
474 474 Some(c) => Some(c),
475 475 None => Some(TimeStampedPathCopies::default()),
476 476 }
477 477 } else {
478 478 match all_copies.get(&parent_rev) {
479 479 Some(c) => Some(c.clone()),
480 480 None => Some(TimeStampedPathCopies::default()),
481 481 }
482 482 }
483 483 }
484 484
485 485 /// Combine ChangedFiles with some existing PathCopies information and return
486 486 /// the result
487 487 fn add_from_changes(
488 488 path_map: &mut TwoWayPathMap,
489 489 base_copies: &TimeStampedPathCopies,
490 490 changes: &ChangedFiles,
491 491 parent: Parent,
492 492 current_rev: Revision,
493 493 ) -> TimeStampedPathCopies {
494 494 let mut copies = base_copies.clone();
495 495 for action in changes.iter_actions(parent) {
496 496 match action {
497 497 Action::Copied(path_dest, path_source) => {
498 498 let dest = path_map.tokenize(path_dest);
499 499 let source = path_map.tokenize(path_source);
500 500 let entry;
501 501 if let Some(v) = base_copies.get(&source) {
502 502 entry = match &v.path {
503 503 Some(path) => Some((*(path)).to_owned()),
504 504 None => Some(source.to_owned()),
505 505 }
506 506 } else {
507 507 entry = Some(source.to_owned());
508 508 }
509 509 // Each new entry is introduced by the children, we
510 510 // record this information as we will need it to take
511 511 // the right decision when merging conflicting copy
512 512 // information. See merge_copies_dict for details.
513 513 let ttpc = TimeStampedPathCopy {
514 514 rev: current_rev,
515 515 path: entry,
516 516 };
517 517 copies.insert(dest.to_owned(), ttpc);
518 518 }
519 519 Action::Removed(deleted_path) => {
520 520 // We must drop copy information for removed file.
521 521 //
522 522 // We need to explicitly record them as dropped to
523 523 // propagate this information when merging two
524 524 // TimeStampedPathCopies object.
525 525 let deleted = path_map.tokenize(deleted_path);
526 if copies.contains_key(&deleted) {
527 let ttpc = TimeStampedPathCopy {
528 rev: current_rev,
529 path: None,
530 };
531 copies.insert(deleted, ttpc);
532 }
526 copies.entry(deleted).and_modify(|old| {
527 old.rev = current_rev;
528 old.path = None;
529 });
533 530 }
534 531 }
535 532 }
536 533 copies
537 534 }
538 535
539 536 /// merge two copies-mapping together, minor and major
540 537 ///
541 538 /// In case of conflict, value from "major" will be picked, unless in some
542 539 /// cases. See inline documentation for details.
543 540 fn merge_copies_dict<A: Fn(Revision, Revision) -> bool>(
544 541 path_map: &TwoWayPathMap,
545 542 mut minor: TimeStampedPathCopies,
546 543 mut major: TimeStampedPathCopies,
547 544 changes: &ChangedFiles,
548 545 oracle: &mut AncestorOracle<A>,
549 546 ) -> TimeStampedPathCopies {
550 547 // This closure exist as temporary help while multiple developper are
551 548 // actively working on this code. Feel free to re-inline it once this
552 549 // code is more settled.
553 550 let mut cmp_value =
554 551 |dest: &PathToken,
555 552 src_minor: &TimeStampedPathCopy,
556 553 src_major: &TimeStampedPathCopy| {
557 554 compare_value(
558 555 path_map, changes, oracle, dest, src_minor, src_major,
559 556 )
560 557 };
561 558 if minor.is_empty() {
562 559 major
563 560 } else if major.is_empty() {
564 561 minor
565 562 } else if minor.len() * 2 < major.len() {
566 563 // Lets says we are merging two TimeStampedPathCopies instance A and B.
567 564 //
568 565 // If A contains N items, the merge result will never contains more
569 566 // than N values differents than the one in A
570 567 //
571 568 // If B contains M items, with M > N, the merge result will always
572 569 // result in a minimum of M - N value differents than the on in
573 570 // A
574 571 //
575 572 // As a result, if N < (M-N), we know that simply iterating over A will
576 573 // yield less difference than iterating over the difference
577 574 // between A and B.
578 575 //
579 576 // This help performance a lot in case were a tiny
580 577 // TimeStampedPathCopies is merged with a much larger one.
581 578 for (dest, src_minor) in minor {
582 579 let src_major = major.get(&dest);
583 580 match src_major {
584 581 None => major.insert(dest, src_minor),
585 582 Some(src_major) => {
586 583 match cmp_value(&dest, &src_minor, src_major) {
587 584 MergePick::Any | MergePick::Major => None,
588 585 MergePick::Minor => major.insert(dest, src_minor),
589 586 }
590 587 }
591 588 };
592 589 }
593 590 major
594 591 } else if major.len() * 2 < minor.len() {
595 592 // This use the same rational than the previous block.
596 593 // (Check previous block documentation for details.)
597 594 for (dest, src_major) in major {
598 595 let src_minor = minor.get(&dest);
599 596 match src_minor {
600 597 None => minor.insert(dest, src_major),
601 598 Some(src_minor) => {
602 599 match cmp_value(&dest, src_minor, &src_major) {
603 600 MergePick::Any | MergePick::Minor => None,
604 601 MergePick::Major => minor.insert(dest, src_major),
605 602 }
606 603 }
607 604 };
608 605 }
609 606 minor
610 607 } else {
611 608 let mut override_minor = Vec::new();
612 609 let mut override_major = Vec::new();
613 610
614 611 let mut to_major = |k: &PathToken, v: &TimeStampedPathCopy| {
615 612 override_major.push((k.clone(), v.clone()))
616 613 };
617 614 let mut to_minor = |k: &PathToken, v: &TimeStampedPathCopy| {
618 615 override_minor.push((k.clone(), v.clone()))
619 616 };
620 617
621 618 // The diff function leverage detection of the identical subpart if
622 619 // minor and major has some common ancestors. This make it very
623 620 // fast is most case.
624 621 //
625 622 // In case where the two map are vastly different in size, the current
626 623 // approach is still slowish because the iteration will iterate over
627 624 // all the "exclusive" content of the larger on. This situation can be
628 625 // frequent when the subgraph of revision we are processing has a lot
629 626 // of roots. Each roots adding they own fully new map to the mix (and
630 627 // likely a small map, if the path from the root to the "main path" is
631 628 // small.
632 629 //
633 630 // We could do better by detecting such situation and processing them
634 631 // differently.
635 632 for d in minor.diff(&major) {
636 633 match d {
637 634 DiffItem::Add(k, v) => to_minor(k, v),
638 635 DiffItem::Remove(k, v) => to_major(k, v),
639 636 DiffItem::Update { old, new } => {
640 637 let (dest, src_major) = new;
641 638 let (_, src_minor) = old;
642 639 match cmp_value(dest, src_minor, src_major) {
643 640 MergePick::Major => to_minor(dest, src_major),
644 641 MergePick::Minor => to_major(dest, src_minor),
645 642 // If the two entry are identical, no need to do
646 643 // anything (but diff should not have yield them)
647 644 MergePick::Any => unreachable!(),
648 645 }
649 646 }
650 647 };
651 648 }
652 649
653 650 let updates;
654 651 let mut result;
655 652 if override_major.is_empty() {
656 653 result = major
657 654 } else if override_minor.is_empty() {
658 655 result = minor
659 656 } else {
660 657 if override_minor.len() < override_major.len() {
661 658 updates = override_minor;
662 659 result = minor;
663 660 } else {
664 661 updates = override_major;
665 662 result = major;
666 663 }
667 664 for (k, v) in updates {
668 665 result.insert(k, v);
669 666 }
670 667 }
671 668 result
672 669 }
673 670 }
674 671
675 672 /// represent the side that should prevail when merging two
676 673 /// TimeStampedPathCopies
677 674 enum MergePick {
678 675 /// The "major" (p1) side prevails
679 676 Major,
680 677 /// The "minor" (p2) side prevails
681 678 Minor,
682 679 /// Any side could be used (because they are the same)
683 680 Any,
684 681 }
685 682
686 683 /// decide which side prevails in case of conflicting values
687 684 #[allow(clippy::if_same_then_else)]
688 685 fn compare_value<A: Fn(Revision, Revision) -> bool>(
689 686 path_map: &TwoWayPathMap,
690 687 changes: &ChangedFiles,
691 688 oracle: &mut AncestorOracle<A>,
692 689 dest: &PathToken,
693 690 src_minor: &TimeStampedPathCopy,
694 691 src_major: &TimeStampedPathCopy,
695 692 ) -> MergePick {
696 693 if src_major.path == src_minor.path {
697 694 // we have the same value, but from other source;
698 695 if src_major.rev == src_minor.rev {
699 696 // If the two entry are identical, they are both valid
700 697 MergePick::Any
701 698 } else if oracle.is_ancestor(src_major.rev, src_minor.rev) {
702 699 MergePick::Minor
703 700 } else {
704 701 MergePick::Major
705 702 }
706 703 } else if src_major.rev == src_minor.rev {
707 704 // We cannot get copy information for both p1 and p2 in the
708 705 // same rev. So this is the same value.
709 706 unreachable!(
710 707 "conflict information from p1 and p2 in the same revision"
711 708 );
712 709 } else {
713 710 let dest_path = path_map.untokenize(*dest);
714 711 let action = changes.get_merge_case(dest_path);
715 712 if src_major.path.is_none() && action == MergeCase::Salvaged {
716 713 // If the file is "deleted" in the major side but was
717 714 // salvaged by the merge, we keep the minor side alive
718 715 MergePick::Minor
719 716 } else if src_minor.path.is_none() && action == MergeCase::Salvaged {
720 717 // If the file is "deleted" in the minor side but was
721 718 // salvaged by the merge, unconditionnaly preserve the
722 719 // major side.
723 720 MergePick::Major
724 721 } else if action == MergeCase::Merged {
725 722 // If the file was actively merged, copy information
726 723 // from each side might conflict. The major side will
727 724 // win such conflict.
728 725 MergePick::Major
729 726 } else if oracle.is_ancestor(src_major.rev, src_minor.rev) {
730 727 // If the minor side is strictly newer than the major
731 728 // side, it should be kept.
732 729 MergePick::Minor
733 730 } else if src_major.path.is_some() {
734 731 // without any special case, the "major" value win
735 732 // other the "minor" one.
736 733 MergePick::Major
737 734 } else if oracle.is_ancestor(src_minor.rev, src_major.rev) {
738 735 // the "major" rev is a direct ancestors of "minor",
739 736 // any different value should
740 737 // overwrite
741 738 MergePick::Major
742 739 } else {
743 740 // major version is None (so the file was deleted on
744 741 // that branch) and that branch is independant (neither
745 742 // minor nor major is an ancestors of the other one.)
746 743 // We preserve the new
747 744 // information about the new file.
748 745 MergePick::Minor
749 746 }
750 747 }
751 748 }
General Comments 0
You need to be logged in to leave comments. Login now