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 } |