comparison src/com/tmate/hgkit/ll/WorkingCopyStatusCollector.java @ 58:4cfc47bc14cc

Status against local working dir extracted into distinct class. Iterating over local files extracted for ease of os-dependant patching
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 17 Jan 2011 23:01:19 +0100
parents
children b771e94a4f7c
comparison
equal deleted inserted replaced
57:8b0d6f1bd6b4 58:4cfc47bc14cc
1 /*
2 * Copyright (c) 2011 Artem Tikhomirov
3 */
4 package com.tmate.hgkit.ll;
5
6 import static com.tmate.hgkit.ll.HgRepository.TIP;
7
8 import java.io.BufferedInputStream;
9 import java.io.File;
10 import java.io.FileInputStream;
11 import java.io.IOException;
12 import java.util.Collections;
13 import java.util.Set;
14 import java.util.TreeSet;
15
16 import com.tmate.hgkit.fs.FileWalker;
17
18 /**
19 *
20 * @author artem
21 */
22 public class WorkingCopyStatusCollector {
23
24 private final HgRepository repo;
25 private final FileWalker repoWalker;
26
27 public WorkingCopyStatusCollector(HgRepository hgRepo, FileWalker hgRepoWalker) {
28 this.repo = hgRepo;
29 this.repoWalker = hgRepoWalker;
30 }
31
32 public void walk(int baseRevision, StatusCollector.Inspector inspector) {
33 final HgIgnore hgIgnore = ((LocalHgRepo) repo).loadIgnore(); // FIXME hack
34 final HgDirstate dirstate = ((LocalHgRepo) repo).loadDirstate(); // FIXME hack
35 TreeSet<String> knownEntries = dirstate.all();
36 final boolean isTipBase = baseRevision == TIP || baseRevision == repo.getManifest().getRevisionCount();
37 StatusCollector.ManifestRevisionInspector collect = null;
38 Set<String> baseRevFiles = Collections.emptySet();
39 if (!isTipBase) {
40 collect = new StatusCollector.ManifestRevisionInspector(baseRevision, baseRevision);
41 repo.getManifest().walk(baseRevision, baseRevision, collect);
42 baseRevFiles = new TreeSet<String>(collect.files(baseRevision));
43 }
44 repoWalker.reset();
45 while (repoWalker.hasNext()) {
46 repoWalker.next();
47 String fname = repoWalker.name();
48 File f = repoWalker.file();
49 if (hgIgnore.isIgnored(fname)) {
50 inspector.ignored(fname);
51 } else if (knownEntries.remove(fname)) {
52 // modified, added, removed, clean
53 if (collect != null) { // need to check against base revision, not FS file
54 Nodeid nid1 = collect.nodeid(baseRevision, fname);
55 String flags = collect.flags(baseRevision, fname);
56 checkLocalStatusAgainstBaseRevision(baseRevFiles, nid1, flags, fname, f, dirstate, inspector);
57 baseRevFiles.remove(fname);
58 } else {
59 checkLocalStatusAgainstFile(fname, f, dirstate, inspector);
60 }
61 } else {
62 inspector.unknown(fname);
63 }
64 }
65 if (collect != null) {
66 for (String r : baseRevFiles) {
67 inspector.removed(r);
68 }
69 }
70 for (String m : knownEntries) {
71 // removed from the repository and missing from working dir shall not be reported as 'deleted'
72 if (dirstate.checkRemoved(m) == null) {
73 inspector.missing(m);
74 }
75 }
76 }
77
78 public StatusCollector.Record status(int baseRevision) {
79 StatusCollector.Record rv = new StatusCollector.Record();
80 walk(baseRevision, rv);
81 return rv;
82 }
83
84 //********************************************
85
86
87 private static void checkLocalStatusAgainstFile(String fname, File f, HgDirstate dirstate, StatusCollector.Inspector inspector) {
88 HgDirstate.Record r;
89 if ((r = dirstate.checkNormal(fname)) != null) {
90 // either clean or modified
91 if (f.lastModified() / 1000 == r.time && r.size == f.length()) {
92 inspector.clean(fname);
93 } else {
94 // FIXME check actual content to avoid false modified files
95 inspector.modified(fname);
96 }
97 } else if ((r = dirstate.checkAdded(fname)) != null) {
98 if (r.name2 == null) {
99 inspector.added(fname);
100 } else {
101 inspector.copied(fname, r.name2);
102 }
103 } else if ((r = dirstate.checkRemoved(fname)) != null) {
104 inspector.removed(fname);
105 } else if ((r = dirstate.checkMerged(fname)) != null) {
106 inspector.modified(fname);
107 }
108 }
109
110 // XXX refactor checkLocalStatus methods in more OO way
111 private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, Nodeid nid1, String flags, String fname, File f, HgDirstate dirstate, StatusCollector.Inspector inspector) {
112 // fname is in the dirstate, either Normal, Added, Removed or Merged
113 HgDirstate.Record r;
114 if (nid1 == null) {
115 // normal: added?
116 // added: not known at the time of baseRevision, shall report
117 // merged: was not known, report as added?
118 if ((r = dirstate.checkAdded(fname)) != null) {
119 if (r.name2 != null && baseRevNames.contains(r.name2)) {
120 baseRevNames.remove(r.name2);
121 inspector.copied(r.name2, fname);
122 return;
123 }
124 // fall-through, report as added
125 } else if (dirstate.checkRemoved(fname) != null) {
126 // removed: removed file was not known at the time of baseRevision, and we should not report it as removed
127 return;
128 }
129 inspector.added(fname);
130 } else {
131 // was known; check whether clean or modified
132 // when added - seems to be the case of a file added once again, hence need to check if content is different
133 if ((r = dirstate.checkNormal(fname)) != null || (r = dirstate.checkMerged(fname)) != null || (r = dirstate.checkAdded(fname)) != null) {
134 // either clean or modified
135 HgDataFile fileNode = repo.getFileNode(fname);
136 final int lengthAtRevision = fileNode.length(nid1);
137 if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) {
138 inspector.modified(fname);
139 } else {
140 // check actual content to see actual changes
141 // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
142 if (areTheSame(f, fileNode.content(nid1))) {
143 inspector.clean(fname);
144 } else {
145 inspector.modified(fname);
146 }
147 }
148 }
149 // only those left in idsMap after processing are reported as removed
150 }
151
152 // 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
153 // 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
154 // 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:
155 // changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest
156 // 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).
157 // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean'
158 }
159
160 private static String todoGenerateFlags(String fname) {
161 // FIXME implement
162 return null;
163 }
164 private static boolean areTheSame(File f, byte[] data) {
165 try {
166 BufferedInputStream is = new BufferedInputStream(new FileInputStream(f));
167 int i = 0;
168 while (i < data.length && data[i] == is.read()) {
169 i++; // increment only for successful match, otherwise won't tell last byte in data was the same as read from the stream
170 }
171 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.
172 } catch (IOException ex) {
173 ex.printStackTrace(); // log warn
174 }
175 return false;
176 }
177
178 }