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")