##// END OF EJS Templates
imported patch foo
Matt Mackall -
r3441:ef2e990f default
parent child Browse files
Show More
@@ -1,260 +1,256 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from node import *
9 from node import *
10 from repo import *
10 from repo import *
11 from demandload import *
11 from demandload import *
12 from i18n import gettext as _
12 from i18n import gettext as _
13 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
13 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
14 demandload(globals(), "errno lock os shutil util merge@_merge verify@_verify")
14 demandload(globals(), "errno lock os shutil util merge@_merge verify@_verify")
15
15
16 def _local(path):
16 def _local(path):
17 return (os.path.isfile(util.drop_scheme('file', path)) and
17 return (os.path.isfile(util.drop_scheme('file', path)) and
18 bundlerepo or localrepo)
18 bundlerepo or localrepo)
19
19
20 schemes = {
20 schemes = {
21 'bundle': bundlerepo,
21 'bundle': bundlerepo,
22 'file': _local,
22 'file': _local,
23 'hg': httprepo,
23 'hg': httprepo,
24 'http': httprepo,
24 'http': httprepo,
25 'https': httprepo,
25 'https': httprepo,
26 'old-http': statichttprepo,
26 'old-http': statichttprepo,
27 'ssh': sshrepo,
27 'ssh': sshrepo,
28 'static-http': statichttprepo,
28 'static-http': statichttprepo,
29 }
29 }
30
30
31 def _lookup(path):
31 def _lookup(path):
32 scheme = 'file'
32 scheme = 'file'
33 if path:
33 if path:
34 c = path.find(':')
34 c = path.find(':')
35 if c > 0:
35 if c > 0:
36 scheme = path[:c]
36 scheme = path[:c]
37 thing = schemes.get(scheme) or schemes['file']
37 thing = schemes.get(scheme) or schemes['file']
38 try:
38 try:
39 return thing(path)
39 return thing(path)
40 except TypeError:
40 except TypeError:
41 return thing
41 return thing
42
42
43 def islocal(repo):
43 def islocal(repo):
44 '''return true if repo or path is local'''
44 '''return true if repo or path is local'''
45 if isinstance(repo, str):
45 if isinstance(repo, str):
46 try:
46 try:
47 return _lookup(repo).islocal(repo)
47 return _lookup(repo).islocal(repo)
48 except AttributeError:
48 except AttributeError:
49 return False
49 return False
50 return repo.local()
50 return repo.local()
51
51
52 repo_setup_hooks = []
52 repo_setup_hooks = []
53
53
54 def repository(ui, path='', create=False):
54 def repository(ui, path='', create=False):
55 """return a repository object for the specified path"""
55 """return a repository object for the specified path"""
56 repo = _lookup(path).instance(ui, path, create)
56 repo = _lookup(path).instance(ui, path, create)
57 for hook in repo_setup_hooks:
57 for hook in repo_setup_hooks:
58 hook(ui, repo)
58 hook(ui, repo)
59 return repo
59 return repo
60
60
61 def defaultdest(source):
61 def defaultdest(source):
62 '''return default destination of clone if none is given'''
62 '''return default destination of clone if none is given'''
63 return os.path.basename(os.path.normpath(source))
63 return os.path.basename(os.path.normpath(source))
64
64
65 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
65 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
66 stream=False):
66 stream=False):
67 """Make a copy of an existing repository.
67 """Make a copy of an existing repository.
68
68
69 Create a copy of an existing repository in a new directory. The
69 Create a copy of an existing repository in a new directory. The
70 source and destination are URLs, as passed to the repository
70 source and destination are URLs, as passed to the repository
71 function. Returns a pair of repository objects, the source and
71 function. Returns a pair of repository objects, the source and
72 newly created destination.
72 newly created destination.
73
73
74 The location of the source is added to the new repository's
74 The location of the source is added to the new repository's
75 .hg/hgrc file, as the default to be used for future pulls and
75 .hg/hgrc file, as the default to be used for future pulls and
76 pushes.
76 pushes.
77
77
78 If an exception is raised, the partly cloned/updated destination
78 If an exception is raised, the partly cloned/updated destination
79 repository will be deleted.
79 repository will be deleted.
80
80
81 Arguments:
81 Arguments:
82
82
83 source: repository object or URL
83 source: repository object or URL
84
84
85 dest: URL of destination repository to create (defaults to base
85 dest: URL of destination repository to create (defaults to base
86 name of source repository)
86 name of source repository)
87
87
88 pull: always pull from source repository, even in local case
88 pull: always pull from source repository, even in local case
89
89
90 stream: stream raw data uncompressed from repository (fast over
90 stream: stream raw data uncompressed from repository (fast over
91 LAN, slow over WAN)
91 LAN, slow over WAN)
92
92
93 rev: revision to clone up to (implies pull=True)
93 rev: revision to clone up to (implies pull=True)
94
94
95 update: update working directory after clone completes, if
95 update: update working directory after clone completes, if
96 destination is local repository
96 destination is local repository
97 """
97 """
98 if isinstance(source, str):
98 if isinstance(source, str):
99 src_repo = repository(ui, source)
99 src_repo = repository(ui, source)
100 else:
100 else:
101 src_repo = source
101 src_repo = source
102 source = src_repo.url()
102 source = src_repo.url()
103
103
104 if dest is None:
104 if dest is None:
105 dest = defaultdest(source)
105 dest = defaultdest(source)
106
106
107 def localpath(path):
107 def localpath(path):
108 if path.startswith('file://'):
108 if path.startswith('file://'):
109 return path[7:]
109 return path[7:]
110 if path.startswith('file:'):
110 if path.startswith('file:'):
111 return path[5:]
111 return path[5:]
112 return path
112 return path
113
113
114 dest = localpath(dest)
114 dest = localpath(dest)
115 source = localpath(source)
115 source = localpath(source)
116
116
117 if os.path.exists(dest):
117 if os.path.exists(dest):
118 raise util.Abort(_("destination '%s' already exists") % dest)
118 raise util.Abort(_("destination '%s' already exists") % dest)
119
119
120 class DirCleanup(object):
120 class DirCleanup(object):
121 def __init__(self, dir_):
121 def __init__(self, dir_):
122 self.rmtree = shutil.rmtree
122 self.rmtree = shutil.rmtree
123 self.dir_ = dir_
123 self.dir_ = dir_
124 def close(self):
124 def close(self):
125 self.dir_ = None
125 self.dir_ = None
126 def __del__(self):
126 def __del__(self):
127 if self.dir_:
127 if self.dir_:
128 self.rmtree(self.dir_, True)
128 self.rmtree(self.dir_, True)
129
129
130 dest_repo = repository(ui, dest, create=True)
130 dest_repo = repository(ui, dest, create=True)
131
131
132 dest_path = None
132 dest_path = None
133 dir_cleanup = None
133 dir_cleanup = None
134 if dest_repo.local():
134 if dest_repo.local():
135 dest_path = os.path.realpath(dest_repo.root)
135 dest_path = os.path.realpath(dest_repo.root)
136 dir_cleanup = DirCleanup(dest_path)
136 dir_cleanup = DirCleanup(dest_path)
137
137
138 abspath = source
138 abspath = source
139 copy = False
139 copy = False
140 if src_repo.local() and dest_repo.local():
140 if src_repo.local() and dest_repo.local():
141 abspath = os.path.abspath(source)
141 abspath = os.path.abspath(source)
142 copy = not pull and not rev
142 copy = not pull and not rev
143
143
144 src_lock, dest_lock = None, None
144 src_lock, dest_lock = None, None
145 if copy:
145 if copy:
146 try:
146 try:
147 # we use a lock here because if we race with commit, we
147 # we use a lock here because if we race with commit, we
148 # can end up with extra data in the cloned revlogs that's
148 # can end up with extra data in the cloned revlogs that's
149 # not pointed to by changesets, thus causing verify to
149 # not pointed to by changesets, thus causing verify to
150 # fail
150 # fail
151 src_lock = src_repo.lock()
151 src_lock = src_repo.lock()
152 except lock.LockException:
152 except lock.LockException:
153 copy = False
153 copy = False
154
154
155 if copy:
155 if copy:
156 # we lock here to avoid premature writing to the target
156 # we lock here to avoid premature writing to the target
157 dest_lock = lock.lock(os.path.join(dest_path, ".hg", "lock"))
157 dest_lock = lock.lock(os.path.join(dest_path, ".hg", "lock"))
158
158
159 # we need to remove the (empty) data dir in dest so copyfiles
159 # we need to remove the (empty) data dir in dest so copyfiles
160 # can do its work
160 # can do its work
161 os.rmdir(os.path.join(dest_path, ".hg", "data"))
161 os.rmdir(os.path.join(dest_path, ".hg", "data"))
162 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
162 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
163 for f in files.split():
163 for f in files.split():
164 src = os.path.join(source, ".hg", f)
164 src = os.path.join(source, ".hg", f)
165 dst = os.path.join(dest_path, ".hg", f)
165 dst = os.path.join(dest_path, ".hg", f)
166 try:
166 try:
167 util.copyfiles(src, dst)
167 util.copyfiles(src, dst)
168 except OSError, inst:
168 except OSError, inst:
169 if inst.errno != errno.ENOENT:
169 if inst.errno != errno.ENOENT:
170 raise
170 raise
171
171
172 # we need to re-init the repo after manually copying the data
172 # we need to re-init the repo after manually copying the data
173 # into it
173 # into it
174 dest_repo = repository(ui, dest)
174 dest_repo = repository(ui, dest)
175
175
176 else:
176 else:
177 revs = None
177 revs = None
178 if rev:
178 if rev:
179 if not src_repo.local():
179 if not src_repo.local():
180 raise util.Abort(_("clone by revision not supported yet "
180 raise util.Abort(_("clone by revision not supported yet "
181 "for remote repositories"))
181 "for remote repositories"))
182 revs = [src_repo.lookup(r) for r in rev]
182 revs = [src_repo.lookup(r) for r in rev]
183
183
184 if dest_repo.local():
184 if dest_repo.local():
185 dest_repo.clone(src_repo, heads=revs, stream=stream)
185 dest_repo.clone(src_repo, heads=revs, stream=stream)
186 elif src_repo.local():
186 elif src_repo.local():
187 src_repo.push(dest_repo, revs=revs)
187 src_repo.push(dest_repo, revs=revs)
188 else:
188 else:
189 raise util.Abort(_("clone from remote to remote not supported"))
189 raise util.Abort(_("clone from remote to remote not supported"))
190
190
191 if src_lock:
191 if src_lock:
192 src_lock.release()
192 src_lock.release()
193
193
194 if dest_repo.local():
194 if dest_repo.local():
195 fp = dest_repo.opener("hgrc", "w", text=True)
195 fp = dest_repo.opener("hgrc", "w", text=True)
196 fp.write("[paths]\n")
196 fp.write("[paths]\n")
197 fp.write("default = %s\n" % abspath)
197 fp.write("default = %s\n" % abspath)
198 fp.close()
198 fp.close()
199
199
200 if dest_lock:
200 if dest_lock:
201 dest_lock.release()
201 dest_lock.release()
202
202
203 if update:
203 if update:
204 _update(dest_repo, dest_repo.changelog.tip())
204 _update(dest_repo, dest_repo.changelog.tip())
205 if dir_cleanup:
205 if dir_cleanup:
206 dir_cleanup.close()
206 dir_cleanup.close()
207
207
208 return src_repo, dest_repo
208 return src_repo, dest_repo
209
209
210 def _showstats(repo, stats):
210 def _showstats(repo, stats):
211 stats = ((stats[0], _("updated")),
211 stats = ((stats[0], _("updated")),
212 (stats[1], _("merged")),
212 (stats[1], _("merged")),
213 (stats[2], _("removed")),
213 (stats[2], _("removed")),
214 (stats[3], _("unresolved")))
214 (stats[3], _("unresolved")))
215 note = ", ".join([_("%d files %s") % s for s in stats])
215 note = ", ".join([_("%d files %s") % s for s in stats])
216 repo.ui.status("%s\n" % note)
216 repo.ui.status("%s\n" % note)
217
217
218 def _update(repo, node): return update(repo, node)
218 def _update(repo, node): return update(repo, node)
219
219
220 def update(repo, node):
220 def update(repo, node):
221 """update the working directory to node, merging linear changes"""
221 """update the working directory to node, merging linear changes"""
222 stats = _merge.update(repo, node, False, False, None, None)
222 stats = _merge.update(repo, node, False, False, None, None)
223 _showstats(repo, stats)
223 _showstats(repo, stats)
224 branch = repo.changectx(node).branch()
225 repo.opener("branch", "w").write(branch + "\n")
226 if stats[3]:
224 if stats[3]:
227 repo.ui.status(_("There are unresolved merges with"
225 repo.ui.status(_("There are unresolved merges with"
228 " locally modified files.\n"))
226 " locally modified files.\n"))
229 return stats[3]
227 return stats[3]
230
228
231 def clean(repo, node, wlock=None, show_stats=True):
229 def clean(repo, node, wlock=None, show_stats=True):
232 """forcibly switch the working directory to node, clobbering changes"""
230 """forcibly switch the working directory to node, clobbering changes"""
233 stats = _merge.update(repo, node, False, True, None, wlock)
231 stats = _merge.update(repo, node, False, True, None, wlock)
234 branch = repo.changectx(node).branch()
235 repo.opener("branch", "w").write(branch + "\n")
236 if show_stats: _showstats(repo, stats)
232 if show_stats: _showstats(repo, stats)
237 return stats[3]
233 return stats[3]
238
234
239 def merge(repo, node, force=None, remind=True, wlock=None):
235 def merge(repo, node, force=None, remind=True, wlock=None):
240 """branch merge with node, resolving changes"""
236 """branch merge with node, resolving changes"""
241 stats = _merge.update(repo, node, True, force, False, wlock)
237 stats = _merge.update(repo, node, True, force, False, wlock)
242 _showstats(repo, stats)
238 _showstats(repo, stats)
243 if stats[3]:
239 if stats[3]:
244 pl = repo.parents()
240 pl = repo.parents()
245 repo.ui.status(_("There are unresolved merges,"
241 repo.ui.status(_("There are unresolved merges,"
246 " you can redo the full merge using:\n"
242 " you can redo the full merge using:\n"
247 " hg update -C %s\n"
243 " hg update -C %s\n"
248 " hg merge %s\n"
244 " hg merge %s\n"
249 % (pl[0].rev(), pl[1].rev())))
245 % (pl[0].rev(), pl[1].rev())))
250 elif remind:
246 elif remind:
251 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
247 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
252 return stats[3]
248 return stats[3]
253
249
254 def revert(repo, node, choose, wlock):
250 def revert(repo, node, choose, wlock):
255 """revert changes to revision in node without updating dirstate"""
251 """revert changes to revision in node without updating dirstate"""
256 return _merge.update(repo, node, False, True, choose, wlock)[3]
252 return _merge.update(repo, node, False, True, choose, wlock)[3]
257
253
258 def verify(repo):
254 def verify(repo):
259 """verify the consistency of a repository"""
255 """verify the consistency of a repository"""
260 return _verify.verify(repo)
256 return _verify.verify(repo)
@@ -1,416 +1,419 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 filemerge(repo, fw, fo, wctx, mctx):
13 def filemerge(repo, fw, fo, wctx, mctx):
14 """perform a 3-way merge in the working directory
14 """perform a 3-way merge in the working directory
15
15
16 fw = filename in the working directory
16 fw = filename in the working directory
17 fo = filename in other parent
17 fo = filename in other parent
18 wctx, mctx = working and merge changecontexts
18 wctx, mctx = working and merge changecontexts
19 """
19 """
20
20
21 def temp(prefix, ctx):
21 def temp(prefix, ctx):
22 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
22 pre = "%s~%s." % (os.path.basename(ctx.path()), 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(ctx.path(), ctx.data(), f)
25 repo.wwrite(ctx.path(), ctx.data(), f)
26 f.close()
26 f.close()
27 return name
27 return name
28
28
29 fcm = wctx.filectx(fw)
29 fcm = wctx.filectx(fw)
30 fco = mctx.filectx(fo)
30 fco = mctx.filectx(fo)
31
31
32 if not fco.cmp(fcm.data()): # files identical?
32 if not fco.cmp(fcm.data()): # files identical?
33 return None
33 return None
34
34
35 fca = fcm.ancestor(fco)
35 fca = fcm.ancestor(fco)
36 if not fca:
36 if not fca:
37 fca = repo.filectx(fw, fileid=-1)
37 fca = repo.filectx(fw, fileid=-1)
38 a = repo.wjoin(fw)
38 a = repo.wjoin(fw)
39 b = temp("base", fca)
39 b = temp("base", fca)
40 c = temp("other", fco)
40 c = temp("other", fco)
41
41
42 if fw != fo:
42 if fw != fo:
43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
44 else:
44 else:
45 repo.ui.status(_("merging %s\n") % fw)
45 repo.ui.status(_("merging %s\n") % fw)
46
46
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
48
48
49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
50 or "hgmerge")
50 or "hgmerge")
51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
52 environ={'HG_FILE': fw,
52 environ={'HG_FILE': fw,
53 'HG_MY_NODE': str(wctx.parents()[0]),
53 'HG_MY_NODE': str(wctx.parents()[0]),
54 'HG_OTHER_NODE': str(mctx)})
54 'HG_OTHER_NODE': str(mctx)})
55 if r:
55 if r:
56 repo.ui.warn(_("merging %s failed!\n") % fw)
56 repo.ui.warn(_("merging %s failed!\n") % fw)
57
57
58 os.unlink(b)
58 os.unlink(b)
59 os.unlink(c)
59 os.unlink(c)
60 return r
60 return r
61
61
62 def checkunknown(wctx, mctx):
62 def checkunknown(wctx, mctx):
63 "check for collisions between unknown files and files in mctx"
63 "check for collisions between unknown files and files in mctx"
64 man = mctx.manifest()
64 man = mctx.manifest()
65 for f in wctx.unknown():
65 for f in wctx.unknown():
66 if f in man:
66 if f in man:
67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
68 raise util.Abort(_("'%s' already exists in the working"
68 raise util.Abort(_("'%s' already exists in the working"
69 " dir and differs from remote") % f)
69 " dir and differs from remote") % f)
70
70
71 def forgetremoved(wctx, mctx):
71 def forgetremoved(wctx, mctx):
72 """
72 """
73 Forget removed files
73 Forget removed files
74
74
75 If we're jumping between revisions (as opposed to merging), and if
75 If we're jumping between revisions (as opposed to merging), and if
76 neither the working directory nor the target rev has the file,
76 neither the working directory nor the target rev has the file,
77 then we need to remove it from the dirstate, to prevent the
77 then we need to remove it from the dirstate, to prevent the
78 dirstate from listing the file when it is no longer in the
78 dirstate from listing the file when it is no longer in the
79 manifest.
79 manifest.
80 """
80 """
81
81
82 action = []
82 action = []
83 man = mctx.manifest()
83 man = mctx.manifest()
84 for f in wctx.deleted() + wctx.removed():
84 for f in wctx.deleted() + wctx.removed():
85 if f not in man:
85 if f not in man:
86 action.append((f, "f"))
86 action.append((f, "f"))
87
87
88 return action
88 return action
89
89
90 def nonoverlap(d1, d2, d3):
90 def nonoverlap(d1, d2, d3):
91 "Return list of elements in d1 not in d2 or d3"
91 "Return list of elements in d1 not in d2 or d3"
92
92
93 l = []
93 l = []
94 for d in d1:
94 for d in d1:
95 if d not in d3 and d not in d2:
95 if d not in d3 and d not in d2:
96 l.append(d)
96 l.append(d)
97
97
98 l.sort()
98 l.sort()
99 return l
99 return l
100
100
101 def findold(fctx, limit):
101 def findold(fctx, limit):
102 "find files that path was copied from, back to linkrev limit"
102 "find files that path was copied from, back to linkrev limit"
103
103
104 old = {}
104 old = {}
105 orig = fctx.path()
105 orig = fctx.path()
106 visit = [fctx]
106 visit = [fctx]
107 while visit:
107 while visit:
108 fc = visit.pop()
108 fc = visit.pop()
109 if fc.rev() < limit:
109 if fc.rev() < limit:
110 continue
110 continue
111 if fc.path() != orig and fc.path() not in old:
111 if fc.path() != orig and fc.path() not in old:
112 old[fc.path()] = 1
112 old[fc.path()] = 1
113 visit += fc.parents()
113 visit += fc.parents()
114
114
115 old = old.keys()
115 old = old.keys()
116 old.sort()
116 old.sort()
117 return old
117 return old
118
118
119 def findcopies(repo, m1, m2, ma, limit):
119 def findcopies(repo, m1, m2, ma, limit):
120 """
120 """
121 Find moves and copies between m1 and m2 back to limit linkrev
121 Find moves and copies between m1 and m2 back to limit linkrev
122 """
122 """
123
123
124 if not repo.ui.configbool("merge", "followcopies", True):
124 if not repo.ui.configbool("merge", "followcopies", True):
125 return {}
125 return {}
126
126
127 # avoid silly behavior for update from empty dir
127 # avoid silly behavior for update from empty dir
128 if not m1:
128 if not m1:
129 return {}
129 return {}
130
130
131 dcopies = repo.dirstate.copies()
131 dcopies = repo.dirstate.copies()
132 copy = {}
132 copy = {}
133 match = {}
133 match = {}
134 u1 = nonoverlap(m1, m2, ma)
134 u1 = nonoverlap(m1, m2, ma)
135 u2 = nonoverlap(m2, m1, ma)
135 u2 = nonoverlap(m2, m1, ma)
136 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
136 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
137
137
138 def checkpair(c, f2, man):
138 def checkpair(c, f2, man):
139 ''' check if an apparent pair actually matches '''
139 ''' check if an apparent pair actually matches '''
140 c2 = ctx(f2, man[f2])
140 c2 = ctx(f2, man[f2])
141 ca = c.ancestor(c2)
141 ca = c.ancestor(c2)
142 if ca and ca.path() == c.path() or ca.path() == c2.path():
142 if ca and ca.path() == c.path() or ca.path() == c2.path():
143 copy[c.path()] = f2
143 copy[c.path()] = f2
144 copy[f2] = c.path()
144 copy[f2] = c.path()
145
145
146 for f in u1:
146 for f in u1:
147 c = ctx(dcopies.get(f, f), m1[f])
147 c = ctx(dcopies.get(f, f), m1[f])
148 for of in findold(c, limit):
148 for of in findold(c, limit):
149 if of in m2:
149 if of in m2:
150 checkpair(c, of, m2)
150 checkpair(c, of, m2)
151 else:
151 else:
152 match.setdefault(of, []).append(f)
152 match.setdefault(of, []).append(f)
153
153
154 for f in u2:
154 for f in u2:
155 c = ctx(f, m2[f])
155 c = ctx(f, m2[f])
156 for of in findold(c, limit):
156 for of in findold(c, limit):
157 if of in m1:
157 if of in m1:
158 checkpair(c, of, m1)
158 checkpair(c, of, m1)
159 elif of in match:
159 elif of in match:
160 for mf in match[of]:
160 for mf in match[of]:
161 checkpair(c, mf, m1)
161 checkpair(c, mf, m1)
162
162
163 return copy
163 return copy
164
164
165 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
165 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
166 """
166 """
167 Merge p1 and p2 with ancestor ma and generate merge action list
167 Merge p1 and p2 with ancestor ma and generate merge action list
168
168
169 overwrite = whether we clobber working files
169 overwrite = whether we clobber working files
170 partial = function to filter file lists
170 partial = function to filter file lists
171 """
171 """
172
172
173 repo.ui.note(_("resolving manifests\n"))
173 repo.ui.note(_("resolving manifests\n"))
174 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
174 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
175 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
175 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
176
176
177 m1 = p1.manifest()
177 m1 = p1.manifest()
178 m2 = p2.manifest()
178 m2 = p2.manifest()
179 ma = pa.manifest()
179 ma = pa.manifest()
180 backwards = (pa == p2)
180 backwards = (pa == p2)
181 action = []
181 action = []
182 copy = {}
182 copy = {}
183
183
184 def fmerge(f, f2=None, fa=None):
184 def fmerge(f, f2=None, fa=None):
185 """merge executable flags"""
185 """merge executable flags"""
186 if not f2:
186 if not f2:
187 f2 = f
187 f2 = f
188 fa = f
188 fa = f
189 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
189 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
190 return ((a^b) | (a^c)) ^ a
190 return ((a^b) | (a^c)) ^ a
191
191
192 def act(msg, m, f, *args):
192 def act(msg, m, f, *args):
193 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
193 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
194 action.append((f, m) + args)
194 action.append((f, m) + args)
195
195
196 if not (backwards or overwrite):
196 if not (backwards or overwrite):
197 copy = findcopies(repo, m1, m2, ma, pa.rev())
197 copy = findcopies(repo, m1, m2, ma, pa.rev())
198
198
199 # Compare manifests
199 # Compare manifests
200 for f, n in m1.iteritems():
200 for f, n in m1.iteritems():
201 if partial and not partial(f):
201 if partial and not partial(f):
202 continue
202 continue
203 if f in m2:
203 if f in m2:
204 # are files different?
204 # are files different?
205 if n != m2[f]:
205 if n != m2[f]:
206 a = ma.get(f, nullid)
206 a = ma.get(f, nullid)
207 # are both different from the ancestor?
207 # are both different from the ancestor?
208 if not overwrite and n != a and m2[f] != a:
208 if not overwrite and n != a and m2[f] != a:
209 act("versions differ", "m", f, f, f, fmerge(f), False)
209 act("versions differ", "m", f, f, f, fmerge(f), False)
210 # are we clobbering?
210 # are we clobbering?
211 # is remote's version newer?
211 # is remote's version newer?
212 # or are we going back in time and clean?
212 # or are we going back in time and clean?
213 elif overwrite or m2[f] != a or (backwards and not n[20:]):
213 elif overwrite or m2[f] != a or (backwards and not n[20:]):
214 act("remote is newer", "g", f, m2.execf(f))
214 act("remote is newer", "g", f, m2.execf(f))
215 # local is newer, not overwrite, check mode bits
215 # local is newer, not overwrite, check mode bits
216 elif fmerge(f) != m1.execf(f):
216 elif fmerge(f) != m1.execf(f):
217 act("update permissions", "e", f, m2.execf(f))
217 act("update permissions", "e", f, m2.execf(f))
218 # contents same, check mode bits
218 # contents same, check mode bits
219 elif m1.execf(f) != m2.execf(f):
219 elif m1.execf(f) != m2.execf(f):
220 if overwrite or fmerge(f) != m1.execf(f):
220 if overwrite or fmerge(f) != m1.execf(f):
221 act("update permissions", "e", f, m2.execf(f))
221 act("update permissions", "e", f, m2.execf(f))
222 elif f in copy:
222 elif f in copy:
223 f2 = copy[f]
223 f2 = copy[f]
224 if f in ma: # case 3,20 A/B/A
224 if f in ma: # case 3,20 A/B/A
225 act("remote moved", "m", f, f2, f2, fmerge(f, f2, f), True)
225 act("remote moved", "m", f, f2, f2, fmerge(f, f2, f), True)
226 else:
226 else:
227 if f2 in m1: # case 2 A,B/B/B
227 if f2 in m1: # case 2 A,B/B/B
228 act("local copied", "m",
228 act("local copied", "m",
229 f, f2, f, fmerge(f, f2, f2), False)
229 f, f2, f, fmerge(f, f2, f2), False)
230 else: # case 4,21 A/B/B
230 else: # case 4,21 A/B/B
231 act("local moved", "m",
231 act("local moved", "m",
232 f, f2, f, fmerge(f, f2, f2), False)
232 f, f2, f, fmerge(f, f2, f2), False)
233 elif f in ma:
233 elif f in ma:
234 if n != ma[f] and not overwrite:
234 if n != ma[f] and not overwrite:
235 if repo.ui.prompt(
235 if repo.ui.prompt(
236 (_(" local changed %s which remote deleted\n") % f) +
236 (_(" local changed %s which remote deleted\n") % f) +
237 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
237 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
238 act("prompt delete", "r", f)
238 act("prompt delete", "r", f)
239 else:
239 else:
240 act("other deleted", "r", f)
240 act("other deleted", "r", f)
241 else:
241 else:
242 # file is created on branch or in working directory
242 # file is created on branch or in working directory
243 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
243 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
244 act("remote deleted", "r", f)
244 act("remote deleted", "r", f)
245
245
246 for f, n in m2.iteritems():
246 for f, n in m2.iteritems():
247 if partial and not partial(f):
247 if partial and not partial(f):
248 continue
248 continue
249 if f in m1:
249 if f in m1:
250 continue
250 continue
251 if f in copy:
251 if f in copy:
252 f2 = copy[f]
252 f2 = copy[f]
253 if f2 not in m2: # already seen
253 if f2 not in m2: # already seen
254 continue
254 continue
255 # rename case 1, A/A,B/A
255 # rename case 1, A/A,B/A
256 act("remote copied", "m", f2, f, f, fmerge(f2, f, f2), False)
256 act("remote copied", "m", f2, f, f, fmerge(f2, f, f2), False)
257 elif f in ma:
257 elif f in ma:
258 if overwrite or backwards:
258 if overwrite or backwards:
259 act("recreating", "g", f, m2.execf(f))
259 act("recreating", "g", f, m2.execf(f))
260 elif n != ma[f]:
260 elif n != ma[f]:
261 if repo.ui.prompt(
261 if repo.ui.prompt(
262 (_("remote changed %s which local deleted\n") % f) +
262 (_("remote changed %s which local deleted\n") % f) +
263 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
263 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
264 act("prompt recreating", "g", f, m2.execf(f))
264 act("prompt recreating", "g", f, m2.execf(f))
265 else:
265 else:
266 act("remote created", "g", f, m2.execf(f))
266 act("remote created", "g", f, m2.execf(f))
267
267
268 return action
268 return action
269
269
270 def applyupdates(repo, action, wctx, mctx):
270 def applyupdates(repo, action, wctx, mctx):
271 "apply the merge action list to the working directory"
271 "apply the merge action list to the working directory"
272
272
273 updated, merged, removed, unresolved = 0, 0, 0, 0
273 updated, merged, removed, unresolved = 0, 0, 0, 0
274 action.sort()
274 action.sort()
275 for a in action:
275 for a in action:
276 f, m = a[:2]
276 f, m = a[:2]
277 if f[0] == "/":
277 if f[0] == "/":
278 continue
278 continue
279 if m == "r": # remove
279 if m == "r": # remove
280 repo.ui.note(_("removing %s\n") % f)
280 repo.ui.note(_("removing %s\n") % f)
281 util.audit_path(f)
281 util.audit_path(f)
282 try:
282 try:
283 util.unlink(repo.wjoin(f))
283 util.unlink(repo.wjoin(f))
284 except OSError, inst:
284 except OSError, inst:
285 if inst.errno != errno.ENOENT:
285 if inst.errno != errno.ENOENT:
286 repo.ui.warn(_("update failed to remove %s: %s!\n") %
286 repo.ui.warn(_("update failed to remove %s: %s!\n") %
287 (f, inst.strerror))
287 (f, inst.strerror))
288 removed +=1
288 removed +=1
289 elif m == "m": # merge
289 elif m == "m": # merge
290 f2, fd, flag, move = a[2:]
290 f2, fd, flag, move = a[2:]
291 r = filemerge(repo, f, f2, wctx, mctx)
291 r = filemerge(repo, f, f2, wctx, mctx)
292 if r > 0:
292 if r > 0:
293 unresolved += 1
293 unresolved += 1
294 else:
294 else:
295 if r is None:
295 if r is None:
296 updated += 1
296 updated += 1
297 else:
297 else:
298 merged += 1
298 merged += 1
299 if f != fd:
299 if f != fd:
300 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
300 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
301 repo.wwrite(fd, repo.wread(f))
301 repo.wwrite(fd, repo.wread(f))
302 if move:
302 if move:
303 repo.ui.debug(_("removing %s\n") % f)
303 repo.ui.debug(_("removing %s\n") % f)
304 os.unlink(repo.wjoin(f))
304 os.unlink(repo.wjoin(f))
305 util.set_exec(repo.wjoin(fd), flag)
305 util.set_exec(repo.wjoin(fd), flag)
306 elif m == "g": # get
306 elif m == "g": # get
307 flag = a[2]
307 flag = a[2]
308 repo.ui.note(_("getting %s\n") % f)
308 repo.ui.note(_("getting %s\n") % f)
309 t = mctx.filectx(f).data()
309 t = mctx.filectx(f).data()
310 repo.wwrite(f, t)
310 repo.wwrite(f, t)
311 util.set_exec(repo.wjoin(f), flag)
311 util.set_exec(repo.wjoin(f), flag)
312 updated += 1
312 updated += 1
313 elif m == "e": # exec
313 elif m == "e": # exec
314 flag = a[2]
314 flag = a[2]
315 util.set_exec(repo.wjoin(f), flag)
315 util.set_exec(repo.wjoin(f), flag)
316
316
317 return updated, merged, removed, unresolved
317 return updated, merged, removed, unresolved
318
318
319 def recordupdates(repo, action, branchmerge):
319 def recordupdates(repo, action, branchmerge):
320 "record merge actions to the dirstate"
320 "record merge actions to the dirstate"
321
321
322 for a in action:
322 for a in action:
323 f, m = a[:2]
323 f, m = a[:2]
324 if m == "r": # remove
324 if m == "r": # remove
325 if branchmerge:
325 if branchmerge:
326 repo.dirstate.update([f], 'r')
326 repo.dirstate.update([f], 'r')
327 else:
327 else:
328 repo.dirstate.forget([f])
328 repo.dirstate.forget([f])
329 elif m == "f": # forget
329 elif m == "f": # forget
330 repo.dirstate.forget([f])
330 repo.dirstate.forget([f])
331 elif m == "g": # get
331 elif m == "g": # get
332 if branchmerge:
332 if branchmerge:
333 repo.dirstate.update([f], 'n', st_mtime=-1)
333 repo.dirstate.update([f], 'n', st_mtime=-1)
334 else:
334 else:
335 repo.dirstate.update([f], 'n')
335 repo.dirstate.update([f], 'n')
336 elif m == "m": # merge
336 elif m == "m": # merge
337 f2, fd, flag, move = a[2:]
337 f2, fd, flag, move = a[2:]
338 if branchmerge:
338 if branchmerge:
339 # We've done a branch merge, mark this file as merged
339 # We've done a branch merge, mark this file as merged
340 # so that we properly record the merger later
340 # so that we properly record the merger later
341 repo.dirstate.update([fd], 'm')
341 repo.dirstate.update([fd], 'm')
342 if f != f2: # copy/rename
342 if f != f2: # copy/rename
343 if move:
343 if move:
344 repo.dirstate.update([f], 'r')
344 repo.dirstate.update([f], 'r')
345 if f != fd:
345 if f != fd:
346 repo.dirstate.copy(f, fd)
346 repo.dirstate.copy(f, fd)
347 else:
347 else:
348 repo.dirstate.copy(f2, fd)
348 repo.dirstate.copy(f2, fd)
349 else:
349 else:
350 # We've update-merged a locally modified file, so
350 # We've update-merged a locally modified file, so
351 # we set the dirstate to emulate a normal checkout
351 # we set the dirstate to emulate a normal checkout
352 # of that file some time in the past. Thus our
352 # of that file some time in the past. Thus our
353 # merge will appear as a normal local file
353 # merge will appear as a normal local file
354 # modification.
354 # modification.
355 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
355 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
356 if move:
356 if move:
357 repo.dirstate.forget([f])
357 repo.dirstate.forget([f])
358
358
359 def update(repo, node, branchmerge, force, partial, wlock):
359 def update(repo, node, branchmerge, force, partial, wlock):
360 """
360 """
361 Perform a merge between the working directory and the given node
361 Perform a merge between the working directory and the given node
362
362
363 branchmerge = whether to merge between branches
363 branchmerge = whether to merge between branches
364 force = whether to force branch merging or file overwriting
364 force = whether to force branch merging or file overwriting
365 partial = a function to filter file lists (dirstate not updated)
365 partial = a function to filter file lists (dirstate not updated)
366 wlock = working dir lock, if already held
366 wlock = working dir lock, if already held
367 """
367 """
368
368
369 if not wlock:
369 if not wlock:
370 wlock = repo.wlock()
370 wlock = repo.wlock()
371
371
372 overwrite = force and not branchmerge
372 overwrite = force and not branchmerge
373 forcemerge = force and branchmerge
373 forcemerge = force and branchmerge
374 wc = repo.workingctx()
374 wc = repo.workingctx()
375 pl = wc.parents()
375 pl = wc.parents()
376 p1, p2 = pl[0], repo.changectx(node)
376 p1, p2 = pl[0], repo.changectx(node)
377 pa = p1.ancestor(p2)
377 pa = p1.ancestor(p2)
378 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
378 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
379
379
380 ### check phase
380 ### check phase
381 if not overwrite and len(pl) > 1:
381 if not overwrite and len(pl) > 1:
382 raise util.Abort(_("outstanding uncommitted merges"))
382 raise util.Abort(_("outstanding uncommitted merges"))
383 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
383 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
384 if branchmerge:
384 if branchmerge:
385 raise util.Abort(_("there is nothing to merge, just use "
385 raise util.Abort(_("there is nothing to merge, just use "
386 "'hg update' or look at 'hg heads'"))
386 "'hg update' or look at 'hg heads'"))
387 elif not (overwrite or branchmerge):
387 elif not (overwrite or branchmerge):
388 raise util.Abort(_("update spans branches, use 'hg merge' "
388 raise util.Abort(_("update spans branches, use 'hg merge' "
389 "or 'hg update -C' to lose changes"))
389 "or 'hg update -C' to lose changes"))
390 if branchmerge and not forcemerge:
390 if branchmerge and not forcemerge:
391 if wc.modified() or wc.added() or wc.removed():
391 if wc.modified() or wc.added() or wc.removed():
392 raise util.Abort(_("outstanding uncommitted changes"))
392 raise util.Abort(_("outstanding uncommitted changes"))
393
393
394 ### calculate phase
394 ### calculate phase
395 action = []
395 action = []
396 if not force:
396 if not force:
397 checkunknown(wc, p2)
397 checkunknown(wc, p2)
398 if not branchmerge:
398 if not branchmerge:
399 action += forgetremoved(wc, p2)
399 action += forgetremoved(wc, p2)
400 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
400 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
401
401
402 ### apply phase
402 ### apply phase
403 if not branchmerge: # just jump to the new rev
403 if not branchmerge: # just jump to the new rev
404 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
404 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
405 if not partial:
405 if not partial:
406 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
406 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
407
407
408 stats = applyupdates(repo, action, wc, p2)
408 stats = applyupdates(repo, action, wc, p2)
409
409
410 if not partial:
410 if not partial:
411 recordupdates(repo, action, branchmerge)
411 recordupdates(repo, action, branchmerge)
412 repo.dirstate.setparents(fp1, fp2)
412 repo.dirstate.setparents(fp1, fp2)
413 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
413 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
414 if not branchmerge:
415 print "yow!"
416 repo.opener("branch", "w").write(p1.branch() + "\n")
414
417
415 return stats
418 return stats
416
419
General Comments 0
You need to be logged in to leave comments. Login now