##// END OF EJS Templates
Add branchtags function with cache...
Matt Mackall -
r3417:028fff46 default
parent child Browse files
Show More
@@ -1,1763 +1,1802 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 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 import repo
11 import repo
12 demandload(globals(), "appendfile changegroup")
12 demandload(globals(), "appendfile changegroup")
13 demandload(globals(), "changelog dirstate filelog manifest context")
13 demandload(globals(), "changelog dirstate filelog manifest context")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
15 demandload(globals(), "os revlog time util")
15 demandload(globals(), "os revlog time util")
16
16
17 class localrepository(repo.repository):
17 class localrepository(repo.repository):
18 capabilities = ()
18 capabilities = ()
19
19
20 def __del__(self):
20 def __del__(self):
21 self.transhandle = None
21 self.transhandle = None
22 def __init__(self, parentui, path=None, create=0):
22 def __init__(self, parentui, path=None, create=0):
23 repo.repository.__init__(self)
23 repo.repository.__init__(self)
24 if not path:
24 if not path:
25 p = os.getcwd()
25 p = os.getcwd()
26 while not os.path.isdir(os.path.join(p, ".hg")):
26 while not os.path.isdir(os.path.join(p, ".hg")):
27 oldp = p
27 oldp = p
28 p = os.path.dirname(p)
28 p = os.path.dirname(p)
29 if p == oldp:
29 if p == oldp:
30 raise repo.RepoError(_("There is no Mercurial repository"
30 raise repo.RepoError(_("There is no Mercurial repository"
31 " here (.hg not found)"))
31 " here (.hg not found)"))
32 path = p
32 path = p
33 self.path = os.path.join(path, ".hg")
33 self.path = os.path.join(path, ".hg")
34
34
35 if not os.path.isdir(self.path):
35 if not os.path.isdir(self.path):
36 if create:
36 if create:
37 if not os.path.exists(path):
37 if not os.path.exists(path):
38 os.mkdir(path)
38 os.mkdir(path)
39 os.mkdir(self.path)
39 os.mkdir(self.path)
40 os.mkdir(self.join("data"))
40 os.mkdir(self.join("data"))
41 else:
41 else:
42 raise repo.RepoError(_("repository %s not found") % path)
42 raise repo.RepoError(_("repository %s not found") % path)
43 elif create:
43 elif create:
44 raise repo.RepoError(_("repository %s already exists") % path)
44 raise repo.RepoError(_("repository %s already exists") % path)
45
45
46 self.root = os.path.abspath(path)
46 self.root = os.path.abspath(path)
47 self.origroot = path
47 self.origroot = path
48 self.ui = ui.ui(parentui=parentui)
48 self.ui = ui.ui(parentui=parentui)
49 self.opener = util.opener(self.path)
49 self.opener = util.opener(self.path)
50 self.wopener = util.opener(self.root)
50 self.wopener = util.opener(self.root)
51
51
52 try:
52 try:
53 self.ui.readconfig(self.join("hgrc"), self.root)
53 self.ui.readconfig(self.join("hgrc"), self.root)
54 except IOError:
54 except IOError:
55 pass
55 pass
56
56
57 v = self.ui.configrevlog()
57 v = self.ui.configrevlog()
58 self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
58 self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
59 self.revlogv1 = self.revlogversion != revlog.REVLOGV0
59 self.revlogv1 = self.revlogversion != revlog.REVLOGV0
60 fl = v.get('flags', None)
60 fl = v.get('flags', None)
61 flags = 0
61 flags = 0
62 if fl != None:
62 if fl != None:
63 for x in fl.split():
63 for x in fl.split():
64 flags |= revlog.flagstr(x)
64 flags |= revlog.flagstr(x)
65 elif self.revlogv1:
65 elif self.revlogv1:
66 flags = revlog.REVLOG_DEFAULT_FLAGS
66 flags = revlog.REVLOG_DEFAULT_FLAGS
67
67
68 v = self.revlogversion | flags
68 v = self.revlogversion | flags
69 self.manifest = manifest.manifest(self.opener, v)
69 self.manifest = manifest.manifest(self.opener, v)
70 self.changelog = changelog.changelog(self.opener, v)
70 self.changelog = changelog.changelog(self.opener, v)
71
71
72 # the changelog might not have the inline index flag
72 # the changelog might not have the inline index flag
73 # on. If the format of the changelog is the same as found in
73 # on. If the format of the changelog is the same as found in
74 # .hgrc, apply any flags found in the .hgrc as well.
74 # .hgrc, apply any flags found in the .hgrc as well.
75 # Otherwise, just version from the changelog
75 # Otherwise, just version from the changelog
76 v = self.changelog.version
76 v = self.changelog.version
77 if v == self.revlogversion:
77 if v == self.revlogversion:
78 v |= flags
78 v |= flags
79 self.revlogversion = v
79 self.revlogversion = v
80
80
81 self.tagscache = None
81 self.tagscache = None
82 self.branchcache = None
82 self.nodetagscache = None
83 self.nodetagscache = None
83 self.encodepats = None
84 self.encodepats = None
84 self.decodepats = None
85 self.decodepats = None
85 self.transhandle = None
86 self.transhandle = None
86
87
87 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
88 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
88
89
89 def url(self):
90 def url(self):
90 return 'file:' + self.root
91 return 'file:' + self.root
91
92
92 def hook(self, name, throw=False, **args):
93 def hook(self, name, throw=False, **args):
93 def callhook(hname, funcname):
94 def callhook(hname, funcname):
94 '''call python hook. hook is callable object, looked up as
95 '''call python hook. hook is callable object, looked up as
95 name in python module. if callable returns "true", hook
96 name in python module. if callable returns "true", hook
96 fails, else passes. if hook raises exception, treated as
97 fails, else passes. if hook raises exception, treated as
97 hook failure. exception propagates if throw is "true".
98 hook failure. exception propagates if throw is "true".
98
99
99 reason for "true" meaning "hook failed" is so that
100 reason for "true" meaning "hook failed" is so that
100 unmodified commands (e.g. mercurial.commands.update) can
101 unmodified commands (e.g. mercurial.commands.update) can
101 be run as hooks without wrappers to convert return values.'''
102 be run as hooks without wrappers to convert return values.'''
102
103
103 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
104 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
104 d = funcname.rfind('.')
105 d = funcname.rfind('.')
105 if d == -1:
106 if d == -1:
106 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
107 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
107 % (hname, funcname))
108 % (hname, funcname))
108 modname = funcname[:d]
109 modname = funcname[:d]
109 try:
110 try:
110 obj = __import__(modname)
111 obj = __import__(modname)
111 except ImportError:
112 except ImportError:
112 try:
113 try:
113 # extensions are loaded with hgext_ prefix
114 # extensions are loaded with hgext_ prefix
114 obj = __import__("hgext_%s" % modname)
115 obj = __import__("hgext_%s" % modname)
115 except ImportError:
116 except ImportError:
116 raise util.Abort(_('%s hook is invalid '
117 raise util.Abort(_('%s hook is invalid '
117 '(import of "%s" failed)') %
118 '(import of "%s" failed)') %
118 (hname, modname))
119 (hname, modname))
119 try:
120 try:
120 for p in funcname.split('.')[1:]:
121 for p in funcname.split('.')[1:]:
121 obj = getattr(obj, p)
122 obj = getattr(obj, p)
122 except AttributeError, err:
123 except AttributeError, err:
123 raise util.Abort(_('%s hook is invalid '
124 raise util.Abort(_('%s hook is invalid '
124 '("%s" is not defined)') %
125 '("%s" is not defined)') %
125 (hname, funcname))
126 (hname, funcname))
126 if not callable(obj):
127 if not callable(obj):
127 raise util.Abort(_('%s hook is invalid '
128 raise util.Abort(_('%s hook is invalid '
128 '("%s" is not callable)') %
129 '("%s" is not callable)') %
129 (hname, funcname))
130 (hname, funcname))
130 try:
131 try:
131 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
132 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
132 except (KeyboardInterrupt, util.SignalInterrupt):
133 except (KeyboardInterrupt, util.SignalInterrupt):
133 raise
134 raise
134 except Exception, exc:
135 except Exception, exc:
135 if isinstance(exc, util.Abort):
136 if isinstance(exc, util.Abort):
136 self.ui.warn(_('error: %s hook failed: %s\n') %
137 self.ui.warn(_('error: %s hook failed: %s\n') %
137 (hname, exc.args[0]))
138 (hname, exc.args[0]))
138 else:
139 else:
139 self.ui.warn(_('error: %s hook raised an exception: '
140 self.ui.warn(_('error: %s hook raised an exception: '
140 '%s\n') % (hname, exc))
141 '%s\n') % (hname, exc))
141 if throw:
142 if throw:
142 raise
143 raise
143 self.ui.print_exc()
144 self.ui.print_exc()
144 return True
145 return True
145 if r:
146 if r:
146 if throw:
147 if throw:
147 raise util.Abort(_('%s hook failed') % hname)
148 raise util.Abort(_('%s hook failed') % hname)
148 self.ui.warn(_('warning: %s hook failed\n') % hname)
149 self.ui.warn(_('warning: %s hook failed\n') % hname)
149 return r
150 return r
150
151
151 def runhook(name, cmd):
152 def runhook(name, cmd):
152 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
153 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
153 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
154 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
154 r = util.system(cmd, environ=env, cwd=self.root)
155 r = util.system(cmd, environ=env, cwd=self.root)
155 if r:
156 if r:
156 desc, r = util.explain_exit(r)
157 desc, r = util.explain_exit(r)
157 if throw:
158 if throw:
158 raise util.Abort(_('%s hook %s') % (name, desc))
159 raise util.Abort(_('%s hook %s') % (name, desc))
159 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
160 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
160 return r
161 return r
161
162
162 r = False
163 r = False
163 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
164 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
164 if hname.split(".", 1)[0] == name and cmd]
165 if hname.split(".", 1)[0] == name and cmd]
165 hooks.sort()
166 hooks.sort()
166 for hname, cmd in hooks:
167 for hname, cmd in hooks:
167 if cmd.startswith('python:'):
168 if cmd.startswith('python:'):
168 r = callhook(hname, cmd[7:].strip()) or r
169 r = callhook(hname, cmd[7:].strip()) or r
169 else:
170 else:
170 r = runhook(hname, cmd) or r
171 r = runhook(hname, cmd) or r
171 return r
172 return r
172
173
173 tag_disallowed = ':\r\n'
174 tag_disallowed = ':\r\n'
174
175
175 def tag(self, name, node, message, local, user, date):
176 def tag(self, name, node, message, local, user, date):
176 '''tag a revision with a symbolic name.
177 '''tag a revision with a symbolic name.
177
178
178 if local is True, the tag is stored in a per-repository file.
179 if local is True, the tag is stored in a per-repository file.
179 otherwise, it is stored in the .hgtags file, and a new
180 otherwise, it is stored in the .hgtags file, and a new
180 changeset is committed with the change.
181 changeset is committed with the change.
181
182
182 keyword arguments:
183 keyword arguments:
183
184
184 local: whether to store tag in non-version-controlled file
185 local: whether to store tag in non-version-controlled file
185 (default False)
186 (default False)
186
187
187 message: commit message to use if committing
188 message: commit message to use if committing
188
189
189 user: name of user to use if committing
190 user: name of user to use if committing
190
191
191 date: date tuple to use if committing'''
192 date: date tuple to use if committing'''
192
193
193 for c in self.tag_disallowed:
194 for c in self.tag_disallowed:
194 if c in name:
195 if c in name:
195 raise util.Abort(_('%r cannot be used in a tag name') % c)
196 raise util.Abort(_('%r cannot be used in a tag name') % c)
196
197
197 self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
198 self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
198
199
199 if local:
200 if local:
200 self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name))
201 self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name))
201 self.hook('tag', node=hex(node), tag=name, local=local)
202 self.hook('tag', node=hex(node), tag=name, local=local)
202 return
203 return
203
204
204 for x in self.status()[:5]:
205 for x in self.status()[:5]:
205 if '.hgtags' in x:
206 if '.hgtags' in x:
206 raise util.Abort(_('working copy of .hgtags is changed '
207 raise util.Abort(_('working copy of .hgtags is changed '
207 '(please commit .hgtags manually)'))
208 '(please commit .hgtags manually)'))
208
209
209 self.wfile('.hgtags', 'ab').write('%s %s\n' % (hex(node), name))
210 self.wfile('.hgtags', 'ab').write('%s %s\n' % (hex(node), name))
210 if self.dirstate.state('.hgtags') == '?':
211 if self.dirstate.state('.hgtags') == '?':
211 self.add(['.hgtags'])
212 self.add(['.hgtags'])
212
213
213 self.commit(['.hgtags'], message, user, date)
214 self.commit(['.hgtags'], message, user, date)
214 self.hook('tag', node=hex(node), tag=name, local=local)
215 self.hook('tag', node=hex(node), tag=name, local=local)
215
216
216 def tags(self):
217 def tags(self):
217 '''return a mapping of tag to node'''
218 '''return a mapping of tag to node'''
218 if not self.tagscache:
219 if not self.tagscache:
219 self.tagscache = {}
220 self.tagscache = {}
220
221
221 def parsetag(line, context):
222 def parsetag(line, context):
222 if not line:
223 if not line:
223 return
224 return
224 s = l.split(" ", 1)
225 s = l.split(" ", 1)
225 if len(s) != 2:
226 if len(s) != 2:
226 self.ui.warn(_("%s: cannot parse entry\n") % context)
227 self.ui.warn(_("%s: cannot parse entry\n") % context)
227 return
228 return
228 node, key = s
229 node, key = s
229 key = key.strip()
230 key = key.strip()
230 try:
231 try:
231 bin_n = bin(node)
232 bin_n = bin(node)
232 except TypeError:
233 except TypeError:
233 self.ui.warn(_("%s: node '%s' is not well formed\n") %
234 self.ui.warn(_("%s: node '%s' is not well formed\n") %
234 (context, node))
235 (context, node))
235 return
236 return
236 if bin_n not in self.changelog.nodemap:
237 if bin_n not in self.changelog.nodemap:
237 self.ui.warn(_("%s: tag '%s' refers to unknown node\n") %
238 self.ui.warn(_("%s: tag '%s' refers to unknown node\n") %
238 (context, key))
239 (context, key))
239 return
240 return
240 self.tagscache[key] = bin_n
241 self.tagscache[key] = bin_n
241
242
242 # read the tags file from each head, ending with the tip,
243 # read the tags file from each head, ending with the tip,
243 # and add each tag found to the map, with "newer" ones
244 # and add each tag found to the map, with "newer" ones
244 # taking precedence
245 # taking precedence
245 heads = self.heads()
246 heads = self.heads()
246 heads.reverse()
247 heads.reverse()
247 fl = self.file(".hgtags")
248 fl = self.file(".hgtags")
248 for node in heads:
249 for node in heads:
249 change = self.changelog.read(node)
250 change = self.changelog.read(node)
250 rev = self.changelog.rev(node)
251 rev = self.changelog.rev(node)
251 fn, ff = self.manifest.find(change[0], '.hgtags')
252 fn, ff = self.manifest.find(change[0], '.hgtags')
252 if fn is None: continue
253 if fn is None: continue
253 count = 0
254 count = 0
254 for l in fl.read(fn).splitlines():
255 for l in fl.read(fn).splitlines():
255 count += 1
256 count += 1
256 parsetag(l, _(".hgtags (rev %d:%s), line %d") %
257 parsetag(l, _(".hgtags (rev %d:%s), line %d") %
257 (rev, short(node), count))
258 (rev, short(node), count))
258 try:
259 try:
259 f = self.opener("localtags")
260 f = self.opener("localtags")
260 count = 0
261 count = 0
261 for l in f:
262 for l in f:
262 count += 1
263 count += 1
263 parsetag(l, _("localtags, line %d") % count)
264 parsetag(l, _("localtags, line %d") % count)
264 except IOError:
265 except IOError:
265 pass
266 pass
266
267
267 self.tagscache['tip'] = self.changelog.tip()
268 self.tagscache['tip'] = self.changelog.tip()
268
269
269 return self.tagscache
270 return self.tagscache
270
271
271 def tagslist(self):
272 def tagslist(self):
272 '''return a list of tags ordered by revision'''
273 '''return a list of tags ordered by revision'''
273 l = []
274 l = []
274 for t, n in self.tags().items():
275 for t, n in self.tags().items():
275 try:
276 try:
276 r = self.changelog.rev(n)
277 r = self.changelog.rev(n)
277 except:
278 except:
278 r = -2 # sort to the beginning of the list if unknown
279 r = -2 # sort to the beginning of the list if unknown
279 l.append((r, t, n))
280 l.append((r, t, n))
280 l.sort()
281 l.sort()
281 return [(t, n) for r, t, n in l]
282 return [(t, n) for r, t, n in l]
282
283
283 def nodetags(self, node):
284 def nodetags(self, node):
284 '''return the tags associated with a node'''
285 '''return the tags associated with a node'''
285 if not self.nodetagscache:
286 if not self.nodetagscache:
286 self.nodetagscache = {}
287 self.nodetagscache = {}
287 for t, n in self.tags().items():
288 for t, n in self.tags().items():
288 self.nodetagscache.setdefault(n, []).append(t)
289 self.nodetagscache.setdefault(n, []).append(t)
289 return self.nodetagscache.get(node, [])
290 return self.nodetagscache.get(node, [])
290
291
292 def branchtags(self):
293 if self.branchcache != None:
294 return self.branchcache
295
296 self.branchcache = {}
297
298 try:
299 f = self.opener("branches.cache")
300 last, lrev = f.readline().rstrip().split(" ", 1)
301 last, lrev = bin(last), int(lrev)
302 if self.changelog.node(lrev) == last: # sanity check
303 for l in f:
304 node, label = l.rstrip().split(" ", 1)
305 self.branchcache[label] = bin(node)
306 f.close()
307 except IOError:
308 last, lrev = nullid, -1
309 lrev = self.changelog.rev(last)
310
311 tip = self.changelog.count() - 1
312 if lrev != tip:
313 for r in range(lrev + 1, tip + 1):
314 n = self.changelog.node(r)
315 c = self.changelog.read(n)
316 b = c[5].get("branch")
317 if b:
318 self.branchcache[b] = n
319 self._writebranchcache()
320
321 return self.branchcache
322
323 def _writebranchcache(self):
324 f = self.opener("branches.cache", "w")
325 t = self.changelog.tip()
326 f.write("%s %s\n" % (hex(t), self.changelog.count() - 1))
327 for label, node in self.branchcache.iteritems():
328 f.write("%s %s\n" % (hex(node), label))
329
291 def lookup(self, key):
330 def lookup(self, key):
292 try:
331 try:
293 return self.tags()[key]
332 return self.tags()[key]
294 except KeyError:
333 except KeyError:
295 if key == '.':
334 if key == '.':
296 key = self.dirstate.parents()[0]
335 key = self.dirstate.parents()[0]
297 if key == nullid:
336 if key == nullid:
298 raise repo.RepoError(_("no revision checked out"))
337 raise repo.RepoError(_("no revision checked out"))
299 try:
338 try:
300 return self.changelog.lookup(key)
339 return self.changelog.lookup(key)
301 except:
340 except:
302 raise repo.RepoError(_("unknown revision '%s'") % key)
341 raise repo.RepoError(_("unknown revision '%s'") % key)
303
342
304 def dev(self):
343 def dev(self):
305 return os.lstat(self.path).st_dev
344 return os.lstat(self.path).st_dev
306
345
307 def local(self):
346 def local(self):
308 return True
347 return True
309
348
310 def join(self, f):
349 def join(self, f):
311 return os.path.join(self.path, f)
350 return os.path.join(self.path, f)
312
351
313 def wjoin(self, f):
352 def wjoin(self, f):
314 return os.path.join(self.root, f)
353 return os.path.join(self.root, f)
315
354
316 def file(self, f):
355 def file(self, f):
317 if f[0] == '/':
356 if f[0] == '/':
318 f = f[1:]
357 f = f[1:]
319 return filelog.filelog(self.opener, f, self.revlogversion)
358 return filelog.filelog(self.opener, f, self.revlogversion)
320
359
321 def changectx(self, changeid=None):
360 def changectx(self, changeid=None):
322 return context.changectx(self, changeid)
361 return context.changectx(self, changeid)
323
362
324 def workingctx(self):
363 def workingctx(self):
325 return context.workingctx(self)
364 return context.workingctx(self)
326
365
327 def parents(self, changeid=None):
366 def parents(self, changeid=None):
328 '''
367 '''
329 get list of changectxs for parents of changeid or working directory
368 get list of changectxs for parents of changeid or working directory
330 '''
369 '''
331 if changeid is None:
370 if changeid is None:
332 pl = self.dirstate.parents()
371 pl = self.dirstate.parents()
333 else:
372 else:
334 n = self.changelog.lookup(changeid)
373 n = self.changelog.lookup(changeid)
335 pl = self.changelog.parents(n)
374 pl = self.changelog.parents(n)
336 if pl[1] == nullid:
375 if pl[1] == nullid:
337 return [self.changectx(pl[0])]
376 return [self.changectx(pl[0])]
338 return [self.changectx(pl[0]), self.changectx(pl[1])]
377 return [self.changectx(pl[0]), self.changectx(pl[1])]
339
378
340 def filectx(self, path, changeid=None, fileid=None):
379 def filectx(self, path, changeid=None, fileid=None):
341 """changeid can be a changeset revision, node, or tag.
380 """changeid can be a changeset revision, node, or tag.
342 fileid can be a file revision or node."""
381 fileid can be a file revision or node."""
343 return context.filectx(self, path, changeid, fileid)
382 return context.filectx(self, path, changeid, fileid)
344
383
345 def getcwd(self):
384 def getcwd(self):
346 return self.dirstate.getcwd()
385 return self.dirstate.getcwd()
347
386
348 def wfile(self, f, mode='r'):
387 def wfile(self, f, mode='r'):
349 return self.wopener(f, mode)
388 return self.wopener(f, mode)
350
389
351 def wread(self, filename):
390 def wread(self, filename):
352 if self.encodepats == None:
391 if self.encodepats == None:
353 l = []
392 l = []
354 for pat, cmd in self.ui.configitems("encode"):
393 for pat, cmd in self.ui.configitems("encode"):
355 mf = util.matcher(self.root, "", [pat], [], [])[1]
394 mf = util.matcher(self.root, "", [pat], [], [])[1]
356 l.append((mf, cmd))
395 l.append((mf, cmd))
357 self.encodepats = l
396 self.encodepats = l
358
397
359 data = self.wopener(filename, 'r').read()
398 data = self.wopener(filename, 'r').read()
360
399
361 for mf, cmd in self.encodepats:
400 for mf, cmd in self.encodepats:
362 if mf(filename):
401 if mf(filename):
363 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
402 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
364 data = util.filter(data, cmd)
403 data = util.filter(data, cmd)
365 break
404 break
366
405
367 return data
406 return data
368
407
369 def wwrite(self, filename, data, fd=None):
408 def wwrite(self, filename, data, fd=None):
370 if self.decodepats == None:
409 if self.decodepats == None:
371 l = []
410 l = []
372 for pat, cmd in self.ui.configitems("decode"):
411 for pat, cmd in self.ui.configitems("decode"):
373 mf = util.matcher(self.root, "", [pat], [], [])[1]
412 mf = util.matcher(self.root, "", [pat], [], [])[1]
374 l.append((mf, cmd))
413 l.append((mf, cmd))
375 self.decodepats = l
414 self.decodepats = l
376
415
377 for mf, cmd in self.decodepats:
416 for mf, cmd in self.decodepats:
378 if mf(filename):
417 if mf(filename):
379 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
418 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
380 data = util.filter(data, cmd)
419 data = util.filter(data, cmd)
381 break
420 break
382
421
383 if fd:
422 if fd:
384 return fd.write(data)
423 return fd.write(data)
385 return self.wopener(filename, 'w').write(data)
424 return self.wopener(filename, 'w').write(data)
386
425
387 def transaction(self):
426 def transaction(self):
388 tr = self.transhandle
427 tr = self.transhandle
389 if tr != None and tr.running():
428 if tr != None and tr.running():
390 return tr.nest()
429 return tr.nest()
391
430
392 # save dirstate for rollback
431 # save dirstate for rollback
393 try:
432 try:
394 ds = self.opener("dirstate").read()
433 ds = self.opener("dirstate").read()
395 except IOError:
434 except IOError:
396 ds = ""
435 ds = ""
397 self.opener("journal.dirstate", "w").write(ds)
436 self.opener("journal.dirstate", "w").write(ds)
398
437
399 tr = transaction.transaction(self.ui.warn, self.opener,
438 tr = transaction.transaction(self.ui.warn, self.opener,
400 self.join("journal"),
439 self.join("journal"),
401 aftertrans(self.path))
440 aftertrans(self.path))
402 self.transhandle = tr
441 self.transhandle = tr
403 return tr
442 return tr
404
443
405 def recover(self):
444 def recover(self):
406 l = self.lock()
445 l = self.lock()
407 if os.path.exists(self.join("journal")):
446 if os.path.exists(self.join("journal")):
408 self.ui.status(_("rolling back interrupted transaction\n"))
447 self.ui.status(_("rolling back interrupted transaction\n"))
409 transaction.rollback(self.opener, self.join("journal"))
448 transaction.rollback(self.opener, self.join("journal"))
410 self.reload()
449 self.reload()
411 return True
450 return True
412 else:
451 else:
413 self.ui.warn(_("no interrupted transaction available\n"))
452 self.ui.warn(_("no interrupted transaction available\n"))
414 return False
453 return False
415
454
416 def rollback(self, wlock=None):
455 def rollback(self, wlock=None):
417 if not wlock:
456 if not wlock:
418 wlock = self.wlock()
457 wlock = self.wlock()
419 l = self.lock()
458 l = self.lock()
420 if os.path.exists(self.join("undo")):
459 if os.path.exists(self.join("undo")):
421 self.ui.status(_("rolling back last transaction\n"))
460 self.ui.status(_("rolling back last transaction\n"))
422 transaction.rollback(self.opener, self.join("undo"))
461 transaction.rollback(self.opener, self.join("undo"))
423 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
462 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
424 self.reload()
463 self.reload()
425 self.wreload()
464 self.wreload()
426 else:
465 else:
427 self.ui.warn(_("no rollback information available\n"))
466 self.ui.warn(_("no rollback information available\n"))
428
467
429 def wreload(self):
468 def wreload(self):
430 self.dirstate.read()
469 self.dirstate.read()
431
470
432 def reload(self):
471 def reload(self):
433 self.changelog.load()
472 self.changelog.load()
434 self.manifest.load()
473 self.manifest.load()
435 self.tagscache = None
474 self.tagscache = None
436 self.nodetagscache = None
475 self.nodetagscache = None
437
476
438 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
477 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
439 desc=None):
478 desc=None):
440 try:
479 try:
441 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
480 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
442 except lock.LockHeld, inst:
481 except lock.LockHeld, inst:
443 if not wait:
482 if not wait:
444 raise
483 raise
445 self.ui.warn(_("waiting for lock on %s held by %s\n") %
484 self.ui.warn(_("waiting for lock on %s held by %s\n") %
446 (desc, inst.args[0]))
485 (desc, inst.args[0]))
447 # default to 600 seconds timeout
486 # default to 600 seconds timeout
448 l = lock.lock(self.join(lockname),
487 l = lock.lock(self.join(lockname),
449 int(self.ui.config("ui", "timeout") or 600),
488 int(self.ui.config("ui", "timeout") or 600),
450 releasefn, desc=desc)
489 releasefn, desc=desc)
451 if acquirefn:
490 if acquirefn:
452 acquirefn()
491 acquirefn()
453 return l
492 return l
454
493
455 def lock(self, wait=1):
494 def lock(self, wait=1):
456 return self.do_lock("lock", wait, acquirefn=self.reload,
495 return self.do_lock("lock", wait, acquirefn=self.reload,
457 desc=_('repository %s') % self.origroot)
496 desc=_('repository %s') % self.origroot)
458
497
459 def wlock(self, wait=1):
498 def wlock(self, wait=1):
460 return self.do_lock("wlock", wait, self.dirstate.write,
499 return self.do_lock("wlock", wait, self.dirstate.write,
461 self.wreload,
500 self.wreload,
462 desc=_('working directory of %s') % self.origroot)
501 desc=_('working directory of %s') % self.origroot)
463
502
464 def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist):
503 def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist):
465 """
504 """
466 commit an individual file as part of a larger transaction
505 commit an individual file as part of a larger transaction
467 """
506 """
468
507
469 t = self.wread(fn)
508 t = self.wread(fn)
470 fl = self.file(fn)
509 fl = self.file(fn)
471 fp1 = manifest1.get(fn, nullid)
510 fp1 = manifest1.get(fn, nullid)
472 fp2 = manifest2.get(fn, nullid)
511 fp2 = manifest2.get(fn, nullid)
473
512
474 meta = {}
513 meta = {}
475 cp = self.dirstate.copied(fn)
514 cp = self.dirstate.copied(fn)
476 if cp:
515 if cp:
477 meta["copy"] = cp
516 meta["copy"] = cp
478 if not manifest2: # not a branch merge
517 if not manifest2: # not a branch merge
479 meta["copyrev"] = hex(manifest1.get(cp, nullid))
518 meta["copyrev"] = hex(manifest1.get(cp, nullid))
480 fp2 = nullid
519 fp2 = nullid
481 elif fp2 != nullid: # copied on remote side
520 elif fp2 != nullid: # copied on remote side
482 meta["copyrev"] = hex(manifest1.get(cp, nullid))
521 meta["copyrev"] = hex(manifest1.get(cp, nullid))
483 else: # copied on local side, reversed
522 else: # copied on local side, reversed
484 meta["copyrev"] = hex(manifest2.get(cp))
523 meta["copyrev"] = hex(manifest2.get(cp))
485 fp2 = nullid
524 fp2 = nullid
486 self.ui.debug(_(" %s: copy %s:%s\n") %
525 self.ui.debug(_(" %s: copy %s:%s\n") %
487 (fn, cp, meta["copyrev"]))
526 (fn, cp, meta["copyrev"]))
488 fp1 = nullid
527 fp1 = nullid
489 elif fp2 != nullid:
528 elif fp2 != nullid:
490 # is one parent an ancestor of the other?
529 # is one parent an ancestor of the other?
491 fpa = fl.ancestor(fp1, fp2)
530 fpa = fl.ancestor(fp1, fp2)
492 if fpa == fp1:
531 if fpa == fp1:
493 fp1, fp2 = fp2, nullid
532 fp1, fp2 = fp2, nullid
494 elif fpa == fp2:
533 elif fpa == fp2:
495 fp2 = nullid
534 fp2 = nullid
496
535
497 # is the file unmodified from the parent? report existing entry
536 # is the file unmodified from the parent? report existing entry
498 if fp2 == nullid and not fl.cmp(fp1, t):
537 if fp2 == nullid and not fl.cmp(fp1, t):
499 return fp1
538 return fp1
500
539
501 changelist.append(fn)
540 changelist.append(fn)
502 return fl.add(t, meta, transaction, linkrev, fp1, fp2)
541 return fl.add(t, meta, transaction, linkrev, fp1, fp2)
503
542
504 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
543 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
505 orig_parent = self.dirstate.parents()[0] or nullid
544 orig_parent = self.dirstate.parents()[0] or nullid
506 p1 = p1 or self.dirstate.parents()[0] or nullid
545 p1 = p1 or self.dirstate.parents()[0] or nullid
507 p2 = p2 or self.dirstate.parents()[1] or nullid
546 p2 = p2 or self.dirstate.parents()[1] or nullid
508 c1 = self.changelog.read(p1)
547 c1 = self.changelog.read(p1)
509 c2 = self.changelog.read(p2)
548 c2 = self.changelog.read(p2)
510 m1 = self.manifest.read(c1[0]).copy()
549 m1 = self.manifest.read(c1[0]).copy()
511 m2 = self.manifest.read(c2[0])
550 m2 = self.manifest.read(c2[0])
512 changed = []
551 changed = []
513 removed = []
552 removed = []
514
553
515 if orig_parent == p1:
554 if orig_parent == p1:
516 update_dirstate = 1
555 update_dirstate = 1
517 else:
556 else:
518 update_dirstate = 0
557 update_dirstate = 0
519
558
520 if not wlock:
559 if not wlock:
521 wlock = self.wlock()
560 wlock = self.wlock()
522 l = self.lock()
561 l = self.lock()
523 tr = self.transaction()
562 tr = self.transaction()
524 linkrev = self.changelog.count()
563 linkrev = self.changelog.count()
525 for f in files:
564 for f in files:
526 try:
565 try:
527 m1[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
566 m1[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
528 m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
567 m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
529 except IOError:
568 except IOError:
530 try:
569 try:
531 del m1[f]
570 del m1[f]
532 if update_dirstate:
571 if update_dirstate:
533 self.dirstate.forget([f])
572 self.dirstate.forget([f])
534 removed.append(f)
573 removed.append(f)
535 except:
574 except:
536 # deleted from p2?
575 # deleted from p2?
537 pass
576 pass
538
577
539 mnode = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
578 mnode = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
540 user = user or self.ui.username()
579 user = user or self.ui.username()
541 n = self.changelog.add(mnode, changed + removed, text,
580 n = self.changelog.add(mnode, changed + removed, text,
542 tr, p1, p2, user, date)
581 tr, p1, p2, user, date)
543 tr.close()
582 tr.close()
544 if update_dirstate:
583 if update_dirstate:
545 self.dirstate.setparents(n, nullid)
584 self.dirstate.setparents(n, nullid)
546
585
547 def commit(self, files=None, text="", user=None, date=None,
586 def commit(self, files=None, text="", user=None, date=None,
548 match=util.always, force=False, lock=None, wlock=None,
587 match=util.always, force=False, lock=None, wlock=None,
549 force_editor=False):
588 force_editor=False):
550 commit = []
589 commit = []
551 remove = []
590 remove = []
552 changed = []
591 changed = []
553
592
554 if files:
593 if files:
555 for f in files:
594 for f in files:
556 s = self.dirstate.state(f)
595 s = self.dirstate.state(f)
557 if s in 'nmai':
596 if s in 'nmai':
558 commit.append(f)
597 commit.append(f)
559 elif s == 'r':
598 elif s == 'r':
560 remove.append(f)
599 remove.append(f)
561 else:
600 else:
562 self.ui.warn(_("%s not tracked!\n") % f)
601 self.ui.warn(_("%s not tracked!\n") % f)
563 else:
602 else:
564 modified, added, removed, deleted, unknown = self.status(match=match)[:5]
603 modified, added, removed, deleted, unknown = self.status(match=match)[:5]
565 commit = modified + added
604 commit = modified + added
566 remove = removed
605 remove = removed
567
606
568 p1, p2 = self.dirstate.parents()
607 p1, p2 = self.dirstate.parents()
569 c1 = self.changelog.read(p1)
608 c1 = self.changelog.read(p1)
570 c2 = self.changelog.read(p2)
609 c2 = self.changelog.read(p2)
571 m1 = self.manifest.read(c1[0]).copy()
610 m1 = self.manifest.read(c1[0]).copy()
572 m2 = self.manifest.read(c2[0])
611 m2 = self.manifest.read(c2[0])
573
612
574 if not commit and not remove and not force and p2 == nullid:
613 if not commit and not remove and not force and p2 == nullid:
575 self.ui.status(_("nothing changed\n"))
614 self.ui.status(_("nothing changed\n"))
576 return None
615 return None
577
616
578 xp1 = hex(p1)
617 xp1 = hex(p1)
579 if p2 == nullid: xp2 = ''
618 if p2 == nullid: xp2 = ''
580 else: xp2 = hex(p2)
619 else: xp2 = hex(p2)
581
620
582 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
621 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
583
622
584 if not wlock:
623 if not wlock:
585 wlock = self.wlock()
624 wlock = self.wlock()
586 if not lock:
625 if not lock:
587 lock = self.lock()
626 lock = self.lock()
588 tr = self.transaction()
627 tr = self.transaction()
589
628
590 # check in files
629 # check in files
591 new = {}
630 new = {}
592 linkrev = self.changelog.count()
631 linkrev = self.changelog.count()
593 commit.sort()
632 commit.sort()
594 for f in commit:
633 for f in commit:
595 self.ui.note(f + "\n")
634 self.ui.note(f + "\n")
596 try:
635 try:
597 new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
636 new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
598 m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
637 m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
599 except IOError:
638 except IOError:
600 self.ui.warn(_("trouble committing %s!\n") % f)
639 self.ui.warn(_("trouble committing %s!\n") % f)
601 raise
640 raise
602
641
603 # update manifest
642 # update manifest
604 m1.update(new)
643 m1.update(new)
605 for f in remove:
644 for f in remove:
606 if f in m1:
645 if f in m1:
607 del m1[f]
646 del m1[f]
608 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, remove))
647 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, remove))
609
648
610 # add changeset
649 # add changeset
611 new = new.keys()
650 new = new.keys()
612 new.sort()
651 new.sort()
613
652
614 user = user or self.ui.username()
653 user = user or self.ui.username()
615 if not text or force_editor:
654 if not text or force_editor:
616 edittext = []
655 edittext = []
617 if text:
656 if text:
618 edittext.append(text)
657 edittext.append(text)
619 edittext.append("")
658 edittext.append("")
620 if p2 != nullid:
659 if p2 != nullid:
621 edittext.append("HG: branch merge")
660 edittext.append("HG: branch merge")
622 edittext.extend(["HG: changed %s" % f for f in changed])
661 edittext.extend(["HG: changed %s" % f for f in changed])
623 edittext.extend(["HG: removed %s" % f for f in remove])
662 edittext.extend(["HG: removed %s" % f for f in remove])
624 if not changed and not remove:
663 if not changed and not remove:
625 edittext.append("HG: no files changed")
664 edittext.append("HG: no files changed")
626 edittext.append("")
665 edittext.append("")
627 # run editor in the repository root
666 # run editor in the repository root
628 olddir = os.getcwd()
667 olddir = os.getcwd()
629 os.chdir(self.root)
668 os.chdir(self.root)
630 text = self.ui.edit("\n".join(edittext), user)
669 text = self.ui.edit("\n".join(edittext), user)
631 os.chdir(olddir)
670 os.chdir(olddir)
632
671
633 lines = [line.rstrip() for line in text.rstrip().splitlines()]
672 lines = [line.rstrip() for line in text.rstrip().splitlines()]
634 while lines and not lines[0]:
673 while lines and not lines[0]:
635 del lines[0]
674 del lines[0]
636 if not lines:
675 if not lines:
637 return None
676 return None
638 text = '\n'.join(lines)
677 text = '\n'.join(lines)
639 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
678 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
640 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
679 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
641 parent2=xp2)
680 parent2=xp2)
642 tr.close()
681 tr.close()
643
682
644 self.dirstate.setparents(n)
683 self.dirstate.setparents(n)
645 self.dirstate.update(new, "n")
684 self.dirstate.update(new, "n")
646 self.dirstate.forget(remove)
685 self.dirstate.forget(remove)
647
686
648 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
687 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
649 return n
688 return n
650
689
651 def walk(self, node=None, files=[], match=util.always, badmatch=None):
690 def walk(self, node=None, files=[], match=util.always, badmatch=None):
652 if node:
691 if node:
653 fdict = dict.fromkeys(files)
692 fdict = dict.fromkeys(files)
654 for fn in self.manifest.read(self.changelog.read(node)[0]):
693 for fn in self.manifest.read(self.changelog.read(node)[0]):
655 for ffn in fdict:
694 for ffn in fdict:
656 # match if the file is the exact name or a directory
695 # match if the file is the exact name or a directory
657 if ffn == fn or fn.startswith("%s/" % ffn):
696 if ffn == fn or fn.startswith("%s/" % ffn):
658 del fdict[ffn]
697 del fdict[ffn]
659 break
698 break
660 if match(fn):
699 if match(fn):
661 yield 'm', fn
700 yield 'm', fn
662 for fn in fdict:
701 for fn in fdict:
663 if badmatch and badmatch(fn):
702 if badmatch and badmatch(fn):
664 if match(fn):
703 if match(fn):
665 yield 'b', fn
704 yield 'b', fn
666 else:
705 else:
667 self.ui.warn(_('%s: No such file in rev %s\n') % (
706 self.ui.warn(_('%s: No such file in rev %s\n') % (
668 util.pathto(self.getcwd(), fn), short(node)))
707 util.pathto(self.getcwd(), fn), short(node)))
669 else:
708 else:
670 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
709 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
671 yield src, fn
710 yield src, fn
672
711
673 def status(self, node1=None, node2=None, files=[], match=util.always,
712 def status(self, node1=None, node2=None, files=[], match=util.always,
674 wlock=None, list_ignored=False, list_clean=False):
713 wlock=None, list_ignored=False, list_clean=False):
675 """return status of files between two nodes or node and working directory
714 """return status of files between two nodes or node and working directory
676
715
677 If node1 is None, use the first dirstate parent instead.
716 If node1 is None, use the first dirstate parent instead.
678 If node2 is None, compare node1 with working directory.
717 If node2 is None, compare node1 with working directory.
679 """
718 """
680
719
681 def fcmp(fn, mf):
720 def fcmp(fn, mf):
682 t1 = self.wread(fn)
721 t1 = self.wread(fn)
683 return self.file(fn).cmp(mf.get(fn, nullid), t1)
722 return self.file(fn).cmp(mf.get(fn, nullid), t1)
684
723
685 def mfmatches(node):
724 def mfmatches(node):
686 change = self.changelog.read(node)
725 change = self.changelog.read(node)
687 mf = self.manifest.read(change[0]).copy()
726 mf = self.manifest.read(change[0]).copy()
688 for fn in mf.keys():
727 for fn in mf.keys():
689 if not match(fn):
728 if not match(fn):
690 del mf[fn]
729 del mf[fn]
691 return mf
730 return mf
692
731
693 modified, added, removed, deleted, unknown = [], [], [], [], []
732 modified, added, removed, deleted, unknown = [], [], [], [], []
694 ignored, clean = [], []
733 ignored, clean = [], []
695
734
696 compareworking = False
735 compareworking = False
697 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
736 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
698 compareworking = True
737 compareworking = True
699
738
700 if not compareworking:
739 if not compareworking:
701 # read the manifest from node1 before the manifest from node2,
740 # read the manifest from node1 before the manifest from node2,
702 # so that we'll hit the manifest cache if we're going through
741 # so that we'll hit the manifest cache if we're going through
703 # all the revisions in parent->child order.
742 # all the revisions in parent->child order.
704 mf1 = mfmatches(node1)
743 mf1 = mfmatches(node1)
705
744
706 # are we comparing the working directory?
745 # are we comparing the working directory?
707 if not node2:
746 if not node2:
708 if not wlock:
747 if not wlock:
709 try:
748 try:
710 wlock = self.wlock(wait=0)
749 wlock = self.wlock(wait=0)
711 except lock.LockException:
750 except lock.LockException:
712 wlock = None
751 wlock = None
713 (lookup, modified, added, removed, deleted, unknown,
752 (lookup, modified, added, removed, deleted, unknown,
714 ignored, clean) = self.dirstate.status(files, match,
753 ignored, clean) = self.dirstate.status(files, match,
715 list_ignored, list_clean)
754 list_ignored, list_clean)
716
755
717 # are we comparing working dir against its parent?
756 # are we comparing working dir against its parent?
718 if compareworking:
757 if compareworking:
719 if lookup:
758 if lookup:
720 # do a full compare of any files that might have changed
759 # do a full compare of any files that might have changed
721 mf2 = mfmatches(self.dirstate.parents()[0])
760 mf2 = mfmatches(self.dirstate.parents()[0])
722 for f in lookup:
761 for f in lookup:
723 if fcmp(f, mf2):
762 if fcmp(f, mf2):
724 modified.append(f)
763 modified.append(f)
725 else:
764 else:
726 clean.append(f)
765 clean.append(f)
727 if wlock is not None:
766 if wlock is not None:
728 self.dirstate.update([f], "n")
767 self.dirstate.update([f], "n")
729 else:
768 else:
730 # we are comparing working dir against non-parent
769 # we are comparing working dir against non-parent
731 # generate a pseudo-manifest for the working dir
770 # generate a pseudo-manifest for the working dir
732 # XXX: create it in dirstate.py ?
771 # XXX: create it in dirstate.py ?
733 mf2 = mfmatches(self.dirstate.parents()[0])
772 mf2 = mfmatches(self.dirstate.parents()[0])
734 for f in lookup + modified + added:
773 for f in lookup + modified + added:
735 mf2[f] = ""
774 mf2[f] = ""
736 mf2.set(f, execf=util.is_exec(self.wjoin(f), mf2.execf(f)))
775 mf2.set(f, execf=util.is_exec(self.wjoin(f), mf2.execf(f)))
737 for f in removed:
776 for f in removed:
738 if f in mf2:
777 if f in mf2:
739 del mf2[f]
778 del mf2[f]
740 else:
779 else:
741 # we are comparing two revisions
780 # we are comparing two revisions
742 mf2 = mfmatches(node2)
781 mf2 = mfmatches(node2)
743
782
744 if not compareworking:
783 if not compareworking:
745 # flush lists from dirstate before comparing manifests
784 # flush lists from dirstate before comparing manifests
746 modified, added, clean = [], [], []
785 modified, added, clean = [], [], []
747
786
748 # make sure to sort the files so we talk to the disk in a
787 # make sure to sort the files so we talk to the disk in a
749 # reasonable order
788 # reasonable order
750 mf2keys = mf2.keys()
789 mf2keys = mf2.keys()
751 mf2keys.sort()
790 mf2keys.sort()
752 for fn in mf2keys:
791 for fn in mf2keys:
753 if mf1.has_key(fn):
792 if mf1.has_key(fn):
754 if mf1.flags(fn) != mf2.flags(fn) or \
793 if mf1.flags(fn) != mf2.flags(fn) or \
755 (mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1))):
794 (mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1))):
756 modified.append(fn)
795 modified.append(fn)
757 elif list_clean:
796 elif list_clean:
758 clean.append(fn)
797 clean.append(fn)
759 del mf1[fn]
798 del mf1[fn]
760 else:
799 else:
761 added.append(fn)
800 added.append(fn)
762
801
763 removed = mf1.keys()
802 removed = mf1.keys()
764
803
765 # sort and return results:
804 # sort and return results:
766 for l in modified, added, removed, deleted, unknown, ignored, clean:
805 for l in modified, added, removed, deleted, unknown, ignored, clean:
767 l.sort()
806 l.sort()
768 return (modified, added, removed, deleted, unknown, ignored, clean)
807 return (modified, added, removed, deleted, unknown, ignored, clean)
769
808
770 def add(self, list, wlock=None):
809 def add(self, list, wlock=None):
771 if not wlock:
810 if not wlock:
772 wlock = self.wlock()
811 wlock = self.wlock()
773 for f in list:
812 for f in list:
774 p = self.wjoin(f)
813 p = self.wjoin(f)
775 if not os.path.exists(p):
814 if not os.path.exists(p):
776 self.ui.warn(_("%s does not exist!\n") % f)
815 self.ui.warn(_("%s does not exist!\n") % f)
777 elif not os.path.isfile(p):
816 elif not os.path.isfile(p):
778 self.ui.warn(_("%s not added: only files supported currently\n")
817 self.ui.warn(_("%s not added: only files supported currently\n")
779 % f)
818 % f)
780 elif self.dirstate.state(f) in 'an':
819 elif self.dirstate.state(f) in 'an':
781 self.ui.warn(_("%s already tracked!\n") % f)
820 self.ui.warn(_("%s already tracked!\n") % f)
782 else:
821 else:
783 self.dirstate.update([f], "a")
822 self.dirstate.update([f], "a")
784
823
785 def forget(self, list, wlock=None):
824 def forget(self, list, wlock=None):
786 if not wlock:
825 if not wlock:
787 wlock = self.wlock()
826 wlock = self.wlock()
788 for f in list:
827 for f in list:
789 if self.dirstate.state(f) not in 'ai':
828 if self.dirstate.state(f) not in 'ai':
790 self.ui.warn(_("%s not added!\n") % f)
829 self.ui.warn(_("%s not added!\n") % f)
791 else:
830 else:
792 self.dirstate.forget([f])
831 self.dirstate.forget([f])
793
832
794 def remove(self, list, unlink=False, wlock=None):
833 def remove(self, list, unlink=False, wlock=None):
795 if unlink:
834 if unlink:
796 for f in list:
835 for f in list:
797 try:
836 try:
798 util.unlink(self.wjoin(f))
837 util.unlink(self.wjoin(f))
799 except OSError, inst:
838 except OSError, inst:
800 if inst.errno != errno.ENOENT:
839 if inst.errno != errno.ENOENT:
801 raise
840 raise
802 if not wlock:
841 if not wlock:
803 wlock = self.wlock()
842 wlock = self.wlock()
804 for f in list:
843 for f in list:
805 p = self.wjoin(f)
844 p = self.wjoin(f)
806 if os.path.exists(p):
845 if os.path.exists(p):
807 self.ui.warn(_("%s still exists!\n") % f)
846 self.ui.warn(_("%s still exists!\n") % f)
808 elif self.dirstate.state(f) == 'a':
847 elif self.dirstate.state(f) == 'a':
809 self.dirstate.forget([f])
848 self.dirstate.forget([f])
810 elif f not in self.dirstate:
849 elif f not in self.dirstate:
811 self.ui.warn(_("%s not tracked!\n") % f)
850 self.ui.warn(_("%s not tracked!\n") % f)
812 else:
851 else:
813 self.dirstate.update([f], "r")
852 self.dirstate.update([f], "r")
814
853
815 def undelete(self, list, wlock=None):
854 def undelete(self, list, wlock=None):
816 p = self.dirstate.parents()[0]
855 p = self.dirstate.parents()[0]
817 mn = self.changelog.read(p)[0]
856 mn = self.changelog.read(p)[0]
818 m = self.manifest.read(mn)
857 m = self.manifest.read(mn)
819 if not wlock:
858 if not wlock:
820 wlock = self.wlock()
859 wlock = self.wlock()
821 for f in list:
860 for f in list:
822 if self.dirstate.state(f) not in "r":
861 if self.dirstate.state(f) not in "r":
823 self.ui.warn("%s not removed!\n" % f)
862 self.ui.warn("%s not removed!\n" % f)
824 else:
863 else:
825 t = self.file(f).read(m[f])
864 t = self.file(f).read(m[f])
826 self.wwrite(f, t)
865 self.wwrite(f, t)
827 util.set_exec(self.wjoin(f), m.execf(f))
866 util.set_exec(self.wjoin(f), m.execf(f))
828 self.dirstate.update([f], "n")
867 self.dirstate.update([f], "n")
829
868
830 def copy(self, source, dest, wlock=None):
869 def copy(self, source, dest, wlock=None):
831 p = self.wjoin(dest)
870 p = self.wjoin(dest)
832 if not os.path.exists(p):
871 if not os.path.exists(p):
833 self.ui.warn(_("%s does not exist!\n") % dest)
872 self.ui.warn(_("%s does not exist!\n") % dest)
834 elif not os.path.isfile(p):
873 elif not os.path.isfile(p):
835 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
874 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
836 else:
875 else:
837 if not wlock:
876 if not wlock:
838 wlock = self.wlock()
877 wlock = self.wlock()
839 if self.dirstate.state(dest) == '?':
878 if self.dirstate.state(dest) == '?':
840 self.dirstate.update([dest], "a")
879 self.dirstate.update([dest], "a")
841 self.dirstate.copy(source, dest)
880 self.dirstate.copy(source, dest)
842
881
843 def heads(self, start=None):
882 def heads(self, start=None):
844 heads = self.changelog.heads(start)
883 heads = self.changelog.heads(start)
845 # sort the output in rev descending order
884 # sort the output in rev descending order
846 heads = [(-self.changelog.rev(h), h) for h in heads]
885 heads = [(-self.changelog.rev(h), h) for h in heads]
847 heads.sort()
886 heads.sort()
848 return [n for (r, n) in heads]
887 return [n for (r, n) in heads]
849
888
850 # branchlookup returns a dict giving a list of branches for
889 # branchlookup returns a dict giving a list of branches for
851 # each head. A branch is defined as the tag of a node or
890 # each head. A branch is defined as the tag of a node or
852 # the branch of the node's parents. If a node has multiple
891 # the branch of the node's parents. If a node has multiple
853 # branch tags, tags are eliminated if they are visible from other
892 # branch tags, tags are eliminated if they are visible from other
854 # branch tags.
893 # branch tags.
855 #
894 #
856 # So, for this graph: a->b->c->d->e
895 # So, for this graph: a->b->c->d->e
857 # \ /
896 # \ /
858 # aa -----/
897 # aa -----/
859 # a has tag 2.6.12
898 # a has tag 2.6.12
860 # d has tag 2.6.13
899 # d has tag 2.6.13
861 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
900 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
862 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
901 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
863 # from the list.
902 # from the list.
864 #
903 #
865 # It is possible that more than one head will have the same branch tag.
904 # It is possible that more than one head will have the same branch tag.
866 # callers need to check the result for multiple heads under the same
905 # callers need to check the result for multiple heads under the same
867 # branch tag if that is a problem for them (ie checkout of a specific
906 # branch tag if that is a problem for them (ie checkout of a specific
868 # branch).
907 # branch).
869 #
908 #
870 # passing in a specific branch will limit the depth of the search
909 # passing in a specific branch will limit the depth of the search
871 # through the parents. It won't limit the branches returned in the
910 # through the parents. It won't limit the branches returned in the
872 # result though.
911 # result though.
873 def branchlookup(self, heads=None, branch=None):
912 def branchlookup(self, heads=None, branch=None):
874 if not heads:
913 if not heads:
875 heads = self.heads()
914 heads = self.heads()
876 headt = [ h for h in heads ]
915 headt = [ h for h in heads ]
877 chlog = self.changelog
916 chlog = self.changelog
878 branches = {}
917 branches = {}
879 merges = []
918 merges = []
880 seenmerge = {}
919 seenmerge = {}
881
920
882 # traverse the tree once for each head, recording in the branches
921 # traverse the tree once for each head, recording in the branches
883 # dict which tags are visible from this head. The branches
922 # dict which tags are visible from this head. The branches
884 # dict also records which tags are visible from each tag
923 # dict also records which tags are visible from each tag
885 # while we traverse.
924 # while we traverse.
886 while headt or merges:
925 while headt or merges:
887 if merges:
926 if merges:
888 n, found = merges.pop()
927 n, found = merges.pop()
889 visit = [n]
928 visit = [n]
890 else:
929 else:
891 h = headt.pop()
930 h = headt.pop()
892 visit = [h]
931 visit = [h]
893 found = [h]
932 found = [h]
894 seen = {}
933 seen = {}
895 while visit:
934 while visit:
896 n = visit.pop()
935 n = visit.pop()
897 if n in seen:
936 if n in seen:
898 continue
937 continue
899 pp = chlog.parents(n)
938 pp = chlog.parents(n)
900 tags = self.nodetags(n)
939 tags = self.nodetags(n)
901 if tags:
940 if tags:
902 for x in tags:
941 for x in tags:
903 if x == 'tip':
942 if x == 'tip':
904 continue
943 continue
905 for f in found:
944 for f in found:
906 branches.setdefault(f, {})[n] = 1
945 branches.setdefault(f, {})[n] = 1
907 branches.setdefault(n, {})[n] = 1
946 branches.setdefault(n, {})[n] = 1
908 break
947 break
909 if n not in found:
948 if n not in found:
910 found.append(n)
949 found.append(n)
911 if branch in tags:
950 if branch in tags:
912 continue
951 continue
913 seen[n] = 1
952 seen[n] = 1
914 if pp[1] != nullid and n not in seenmerge:
953 if pp[1] != nullid and n not in seenmerge:
915 merges.append((pp[1], [x for x in found]))
954 merges.append((pp[1], [x for x in found]))
916 seenmerge[n] = 1
955 seenmerge[n] = 1
917 if pp[0] != nullid:
956 if pp[0] != nullid:
918 visit.append(pp[0])
957 visit.append(pp[0])
919 # traverse the branches dict, eliminating branch tags from each
958 # traverse the branches dict, eliminating branch tags from each
920 # head that are visible from another branch tag for that head.
959 # head that are visible from another branch tag for that head.
921 out = {}
960 out = {}
922 viscache = {}
961 viscache = {}
923 for h in heads:
962 for h in heads:
924 def visible(node):
963 def visible(node):
925 if node in viscache:
964 if node in viscache:
926 return viscache[node]
965 return viscache[node]
927 ret = {}
966 ret = {}
928 visit = [node]
967 visit = [node]
929 while visit:
968 while visit:
930 x = visit.pop()
969 x = visit.pop()
931 if x in viscache:
970 if x in viscache:
932 ret.update(viscache[x])
971 ret.update(viscache[x])
933 elif x not in ret:
972 elif x not in ret:
934 ret[x] = 1
973 ret[x] = 1
935 if x in branches:
974 if x in branches:
936 visit[len(visit):] = branches[x].keys()
975 visit[len(visit):] = branches[x].keys()
937 viscache[node] = ret
976 viscache[node] = ret
938 return ret
977 return ret
939 if h not in branches:
978 if h not in branches:
940 continue
979 continue
941 # O(n^2), but somewhat limited. This only searches the
980 # O(n^2), but somewhat limited. This only searches the
942 # tags visible from a specific head, not all the tags in the
981 # tags visible from a specific head, not all the tags in the
943 # whole repo.
982 # whole repo.
944 for b in branches[h]:
983 for b in branches[h]:
945 vis = False
984 vis = False
946 for bb in branches[h].keys():
985 for bb in branches[h].keys():
947 if b != bb:
986 if b != bb:
948 if b in visible(bb):
987 if b in visible(bb):
949 vis = True
988 vis = True
950 break
989 break
951 if not vis:
990 if not vis:
952 l = out.setdefault(h, [])
991 l = out.setdefault(h, [])
953 l[len(l):] = self.nodetags(b)
992 l[len(l):] = self.nodetags(b)
954 return out
993 return out
955
994
956 def branches(self, nodes):
995 def branches(self, nodes):
957 if not nodes:
996 if not nodes:
958 nodes = [self.changelog.tip()]
997 nodes = [self.changelog.tip()]
959 b = []
998 b = []
960 for n in nodes:
999 for n in nodes:
961 t = n
1000 t = n
962 while 1:
1001 while 1:
963 p = self.changelog.parents(n)
1002 p = self.changelog.parents(n)
964 if p[1] != nullid or p[0] == nullid:
1003 if p[1] != nullid or p[0] == nullid:
965 b.append((t, n, p[0], p[1]))
1004 b.append((t, n, p[0], p[1]))
966 break
1005 break
967 n = p[0]
1006 n = p[0]
968 return b
1007 return b
969
1008
970 def between(self, pairs):
1009 def between(self, pairs):
971 r = []
1010 r = []
972
1011
973 for top, bottom in pairs:
1012 for top, bottom in pairs:
974 n, l, i = top, [], 0
1013 n, l, i = top, [], 0
975 f = 1
1014 f = 1
976
1015
977 while n != bottom:
1016 while n != bottom:
978 p = self.changelog.parents(n)[0]
1017 p = self.changelog.parents(n)[0]
979 if i == f:
1018 if i == f:
980 l.append(n)
1019 l.append(n)
981 f = f * 2
1020 f = f * 2
982 n = p
1021 n = p
983 i += 1
1022 i += 1
984
1023
985 r.append(l)
1024 r.append(l)
986
1025
987 return r
1026 return r
988
1027
989 def findincoming(self, remote, base=None, heads=None, force=False):
1028 def findincoming(self, remote, base=None, heads=None, force=False):
990 """Return list of roots of the subsets of missing nodes from remote
1029 """Return list of roots of the subsets of missing nodes from remote
991
1030
992 If base dict is specified, assume that these nodes and their parents
1031 If base dict is specified, assume that these nodes and their parents
993 exist on the remote side and that no child of a node of base exists
1032 exist on the remote side and that no child of a node of base exists
994 in both remote and self.
1033 in both remote and self.
995 Furthermore base will be updated to include the nodes that exists
1034 Furthermore base will be updated to include the nodes that exists
996 in self and remote but no children exists in self and remote.
1035 in self and remote but no children exists in self and remote.
997 If a list of heads is specified, return only nodes which are heads
1036 If a list of heads is specified, return only nodes which are heads
998 or ancestors of these heads.
1037 or ancestors of these heads.
999
1038
1000 All the ancestors of base are in self and in remote.
1039 All the ancestors of base are in self and in remote.
1001 All the descendants of the list returned are missing in self.
1040 All the descendants of the list returned are missing in self.
1002 (and so we know that the rest of the nodes are missing in remote, see
1041 (and so we know that the rest of the nodes are missing in remote, see
1003 outgoing)
1042 outgoing)
1004 """
1043 """
1005 m = self.changelog.nodemap
1044 m = self.changelog.nodemap
1006 search = []
1045 search = []
1007 fetch = {}
1046 fetch = {}
1008 seen = {}
1047 seen = {}
1009 seenbranch = {}
1048 seenbranch = {}
1010 if base == None:
1049 if base == None:
1011 base = {}
1050 base = {}
1012
1051
1013 if not heads:
1052 if not heads:
1014 heads = remote.heads()
1053 heads = remote.heads()
1015
1054
1016 if self.changelog.tip() == nullid:
1055 if self.changelog.tip() == nullid:
1017 base[nullid] = 1
1056 base[nullid] = 1
1018 if heads != [nullid]:
1057 if heads != [nullid]:
1019 return [nullid]
1058 return [nullid]
1020 return []
1059 return []
1021
1060
1022 # assume we're closer to the tip than the root
1061 # assume we're closer to the tip than the root
1023 # and start by examining the heads
1062 # and start by examining the heads
1024 self.ui.status(_("searching for changes\n"))
1063 self.ui.status(_("searching for changes\n"))
1025
1064
1026 unknown = []
1065 unknown = []
1027 for h in heads:
1066 for h in heads:
1028 if h not in m:
1067 if h not in m:
1029 unknown.append(h)
1068 unknown.append(h)
1030 else:
1069 else:
1031 base[h] = 1
1070 base[h] = 1
1032
1071
1033 if not unknown:
1072 if not unknown:
1034 return []
1073 return []
1035
1074
1036 req = dict.fromkeys(unknown)
1075 req = dict.fromkeys(unknown)
1037 reqcnt = 0
1076 reqcnt = 0
1038
1077
1039 # search through remote branches
1078 # search through remote branches
1040 # a 'branch' here is a linear segment of history, with four parts:
1079 # a 'branch' here is a linear segment of history, with four parts:
1041 # head, root, first parent, second parent
1080 # head, root, first parent, second parent
1042 # (a branch always has two parents (or none) by definition)
1081 # (a branch always has two parents (or none) by definition)
1043 unknown = remote.branches(unknown)
1082 unknown = remote.branches(unknown)
1044 while unknown:
1083 while unknown:
1045 r = []
1084 r = []
1046 while unknown:
1085 while unknown:
1047 n = unknown.pop(0)
1086 n = unknown.pop(0)
1048 if n[0] in seen:
1087 if n[0] in seen:
1049 continue
1088 continue
1050
1089
1051 self.ui.debug(_("examining %s:%s\n")
1090 self.ui.debug(_("examining %s:%s\n")
1052 % (short(n[0]), short(n[1])))
1091 % (short(n[0]), short(n[1])))
1053 if n[0] == nullid: # found the end of the branch
1092 if n[0] == nullid: # found the end of the branch
1054 pass
1093 pass
1055 elif n in seenbranch:
1094 elif n in seenbranch:
1056 self.ui.debug(_("branch already found\n"))
1095 self.ui.debug(_("branch already found\n"))
1057 continue
1096 continue
1058 elif n[1] and n[1] in m: # do we know the base?
1097 elif n[1] and n[1] in m: # do we know the base?
1059 self.ui.debug(_("found incomplete branch %s:%s\n")
1098 self.ui.debug(_("found incomplete branch %s:%s\n")
1060 % (short(n[0]), short(n[1])))
1099 % (short(n[0]), short(n[1])))
1061 search.append(n) # schedule branch range for scanning
1100 search.append(n) # schedule branch range for scanning
1062 seenbranch[n] = 1
1101 seenbranch[n] = 1
1063 else:
1102 else:
1064 if n[1] not in seen and n[1] not in fetch:
1103 if n[1] not in seen and n[1] not in fetch:
1065 if n[2] in m and n[3] in m:
1104 if n[2] in m and n[3] in m:
1066 self.ui.debug(_("found new changeset %s\n") %
1105 self.ui.debug(_("found new changeset %s\n") %
1067 short(n[1]))
1106 short(n[1]))
1068 fetch[n[1]] = 1 # earliest unknown
1107 fetch[n[1]] = 1 # earliest unknown
1069 for p in n[2:4]:
1108 for p in n[2:4]:
1070 if p in m:
1109 if p in m:
1071 base[p] = 1 # latest known
1110 base[p] = 1 # latest known
1072
1111
1073 for p in n[2:4]:
1112 for p in n[2:4]:
1074 if p not in req and p not in m:
1113 if p not in req and p not in m:
1075 r.append(p)
1114 r.append(p)
1076 req[p] = 1
1115 req[p] = 1
1077 seen[n[0]] = 1
1116 seen[n[0]] = 1
1078
1117
1079 if r:
1118 if r:
1080 reqcnt += 1
1119 reqcnt += 1
1081 self.ui.debug(_("request %d: %s\n") %
1120 self.ui.debug(_("request %d: %s\n") %
1082 (reqcnt, " ".join(map(short, r))))
1121 (reqcnt, " ".join(map(short, r))))
1083 for p in range(0, len(r), 10):
1122 for p in range(0, len(r), 10):
1084 for b in remote.branches(r[p:p+10]):
1123 for b in remote.branches(r[p:p+10]):
1085 self.ui.debug(_("received %s:%s\n") %
1124 self.ui.debug(_("received %s:%s\n") %
1086 (short(b[0]), short(b[1])))
1125 (short(b[0]), short(b[1])))
1087 unknown.append(b)
1126 unknown.append(b)
1088
1127
1089 # do binary search on the branches we found
1128 # do binary search on the branches we found
1090 while search:
1129 while search:
1091 n = search.pop(0)
1130 n = search.pop(0)
1092 reqcnt += 1
1131 reqcnt += 1
1093 l = remote.between([(n[0], n[1])])[0]
1132 l = remote.between([(n[0], n[1])])[0]
1094 l.append(n[1])
1133 l.append(n[1])
1095 p = n[0]
1134 p = n[0]
1096 f = 1
1135 f = 1
1097 for i in l:
1136 for i in l:
1098 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1137 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1099 if i in m:
1138 if i in m:
1100 if f <= 2:
1139 if f <= 2:
1101 self.ui.debug(_("found new branch changeset %s\n") %
1140 self.ui.debug(_("found new branch changeset %s\n") %
1102 short(p))
1141 short(p))
1103 fetch[p] = 1
1142 fetch[p] = 1
1104 base[i] = 1
1143 base[i] = 1
1105 else:
1144 else:
1106 self.ui.debug(_("narrowed branch search to %s:%s\n")
1145 self.ui.debug(_("narrowed branch search to %s:%s\n")
1107 % (short(p), short(i)))
1146 % (short(p), short(i)))
1108 search.append((p, i))
1147 search.append((p, i))
1109 break
1148 break
1110 p, f = i, f * 2
1149 p, f = i, f * 2
1111
1150
1112 # sanity check our fetch list
1151 # sanity check our fetch list
1113 for f in fetch.keys():
1152 for f in fetch.keys():
1114 if f in m:
1153 if f in m:
1115 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1154 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1116
1155
1117 if base.keys() == [nullid]:
1156 if base.keys() == [nullid]:
1118 if force:
1157 if force:
1119 self.ui.warn(_("warning: repository is unrelated\n"))
1158 self.ui.warn(_("warning: repository is unrelated\n"))
1120 else:
1159 else:
1121 raise util.Abort(_("repository is unrelated"))
1160 raise util.Abort(_("repository is unrelated"))
1122
1161
1123 self.ui.debug(_("found new changesets starting at ") +
1162 self.ui.debug(_("found new changesets starting at ") +
1124 " ".join([short(f) for f in fetch]) + "\n")
1163 " ".join([short(f) for f in fetch]) + "\n")
1125
1164
1126 self.ui.debug(_("%d total queries\n") % reqcnt)
1165 self.ui.debug(_("%d total queries\n") % reqcnt)
1127
1166
1128 return fetch.keys()
1167 return fetch.keys()
1129
1168
1130 def findoutgoing(self, remote, base=None, heads=None, force=False):
1169 def findoutgoing(self, remote, base=None, heads=None, force=False):
1131 """Return list of nodes that are roots of subsets not in remote
1170 """Return list of nodes that are roots of subsets not in remote
1132
1171
1133 If base dict is specified, assume that these nodes and their parents
1172 If base dict is specified, assume that these nodes and their parents
1134 exist on the remote side.
1173 exist on the remote side.
1135 If a list of heads is specified, return only nodes which are heads
1174 If a list of heads is specified, return only nodes which are heads
1136 or ancestors of these heads, and return a second element which
1175 or ancestors of these heads, and return a second element which
1137 contains all remote heads which get new children.
1176 contains all remote heads which get new children.
1138 """
1177 """
1139 if base == None:
1178 if base == None:
1140 base = {}
1179 base = {}
1141 self.findincoming(remote, base, heads, force=force)
1180 self.findincoming(remote, base, heads, force=force)
1142
1181
1143 self.ui.debug(_("common changesets up to ")
1182 self.ui.debug(_("common changesets up to ")
1144 + " ".join(map(short, base.keys())) + "\n")
1183 + " ".join(map(short, base.keys())) + "\n")
1145
1184
1146 remain = dict.fromkeys(self.changelog.nodemap)
1185 remain = dict.fromkeys(self.changelog.nodemap)
1147
1186
1148 # prune everything remote has from the tree
1187 # prune everything remote has from the tree
1149 del remain[nullid]
1188 del remain[nullid]
1150 remove = base.keys()
1189 remove = base.keys()
1151 while remove:
1190 while remove:
1152 n = remove.pop(0)
1191 n = remove.pop(0)
1153 if n in remain:
1192 if n in remain:
1154 del remain[n]
1193 del remain[n]
1155 for p in self.changelog.parents(n):
1194 for p in self.changelog.parents(n):
1156 remove.append(p)
1195 remove.append(p)
1157
1196
1158 # find every node whose parents have been pruned
1197 # find every node whose parents have been pruned
1159 subset = []
1198 subset = []
1160 # find every remote head that will get new children
1199 # find every remote head that will get new children
1161 updated_heads = {}
1200 updated_heads = {}
1162 for n in remain:
1201 for n in remain:
1163 p1, p2 = self.changelog.parents(n)
1202 p1, p2 = self.changelog.parents(n)
1164 if p1 not in remain and p2 not in remain:
1203 if p1 not in remain and p2 not in remain:
1165 subset.append(n)
1204 subset.append(n)
1166 if heads:
1205 if heads:
1167 if p1 in heads:
1206 if p1 in heads:
1168 updated_heads[p1] = True
1207 updated_heads[p1] = True
1169 if p2 in heads:
1208 if p2 in heads:
1170 updated_heads[p2] = True
1209 updated_heads[p2] = True
1171
1210
1172 # this is the set of all roots we have to push
1211 # this is the set of all roots we have to push
1173 if heads:
1212 if heads:
1174 return subset, updated_heads.keys()
1213 return subset, updated_heads.keys()
1175 else:
1214 else:
1176 return subset
1215 return subset
1177
1216
1178 def pull(self, remote, heads=None, force=False, lock=None):
1217 def pull(self, remote, heads=None, force=False, lock=None):
1179 mylock = False
1218 mylock = False
1180 if not lock:
1219 if not lock:
1181 lock = self.lock()
1220 lock = self.lock()
1182 mylock = True
1221 mylock = True
1183
1222
1184 try:
1223 try:
1185 fetch = self.findincoming(remote, force=force)
1224 fetch = self.findincoming(remote, force=force)
1186 if fetch == [nullid]:
1225 if fetch == [nullid]:
1187 self.ui.status(_("requesting all changes\n"))
1226 self.ui.status(_("requesting all changes\n"))
1188
1227
1189 if not fetch:
1228 if not fetch:
1190 self.ui.status(_("no changes found\n"))
1229 self.ui.status(_("no changes found\n"))
1191 return 0
1230 return 0
1192
1231
1193 if heads is None:
1232 if heads is None:
1194 cg = remote.changegroup(fetch, 'pull')
1233 cg = remote.changegroup(fetch, 'pull')
1195 else:
1234 else:
1196 cg = remote.changegroupsubset(fetch, heads, 'pull')
1235 cg = remote.changegroupsubset(fetch, heads, 'pull')
1197 return self.addchangegroup(cg, 'pull', remote.url())
1236 return self.addchangegroup(cg, 'pull', remote.url())
1198 finally:
1237 finally:
1199 if mylock:
1238 if mylock:
1200 lock.release()
1239 lock.release()
1201
1240
1202 def push(self, remote, force=False, revs=None):
1241 def push(self, remote, force=False, revs=None):
1203 # there are two ways to push to remote repo:
1242 # there are two ways to push to remote repo:
1204 #
1243 #
1205 # addchangegroup assumes local user can lock remote
1244 # addchangegroup assumes local user can lock remote
1206 # repo (local filesystem, old ssh servers).
1245 # repo (local filesystem, old ssh servers).
1207 #
1246 #
1208 # unbundle assumes local user cannot lock remote repo (new ssh
1247 # unbundle assumes local user cannot lock remote repo (new ssh
1209 # servers, http servers).
1248 # servers, http servers).
1210
1249
1211 if remote.capable('unbundle'):
1250 if remote.capable('unbundle'):
1212 return self.push_unbundle(remote, force, revs)
1251 return self.push_unbundle(remote, force, revs)
1213 return self.push_addchangegroup(remote, force, revs)
1252 return self.push_addchangegroup(remote, force, revs)
1214
1253
1215 def prepush(self, remote, force, revs):
1254 def prepush(self, remote, force, revs):
1216 base = {}
1255 base = {}
1217 remote_heads = remote.heads()
1256 remote_heads = remote.heads()
1218 inc = self.findincoming(remote, base, remote_heads, force=force)
1257 inc = self.findincoming(remote, base, remote_heads, force=force)
1219 if not force and inc:
1258 if not force and inc:
1220 self.ui.warn(_("abort: unsynced remote changes!\n"))
1259 self.ui.warn(_("abort: unsynced remote changes!\n"))
1221 self.ui.status(_("(did you forget to sync?"
1260 self.ui.status(_("(did you forget to sync?"
1222 " use push -f to force)\n"))
1261 " use push -f to force)\n"))
1223 return None, 1
1262 return None, 1
1224
1263
1225 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1264 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1226 if revs is not None:
1265 if revs is not None:
1227 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1266 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1228 else:
1267 else:
1229 bases, heads = update, self.changelog.heads()
1268 bases, heads = update, self.changelog.heads()
1230
1269
1231 if not bases:
1270 if not bases:
1232 self.ui.status(_("no changes found\n"))
1271 self.ui.status(_("no changes found\n"))
1233 return None, 1
1272 return None, 1
1234 elif not force:
1273 elif not force:
1235 # FIXME we don't properly detect creation of new heads
1274 # FIXME we don't properly detect creation of new heads
1236 # in the push -r case, assume the user knows what he's doing
1275 # in the push -r case, assume the user knows what he's doing
1237 if not revs and len(remote_heads) < len(heads) \
1276 if not revs and len(remote_heads) < len(heads) \
1238 and remote_heads != [nullid]:
1277 and remote_heads != [nullid]:
1239 self.ui.warn(_("abort: push creates new remote branches!\n"))
1278 self.ui.warn(_("abort: push creates new remote branches!\n"))
1240 self.ui.status(_("(did you forget to merge?"
1279 self.ui.status(_("(did you forget to merge?"
1241 " use push -f to force)\n"))
1280 " use push -f to force)\n"))
1242 return None, 1
1281 return None, 1
1243
1282
1244 if revs is None:
1283 if revs is None:
1245 cg = self.changegroup(update, 'push')
1284 cg = self.changegroup(update, 'push')
1246 else:
1285 else:
1247 cg = self.changegroupsubset(update, revs, 'push')
1286 cg = self.changegroupsubset(update, revs, 'push')
1248 return cg, remote_heads
1287 return cg, remote_heads
1249
1288
1250 def push_addchangegroup(self, remote, force, revs):
1289 def push_addchangegroup(self, remote, force, revs):
1251 lock = remote.lock()
1290 lock = remote.lock()
1252
1291
1253 ret = self.prepush(remote, force, revs)
1292 ret = self.prepush(remote, force, revs)
1254 if ret[0] is not None:
1293 if ret[0] is not None:
1255 cg, remote_heads = ret
1294 cg, remote_heads = ret
1256 return remote.addchangegroup(cg, 'push', self.url())
1295 return remote.addchangegroup(cg, 'push', self.url())
1257 return ret[1]
1296 return ret[1]
1258
1297
1259 def push_unbundle(self, remote, force, revs):
1298 def push_unbundle(self, remote, force, revs):
1260 # local repo finds heads on server, finds out what revs it
1299 # local repo finds heads on server, finds out what revs it
1261 # must push. once revs transferred, if server finds it has
1300 # must push. once revs transferred, if server finds it has
1262 # different heads (someone else won commit/push race), server
1301 # different heads (someone else won commit/push race), server
1263 # aborts.
1302 # aborts.
1264
1303
1265 ret = self.prepush(remote, force, revs)
1304 ret = self.prepush(remote, force, revs)
1266 if ret[0] is not None:
1305 if ret[0] is not None:
1267 cg, remote_heads = ret
1306 cg, remote_heads = ret
1268 if force: remote_heads = ['force']
1307 if force: remote_heads = ['force']
1269 return remote.unbundle(cg, remote_heads, 'push')
1308 return remote.unbundle(cg, remote_heads, 'push')
1270 return ret[1]
1309 return ret[1]
1271
1310
1272 def changegroupsubset(self, bases, heads, source):
1311 def changegroupsubset(self, bases, heads, source):
1273 """This function generates a changegroup consisting of all the nodes
1312 """This function generates a changegroup consisting of all the nodes
1274 that are descendents of any of the bases, and ancestors of any of
1313 that are descendents of any of the bases, and ancestors of any of
1275 the heads.
1314 the heads.
1276
1315
1277 It is fairly complex as determining which filenodes and which
1316 It is fairly complex as determining which filenodes and which
1278 manifest nodes need to be included for the changeset to be complete
1317 manifest nodes need to be included for the changeset to be complete
1279 is non-trivial.
1318 is non-trivial.
1280
1319
1281 Another wrinkle is doing the reverse, figuring out which changeset in
1320 Another wrinkle is doing the reverse, figuring out which changeset in
1282 the changegroup a particular filenode or manifestnode belongs to."""
1321 the changegroup a particular filenode or manifestnode belongs to."""
1283
1322
1284 self.hook('preoutgoing', throw=True, source=source)
1323 self.hook('preoutgoing', throw=True, source=source)
1285
1324
1286 # Set up some initial variables
1325 # Set up some initial variables
1287 # Make it easy to refer to self.changelog
1326 # Make it easy to refer to self.changelog
1288 cl = self.changelog
1327 cl = self.changelog
1289 # msng is short for missing - compute the list of changesets in this
1328 # msng is short for missing - compute the list of changesets in this
1290 # changegroup.
1329 # changegroup.
1291 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1330 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1292 # Some bases may turn out to be superfluous, and some heads may be
1331 # Some bases may turn out to be superfluous, and some heads may be
1293 # too. nodesbetween will return the minimal set of bases and heads
1332 # too. nodesbetween will return the minimal set of bases and heads
1294 # necessary to re-create the changegroup.
1333 # necessary to re-create the changegroup.
1295
1334
1296 # Known heads are the list of heads that it is assumed the recipient
1335 # Known heads are the list of heads that it is assumed the recipient
1297 # of this changegroup will know about.
1336 # of this changegroup will know about.
1298 knownheads = {}
1337 knownheads = {}
1299 # We assume that all parents of bases are known heads.
1338 # We assume that all parents of bases are known heads.
1300 for n in bases:
1339 for n in bases:
1301 for p in cl.parents(n):
1340 for p in cl.parents(n):
1302 if p != nullid:
1341 if p != nullid:
1303 knownheads[p] = 1
1342 knownheads[p] = 1
1304 knownheads = knownheads.keys()
1343 knownheads = knownheads.keys()
1305 if knownheads:
1344 if knownheads:
1306 # Now that we know what heads are known, we can compute which
1345 # Now that we know what heads are known, we can compute which
1307 # changesets are known. The recipient must know about all
1346 # changesets are known. The recipient must know about all
1308 # changesets required to reach the known heads from the null
1347 # changesets required to reach the known heads from the null
1309 # changeset.
1348 # changeset.
1310 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1349 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1311 junk = None
1350 junk = None
1312 # Transform the list into an ersatz set.
1351 # Transform the list into an ersatz set.
1313 has_cl_set = dict.fromkeys(has_cl_set)
1352 has_cl_set = dict.fromkeys(has_cl_set)
1314 else:
1353 else:
1315 # If there were no known heads, the recipient cannot be assumed to
1354 # If there were no known heads, the recipient cannot be assumed to
1316 # know about any changesets.
1355 # know about any changesets.
1317 has_cl_set = {}
1356 has_cl_set = {}
1318
1357
1319 # Make it easy to refer to self.manifest
1358 # Make it easy to refer to self.manifest
1320 mnfst = self.manifest
1359 mnfst = self.manifest
1321 # We don't know which manifests are missing yet
1360 # We don't know which manifests are missing yet
1322 msng_mnfst_set = {}
1361 msng_mnfst_set = {}
1323 # Nor do we know which filenodes are missing.
1362 # Nor do we know which filenodes are missing.
1324 msng_filenode_set = {}
1363 msng_filenode_set = {}
1325
1364
1326 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1365 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1327 junk = None
1366 junk = None
1328
1367
1329 # A changeset always belongs to itself, so the changenode lookup
1368 # A changeset always belongs to itself, so the changenode lookup
1330 # function for a changenode is identity.
1369 # function for a changenode is identity.
1331 def identity(x):
1370 def identity(x):
1332 return x
1371 return x
1333
1372
1334 # A function generating function. Sets up an environment for the
1373 # A function generating function. Sets up an environment for the
1335 # inner function.
1374 # inner function.
1336 def cmp_by_rev_func(revlog):
1375 def cmp_by_rev_func(revlog):
1337 # Compare two nodes by their revision number in the environment's
1376 # Compare two nodes by their revision number in the environment's
1338 # revision history. Since the revision number both represents the
1377 # revision history. Since the revision number both represents the
1339 # most efficient order to read the nodes in, and represents a
1378 # most efficient order to read the nodes in, and represents a
1340 # topological sorting of the nodes, this function is often useful.
1379 # topological sorting of the nodes, this function is often useful.
1341 def cmp_by_rev(a, b):
1380 def cmp_by_rev(a, b):
1342 return cmp(revlog.rev(a), revlog.rev(b))
1381 return cmp(revlog.rev(a), revlog.rev(b))
1343 return cmp_by_rev
1382 return cmp_by_rev
1344
1383
1345 # If we determine that a particular file or manifest node must be a
1384 # If we determine that a particular file or manifest node must be a
1346 # node that the recipient of the changegroup will already have, we can
1385 # node that the recipient of the changegroup will already have, we can
1347 # also assume the recipient will have all the parents. This function
1386 # also assume the recipient will have all the parents. This function
1348 # prunes them from the set of missing nodes.
1387 # prunes them from the set of missing nodes.
1349 def prune_parents(revlog, hasset, msngset):
1388 def prune_parents(revlog, hasset, msngset):
1350 haslst = hasset.keys()
1389 haslst = hasset.keys()
1351 haslst.sort(cmp_by_rev_func(revlog))
1390 haslst.sort(cmp_by_rev_func(revlog))
1352 for node in haslst:
1391 for node in haslst:
1353 parentlst = [p for p in revlog.parents(node) if p != nullid]
1392 parentlst = [p for p in revlog.parents(node) if p != nullid]
1354 while parentlst:
1393 while parentlst:
1355 n = parentlst.pop()
1394 n = parentlst.pop()
1356 if n not in hasset:
1395 if n not in hasset:
1357 hasset[n] = 1
1396 hasset[n] = 1
1358 p = [p for p in revlog.parents(n) if p != nullid]
1397 p = [p for p in revlog.parents(n) if p != nullid]
1359 parentlst.extend(p)
1398 parentlst.extend(p)
1360 for n in hasset:
1399 for n in hasset:
1361 msngset.pop(n, None)
1400 msngset.pop(n, None)
1362
1401
1363 # This is a function generating function used to set up an environment
1402 # This is a function generating function used to set up an environment
1364 # for the inner function to execute in.
1403 # for the inner function to execute in.
1365 def manifest_and_file_collector(changedfileset):
1404 def manifest_and_file_collector(changedfileset):
1366 # This is an information gathering function that gathers
1405 # This is an information gathering function that gathers
1367 # information from each changeset node that goes out as part of
1406 # information from each changeset node that goes out as part of
1368 # the changegroup. The information gathered is a list of which
1407 # the changegroup. The information gathered is a list of which
1369 # manifest nodes are potentially required (the recipient may
1408 # manifest nodes are potentially required (the recipient may
1370 # already have them) and total list of all files which were
1409 # already have them) and total list of all files which were
1371 # changed in any changeset in the changegroup.
1410 # changed in any changeset in the changegroup.
1372 #
1411 #
1373 # We also remember the first changenode we saw any manifest
1412 # We also remember the first changenode we saw any manifest
1374 # referenced by so we can later determine which changenode 'owns'
1413 # referenced by so we can later determine which changenode 'owns'
1375 # the manifest.
1414 # the manifest.
1376 def collect_manifests_and_files(clnode):
1415 def collect_manifests_and_files(clnode):
1377 c = cl.read(clnode)
1416 c = cl.read(clnode)
1378 for f in c[3]:
1417 for f in c[3]:
1379 # This is to make sure we only have one instance of each
1418 # This is to make sure we only have one instance of each
1380 # filename string for each filename.
1419 # filename string for each filename.
1381 changedfileset.setdefault(f, f)
1420 changedfileset.setdefault(f, f)
1382 msng_mnfst_set.setdefault(c[0], clnode)
1421 msng_mnfst_set.setdefault(c[0], clnode)
1383 return collect_manifests_and_files
1422 return collect_manifests_and_files
1384
1423
1385 # Figure out which manifest nodes (of the ones we think might be part
1424 # Figure out which manifest nodes (of the ones we think might be part
1386 # of the changegroup) the recipient must know about and remove them
1425 # of the changegroup) the recipient must know about and remove them
1387 # from the changegroup.
1426 # from the changegroup.
1388 def prune_manifests():
1427 def prune_manifests():
1389 has_mnfst_set = {}
1428 has_mnfst_set = {}
1390 for n in msng_mnfst_set:
1429 for n in msng_mnfst_set:
1391 # If a 'missing' manifest thinks it belongs to a changenode
1430 # If a 'missing' manifest thinks it belongs to a changenode
1392 # the recipient is assumed to have, obviously the recipient
1431 # the recipient is assumed to have, obviously the recipient
1393 # must have that manifest.
1432 # must have that manifest.
1394 linknode = cl.node(mnfst.linkrev(n))
1433 linknode = cl.node(mnfst.linkrev(n))
1395 if linknode in has_cl_set:
1434 if linknode in has_cl_set:
1396 has_mnfst_set[n] = 1
1435 has_mnfst_set[n] = 1
1397 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1436 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1398
1437
1399 # Use the information collected in collect_manifests_and_files to say
1438 # Use the information collected in collect_manifests_and_files to say
1400 # which changenode any manifestnode belongs to.
1439 # which changenode any manifestnode belongs to.
1401 def lookup_manifest_link(mnfstnode):
1440 def lookup_manifest_link(mnfstnode):
1402 return msng_mnfst_set[mnfstnode]
1441 return msng_mnfst_set[mnfstnode]
1403
1442
1404 # A function generating function that sets up the initial environment
1443 # A function generating function that sets up the initial environment
1405 # the inner function.
1444 # the inner function.
1406 def filenode_collector(changedfiles):
1445 def filenode_collector(changedfiles):
1407 next_rev = [0]
1446 next_rev = [0]
1408 # This gathers information from each manifestnode included in the
1447 # This gathers information from each manifestnode included in the
1409 # changegroup about which filenodes the manifest node references
1448 # changegroup about which filenodes the manifest node references
1410 # so we can include those in the changegroup too.
1449 # so we can include those in the changegroup too.
1411 #
1450 #
1412 # It also remembers which changenode each filenode belongs to. It
1451 # It also remembers which changenode each filenode belongs to. It
1413 # does this by assuming the a filenode belongs to the changenode
1452 # does this by assuming the a filenode belongs to the changenode
1414 # the first manifest that references it belongs to.
1453 # the first manifest that references it belongs to.
1415 def collect_msng_filenodes(mnfstnode):
1454 def collect_msng_filenodes(mnfstnode):
1416 r = mnfst.rev(mnfstnode)
1455 r = mnfst.rev(mnfstnode)
1417 if r == next_rev[0]:
1456 if r == next_rev[0]:
1418 # If the last rev we looked at was the one just previous,
1457 # If the last rev we looked at was the one just previous,
1419 # we only need to see a diff.
1458 # we only need to see a diff.
1420 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1459 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1421 # For each line in the delta
1460 # For each line in the delta
1422 for dline in delta.splitlines():
1461 for dline in delta.splitlines():
1423 # get the filename and filenode for that line
1462 # get the filename and filenode for that line
1424 f, fnode = dline.split('\0')
1463 f, fnode = dline.split('\0')
1425 fnode = bin(fnode[:40])
1464 fnode = bin(fnode[:40])
1426 f = changedfiles.get(f, None)
1465 f = changedfiles.get(f, None)
1427 # And if the file is in the list of files we care
1466 # And if the file is in the list of files we care
1428 # about.
1467 # about.
1429 if f is not None:
1468 if f is not None:
1430 # Get the changenode this manifest belongs to
1469 # Get the changenode this manifest belongs to
1431 clnode = msng_mnfst_set[mnfstnode]
1470 clnode = msng_mnfst_set[mnfstnode]
1432 # Create the set of filenodes for the file if
1471 # Create the set of filenodes for the file if
1433 # there isn't one already.
1472 # there isn't one already.
1434 ndset = msng_filenode_set.setdefault(f, {})
1473 ndset = msng_filenode_set.setdefault(f, {})
1435 # And set the filenode's changelog node to the
1474 # And set the filenode's changelog node to the
1436 # manifest's if it hasn't been set already.
1475 # manifest's if it hasn't been set already.
1437 ndset.setdefault(fnode, clnode)
1476 ndset.setdefault(fnode, clnode)
1438 else:
1477 else:
1439 # Otherwise we need a full manifest.
1478 # Otherwise we need a full manifest.
1440 m = mnfst.read(mnfstnode)
1479 m = mnfst.read(mnfstnode)
1441 # For every file in we care about.
1480 # For every file in we care about.
1442 for f in changedfiles:
1481 for f in changedfiles:
1443 fnode = m.get(f, None)
1482 fnode = m.get(f, None)
1444 # If it's in the manifest
1483 # If it's in the manifest
1445 if fnode is not None:
1484 if fnode is not None:
1446 # See comments above.
1485 # See comments above.
1447 clnode = msng_mnfst_set[mnfstnode]
1486 clnode = msng_mnfst_set[mnfstnode]
1448 ndset = msng_filenode_set.setdefault(f, {})
1487 ndset = msng_filenode_set.setdefault(f, {})
1449 ndset.setdefault(fnode, clnode)
1488 ndset.setdefault(fnode, clnode)
1450 # Remember the revision we hope to see next.
1489 # Remember the revision we hope to see next.
1451 next_rev[0] = r + 1
1490 next_rev[0] = r + 1
1452 return collect_msng_filenodes
1491 return collect_msng_filenodes
1453
1492
1454 # We have a list of filenodes we think we need for a file, lets remove
1493 # We have a list of filenodes we think we need for a file, lets remove
1455 # all those we now the recipient must have.
1494 # all those we now the recipient must have.
1456 def prune_filenodes(f, filerevlog):
1495 def prune_filenodes(f, filerevlog):
1457 msngset = msng_filenode_set[f]
1496 msngset = msng_filenode_set[f]
1458 hasset = {}
1497 hasset = {}
1459 # If a 'missing' filenode thinks it belongs to a changenode we
1498 # If a 'missing' filenode thinks it belongs to a changenode we
1460 # assume the recipient must have, then the recipient must have
1499 # assume the recipient must have, then the recipient must have
1461 # that filenode.
1500 # that filenode.
1462 for n in msngset:
1501 for n in msngset:
1463 clnode = cl.node(filerevlog.linkrev(n))
1502 clnode = cl.node(filerevlog.linkrev(n))
1464 if clnode in has_cl_set:
1503 if clnode in has_cl_set:
1465 hasset[n] = 1
1504 hasset[n] = 1
1466 prune_parents(filerevlog, hasset, msngset)
1505 prune_parents(filerevlog, hasset, msngset)
1467
1506
1468 # A function generator function that sets up the a context for the
1507 # A function generator function that sets up the a context for the
1469 # inner function.
1508 # inner function.
1470 def lookup_filenode_link_func(fname):
1509 def lookup_filenode_link_func(fname):
1471 msngset = msng_filenode_set[fname]
1510 msngset = msng_filenode_set[fname]
1472 # Lookup the changenode the filenode belongs to.
1511 # Lookup the changenode the filenode belongs to.
1473 def lookup_filenode_link(fnode):
1512 def lookup_filenode_link(fnode):
1474 return msngset[fnode]
1513 return msngset[fnode]
1475 return lookup_filenode_link
1514 return lookup_filenode_link
1476
1515
1477 # Now that we have all theses utility functions to help out and
1516 # Now that we have all theses utility functions to help out and
1478 # logically divide up the task, generate the group.
1517 # logically divide up the task, generate the group.
1479 def gengroup():
1518 def gengroup():
1480 # The set of changed files starts empty.
1519 # The set of changed files starts empty.
1481 changedfiles = {}
1520 changedfiles = {}
1482 # Create a changenode group generator that will call our functions
1521 # Create a changenode group generator that will call our functions
1483 # back to lookup the owning changenode and collect information.
1522 # back to lookup the owning changenode and collect information.
1484 group = cl.group(msng_cl_lst, identity,
1523 group = cl.group(msng_cl_lst, identity,
1485 manifest_and_file_collector(changedfiles))
1524 manifest_and_file_collector(changedfiles))
1486 for chnk in group:
1525 for chnk in group:
1487 yield chnk
1526 yield chnk
1488
1527
1489 # The list of manifests has been collected by the generator
1528 # The list of manifests has been collected by the generator
1490 # calling our functions back.
1529 # calling our functions back.
1491 prune_manifests()
1530 prune_manifests()
1492 msng_mnfst_lst = msng_mnfst_set.keys()
1531 msng_mnfst_lst = msng_mnfst_set.keys()
1493 # Sort the manifestnodes by revision number.
1532 # Sort the manifestnodes by revision number.
1494 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1533 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1495 # Create a generator for the manifestnodes that calls our lookup
1534 # Create a generator for the manifestnodes that calls our lookup
1496 # and data collection functions back.
1535 # and data collection functions back.
1497 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1536 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1498 filenode_collector(changedfiles))
1537 filenode_collector(changedfiles))
1499 for chnk in group:
1538 for chnk in group:
1500 yield chnk
1539 yield chnk
1501
1540
1502 # These are no longer needed, dereference and toss the memory for
1541 # These are no longer needed, dereference and toss the memory for
1503 # them.
1542 # them.
1504 msng_mnfst_lst = None
1543 msng_mnfst_lst = None
1505 msng_mnfst_set.clear()
1544 msng_mnfst_set.clear()
1506
1545
1507 changedfiles = changedfiles.keys()
1546 changedfiles = changedfiles.keys()
1508 changedfiles.sort()
1547 changedfiles.sort()
1509 # Go through all our files in order sorted by name.
1548 # Go through all our files in order sorted by name.
1510 for fname in changedfiles:
1549 for fname in changedfiles:
1511 filerevlog = self.file(fname)
1550 filerevlog = self.file(fname)
1512 # Toss out the filenodes that the recipient isn't really
1551 # Toss out the filenodes that the recipient isn't really
1513 # missing.
1552 # missing.
1514 if msng_filenode_set.has_key(fname):
1553 if msng_filenode_set.has_key(fname):
1515 prune_filenodes(fname, filerevlog)
1554 prune_filenodes(fname, filerevlog)
1516 msng_filenode_lst = msng_filenode_set[fname].keys()
1555 msng_filenode_lst = msng_filenode_set[fname].keys()
1517 else:
1556 else:
1518 msng_filenode_lst = []
1557 msng_filenode_lst = []
1519 # If any filenodes are left, generate the group for them,
1558 # If any filenodes are left, generate the group for them,
1520 # otherwise don't bother.
1559 # otherwise don't bother.
1521 if len(msng_filenode_lst) > 0:
1560 if len(msng_filenode_lst) > 0:
1522 yield changegroup.genchunk(fname)
1561 yield changegroup.genchunk(fname)
1523 # Sort the filenodes by their revision #
1562 # Sort the filenodes by their revision #
1524 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1563 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1525 # Create a group generator and only pass in a changenode
1564 # Create a group generator and only pass in a changenode
1526 # lookup function as we need to collect no information
1565 # lookup function as we need to collect no information
1527 # from filenodes.
1566 # from filenodes.
1528 group = filerevlog.group(msng_filenode_lst,
1567 group = filerevlog.group(msng_filenode_lst,
1529 lookup_filenode_link_func(fname))
1568 lookup_filenode_link_func(fname))
1530 for chnk in group:
1569 for chnk in group:
1531 yield chnk
1570 yield chnk
1532 if msng_filenode_set.has_key(fname):
1571 if msng_filenode_set.has_key(fname):
1533 # Don't need this anymore, toss it to free memory.
1572 # Don't need this anymore, toss it to free memory.
1534 del msng_filenode_set[fname]
1573 del msng_filenode_set[fname]
1535 # Signal that no more groups are left.
1574 # Signal that no more groups are left.
1536 yield changegroup.closechunk()
1575 yield changegroup.closechunk()
1537
1576
1538 if msng_cl_lst:
1577 if msng_cl_lst:
1539 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1578 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1540
1579
1541 return util.chunkbuffer(gengroup())
1580 return util.chunkbuffer(gengroup())
1542
1581
1543 def changegroup(self, basenodes, source):
1582 def changegroup(self, basenodes, source):
1544 """Generate a changegroup of all nodes that we have that a recipient
1583 """Generate a changegroup of all nodes that we have that a recipient
1545 doesn't.
1584 doesn't.
1546
1585
1547 This is much easier than the previous function as we can assume that
1586 This is much easier than the previous function as we can assume that
1548 the recipient has any changenode we aren't sending them."""
1587 the recipient has any changenode we aren't sending them."""
1549
1588
1550 self.hook('preoutgoing', throw=True, source=source)
1589 self.hook('preoutgoing', throw=True, source=source)
1551
1590
1552 cl = self.changelog
1591 cl = self.changelog
1553 nodes = cl.nodesbetween(basenodes, None)[0]
1592 nodes = cl.nodesbetween(basenodes, None)[0]
1554 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1593 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1555
1594
1556 def identity(x):
1595 def identity(x):
1557 return x
1596 return x
1558
1597
1559 def gennodelst(revlog):
1598 def gennodelst(revlog):
1560 for r in xrange(0, revlog.count()):
1599 for r in xrange(0, revlog.count()):
1561 n = revlog.node(r)
1600 n = revlog.node(r)
1562 if revlog.linkrev(n) in revset:
1601 if revlog.linkrev(n) in revset:
1563 yield n
1602 yield n
1564
1603
1565 def changed_file_collector(changedfileset):
1604 def changed_file_collector(changedfileset):
1566 def collect_changed_files(clnode):
1605 def collect_changed_files(clnode):
1567 c = cl.read(clnode)
1606 c = cl.read(clnode)
1568 for fname in c[3]:
1607 for fname in c[3]:
1569 changedfileset[fname] = 1
1608 changedfileset[fname] = 1
1570 return collect_changed_files
1609 return collect_changed_files
1571
1610
1572 def lookuprevlink_func(revlog):
1611 def lookuprevlink_func(revlog):
1573 def lookuprevlink(n):
1612 def lookuprevlink(n):
1574 return cl.node(revlog.linkrev(n))
1613 return cl.node(revlog.linkrev(n))
1575 return lookuprevlink
1614 return lookuprevlink
1576
1615
1577 def gengroup():
1616 def gengroup():
1578 # construct a list of all changed files
1617 # construct a list of all changed files
1579 changedfiles = {}
1618 changedfiles = {}
1580
1619
1581 for chnk in cl.group(nodes, identity,
1620 for chnk in cl.group(nodes, identity,
1582 changed_file_collector(changedfiles)):
1621 changed_file_collector(changedfiles)):
1583 yield chnk
1622 yield chnk
1584 changedfiles = changedfiles.keys()
1623 changedfiles = changedfiles.keys()
1585 changedfiles.sort()
1624 changedfiles.sort()
1586
1625
1587 mnfst = self.manifest
1626 mnfst = self.manifest
1588 nodeiter = gennodelst(mnfst)
1627 nodeiter = gennodelst(mnfst)
1589 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1628 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1590 yield chnk
1629 yield chnk
1591
1630
1592 for fname in changedfiles:
1631 for fname in changedfiles:
1593 filerevlog = self.file(fname)
1632 filerevlog = self.file(fname)
1594 nodeiter = gennodelst(filerevlog)
1633 nodeiter = gennodelst(filerevlog)
1595 nodeiter = list(nodeiter)
1634 nodeiter = list(nodeiter)
1596 if nodeiter:
1635 if nodeiter:
1597 yield changegroup.genchunk(fname)
1636 yield changegroup.genchunk(fname)
1598 lookup = lookuprevlink_func(filerevlog)
1637 lookup = lookuprevlink_func(filerevlog)
1599 for chnk in filerevlog.group(nodeiter, lookup):
1638 for chnk in filerevlog.group(nodeiter, lookup):
1600 yield chnk
1639 yield chnk
1601
1640
1602 yield changegroup.closechunk()
1641 yield changegroup.closechunk()
1603
1642
1604 if nodes:
1643 if nodes:
1605 self.hook('outgoing', node=hex(nodes[0]), source=source)
1644 self.hook('outgoing', node=hex(nodes[0]), source=source)
1606
1645
1607 return util.chunkbuffer(gengroup())
1646 return util.chunkbuffer(gengroup())
1608
1647
1609 def addchangegroup(self, source, srctype, url):
1648 def addchangegroup(self, source, srctype, url):
1610 """add changegroup to repo.
1649 """add changegroup to repo.
1611 returns number of heads modified or added + 1."""
1650 returns number of heads modified or added + 1."""
1612
1651
1613 def csmap(x):
1652 def csmap(x):
1614 self.ui.debug(_("add changeset %s\n") % short(x))
1653 self.ui.debug(_("add changeset %s\n") % short(x))
1615 return cl.count()
1654 return cl.count()
1616
1655
1617 def revmap(x):
1656 def revmap(x):
1618 return cl.rev(x)
1657 return cl.rev(x)
1619
1658
1620 if not source:
1659 if not source:
1621 return 0
1660 return 0
1622
1661
1623 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1662 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1624
1663
1625 changesets = files = revisions = 0
1664 changesets = files = revisions = 0
1626
1665
1627 tr = self.transaction()
1666 tr = self.transaction()
1628
1667
1629 # write changelog data to temp files so concurrent readers will not see
1668 # write changelog data to temp files so concurrent readers will not see
1630 # inconsistent view
1669 # inconsistent view
1631 cl = None
1670 cl = None
1632 try:
1671 try:
1633 cl = appendfile.appendchangelog(self.opener, self.changelog.version)
1672 cl = appendfile.appendchangelog(self.opener, self.changelog.version)
1634
1673
1635 oldheads = len(cl.heads())
1674 oldheads = len(cl.heads())
1636
1675
1637 # pull off the changeset group
1676 # pull off the changeset group
1638 self.ui.status(_("adding changesets\n"))
1677 self.ui.status(_("adding changesets\n"))
1639 cor = cl.count() - 1
1678 cor = cl.count() - 1
1640 chunkiter = changegroup.chunkiter(source)
1679 chunkiter = changegroup.chunkiter(source)
1641 if cl.addgroup(chunkiter, csmap, tr, 1) is None:
1680 if cl.addgroup(chunkiter, csmap, tr, 1) is None:
1642 raise util.Abort(_("received changelog group is empty"))
1681 raise util.Abort(_("received changelog group is empty"))
1643 cnr = cl.count() - 1
1682 cnr = cl.count() - 1
1644 changesets = cnr - cor
1683 changesets = cnr - cor
1645
1684
1646 # pull off the manifest group
1685 # pull off the manifest group
1647 self.ui.status(_("adding manifests\n"))
1686 self.ui.status(_("adding manifests\n"))
1648 chunkiter = changegroup.chunkiter(source)
1687 chunkiter = changegroup.chunkiter(source)
1649 # no need to check for empty manifest group here:
1688 # no need to check for empty manifest group here:
1650 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1689 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1651 # no new manifest will be created and the manifest group will
1690 # no new manifest will be created and the manifest group will
1652 # be empty during the pull
1691 # be empty during the pull
1653 self.manifest.addgroup(chunkiter, revmap, tr)
1692 self.manifest.addgroup(chunkiter, revmap, tr)
1654
1693
1655 # process the files
1694 # process the files
1656 self.ui.status(_("adding file changes\n"))
1695 self.ui.status(_("adding file changes\n"))
1657 while 1:
1696 while 1:
1658 f = changegroup.getchunk(source)
1697 f = changegroup.getchunk(source)
1659 if not f:
1698 if not f:
1660 break
1699 break
1661 self.ui.debug(_("adding %s revisions\n") % f)
1700 self.ui.debug(_("adding %s revisions\n") % f)
1662 fl = self.file(f)
1701 fl = self.file(f)
1663 o = fl.count()
1702 o = fl.count()
1664 chunkiter = changegroup.chunkiter(source)
1703 chunkiter = changegroup.chunkiter(source)
1665 if fl.addgroup(chunkiter, revmap, tr) is None:
1704 if fl.addgroup(chunkiter, revmap, tr) is None:
1666 raise util.Abort(_("received file revlog group is empty"))
1705 raise util.Abort(_("received file revlog group is empty"))
1667 revisions += fl.count() - o
1706 revisions += fl.count() - o
1668 files += 1
1707 files += 1
1669
1708
1670 cl.writedata()
1709 cl.writedata()
1671 finally:
1710 finally:
1672 if cl:
1711 if cl:
1673 cl.cleanup()
1712 cl.cleanup()
1674
1713
1675 # make changelog see real files again
1714 # make changelog see real files again
1676 self.changelog = changelog.changelog(self.opener, self.changelog.version)
1715 self.changelog = changelog.changelog(self.opener, self.changelog.version)
1677 self.changelog.checkinlinesize(tr)
1716 self.changelog.checkinlinesize(tr)
1678
1717
1679 newheads = len(self.changelog.heads())
1718 newheads = len(self.changelog.heads())
1680 heads = ""
1719 heads = ""
1681 if oldheads and newheads != oldheads:
1720 if oldheads and newheads != oldheads:
1682 heads = _(" (%+d heads)") % (newheads - oldheads)
1721 heads = _(" (%+d heads)") % (newheads - oldheads)
1683
1722
1684 self.ui.status(_("added %d changesets"
1723 self.ui.status(_("added %d changesets"
1685 " with %d changes to %d files%s\n")
1724 " with %d changes to %d files%s\n")
1686 % (changesets, revisions, files, heads))
1725 % (changesets, revisions, files, heads))
1687
1726
1688 if changesets > 0:
1727 if changesets > 0:
1689 self.hook('pretxnchangegroup', throw=True,
1728 self.hook('pretxnchangegroup', throw=True,
1690 node=hex(self.changelog.node(cor+1)), source=srctype,
1729 node=hex(self.changelog.node(cor+1)), source=srctype,
1691 url=url)
1730 url=url)
1692
1731
1693 tr.close()
1732 tr.close()
1694
1733
1695 if changesets > 0:
1734 if changesets > 0:
1696 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1735 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1697 source=srctype, url=url)
1736 source=srctype, url=url)
1698
1737
1699 for i in range(cor + 1, cnr + 1):
1738 for i in range(cor + 1, cnr + 1):
1700 self.hook("incoming", node=hex(self.changelog.node(i)),
1739 self.hook("incoming", node=hex(self.changelog.node(i)),
1701 source=srctype, url=url)
1740 source=srctype, url=url)
1702
1741
1703 return newheads - oldheads + 1
1742 return newheads - oldheads + 1
1704
1743
1705
1744
1706 def stream_in(self, remote):
1745 def stream_in(self, remote):
1707 fp = remote.stream_out()
1746 fp = remote.stream_out()
1708 resp = int(fp.readline())
1747 resp = int(fp.readline())
1709 if resp != 0:
1748 if resp != 0:
1710 raise util.Abort(_('operation forbidden by server'))
1749 raise util.Abort(_('operation forbidden by server'))
1711 self.ui.status(_('streaming all changes\n'))
1750 self.ui.status(_('streaming all changes\n'))
1712 total_files, total_bytes = map(int, fp.readline().split(' ', 1))
1751 total_files, total_bytes = map(int, fp.readline().split(' ', 1))
1713 self.ui.status(_('%d files to transfer, %s of data\n') %
1752 self.ui.status(_('%d files to transfer, %s of data\n') %
1714 (total_files, util.bytecount(total_bytes)))
1753 (total_files, util.bytecount(total_bytes)))
1715 start = time.time()
1754 start = time.time()
1716 for i in xrange(total_files):
1755 for i in xrange(total_files):
1717 name, size = fp.readline().split('\0', 1)
1756 name, size = fp.readline().split('\0', 1)
1718 size = int(size)
1757 size = int(size)
1719 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1758 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1720 ofp = self.opener(name, 'w')
1759 ofp = self.opener(name, 'w')
1721 for chunk in util.filechunkiter(fp, limit=size):
1760 for chunk in util.filechunkiter(fp, limit=size):
1722 ofp.write(chunk)
1761 ofp.write(chunk)
1723 ofp.close()
1762 ofp.close()
1724 elapsed = time.time() - start
1763 elapsed = time.time() - start
1725 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1764 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1726 (util.bytecount(total_bytes), elapsed,
1765 (util.bytecount(total_bytes), elapsed,
1727 util.bytecount(total_bytes / elapsed)))
1766 util.bytecount(total_bytes / elapsed)))
1728 self.reload()
1767 self.reload()
1729 return len(self.heads()) + 1
1768 return len(self.heads()) + 1
1730
1769
1731 def clone(self, remote, heads=[], stream=False):
1770 def clone(self, remote, heads=[], stream=False):
1732 '''clone remote repository.
1771 '''clone remote repository.
1733
1772
1734 keyword arguments:
1773 keyword arguments:
1735 heads: list of revs to clone (forces use of pull)
1774 heads: list of revs to clone (forces use of pull)
1736 stream: use streaming clone if possible'''
1775 stream: use streaming clone if possible'''
1737
1776
1738 # now, all clients that can request uncompressed clones can
1777 # now, all clients that can request uncompressed clones can
1739 # read repo formats supported by all servers that can serve
1778 # read repo formats supported by all servers that can serve
1740 # them.
1779 # them.
1741
1780
1742 # if revlog format changes, client will have to check version
1781 # if revlog format changes, client will have to check version
1743 # and format flags on "stream" capability, and use
1782 # and format flags on "stream" capability, and use
1744 # uncompressed only if compatible.
1783 # uncompressed only if compatible.
1745
1784
1746 if stream and not heads and remote.capable('stream'):
1785 if stream and not heads and remote.capable('stream'):
1747 return self.stream_in(remote)
1786 return self.stream_in(remote)
1748 return self.pull(remote, heads)
1787 return self.pull(remote, heads)
1749
1788
1750 # used to avoid circular references so destructors work
1789 # used to avoid circular references so destructors work
1751 def aftertrans(base):
1790 def aftertrans(base):
1752 p = base
1791 p = base
1753 def a():
1792 def a():
1754 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
1793 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
1755 util.rename(os.path.join(p, "journal.dirstate"),
1794 util.rename(os.path.join(p, "journal.dirstate"),
1756 os.path.join(p, "undo.dirstate"))
1795 os.path.join(p, "undo.dirstate"))
1757 return a
1796 return a
1758
1797
1759 def instance(ui, path, create):
1798 def instance(ui, path, create):
1760 return localrepository(ui, util.drop_scheme('file', path), create)
1799 return localrepository(ui, util.drop_scheme('file', path), create)
1761
1800
1762 def islocal(path):
1801 def islocal(path):
1763 return True
1802 return True
General Comments 0
You need to be logged in to leave comments. Login now