##// END OF EJS Templates
dirstate: more explicit name, rename normaldirty() to otherparent()
Benoit Boissinot -
r10968:7a0d096e default
parent child Browse files
Show More
@@ -1,656 +1,661 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-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
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import util, ignore, osutil, parsers
10 import util, ignore, osutil, parsers
11 import struct, os, stat, errno
11 import struct, os, stat, errno
12 import cStringIO
12 import cStringIO
13
13
14 _format = ">cllll"
14 _format = ">cllll"
15 propertycache = util.propertycache
15 propertycache = util.propertycache
16
16
17 def _finddirs(path):
17 def _finddirs(path):
18 pos = path.rfind('/')
18 pos = path.rfind('/')
19 while pos != -1:
19 while pos != -1:
20 yield path[:pos]
20 yield path[:pos]
21 pos = path.rfind('/', 0, pos)
21 pos = path.rfind('/', 0, pos)
22
22
23 def _incdirs(dirs, path):
23 def _incdirs(dirs, path):
24 for base in _finddirs(path):
24 for base in _finddirs(path):
25 if base in dirs:
25 if base in dirs:
26 dirs[base] += 1
26 dirs[base] += 1
27 return
27 return
28 dirs[base] = 1
28 dirs[base] = 1
29
29
30 def _decdirs(dirs, path):
30 def _decdirs(dirs, path):
31 for base in _finddirs(path):
31 for base in _finddirs(path):
32 if dirs[base] > 1:
32 if dirs[base] > 1:
33 dirs[base] -= 1
33 dirs[base] -= 1
34 return
34 return
35 del dirs[base]
35 del dirs[base]
36
36
37 class dirstate(object):
37 class dirstate(object):
38
38
39 def __init__(self, opener, ui, root):
39 def __init__(self, opener, ui, root):
40 '''Create a new dirstate object.
40 '''Create a new dirstate object.
41
41
42 opener is an open()-like callable that can be used to open the
42 opener is an open()-like callable that can be used to open the
43 dirstate file; root is the root of the directory tracked by
43 dirstate file; root is the root of the directory tracked by
44 the dirstate.
44 the dirstate.
45 '''
45 '''
46 self._opener = opener
46 self._opener = opener
47 self._root = root
47 self._root = root
48 self._rootdir = os.path.join(root, '')
48 self._rootdir = os.path.join(root, '')
49 self._dirty = False
49 self._dirty = False
50 self._dirtypl = False
50 self._dirtypl = False
51 self._ui = ui
51 self._ui = ui
52
52
53 @propertycache
53 @propertycache
54 def _map(self):
54 def _map(self):
55 '''Return the dirstate contents as a map from filename to
55 '''Return the dirstate contents as a map from filename to
56 (state, mode, size, time).'''
56 (state, mode, size, time).'''
57 self._read()
57 self._read()
58 return self._map
58 return self._map
59
59
60 @propertycache
60 @propertycache
61 def _copymap(self):
61 def _copymap(self):
62 self._read()
62 self._read()
63 return self._copymap
63 return self._copymap
64
64
65 @propertycache
65 @propertycache
66 def _foldmap(self):
66 def _foldmap(self):
67 f = {}
67 f = {}
68 for name in self._map:
68 for name in self._map:
69 f[os.path.normcase(name)] = name
69 f[os.path.normcase(name)] = name
70 return f
70 return f
71
71
72 @propertycache
72 @propertycache
73 def _branch(self):
73 def _branch(self):
74 try:
74 try:
75 return self._opener("branch").read().strip() or "default"
75 return self._opener("branch").read().strip() or "default"
76 except IOError:
76 except IOError:
77 return "default"
77 return "default"
78
78
79 @propertycache
79 @propertycache
80 def _pl(self):
80 def _pl(self):
81 try:
81 try:
82 st = self._opener("dirstate").read(40)
82 st = self._opener("dirstate").read(40)
83 l = len(st)
83 l = len(st)
84 if l == 40:
84 if l == 40:
85 return st[:20], st[20:40]
85 return st[:20], st[20:40]
86 elif l > 0 and l < 40:
86 elif l > 0 and l < 40:
87 raise util.Abort(_('working directory state appears damaged!'))
87 raise util.Abort(_('working directory state appears damaged!'))
88 except IOError, err:
88 except IOError, err:
89 if err.errno != errno.ENOENT:
89 if err.errno != errno.ENOENT:
90 raise
90 raise
91 return [nullid, nullid]
91 return [nullid, nullid]
92
92
93 @propertycache
93 @propertycache
94 def _dirs(self):
94 def _dirs(self):
95 dirs = {}
95 dirs = {}
96 for f, s in self._map.iteritems():
96 for f, s in self._map.iteritems():
97 if s[0] != 'r':
97 if s[0] != 'r':
98 _incdirs(dirs, f)
98 _incdirs(dirs, f)
99 return dirs
99 return dirs
100
100
101 @propertycache
101 @propertycache
102 def _ignore(self):
102 def _ignore(self):
103 files = [self._join('.hgignore')]
103 files = [self._join('.hgignore')]
104 for name, path in self._ui.configitems("ui"):
104 for name, path in self._ui.configitems("ui"):
105 if name == 'ignore' or name.startswith('ignore.'):
105 if name == 'ignore' or name.startswith('ignore.'):
106 files.append(util.expandpath(path))
106 files.append(util.expandpath(path))
107 return ignore.ignore(self._root, files, self._ui.warn)
107 return ignore.ignore(self._root, files, self._ui.warn)
108
108
109 @propertycache
109 @propertycache
110 def _slash(self):
110 def _slash(self):
111 return self._ui.configbool('ui', 'slash') and os.sep != '/'
111 return self._ui.configbool('ui', 'slash') and os.sep != '/'
112
112
113 @propertycache
113 @propertycache
114 def _checklink(self):
114 def _checklink(self):
115 return util.checklink(self._root)
115 return util.checklink(self._root)
116
116
117 @propertycache
117 @propertycache
118 def _checkexec(self):
118 def _checkexec(self):
119 return util.checkexec(self._root)
119 return util.checkexec(self._root)
120
120
121 @propertycache
121 @propertycache
122 def _checkcase(self):
122 def _checkcase(self):
123 return not util.checkcase(self._join('.hg'))
123 return not util.checkcase(self._join('.hg'))
124
124
125 def _join(self, f):
125 def _join(self, f):
126 # much faster than os.path.join()
126 # much faster than os.path.join()
127 # it's safe because f is always a relative path
127 # it's safe because f is always a relative path
128 return self._rootdir + f
128 return self._rootdir + f
129
129
130 def flagfunc(self, fallback):
130 def flagfunc(self, fallback):
131 if self._checklink:
131 if self._checklink:
132 if self._checkexec:
132 if self._checkexec:
133 def f(x):
133 def f(x):
134 p = self._join(x)
134 p = self._join(x)
135 if os.path.islink(p):
135 if os.path.islink(p):
136 return 'l'
136 return 'l'
137 if util.is_exec(p):
137 if util.is_exec(p):
138 return 'x'
138 return 'x'
139 return ''
139 return ''
140 return f
140 return f
141 def f(x):
141 def f(x):
142 if os.path.islink(self._join(x)):
142 if os.path.islink(self._join(x)):
143 return 'l'
143 return 'l'
144 if 'x' in fallback(x):
144 if 'x' in fallback(x):
145 return 'x'
145 return 'x'
146 return ''
146 return ''
147 return f
147 return f
148 if self._checkexec:
148 if self._checkexec:
149 def f(x):
149 def f(x):
150 if 'l' in fallback(x):
150 if 'l' in fallback(x):
151 return 'l'
151 return 'l'
152 if util.is_exec(self._join(x)):
152 if util.is_exec(self._join(x)):
153 return 'x'
153 return 'x'
154 return ''
154 return ''
155 return f
155 return f
156 return fallback
156 return fallback
157
157
158 def getcwd(self):
158 def getcwd(self):
159 cwd = os.getcwd()
159 cwd = os.getcwd()
160 if cwd == self._root:
160 if cwd == self._root:
161 return ''
161 return ''
162 # self._root ends with a path separator if self._root is '/' or 'C:\'
162 # self._root ends with a path separator if self._root is '/' or 'C:\'
163 rootsep = self._root
163 rootsep = self._root
164 if not util.endswithsep(rootsep):
164 if not util.endswithsep(rootsep):
165 rootsep += os.sep
165 rootsep += os.sep
166 if cwd.startswith(rootsep):
166 if cwd.startswith(rootsep):
167 return cwd[len(rootsep):]
167 return cwd[len(rootsep):]
168 else:
168 else:
169 # we're outside the repo. return an absolute path.
169 # we're outside the repo. return an absolute path.
170 return cwd
170 return cwd
171
171
172 def pathto(self, f, cwd=None):
172 def pathto(self, f, cwd=None):
173 if cwd is None:
173 if cwd is None:
174 cwd = self.getcwd()
174 cwd = self.getcwd()
175 path = util.pathto(self._root, cwd, f)
175 path = util.pathto(self._root, cwd, f)
176 if self._slash:
176 if self._slash:
177 return util.normpath(path)
177 return util.normpath(path)
178 return path
178 return path
179
179
180 def __getitem__(self, key):
180 def __getitem__(self, key):
181 '''Return the current state of key (a filename) in the dirstate.
181 '''Return the current state of key (a filename) in the dirstate.
182
182
183 States are:
183 States are:
184 n normal
184 n normal
185 m needs merging
185 m needs merging
186 r marked for removal
186 r marked for removal
187 a marked for addition
187 a marked for addition
188 ? not tracked
188 ? not tracked
189 '''
189 '''
190 return self._map.get(key, ("?",))[0]
190 return self._map.get(key, ("?",))[0]
191
191
192 def __contains__(self, key):
192 def __contains__(self, key):
193 return key in self._map
193 return key in self._map
194
194
195 def __iter__(self):
195 def __iter__(self):
196 for x in sorted(self._map):
196 for x in sorted(self._map):
197 yield x
197 yield x
198
198
199 def parents(self):
199 def parents(self):
200 return self._pl
200 return self._pl
201
201
202 def branch(self):
202 def branch(self):
203 return self._branch
203 return self._branch
204
204
205 def setparents(self, p1, p2=nullid):
205 def setparents(self, p1, p2=nullid):
206 self._dirty = self._dirtypl = True
206 self._dirty = self._dirtypl = True
207 self._pl = p1, p2
207 self._pl = p1, p2
208
208
209 def setbranch(self, branch):
209 def setbranch(self, branch):
210 if branch in ['tip', '.', 'null']:
210 if branch in ['tip', '.', 'null']:
211 raise util.Abort(_('the name \'%s\' is reserved') % branch)
211 raise util.Abort(_('the name \'%s\' is reserved') % branch)
212 self._branch = branch
212 self._branch = branch
213 self._opener("branch", "w").write(branch + '\n')
213 self._opener("branch", "w").write(branch + '\n')
214
214
215 def _read(self):
215 def _read(self):
216 self._map = {}
216 self._map = {}
217 self._copymap = {}
217 self._copymap = {}
218 try:
218 try:
219 st = self._opener("dirstate").read()
219 st = self._opener("dirstate").read()
220 except IOError, err:
220 except IOError, err:
221 if err.errno != errno.ENOENT:
221 if err.errno != errno.ENOENT:
222 raise
222 raise
223 return
223 return
224 if not st:
224 if not st:
225 return
225 return
226
226
227 p = parsers.parse_dirstate(self._map, self._copymap, st)
227 p = parsers.parse_dirstate(self._map, self._copymap, st)
228 if not self._dirtypl:
228 if not self._dirtypl:
229 self._pl = p
229 self._pl = p
230
230
231 def invalidate(self):
231 def invalidate(self):
232 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
232 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
233 if a in self.__dict__:
233 if a in self.__dict__:
234 delattr(self, a)
234 delattr(self, a)
235 self._dirty = False
235 self._dirty = False
236
236
237 def copy(self, source, dest):
237 def copy(self, source, dest):
238 """Mark dest as a copy of source. Unmark dest if source is None."""
238 """Mark dest as a copy of source. Unmark dest if source is None."""
239 if source == dest:
239 if source == dest:
240 return
240 return
241 self._dirty = True
241 self._dirty = True
242 if source is not None:
242 if source is not None:
243 self._copymap[dest] = source
243 self._copymap[dest] = source
244 elif dest in self._copymap:
244 elif dest in self._copymap:
245 del self._copymap[dest]
245 del self._copymap[dest]
246
246
247 def copied(self, file):
247 def copied(self, file):
248 return self._copymap.get(file, None)
248 return self._copymap.get(file, None)
249
249
250 def copies(self):
250 def copies(self):
251 return self._copymap
251 return self._copymap
252
252
253 def _droppath(self, f):
253 def _droppath(self, f):
254 if self[f] not in "?r" and "_dirs" in self.__dict__:
254 if self[f] not in "?r" and "_dirs" in self.__dict__:
255 _decdirs(self._dirs, f)
255 _decdirs(self._dirs, f)
256
256
257 def _addpath(self, f, check=False):
257 def _addpath(self, f, check=False):
258 oldstate = self[f]
258 oldstate = self[f]
259 if check or oldstate == "r":
259 if check or oldstate == "r":
260 if '\r' in f or '\n' in f:
260 if '\r' in f or '\n' in f:
261 raise util.Abort(
261 raise util.Abort(
262 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
262 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
263 if f in self._dirs:
263 if f in self._dirs:
264 raise util.Abort(_('directory %r already in dirstate') % f)
264 raise util.Abort(_('directory %r already in dirstate') % f)
265 # shadows
265 # shadows
266 for d in _finddirs(f):
266 for d in _finddirs(f):
267 if d in self._dirs:
267 if d in self._dirs:
268 break
268 break
269 if d in self._map and self[d] != 'r':
269 if d in self._map and self[d] != 'r':
270 raise util.Abort(
270 raise util.Abort(
271 _('file %r in dirstate clashes with %r') % (d, f))
271 _('file %r in dirstate clashes with %r') % (d, f))
272 if oldstate in "?r" and "_dirs" in self.__dict__:
272 if oldstate in "?r" and "_dirs" in self.__dict__:
273 _incdirs(self._dirs, f)
273 _incdirs(self._dirs, f)
274
274
275 def normal(self, f):
275 def normal(self, f):
276 '''Mark a file normal and clean.'''
276 '''Mark a file normal and clean.'''
277 self._dirty = True
277 self._dirty = True
278 self._addpath(f)
278 self._addpath(f)
279 s = os.lstat(self._join(f))
279 s = os.lstat(self._join(f))
280 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
280 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
281 if f in self._copymap:
281 if f in self._copymap:
282 del self._copymap[f]
282 del self._copymap[f]
283
283
284 def normallookup(self, f):
284 def normallookup(self, f):
285 '''Mark a file normal, but possibly dirty.'''
285 '''Mark a file normal, but possibly dirty.'''
286 if self._pl[1] != nullid and f in self._map:
286 if self._pl[1] != nullid and f in self._map:
287 # if there is a merge going on and the file was either
287 # if there is a merge going on and the file was either
288 # in state 'm' or dirty before being removed, restore that state.
288 # in state 'm' (-1) or coming from other parent (-2) before
289 # being removed, restore that state.
289 entry = self._map[f]
290 entry = self._map[f]
290 if entry[0] == 'r' and entry[2] in (-1, -2):
291 if entry[0] == 'r' and entry[2] in (-1, -2):
291 source = self._copymap.get(f)
292 source = self._copymap.get(f)
292 if entry[2] == -1:
293 if entry[2] == -1:
293 self.merge(f)
294 self.merge(f)
294 elif entry[2] == -2:
295 elif entry[2] == -2:
295 self.normaldirty(f)
296 self.otherparent(f)
296 if source:
297 if source:
297 self.copy(source, f)
298 self.copy(source, f)
298 return
299 return
299 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
300 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
300 return
301 return
301 self._dirty = True
302 self._dirty = True
302 self._addpath(f)
303 self._addpath(f)
303 self._map[f] = ('n', 0, -1, -1)
304 self._map[f] = ('n', 0, -1, -1)
304 if f in self._copymap:
305 if f in self._copymap:
305 del self._copymap[f]
306 del self._copymap[f]
306
307
307 def normaldirty(self, f):
308 def otherparent(self, f):
308 '''Mark a file normal, but dirty.'''
309 '''Mark as coming from the other parent, always dirty.'''
310 if self._pl[1] == nullid:
311 raise util.Abort(_("setting %r to other parent "
312 "only allowed in merges") % f)
309 self._dirty = True
313 self._dirty = True
310 self._addpath(f)
314 self._addpath(f)
311 self._map[f] = ('n', 0, -2, -1)
315 self._map[f] = ('n', 0, -2, -1)
312 if f in self._copymap:
316 if f in self._copymap:
313 del self._copymap[f]
317 del self._copymap[f]
314
318
315 def add(self, f):
319 def add(self, f):
316 '''Mark a file added.'''
320 '''Mark a file added.'''
317 self._dirty = True
321 self._dirty = True
318 self._addpath(f, True)
322 self._addpath(f, True)
319 self._map[f] = ('a', 0, -1, -1)
323 self._map[f] = ('a', 0, -1, -1)
320 if f in self._copymap:
324 if f in self._copymap:
321 del self._copymap[f]
325 del self._copymap[f]
322
326
323 def remove(self, f):
327 def remove(self, f):
324 '''Mark a file removed.'''
328 '''Mark a file removed.'''
325 self._dirty = True
329 self._dirty = True
326 self._droppath(f)
330 self._droppath(f)
327 size = 0
331 size = 0
328 if self._pl[1] != nullid and f in self._map:
332 if self._pl[1] != nullid and f in self._map:
333 # backup the previous state
329 entry = self._map[f]
334 entry = self._map[f]
330 if entry[0] == 'm':
335 if entry[0] == 'm': # merge
331 size = -1
336 size = -1
332 elif entry[0] == 'n' and entry[2] == -2:
337 elif entry[0] == 'n' and entry[2] == -2: # other parent
333 size = -2
338 size = -2
334 self._map[f] = ('r', 0, size, 0)
339 self._map[f] = ('r', 0, size, 0)
335 if size == 0 and f in self._copymap:
340 if size == 0 and f in self._copymap:
336 del self._copymap[f]
341 del self._copymap[f]
337
342
338 def merge(self, f):
343 def merge(self, f):
339 '''Mark a file merged.'''
344 '''Mark a file merged.'''
340 self._dirty = True
345 self._dirty = True
341 s = os.lstat(self._join(f))
346 s = os.lstat(self._join(f))
342 self._addpath(f)
347 self._addpath(f)
343 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
348 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
344 if f in self._copymap:
349 if f in self._copymap:
345 del self._copymap[f]
350 del self._copymap[f]
346
351
347 def forget(self, f):
352 def forget(self, f):
348 '''Forget a file.'''
353 '''Forget a file.'''
349 self._dirty = True
354 self._dirty = True
350 try:
355 try:
351 self._droppath(f)
356 self._droppath(f)
352 del self._map[f]
357 del self._map[f]
353 except KeyError:
358 except KeyError:
354 self._ui.warn(_("not in dirstate: %s\n") % f)
359 self._ui.warn(_("not in dirstate: %s\n") % f)
355
360
356 def _normalize(self, path, knownpath):
361 def _normalize(self, path, knownpath):
357 norm_path = os.path.normcase(path)
362 norm_path = os.path.normcase(path)
358 fold_path = self._foldmap.get(norm_path, None)
363 fold_path = self._foldmap.get(norm_path, None)
359 if fold_path is None:
364 if fold_path is None:
360 if knownpath or not os.path.exists(os.path.join(self._root, path)):
365 if knownpath or not os.path.exists(os.path.join(self._root, path)):
361 fold_path = path
366 fold_path = path
362 else:
367 else:
363 fold_path = self._foldmap.setdefault(norm_path,
368 fold_path = self._foldmap.setdefault(norm_path,
364 util.fspath(path, self._root))
369 util.fspath(path, self._root))
365 return fold_path
370 return fold_path
366
371
367 def clear(self):
372 def clear(self):
368 self._map = {}
373 self._map = {}
369 if "_dirs" in self.__dict__:
374 if "_dirs" in self.__dict__:
370 delattr(self, "_dirs")
375 delattr(self, "_dirs")
371 self._copymap = {}
376 self._copymap = {}
372 self._pl = [nullid, nullid]
377 self._pl = [nullid, nullid]
373 self._dirty = True
378 self._dirty = True
374
379
375 def rebuild(self, parent, files):
380 def rebuild(self, parent, files):
376 self.clear()
381 self.clear()
377 for f in files:
382 for f in files:
378 if 'x' in files.flags(f):
383 if 'x' in files.flags(f):
379 self._map[f] = ('n', 0777, -1, 0)
384 self._map[f] = ('n', 0777, -1, 0)
380 else:
385 else:
381 self._map[f] = ('n', 0666, -1, 0)
386 self._map[f] = ('n', 0666, -1, 0)
382 self._pl = (parent, nullid)
387 self._pl = (parent, nullid)
383 self._dirty = True
388 self._dirty = True
384
389
385 def write(self):
390 def write(self):
386 if not self._dirty:
391 if not self._dirty:
387 return
392 return
388 st = self._opener("dirstate", "w", atomictemp=True)
393 st = self._opener("dirstate", "w", atomictemp=True)
389
394
390 # use the modification time of the newly created temporary file as the
395 # use the modification time of the newly created temporary file as the
391 # filesystem's notion of 'now'
396 # filesystem's notion of 'now'
392 now = int(util.fstat(st).st_mtime)
397 now = int(util.fstat(st).st_mtime)
393
398
394 cs = cStringIO.StringIO()
399 cs = cStringIO.StringIO()
395 copymap = self._copymap
400 copymap = self._copymap
396 pack = struct.pack
401 pack = struct.pack
397 write = cs.write
402 write = cs.write
398 write("".join(self._pl))
403 write("".join(self._pl))
399 for f, e in self._map.iteritems():
404 for f, e in self._map.iteritems():
400 if e[0] == 'n' and e[3] == now:
405 if e[0] == 'n' and e[3] == now:
401 # The file was last modified "simultaneously" with the current
406 # The file was last modified "simultaneously" with the current
402 # write to dirstate (i.e. within the same second for file-
407 # write to dirstate (i.e. within the same second for file-
403 # systems with a granularity of 1 sec). This commonly happens
408 # systems with a granularity of 1 sec). This commonly happens
404 # for at least a couple of files on 'update'.
409 # for at least a couple of files on 'update'.
405 # The user could change the file without changing its size
410 # The user could change the file without changing its size
406 # within the same second. Invalidate the file's stat data in
411 # within the same second. Invalidate the file's stat data in
407 # dirstate, forcing future 'status' calls to compare the
412 # dirstate, forcing future 'status' calls to compare the
408 # contents of the file. This prevents mistakenly treating such
413 # contents of the file. This prevents mistakenly treating such
409 # files as clean.
414 # files as clean.
410 e = (e[0], 0, -1, -1) # mark entry as 'unset'
415 e = (e[0], 0, -1, -1) # mark entry as 'unset'
411 self._map[f] = e
416 self._map[f] = e
412
417
413 if f in copymap:
418 if f in copymap:
414 f = "%s\0%s" % (f, copymap[f])
419 f = "%s\0%s" % (f, copymap[f])
415 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
420 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
416 write(e)
421 write(e)
417 write(f)
422 write(f)
418 st.write(cs.getvalue())
423 st.write(cs.getvalue())
419 st.rename()
424 st.rename()
420 self._dirty = self._dirtypl = False
425 self._dirty = self._dirtypl = False
421
426
422 def _dirignore(self, f):
427 def _dirignore(self, f):
423 if f == '.':
428 if f == '.':
424 return False
429 return False
425 if self._ignore(f):
430 if self._ignore(f):
426 return True
431 return True
427 for p in _finddirs(f):
432 for p in _finddirs(f):
428 if self._ignore(p):
433 if self._ignore(p):
429 return True
434 return True
430 return False
435 return False
431
436
432 def walk(self, match, subrepos, unknown, ignored):
437 def walk(self, match, subrepos, unknown, ignored):
433 '''
438 '''
434 Walk recursively through the directory tree, finding all files
439 Walk recursively through the directory tree, finding all files
435 matched by match.
440 matched by match.
436
441
437 Return a dict mapping filename to stat-like object (either
442 Return a dict mapping filename to stat-like object (either
438 mercurial.osutil.stat instance or return value of os.stat()).
443 mercurial.osutil.stat instance or return value of os.stat()).
439 '''
444 '''
440
445
441 def fwarn(f, msg):
446 def fwarn(f, msg):
442 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
447 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
443 return False
448 return False
444
449
445 def badtype(mode):
450 def badtype(mode):
446 kind = _('unknown')
451 kind = _('unknown')
447 if stat.S_ISCHR(mode):
452 if stat.S_ISCHR(mode):
448 kind = _('character device')
453 kind = _('character device')
449 elif stat.S_ISBLK(mode):
454 elif stat.S_ISBLK(mode):
450 kind = _('block device')
455 kind = _('block device')
451 elif stat.S_ISFIFO(mode):
456 elif stat.S_ISFIFO(mode):
452 kind = _('fifo')
457 kind = _('fifo')
453 elif stat.S_ISSOCK(mode):
458 elif stat.S_ISSOCK(mode):
454 kind = _('socket')
459 kind = _('socket')
455 elif stat.S_ISDIR(mode):
460 elif stat.S_ISDIR(mode):
456 kind = _('directory')
461 kind = _('directory')
457 return _('unsupported file type (type is %s)') % kind
462 return _('unsupported file type (type is %s)') % kind
458
463
459 ignore = self._ignore
464 ignore = self._ignore
460 dirignore = self._dirignore
465 dirignore = self._dirignore
461 if ignored:
466 if ignored:
462 ignore = util.never
467 ignore = util.never
463 dirignore = util.never
468 dirignore = util.never
464 elif not unknown:
469 elif not unknown:
465 # if unknown and ignored are False, skip step 2
470 # if unknown and ignored are False, skip step 2
466 ignore = util.always
471 ignore = util.always
467 dirignore = util.always
472 dirignore = util.always
468
473
469 matchfn = match.matchfn
474 matchfn = match.matchfn
470 badfn = match.bad
475 badfn = match.bad
471 dmap = self._map
476 dmap = self._map
472 normpath = util.normpath
477 normpath = util.normpath
473 listdir = osutil.listdir
478 listdir = osutil.listdir
474 lstat = os.lstat
479 lstat = os.lstat
475 getkind = stat.S_IFMT
480 getkind = stat.S_IFMT
476 dirkind = stat.S_IFDIR
481 dirkind = stat.S_IFDIR
477 regkind = stat.S_IFREG
482 regkind = stat.S_IFREG
478 lnkkind = stat.S_IFLNK
483 lnkkind = stat.S_IFLNK
479 join = self._join
484 join = self._join
480 work = []
485 work = []
481 wadd = work.append
486 wadd = work.append
482
487
483 if self._checkcase:
488 if self._checkcase:
484 normalize = self._normalize
489 normalize = self._normalize
485 else:
490 else:
486 normalize = lambda x, y: x
491 normalize = lambda x, y: x
487
492
488 exact = skipstep3 = False
493 exact = skipstep3 = False
489 if matchfn == match.exact: # match.exact
494 if matchfn == match.exact: # match.exact
490 exact = True
495 exact = True
491 dirignore = util.always # skip step 2
496 dirignore = util.always # skip step 2
492 elif match.files() and not match.anypats(): # match.match, no patterns
497 elif match.files() and not match.anypats(): # match.match, no patterns
493 skipstep3 = True
498 skipstep3 = True
494
499
495 files = set(match.files())
500 files = set(match.files())
496 if not files or '.' in files:
501 if not files or '.' in files:
497 files = ['']
502 files = ['']
498 results = dict.fromkeys(subrepos)
503 results = dict.fromkeys(subrepos)
499 results['.hg'] = None
504 results['.hg'] = None
500
505
501 # step 1: find all explicit files
506 # step 1: find all explicit files
502 for ff in sorted(files):
507 for ff in sorted(files):
503 nf = normalize(normpath(ff), False)
508 nf = normalize(normpath(ff), False)
504 if nf in results:
509 if nf in results:
505 continue
510 continue
506
511
507 try:
512 try:
508 st = lstat(join(nf))
513 st = lstat(join(nf))
509 kind = getkind(st.st_mode)
514 kind = getkind(st.st_mode)
510 if kind == dirkind:
515 if kind == dirkind:
511 skipstep3 = False
516 skipstep3 = False
512 if nf in dmap:
517 if nf in dmap:
513 #file deleted on disk but still in dirstate
518 #file deleted on disk but still in dirstate
514 results[nf] = None
519 results[nf] = None
515 match.dir(nf)
520 match.dir(nf)
516 if not dirignore(nf):
521 if not dirignore(nf):
517 wadd(nf)
522 wadd(nf)
518 elif kind == regkind or kind == lnkkind:
523 elif kind == regkind or kind == lnkkind:
519 results[nf] = st
524 results[nf] = st
520 else:
525 else:
521 badfn(ff, badtype(kind))
526 badfn(ff, badtype(kind))
522 if nf in dmap:
527 if nf in dmap:
523 results[nf] = None
528 results[nf] = None
524 except OSError, inst:
529 except OSError, inst:
525 if nf in dmap: # does it exactly match a file?
530 if nf in dmap: # does it exactly match a file?
526 results[nf] = None
531 results[nf] = None
527 else: # does it match a directory?
532 else: # does it match a directory?
528 prefix = nf + "/"
533 prefix = nf + "/"
529 for fn in dmap:
534 for fn in dmap:
530 if fn.startswith(prefix):
535 if fn.startswith(prefix):
531 match.dir(nf)
536 match.dir(nf)
532 skipstep3 = False
537 skipstep3 = False
533 break
538 break
534 else:
539 else:
535 badfn(ff, inst.strerror)
540 badfn(ff, inst.strerror)
536
541
537 # step 2: visit subdirectories
542 # step 2: visit subdirectories
538 while work:
543 while work:
539 nd = work.pop()
544 nd = work.pop()
540 skip = None
545 skip = None
541 if nd == '.':
546 if nd == '.':
542 nd = ''
547 nd = ''
543 else:
548 else:
544 skip = '.hg'
549 skip = '.hg'
545 try:
550 try:
546 entries = listdir(join(nd), stat=True, skip=skip)
551 entries = listdir(join(nd), stat=True, skip=skip)
547 except OSError, inst:
552 except OSError, inst:
548 if inst.errno == errno.EACCES:
553 if inst.errno == errno.EACCES:
549 fwarn(nd, inst.strerror)
554 fwarn(nd, inst.strerror)
550 continue
555 continue
551 raise
556 raise
552 for f, kind, st in entries:
557 for f, kind, st in entries:
553 nf = normalize(nd and (nd + "/" + f) or f, True)
558 nf = normalize(nd and (nd + "/" + f) or f, True)
554 if nf not in results:
559 if nf not in results:
555 if kind == dirkind:
560 if kind == dirkind:
556 if not ignore(nf):
561 if not ignore(nf):
557 match.dir(nf)
562 match.dir(nf)
558 wadd(nf)
563 wadd(nf)
559 if nf in dmap and matchfn(nf):
564 if nf in dmap and matchfn(nf):
560 results[nf] = None
565 results[nf] = None
561 elif kind == regkind or kind == lnkkind:
566 elif kind == regkind or kind == lnkkind:
562 if nf in dmap:
567 if nf in dmap:
563 if matchfn(nf):
568 if matchfn(nf):
564 results[nf] = st
569 results[nf] = st
565 elif matchfn(nf) and not ignore(nf):
570 elif matchfn(nf) and not ignore(nf):
566 results[nf] = st
571 results[nf] = st
567 elif nf in dmap and matchfn(nf):
572 elif nf in dmap and matchfn(nf):
568 results[nf] = None
573 results[nf] = None
569
574
570 # step 3: report unseen items in the dmap hash
575 # step 3: report unseen items in the dmap hash
571 if not skipstep3 and not exact:
576 if not skipstep3 and not exact:
572 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
577 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
573 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
578 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
574 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
579 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
575 st = None
580 st = None
576 results[nf] = st
581 results[nf] = st
577 for s in subrepos:
582 for s in subrepos:
578 del results[s]
583 del results[s]
579 del results['.hg']
584 del results['.hg']
580 return results
585 return results
581
586
582 def status(self, match, subrepos, ignored, clean, unknown):
587 def status(self, match, subrepos, ignored, clean, unknown):
583 '''Determine the status of the working copy relative to the
588 '''Determine the status of the working copy relative to the
584 dirstate and return a tuple of lists (unsure, modified, added,
589 dirstate and return a tuple of lists (unsure, modified, added,
585 removed, deleted, unknown, ignored, clean), where:
590 removed, deleted, unknown, ignored, clean), where:
586
591
587 unsure:
592 unsure:
588 files that might have been modified since the dirstate was
593 files that might have been modified since the dirstate was
589 written, but need to be read to be sure (size is the same
594 written, but need to be read to be sure (size is the same
590 but mtime differs)
595 but mtime differs)
591 modified:
596 modified:
592 files that have definitely been modified since the dirstate
597 files that have definitely been modified since the dirstate
593 was written (different size or mode)
598 was written (different size or mode)
594 added:
599 added:
595 files that have been explicitly added with hg add
600 files that have been explicitly added with hg add
596 removed:
601 removed:
597 files that have been explicitly removed with hg remove
602 files that have been explicitly removed with hg remove
598 deleted:
603 deleted:
599 files that have been deleted through other means ("missing")
604 files that have been deleted through other means ("missing")
600 unknown:
605 unknown:
601 files not in the dirstate that are not ignored
606 files not in the dirstate that are not ignored
602 ignored:
607 ignored:
603 files not in the dirstate that are ignored
608 files not in the dirstate that are ignored
604 (by _dirignore())
609 (by _dirignore())
605 clean:
610 clean:
606 files that have definitely not been modified since the
611 files that have definitely not been modified since the
607 dirstate was written
612 dirstate was written
608 '''
613 '''
609 listignored, listclean, listunknown = ignored, clean, unknown
614 listignored, listclean, listunknown = ignored, clean, unknown
610 lookup, modified, added, unknown, ignored = [], [], [], [], []
615 lookup, modified, added, unknown, ignored = [], [], [], [], []
611 removed, deleted, clean = [], [], []
616 removed, deleted, clean = [], [], []
612
617
613 dmap = self._map
618 dmap = self._map
614 ladd = lookup.append # aka "unsure"
619 ladd = lookup.append # aka "unsure"
615 madd = modified.append
620 madd = modified.append
616 aadd = added.append
621 aadd = added.append
617 uadd = unknown.append
622 uadd = unknown.append
618 iadd = ignored.append
623 iadd = ignored.append
619 radd = removed.append
624 radd = removed.append
620 dadd = deleted.append
625 dadd = deleted.append
621 cadd = clean.append
626 cadd = clean.append
622
627
623 for fn, st in self.walk(match, subrepos, listunknown,
628 for fn, st in self.walk(match, subrepos, listunknown,
624 listignored).iteritems():
629 listignored).iteritems():
625 if fn not in dmap:
630 if fn not in dmap:
626 if (listignored or match.exact(fn)) and self._dirignore(fn):
631 if (listignored or match.exact(fn)) and self._dirignore(fn):
627 if listignored:
632 if listignored:
628 iadd(fn)
633 iadd(fn)
629 elif listunknown:
634 elif listunknown:
630 uadd(fn)
635 uadd(fn)
631 continue
636 continue
632
637
633 state, mode, size, time = dmap[fn]
638 state, mode, size, time = dmap[fn]
634
639
635 if not st and state in "nma":
640 if not st and state in "nma":
636 dadd(fn)
641 dadd(fn)
637 elif state == 'n':
642 elif state == 'n':
638 if (size >= 0 and
643 if (size >= 0 and
639 (size != st.st_size
644 (size != st.st_size
640 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
645 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
641 or size == -2
646 or size == -2 # other parent
642 or fn in self._copymap):
647 or fn in self._copymap):
643 madd(fn)
648 madd(fn)
644 elif time != int(st.st_mtime):
649 elif time != int(st.st_mtime):
645 ladd(fn)
650 ladd(fn)
646 elif listclean:
651 elif listclean:
647 cadd(fn)
652 cadd(fn)
648 elif state == 'm':
653 elif state == 'm':
649 madd(fn)
654 madd(fn)
650 elif state == 'a':
655 elif state == 'a':
651 aadd(fn)
656 aadd(fn)
652 elif state == 'r':
657 elif state == 'r':
653 radd(fn)
658 radd(fn)
654
659
655 return (lookup, modified, added, removed, deleted, unknown, ignored,
660 return (lookup, modified, added, removed, deleted, unknown, ignored,
656 clean)
661 clean)
@@ -1,519 +1,519 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._read()
17 self._read()
18 def reset(self, node=None):
18 def reset(self, node=None):
19 self._state = {}
19 self._state = {}
20 if node:
20 if node:
21 self._local = node
21 self._local = node
22 shutil.rmtree(self._repo.join("merge"), True)
22 shutil.rmtree(self._repo.join("merge"), True)
23 def _read(self):
23 def _read(self):
24 self._state = {}
24 self._state = {}
25 try:
25 try:
26 localnode = None
26 localnode = None
27 f = self._repo.opener("merge/state")
27 f = self._repo.opener("merge/state")
28 for i, l in enumerate(f):
28 for i, l in enumerate(f):
29 if i == 0:
29 if i == 0:
30 localnode = l[:-1]
30 localnode = l[:-1]
31 else:
31 else:
32 bits = l[:-1].split("\0")
32 bits = l[:-1].split("\0")
33 self._state[bits[0]] = bits[1:]
33 self._state[bits[0]] = bits[1:]
34 self._local = bin(localnode)
34 self._local = bin(localnode)
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 def _write(self):
38 def _write(self):
39 f = self._repo.opener("merge/state", "w")
39 f = self._repo.opener("merge/state", "w")
40 f.write(hex(self._local) + "\n")
40 f.write(hex(self._local) + "\n")
41 for d, v in self._state.iteritems():
41 for d, v in self._state.iteritems():
42 f.write("\0".join([d] + v) + "\n")
42 f.write("\0".join([d] + v) + "\n")
43 def add(self, fcl, fco, fca, fd, flags):
43 def add(self, fcl, fco, fca, fd, flags):
44 hash = util.sha1(fcl.path()).hexdigest()
44 hash = util.sha1(fcl.path()).hexdigest()
45 self._repo.opener("merge/" + hash, "w").write(fcl.data())
45 self._repo.opener("merge/" + hash, "w").write(fcl.data())
46 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
46 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
47 hex(fca.filenode()), fco.path(), flags]
47 hex(fca.filenode()), fco.path(), flags]
48 self._write()
48 self._write()
49 def __contains__(self, dfile):
49 def __contains__(self, dfile):
50 return dfile in self._state
50 return dfile in self._state
51 def __getitem__(self, dfile):
51 def __getitem__(self, dfile):
52 return self._state[dfile][0]
52 return self._state[dfile][0]
53 def __iter__(self):
53 def __iter__(self):
54 l = self._state.keys()
54 l = self._state.keys()
55 l.sort()
55 l.sort()
56 for f in l:
56 for f in l:
57 yield f
57 yield f
58 def mark(self, dfile, state):
58 def mark(self, dfile, state):
59 self._state[dfile][0] = state
59 self._state[dfile][0] = state
60 self._write()
60 self._write()
61 def resolve(self, dfile, wctx, octx):
61 def resolve(self, dfile, wctx, octx):
62 if self[dfile] == 'r':
62 if self[dfile] == 'r':
63 return 0
63 return 0
64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
65 f = self._repo.opener("merge/" + hash)
65 f = self._repo.opener("merge/" + hash)
66 self._repo.wwrite(dfile, f.read(), flags)
66 self._repo.wwrite(dfile, f.read(), flags)
67 fcd = wctx[dfile]
67 fcd = wctx[dfile]
68 fco = octx[ofile]
68 fco = octx[ofile]
69 fca = self._repo.filectx(afile, fileid=anode)
69 fca = self._repo.filectx(afile, fileid=anode)
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
71 if not r:
71 if not r:
72 self.mark(dfile, 'r')
72 self.mark(dfile, 'r')
73 return r
73 return r
74
74
75 def _checkunknown(wctx, mctx):
75 def _checkunknown(wctx, mctx):
76 "check for collisions between unknown files and files in mctx"
76 "check for collisions between unknown files and files in mctx"
77 for f in wctx.unknown():
77 for f in wctx.unknown():
78 if f in mctx and mctx[f].cmp(wctx[f].data()):
78 if f in mctx and mctx[f].cmp(wctx[f].data()):
79 raise util.Abort(_("untracked file in working directory differs"
79 raise util.Abort(_("untracked file in working directory differs"
80 " from file in requested revision: '%s'") % f)
80 " from file in requested revision: '%s'") % f)
81
81
82 def _checkcollision(mctx):
82 def _checkcollision(mctx):
83 "check for case folding collisions in the destination context"
83 "check for case folding collisions in the destination context"
84 folded = {}
84 folded = {}
85 for fn in mctx:
85 for fn in mctx:
86 fold = fn.lower()
86 fold = fn.lower()
87 if fold in folded:
87 if fold in folded:
88 raise util.Abort(_("case-folding collision between %s and %s")
88 raise util.Abort(_("case-folding collision between %s and %s")
89 % (fn, folded[fold]))
89 % (fn, folded[fold]))
90 folded[fold] = fn
90 folded[fold] = fn
91
91
92 def _forgetremoved(wctx, mctx, branchmerge):
92 def _forgetremoved(wctx, mctx, branchmerge):
93 """
93 """
94 Forget removed files
94 Forget removed files
95
95
96 If we're jumping between revisions (as opposed to merging), and if
96 If we're jumping between revisions (as opposed to merging), and if
97 neither the working directory nor the target rev has the file,
97 neither the working directory nor the target rev has the file,
98 then we need to remove it from the dirstate, to prevent the
98 then we need to remove it from the dirstate, to prevent the
99 dirstate from listing the file when it is no longer in the
99 dirstate from listing the file when it is no longer in the
100 manifest.
100 manifest.
101
101
102 If we're merging, and the other revision has removed a file
102 If we're merging, and the other revision has removed a file
103 that is not present in the working directory, we need to mark it
103 that is not present in the working directory, we need to mark it
104 as removed.
104 as removed.
105 """
105 """
106
106
107 action = []
107 action = []
108 state = branchmerge and 'r' or 'f'
108 state = branchmerge and 'r' or 'f'
109 for f in wctx.deleted():
109 for f in wctx.deleted():
110 if f not in mctx:
110 if f not in mctx:
111 action.append((f, state))
111 action.append((f, state))
112
112
113 if not branchmerge:
113 if not branchmerge:
114 for f in wctx.removed():
114 for f in wctx.removed():
115 if f not in mctx:
115 if f not in mctx:
116 action.append((f, "f"))
116 action.append((f, "f"))
117
117
118 return action
118 return action
119
119
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
121 """
121 """
122 Merge p1 and p2 with ancestor ma and generate merge action list
122 Merge p1 and p2 with ancestor ma and generate merge action list
123
123
124 overwrite = whether we clobber working files
124 overwrite = whether we clobber working files
125 partial = function to filter file lists
125 partial = function to filter file lists
126 """
126 """
127
127
128 def fmerge(f, f2, fa):
128 def fmerge(f, f2, fa):
129 """merge flags"""
129 """merge flags"""
130 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
130 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
131 if m == n: # flags agree
131 if m == n: # flags agree
132 return m # unchanged
132 return m # unchanged
133 if m and n and not a: # flags set, don't agree, differ from parent
133 if m and n and not a: # flags set, don't agree, differ from parent
134 r = repo.ui.promptchoice(
134 r = repo.ui.promptchoice(
135 _(" conflicting flags for %s\n"
135 _(" conflicting flags for %s\n"
136 "(n)one, e(x)ec or sym(l)ink?") % f,
136 "(n)one, e(x)ec or sym(l)ink?") % f,
137 (_("&None"), _("E&xec"), _("Sym&link")), 0)
137 (_("&None"), _("E&xec"), _("Sym&link")), 0)
138 if r == 1:
138 if r == 1:
139 return "x" # Exec
139 return "x" # Exec
140 if r == 2:
140 if r == 2:
141 return "l" # Symlink
141 return "l" # Symlink
142 return ""
142 return ""
143 if m and m != a: # changed from a to m
143 if m and m != a: # changed from a to m
144 return m
144 return m
145 if n and n != a: # changed from a to n
145 if n and n != a: # changed from a to n
146 return n
146 return n
147 return '' # flag was cleared
147 return '' # flag was cleared
148
148
149 def act(msg, m, f, *args):
149 def act(msg, m, f, *args):
150 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
150 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
151 action.append((f, m) + args)
151 action.append((f, m) + args)
152
152
153 action, copy = [], {}
153 action, copy = [], {}
154
154
155 if overwrite:
155 if overwrite:
156 pa = p1
156 pa = p1
157 elif pa == p2: # backwards
157 elif pa == p2: # backwards
158 pa = p1.p1()
158 pa = p1.p1()
159 elif pa and repo.ui.configbool("merge", "followcopies", True):
159 elif pa and repo.ui.configbool("merge", "followcopies", True):
160 dirs = repo.ui.configbool("merge", "followdirs", True)
160 dirs = repo.ui.configbool("merge", "followdirs", True)
161 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
161 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
162 for of, fl in diverge.iteritems():
162 for of, fl in diverge.iteritems():
163 act("divergent renames", "dr", of, fl)
163 act("divergent renames", "dr", of, fl)
164
164
165 repo.ui.note(_("resolving manifests\n"))
165 repo.ui.note(_("resolving manifests\n"))
166 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
166 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
167 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
167 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
168
168
169 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
169 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
170 copied = set(copy.values())
170 copied = set(copy.values())
171
171
172 if not overwrite and '.hgsubstate' in m1:
172 if not overwrite and '.hgsubstate' in m1:
173 # check whether sub state is modified
173 # check whether sub state is modified
174 for s in p1.substate:
174 for s in p1.substate:
175 if p1.sub(s).dirty():
175 if p1.sub(s).dirty():
176 m1['.hgsubstate'] += "+"
176 m1['.hgsubstate'] += "+"
177 break
177 break
178
178
179 # Compare manifests
179 # Compare manifests
180 for f, n in m1.iteritems():
180 for f, n in m1.iteritems():
181 if partial and not partial(f):
181 if partial and not partial(f):
182 continue
182 continue
183 if f in m2:
183 if f in m2:
184 rflags = fmerge(f, f, f)
184 rflags = fmerge(f, f, f)
185 a = ma.get(f, nullid)
185 a = ma.get(f, nullid)
186 if n == m2[f] or m2[f] == a: # same or local newer
186 if n == m2[f] or m2[f] == a: # same or local newer
187 if m1.flags(f) != rflags:
187 if m1.flags(f) != rflags:
188 act("update permissions", "e", f, rflags)
188 act("update permissions", "e", f, rflags)
189 elif n == a: # remote newer
189 elif n == a: # remote newer
190 act("remote is newer", "g", f, rflags)
190 act("remote is newer", "g", f, rflags)
191 else: # both changed
191 else: # both changed
192 act("versions differ", "m", f, f, f, rflags, False)
192 act("versions differ", "m", f, f, f, rflags, False)
193 elif f in copied: # files we'll deal with on m2 side
193 elif f in copied: # files we'll deal with on m2 side
194 pass
194 pass
195 elif f in copy:
195 elif f in copy:
196 f2 = copy[f]
196 f2 = copy[f]
197 if f2 not in m2: # directory rename
197 if f2 not in m2: # directory rename
198 act("remote renamed directory to " + f2, "d",
198 act("remote renamed directory to " + f2, "d",
199 f, None, f2, m1.flags(f))
199 f, None, f2, m1.flags(f))
200 else: # case 2 A,B/B/B or case 4,21 A/B/B
200 else: # case 2 A,B/B/B or case 4,21 A/B/B
201 act("local copied/moved to " + f2, "m",
201 act("local copied/moved to " + f2, "m",
202 f, f2, f, fmerge(f, f2, f2), False)
202 f, f2, f, fmerge(f, f2, f2), False)
203 elif f in ma: # clean, a different, no remote
203 elif f in ma: # clean, a different, no remote
204 if n != ma[f]:
204 if n != ma[f]:
205 if repo.ui.promptchoice(
205 if repo.ui.promptchoice(
206 _(" local changed %s which remote deleted\n"
206 _(" local changed %s which remote deleted\n"
207 "use (c)hanged version or (d)elete?") % f,
207 "use (c)hanged version or (d)elete?") % f,
208 (_("&Changed"), _("&Delete")), 0):
208 (_("&Changed"), _("&Delete")), 0):
209 act("prompt delete", "r", f)
209 act("prompt delete", "r", f)
210 else:
210 else:
211 act("prompt keep", "a", f)
211 act("prompt keep", "a", f)
212 elif n[20:] == "a": # added, no remote
212 elif n[20:] == "a": # added, no remote
213 act("remote deleted", "f", f)
213 act("remote deleted", "f", f)
214 elif n[20:] != "u":
214 elif n[20:] != "u":
215 act("other deleted", "r", f)
215 act("other deleted", "r", f)
216
216
217 for f, n in m2.iteritems():
217 for f, n in m2.iteritems():
218 if partial and not partial(f):
218 if partial and not partial(f):
219 continue
219 continue
220 if f in m1 or f in copied: # files already visited
220 if f in m1 or f in copied: # files already visited
221 continue
221 continue
222 if f in copy:
222 if f in copy:
223 f2 = copy[f]
223 f2 = copy[f]
224 if f2 not in m1: # directory rename
224 if f2 not in m1: # directory rename
225 act("local renamed directory to " + f2, "d",
225 act("local renamed directory to " + f2, "d",
226 None, f, f2, m2.flags(f))
226 None, f, f2, m2.flags(f))
227 elif f2 in m2: # rename case 1, A/A,B/A
227 elif f2 in m2: # rename case 1, A/A,B/A
228 act("remote copied to " + f, "m",
228 act("remote copied to " + f, "m",
229 f2, f, f, fmerge(f2, f, f2), False)
229 f2, f, f, fmerge(f2, f, f2), False)
230 else: # case 3,20 A/B/A
230 else: # case 3,20 A/B/A
231 act("remote moved to " + f, "m",
231 act("remote moved to " + f, "m",
232 f2, f, f, fmerge(f2, f, f2), True)
232 f2, f, f, fmerge(f2, f, f2), True)
233 elif f not in ma:
233 elif f not in ma:
234 act("remote created", "g", f, m2.flags(f))
234 act("remote created", "g", f, m2.flags(f))
235 elif n != ma[f]:
235 elif n != ma[f]:
236 if repo.ui.promptchoice(
236 if repo.ui.promptchoice(
237 _("remote changed %s which local deleted\n"
237 _("remote changed %s which local deleted\n"
238 "use (c)hanged version or leave (d)eleted?") % f,
238 "use (c)hanged version or leave (d)eleted?") % f,
239 (_("&Changed"), _("&Deleted")), 0) == 0:
239 (_("&Changed"), _("&Deleted")), 0) == 0:
240 act("prompt recreating", "g", f, m2.flags(f))
240 act("prompt recreating", "g", f, m2.flags(f))
241
241
242 return action
242 return action
243
243
244 def actionkey(a):
244 def actionkey(a):
245 return a[1] == 'r' and -1 or 0, a
245 return a[1] == 'r' and -1 or 0, a
246
246
247 def applyupdates(repo, action, wctx, mctx):
247 def applyupdates(repo, action, wctx, mctx):
248 "apply the merge action list to the working directory"
248 "apply the merge action list to the working directory"
249
249
250 updated, merged, removed, unresolved = 0, 0, 0, 0
250 updated, merged, removed, unresolved = 0, 0, 0, 0
251 ms = mergestate(repo)
251 ms = mergestate(repo)
252 ms.reset(wctx.parents()[0].node())
252 ms.reset(wctx.parents()[0].node())
253 moves = []
253 moves = []
254 action.sort(key=actionkey)
254 action.sort(key=actionkey)
255 substate = wctx.substate # prime
255 substate = wctx.substate # prime
256
256
257 # prescan for merges
257 # prescan for merges
258 u = repo.ui
258 u = repo.ui
259 for a in action:
259 for a in action:
260 f, m = a[:2]
260 f, m = a[:2]
261 if m == 'm': # merge
261 if m == 'm': # merge
262 f2, fd, flags, move = a[2:]
262 f2, fd, flags, move = a[2:]
263 if f == '.hgsubstate': # merged internally
263 if f == '.hgsubstate': # merged internally
264 continue
264 continue
265 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
265 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
266 fcl = wctx[f]
266 fcl = wctx[f]
267 fco = mctx[f2]
267 fco = mctx[f2]
268 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
268 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
269 ms.add(fcl, fco, fca, fd, flags)
269 ms.add(fcl, fco, fca, fd, flags)
270 if f != fd and move:
270 if f != fd and move:
271 moves.append(f)
271 moves.append(f)
272
272
273 # remove renamed files after safely stored
273 # remove renamed files after safely stored
274 for f in moves:
274 for f in moves:
275 if util.lexists(repo.wjoin(f)):
275 if util.lexists(repo.wjoin(f)):
276 repo.ui.debug("removing %s\n" % f)
276 repo.ui.debug("removing %s\n" % f)
277 os.unlink(repo.wjoin(f))
277 os.unlink(repo.wjoin(f))
278
278
279 audit_path = util.path_auditor(repo.root)
279 audit_path = util.path_auditor(repo.root)
280
280
281 numupdates = len(action)
281 numupdates = len(action)
282 for i, a in enumerate(action):
282 for i, a in enumerate(action):
283 f, m = a[:2]
283 f, m = a[:2]
284 u.progress('update', i + 1, item=f, total=numupdates, unit='files')
284 u.progress('update', i + 1, item=f, total=numupdates, unit='files')
285 if f and f[0] == "/":
285 if f and f[0] == "/":
286 continue
286 continue
287 if m == "r": # remove
287 if m == "r": # remove
288 repo.ui.note(_("removing %s\n") % f)
288 repo.ui.note(_("removing %s\n") % f)
289 audit_path(f)
289 audit_path(f)
290 if f == '.hgsubstate': # subrepo states need updating
290 if f == '.hgsubstate': # subrepo states need updating
291 subrepo.submerge(repo, wctx, mctx, wctx)
291 subrepo.submerge(repo, wctx, mctx, wctx)
292 try:
292 try:
293 util.unlink(repo.wjoin(f))
293 util.unlink(repo.wjoin(f))
294 except OSError, inst:
294 except OSError, inst:
295 if inst.errno != errno.ENOENT:
295 if inst.errno != errno.ENOENT:
296 repo.ui.warn(_("update failed to remove %s: %s!\n") %
296 repo.ui.warn(_("update failed to remove %s: %s!\n") %
297 (f, inst.strerror))
297 (f, inst.strerror))
298 removed += 1
298 removed += 1
299 elif m == "m": # merge
299 elif m == "m": # merge
300 if f == '.hgsubstate': # subrepo states need updating
300 if f == '.hgsubstate': # subrepo states need updating
301 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
301 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
302 continue
302 continue
303 f2, fd, flags, move = a[2:]
303 f2, fd, flags, move = a[2:]
304 r = ms.resolve(fd, wctx, mctx)
304 r = ms.resolve(fd, wctx, mctx)
305 if r is not None and r > 0:
305 if r is not None and r > 0:
306 unresolved += 1
306 unresolved += 1
307 else:
307 else:
308 if r is None:
308 if r is None:
309 updated += 1
309 updated += 1
310 else:
310 else:
311 merged += 1
311 merged += 1
312 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
312 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
313 if f != fd and move and util.lexists(repo.wjoin(f)):
313 if f != fd and move and util.lexists(repo.wjoin(f)):
314 repo.ui.debug("removing %s\n" % f)
314 repo.ui.debug("removing %s\n" % f)
315 os.unlink(repo.wjoin(f))
315 os.unlink(repo.wjoin(f))
316 elif m == "g": # get
316 elif m == "g": # get
317 flags = a[2]
317 flags = a[2]
318 repo.ui.note(_("getting %s\n") % f)
318 repo.ui.note(_("getting %s\n") % f)
319 t = mctx.filectx(f).data()
319 t = mctx.filectx(f).data()
320 repo.wwrite(f, t, flags)
320 repo.wwrite(f, t, flags)
321 updated += 1
321 updated += 1
322 if f == '.hgsubstate': # subrepo states need updating
322 if f == '.hgsubstate': # subrepo states need updating
323 subrepo.submerge(repo, wctx, mctx, wctx)
323 subrepo.submerge(repo, wctx, mctx, wctx)
324 elif m == "d": # directory rename
324 elif m == "d": # directory rename
325 f2, fd, flags = a[2:]
325 f2, fd, flags = a[2:]
326 if f:
326 if f:
327 repo.ui.note(_("moving %s to %s\n") % (f, fd))
327 repo.ui.note(_("moving %s to %s\n") % (f, fd))
328 t = wctx.filectx(f).data()
328 t = wctx.filectx(f).data()
329 repo.wwrite(fd, t, flags)
329 repo.wwrite(fd, t, flags)
330 util.unlink(repo.wjoin(f))
330 util.unlink(repo.wjoin(f))
331 if f2:
331 if f2:
332 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
332 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
333 t = mctx.filectx(f2).data()
333 t = mctx.filectx(f2).data()
334 repo.wwrite(fd, t, flags)
334 repo.wwrite(fd, t, flags)
335 updated += 1
335 updated += 1
336 elif m == "dr": # divergent renames
336 elif m == "dr": # divergent renames
337 fl = a[2]
337 fl = a[2]
338 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
338 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
339 for nf in fl:
339 for nf in fl:
340 repo.ui.warn(" %s\n" % nf)
340 repo.ui.warn(" %s\n" % nf)
341 elif m == "e": # exec
341 elif m == "e": # exec
342 flags = a[2]
342 flags = a[2]
343 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
343 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
344 u.progress('update', None, total=numupdates, unit='files')
344 u.progress('update', None, total=numupdates, unit='files')
345
345
346 return updated, merged, removed, unresolved
346 return updated, merged, removed, unresolved
347
347
348 def recordupdates(repo, action, branchmerge):
348 def recordupdates(repo, action, branchmerge):
349 "record merge actions to the dirstate"
349 "record merge actions to the dirstate"
350
350
351 for a in action:
351 for a in action:
352 f, m = a[:2]
352 f, m = a[:2]
353 if m == "r": # remove
353 if m == "r": # remove
354 if branchmerge:
354 if branchmerge:
355 repo.dirstate.remove(f)
355 repo.dirstate.remove(f)
356 else:
356 else:
357 repo.dirstate.forget(f)
357 repo.dirstate.forget(f)
358 elif m == "a": # re-add
358 elif m == "a": # re-add
359 if not branchmerge:
359 if not branchmerge:
360 repo.dirstate.add(f)
360 repo.dirstate.add(f)
361 elif m == "f": # forget
361 elif m == "f": # forget
362 repo.dirstate.forget(f)
362 repo.dirstate.forget(f)
363 elif m == "e": # exec change
363 elif m == "e": # exec change
364 repo.dirstate.normallookup(f)
364 repo.dirstate.normallookup(f)
365 elif m == "g": # get
365 elif m == "g": # get
366 if branchmerge:
366 if branchmerge:
367 repo.dirstate.normaldirty(f)
367 repo.dirstate.otherparent(f)
368 else:
368 else:
369 repo.dirstate.normal(f)
369 repo.dirstate.normal(f)
370 elif m == "m": # merge
370 elif m == "m": # merge
371 f2, fd, flag, move = a[2:]
371 f2, fd, flag, move = a[2:]
372 if branchmerge:
372 if branchmerge:
373 # We've done a branch merge, mark this file as merged
373 # We've done a branch merge, mark this file as merged
374 # so that we properly record the merger later
374 # so that we properly record the merger later
375 repo.dirstate.merge(fd)
375 repo.dirstate.merge(fd)
376 if f != f2: # copy/rename
376 if f != f2: # copy/rename
377 if move:
377 if move:
378 repo.dirstate.remove(f)
378 repo.dirstate.remove(f)
379 if f != fd:
379 if f != fd:
380 repo.dirstate.copy(f, fd)
380 repo.dirstate.copy(f, fd)
381 else:
381 else:
382 repo.dirstate.copy(f2, fd)
382 repo.dirstate.copy(f2, fd)
383 else:
383 else:
384 # We've update-merged a locally modified file, so
384 # We've update-merged a locally modified file, so
385 # we set the dirstate to emulate a normal checkout
385 # we set the dirstate to emulate a normal checkout
386 # of that file some time in the past. Thus our
386 # of that file some time in the past. Thus our
387 # merge will appear as a normal local file
387 # merge will appear as a normal local file
388 # modification.
388 # modification.
389 repo.dirstate.normallookup(fd)
389 repo.dirstate.normallookup(fd)
390 if move:
390 if move:
391 repo.dirstate.forget(f)
391 repo.dirstate.forget(f)
392 elif m == "d": # directory rename
392 elif m == "d": # directory rename
393 f2, fd, flag = a[2:]
393 f2, fd, flag = a[2:]
394 if not f2 and f not in repo.dirstate:
394 if not f2 and f not in repo.dirstate:
395 # untracked file moved
395 # untracked file moved
396 continue
396 continue
397 if branchmerge:
397 if branchmerge:
398 repo.dirstate.add(fd)
398 repo.dirstate.add(fd)
399 if f:
399 if f:
400 repo.dirstate.remove(f)
400 repo.dirstate.remove(f)
401 repo.dirstate.copy(f, fd)
401 repo.dirstate.copy(f, fd)
402 if f2:
402 if f2:
403 repo.dirstate.copy(f2, fd)
403 repo.dirstate.copy(f2, fd)
404 else:
404 else:
405 repo.dirstate.normal(fd)
405 repo.dirstate.normal(fd)
406 if f:
406 if f:
407 repo.dirstate.forget(f)
407 repo.dirstate.forget(f)
408
408
409 def update(repo, node, branchmerge, force, partial):
409 def update(repo, node, branchmerge, force, partial):
410 """
410 """
411 Perform a merge between the working directory and the given node
411 Perform a merge between the working directory and the given node
412
412
413 node = the node to update to, or None if unspecified
413 node = the node to update to, or None if unspecified
414 branchmerge = whether to merge between branches
414 branchmerge = whether to merge between branches
415 force = whether to force branch merging or file overwriting
415 force = whether to force branch merging or file overwriting
416 partial = a function to filter file lists (dirstate not updated)
416 partial = a function to filter file lists (dirstate not updated)
417
417
418 The table below shows all the behaviors of the update command
418 The table below shows all the behaviors of the update command
419 given the -c and -C or no options, whether the working directory
419 given the -c and -C or no options, whether the working directory
420 is dirty, whether a revision is specified, and the relationship of
420 is dirty, whether a revision is specified, and the relationship of
421 the parent rev to the target rev (linear, on the same named
421 the parent rev to the target rev (linear, on the same named
422 branch, or on another named branch).
422 branch, or on another named branch).
423
423
424 This logic is tested by test-update-branches.
424 This logic is tested by test-update-branches.
425
425
426 -c -C dirty rev | linear same cross
426 -c -C dirty rev | linear same cross
427 n n n n | ok (1) x
427 n n n n | ok (1) x
428 n n n y | ok ok ok
428 n n n y | ok ok ok
429 n n y * | merge (2) (2)
429 n n y * | merge (2) (2)
430 n y * * | --- discard ---
430 n y * * | --- discard ---
431 y n y * | --- (3) ---
431 y n y * | --- (3) ---
432 y n n * | --- ok ---
432 y n n * | --- ok ---
433 y y * * | --- (4) ---
433 y y * * | --- (4) ---
434
434
435 x = can't happen
435 x = can't happen
436 * = don't-care
436 * = don't-care
437 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
437 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
438 2 = abort: crosses branches (use 'hg merge' to merge or
438 2 = abort: crosses branches (use 'hg merge' to merge or
439 use 'hg update -C' to discard changes)
439 use 'hg update -C' to discard changes)
440 3 = abort: uncommitted local changes
440 3 = abort: uncommitted local changes
441 4 = incompatible options (checked in commands.py)
441 4 = incompatible options (checked in commands.py)
442 """
442 """
443
443
444 onode = node
444 onode = node
445 wlock = repo.wlock()
445 wlock = repo.wlock()
446 try:
446 try:
447 wc = repo[None]
447 wc = repo[None]
448 if node is None:
448 if node is None:
449 # tip of current branch
449 # tip of current branch
450 try:
450 try:
451 node = repo.branchtags()[wc.branch()]
451 node = repo.branchtags()[wc.branch()]
452 except KeyError:
452 except KeyError:
453 if wc.branch() == "default": # no default branch!
453 if wc.branch() == "default": # no default branch!
454 node = repo.lookup("tip") # update to tip
454 node = repo.lookup("tip") # update to tip
455 else:
455 else:
456 raise util.Abort(_("branch %s not found") % wc.branch())
456 raise util.Abort(_("branch %s not found") % wc.branch())
457 overwrite = force and not branchmerge
457 overwrite = force and not branchmerge
458 pl = wc.parents()
458 pl = wc.parents()
459 p1, p2 = pl[0], repo[node]
459 p1, p2 = pl[0], repo[node]
460 pa = p1.ancestor(p2)
460 pa = p1.ancestor(p2)
461 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
461 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
462 fastforward = False
462 fastforward = False
463
463
464 ### check phase
464 ### check phase
465 if not overwrite and len(pl) > 1:
465 if not overwrite and len(pl) > 1:
466 raise util.Abort(_("outstanding uncommitted merges"))
466 raise util.Abort(_("outstanding uncommitted merges"))
467 if branchmerge:
467 if branchmerge:
468 if pa == p2:
468 if pa == p2:
469 raise util.Abort(_("can't merge with ancestor"))
469 raise util.Abort(_("can't merge with ancestor"))
470 elif pa == p1:
470 elif pa == p1:
471 if p1.branch() != p2.branch():
471 if p1.branch() != p2.branch():
472 fastforward = True
472 fastforward = True
473 else:
473 else:
474 raise util.Abort(_("nothing to merge (use 'hg update'"
474 raise util.Abort(_("nothing to merge (use 'hg update'"
475 " or check 'hg heads')"))
475 " or check 'hg heads')"))
476 if not force and (wc.files() or wc.deleted()):
476 if not force and (wc.files() or wc.deleted()):
477 raise util.Abort(_("outstanding uncommitted changes "
477 raise util.Abort(_("outstanding uncommitted changes "
478 "(use 'hg status' to list changes)"))
478 "(use 'hg status' to list changes)"))
479 elif not overwrite:
479 elif not overwrite:
480 if pa == p1 or pa == p2: # linear
480 if pa == p1 or pa == p2: # linear
481 pass # all good
481 pass # all good
482 elif wc.files() or wc.deleted():
482 elif wc.files() or wc.deleted():
483 raise util.Abort(_("crosses branches (use 'hg merge' to merge "
483 raise util.Abort(_("crosses branches (use 'hg merge' to merge "
484 "or use 'hg update -C' to discard changes)"))
484 "or use 'hg update -C' to discard changes)"))
485 elif onode is None:
485 elif onode is None:
486 raise util.Abort(_("crosses branches (use 'hg merge' or use "
486 raise util.Abort(_("crosses branches (use 'hg merge' or use "
487 "'hg update -c')"))
487 "'hg update -c')"))
488 else:
488 else:
489 # Allow jumping branches if clean and specific rev given
489 # Allow jumping branches if clean and specific rev given
490 overwrite = True
490 overwrite = True
491
491
492 ### calculate phase
492 ### calculate phase
493 action = []
493 action = []
494 if not force:
494 if not force:
495 _checkunknown(wc, p2)
495 _checkunknown(wc, p2)
496 if not util.checkcase(repo.path):
496 if not util.checkcase(repo.path):
497 _checkcollision(p2)
497 _checkcollision(p2)
498 action += _forgetremoved(wc, p2, branchmerge)
498 action += _forgetremoved(wc, p2, branchmerge)
499 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
499 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
500
500
501 ### apply phase
501 ### apply phase
502 if not branchmerge: # just jump to the new rev
502 if not branchmerge: # just jump to the new rev
503 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
503 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
504 if not partial:
504 if not partial:
505 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
505 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
506
506
507 stats = applyupdates(repo, action, wc, p2)
507 stats = applyupdates(repo, action, wc, p2)
508
508
509 if not partial:
509 if not partial:
510 repo.dirstate.setparents(fp1, fp2)
510 recordupdates(repo, action, branchmerge)
511 recordupdates(repo, action, branchmerge)
511 repo.dirstate.setparents(fp1, fp2)
512 if not branchmerge and not fastforward:
512 if not branchmerge and not fastforward:
513 repo.dirstate.setbranch(p2.branch())
513 repo.dirstate.setbranch(p2.branch())
514 finally:
514 finally:
515 wlock.release()
515 wlock.release()
516
516
517 if not partial:
517 if not partial:
518 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
518 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
519 return stats
519 return stats
General Comments 0
You need to be logged in to leave comments. Login now