comparison src/org/tmatesoft/hg/repo/HgRepository.java @ 490:b3c16d1aede0

Refactoring: move HgRepository's implementation aspects to Internals (which is now its imlementation counterpart and primary repository class to be used by other parts of the library)
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 16 Aug 2012 17:08:34 +0200
parents 45b3b6ca046f
children 4a670f76e7d1
comparison
equal deleted inserted replaced
489:9c0138cda59a 490:b3c16d1aede0
32 32
33 import org.tmatesoft.hg.core.Nodeid; 33 import org.tmatesoft.hg.core.Nodeid;
34 import org.tmatesoft.hg.core.SessionContext; 34 import org.tmatesoft.hg.core.SessionContext;
35 import org.tmatesoft.hg.internal.ByteArrayChannel; 35 import org.tmatesoft.hg.internal.ByteArrayChannel;
36 import org.tmatesoft.hg.internal.ConfigFile; 36 import org.tmatesoft.hg.internal.ConfigFile;
37 import org.tmatesoft.hg.internal.DataAccessProvider;
38 import org.tmatesoft.hg.internal.Experimental; 37 import org.tmatesoft.hg.internal.Experimental;
39 import org.tmatesoft.hg.internal.Filter; 38 import org.tmatesoft.hg.internal.Filter;
40 import org.tmatesoft.hg.internal.Internals; 39 import org.tmatesoft.hg.internal.Internals;
41 import org.tmatesoft.hg.internal.RevlogStream; 40 import org.tmatesoft.hg.internal.RevlogStream;
42 import org.tmatesoft.hg.internal.SubrepoManager; 41 import org.tmatesoft.hg.internal.SubrepoManager;
52 * Shall be as state-less as possible, all the caching happens outside the repo, in commands/walkers 51 * Shall be as state-less as possible, all the caching happens outside the repo, in commands/walkers
53 * 52 *
54 * @author Artem Tikhomirov 53 * @author Artem Tikhomirov
55 * @author TMate Software Ltd. 54 * @author TMate Software Ltd.
56 */ 55 */
57 public final class HgRepository { 56 public final class HgRepository implements SessionContext.Source {
58 57
59 // IMPORTANT: if new constants added, consider fixing HgInternals#wrongRevisionIndex and HgInvalidRevisionException#getMessage 58 // IMPORTANT: if new constants added, consider fixing HgInternals#wrongRevisionIndex and HgInvalidRevisionException#getMessage
60 59
61 /** 60 /**
62 * Revision index constant to indicate most recent revision 61 * Revision index constant to indicate most recent revision
98 } 97 }
99 98
100 private final File repoDir; // .hg folder 99 private final File repoDir; // .hg folder
101 private final File workingDir; // .hg/../ 100 private final File workingDir; // .hg/../
102 private final String repoLocation; 101 private final String repoLocation;
103 private final DataAccessProvider dataAccess;
104 private final PathRewrite normalizePath; // normalized slashes but otherwise regular file names 102 private final PathRewrite normalizePath; // normalized slashes but otherwise regular file names
105 private final PathRewrite dataPathHelper; // access to file storage area (usually under .hg/store/data/), with filenames mangled 103 private final PathRewrite dataPathHelper; // access to file storage area (usually under .hg/store/data/), with filenames mangled
106 private final PathRewrite repoPathHelper; // access to system files 104 private final PathRewrite repoPathHelper; // access to system files
107 private final SessionContext sessionContext; 105 private final SessionContext sessionContext;
108 106
131 129
132 HgRepository(String repositoryPath) { 130 HgRepository(String repositoryPath) {
133 repoDir = null; 131 repoDir = null;
134 workingDir = null; 132 workingDir = null;
135 repoLocation = repositoryPath; 133 repoLocation = repositoryPath;
136 dataAccess = null;
137 dataPathHelper = repoPathHelper = null; 134 dataPathHelper = repoPathHelper = null;
138 normalizePath = null; 135 normalizePath = null;
139 sessionContext = null; 136 sessionContext = null;
140 impl = null; 137 impl = null;
141 } 138 }
151 repoDir = repositoryRoot; 148 repoDir = repositoryRoot;
152 workingDir = repoDir.getParentFile(); 149 workingDir = repoDir.getParentFile();
153 if (workingDir == null) { 150 if (workingDir == null) {
154 throw new IllegalArgumentException(repoDir.toString()); 151 throw new IllegalArgumentException(repoDir.toString());
155 } 152 }
156 impl = new org.tmatesoft.hg.internal.Internals(ctx);
157 repoLocation = repositoryPath; 153 repoLocation = repositoryPath;
158 sessionContext = ctx; 154 sessionContext = ctx;
159 dataAccess = new DataAccessProvider(ctx); 155 impl = new org.tmatesoft.hg.internal.Internals(this, repositoryRoot);
160 impl.parseRequires(this, new File(repoDir, "requires")); 156 impl.parseRequires();
161 normalizePath = impl.buildNormalizePathRewrite(); 157 normalizePath = impl.buildNormalizePathRewrite();
162 dataPathHelper = impl.buildDataFilesHelper(); 158 dataPathHelper = impl.buildDataFilesHelper();
163 repoPathHelper = impl.buildRepositoryFilesHelper(); 159 repoPathHelper = impl.buildRepositoryFilesHelper();
164 } 160 }
165 161
171 public String getLocation() { 167 public String getLocation() {
172 return repoLocation; 168 return repoLocation;
173 } 169 }
174 170
175 public boolean isInvalid() { 171 public boolean isInvalid() {
176 return repoDir == null || !repoDir.exists() || !repoDir.isDirectory(); 172 return impl == null || impl.isInvalid();
177 } 173 }
178 174
179 public HgChangelog getChangelog() { 175 public HgChangelog getChangelog() {
180 if (changelog == null) { 176 if (changelog == null) {
181 CharSequence storagePath = repoPathHelper.rewrite("00changelog.i"); 177 CharSequence storagePath = repoPathHelper.rewrite("00changelog.i");
208 hgTags.content(i, sink); 204 hgTags.content(i, sink);
209 final String content = new String(sink.toArray(), "UTF8"); 205 final String content = new String(sink.toArray(), "UTF8");
210 tags.readGlobal(new StringReader(content)); 206 tags.readGlobal(new StringReader(content));
211 } catch (CancelledException ex) { 207 } catch (CancelledException ex) {
212 // IGNORE, can't happen, we did not configure cancellation 208 // IGNORE, can't happen, we did not configure cancellation
213 getContext().getLog().dump(getClass(), Debug, ex, null); 209 getSessionContext().getLog().dump(getClass(), Debug, ex, null);
214 } catch (IOException ex) { 210 } catch (IOException ex) {
215 // UnsupportedEncodingException can't happen (UTF8) 211 // UnsupportedEncodingException can't happen (UTF8)
216 // only from readGlobal. Need to reconsider exceptions thrown from there: 212 // only from readGlobal. Need to reconsider exceptions thrown from there:
217 // BufferedReader wraps String and unlikely to throw IOException, perhaps, log is enough? 213 // BufferedReader wraps String and unlikely to throw IOException, perhaps, log is enough?
218 getContext().getLog().dump(getClass(), Error, ex, null); 214 getSessionContext().getLog().dump(getClass(), Error, ex, null);
219 // XXX need to decide what to do this. failure to read single revision shall not break complete cycle 215 // XXX need to decide what to do this. failure to read single revision shall not break complete cycle
220 } 216 }
221 } 217 }
222 } 218 }
223 File file2read = null; 219 File file2read = null;
224 try { 220 try {
225 file2read = new File(getWorkingDir(), HgTags.getPath()); 221 file2read = new File(getWorkingDir(), HgTags.getPath());
226 tags.readGlobal(file2read); // XXX replace with HgDataFile.workingCopy 222 tags.readGlobal(file2read); // XXX replace with HgDataFile.workingCopy
227 file2read = new File(repoDir, HgLocalTags.getName()); 223 file2read = impl.getFileFromRepoDir(HgLocalTags.getName()); // XXX pass internalrepo to readLocal, keep filename there
228 tags.readLocal(file2read); 224 tags.readLocal(file2read);
229 } catch (IOException ex) { 225 } catch (IOException ex) {
230 getContext().getLog().dump(getClass(), Error, ex, null); 226 getSessionContext().getLog().dump(getClass(), Error, ex, null);
231 throw new HgInvalidControlFileException("Failed to read tags", ex, file2read); 227 throw new HgInvalidControlFileException("Failed to read tags", ex, file2read);
232 } 228 }
233 } 229 }
234 return tags; 230 return tags;
235 } 231 }
239 * @return branch manager instance, never <code>null</code> 235 * @return branch manager instance, never <code>null</code>
240 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 236 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
241 */ 237 */
242 public HgBranches getBranches() throws HgInvalidControlFileException { 238 public HgBranches getBranches() throws HgInvalidControlFileException {
243 if (branches == null) { 239 if (branches == null) {
244 branches = new HgBranches(this); 240 branches = new HgBranches(impl);
245 branches.collect(ProgressSupport.Factory.get(null)); 241 branches.collect(ProgressSupport.Factory.get(null));
246 } 242 }
247 return branches; 243 return branches;
248 } 244 }
249 245
251 * Access state of the recent merge 247 * Access state of the recent merge
252 * @return merge state facility, never <code>null</code> 248 * @return merge state facility, never <code>null</code>
253 */ 249 */
254 public HgMergeState getMergeState() { 250 public HgMergeState getMergeState() {
255 if (mergeState == null) { 251 if (mergeState == null) {
256 mergeState = new HgMergeState(this); 252 mergeState = new HgMergeState(impl);
257 } 253 }
258 return mergeState; 254 return mergeState;
259 } 255 }
260 256
261 public HgDataFile getFileNode(String path) { 257 public HgDataFile getFileNode(String path) {
287 /** 283 /**
288 * @return pair of values, {@link Pair#first()} and {@link Pair#second()} are respective parents, never <code>null</code>. 284 * @return pair of values, {@link Pair#first()} and {@link Pair#second()} are respective parents, never <code>null</code>.
289 * @throws HgInvalidControlFileException if attempt to read information about working copy parents from dirstate failed 285 * @throws HgInvalidControlFileException if attempt to read information about working copy parents from dirstate failed
290 */ 286 */
291 public Pair<Nodeid,Nodeid> getWorkingCopyParents() throws HgInvalidControlFileException { 287 public Pair<Nodeid,Nodeid> getWorkingCopyParents() throws HgInvalidControlFileException {
292 return HgDirstate.readParents(this, new File(repoDir, Dirstate.getName())); 288 return HgDirstate.readParents(impl);
293 } 289 }
294 290
295 /** 291 /**
296 * @return name of the branch associated with working directory, never <code>null</code>. 292 * @return name of the branch associated with working directory, never <code>null</code>.
297 * @throws HgInvalidControlFileException if attempt to read branch name failed. 293 * @throws HgInvalidControlFileException if attempt to read branch name failed.
298 */ 294 */
299 public String getWorkingCopyBranchName() throws HgInvalidControlFileException { 295 public String getWorkingCopyBranchName() throws HgInvalidControlFileException {
300 if (wcBranch == null) { 296 if (wcBranch == null) {
301 wcBranch = HgDirstate.readBranch(this, new File(repoDir, "branch")); 297 wcBranch = HgDirstate.readBranch(impl);
302 } 298 }
303 return wcBranch; 299 return wcBranch;
304 } 300 }
305 301
306 /** 302 /**
326 322
327 323
328 public HgRepoConfig getConfiguration() /* XXX throws HgInvalidControlFileException? Description of the exception suggests it is only for files under ./hg/*/ { 324 public HgRepoConfig getConfiguration() /* XXX throws HgInvalidControlFileException? Description of the exception suggests it is only for files under ./hg/*/ {
329 if (repoConfig == null) { 325 if (repoConfig == null) {
330 try { 326 try {
331 ConfigFile configFile = impl.readConfiguration(this, getRepositoryRoot()); 327 ConfigFile configFile = impl.readConfiguration();
332 repoConfig = new HgRepoConfig(configFile); 328 repoConfig = new HgRepoConfig(configFile);
333 } catch (IOException ex) { 329 } catch (IOException ex) {
334 String m = "Errors while reading user configuration file"; 330 String m = "Errors while reading user configuration file";
335 getContext().getLog().dump(getClass(), Warn, ex, m); 331 getSessionContext().getLog().dump(getClass(), Warn, ex, m);
336 return new HgRepoConfig(new ConfigFile(getContext())); // empty config, do not cache, allow to try once again 332 return new HgRepoConfig(new ConfigFile(getSessionContext())); // empty config, do not cache, allow to try once again
337 //throw new HgInvalidControlFileException(m, ex, null); 333 //throw new HgInvalidControlFileException(m, ex, null);
338 } 334 }
339 } 335 }
340 return repoConfig; 336 return repoConfig;
341 } 337 }
360 public CharSequence rewrite(CharSequence path) { 356 public CharSequence rewrite(CharSequence path) {
361 return path.toString().toLowerCase(); 357 return path.toString().toLowerCase();
362 } 358 }
363 }; 359 };
364 } 360 }
365 File dirstateFile = new File(repoDir, Dirstate.getName()); 361 HgDirstate ds = new HgDirstate(impl, pathFactory, canonicalPath);
366 HgDirstate ds = new HgDirstate(this, dirstateFile, pathFactory, canonicalPath); 362 ds.read();
367 ds.read(impl.buildFileNameEncodingHelper());
368 return ds; 363 return ds;
369 } 364 }
370 365
371 /** 366 /**
372 * Access to configured set of ignored files. 367 * Access to configured set of ignored files.
379 ignore = new HgIgnore(getToRepoPathHelper()); 374 ignore = new HgIgnore(getToRepoPathHelper());
380 File ignoreFile = new File(getWorkingDir(), HgIgnore.getPath()); 375 File ignoreFile = new File(getWorkingDir(), HgIgnore.getPath());
381 try { 376 try {
382 final List<String> errors = ignore.read(ignoreFile); 377 final List<String> errors = ignore.read(ignoreFile);
383 if (errors != null) { 378 if (errors != null) {
384 getContext().getLog().dump(getClass(), Warn, "Syntax errors parsing %s:\n%s", ignoreFile.getName(), Internals.join(errors, ",\n")); 379 getSessionContext().getLog().dump(getClass(), Warn, "Syntax errors parsing %s:\n%s", ignoreFile.getName(), Internals.join(errors, ",\n"));
385 } 380 }
386 } catch (IOException ex) { 381 } catch (IOException ex) {
387 final String m = String.format("Error reading %s file", ignoreFile); 382 final String m = String.format("Error reading %s file", ignoreFile);
388 throw new HgInvalidControlFileException(m, ex, ignoreFile); 383 throw new HgInvalidControlFileException(m, ex, ignoreFile);
389 } 384 }
397 * 392 *
398 * @return message used for last commit attempt, or <code>null</code> if none 393 * @return message used for last commit attempt, or <code>null</code> if none
399 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 394 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
400 */ 395 */
401 public String getCommitLastMessage() throws HgInvalidControlFileException { 396 public String getCommitLastMessage() throws HgInvalidControlFileException {
402 File lastMessage = new File(repoDir, LastMessage.getPath()); 397 File lastMessage = impl.getFileFromRepoDir(LastMessage.getPath());
403 if (!lastMessage.canRead()) { 398 if (!lastMessage.canRead()) {
404 return null; 399 return null;
405 } 400 }
406 FileReader fr = null; 401 FileReader fr = null;
407 try { 402 try {
414 } finally { 409 } finally {
415 if (fr != null) { 410 if (fr != null) {
416 try { 411 try {
417 fr.close(); 412 fr.close();
418 } catch (IOException ex) { 413 } catch (IOException ex) {
419 getContext().getLog().dump(getClass(), Warn, "Failed to close %s after read", lastMessage); 414 getSessionContext().getLog().dump(getClass(), Warn, "Failed to close %s after read", lastMessage);
420 } 415 }
421 } 416 }
422 } 417 }
423 } 418 }
424 419
437 */ 432 */
438 @Experimental(reason="WORK IN PROGRESS") 433 @Experimental(reason="WORK IN PROGRESS")
439 public HgRepositoryLock getWorkingDirLock() { 434 public HgRepositoryLock getWorkingDirLock() {
440 if (wdLock == null) { 435 if (wdLock == null) {
441 int timeout = getLockTimeout(); 436 int timeout = getLockTimeout();
442 File lf = new File(getRepositoryRoot(), "wlock"); 437 File lf = impl.getFileFromRepoDir("wlock");
443 synchronized (this) { 438 synchronized (this) {
444 if (wdLock == null) { 439 if (wdLock == null) {
445 wdLock = new HgRepositoryLock(lf, timeout); 440 wdLock = new HgRepositoryLock(lf, timeout);
446 } 441 }
447 } 442 }
451 446
452 @Experimental(reason="WORK IN PROGRESS") 447 @Experimental(reason="WORK IN PROGRESS")
453 public HgRepositoryLock getStoreLock() { 448 public HgRepositoryLock getStoreLock() {
454 if (storeLock == null) { 449 if (storeLock == null) {
455 int timeout = getLockTimeout(); 450 int timeout = getLockTimeout();
456 File fl = new File(getRepositoryRoot(), repoPathHelper.rewrite("lock").toString()); 451 File fl = impl.getFileFromRepoDir(repoPathHelper.rewrite("lock").toString());
457 synchronized (this) { 452 synchronized (this) {
458 if (storeLock == null) { 453 if (storeLock == null) {
459 storeLock = new HgRepositoryLock(fl, timeout); 454 storeLock = new HgRepositoryLock(fl, timeout);
460 } 455 }
461 } 456 }
468 * @return facility to manage bookmarks, never <code>null</code> 463 * @return facility to manage bookmarks, never <code>null</code>
469 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 464 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
470 */ 465 */
471 public HgBookmarks getBookmarks() throws HgInvalidControlFileException { 466 public HgBookmarks getBookmarks() throws HgInvalidControlFileException {
472 if (bookmarks == null) { 467 if (bookmarks == null) {
473 bookmarks = new HgBookmarks(this); 468 bookmarks = new HgBookmarks(impl);
474 bookmarks.read(); 469 bookmarks.read();
475 } 470 }
476 return bookmarks; 471 return bookmarks;
477 } 472 }
478 473
479 /*package-local*/ DataAccessProvider getDataAccess() { 474 /**
480 return dataAccess; 475 * @return session environment of the repository
476 */
477 public SessionContext getSessionContext() {
478 return sessionContext;
481 } 479 }
482 480
483 /** 481 /**
484 * Perhaps, should be separate interface, like ContentLookup 482 * Perhaps, should be separate interface, like ContentLookup
485 * path - repository storage path (i.e. one usually with .i or .d) 483 * path - repository storage path (i.e. one usually with .i or .d)
490 if (cached != null) { 488 if (cached != null) {
491 return cached; 489 return cached;
492 } 490 }
493 File f = new File(repoDir, path.toString()); 491 File f = new File(repoDir, path.toString());
494 if (f.exists()) { 492 if (f.exists()) {
495 RevlogStream s = new RevlogStream(dataAccess, f); 493 RevlogStream s = new RevlogStream(impl.getDataAccess(), f);
496 if (impl.shallCacheRevlogs()) { 494 if (impl.shallCacheRevlogs()) {
497 streamsCache.put(path, new SoftReference<RevlogStream>(s)); 495 streamsCache.put(path, new SoftReference<RevlogStream>(s));
498 } 496 }
499 return s; 497 return s;
500 } else { 498 } else {
501 if (shallFakeNonExistent) { 499 if (shallFakeNonExistent) {
502 try { 500 try {
503 File fake = File.createTempFile(f.getName(), null); 501 File fake = File.createTempFile(f.getName(), null);
504 fake.deleteOnExit(); 502 fake.deleteOnExit();
505 return new RevlogStream(dataAccess, fake); 503 return new RevlogStream(impl.getDataAccess(), fake);
506 } catch (IOException ex) { 504 } catch (IOException ex) {
507 getContext().getLog().dump(getClass(), Info, ex, null); 505 getSessionContext().getLog().dump(getClass(), Info, ex, null);
508 } 506 }
509 } 507 }
510 } 508 }
511 return null; // XXX empty stream instead? 509 return null; // XXX empty stream instead?
512 } 510 }
521 519
522 /*package-local*/ File getFile(HgDataFile dataFile) { 520 /*package-local*/ File getFile(HgDataFile dataFile) {
523 return new File(getWorkingDir(), dataFile.getPath().toString()); 521 return new File(getWorkingDir(), dataFile.getPath().toString());
524 } 522 }
525 523
526 /*package-local*/ SessionContext getContext() {
527 return sessionContext;
528 }
529
530 /*package-local*/ Internals getImplHelper() { 524 /*package-local*/ Internals getImplHelper() {
531 return impl; 525 return impl;
532 } 526 }
533 527
534 private List<Filter> instantiateFilters(Path p, Filter.Options opts) { 528 private List<Filter> instantiateFilters(Path p, Filter.Options opts) {
535 List<Filter.Factory> factories = impl.getFilters(this); 529 List<Filter.Factory> factories = impl.getFilters();
536 if (factories.isEmpty()) { 530 if (factories.isEmpty()) {
537 return Collections.emptyList(); 531 return Collections.emptyList();
538 } 532 }
539 ArrayList<Filter> rv = new ArrayList<Filter>(factories.size()); 533 ArrayList<Filter> rv = new ArrayList<Filter>(factories.size());
540 for (Filter.Factory ff : factories) { 534 for (Filter.Factory ff : factories) {