# HG changeset patch # User Artem Tikhomirov # Date 1368126408 -7200 # Node ID 5c68567b36451cbf114e8f799f809448746dee95 # Parent e4a71afd3c71a74f8c5d69517c251ccdc819c743 Refresh tags, branches, bookmarks and ignore when their files (or csets in the repo) are changed diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/internal/ChangelogMonitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/ChangelogMonitor.java Thu May 09 21:06:48 2013 +0200 @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.internal; + +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.repo.HgRepository; + +/** + * Track changes to a repository based on recent changelog revision. + * TODO shall be merged with {@link RevlogChangeMonitor} and {@link FileChangeMonitor} into + * a single facility available from {@link SessionContext} + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class ChangelogMonitor { + private final HgRepository repo; + private int changelogRevCount = -1; + private Nodeid changelogLastRev = null; + + public ChangelogMonitor(HgRepository hgRepo) { + repo = hgRepo; + } + + // memorize the state of the repository's changelog + public void touch() { + changelogRevCount = repo.getChangelog().getRevisionCount(); + changelogLastRev = safeGetRevision(changelogRevCount-1); + } + + // if present state doesn't match the one we remember + public boolean isChanged() { + int rc = repo.getChangelog().getRevisionCount(); + if (rc != changelogRevCount) { + return true; + } + Nodeid r = safeGetRevision(rc-1); + return !r.equals(changelogLastRev); + } + + // handles empty repository case + private Nodeid safeGetRevision(int revIndex) { + if (revIndex >= 0) { + return repo.getChangelog().getRevision(revIndex); + } + return Nodeid.NULL; + } +} diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/internal/CommitFacility.java --- a/src/org/tmatesoft/hg/internal/CommitFacility.java Wed May 08 17:11:45 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/CommitFacility.java Thu May 09 21:06:48 2013 +0200 @@ -192,7 +192,7 @@ fncache.write(); } catch (IOException ex) { // see comment above for fnchache.read() - repo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Failed to write fncache, error ignored"); + repo.getLog().dump(getClass(), Severity.Error, ex, "Failed to write fncache, error ignored"); } } // bring dirstate up to commit state @@ -210,6 +210,9 @@ if (p1Commit != NO_REVISION || p2Commit != NO_REVISION) { repo.getRepo().getBookmarks().updateActive(p1Cset, p2Cset, changesetRev); } + // TODO Revisit: might be reasonable to send out a "Repo changed" notification, to clear + // e.g. cached branch, tags and so on, not to rely on file change detection methods? + // The same notificaion might come useful once Pull is implemented return changesetRev; } /* diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/internal/DirstateReader.java --- a/src/org/tmatesoft/hg/internal/DirstateReader.java Wed May 08 17:11:45 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/DirstateReader.java Thu May 09 21:06:48 2013 +0200 @@ -102,7 +102,7 @@ } else if (state == 'm') { target.next(EntryKind.Merged, r); } else { - repo.getSessionContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name(), r.size(), r.modificationTime(), state); + repo.getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name(), r.size(), r.modificationTime(), state); } } } catch (IOException ex) { @@ -178,7 +178,7 @@ branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b; r.close(); } catch (FileNotFoundException ex) { - internalRepo.getSessionContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here + internalRepo.getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here // IGNORE } catch (IOException ex) { throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile); diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/internal/FileChangeMonitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/FileChangeMonitor.java Thu May 09 21:06:48 2013 +0200 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.internal; + +import java.io.File; + +/** + * This shall become interface/abstract class accessible from SessionContext, + * with plugable implementations, e.g. Java7 (file monitoring facilities) based, + * or any other convenient means. It shall allow both "check at the moment asked" + * and "collect changes and dispatch on demand" implementation approaches, so that + * implementors may use best available technology + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class FileChangeMonitor { + private final File file; + private long lastModified; + private long length; + + /** + * First round: support for 1-monitor-1-file only + * Next round: 1-monitor-N files + */ + public FileChangeMonitor(File f) { + file = f; + } + + // shall work for files that do not exist + public void touch(Object source) { + lastModified = file.lastModified(); + length = file.length(); + } + + public void check(Object source, Action onChange) { + if (changed(source)) { + onChange.changed(); + } + } + + public boolean changed(Object source) { + if (file.lastModified() != lastModified) { + return true; + } + return file.length() != length; + } + + public interface Action { + public void changed(); + } +} diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/internal/Internals.java --- a/src/org/tmatesoft/hg/internal/Internals.java Wed May 08 17:11:45 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/Internals.java Thu May 09 21:06:48 2013 +0200 @@ -38,6 +38,7 @@ import org.tmatesoft.hg.repo.HgRepositoryFiles; import org.tmatesoft.hg.repo.HgRepositoryLock; import org.tmatesoft.hg.repo.HgRuntimeException; +import org.tmatesoft.hg.util.LogFacility; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathRewrite; @@ -150,7 +151,7 @@ } public File getRepositoryFile(HgRepositoryFiles f) { - return f.residesUnderRepositoryRoot() ? getFileFromRepoDir(f.getName()) : getFileFromDataDir(f.getName()); + return f.residesUnderRepositoryRoot() ? getFileFromRepoDir(f.getName()) : new File(repo.getWorkingDir(), f.getName()); } /** @@ -189,6 +190,10 @@ return repo.getSessionContext(); } + public LogFacility getLog() { + return getSessionContext().getLog(); + } + public HgRepository getRepo() { return repo; } diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/repo/HgBookmarks.java --- a/src/org/tmatesoft/hg/repo/HgBookmarks.java Wed May 08 17:11:45 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgBookmarks.java Thu May 09 21:06:48 2013 +0200 @@ -31,31 +31,38 @@ import org.tmatesoft.hg.core.HgRepositoryLockException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.Experimental; +import org.tmatesoft.hg.internal.FileChangeMonitor; import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.LineReader; import org.tmatesoft.hg.util.LogFacility; /** + * Access to bookmarks state * * @see http://mercurial.selenic.com/wiki/Bookmarks * @author Artem Tikhomirov * @author TMate Software Ltd. */ public final class HgBookmarks { - private final Internals internalRepo; + private final Internals repo; private Map bookmarks = Collections.emptyMap(); - private String activeBookmark; + private String activeBookmark; + private FileChangeMonitor activeTracker, bmFileTracker; HgBookmarks(Internals internals) { - internalRepo = internals; + repo = internals; } /*package-local*/ void read() throws HgInvalidControlFileException { - final LogFacility log = internalRepo.getSessionContext().getLog(); - final HgRepository repo = internalRepo.getRepo(); - File all = internalRepo.getFileFromRepoDir(HgRepositoryFiles.Bookmarks.getName()); + readBookmarks(); + readActiveBookmark(); + } + + private void readBookmarks() throws HgInvalidControlFileException { + final LogFacility log = repo.getLog(); + File all = repo.getRepositoryFile(HgRepositoryFiles.Bookmarks); LinkedHashMap bm = new LinkedHashMap(); - if (all.canRead()) { + if (all.canRead() && all.isFile()) { LineReader lr1 = new LineReader(all, log); ArrayList c = new ArrayList(); lr1.read(new LineReader.SimpleLineCollector(), c); @@ -65,7 +72,7 @@ if (x > 0) { Nodeid nid = Nodeid.fromAscii(s.substring(0, x)); String name = new String(s.substring(x+1)); - if (repo.getChangelog().isKnown(nid)) { + if (repo.getRepo().getChangelog().isKnown(nid)) { // copy name part not to drag complete line bm.put(name, nid); } else { @@ -82,18 +89,40 @@ } else { bookmarks = Collections.emptyMap(); } + if (bmFileTracker == null) { + bmFileTracker = new FileChangeMonitor(all); + } + bmFileTracker.touch(this); + } + private void readActiveBookmark() throws HgInvalidControlFileException { activeBookmark = null; - File active = internalRepo.getFileFromRepoDir(HgRepositoryFiles.BookmarksCurrent.getName()); - if (active.canRead()) { - LineReader lr2 = new LineReader(active, log); + File active = repo.getRepositoryFile(HgRepositoryFiles.BookmarksCurrent); + if (active.canRead() && active.isFile()) { + LineReader lr2 = new LineReader(active, repo.getLog()); ArrayList c = new ArrayList(2); lr2.read(new LineReader.SimpleLineCollector(), c); if (c.size() > 0) { activeBookmark = c.get(0); } } + if (activeTracker == null) { + activeTracker = new FileChangeMonitor(active); + } + activeTracker.touch(this); } + + /*package-local*/void reloadIfChanged() throws HgInvalidControlFileException { + assert activeTracker != null; + assert bmFileTracker != null; + if (bmFileTracker.changed(this)) { + readBookmarks(); + } + if (activeTracker.changed(this)) { + readActiveBookmark(); + } + } + /** * Tell name of the active bookmark @@ -155,8 +184,8 @@ } private void write() throws HgIOException, HgRepositoryLockException { - File bookmarksFile = internalRepo.getRepositoryFile(HgRepositoryFiles.Bookmarks); - HgRepositoryLock workingDirLock = internalRepo.getRepo().getWorkingDirLock(); + File bookmarksFile = repo.getRepositoryFile(HgRepositoryFiles.Bookmarks); + HgRepositoryLock workingDirLock = repo.getRepo().getWorkingDirLock(); FileWriter fileWriter = null; workingDirLock.acquire(); try { @@ -174,7 +203,7 @@ fileWriter.close(); } } catch (IOException ex) { - internalRepo.getSessionContext().getLog().dump(getClass(), Error, ex, null); + repo.getLog().dump(getClass(), Error, ex, null); } workingDirLock.release(); } diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/repo/HgBranches.java --- a/src/org/tmatesoft/hg/repo/HgBranches.java Wed May 08 17:11:45 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgBranches.java Thu May 09 21:06:48 2013 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2012 TMate Software Ltd + * Copyright (c) 2011-2013 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,6 +38,7 @@ import java.util.regex.Pattern; import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.internal.ChangelogMonitor; import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; @@ -50,17 +51,18 @@ */ public class HgBranches { + private final Internals internalRepo; + private final ChangelogMonitor repoChangeTracker; private final Map branches = new TreeMap(); - private final HgRepository repo; - private final Internals internalRepo; private boolean isCacheActual = false; HgBranches(Internals internals) { internalRepo = internals; - repo = internals.getRepo(); // merely a cached value + repoChangeTracker = new ChangelogMonitor(internals.getRepo()); } private int readCache() { + final HgRepository repo = internalRepo.getRepo(); File branchheadsCache = getCacheFile(); int lastInCache = -1; if (!branchheadsCache.canRead()) { @@ -130,6 +132,7 @@ void collect(final ProgressSupport ps) throws HgInvalidControlFileException { branches.clear(); + final HgRepository repo = internalRepo.getRepo(); ps.start(1 + repo.getChangelog().getRevisionCount() * 2); // int lastCached = readCache(); @@ -227,15 +230,16 @@ for (BranchInfo bi : branches.values()) { bi.validate(clog, rmap); } + repoChangeTracker.touch(); ps.done(); } - public List getAllBranches() { + public List getAllBranches() throws HgInvalidControlFileException { return new LinkedList(branches.values()); } - public BranchInfo getBranch(String name) { + public BranchInfo getBranch(String name) throws HgInvalidControlFileException { return branches.get(name); } @@ -258,6 +262,7 @@ if (!branchheadsCache.canWrite()) { return; } + final HgRepository repo = internalRepo.getRepo(); final int lastRev = repo.getChangelog().getLastRevision(); final Nodeid lastNid = repo.getChangelog().getRevision(lastRev); BufferedWriter bw = new BufferedWriter(new FileWriter(branchheadsCache)); @@ -280,6 +285,12 @@ // prior to 1.8 used to be .hg/branchheads.cache return internalRepo.getFileFromRepoDir("cache/branchheads"); } + + /*package-local*/ void reloadIfChanged(ProgressSupport ps) throws HgInvalidControlFileException { + if (repoChangeTracker.isChanged()) { + collect(ps); + } + } public static class BranchInfo { private final String name; diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/repo/HgIgnore.java --- a/src/org/tmatesoft/hg/repo/HgIgnore.java Wed May 08 17:11:45 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgIgnore.java Thu May 09 21:06:48 2013 +0200 @@ -16,6 +16,9 @@ */ package org.tmatesoft.hg.repo; +import static org.tmatesoft.hg.repo.HgRepositoryFiles.HgIgnore; +import static org.tmatesoft.hg.util.LogFacility.Severity.Warn; + import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -26,6 +29,8 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import org.tmatesoft.hg.internal.FileChangeMonitor; +import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathRewrite; @@ -39,21 +44,47 @@ private List entries; private final PathRewrite globPathHelper; + private FileChangeMonitor ignoreFileTracker; HgIgnore(PathRewrite globPathRewrite) { entries = Collections.emptyList(); globPathHelper = globPathRewrite; } - /* package-local */ List read(File hgignoreFile) throws IOException { - if (!hgignoreFile.exists()) { - return null; + /* package-local */ void read(Internals repo) throws HgInvalidControlFileException { + File ignoreFile = repo.getRepositoryFile(HgIgnore); + BufferedReader fr = null; + try { + if (ignoreFile.canRead() && ignoreFile.isFile()) { + fr = new BufferedReader(new FileReader(ignoreFile)); + final List errors = read(fr); + if (errors != null) { + repo.getLog().dump(getClass(), Warn, "Syntax errors parsing %s:\n%s", ignoreFile.getName(), Internals.join(errors, ",\n")); + } + } + if (ignoreFileTracker == null) { + ignoreFileTracker = new FileChangeMonitor(ignoreFile); + } + ignoreFileTracker.touch(this); + } catch (IOException ex) { + final String m = String.format("Error reading %s file", ignoreFile); + throw new HgInvalidControlFileException(m, ex, ignoreFile); + } finally { + try { + if (fr != null) { + fr.close(); + } + } catch (IOException ex) { + repo.getLog().dump(getClass(), Warn, ex, null); // it's read, don't treat as error + } } - BufferedReader fr = new BufferedReader(new FileReader(hgignoreFile)); - try { - return read(fr); - } finally { - fr.close(); + } + + /*package-local*/ void reloadIfChanged(Internals repo) throws HgInvalidControlFileException { + assert ignoreFileTracker != null; + if (ignoreFileTracker.changed(this)) { + entries = Collections.emptyList(); + read(repo); } } diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Wed May 08 17:11:45 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Thu May 09 21:06:48 2013 +0200 @@ -16,13 +16,12 @@ */ package org.tmatesoft.hg.repo; -import static org.tmatesoft.hg.repo.HgRepositoryFiles.*; -import static org.tmatesoft.hg.util.LogFacility.Severity.*; +import static org.tmatesoft.hg.repo.HgRepositoryFiles.LastMessage; +import static org.tmatesoft.hg.util.LogFacility.Severity.Warn; import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.io.StringReader; import java.nio.CharBuffer; import java.util.ArrayList; import java.util.Collections; @@ -30,7 +29,6 @@ import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; -import org.tmatesoft.hg.internal.ByteArrayChannel; import org.tmatesoft.hg.internal.ConfigFile; import org.tmatesoft.hg.internal.DirstateReader; import org.tmatesoft.hg.internal.Experimental; @@ -40,7 +38,6 @@ import org.tmatesoft.hg.internal.RevlogStream; import org.tmatesoft.hg.internal.SubrepoManager; import org.tmatesoft.hg.repo.ext.HgExtensionsManager; -import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Pair; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathRewrite; @@ -110,11 +107,11 @@ private SubrepoManager subRepos; private HgBookmarks bookmarks; private HgExtensionsManager extManager; - - private final org.tmatesoft.hg.internal.Internals impl; private HgIgnore ignore; private HgRepoConfig repoConfig; + private final org.tmatesoft.hg.internal.Internals impl; + /* * TODO [post-1.0] move to a better place, e.g. WorkingCopy container that tracks both dirstate and branches * (and, perhaps, undo, lastcommit and other similar information), and is change listener so that we don't need to @@ -157,7 +154,7 @@ return HgRepository.this.getChangelog().content; } }); - normalizePath = impl.buildNormalizePathRewrite(); + normalizePath = impl.buildNormalizePathRewrite(); } @Override @@ -201,55 +198,34 @@ } /** + * Access snapshot of repository tags. + * * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ public HgTags getTags() throws HgInvalidControlFileException { if (tags == null) { - tags = new HgTags(this); - HgDataFile hgTags = getFileNode(HgTags.getPath()); - if (hgTags.exists()) { - for (int i = 0; i <= hgTags.getLastRevision(); i++) { // TODO post-1.0 in fact, would be handy to have walk(start,end) - // method for data files as well, though it looks odd. - try { - ByteArrayChannel sink = new ByteArrayChannel(); - hgTags.content(i, sink); - final String content = new String(sink.toArray(), "UTF8"); - tags.readGlobal(new StringReader(content)); - } catch (CancelledException ex) { - // IGNORE, can't happen, we did not configure cancellation - getSessionContext().getLog().dump(getClass(), Debug, ex, null); - } catch (IOException ex) { - // UnsupportedEncodingException can't happen (UTF8) - // only from readGlobal. Need to reconsider exceptions thrown from there: - // BufferedReader wraps String and unlikely to throw IOException, perhaps, log is enough? - getSessionContext().getLog().dump(getClass(), Error, ex, null); - // XXX need to decide what to do this. failure to read single revision shall not break complete cycle - } - } - } - File file2read = null; - try { - file2read = new File(getWorkingDir(), HgTags.getPath()); - tags.readGlobal(file2read); // XXX replace with HgDataFile.workingCopy - file2read = impl.getFileFromRepoDir(HgLocalTags.getName()); // XXX pass internalrepo to readLocal, keep filename there - tags.readLocal(file2read); - } catch (IOException ex) { - getSessionContext().getLog().dump(getClass(), Error, ex, null); - throw new HgInvalidControlFileException("Failed to read tags", ex, file2read); - } + tags = new HgTags(impl); + tags.read(); + } else { + tags.reloadIfChanged(); } return tags; } /** - * Access branch information + * Access branch information. Returns a snapshot of branch information as it's available at the time of the call. + * If repository get changed, use this method to obtain an up-to-date state. + * * @return branch manager instance, never null * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ public HgBranches getBranches() throws HgInvalidControlFileException { + final ProgressSupport ps = ProgressSupport.Factory.get(null); if (branches == null) { branches = new HgBranches(impl); - branches.collect(ProgressSupport.Factory.get(null)); + branches.collect(ps); + } else { + branches.reloadIfChanged(ps); } return branches; } @@ -370,16 +346,9 @@ // TODO read config for additional locations if (ignore == null) { ignore = new HgIgnore(getToRepoPathHelper()); - File ignoreFile = new File(getWorkingDir(), HgIgnore.getPath()); - try { - final List errors = ignore.read(ignoreFile); - if (errors != null) { - getSessionContext().getLog().dump(getClass(), Warn, "Syntax errors parsing %s:\n%s", ignoreFile.getName(), Internals.join(errors, ",\n")); - } - } catch (IOException ex) { - final String m = String.format("Error reading %s file", ignoreFile); - throw new HgInvalidControlFileException(m, ex, ignoreFile); - } + ignore.read(impl); + } else { + ignore.reloadIfChanged(impl); } return ignore; } @@ -465,6 +434,8 @@ if (bookmarks == null) { bookmarks = new HgBookmarks(impl); bookmarks.read(); + } else { + bookmarks.reloadIfChanged(); } return bookmarks; } diff -r e4a71afd3c71 -r 5c68567b3645 src/org/tmatesoft/hg/repo/HgTags.java --- a/src/org/tmatesoft/hg/repo/HgTags.java Wed May 08 17:11:45 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgTags.java Thu May 09 21:06:48 2013 +0200 @@ -16,6 +16,9 @@ */ package org.tmatesoft.hg.repo; +import static org.tmatesoft.hg.repo.HgRepositoryFiles.HgLocalTags; +import static org.tmatesoft.hg.repo.HgRepositoryFiles.HgTags; +import static org.tmatesoft.hg.util.LogFacility.Severity.*; import static org.tmatesoft.hg.util.LogFacility.Severity.Error; import static org.tmatesoft.hg.util.LogFacility.Severity.Warn; @@ -24,6 +27,7 @@ import java.io.FileReader; import java.io.IOException; import java.io.Reader; +import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -34,6 +38,11 @@ import org.tmatesoft.hg.core.HgBadNodeidFormatException; import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.internal.ByteArrayChannel; +import org.tmatesoft.hg.internal.ChangelogMonitor; +import org.tmatesoft.hg.internal.FileChangeMonitor; +import org.tmatesoft.hg.internal.Internals; +import org.tmatesoft.hg.util.CancelledException; /** * @see http://mercurial.selenic.com/wiki/TagDesign @@ -45,38 +54,83 @@ // global tags come from ".hgtags" // local come from ".hg/localtags" - private final HgRepository repo; + private final Internals repo; private final Map> globalToName; private final Map> localToName; private final Map> globalFromName; private final Map> localFromName; + private FileChangeMonitor globalTagsFileMonitor, localTagsFileMonitor; + private ChangelogMonitor repoChangeMonitor; + private Map tags; - /*package-local*/ HgTags(HgRepository hgRepo) { - repo = hgRepo; + /*package-local*/ HgTags(Internals internalRepo) { + repo = internalRepo; globalToName = new HashMap>(); localToName = new HashMap>(); globalFromName = new TreeMap>(); localFromName = new TreeMap>(); } - /*package-local*/ void readLocal(File localTags) throws IOException { - if (localTags == null || localTags.isDirectory()) { - throw new IllegalArgumentException(String.valueOf(localTags)); - } - read(localTags, localToName, localFromName); + /*package-local*/ void read() throws HgInvalidControlFileException { + readTagsFromHistory(); + readGlobal(); + readLocal(); } - /*package-local*/ void readGlobal(File globalTags) throws IOException { - if (globalTags == null || globalTags.isDirectory()) { - throw new IllegalArgumentException(String.valueOf(globalTags)); + private void readTagsFromHistory() throws HgInvalidControlFileException { + HgDataFile hgTags = repo.getRepo().getFileNode(HgTags.getPath()); + if (hgTags.exists()) { + for (int i = 0; i <= hgTags.getLastRevision(); i++) { // TODO post-1.0 in fact, would be handy to have walk(start,end) + // method for data files as well, though it looks odd. + try { + ByteArrayChannel sink = new ByteArrayChannel(); + hgTags.content(i, sink); + final String content = new String(sink.toArray(), "UTF8"); + readGlobal(new StringReader(content)); + } catch (CancelledException ex) { + // IGNORE, can't happen, we did not configure cancellation + repo.getLog().dump(getClass(), Debug, ex, null); + } catch (IOException ex) { + // UnsupportedEncodingException can't happen (UTF8) + // only from readGlobal. Need to reconsider exceptions thrown from there: + // BufferedReader wraps String and unlikely to throw IOException, perhaps, log is enough? + repo.getLog().dump(getClass(), Error, ex, null); + // XXX need to decide what to do this. failure to read single revision shall not break complete cycle + } + } } - read(globalTags, globalToName, globalFromName); + if (repoChangeMonitor == null) { + repoChangeMonitor = new ChangelogMonitor(repo.getRepo()); + } + repoChangeMonitor.touch(); + } + + private void readLocal() throws HgInvalidControlFileException { + File localTags = repo.getRepositoryFile(HgLocalTags); + if (localTags.canRead() && localTags.isFile()) { + read(localTags, localToName, localFromName); + } + if (localTagsFileMonitor == null) { + localTagsFileMonitor = new FileChangeMonitor(localTags); + } + localTagsFileMonitor.touch(this); + } + + private void readGlobal() throws HgInvalidControlFileException { + File globalTags = repo.getRepositoryFile(HgTags); // XXX replace with HgDataFile.workingCopy + if (globalTags.canRead() && globalTags.isFile()) { + read(globalTags, globalToName, globalFromName); + } + if (globalTagsFileMonitor == null) { + globalTagsFileMonitor = new FileChangeMonitor(globalTags); + } + globalTagsFileMonitor.touch(this); } - /*package-local*/ void readGlobal(Reader globalTags) throws IOException { + private void readGlobal(Reader globalTags) throws IOException { BufferedReader r = null; try { r = new BufferedReader(globalTags); @@ -88,7 +142,7 @@ } } - private void read(File f, Map> nid2name, Map> name2nid) throws IOException { + private void read(File f, Map> nid2name, Map> name2nid) throws HgInvalidControlFileException { if (!f.canRead()) { return; } @@ -96,9 +150,17 @@ try { r = new BufferedReader(new FileReader(f)); read(r, nid2name, name2nid); + } catch (IOException ex) { + repo.getLog().dump(getClass(), Error, ex, null); + throw new HgInvalidControlFileException("Failed to read tags", ex, f); } finally { if (r != null) { - r.close(); + try { + r.close(); + } catch (IOException ex) { + // since it's read operation, do not treat close failure as error, but let user know, anyway + repo.getLog().dump(getClass(), Warn, ex, null); + } } } } @@ -112,7 +174,7 @@ } final int spacePos = line.indexOf(' '); if (line.length() < 40+2 /*nodeid, space and at least single-char tagname*/ || spacePos != 40) { - repo.getSessionContext().getLog().dump(getClass(), Warn, "Bad tags line: %s", line); + repo.getLog().dump(getClass(), Warn, "Bad tags line: %s", line); continue; } try { @@ -154,7 +216,7 @@ revTags.add(tagName); } } catch (HgBadNodeidFormatException ex) { - repo.getSessionContext().getLog().dump(getClass(), Error, "Bad revision '%s' in line '%s':%s", line.substring(0, spacePos), line, ex.getMessage()); + repo.getLog().dump(getClass(), Error, "Bad revision '%s' in line '%s':%s", line.substring(0, spacePos), line, ex.getMessage()); } } } @@ -217,6 +279,23 @@ return rv; } + // can be called only after instance has been initialized (#read() invoked) + /*package-local*/void reloadIfChanged() throws HgInvalidControlFileException { + assert repoChangeMonitor != null; + assert localTagsFileMonitor != null; + assert globalTagsFileMonitor != null; + if (repoChangeMonitor.isChanged() || globalTagsFileMonitor.changed(this)) { + globalFromName.clear(); + globalToName.clear(); + readTagsFromHistory(); + readGlobal(); + tags = null; + } + if (localTagsFileMonitor.changed(this)) { + readLocal(); + tags = null; + } + } public final class TagInfo { private final String name; @@ -235,8 +314,8 @@ public String branch() throws HgInvalidControlFileException { if (branch == null) { - int x = repo.getChangelog().getRevisionIndex(revision()); - branch = repo.getChangelog().range(x, x).get(0).branch(); + int x = repo.getRepo().getChangelog().getRevisionIndex(revision()); + branch = repo.getRepo().getChangelog().range(x, x).get(0).branch(); } return branch; }