changeset 220:8de327242aa0

Basic information about branches
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 19 May 2011 04:14:45 +0200
parents d63583b47bfa
children 04735ce6bed2
files cmdline/org/tmatesoft/hg/console/Main.java src/org/tmatesoft/hg/repo/HgBranches.java src/org/tmatesoft/hg/repo/HgManifest.java src/org/tmatesoft/hg/repo/HgRepository.java
diffstat 4 files changed, 168 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Tue May 17 05:43:09 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Thu May 19 04:14:45 2011 +0200
@@ -28,6 +28,7 @@
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.DigestHelper;
+import org.tmatesoft.hg.repo.HgBranches;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgManifest;
@@ -60,7 +61,8 @@
 
 	public static void main(String[] args) throws Exception {
 		Main m = new Main(args);
-		m.inflaterLengthException();
+		m.dumpBranches();
+//		m.inflaterLengthException();
 //		m.dumpIgnored();
 //		m.dumpDirstate();
 //		m.testStatusInternals();
@@ -70,6 +72,20 @@
 //		m.bunchOfTests();
 	}
 	
+	private void dumpBranches() {
+		HgBranches b = hgRepo.getBranches();
+		for (HgBranches.BranchInfo bi : b.getAllBranches()) {
+			System.out.print(bi.getName());
+			if (bi.isClosed()) {
+				System.out.print("!");
+			}
+			System.out.print(" ");
+			System.out.print(bi.getStart());
+			System.out.print(" ");
+			System.out.println(bi.getHeads());
+		}
+	}
+	
 	private void inflaterLengthException() throws Exception {
 		HgDataFile f1 = hgRepo.getFileNode("src/com/tmate/hgkit/console/Bundle.java");
 		HgDataFile f2 = hgRepo.getFileNode("test-repos.jar");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgBranches.java	Thu May 19 04:14:45 2011 +0200
@@ -0,0 +1,134 @@
+/*
+ * 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.repo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.util.ProgressSupport;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgBranches {
+	
+	private final Map<String, BranchInfo> branches = new TreeMap<String, BranchInfo>();
+	private final HgRepository repo;
+	
+	HgBranches(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+
+	void collect(final ProgressSupport ps) {
+		ps.start(1 + repo.getChangelog().getRevisionCount() * 2);
+		final HgChangelog.ParentWalker pw = repo.getChangelog().new ParentWalker();
+		pw.init();
+		ps.worked(repo.getChangelog().getRevisionCount());
+		final HashMap<String, Nodeid> branchStart = new HashMap<String, Nodeid>();
+		final HashMap<String, Nodeid> branchLastSeen = new HashMap<String, Nodeid>();
+		final HashMap<String, List<Nodeid>> branchHeads = new HashMap<String, List<Nodeid>>();
+		final HashSet<String> closedBranches = new HashSet<String>();
+		HgChangelog.Inspector insp = new HgChangelog.Inspector() {
+			
+			public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+				String branchName = cset.branch();
+				if (!branchStart.containsKey(branchName)) {
+					branchStart.put(branchName, nodeid);
+					branchHeads.put(branchName, new LinkedList<Nodeid>());
+				}
+				branchLastSeen.remove(branchName);
+				if ("1".equals(cset.extras().get("close"))) {
+					branchHeads.get(branchName).add(nodeid); // XXX what if it still has children?
+					closedBranches.add(branchName);
+				} else {
+					if (pw.hasChildren(nodeid)) {
+						// children may be in another branch
+						// and unless we later came across another element from this branch,
+						// we need to record all these as valid heads
+						// XXX what about next case: head1 with children in different branch, and head2 without children
+						// head1 would get lost
+						branchLastSeen.put(branchName, nodeid);
+					} else {
+						// no more children known for this node, it's (one of the) head of the branch
+						branchHeads.get(branchName).add(nodeid);
+					}
+				}
+				ps.worked(1);
+			}
+		}; 
+		repo.getChangelog().all(insp);
+		for (String bn : branchLastSeen.keySet()) {
+			branchHeads.get(bn).add(branchLastSeen.get(bn));
+		}
+		for (String bn : branchStart.keySet()) {
+			Nodeid[] heads = branchHeads.get(bn).toArray(new Nodeid[0]);
+			BranchInfo bi = new BranchInfo(bn, branchStart.get(bn), heads, closedBranches.contains(bn));
+			branches.put(bn, bi);
+		}
+		ps.done();
+	}
+
+	public List<BranchInfo> getAllBranches() {
+		return new LinkedList<BranchInfo>(branches.values());
+				
+	}
+
+	public BranchInfo getBranch(String name) {
+		return branches.get(name);
+	}
+	
+	public static class BranchInfo {
+		private final String name;
+		private final List<Nodeid> heads;
+		private final boolean closed;
+		private final Nodeid start;
+
+		BranchInfo(String branchName, Nodeid first, Nodeid[] branchHeads, boolean isClosed) {
+			name = branchName;
+			start = first;
+			heads = Collections.unmodifiableList(new ArrayList<Nodeid>(Arrays.asList(branchHeads)));
+			closed = isClosed;
+		}
+
+		public String getName() {
+			return name;
+		}
+		public boolean isClosed() {
+			return closed;
+		}
+		public List<Nodeid> getHeads() {
+			return heads;
+		}
+//		public Nodeid getTip() {
+//		}
+		public Nodeid getStart() {
+			// first node where branch appears
+			return start;
+		}
+	}
+}
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Tue May 17 05:43:09 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Thu May 19 04:14:45 2011 +0200
@@ -149,6 +149,10 @@
 
 		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
 			if (changelog2manifest != null) {
+				// next assertion is not an error, rather assumption check, which is too development-related to be explicit exception - 
+				// I just wonder if there are manifests that have two entries pointing to single changeset. It seems unrealistic, though -
+				// changeset records one and only one manifest nodeid
+				assert changelog2manifest[linkRevision] == -1 : String.format("revision:%d, link:%d, already linked to revision:%d", revisionNumber, linkRevision, changelog2manifest[linkRevision]);
 				changelog2manifest[linkRevision] = revisionNumber;
 			} else {
 				if (revisionNumber != linkRevision) {
@@ -186,7 +190,7 @@
 			}
 			for (int u : undefinedChangelogRevision) {
 				Nodeid manifest = repo.getChangelog().range(u, u).get(0).manifest();
-				// FIXME calculate those missing effectively (e.g. cache and sort nodeids to spead lookup
+				// FIXME calculate those missing effectively (e.g. cache and sort nodeids to speed lookup
 				// right away in the #next (may refactor ParentWalker's sequential and sorted into dedicated helper and reuse here)
 				changelog2manifest[u] = repo.getManifest().getLocalRevision(manifest);
 			}
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Tue May 17 05:43:09 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Thu May 19 04:14:45 2011 +0200
@@ -34,6 +34,7 @@
 import org.tmatesoft.hg.util.FileWalker;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathRewrite;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 
 
@@ -65,13 +66,15 @@
 	private HgChangelog changelog;
 	private HgManifest manifest;
 	private HgTags tags;
+	private HgBranches branches;
+
 	// XXX perhaps, shall enable caching explicitly
 	private final HashMap<Path, SoftReference<RevlogStream>> streamsCache = new HashMap<Path, SoftReference<RevlogStream>>();
 	
 	private final org.tmatesoft.hg.internal.Internals impl = new org.tmatesoft.hg.internal.Internals();
 	private HgIgnore ignore;
 	private ConfigFile configFile;
-
+	
 	HgRepository(String repositoryPath) {
 		repoDir = null;
 		repoLocation = repositoryPath;
@@ -151,6 +154,14 @@
 		return tags;
 	}
 	
+	public final HgBranches getBranches() {
+		if (branches == null) {
+			branches = new HgBranches(this);
+			branches.collect(ProgressSupport.Factory.get(null));
+		}
+		return branches;
+	}
+	
 	public HgDataFile getFileNode(String path) {
 		String nPath = normalizePath.rewrite(path);
 		String storagePath = dataPathHelper.rewrite(nPath);