##// END OF EJS Templates
merge: pull user messages out to hg.py...
Matt Mackall -
r3316:39fd6e82 default
parent child Browse files
Show More
@@ -1,231 +1,256 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from node import *
10 10 from repo import *
11 11 from demandload import *
12 12 from i18n import gettext as _
13 13 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
14 14 demandload(globals(), "errno lock os shutil util merge@_merge verify@_verify")
15 15
16 16 def _local(path):
17 17 return (os.path.isfile(util.drop_scheme('file', path)) and
18 18 bundlerepo or localrepo)
19 19
20 20 schemes = {
21 21 'bundle': bundlerepo,
22 22 'file': _local,
23 23 'hg': httprepo,
24 24 'http': httprepo,
25 25 'https': httprepo,
26 26 'old-http': statichttprepo,
27 27 'ssh': sshrepo,
28 28 'static-http': statichttprepo,
29 29 }
30 30
31 31 def _lookup(path):
32 32 scheme = 'file'
33 33 if path:
34 34 c = path.find(':')
35 35 if c > 0:
36 36 scheme = path[:c]
37 37 thing = schemes.get(scheme) or schemes['file']
38 38 try:
39 39 return thing(path)
40 40 except TypeError:
41 41 return thing
42 42
43 43 def islocal(repo):
44 44 '''return true if repo or path is local'''
45 45 if isinstance(repo, str):
46 46 try:
47 47 return _lookup(repo).islocal(repo)
48 48 except AttributeError:
49 49 return False
50 50 return repo.local()
51 51
52 52 repo_setup_hooks = []
53 53
54 54 def repository(ui, path='', create=False):
55 55 """return a repository object for the specified path"""
56 56 repo = _lookup(path).instance(ui, path, create)
57 57 for hook in repo_setup_hooks:
58 58 hook(ui, repo)
59 59 return repo
60 60
61 61 def defaultdest(source):
62 62 '''return default destination of clone if none is given'''
63 63 return os.path.basename(os.path.normpath(source))
64 64
65 65 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
66 66 stream=False):
67 67 """Make a copy of an existing repository.
68 68
69 69 Create a copy of an existing repository in a new directory. The
70 70 source and destination are URLs, as passed to the repository
71 71 function. Returns a pair of repository objects, the source and
72 72 newly created destination.
73 73
74 74 The location of the source is added to the new repository's
75 75 .hg/hgrc file, as the default to be used for future pulls and
76 76 pushes.
77 77
78 78 If an exception is raised, the partly cloned/updated destination
79 79 repository will be deleted.
80 80
81 81 Arguments:
82 82
83 83 source: repository object or URL
84 84
85 85 dest: URL of destination repository to create (defaults to base
86 86 name of source repository)
87 87
88 88 pull: always pull from source repository, even in local case
89 89
90 90 stream: stream raw data uncompressed from repository (fast over
91 91 LAN, slow over WAN)
92 92
93 93 rev: revision to clone up to (implies pull=True)
94 94
95 95 update: update working directory after clone completes, if
96 96 destination is local repository
97 97 """
98 98 if isinstance(source, str):
99 99 src_repo = repository(ui, source)
100 100 else:
101 101 src_repo = source
102 102 source = src_repo.url()
103 103
104 104 if dest is None:
105 105 dest = defaultdest(source)
106 106
107 107 def localpath(path):
108 108 if path.startswith('file://'):
109 109 return path[7:]
110 110 if path.startswith('file:'):
111 111 return path[5:]
112 112 return path
113 113
114 114 dest = localpath(dest)
115 115 source = localpath(source)
116 116
117 117 if os.path.exists(dest):
118 118 raise util.Abort(_("destination '%s' already exists") % dest)
119 119
120 120 class DirCleanup(object):
121 121 def __init__(self, dir_):
122 122 self.rmtree = shutil.rmtree
123 123 self.dir_ = dir_
124 124 def close(self):
125 125 self.dir_ = None
126 126 def __del__(self):
127 127 if self.dir_:
128 128 self.rmtree(self.dir_, True)
129 129
130 130 dest_repo = repository(ui, dest, create=True)
131 131
132 132 dest_path = None
133 133 dir_cleanup = None
134 134 if dest_repo.local():
135 135 dest_path = os.path.realpath(dest_repo.root)
136 136 dir_cleanup = DirCleanup(dest_path)
137 137
138 138 abspath = source
139 139 copy = False
140 140 if src_repo.local() and dest_repo.local():
141 141 abspath = os.path.abspath(source)
142 142 copy = not pull and not rev
143 143
144 144 src_lock, dest_lock = None, None
145 145 if copy:
146 146 try:
147 147 # we use a lock here because if we race with commit, we
148 148 # can end up with extra data in the cloned revlogs that's
149 149 # not pointed to by changesets, thus causing verify to
150 150 # fail
151 151 src_lock = src_repo.lock()
152 152 except lock.LockException:
153 153 copy = False
154 154
155 155 if copy:
156 156 # we lock here to avoid premature writing to the target
157 157 dest_lock = lock.lock(os.path.join(dest_path, ".hg", "lock"))
158 158
159 159 # we need to remove the (empty) data dir in dest so copyfiles
160 160 # can do its work
161 161 os.rmdir(os.path.join(dest_path, ".hg", "data"))
162 162 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
163 163 for f in files.split():
164 164 src = os.path.join(source, ".hg", f)
165 165 dst = os.path.join(dest_path, ".hg", f)
166 166 try:
167 167 util.copyfiles(src, dst)
168 168 except OSError, inst:
169 169 if inst.errno != errno.ENOENT:
170 170 raise
171 171
172 172 # we need to re-init the repo after manually copying the data
173 173 # into it
174 174 dest_repo = repository(ui, dest)
175 175
176 176 else:
177 177 revs = None
178 178 if rev:
179 179 if not src_repo.local():
180 180 raise util.Abort(_("clone by revision not supported yet "
181 181 "for remote repositories"))
182 182 revs = [src_repo.lookup(r) for r in rev]
183 183
184 184 if dest_repo.local():
185 185 dest_repo.clone(src_repo, heads=revs, stream=stream)
186 186 elif src_repo.local():
187 187 src_repo.push(dest_repo, revs=revs)
188 188 else:
189 189 raise util.Abort(_("clone from remote to remote not supported"))
190 190
191 191 if src_lock:
192 192 src_lock.release()
193 193
194 194 if dest_repo.local():
195 195 fp = dest_repo.opener("hgrc", "w", text=True)
196 196 fp.write("[paths]\n")
197 197 fp.write("default = %s\n" % abspath)
198 198 fp.close()
199 199
200 200 if dest_lock:
201 201 dest_lock.release()
202 202
203 203 if update:
204 _merge.update(dest_repo, dest_repo.changelog.tip())
204 _update(dest_repo, dest_repo.changelog.tip())
205 205 if dir_cleanup:
206 206 dir_cleanup.close()
207 207
208 208 return src_repo, dest_repo
209 209
210 def _showstats(repo, stats):
211 stats = ((stats[0], _("updated")),
212 (stats[1], _("merged")),
213 (stats[2], _("removed")),
214 (stats[3], _("unresolved")))
215 note = ", ".join([_("%d files %s") % s for s in stats])
216 repo.ui.status("%s\n" % note)
217
218 def _update(repo, node): return update(repo, node)
219
210 220 def update(repo, node):
211 221 """update the working directory to node, merging linear changes"""
212 return _merge.update(repo, node)
222 stats = _merge.update(repo, node, False, False, None, None)
223 _showstats(repo, stats)
224 if stats[3]:
225 repo.ui.status(_("There are unresolved merges with"
226 " locally modified files.\n"))
227 return stats[3]
213 228
214 229 def clean(repo, node, wlock=None, show_stats=True):
215 230 """forcibly switch the working directory to node, clobbering changes"""
216 return _merge.update(repo, node, force=True, wlock=wlock,
217 show_stats=show_stats)
231 stats = _merge.update(repo, node, False, True, None, wlock)
232 if show_stats: _showstats(repo, stats)
233 return stats[3]
218 234
219 235 def merge(repo, node, force=None, remind=True, wlock=None):
220 236 """branch merge with node, resolving changes"""
221 return _merge.update(repo, node, branchmerge=True, force=force,
222 remind=remind, wlock=wlock)
237 stats = _merge.update(repo, node, True, force, False, wlock)
238 _showstats(repo, stats)
239 if stats[3]:
240 pl = repo.parents()
241 repo.ui.status(_("There are unresolved merges,"
242 " you can redo the full merge using:\n"
243 " hg update -C %s\n"
244 " hg merge %s\n"
245 % (pl[0].rev(), pl[1].rev())))
246 elif remind:
247 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
248 return stats[3]
223 249
224 250 def revert(repo, node, choose, wlock):
225 251 """revert changes to revision in node without updating dirstate"""
226 return _merge.update(repo, node, force=True, partial=choose,
227 show_stats=False, wlock=wlock)
252 return _merge.update(repo, node, False, True, choose, wlock)[3]
228 253
229 254 def verify(repo):
230 255 """verify the consistency of a repository"""
231 256 return _verify.verify(repo)
@@ -1,435 +1,411 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import gettext as _
10 10 from demandload import *
11 11 demandload(globals(), "errno util os tempfile")
12 12
13 13 def filemerge(repo, fw, fo, wctx, mctx):
14 14 """perform a 3-way merge in the working directory
15 15
16 16 fw = filename in the working directory
17 17 fo = filename in other parent
18 18 wctx, mctx = working and merge changecontexts
19 19 """
20 20
21 21 def temp(prefix, ctx):
22 22 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
23 23 (fd, name) = tempfile.mkstemp(prefix=pre)
24 24 f = os.fdopen(fd, "wb")
25 25 repo.wwrite(ctx.path(), ctx.data(), f)
26 26 f.close()
27 27 return name
28 28
29 29 fcm = wctx.filectx(fw)
30 30 fco = mctx.filectx(fo)
31 31
32 32 if not fco.cmp(fcm.data()): # files identical?
33 33 return 0
34 34
35 35 fca = fcm.ancestor(fco)
36 36 if not fca:
37 37 fca = repo.filectx(fw, fileid=-1)
38 38 a = repo.wjoin(fw)
39 39 b = temp("base", fca)
40 40 c = temp("other", fco)
41 41
42 42 if fw != fo:
43 43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
44 44 else:
45 45 repo.ui.status(_("merging %s\n") % fw)
46 46
47 47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
48 48
49 49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
50 50 or "hgmerge")
51 51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
52 52 environ={'HG_FILE': fw,
53 53 'HG_MY_NODE': str(wctx.parents()[0]),
54 54 'HG_OTHER_NODE': str(mctx)})
55 55 if r:
56 56 repo.ui.warn(_("merging %s failed!\n") % fw)
57 57
58 58 os.unlink(b)
59 59 os.unlink(c)
60 60 return r
61 61
62 62 def checkunknown(wctx, mctx):
63 63 "check for collisions between unknown files and files in mctx"
64 64 man = mctx.manifest()
65 65 for f in wctx.unknown():
66 66 if f in man:
67 67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
68 68 raise util.Abort(_("'%s' already exists in the working"
69 69 " dir and differs from remote") % f)
70 70
71 71 def forgetremoved(wctx, mctx):
72 72 """
73 73 Forget removed files
74 74
75 75 If we're jumping between revisions (as opposed to merging), and if
76 76 neither the working directory nor the target rev has the file,
77 77 then we need to remove it from the dirstate, to prevent the
78 78 dirstate from listing the file when it is no longer in the
79 79 manifest.
80 80 """
81 81
82 82 action = []
83 83 man = mctx.manifest()
84 84 for f in wctx.deleted() + wctx.removed():
85 85 if f not in man:
86 86 action.append((f, "f"))
87 87
88 88 return action
89 89
90 90 def nonoverlap(d1, d2):
91 91 "Return list of elements in d1 not in d2"
92 92
93 93 l = []
94 94 for d in d1:
95 95 if d not in d2:
96 96 l.append(d)
97 97
98 98 l.sort()
99 99 return l
100 100
101 101 def findold(fctx, limit):
102 102 "find files that path was copied from, back to linkrev limit"
103 103
104 104 old = {}
105 105 orig = fctx.path()
106 106 visit = [fctx]
107 107 while visit:
108 108 fc = visit.pop()
109 109 if fc.rev() < limit:
110 110 continue
111 111 if fc.path() != orig and fc.path() not in old:
112 112 old[fc.path()] = 1
113 113 visit += fc.parents()
114 114
115 115 old = old.keys()
116 116 old.sort()
117 117 return old
118 118
119 119 def findcopies(repo, m1, m2, limit):
120 120 """
121 121 Find moves and copies between m1 and m2 back to limit linkrev
122 122 """
123 123
124 124 if not repo.ui.config("merge", "followcopies"):
125 125 return {}
126 126
127 127 # avoid silly behavior for update from empty dir
128 128 if not m1:
129 129 return {}
130 130
131 131 dcopies = repo.dirstate.copies()
132 132 copy = {}
133 133 match = {}
134 134 u1 = nonoverlap(m1, m2)
135 135 u2 = nonoverlap(m2, m1)
136 136 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
137 137
138 138 def checkpair(c, f2, man):
139 139 ''' check if an apparent pair actually matches '''
140 140 c2 = ctx(f2, man[f2])
141 141 ca = c.ancestor(c2)
142 142 if ca and ca.path() == c.path() or ca.path() == c2.path():
143 143 copy[c.path()] = f2
144 144 copy[f2] = c.path()
145 145
146 146 for f in u1:
147 147 c = ctx(dcopies.get(f, f), m1[f])
148 148 for of in findold(c, limit):
149 149 if of in m2:
150 150 checkpair(c, of, m2)
151 151 else:
152 152 match.setdefault(of, []).append(f)
153 153
154 154 for f in u2:
155 155 c = ctx(f, m2[f])
156 156 for of in findold(c, limit):
157 157 if of in m1:
158 158 checkpair(c, of, m1)
159 159 elif of in match:
160 160 for mf in match[of]:
161 161 checkpair(c, mf, m1)
162 162
163 163 return copy
164 164
165 165 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
166 166 """
167 167 Merge p1 and p2 with ancestor ma and generate merge action list
168 168
169 169 overwrite = whether we clobber working files
170 170 partial = function to filter file lists
171 171 """
172 172
173 173 repo.ui.note(_("resolving manifests\n"))
174 174 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
175 175 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
176 176
177 177 m1 = p1.manifest()
178 178 m2 = p2.manifest()
179 179 ma = pa.manifest()
180 180 backwards = (pa == p2)
181 181 action = []
182 182 copy = {}
183 183
184 184 def fmerge(f, f2=None, fa=None):
185 185 """merge executable flags"""
186 186 if not f2:
187 187 f2 = f
188 188 fa = f
189 189 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
190 190 return ((a^b) | (a^c)) ^ a
191 191
192 192 def act(msg, m, f, *args):
193 193 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
194 194 action.append((f, m) + args)
195 195
196 196 if not (backwards or overwrite):
197 197 copy = findcopies(repo, m1, m2, pa.rev())
198 198
199 199 # Compare manifests
200 200 for f, n in m1.iteritems():
201 201 if partial and not partial(f):
202 202 continue
203 203 if f in m2:
204 204 # are files different?
205 205 if n != m2[f]:
206 206 a = ma.get(f, nullid)
207 207 # are both different from the ancestor?
208 208 if not overwrite and n != a and m2[f] != a:
209 209 act("versions differ", "m", f, f, f, fmerge(f), False)
210 210 # are we clobbering?
211 211 # is remote's version newer?
212 212 # or are we going back in time and clean?
213 213 elif overwrite or m2[f] != a or (backwards and not n[20:]):
214 214 act("remote is newer", "g", f, m2.execf(f))
215 215 # local is newer, not overwrite, check mode bits
216 216 elif fmerge(f) != m1.execf(f):
217 217 act("update permissions", "e", f, m2.execf(f))
218 218 # contents same, check mode bits
219 219 elif m1.execf(f) != m2.execf(f):
220 220 if overwrite or fmerge(f) != m1.execf(f):
221 221 act("update permissions", "e", f, m2.execf(f))
222 222 elif f in copy:
223 223 f2 = copy[f]
224 224 if f in ma: # case 3,20 A/B/A
225 225 act("remote moved", "m", f, f2, f2, fmerge(f, f2, f), True)
226 226 else:
227 227 if f2 in m1: # case 2 A,B/B/B
228 228 act("local copied", "m",
229 229 f, f2, f, fmerge(f, f2, f2), False)
230 230 else: # case 4,21 A/B/B
231 231 act("local moved", "m",
232 232 f, f2, f, fmerge(f, f2, f2), False)
233 233 elif f in ma:
234 234 if n != ma[f] and not overwrite:
235 235 if repo.ui.prompt(
236 236 (_(" local changed %s which remote deleted\n") % f) +
237 237 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
238 238 act("prompt delete", "r", f)
239 239 else:
240 240 act("other deleted", "r", f)
241 241 else:
242 242 # file is created on branch or in working directory
243 243 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
244 244 act("remote deleted", "r", f)
245 245
246 246 for f, n in m2.iteritems():
247 247 if partial and not partial(f):
248 248 continue
249 249 if f in m1:
250 250 continue
251 251 if f in copy:
252 252 f2 = copy[f]
253 253 if f2 not in m2: # already seen
254 254 continue
255 255 # rename case 1, A/A,B/A
256 256 act("remote copied", "m", f2, f, f, fmerge(f2, f, f2), False)
257 257 elif f in ma:
258 258 if overwrite or backwards:
259 259 act("recreating", "g", f, m2.execf(f))
260 260 elif n != ma[f]:
261 261 if repo.ui.prompt(
262 262 (_("remote changed %s which local deleted\n") % f) +
263 263 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
264 264 act("prompt recreating", "g", f, m2.execf(f))
265 265 else:
266 266 act("remote created", "g", f, m2.execf(f))
267 267
268 268 return action
269 269
270 270 def applyupdates(repo, action, wctx, mctx):
271 271 "apply the merge action list to the working directory"
272 272
273 273 updated, merged, removed, unresolved = 0, 0, 0, 0
274 274 action.sort()
275 275 for a in action:
276 276 f, m = a[:2]
277 277 if f[0] == "/":
278 278 continue
279 279 if m == "r": # remove
280 280 repo.ui.note(_("removing %s\n") % f)
281 281 util.audit_path(f)
282 282 try:
283 283 util.unlink(repo.wjoin(f))
284 284 except OSError, inst:
285 285 if inst.errno != errno.ENOENT:
286 286 repo.ui.warn(_("update failed to remove %s: %s!\n") %
287 287 (f, inst.strerror))
288 288 removed +=1
289 289 elif m == "m": # merge
290 290 f2, fd, flag, move = a[2:]
291 291 if filemerge(repo, f, f2, wctx, mctx):
292 292 unresolved += 1
293 293 else:
294 merged += 1
294 295 if f != fd:
295 296 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
296 297 repo.wwrite(fd, repo.wread(f))
297 298 if move:
298 299 repo.ui.debug(_("removing %s\n") % f)
299 300 os.unlink(repo.wjoin(f))
300
301 301 util.set_exec(repo.wjoin(fd), flag)
302 merged += 1
303 302 elif m == "g": # get
304 303 flag = a[2]
305 304 repo.ui.note(_("getting %s\n") % f)
306 305 t = mctx.filectx(f).data()
307 306 repo.wwrite(f, t)
308 307 util.set_exec(repo.wjoin(f), flag)
309 308 updated += 1
310 309 elif m == "e": # exec
311 310 flag = a[2]
312 311 util.set_exec(repo.wjoin(f), flag)
313 312
314 313 return updated, merged, removed, unresolved
315 314
316 315 def recordupdates(repo, action, branchmerge, mctx):
317 316 "record merge actions to the dirstate"
318 317
319 318 for a in action:
320 319 f, m = a[:2]
321 320 if m == "r": # remove
322 321 if branchmerge:
323 322 repo.dirstate.update([f], 'r')
324 323 else:
325 324 repo.dirstate.forget([f])
326 325 elif m == "f": # forget
327 326 repo.dirstate.forget([f])
328 327 elif m == "g": # get
329 328 if branchmerge:
330 329 repo.dirstate.update([f], 'n', st_mtime=-1)
331 330 else:
332 331 repo.dirstate.update([f], 'n')
333 332 elif m == "m": # merge
334 333 f2, fd, flag, move = a[2:]
335 334 if branchmerge:
336 335 # We've done a branch merge, mark this file as merged
337 336 # so that we properly record the merger later
338 337 repo.dirstate.update([fd], 'm')
339 338 else:
340 339 # We've update-merged a locally modified file, so
341 340 # we set the dirstate to emulate a normal checkout
342 341 # of that file some time in the past. Thus our
343 342 # merge will appear as a normal local file
344 343 # modification.
345 344 f_len = mctx.filectx(f).size()
346 345 repo.dirstate.update([fd], 'n', st_size=f_len, st_mtime=-1)
347 346 if f != f2: # copy/rename
348 347 if move:
349 348 repo.dirstate.update([f], 'r')
350 349 if f != fd:
351 350 repo.dirstate.copy(f, fd)
352 351 else:
353 352 repo.dirstate.copy(f2, fd)
354 353
355 def update(repo, node, branchmerge=False, force=False, partial=None,
356 wlock=None, show_stats=True, remind=True):
354 def update(repo, node, branchmerge, force, partial, wlock):
357 355 """
358 356 Perform a merge between the working directory and the given node
359 357
360 358 branchmerge = whether to merge between branches
361 359 force = whether to force branch merging or file overwriting
362 360 partial = a function to filter file lists (dirstate not updated)
363 361 wlock = working dir lock, if already held
364 show_stats = whether to report merge statistics
365 remind = whether to remind about merge
366 362 """
367 363
368 364 if not wlock:
369 365 wlock = repo.wlock()
370 366
371 367 overwrite = force and not branchmerge
372 368 forcemerge = force and branchmerge
373 369 wc = repo.workingctx()
374 370 pl = wc.parents()
375 371 p1, p2 = pl[0], repo.changectx(node)
376 372 pa = p1.ancestor(p2)
377 373 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
378 374
379 375 ### check phase
380 376 if not overwrite and len(pl) > 1:
381 377 raise util.Abort(_("outstanding uncommitted merges"))
382 378 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
383 379 if branchmerge:
384 380 raise util.Abort(_("there is nothing to merge, just use "
385 381 "'hg update' or look at 'hg heads'"))
386 382 elif not (overwrite or branchmerge):
387 383 raise util.Abort(_("update spans branches, use 'hg merge' "
388 384 "or 'hg update -C' to lose changes"))
389 385 if branchmerge and not forcemerge:
390 386 if wc.modified() or wc.added() or wc.removed():
391 387 raise util.Abort(_("outstanding uncommitted changes"))
392 388
393 389 ### calculate phase
394 390 action = []
395 391 if not force:
396 392 checkunknown(wc, p2)
397 393 if not branchmerge:
398 394 action += forgetremoved(wc, p2)
399 395 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
400 396
401 397 ### apply phase
402 398 if not branchmerge: # just jump to the new rev
403 399 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
404 400 if not partial:
405 401 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
406 402
407 updated, merged, removed, unresolved = applyupdates(repo, action, wc, p2)
403 stats = applyupdates(repo, action, wc, p2)
408 404
409 if show_stats:
410 stats = ((updated, _("updated")),
411 (merged - unresolved, _("merged")),
412 (removed, _("removed")),
413 (unresolved, _("unresolved")))
414 note = ", ".join([_("%d files %s") % s for s in stats])
415 repo.ui.status("%s\n" % note)
416 405 if not partial:
417 406 recordupdates(repo, action, branchmerge, p2)
418 407 repo.dirstate.setparents(fp1, fp2)
419 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
408 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
420 409
421 if branchmerge:
422 if unresolved:
423 repo.ui.status(_("There are unresolved merges,"
424 " you can redo the full merge using:\n"
425 " hg update -C %s\n"
426 " hg merge %s\n"
427 % (p1.rev(), p2.rev())))
428 elif remind:
429 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
430 elif unresolved:
431 repo.ui.status(_("There are unresolved merges with"
432 " locally modified files.\n"))
410 return stats
433 411
434 return unresolved
435
General Comments 0
You need to be logged in to leave comments. Login now