changeset 234:b2cfbe46f9b6

HgTags got TagInfo to access tags. Tags are read from all branches/revisions now, not only working copy
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 07 Jun 2011 04:28:32 +0200
parents 1d389c0cb0a5
children fd845a53f53d
files cmdline/org/tmatesoft/hg/console/Tags.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/repo/HgTags.java
diffstat 3 files changed, 178 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- /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<TagInfo, Integer> ti2index = new HashMap<TagInfo, Integer>();
+		final TreeSet<TagInfo> sorted = new TreeSet<HgTags.TagInfo>(new Comparator<TagInfo>() {
+
+			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());
+		}
+	}
+}
--- 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
--- 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<Nodeid, List<String>> globalToName;
 	private final Map<Nodeid, List<String>> localToName;
 	private final Map<String, List<Nodeid>> globalFromName;
 	private final Map<String, List<Nodeid>> localFromName;
 	
+	private Map<String, TagInfo> tags;
 	
-	/*package-local*/ HgTags() {
+	/*package-local*/ HgTags(HgRepository hgRepo) {
+		repo = hgRepo;
 		globalToName =  new HashMap<Nodeid, List<String>>();
 		localToName  =  new HashMap<Nodeid, List<String>>();
 		globalFromName = new TreeMap<String, List<Nodeid>>();
@@ -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<Nodeid,List<String>> nid2name, Map<String, List<Nodeid>> name2nid) throws IOException {
 		if (!f.canRead()) {
@@ -101,19 +119,37 @@
 				List<Nodeid> nids = name2nid.get(tagName);
 				if (nids == null) {
 					nids = new LinkedList<Nodeid>();
+					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<Nodeid>) nids).addFirst(nid);
+					// XXX repo.getNodeidCache().nodeid(nid);
 				}
-				// XXX repo.getNodeidCache().nodeid(nid);
-				((LinkedList<Nodeid>) nids).addFirst(nid);
 				List<String> revTags = nid2name.get(nid);
 				if (revTags == null) {
 					revTags = new LinkedList<String>();
+					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<String, TagInfo> getTags() {
+		if (tags == null) {
+			tags = new TreeMap<String, TagInfo>();
+			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);
+		}
+	}
 }