##// END OF EJS Templates
merge: document some internal return values.
Greg Ward -
r13162:115a9760 default
parent child Browse files
Show More
@@ -1,550 +1,551 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from i18n import _
9 from i18n import _
10 from lock import release
10 from lock import release
11 from node import hex, nullid, nullrev, short
11 from node import hex, nullid, nullrev, short
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
13 import lock, util, extensions, error, encoding, node
13 import lock, util, extensions, error, encoding, node
14 import cmdutil, discovery, url
14 import cmdutil, discovery, url
15 import merge as mergemod
15 import merge as mergemod
16 import verify as verifymod
16 import verify as verifymod
17 import errno, os, shutil
17 import errno, os, shutil
18
18
19 def _local(path):
19 def _local(path):
20 path = util.expandpath(util.drop_scheme('file', path))
20 path = util.expandpath(util.drop_scheme('file', path))
21 return (os.path.isfile(path) and bundlerepo or localrepo)
21 return (os.path.isfile(path) and bundlerepo or localrepo)
22
22
23 def addbranchrevs(lrepo, repo, branches, revs):
23 def addbranchrevs(lrepo, repo, branches, revs):
24 hashbranch, branches = branches
24 hashbranch, branches = branches
25 if not hashbranch and not branches:
25 if not hashbranch and not branches:
26 return revs or None, revs and revs[0] or None
26 return revs or None, revs and revs[0] or None
27 revs = revs and list(revs) or []
27 revs = revs and list(revs) or []
28 if not repo.capable('branchmap'):
28 if not repo.capable('branchmap'):
29 if branches:
29 if branches:
30 raise util.Abort(_("remote branch lookup not supported"))
30 raise util.Abort(_("remote branch lookup not supported"))
31 revs.append(hashbranch)
31 revs.append(hashbranch)
32 return revs, revs[0]
32 return revs, revs[0]
33 branchmap = repo.branchmap()
33 branchmap = repo.branchmap()
34
34
35 def primary(branch):
35 def primary(branch):
36 if branch == '.':
36 if branch == '.':
37 if not lrepo or not lrepo.local():
37 if not lrepo or not lrepo.local():
38 raise util.Abort(_("dirstate branch not accessible"))
38 raise util.Abort(_("dirstate branch not accessible"))
39 branch = lrepo.dirstate.branch()
39 branch = lrepo.dirstate.branch()
40 if branch in branchmap:
40 if branch in branchmap:
41 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
41 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
42 return True
42 return True
43 else:
43 else:
44 return False
44 return False
45
45
46 for branch in branches:
46 for branch in branches:
47 if not primary(branch):
47 if not primary(branch):
48 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
48 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
49 if hashbranch:
49 if hashbranch:
50 if not primary(hashbranch):
50 if not primary(hashbranch):
51 revs.append(hashbranch)
51 revs.append(hashbranch)
52 return revs, revs[0]
52 return revs, revs[0]
53
53
54 def parseurl(url, branches=None):
54 def parseurl(url, branches=None):
55 '''parse url#branch, returning (url, (branch, branches))'''
55 '''parse url#branch, returning (url, (branch, branches))'''
56
56
57 if '#' not in url:
57 if '#' not in url:
58 return url, (None, branches or [])
58 return url, (None, branches or [])
59 url, branch = url.split('#', 1)
59 url, branch = url.split('#', 1)
60 return url, (branch, branches or [])
60 return url, (branch, branches or [])
61
61
62 schemes = {
62 schemes = {
63 'bundle': bundlerepo,
63 'bundle': bundlerepo,
64 'file': _local,
64 'file': _local,
65 'http': httprepo,
65 'http': httprepo,
66 'https': httprepo,
66 'https': httprepo,
67 'ssh': sshrepo,
67 'ssh': sshrepo,
68 'static-http': statichttprepo,
68 'static-http': statichttprepo,
69 }
69 }
70
70
71 def _lookup(path):
71 def _lookup(path):
72 scheme = 'file'
72 scheme = 'file'
73 if path:
73 if path:
74 c = path.find(':')
74 c = path.find(':')
75 if c > 0:
75 if c > 0:
76 scheme = path[:c]
76 scheme = path[:c]
77 thing = schemes.get(scheme) or schemes['file']
77 thing = schemes.get(scheme) or schemes['file']
78 try:
78 try:
79 return thing(path)
79 return thing(path)
80 except TypeError:
80 except TypeError:
81 return thing
81 return thing
82
82
83 def islocal(repo):
83 def islocal(repo):
84 '''return true if repo or path is local'''
84 '''return true if repo or path is local'''
85 if isinstance(repo, str):
85 if isinstance(repo, str):
86 try:
86 try:
87 return _lookup(repo).islocal(repo)
87 return _lookup(repo).islocal(repo)
88 except AttributeError:
88 except AttributeError:
89 return False
89 return False
90 return repo.local()
90 return repo.local()
91
91
92 def repository(ui, path='', create=False):
92 def repository(ui, path='', create=False):
93 """return a repository object for the specified path"""
93 """return a repository object for the specified path"""
94 repo = _lookup(path).instance(ui, path, create)
94 repo = _lookup(path).instance(ui, path, create)
95 ui = getattr(repo, "ui", ui)
95 ui = getattr(repo, "ui", ui)
96 for name, module in extensions.extensions():
96 for name, module in extensions.extensions():
97 hook = getattr(module, 'reposetup', None)
97 hook = getattr(module, 'reposetup', None)
98 if hook:
98 if hook:
99 hook(ui, repo)
99 hook(ui, repo)
100 return repo
100 return repo
101
101
102 def defaultdest(source):
102 def defaultdest(source):
103 '''return default destination of clone if none is given'''
103 '''return default destination of clone if none is given'''
104 return os.path.basename(os.path.normpath(source))
104 return os.path.basename(os.path.normpath(source))
105
105
106 def localpath(path):
106 def localpath(path):
107 if path.startswith('file://localhost/'):
107 if path.startswith('file://localhost/'):
108 return path[16:]
108 return path[16:]
109 if path.startswith('file://'):
109 if path.startswith('file://'):
110 return path[7:]
110 return path[7:]
111 if path.startswith('file:'):
111 if path.startswith('file:'):
112 return path[5:]
112 return path[5:]
113 return path
113 return path
114
114
115 def share(ui, source, dest=None, update=True):
115 def share(ui, source, dest=None, update=True):
116 '''create a shared repository'''
116 '''create a shared repository'''
117
117
118 if not islocal(source):
118 if not islocal(source):
119 raise util.Abort(_('can only share local repositories'))
119 raise util.Abort(_('can only share local repositories'))
120
120
121 if not dest:
121 if not dest:
122 dest = defaultdest(source)
122 dest = defaultdest(source)
123 else:
123 else:
124 dest = ui.expandpath(dest)
124 dest = ui.expandpath(dest)
125
125
126 if isinstance(source, str):
126 if isinstance(source, str):
127 origsource = ui.expandpath(source)
127 origsource = ui.expandpath(source)
128 source, branches = parseurl(origsource)
128 source, branches = parseurl(origsource)
129 srcrepo = repository(ui, source)
129 srcrepo = repository(ui, source)
130 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
130 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
131 else:
131 else:
132 srcrepo = source
132 srcrepo = source
133 origsource = source = srcrepo.url()
133 origsource = source = srcrepo.url()
134 checkout = None
134 checkout = None
135
135
136 sharedpath = srcrepo.sharedpath # if our source is already sharing
136 sharedpath = srcrepo.sharedpath # if our source is already sharing
137
137
138 root = os.path.realpath(dest)
138 root = os.path.realpath(dest)
139 roothg = os.path.join(root, '.hg')
139 roothg = os.path.join(root, '.hg')
140
140
141 if os.path.exists(roothg):
141 if os.path.exists(roothg):
142 raise util.Abort(_('destination already exists'))
142 raise util.Abort(_('destination already exists'))
143
143
144 if not os.path.isdir(root):
144 if not os.path.isdir(root):
145 os.mkdir(root)
145 os.mkdir(root)
146 os.mkdir(roothg)
146 os.mkdir(roothg)
147
147
148 requirements = ''
148 requirements = ''
149 try:
149 try:
150 requirements = srcrepo.opener('requires').read()
150 requirements = srcrepo.opener('requires').read()
151 except IOError, inst:
151 except IOError, inst:
152 if inst.errno != errno.ENOENT:
152 if inst.errno != errno.ENOENT:
153 raise
153 raise
154
154
155 requirements += 'shared\n'
155 requirements += 'shared\n'
156 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
156 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
157 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
157 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
158
158
159 default = srcrepo.ui.config('paths', 'default')
159 default = srcrepo.ui.config('paths', 'default')
160 if default:
160 if default:
161 f = file(os.path.join(roothg, 'hgrc'), 'w')
161 f = file(os.path.join(roothg, 'hgrc'), 'w')
162 f.write('[paths]\ndefault = %s\n' % default)
162 f.write('[paths]\ndefault = %s\n' % default)
163 f.close()
163 f.close()
164
164
165 r = repository(ui, root)
165 r = repository(ui, root)
166
166
167 if update:
167 if update:
168 r.ui.status(_("updating working directory\n"))
168 r.ui.status(_("updating working directory\n"))
169 if update is not True:
169 if update is not True:
170 checkout = update
170 checkout = update
171 for test in (checkout, 'default', 'tip'):
171 for test in (checkout, 'default', 'tip'):
172 if test is None:
172 if test is None:
173 continue
173 continue
174 try:
174 try:
175 uprev = r.lookup(test)
175 uprev = r.lookup(test)
176 break
176 break
177 except error.RepoLookupError:
177 except error.RepoLookupError:
178 continue
178 continue
179 _update(r, uprev)
179 _update(r, uprev)
180
180
181 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
181 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
182 stream=False, branch=None):
182 stream=False, branch=None):
183 """Make a copy of an existing repository.
183 """Make a copy of an existing repository.
184
184
185 Create a copy of an existing repository in a new directory. The
185 Create a copy of an existing repository in a new directory. The
186 source and destination are URLs, as passed to the repository
186 source and destination are URLs, as passed to the repository
187 function. Returns a pair of repository objects, the source and
187 function. Returns a pair of repository objects, the source and
188 newly created destination.
188 newly created destination.
189
189
190 The location of the source is added to the new repository's
190 The location of the source is added to the new repository's
191 .hg/hgrc file, as the default to be used for future pulls and
191 .hg/hgrc file, as the default to be used for future pulls and
192 pushes.
192 pushes.
193
193
194 If an exception is raised, the partly cloned/updated destination
194 If an exception is raised, the partly cloned/updated destination
195 repository will be deleted.
195 repository will be deleted.
196
196
197 Arguments:
197 Arguments:
198
198
199 source: repository object or URL
199 source: repository object or URL
200
200
201 dest: URL of destination repository to create (defaults to base
201 dest: URL of destination repository to create (defaults to base
202 name of source repository)
202 name of source repository)
203
203
204 pull: always pull from source repository, even in local case
204 pull: always pull from source repository, even in local case
205
205
206 stream: stream raw data uncompressed from repository (fast over
206 stream: stream raw data uncompressed from repository (fast over
207 LAN, slow over WAN)
207 LAN, slow over WAN)
208
208
209 rev: revision to clone up to (implies pull=True)
209 rev: revision to clone up to (implies pull=True)
210
210
211 update: update working directory after clone completes, if
211 update: update working directory after clone completes, if
212 destination is local repository (True means update to default rev,
212 destination is local repository (True means update to default rev,
213 anything else is treated as a revision)
213 anything else is treated as a revision)
214
214
215 branch: branches to clone
215 branch: branches to clone
216 """
216 """
217
217
218 if isinstance(source, str):
218 if isinstance(source, str):
219 origsource = ui.expandpath(source)
219 origsource = ui.expandpath(source)
220 source, branch = parseurl(origsource, branch)
220 source, branch = parseurl(origsource, branch)
221 src_repo = repository(ui, source)
221 src_repo = repository(ui, source)
222 else:
222 else:
223 src_repo = source
223 src_repo = source
224 branch = (None, branch or [])
224 branch = (None, branch or [])
225 origsource = source = src_repo.url()
225 origsource = source = src_repo.url()
226 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
226 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
227
227
228 if dest is None:
228 if dest is None:
229 dest = defaultdest(source)
229 dest = defaultdest(source)
230 ui.status(_("destination directory: %s\n") % dest)
230 ui.status(_("destination directory: %s\n") % dest)
231 else:
231 else:
232 dest = ui.expandpath(dest)
232 dest = ui.expandpath(dest)
233
233
234 dest = localpath(dest)
234 dest = localpath(dest)
235 source = localpath(source)
235 source = localpath(source)
236
236
237 if os.path.exists(dest):
237 if os.path.exists(dest):
238 if not os.path.isdir(dest):
238 if not os.path.isdir(dest):
239 raise util.Abort(_("destination '%s' already exists") % dest)
239 raise util.Abort(_("destination '%s' already exists") % dest)
240 elif os.listdir(dest):
240 elif os.listdir(dest):
241 raise util.Abort(_("destination '%s' is not empty") % dest)
241 raise util.Abort(_("destination '%s' is not empty") % dest)
242
242
243 class DirCleanup(object):
243 class DirCleanup(object):
244 def __init__(self, dir_):
244 def __init__(self, dir_):
245 self.rmtree = shutil.rmtree
245 self.rmtree = shutil.rmtree
246 self.dir_ = dir_
246 self.dir_ = dir_
247 def close(self):
247 def close(self):
248 self.dir_ = None
248 self.dir_ = None
249 def cleanup(self):
249 def cleanup(self):
250 if self.dir_:
250 if self.dir_:
251 self.rmtree(self.dir_, True)
251 self.rmtree(self.dir_, True)
252
252
253 src_lock = dest_lock = dir_cleanup = None
253 src_lock = dest_lock = dir_cleanup = None
254 try:
254 try:
255 if islocal(dest):
255 if islocal(dest):
256 dir_cleanup = DirCleanup(dest)
256 dir_cleanup = DirCleanup(dest)
257
257
258 abspath = origsource
258 abspath = origsource
259 copy = False
259 copy = False
260 if src_repo.cancopy() and islocal(dest):
260 if src_repo.cancopy() and islocal(dest):
261 abspath = os.path.abspath(util.drop_scheme('file', origsource))
261 abspath = os.path.abspath(util.drop_scheme('file', origsource))
262 copy = not pull and not rev
262 copy = not pull and not rev
263
263
264 if copy:
264 if copy:
265 try:
265 try:
266 # we use a lock here because if we race with commit, we
266 # we use a lock here because if we race with commit, we
267 # can end up with extra data in the cloned revlogs that's
267 # can end up with extra data in the cloned revlogs that's
268 # not pointed to by changesets, thus causing verify to
268 # not pointed to by changesets, thus causing verify to
269 # fail
269 # fail
270 src_lock = src_repo.lock(wait=False)
270 src_lock = src_repo.lock(wait=False)
271 except error.LockError:
271 except error.LockError:
272 copy = False
272 copy = False
273
273
274 if copy:
274 if copy:
275 src_repo.hook('preoutgoing', throw=True, source='clone')
275 src_repo.hook('preoutgoing', throw=True, source='clone')
276 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
276 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
277 if not os.path.exists(dest):
277 if not os.path.exists(dest):
278 os.mkdir(dest)
278 os.mkdir(dest)
279 else:
279 else:
280 # only clean up directories we create ourselves
280 # only clean up directories we create ourselves
281 dir_cleanup.dir_ = hgdir
281 dir_cleanup.dir_ = hgdir
282 try:
282 try:
283 dest_path = hgdir
283 dest_path = hgdir
284 os.mkdir(dest_path)
284 os.mkdir(dest_path)
285 except OSError, inst:
285 except OSError, inst:
286 if inst.errno == errno.EEXIST:
286 if inst.errno == errno.EEXIST:
287 dir_cleanup.close()
287 dir_cleanup.close()
288 raise util.Abort(_("destination '%s' already exists")
288 raise util.Abort(_("destination '%s' already exists")
289 % dest)
289 % dest)
290 raise
290 raise
291
291
292 hardlink = None
292 hardlink = None
293 num = 0
293 num = 0
294 for f in src_repo.store.copylist():
294 for f in src_repo.store.copylist():
295 src = os.path.join(src_repo.sharedpath, f)
295 src = os.path.join(src_repo.sharedpath, f)
296 dst = os.path.join(dest_path, f)
296 dst = os.path.join(dest_path, f)
297 dstbase = os.path.dirname(dst)
297 dstbase = os.path.dirname(dst)
298 if dstbase and not os.path.exists(dstbase):
298 if dstbase and not os.path.exists(dstbase):
299 os.mkdir(dstbase)
299 os.mkdir(dstbase)
300 if os.path.exists(src):
300 if os.path.exists(src):
301 if dst.endswith('data'):
301 if dst.endswith('data'):
302 # lock to avoid premature writing to the target
302 # lock to avoid premature writing to the target
303 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
303 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
304 hardlink, n = util.copyfiles(src, dst, hardlink)
304 hardlink, n = util.copyfiles(src, dst, hardlink)
305 num += n
305 num += n
306 if hardlink:
306 if hardlink:
307 ui.debug("linked %d files\n" % num)
307 ui.debug("linked %d files\n" % num)
308 else:
308 else:
309 ui.debug("copied %d files\n" % num)
309 ui.debug("copied %d files\n" % num)
310
310
311 # we need to re-init the repo after manually copying the data
311 # we need to re-init the repo after manually copying the data
312 # into it
312 # into it
313 dest_repo = repository(ui, dest)
313 dest_repo = repository(ui, dest)
314 src_repo.hook('outgoing', source='clone',
314 src_repo.hook('outgoing', source='clone',
315 node=node.hex(node.nullid))
315 node=node.hex(node.nullid))
316 else:
316 else:
317 try:
317 try:
318 dest_repo = repository(ui, dest, create=True)
318 dest_repo = repository(ui, dest, create=True)
319 except OSError, inst:
319 except OSError, inst:
320 if inst.errno == errno.EEXIST:
320 if inst.errno == errno.EEXIST:
321 dir_cleanup.close()
321 dir_cleanup.close()
322 raise util.Abort(_("destination '%s' already exists")
322 raise util.Abort(_("destination '%s' already exists")
323 % dest)
323 % dest)
324 raise
324 raise
325
325
326 revs = None
326 revs = None
327 if rev:
327 if rev:
328 if 'lookup' not in src_repo.capabilities:
328 if 'lookup' not in src_repo.capabilities:
329 raise util.Abort(_("src repository does not support "
329 raise util.Abort(_("src repository does not support "
330 "revision lookup and so doesn't "
330 "revision lookup and so doesn't "
331 "support clone by revision"))
331 "support clone by revision"))
332 revs = [src_repo.lookup(r) for r in rev]
332 revs = [src_repo.lookup(r) for r in rev]
333 checkout = revs[0]
333 checkout = revs[0]
334 if dest_repo.local():
334 if dest_repo.local():
335 dest_repo.clone(src_repo, heads=revs, stream=stream)
335 dest_repo.clone(src_repo, heads=revs, stream=stream)
336 elif src_repo.local():
336 elif src_repo.local():
337 src_repo.push(dest_repo, revs=revs)
337 src_repo.push(dest_repo, revs=revs)
338 else:
338 else:
339 raise util.Abort(_("clone from remote to remote not supported"))
339 raise util.Abort(_("clone from remote to remote not supported"))
340
340
341 if dir_cleanup:
341 if dir_cleanup:
342 dir_cleanup.close()
342 dir_cleanup.close()
343
343
344 if dest_repo.local():
344 if dest_repo.local():
345 fp = dest_repo.opener("hgrc", "w", text=True)
345 fp = dest_repo.opener("hgrc", "w", text=True)
346 fp.write("[paths]\n")
346 fp.write("[paths]\n")
347 fp.write("default = %s\n" % abspath)
347 fp.write("default = %s\n" % abspath)
348 fp.close()
348 fp.close()
349
349
350 dest_repo.ui.setconfig('paths', 'default', abspath)
350 dest_repo.ui.setconfig('paths', 'default', abspath)
351
351
352 if update:
352 if update:
353 if update is not True:
353 if update is not True:
354 checkout = update
354 checkout = update
355 if src_repo.local():
355 if src_repo.local():
356 checkout = src_repo.lookup(update)
356 checkout = src_repo.lookup(update)
357 for test in (checkout, 'default', 'tip'):
357 for test in (checkout, 'default', 'tip'):
358 if test is None:
358 if test is None:
359 continue
359 continue
360 try:
360 try:
361 uprev = dest_repo.lookup(test)
361 uprev = dest_repo.lookup(test)
362 break
362 break
363 except error.RepoLookupError:
363 except error.RepoLookupError:
364 continue
364 continue
365 bn = dest_repo[uprev].branch()
365 bn = dest_repo[uprev].branch()
366 dest_repo.ui.status(_("updating to branch %s\n") % bn)
366 dest_repo.ui.status(_("updating to branch %s\n") % bn)
367 _update(dest_repo, uprev)
367 _update(dest_repo, uprev)
368
368
369 return src_repo, dest_repo
369 return src_repo, dest_repo
370 finally:
370 finally:
371 release(src_lock, dest_lock)
371 release(src_lock, dest_lock)
372 if dir_cleanup is not None:
372 if dir_cleanup is not None:
373 dir_cleanup.cleanup()
373 dir_cleanup.cleanup()
374
374
375 def _showstats(repo, stats):
375 def _showstats(repo, stats):
376 repo.ui.status(_("%d files updated, %d files merged, "
376 repo.ui.status(_("%d files updated, %d files merged, "
377 "%d files removed, %d files unresolved\n") % stats)
377 "%d files removed, %d files unresolved\n") % stats)
378
378
379 def update(repo, node):
379 def update(repo, node):
380 """update the working directory to node, merging linear changes"""
380 """update the working directory to node, merging linear changes"""
381 stats = mergemod.update(repo, node, False, False, None)
381 stats = mergemod.update(repo, node, False, False, None)
382 _showstats(repo, stats)
382 _showstats(repo, stats)
383 if stats[3]:
383 if stats[3]:
384 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
384 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
385 return stats[3] > 0
385 return stats[3] > 0
386
386
387 # naming conflict in clone()
387 # naming conflict in clone()
388 _update = update
388 _update = update
389
389
390 def clean(repo, node, show_stats=True):
390 def clean(repo, node, show_stats=True):
391 """forcibly switch the working directory to node, clobbering changes"""
391 """forcibly switch the working directory to node, clobbering changes"""
392 stats = mergemod.update(repo, node, False, True, None)
392 stats = mergemod.update(repo, node, False, True, None)
393 if show_stats:
393 if show_stats:
394 _showstats(repo, stats)
394 _showstats(repo, stats)
395 return stats[3] > 0
395 return stats[3] > 0
396
396
397 def merge(repo, node, force=None, remind=True):
397 def merge(repo, node, force=None, remind=True):
398 """branch merge with node, resolving changes"""
398 """Branch merge with node, resolving changes. Return true if any
399 unresolved conflicts."""
399 stats = mergemod.update(repo, node, True, force, False)
400 stats = mergemod.update(repo, node, True, force, False)
400 _showstats(repo, stats)
401 _showstats(repo, stats)
401 if stats[3]:
402 if stats[3]:
402 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
403 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
403 "or 'hg update -C .' to abandon\n"))
404 "or 'hg update -C .' to abandon\n"))
404 elif remind:
405 elif remind:
405 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
406 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
406 return stats[3] > 0
407 return stats[3] > 0
407
408
408 def _incoming(displaychlist, subreporecurse, ui, repo, source,
409 def _incoming(displaychlist, subreporecurse, ui, repo, source,
409 opts, buffered=False):
410 opts, buffered=False):
410 """
411 """
411 Helper for incoming / gincoming.
412 Helper for incoming / gincoming.
412 displaychlist gets called with
413 displaychlist gets called with
413 (remoterepo, incomingchangesetlist, displayer) parameters,
414 (remoterepo, incomingchangesetlist, displayer) parameters,
414 and is supposed to contain only code that can't be unified.
415 and is supposed to contain only code that can't be unified.
415 """
416 """
416 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
417 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
417 other = repository(remoteui(repo, opts), source)
418 other = repository(remoteui(repo, opts), source)
418 ui.status(_('comparing with %s\n') % url.hidepassword(source))
419 ui.status(_('comparing with %s\n') % url.hidepassword(source))
419 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
420 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
420
421
421 if revs:
422 if revs:
422 revs = [other.lookup(rev) for rev in revs]
423 revs = [other.lookup(rev) for rev in revs]
423 other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
424 other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
424 opts["bundle"], opts["force"])
425 opts["bundle"], opts["force"])
425 if incoming is None:
426 if incoming is None:
426 ui.status(_("no changes found\n"))
427 ui.status(_("no changes found\n"))
427 return subreporecurse()
428 return subreporecurse()
428
429
429 try:
430 try:
430 chlist = other.changelog.nodesbetween(incoming, revs)[0]
431 chlist = other.changelog.nodesbetween(incoming, revs)[0]
431 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
432 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
432
433
433 # XXX once graphlog extension makes it into core,
434 # XXX once graphlog extension makes it into core,
434 # should be replaced by a if graph/else
435 # should be replaced by a if graph/else
435 displaychlist(other, chlist, displayer)
436 displaychlist(other, chlist, displayer)
436
437
437 displayer.close()
438 displayer.close()
438 finally:
439 finally:
439 if hasattr(other, 'close'):
440 if hasattr(other, 'close'):
440 other.close()
441 other.close()
441 if bundle:
442 if bundle:
442 os.unlink(bundle)
443 os.unlink(bundle)
443 subreporecurse()
444 subreporecurse()
444 return 0 # exit code is zero since we found incoming changes
445 return 0 # exit code is zero since we found incoming changes
445
446
446 def incoming(ui, repo, source, opts):
447 def incoming(ui, repo, source, opts):
447 def subreporecurse():
448 def subreporecurse():
448 ret = 1
449 ret = 1
449 if opts.get('subrepos'):
450 if opts.get('subrepos'):
450 ctx = repo[None]
451 ctx = repo[None]
451 for subpath in sorted(ctx.substate):
452 for subpath in sorted(ctx.substate):
452 sub = ctx.sub(subpath)
453 sub = ctx.sub(subpath)
453 ret = min(ret, sub.incoming(ui, source, opts))
454 ret = min(ret, sub.incoming(ui, source, opts))
454 return ret
455 return ret
455
456
456 def display(other, chlist, displayer):
457 def display(other, chlist, displayer):
457 limit = cmdutil.loglimit(opts)
458 limit = cmdutil.loglimit(opts)
458 if opts.get('newest_first'):
459 if opts.get('newest_first'):
459 chlist.reverse()
460 chlist.reverse()
460 count = 0
461 count = 0
461 for n in chlist:
462 for n in chlist:
462 if limit is not None and count >= limit:
463 if limit is not None and count >= limit:
463 break
464 break
464 parents = [p for p in other.changelog.parents(n) if p != nullid]
465 parents = [p for p in other.changelog.parents(n) if p != nullid]
465 if opts.get('no_merges') and len(parents) == 2:
466 if opts.get('no_merges') and len(parents) == 2:
466 continue
467 continue
467 count += 1
468 count += 1
468 displayer.show(other[n])
469 displayer.show(other[n])
469 return _incoming(display, subreporecurse, ui, repo, source, opts)
470 return _incoming(display, subreporecurse, ui, repo, source, opts)
470
471
471 def _outgoing(ui, repo, dest, opts):
472 def _outgoing(ui, repo, dest, opts):
472 dest = ui.expandpath(dest or 'default-push', dest or 'default')
473 dest = ui.expandpath(dest or 'default-push', dest or 'default')
473 dest, branches = parseurl(dest, opts.get('branch'))
474 dest, branches = parseurl(dest, opts.get('branch'))
474 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
475 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
475 if revs:
476 if revs:
476 revs = [repo.lookup(rev) for rev in revs]
477 revs = [repo.lookup(rev) for rev in revs]
477
478
478 other = repository(remoteui(repo, opts), dest)
479 other = repository(remoteui(repo, opts), dest)
479 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
480 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
480 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
481 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
481 if not o:
482 if not o:
482 ui.status(_("no changes found\n"))
483 ui.status(_("no changes found\n"))
483 return None
484 return None
484
485
485 return repo.changelog.nodesbetween(o, revs)[0]
486 return repo.changelog.nodesbetween(o, revs)[0]
486
487
487 def outgoing(ui, repo, dest, opts):
488 def outgoing(ui, repo, dest, opts):
488 def recurse():
489 def recurse():
489 ret = 1
490 ret = 1
490 if opts.get('subrepos'):
491 if opts.get('subrepos'):
491 ctx = repo[None]
492 ctx = repo[None]
492 for subpath in sorted(ctx.substate):
493 for subpath in sorted(ctx.substate):
493 sub = ctx.sub(subpath)
494 sub = ctx.sub(subpath)
494 ret = min(ret, sub.outgoing(ui, dest, opts))
495 ret = min(ret, sub.outgoing(ui, dest, opts))
495 return ret
496 return ret
496
497
497 limit = cmdutil.loglimit(opts)
498 limit = cmdutil.loglimit(opts)
498 o = _outgoing(ui, repo, dest, opts)
499 o = _outgoing(ui, repo, dest, opts)
499 if o is None:
500 if o is None:
500 return recurse()
501 return recurse()
501
502
502 if opts.get('newest_first'):
503 if opts.get('newest_first'):
503 o.reverse()
504 o.reverse()
504 displayer = cmdutil.show_changeset(ui, repo, opts)
505 displayer = cmdutil.show_changeset(ui, repo, opts)
505 count = 0
506 count = 0
506 for n in o:
507 for n in o:
507 if limit is not None and count >= limit:
508 if limit is not None and count >= limit:
508 break
509 break
509 parents = [p for p in repo.changelog.parents(n) if p != nullid]
510 parents = [p for p in repo.changelog.parents(n) if p != nullid]
510 if opts.get('no_merges') and len(parents) == 2:
511 if opts.get('no_merges') and len(parents) == 2:
511 continue
512 continue
512 count += 1
513 count += 1
513 displayer.show(repo[n])
514 displayer.show(repo[n])
514 displayer.close()
515 displayer.close()
515 recurse()
516 recurse()
516 return 0 # exit code is zero since we found outgoing changes
517 return 0 # exit code is zero since we found outgoing changes
517
518
518 def revert(repo, node, choose):
519 def revert(repo, node, choose):
519 """revert changes to revision in node without updating dirstate"""
520 """revert changes to revision in node without updating dirstate"""
520 return mergemod.update(repo, node, False, True, choose)[3] > 0
521 return mergemod.update(repo, node, False, True, choose)[3] > 0
521
522
522 def verify(repo):
523 def verify(repo):
523 """verify the consistency of a repository"""
524 """verify the consistency of a repository"""
524 return verifymod.verify(repo)
525 return verifymod.verify(repo)
525
526
526 def remoteui(src, opts):
527 def remoteui(src, opts):
527 'build a remote ui from ui or repo and opts'
528 'build a remote ui from ui or repo and opts'
528 if hasattr(src, 'baseui'): # looks like a repository
529 if hasattr(src, 'baseui'): # looks like a repository
529 dst = src.baseui.copy() # drop repo-specific config
530 dst = src.baseui.copy() # drop repo-specific config
530 src = src.ui # copy target options from repo
531 src = src.ui # copy target options from repo
531 else: # assume it's a global ui object
532 else: # assume it's a global ui object
532 dst = src.copy() # keep all global options
533 dst = src.copy() # keep all global options
533
534
534 # copy ssh-specific options
535 # copy ssh-specific options
535 for o in 'ssh', 'remotecmd':
536 for o in 'ssh', 'remotecmd':
536 v = opts.get(o) or src.config('ui', o)
537 v = opts.get(o) or src.config('ui', o)
537 if v:
538 if v:
538 dst.setconfig("ui", o, v)
539 dst.setconfig("ui", o, v)
539
540
540 # copy bundle-specific options
541 # copy bundle-specific options
541 r = src.config('bundle', 'mainreporoot')
542 r = src.config('bundle', 'mainreporoot')
542 if r:
543 if r:
543 dst.setconfig('bundle', 'mainreporoot', r)
544 dst.setconfig('bundle', 'mainreporoot', r)
544
545
545 # copy auth and http_proxy section settings
546 # copy auth and http_proxy section settings
546 for sect in ('auth', 'http_proxy'):
547 for sect in ('auth', 'http_proxy'):
547 for key, val in src.configitems(sect):
548 for key, val in src.configitems(sect):
548 dst.setconfig(sect, key, val)
549 dst.setconfig(sect, key, val)
549
550
550 return dst
551 return dst
@@ -1,544 +1,549 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import util, filemerge, copies, subrepo
10 import util, filemerge, copies, subrepo
11 import errno, os, shutil
11 import errno, os, shutil
12
12
13 class mergestate(object):
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
15 def __init__(self, repo):
16 self._repo = repo
16 self._repo = repo
17 self._dirty = False
17 self._dirty = False
18 self._read()
18 self._read()
19 def reset(self, node=None):
19 def reset(self, node=None):
20 self._state = {}
20 self._state = {}
21 if node:
21 if node:
22 self._local = node
22 self._local = node
23 shutil.rmtree(self._repo.join("merge"), True)
23 shutil.rmtree(self._repo.join("merge"), True)
24 self._dirty = False
24 self._dirty = False
25 def _read(self):
25 def _read(self):
26 self._state = {}
26 self._state = {}
27 try:
27 try:
28 f = self._repo.opener("merge/state")
28 f = self._repo.opener("merge/state")
29 for i, l in enumerate(f):
29 for i, l in enumerate(f):
30 if i == 0:
30 if i == 0:
31 self._local = bin(l[:-1])
31 self._local = bin(l[:-1])
32 else:
32 else:
33 bits = l[:-1].split("\0")
33 bits = l[:-1].split("\0")
34 self._state[bits[0]] = bits[1:]
34 self._state[bits[0]] = bits[1:]
35 except IOError, err:
35 except IOError, err:
36 if err.errno != errno.ENOENT:
36 if err.errno != errno.ENOENT:
37 raise
37 raise
38 self._dirty = False
38 self._dirty = False
39 def commit(self):
39 def commit(self):
40 if self._dirty:
40 if self._dirty:
41 f = self._repo.opener("merge/state", "w")
41 f = self._repo.opener("merge/state", "w")
42 f.write(hex(self._local) + "\n")
42 f.write(hex(self._local) + "\n")
43 for d, v in self._state.iteritems():
43 for d, v in self._state.iteritems():
44 f.write("\0".join([d] + v) + "\n")
44 f.write("\0".join([d] + v) + "\n")
45 self._dirty = False
45 self._dirty = False
46 def add(self, fcl, fco, fca, fd, flags):
46 def add(self, fcl, fco, fca, fd, flags):
47 hash = util.sha1(fcl.path()).hexdigest()
47 hash = util.sha1(fcl.path()).hexdigest()
48 self._repo.opener("merge/" + hash, "w").write(fcl.data())
48 self._repo.opener("merge/" + hash, "w").write(fcl.data())
49 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
49 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
50 hex(fca.filenode()), fco.path(), flags]
50 hex(fca.filenode()), fco.path(), flags]
51 self._dirty = True
51 self._dirty = True
52 def __contains__(self, dfile):
52 def __contains__(self, dfile):
53 return dfile in self._state
53 return dfile in self._state
54 def __getitem__(self, dfile):
54 def __getitem__(self, dfile):
55 return self._state[dfile][0]
55 return self._state[dfile][0]
56 def __iter__(self):
56 def __iter__(self):
57 l = self._state.keys()
57 l = self._state.keys()
58 l.sort()
58 l.sort()
59 for f in l:
59 for f in l:
60 yield f
60 yield f
61 def mark(self, dfile, state):
61 def mark(self, dfile, state):
62 self._state[dfile][0] = state
62 self._state[dfile][0] = state
63 self._dirty = True
63 self._dirty = True
64 def resolve(self, dfile, wctx, octx):
64 def resolve(self, dfile, wctx, octx):
65 if self[dfile] == 'r':
65 if self[dfile] == 'r':
66 return 0
66 return 0
67 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
67 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
68 f = self._repo.opener("merge/" + hash)
68 f = self._repo.opener("merge/" + hash)
69 self._repo.wwrite(dfile, f.read(), flags)
69 self._repo.wwrite(dfile, f.read(), flags)
70 fcd = wctx[dfile]
70 fcd = wctx[dfile]
71 fco = octx[ofile]
71 fco = octx[ofile]
72 fca = self._repo.filectx(afile, fileid=anode)
72 fca = self._repo.filectx(afile, fileid=anode)
73 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
73 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
74 if not r:
74 if not r:
75 self.mark(dfile, 'r')
75 self.mark(dfile, 'r')
76 return r
76 return r
77
77
78 def _checkunknown(wctx, mctx):
78 def _checkunknown(wctx, mctx):
79 "check for collisions between unknown files and files in mctx"
79 "check for collisions between unknown files and files in mctx"
80 for f in wctx.unknown():
80 for f in wctx.unknown():
81 if f in mctx and mctx[f].cmp(wctx[f]):
81 if f in mctx and mctx[f].cmp(wctx[f]):
82 raise util.Abort(_("untracked file in working directory differs"
82 raise util.Abort(_("untracked file in working directory differs"
83 " from file in requested revision: '%s'") % f)
83 " from file in requested revision: '%s'") % f)
84
84
85 def _checkcollision(mctx):
85 def _checkcollision(mctx):
86 "check for case folding collisions in the destination context"
86 "check for case folding collisions in the destination context"
87 folded = {}
87 folded = {}
88 for fn in mctx:
88 for fn in mctx:
89 fold = fn.lower()
89 fold = fn.lower()
90 if fold in folded:
90 if fold in folded:
91 raise util.Abort(_("case-folding collision between %s and %s")
91 raise util.Abort(_("case-folding collision between %s and %s")
92 % (fn, folded[fold]))
92 % (fn, folded[fold]))
93 folded[fold] = fn
93 folded[fold] = fn
94
94
95 def _forgetremoved(wctx, mctx, branchmerge):
95 def _forgetremoved(wctx, mctx, branchmerge):
96 """
96 """
97 Forget removed files
97 Forget removed files
98
98
99 If we're jumping between revisions (as opposed to merging), and if
99 If we're jumping between revisions (as opposed to merging), and if
100 neither the working directory nor the target rev has the file,
100 neither the working directory nor the target rev has the file,
101 then we need to remove it from the dirstate, to prevent the
101 then we need to remove it from the dirstate, to prevent the
102 dirstate from listing the file when it is no longer in the
102 dirstate from listing the file when it is no longer in the
103 manifest.
103 manifest.
104
104
105 If we're merging, and the other revision has removed a file
105 If we're merging, and the other revision has removed a file
106 that is not present in the working directory, we need to mark it
106 that is not present in the working directory, we need to mark it
107 as removed.
107 as removed.
108 """
108 """
109
109
110 action = []
110 action = []
111 state = branchmerge and 'r' or 'f'
111 state = branchmerge and 'r' or 'f'
112 for f in wctx.deleted():
112 for f in wctx.deleted():
113 if f not in mctx:
113 if f not in mctx:
114 action.append((f, state))
114 action.append((f, state))
115
115
116 if not branchmerge:
116 if not branchmerge:
117 for f in wctx.removed():
117 for f in wctx.removed():
118 if f not in mctx:
118 if f not in mctx:
119 action.append((f, "f"))
119 action.append((f, "f"))
120
120
121 return action
121 return action
122
122
123 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
123 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
124 """
124 """
125 Merge p1 and p2 with ancestor pa and generate merge action list
125 Merge p1 and p2 with ancestor pa and generate merge action list
126
126
127 overwrite = whether we clobber working files
127 overwrite = whether we clobber working files
128 partial = function to filter file lists
128 partial = function to filter file lists
129 """
129 """
130
130
131 def fmerge(f, f2, fa):
131 def fmerge(f, f2, fa):
132 """merge flags"""
132 """merge flags"""
133 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
133 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
134 if m == n: # flags agree
134 if m == n: # flags agree
135 return m # unchanged
135 return m # unchanged
136 if m and n and not a: # flags set, don't agree, differ from parent
136 if m and n and not a: # flags set, don't agree, differ from parent
137 r = repo.ui.promptchoice(
137 r = repo.ui.promptchoice(
138 _(" conflicting flags for %s\n"
138 _(" conflicting flags for %s\n"
139 "(n)one, e(x)ec or sym(l)ink?") % f,
139 "(n)one, e(x)ec or sym(l)ink?") % f,
140 (_("&None"), _("E&xec"), _("Sym&link")), 0)
140 (_("&None"), _("E&xec"), _("Sym&link")), 0)
141 if r == 1:
141 if r == 1:
142 return "x" # Exec
142 return "x" # Exec
143 if r == 2:
143 if r == 2:
144 return "l" # Symlink
144 return "l" # Symlink
145 return ""
145 return ""
146 if m and m != a: # changed from a to m
146 if m and m != a: # changed from a to m
147 return m
147 return m
148 if n and n != a: # changed from a to n
148 if n and n != a: # changed from a to n
149 return n
149 return n
150 return '' # flag was cleared
150 return '' # flag was cleared
151
151
152 def act(msg, m, f, *args):
152 def act(msg, m, f, *args):
153 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
153 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
154 action.append((f, m) + args)
154 action.append((f, m) + args)
155
155
156 action, copy = [], {}
156 action, copy = [], {}
157
157
158 if overwrite:
158 if overwrite:
159 pa = p1
159 pa = p1
160 elif pa == p2: # backwards
160 elif pa == p2: # backwards
161 pa = p1.p1()
161 pa = p1.p1()
162 elif pa and repo.ui.configbool("merge", "followcopies", True):
162 elif pa and repo.ui.configbool("merge", "followcopies", True):
163 dirs = repo.ui.configbool("merge", "followdirs", True)
163 dirs = repo.ui.configbool("merge", "followdirs", True)
164 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
164 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
165 for of, fl in diverge.iteritems():
165 for of, fl in diverge.iteritems():
166 act("divergent renames", "dr", of, fl)
166 act("divergent renames", "dr", of, fl)
167
167
168 repo.ui.note(_("resolving manifests\n"))
168 repo.ui.note(_("resolving manifests\n"))
169 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
169 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
170 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
170 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
171
171
172 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
172 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
173 copied = set(copy.values())
173 copied = set(copy.values())
174
174
175 if '.hgsubstate' in m1:
175 if '.hgsubstate' in m1:
176 # check whether sub state is modified
176 # check whether sub state is modified
177 for s in p1.substate:
177 for s in p1.substate:
178 if p1.sub(s).dirty():
178 if p1.sub(s).dirty():
179 m1['.hgsubstate'] += "+"
179 m1['.hgsubstate'] += "+"
180 break
180 break
181
181
182 # Compare manifests
182 # Compare manifests
183 for f, n in m1.iteritems():
183 for f, n in m1.iteritems():
184 if partial and not partial(f):
184 if partial and not partial(f):
185 continue
185 continue
186 if f in m2:
186 if f in m2:
187 rflags = fmerge(f, f, f)
187 rflags = fmerge(f, f, f)
188 a = ma.get(f, nullid)
188 a = ma.get(f, nullid)
189 if n == m2[f] or m2[f] == a: # same or local newer
189 if n == m2[f] or m2[f] == a: # same or local newer
190 # is file locally modified or flags need changing?
190 # is file locally modified or flags need changing?
191 # dirstate flags may need to be made current
191 # dirstate flags may need to be made current
192 if m1.flags(f) != rflags or n[20:]:
192 if m1.flags(f) != rflags or n[20:]:
193 act("update permissions", "e", f, rflags)
193 act("update permissions", "e", f, rflags)
194 elif n == a: # remote newer
194 elif n == a: # remote newer
195 act("remote is newer", "g", f, rflags)
195 act("remote is newer", "g", f, rflags)
196 else: # both changed
196 else: # both changed
197 act("versions differ", "m", f, f, f, rflags, False)
197 act("versions differ", "m", f, f, f, rflags, False)
198 elif f in copied: # files we'll deal with on m2 side
198 elif f in copied: # files we'll deal with on m2 side
199 pass
199 pass
200 elif f in copy:
200 elif f in copy:
201 f2 = copy[f]
201 f2 = copy[f]
202 if f2 not in m2: # directory rename
202 if f2 not in m2: # directory rename
203 act("remote renamed directory to " + f2, "d",
203 act("remote renamed directory to " + f2, "d",
204 f, None, f2, m1.flags(f))
204 f, None, f2, m1.flags(f))
205 else: # case 2 A,B/B/B or case 4,21 A/B/B
205 else: # case 2 A,B/B/B or case 4,21 A/B/B
206 act("local copied/moved to " + f2, "m",
206 act("local copied/moved to " + f2, "m",
207 f, f2, f, fmerge(f, f2, f2), False)
207 f, f2, f, fmerge(f, f2, f2), False)
208 elif f in ma: # clean, a different, no remote
208 elif f in ma: # clean, a different, no remote
209 if n != ma[f]:
209 if n != ma[f]:
210 if repo.ui.promptchoice(
210 if repo.ui.promptchoice(
211 _(" local changed %s which remote deleted\n"
211 _(" local changed %s which remote deleted\n"
212 "use (c)hanged version or (d)elete?") % f,
212 "use (c)hanged version or (d)elete?") % f,
213 (_("&Changed"), _("&Delete")), 0):
213 (_("&Changed"), _("&Delete")), 0):
214 act("prompt delete", "r", f)
214 act("prompt delete", "r", f)
215 else:
215 else:
216 act("prompt keep", "a", f)
216 act("prompt keep", "a", f)
217 elif n[20:] == "a": # added, no remote
217 elif n[20:] == "a": # added, no remote
218 act("remote deleted", "f", f)
218 act("remote deleted", "f", f)
219 elif n[20:] != "u":
219 elif n[20:] != "u":
220 act("other deleted", "r", f)
220 act("other deleted", "r", f)
221
221
222 for f, n in m2.iteritems():
222 for f, n in m2.iteritems():
223 if partial and not partial(f):
223 if partial and not partial(f):
224 continue
224 continue
225 if f in m1 or f in copied: # files already visited
225 if f in m1 or f in copied: # files already visited
226 continue
226 continue
227 if f in copy:
227 if f in copy:
228 f2 = copy[f]
228 f2 = copy[f]
229 if f2 not in m1: # directory rename
229 if f2 not in m1: # directory rename
230 act("local renamed directory to " + f2, "d",
230 act("local renamed directory to " + f2, "d",
231 None, f, f2, m2.flags(f))
231 None, f, f2, m2.flags(f))
232 elif f2 in m2: # rename case 1, A/A,B/A
232 elif f2 in m2: # rename case 1, A/A,B/A
233 act("remote copied to " + f, "m",
233 act("remote copied to " + f, "m",
234 f2, f, f, fmerge(f2, f, f2), False)
234 f2, f, f, fmerge(f2, f, f2), False)
235 else: # case 3,20 A/B/A
235 else: # case 3,20 A/B/A
236 act("remote moved to " + f, "m",
236 act("remote moved to " + f, "m",
237 f2, f, f, fmerge(f2, f, f2), True)
237 f2, f, f, fmerge(f2, f, f2), True)
238 elif f not in ma:
238 elif f not in ma:
239 act("remote created", "g", f, m2.flags(f))
239 act("remote created", "g", f, m2.flags(f))
240 elif n != ma[f]:
240 elif n != ma[f]:
241 if repo.ui.promptchoice(
241 if repo.ui.promptchoice(
242 _("remote changed %s which local deleted\n"
242 _("remote changed %s which local deleted\n"
243 "use (c)hanged version or leave (d)eleted?") % f,
243 "use (c)hanged version or leave (d)eleted?") % f,
244 (_("&Changed"), _("&Deleted")), 0) == 0:
244 (_("&Changed"), _("&Deleted")), 0) == 0:
245 act("prompt recreating", "g", f, m2.flags(f))
245 act("prompt recreating", "g", f, m2.flags(f))
246
246
247 return action
247 return action
248
248
249 def actionkey(a):
249 def actionkey(a):
250 return a[1] == 'r' and -1 or 0, a
250 return a[1] == 'r' and -1 or 0, a
251
251
252 def applyupdates(repo, action, wctx, mctx, actx):
252 def applyupdates(repo, action, wctx, mctx, actx):
253 """apply the merge action list to the working directory
253 """apply the merge action list to the working directory
254
254
255 wctx is the working copy context
255 wctx is the working copy context
256 mctx is the context to be merged into the working copy
256 mctx is the context to be merged into the working copy
257 actx is the context of the common ancestor
257 actx is the context of the common ancestor
258
259 Return a tuple of counts (updated, merged, removed, unresolved) that
260 describes how many files were affected by the update.
258 """
261 """
259
262
260 updated, merged, removed, unresolved = 0, 0, 0, 0
263 updated, merged, removed, unresolved = 0, 0, 0, 0
261 ms = mergestate(repo)
264 ms = mergestate(repo)
262 ms.reset(wctx.parents()[0].node())
265 ms.reset(wctx.parents()[0].node())
263 moves = []
266 moves = []
264 action.sort(key=actionkey)
267 action.sort(key=actionkey)
265 substate = wctx.substate # prime
268 substate = wctx.substate # prime
266
269
267 # prescan for merges
270 # prescan for merges
268 u = repo.ui
271 u = repo.ui
269 for a in action:
272 for a in action:
270 f, m = a[:2]
273 f, m = a[:2]
271 if m == 'm': # merge
274 if m == 'm': # merge
272 f2, fd, flags, move = a[2:]
275 f2, fd, flags, move = a[2:]
273 if f == '.hgsubstate': # merged internally
276 if f == '.hgsubstate': # merged internally
274 continue
277 continue
275 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
278 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
276 fcl = wctx[f]
279 fcl = wctx[f]
277 fco = mctx[f2]
280 fco = mctx[f2]
278 if mctx == actx: # backwards, use working dir parent as ancestor
281 if mctx == actx: # backwards, use working dir parent as ancestor
279 if fcl.parents():
282 if fcl.parents():
280 fca = fcl.parents()[0]
283 fca = fcl.parents()[0]
281 else:
284 else:
282 fca = repo.filectx(f, fileid=nullrev)
285 fca = repo.filectx(f, fileid=nullrev)
283 else:
286 else:
284 fca = fcl.ancestor(fco, actx)
287 fca = fcl.ancestor(fco, actx)
285 if not fca:
288 if not fca:
286 fca = repo.filectx(f, fileid=nullrev)
289 fca = repo.filectx(f, fileid=nullrev)
287 ms.add(fcl, fco, fca, fd, flags)
290 ms.add(fcl, fco, fca, fd, flags)
288 if f != fd and move:
291 if f != fd and move:
289 moves.append(f)
292 moves.append(f)
290
293
291 # remove renamed files after safely stored
294 # remove renamed files after safely stored
292 for f in moves:
295 for f in moves:
293 if os.path.lexists(repo.wjoin(f)):
296 if os.path.lexists(repo.wjoin(f)):
294 repo.ui.debug("removing %s\n" % f)
297 repo.ui.debug("removing %s\n" % f)
295 os.unlink(repo.wjoin(f))
298 os.unlink(repo.wjoin(f))
296
299
297 audit_path = util.path_auditor(repo.root)
300 audit_path = util.path_auditor(repo.root)
298
301
299 numupdates = len(action)
302 numupdates = len(action)
300 for i, a in enumerate(action):
303 for i, a in enumerate(action):
301 f, m = a[:2]
304 f, m = a[:2]
302 u.progress(_('updating'), i + 1, item=f, total=numupdates,
305 u.progress(_('updating'), i + 1, item=f, total=numupdates,
303 unit=_('files'))
306 unit=_('files'))
304 if f and f[0] == "/":
307 if f and f[0] == "/":
305 continue
308 continue
306 if m == "r": # remove
309 if m == "r": # remove
307 repo.ui.note(_("removing %s\n") % f)
310 repo.ui.note(_("removing %s\n") % f)
308 audit_path(f)
311 audit_path(f)
309 if f == '.hgsubstate': # subrepo states need updating
312 if f == '.hgsubstate': # subrepo states need updating
310 subrepo.submerge(repo, wctx, mctx, wctx)
313 subrepo.submerge(repo, wctx, mctx, wctx)
311 try:
314 try:
312 util.unlink(repo.wjoin(f))
315 util.unlink(repo.wjoin(f))
313 except OSError, inst:
316 except OSError, inst:
314 if inst.errno != errno.ENOENT:
317 if inst.errno != errno.ENOENT:
315 repo.ui.warn(_("update failed to remove %s: %s!\n") %
318 repo.ui.warn(_("update failed to remove %s: %s!\n") %
316 (f, inst.strerror))
319 (f, inst.strerror))
317 removed += 1
320 removed += 1
318 elif m == "m": # merge
321 elif m == "m": # merge
319 if f == '.hgsubstate': # subrepo states need updating
322 if f == '.hgsubstate': # subrepo states need updating
320 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
323 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
321 continue
324 continue
322 f2, fd, flags, move = a[2:]
325 f2, fd, flags, move = a[2:]
323 r = ms.resolve(fd, wctx, mctx)
326 r = ms.resolve(fd, wctx, mctx)
324 if r is not None and r > 0:
327 if r is not None and r > 0:
325 unresolved += 1
328 unresolved += 1
326 else:
329 else:
327 if r is None:
330 if r is None:
328 updated += 1
331 updated += 1
329 else:
332 else:
330 merged += 1
333 merged += 1
331 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
334 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
332 if f != fd and move and os.path.lexists(repo.wjoin(f)):
335 if f != fd and move and os.path.lexists(repo.wjoin(f)):
333 repo.ui.debug("removing %s\n" % f)
336 repo.ui.debug("removing %s\n" % f)
334 os.unlink(repo.wjoin(f))
337 os.unlink(repo.wjoin(f))
335 elif m == "g": # get
338 elif m == "g": # get
336 flags = a[2]
339 flags = a[2]
337 repo.ui.note(_("getting %s\n") % f)
340 repo.ui.note(_("getting %s\n") % f)
338 t = mctx.filectx(f).data()
341 t = mctx.filectx(f).data()
339 repo.wwrite(f, t, flags)
342 repo.wwrite(f, t, flags)
340 t = None
343 t = None
341 updated += 1
344 updated += 1
342 if f == '.hgsubstate': # subrepo states need updating
345 if f == '.hgsubstate': # subrepo states need updating
343 subrepo.submerge(repo, wctx, mctx, wctx)
346 subrepo.submerge(repo, wctx, mctx, wctx)
344 elif m == "d": # directory rename
347 elif m == "d": # directory rename
345 f2, fd, flags = a[2:]
348 f2, fd, flags = a[2:]
346 if f:
349 if f:
347 repo.ui.note(_("moving %s to %s\n") % (f, fd))
350 repo.ui.note(_("moving %s to %s\n") % (f, fd))
348 t = wctx.filectx(f).data()
351 t = wctx.filectx(f).data()
349 repo.wwrite(fd, t, flags)
352 repo.wwrite(fd, t, flags)
350 util.unlink(repo.wjoin(f))
353 util.unlink(repo.wjoin(f))
351 if f2:
354 if f2:
352 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
355 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
353 t = mctx.filectx(f2).data()
356 t = mctx.filectx(f2).data()
354 repo.wwrite(fd, t, flags)
357 repo.wwrite(fd, t, flags)
355 updated += 1
358 updated += 1
356 elif m == "dr": # divergent renames
359 elif m == "dr": # divergent renames
357 fl = a[2]
360 fl = a[2]
358 repo.ui.warn(_("note: possible conflict - %s was renamed "
361 repo.ui.warn(_("note: possible conflict - %s was renamed "
359 "multiple times to:\n") % f)
362 "multiple times to:\n") % f)
360 for nf in fl:
363 for nf in fl:
361 repo.ui.warn(" %s\n" % nf)
364 repo.ui.warn(" %s\n" % nf)
362 elif m == "e": # exec
365 elif m == "e": # exec
363 flags = a[2]
366 flags = a[2]
364 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
367 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
365 ms.commit()
368 ms.commit()
366 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
369 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
367
370
368 return updated, merged, removed, unresolved
371 return updated, merged, removed, unresolved
369
372
370 def recordupdates(repo, action, branchmerge):
373 def recordupdates(repo, action, branchmerge):
371 "record merge actions to the dirstate"
374 "record merge actions to the dirstate"
372
375
373 for a in action:
376 for a in action:
374 f, m = a[:2]
377 f, m = a[:2]
375 if m == "r": # remove
378 if m == "r": # remove
376 if branchmerge:
379 if branchmerge:
377 repo.dirstate.remove(f)
380 repo.dirstate.remove(f)
378 else:
381 else:
379 repo.dirstate.forget(f)
382 repo.dirstate.forget(f)
380 elif m == "a": # re-add
383 elif m == "a": # re-add
381 if not branchmerge:
384 if not branchmerge:
382 repo.dirstate.add(f)
385 repo.dirstate.add(f)
383 elif m == "f": # forget
386 elif m == "f": # forget
384 repo.dirstate.forget(f)
387 repo.dirstate.forget(f)
385 elif m == "e": # exec change
388 elif m == "e": # exec change
386 repo.dirstate.normallookup(f)
389 repo.dirstate.normallookup(f)
387 elif m == "g": # get
390 elif m == "g": # get
388 if branchmerge:
391 if branchmerge:
389 repo.dirstate.otherparent(f)
392 repo.dirstate.otherparent(f)
390 else:
393 else:
391 repo.dirstate.normal(f)
394 repo.dirstate.normal(f)
392 elif m == "m": # merge
395 elif m == "m": # merge
393 f2, fd, flag, move = a[2:]
396 f2, fd, flag, move = a[2:]
394 if branchmerge:
397 if branchmerge:
395 # We've done a branch merge, mark this file as merged
398 # We've done a branch merge, mark this file as merged
396 # so that we properly record the merger later
399 # so that we properly record the merger later
397 repo.dirstate.merge(fd)
400 repo.dirstate.merge(fd)
398 if f != f2: # copy/rename
401 if f != f2: # copy/rename
399 if move:
402 if move:
400 repo.dirstate.remove(f)
403 repo.dirstate.remove(f)
401 if f != fd:
404 if f != fd:
402 repo.dirstate.copy(f, fd)
405 repo.dirstate.copy(f, fd)
403 else:
406 else:
404 repo.dirstate.copy(f2, fd)
407 repo.dirstate.copy(f2, fd)
405 else:
408 else:
406 # We've update-merged a locally modified file, so
409 # We've update-merged a locally modified file, so
407 # we set the dirstate to emulate a normal checkout
410 # we set the dirstate to emulate a normal checkout
408 # of that file some time in the past. Thus our
411 # of that file some time in the past. Thus our
409 # merge will appear as a normal local file
412 # merge will appear as a normal local file
410 # modification.
413 # modification.
411 if f2 == fd: # file not locally copied/moved
414 if f2 == fd: # file not locally copied/moved
412 repo.dirstate.normallookup(fd)
415 repo.dirstate.normallookup(fd)
413 if move:
416 if move:
414 repo.dirstate.forget(f)
417 repo.dirstate.forget(f)
415 elif m == "d": # directory rename
418 elif m == "d": # directory rename
416 f2, fd, flag = a[2:]
419 f2, fd, flag = a[2:]
417 if not f2 and f not in repo.dirstate:
420 if not f2 and f not in repo.dirstate:
418 # untracked file moved
421 # untracked file moved
419 continue
422 continue
420 if branchmerge:
423 if branchmerge:
421 repo.dirstate.add(fd)
424 repo.dirstate.add(fd)
422 if f:
425 if f:
423 repo.dirstate.remove(f)
426 repo.dirstate.remove(f)
424 repo.dirstate.copy(f, fd)
427 repo.dirstate.copy(f, fd)
425 if f2:
428 if f2:
426 repo.dirstate.copy(f2, fd)
429 repo.dirstate.copy(f2, fd)
427 else:
430 else:
428 repo.dirstate.normal(fd)
431 repo.dirstate.normal(fd)
429 if f:
432 if f:
430 repo.dirstate.forget(f)
433 repo.dirstate.forget(f)
431
434
432 def update(repo, node, branchmerge, force, partial):
435 def update(repo, node, branchmerge, force, partial):
433 """
436 """
434 Perform a merge between the working directory and the given node
437 Perform a merge between the working directory and the given node
435
438
436 node = the node to update to, or None if unspecified
439 node = the node to update to, or None if unspecified
437 branchmerge = whether to merge between branches
440 branchmerge = whether to merge between branches
438 force = whether to force branch merging or file overwriting
441 force = whether to force branch merging or file overwriting
439 partial = a function to filter file lists (dirstate not updated)
442 partial = a function to filter file lists (dirstate not updated)
440
443
441 The table below shows all the behaviors of the update command
444 The table below shows all the behaviors of the update command
442 given the -c and -C or no options, whether the working directory
445 given the -c and -C or no options, whether the working directory
443 is dirty, whether a revision is specified, and the relationship of
446 is dirty, whether a revision is specified, and the relationship of
444 the parent rev to the target rev (linear, on the same named
447 the parent rev to the target rev (linear, on the same named
445 branch, or on another named branch).
448 branch, or on another named branch).
446
449
447 This logic is tested by test-update-branches.t.
450 This logic is tested by test-update-branches.t.
448
451
449 -c -C dirty rev | linear same cross
452 -c -C dirty rev | linear same cross
450 n n n n | ok (1) x
453 n n n n | ok (1) x
451 n n n y | ok ok ok
454 n n n y | ok ok ok
452 n n y * | merge (2) (2)
455 n n y * | merge (2) (2)
453 n y * * | --- discard ---
456 n y * * | --- discard ---
454 y n y * | --- (3) ---
457 y n y * | --- (3) ---
455 y n n * | --- ok ---
458 y n n * | --- ok ---
456 y y * * | --- (4) ---
459 y y * * | --- (4) ---
457
460
458 x = can't happen
461 x = can't happen
459 * = don't-care
462 * = don't-care
460 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
463 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
461 2 = abort: crosses branches (use 'hg merge' to merge or
464 2 = abort: crosses branches (use 'hg merge' to merge or
462 use 'hg update -C' to discard changes)
465 use 'hg update -C' to discard changes)
463 3 = abort: uncommitted local changes
466 3 = abort: uncommitted local changes
464 4 = incompatible options (checked in commands.py)
467 4 = incompatible options (checked in commands.py)
468
469 Return the same tuple as applyupdates().
465 """
470 """
466
471
467 onode = node
472 onode = node
468 wlock = repo.wlock()
473 wlock = repo.wlock()
469 try:
474 try:
470 wc = repo[None]
475 wc = repo[None]
471 if node is None:
476 if node is None:
472 # tip of current branch
477 # tip of current branch
473 try:
478 try:
474 node = repo.branchtags()[wc.branch()]
479 node = repo.branchtags()[wc.branch()]
475 except KeyError:
480 except KeyError:
476 if wc.branch() == "default": # no default branch!
481 if wc.branch() == "default": # no default branch!
477 node = repo.lookup("tip") # update to tip
482 node = repo.lookup("tip") # update to tip
478 else:
483 else:
479 raise util.Abort(_("branch %s not found") % wc.branch())
484 raise util.Abort(_("branch %s not found") % wc.branch())
480 overwrite = force and not branchmerge
485 overwrite = force and not branchmerge
481 pl = wc.parents()
486 pl = wc.parents()
482 p1, p2 = pl[0], repo[node]
487 p1, p2 = pl[0], repo[node]
483 pa = p1.ancestor(p2)
488 pa = p1.ancestor(p2)
484 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
489 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
485 fastforward = False
490 fastforward = False
486
491
487 ### check phase
492 ### check phase
488 if not overwrite and len(pl) > 1:
493 if not overwrite and len(pl) > 1:
489 raise util.Abort(_("outstanding uncommitted merges"))
494 raise util.Abort(_("outstanding uncommitted merges"))
490 if branchmerge:
495 if branchmerge:
491 if pa == p2:
496 if pa == p2:
492 raise util.Abort(_("merging with a working directory ancestor"
497 raise util.Abort(_("merging with a working directory ancestor"
493 " has no effect"))
498 " has no effect"))
494 elif pa == p1:
499 elif pa == p1:
495 if p1.branch() != p2.branch():
500 if p1.branch() != p2.branch():
496 fastforward = True
501 fastforward = True
497 else:
502 else:
498 raise util.Abort(_("nothing to merge (use 'hg update'"
503 raise util.Abort(_("nothing to merge (use 'hg update'"
499 " or check 'hg heads')"))
504 " or check 'hg heads')"))
500 if not force and (wc.files() or wc.deleted()):
505 if not force and (wc.files() or wc.deleted()):
501 raise util.Abort(_("outstanding uncommitted changes "
506 raise util.Abort(_("outstanding uncommitted changes "
502 "(use 'hg status' to list changes)"))
507 "(use 'hg status' to list changes)"))
503 elif not overwrite:
508 elif not overwrite:
504 if pa == p1 or pa == p2: # linear
509 if pa == p1 or pa == p2: # linear
505 pass # all good
510 pass # all good
506 elif wc.files() or wc.deleted():
511 elif wc.files() or wc.deleted():
507 raise util.Abort(_("crosses branches (merge branches or use"
512 raise util.Abort(_("crosses branches (merge branches or use"
508 " --clean to discard changes)"))
513 " --clean to discard changes)"))
509 elif onode is None:
514 elif onode is None:
510 raise util.Abort(_("crosses branches (merge branches or use"
515 raise util.Abort(_("crosses branches (merge branches or use"
511 " --check to force update)"))
516 " --check to force update)"))
512 else:
517 else:
513 # Allow jumping branches if clean and specific rev given
518 # Allow jumping branches if clean and specific rev given
514 overwrite = True
519 overwrite = True
515
520
516 ### calculate phase
521 ### calculate phase
517 action = []
522 action = []
518 wc.status(unknown=True) # prime cache
523 wc.status(unknown=True) # prime cache
519 if not force:
524 if not force:
520 _checkunknown(wc, p2)
525 _checkunknown(wc, p2)
521 if not util.checkcase(repo.path):
526 if not util.checkcase(repo.path):
522 _checkcollision(p2)
527 _checkcollision(p2)
523 action += _forgetremoved(wc, p2, branchmerge)
528 action += _forgetremoved(wc, p2, branchmerge)
524 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
529 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
525
530
526 ### apply phase
531 ### apply phase
527 if not branchmerge or fastforward: # just jump to the new rev
532 if not branchmerge or fastforward: # just jump to the new rev
528 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
533 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
529 if not partial:
534 if not partial:
530 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
535 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
531
536
532 stats = applyupdates(repo, action, wc, p2, pa)
537 stats = applyupdates(repo, action, wc, p2, pa)
533
538
534 if not partial:
539 if not partial:
535 repo.dirstate.setparents(fp1, fp2)
540 repo.dirstate.setparents(fp1, fp2)
536 recordupdates(repo, action, branchmerge and not fastforward)
541 recordupdates(repo, action, branchmerge and not fastforward)
537 if not branchmerge and not fastforward:
542 if not branchmerge and not fastforward:
538 repo.dirstate.setbranch(p2.branch())
543 repo.dirstate.setbranch(p2.branch())
539 finally:
544 finally:
540 wlock.release()
545 wlock.release()
541
546
542 if not partial:
547 if not partial:
543 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
548 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
544 return stats
549 return stats
General Comments 0
You need to be logged in to leave comments. Login now