##// END OF EJS Templates
merge/progress: marking strings for localization
timeless -
r12746:8b438cb8 default
parent child Browse files
Show More
@@ -1,542 +1,543 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 """
258 """
259
259
260 updated, merged, removed, unresolved = 0, 0, 0, 0
260 updated, merged, removed, unresolved = 0, 0, 0, 0
261 ms = mergestate(repo)
261 ms = mergestate(repo)
262 ms.reset(wctx.parents()[0].node())
262 ms.reset(wctx.parents()[0].node())
263 moves = []
263 moves = []
264 action.sort(key=actionkey)
264 action.sort(key=actionkey)
265 substate = wctx.substate # prime
265 substate = wctx.substate # prime
266
266
267 # prescan for merges
267 # prescan for merges
268 u = repo.ui
268 u = repo.ui
269 for a in action:
269 for a in action:
270 f, m = a[:2]
270 f, m = a[:2]
271 if m == 'm': # merge
271 if m == 'm': # merge
272 f2, fd, flags, move = a[2:]
272 f2, fd, flags, move = a[2:]
273 if f == '.hgsubstate': # merged internally
273 if f == '.hgsubstate': # merged internally
274 continue
274 continue
275 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
275 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
276 fcl = wctx[f]
276 fcl = wctx[f]
277 fco = mctx[f2]
277 fco = mctx[f2]
278 if mctx == actx: # backwards, use working dir parent as ancestor
278 if mctx == actx: # backwards, use working dir parent as ancestor
279 if fcl.parents():
279 if fcl.parents():
280 fca = fcl.parents()[0]
280 fca = fcl.parents()[0]
281 else:
281 else:
282 fca = repo.filectx(f, fileid=nullrev)
282 fca = repo.filectx(f, fileid=nullrev)
283 else:
283 else:
284 fca = fcl.ancestor(fco, actx)
284 fca = fcl.ancestor(fco, actx)
285 if not fca:
285 if not fca:
286 fca = repo.filectx(f, fileid=nullrev)
286 fca = repo.filectx(f, fileid=nullrev)
287 ms.add(fcl, fco, fca, fd, flags)
287 ms.add(fcl, fco, fca, fd, flags)
288 if f != fd and move:
288 if f != fd and move:
289 moves.append(f)
289 moves.append(f)
290
290
291 # remove renamed files after safely stored
291 # remove renamed files after safely stored
292 for f in moves:
292 for f in moves:
293 if os.path.lexists(repo.wjoin(f)):
293 if os.path.lexists(repo.wjoin(f)):
294 repo.ui.debug("removing %s\n" % f)
294 repo.ui.debug("removing %s\n" % f)
295 os.unlink(repo.wjoin(f))
295 os.unlink(repo.wjoin(f))
296
296
297 audit_path = util.path_auditor(repo.root)
297 audit_path = util.path_auditor(repo.root)
298
298
299 numupdates = len(action)
299 numupdates = len(action)
300 for i, a in enumerate(action):
300 for i, a in enumerate(action):
301 f, m = a[:2]
301 f, m = a[:2]
302 u.progress(_('updating'), i + 1, item=f, total=numupdates, unit='files')
302 u.progress(_('updating'), i + 1, item=f, total=numupdates,
303 unit=_('files'))
303 if f and f[0] == "/":
304 if f and f[0] == "/":
304 continue
305 continue
305 if m == "r": # remove
306 if m == "r": # remove
306 repo.ui.note(_("removing %s\n") % f)
307 repo.ui.note(_("removing %s\n") % f)
307 audit_path(f)
308 audit_path(f)
308 if f == '.hgsubstate': # subrepo states need updating
309 if f == '.hgsubstate': # subrepo states need updating
309 subrepo.submerge(repo, wctx, mctx, wctx)
310 subrepo.submerge(repo, wctx, mctx, wctx)
310 try:
311 try:
311 util.unlink(repo.wjoin(f))
312 util.unlink(repo.wjoin(f))
312 except OSError, inst:
313 except OSError, inst:
313 if inst.errno != errno.ENOENT:
314 if inst.errno != errno.ENOENT:
314 repo.ui.warn(_("update failed to remove %s: %s!\n") %
315 repo.ui.warn(_("update failed to remove %s: %s!\n") %
315 (f, inst.strerror))
316 (f, inst.strerror))
316 removed += 1
317 removed += 1
317 elif m == "m": # merge
318 elif m == "m": # merge
318 if f == '.hgsubstate': # subrepo states need updating
319 if f == '.hgsubstate': # subrepo states need updating
319 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
320 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
320 continue
321 continue
321 f2, fd, flags, move = a[2:]
322 f2, fd, flags, move = a[2:]
322 r = ms.resolve(fd, wctx, mctx)
323 r = ms.resolve(fd, wctx, mctx)
323 if r is not None and r > 0:
324 if r is not None and r > 0:
324 unresolved += 1
325 unresolved += 1
325 else:
326 else:
326 if r is None:
327 if r is None:
327 updated += 1
328 updated += 1
328 else:
329 else:
329 merged += 1
330 merged += 1
330 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
331 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
331 if f != fd and move and os.path.lexists(repo.wjoin(f)):
332 if f != fd and move and os.path.lexists(repo.wjoin(f)):
332 repo.ui.debug("removing %s\n" % f)
333 repo.ui.debug("removing %s\n" % f)
333 os.unlink(repo.wjoin(f))
334 os.unlink(repo.wjoin(f))
334 elif m == "g": # get
335 elif m == "g": # get
335 flags = a[2]
336 flags = a[2]
336 repo.ui.note(_("getting %s\n") % f)
337 repo.ui.note(_("getting %s\n") % f)
337 t = mctx.filectx(f).data()
338 t = mctx.filectx(f).data()
338 repo.wwrite(f, t, flags)
339 repo.wwrite(f, t, flags)
339 t = None
340 t = None
340 updated += 1
341 updated += 1
341 if f == '.hgsubstate': # subrepo states need updating
342 if f == '.hgsubstate': # subrepo states need updating
342 subrepo.submerge(repo, wctx, mctx, wctx)
343 subrepo.submerge(repo, wctx, mctx, wctx)
343 elif m == "d": # directory rename
344 elif m == "d": # directory rename
344 f2, fd, flags = a[2:]
345 f2, fd, flags = a[2:]
345 if f:
346 if f:
346 repo.ui.note(_("moving %s to %s\n") % (f, fd))
347 repo.ui.note(_("moving %s to %s\n") % (f, fd))
347 t = wctx.filectx(f).data()
348 t = wctx.filectx(f).data()
348 repo.wwrite(fd, t, flags)
349 repo.wwrite(fd, t, flags)
349 util.unlink(repo.wjoin(f))
350 util.unlink(repo.wjoin(f))
350 if f2:
351 if f2:
351 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
352 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
352 t = mctx.filectx(f2).data()
353 t = mctx.filectx(f2).data()
353 repo.wwrite(fd, t, flags)
354 repo.wwrite(fd, t, flags)
354 updated += 1
355 updated += 1
355 elif m == "dr": # divergent renames
356 elif m == "dr": # divergent renames
356 fl = a[2]
357 fl = a[2]
357 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
358 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
358 for nf in fl:
359 for nf in fl:
359 repo.ui.warn(" %s\n" % nf)
360 repo.ui.warn(" %s\n" % nf)
360 elif m == "e": # exec
361 elif m == "e": # exec
361 flags = a[2]
362 flags = a[2]
362 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
363 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
363 ms.commit()
364 ms.commit()
364 u.progress(_('updating'), None, total=numupdates, unit='files')
365 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
365
366
366 return updated, merged, removed, unresolved
367 return updated, merged, removed, unresolved
367
368
368 def recordupdates(repo, action, branchmerge):
369 def recordupdates(repo, action, branchmerge):
369 "record merge actions to the dirstate"
370 "record merge actions to the dirstate"
370
371
371 for a in action:
372 for a in action:
372 f, m = a[:2]
373 f, m = a[:2]
373 if m == "r": # remove
374 if m == "r": # remove
374 if branchmerge:
375 if branchmerge:
375 repo.dirstate.remove(f)
376 repo.dirstate.remove(f)
376 else:
377 else:
377 repo.dirstate.forget(f)
378 repo.dirstate.forget(f)
378 elif m == "a": # re-add
379 elif m == "a": # re-add
379 if not branchmerge:
380 if not branchmerge:
380 repo.dirstate.add(f)
381 repo.dirstate.add(f)
381 elif m == "f": # forget
382 elif m == "f": # forget
382 repo.dirstate.forget(f)
383 repo.dirstate.forget(f)
383 elif m == "e": # exec change
384 elif m == "e": # exec change
384 repo.dirstate.normallookup(f)
385 repo.dirstate.normallookup(f)
385 elif m == "g": # get
386 elif m == "g": # get
386 if branchmerge:
387 if branchmerge:
387 repo.dirstate.otherparent(f)
388 repo.dirstate.otherparent(f)
388 else:
389 else:
389 repo.dirstate.normal(f)
390 repo.dirstate.normal(f)
390 elif m == "m": # merge
391 elif m == "m": # merge
391 f2, fd, flag, move = a[2:]
392 f2, fd, flag, move = a[2:]
392 if branchmerge:
393 if branchmerge:
393 # We've done a branch merge, mark this file as merged
394 # We've done a branch merge, mark this file as merged
394 # so that we properly record the merger later
395 # so that we properly record the merger later
395 repo.dirstate.merge(fd)
396 repo.dirstate.merge(fd)
396 if f != f2: # copy/rename
397 if f != f2: # copy/rename
397 if move:
398 if move:
398 repo.dirstate.remove(f)
399 repo.dirstate.remove(f)
399 if f != fd:
400 if f != fd:
400 repo.dirstate.copy(f, fd)
401 repo.dirstate.copy(f, fd)
401 else:
402 else:
402 repo.dirstate.copy(f2, fd)
403 repo.dirstate.copy(f2, fd)
403 else:
404 else:
404 # We've update-merged a locally modified file, so
405 # We've update-merged a locally modified file, so
405 # we set the dirstate to emulate a normal checkout
406 # we set the dirstate to emulate a normal checkout
406 # of that file some time in the past. Thus our
407 # of that file some time in the past. Thus our
407 # merge will appear as a normal local file
408 # merge will appear as a normal local file
408 # modification.
409 # modification.
409 if f2 == fd: # file not locally copied/moved
410 if f2 == fd: # file not locally copied/moved
410 repo.dirstate.normallookup(fd)
411 repo.dirstate.normallookup(fd)
411 if move:
412 if move:
412 repo.dirstate.forget(f)
413 repo.dirstate.forget(f)
413 elif m == "d": # directory rename
414 elif m == "d": # directory rename
414 f2, fd, flag = a[2:]
415 f2, fd, flag = a[2:]
415 if not f2 and f not in repo.dirstate:
416 if not f2 and f not in repo.dirstate:
416 # untracked file moved
417 # untracked file moved
417 continue
418 continue
418 if branchmerge:
419 if branchmerge:
419 repo.dirstate.add(fd)
420 repo.dirstate.add(fd)
420 if f:
421 if f:
421 repo.dirstate.remove(f)
422 repo.dirstate.remove(f)
422 repo.dirstate.copy(f, fd)
423 repo.dirstate.copy(f, fd)
423 if f2:
424 if f2:
424 repo.dirstate.copy(f2, fd)
425 repo.dirstate.copy(f2, fd)
425 else:
426 else:
426 repo.dirstate.normal(fd)
427 repo.dirstate.normal(fd)
427 if f:
428 if f:
428 repo.dirstate.forget(f)
429 repo.dirstate.forget(f)
429
430
430 def update(repo, node, branchmerge, force, partial):
431 def update(repo, node, branchmerge, force, partial):
431 """
432 """
432 Perform a merge between the working directory and the given node
433 Perform a merge between the working directory and the given node
433
434
434 node = the node to update to, or None if unspecified
435 node = the node to update to, or None if unspecified
435 branchmerge = whether to merge between branches
436 branchmerge = whether to merge between branches
436 force = whether to force branch merging or file overwriting
437 force = whether to force branch merging or file overwriting
437 partial = a function to filter file lists (dirstate not updated)
438 partial = a function to filter file lists (dirstate not updated)
438
439
439 The table below shows all the behaviors of the update command
440 The table below shows all the behaviors of the update command
440 given the -c and -C or no options, whether the working directory
441 given the -c and -C or no options, whether the working directory
441 is dirty, whether a revision is specified, and the relationship of
442 is dirty, whether a revision is specified, and the relationship of
442 the parent rev to the target rev (linear, on the same named
443 the parent rev to the target rev (linear, on the same named
443 branch, or on another named branch).
444 branch, or on another named branch).
444
445
445 This logic is tested by test-update-branches.t.
446 This logic is tested by test-update-branches.t.
446
447
447 -c -C dirty rev | linear same cross
448 -c -C dirty rev | linear same cross
448 n n n n | ok (1) x
449 n n n n | ok (1) x
449 n n n y | ok ok ok
450 n n n y | ok ok ok
450 n n y * | merge (2) (2)
451 n n y * | merge (2) (2)
451 n y * * | --- discard ---
452 n y * * | --- discard ---
452 y n y * | --- (3) ---
453 y n y * | --- (3) ---
453 y n n * | --- ok ---
454 y n n * | --- ok ---
454 y y * * | --- (4) ---
455 y y * * | --- (4) ---
455
456
456 x = can't happen
457 x = can't happen
457 * = don't-care
458 * = don't-care
458 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
459 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
459 2 = abort: crosses branches (use 'hg merge' to merge or
460 2 = abort: crosses branches (use 'hg merge' to merge or
460 use 'hg update -C' to discard changes)
461 use 'hg update -C' to discard changes)
461 3 = abort: uncommitted local changes
462 3 = abort: uncommitted local changes
462 4 = incompatible options (checked in commands.py)
463 4 = incompatible options (checked in commands.py)
463 """
464 """
464
465
465 onode = node
466 onode = node
466 wlock = repo.wlock()
467 wlock = repo.wlock()
467 try:
468 try:
468 wc = repo[None]
469 wc = repo[None]
469 if node is None:
470 if node is None:
470 # tip of current branch
471 # tip of current branch
471 try:
472 try:
472 node = repo.branchtags()[wc.branch()]
473 node = repo.branchtags()[wc.branch()]
473 except KeyError:
474 except KeyError:
474 if wc.branch() == "default": # no default branch!
475 if wc.branch() == "default": # no default branch!
475 node = repo.lookup("tip") # update to tip
476 node = repo.lookup("tip") # update to tip
476 else:
477 else:
477 raise util.Abort(_("branch %s not found") % wc.branch())
478 raise util.Abort(_("branch %s not found") % wc.branch())
478 overwrite = force and not branchmerge
479 overwrite = force and not branchmerge
479 pl = wc.parents()
480 pl = wc.parents()
480 p1, p2 = pl[0], repo[node]
481 p1, p2 = pl[0], repo[node]
481 pa = p1.ancestor(p2)
482 pa = p1.ancestor(p2)
482 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
483 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
483 fastforward = False
484 fastforward = False
484
485
485 ### check phase
486 ### check phase
486 if not overwrite and len(pl) > 1:
487 if not overwrite and len(pl) > 1:
487 raise util.Abort(_("outstanding uncommitted merges"))
488 raise util.Abort(_("outstanding uncommitted merges"))
488 if branchmerge:
489 if branchmerge:
489 if pa == p2:
490 if pa == p2:
490 raise util.Abort(_("merging with a working directory ancestor"
491 raise util.Abort(_("merging with a working directory ancestor"
491 " has no effect"))
492 " has no effect"))
492 elif pa == p1:
493 elif pa == p1:
493 if p1.branch() != p2.branch():
494 if p1.branch() != p2.branch():
494 fastforward = True
495 fastforward = True
495 else:
496 else:
496 raise util.Abort(_("nothing to merge (use 'hg update'"
497 raise util.Abort(_("nothing to merge (use 'hg update'"
497 " or check 'hg heads')"))
498 " or check 'hg heads')"))
498 if not force and (wc.files() or wc.deleted()):
499 if not force and (wc.files() or wc.deleted()):
499 raise util.Abort(_("outstanding uncommitted changes "
500 raise util.Abort(_("outstanding uncommitted changes "
500 "(use 'hg status' to list changes)"))
501 "(use 'hg status' to list changes)"))
501 elif not overwrite:
502 elif not overwrite:
502 if pa == p1 or pa == p2: # linear
503 if pa == p1 or pa == p2: # linear
503 pass # all good
504 pass # all good
504 elif wc.files() or wc.deleted():
505 elif wc.files() or wc.deleted():
505 raise util.Abort(_("crosses branches (merge branches or use"
506 raise util.Abort(_("crosses branches (merge branches or use"
506 " --clean to discard changes)"))
507 " --clean to discard changes)"))
507 elif onode is None:
508 elif onode is None:
508 raise util.Abort(_("crosses branches (merge branches or use"
509 raise util.Abort(_("crosses branches (merge branches or use"
509 " --check to force update)"))
510 " --check to force update)"))
510 else:
511 else:
511 # Allow jumping branches if clean and specific rev given
512 # Allow jumping branches if clean and specific rev given
512 overwrite = True
513 overwrite = True
513
514
514 ### calculate phase
515 ### calculate phase
515 action = []
516 action = []
516 wc.status(unknown=True) # prime cache
517 wc.status(unknown=True) # prime cache
517 if not force:
518 if not force:
518 _checkunknown(wc, p2)
519 _checkunknown(wc, p2)
519 if not util.checkcase(repo.path):
520 if not util.checkcase(repo.path):
520 _checkcollision(p2)
521 _checkcollision(p2)
521 action += _forgetremoved(wc, p2, branchmerge)
522 action += _forgetremoved(wc, p2, branchmerge)
522 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
523 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
523
524
524 ### apply phase
525 ### apply phase
525 if not branchmerge: # just jump to the new rev
526 if not branchmerge: # just jump to the new rev
526 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
527 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
527 if not partial:
528 if not partial:
528 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
529 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
529
530
530 stats = applyupdates(repo, action, wc, p2, pa)
531 stats = applyupdates(repo, action, wc, p2, pa)
531
532
532 if not partial:
533 if not partial:
533 repo.dirstate.setparents(fp1, fp2)
534 repo.dirstate.setparents(fp1, fp2)
534 recordupdates(repo, action, branchmerge)
535 recordupdates(repo, action, branchmerge)
535 if not branchmerge and not fastforward:
536 if not branchmerge and not fastforward:
536 repo.dirstate.setbranch(p2.branch())
537 repo.dirstate.setbranch(p2.branch())
537 finally:
538 finally:
538 wlock.release()
539 wlock.release()
539
540
540 if not partial:
541 if not partial:
541 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
542 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
542 return stats
543 return stats
General Comments 0
You need to be logged in to leave comments. Login now