# HG changeset patch # User Artem Tikhomirov # Date 1307413712 -7200 # Node ID b2cfbe46f9b62d1d661db920dd4e1591855ba356 # Parent 1d389c0cb0a5ef03d4249aeffb2fdebba9dcb1e7 HgTags got TagInfo to access tags. Tags are read from all branches/revisions now, not only working copy diff -r 1d389c0cb0a5 -r b2cfbe46f9b6 cmdline/org/tmatesoft/hg/console/Tags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmdline/org/tmatesoft/hg/console/Tags.java Tue Jun 07 04:28:32 2011 +0200 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2011 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.console; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; + +import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgTags; +import org.tmatesoft.hg.repo.HgTags.TagInfo; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class Tags { + + public static void main(String[] args) throws Exception { + Options cmdLineOpts = Options.parse(args); + HgRepository hgRepo = cmdLineOpts.findRepository(); + if (hgRepo.isInvalid()) { + System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation()); + return; + } + HgTags tags = hgRepo.getTags(); + final HgChangelog clog = hgRepo.getChangelog(); + final Map ti2index = new HashMap(); + final TreeSet sorted = new TreeSet(new Comparator() { + + public int compare(TagInfo o1, TagInfo o2) { + // reverse, from newer to older (bigger indexes first); + // never ==, tags from same revision in any order, just next to each other + int x1 = ti2index.get(o1); + int x2 = ti2index.get(o2); + return x1 < x2 ? 1 : -1; + } + }); + for (TagInfo ti : tags.getTags().values()) { + int x = clog.getLocalRevision(ti.revision()); // XXX in fact, performance hog. Need batch localRevision or another improvement + ti2index.put(ti, x); + sorted.add(ti); + } + for (TagInfo ti : sorted) { + int x = ti2index.get(ti); + System.out.printf("%-30s%8d:%s\n", ti.name(), x, ti.revision().shortNotation()); + } + } +} diff -r 1d389c0cb0a5 -r b2cfbe46f9b6 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Fri Jun 03 04:50:09 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Tue Jun 07 04:28:32 2011 +0200 @@ -18,18 +18,23 @@ import java.io.File; import java.io.IOException; +import java.io.StringReader; import java.lang.ref.SoftReference; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import org.tmatesoft.hg.core.HgDataStreamException; +import org.tmatesoft.hg.internal.ByteArrayChannel; import org.tmatesoft.hg.internal.ConfigFile; import org.tmatesoft.hg.internal.DataAccessProvider; import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.Filter; import org.tmatesoft.hg.internal.RequiresFile; import org.tmatesoft.hg.internal.RevlogStream; +import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathRewrite; import org.tmatesoft.hg.util.ProgressSupport; @@ -142,9 +147,29 @@ public HgTags getTags() { if (tags == null) { - tags = new HgTags(); + tags = new HgTags(this); try { - tags.readGlobal(new File(repoDir.getParentFile(), ".hgtags")); + HgDataFile hgTags = getFileNode(".hgtags"); + if (hgTags.exists()) { + for (int i = 0; i <= hgTags.getLastRevision(); i++) { // FIXME 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) { + ex.printStackTrace(); // IGNORE, can't happen, we did not configure cancellation + } catch (HgDataStreamException ex) { + ex.printStackTrace(); // FIXME need to react + } catch (IOException ex) { + // UnsupportedEncodingException can't happen (UTF8) + // only from readGlobal. Need to reconsider exceptions thrown from there + ex.printStackTrace(); // XXX need to decide what to do this. failure to read single revision shall not break complete cycle + } + } + } + tags.readGlobal(new File(repoDir.getParentFile(), ".hgtags")); // XXX replace with HgDataFile.workingCopy tags.readLocal(new File(repoDir, "localtags")); } catch (IOException ex) { ex.printStackTrace(); // FIXME log or othewise report diff -r 1d389c0cb0a5 -r b2cfbe46f9b6 src/org/tmatesoft/hg/repo/HgTags.java --- a/src/org/tmatesoft/hg/repo/HgTags.java Fri Jun 03 04:50:09 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgTags.java Tue Jun 07 04:28:32 2011 +0200 @@ -20,7 +20,9 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.io.Reader; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -39,13 +41,17 @@ // global tags come from ".hgtags" // local come from ".hg/localtags" + private final HgRepository repo; + private final Map> globalToName; private final Map> localToName; private final Map> globalFromName; private final Map> localFromName; + private Map tags; - /*package-local*/ HgTags() { + /*package-local*/ HgTags(HgRepository hgRepo) { + repo = hgRepo; globalToName = new HashMap>(); localToName = new HashMap>(); globalFromName = new TreeMap>(); @@ -65,6 +71,18 @@ } read(globalTags, globalToName, globalFromName); } + + /*package-local*/ void readGlobal(Reader globalTags) throws IOException { + BufferedReader r = null; + try { + r = new BufferedReader(globalTags); + read(r, globalToName, globalFromName); + } finally { + if (r != null) { + r.close(); + } + } + } private void read(File f, Map> nid2name, Map> name2nid) throws IOException { if (!f.canRead()) { @@ -101,19 +119,37 @@ List nids = name2nid.get(tagName); if (nids == null) { nids = new LinkedList(); + nids.add(nid); // tagName is substring of full line, thus need a copy to let the line be GC'ed // new String(tagName.toCharArray()) is more expressive, but results in 1 extra arraycopy tagName = new String(tagName); name2nid.put(tagName, nids); + } else if (!nid.equals(nids.get(0))) { + // Alternatively, !nids.contains(nid) might have come to mind. + // However, I guess that 'tag history' means we need to record each change of revision + // associated with the tag, i.e. imagine project evolution: + // tag1=r1, tag1=r2, tag1=r1. If we choose !contains, list top of tag1 would point to r2 + // while we need it to point to r1. + // In fact, there are still possible odd patterns in name2nid list, e.g. + // when tag was removed and added back(initially rev1 tag1, on removal *added* nullrev tag1), + // then added back (rev2 tag1). + // name2nid would list (rev2 nullrev rev1) as many times, as there were revisions of the .hgtags file + // See cpython "v2.4.3c1" revision for example. + // It doesn't seem to hurt (unless there are clients that care about tag history and depend on + // unique revisions there), XXX but better to be fixed (not sure how, though) + ((LinkedList) nids).addFirst(nid); + // XXX repo.getNodeidCache().nodeid(nid); } - // XXX repo.getNodeidCache().nodeid(nid); - ((LinkedList) nids).addFirst(nid); List revTags = nid2name.get(nid); if (revTags == null) { revTags = new LinkedList(); + revTags.add(tagName); nid2name.put(nid, revTags); + } else if (!revTags.contains(tagName)) { + // !contains because we don't care about order of the tags per revision + revTags.add(tagName); } - revTags.add(tagName); + } else { System.out.println("Bad tags line:" + line); // FIXME see above } @@ -147,4 +183,49 @@ } return rv; } + + public Map getTags() { + if (tags == null) { + tags = new TreeMap(); + for (String t : globalFromName.keySet()) { + tags.put(t, new TagInfo(t)); + } + for (String t : localFromName.keySet()) { + tags.put(t, new TagInfo(t)); + } + tags = Collections.unmodifiableMap(tags); + } + return tags; + } + + + public final class TagInfo { + private final String name; + private String branch; + + TagInfo(String tagName) { + this.name = tagName; + } + public String name() { + return name; + } + + public boolean isLocal() { + return localFromName.containsKey(name); + } + + public String branch() { + if (branch == null) { + int x = repo.getChangelog().getLocalRevision(revision()); + branch = repo.getChangelog().range(x, x).get(0).branch(); + } + return branch; + } + public Nodeid revision() { + if (isLocal()) { + return localFromName.get(name).get(0); + } + return globalFromName.get(name).get(0); + } + } }