comparison src/org/tmatesoft/hg/repo/HgDirstate.java @ 284:7232b94f2ae3

HgDirstate shall operate with Path instead of String for file names. Use of Pair instead of array of unspecified length for parents.
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sat, 03 Sep 2011 13:12:13 +0200
parents 35125450c804
children 8faad08c709b
comparison
equal deleted inserted replaced
283:7a8e1a305a78 284:7232b94f2ae3
14 * the terms of a license other than GNU General Public License 14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com 15 * contact TMate Software at support@hg4j.com
16 */ 16 */
17 package org.tmatesoft.hg.repo; 17 package org.tmatesoft.hg.repo;
18 18
19 import static org.tmatesoft.hg.core.Nodeid.NULL;
20
19 import java.io.BufferedReader; 21 import java.io.BufferedReader;
20 import java.io.File; 22 import java.io.File;
21 import java.io.FileReader; 23 import java.io.FileReader;
22 import java.io.IOException; 24 import java.io.IOException;
23 import java.util.Collections; 25 import java.util.Collections;
26 import java.util.TreeSet; 28 import java.util.TreeSet;
27 29
28 import org.tmatesoft.hg.core.HgBadStateException; 30 import org.tmatesoft.hg.core.HgBadStateException;
29 import org.tmatesoft.hg.core.Nodeid; 31 import org.tmatesoft.hg.core.Nodeid;
30 import org.tmatesoft.hg.internal.DataAccess; 32 import org.tmatesoft.hg.internal.DataAccess;
33 import org.tmatesoft.hg.util.Pair;
31 import org.tmatesoft.hg.util.Path; 34 import org.tmatesoft.hg.util.Path;
35 import org.tmatesoft.hg.util.PathPool;
32 36
33 37
34 /** 38 /**
35 * @see http://mercurial.selenic.com/wiki/DirState 39 * @see http://mercurial.selenic.com/wiki/DirState
36 * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate 40 * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate
40 */ 44 */
41 class HgDirstate /* XXX RepoChangeListener */{ 45 class HgDirstate /* XXX RepoChangeListener */{
42 46
43 private final HgRepository repo; 47 private final HgRepository repo;
44 private final File dirstateFile; 48 private final File dirstateFile;
45 // deliberate String, not Path as it seems useless to keep Path here 49 private final PathPool pathPool;
46 private Map<String, Record> normal; 50 private Map<Path, Record> normal;
47 private Map<String, Record> added; 51 private Map<Path, Record> added;
48 private Map<String, Record> removed; 52 private Map<Path, Record> removed;
49 private Map<String, Record> merged; 53 private Map<Path, Record> merged;
50 private Nodeid p1, p2; 54 private Pair<Nodeid, Nodeid> parents;
51 private String currentBranch; 55 private String currentBranch;
52 56
53 public HgDirstate(HgRepository hgRepo, File dirstate) { 57 public HgDirstate(HgRepository hgRepo, File dirstate, PathPool pathPool) {
54 repo = hgRepo; 58 repo = hgRepo;
55 dirstateFile = dirstate; // XXX decide whether file names shall be kept local to reader (see #branches()) or passed from outside 59 dirstateFile = dirstate; // XXX decide whether file names shall be kept local to reader (see #branches()) or passed from outside
60 this.pathPool = pathPool;
56 } 61 }
57 62
58 private void read() { 63 private void read() {
59 normal = added = removed = merged = Collections.<String, Record>emptyMap(); 64 normal = added = removed = merged = Collections.<Path, Record>emptyMap();
60 if (dirstateFile == null || !dirstateFile.exists()) { 65 if (dirstateFile == null || !dirstateFile.exists()) {
61 return; 66 return;
62 } 67 }
63 DataAccess da = repo.getDataAccess().create(dirstateFile); 68 DataAccess da = repo.getDataAccess().create(dirstateFile);
64 if (da.isEmpty()) { 69 if (da.isEmpty()) {
65 return; 70 return;
66 } 71 }
67 // not sure linked is really needed here, just for ease of debug 72 // not sure linked is really needed here, just for ease of debug
68 normal = new LinkedHashMap<String, Record>(); 73 normal = new LinkedHashMap<Path, Record>();
69 added = new LinkedHashMap<String, Record>(); 74 added = new LinkedHashMap<Path, Record>();
70 removed = new LinkedHashMap<String, Record>(); 75 removed = new LinkedHashMap<Path, Record>();
71 merged = new LinkedHashMap<String, Record>(); 76 merged = new LinkedHashMap<Path, Record>();
72 try { 77 try {
73 byte[] parents = new byte[40]; 78 parents = internalReadParents(da);
74 da.readBytes(parents, 0, 40);
75 p1 = Nodeid.fromBinary(parents, 0);
76 p2 = Nodeid.fromBinary(parents, 20);
77 parents = null;
78 // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only 79 // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only
79 while (!da.isEmpty()) { 80 while (!da.isEmpty()) {
80 final byte state = da.readByte(); 81 final byte state = da.readByte();
81 final int fmode = da.readInt(); 82 final int fmode = da.readInt();
82 final int size = da.readInt(); 83 final int size = da.readInt();
93 } 94 }
94 } 95 }
95 if (fn1 == null) { 96 if (fn1 == null) {
96 fn1 = new String(name); 97 fn1 = new String(name);
97 } 98 }
98 Record r = new Record(fmode, size, time, fn1, fn2); 99 Record r = new Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2));
99 if (state == 'n') { 100 if (state == 'n') {
100 normal.put(r.name1, r); 101 normal.put(r.name1, r);
101 } else if (state == 'a') { 102 } else if (state == 'a') {
102 added.put(r.name1, r); 103 added.put(r.name1, r);
103 } else if (state == 'r') { 104 } else if (state == 'r') {
113 } finally { 114 } finally {
114 da.done(); 115 da.done();
115 } 116 }
116 } 117 }
117 118
118 // do not read whole dirstate if all we need is WC parent information 119 private static Pair<Nodeid, Nodeid> internalReadParents(DataAccess da) throws IOException {
119 private void readParents() { 120 byte[] parents = new byte[40];
121 da.readBytes(parents, 0, 40);
122 Nodeid n1 = Nodeid.fromBinary(parents, 0);
123 Nodeid n2 = Nodeid.fromBinary(parents, 20);
124 parents = null;
125 return new Pair<Nodeid, Nodeid>(n1, n2);
126 }
127
128 /**
129 * @return array of length 2 with working copy parents, non null.
130 */
131 public Pair<Nodeid,Nodeid> parents() {
132 if (parents == null) {
133 parents = readParents(repo, dirstateFile);
134 }
135 return parents;
136 }
137
138 /**
139 * @return pair of parents, both {@link Nodeid#NULL} if dirstate is not available
140 */
141 public static Pair<Nodeid, Nodeid> readParents(HgRepository repo, File dirstateFile) {
142 // do not read whole dirstate if all we need is WC parent information
120 if (dirstateFile == null || !dirstateFile.exists()) { 143 if (dirstateFile == null || !dirstateFile.exists()) {
121 return; 144 return new Pair<Nodeid,Nodeid>(NULL, NULL);
122 } 145 }
123 DataAccess da = repo.getDataAccess().create(dirstateFile); 146 DataAccess da = repo.getDataAccess().create(dirstateFile);
124 if (da.isEmpty()) { 147 if (da.isEmpty()) {
125 return; 148 return new Pair<Nodeid,Nodeid>(NULL, NULL);
126 } 149 }
127 try { 150 try {
128 byte[] parents = new byte[40]; 151 return internalReadParents(da);
129 da.readBytes(parents, 0, 40);
130 p1 = Nodeid.fromBinary(parents, 0);
131 p2 = Nodeid.fromBinary(parents, 20);
132 parents = null;
133 } catch (IOException ex) { 152 } catch (IOException ex) {
134 throw new HgBadStateException(ex); // XXX in fact, our exception is not the best solution here. 153 throw new HgBadStateException(ex); // XXX in fact, our exception is not the best solution here.
135 } finally { 154 } finally {
136 da.done(); 155 da.done();
137 } 156 }
138 } 157 }
139 158
140 /** 159 /**
141 * @return array of length 2 with working copy parents, non null. 160 * XXX is it really proper place for the method?
142 */
143 public Nodeid[] parents() {
144 if (p1 == null) {
145 readParents();
146 }
147 Nodeid[] rv = new Nodeid[2];
148 rv[0] = p1;
149 rv[1] = p2;
150 return rv;
151 }
152
153 /**
154 * @return branch associated with the working directory 161 * @return branch associated with the working directory
155 */ 162 */
156 public String branch() { 163 public String branch() {
157 if (currentBranch == null) { 164 if (currentBranch == null) {
158 currentBranch = HgRepository.DEFAULT_BRANCH_NAME; 165 currentBranch = readBranch(repo);
159 File branchFile = new File(repo.getRepositoryRoot(), "branch");
160 if (branchFile.exists()) {
161 try {
162 BufferedReader r = new BufferedReader(new FileReader(branchFile));
163 String b = r.readLine();
164 if (b != null) {
165 b = b.trim().intern();
166 }
167 currentBranch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b;
168 r.close();
169 } catch (IOException ex) {
170 ex.printStackTrace(); // XXX log verbose debug, exception might be legal here (i.e. FileNotFound)
171 // IGNORE
172 }
173 }
174 } 166 }
175 return currentBranch; 167 return currentBranch;
176 } 168 }
169
170 /**
171 * XXX is it really proper place for the method?
172 * @return branch associated with the working directory
173 */
174 public static String readBranch(HgRepository repo) {
175 String branch = HgRepository.DEFAULT_BRANCH_NAME;
176 File branchFile = new File(repo.getRepositoryRoot(), "branch");
177 if (branchFile.exists()) {
178 try {
179 BufferedReader r = new BufferedReader(new FileReader(branchFile));
180 String b = r.readLine();
181 if (b != null) {
182 b = b.trim().intern();
183 }
184 branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b;
185 r.close();
186 } catch (IOException ex) {
187 ex.printStackTrace(); // XXX log verbose debug, exception might be legal here (i.e. FileNotFound)
188 // IGNORE
189 }
190 }
191 return branch;
192 }
177 193
178 // new, modifiable collection 194 // new, modifiable collection
179 /*package-local*/ TreeSet<String> all() { 195 /*package-local*/ TreeSet<Path> all() {
180 read(); 196 read();
181 TreeSet<String> rv = new TreeSet<String>(); 197 TreeSet<Path> rv = new TreeSet<Path>();
182 @SuppressWarnings("unchecked") 198 @SuppressWarnings("unchecked")
183 Map<String, Record>[] all = new Map[] { normal, added, removed, merged }; 199 Map<Path, Record>[] all = new Map[] { normal, added, removed, merged };
184 for (int i = 0; i < all.length; i++) { 200 for (int i = 0; i < all.length; i++) {
185 for (Record r : all[i].values()) { 201 for (Record r : all[i].values()) {
186 rv.add(r.name1); 202 rv.add(r.name1);
187 } 203 }
188 } 204 }
189 return rv; 205 return rv;
190 } 206 }
191 207
192 /*package-local*/ Record checkNormal(Path fname) { 208 /*package-local*/ Record checkNormal(Path fname) {
193 return normal.get(fname.toString()); 209 return normal.get(fname);
194 } 210 }
195 211
196 /*package-local*/ Record checkAdded(Path fname) { 212 /*package-local*/ Record checkAdded(Path fname) {
197 return added.get(fname.toString()); 213 return added.get(fname);
198 } 214 }
199 /*package-local*/ Record checkRemoved(Path fname) { 215 /*package-local*/ Record checkRemoved(Path fname) {
200 return removed.get(fname.toString());
201 }
202 /*package-local*/ Record checkRemoved(String fname) {
203 return removed.get(fname); 216 return removed.get(fname);
204 } 217 }
205 /*package-local*/ Record checkMerged(Path fname) { 218 /*package-local*/ Record checkMerged(Path fname) {
206 return merged.get(fname.toString()); 219 return merged.get(fname);
207 } 220 }
208 221
209 222
210 223
211 224
212 /*package-local*/ void dump() { 225 /*package-local*/ void dump() {
213 read(); 226 read();
214 @SuppressWarnings("unchecked") 227 @SuppressWarnings("unchecked")
215 Map<String, Record>[] all = new Map[] { normal, added, removed, merged }; 228 Map<Path, Record>[] all = new Map[] { normal, added, removed, merged };
216 char[] x = new char[] {'n', 'a', 'r', 'm' }; 229 char[] x = new char[] {'n', 'a', 'r', 'm' };
217 for (int i = 0; i < all.length; i++) { 230 for (int i = 0; i < all.length; i++) {
218 for (Record r : all[i].values()) { 231 for (Record r : all[i].values()) {
219 System.out.printf("%c %3o%6d %30tc\t\t%s", x[i], r.mode, r.size, (long) r.time * 1000, r.name1); 232 System.out.printf("%c %3o%6d %30tc\t\t%s", x[i], r.mode, r.size, (long) r.time * 1000, r.name1);
220 if (r.name2 != null) { 233 if (r.name2 != null) {
230 final int mode; 243 final int mode;
231 // it seems Dirstate keeps local file size (i.e. that with any filters already applied). 244 // it seems Dirstate keeps local file size (i.e. that with any filters already applied).
232 // Thus, can't compare directly to HgDataFile.length() 245 // Thus, can't compare directly to HgDataFile.length()
233 final int size; 246 final int size;
234 final int time; 247 final int time;
235 final String name1; 248 final Path name1;
236 final String name2; 249 final Path name2;
237 250
238 public Record(int fmode, int fsize, int ftime, String name1, String name2) { 251 public Record(int fmode, int fsize, int ftime, Path name1, Path name2) {
239 mode = fmode; 252 mode = fmode;
240 size = fsize; 253 size = fsize;
241 time = ftime; 254 time = ftime;
242 this.name1 = name1; 255 this.name1 = name1;
243 this.name2 = name2; 256 this.name2 = name2;