Mercurial > hg4j
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) { |