##// END OF EJS Templates
merge: pull manifest checks and updates into separate functions
Matt Mackall -
r3107:3bd05ad6 default
parent child Browse files
Show More
@@ -1,336 +1,366 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 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "errno util os tempfile")
11 demandload(globals(), "errno util os tempfile")
12
12
13 def fmerge(f, local, other, ancestor):
13 def fmerge(f, local, other, ancestor):
14 """merge executable flags"""
14 """merge executable flags"""
15 a, b, c = ancestor.execf(f), local.execf(f), other.execf(f)
15 a, b, c = ancestor.execf(f), local.execf(f), other.execf(f)
16 return ((a^b) | (a^c)) ^ a
16 return ((a^b) | (a^c)) ^ a
17
17
18 def merge3(repo, fn, my, other, p1, p2):
18 def merge3(repo, fn, my, other, p1, p2):
19 """perform a 3-way merge in the working directory"""
19 """perform a 3-way merge in the working directory"""
20
20
21 def temp(prefix, node):
21 def temp(prefix, node):
22 pre = "%s~%s." % (os.path.basename(fn), prefix)
22 pre = "%s~%s." % (os.path.basename(fn), prefix)
23 (fd, name) = tempfile.mkstemp(prefix=pre)
23 (fd, name) = tempfile.mkstemp(prefix=pre)
24 f = os.fdopen(fd, "wb")
24 f = os.fdopen(fd, "wb")
25 repo.wwrite(fn, fl.read(node), f)
25 repo.wwrite(fn, fl.read(node), f)
26 f.close()
26 f.close()
27 return name
27 return name
28
28
29 fl = repo.file(fn)
29 fl = repo.file(fn)
30 base = fl.ancestor(my, other)
30 base = fl.ancestor(my, other)
31 a = repo.wjoin(fn)
31 a = repo.wjoin(fn)
32 b = temp("base", base)
32 b = temp("base", base)
33 c = temp("other", other)
33 c = temp("other", other)
34
34
35 repo.ui.note(_("resolving %s\n") % fn)
35 repo.ui.note(_("resolving %s\n") % fn)
36 repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
36 repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
37 (fn, short(my), short(other), short(base)))
37 (fn, short(my), short(other), short(base)))
38
38
39 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
39 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
40 or "hgmerge")
40 or "hgmerge")
41 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
41 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
42 environ={'HG_FILE': fn,
42 environ={'HG_FILE': fn,
43 'HG_MY_NODE': p1,
43 'HG_MY_NODE': p1,
44 'HG_OTHER_NODE': p2,
44 'HG_OTHER_NODE': p2,
45 'HG_FILE_MY_NODE': hex(my),
45 'HG_FILE_MY_NODE': hex(my),
46 'HG_FILE_OTHER_NODE': hex(other),
46 'HG_FILE_OTHER_NODE': hex(other),
47 'HG_FILE_BASE_NODE': hex(base)})
47 'HG_FILE_BASE_NODE': hex(base)})
48 if r:
48 if r:
49 repo.ui.warn(_("merging %s failed!\n") % fn)
49 repo.ui.warn(_("merging %s failed!\n") % fn)
50
50
51 os.unlink(b)
51 os.unlink(b)
52 os.unlink(c)
52 os.unlink(c)
53 return r
53 return r
54
54
55 def manifestmerge(ui, m1, m2, ma, overwrite, backwards):
55 def checkunknown(repo, m2, status):
56 """
57 check for collisions between unknown files and files in m2
58 """
59 modified, added, removed, deleted, unknown = status[:5]
60 for f in unknown:
61 if f in m2:
62 if repo.file(f).cmp(m2[f], repo.wread(f)):
63 raise util.Abort(_("'%s' already exists in the working"
64 " dir and differs from remote") % f)
65
66 def workingmanifest(repo, man, status):
67 """
68 Update manifest to correspond to the working directory
69 """
70
71 modified, added, removed, deleted, unknown = status[:5]
72 for i,l in (("a", added), ("m", modified), ("u", unknown)):
73 for f in l:
74 man[f] = man.get(f, nullid) + i
75 man.set(f, util.is_exec(repo.wjoin(f), man.execf(f)))
76
77 for f in deleted + removed:
78 del man[f]
79
80 return man
81
82 def forgetremoved(m2, status):
83 """
84 Forget removed files
85
86 If we're jumping between revisions (as opposed to merging), and if
87 neither the working directory nor the target rev has the file,
88 then we need to remove it from the dirstate, to prevent the
89 dirstate from listing the file when it is no longer in the
90 manifest.
91 """
92
93 modified, added, removed, deleted, unknown = status[:5]
94 action = []
95
96 for f in deleted + removed:
97 if f not in m2:
98 action.append((f, "f"))
99
100 return action
101
102 def manifestmerge(ui, m1, m2, ma, overwrite, backwards, partial):
56 """
103 """
57 Merge manifest m1 with m2 using ancestor ma and generate merge action list
104 Merge manifest m1 with m2 using ancestor ma and generate merge action list
58 """
105 """
59
106
60 action = []
107 action = []
61
108
109 # Filter manifests
110 if partial:
111 for f in m1.keys():
112 if not partial(f): del m1[f]
113 for f in m2.keys():
114 if not partial(f): del m2[f]
115
62 # Compare manifests
116 # Compare manifests
63 for f, n in m1.iteritems():
117 for f, n in m1.iteritems():
64 if f in m2:
118 if f in m2:
65 queued = 0
119 queued = 0
66
120
67 # are files different?
121 # are files different?
68 if n != m2[f]:
122 if n != m2[f]:
69 a = ma.get(f, nullid)
123 a = ma.get(f, nullid)
70 # are both different from the ancestor?
124 # are both different from the ancestor?
71 if not overwrite and n != a and m2[f] != a:
125 if not overwrite and n != a and m2[f] != a:
72 ui.debug(_(" %s versions differ, resolve\n") % f)
126 ui.debug(_(" %s versions differ, resolve\n") % f)
73 action.append((f, "m", fmerge(f, m1, m2, ma), n[:20], m2[f]))
127 action.append((f, "m", fmerge(f, m1, m2, ma), n[:20], m2[f]))
74 queued = 1
128 queued = 1
75 # are we clobbering?
129 # are we clobbering?
76 # is remote's version newer?
130 # is remote's version newer?
77 # or are we going back in time and clean?
131 # or are we going back in time and clean?
78 elif overwrite or m2[f] != a or (backwards and not n[20:]):
132 elif overwrite or m2[f] != a or (backwards and not n[20:]):
79 ui.debug(_(" remote %s is newer, get\n") % f)
133 ui.debug(_(" remote %s is newer, get\n") % f)
80 action.append((f, "g", m2.execf(f), m2[f]))
134 action.append((f, "g", m2.execf(f), m2[f]))
81 queued = 1
135 queued = 1
82 elif n[20:] in ("u","a"):
136 elif n[20:] in ("u","a"):
83 # this unknown file is the same as the checkout
137 # this unknown file is the same as the checkout
84 # we need to reset the dirstate if the file was added
138 # we need to reset the dirstate if the file was added
85 action.append((f, "g", m2.execf(f), m2[f]))
139 action.append((f, "g", m2.execf(f), m2[f]))
86
140
87 # do we still need to look at mode bits?
141 # do we still need to look at mode bits?
88 if not queued and m1.execf(f) != m2.execf(f):
142 if not queued and m1.execf(f) != m2.execf(f):
89 if overwrite:
143 if overwrite:
90 ui.debug(_(" updating permissions for %s\n") % f)
144 ui.debug(_(" updating permissions for %s\n") % f)
91 action.append((f, "e", m2.execf(f)))
145 action.append((f, "e", m2.execf(f)))
92 else:
146 else:
93 mode = fmerge(f, m1, m2, ma)
147 mode = fmerge(f, m1, m2, ma)
94 if mode != m1.execf(f):
148 if mode != m1.execf(f):
95 ui.debug(_(" updating permissions for %s\n")
149 ui.debug(_(" updating permissions for %s\n")
96 % f)
150 % f)
97 action.append((f, "e", m2.execf(f)))
151 action.append((f, "e", m2.execf(f)))
98 del m2[f]
152 del m2[f]
99 elif f in ma:
153 elif f in ma:
100 if n != ma[f]:
154 if n != ma[f]:
101 r = _("d")
155 r = _("d")
102 if not overwrite:
156 if not overwrite:
103 r = ui.prompt(
157 r = ui.prompt(
104 (_(" local changed %s which remote deleted\n") % f) +
158 (_(" local changed %s which remote deleted\n") % f) +
105 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
159 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
106 if r == _("d"):
160 if r == _("d"):
107 action.append((f, "r"))
161 action.append((f, "r"))
108 else:
162 else:
109 ui.debug(_("other deleted %s\n") % f)
163 ui.debug(_("other deleted %s\n") % f)
110 action.append((f, "r"))
164 action.append((f, "r"))
111 else:
165 else:
112 # file is created on branch or in working directory
166 # file is created on branch or in working directory
113 if overwrite and n[20:] != "u":
167 if overwrite and n[20:] != "u":
114 ui.debug(_("remote deleted %s, clobbering\n") % f)
168 ui.debug(_("remote deleted %s, clobbering\n") % f)
115 action.append((f, "r"))
169 action.append((f, "r"))
116 elif not n[20:]: # same as parent
170 elif not n[20:]: # same as parent
117 if backwards:
171 if backwards:
118 ui.debug(_("remote deleted %s\n") % f)
172 ui.debug(_("remote deleted %s\n") % f)
119 action.append((f, "r"))
173 action.append((f, "r"))
120 else:
174 else:
121 ui.debug(_("local modified %s, keeping\n") % f)
175 ui.debug(_("local modified %s, keeping\n") % f)
122 else:
176 else:
123 ui.debug(_("working dir created %s, keeping\n") % f)
177 ui.debug(_("working dir created %s, keeping\n") % f)
124
178
125 for f, n in m2.iteritems():
179 for f, n in m2.iteritems():
126 if f[0] == "/":
180 if f[0] == "/":
127 continue
181 continue
128 if f in ma and n != ma[f]:
182 if f in ma and n != ma[f]:
129 r = _("k")
183 r = _("k")
130 if not overwrite:
184 if not overwrite:
131 r = ui.prompt(
185 r = ui.prompt(
132 (_("remote changed %s which local deleted\n") % f) +
186 (_("remote changed %s which local deleted\n") % f) +
133 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
187 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
134 if r == _("k"):
188 if r == _("k"):
135 action.append((f, "g", m2.execf(f), n))
189 action.append((f, "g", m2.execf(f), n))
136 elif f not in ma:
190 elif f not in ma:
137 ui.debug(_("remote created %s\n") % f)
191 ui.debug(_("remote created %s\n") % f)
138 action.append((f, "g", m2.execf(f), n))
192 action.append((f, "g", m2.execf(f), n))
139 else:
193 else:
140 if overwrite or backwards:
194 if overwrite or backwards:
141 ui.debug(_("local deleted %s, recreating\n") % f)
195 ui.debug(_("local deleted %s, recreating\n") % f)
142 action.append((f, "g", m2.execf(f), n))
196 action.append((f, "g", m2.execf(f), n))
143 else:
197 else:
144 ui.debug(_("local deleted %s\n") % f)
198 ui.debug(_("local deleted %s\n") % f)
145
199
146 return action
200 return action
147
201
148 def update(repo, node, branchmerge=False, force=False, partial=None,
202 def update(repo, node, branchmerge=False, force=False, partial=None,
149 wlock=None, show_stats=True, remind=True):
203 wlock=None, show_stats=True, remind=True):
150
204
151 overwrite = force and not branchmerge
205 overwrite = force and not branchmerge
152 forcemerge = force and branchmerge
206 forcemerge = force and branchmerge
153
207
154 if not wlock:
208 if not wlock:
155 wlock = repo.wlock()
209 wlock = repo.wlock()
156
210
157 ### check phase
211 ### check phase
158
212
159 pl = repo.dirstate.parents()
213 pl = repo.dirstate.parents()
160 if not overwrite and pl[1] != nullid:
214 if not overwrite and pl[1] != nullid:
161 raise util.Abort(_("outstanding uncommitted merges"))
215 raise util.Abort(_("outstanding uncommitted merges"))
162
216
163 p1, p2 = pl[0], node
217 p1, p2 = pl[0], node
164 pa = repo.changelog.ancestor(p1, p2)
218 pa = repo.changelog.ancestor(p1, p2)
165
219
166 # are we going backwards?
220 # are we going backwards?
167 backwards = (pa == p2)
221 backwards = (pa == p2)
168
222
169 # is there a linear path from p1 to p2?
223 # is there a linear path from p1 to p2?
170 linear_path = (pa == p1 or pa == p2)
224 linear_path = (pa == p1 or pa == p2)
171 if branchmerge and linear_path:
225 if branchmerge and linear_path:
172 raise util.Abort(_("there is nothing to merge, just use "
226 raise util.Abort(_("there is nothing to merge, just use "
173 "'hg update' or look at 'hg heads'"))
227 "'hg update' or look at 'hg heads'"))
174
228
175 if not linear_path and not (overwrite or branchmerge):
229 if not linear_path and not (overwrite or branchmerge):
176 raise util.Abort(_("update spans branches, use 'hg merge' "
230 raise util.Abort(_("update spans branches, use 'hg merge' "
177 "or 'hg update -C' to lose changes"))
231 "or 'hg update -C' to lose changes"))
178
232
179 modified, added, removed, deleted, unknown = repo.status()[:5]
233 status = repo.status()
234 modified, added, removed, deleted, unknown = status[:5]
180 if branchmerge and not forcemerge:
235 if branchmerge and not forcemerge:
181 if modified or added or removed:
236 if modified or added or removed:
182 raise util.Abort(_("outstanding uncommitted changes"))
237 raise util.Abort(_("outstanding uncommitted changes"))
183
238
184 m1 = repo.changectx(p1).manifest().copy()
239 m1 = repo.changectx(p1).manifest().copy()
185 m2 = repo.changectx(p2).manifest().copy()
240 m2 = repo.changectx(p2).manifest().copy()
186 ma = repo.changectx(pa).manifest()
241 ma = repo.changectx(pa).manifest()
187
242
188 if not force:
189 for f in unknown:
190 if f in m2:
191 if repo.file(f).cmp(m2[f], repo.wread(f)):
192 raise util.Abort(_("'%s' already exists in the working"
193 " dir and differs from remote") % f)
194
195 # resolve the manifest to determine which files
243 # resolve the manifest to determine which files
196 # we care about merging
244 # we care about merging
197 repo.ui.note(_("resolving manifests\n"))
245 repo.ui.note(_("resolving manifests\n"))
198 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") %
246 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") %
199 (overwrite, branchmerge, bool(partial), linear_path))
247 (overwrite, branchmerge, bool(partial), linear_path))
200 repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
248 repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
201 (short(p1), short(p2), short(pa)))
249 (short(p1), short(p2), short(pa)))
202
250
203 action = []
251 action = []
204
252 m1 = workingmanifest(repo, m1, status)
205 # update m1 from working dir
206 for i,l in (("a", added), ("m", modified), ("u", unknown)):
207 for f in l:
208 m1[f] = m1.get(f, nullid) + i
209 m1.set(f, util.is_exec(repo.wjoin(f), m1.execf(f)))
210
211 for f in deleted + removed:
212 del m1[f]
213
253
214 # If we're jumping between revisions (as opposed to merging),
254 if not force:
215 # and if neither the working directory nor the target rev has
255 checkunknown(repo, m2, status)
216 # the file, then we need to remove it from the dirstate, to
256 if linear_path:
217 # prevent the dirstate from listing the file when it is no
257 action += forgetremoved(m2, status)
218 # longer in the manifest.
258 action += manifestmerge(repo.ui, m1, m2, ma, overwrite, backwards, partial)
219 if linear_path and f not in m2:
220 action.append((f, "f"))
221
222 if partial:
223 for f in m1.keys():
224 if not partial(f): del m1[f]
225 for f in m2.keys():
226 if not partial(f): del m2[f]
227
228 action += manifestmerge(repo.ui, m1, m2, ma, overwrite, backwards)
229 del m1, m2, ma
259 del m1, m2, ma
230
260
231 ### apply phase
261 ### apply phase
232
262
233 if linear_path or overwrite:
263 if linear_path or overwrite:
234 # we don't need to do any magic, just jump to the new rev
264 # we don't need to do any magic, just jump to the new rev
235 p1, p2 = p2, nullid
265 p1, p2 = p2, nullid
236
266
237 xp1 = hex(p1)
267 xp1 = hex(p1)
238 xp2 = hex(p2)
268 xp2 = hex(p2)
239 if p2 == nullid: xxp2 = ''
269 if p2 == nullid: xxp2 = ''
240 else: xxp2 = xp2
270 else: xxp2 = xp2
241
271
242 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
272 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
243
273
244 # update files
274 # update files
245 updated, merged, removed, unresolved = 0, 0, 0, 0
275 updated, merged, removed, unresolved = 0, 0, 0, 0
246 action.sort()
276 action.sort()
247 for a in action:
277 for a in action:
248 f, m = a[:2]
278 f, m = a[:2]
249 if f[0] == "/":
279 if f[0] == "/":
250 continue
280 continue
251 if m == "r": # remove
281 if m == "r": # remove
252 repo.ui.note(_("removing %s\n") % f)
282 repo.ui.note(_("removing %s\n") % f)
253 util.audit_path(f)
283 util.audit_path(f)
254 try:
284 try:
255 util.unlink(repo.wjoin(f))
285 util.unlink(repo.wjoin(f))
256 except OSError, inst:
286 except OSError, inst:
257 if inst.errno != errno.ENOENT:
287 if inst.errno != errno.ENOENT:
258 repo.ui.warn(_("update failed to remove %s: %s!\n") %
288 repo.ui.warn(_("update failed to remove %s: %s!\n") %
259 (f, inst.strerror))
289 (f, inst.strerror))
260 removed +=1
290 removed +=1
261 elif m == "m": # merge
291 elif m == "m": # merge
262 flag, my, other = a[2:]
292 flag, my, other = a[2:]
263 repo.ui.status(_("merging %s\n") % f)
293 repo.ui.status(_("merging %s\n") % f)
264 if merge3(repo, f, my, other, xp1, xp2):
294 if merge3(repo, f, my, other, xp1, xp2):
265 unresolved += 1
295 unresolved += 1
266 util.set_exec(repo.wjoin(f), flag)
296 util.set_exec(repo.wjoin(f), flag)
267 merged += 1
297 merged += 1
268 elif m == "g": # get
298 elif m == "g": # get
269 flag, node = a[2:]
299 flag, node = a[2:]
270 repo.ui.note(_("getting %s\n") % f)
300 repo.ui.note(_("getting %s\n") % f)
271 t = repo.file(f).read(node)
301 t = repo.file(f).read(node)
272 repo.wwrite(f, t)
302 repo.wwrite(f, t)
273 util.set_exec(repo.wjoin(f), flag)
303 util.set_exec(repo.wjoin(f), flag)
274 updated += 1
304 updated += 1
275 elif m == "e": # exec
305 elif m == "e": # exec
276 flag = a[2:]
306 flag = a[2:]
277 util.set_exec(repo.wjoin(f), flag)
307 util.set_exec(repo.wjoin(f), flag)
278
308
279 # update dirstate
309 # update dirstate
280 if not partial:
310 if not partial:
281 repo.dirstate.setparents(p1, p2)
311 repo.dirstate.setparents(p1, p2)
282 for a in action:
312 for a in action:
283 f, m = a[:2]
313 f, m = a[:2]
284 if m == "r": # remove
314 if m == "r": # remove
285 if branchmerge:
315 if branchmerge:
286 repo.dirstate.update([f], 'r')
316 repo.dirstate.update([f], 'r')
287 else:
317 else:
288 repo.dirstate.forget([f])
318 repo.dirstate.forget([f])
289 elif m == "f": # forget
319 elif m == "f": # forget
290 repo.dirstate.forget([f])
320 repo.dirstate.forget([f])
291 elif m == "g": # get
321 elif m == "g": # get
292 if branchmerge:
322 if branchmerge:
293 repo.dirstate.update([f], 'n', st_mtime=-1)
323 repo.dirstate.update([f], 'n', st_mtime=-1)
294 else:
324 else:
295 repo.dirstate.update([f], 'n')
325 repo.dirstate.update([f], 'n')
296 elif m == "m": # merge
326 elif m == "m": # merge
297 flag, my, other = a[2:]
327 flag, my, other = a[2:]
298 if branchmerge:
328 if branchmerge:
299 # We've done a branch merge, mark this file as merged
329 # We've done a branch merge, mark this file as merged
300 # so that we properly record the merger later
330 # so that we properly record the merger later
301 repo.dirstate.update([f], 'm')
331 repo.dirstate.update([f], 'm')
302 else:
332 else:
303 # We've update-merged a locally modified file, so
333 # We've update-merged a locally modified file, so
304 # we set the dirstate to emulate a normal checkout
334 # we set the dirstate to emulate a normal checkout
305 # of that file some time in the past. Thus our
335 # of that file some time in the past. Thus our
306 # merge will appear as a normal local file
336 # merge will appear as a normal local file
307 # modification.
337 # modification.
308 fl = repo.file(f)
338 fl = repo.file(f)
309 f_len = fl.size(fl.rev(other))
339 f_len = fl.size(fl.rev(other))
310 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
340 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
311
341
312 if show_stats:
342 if show_stats:
313 stats = ((updated, _("updated")),
343 stats = ((updated, _("updated")),
314 (merged - unresolved, _("merged")),
344 (merged - unresolved, _("merged")),
315 (removed, _("removed")),
345 (removed, _("removed")),
316 (unresolved, _("unresolved")))
346 (unresolved, _("unresolved")))
317 note = ", ".join([_("%d files %s") % s for s in stats])
347 note = ", ".join([_("%d files %s") % s for s in stats])
318 repo.ui.status("%s\n" % note)
348 repo.ui.status("%s\n" % note)
319 if not partial:
349 if not partial:
320 if branchmerge:
350 if branchmerge:
321 if unresolved:
351 if unresolved:
322 repo.ui.status(_("There are unresolved merges,"
352 repo.ui.status(_("There are unresolved merges,"
323 " you can redo the full merge using:\n"
353 " you can redo the full merge using:\n"
324 " hg update -C %s\n"
354 " hg update -C %s\n"
325 " hg merge %s\n"
355 " hg merge %s\n"
326 % (repo.changelog.rev(p1),
356 % (repo.changelog.rev(p1),
327 repo.changelog.rev(p2))))
357 repo.changelog.rev(p2))))
328 elif remind:
358 elif remind:
329 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
359 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
330 elif unresolved:
360 elif unresolved:
331 repo.ui.status(_("There are unresolved merges with"
361 repo.ui.status(_("There are unresolved merges with"
332 " locally modified files.\n"))
362 " locally modified files.\n"))
333
363
334 repo.hook('update', parent1=xp1, parent2=xxp2, error=unresolved)
364 repo.hook('update', parent1=xp1, parent2=xxp2, error=unresolved)
335 return unresolved
365 return unresolved
336
366
General Comments 0
You need to be logged in to leave comments. Login now