Mercurial > hg4j
comparison src/org/tmatesoft/hg/core/HgMergeCommand.java @ 704:7743a9c10bfa
Merge command introduced
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Wed, 14 Aug 2013 20:07:26 +0200 |
| parents | |
| children | b4242b7e7dfe |
comparison
equal
deleted
inserted
replaced
| 703:7839ff0bfd78 | 704:7743a9c10bfa |
|---|---|
| 1 /* | |
| 2 * Copyright (c) 2013 TMate Software Ltd | |
| 3 * | |
| 4 * This program is free software; you can redistribute it and/or modify | |
| 5 * it under the terms of the GNU General Public License as published by | |
| 6 * the Free Software Foundation; version 2 of the License. | |
| 7 * | |
| 8 * This program is distributed in the hope that it will be useful, | |
| 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 11 * GNU General Public License for more details. | |
| 12 * | |
| 13 * For information on how to redistribute this software under | |
| 14 * the terms of a license other than GNU General Public License | |
| 15 * contact TMate Software at support@hg4j.com | |
| 16 */ | |
| 17 package org.tmatesoft.hg.core; | |
| 18 | |
| 19 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; | |
| 20 | |
| 21 import java.io.InputStream; | |
| 22 | |
| 23 import org.tmatesoft.hg.internal.Callback; | |
| 24 import org.tmatesoft.hg.internal.CsetParamKeeper; | |
| 25 import org.tmatesoft.hg.internal.Experimental; | |
| 26 import org.tmatesoft.hg.internal.ManifestRevision; | |
| 27 import org.tmatesoft.hg.internal.Pool; | |
| 28 import org.tmatesoft.hg.repo.HgChangelog; | |
| 29 import org.tmatesoft.hg.repo.HgParentChildMap; | |
| 30 import org.tmatesoft.hg.repo.HgRepository; | |
| 31 import org.tmatesoft.hg.repo.HgRepositoryLock; | |
| 32 import org.tmatesoft.hg.repo.HgRevisionMap; | |
| 33 import org.tmatesoft.hg.repo.HgRuntimeException; | |
| 34 import org.tmatesoft.hg.util.CancelledException; | |
| 35 import org.tmatesoft.hg.util.Path; | |
| 36 | |
| 37 /** | |
| 38 * Merge two revisions, 'hg merge REV' counterpart | |
| 39 * | |
| 40 * @author Artem Tikhomirov | |
| 41 * @author TMate Software Ltd. | |
| 42 * @since 1.2 | |
| 43 */ | |
| 44 @Experimental(reason="Provisional API. Work in progress") | |
| 45 public class HgMergeCommand extends HgAbstractCommand<HgMergeCommand> { | |
| 46 | |
| 47 private final HgRepository repo; | |
| 48 private int firstCset, secondCset, ancestorCset; | |
| 49 | |
| 50 public HgMergeCommand(HgRepository hgRepo) { | |
| 51 repo = hgRepo; | |
| 52 firstCset = secondCset = ancestorCset = BAD_REVISION; | |
| 53 } | |
| 54 | |
| 55 public HgMergeCommand changeset(Nodeid changeset) throws HgBadArgumentException { | |
| 56 initHeadsAndAncestor(new CsetParamKeeper(repo).set(changeset).get()); | |
| 57 return this; | |
| 58 } | |
| 59 | |
| 60 public HgMergeCommand changeset(int revisionIndex) throws HgBadArgumentException { | |
| 61 initHeadsAndAncestor(new CsetParamKeeper(repo).set(revisionIndex).get()); | |
| 62 return this; | |
| 63 } | |
| 64 | |
| 65 public void execute(Mediator mediator) throws HgCallbackTargetException, HgRepositoryLockException, HgLibraryFailureException, CancelledException { | |
| 66 if (firstCset == BAD_REVISION || secondCset == BAD_REVISION || ancestorCset == BAD_REVISION) { | |
| 67 throw new IllegalArgumentException("Merge heads and their ancestors are not initialized"); | |
| 68 } | |
| 69 final HgRepositoryLock wdLock = repo.getWorkingDirLock(); | |
| 70 wdLock.acquire(); | |
| 71 try { | |
| 72 Pool<Nodeid> cacheRevs = new Pool<Nodeid>(); | |
| 73 Pool<Path> cacheFiles = new Pool<Path>(); | |
| 74 ManifestRevision m1, m2, ma; | |
| 75 m1 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, firstCset); | |
| 76 m2 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, secondCset); | |
| 77 ma = new ManifestRevision(cacheRevs, cacheFiles).init(repo, ancestorCset); | |
| 78 ResolverImpl resolver = new ResolverImpl(); | |
| 79 for (Path f : m1.files()) { | |
| 80 Nodeid fileRevBase, fileRevA, fileRevB; | |
| 81 if (m2.contains(f)) { | |
| 82 fileRevA = m1.nodeid(f); | |
| 83 fileRevB = m2.nodeid(f); | |
| 84 fileRevBase = ma.contains(f) ? ma.nodeid(f) : null; | |
| 85 if (fileRevA.equals(fileRevB)) { | |
| 86 HgFileRevision fr = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | |
| 87 mediator.same(fr, fr, resolver); | |
| 88 } else if (fileRevBase == fileRevA) { | |
| 89 assert fileRevBase != null; | |
| 90 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | |
| 91 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); | |
| 92 mediator.fastForwardB(frBase, frSecond, resolver); | |
| 93 } else if (fileRevBase == fileRevB) { | |
| 94 assert fileRevBase != null; | |
| 95 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | |
| 96 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | |
| 97 mediator.fastForwardA(frBase, frFirst, resolver); | |
| 98 } else { | |
| 99 HgFileRevision frBase = fileRevBase == null ? null : new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | |
| 100 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | |
| 101 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); | |
| 102 mediator.resolve(frBase, frFirst, frSecond, resolver); | |
| 103 } | |
| 104 } else { | |
| 105 // m2 doesn't contain the file, either new in m1, or deleted in m2 | |
| 106 HgFileRevision frFirst = new HgFileRevision(repo, m1.nodeid(f), m1.flags(f), f); | |
| 107 if (ma.contains(f)) { | |
| 108 // deleted in m2 | |
| 109 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); | |
| 110 mediator.onlyA(frBase, frFirst, resolver); | |
| 111 } else { | |
| 112 // new in m1 | |
| 113 mediator.newInA(frFirst, resolver); | |
| 114 } | |
| 115 } | |
| 116 resolver.apply(); | |
| 117 } // for m1 files | |
| 118 for (Path f : m2.files()) { | |
| 119 if (m1.contains(f)) { | |
| 120 continue; | |
| 121 } | |
| 122 HgFileRevision frSecond= new HgFileRevision(repo, m2.nodeid(f), m2.flags(f), f); | |
| 123 // file in m2 is either new or deleted in m1 | |
| 124 if (ma.contains(f)) { | |
| 125 // deleted in m1 | |
| 126 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); | |
| 127 mediator.onlyB(frBase, frSecond, resolver); | |
| 128 } else { | |
| 129 // new in m2 | |
| 130 mediator.newInB(frSecond, resolver); | |
| 131 } | |
| 132 resolver.apply(); | |
| 133 } | |
| 134 } catch (HgRuntimeException ex) { | |
| 135 throw new HgLibraryFailureException(ex); | |
| 136 } finally { | |
| 137 wdLock.release(); | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 private void initHeadsAndAncestor(int csetIndexB) throws HgBadArgumentException { | |
| 142 firstCset = secondCset = ancestorCset = BAD_REVISION; | |
| 143 if (csetIndexB == HgRepository.BAD_REVISION) { | |
| 144 throw new HgBadArgumentException("Need valid second head for merge", null); | |
| 145 } | |
| 146 // TODO cache/share parent-child map, e.g. right in HgChangelog?! #getOrCreate | |
| 147 HgParentChildMap<HgChangelog> pmap = new HgParentChildMap<HgChangelog>(repo.getChangelog()); | |
| 148 pmap.init(); | |
| 149 final HgRevisionMap<HgChangelog> rmap = pmap.getRevisionMap(); | |
| 150 final Nodeid csetA = repo.getWorkingCopyParents().first(); | |
| 151 final Nodeid csetB = rmap.revision(csetIndexB); | |
| 152 final Nodeid ancestor = pmap.ancestor(csetA, csetB); | |
| 153 assert !ancestor.isNull(); | |
| 154 if (ancestor.equals(csetA) || ancestor.equals(csetB)) { | |
| 155 throw new HgBadArgumentException(String.format("Revisions %s and %s are on the same line of descent, use update instead of merge", csetA.shortNotation(), csetB.shortNotation()), null); | |
| 156 } | |
| 157 firstCset = rmap.revisionIndex(csetA); | |
| 158 secondCset = csetIndexB; | |
| 159 ancestorCset = rmap.revisionIndex(ancestor); | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * This is the way client code takes part in the merge process | |
| 164 */ | |
| 165 @Experimental(reason="Provisional API. Work in progress") | |
| 166 @Callback | |
| 167 public interface Mediator { | |
| 168 public void same(HgFileRevision first, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException; | |
| 169 public void onlyA(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | |
| 170 public void onlyB(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | |
| 171 public void newInA(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | |
| 172 public void newInB(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | |
| 173 public void fastForwardA(HgFileRevision base, HgFileRevision first, Resolver resolver) throws HgCallbackTargetException; | |
| 174 public void fastForwardB(HgFileRevision base, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException; | |
| 175 public void resolve(HgFileRevision base, HgFileRevision first, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException; | |
| 176 } | |
| 177 | |
| 178 /** | |
| 179 * Clients shall not implement this interface. | |
| 180 * They use this API from inside {@link Mediator#resolve(HgFileRevision, HgFileRevision, HgFileRevision, Resolver)} | |
| 181 */ | |
| 182 @Experimental(reason="Provisional API. Work in progress") | |
| 183 public interface Resolver { | |
| 184 public void use(HgFileRevision rev); | |
| 185 public void use(InputStream content); | |
| 186 public void unresolved(); // record the file for later processing by 'hg resolve' | |
| 187 } | |
| 188 | |
| 189 private static class ResolverImpl implements Resolver { | |
| 190 void apply() { | |
| 191 } | |
| 192 | |
| 193 public void use(HgFileRevision rev) { | |
| 194 // TODO Auto-generated method stub | |
| 195 } | |
| 196 | |
| 197 public void use(InputStream content) { | |
| 198 // TODO Auto-generated method stub | |
| 199 } | |
| 200 | |
| 201 public void unresolved() { | |
| 202 // TODO Auto-generated method stub | |
| 203 } | |
| 204 } | |
| 205 } |
