changeset 128:44b97930570c

Introduced ChangelogHelper to look up changesets files were modified in
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 16 Feb 2011 20:13:41 +0100
parents 2e395db595e2
children 645829962785
files TODO design.txt src/org/tmatesoft/hg/core/HgStatus.java src/org/tmatesoft/hg/core/StatusCommand.java src/org/tmatesoft/hg/internal/ChangelogHelper.java src/org/tmatesoft/hg/internal/ConfigFile.java src/org/tmatesoft/hg/internal/KeywordFilter.java src/org/tmatesoft/hg/repo/Changeset.java src/org/tmatesoft/hg/repo/HgInternals.java
diffstat 9 files changed, 183 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/TODO	Wed Feb 16 18:42:10 2011 +0100
+++ b/TODO	Wed Feb 16 20:13:41 2011 +0100
@@ -41,6 +41,14 @@
   + \r\n <==> \n
   - force translation if inconsistent (now either fails or does nothing)
 
+* API
+  - rename in .core Cset -> HgChangeset,
+  - rename in .repo Changeset to HgChangelog.Changeset, Changeset.Inspector -> HgChangelog.Inspector
+  - CommandContext
+  - Data access - not bytes, but ByteChannel
+
+* defects
+  ConfigFile to strip comments from values (#)
 
 Proposed:
 - LogCommand.revision(int... rev)+ to walk selected revisions only (list->sort(array) on execute, binary search)
@@ -50,7 +58,6 @@
 - hgignore: read extra ignore files from config file (ui.ignore)
 
 
-
 Read-only support, version 1.1
 ==============================
 Committed:
@@ -59,3 +66,5 @@
 * incoming
 
 * outgoing
+
+- clone remote repo
--- a/design.txt	Wed Feb 16 18:42:10 2011 +0100
+++ b/design.txt	Wed Feb 16 20:13:41 2011 +0100
@@ -42,7 +42,9 @@
 DataAccess - collect debug info (buffer misses, file size/total read operations) to find out better strategy to buffer size detection. Compare performance.
 
 Strip off metadata from beg of the stream - DataAccess (with rebase/moveBaseOffset(int)) would be handy
-Parameterize StatusCollector to produce copy only when needed. And HgDataFile.metadata perhaps should be moved to cacheable place? 
+Parameterize StatusCollector to produce copy only when needed. And HgDataFile.metadata perhaps should be moved to cacheable place?
+
+RevisionMap to replace TreeMap<Integer, ?> 
 
 Status operation from GUI - guess, usually on a file/subfolder, hence API should allow for starting path (unlike cmdline, seems useless to implement include/exclide patterns - GUI users hardly enter them, ever)
   -> recently introduced FileWalker may perhaps help solving this (if starts walking from selected folder) for status op against WorkingDir?
--- a/src/org/tmatesoft/hg/core/HgStatus.java	Wed Feb 16 18:42:10 2011 +0100
+++ b/src/org/tmatesoft/hg/core/HgStatus.java	Wed Feb 16 20:13:41 2011 +0100
@@ -12,10 +12,15 @@
  *
  * 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@svnkit.com
+ * contact TMate Software at support@hg4j.com
  */
 package org.tmatesoft.hg.core;
 
+import java.util.Date;
+
+import org.tmatesoft.hg.internal.ChangelogHelper;
+import org.tmatesoft.hg.repo.Changeset;
+
 public class HgStatus {
 
 	public enum Kind {
@@ -25,15 +30,17 @@
 	private final HgStatus.Kind kind;
 	private final Path path;
 	private final Path origin;
+	private final ChangelogHelper logHelper;
 		
-	HgStatus(HgStatus.Kind kind, Path path) {
-		this(kind, path, null);
+	HgStatus(HgStatus.Kind kind, Path path, ChangelogHelper changelogHelper) {
+		this(kind, path, null, changelogHelper);
 	}
 
-	HgStatus(HgStatus.Kind kind, Path path, Path copyOrigin) {
+	HgStatus(HgStatus.Kind kind, Path path, Path copyOrigin, ChangelogHelper changelogHelper) {
 		this.kind = kind;
 		this.path  = path;
 		origin = copyOrigin;
+		logHelper = changelogHelper;
 	}
 
 	public HgStatus.Kind getKind() {
@@ -51,10 +58,29 @@
 	public boolean isCopy() {
 		return origin != null;
 	}
-		
-//	public String getModificationAuthor() {
-//	}
-//
-//	public Date getModificationDate() {
-//	}
+
+	/**
+	 * @return <code>null</code> if author for the change can't be deduced (e.g. for clean files it's senseless)
+	 */
+	public String getModificationAuthor() {
+		Changeset cset = logHelper.findLatestChangeWith(path);
+		if (cset == null) {
+			if (kind == Kind.Modified || kind == Kind.Added || kind == Kind.Removed /*&& RightBoundary is TIP*/) {
+				return logHelper.getNextCommitUsername();
+			}
+		} else {
+			return cset.user();
+		}
+		return null;
+	}
+
+	public Date getModificationDate() {
+		Changeset cset = logHelper.findLatestChangeWith(path);
+		if (cset == null) {
+			// FIXME check dirstate and/or local file for tstamp
+			return new Date(); // what's correct 
+		} else {
+			return cset.date();
+		}
+	}
 }
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/StatusCommand.java	Wed Feb 16 18:42:10 2011 +0100
+++ b/src/org/tmatesoft/hg/core/StatusCommand.java	Wed Feb 16 20:13:41 2011 +0100
@@ -22,6 +22,7 @@
 import java.util.ConcurrentModificationException;
 
 import org.tmatesoft.hg.core.Path.Matcher;
+import org.tmatesoft.hg.internal.ChangelogHelper;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.repo.HgStatusInspector;
@@ -39,7 +40,6 @@
 	private int endRevision = WORKING_COPY; 
 	private boolean visitSubRepo = true;
 	
-	private Handler handler;
 	private final Mediator mediator = new Mediator();
 
 	public StatusCommand(HgRepository hgRepo) { 
@@ -145,16 +145,15 @@
 		if (statusHandler == null) {
 			throw new IllegalArgumentException();
 		}
-		if (handler != null) {
+		if (mediator.busy()) {
 			throw new ConcurrentModificationException();
 		}
-		handler = statusHandler;
 		HgStatusCollector sc = new HgStatusCollector(repo); // TODO from CommandContext
 //		PathPool pathHelper = new PathPool(repo.getPathHelper()); // TODO from CommandContext
 		try {
 			// XXX if I need a rough estimation (for ProgressMonitor) of number of work units,
 			// I may use number of files in either rev1 or rev2 manifest edition
-			mediator.start();
+			mediator.start(statusHandler, new ChangelogHelper(repo, startRevision));
 			if (endRevision == WORKING_COPY) {
 				HgWorkingCopyStatusCollector wcsc = new HgWorkingCopyStatusCollector(repo);
 				wcsc.setBaseRevisionCollector(sc);
@@ -168,7 +167,6 @@
 			}
 		} finally {
 			mediator.done();
-			handler = null;
 		}
 	}
 
@@ -186,69 +184,79 @@
 		boolean needIgnored;
 		boolean needCopies;
 		Matcher matcher;
+		Handler handler;
+		private ChangelogHelper logHelper;
 
 		Mediator() {
 		}
 		
-		public void start() {
-			
+		public void start(Handler h, ChangelogHelper changelogHelper) {
+			handler = h;
+			logHelper = changelogHelper;
 		}
+
 		public void done() {
+			handler = null;
+			logHelper = null;
+		}
+		
+		public boolean busy() {
+			return handler != null;
 		}
 
 		public void modified(Path fname) {
 			if (needModified) {
 				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Modified, fname));
+					handler.handleStatus(new HgStatus(Modified, fname, logHelper));
 				}
 			}
 		}
 		public void added(Path fname) {
 			if (needAdded) {
 				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Added, fname));
+					handler.handleStatus(new HgStatus(Added, fname, logHelper));
 				}
 			}
 		}
 		public void removed(Path fname) {
 			if (needRemoved) {
 				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Removed, fname));
+					handler.handleStatus(new HgStatus(Removed, fname, logHelper));
 				}
 			}
 		}
 		public void copied(Path fnameOrigin, Path fnameAdded) {
 			if (needCopies) {
 				if (matcher == null || matcher.accept(fnameAdded)) {
-					handler.handleStatus(new HgStatus(Added, fnameAdded, fnameOrigin));
+					handler.handleStatus(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
 				}
 			}
 		}
 		public void missing(Path fname) {
 			if (needMissing) {
 				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Missing, fname));
+					handler.handleStatus(new HgStatus(Missing, fname, logHelper));
 				}
 			}
 		}
 		public void unknown(Path fname) {
 			if (needUnknown) {
 				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Unknown, fname));
+					handler.handleStatus(new HgStatus(Unknown, fname, logHelper));
 				}
 			}
 		}
 		public void clean(Path fname) {
 			if (needClean) {
 				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Clean, fname));
+					handler.handleStatus(new HgStatus(Clean, fname, logHelper));
 				}
 			}
 		}
 		public void ignored(Path fname) {
 			if (needIgnored) {
 				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Ignored, fname));
+					handler.handleStatus(new HgStatus(Ignored, fname, logHelper));
 				}
 			}
 		}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/ChangelogHelper.java	Wed Feb 16 20:13:41 2011 +0100
@@ -0,0 +1,79 @@
+/*
+ * 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.internal;
+
+import java.util.TreeMap;
+
+import org.tmatesoft.hg.core.Path;
+import org.tmatesoft.hg.repo.Changeset;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ChangelogHelper {
+	private final int leftBoundary;
+	private final HgRepository repo;
+	private final TreeMap<Integer, Changeset> cache = new TreeMap<Integer, Changeset>();
+
+	/**
+	 * @param hgRepo
+	 * @param leftBoundaryRevision walker never visits revisions with local numbers less than specified,
+	 * IOW only revisions [leftBoundaryRevision..TIP] are considered.
+	 */
+	public ChangelogHelper(HgRepository hgRepo, int leftBoundaryRevision) {
+		repo = hgRepo;
+		leftBoundary = leftBoundaryRevision;
+	}
+	
+	/**
+	 * @return the repo
+	 */
+	public HgRepository getRepo() {
+		return repo;
+	}
+
+	/**
+	 * Walks changelog in reverse order
+	 * @param file
+	 * @return changeset where specified file is mentioned among affected files, or 
+	 * <code>null</code> if none found up to leftBoundary 
+	 */
+	public Changeset findLatestChangeWith(Path file) {
+		HgDataFile df = repo.getFileNode(file);
+		int changelogRev = df.getChangesetLocalRevision(HgRepository.TIP);
+		if (changelogRev >= leftBoundary) {
+			// the method is likely to be invoked for different files, 
+			// while changesets might be the same. Cache 'em not to read too much. 
+			Changeset cs = cache.get(changelogRev);
+			if (cs == null) {
+				cs = repo.getChangelog().range(changelogRev, changelogRev).get(0);
+				cache.put(changelogRev, cs);
+			}
+			return cs;
+		}
+		return null;
+	}
+
+	public String getNextCommitUsername() {
+		return new HgInternals(repo).getNextCommitUsername();
+	}
+}
--- a/src/org/tmatesoft/hg/internal/ConfigFile.java	Wed Feb 16 18:42:10 2011 +0100
+++ b/src/org/tmatesoft/hg/internal/ConfigFile.java	Wed Feb 16 20:13:41 2011 +0100
@@ -84,6 +84,11 @@
 		}
 		return false;
 	}
+	
+	public String getString(String sectionName, String key, String defaultValue) {
+		String value = getSection(sectionName).get(key);
+		return value == null ? defaultValue : value;
+	}
 
 	// TODO handle %include and %unset directives
 	// TODO "" and lists
--- a/src/org/tmatesoft/hg/internal/KeywordFilter.java	Wed Feb 16 18:42:10 2011 +0100
+++ b/src/org/tmatesoft/hg/internal/KeywordFilter.java	Wed Feb 16 20:13:41 2011 +0100
@@ -268,6 +268,7 @@
 	
 	private Changeset getChangeset() {
 		if (latestFileCset == null) {
+			// XXX consider use of ChangelogHelper
 			int csetRev = repo.getFileNode(path).getChangesetLocalRevision(HgRepository.TIP);
 			latestFileCset = repo.getChangelog().range(csetRev, csetRev).get(0);
 		}
--- a/src/org/tmatesoft/hg/repo/Changeset.java	Wed Feb 16 18:42:10 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/Changeset.java	Wed Feb 16 20:13:41 2011 +0100
@@ -202,6 +202,7 @@
 		String _comment;
 		try {
 			_comment = new String(data, breakIndex4+2, bufferEndIndex - breakIndex4 - 2, "UTF-8");
+			// FIXME respect ui.fallbackencoding and try to decode if set 
 		} catch (UnsupportedEncodingException ex) {
 			_comment = "";
 			throw new IllegalStateException("Could hardly happen");
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Wed Feb 16 18:42:10 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/HgInternals.java	Wed Feb 16 20:13:41 2011 +0100
@@ -17,6 +17,8 @@
 package org.tmatesoft.hg.repo;
 
 import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 
 import org.tmatesoft.hg.internal.ConfigFile;
 
@@ -58,4 +60,27 @@
 	public ConfigFile getRepoConfig() {
 		return repo.getConfigFile();
 	}
+
+	// in fact, need a setter for this anyway, shall move to internal.Internals perhaps?
+	public String getNextCommitUsername() {
+		String hgUser = System.getenv("HGUSER");
+		if (hgUser != null && hgUser.trim().length() > 0) {
+			return hgUser.trim();
+		}
+		String configValue = getRepoConfig().getString("ui", "username", null);
+		if (configValue != null) {
+			return configValue;
+		}
+		String email = System.getenv("EMAIL");
+		if (email != null && email.trim().length() > 0) {
+			return email;
+		}
+		String username = System.getProperty("user.name");
+		try {
+			String hostname = InetAddress.getLocalHost().getHostName();
+			return username + '@' + hostname; 
+		} catch (UnknownHostException ex) {
+			return username;
+		}
+	}
 }