comparison src/com/tmate/hgkit/ll/LocalHgRepo.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 8b0d6f1bd6b4
children b771e94a4f7c
comparison
equal deleted inserted replaced
57:8b0d6f1bd6b4 58:4cfc47bc14cc
4 package com.tmate.hgkit.ll; 4 package com.tmate.hgkit.ll;
5 5
6 import java.io.BufferedInputStream; 6 import java.io.BufferedInputStream;
7 import java.io.BufferedReader; 7 import java.io.BufferedReader;
8 import java.io.File; 8 import java.io.File;
9 import java.io.FileFilter;
9 import java.io.FileInputStream; 10 import java.io.FileInputStream;
11 import java.io.FilenameFilter;
10 import java.io.IOException; 12 import java.io.IOException;
11 import java.io.InputStreamReader; 13 import java.io.InputStreamReader;
12 import java.lang.ref.SoftReference; 14 import java.lang.ref.SoftReference;
13 import java.util.Arrays; 15 import java.util.Arrays;
14 import java.util.Collections; 16 import java.util.Collections;
16 import java.util.LinkedList; 18 import java.util.LinkedList;
17 import java.util.Set; 19 import java.util.Set;
18 import java.util.TreeSet; 20 import java.util.TreeSet;
19 21
20 import com.tmate.hgkit.fs.DataAccessProvider; 22 import com.tmate.hgkit.fs.DataAccessProvider;
23 import com.tmate.hgkit.fs.FileWalker;
21 24
22 /** 25 /**
23 * @author artem 26 * @author artem
24 */ 27 */
25 public class LocalHgRepo extends HgRepository { 28 public class LocalHgRepo extends HgRepository {
29 private final DataAccessProvider dataAccess; 32 private final DataAccessProvider dataAccess;
30 33
31 public LocalHgRepo(String repositoryPath) { 34 public LocalHgRepo(String repositoryPath) {
32 setInvalid(true); 35 setInvalid(true);
33 repoLocation = repositoryPath; 36 repoLocation = repositoryPath;
34 dataAccess = null; 37 dataAccess = null;
35 } 38 }
36 39
37 public LocalHgRepo(File repositoryRoot) throws IOException { 40 public LocalHgRepo(File repositoryRoot) throws IOException {
38 assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory(); 41 assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory();
39 setInvalid(false); 42 setInvalid(false);
46 @Override 49 @Override
47 public String getLocation() { 50 public String getLocation() {
48 return repoLocation; 51 return repoLocation;
49 } 52 }
50 53
51 public void statusLocal(int baseRevision, StatusCollector.Inspector inspector) { 54 public FileWalker createWorkingDirWalker() {
52 LinkedList<File> folders = new LinkedList<File>(); 55 return new FileWalker(repoDir.getParentFile());
53 final File rootDir = repoDir.getParentFile(); 56 }
54 folders.add(rootDir); 57
55 final HgDirstate dirstate = loadDirstate();
56 final HgIgnore hgignore = loadIgnore();
57 TreeSet<String> knownEntries = dirstate.all();
58 final boolean isTipBase = baseRevision == TIP || baseRevision == getManifest().getRevisionCount();
59 StatusCollector.ManifestRevisionInspector collect = null;
60 Set<String> baseRevFiles = Collections.emptySet();
61 if (!isTipBase) {
62 collect = new StatusCollector.ManifestRevisionInspector(baseRevision, baseRevision);
63 getManifest().walk(baseRevision, baseRevision, collect);
64 baseRevFiles = new TreeSet<String>(collect.files(baseRevision));
65 }
66 do {
67 File d = folders.removeFirst();
68 for (File f : d.listFiles()) {
69 if (f.isDirectory()) {
70 if (!".hg".equals(f.getName())) {
71 folders.addLast(f);
72 }
73 } else {
74 // FIXME path relative to rootDir - need more robust approach
75 String fname = normalize(f.getPath().substring(rootDir.getPath().length() + 1));
76 if (hgignore.isIgnored(fname)) {
77 inspector.ignored(fname);
78 } else {
79 if (knownEntries.remove(fname)) {
80 // modified, added, removed, clean
81 if (collect != null) { // need to check against base revision, not FS file
82 Nodeid nid1 = collect.nodeid(baseRevision, fname);
83 String flags = collect.flags(baseRevision, fname);
84 checkLocalStatusAgainstBaseRevision(baseRevFiles, nid1, flags, fname, f, dirstate, inspector);
85 baseRevFiles.remove(fname);
86 } else {
87 checkLocalStatusAgainstFile(fname, f, dirstate, inspector);
88 }
89 } else {
90 inspector.unknown(fname);
91 }
92 }
93 }
94 }
95 } while (!folders.isEmpty());
96 if (collect != null) {
97 for (String r : baseRevFiles) {
98 inspector.removed(r);
99 }
100 }
101 for (String m : knownEntries) {
102 // removed from the repository and missing from working dir shall not be reported as 'deleted'
103 if (dirstate.checkRemoved(m) == null) {
104 inspector.missing(m);
105 }
106 }
107 }
108
109 private static void checkLocalStatusAgainstFile(String fname, File f, HgDirstate dirstate, StatusCollector.Inspector inspector) {
110 HgDirstate.Record r;
111 if ((r = dirstate.checkNormal(fname)) != null) {
112 // either clean or modified
113 if (f.lastModified() / 1000 == r.time && r.size == f.length()) {
114 inspector.clean(fname);
115 } else {
116 // FIXME check actual content to avoid false modified files
117 inspector.modified(fname);
118 }
119 } else if ((r = dirstate.checkAdded(fname)) != null) {
120 if (r.name2 == null) {
121 inspector.added(fname);
122 } else {
123 inspector.copied(fname, r.name2);
124 }
125 } else if ((r = dirstate.checkRemoved(fname)) != null) {
126 inspector.removed(fname);
127 } else if ((r = dirstate.checkMerged(fname)) != null) {
128 inspector.modified(fname);
129 }
130 }
131
132 // XXX refactor checkLocalStatus methods in more OO way
133 private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, Nodeid nid1, String flags, String fname, File f, HgDirstate dirstate, StatusCollector.Inspector inspector) {
134 // fname is in the dirstate, either Normal, Added, Removed or Merged
135 HgDirstate.Record r;
136 if (nid1 == null) {
137 // normal: added?
138 // added: not known at the time of baseRevision, shall report
139 // merged: was not known, report as added?
140 if ((r = dirstate.checkAdded(fname)) != null) {
141 if (r.name2 != null && baseRevNames.contains(r.name2)) {
142 baseRevNames.remove(r.name2);
143 inspector.copied(r.name2, fname);
144 return;
145 }
146 // fall-through, report as added
147 } else if (dirstate.checkRemoved(fname) != null) {
148 // removed: removed file was not known at the time of baseRevision, and we should not report it as removed
149 return;
150 }
151 inspector.added(fname);
152 } else {
153 // was known; check whether clean or modified
154 // when added - seems to be the case of a file added once again, hence need to check if content is different
155 if ((r = dirstate.checkNormal(fname)) != null || (r = dirstate.checkMerged(fname)) != null || (r = dirstate.checkAdded(fname)) != null) {
156 // either clean or modified
157 HgDataFile fileNode = getFileNode(fname);
158 final int lengthAtRevision = fileNode.length(nid1);
159 if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) {
160 inspector.modified(fname);
161 } else {
162 // check actual content to see actual changes
163 // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
164 if (areTheSame(f, fileNode.content(nid1))) {
165 inspector.clean(fname);
166 } else {
167 inspector.modified(fname);
168 }
169 }
170 }
171 // only those left in idsMap after processing are reported as removed
172 }
173
174 // 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
175 // 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
176 // 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:
177 // changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest
178 // 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).
179 // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean'
180 }
181
182 private static String todoGenerateFlags(String fname) {
183 // FIXME implement
184 return null;
185 }
186 private static boolean areTheSame(File f, byte[] data) {
187 try {
188 BufferedInputStream is = new BufferedInputStream(new FileInputStream(f));
189 int i = 0;
190 while (i < data.length && data[i] == is.read()) {
191 i++; // increment only for successful match, otherwise won't tell last byte in data was the same as read from the stream
192 }
193 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.
194 } catch (IOException ex) {
195 ex.printStackTrace(); // log warn
196 }
197 return false;
198 }
199
200 // XXX package-local, unless there are cases when required from outside (guess, working dir/revision walkers may hide dirstate access and no public visibility needed) 58 // XXX package-local, unless there are cases when required from outside (guess, working dir/revision walkers may hide dirstate access and no public visibility needed)
201 public final HgDirstate loadDirstate() { 59 public final HgDirstate loadDirstate() {
202 // XXX may cache in SoftReference if creation is expensive 60 // XXX may cache in SoftReference if creation is expensive
203 return new HgDirstate(this, new File(repoDir, "dirstate")); 61 return new HgDirstate(this, new File(repoDir, "dirstate"));
204 } 62 }