comparison src/org/tmatesoft/hg/repo/WorkingCopyStatusCollector.java @ 74:6f1b88693d48

Complete refactoring to org.tmatesoft
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 24 Jan 2011 03:14:45 +0100
parents src/com/tmate/hgkit/ll/WorkingCopyStatusCollector.java@0e499fed9b3d
children 658fa6b3a371
comparison
equal deleted inserted replaced
73:0d279bcc4442 74:6f1b88693d48
1 /*
2 * Copyright (c) 2011 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@svnkit.com
16 */
17 package org.tmatesoft.hg.repo;
18
19 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
20 import static org.tmatesoft.hg.repo.HgRepository.TIP;
21
22 import java.io.BufferedInputStream;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.IOException;
26 import java.util.Collections;
27 import java.util.Set;
28 import java.util.TreeSet;
29
30 import org.tmatesoft.hg.core.Nodeid;
31 import org.tmatesoft.hg.util.FileWalker;
32
33 /**
34 *
35 * @author Artem Tikhomirov
36 * @author TMate Software Ltd.
37 */
38 public class WorkingCopyStatusCollector {
39
40 private final HgRepository repo;
41 private final FileWalker repoWalker;
42 private HgDirstate dirstate;
43 private StatusCollector baseRevisionCollector;
44
45 public WorkingCopyStatusCollector(HgRepository hgRepo) {
46 this(hgRepo, hgRepo.createWorkingDirWalker());
47 }
48
49 WorkingCopyStatusCollector(HgRepository hgRepo, FileWalker hgRepoWalker) {
50 this.repo = hgRepo;
51 this.repoWalker = hgRepoWalker;
52 }
53
54 /**
55 * Optionally, supply a collector instance that may cache (or have already cached) base revision
56 * @param sc may be null
57 */
58 public void setBaseRevisionCollector(StatusCollector sc) {
59 baseRevisionCollector = sc;
60 }
61
62 private HgDirstate getDirstate() {
63 if (dirstate == null) {
64 dirstate = repo.loadDirstate();
65 }
66 return dirstate;
67 }
68
69 // may be invoked few times
70 public void walk(int baseRevision, StatusCollector.Inspector inspector) {
71 final HgIgnore hgIgnore = repo.loadIgnore();
72 TreeSet<String> knownEntries = getDirstate().all();
73 final boolean isTipBase;
74 if (baseRevision == TIP) {
75 baseRevision = repo.getManifest().getRevisionCount() - 1;
76 isTipBase = true;
77 } else {
78 isTipBase = baseRevision == repo.getManifest().getRevisionCount() - 1;
79 }
80 StatusCollector.ManifestRevisionInspector collect = null;
81 Set<String> baseRevFiles = Collections.emptySet();
82 if (!isTipBase) {
83 if (baseRevisionCollector != null) {
84 collect = baseRevisionCollector.raw(baseRevision);
85 } else {
86 collect = new StatusCollector.ManifestRevisionInspector(baseRevision, baseRevision);
87 repo.getManifest().walk(baseRevision, baseRevision, collect);
88 }
89 baseRevFiles = new TreeSet<String>(collect.files(baseRevision));
90 }
91 if (inspector instanceof StatusCollector.Record) {
92 StatusCollector sc = baseRevisionCollector == null ? new StatusCollector(repo) : baseRevisionCollector;
93 ((StatusCollector.Record) inspector).init(baseRevision, BAD_REVISION, sc);
94 }
95 repoWalker.reset();
96 while (repoWalker.hasNext()) {
97 repoWalker.next();
98 String fname = repoWalker.name();
99 File f = repoWalker.file();
100 if (hgIgnore.isIgnored(fname)) {
101 inspector.ignored(fname);
102 } else if (knownEntries.remove(fname)) {
103 // modified, added, removed, clean
104 if (collect != null) { // need to check against base revision, not FS file
105 Nodeid nid1 = collect.nodeid(baseRevision, fname);
106 String flags = collect.flags(baseRevision, fname);
107 checkLocalStatusAgainstBaseRevision(baseRevFiles, nid1, flags, fname, f, inspector);
108 baseRevFiles.remove(fname);
109 } else {
110 checkLocalStatusAgainstFile(fname, f, inspector);
111 }
112 } else {
113 inspector.unknown(fname);
114 }
115 }
116 if (collect != null) {
117 for (String r : baseRevFiles) {
118 inspector.removed(r);
119 }
120 }
121 for (String m : knownEntries) {
122 // missing known file from a working dir
123 if (getDirstate().checkRemoved(m) == null) {
124 // not removed from the repository = 'deleted'
125 inspector.missing(m);
126 } else {
127 // removed from the repo
128 inspector.removed(m);
129 }
130 }
131 }
132
133 public StatusCollector.Record status(int baseRevision) {
134 StatusCollector.Record rv = new StatusCollector.Record();
135 walk(baseRevision, rv);
136 return rv;
137 }
138
139 //********************************************
140
141
142 private void checkLocalStatusAgainstFile(String fname, File f, StatusCollector.Inspector inspector) {
143 HgDirstate.Record r;
144 if ((r = getDirstate().checkNormal(fname)) != null) {
145 // either clean or modified
146 if (f.lastModified() / 1000 == r.time && r.size == f.length()) {
147 inspector.clean(fname);
148 } else {
149 // FIXME check actual content to avoid false modified files
150 inspector.modified(fname);
151 }
152 } else if ((r = getDirstate().checkAdded(fname)) != null) {
153 if (r.name2 == null) {
154 inspector.added(fname);
155 } else {
156 inspector.copied(r.name2, fname);
157 }
158 } else if ((r = getDirstate().checkRemoved(fname)) != null) {
159 inspector.removed(fname);
160 } else if ((r = getDirstate().checkMerged(fname)) != null) {
161 inspector.modified(fname);
162 }
163 }
164
165 // XXX refactor checkLocalStatus methods in more OO way
166 private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, Nodeid nid1, String flags, String fname, File f, StatusCollector.Inspector inspector) {
167 // fname is in the dirstate, either Normal, Added, Removed or Merged
168 HgDirstate.Record r;
169 if (nid1 == null) {
170 // normal: added?
171 // added: not known at the time of baseRevision, shall report
172 // merged: was not known, report as added?
173 if ((r = getDirstate().checkAdded(fname)) != null) {
174 if (r.name2 != null && baseRevNames.contains(r.name2)) {
175 baseRevNames.remove(r.name2);
176 inspector.copied(r.name2, fname);
177 return;
178 }
179 // fall-through, report as added
180 } else if (getDirstate().checkRemoved(fname) != null) {
181 // removed: removed file was not known at the time of baseRevision, and we should not report it as removed
182 return;
183 }
184 inspector.added(fname);
185 } else {
186 // was known; check whether clean or modified
187 // when added - seems to be the case of a file added once again, hence need to check if content is different
188 if ((r = getDirstate().checkNormal(fname)) != null || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) {
189 // either clean or modified
190 HgDataFile fileNode = repo.getFileNode(fname);
191 final int lengthAtRevision = fileNode.length(nid1);
192 if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) {
193 inspector.modified(fname);
194 } else {
195 // check actual content to see actual changes
196 // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
197 if (areTheSame(f, fileNode.content(nid1))) {
198 inspector.clean(fname);
199 } else {
200 inspector.modified(fname);
201 }
202 }
203 }
204 // only those left in idsMap after processing are reported as removed
205 }
206
207 // TODO think over if content comparison may be done more effectively by e.g. calculating nodeid for a local file and comparing it with nodeid from manifest
208 // we don't need to tell exact difference, hash should be enough to detect difference, and it doesn't involve reading historical file content, and it's relatively
209 // cheap to calc hash on a file (no need to keep it completely in memory). OTOH, if I'm right that the next approach is used for nodeids:
210 // changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest
211 // then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids).
212 // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean'
213 }
214
215 private static String todoGenerateFlags(String fname) {
216 // FIXME implement
217 return null;
218 }
219 private static boolean areTheSame(File f, byte[] data) {
220 try {
221 BufferedInputStream is = new BufferedInputStream(new FileInputStream(f));
222 int i = 0;
223 while (i < data.length && data[i] == is.read()) {
224 i++; // increment only for successful match, otherwise won't tell last byte in data was the same as read from the stream
225 }
226 return i == data.length && is.read() == -1; // although data length is expected to be the same (see caller), check that we reached EOF, no more data left.
227 } catch (IOException ex) {
228 ex.printStackTrace(); // log warn
229 }
230 return false;
231 }
232
233 }