# HG changeset patch # User Artem Tikhomirov # Date 1306899865 -7200 # Node ID 1792b37650f22a878f18e9f286de3b49f1571d61 # Parent 0dd9da7489dc4e819c88ccaa0a2b12e7ca391b0e Introduced access to conflict resolution information (merge state) diff -r 0dd9da7489dc -r 1792b37650f2 cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java --- a/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java Tue May 31 05:33:16 2011 +0200 +++ b/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java Wed Jun 01 05:44:25 2011 +0200 @@ -22,8 +22,8 @@ import org.tmatesoft.hg.core.HgChangeset; import org.tmatesoft.hg.core.HgChangesetHandler; +import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.Nodeid; -import org.tmatesoft.hg.core.HgLogCommand.FileRevision; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.Path; @@ -122,7 +122,7 @@ if (complete) { if (!cset.getModifiedFiles().isEmpty()) { sb.append("files: "); - for (FileRevision s : cset.getModifiedFiles()) { + for (HgFileRevision s : cset.getModifiedFiles()) { sb.append(' '); sb.append(s.getPath()); } @@ -130,7 +130,7 @@ } if (!cset.getAddedFiles().isEmpty()) { sb.append("files+: "); - for (FileRevision s : cset.getAddedFiles()) { + for (HgFileRevision s : cset.getAddedFiles()) { sb.append(' '); sb.append(s.getPath()); } diff -r 0dd9da7489dc -r 1792b37650f2 cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Tue May 31 05:33:16 2011 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Wed Jun 01 05:44:25 2011 +0200 @@ -24,6 +24,7 @@ import java.util.Map; import org.tmatesoft.hg.core.HgLogCommand.FileRevision; +import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.HgManifestCommand; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.ByteArrayChannel; @@ -33,6 +34,7 @@ import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgInternals; import org.tmatesoft.hg.repo.HgManifest; +import org.tmatesoft.hg.repo.HgMergeState; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgStatusCollector; import org.tmatesoft.hg.repo.HgStatusInspector; @@ -62,7 +64,8 @@ public static void main(String[] args) throws Exception { Main m = new Main(args); - m.testFileStatus(); + m.testMergeState(); +// m.testFileStatus(); // m.dumpBranches(); // m.inflaterLengthException(); // m.dumpIgnored(); @@ -74,28 +77,31 @@ // m.bunchOfTests(); } + private void testMergeState() throws Exception { + final HgMergeState mergeState = hgRepo.getMergeState(); + mergeState.refresh(); + for (HgMergeState.Entry e : mergeState.getConflicts()) { + System.out.println(e.getState() + " " + e.getActualFile()); + System.out.println("p1: " + formatFileRevision(e.getFirstParent())); + System.out.println("p2: " + formatFileRevision(e.getSecondParent())); + System.out.println("ancestor: " + formatFileRevision(e.getCommonAncestor())); + System.out.println(); + } + } + + private static String formatFileRevision(HgFileRevision r) throws Exception { + final ByteArrayChannel sink = new ByteArrayChannel(); + r.putContentTo(sink); + return String.format("%s %s (%d bytes)", r.getPath(), r.getRevision(), sink.toArray().length); + } + private void testFileStatus() { // final Path path = Path.create("src/org/tmatesoft/hg/util/"); // final Path path = Path.create("src/org/tmatesoft/hg/internal/Experimental.java"); -// final Path path = Path.create("dir/file3"); +// final Path path = Path.create("missing-dir/"); // HgWorkingCopyStatusCollector wcsc = HgWorkingCopyStatusCollector.create(hgRepo, path); - HgWorkingCopyStatusCollector wcsc = HgWorkingCopyStatusCollector.create(hgRepo, new PathGlobMatcher("*")); + HgWorkingCopyStatusCollector wcsc = HgWorkingCopyStatusCollector.create(hgRepo, new PathGlobMatcher("missing-dir/**/*")); wcsc.walk(TIP, new StatusDump()); - new HgManifestCommand(hgRepo).dirs(true).revision(TIP).execute(new HgManifestCommand.Handler() { - - public void file(FileRevision fileRevision) { - } - - public void end(Nodeid manifestRevision) { - } - - public void dir(Path p) { - System.out.println(p); - } - - public void begin(Nodeid manifestRevision) { - } - }); } private void dumpBranches() { diff -r 0dd9da7489dc -r 1792b37650f2 src/org/tmatesoft/hg/core/HgChangeset.java --- a/src/org/tmatesoft/hg/core/HgChangeset.java Tue May 31 05:33:16 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgChangeset.java Wed Jun 01 05:44:25 2011 +0200 @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.List; -import org.tmatesoft.hg.core.HgLogCommand.FileRevision; import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgChangelog; import org.tmatesoft.hg.repo.HgRepository; @@ -49,7 +48,7 @@ private Nodeid nodeid; // - private List modifiedFiles, addedFiles; + private List modifiedFiles, addedFiles; private List deletedFiles; private int revNumber; private byte[] parent1, parent2; @@ -118,14 +117,14 @@ return rv; } - public List getModifiedFiles() { + public List getModifiedFiles() { if (modifiedFiles == null) { initFileChanges(); } return modifiedFiles; } - public List getAddedFiles() { + public List getAddedFiles() { if (addedFiles == null) { initFileChanges(); } @@ -182,8 +181,8 @@ private /*synchronized*/ void initFileChanges() { ArrayList deleted = new ArrayList(); - ArrayList modified = new ArrayList(); - ArrayList added = new ArrayList(); + ArrayList modified = new ArrayList(); + ArrayList added = new ArrayList(); HgStatusCollector.Record r = new HgStatusCollector.Record(); statusHelper.change(revNumber, r); final HgRepository repo = statusHelper.getRepo(); @@ -192,14 +191,14 @@ if (nid == null) { throw new HgBadStateException(); } - modified.add(new FileRevision(repo, nid, s)); + modified.add(new HgFileRevision(repo, nid, s)); } for (Path s : r.getAdded()) { Nodeid nid = r.nodeidAfterChange(s); if (nid == null) { throw new HgBadStateException(); } - added.add(new FileRevision(repo, nid, s)); + added.add(new HgFileRevision(repo, nid, s)); } for (Path s : r.getRemoved()) { // with Path from getRemoved, may just copy diff -r 0dd9da7489dc -r 1792b37650f2 src/org/tmatesoft/hg/core/HgFileRevision.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgFileRevision.java Wed Jun 01 05:44:25 2011 +0200 @@ -0,0 +1,60 @@ +/* + * 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.core; + +import java.io.IOException; + +import org.tmatesoft.hg.repo.HgDataFile; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.util.ByteChannel; +import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.Path; + +/** + * Keeps together information about specific file revision + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public final class HgFileRevision implements HgLogCommand.FileRevision { + private final HgRepository repo; + private final Nodeid revision; + private final Path path; + + public HgFileRevision(HgRepository hgRepo, Nodeid rev, Path p) { + if (hgRepo == null || rev == null || p == null) { + // since it's package local, it is our code to blame for non validated arguments + throw new HgBadStateException(); + } + repo = hgRepo; + revision = rev; + path = p; + } + + public Path getPath() { + return path; + } + public Nodeid getRevision() { + return revision; + } + public void putContentTo(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException { + HgDataFile fn = repo.getFileNode(path); + int localRevision = fn.getLocalRevision(revision); + fn.contentWithFilters(localRevision, sink); + } + +} diff -r 0dd9da7489dc -r 1792b37650f2 src/org/tmatesoft/hg/core/HgLogCommand.java --- a/src/org/tmatesoft/hg/core/HgLogCommand.java Tue May 31 05:33:16 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Wed Jun 01 05:44:25 2011 +0200 @@ -215,8 +215,8 @@ // even if we do not follow history, report file rename do { if (handler instanceof FileHistoryHandler) { - FileRevision src = new FileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName()); - FileRevision dst = new FileRevision(repo, fileNode.getRevision(0), fileNode.getPath()); + HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName()); + HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), fileNode.getPath()); try { ((FileHistoryHandler) handler).copy(src, dst); } catch (RuntimeException ex) { @@ -315,31 +315,13 @@ } } - public static final class FileRevision { - private final HgRepository repo; - private final Nodeid revision; - private final Path path; - - /*package-local*/FileRevision(HgRepository hgRepo, Nodeid rev, Path p) { - if (hgRepo == null || rev == null || p == null) { - // since it's package local, it is our code to blame for non validated arguments - throw new HgBadStateException(); - } - repo = hgRepo; - revision = rev; - path = p; - } - - public Path getPath() { - return path; - } - public Nodeid getRevision() { - return revision; - } - public void putContentTo(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException { - HgDataFile fn = repo.getFileNode(path); - int localRevision = fn.getLocalRevision(revision); - fn.contentWithFilters(localRevision, sink); - } + /** + * @deprecated pulled up, use {@link HgFileRevision} instead. + */ + @Deprecated + public interface FileRevision { + public abstract Path getPath(); + public abstract Nodeid getRevision(); + public abstract void putContentTo(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException; } } diff -r 0dd9da7489dc -r 1792b37650f2 src/org/tmatesoft/hg/core/HgManifestCommand.java --- a/src/org/tmatesoft/hg/core/HgManifestCommand.java Tue May 31 05:33:16 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgManifestCommand.java Wed Jun 01 05:44:25 2011 +0200 @@ -25,7 +25,6 @@ import java.util.LinkedList; import java.util.List; -import org.tmatesoft.hg.core.HgLogCommand.FileRevision; import org.tmatesoft.hg.repo.HgManifest; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.Path; @@ -124,7 +123,7 @@ public interface Handler { void begin(Nodeid manifestRevision); void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs - void file(FileRevision fileRevision); // XXX allow to check p is invalid (df.exists()) + void file(HgLogCommand.FileRevision fileRevision); // XXX allow to check p is invalid (df.exists()) void end(Nodeid manifestRevision); } @@ -134,7 +133,7 @@ // However, once HgManifest.Inspector switches to Path objects, perhaps global Path pool // might be more effective? private PathPool pathPool; - private List manifestContent; + private List manifestContent; private Nodeid manifestNodeid; public void start() { @@ -149,27 +148,27 @@ public boolean begin(int manifestRevision, Nodeid nid, int changelogRevision) { if (needDirs && manifestContent == null) { - manifestContent = new LinkedList(); + manifestContent = new LinkedList(); } visitor.begin(manifestNodeid = nid); return true; } public boolean end(int revision) { if (needDirs) { - LinkedHashMap> breakDown = new LinkedHashMap>(); - for (FileRevision fr : manifestContent) { + LinkedHashMap> breakDown = new LinkedHashMap>(); + for (HgFileRevision fr : manifestContent) { Path filePath = fr.getPath(); Path dirPath = pathPool.parent(filePath); - LinkedList revs = breakDown.get(dirPath); + LinkedList revs = breakDown.get(dirPath); if (revs == null) { - revs = new LinkedList(); + revs = new LinkedList(); breakDown.put(dirPath, revs); } revs.addLast(fr); } for (Path dir : breakDown.keySet()) { visitor.dir(dir); - for (FileRevision fr : breakDown.get(dir)) { + for (HgFileRevision fr : breakDown.get(dir)) { visitor.file(fr); } } @@ -184,7 +183,7 @@ if (matcher != null && !matcher.accept(p)) { return true; } - FileRevision fr = new FileRevision(repo, nid, p); + HgFileRevision fr = new HgFileRevision(repo, nid, p); if (needDirs) { manifestContent.add(fr); } else { diff -r 0dd9da7489dc -r 1792b37650f2 src/org/tmatesoft/hg/repo/HgDirstate.java --- a/src/org/tmatesoft/hg/repo/HgDirstate.java Tue May 31 05:33:16 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgDirstate.java Wed Jun 01 05:44:25 2011 +0200 @@ -23,6 +23,8 @@ import java.util.Map; import java.util.TreeSet; +import org.tmatesoft.hg.core.HgBadStateException; +import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.DataAccess; import org.tmatesoft.hg.internal.DataAccessProvider; import org.tmatesoft.hg.util.Path; @@ -44,6 +46,7 @@ private Map added; private Map removed; private Map merged; + private Nodeid p1, p2; /*package-local*/ HgDirstate() { // empty instance @@ -71,9 +74,10 @@ removed = new LinkedHashMap(); merged = new LinkedHashMap(); try { - // XXX skip(40) if we don't need these? byte[] parents = new byte[40]; da.readBytes(parents, 0, 40); + p1 = Nodeid.fromBinary(parents, 0); + p2 = Nodeid.fromBinary(parents, 20); parents = null; // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only while (!da.isEmpty()) { @@ -115,6 +119,41 @@ } } + // do not read whole dirstate if all we need is WC parent information + private void readParents() { + if (dirstateFile == null || !dirstateFile.exists()) { + return; + } + DataAccess da = accessProvider.create(dirstateFile); + if (da.isEmpty()) { + return; + } + try { + byte[] parents = new byte[40]; + da.readBytes(parents, 0, 40); + p1 = Nodeid.fromBinary(parents, 0); + p2 = Nodeid.fromBinary(parents, 20); + parents = null; + } catch (IOException ex) { + throw new HgBadStateException(ex); // XXX in fact, our exception is not the best solution here. + } finally { + da.done(); + } + } + + /** + * @return array of length 2 with working copy parents, non null. + */ + public Nodeid[] parents() { + if (p1 == null) { + readParents(); + } + Nodeid[] rv = new Nodeid[2]; + rv[0] = p1; + rv[1] = p2; + return rv; + } + // new, modifiable collection /*package-local*/ TreeSet all() { read(); diff -r 0dd9da7489dc -r 1792b37650f2 src/org/tmatesoft/hg/repo/HgMergeState.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/repo/HgMergeState.java Wed Jun 01 05:44:25 2011 +0200 @@ -0,0 +1,155 @@ +/* + * 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.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.tmatesoft.hg.core.HgBadStateException; +import org.tmatesoft.hg.core.HgFileRevision; +import org.tmatesoft.hg.core.HgLogCommand.FileRevision; +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.internal.Pool; +import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector; +import org.tmatesoft.hg.util.Path; +import org.tmatesoft.hg.util.PathPool; +import org.tmatesoft.hg.util.PathRewrite; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class HgMergeState { + private Nodeid wcp1, wcp2; + + public enum Kind { + Resolved, Unresolved; + } + + public static class Entry { + private final Kind state; + private final HgFileRevision parent1; + private final HgFileRevision parent2; + private final HgFileRevision ancestor; + private final Path wcFile; + + /*package-local*/Entry(Kind s, Path actualCopy, HgFileRevision p1, HgFileRevision p2, HgFileRevision ca) { + if (p1 == null || p2 == null || ca == null || actualCopy == null) { + throw new IllegalArgumentException(); + } + state = s; + wcFile = actualCopy; + parent1 = p1; + parent2 = p2; + ancestor = ca; + } + + public Kind getState() { + return state; + } + public Path getActualFile() { + return wcFile; + } + public HgFileRevision getFirstParent() { + return parent1; + } + public HgFileRevision getSecondParent() { + return parent2; + } + public HgFileRevision getCommonAncestor() { + return ancestor; + } + } + + private final HgRepository repo; + private Entry[] entries; + + HgMergeState(HgRepository hgRepo) { + repo = hgRepo; + } + + public void refresh() throws IOException/*XXX it's unlikely caller can do anything reasonable about IOException */ { + entries = null; + final File f = new File(repo.getRepositoryRoot(), "merge/state"); + if (!f.canRead()) { + // empty state + return; + } + Nodeid[] wcParents = repo.loadDirstate().parents(); + wcp1 = wcParents[0]; wcp2 = wcParents[1]; + ArrayList result = new ArrayList(); + PathPool pathPool = new PathPool(new PathRewrite.Empty()); + Pool nodeidPool = new Pool(); + Pool fnamePool = new Pool(); + final ManifestRevisionInspector m1 = new ManifestRevisionInspector(nodeidPool, fnamePool); + final ManifestRevisionInspector m2 = new ManifestRevisionInspector(nodeidPool, fnamePool); + final int rp1 = repo.getChangelog().getLocalRevision(wcp1); + final int rp2 = repo.getChangelog().getLocalRevision(wcp2); + repo.getManifest().walk(rp1, rp1, m1); + repo.getManifest().walk(rp2, rp2, m2); + BufferedReader br = new BufferedReader(new FileReader(f)); + String s = br.readLine(); + Nodeid n = Nodeid.fromAscii(s); + if (!wcp1.equals(n)) { + throw new AssertionError("I assume merge/state records revision of the wc we merge into"); + } + while ((s = br.readLine()) != null) { + String[] r = s.split("\\00"); + HgFileRevision p1 = new HgFileRevision(repo, m1.nodeid(r[3]), pathPool.path(r[3])); + HgFileRevision ca = new HgFileRevision(repo, Nodeid.fromAscii(r[5]), pathPool.path(r[4])); + HgFileRevision p2 = new HgFileRevision(repo, m2.nodeid(r[6]), pathPool.path(r[6])); + final Kind k; + if ("u".equals(r[1])) { + k = Kind.Unresolved; + } else if ("r".equals(r[1])) { + k = Kind.Resolved; + } else { + throw new HgBadStateException(r[1]); + } + Entry e = new Entry(k, pathPool.path(r[0]), p1, p2, ca); + result.add(e); + } + entries = result.toArray(new Entry[result.size()]); + br.close(); + pathPool.clear(); + } + + public Nodeid getFirstParent() { + if (wcp1 == null) { + throw new HgBadStateException("Call #refresh() first"); + } + return wcp1; + } + + public Nodeid getSecondParent() { + if (wcp2 == null) { + throw new HgBadStateException("Call #refresh() first"); + } + return wcp2; + } + + public List getConflicts() { + return entries == null ? Collections.emptyList() : Arrays.asList(entries); + } +} diff -r 0dd9da7489dc -r 1792b37650f2 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Tue May 31 05:33:16 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Wed Jun 01 05:44:25 2011 +0200 @@ -26,6 +26,7 @@ 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; @@ -64,6 +65,7 @@ private HgManifest manifest; private HgTags tags; private HgBranches branches; + private HgMergeState mergeState; // XXX perhaps, shall enable caching explicitly private final HashMap> streamsCache = new HashMap>(); @@ -138,7 +140,7 @@ return this.manifest; } - public final HgTags getTags() { + public HgTags getTags() { if (tags == null) { tags = new HgTags(); try { @@ -151,13 +153,21 @@ return tags; } - public final HgBranches getBranches() { + public HgBranches getBranches() { if (branches == null) { branches = new HgBranches(this); branches.collect(ProgressSupport.Factory.get(null)); } return branches; } + + @Experimental(reason="Perhaps, shall not cache instance, and provide loadMergeState as it may change often") + public HgMergeState getMergeState() { + if (mergeState == null) { + mergeState = new HgMergeState(this); + } + return mergeState; + } public HgDataFile getFileNode(String path) { String nPath = normalizePath.rewrite(path); diff -r 0dd9da7489dc -r 1792b37650f2 src/org/tmatesoft/hg/util/PathPool.java --- a/src/org/tmatesoft/hg/util/PathPool.java Tue May 31 05:33:16 2011 +0200 +++ b/src/org/tmatesoft/hg/util/PathPool.java Wed Jun 01 05:44:25 2011 +0200 @@ -41,6 +41,7 @@ } // pipes path object through cache to reuse instance, if possible + // TODO unify with Pool public Path path(Path p) { String s = pathRewrite.rewrite(p.toString()); Path cached = get(s, false); @@ -63,6 +64,11 @@ } return get("", true); } + + // invoke when path pool is no longer in use, to ease gc work + public void clear() { + cache.clear(); + } private Path get(String p, boolean create) { SoftReference sr = cache.get(p);