##// END OF EJS Templates
Move merge code to its own module...
Matt Mackall -
r2775:b550cd82 default
parent child Browse files
Show More
@@ -0,0 +1,348 b''
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 from node import *
9 from i18n import gettext as _
10 from demandload import *
11 demandload(globals(), "util os tempfile")
12
13 def merge3(repo, fn, my, other, p1, p2):
14 """perform a 3-way merge in the working directory"""
15
16 def temp(prefix, node):
17 pre = "%s~%s." % (os.path.basename(fn), prefix)
18 (fd, name) = tempfile.mkstemp(prefix=pre)
19 f = os.fdopen(fd, "wb")
20 repo.wwrite(fn, fl.read(node), f)
21 f.close()
22 return name
23
24 fl = repo.file(fn)
25 base = fl.ancestor(my, other)
26 a = repo.wjoin(fn)
27 b = temp("base", base)
28 c = temp("other", other)
29
30 repo.ui.note(_("resolving %s\n") % fn)
31 repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
32 (fn, short(my), short(other), short(base)))
33
34 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
35 or "hgmerge")
36 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
37 environ={'HG_FILE': fn,
38 'HG_MY_NODE': p1,
39 'HG_OTHER_NODE': p2,
40 'HG_FILE_MY_NODE': hex(my),
41 'HG_FILE_OTHER_NODE': hex(other),
42 'HG_FILE_BASE_NODE': hex(base)})
43 if r:
44 repo.ui.warn(_("merging %s failed!\n") % fn)
45
46 os.unlink(b)
47 os.unlink(c)
48 return r
49
50 def update(repo, node, allow=False, force=False, choose=None,
51 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
52 pl = repo.dirstate.parents()
53 if not force and pl[1] != nullid:
54 raise util.Abort(_("outstanding uncommitted merges"))
55
56 err = False
57
58 p1, p2 = pl[0], node
59 pa = repo.changelog.ancestor(p1, p2)
60 m1n = repo.changelog.read(p1)[0]
61 m2n = repo.changelog.read(p2)[0]
62 man = repo.manifest.ancestor(m1n, m2n)
63 m1 = repo.manifest.read(m1n)
64 mf1 = repo.manifest.readflags(m1n)
65 m2 = repo.manifest.read(m2n).copy()
66 mf2 = repo.manifest.readflags(m2n)
67 ma = repo.manifest.read(man)
68 mfa = repo.manifest.readflags(man)
69
70 modified, added, removed, deleted, unknown = repo.changes()
71
72 # is this a jump, or a merge? i.e. is there a linear path
73 # from p1 to p2?
74 linear_path = (pa == p1 or pa == p2)
75
76 if allow and linear_path:
77 raise util.Abort(_("there is nothing to merge, just use "
78 "'hg update' or look at 'hg heads'"))
79 if allow and not forcemerge:
80 if modified or added or removed:
81 raise util.Abort(_("outstanding uncommitted changes"))
82
83 if not forcemerge and not force:
84 for f in unknown:
85 if f in m2:
86 t1 = repo.wread(f)
87 t2 = repo.file(f).read(m2[f])
88 if cmp(t1, t2) != 0:
89 raise util.Abort(_("'%s' already exists in the working"
90 " dir and differs from remote") % f)
91
92 # resolve the manifest to determine which files
93 # we care about merging
94 repo.ui.note(_("resolving manifests\n"))
95 repo.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
96 (force, allow, moddirstate, linear_path))
97 repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
98 (short(man), short(m1n), short(m2n)))
99
100 merge = {}
101 get = {}
102 remove = []
103
104 # construct a working dir manifest
105 mw = m1.copy()
106 mfw = mf1.copy()
107 umap = dict.fromkeys(unknown)
108
109 for f in added + modified + unknown:
110 mw[f] = ""
111 mfw[f] = util.is_exec(repo.wjoin(f), mfw.get(f, False))
112
113 if moddirstate and not wlock:
114 wlock = repo.wlock()
115
116 for f in deleted + removed:
117 if f in mw:
118 del mw[f]
119
120 # If we're jumping between revisions (as opposed to merging),
121 # and if neither the working directory nor the target rev has
122 # the file, then we need to remove it from the dirstate, to
123 # prevent the dirstate from listing the file when it is no
124 # longer in the manifest.
125 if moddirstate and linear_path and f not in m2:
126 repo.dirstate.forget((f,))
127
128 # Compare manifests
129 for f, n in mw.iteritems():
130 if choose and not choose(f):
131 continue
132 if f in m2:
133 s = 0
134
135 # is the wfile new since m1, and match m2?
136 if f not in m1:
137 t1 = repo.wread(f)
138 t2 = repo.file(f).read(m2[f])
139 if cmp(t1, t2) == 0:
140 n = m2[f]
141 del t1, t2
142
143 # are files different?
144 if n != m2[f]:
145 a = ma.get(f, nullid)
146 # are both different from the ancestor?
147 if n != a and m2[f] != a:
148 repo.ui.debug(_(" %s versions differ, resolve\n") % f)
149 # merge executable bits
150 # "if we changed or they changed, change in merge"
151 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
152 mode = ((a^b) | (a^c)) ^ a
153 merge[f] = (m1.get(f, nullid), m2[f], mode)
154 s = 1
155 # are we clobbering?
156 # is remote's version newer?
157 # or are we going back in time?
158 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
159 repo.ui.debug(_(" remote %s is newer, get\n") % f)
160 get[f] = m2[f]
161 s = 1
162 elif f in umap or f in added:
163 # this unknown file is the same as the checkout
164 # we need to reset the dirstate if the file was added
165 get[f] = m2[f]
166
167 if not s and mfw[f] != mf2[f]:
168 if force:
169 repo.ui.debug(_(" updating permissions for %s\n") % f)
170 util.set_exec(repo.wjoin(f), mf2[f])
171 else:
172 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
173 mode = ((a^b) | (a^c)) ^ a
174 if mode != b:
175 repo.ui.debug(_(" updating permissions for %s\n")
176 % f)
177 util.set_exec(repo.wjoin(f), mode)
178 del m2[f]
179 elif f in ma:
180 if n != ma[f]:
181 r = _("d")
182 if not force and (linear_path or allow):
183 r = repo.ui.prompt(
184 (_(" local changed %s which remote deleted\n") % f) +
185 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
186 if r == _("d"):
187 remove.append(f)
188 else:
189 repo.ui.debug(_("other deleted %s\n") % f)
190 remove.append(f) # other deleted it
191 else:
192 # file is created on branch or in working directory
193 if force and f not in umap:
194 repo.ui.debug(_("remote deleted %s, clobbering\n") % f)
195 remove.append(f)
196 elif n == m1.get(f, nullid): # same as parent
197 if p2 == pa: # going backwards?
198 repo.ui.debug(_("remote deleted %s\n") % f)
199 remove.append(f)
200 else:
201 repo.ui.debug(_("local modified %s, keeping\n") % f)
202 else:
203 repo.ui.debug(_("working dir created %s, keeping\n") % f)
204
205 for f, n in m2.iteritems():
206 if choose and not choose(f):
207 continue
208 if f[0] == "/":
209 continue
210 if f in ma and n != ma[f]:
211 r = _("k")
212 if not force and (linear_path or allow):
213 r = repo.ui.prompt(
214 (_("remote changed %s which local deleted\n") % f) +
215 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
216 if r == _("k"):
217 get[f] = n
218 elif f not in ma:
219 repo.ui.debug(_("remote created %s\n") % f)
220 get[f] = n
221 else:
222 if force or p2 == pa: # going backwards?
223 repo.ui.debug(_("local deleted %s, recreating\n") % f)
224 get[f] = n
225 else:
226 repo.ui.debug(_("local deleted %s\n") % f)
227
228 del mw, m1, m2, ma
229
230 if force:
231 for f in merge:
232 get[f] = merge[f][1]
233 merge = {}
234
235 if linear_path or force:
236 # we don't need to do any magic, just jump to the new rev
237 branch_merge = False
238 p1, p2 = p2, nullid
239 else:
240 if not allow:
241 repo.ui.status(_("this update spans a branch"
242 " affecting the following files:\n"))
243 fl = merge.keys() + get.keys()
244 fl.sort()
245 for f in fl:
246 cf = ""
247 if f in merge:
248 cf = _(" (resolve)")
249 repo.ui.status(" %s%s\n" % (f, cf))
250 repo.ui.warn(_("aborting update spanning branches!\n"))
251 repo.ui.status(_("(use 'hg merge' to merge across branches"
252 " or 'hg update -C' to lose changes)\n"))
253 return 1
254 branch_merge = True
255
256 xp1 = hex(p1)
257 xp2 = hex(p2)
258 if p2 == nullid: xxp2 = ''
259 else: xxp2 = xp2
260
261 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
262
263 # get the files we don't need to change
264 files = get.keys()
265 files.sort()
266 for f in files:
267 if f[0] == "/":
268 continue
269 repo.ui.note(_("getting %s\n") % f)
270 t = repo.file(f).read(get[f])
271 repo.wwrite(f, t)
272 util.set_exec(repo.wjoin(f), mf2[f])
273 if moddirstate:
274 if branch_merge:
275 repo.dirstate.update([f], 'n', st_mtime=-1)
276 else:
277 repo.dirstate.update([f], 'n')
278
279 # merge the tricky bits
280 failedmerge = []
281 files = merge.keys()
282 files.sort()
283 for f in files:
284 repo.ui.status(_("merging %s\n") % f)
285 my, other, flag = merge[f]
286 ret = merge3(repo, f, my, other, xp1, xp2)
287 if ret:
288 err = True
289 failedmerge.append(f)
290 util.set_exec(repo.wjoin(f), flag)
291 if moddirstate:
292 if branch_merge:
293 # We've done a branch merge, mark this file as merged
294 # so that we properly record the merger later
295 repo.dirstate.update([f], 'm')
296 else:
297 # We've update-merged a locally modified file, so
298 # we set the dirstate to emulate a normal checkout
299 # of that file some time in the past. Thus our
300 # merge will appear as a normal local file
301 # modification.
302 f_len = len(repo.file(f).read(other))
303 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
304
305 remove.sort()
306 for f in remove:
307 repo.ui.note(_("removing %s\n") % f)
308 util.audit_path(f)
309 try:
310 util.unlink(repo.wjoin(f))
311 except OSError, inst:
312 if inst.errno != errno.ENOENT:
313 repo.ui.warn(_("update failed to remove %s: %s!\n") %
314 (f, inst.strerror))
315 if moddirstate:
316 if branch_merge:
317 repo.dirstate.update(remove, 'r')
318 else:
319 repo.dirstate.forget(remove)
320
321 if moddirstate:
322 repo.dirstate.setparents(p1, p2)
323
324 if show_stats:
325 stats = ((len(get), _("updated")),
326 (len(merge) - len(failedmerge), _("merged")),
327 (len(remove), _("removed")),
328 (len(failedmerge), _("unresolved")))
329 note = ", ".join([_("%d files %s") % s for s in stats])
330 repo.ui.status("%s\n" % note)
331 if moddirstate:
332 if branch_merge:
333 if failedmerge:
334 repo.ui.status(_("There are unresolved merges,"
335 " you can redo the full merge using:\n"
336 " hg update -C %s\n"
337 " hg merge %s\n"
338 % (repo.changelog.rev(p1),
339 repo.changelog.rev(p2))))
340 else:
341 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
342 elif failedmerge:
343 repo.ui.status(_("There are unresolved merges with"
344 " locally modified files.\n"))
345
346 repo.hook('update', parent1=xp1, parent2=xxp2, error=int(err))
347 return err
348
@@ -179,11 +179,11 b' class queue:'
179 self.ui.warn("patch didn't work out, merging %s\n" % patch)
179 self.ui.warn("patch didn't work out, merging %s\n" % patch)
180
180
181 # apply failed, strip away that rev and merge.
181 # apply failed, strip away that rev and merge.
182 repo.update(head, allow=False, force=True, wlock=wlock)
182 hg.update(repo, head, allow=False, force=True, wlock=wlock)
183 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
183 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
184
184
185 c = repo.changelog.read(rev)
185 c = repo.changelog.read(rev)
186 ret = repo.update(rev, allow=True, wlock=wlock)
186 ret = hg.update(repo, rev, allow=True, wlock=wlock)
187 if ret:
187 if ret:
188 raise util.Abort(_("update returned %d") % ret)
188 raise util.Abort(_("update returned %d") % ret)
189 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
189 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
@@ -299,7 +299,7 b' class queue:'
299 self.ui.warn(l + '\n')
299 self.ui.warn(l + '\n')
300
300
301 return (not f.close(), files, fuzz)
301 return (not f.close(), files, fuzz)
302
302
303 def apply(self, repo, series, list=False, update_status=True,
303 def apply(self, repo, series, list=False, update_status=True,
304 strict=False, patchdir=None, merge=None, wlock=None):
304 strict=False, patchdir=None, merge=None, wlock=None):
305 # TODO unify with commands.py
305 # TODO unify with commands.py
@@ -526,7 +526,7 b' class queue:'
526 if c or a or d or r:
526 if c or a or d or r:
527 raise util.Abort(_("local changes found"))
527 raise util.Abort(_("local changes found"))
528 urev = self.qparents(repo, rev)
528 urev = self.qparents(repo, rev)
529 repo.update(urev, allow=False, force=True, wlock=wlock)
529 hg.update(repo, urev, allow=False, force=True, wlock=wlock)
530 repo.dirstate.write()
530 repo.dirstate.write()
531
531
532 # save is a list of all the branches we are truncating away
532 # save is a list of all the branches we are truncating away
@@ -1023,7 +1023,7 b' class queue:'
1023 if not r:
1023 if not r:
1024 self.ui.warn("Unable to load queue repository\n")
1024 self.ui.warn("Unable to load queue repository\n")
1025 return 1
1025 return 1
1026 r.update(qpp[0], allow=False, force=True)
1026 hg.update(r, qpp[0], allow=False, force=True)
1027
1027
1028 def save(self, repo, msg=None):
1028 def save(self, repo, msg=None):
1029 if len(self.applied) == 0:
1029 if len(self.applied) == 0:
@@ -1245,7 +1245,7 b' def clone(ui, source, dest=None, **opts)'
1245 dr.mq.strip(dr, qbase, update=False, backup=None)
1245 dr.mq.strip(dr, qbase, update=False, backup=None)
1246 if not opts['noupdate']:
1246 if not opts['noupdate']:
1247 ui.note(_('updating destination repo\n'))
1247 ui.note(_('updating destination repo\n'))
1248 dr.update(dr.changelog.tip())
1248 hg.update(dr, dr.changelog.tip())
1249
1249
1250 def commit(ui, repo, *pats, **opts):
1250 def commit(ui, repo, *pats, **opts):
1251 """commit changes in the queue repository"""
1251 """commit changes in the queue repository"""
@@ -921,7 +921,7 b' def backout(ui, repo, rev, **opts):'
921 if opts['parent']:
921 if opts['parent']:
922 raise util.Abort(_('cannot use --parent on non-merge changeset'))
922 raise util.Abort(_('cannot use --parent on non-merge changeset'))
923 parent = p1
923 parent = p1
924 repo.update(node, force=True, show_stats=False)
924 hg.update(repo, node, force=True, show_stats=False)
925 revert_opts = opts.copy()
925 revert_opts = opts.copy()
926 revert_opts['rev'] = hex(parent)
926 revert_opts['rev'] = hex(parent)
927 revert(ui, repo, **revert_opts)
927 revert(ui, repo, **revert_opts)
@@ -2542,8 +2542,8 b' def revert(ui, repo, *pats, **opts):'
2542
2542
2543 if not opts.get('dry_run'):
2543 if not opts.get('dry_run'):
2544 repo.dirstate.forget(forget[0])
2544 repo.dirstate.forget(forget[0])
2545 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2545 r = hg.update(repo, node, False, True, update.has_key, False,
2546 show_stats=False)
2546 wlock=wlock, show_stats=False)
2547 repo.dirstate.update(add[0], 'a')
2547 repo.dirstate.update(add[0], 'a')
2548 repo.dirstate.update(undelete[0], 'n')
2548 repo.dirstate.update(undelete[0], 'n')
2549 repo.dirstate.update(remove[0], 'r')
2549 repo.dirstate.update(remove[0], 'r')
@@ -2867,7 +2867,7 b' def doupdate(ui, repo, node=None, merge='
2867 return 1
2867 return 1
2868 else:
2868 else:
2869 node = node and repo.lookup(node) or repo.changelog.tip()
2869 node = node and repo.lookup(node) or repo.changelog.tip()
2870 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2870 return hg.update(repo, node, allow=merge, force=clean, forcemerge=force)
2871
2871
2872 def verify(ui, repo):
2872 def verify(ui, repo):
2873 """verify the integrity of the repository
2873 """verify the integrity of the repository
@@ -10,7 +10,7 b' from repo import *'
10 from demandload import *
10 from demandload import *
11 from i18n import gettext as _
11 from i18n import gettext as _
12 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
12 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
13 demandload(globals(), "errno lock os shutil util")
13 demandload(globals(), "errno lock os shutil util merge")
14
14
15 def _local(path):
15 def _local(path):
16 return (os.path.isfile(path and util.drop_scheme('file', path)) and
16 return (os.path.isfile(path and util.drop_scheme('file', path)) and
@@ -38,7 +38,7 b' def _lookup(path):'
38 return thing(path)
38 return thing(path)
39 except TypeError:
39 except TypeError:
40 return thing
40 return thing
41
41
42 def islocal(repo):
42 def islocal(repo):
43 '''return true if repo or path is local'''
43 '''return true if repo or path is local'''
44 if isinstance(repo, str):
44 if isinstance(repo, str):
@@ -200,8 +200,17 b' def clone(ui, source, dest=None, pull=Fa'
200 dest_lock.release()
200 dest_lock.release()
201
201
202 if update:
202 if update:
203 dest_repo.update(dest_repo.changelog.tip())
203 merge.update(dest_repo, dest_repo.changelog.tip())
204 if dir_cleanup:
204 if dir_cleanup:
205 dir_cleanup.close()
205 dir_cleanup.close()
206
206
207 return src_repo, dest_repo
207 return src_repo, dest_repo
208
209
210 # This should instead be several functions with short arglists, like
211 # update/merge/revert
212
213 def update(repo, node, allow=False, force=False, choose=None,
214 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
215 return merge.update(repo, node, allow, force, choose, moddirstate,
216 forcemerge, wlock, show_stats)
@@ -1693,342 +1693,6 b' class localrepository(repo.repository):'
1693
1693
1694 return newheads - oldheads + 1
1694 return newheads - oldheads + 1
1695
1695
1696 def update(self, node, allow=False, force=False, choose=None,
1697 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
1698 pl = self.dirstate.parents()
1699 if not force and pl[1] != nullid:
1700 raise util.Abort(_("outstanding uncommitted merges"))
1701
1702 err = False
1703
1704 p1, p2 = pl[0], node
1705 pa = self.changelog.ancestor(p1, p2)
1706 m1n = self.changelog.read(p1)[0]
1707 m2n = self.changelog.read(p2)[0]
1708 man = self.manifest.ancestor(m1n, m2n)
1709 m1 = self.manifest.read(m1n)
1710 mf1 = self.manifest.readflags(m1n)
1711 m2 = self.manifest.read(m2n).copy()
1712 mf2 = self.manifest.readflags(m2n)
1713 ma = self.manifest.read(man)
1714 mfa = self.manifest.readflags(man)
1715
1716 modified, added, removed, deleted, unknown = self.changes()
1717
1718 # is this a jump, or a merge? i.e. is there a linear path
1719 # from p1 to p2?
1720 linear_path = (pa == p1 or pa == p2)
1721
1722 if allow and linear_path:
1723 raise util.Abort(_("there is nothing to merge, just use "
1724 "'hg update' or look at 'hg heads'"))
1725 if allow and not forcemerge:
1726 if modified or added or removed:
1727 raise util.Abort(_("outstanding uncommitted changes"))
1728
1729 if not forcemerge and not force:
1730 for f in unknown:
1731 if f in m2:
1732 t1 = self.wread(f)
1733 t2 = self.file(f).read(m2[f])
1734 if cmp(t1, t2) != 0:
1735 raise util.Abort(_("'%s' already exists in the working"
1736 " dir and differs from remote") % f)
1737
1738 # resolve the manifest to determine which files
1739 # we care about merging
1740 self.ui.note(_("resolving manifests\n"))
1741 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1742 (force, allow, moddirstate, linear_path))
1743 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1744 (short(man), short(m1n), short(m2n)))
1745
1746 merge = {}
1747 get = {}
1748 remove = []
1749
1750 # construct a working dir manifest
1751 mw = m1.copy()
1752 mfw = mf1.copy()
1753 umap = dict.fromkeys(unknown)
1754
1755 for f in added + modified + unknown:
1756 mw[f] = ""
1757 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1758
1759 if moddirstate and not wlock:
1760 wlock = self.wlock()
1761
1762 for f in deleted + removed:
1763 if f in mw:
1764 del mw[f]
1765
1766 # If we're jumping between revisions (as opposed to merging),
1767 # and if neither the working directory nor the target rev has
1768 # the file, then we need to remove it from the dirstate, to
1769 # prevent the dirstate from listing the file when it is no
1770 # longer in the manifest.
1771 if moddirstate and linear_path and f not in m2:
1772 self.dirstate.forget((f,))
1773
1774 # Compare manifests
1775 for f, n in mw.iteritems():
1776 if choose and not choose(f):
1777 continue
1778 if f in m2:
1779 s = 0
1780
1781 # is the wfile new since m1, and match m2?
1782 if f not in m1:
1783 t1 = self.wread(f)
1784 t2 = self.file(f).read(m2[f])
1785 if cmp(t1, t2) == 0:
1786 n = m2[f]
1787 del t1, t2
1788
1789 # are files different?
1790 if n != m2[f]:
1791 a = ma.get(f, nullid)
1792 # are both different from the ancestor?
1793 if n != a and m2[f] != a:
1794 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1795 # merge executable bits
1796 # "if we changed or they changed, change in merge"
1797 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1798 mode = ((a^b) | (a^c)) ^ a
1799 merge[f] = (m1.get(f, nullid), m2[f], mode)
1800 s = 1
1801 # are we clobbering?
1802 # is remote's version newer?
1803 # or are we going back in time?
1804 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1805 self.ui.debug(_(" remote %s is newer, get\n") % f)
1806 get[f] = m2[f]
1807 s = 1
1808 elif f in umap or f in added:
1809 # this unknown file is the same as the checkout
1810 # we need to reset the dirstate if the file was added
1811 get[f] = m2[f]
1812
1813 if not s and mfw[f] != mf2[f]:
1814 if force:
1815 self.ui.debug(_(" updating permissions for %s\n") % f)
1816 util.set_exec(self.wjoin(f), mf2[f])
1817 else:
1818 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1819 mode = ((a^b) | (a^c)) ^ a
1820 if mode != b:
1821 self.ui.debug(_(" updating permissions for %s\n")
1822 % f)
1823 util.set_exec(self.wjoin(f), mode)
1824 del m2[f]
1825 elif f in ma:
1826 if n != ma[f]:
1827 r = _("d")
1828 if not force and (linear_path or allow):
1829 r = self.ui.prompt(
1830 (_(" local changed %s which remote deleted\n") % f) +
1831 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1832 if r == _("d"):
1833 remove.append(f)
1834 else:
1835 self.ui.debug(_("other deleted %s\n") % f)
1836 remove.append(f) # other deleted it
1837 else:
1838 # file is created on branch or in working directory
1839 if force and f not in umap:
1840 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1841 remove.append(f)
1842 elif n == m1.get(f, nullid): # same as parent
1843 if p2 == pa: # going backwards?
1844 self.ui.debug(_("remote deleted %s\n") % f)
1845 remove.append(f)
1846 else:
1847 self.ui.debug(_("local modified %s, keeping\n") % f)
1848 else:
1849 self.ui.debug(_("working dir created %s, keeping\n") % f)
1850
1851 for f, n in m2.iteritems():
1852 if choose and not choose(f):
1853 continue
1854 if f[0] == "/":
1855 continue
1856 if f in ma and n != ma[f]:
1857 r = _("k")
1858 if not force and (linear_path or allow):
1859 r = self.ui.prompt(
1860 (_("remote changed %s which local deleted\n") % f) +
1861 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1862 if r == _("k"):
1863 get[f] = n
1864 elif f not in ma:
1865 self.ui.debug(_("remote created %s\n") % f)
1866 get[f] = n
1867 else:
1868 if force or p2 == pa: # going backwards?
1869 self.ui.debug(_("local deleted %s, recreating\n") % f)
1870 get[f] = n
1871 else:
1872 self.ui.debug(_("local deleted %s\n") % f)
1873
1874 del mw, m1, m2, ma
1875
1876 if force:
1877 for f in merge:
1878 get[f] = merge[f][1]
1879 merge = {}
1880
1881 if linear_path or force:
1882 # we don't need to do any magic, just jump to the new rev
1883 branch_merge = False
1884 p1, p2 = p2, nullid
1885 else:
1886 if not allow:
1887 self.ui.status(_("this update spans a branch"
1888 " affecting the following files:\n"))
1889 fl = merge.keys() + get.keys()
1890 fl.sort()
1891 for f in fl:
1892 cf = ""
1893 if f in merge:
1894 cf = _(" (resolve)")
1895 self.ui.status(" %s%s\n" % (f, cf))
1896 self.ui.warn(_("aborting update spanning branches!\n"))
1897 self.ui.status(_("(use 'hg merge' to merge across branches"
1898 " or 'hg update -C' to lose changes)\n"))
1899 return 1
1900 branch_merge = True
1901
1902 xp1 = hex(p1)
1903 xp2 = hex(p2)
1904 if p2 == nullid: xxp2 = ''
1905 else: xxp2 = xp2
1906
1907 self.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
1908
1909 # get the files we don't need to change
1910 files = get.keys()
1911 files.sort()
1912 for f in files:
1913 if f[0] == "/":
1914 continue
1915 self.ui.note(_("getting %s\n") % f)
1916 t = self.file(f).read(get[f])
1917 self.wwrite(f, t)
1918 util.set_exec(self.wjoin(f), mf2[f])
1919 if moddirstate:
1920 if branch_merge:
1921 self.dirstate.update([f], 'n', st_mtime=-1)
1922 else:
1923 self.dirstate.update([f], 'n')
1924
1925 # merge the tricky bits
1926 failedmerge = []
1927 files = merge.keys()
1928 files.sort()
1929 for f in files:
1930 self.ui.status(_("merging %s\n") % f)
1931 my, other, flag = merge[f]
1932 ret = self.merge3(f, my, other, xp1, xp2)
1933 if ret:
1934 err = True
1935 failedmerge.append(f)
1936 util.set_exec(self.wjoin(f), flag)
1937 if moddirstate:
1938 if branch_merge:
1939 # We've done a branch merge, mark this file as merged
1940 # so that we properly record the merger later
1941 self.dirstate.update([f], 'm')
1942 else:
1943 # We've update-merged a locally modified file, so
1944 # we set the dirstate to emulate a normal checkout
1945 # of that file some time in the past. Thus our
1946 # merge will appear as a normal local file
1947 # modification.
1948 f_len = len(self.file(f).read(other))
1949 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1950
1951 remove.sort()
1952 for f in remove:
1953 self.ui.note(_("removing %s\n") % f)
1954 util.audit_path(f)
1955 try:
1956 util.unlink(self.wjoin(f))
1957 except OSError, inst:
1958 if inst.errno != errno.ENOENT:
1959 self.ui.warn(_("update failed to remove %s: %s!\n") %
1960 (f, inst.strerror))
1961 if moddirstate:
1962 if branch_merge:
1963 self.dirstate.update(remove, 'r')
1964 else:
1965 self.dirstate.forget(remove)
1966
1967 if moddirstate:
1968 self.dirstate.setparents(p1, p2)
1969
1970 if show_stats:
1971 stats = ((len(get), _("updated")),
1972 (len(merge) - len(failedmerge), _("merged")),
1973 (len(remove), _("removed")),
1974 (len(failedmerge), _("unresolved")))
1975 note = ", ".join([_("%d files %s") % s for s in stats])
1976 self.ui.status("%s\n" % note)
1977 if moddirstate:
1978 if branch_merge:
1979 if failedmerge:
1980 self.ui.status(_("There are unresolved merges,"
1981 " you can redo the full merge using:\n"
1982 " hg update -C %s\n"
1983 " hg merge %s\n"
1984 % (self.changelog.rev(p1),
1985 self.changelog.rev(p2))))
1986 else:
1987 self.ui.status(_("(branch merge, don't forget to commit)\n"))
1988 elif failedmerge:
1989 self.ui.status(_("There are unresolved merges with"
1990 " locally modified files.\n"))
1991
1992 self.hook('update', parent1=xp1, parent2=xxp2, error=int(err))
1993 return err
1994
1995 def merge3(self, fn, my, other, p1, p2):
1996 """perform a 3-way merge in the working directory"""
1997
1998 def temp(prefix, node):
1999 pre = "%s~%s." % (os.path.basename(fn), prefix)
2000 (fd, name) = tempfile.mkstemp(prefix=pre)
2001 f = os.fdopen(fd, "wb")
2002 self.wwrite(fn, fl.read(node), f)
2003 f.close()
2004 return name
2005
2006 fl = self.file(fn)
2007 base = fl.ancestor(my, other)
2008 a = self.wjoin(fn)
2009 b = temp("base", base)
2010 c = temp("other", other)
2011
2012 self.ui.note(_("resolving %s\n") % fn)
2013 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
2014 (fn, short(my), short(other), short(base)))
2015
2016 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
2017 or "hgmerge")
2018 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
2019 environ={'HG_FILE': fn,
2020 'HG_MY_NODE': p1,
2021 'HG_OTHER_NODE': p2,
2022 'HG_FILE_MY_NODE': hex(my),
2023 'HG_FILE_OTHER_NODE': hex(other),
2024 'HG_FILE_BASE_NODE': hex(base)})
2025 if r:
2026 self.ui.warn(_("merging %s failed!\n") % fn)
2027
2028 os.unlink(b)
2029 os.unlink(c)
2030 return r
2031
2032 def verify(self):
1696 def verify(self):
2033 filelinkrevs = {}
1697 filelinkrevs = {}
2034 filenodes = {}
1698 filenodes = {}
General Comments 0
You need to be logged in to leave comments. Login now