##// 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 179 self.ui.warn("patch didn't work out, merging %s\n" % patch)
180 180
181 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 183 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
184 184
185 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 187 if ret:
188 188 raise util.Abort(_("update returned %d") % ret)
189 189 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
@@ -299,7 +299,7 b' class queue:'
299 299 self.ui.warn(l + '\n')
300 300
301 301 return (not f.close(), files, fuzz)
302
302
303 303 def apply(self, repo, series, list=False, update_status=True,
304 304 strict=False, patchdir=None, merge=None, wlock=None):
305 305 # TODO unify with commands.py
@@ -526,7 +526,7 b' class queue:'
526 526 if c or a or d or r:
527 527 raise util.Abort(_("local changes found"))
528 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 530 repo.dirstate.write()
531 531
532 532 # save is a list of all the branches we are truncating away
@@ -1023,7 +1023,7 b' class queue:'
1023 1023 if not r:
1024 1024 self.ui.warn("Unable to load queue repository\n")
1025 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 1028 def save(self, repo, msg=None):
1029 1029 if len(self.applied) == 0:
@@ -1245,7 +1245,7 b' def clone(ui, source, dest=None, **opts)'
1245 1245 dr.mq.strip(dr, qbase, update=False, backup=None)
1246 1246 if not opts['noupdate']:
1247 1247 ui.note(_('updating destination repo\n'))
1248 dr.update(dr.changelog.tip())
1248 hg.update(dr, dr.changelog.tip())
1249 1249
1250 1250 def commit(ui, repo, *pats, **opts):
1251 1251 """commit changes in the queue repository"""
@@ -921,7 +921,7 b' def backout(ui, repo, rev, **opts):'
921 921 if opts['parent']:
922 922 raise util.Abort(_('cannot use --parent on non-merge changeset'))
923 923 parent = p1
924 repo.update(node, force=True, show_stats=False)
924 hg.update(repo, node, force=True, show_stats=False)
925 925 revert_opts = opts.copy()
926 926 revert_opts['rev'] = hex(parent)
927 927 revert(ui, repo, **revert_opts)
@@ -2542,8 +2542,8 b' def revert(ui, repo, *pats, **opts):'
2542 2542
2543 2543 if not opts.get('dry_run'):
2544 2544 repo.dirstate.forget(forget[0])
2545 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2546 show_stats=False)
2545 r = hg.update(repo, node, False, True, update.has_key, False,
2546 wlock=wlock, show_stats=False)
2547 2547 repo.dirstate.update(add[0], 'a')
2548 2548 repo.dirstate.update(undelete[0], 'n')
2549 2549 repo.dirstate.update(remove[0], 'r')
@@ -2867,7 +2867,7 b' def doupdate(ui, repo, node=None, merge='
2867 2867 return 1
2868 2868 else:
2869 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 2872 def verify(ui, repo):
2873 2873 """verify the integrity of the repository
@@ -10,7 +10,7 b' from repo import *'
10 10 from demandload import *
11 11 from i18n import gettext as _
12 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 15 def _local(path):
16 16 return (os.path.isfile(path and util.drop_scheme('file', path)) and
@@ -38,7 +38,7 b' def _lookup(path):'
38 38 return thing(path)
39 39 except TypeError:
40 40 return thing
41
41
42 42 def islocal(repo):
43 43 '''return true if repo or path is local'''
44 44 if isinstance(repo, str):
@@ -200,8 +200,17 b' def clone(ui, source, dest=None, pull=Fa'
200 200 dest_lock.release()
201 201
202 202 if update:
203 dest_repo.update(dest_repo.changelog.tip())
203 merge.update(dest_repo, dest_repo.changelog.tip())
204 204 if dir_cleanup:
205 205 dir_cleanup.close()
206 206
207 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 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 1696 def verify(self):
2033 1697 filelinkrevs = {}
2034 1698 filenodes = {}
General Comments 0
You need to be logged in to leave comments. Login now