Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgDirstate.java @ 526:2f9ed6bcefa2
Initial support for Revert command with accompanying minor refactoring
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Tue, 15 Jan 2013 17:07:19 +0100 |
| parents | 0be5be8d57e9 |
| children |
comparison
equal
deleted
inserted
replaced
| 525:0be5be8d57e9 | 526:2f9ed6bcefa2 |
|---|---|
| 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 import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate; | |
| 21 import static org.tmatesoft.hg.util.LogFacility.Severity.Debug; | |
| 22 | |
| 23 import java.io.BufferedReader; | |
| 24 import java.io.File; | |
| 25 import java.io.FileNotFoundException; | |
| 26 import java.io.FileReader; | |
| 27 import java.io.IOException; | |
| 28 import java.util.Collections; | 19 import java.util.Collections; |
| 29 import java.util.HashMap; | 20 import java.util.HashMap; |
| 30 import java.util.LinkedHashMap; | 21 import java.util.LinkedHashMap; |
| 31 import java.util.Map; | 22 import java.util.Map; |
| 32 import java.util.TreeSet; | 23 import java.util.TreeSet; |
| 33 | 24 |
| 34 import org.tmatesoft.hg.core.Nodeid; | 25 import org.tmatesoft.hg.core.Nodeid; |
| 35 import org.tmatesoft.hg.internal.DataAccess; | 26 import org.tmatesoft.hg.internal.DirstateReader; |
| 36 import org.tmatesoft.hg.internal.EncodingHelper; | |
| 37 import org.tmatesoft.hg.internal.Internals; | 27 import org.tmatesoft.hg.internal.Internals; |
| 38 import org.tmatesoft.hg.util.Pair; | 28 import org.tmatesoft.hg.util.Pair; |
| 39 import org.tmatesoft.hg.util.Path; | 29 import org.tmatesoft.hg.util.Path; |
| 40 import org.tmatesoft.hg.util.PathRewrite; | 30 import org.tmatesoft.hg.util.PathRewrite; |
| 41 import org.tmatesoft.hg.util.LogFacility.Severity; | |
| 42 | 31 |
| 43 | 32 |
| 44 /** | 33 /** |
| 45 * @see http://mercurial.selenic.com/wiki/DirState | 34 * @see http://mercurial.selenic.com/wiki/DirState |
| 46 * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate | 35 * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate |
| 73 pathPool = pathSource; | 62 pathPool = pathSource; |
| 74 canonicalPathRewrite = canonicalPath; | 63 canonicalPathRewrite = canonicalPath; |
| 75 } | 64 } |
| 76 | 65 |
| 77 /*package-local*/ void read() throws HgInvalidControlFileException { | 66 /*package-local*/ void read() throws HgInvalidControlFileException { |
| 78 EncodingHelper encodingHelper = repo.buildFileNameEncodingHelper(); | |
| 79 normal = added = removed = merged = Collections.<Path, Record>emptyMap(); | 67 normal = added = removed = merged = Collections.<Path, Record>emptyMap(); |
| 80 parents = new Pair<Nodeid,Nodeid>(Nodeid.NULL, Nodeid.NULL); | 68 parents = new Pair<Nodeid,Nodeid>(Nodeid.NULL, Nodeid.NULL); |
| 81 if (canonicalPathRewrite != null) { | 69 if (canonicalPathRewrite != null) { |
| 82 canonical2dirstateName = new HashMap<Path,Path>(); | 70 canonical2dirstateName = new HashMap<Path,Path>(); |
| 83 } else { | 71 } else { |
| 84 canonical2dirstateName = Collections.emptyMap(); | 72 canonical2dirstateName = Collections.emptyMap(); |
| 85 } | 73 } |
| 86 File dirstateFile = getDirstateFile(repo); | 74 // not sure linked is really needed here, just for ease of debug |
| 87 if (dirstateFile == null || !dirstateFile.exists()) { | 75 normal = new LinkedHashMap<Path, Record>(); |
| 88 return; | 76 added = new LinkedHashMap<Path, Record>(); |
| 89 } | 77 removed = new LinkedHashMap<Path, Record>(); |
| 90 DataAccess da = repo.getDataAccess().create(dirstateFile); | 78 merged = new LinkedHashMap<Path, Record>(); |
| 91 try { | 79 |
| 92 if (da.isEmpty()) { | 80 DirstateReader dirstateReader = new DirstateReader(repo, pathPool); |
| 93 return; | 81 dirstateReader.readInto(new Inspector() { |
| 94 } | |
| 95 // not sure linked is really needed here, just for ease of debug | |
| 96 normal = new LinkedHashMap<Path, Record>(); | |
| 97 added = new LinkedHashMap<Path, Record>(); | |
| 98 removed = new LinkedHashMap<Path, Record>(); | |
| 99 merged = new LinkedHashMap<Path, Record>(); | |
| 100 | 82 |
| 101 parents = internalReadParents(da); | 83 public boolean next(EntryKind kind, Record r) { |
| 102 // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only | |
| 103 while (!da.isEmpty()) { | |
| 104 final byte state = da.readByte(); | |
| 105 final int fmode = da.readInt(); | |
| 106 final int size = da.readInt(); | |
| 107 final int time = da.readInt(); | |
| 108 final int nameLen = da.readInt(); | |
| 109 String fn1 = null, fn2 = null; | |
| 110 byte[] name = new byte[nameLen]; | |
| 111 da.readBytes(name, 0, nameLen); | |
| 112 for (int i = 0; i < nameLen; i++) { | |
| 113 if (name[i] == 0) { | |
| 114 fn1 = encodingHelper.fromDirstate(name, 0, i); | |
| 115 fn2 = encodingHelper.fromDirstate(name, i+1, nameLen - i - 1); | |
| 116 break; | |
| 117 } | |
| 118 } | |
| 119 if (fn1 == null) { | |
| 120 fn1 = encodingHelper.fromDirstate(name, 0, nameLen); | |
| 121 } | |
| 122 Record r = new Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2)); | |
| 123 if (canonicalPathRewrite != null) { | 84 if (canonicalPathRewrite != null) { |
| 124 Path canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(fn1).toString()); | 85 Path canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(r.name())); |
| 125 if (canonicalPath != r.name()) { // == as they come from the same pool | 86 if (canonicalPath != r.name()) { // == as they come from the same pool |
| 126 assert !canonical2dirstateName.containsKey(canonicalPath); // otherwise there's already a file with same canonical name | 87 assert !canonical2dirstateName.containsKey(canonicalPath); // otherwise there's already a file with same canonical name |
| 127 // which can't happen for case-insensitive file system (or there's erroneous PathRewrite, perhaps doing smth else) | 88 // which can't happen for case-insensitive file system (or there's erroneous PathRewrite, perhaps doing smth else) |
| 128 canonical2dirstateName.put(canonicalPath, r.name()); | 89 canonical2dirstateName.put(canonicalPath, r.name()); |
| 129 } | 90 } |
| 130 if (fn2 != null) { | 91 if (r.copySource() != null) { |
| 131 // not sure I need copy origin in the map, I don't seem to use it anywhere, | 92 // not sure I need copy origin in the map, I don't seem to use it anywhere, |
| 132 // but I guess I'll have to use it some day. | 93 // but I guess I'll have to use it some day. |
| 133 canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(fn2).toString()); | 94 canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(r.copySource())); |
| 134 if (canonicalPath != r.copySource()) { | 95 if (canonicalPath != r.copySource()) { |
| 135 canonical2dirstateName.put(canonicalPath, r.copySource()); | 96 canonical2dirstateName.put(canonicalPath, r.copySource()); |
| 136 } | 97 } |
| 137 } | 98 } |
| 138 } | 99 } |
| 139 if (state == 'n') { | 100 switch (kind) { |
| 140 normal.put(r.name1, r); | 101 case Normal : normal.put(r.name(), r); break; |
| 141 } else if (state == 'a') { | 102 case Added : added.put(r.name(), r); break; |
| 142 added.put(r.name1, r); | 103 case Removed : removed.put(r.name(), r); break; |
| 143 } else if (state == 'r') { | 104 case Merged : merged.put(r.name1, r); break; |
| 144 removed.put(r.name1, r); | 105 default: throw new HgInvalidStateException(String.format("Unexpected entry in the dirstate: %s", kind)); |
| 145 } else if (state == 'm') { | |
| 146 merged.put(r.name1, r); | |
| 147 } else { | |
| 148 repo.getSessionContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name1, r.size(), r.time, state); | |
| 149 } | 106 } |
| 150 } | 107 return true; |
| 151 } catch (IOException ex) { | 108 } |
| 152 throw new HgInvalidControlFileException("Dirstate read failed", ex, dirstateFile); | 109 }); |
| 153 } finally { | 110 parents = dirstateReader.parents(); |
| 154 da.done(); | 111 } |
| 155 } | 112 |
| 156 } | |
| 157 | |
| 158 private static Pair<Nodeid, Nodeid> internalReadParents(DataAccess da) throws IOException { | |
| 159 byte[] parents = new byte[40]; | |
| 160 da.readBytes(parents, 0, 40); | |
| 161 Nodeid n1 = Nodeid.fromBinary(parents, 0); | |
| 162 Nodeid n2 = Nodeid.fromBinary(parents, 20); | |
| 163 parents = null; | |
| 164 return new Pair<Nodeid, Nodeid>(n1, n2); | |
| 165 } | |
| 166 | |
| 167 /** | 113 /** |
| 168 * @return pair of working copy parents, with {@link Nodeid#NULL} for missing values. | 114 * @return pair of working copy parents, with {@link Nodeid#NULL} for missing values. |
| 169 */ | 115 */ |
| 170 public Pair<Nodeid,Nodeid> parents() { | 116 public Pair<Nodeid,Nodeid> parents() { |
| 171 assert parents != null; // instance not initialized with #read() | 117 assert parents != null; // instance not initialized with #read() |
| 172 return parents; | 118 return parents; |
| 173 } | 119 } |
| 174 | 120 |
| 175 private static File getDirstateFile(Internals repo) { | |
| 176 return repo.getFileFromRepoDir(Dirstate.getName()); | |
| 177 } | |
| 178 | |
| 179 /** | |
| 180 * @return pair of parents, both {@link Nodeid#NULL} if dirstate is not available | |
| 181 */ | |
| 182 /*package-local*/ static Pair<Nodeid, Nodeid> readParents(Internals internalRepo) throws HgInvalidControlFileException { | |
| 183 // do not read whole dirstate if all we need is WC parent information | |
| 184 File dirstateFile = getDirstateFile(internalRepo); | |
| 185 if (dirstateFile == null || !dirstateFile.exists()) { | |
| 186 return new Pair<Nodeid,Nodeid>(NULL, NULL); | |
| 187 } | |
| 188 DataAccess da = internalRepo.getDataAccess().create(dirstateFile); | |
| 189 try { | |
| 190 if (da.isEmpty()) { | |
| 191 return new Pair<Nodeid,Nodeid>(NULL, NULL); | |
| 192 } | |
| 193 return internalReadParents(da); | |
| 194 } catch (IOException ex) { | |
| 195 throw new HgInvalidControlFileException("Error reading working copy parents from dirstate", ex, dirstateFile); | |
| 196 } finally { | |
| 197 da.done(); | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 /** | |
| 202 * TODO [post-1.0] it's really not a proper place for the method, need WorkingCopyContainer or similar | |
| 203 * @return branch associated with the working directory | |
| 204 */ | |
| 205 /*package-local*/ static String readBranch(Internals internalRepo) throws HgInvalidControlFileException { | |
| 206 File branchFile = internalRepo.getFileFromRepoDir("branch"); | |
| 207 String branch = HgRepository.DEFAULT_BRANCH_NAME; | |
| 208 if (branchFile.exists()) { | |
| 209 try { | |
| 210 BufferedReader r = new BufferedReader(new FileReader(branchFile)); | |
| 211 String b = r.readLine(); | |
| 212 if (b != null) { | |
| 213 b = b.trim().intern(); | |
| 214 } | |
| 215 branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b; | |
| 216 r.close(); | |
| 217 } catch (FileNotFoundException ex) { | |
| 218 internalRepo.getSessionContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here | |
| 219 // IGNORE | |
| 220 } catch (IOException ex) { | |
| 221 throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile); | |
| 222 } | |
| 223 } | |
| 224 return branch; | |
| 225 } | |
| 226 | |
| 227 // new, modifiable collection | 121 // new, modifiable collection |
| 228 /*package-local*/ TreeSet<Path> all() { | 122 /*package-local*/ TreeSet<Path> all() { |
| 229 assert normal != null; | 123 assert normal != null; |
| 230 TreeSet<Path> rv = new TreeSet<Path>(); | 124 TreeSet<Path> rv = new TreeSet<Path>(); |
| 231 @SuppressWarnings("unchecked") | 125 @SuppressWarnings("unchecked") |
