comparison src/org/tmatesoft/hg/internal/FileHistory.java @ 596:43cfa08ff3fd

HgBlameFacility refactoring: extract code to build file history that spans renames
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 02 May 2013 19:23:53 +0200
parents
children 507602cb4fb3
comparison
equal deleted inserted replaced
595:92c3ad9c2a51 596:43cfa08ff3fd
1 /*
2 * Copyright (c) 2013 TMate Software Ltd
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * For information on how to redistribute this software under
14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com
16 */
17 package org.tmatesoft.hg.internal;
18
19 import static org.tmatesoft.hg.core.HgIterateDirection.NewToOld;
20
21 import java.util.Collections;
22 import java.util.LinkedList;
23
24 import org.tmatesoft.hg.core.HgIterateDirection;
25 import org.tmatesoft.hg.core.Nodeid;
26 import org.tmatesoft.hg.repo.HgDataFile;
27 import org.tmatesoft.hg.repo.HgRepository;
28
29 /**
30 * History of a file, with copy/renames, and corresponding revision information.
31 * Facility for file history iteration.
32 *
33 * FIXME [post-1.1] Utilize in HgLogCommand and anywhere else we need to follow file history
34 *
35 * @author Artem Tikhomirov
36 * @author TMate Software Ltd.
37 */
38 public class FileHistory {
39
40 private LinkedList<FileRevisionHistoryChunk> fileCompleteHistory = new LinkedList<FileRevisionHistoryChunk>();
41 private final HgDataFile df;
42 private final int csetTo;
43 private final int csetFrom;
44
45 public FileHistory(HgDataFile file, int fromChangeset, int toChangeset) {
46 df = file;
47 csetFrom = fromChangeset;
48 csetTo = toChangeset;
49 }
50
51 public int getStartChangeset() {
52 return csetFrom;
53 }
54
55 public int getEndChangeset() {
56 return csetTo;
57 }
58
59 public void build() {
60 assert fileCompleteHistory.isEmpty();
61 HgDataFile currentFile = df;
62 final int changelogRevIndexEnd = csetTo;
63 final int changelogRevIndexStart = csetFrom;
64 int fileLastClogRevIndex = changelogRevIndexEnd;
65 FileRevisionHistoryChunk nextChunk = null;
66 fileCompleteHistory.clear(); // just in case, #build() is not expected to be called more than once
67 do {
68 FileRevisionHistoryChunk fileHistory = new FileRevisionHistoryChunk(currentFile);
69 fileHistory.init(fileLastClogRevIndex);
70 fileHistory.linkTo(nextChunk);
71 fileCompleteHistory.addFirst(fileHistory); // to get the list in old-to-new order
72 nextChunk = fileHistory;
73 if (fileHistory.changeset(0) > changelogRevIndexStart && currentFile.isCopy()) {
74 // fileHistory.changeset(0) is the earliest revision we know about so far,
75 // once we get to revisions earlier than the requested start, stop digging.
76 // The reason there's NO == (i.e. not >=) because:
77 // (easy): once it's equal, we've reached our intended start
78 // (hard): if changelogRevIndexStart happens to be exact start of one of renames in the
79 // chain of renames (test-annotate2 repository, file1->file1a->file1b, i.e. points
80 // to the very start of file1a or file1 history), presence of == would get us to the next
81 // chunk and hence changed parents of present chunk's first element. Our annotate alg
82 // relies on parents only (i.e. knows nothing about 'last iteration element') to find out
83 // what to compare, and hence won't report all lines of 'last iteration element' (which is the
84 // first revision of the renamed file) as "added in this revision", leaving gaps in annotate
85 HgRepository repo = currentFile.getRepo();
86 Nodeid originLastRev = currentFile.getCopySourceRevision();
87 currentFile = repo.getFileNode(currentFile.getCopySourceName());
88 fileLastClogRevIndex = currentFile.getChangesetRevisionIndex(currentFile.getRevisionIndex(originLastRev));
89 // XXX perhaps, shall fail with meaningful exception if new file doesn't exist (.i/.d not found for whatever reason)
90 // or source revision is missing?
91 } else {
92 fileHistory.chopAtChangeset(changelogRevIndexStart);
93 currentFile = null; // stop iterating
94 }
95 } while (currentFile != null && fileLastClogRevIndex > changelogRevIndexStart);
96 // fileCompleteHistory is in (origin, intermediate target, ultimate target) order
97 }
98
99 public Iterable<FileRevisionHistoryChunk> iterate(HgIterateDirection order) {
100 if (order == NewToOld) {
101 return ReverseIterator.reversed(fileCompleteHistory);
102 }
103 return Collections.unmodifiableList(fileCompleteHistory);
104 }
105 }