Mercurial > jhg
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; |
