##// END OF EJS Templates
discovery: avoid discovery when local graph is a subset of remote...
Peter Arrenbrecht -
r13742:7abab875 default
parent child Browse files
Show More
@@ -1,643 +1,643 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to transplant changesets from another branch
8 '''command to transplant changesets from another branch
9
9
10 This extension allows you to transplant patches from another branch.
10 This extension allows you to transplant patches from another branch.
11
11
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 map from a changeset hash to its hash in the source repository.
13 map from a changeset hash to its hash in the source repository.
14 '''
14 '''
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 import os, tempfile
17 import os, tempfile
18 from mercurial import bundlerepo, cmdutil, hg, merge, match
18 from mercurial import bundlerepo, cmdutil, hg, merge, match
19 from mercurial import patch, revlog, util, error
19 from mercurial import patch, revlog, util, error
20 from mercurial import revset, templatekw
20 from mercurial import revset, templatekw
21
21
22 class transplantentry(object):
22 class transplantentry(object):
23 def __init__(self, lnode, rnode):
23 def __init__(self, lnode, rnode):
24 self.lnode = lnode
24 self.lnode = lnode
25 self.rnode = rnode
25 self.rnode = rnode
26
26
27 class transplants(object):
27 class transplants(object):
28 def __init__(self, path=None, transplantfile=None, opener=None):
28 def __init__(self, path=None, transplantfile=None, opener=None):
29 self.path = path
29 self.path = path
30 self.transplantfile = transplantfile
30 self.transplantfile = transplantfile
31 self.opener = opener
31 self.opener = opener
32
32
33 if not opener:
33 if not opener:
34 self.opener = util.opener(self.path)
34 self.opener = util.opener(self.path)
35 self.transplants = {}
35 self.transplants = {}
36 self.dirty = False
36 self.dirty = False
37 self.read()
37 self.read()
38
38
39 def read(self):
39 def read(self):
40 abspath = os.path.join(self.path, self.transplantfile)
40 abspath = os.path.join(self.path, self.transplantfile)
41 if self.transplantfile and os.path.exists(abspath):
41 if self.transplantfile and os.path.exists(abspath):
42 for line in self.opener(self.transplantfile).read().splitlines():
42 for line in self.opener(self.transplantfile).read().splitlines():
43 lnode, rnode = map(revlog.bin, line.split(':'))
43 lnode, rnode = map(revlog.bin, line.split(':'))
44 list = self.transplants.setdefault(rnode, [])
44 list = self.transplants.setdefault(rnode, [])
45 list.append(transplantentry(lnode, rnode))
45 list.append(transplantentry(lnode, rnode))
46
46
47 def write(self):
47 def write(self):
48 if self.dirty and self.transplantfile:
48 if self.dirty and self.transplantfile:
49 if not os.path.isdir(self.path):
49 if not os.path.isdir(self.path):
50 os.mkdir(self.path)
50 os.mkdir(self.path)
51 fp = self.opener(self.transplantfile, 'w')
51 fp = self.opener(self.transplantfile, 'w')
52 for list in self.transplants.itervalues():
52 for list in self.transplants.itervalues():
53 for t in list:
53 for t in list:
54 l, r = map(revlog.hex, (t.lnode, t.rnode))
54 l, r = map(revlog.hex, (t.lnode, t.rnode))
55 fp.write(l + ':' + r + '\n')
55 fp.write(l + ':' + r + '\n')
56 fp.close()
56 fp.close()
57 self.dirty = False
57 self.dirty = False
58
58
59 def get(self, rnode):
59 def get(self, rnode):
60 return self.transplants.get(rnode) or []
60 return self.transplants.get(rnode) or []
61
61
62 def set(self, lnode, rnode):
62 def set(self, lnode, rnode):
63 list = self.transplants.setdefault(rnode, [])
63 list = self.transplants.setdefault(rnode, [])
64 list.append(transplantentry(lnode, rnode))
64 list.append(transplantentry(lnode, rnode))
65 self.dirty = True
65 self.dirty = True
66
66
67 def remove(self, transplant):
67 def remove(self, transplant):
68 list = self.transplants.get(transplant.rnode)
68 list = self.transplants.get(transplant.rnode)
69 if list:
69 if list:
70 del list[list.index(transplant)]
70 del list[list.index(transplant)]
71 self.dirty = True
71 self.dirty = True
72
72
73 class transplanter(object):
73 class transplanter(object):
74 def __init__(self, ui, repo):
74 def __init__(self, ui, repo):
75 self.ui = ui
75 self.ui = ui
76 self.path = repo.join('transplant')
76 self.path = repo.join('transplant')
77 self.opener = util.opener(self.path)
77 self.opener = util.opener(self.path)
78 self.transplants = transplants(self.path, 'transplants',
78 self.transplants = transplants(self.path, 'transplants',
79 opener=self.opener)
79 opener=self.opener)
80
80
81 def applied(self, repo, node, parent):
81 def applied(self, repo, node, parent):
82 '''returns True if a node is already an ancestor of parent
82 '''returns True if a node is already an ancestor of parent
83 or has already been transplanted'''
83 or has already been transplanted'''
84 if hasnode(repo, node):
84 if hasnode(repo, node):
85 if node in repo.changelog.reachable(parent, stop=node):
85 if node in repo.changelog.reachable(parent, stop=node):
86 return True
86 return True
87 for t in self.transplants.get(node):
87 for t in self.transplants.get(node):
88 # it might have been stripped
88 # it might have been stripped
89 if not hasnode(repo, t.lnode):
89 if not hasnode(repo, t.lnode):
90 self.transplants.remove(t)
90 self.transplants.remove(t)
91 return False
91 return False
92 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
92 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
93 return True
93 return True
94 return False
94 return False
95
95
96 def apply(self, repo, source, revmap, merges, opts={}):
96 def apply(self, repo, source, revmap, merges, opts={}):
97 '''apply the revisions in revmap one by one in revision order'''
97 '''apply the revisions in revmap one by one in revision order'''
98 revs = sorted(revmap)
98 revs = sorted(revmap)
99 p1, p2 = repo.dirstate.parents()
99 p1, p2 = repo.dirstate.parents()
100 pulls = []
100 pulls = []
101 diffopts = patch.diffopts(self.ui, opts)
101 diffopts = patch.diffopts(self.ui, opts)
102 diffopts.git = True
102 diffopts.git = True
103
103
104 lock = wlock = None
104 lock = wlock = None
105 try:
105 try:
106 wlock = repo.wlock()
106 wlock = repo.wlock()
107 lock = repo.lock()
107 lock = repo.lock()
108 for rev in revs:
108 for rev in revs:
109 node = revmap[rev]
109 node = revmap[rev]
110 revstr = '%s:%s' % (rev, revlog.short(node))
110 revstr = '%s:%s' % (rev, revlog.short(node))
111
111
112 if self.applied(repo, node, p1):
112 if self.applied(repo, node, p1):
113 self.ui.warn(_('skipping already applied revision %s\n') %
113 self.ui.warn(_('skipping already applied revision %s\n') %
114 revstr)
114 revstr)
115 continue
115 continue
116
116
117 parents = source.changelog.parents(node)
117 parents = source.changelog.parents(node)
118 if not opts.get('filter'):
118 if not opts.get('filter'):
119 # If the changeset parent is the same as the
119 # If the changeset parent is the same as the
120 # wdir's parent, just pull it.
120 # wdir's parent, just pull it.
121 if parents[0] == p1:
121 if parents[0] == p1:
122 pulls.append(node)
122 pulls.append(node)
123 p1 = node
123 p1 = node
124 continue
124 continue
125 if pulls:
125 if pulls:
126 if source != repo:
126 if source != repo:
127 repo.pull(source, heads=pulls)
127 repo.pull(source, heads=pulls)
128 merge.update(repo, pulls[-1], False, False, None)
128 merge.update(repo, pulls[-1], False, False, None)
129 p1, p2 = repo.dirstate.parents()
129 p1, p2 = repo.dirstate.parents()
130 pulls = []
130 pulls = []
131
131
132 domerge = False
132 domerge = False
133 if node in merges:
133 if node in merges:
134 # pulling all the merge revs at once would mean we
134 # pulling all the merge revs at once would mean we
135 # couldn't transplant after the latest even if
135 # couldn't transplant after the latest even if
136 # transplants before them fail.
136 # transplants before them fail.
137 domerge = True
137 domerge = True
138 if not hasnode(repo, node):
138 if not hasnode(repo, node):
139 repo.pull(source, heads=[node])
139 repo.pull(source, heads=[node])
140
140
141 if parents[1] != revlog.nullid:
141 if parents[1] != revlog.nullid:
142 self.ui.note(_('skipping merge changeset %s:%s\n')
142 self.ui.note(_('skipping merge changeset %s:%s\n')
143 % (rev, revlog.short(node)))
143 % (rev, revlog.short(node)))
144 patchfile = None
144 patchfile = None
145 else:
145 else:
146 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
146 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
147 fp = os.fdopen(fd, 'w')
147 fp = os.fdopen(fd, 'w')
148 gen = patch.diff(source, parents[0], node, opts=diffopts)
148 gen = patch.diff(source, parents[0], node, opts=diffopts)
149 for chunk in gen:
149 for chunk in gen:
150 fp.write(chunk)
150 fp.write(chunk)
151 fp.close()
151 fp.close()
152
152
153 del revmap[rev]
153 del revmap[rev]
154 if patchfile or domerge:
154 if patchfile or domerge:
155 try:
155 try:
156 n = self.applyone(repo, node,
156 n = self.applyone(repo, node,
157 source.changelog.read(node),
157 source.changelog.read(node),
158 patchfile, merge=domerge,
158 patchfile, merge=domerge,
159 log=opts.get('log'),
159 log=opts.get('log'),
160 filter=opts.get('filter'))
160 filter=opts.get('filter'))
161 if n and domerge:
161 if n and domerge:
162 self.ui.status(_('%s merged at %s\n') % (revstr,
162 self.ui.status(_('%s merged at %s\n') % (revstr,
163 revlog.short(n)))
163 revlog.short(n)))
164 elif n:
164 elif n:
165 self.ui.status(_('%s transplanted to %s\n')
165 self.ui.status(_('%s transplanted to %s\n')
166 % (revlog.short(node),
166 % (revlog.short(node),
167 revlog.short(n)))
167 revlog.short(n)))
168 finally:
168 finally:
169 if patchfile:
169 if patchfile:
170 os.unlink(patchfile)
170 os.unlink(patchfile)
171 if pulls:
171 if pulls:
172 repo.pull(source, heads=pulls)
172 repo.pull(source, heads=pulls)
173 merge.update(repo, pulls[-1], False, False, None)
173 merge.update(repo, pulls[-1], False, False, None)
174 finally:
174 finally:
175 self.saveseries(revmap, merges)
175 self.saveseries(revmap, merges)
176 self.transplants.write()
176 self.transplants.write()
177 lock.release()
177 lock.release()
178 wlock.release()
178 wlock.release()
179
179
180 def filter(self, filter, node, changelog, patchfile):
180 def filter(self, filter, node, changelog, patchfile):
181 '''arbitrarily rewrite changeset before applying it'''
181 '''arbitrarily rewrite changeset before applying it'''
182
182
183 self.ui.status(_('filtering %s\n') % patchfile)
183 self.ui.status(_('filtering %s\n') % patchfile)
184 user, date, msg = (changelog[1], changelog[2], changelog[4])
184 user, date, msg = (changelog[1], changelog[2], changelog[4])
185 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
185 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
186 fp = os.fdopen(fd, 'w')
186 fp = os.fdopen(fd, 'w')
187 fp.write("# HG changeset patch\n")
187 fp.write("# HG changeset patch\n")
188 fp.write("# User %s\n" % user)
188 fp.write("# User %s\n" % user)
189 fp.write("# Date %d %d\n" % date)
189 fp.write("# Date %d %d\n" % date)
190 fp.write(msg + '\n')
190 fp.write(msg + '\n')
191 fp.close()
191 fp.close()
192
192
193 try:
193 try:
194 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
194 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
195 util.shellquote(patchfile)),
195 util.shellquote(patchfile)),
196 environ={'HGUSER': changelog[1],
196 environ={'HGUSER': changelog[1],
197 'HGREVISION': revlog.hex(node),
197 'HGREVISION': revlog.hex(node),
198 },
198 },
199 onerr=util.Abort, errprefix=_('filter failed'))
199 onerr=util.Abort, errprefix=_('filter failed'))
200 user, date, msg = self.parselog(file(headerfile))[1:4]
200 user, date, msg = self.parselog(file(headerfile))[1:4]
201 finally:
201 finally:
202 os.unlink(headerfile)
202 os.unlink(headerfile)
203
203
204 return (user, date, msg)
204 return (user, date, msg)
205
205
206 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
206 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
207 filter=None):
207 filter=None):
208 '''apply the patch in patchfile to the repository as a transplant'''
208 '''apply the patch in patchfile to the repository as a transplant'''
209 (manifest, user, (time, timezone), files, message) = cl[:5]
209 (manifest, user, (time, timezone), files, message) = cl[:5]
210 date = "%d %d" % (time, timezone)
210 date = "%d %d" % (time, timezone)
211 extra = {'transplant_source': node}
211 extra = {'transplant_source': node}
212 if filter:
212 if filter:
213 (user, date, message) = self.filter(filter, node, cl, patchfile)
213 (user, date, message) = self.filter(filter, node, cl, patchfile)
214
214
215 if log:
215 if log:
216 # we don't translate messages inserted into commits
216 # we don't translate messages inserted into commits
217 message += '\n(transplanted from %s)' % revlog.hex(node)
217 message += '\n(transplanted from %s)' % revlog.hex(node)
218
218
219 self.ui.status(_('applying %s\n') % revlog.short(node))
219 self.ui.status(_('applying %s\n') % revlog.short(node))
220 self.ui.note('%s %s\n%s\n' % (user, date, message))
220 self.ui.note('%s %s\n%s\n' % (user, date, message))
221
221
222 if not patchfile and not merge:
222 if not patchfile and not merge:
223 raise util.Abort(_('can only omit patchfile if merging'))
223 raise util.Abort(_('can only omit patchfile if merging'))
224 if patchfile:
224 if patchfile:
225 try:
225 try:
226 files = {}
226 files = {}
227 try:
227 try:
228 patch.patch(patchfile, self.ui, cwd=repo.root,
228 patch.patch(patchfile, self.ui, cwd=repo.root,
229 files=files, eolmode=None)
229 files=files, eolmode=None)
230 if not files:
230 if not files:
231 self.ui.warn(_('%s: empty changeset')
231 self.ui.warn(_('%s: empty changeset')
232 % revlog.hex(node))
232 % revlog.hex(node))
233 return None
233 return None
234 finally:
234 finally:
235 files = cmdutil.updatedir(self.ui, repo, files)
235 files = cmdutil.updatedir(self.ui, repo, files)
236 except Exception, inst:
236 except Exception, inst:
237 seriespath = os.path.join(self.path, 'series')
237 seriespath = os.path.join(self.path, 'series')
238 if os.path.exists(seriespath):
238 if os.path.exists(seriespath):
239 os.unlink(seriespath)
239 os.unlink(seriespath)
240 p1 = repo.dirstate.parents()[0]
240 p1 = repo.dirstate.parents()[0]
241 p2 = node
241 p2 = node
242 self.log(user, date, message, p1, p2, merge=merge)
242 self.log(user, date, message, p1, p2, merge=merge)
243 self.ui.write(str(inst) + '\n')
243 self.ui.write(str(inst) + '\n')
244 raise util.Abort(_('fix up the merge and run '
244 raise util.Abort(_('fix up the merge and run '
245 'hg transplant --continue'))
245 'hg transplant --continue'))
246 else:
246 else:
247 files = None
247 files = None
248 if merge:
248 if merge:
249 p1, p2 = repo.dirstate.parents()
249 p1, p2 = repo.dirstate.parents()
250 repo.dirstate.setparents(p1, node)
250 repo.dirstate.setparents(p1, node)
251 m = match.always(repo.root, '')
251 m = match.always(repo.root, '')
252 else:
252 else:
253 m = match.exact(repo.root, '', files)
253 m = match.exact(repo.root, '', files)
254
254
255 n = repo.commit(message, user, date, extra=extra, match=m)
255 n = repo.commit(message, user, date, extra=extra, match=m)
256 if not n:
256 if not n:
257 # Crash here to prevent an unclear crash later, in
257 # Crash here to prevent an unclear crash later, in
258 # transplants.write(). This can happen if patch.patch()
258 # transplants.write(). This can happen if patch.patch()
259 # does nothing but claims success or if repo.status() fails
259 # does nothing but claims success or if repo.status() fails
260 # to report changes done by patch.patch(). These both
260 # to report changes done by patch.patch(). These both
261 # appear to be bugs in other parts of Mercurial, but dying
261 # appear to be bugs in other parts of Mercurial, but dying
262 # here, as soon as we can detect the problem, is preferable
262 # here, as soon as we can detect the problem, is preferable
263 # to silently dropping changesets on the floor.
263 # to silently dropping changesets on the floor.
264 raise RuntimeError('nothing committed after transplant')
264 raise RuntimeError('nothing committed after transplant')
265 if not merge:
265 if not merge:
266 self.transplants.set(n, node)
266 self.transplants.set(n, node)
267
267
268 return n
268 return n
269
269
270 def resume(self, repo, source, opts=None):
270 def resume(self, repo, source, opts=None):
271 '''recover last transaction and apply remaining changesets'''
271 '''recover last transaction and apply remaining changesets'''
272 if os.path.exists(os.path.join(self.path, 'journal')):
272 if os.path.exists(os.path.join(self.path, 'journal')):
273 n, node = self.recover(repo)
273 n, node = self.recover(repo)
274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
275 revlog.short(n)))
275 revlog.short(n)))
276 seriespath = os.path.join(self.path, 'series')
276 seriespath = os.path.join(self.path, 'series')
277 if not os.path.exists(seriespath):
277 if not os.path.exists(seriespath):
278 self.transplants.write()
278 self.transplants.write()
279 return
279 return
280 nodes, merges = self.readseries()
280 nodes, merges = self.readseries()
281 revmap = {}
281 revmap = {}
282 for n in nodes:
282 for n in nodes:
283 revmap[source.changelog.rev(n)] = n
283 revmap[source.changelog.rev(n)] = n
284 os.unlink(seriespath)
284 os.unlink(seriespath)
285
285
286 self.apply(repo, source, revmap, merges, opts)
286 self.apply(repo, source, revmap, merges, opts)
287
287
288 def recover(self, repo):
288 def recover(self, repo):
289 '''commit working directory using journal metadata'''
289 '''commit working directory using journal metadata'''
290 node, user, date, message, parents = self.readlog()
290 node, user, date, message, parents = self.readlog()
291 merge = len(parents) == 2
291 merge = len(parents) == 2
292
292
293 if not user or not date or not message or not parents[0]:
293 if not user or not date or not message or not parents[0]:
294 raise util.Abort(_('transplant log file is corrupt'))
294 raise util.Abort(_('transplant log file is corrupt'))
295
295
296 extra = {'transplant_source': node}
296 extra = {'transplant_source': node}
297 wlock = repo.wlock()
297 wlock = repo.wlock()
298 try:
298 try:
299 p1, p2 = repo.dirstate.parents()
299 p1, p2 = repo.dirstate.parents()
300 if p1 != parents[0]:
300 if p1 != parents[0]:
301 raise util.Abort(
301 raise util.Abort(
302 _('working dir not at transplant parent %s') %
302 _('working dir not at transplant parent %s') %
303 revlog.hex(parents[0]))
303 revlog.hex(parents[0]))
304 if merge:
304 if merge:
305 repo.dirstate.setparents(p1, parents[1])
305 repo.dirstate.setparents(p1, parents[1])
306 n = repo.commit(message, user, date, extra=extra)
306 n = repo.commit(message, user, date, extra=extra)
307 if not n:
307 if not n:
308 raise util.Abort(_('commit failed'))
308 raise util.Abort(_('commit failed'))
309 if not merge:
309 if not merge:
310 self.transplants.set(n, node)
310 self.transplants.set(n, node)
311 self.unlog()
311 self.unlog()
312
312
313 return n, node
313 return n, node
314 finally:
314 finally:
315 wlock.release()
315 wlock.release()
316
316
317 def readseries(self):
317 def readseries(self):
318 nodes = []
318 nodes = []
319 merges = []
319 merges = []
320 cur = nodes
320 cur = nodes
321 for line in self.opener('series').read().splitlines():
321 for line in self.opener('series').read().splitlines():
322 if line.startswith('# Merges'):
322 if line.startswith('# Merges'):
323 cur = merges
323 cur = merges
324 continue
324 continue
325 cur.append(revlog.bin(line))
325 cur.append(revlog.bin(line))
326
326
327 return (nodes, merges)
327 return (nodes, merges)
328
328
329 def saveseries(self, revmap, merges):
329 def saveseries(self, revmap, merges):
330 if not revmap:
330 if not revmap:
331 return
331 return
332
332
333 if not os.path.isdir(self.path):
333 if not os.path.isdir(self.path):
334 os.mkdir(self.path)
334 os.mkdir(self.path)
335 series = self.opener('series', 'w')
335 series = self.opener('series', 'w')
336 for rev in sorted(revmap):
336 for rev in sorted(revmap):
337 series.write(revlog.hex(revmap[rev]) + '\n')
337 series.write(revlog.hex(revmap[rev]) + '\n')
338 if merges:
338 if merges:
339 series.write('# Merges\n')
339 series.write('# Merges\n')
340 for m in merges:
340 for m in merges:
341 series.write(revlog.hex(m) + '\n')
341 series.write(revlog.hex(m) + '\n')
342 series.close()
342 series.close()
343
343
344 def parselog(self, fp):
344 def parselog(self, fp):
345 parents = []
345 parents = []
346 message = []
346 message = []
347 node = revlog.nullid
347 node = revlog.nullid
348 inmsg = False
348 inmsg = False
349 for line in fp.read().splitlines():
349 for line in fp.read().splitlines():
350 if inmsg:
350 if inmsg:
351 message.append(line)
351 message.append(line)
352 elif line.startswith('# User '):
352 elif line.startswith('# User '):
353 user = line[7:]
353 user = line[7:]
354 elif line.startswith('# Date '):
354 elif line.startswith('# Date '):
355 date = line[7:]
355 date = line[7:]
356 elif line.startswith('# Node ID '):
356 elif line.startswith('# Node ID '):
357 node = revlog.bin(line[10:])
357 node = revlog.bin(line[10:])
358 elif line.startswith('# Parent '):
358 elif line.startswith('# Parent '):
359 parents.append(revlog.bin(line[9:]))
359 parents.append(revlog.bin(line[9:]))
360 elif not line.startswith('# '):
360 elif not line.startswith('# '):
361 inmsg = True
361 inmsg = True
362 message.append(line)
362 message.append(line)
363 return (node, user, date, '\n'.join(message), parents)
363 return (node, user, date, '\n'.join(message), parents)
364
364
365 def log(self, user, date, message, p1, p2, merge=False):
365 def log(self, user, date, message, p1, p2, merge=False):
366 '''journal changelog metadata for later recover'''
366 '''journal changelog metadata for later recover'''
367
367
368 if not os.path.isdir(self.path):
368 if not os.path.isdir(self.path):
369 os.mkdir(self.path)
369 os.mkdir(self.path)
370 fp = self.opener('journal', 'w')
370 fp = self.opener('journal', 'w')
371 fp.write('# User %s\n' % user)
371 fp.write('# User %s\n' % user)
372 fp.write('# Date %s\n' % date)
372 fp.write('# Date %s\n' % date)
373 fp.write('# Node ID %s\n' % revlog.hex(p2))
373 fp.write('# Node ID %s\n' % revlog.hex(p2))
374 fp.write('# Parent ' + revlog.hex(p1) + '\n')
374 fp.write('# Parent ' + revlog.hex(p1) + '\n')
375 if merge:
375 if merge:
376 fp.write('# Parent ' + revlog.hex(p2) + '\n')
376 fp.write('# Parent ' + revlog.hex(p2) + '\n')
377 fp.write(message.rstrip() + '\n')
377 fp.write(message.rstrip() + '\n')
378 fp.close()
378 fp.close()
379
379
380 def readlog(self):
380 def readlog(self):
381 return self.parselog(self.opener('journal'))
381 return self.parselog(self.opener('journal'))
382
382
383 def unlog(self):
383 def unlog(self):
384 '''remove changelog journal'''
384 '''remove changelog journal'''
385 absdst = os.path.join(self.path, 'journal')
385 absdst = os.path.join(self.path, 'journal')
386 if os.path.exists(absdst):
386 if os.path.exists(absdst):
387 os.unlink(absdst)
387 os.unlink(absdst)
388
388
389 def transplantfilter(self, repo, source, root):
389 def transplantfilter(self, repo, source, root):
390 def matchfn(node):
390 def matchfn(node):
391 if self.applied(repo, node, root):
391 if self.applied(repo, node, root):
392 return False
392 return False
393 if source.changelog.parents(node)[1] != revlog.nullid:
393 if source.changelog.parents(node)[1] != revlog.nullid:
394 return False
394 return False
395 extra = source.changelog.read(node)[5]
395 extra = source.changelog.read(node)[5]
396 cnode = extra.get('transplant_source')
396 cnode = extra.get('transplant_source')
397 if cnode and self.applied(repo, cnode, root):
397 if cnode and self.applied(repo, cnode, root):
398 return False
398 return False
399 return True
399 return True
400
400
401 return matchfn
401 return matchfn
402
402
403 def hasnode(repo, node):
403 def hasnode(repo, node):
404 try:
404 try:
405 return repo.changelog.rev(node) is not None
405 return repo.changelog.rev(node) is not None
406 except error.RevlogError:
406 except error.RevlogError:
407 return False
407 return False
408
408
409 def browserevs(ui, repo, nodes, opts):
409 def browserevs(ui, repo, nodes, opts):
410 '''interactively transplant changesets'''
410 '''interactively transplant changesets'''
411 def browsehelp(ui):
411 def browsehelp(ui):
412 ui.write(_('y: transplant this changeset\n'
412 ui.write(_('y: transplant this changeset\n'
413 'n: skip this changeset\n'
413 'n: skip this changeset\n'
414 'm: merge at this changeset\n'
414 'm: merge at this changeset\n'
415 'p: show patch\n'
415 'p: show patch\n'
416 'c: commit selected changesets\n'
416 'c: commit selected changesets\n'
417 'q: cancel transplant\n'
417 'q: cancel transplant\n'
418 '?: show this help\n'))
418 '?: show this help\n'))
419
419
420 displayer = cmdutil.show_changeset(ui, repo, opts)
420 displayer = cmdutil.show_changeset(ui, repo, opts)
421 transplants = []
421 transplants = []
422 merges = []
422 merges = []
423 for node in nodes:
423 for node in nodes:
424 displayer.show(repo[node])
424 displayer.show(repo[node])
425 action = None
425 action = None
426 while not action:
426 while not action:
427 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
427 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
428 if action == '?':
428 if action == '?':
429 browsehelp(ui)
429 browsehelp(ui)
430 action = None
430 action = None
431 elif action == 'p':
431 elif action == 'p':
432 parent = repo.changelog.parents(node)[0]
432 parent = repo.changelog.parents(node)[0]
433 for chunk in patch.diff(repo, parent, node):
433 for chunk in patch.diff(repo, parent, node):
434 ui.write(chunk)
434 ui.write(chunk)
435 action = None
435 action = None
436 elif action not in ('y', 'n', 'm', 'c', 'q'):
436 elif action not in ('y', 'n', 'm', 'c', 'q'):
437 ui.write(_('no such option\n'))
437 ui.write(_('no such option\n'))
438 action = None
438 action = None
439 if action == 'y':
439 if action == 'y':
440 transplants.append(node)
440 transplants.append(node)
441 elif action == 'm':
441 elif action == 'm':
442 merges.append(node)
442 merges.append(node)
443 elif action == 'c':
443 elif action == 'c':
444 break
444 break
445 elif action == 'q':
445 elif action == 'q':
446 transplants = ()
446 transplants = ()
447 merges = ()
447 merges = ()
448 break
448 break
449 displayer.close()
449 displayer.close()
450 return (transplants, merges)
450 return (transplants, merges)
451
451
452 def transplant(ui, repo, *revs, **opts):
452 def transplant(ui, repo, *revs, **opts):
453 '''transplant changesets from another branch
453 '''transplant changesets from another branch
454
454
455 Selected changesets will be applied on top of the current working
455 Selected changesets will be applied on top of the current working
456 directory with the log of the original changeset. The changesets
456 directory with the log of the original changeset. The changesets
457 are copied and will thus appear twice in the history. Use the
457 are copied and will thus appear twice in the history. Use the
458 rebase extension instead if you want to move a whole branch of
458 rebase extension instead if you want to move a whole branch of
459 unpublished changesets.
459 unpublished changesets.
460
460
461 If --log is specified, log messages will have a comment appended
461 If --log is specified, log messages will have a comment appended
462 of the form::
462 of the form::
463
463
464 (transplanted from CHANGESETHASH)
464 (transplanted from CHANGESETHASH)
465
465
466 You can rewrite the changelog message with the --filter option.
466 You can rewrite the changelog message with the --filter option.
467 Its argument will be invoked with the current changelog message as
467 Its argument will be invoked with the current changelog message as
468 $1 and the patch as $2.
468 $1 and the patch as $2.
469
469
470 If --source/-s is specified, selects changesets from the named
470 If --source/-s is specified, selects changesets from the named
471 repository. If --branch/-b is specified, selects changesets from
471 repository. If --branch/-b is specified, selects changesets from
472 the branch holding the named revision, up to that revision. If
472 the branch holding the named revision, up to that revision. If
473 --all/-a is specified, all changesets on the branch will be
473 --all/-a is specified, all changesets on the branch will be
474 transplanted, otherwise you will be prompted to select the
474 transplanted, otherwise you will be prompted to select the
475 changesets you want.
475 changesets you want.
476
476
477 :hg:`transplant --branch REVISION --all` will transplant the
477 :hg:`transplant --branch REVISION --all` will transplant the
478 selected branch (up to the named revision) onto your current
478 selected branch (up to the named revision) onto your current
479 working directory.
479 working directory.
480
480
481 You can optionally mark selected transplanted changesets as merge
481 You can optionally mark selected transplanted changesets as merge
482 changesets. You will not be prompted to transplant any ancestors
482 changesets. You will not be prompted to transplant any ancestors
483 of a merged transplant, and you can merge descendants of them
483 of a merged transplant, and you can merge descendants of them
484 normally instead of transplanting them.
484 normally instead of transplanting them.
485
485
486 If no merges or revisions are provided, :hg:`transplant` will
486 If no merges or revisions are provided, :hg:`transplant` will
487 start an interactive changeset browser.
487 start an interactive changeset browser.
488
488
489 If a changeset application fails, you can fix the merge by hand
489 If a changeset application fails, you can fix the merge by hand
490 and then resume where you left off by calling :hg:`transplant
490 and then resume where you left off by calling :hg:`transplant
491 --continue/-c`.
491 --continue/-c`.
492 '''
492 '''
493 def incwalk(repo, incoming, branches, match=util.always):
493 def incwalk(repo, incoming, branches, match=util.always):
494 if not branches:
494 if not branches:
495 branches = None
495 branches = None
496 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
496 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
497 if match(node):
497 if match(node):
498 yield node
498 yield node
499
499
500 def transplantwalk(repo, root, branches, match=util.always):
500 def transplantwalk(repo, root, branches, match=util.always):
501 if not branches:
501 if not branches:
502 branches = repo.heads()
502 branches = repo.heads()
503 ancestors = []
503 ancestors = []
504 for branch in branches:
504 for branch in branches:
505 ancestors.append(repo.changelog.ancestor(root, branch))
505 ancestors.append(repo.changelog.ancestor(root, branch))
506 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
506 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
507 if match(node):
507 if match(node):
508 yield node
508 yield node
509
509
510 def checkopts(opts, revs):
510 def checkopts(opts, revs):
511 if opts.get('continue'):
511 if opts.get('continue'):
512 if opts.get('branch') or opts.get('all') or opts.get('merge'):
512 if opts.get('branch') or opts.get('all') or opts.get('merge'):
513 raise util.Abort(_('--continue is incompatible with '
513 raise util.Abort(_('--continue is incompatible with '
514 'branch, all or merge'))
514 'branch, all or merge'))
515 return
515 return
516 if not (opts.get('source') or revs or
516 if not (opts.get('source') or revs or
517 opts.get('merge') or opts.get('branch')):
517 opts.get('merge') or opts.get('branch')):
518 raise util.Abort(_('no source URL, branch tag or revision '
518 raise util.Abort(_('no source URL, branch tag or revision '
519 'list provided'))
519 'list provided'))
520 if opts.get('all'):
520 if opts.get('all'):
521 if not opts.get('branch'):
521 if not opts.get('branch'):
522 raise util.Abort(_('--all requires a branch revision'))
522 raise util.Abort(_('--all requires a branch revision'))
523 if revs:
523 if revs:
524 raise util.Abort(_('--all is incompatible with a '
524 raise util.Abort(_('--all is incompatible with a '
525 'revision list'))
525 'revision list'))
526
526
527 checkopts(opts, revs)
527 checkopts(opts, revs)
528
528
529 if not opts.get('log'):
529 if not opts.get('log'):
530 opts['log'] = ui.config('transplant', 'log')
530 opts['log'] = ui.config('transplant', 'log')
531 if not opts.get('filter'):
531 if not opts.get('filter'):
532 opts['filter'] = ui.config('transplant', 'filter')
532 opts['filter'] = ui.config('transplant', 'filter')
533
533
534 tp = transplanter(ui, repo)
534 tp = transplanter(ui, repo)
535
535
536 p1, p2 = repo.dirstate.parents()
536 p1, p2 = repo.dirstate.parents()
537 if len(repo) > 0 and p1 == revlog.nullid:
537 if len(repo) > 0 and p1 == revlog.nullid:
538 raise util.Abort(_('no revision checked out'))
538 raise util.Abort(_('no revision checked out'))
539 if not opts.get('continue'):
539 if not opts.get('continue'):
540 if p2 != revlog.nullid:
540 if p2 != revlog.nullid:
541 raise util.Abort(_('outstanding uncommitted merges'))
541 raise util.Abort(_('outstanding uncommitted merges'))
542 m, a, r, d = repo.status()[:4]
542 m, a, r, d = repo.status()[:4]
543 if m or a or r or d:
543 if m or a or r or d:
544 raise util.Abort(_('outstanding local changes'))
544 raise util.Abort(_('outstanding local changes'))
545
545
546 bundle = None
546 bundle = None
547 source = opts.get('source')
547 source = opts.get('source')
548 if source:
548 if source:
549 sourcerepo = ui.expandpath(source)
549 sourcerepo = ui.expandpath(source)
550 source = hg.repository(ui, sourcerepo)
550 source = hg.repository(ui, sourcerepo)
551 source, incoming, bundle = bundlerepo.getremotechanges(ui, repo, source,
551 source, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo,
552 force=True)
552 source, force=True)
553 else:
553 else:
554 source = repo
554 source = repo
555
555
556 try:
556 try:
557 if opts.get('continue'):
557 if opts.get('continue'):
558 tp.resume(repo, source, opts)
558 tp.resume(repo, source, opts)
559 return
559 return
560
560
561 tf = tp.transplantfilter(repo, source, p1)
561 tf = tp.transplantfilter(repo, source, p1)
562 if opts.get('prune'):
562 if opts.get('prune'):
563 prune = [source.lookup(r)
563 prune = [source.lookup(r)
564 for r in cmdutil.revrange(source, opts.get('prune'))]
564 for r in cmdutil.revrange(source, opts.get('prune'))]
565 matchfn = lambda x: tf(x) and x not in prune
565 matchfn = lambda x: tf(x) and x not in prune
566 else:
566 else:
567 matchfn = tf
567 matchfn = tf
568 branches = map(source.lookup, opts.get('branch', ()))
568 branches = map(source.lookup, opts.get('branch', ()))
569 merges = map(source.lookup, opts.get('merge', ()))
569 merges = map(source.lookup, opts.get('merge', ()))
570 revmap = {}
570 revmap = {}
571 if revs:
571 if revs:
572 for r in cmdutil.revrange(source, revs):
572 for r in cmdutil.revrange(source, revs):
573 revmap[int(r)] = source.lookup(r)
573 revmap[int(r)] = source.lookup(r)
574 elif opts.get('all') or not merges:
574 elif opts.get('all') or not merges:
575 if source != repo:
575 if source != repo:
576 alltransplants = incwalk(source, incoming, branches,
576 alltransplants = incwalk(source, incoming, branches,
577 match=matchfn)
577 match=matchfn)
578 else:
578 else:
579 alltransplants = transplantwalk(source, p1, branches,
579 alltransplants = transplantwalk(source, p1, branches,
580 match=matchfn)
580 match=matchfn)
581 if opts.get('all'):
581 if opts.get('all'):
582 revs = alltransplants
582 revs = alltransplants
583 else:
583 else:
584 revs, newmerges = browserevs(ui, source, alltransplants, opts)
584 revs, newmerges = browserevs(ui, source, alltransplants, opts)
585 merges.extend(newmerges)
585 merges.extend(newmerges)
586 for r in revs:
586 for r in revs:
587 revmap[source.changelog.rev(r)] = r
587 revmap[source.changelog.rev(r)] = r
588 for r in merges:
588 for r in merges:
589 revmap[source.changelog.rev(r)] = r
589 revmap[source.changelog.rev(r)] = r
590
590
591 tp.apply(repo, source, revmap, merges, opts)
591 tp.apply(repo, source, revmap, merges, opts)
592 finally:
592 finally:
593 if bundle:
593 if bundle:
594 source.close()
594 source.close()
595 os.unlink(bundle)
595 os.unlink(bundle)
596
596
597 def revsettransplanted(repo, subset, x):
597 def revsettransplanted(repo, subset, x):
598 """``transplanted(set)``
598 """``transplanted(set)``
599 Transplanted changesets in set.
599 Transplanted changesets in set.
600 """
600 """
601 if x:
601 if x:
602 s = revset.getset(repo, subset, x)
602 s = revset.getset(repo, subset, x)
603 else:
603 else:
604 s = subset
604 s = subset
605 cs = set()
605 cs = set()
606 for r in xrange(0, len(repo)):
606 for r in xrange(0, len(repo)):
607 if repo[r].extra().get('transplant_source'):
607 if repo[r].extra().get('transplant_source'):
608 cs.add(r)
608 cs.add(r)
609 return [r for r in s if r in cs]
609 return [r for r in s if r in cs]
610
610
611 def kwtransplanted(repo, ctx, **args):
611 def kwtransplanted(repo, ctx, **args):
612 """:transplanted: String. The node identifier of the transplanted
612 """:transplanted: String. The node identifier of the transplanted
613 changeset if any."""
613 changeset if any."""
614 n = ctx.extra().get('transplant_source')
614 n = ctx.extra().get('transplant_source')
615 return n and revlog.hex(n) or ''
615 return n and revlog.hex(n) or ''
616
616
617 def extsetup(ui):
617 def extsetup(ui):
618 revset.symbols['transplanted'] = revsettransplanted
618 revset.symbols['transplanted'] = revsettransplanted
619 templatekw.keywords['transplanted'] = kwtransplanted
619 templatekw.keywords['transplanted'] = kwtransplanted
620
620
621 cmdtable = {
621 cmdtable = {
622 "transplant":
622 "transplant":
623 (transplant,
623 (transplant,
624 [('s', 'source', '',
624 [('s', 'source', '',
625 _('pull patches from REPO'), _('REPO')),
625 _('pull patches from REPO'), _('REPO')),
626 ('b', 'branch', [],
626 ('b', 'branch', [],
627 _('pull patches from branch BRANCH'), _('BRANCH')),
627 _('pull patches from branch BRANCH'), _('BRANCH')),
628 ('a', 'all', None, _('pull all changesets up to BRANCH')),
628 ('a', 'all', None, _('pull all changesets up to BRANCH')),
629 ('p', 'prune', [],
629 ('p', 'prune', [],
630 _('skip over REV'), _('REV')),
630 _('skip over REV'), _('REV')),
631 ('m', 'merge', [],
631 ('m', 'merge', [],
632 _('merge at REV'), _('REV')),
632 _('merge at REV'), _('REV')),
633 ('', 'log', None, _('append transplant info to log message')),
633 ('', 'log', None, _('append transplant info to log message')),
634 ('c', 'continue', None, _('continue last transplant session '
634 ('c', 'continue', None, _('continue last transplant session '
635 'after repair')),
635 'after repair')),
636 ('', 'filter', '',
636 ('', 'filter', '',
637 _('filter changesets through command'), _('CMD'))],
637 _('filter changesets through command'), _('CMD'))],
638 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
638 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
639 '[-m REV] [REV]...'))
639 '[-m REV] [REV]...'))
640 }
640 }
641
641
642 # tell hggettext to extract docstrings from these functions:
642 # tell hggettext to extract docstrings from these functions:
643 i18nfunctions = [revsettransplanted, kwtransplanted]
643 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,319 +1,323 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from node import nullid
14 from node import nullid
15 from i18n import _
15 from i18n import _
16 import os, struct, tempfile, shutil
16 import os, struct, tempfile, shutil
17 import changegroup, util, mdiff, discovery
17 import changegroup, util, mdiff, discovery
18 import localrepo, changelog, manifest, filelog, revlog, error
18 import localrepo, changelog, manifest, filelog, revlog, error
19
19
20 class bundlerevlog(revlog.revlog):
20 class bundlerevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, bundle,
21 def __init__(self, opener, indexfile, bundle,
22 linkmapper=None):
22 linkmapper=None):
23 # How it works:
23 # How it works:
24 # to retrieve a revision, we need to know the offset of
24 # to retrieve a revision, we need to know the offset of
25 # the revision in the bundle (an unbundle object).
25 # the revision in the bundle (an unbundle object).
26 #
26 #
27 # We store this offset in the index (start), to differentiate a
27 # We store this offset in the index (start), to differentiate a
28 # rev in the bundle and from a rev in the revlog, we check
28 # rev in the bundle and from a rev in the revlog, we check
29 # len(index[r]). If the tuple is bigger than 7, it is a bundle
29 # len(index[r]). If the tuple is bigger than 7, it is a bundle
30 # (it is bigger since we store the node to which the delta is)
30 # (it is bigger since we store the node to which the delta is)
31 #
31 #
32 revlog.revlog.__init__(self, opener, indexfile)
32 revlog.revlog.__init__(self, opener, indexfile)
33 self.bundle = bundle
33 self.bundle = bundle
34 self.basemap = {}
34 self.basemap = {}
35 def chunkpositer():
35 def chunkpositer():
36 while 1:
36 while 1:
37 chunk = bundle.chunk()
37 chunk = bundle.chunk()
38 if not chunk:
38 if not chunk:
39 break
39 break
40 pos = bundle.tell()
40 pos = bundle.tell()
41 yield chunk, pos - len(chunk)
41 yield chunk, pos - len(chunk)
42 n = len(self)
42 n = len(self)
43 prev = None
43 prev = None
44 for chunk, start in chunkpositer():
44 for chunk, start in chunkpositer():
45 size = len(chunk)
45 size = len(chunk)
46 if size < 80:
46 if size < 80:
47 raise util.Abort(_("invalid changegroup"))
47 raise util.Abort(_("invalid changegroup"))
48 start += 80
48 start += 80
49 size -= 80
49 size -= 80
50 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
50 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
51 if node in self.nodemap:
51 if node in self.nodemap:
52 prev = node
52 prev = node
53 continue
53 continue
54 for p in (p1, p2):
54 for p in (p1, p2):
55 if not p in self.nodemap:
55 if not p in self.nodemap:
56 raise error.LookupError(p, self.indexfile,
56 raise error.LookupError(p, self.indexfile,
57 _("unknown parent"))
57 _("unknown parent"))
58 if linkmapper is None:
58 if linkmapper is None:
59 link = n
59 link = n
60 else:
60 else:
61 link = linkmapper(cs)
61 link = linkmapper(cs)
62
62
63 if not prev:
63 if not prev:
64 prev = p1
64 prev = p1
65 # start, size, full unc. size, base (unused), link, p1, p2, node
65 # start, size, full unc. size, base (unused), link, p1, p2, node
66 e = (revlog.offset_type(start, 0), size, -1, -1, link,
66 e = (revlog.offset_type(start, 0), size, -1, -1, link,
67 self.rev(p1), self.rev(p2), node)
67 self.rev(p1), self.rev(p2), node)
68 self.basemap[n] = prev
68 self.basemap[n] = prev
69 self.index.insert(-1, e)
69 self.index.insert(-1, e)
70 self.nodemap[node] = n
70 self.nodemap[node] = n
71 prev = node
71 prev = node
72 n += 1
72 n += 1
73
73
74 def inbundle(self, rev):
74 def inbundle(self, rev):
75 """is rev from the bundle"""
75 """is rev from the bundle"""
76 if rev < 0:
76 if rev < 0:
77 return False
77 return False
78 return rev in self.basemap
78 return rev in self.basemap
79 def bundlebase(self, rev):
79 def bundlebase(self, rev):
80 return self.basemap[rev]
80 return self.basemap[rev]
81 def _chunk(self, rev):
81 def _chunk(self, rev):
82 # Warning: in case of bundle, the diff is against bundlebase,
82 # Warning: in case of bundle, the diff is against bundlebase,
83 # not against rev - 1
83 # not against rev - 1
84 # XXX: could use some caching
84 # XXX: could use some caching
85 if not self.inbundle(rev):
85 if not self.inbundle(rev):
86 return revlog.revlog._chunk(self, rev)
86 return revlog.revlog._chunk(self, rev)
87 self.bundle.seek(self.start(rev))
87 self.bundle.seek(self.start(rev))
88 return self.bundle.read(self.length(rev))
88 return self.bundle.read(self.length(rev))
89
89
90 def revdiff(self, rev1, rev2):
90 def revdiff(self, rev1, rev2):
91 """return or calculate a delta between two revisions"""
91 """return or calculate a delta between two revisions"""
92 if self.inbundle(rev1) and self.inbundle(rev2):
92 if self.inbundle(rev1) and self.inbundle(rev2):
93 # hot path for bundle
93 # hot path for bundle
94 revb = self.rev(self.bundlebase(rev2))
94 revb = self.rev(self.bundlebase(rev2))
95 if revb == rev1:
95 if revb == rev1:
96 return self._chunk(rev2)
96 return self._chunk(rev2)
97 elif not self.inbundle(rev1) and not self.inbundle(rev2):
97 elif not self.inbundle(rev1) and not self.inbundle(rev2):
98 return revlog.revlog.revdiff(self, rev1, rev2)
98 return revlog.revlog.revdiff(self, rev1, rev2)
99
99
100 return mdiff.textdiff(self.revision(self.node(rev1)),
100 return mdiff.textdiff(self.revision(self.node(rev1)),
101 self.revision(self.node(rev2)))
101 self.revision(self.node(rev2)))
102
102
103 def revision(self, node):
103 def revision(self, node):
104 """return an uncompressed revision of a given"""
104 """return an uncompressed revision of a given"""
105 if node == nullid:
105 if node == nullid:
106 return ""
106 return ""
107
107
108 text = None
108 text = None
109 chain = []
109 chain = []
110 iter_node = node
110 iter_node = node
111 rev = self.rev(iter_node)
111 rev = self.rev(iter_node)
112 # reconstruct the revision if it is from a changegroup
112 # reconstruct the revision if it is from a changegroup
113 while self.inbundle(rev):
113 while self.inbundle(rev):
114 if self._cache and self._cache[0] == iter_node:
114 if self._cache and self._cache[0] == iter_node:
115 text = self._cache[2]
115 text = self._cache[2]
116 break
116 break
117 chain.append(rev)
117 chain.append(rev)
118 iter_node = self.bundlebase(rev)
118 iter_node = self.bundlebase(rev)
119 rev = self.rev(iter_node)
119 rev = self.rev(iter_node)
120 if text is None:
120 if text is None:
121 text = revlog.revlog.revision(self, iter_node)
121 text = revlog.revlog.revision(self, iter_node)
122
122
123 while chain:
123 while chain:
124 delta = self._chunk(chain.pop())
124 delta = self._chunk(chain.pop())
125 text = mdiff.patches(text, [delta])
125 text = mdiff.patches(text, [delta])
126
126
127 p1, p2 = self.parents(node)
127 p1, p2 = self.parents(node)
128 if node != revlog.hash(text, p1, p2):
128 if node != revlog.hash(text, p1, p2):
129 raise error.RevlogError(_("integrity check failed on %s:%d")
129 raise error.RevlogError(_("integrity check failed on %s:%d")
130 % (self.datafile, self.rev(node)))
130 % (self.datafile, self.rev(node)))
131
131
132 self._cache = (node, self.rev(node), text)
132 self._cache = (node, self.rev(node), text)
133 return text
133 return text
134
134
135 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
135 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
136 raise NotImplementedError
136 raise NotImplementedError
137 def addgroup(self, revs, linkmapper, transaction):
137 def addgroup(self, revs, linkmapper, transaction):
138 raise NotImplementedError
138 raise NotImplementedError
139 def strip(self, rev, minlink):
139 def strip(self, rev, minlink):
140 raise NotImplementedError
140 raise NotImplementedError
141 def checksize(self):
141 def checksize(self):
142 raise NotImplementedError
142 raise NotImplementedError
143
143
144 class bundlechangelog(bundlerevlog, changelog.changelog):
144 class bundlechangelog(bundlerevlog, changelog.changelog):
145 def __init__(self, opener, bundle):
145 def __init__(self, opener, bundle):
146 changelog.changelog.__init__(self, opener)
146 changelog.changelog.__init__(self, opener)
147 bundlerevlog.__init__(self, opener, self.indexfile, bundle)
147 bundlerevlog.__init__(self, opener, self.indexfile, bundle)
148
148
149 class bundlemanifest(bundlerevlog, manifest.manifest):
149 class bundlemanifest(bundlerevlog, manifest.manifest):
150 def __init__(self, opener, bundle, linkmapper):
150 def __init__(self, opener, bundle, linkmapper):
151 manifest.manifest.__init__(self, opener)
151 manifest.manifest.__init__(self, opener)
152 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
152 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
153 linkmapper)
153 linkmapper)
154
154
155 class bundlefilelog(bundlerevlog, filelog.filelog):
155 class bundlefilelog(bundlerevlog, filelog.filelog):
156 def __init__(self, opener, path, bundle, linkmapper):
156 def __init__(self, opener, path, bundle, linkmapper):
157 filelog.filelog.__init__(self, opener, path)
157 filelog.filelog.__init__(self, opener, path)
158 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
158 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
159 linkmapper)
159 linkmapper)
160
160
161 class bundlerepository(localrepo.localrepository):
161 class bundlerepository(localrepo.localrepository):
162 def __init__(self, ui, path, bundlename):
162 def __init__(self, ui, path, bundlename):
163 self._tempparent = None
163 self._tempparent = None
164 try:
164 try:
165 localrepo.localrepository.__init__(self, ui, path)
165 localrepo.localrepository.__init__(self, ui, path)
166 except error.RepoError:
166 except error.RepoError:
167 self._tempparent = tempfile.mkdtemp()
167 self._tempparent = tempfile.mkdtemp()
168 localrepo.instance(ui, self._tempparent, 1)
168 localrepo.instance(ui, self._tempparent, 1)
169 localrepo.localrepository.__init__(self, ui, self._tempparent)
169 localrepo.localrepository.__init__(self, ui, self._tempparent)
170
170
171 if path:
171 if path:
172 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
172 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
173 else:
173 else:
174 self._url = 'bundle:' + bundlename
174 self._url = 'bundle:' + bundlename
175
175
176 self.tempfile = None
176 self.tempfile = None
177 f = util.posixfile(bundlename, "rb")
177 f = util.posixfile(bundlename, "rb")
178 self.bundle = changegroup.readbundle(f, bundlename)
178 self.bundle = changegroup.readbundle(f, bundlename)
179 if self.bundle.compressed():
179 if self.bundle.compressed():
180 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
180 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
181 suffix=".hg10un", dir=self.path)
181 suffix=".hg10un", dir=self.path)
182 self.tempfile = temp
182 self.tempfile = temp
183 fptemp = os.fdopen(fdtemp, 'wb')
183 fptemp = os.fdopen(fdtemp, 'wb')
184
184
185 try:
185 try:
186 fptemp.write("HG10UN")
186 fptemp.write("HG10UN")
187 while 1:
187 while 1:
188 chunk = self.bundle.read(2**18)
188 chunk = self.bundle.read(2**18)
189 if not chunk:
189 if not chunk:
190 break
190 break
191 fptemp.write(chunk)
191 fptemp.write(chunk)
192 finally:
192 finally:
193 fptemp.close()
193 fptemp.close()
194
194
195 f = util.posixfile(self.tempfile, "rb")
195 f = util.posixfile(self.tempfile, "rb")
196 self.bundle = changegroup.readbundle(f, bundlename)
196 self.bundle = changegroup.readbundle(f, bundlename)
197
197
198 # dict with the mapping 'filename' -> position in the bundle
198 # dict with the mapping 'filename' -> position in the bundle
199 self.bundlefilespos = {}
199 self.bundlefilespos = {}
200
200
201 @util.propertycache
201 @util.propertycache
202 def changelog(self):
202 def changelog(self):
203 c = bundlechangelog(self.sopener, self.bundle)
203 c = bundlechangelog(self.sopener, self.bundle)
204 self.manstart = self.bundle.tell()
204 self.manstart = self.bundle.tell()
205 return c
205 return c
206
206
207 @util.propertycache
207 @util.propertycache
208 def manifest(self):
208 def manifest(self):
209 self.bundle.seek(self.manstart)
209 self.bundle.seek(self.manstart)
210 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
210 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
211 self.filestart = self.bundle.tell()
211 self.filestart = self.bundle.tell()
212 return m
212 return m
213
213
214 @util.propertycache
214 @util.propertycache
215 def manstart(self):
215 def manstart(self):
216 self.changelog
216 self.changelog
217 return self.manstart
217 return self.manstart
218
218
219 @util.propertycache
219 @util.propertycache
220 def filestart(self):
220 def filestart(self):
221 self.manifest
221 self.manifest
222 return self.filestart
222 return self.filestart
223
223
224 def url(self):
224 def url(self):
225 return self._url
225 return self._url
226
226
227 def file(self, f):
227 def file(self, f):
228 if not self.bundlefilespos:
228 if not self.bundlefilespos:
229 self.bundle.seek(self.filestart)
229 self.bundle.seek(self.filestart)
230 while 1:
230 while 1:
231 chunk = self.bundle.chunk()
231 chunk = self.bundle.chunk()
232 if not chunk:
232 if not chunk:
233 break
233 break
234 self.bundlefilespos[chunk] = self.bundle.tell()
234 self.bundlefilespos[chunk] = self.bundle.tell()
235 while 1:
235 while 1:
236 c = self.bundle.chunk()
236 c = self.bundle.chunk()
237 if not c:
237 if not c:
238 break
238 break
239
239
240 if f[0] == '/':
240 if f[0] == '/':
241 f = f[1:]
241 f = f[1:]
242 if f in self.bundlefilespos:
242 if f in self.bundlefilespos:
243 self.bundle.seek(self.bundlefilespos[f])
243 self.bundle.seek(self.bundlefilespos[f])
244 return bundlefilelog(self.sopener, f, self.bundle,
244 return bundlefilelog(self.sopener, f, self.bundle,
245 self.changelog.rev)
245 self.changelog.rev)
246 else:
246 else:
247 return filelog.filelog(self.sopener, f)
247 return filelog.filelog(self.sopener, f)
248
248
249 def close(self):
249 def close(self):
250 """Close assigned bundle file immediately."""
250 """Close assigned bundle file immediately."""
251 self.bundle.close()
251 self.bundle.close()
252 if self.tempfile is not None:
252 if self.tempfile is not None:
253 os.unlink(self.tempfile)
253 os.unlink(self.tempfile)
254 if self._tempparent:
254 if self._tempparent:
255 shutil.rmtree(self._tempparent, True)
255 shutil.rmtree(self._tempparent, True)
256
256
257 def cancopy(self):
257 def cancopy(self):
258 return False
258 return False
259
259
260 def getcwd(self):
260 def getcwd(self):
261 return os.getcwd() # always outside the repo
261 return os.getcwd() # always outside the repo
262
262
263 def instance(ui, path, create):
263 def instance(ui, path, create):
264 if create:
264 if create:
265 raise util.Abort(_('cannot create new bundle repository'))
265 raise util.Abort(_('cannot create new bundle repository'))
266 parentpath = ui.config("bundle", "mainreporoot", "")
266 parentpath = ui.config("bundle", "mainreporoot", "")
267 if parentpath:
267 if parentpath:
268 # Try to make the full path relative so we get a nice, short URL.
268 # Try to make the full path relative so we get a nice, short URL.
269 # In particular, we don't want temp dir names in test outputs.
269 # In particular, we don't want temp dir names in test outputs.
270 cwd = os.getcwd()
270 cwd = os.getcwd()
271 if parentpath == cwd:
271 if parentpath == cwd:
272 parentpath = ''
272 parentpath = ''
273 else:
273 else:
274 cwd = os.path.join(cwd,'')
274 cwd = os.path.join(cwd,'')
275 if parentpath.startswith(cwd):
275 if parentpath.startswith(cwd):
276 parentpath = parentpath[len(cwd):]
276 parentpath = parentpath[len(cwd):]
277 path = util.drop_scheme('file', path)
277 path = util.drop_scheme('file', path)
278 if path.startswith('bundle:'):
278 if path.startswith('bundle:'):
279 path = util.drop_scheme('bundle', path)
279 path = util.drop_scheme('bundle', path)
280 s = path.split("+", 1)
280 s = path.split("+", 1)
281 if len(s) == 1:
281 if len(s) == 1:
282 repopath, bundlename = parentpath, s[0]
282 repopath, bundlename = parentpath, s[0]
283 else:
283 else:
284 repopath, bundlename = s
284 repopath, bundlename = s
285 else:
285 else:
286 repopath, bundlename = parentpath, path
286 repopath, bundlename = parentpath, path
287 return bundlerepository(ui, repopath, bundlename)
287 return bundlerepository(ui, repopath, bundlename)
288
288
289 def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False):
289 def getremotechanges(ui, repo, other, revs=None, bundlename=None,
290 tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
290 force=False, usecommon=False):
291 tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force,
292 commononly=usecommon)
291 common, incoming, rheads = tmp
293 common, incoming, rheads = tmp
292 if not incoming:
294 if not incoming:
293 try:
295 try:
294 os.unlink(bundlename)
296 os.unlink(bundlename)
295 except:
297 except:
296 pass
298 pass
297 return other, None, None
299 return other, None, None, None
298
300
299 bundle = None
301 bundle = None
300 if bundlename or not other.local():
302 if bundlename or not other.local():
301 # create a bundle (uncompressed if other repo is not local)
303 # create a bundle (uncompressed if other repo is not local)
302
304
303 if revs is None and other.capable('changegroupsubset'):
305 if revs is None and other.capable('changegroupsubset'):
304 revs = rheads
306 revs = rheads
305
307
306 if revs is None:
308 if usecommon:
309 cg = other.getbundle('incoming', common=common, heads=revs)
310 elif revs is None:
307 cg = other.changegroup(incoming, "incoming")
311 cg = other.changegroup(incoming, "incoming")
308 else:
312 else:
309 cg = other.changegroupsubset(incoming, revs, 'incoming')
313 cg = other.changegroupsubset(incoming, revs, 'incoming')
310 bundletype = other.local() and "HG10BZ" or "HG10UN"
314 bundletype = other.local() and "HG10BZ" or "HG10UN"
311 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
315 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
312 # keep written bundle?
316 # keep written bundle?
313 if bundlename:
317 if bundlename:
314 bundle = None
318 bundle = None
315 if not other.local():
319 if not other.local():
316 # use the created uncompressed bundlerepo
320 # use the created uncompressed bundlerepo
317 other = bundlerepository(ui, repo.root, fname)
321 other = bundlerepository(ui, repo.root, fname)
318 return (other, incoming, bundle)
322 return (other, common, incoming, bundle)
319
323
@@ -1,316 +1,324 b''
1 # discovery.py - protocol changeset discovery functions
1 # discovery.py - protocol changeset discovery functions
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, short
8 from node import nullid, short
9 from i18n import _
9 from i18n import _
10 import util, error
10 import util, error
11
11
12 def findcommonincoming(repo, remote, heads=None, force=False):
12 def findcommonincoming(repo, remote, heads=None, force=False, commononly=False):
13 """Return a tuple (common, missing roots, heads) used to identify
13 """Return a tuple (common, missing, heads) used to identify missing nodes
14 missing nodes from remote.
14 from remote. "missing" is either a boolean indicating if any nodes are missing
15 (when commononly=True), or else a list of the root nodes of the missing set.
15
16
16 If a list of heads is specified, return only nodes which are heads
17 If a list of heads is specified, return only nodes which are heads
17 or ancestors of these heads.
18 or ancestors of these heads.
18 """
19 """
19 m = repo.changelog.nodemap
20 m = repo.changelog.nodemap
20 search = []
21 search = []
21 fetch = set()
22 fetch = set()
22 seen = set()
23 seen = set()
23 seenbranch = set()
24 seenbranch = set()
24 base = set()
25 base = set()
25
26
26 if not heads:
27 if not heads:
27 heads = remote.heads()
28 heads = remote.heads()
28
29
29 if repo.changelog.tip() == nullid:
30 if repo.changelog.tip() == nullid:
30 base.add(nullid)
31 base.add(nullid)
31 if heads != [nullid]:
32 if heads != [nullid]:
32 return [nullid], [nullid], list(heads)
33 return [nullid], [nullid], list(heads)
33 return [nullid], [], []
34 return [nullid], [], []
34
35
35 # assume we're closer to the tip than the root
36 # assume we're closer to the tip than the root
36 # and start by examining the heads
37 # and start by examining the heads
37 repo.ui.status(_("searching for changes\n"))
38 repo.ui.status(_("searching for changes\n"))
38
39
40 if commononly:
41 myheads = repo.heads()
42 known = remote.known(myheads)
43 if util.all(known):
44 hasincoming = set(heads).difference(set(myheads)) and True
45 return myheads, hasincoming, heads
46
39 unknown = []
47 unknown = []
40 for h in heads:
48 for h in heads:
41 if h not in m:
49 if h not in m:
42 unknown.append(h)
50 unknown.append(h)
43 else:
51 else:
44 base.add(h)
52 base.add(h)
45
53
46 heads = unknown
54 heads = unknown
47 if not unknown:
55 if not unknown:
48 return list(base), [], []
56 return list(base), [], []
49
57
50 req = set(unknown)
58 req = set(unknown)
51 reqcnt = 0
59 reqcnt = 0
52
60
53 # search through remote branches
61 # search through remote branches
54 # a 'branch' here is a linear segment of history, with four parts:
62 # a 'branch' here is a linear segment of history, with four parts:
55 # head, root, first parent, second parent
63 # head, root, first parent, second parent
56 # (a branch always has two parents (or none) by definition)
64 # (a branch always has two parents (or none) by definition)
57 unknown = remote.branches(unknown)
65 unknown = remote.branches(unknown)
58 while unknown:
66 while unknown:
59 r = []
67 r = []
60 while unknown:
68 while unknown:
61 n = unknown.pop(0)
69 n = unknown.pop(0)
62 if n[0] in seen:
70 if n[0] in seen:
63 continue
71 continue
64
72
65 repo.ui.debug("examining %s:%s\n"
73 repo.ui.debug("examining %s:%s\n"
66 % (short(n[0]), short(n[1])))
74 % (short(n[0]), short(n[1])))
67 if n[0] == nullid: # found the end of the branch
75 if n[0] == nullid: # found the end of the branch
68 pass
76 pass
69 elif n in seenbranch:
77 elif n in seenbranch:
70 repo.ui.debug("branch already found\n")
78 repo.ui.debug("branch already found\n")
71 continue
79 continue
72 elif n[1] and n[1] in m: # do we know the base?
80 elif n[1] and n[1] in m: # do we know the base?
73 repo.ui.debug("found incomplete branch %s:%s\n"
81 repo.ui.debug("found incomplete branch %s:%s\n"
74 % (short(n[0]), short(n[1])))
82 % (short(n[0]), short(n[1])))
75 search.append(n[0:2]) # schedule branch range for scanning
83 search.append(n[0:2]) # schedule branch range for scanning
76 seenbranch.add(n)
84 seenbranch.add(n)
77 else:
85 else:
78 if n[1] not in seen and n[1] not in fetch:
86 if n[1] not in seen and n[1] not in fetch:
79 if n[2] in m and n[3] in m:
87 if n[2] in m and n[3] in m:
80 repo.ui.debug("found new changeset %s\n" %
88 repo.ui.debug("found new changeset %s\n" %
81 short(n[1]))
89 short(n[1]))
82 fetch.add(n[1]) # earliest unknown
90 fetch.add(n[1]) # earliest unknown
83 for p in n[2:4]:
91 for p in n[2:4]:
84 if p in m:
92 if p in m:
85 base.add(p) # latest known
93 base.add(p) # latest known
86
94
87 for p in n[2:4]:
95 for p in n[2:4]:
88 if p not in req and p not in m:
96 if p not in req and p not in m:
89 r.append(p)
97 r.append(p)
90 req.add(p)
98 req.add(p)
91 seen.add(n[0])
99 seen.add(n[0])
92
100
93 if r:
101 if r:
94 reqcnt += 1
102 reqcnt += 1
95 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
103 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
96 repo.ui.debug("request %d: %s\n" %
104 repo.ui.debug("request %d: %s\n" %
97 (reqcnt, " ".join(map(short, r))))
105 (reqcnt, " ".join(map(short, r))))
98 for p in xrange(0, len(r), 10):
106 for p in xrange(0, len(r), 10):
99 for b in remote.branches(r[p:p + 10]):
107 for b in remote.branches(r[p:p + 10]):
100 repo.ui.debug("received %s:%s\n" %
108 repo.ui.debug("received %s:%s\n" %
101 (short(b[0]), short(b[1])))
109 (short(b[0]), short(b[1])))
102 unknown.append(b)
110 unknown.append(b)
103
111
104 # do binary search on the branches we found
112 # do binary search on the branches we found
105 while search:
113 while search:
106 newsearch = []
114 newsearch = []
107 reqcnt += 1
115 reqcnt += 1
108 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
116 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
109 for n, l in zip(search, remote.between(search)):
117 for n, l in zip(search, remote.between(search)):
110 l.append(n[1])
118 l.append(n[1])
111 p = n[0]
119 p = n[0]
112 f = 1
120 f = 1
113 for i in l:
121 for i in l:
114 repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
122 repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
115 if i in m:
123 if i in m:
116 if f <= 2:
124 if f <= 2:
117 repo.ui.debug("found new branch changeset %s\n" %
125 repo.ui.debug("found new branch changeset %s\n" %
118 short(p))
126 short(p))
119 fetch.add(p)
127 fetch.add(p)
120 base.add(i)
128 base.add(i)
121 else:
129 else:
122 repo.ui.debug("narrowed branch search to %s:%s\n"
130 repo.ui.debug("narrowed branch search to %s:%s\n"
123 % (short(p), short(i)))
131 % (short(p), short(i)))
124 newsearch.append((p, i))
132 newsearch.append((p, i))
125 break
133 break
126 p, f = i, f * 2
134 p, f = i, f * 2
127 search = newsearch
135 search = newsearch
128
136
129 # sanity check our fetch list
137 # sanity check our fetch list
130 for f in fetch:
138 for f in fetch:
131 if f in m:
139 if f in m:
132 raise error.RepoError(_("already have changeset ")
140 raise error.RepoError(_("already have changeset ")
133 + short(f[:4]))
141 + short(f[:4]))
134
142
135 base = list(base)
143 base = list(base)
136 if base == [nullid]:
144 if base == [nullid]:
137 if force:
145 if force:
138 repo.ui.warn(_("warning: repository is unrelated\n"))
146 repo.ui.warn(_("warning: repository is unrelated\n"))
139 else:
147 else:
140 raise util.Abort(_("repository is unrelated"))
148 raise util.Abort(_("repository is unrelated"))
141
149
142 repo.ui.debug("found new changesets starting at " +
150 repo.ui.debug("found new changesets starting at " +
143 " ".join([short(f) for f in fetch]) + "\n")
151 " ".join([short(f) for f in fetch]) + "\n")
144
152
145 repo.ui.progress(_('searching'), None)
153 repo.ui.progress(_('searching'), None)
146 repo.ui.debug("%d total queries\n" % reqcnt)
154 repo.ui.debug("%d total queries\n" % reqcnt)
147
155
148 return base, list(fetch), heads
156 return base, list(fetch), heads
149
157
150 def findoutgoing(repo, remote, base=None, remoteheads=None, force=False):
158 def findoutgoing(repo, remote, base=None, remoteheads=None, force=False):
151 """Return list of nodes that are roots of subsets not in remote
159 """Return list of nodes that are roots of subsets not in remote
152
160
153 If base dict is specified, assume that these nodes and their parents
161 If base dict is specified, assume that these nodes and their parents
154 exist on the remote side.
162 exist on the remote side.
155 If remotehead is specified, assume it is the list of the heads from
163 If remotehead is specified, assume it is the list of the heads from
156 the remote repository.
164 the remote repository.
157 """
165 """
158 if base is None:
166 if base is None:
159 base = findcommonincoming(repo, remote, heads=remoteheads,
167 base = findcommonincoming(repo, remote, heads=remoteheads,
160 force=force)[0]
168 force=force)[0]
161 else:
169 else:
162 base = list(base)
170 base = list(base)
163
171
164 repo.ui.debug("common changesets up to "
172 repo.ui.debug("common changesets up to "
165 + " ".join(map(short, base)) + "\n")
173 + " ".join(map(short, base)) + "\n")
166
174
167 remain = set(repo.changelog.nodemap)
175 remain = set(repo.changelog.nodemap)
168
176
169 # prune everything remote has from the tree
177 # prune everything remote has from the tree
170 remain.remove(nullid)
178 remain.remove(nullid)
171 remove = base
179 remove = base
172 while remove:
180 while remove:
173 n = remove.pop(0)
181 n = remove.pop(0)
174 if n in remain:
182 if n in remain:
175 remain.remove(n)
183 remain.remove(n)
176 for p in repo.changelog.parents(n):
184 for p in repo.changelog.parents(n):
177 remove.append(p)
185 remove.append(p)
178
186
179 # find every node whose parents have been pruned
187 # find every node whose parents have been pruned
180 subset = []
188 subset = []
181 # find every remote head that will get new children
189 # find every remote head that will get new children
182 for n in remain:
190 for n in remain:
183 p1, p2 = repo.changelog.parents(n)
191 p1, p2 = repo.changelog.parents(n)
184 if p1 not in remain and p2 not in remain:
192 if p1 not in remain and p2 not in remain:
185 subset.append(n)
193 subset.append(n)
186
194
187 return subset
195 return subset
188
196
189 def prepush(repo, remote, force, revs, newbranch):
197 def prepush(repo, remote, force, revs, newbranch):
190 '''Analyze the local and remote repositories and determine which
198 '''Analyze the local and remote repositories and determine which
191 changesets need to be pushed to the remote. Return value depends
199 changesets need to be pushed to the remote. Return value depends
192 on circumstances:
200 on circumstances:
193
201
194 If we are not going to push anything, return a tuple (None,
202 If we are not going to push anything, return a tuple (None,
195 outgoing) where outgoing is 0 if there are no outgoing
203 outgoing) where outgoing is 0 if there are no outgoing
196 changesets and 1 if there are, but we refuse to push them
204 changesets and 1 if there are, but we refuse to push them
197 (e.g. would create new remote heads).
205 (e.g. would create new remote heads).
198
206
199 Otherwise, return a tuple (changegroup, remoteheads), where
207 Otherwise, return a tuple (changegroup, remoteheads), where
200 changegroup is a readable file-like object whose read() returns
208 changegroup is a readable file-like object whose read() returns
201 successive changegroup chunks ready to be sent over the wire and
209 successive changegroup chunks ready to be sent over the wire and
202 remoteheads is the list of remote heads.'''
210 remoteheads is the list of remote heads.'''
203 remoteheads = remote.heads()
211 remoteheads = remote.heads()
204 common, inc, rheads = findcommonincoming(repo, remote, heads=remoteheads,
212 common, inc, rheads = findcommonincoming(repo, remote, heads=remoteheads,
205 force=force)
213 force=force)
206
214
207 cl = repo.changelog
215 cl = repo.changelog
208 update = findoutgoing(repo, remote, common, remoteheads)
216 update = findoutgoing(repo, remote, common, remoteheads)
209 outg, bases, heads = cl.nodesbetween(update, revs)
217 outg, bases, heads = cl.nodesbetween(update, revs)
210
218
211 if not bases:
219 if not bases:
212 repo.ui.status(_("no changes found\n"))
220 repo.ui.status(_("no changes found\n"))
213 return None, 1
221 return None, 1
214
222
215 if not force and remoteheads != [nullid]:
223 if not force and remoteheads != [nullid]:
216 if remote.capable('branchmap'):
224 if remote.capable('branchmap'):
217 # Check for each named branch if we're creating new remote heads.
225 # Check for each named branch if we're creating new remote heads.
218 # To be a remote head after push, node must be either:
226 # To be a remote head after push, node must be either:
219 # - unknown locally
227 # - unknown locally
220 # - a local outgoing head descended from update
228 # - a local outgoing head descended from update
221 # - a remote head that's known locally and not
229 # - a remote head that's known locally and not
222 # ancestral to an outgoing head
230 # ancestral to an outgoing head
223
231
224 # 1. Create set of branches involved in the push.
232 # 1. Create set of branches involved in the push.
225 branches = set(repo[n].branch() for n in outg)
233 branches = set(repo[n].branch() for n in outg)
226
234
227 # 2. Check for new branches on the remote.
235 # 2. Check for new branches on the remote.
228 remotemap = remote.branchmap()
236 remotemap = remote.branchmap()
229 newbranches = branches - set(remotemap)
237 newbranches = branches - set(remotemap)
230 if newbranches and not newbranch: # new branch requires --new-branch
238 if newbranches and not newbranch: # new branch requires --new-branch
231 branchnames = ', '.join(sorted(newbranches))
239 branchnames = ', '.join(sorted(newbranches))
232 raise util.Abort(_("push creates new remote branches: %s!")
240 raise util.Abort(_("push creates new remote branches: %s!")
233 % branchnames,
241 % branchnames,
234 hint=_("use 'hg push --new-branch' to create"
242 hint=_("use 'hg push --new-branch' to create"
235 " new remote branches"))
243 " new remote branches"))
236 branches.difference_update(newbranches)
244 branches.difference_update(newbranches)
237
245
238 # 3. Construct the initial oldmap and newmap dicts.
246 # 3. Construct the initial oldmap and newmap dicts.
239 # They contain information about the remote heads before and
247 # They contain information about the remote heads before and
240 # after the push, respectively.
248 # after the push, respectively.
241 # Heads not found locally are not included in either dict,
249 # Heads not found locally are not included in either dict,
242 # since they won't be affected by the push.
250 # since they won't be affected by the push.
243 # unsynced contains all branches with incoming changesets.
251 # unsynced contains all branches with incoming changesets.
244 oldmap = {}
252 oldmap = {}
245 newmap = {}
253 newmap = {}
246 unsynced = set()
254 unsynced = set()
247 for branch in branches:
255 for branch in branches:
248 remotebrheads = remotemap[branch]
256 remotebrheads = remotemap[branch]
249 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
257 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
250 oldmap[branch] = prunedbrheads
258 oldmap[branch] = prunedbrheads
251 newmap[branch] = list(prunedbrheads)
259 newmap[branch] = list(prunedbrheads)
252 if len(remotebrheads) > len(prunedbrheads):
260 if len(remotebrheads) > len(prunedbrheads):
253 unsynced.add(branch)
261 unsynced.add(branch)
254
262
255 # 4. Update newmap with outgoing changes.
263 # 4. Update newmap with outgoing changes.
256 # This will possibly add new heads and remove existing ones.
264 # This will possibly add new heads and remove existing ones.
257 ctxgen = (repo[n] for n in outg)
265 ctxgen = (repo[n] for n in outg)
258 repo._updatebranchcache(newmap, ctxgen)
266 repo._updatebranchcache(newmap, ctxgen)
259
267
260 else:
268 else:
261 # 1-4b. old servers: Check for new topological heads.
269 # 1-4b. old servers: Check for new topological heads.
262 # Construct {old,new}map with branch = None (topological branch).
270 # Construct {old,new}map with branch = None (topological branch).
263 # (code based on _updatebranchcache)
271 # (code based on _updatebranchcache)
264 oldheads = set(h for h in remoteheads if h in cl.nodemap)
272 oldheads = set(h for h in remoteheads if h in cl.nodemap)
265 newheads = oldheads.union(outg)
273 newheads = oldheads.union(outg)
266 if len(newheads) > 1:
274 if len(newheads) > 1:
267 for latest in reversed(outg):
275 for latest in reversed(outg):
268 if latest not in newheads:
276 if latest not in newheads:
269 continue
277 continue
270 minhrev = min(cl.rev(h) for h in newheads)
278 minhrev = min(cl.rev(h) for h in newheads)
271 reachable = cl.reachable(latest, cl.node(minhrev))
279 reachable = cl.reachable(latest, cl.node(minhrev))
272 reachable.remove(latest)
280 reachable.remove(latest)
273 newheads.difference_update(reachable)
281 newheads.difference_update(reachable)
274 branches = set([None])
282 branches = set([None])
275 newmap = {None: newheads}
283 newmap = {None: newheads}
276 oldmap = {None: oldheads}
284 oldmap = {None: oldheads}
277 unsynced = inc and branches or set()
285 unsynced = inc and branches or set()
278
286
279 # 5. Check for new heads.
287 # 5. Check for new heads.
280 # If there are more heads after the push than before, a suitable
288 # If there are more heads after the push than before, a suitable
281 # error message, depending on unsynced status, is displayed.
289 # error message, depending on unsynced status, is displayed.
282 error = None
290 error = None
283 for branch in branches:
291 for branch in branches:
284 newhs = set(newmap[branch])
292 newhs = set(newmap[branch])
285 oldhs = set(oldmap[branch])
293 oldhs = set(oldmap[branch])
286 if len(newhs) > len(oldhs):
294 if len(newhs) > len(oldhs):
287 if error is None:
295 if error is None:
288 if branch:
296 if branch:
289 error = _("push creates new remote heads "
297 error = _("push creates new remote heads "
290 "on branch '%s'!") % branch
298 "on branch '%s'!") % branch
291 else:
299 else:
292 error = _("push creates new remote heads!")
300 error = _("push creates new remote heads!")
293 if branch in unsynced:
301 if branch in unsynced:
294 hint = _("you should pull and merge or "
302 hint = _("you should pull and merge or "
295 "use push -f to force")
303 "use push -f to force")
296 else:
304 else:
297 hint = _("did you forget to merge? "
305 hint = _("did you forget to merge? "
298 "use push -f to force")
306 "use push -f to force")
299 if branch:
307 if branch:
300 repo.ui.debug("new remote heads on branch '%s'\n" % branch)
308 repo.ui.debug("new remote heads on branch '%s'\n" % branch)
301 for h in (newhs - oldhs):
309 for h in (newhs - oldhs):
302 repo.ui.debug("new remote head %s\n" % short(h))
310 repo.ui.debug("new remote head %s\n" % short(h))
303 if error:
311 if error:
304 raise util.Abort(error, hint=hint)
312 raise util.Abort(error, hint=hint)
305
313
306 # 6. Check for unsynced changes on involved branches.
314 # 6. Check for unsynced changes on involved branches.
307 if unsynced:
315 if unsynced:
308 repo.ui.warn(_("note: unsynced remote changes!\n"))
316 repo.ui.warn(_("note: unsynced remote changes!\n"))
309
317
310 if revs is None:
318 if revs is None:
311 # use the fast path, no race possible on push
319 # use the fast path, no race possible on push
312 nodes = repo.changelog.findmissing(common)
320 nodes = repo.changelog.findmissing(common)
313 cg = repo._changegroup(nodes, 'push')
321 cg = repo._changegroup(nodes, 'push')
314 else:
322 else:
315 cg = repo.changegroupsubset(update, revs, 'push')
323 cg = repo.changegroupsubset(update, revs, 'push')
316 return cg, remoteheads
324 return cg, remoteheads
@@ -1,569 +1,574 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from i18n import _
9 from i18n import _
10 from lock import release
10 from lock import release
11 from node import hex, nullid, nullrev, short
11 from node import hex, nullid, nullrev, short
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, bookmarks
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, bookmarks
13 import lock, util, extensions, error, encoding, node
13 import lock, util, extensions, error, encoding, node
14 import cmdutil, discovery, url
14 import cmdutil, discovery, url
15 import merge as mergemod
15 import merge as mergemod
16 import verify as verifymod
16 import verify as verifymod
17 import errno, os, shutil
17 import errno, os, shutil
18
18
19 def _local(path):
19 def _local(path):
20 path = util.expandpath(util.drop_scheme('file', path))
20 path = util.expandpath(util.drop_scheme('file', path))
21 return (os.path.isfile(path) and bundlerepo or localrepo)
21 return (os.path.isfile(path) and bundlerepo or localrepo)
22
22
23 def addbranchrevs(lrepo, repo, branches, revs):
23 def addbranchrevs(lrepo, repo, branches, revs):
24 hashbranch, branches = branches
24 hashbranch, branches = branches
25 if not hashbranch and not branches:
25 if not hashbranch and not branches:
26 return revs or None, revs and revs[0] or None
26 return revs or None, revs and revs[0] or None
27 revs = revs and list(revs) or []
27 revs = revs and list(revs) or []
28 if not repo.capable('branchmap'):
28 if not repo.capable('branchmap'):
29 if branches:
29 if branches:
30 raise util.Abort(_("remote branch lookup not supported"))
30 raise util.Abort(_("remote branch lookup not supported"))
31 revs.append(hashbranch)
31 revs.append(hashbranch)
32 return revs, revs[0]
32 return revs, revs[0]
33 branchmap = repo.branchmap()
33 branchmap = repo.branchmap()
34
34
35 def primary(branch):
35 def primary(branch):
36 if branch == '.':
36 if branch == '.':
37 if not lrepo or not lrepo.local():
37 if not lrepo or not lrepo.local():
38 raise util.Abort(_("dirstate branch not accessible"))
38 raise util.Abort(_("dirstate branch not accessible"))
39 branch = lrepo.dirstate.branch()
39 branch = lrepo.dirstate.branch()
40 if branch in branchmap:
40 if branch in branchmap:
41 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
41 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
42 return True
42 return True
43 else:
43 else:
44 return False
44 return False
45
45
46 for branch in branches:
46 for branch in branches:
47 if not primary(branch):
47 if not primary(branch):
48 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
48 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
49 if hashbranch:
49 if hashbranch:
50 if not primary(hashbranch):
50 if not primary(hashbranch):
51 revs.append(hashbranch)
51 revs.append(hashbranch)
52 return revs, revs[0]
52 return revs, revs[0]
53
53
54 def parseurl(url, branches=None):
54 def parseurl(url, branches=None):
55 '''parse url#branch, returning (url, (branch, branches))'''
55 '''parse url#branch, returning (url, (branch, branches))'''
56
56
57 if '#' not in url:
57 if '#' not in url:
58 return url, (None, branches or [])
58 return url, (None, branches or [])
59 url, branch = url.split('#', 1)
59 url, branch = url.split('#', 1)
60 return url, (branch, branches or [])
60 return url, (branch, branches or [])
61
61
62 schemes = {
62 schemes = {
63 'bundle': bundlerepo,
63 'bundle': bundlerepo,
64 'file': _local,
64 'file': _local,
65 'http': httprepo,
65 'http': httprepo,
66 'https': httprepo,
66 'https': httprepo,
67 'ssh': sshrepo,
67 'ssh': sshrepo,
68 'static-http': statichttprepo,
68 'static-http': statichttprepo,
69 }
69 }
70
70
71 def _lookup(path):
71 def _lookup(path):
72 scheme = 'file'
72 scheme = 'file'
73 if path:
73 if path:
74 c = path.find(':')
74 c = path.find(':')
75 if c > 0:
75 if c > 0:
76 scheme = path[:c]
76 scheme = path[:c]
77 thing = schemes.get(scheme) or schemes['file']
77 thing = schemes.get(scheme) or schemes['file']
78 try:
78 try:
79 return thing(path)
79 return thing(path)
80 except TypeError:
80 except TypeError:
81 return thing
81 return thing
82
82
83 def islocal(repo):
83 def islocal(repo):
84 '''return true if repo or path is local'''
84 '''return true if repo or path is local'''
85 if isinstance(repo, str):
85 if isinstance(repo, str):
86 try:
86 try:
87 return _lookup(repo).islocal(repo)
87 return _lookup(repo).islocal(repo)
88 except AttributeError:
88 except AttributeError:
89 return False
89 return False
90 return repo.local()
90 return repo.local()
91
91
92 def repository(ui, path='', create=False):
92 def repository(ui, path='', create=False):
93 """return a repository object for the specified path"""
93 """return a repository object for the specified path"""
94 repo = _lookup(path).instance(ui, path, create)
94 repo = _lookup(path).instance(ui, path, create)
95 ui = getattr(repo, "ui", ui)
95 ui = getattr(repo, "ui", ui)
96 for name, module in extensions.extensions():
96 for name, module in extensions.extensions():
97 hook = getattr(module, 'reposetup', None)
97 hook = getattr(module, 'reposetup', None)
98 if hook:
98 if hook:
99 hook(ui, repo)
99 hook(ui, repo)
100 return repo
100 return repo
101
101
102 def defaultdest(source):
102 def defaultdest(source):
103 '''return default destination of clone if none is given'''
103 '''return default destination of clone if none is given'''
104 return os.path.basename(os.path.normpath(source))
104 return os.path.basename(os.path.normpath(source))
105
105
106 def localpath(path):
106 def localpath(path):
107 if path.startswith('file://localhost/'):
107 if path.startswith('file://localhost/'):
108 return path[16:]
108 return path[16:]
109 if path.startswith('file://'):
109 if path.startswith('file://'):
110 return path[7:]
110 return path[7:]
111 if path.startswith('file:'):
111 if path.startswith('file:'):
112 return path[5:]
112 return path[5:]
113 return path
113 return path
114
114
115 def share(ui, source, dest=None, update=True):
115 def share(ui, source, dest=None, update=True):
116 '''create a shared repository'''
116 '''create a shared repository'''
117
117
118 if not islocal(source):
118 if not islocal(source):
119 raise util.Abort(_('can only share local repositories'))
119 raise util.Abort(_('can only share local repositories'))
120
120
121 if not dest:
121 if not dest:
122 dest = defaultdest(source)
122 dest = defaultdest(source)
123 else:
123 else:
124 dest = ui.expandpath(dest)
124 dest = ui.expandpath(dest)
125
125
126 if isinstance(source, str):
126 if isinstance(source, str):
127 origsource = ui.expandpath(source)
127 origsource = ui.expandpath(source)
128 source, branches = parseurl(origsource)
128 source, branches = parseurl(origsource)
129 srcrepo = repository(ui, source)
129 srcrepo = repository(ui, source)
130 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
130 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
131 else:
131 else:
132 srcrepo = source
132 srcrepo = source
133 origsource = source = srcrepo.url()
133 origsource = source = srcrepo.url()
134 checkout = None
134 checkout = None
135
135
136 sharedpath = srcrepo.sharedpath # if our source is already sharing
136 sharedpath = srcrepo.sharedpath # if our source is already sharing
137
137
138 root = os.path.realpath(dest)
138 root = os.path.realpath(dest)
139 roothg = os.path.join(root, '.hg')
139 roothg = os.path.join(root, '.hg')
140
140
141 if os.path.exists(roothg):
141 if os.path.exists(roothg):
142 raise util.Abort(_('destination already exists'))
142 raise util.Abort(_('destination already exists'))
143
143
144 if not os.path.isdir(root):
144 if not os.path.isdir(root):
145 os.mkdir(root)
145 os.mkdir(root)
146 os.mkdir(roothg)
146 os.mkdir(roothg)
147
147
148 requirements = ''
148 requirements = ''
149 try:
149 try:
150 requirements = srcrepo.opener('requires').read()
150 requirements = srcrepo.opener('requires').read()
151 except IOError, inst:
151 except IOError, inst:
152 if inst.errno != errno.ENOENT:
152 if inst.errno != errno.ENOENT:
153 raise
153 raise
154
154
155 requirements += 'shared\n'
155 requirements += 'shared\n'
156 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
156 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
157 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
157 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
158
158
159 default = srcrepo.ui.config('paths', 'default')
159 default = srcrepo.ui.config('paths', 'default')
160 if default:
160 if default:
161 f = file(os.path.join(roothg, 'hgrc'), 'w')
161 f = file(os.path.join(roothg, 'hgrc'), 'w')
162 f.write('[paths]\ndefault = %s\n' % default)
162 f.write('[paths]\ndefault = %s\n' % default)
163 f.close()
163 f.close()
164
164
165 r = repository(ui, root)
165 r = repository(ui, root)
166
166
167 if update:
167 if update:
168 r.ui.status(_("updating working directory\n"))
168 r.ui.status(_("updating working directory\n"))
169 if update is not True:
169 if update is not True:
170 checkout = update
170 checkout = update
171 for test in (checkout, 'default', 'tip'):
171 for test in (checkout, 'default', 'tip'):
172 if test is None:
172 if test is None:
173 continue
173 continue
174 try:
174 try:
175 uprev = r.lookup(test)
175 uprev = r.lookup(test)
176 break
176 break
177 except error.RepoLookupError:
177 except error.RepoLookupError:
178 continue
178 continue
179 _update(r, uprev)
179 _update(r, uprev)
180
180
181 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
181 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
182 stream=False, branch=None):
182 stream=False, branch=None):
183 """Make a copy of an existing repository.
183 """Make a copy of an existing repository.
184
184
185 Create a copy of an existing repository in a new directory. The
185 Create a copy of an existing repository in a new directory. The
186 source and destination are URLs, as passed to the repository
186 source and destination are URLs, as passed to the repository
187 function. Returns a pair of repository objects, the source and
187 function. Returns a pair of repository objects, the source and
188 newly created destination.
188 newly created destination.
189
189
190 The location of the source is added to the new repository's
190 The location of the source is added to the new repository's
191 .hg/hgrc file, as the default to be used for future pulls and
191 .hg/hgrc file, as the default to be used for future pulls and
192 pushes.
192 pushes.
193
193
194 If an exception is raised, the partly cloned/updated destination
194 If an exception is raised, the partly cloned/updated destination
195 repository will be deleted.
195 repository will be deleted.
196
196
197 Arguments:
197 Arguments:
198
198
199 source: repository object or URL
199 source: repository object or URL
200
200
201 dest: URL of destination repository to create (defaults to base
201 dest: URL of destination repository to create (defaults to base
202 name of source repository)
202 name of source repository)
203
203
204 pull: always pull from source repository, even in local case
204 pull: always pull from source repository, even in local case
205
205
206 stream: stream raw data uncompressed from repository (fast over
206 stream: stream raw data uncompressed from repository (fast over
207 LAN, slow over WAN)
207 LAN, slow over WAN)
208
208
209 rev: revision to clone up to (implies pull=True)
209 rev: revision to clone up to (implies pull=True)
210
210
211 update: update working directory after clone completes, if
211 update: update working directory after clone completes, if
212 destination is local repository (True means update to default rev,
212 destination is local repository (True means update to default rev,
213 anything else is treated as a revision)
213 anything else is treated as a revision)
214
214
215 branch: branches to clone
215 branch: branches to clone
216 """
216 """
217
217
218 if isinstance(source, str):
218 if isinstance(source, str):
219 origsource = ui.expandpath(source)
219 origsource = ui.expandpath(source)
220 source, branch = parseurl(origsource, branch)
220 source, branch = parseurl(origsource, branch)
221 src_repo = repository(ui, source)
221 src_repo = repository(ui, source)
222 else:
222 else:
223 src_repo = source
223 src_repo = source
224 branch = (None, branch or [])
224 branch = (None, branch or [])
225 origsource = source = src_repo.url()
225 origsource = source = src_repo.url()
226 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
226 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
227
227
228 if dest is None:
228 if dest is None:
229 dest = defaultdest(source)
229 dest = defaultdest(source)
230 ui.status(_("destination directory: %s\n") % dest)
230 ui.status(_("destination directory: %s\n") % dest)
231 else:
231 else:
232 dest = ui.expandpath(dest)
232 dest = ui.expandpath(dest)
233
233
234 dest = localpath(dest)
234 dest = localpath(dest)
235 source = localpath(source)
235 source = localpath(source)
236
236
237 if os.path.exists(dest):
237 if os.path.exists(dest):
238 if not os.path.isdir(dest):
238 if not os.path.isdir(dest):
239 raise util.Abort(_("destination '%s' already exists") % dest)
239 raise util.Abort(_("destination '%s' already exists") % dest)
240 elif os.listdir(dest):
240 elif os.listdir(dest):
241 raise util.Abort(_("destination '%s' is not empty") % dest)
241 raise util.Abort(_("destination '%s' is not empty") % dest)
242
242
243 class DirCleanup(object):
243 class DirCleanup(object):
244 def __init__(self, dir_):
244 def __init__(self, dir_):
245 self.rmtree = shutil.rmtree
245 self.rmtree = shutil.rmtree
246 self.dir_ = dir_
246 self.dir_ = dir_
247 def close(self):
247 def close(self):
248 self.dir_ = None
248 self.dir_ = None
249 def cleanup(self):
249 def cleanup(self):
250 if self.dir_:
250 if self.dir_:
251 self.rmtree(self.dir_, True)
251 self.rmtree(self.dir_, True)
252
252
253 src_lock = dest_lock = dir_cleanup = None
253 src_lock = dest_lock = dir_cleanup = None
254 try:
254 try:
255 if islocal(dest):
255 if islocal(dest):
256 dir_cleanup = DirCleanup(dest)
256 dir_cleanup = DirCleanup(dest)
257
257
258 abspath = origsource
258 abspath = origsource
259 copy = False
259 copy = False
260 if src_repo.cancopy() and islocal(dest):
260 if src_repo.cancopy() and islocal(dest):
261 abspath = os.path.abspath(util.drop_scheme('file', origsource))
261 abspath = os.path.abspath(util.drop_scheme('file', origsource))
262 copy = not pull and not rev
262 copy = not pull and not rev
263
263
264 if copy:
264 if copy:
265 try:
265 try:
266 # we use a lock here because if we race with commit, we
266 # we use a lock here because if we race with commit, we
267 # can end up with extra data in the cloned revlogs that's
267 # can end up with extra data in the cloned revlogs that's
268 # not pointed to by changesets, thus causing verify to
268 # not pointed to by changesets, thus causing verify to
269 # fail
269 # fail
270 src_lock = src_repo.lock(wait=False)
270 src_lock = src_repo.lock(wait=False)
271 except error.LockError:
271 except error.LockError:
272 copy = False
272 copy = False
273
273
274 if copy:
274 if copy:
275 src_repo.hook('preoutgoing', throw=True, source='clone')
275 src_repo.hook('preoutgoing', throw=True, source='clone')
276 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
276 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
277 if not os.path.exists(dest):
277 if not os.path.exists(dest):
278 os.mkdir(dest)
278 os.mkdir(dest)
279 else:
279 else:
280 # only clean up directories we create ourselves
280 # only clean up directories we create ourselves
281 dir_cleanup.dir_ = hgdir
281 dir_cleanup.dir_ = hgdir
282 try:
282 try:
283 dest_path = hgdir
283 dest_path = hgdir
284 os.mkdir(dest_path)
284 os.mkdir(dest_path)
285 except OSError, inst:
285 except OSError, inst:
286 if inst.errno == errno.EEXIST:
286 if inst.errno == errno.EEXIST:
287 dir_cleanup.close()
287 dir_cleanup.close()
288 raise util.Abort(_("destination '%s' already exists")
288 raise util.Abort(_("destination '%s' already exists")
289 % dest)
289 % dest)
290 raise
290 raise
291
291
292 hardlink = None
292 hardlink = None
293 num = 0
293 num = 0
294 for f in src_repo.store.copylist():
294 for f in src_repo.store.copylist():
295 src = os.path.join(src_repo.sharedpath, f)
295 src = os.path.join(src_repo.sharedpath, f)
296 dst = os.path.join(dest_path, f)
296 dst = os.path.join(dest_path, f)
297 dstbase = os.path.dirname(dst)
297 dstbase = os.path.dirname(dst)
298 if dstbase and not os.path.exists(dstbase):
298 if dstbase and not os.path.exists(dstbase):
299 os.mkdir(dstbase)
299 os.mkdir(dstbase)
300 if os.path.exists(src):
300 if os.path.exists(src):
301 if dst.endswith('data'):
301 if dst.endswith('data'):
302 # lock to avoid premature writing to the target
302 # lock to avoid premature writing to the target
303 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
303 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
304 hardlink, n = util.copyfiles(src, dst, hardlink)
304 hardlink, n = util.copyfiles(src, dst, hardlink)
305 num += n
305 num += n
306 if hardlink:
306 if hardlink:
307 ui.debug("linked %d files\n" % num)
307 ui.debug("linked %d files\n" % num)
308 else:
308 else:
309 ui.debug("copied %d files\n" % num)
309 ui.debug("copied %d files\n" % num)
310
310
311 # we need to re-init the repo after manually copying the data
311 # we need to re-init the repo after manually copying the data
312 # into it
312 # into it
313 dest_repo = repository(ui, dest)
313 dest_repo = repository(ui, dest)
314 src_repo.hook('outgoing', source='clone',
314 src_repo.hook('outgoing', source='clone',
315 node=node.hex(node.nullid))
315 node=node.hex(node.nullid))
316 else:
316 else:
317 try:
317 try:
318 dest_repo = repository(ui, dest, create=True)
318 dest_repo = repository(ui, dest, create=True)
319 except OSError, inst:
319 except OSError, inst:
320 if inst.errno == errno.EEXIST:
320 if inst.errno == errno.EEXIST:
321 dir_cleanup.close()
321 dir_cleanup.close()
322 raise util.Abort(_("destination '%s' already exists")
322 raise util.Abort(_("destination '%s' already exists")
323 % dest)
323 % dest)
324 raise
324 raise
325
325
326 revs = None
326 revs = None
327 if rev:
327 if rev:
328 if 'lookup' not in src_repo.capabilities:
328 if 'lookup' not in src_repo.capabilities:
329 raise util.Abort(_("src repository does not support "
329 raise util.Abort(_("src repository does not support "
330 "revision lookup and so doesn't "
330 "revision lookup and so doesn't "
331 "support clone by revision"))
331 "support clone by revision"))
332 revs = [src_repo.lookup(r) for r in rev]
332 revs = [src_repo.lookup(r) for r in rev]
333 checkout = revs[0]
333 checkout = revs[0]
334 if dest_repo.local():
334 if dest_repo.local():
335 dest_repo.clone(src_repo, heads=revs, stream=stream)
335 dest_repo.clone(src_repo, heads=revs, stream=stream)
336 elif src_repo.local():
336 elif src_repo.local():
337 src_repo.push(dest_repo, revs=revs)
337 src_repo.push(dest_repo, revs=revs)
338 else:
338 else:
339 raise util.Abort(_("clone from remote to remote not supported"))
339 raise util.Abort(_("clone from remote to remote not supported"))
340
340
341 if dir_cleanup:
341 if dir_cleanup:
342 dir_cleanup.close()
342 dir_cleanup.close()
343
343
344 if dest_repo.local():
344 if dest_repo.local():
345 fp = dest_repo.opener("hgrc", "w", text=True)
345 fp = dest_repo.opener("hgrc", "w", text=True)
346 fp.write("[paths]\n")
346 fp.write("[paths]\n")
347 fp.write("default = %s\n" % abspath)
347 fp.write("default = %s\n" % abspath)
348 fp.close()
348 fp.close()
349
349
350 dest_repo.ui.setconfig('paths', 'default', abspath)
350 dest_repo.ui.setconfig('paths', 'default', abspath)
351
351
352 if update:
352 if update:
353 if update is not True:
353 if update is not True:
354 checkout = update
354 checkout = update
355 if src_repo.local():
355 if src_repo.local():
356 checkout = src_repo.lookup(update)
356 checkout = src_repo.lookup(update)
357 for test in (checkout, 'default', 'tip'):
357 for test in (checkout, 'default', 'tip'):
358 if test is None:
358 if test is None:
359 continue
359 continue
360 try:
360 try:
361 uprev = dest_repo.lookup(test)
361 uprev = dest_repo.lookup(test)
362 break
362 break
363 except error.RepoLookupError:
363 except error.RepoLookupError:
364 continue
364 continue
365 bn = dest_repo[uprev].branch()
365 bn = dest_repo[uprev].branch()
366 dest_repo.ui.status(_("updating to branch %s\n") % bn)
366 dest_repo.ui.status(_("updating to branch %s\n") % bn)
367 _update(dest_repo, uprev)
367 _update(dest_repo, uprev)
368
368
369 # clone all bookmarks
369 # clone all bookmarks
370 if dest_repo.local() and src_repo.capable("pushkey"):
370 if dest_repo.local() and src_repo.capable("pushkey"):
371 rb = src_repo.listkeys('bookmarks')
371 rb = src_repo.listkeys('bookmarks')
372 for k, n in rb.iteritems():
372 for k, n in rb.iteritems():
373 try:
373 try:
374 m = dest_repo.lookup(n)
374 m = dest_repo.lookup(n)
375 dest_repo._bookmarks[k] = m
375 dest_repo._bookmarks[k] = m
376 except:
376 except:
377 pass
377 pass
378 if rb:
378 if rb:
379 bookmarks.write(dest_repo)
379 bookmarks.write(dest_repo)
380 elif src_repo.local() and dest_repo.capable("pushkey"):
380 elif src_repo.local() and dest_repo.capable("pushkey"):
381 for k, n in src_repo._bookmarks.iteritems():
381 for k, n in src_repo._bookmarks.iteritems():
382 dest_repo.pushkey('bookmarks', k, '', hex(n))
382 dest_repo.pushkey('bookmarks', k, '', hex(n))
383
383
384 return src_repo, dest_repo
384 return src_repo, dest_repo
385 finally:
385 finally:
386 release(src_lock, dest_lock)
386 release(src_lock, dest_lock)
387 if dir_cleanup is not None:
387 if dir_cleanup is not None:
388 dir_cleanup.cleanup()
388 dir_cleanup.cleanup()
389
389
390 def _showstats(repo, stats):
390 def _showstats(repo, stats):
391 repo.ui.status(_("%d files updated, %d files merged, "
391 repo.ui.status(_("%d files updated, %d files merged, "
392 "%d files removed, %d files unresolved\n") % stats)
392 "%d files removed, %d files unresolved\n") % stats)
393
393
394 def update(repo, node):
394 def update(repo, node):
395 """update the working directory to node, merging linear changes"""
395 """update the working directory to node, merging linear changes"""
396 stats = mergemod.update(repo, node, False, False, None)
396 stats = mergemod.update(repo, node, False, False, None)
397 _showstats(repo, stats)
397 _showstats(repo, stats)
398 if stats[3]:
398 if stats[3]:
399 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
399 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
400 return stats[3] > 0
400 return stats[3] > 0
401
401
402 # naming conflict in clone()
402 # naming conflict in clone()
403 _update = update
403 _update = update
404
404
405 def clean(repo, node, show_stats=True):
405 def clean(repo, node, show_stats=True):
406 """forcibly switch the working directory to node, clobbering changes"""
406 """forcibly switch the working directory to node, clobbering changes"""
407 stats = mergemod.update(repo, node, False, True, None)
407 stats = mergemod.update(repo, node, False, True, None)
408 if show_stats:
408 if show_stats:
409 _showstats(repo, stats)
409 _showstats(repo, stats)
410 return stats[3] > 0
410 return stats[3] > 0
411
411
412 def merge(repo, node, force=None, remind=True):
412 def merge(repo, node, force=None, remind=True):
413 """Branch merge with node, resolving changes. Return true if any
413 """Branch merge with node, resolving changes. Return true if any
414 unresolved conflicts."""
414 unresolved conflicts."""
415 stats = mergemod.update(repo, node, True, force, False)
415 stats = mergemod.update(repo, node, True, force, False)
416 _showstats(repo, stats)
416 _showstats(repo, stats)
417 if stats[3]:
417 if stats[3]:
418 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
418 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
419 "or 'hg update -C .' to abandon\n"))
419 "or 'hg update -C .' to abandon\n"))
420 elif remind:
420 elif remind:
421 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
421 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
422 return stats[3] > 0
422 return stats[3] > 0
423
423
424 def _incoming(displaychlist, subreporecurse, ui, repo, source,
424 def _incoming(displaychlist, subreporecurse, ui, repo, source,
425 opts, buffered=False):
425 opts, buffered=False):
426 """
426 """
427 Helper for incoming / gincoming.
427 Helper for incoming / gincoming.
428 displaychlist gets called with
428 displaychlist gets called with
429 (remoterepo, incomingchangesetlist, displayer) parameters,
429 (remoterepo, incomingchangesetlist, displayer) parameters,
430 and is supposed to contain only code that can't be unified.
430 and is supposed to contain only code that can't be unified.
431 """
431 """
432 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
432 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
433 other = repository(remoteui(repo, opts), source)
433 other = repository(remoteui(repo, opts), source)
434 ui.status(_('comparing with %s\n') % url.hidepassword(source))
434 ui.status(_('comparing with %s\n') % url.hidepassword(source))
435 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
435 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
436
436
437 if revs:
437 if revs:
438 revs = [other.lookup(rev) for rev in revs]
438 revs = [other.lookup(rev) for rev in revs]
439 other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
439 usecommon = other.capable('getbundle')
440 opts["bundle"], opts["force"])
440 other, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other,
441 if incoming is None:
441 revs, opts["bundle"], opts["force"],
442 usecommon=usecommon)
443 if not incoming:
442 ui.status(_("no changes found\n"))
444 ui.status(_("no changes found\n"))
443 return subreporecurse()
445 return subreporecurse()
444
446
445 try:
447 try:
446 chlist = other.changelog.nodesbetween(incoming, revs)[0]
448 if usecommon:
449 chlist = other.changelog.findmissing(common, revs)
450 else:
451 chlist = other.changelog.nodesbetween(incoming, revs)[0]
447 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
452 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
448
453
449 # XXX once graphlog extension makes it into core,
454 # XXX once graphlog extension makes it into core,
450 # should be replaced by a if graph/else
455 # should be replaced by a if graph/else
451 displaychlist(other, chlist, displayer)
456 displaychlist(other, chlist, displayer)
452
457
453 displayer.close()
458 displayer.close()
454 finally:
459 finally:
455 if hasattr(other, 'close'):
460 if hasattr(other, 'close'):
456 other.close()
461 other.close()
457 if bundle:
462 if bundle:
458 os.unlink(bundle)
463 os.unlink(bundle)
459 subreporecurse()
464 subreporecurse()
460 return 0 # exit code is zero since we found incoming changes
465 return 0 # exit code is zero since we found incoming changes
461
466
462 def incoming(ui, repo, source, opts):
467 def incoming(ui, repo, source, opts):
463 def subreporecurse():
468 def subreporecurse():
464 ret = 1
469 ret = 1
465 if opts.get('subrepos'):
470 if opts.get('subrepos'):
466 ctx = repo[None]
471 ctx = repo[None]
467 for subpath in sorted(ctx.substate):
472 for subpath in sorted(ctx.substate):
468 sub = ctx.sub(subpath)
473 sub = ctx.sub(subpath)
469 ret = min(ret, sub.incoming(ui, source, opts))
474 ret = min(ret, sub.incoming(ui, source, opts))
470 return ret
475 return ret
471
476
472 def display(other, chlist, displayer):
477 def display(other, chlist, displayer):
473 limit = cmdutil.loglimit(opts)
478 limit = cmdutil.loglimit(opts)
474 if opts.get('newest_first'):
479 if opts.get('newest_first'):
475 chlist.reverse()
480 chlist.reverse()
476 count = 0
481 count = 0
477 for n in chlist:
482 for n in chlist:
478 if limit is not None and count >= limit:
483 if limit is not None and count >= limit:
479 break
484 break
480 parents = [p for p in other.changelog.parents(n) if p != nullid]
485 parents = [p for p in other.changelog.parents(n) if p != nullid]
481 if opts.get('no_merges') and len(parents) == 2:
486 if opts.get('no_merges') and len(parents) == 2:
482 continue
487 continue
483 count += 1
488 count += 1
484 displayer.show(other[n])
489 displayer.show(other[n])
485 return _incoming(display, subreporecurse, ui, repo, source, opts)
490 return _incoming(display, subreporecurse, ui, repo, source, opts)
486
491
487 def _outgoing(ui, repo, dest, opts):
492 def _outgoing(ui, repo, dest, opts):
488 dest = ui.expandpath(dest or 'default-push', dest or 'default')
493 dest = ui.expandpath(dest or 'default-push', dest or 'default')
489 dest, branches = parseurl(dest, opts.get('branch'))
494 dest, branches = parseurl(dest, opts.get('branch'))
490 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
495 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
491 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
496 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
492 if revs:
497 if revs:
493 revs = [repo.lookup(rev) for rev in revs]
498 revs = [repo.lookup(rev) for rev in revs]
494
499
495 other = repository(remoteui(repo, opts), dest)
500 other = repository(remoteui(repo, opts), dest)
496 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
501 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
497 if not o:
502 if not o:
498 ui.status(_("no changes found\n"))
503 ui.status(_("no changes found\n"))
499 return None
504 return None
500
505
501 return repo.changelog.nodesbetween(o, revs)[0]
506 return repo.changelog.nodesbetween(o, revs)[0]
502
507
503 def outgoing(ui, repo, dest, opts):
508 def outgoing(ui, repo, dest, opts):
504 def recurse():
509 def recurse():
505 ret = 1
510 ret = 1
506 if opts.get('subrepos'):
511 if opts.get('subrepos'):
507 ctx = repo[None]
512 ctx = repo[None]
508 for subpath in sorted(ctx.substate):
513 for subpath in sorted(ctx.substate):
509 sub = ctx.sub(subpath)
514 sub = ctx.sub(subpath)
510 ret = min(ret, sub.outgoing(ui, dest, opts))
515 ret = min(ret, sub.outgoing(ui, dest, opts))
511 return ret
516 return ret
512
517
513 limit = cmdutil.loglimit(opts)
518 limit = cmdutil.loglimit(opts)
514 o = _outgoing(ui, repo, dest, opts)
519 o = _outgoing(ui, repo, dest, opts)
515 if o is None:
520 if o is None:
516 return recurse()
521 return recurse()
517
522
518 if opts.get('newest_first'):
523 if opts.get('newest_first'):
519 o.reverse()
524 o.reverse()
520 displayer = cmdutil.show_changeset(ui, repo, opts)
525 displayer = cmdutil.show_changeset(ui, repo, opts)
521 count = 0
526 count = 0
522 for n in o:
527 for n in o:
523 if limit is not None and count >= limit:
528 if limit is not None and count >= limit:
524 break
529 break
525 parents = [p for p in repo.changelog.parents(n) if p != nullid]
530 parents = [p for p in repo.changelog.parents(n) if p != nullid]
526 if opts.get('no_merges') and len(parents) == 2:
531 if opts.get('no_merges') and len(parents) == 2:
527 continue
532 continue
528 count += 1
533 count += 1
529 displayer.show(repo[n])
534 displayer.show(repo[n])
530 displayer.close()
535 displayer.close()
531 recurse()
536 recurse()
532 return 0 # exit code is zero since we found outgoing changes
537 return 0 # exit code is zero since we found outgoing changes
533
538
534 def revert(repo, node, choose):
539 def revert(repo, node, choose):
535 """revert changes to revision in node without updating dirstate"""
540 """revert changes to revision in node without updating dirstate"""
536 return mergemod.update(repo, node, False, True, choose)[3] > 0
541 return mergemod.update(repo, node, False, True, choose)[3] > 0
537
542
538 def verify(repo):
543 def verify(repo):
539 """verify the consistency of a repository"""
544 """verify the consistency of a repository"""
540 return verifymod.verify(repo)
545 return verifymod.verify(repo)
541
546
542 def remoteui(src, opts):
547 def remoteui(src, opts):
543 'build a remote ui from ui or repo and opts'
548 'build a remote ui from ui or repo and opts'
544 if hasattr(src, 'baseui'): # looks like a repository
549 if hasattr(src, 'baseui'): # looks like a repository
545 dst = src.baseui.copy() # drop repo-specific config
550 dst = src.baseui.copy() # drop repo-specific config
546 src = src.ui # copy target options from repo
551 src = src.ui # copy target options from repo
547 else: # assume it's a global ui object
552 else: # assume it's a global ui object
548 dst = src.copy() # keep all global options
553 dst = src.copy() # keep all global options
549
554
550 # copy ssh-specific options
555 # copy ssh-specific options
551 for o in 'ssh', 'remotecmd':
556 for o in 'ssh', 'remotecmd':
552 v = opts.get(o) or src.config('ui', o)
557 v = opts.get(o) or src.config('ui', o)
553 if v:
558 if v:
554 dst.setconfig("ui", o, v)
559 dst.setconfig("ui", o, v)
555
560
556 # copy bundle-specific options
561 # copy bundle-specific options
557 r = src.config('bundle', 'mainreporoot')
562 r = src.config('bundle', 'mainreporoot')
558 if r:
563 if r:
559 dst.setconfig('bundle', 'mainreporoot', r)
564 dst.setconfig('bundle', 'mainreporoot', r)
560
565
561 # copy selected local settings to the remote ui
566 # copy selected local settings to the remote ui
562 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
567 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
563 for key, val in src.configitems(sect):
568 for key, val in src.configitems(sect):
564 dst.setconfig(sect, key, val)
569 dst.setconfig(sect, key, val)
565 v = src.config('web', 'cacerts')
570 v = src.config('web', 'cacerts')
566 if v:
571 if v:
567 dst.setconfig('web', 'cacerts', util.expandpath(v))
572 dst.setconfig('web', 'cacerts', util.expandpath(v))
568
573
569 return dst
574 return dst
@@ -1,1951 +1,1955 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import bin, hex, nullid, nullrev, short
8 from node import bin, hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import repo, changegroup, subrepo, discovery, pushkey
10 import repo, changegroup, subrepo, discovery, pushkey
11 import changelog, dirstate, filelog, manifest, context, bookmarks
11 import changelog, dirstate, filelog, manifest, context, bookmarks
12 import lock, transaction, store, encoding
12 import lock, transaction, store, encoding
13 import util, extensions, hook, error
13 import util, extensions, hook, error
14 import match as matchmod
14 import match as matchmod
15 import merge as mergemod
15 import merge as mergemod
16 import tags as tagsmod
16 import tags as tagsmod
17 import url as urlmod
17 import url as urlmod
18 from lock import release
18 from lock import release
19 import weakref, errno, os, time, inspect
19 import weakref, errno, os, time, inspect
20 propertycache = util.propertycache
20 propertycache = util.propertycache
21
21
22 class localrepository(repo.repository):
22 class localrepository(repo.repository):
23 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
23 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
24 'known', 'getbundle'))
24 'known', 'getbundle'))
25 supportedformats = set(('revlogv1', 'parentdelta'))
25 supportedformats = set(('revlogv1', 'parentdelta'))
26 supported = supportedformats | set(('store', 'fncache', 'shared',
26 supported = supportedformats | set(('store', 'fncache', 'shared',
27 'dotencode'))
27 'dotencode'))
28
28
29 def __init__(self, baseui, path=None, create=0):
29 def __init__(self, baseui, path=None, create=0):
30 repo.repository.__init__(self)
30 repo.repository.__init__(self)
31 self.root = os.path.realpath(util.expandpath(path))
31 self.root = os.path.realpath(util.expandpath(path))
32 self.path = os.path.join(self.root, ".hg")
32 self.path = os.path.join(self.root, ".hg")
33 self.origroot = path
33 self.origroot = path
34 self.auditor = util.path_auditor(self.root, self._checknested)
34 self.auditor = util.path_auditor(self.root, self._checknested)
35 self.opener = util.opener(self.path)
35 self.opener = util.opener(self.path)
36 self.wopener = util.opener(self.root)
36 self.wopener = util.opener(self.root)
37 self.baseui = baseui
37 self.baseui = baseui
38 self.ui = baseui.copy()
38 self.ui = baseui.copy()
39
39
40 try:
40 try:
41 self.ui.readconfig(self.join("hgrc"), self.root)
41 self.ui.readconfig(self.join("hgrc"), self.root)
42 extensions.loadall(self.ui)
42 extensions.loadall(self.ui)
43 except IOError:
43 except IOError:
44 pass
44 pass
45
45
46 if not os.path.isdir(self.path):
46 if not os.path.isdir(self.path):
47 if create:
47 if create:
48 if not os.path.exists(path):
48 if not os.path.exists(path):
49 util.makedirs(path)
49 util.makedirs(path)
50 os.mkdir(self.path)
50 os.mkdir(self.path)
51 requirements = ["revlogv1"]
51 requirements = ["revlogv1"]
52 if self.ui.configbool('format', 'usestore', True):
52 if self.ui.configbool('format', 'usestore', True):
53 os.mkdir(os.path.join(self.path, "store"))
53 os.mkdir(os.path.join(self.path, "store"))
54 requirements.append("store")
54 requirements.append("store")
55 if self.ui.configbool('format', 'usefncache', True):
55 if self.ui.configbool('format', 'usefncache', True):
56 requirements.append("fncache")
56 requirements.append("fncache")
57 if self.ui.configbool('format', 'dotencode', True):
57 if self.ui.configbool('format', 'dotencode', True):
58 requirements.append('dotencode')
58 requirements.append('dotencode')
59 # create an invalid changelog
59 # create an invalid changelog
60 self.opener("00changelog.i", "a").write(
60 self.opener("00changelog.i", "a").write(
61 '\0\0\0\2' # represents revlogv2
61 '\0\0\0\2' # represents revlogv2
62 ' dummy changelog to prevent using the old repo layout'
62 ' dummy changelog to prevent using the old repo layout'
63 )
63 )
64 if self.ui.configbool('format', 'parentdelta', False):
64 if self.ui.configbool('format', 'parentdelta', False):
65 requirements.append("parentdelta")
65 requirements.append("parentdelta")
66 else:
66 else:
67 raise error.RepoError(_("repository %s not found") % path)
67 raise error.RepoError(_("repository %s not found") % path)
68 elif create:
68 elif create:
69 raise error.RepoError(_("repository %s already exists") % path)
69 raise error.RepoError(_("repository %s already exists") % path)
70 else:
70 else:
71 # find requirements
71 # find requirements
72 requirements = set()
72 requirements = set()
73 try:
73 try:
74 requirements = set(self.opener("requires").read().splitlines())
74 requirements = set(self.opener("requires").read().splitlines())
75 except IOError, inst:
75 except IOError, inst:
76 if inst.errno != errno.ENOENT:
76 if inst.errno != errno.ENOENT:
77 raise
77 raise
78 for r in requirements - self.supported:
78 for r in requirements - self.supported:
79 raise error.RequirementError(
79 raise error.RequirementError(
80 _("requirement '%s' not supported") % r)
80 _("requirement '%s' not supported") % r)
81
81
82 self.sharedpath = self.path
82 self.sharedpath = self.path
83 try:
83 try:
84 s = os.path.realpath(self.opener("sharedpath").read())
84 s = os.path.realpath(self.opener("sharedpath").read())
85 if not os.path.exists(s):
85 if not os.path.exists(s):
86 raise error.RepoError(
86 raise error.RepoError(
87 _('.hg/sharedpath points to nonexistent directory %s') % s)
87 _('.hg/sharedpath points to nonexistent directory %s') % s)
88 self.sharedpath = s
88 self.sharedpath = s
89 except IOError, inst:
89 except IOError, inst:
90 if inst.errno != errno.ENOENT:
90 if inst.errno != errno.ENOENT:
91 raise
91 raise
92
92
93 self.store = store.store(requirements, self.sharedpath, util.opener)
93 self.store = store.store(requirements, self.sharedpath, util.opener)
94 self.spath = self.store.path
94 self.spath = self.store.path
95 self.sopener = self.store.opener
95 self.sopener = self.store.opener
96 self.sjoin = self.store.join
96 self.sjoin = self.store.join
97 self.opener.createmode = self.store.createmode
97 self.opener.createmode = self.store.createmode
98 self._applyrequirements(requirements)
98 self._applyrequirements(requirements)
99 if create:
99 if create:
100 self._writerequirements()
100 self._writerequirements()
101
101
102 # These two define the set of tags for this repository. _tags
102 # These two define the set of tags for this repository. _tags
103 # maps tag name to node; _tagtypes maps tag name to 'global' or
103 # maps tag name to node; _tagtypes maps tag name to 'global' or
104 # 'local'. (Global tags are defined by .hgtags across all
104 # 'local'. (Global tags are defined by .hgtags across all
105 # heads, and local tags are defined in .hg/localtags.) They
105 # heads, and local tags are defined in .hg/localtags.) They
106 # constitute the in-memory cache of tags.
106 # constitute the in-memory cache of tags.
107 self._tags = None
107 self._tags = None
108 self._tagtypes = None
108 self._tagtypes = None
109
109
110 self._branchcache = None
110 self._branchcache = None
111 self._branchcachetip = None
111 self._branchcachetip = None
112 self.nodetagscache = None
112 self.nodetagscache = None
113 self.filterpats = {}
113 self.filterpats = {}
114 self._datafilters = {}
114 self._datafilters = {}
115 self._transref = self._lockref = self._wlockref = None
115 self._transref = self._lockref = self._wlockref = None
116
116
117 def _applyrequirements(self, requirements):
117 def _applyrequirements(self, requirements):
118 self.requirements = requirements
118 self.requirements = requirements
119 self.sopener.options = {}
119 self.sopener.options = {}
120 if 'parentdelta' in requirements:
120 if 'parentdelta' in requirements:
121 self.sopener.options['parentdelta'] = 1
121 self.sopener.options['parentdelta'] = 1
122
122
123 def _writerequirements(self):
123 def _writerequirements(self):
124 reqfile = self.opener("requires", "w")
124 reqfile = self.opener("requires", "w")
125 for r in self.requirements:
125 for r in self.requirements:
126 reqfile.write("%s\n" % r)
126 reqfile.write("%s\n" % r)
127 reqfile.close()
127 reqfile.close()
128
128
129 def _checknested(self, path):
129 def _checknested(self, path):
130 """Determine if path is a legal nested repository."""
130 """Determine if path is a legal nested repository."""
131 if not path.startswith(self.root):
131 if not path.startswith(self.root):
132 return False
132 return False
133 subpath = path[len(self.root) + 1:]
133 subpath = path[len(self.root) + 1:]
134
134
135 # XXX: Checking against the current working copy is wrong in
135 # XXX: Checking against the current working copy is wrong in
136 # the sense that it can reject things like
136 # the sense that it can reject things like
137 #
137 #
138 # $ hg cat -r 10 sub/x.txt
138 # $ hg cat -r 10 sub/x.txt
139 #
139 #
140 # if sub/ is no longer a subrepository in the working copy
140 # if sub/ is no longer a subrepository in the working copy
141 # parent revision.
141 # parent revision.
142 #
142 #
143 # However, it can of course also allow things that would have
143 # However, it can of course also allow things that would have
144 # been rejected before, such as the above cat command if sub/
144 # been rejected before, such as the above cat command if sub/
145 # is a subrepository now, but was a normal directory before.
145 # is a subrepository now, but was a normal directory before.
146 # The old path auditor would have rejected by mistake since it
146 # The old path auditor would have rejected by mistake since it
147 # panics when it sees sub/.hg/.
147 # panics when it sees sub/.hg/.
148 #
148 #
149 # All in all, checking against the working copy seems sensible
149 # All in all, checking against the working copy seems sensible
150 # since we want to prevent access to nested repositories on
150 # since we want to prevent access to nested repositories on
151 # the filesystem *now*.
151 # the filesystem *now*.
152 ctx = self[None]
152 ctx = self[None]
153 parts = util.splitpath(subpath)
153 parts = util.splitpath(subpath)
154 while parts:
154 while parts:
155 prefix = os.sep.join(parts)
155 prefix = os.sep.join(parts)
156 if prefix in ctx.substate:
156 if prefix in ctx.substate:
157 if prefix == subpath:
157 if prefix == subpath:
158 return True
158 return True
159 else:
159 else:
160 sub = ctx.sub(prefix)
160 sub = ctx.sub(prefix)
161 return sub.checknested(subpath[len(prefix) + 1:])
161 return sub.checknested(subpath[len(prefix) + 1:])
162 else:
162 else:
163 parts.pop()
163 parts.pop()
164 return False
164 return False
165
165
166 @util.propertycache
166 @util.propertycache
167 def _bookmarks(self):
167 def _bookmarks(self):
168 return bookmarks.read(self)
168 return bookmarks.read(self)
169
169
170 @util.propertycache
170 @util.propertycache
171 def _bookmarkcurrent(self):
171 def _bookmarkcurrent(self):
172 return bookmarks.readcurrent(self)
172 return bookmarks.readcurrent(self)
173
173
174 @propertycache
174 @propertycache
175 def changelog(self):
175 def changelog(self):
176 c = changelog.changelog(self.sopener)
176 c = changelog.changelog(self.sopener)
177 if 'HG_PENDING' in os.environ:
177 if 'HG_PENDING' in os.environ:
178 p = os.environ['HG_PENDING']
178 p = os.environ['HG_PENDING']
179 if p.startswith(self.root):
179 if p.startswith(self.root):
180 c.readpending('00changelog.i.a')
180 c.readpending('00changelog.i.a')
181 self.sopener.options['defversion'] = c.version
181 self.sopener.options['defversion'] = c.version
182 return c
182 return c
183
183
184 @propertycache
184 @propertycache
185 def manifest(self):
185 def manifest(self):
186 return manifest.manifest(self.sopener)
186 return manifest.manifest(self.sopener)
187
187
188 @propertycache
188 @propertycache
189 def dirstate(self):
189 def dirstate(self):
190 warned = [0]
190 warned = [0]
191 def validate(node):
191 def validate(node):
192 try:
192 try:
193 r = self.changelog.rev(node)
193 r = self.changelog.rev(node)
194 return node
194 return node
195 except error.LookupError:
195 except error.LookupError:
196 if not warned[0]:
196 if not warned[0]:
197 warned[0] = True
197 warned[0] = True
198 self.ui.warn(_("warning: ignoring unknown"
198 self.ui.warn(_("warning: ignoring unknown"
199 " working parent %s!\n") % short(node))
199 " working parent %s!\n") % short(node))
200 return nullid
200 return nullid
201
201
202 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
202 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
203
203
204 def __getitem__(self, changeid):
204 def __getitem__(self, changeid):
205 if changeid is None:
205 if changeid is None:
206 return context.workingctx(self)
206 return context.workingctx(self)
207 return context.changectx(self, changeid)
207 return context.changectx(self, changeid)
208
208
209 def __contains__(self, changeid):
209 def __contains__(self, changeid):
210 try:
210 try:
211 return bool(self.lookup(changeid))
211 return bool(self.lookup(changeid))
212 except error.RepoLookupError:
212 except error.RepoLookupError:
213 return False
213 return False
214
214
215 def __nonzero__(self):
215 def __nonzero__(self):
216 return True
216 return True
217
217
218 def __len__(self):
218 def __len__(self):
219 return len(self.changelog)
219 return len(self.changelog)
220
220
221 def __iter__(self):
221 def __iter__(self):
222 for i in xrange(len(self)):
222 for i in xrange(len(self)):
223 yield i
223 yield i
224
224
225 def url(self):
225 def url(self):
226 return 'file:' + self.root
226 return 'file:' + self.root
227
227
228 def hook(self, name, throw=False, **args):
228 def hook(self, name, throw=False, **args):
229 return hook.hook(self.ui, self, name, throw, **args)
229 return hook.hook(self.ui, self, name, throw, **args)
230
230
231 tag_disallowed = ':\r\n'
231 tag_disallowed = ':\r\n'
232
232
233 def _tag(self, names, node, message, local, user, date, extra={}):
233 def _tag(self, names, node, message, local, user, date, extra={}):
234 if isinstance(names, str):
234 if isinstance(names, str):
235 allchars = names
235 allchars = names
236 names = (names,)
236 names = (names,)
237 else:
237 else:
238 allchars = ''.join(names)
238 allchars = ''.join(names)
239 for c in self.tag_disallowed:
239 for c in self.tag_disallowed:
240 if c in allchars:
240 if c in allchars:
241 raise util.Abort(_('%r cannot be used in a tag name') % c)
241 raise util.Abort(_('%r cannot be used in a tag name') % c)
242
242
243 branches = self.branchmap()
243 branches = self.branchmap()
244 for name in names:
244 for name in names:
245 self.hook('pretag', throw=True, node=hex(node), tag=name,
245 self.hook('pretag', throw=True, node=hex(node), tag=name,
246 local=local)
246 local=local)
247 if name in branches:
247 if name in branches:
248 self.ui.warn(_("warning: tag %s conflicts with existing"
248 self.ui.warn(_("warning: tag %s conflicts with existing"
249 " branch name\n") % name)
249 " branch name\n") % name)
250
250
251 def writetags(fp, names, munge, prevtags):
251 def writetags(fp, names, munge, prevtags):
252 fp.seek(0, 2)
252 fp.seek(0, 2)
253 if prevtags and prevtags[-1] != '\n':
253 if prevtags and prevtags[-1] != '\n':
254 fp.write('\n')
254 fp.write('\n')
255 for name in names:
255 for name in names:
256 m = munge and munge(name) or name
256 m = munge and munge(name) or name
257 if self._tagtypes and name in self._tagtypes:
257 if self._tagtypes and name in self._tagtypes:
258 old = self._tags.get(name, nullid)
258 old = self._tags.get(name, nullid)
259 fp.write('%s %s\n' % (hex(old), m))
259 fp.write('%s %s\n' % (hex(old), m))
260 fp.write('%s %s\n' % (hex(node), m))
260 fp.write('%s %s\n' % (hex(node), m))
261 fp.close()
261 fp.close()
262
262
263 prevtags = ''
263 prevtags = ''
264 if local:
264 if local:
265 try:
265 try:
266 fp = self.opener('localtags', 'r+')
266 fp = self.opener('localtags', 'r+')
267 except IOError:
267 except IOError:
268 fp = self.opener('localtags', 'a')
268 fp = self.opener('localtags', 'a')
269 else:
269 else:
270 prevtags = fp.read()
270 prevtags = fp.read()
271
271
272 # local tags are stored in the current charset
272 # local tags are stored in the current charset
273 writetags(fp, names, None, prevtags)
273 writetags(fp, names, None, prevtags)
274 for name in names:
274 for name in names:
275 self.hook('tag', node=hex(node), tag=name, local=local)
275 self.hook('tag', node=hex(node), tag=name, local=local)
276 return
276 return
277
277
278 try:
278 try:
279 fp = self.wfile('.hgtags', 'rb+')
279 fp = self.wfile('.hgtags', 'rb+')
280 except IOError:
280 except IOError:
281 fp = self.wfile('.hgtags', 'ab')
281 fp = self.wfile('.hgtags', 'ab')
282 else:
282 else:
283 prevtags = fp.read()
283 prevtags = fp.read()
284
284
285 # committed tags are stored in UTF-8
285 # committed tags are stored in UTF-8
286 writetags(fp, names, encoding.fromlocal, prevtags)
286 writetags(fp, names, encoding.fromlocal, prevtags)
287
287
288 fp.close()
288 fp.close()
289
289
290 if '.hgtags' not in self.dirstate:
290 if '.hgtags' not in self.dirstate:
291 self[None].add(['.hgtags'])
291 self[None].add(['.hgtags'])
292
292
293 m = matchmod.exact(self.root, '', ['.hgtags'])
293 m = matchmod.exact(self.root, '', ['.hgtags'])
294 tagnode = self.commit(message, user, date, extra=extra, match=m)
294 tagnode = self.commit(message, user, date, extra=extra, match=m)
295
295
296 for name in names:
296 for name in names:
297 self.hook('tag', node=hex(node), tag=name, local=local)
297 self.hook('tag', node=hex(node), tag=name, local=local)
298
298
299 return tagnode
299 return tagnode
300
300
301 def tag(self, names, node, message, local, user, date):
301 def tag(self, names, node, message, local, user, date):
302 '''tag a revision with one or more symbolic names.
302 '''tag a revision with one or more symbolic names.
303
303
304 names is a list of strings or, when adding a single tag, names may be a
304 names is a list of strings or, when adding a single tag, names may be a
305 string.
305 string.
306
306
307 if local is True, the tags are stored in a per-repository file.
307 if local is True, the tags are stored in a per-repository file.
308 otherwise, they are stored in the .hgtags file, and a new
308 otherwise, they are stored in the .hgtags file, and a new
309 changeset is committed with the change.
309 changeset is committed with the change.
310
310
311 keyword arguments:
311 keyword arguments:
312
312
313 local: whether to store tags in non-version-controlled file
313 local: whether to store tags in non-version-controlled file
314 (default False)
314 (default False)
315
315
316 message: commit message to use if committing
316 message: commit message to use if committing
317
317
318 user: name of user to use if committing
318 user: name of user to use if committing
319
319
320 date: date tuple to use if committing'''
320 date: date tuple to use if committing'''
321
321
322 if not local:
322 if not local:
323 for x in self.status()[:5]:
323 for x in self.status()[:5]:
324 if '.hgtags' in x:
324 if '.hgtags' in x:
325 raise util.Abort(_('working copy of .hgtags is changed '
325 raise util.Abort(_('working copy of .hgtags is changed '
326 '(please commit .hgtags manually)'))
326 '(please commit .hgtags manually)'))
327
327
328 self.tags() # instantiate the cache
328 self.tags() # instantiate the cache
329 self._tag(names, node, message, local, user, date)
329 self._tag(names, node, message, local, user, date)
330
330
331 def tags(self):
331 def tags(self):
332 '''return a mapping of tag to node'''
332 '''return a mapping of tag to node'''
333 if self._tags is None:
333 if self._tags is None:
334 (self._tags, self._tagtypes) = self._findtags()
334 (self._tags, self._tagtypes) = self._findtags()
335
335
336 return self._tags
336 return self._tags
337
337
338 def _findtags(self):
338 def _findtags(self):
339 '''Do the hard work of finding tags. Return a pair of dicts
339 '''Do the hard work of finding tags. Return a pair of dicts
340 (tags, tagtypes) where tags maps tag name to node, and tagtypes
340 (tags, tagtypes) where tags maps tag name to node, and tagtypes
341 maps tag name to a string like \'global\' or \'local\'.
341 maps tag name to a string like \'global\' or \'local\'.
342 Subclasses or extensions are free to add their own tags, but
342 Subclasses or extensions are free to add their own tags, but
343 should be aware that the returned dicts will be retained for the
343 should be aware that the returned dicts will be retained for the
344 duration of the localrepo object.'''
344 duration of the localrepo object.'''
345
345
346 # XXX what tagtype should subclasses/extensions use? Currently
346 # XXX what tagtype should subclasses/extensions use? Currently
347 # mq and bookmarks add tags, but do not set the tagtype at all.
347 # mq and bookmarks add tags, but do not set the tagtype at all.
348 # Should each extension invent its own tag type? Should there
348 # Should each extension invent its own tag type? Should there
349 # be one tagtype for all such "virtual" tags? Or is the status
349 # be one tagtype for all such "virtual" tags? Or is the status
350 # quo fine?
350 # quo fine?
351
351
352 alltags = {} # map tag name to (node, hist)
352 alltags = {} # map tag name to (node, hist)
353 tagtypes = {}
353 tagtypes = {}
354
354
355 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
355 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
356 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
356 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
357
357
358 # Build the return dicts. Have to re-encode tag names because
358 # Build the return dicts. Have to re-encode tag names because
359 # the tags module always uses UTF-8 (in order not to lose info
359 # the tags module always uses UTF-8 (in order not to lose info
360 # writing to the cache), but the rest of Mercurial wants them in
360 # writing to the cache), but the rest of Mercurial wants them in
361 # local encoding.
361 # local encoding.
362 tags = {}
362 tags = {}
363 for (name, (node, hist)) in alltags.iteritems():
363 for (name, (node, hist)) in alltags.iteritems():
364 if node != nullid:
364 if node != nullid:
365 tags[encoding.tolocal(name)] = node
365 tags[encoding.tolocal(name)] = node
366 tags['tip'] = self.changelog.tip()
366 tags['tip'] = self.changelog.tip()
367 tagtypes = dict([(encoding.tolocal(name), value)
367 tagtypes = dict([(encoding.tolocal(name), value)
368 for (name, value) in tagtypes.iteritems()])
368 for (name, value) in tagtypes.iteritems()])
369 return (tags, tagtypes)
369 return (tags, tagtypes)
370
370
371 def tagtype(self, tagname):
371 def tagtype(self, tagname):
372 '''
372 '''
373 return the type of the given tag. result can be:
373 return the type of the given tag. result can be:
374
374
375 'local' : a local tag
375 'local' : a local tag
376 'global' : a global tag
376 'global' : a global tag
377 None : tag does not exist
377 None : tag does not exist
378 '''
378 '''
379
379
380 self.tags()
380 self.tags()
381
381
382 return self._tagtypes.get(tagname)
382 return self._tagtypes.get(tagname)
383
383
384 def tagslist(self):
384 def tagslist(self):
385 '''return a list of tags ordered by revision'''
385 '''return a list of tags ordered by revision'''
386 l = []
386 l = []
387 for t, n in self.tags().iteritems():
387 for t, n in self.tags().iteritems():
388 try:
388 try:
389 r = self.changelog.rev(n)
389 r = self.changelog.rev(n)
390 except:
390 except:
391 r = -2 # sort to the beginning of the list if unknown
391 r = -2 # sort to the beginning of the list if unknown
392 l.append((r, t, n))
392 l.append((r, t, n))
393 return [(t, n) for r, t, n in sorted(l)]
393 return [(t, n) for r, t, n in sorted(l)]
394
394
395 def nodetags(self, node):
395 def nodetags(self, node):
396 '''return the tags associated with a node'''
396 '''return the tags associated with a node'''
397 if not self.nodetagscache:
397 if not self.nodetagscache:
398 self.nodetagscache = {}
398 self.nodetagscache = {}
399 for t, n in self.tags().iteritems():
399 for t, n in self.tags().iteritems():
400 self.nodetagscache.setdefault(n, []).append(t)
400 self.nodetagscache.setdefault(n, []).append(t)
401 for tags in self.nodetagscache.itervalues():
401 for tags in self.nodetagscache.itervalues():
402 tags.sort()
402 tags.sort()
403 return self.nodetagscache.get(node, [])
403 return self.nodetagscache.get(node, [])
404
404
405 def nodebookmarks(self, node):
405 def nodebookmarks(self, node):
406 marks = []
406 marks = []
407 for bookmark, n in self._bookmarks.iteritems():
407 for bookmark, n in self._bookmarks.iteritems():
408 if n == node:
408 if n == node:
409 marks.append(bookmark)
409 marks.append(bookmark)
410 return sorted(marks)
410 return sorted(marks)
411
411
412 def _branchtags(self, partial, lrev):
412 def _branchtags(self, partial, lrev):
413 # TODO: rename this function?
413 # TODO: rename this function?
414 tiprev = len(self) - 1
414 tiprev = len(self) - 1
415 if lrev != tiprev:
415 if lrev != tiprev:
416 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
416 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
417 self._updatebranchcache(partial, ctxgen)
417 self._updatebranchcache(partial, ctxgen)
418 self._writebranchcache(partial, self.changelog.tip(), tiprev)
418 self._writebranchcache(partial, self.changelog.tip(), tiprev)
419
419
420 return partial
420 return partial
421
421
422 def updatebranchcache(self):
422 def updatebranchcache(self):
423 tip = self.changelog.tip()
423 tip = self.changelog.tip()
424 if self._branchcache is not None and self._branchcachetip == tip:
424 if self._branchcache is not None and self._branchcachetip == tip:
425 return self._branchcache
425 return self._branchcache
426
426
427 oldtip = self._branchcachetip
427 oldtip = self._branchcachetip
428 self._branchcachetip = tip
428 self._branchcachetip = tip
429 if oldtip is None or oldtip not in self.changelog.nodemap:
429 if oldtip is None or oldtip not in self.changelog.nodemap:
430 partial, last, lrev = self._readbranchcache()
430 partial, last, lrev = self._readbranchcache()
431 else:
431 else:
432 lrev = self.changelog.rev(oldtip)
432 lrev = self.changelog.rev(oldtip)
433 partial = self._branchcache
433 partial = self._branchcache
434
434
435 self._branchtags(partial, lrev)
435 self._branchtags(partial, lrev)
436 # this private cache holds all heads (not just tips)
436 # this private cache holds all heads (not just tips)
437 self._branchcache = partial
437 self._branchcache = partial
438
438
439 def branchmap(self):
439 def branchmap(self):
440 '''returns a dictionary {branch: [branchheads]}'''
440 '''returns a dictionary {branch: [branchheads]}'''
441 self.updatebranchcache()
441 self.updatebranchcache()
442 return self._branchcache
442 return self._branchcache
443
443
444 def branchtags(self):
444 def branchtags(self):
445 '''return a dict where branch names map to the tipmost head of
445 '''return a dict where branch names map to the tipmost head of
446 the branch, open heads come before closed'''
446 the branch, open heads come before closed'''
447 bt = {}
447 bt = {}
448 for bn, heads in self.branchmap().iteritems():
448 for bn, heads in self.branchmap().iteritems():
449 tip = heads[-1]
449 tip = heads[-1]
450 for h in reversed(heads):
450 for h in reversed(heads):
451 if 'close' not in self.changelog.read(h)[5]:
451 if 'close' not in self.changelog.read(h)[5]:
452 tip = h
452 tip = h
453 break
453 break
454 bt[bn] = tip
454 bt[bn] = tip
455 return bt
455 return bt
456
456
457 def _readbranchcache(self):
457 def _readbranchcache(self):
458 partial = {}
458 partial = {}
459 try:
459 try:
460 f = self.opener("cache/branchheads")
460 f = self.opener("cache/branchheads")
461 lines = f.read().split('\n')
461 lines = f.read().split('\n')
462 f.close()
462 f.close()
463 except (IOError, OSError):
463 except (IOError, OSError):
464 return {}, nullid, nullrev
464 return {}, nullid, nullrev
465
465
466 try:
466 try:
467 last, lrev = lines.pop(0).split(" ", 1)
467 last, lrev = lines.pop(0).split(" ", 1)
468 last, lrev = bin(last), int(lrev)
468 last, lrev = bin(last), int(lrev)
469 if lrev >= len(self) or self[lrev].node() != last:
469 if lrev >= len(self) or self[lrev].node() != last:
470 # invalidate the cache
470 # invalidate the cache
471 raise ValueError('invalidating branch cache (tip differs)')
471 raise ValueError('invalidating branch cache (tip differs)')
472 for l in lines:
472 for l in lines:
473 if not l:
473 if not l:
474 continue
474 continue
475 node, label = l.split(" ", 1)
475 node, label = l.split(" ", 1)
476 label = encoding.tolocal(label.strip())
476 label = encoding.tolocal(label.strip())
477 partial.setdefault(label, []).append(bin(node))
477 partial.setdefault(label, []).append(bin(node))
478 except KeyboardInterrupt:
478 except KeyboardInterrupt:
479 raise
479 raise
480 except Exception, inst:
480 except Exception, inst:
481 if self.ui.debugflag:
481 if self.ui.debugflag:
482 self.ui.warn(str(inst), '\n')
482 self.ui.warn(str(inst), '\n')
483 partial, last, lrev = {}, nullid, nullrev
483 partial, last, lrev = {}, nullid, nullrev
484 return partial, last, lrev
484 return partial, last, lrev
485
485
486 def _writebranchcache(self, branches, tip, tiprev):
486 def _writebranchcache(self, branches, tip, tiprev):
487 try:
487 try:
488 f = self.opener("cache/branchheads", "w", atomictemp=True)
488 f = self.opener("cache/branchheads", "w", atomictemp=True)
489 f.write("%s %s\n" % (hex(tip), tiprev))
489 f.write("%s %s\n" % (hex(tip), tiprev))
490 for label, nodes in branches.iteritems():
490 for label, nodes in branches.iteritems():
491 for node in nodes:
491 for node in nodes:
492 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
492 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
493 f.rename()
493 f.rename()
494 except (IOError, OSError):
494 except (IOError, OSError):
495 pass
495 pass
496
496
497 def _updatebranchcache(self, partial, ctxgen):
497 def _updatebranchcache(self, partial, ctxgen):
498 # collect new branch entries
498 # collect new branch entries
499 newbranches = {}
499 newbranches = {}
500 for c in ctxgen:
500 for c in ctxgen:
501 newbranches.setdefault(c.branch(), []).append(c.node())
501 newbranches.setdefault(c.branch(), []).append(c.node())
502 # if older branchheads are reachable from new ones, they aren't
502 # if older branchheads are reachable from new ones, they aren't
503 # really branchheads. Note checking parents is insufficient:
503 # really branchheads. Note checking parents is insufficient:
504 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
504 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
505 for branch, newnodes in newbranches.iteritems():
505 for branch, newnodes in newbranches.iteritems():
506 bheads = partial.setdefault(branch, [])
506 bheads = partial.setdefault(branch, [])
507 bheads.extend(newnodes)
507 bheads.extend(newnodes)
508 if len(bheads) <= 1:
508 if len(bheads) <= 1:
509 continue
509 continue
510 # starting from tip means fewer passes over reachable
510 # starting from tip means fewer passes over reachable
511 while newnodes:
511 while newnodes:
512 latest = newnodes.pop()
512 latest = newnodes.pop()
513 if latest not in bheads:
513 if latest not in bheads:
514 continue
514 continue
515 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
515 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
516 reachable = self.changelog.reachable(latest, minbhrev)
516 reachable = self.changelog.reachable(latest, minbhrev)
517 reachable.remove(latest)
517 reachable.remove(latest)
518 bheads = [b for b in bheads if b not in reachable]
518 bheads = [b for b in bheads if b not in reachable]
519 partial[branch] = bheads
519 partial[branch] = bheads
520
520
521 def lookup(self, key):
521 def lookup(self, key):
522 if isinstance(key, int):
522 if isinstance(key, int):
523 return self.changelog.node(key)
523 return self.changelog.node(key)
524 elif key == '.':
524 elif key == '.':
525 return self.dirstate.parents()[0]
525 return self.dirstate.parents()[0]
526 elif key == 'null':
526 elif key == 'null':
527 return nullid
527 return nullid
528 elif key == 'tip':
528 elif key == 'tip':
529 return self.changelog.tip()
529 return self.changelog.tip()
530 n = self.changelog._match(key)
530 n = self.changelog._match(key)
531 if n:
531 if n:
532 return n
532 return n
533 if key in self._bookmarks:
533 if key in self._bookmarks:
534 return self._bookmarks[key]
534 return self._bookmarks[key]
535 if key in self.tags():
535 if key in self.tags():
536 return self.tags()[key]
536 return self.tags()[key]
537 if key in self.branchtags():
537 if key in self.branchtags():
538 return self.branchtags()[key]
538 return self.branchtags()[key]
539 n = self.changelog._partialmatch(key)
539 n = self.changelog._partialmatch(key)
540 if n:
540 if n:
541 return n
541 return n
542
542
543 # can't find key, check if it might have come from damaged dirstate
543 # can't find key, check if it might have come from damaged dirstate
544 if key in self.dirstate.parents():
544 if key in self.dirstate.parents():
545 raise error.Abort(_("working directory has unknown parent '%s'!")
545 raise error.Abort(_("working directory has unknown parent '%s'!")
546 % short(key))
546 % short(key))
547 try:
547 try:
548 if len(key) == 20:
548 if len(key) == 20:
549 key = hex(key)
549 key = hex(key)
550 except:
550 except:
551 pass
551 pass
552 raise error.RepoLookupError(_("unknown revision '%s'") % key)
552 raise error.RepoLookupError(_("unknown revision '%s'") % key)
553
553
554 def lookupbranch(self, key, remote=None):
554 def lookupbranch(self, key, remote=None):
555 repo = remote or self
555 repo = remote or self
556 if key in repo.branchmap():
556 if key in repo.branchmap():
557 return key
557 return key
558
558
559 repo = (remote and remote.local()) and remote or self
559 repo = (remote and remote.local()) and remote or self
560 return repo[key].branch()
560 return repo[key].branch()
561
561
562 def known(self, nodes):
562 def known(self, nodes):
563 nm = self.changelog.nodemap
563 nm = self.changelog.nodemap
564 return [(n in nm) for n in nodes]
564 return [(n in nm) for n in nodes]
565
565
566 def local(self):
566 def local(self):
567 return True
567 return True
568
568
569 def join(self, f):
569 def join(self, f):
570 return os.path.join(self.path, f)
570 return os.path.join(self.path, f)
571
571
572 def wjoin(self, f):
572 def wjoin(self, f):
573 return os.path.join(self.root, f)
573 return os.path.join(self.root, f)
574
574
575 def file(self, f):
575 def file(self, f):
576 if f[0] == '/':
576 if f[0] == '/':
577 f = f[1:]
577 f = f[1:]
578 return filelog.filelog(self.sopener, f)
578 return filelog.filelog(self.sopener, f)
579
579
580 def changectx(self, changeid):
580 def changectx(self, changeid):
581 return self[changeid]
581 return self[changeid]
582
582
583 def parents(self, changeid=None):
583 def parents(self, changeid=None):
584 '''get list of changectxs for parents of changeid'''
584 '''get list of changectxs for parents of changeid'''
585 return self[changeid].parents()
585 return self[changeid].parents()
586
586
587 def filectx(self, path, changeid=None, fileid=None):
587 def filectx(self, path, changeid=None, fileid=None):
588 """changeid can be a changeset revision, node, or tag.
588 """changeid can be a changeset revision, node, or tag.
589 fileid can be a file revision or node."""
589 fileid can be a file revision or node."""
590 return context.filectx(self, path, changeid, fileid)
590 return context.filectx(self, path, changeid, fileid)
591
591
592 def getcwd(self):
592 def getcwd(self):
593 return self.dirstate.getcwd()
593 return self.dirstate.getcwd()
594
594
595 def pathto(self, f, cwd=None):
595 def pathto(self, f, cwd=None):
596 return self.dirstate.pathto(f, cwd)
596 return self.dirstate.pathto(f, cwd)
597
597
598 def wfile(self, f, mode='r'):
598 def wfile(self, f, mode='r'):
599 return self.wopener(f, mode)
599 return self.wopener(f, mode)
600
600
601 def _link(self, f):
601 def _link(self, f):
602 return os.path.islink(self.wjoin(f))
602 return os.path.islink(self.wjoin(f))
603
603
604 def _loadfilter(self, filter):
604 def _loadfilter(self, filter):
605 if filter not in self.filterpats:
605 if filter not in self.filterpats:
606 l = []
606 l = []
607 for pat, cmd in self.ui.configitems(filter):
607 for pat, cmd in self.ui.configitems(filter):
608 if cmd == '!':
608 if cmd == '!':
609 continue
609 continue
610 mf = matchmod.match(self.root, '', [pat])
610 mf = matchmod.match(self.root, '', [pat])
611 fn = None
611 fn = None
612 params = cmd
612 params = cmd
613 for name, filterfn in self._datafilters.iteritems():
613 for name, filterfn in self._datafilters.iteritems():
614 if cmd.startswith(name):
614 if cmd.startswith(name):
615 fn = filterfn
615 fn = filterfn
616 params = cmd[len(name):].lstrip()
616 params = cmd[len(name):].lstrip()
617 break
617 break
618 if not fn:
618 if not fn:
619 fn = lambda s, c, **kwargs: util.filter(s, c)
619 fn = lambda s, c, **kwargs: util.filter(s, c)
620 # Wrap old filters not supporting keyword arguments
620 # Wrap old filters not supporting keyword arguments
621 if not inspect.getargspec(fn)[2]:
621 if not inspect.getargspec(fn)[2]:
622 oldfn = fn
622 oldfn = fn
623 fn = lambda s, c, **kwargs: oldfn(s, c)
623 fn = lambda s, c, **kwargs: oldfn(s, c)
624 l.append((mf, fn, params))
624 l.append((mf, fn, params))
625 self.filterpats[filter] = l
625 self.filterpats[filter] = l
626 return self.filterpats[filter]
626 return self.filterpats[filter]
627
627
628 def _filter(self, filterpats, filename, data):
628 def _filter(self, filterpats, filename, data):
629 for mf, fn, cmd in filterpats:
629 for mf, fn, cmd in filterpats:
630 if mf(filename):
630 if mf(filename):
631 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
631 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
632 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
632 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
633 break
633 break
634
634
635 return data
635 return data
636
636
637 @propertycache
637 @propertycache
638 def _encodefilterpats(self):
638 def _encodefilterpats(self):
639 return self._loadfilter('encode')
639 return self._loadfilter('encode')
640
640
641 @propertycache
641 @propertycache
642 def _decodefilterpats(self):
642 def _decodefilterpats(self):
643 return self._loadfilter('decode')
643 return self._loadfilter('decode')
644
644
645 def adddatafilter(self, name, filter):
645 def adddatafilter(self, name, filter):
646 self._datafilters[name] = filter
646 self._datafilters[name] = filter
647
647
648 def wread(self, filename):
648 def wread(self, filename):
649 if self._link(filename):
649 if self._link(filename):
650 data = os.readlink(self.wjoin(filename))
650 data = os.readlink(self.wjoin(filename))
651 else:
651 else:
652 data = self.wopener(filename, 'r').read()
652 data = self.wopener(filename, 'r').read()
653 return self._filter(self._encodefilterpats, filename, data)
653 return self._filter(self._encodefilterpats, filename, data)
654
654
655 def wwrite(self, filename, data, flags):
655 def wwrite(self, filename, data, flags):
656 data = self._filter(self._decodefilterpats, filename, data)
656 data = self._filter(self._decodefilterpats, filename, data)
657 if 'l' in flags:
657 if 'l' in flags:
658 self.wopener.symlink(data, filename)
658 self.wopener.symlink(data, filename)
659 else:
659 else:
660 self.wopener(filename, 'w').write(data)
660 self.wopener(filename, 'w').write(data)
661 if 'x' in flags:
661 if 'x' in flags:
662 util.set_flags(self.wjoin(filename), False, True)
662 util.set_flags(self.wjoin(filename), False, True)
663
663
664 def wwritedata(self, filename, data):
664 def wwritedata(self, filename, data):
665 return self._filter(self._decodefilterpats, filename, data)
665 return self._filter(self._decodefilterpats, filename, data)
666
666
667 def transaction(self, desc):
667 def transaction(self, desc):
668 tr = self._transref and self._transref() or None
668 tr = self._transref and self._transref() or None
669 if tr and tr.running():
669 if tr and tr.running():
670 return tr.nest()
670 return tr.nest()
671
671
672 # abort here if the journal already exists
672 # abort here if the journal already exists
673 if os.path.exists(self.sjoin("journal")):
673 if os.path.exists(self.sjoin("journal")):
674 raise error.RepoError(
674 raise error.RepoError(
675 _("abandoned transaction found - run hg recover"))
675 _("abandoned transaction found - run hg recover"))
676
676
677 # save dirstate for rollback
677 # save dirstate for rollback
678 try:
678 try:
679 ds = self.opener("dirstate").read()
679 ds = self.opener("dirstate").read()
680 except IOError:
680 except IOError:
681 ds = ""
681 ds = ""
682 self.opener("journal.dirstate", "w").write(ds)
682 self.opener("journal.dirstate", "w").write(ds)
683 self.opener("journal.branch", "w").write(
683 self.opener("journal.branch", "w").write(
684 encoding.fromlocal(self.dirstate.branch()))
684 encoding.fromlocal(self.dirstate.branch()))
685 self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc))
685 self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc))
686
686
687 renames = [(self.sjoin("journal"), self.sjoin("undo")),
687 renames = [(self.sjoin("journal"), self.sjoin("undo")),
688 (self.join("journal.dirstate"), self.join("undo.dirstate")),
688 (self.join("journal.dirstate"), self.join("undo.dirstate")),
689 (self.join("journal.branch"), self.join("undo.branch")),
689 (self.join("journal.branch"), self.join("undo.branch")),
690 (self.join("journal.desc"), self.join("undo.desc"))]
690 (self.join("journal.desc"), self.join("undo.desc"))]
691 tr = transaction.transaction(self.ui.warn, self.sopener,
691 tr = transaction.transaction(self.ui.warn, self.sopener,
692 self.sjoin("journal"),
692 self.sjoin("journal"),
693 aftertrans(renames),
693 aftertrans(renames),
694 self.store.createmode)
694 self.store.createmode)
695 self._transref = weakref.ref(tr)
695 self._transref = weakref.ref(tr)
696 return tr
696 return tr
697
697
698 def recover(self):
698 def recover(self):
699 lock = self.lock()
699 lock = self.lock()
700 try:
700 try:
701 if os.path.exists(self.sjoin("journal")):
701 if os.path.exists(self.sjoin("journal")):
702 self.ui.status(_("rolling back interrupted transaction\n"))
702 self.ui.status(_("rolling back interrupted transaction\n"))
703 transaction.rollback(self.sopener, self.sjoin("journal"),
703 transaction.rollback(self.sopener, self.sjoin("journal"),
704 self.ui.warn)
704 self.ui.warn)
705 self.invalidate()
705 self.invalidate()
706 return True
706 return True
707 else:
707 else:
708 self.ui.warn(_("no interrupted transaction available\n"))
708 self.ui.warn(_("no interrupted transaction available\n"))
709 return False
709 return False
710 finally:
710 finally:
711 lock.release()
711 lock.release()
712
712
713 def rollback(self, dryrun=False):
713 def rollback(self, dryrun=False):
714 wlock = lock = None
714 wlock = lock = None
715 try:
715 try:
716 wlock = self.wlock()
716 wlock = self.wlock()
717 lock = self.lock()
717 lock = self.lock()
718 if os.path.exists(self.sjoin("undo")):
718 if os.path.exists(self.sjoin("undo")):
719 try:
719 try:
720 args = self.opener("undo.desc", "r").read().splitlines()
720 args = self.opener("undo.desc", "r").read().splitlines()
721 if len(args) >= 3 and self.ui.verbose:
721 if len(args) >= 3 and self.ui.verbose:
722 desc = _("repository tip rolled back to revision %s"
722 desc = _("repository tip rolled back to revision %s"
723 " (undo %s: %s)\n") % (
723 " (undo %s: %s)\n") % (
724 int(args[0]) - 1, args[1], args[2])
724 int(args[0]) - 1, args[1], args[2])
725 elif len(args) >= 2:
725 elif len(args) >= 2:
726 desc = _("repository tip rolled back to revision %s"
726 desc = _("repository tip rolled back to revision %s"
727 " (undo %s)\n") % (
727 " (undo %s)\n") % (
728 int(args[0]) - 1, args[1])
728 int(args[0]) - 1, args[1])
729 except IOError:
729 except IOError:
730 desc = _("rolling back unknown transaction\n")
730 desc = _("rolling back unknown transaction\n")
731 self.ui.status(desc)
731 self.ui.status(desc)
732 if dryrun:
732 if dryrun:
733 return
733 return
734 transaction.rollback(self.sopener, self.sjoin("undo"),
734 transaction.rollback(self.sopener, self.sjoin("undo"),
735 self.ui.warn)
735 self.ui.warn)
736 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
736 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
737 if os.path.exists(self.join('undo.bookmarks')):
737 if os.path.exists(self.join('undo.bookmarks')):
738 util.rename(self.join('undo.bookmarks'),
738 util.rename(self.join('undo.bookmarks'),
739 self.join('bookmarks'))
739 self.join('bookmarks'))
740 try:
740 try:
741 branch = self.opener("undo.branch").read()
741 branch = self.opener("undo.branch").read()
742 self.dirstate.setbranch(branch)
742 self.dirstate.setbranch(branch)
743 except IOError:
743 except IOError:
744 self.ui.warn(_("Named branch could not be reset, "
744 self.ui.warn(_("Named branch could not be reset, "
745 "current branch still is: %s\n")
745 "current branch still is: %s\n")
746 % self.dirstate.branch())
746 % self.dirstate.branch())
747 self.invalidate()
747 self.invalidate()
748 self.dirstate.invalidate()
748 self.dirstate.invalidate()
749 self.destroyed()
749 self.destroyed()
750 parents = tuple([p.rev() for p in self.parents()])
750 parents = tuple([p.rev() for p in self.parents()])
751 if len(parents) > 1:
751 if len(parents) > 1:
752 self.ui.status(_("working directory now based on "
752 self.ui.status(_("working directory now based on "
753 "revisions %d and %d\n") % parents)
753 "revisions %d and %d\n") % parents)
754 else:
754 else:
755 self.ui.status(_("working directory now based on "
755 self.ui.status(_("working directory now based on "
756 "revision %d\n") % parents)
756 "revision %d\n") % parents)
757 else:
757 else:
758 self.ui.warn(_("no rollback information available\n"))
758 self.ui.warn(_("no rollback information available\n"))
759 return 1
759 return 1
760 finally:
760 finally:
761 release(lock, wlock)
761 release(lock, wlock)
762
762
763 def invalidatecaches(self):
763 def invalidatecaches(self):
764 self._tags = None
764 self._tags = None
765 self._tagtypes = None
765 self._tagtypes = None
766 self.nodetagscache = None
766 self.nodetagscache = None
767 self._branchcache = None # in UTF-8
767 self._branchcache = None # in UTF-8
768 self._branchcachetip = None
768 self._branchcachetip = None
769
769
770 def invalidate(self):
770 def invalidate(self):
771 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
771 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
772 if a in self.__dict__:
772 if a in self.__dict__:
773 delattr(self, a)
773 delattr(self, a)
774 self.invalidatecaches()
774 self.invalidatecaches()
775
775
776 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
776 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
777 try:
777 try:
778 l = lock.lock(lockname, 0, releasefn, desc=desc)
778 l = lock.lock(lockname, 0, releasefn, desc=desc)
779 except error.LockHeld, inst:
779 except error.LockHeld, inst:
780 if not wait:
780 if not wait:
781 raise
781 raise
782 self.ui.warn(_("waiting for lock on %s held by %r\n") %
782 self.ui.warn(_("waiting for lock on %s held by %r\n") %
783 (desc, inst.locker))
783 (desc, inst.locker))
784 # default to 600 seconds timeout
784 # default to 600 seconds timeout
785 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
785 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
786 releasefn, desc=desc)
786 releasefn, desc=desc)
787 if acquirefn:
787 if acquirefn:
788 acquirefn()
788 acquirefn()
789 return l
789 return l
790
790
791 def lock(self, wait=True):
791 def lock(self, wait=True):
792 '''Lock the repository store (.hg/store) and return a weak reference
792 '''Lock the repository store (.hg/store) and return a weak reference
793 to the lock. Use this before modifying the store (e.g. committing or
793 to the lock. Use this before modifying the store (e.g. committing or
794 stripping). If you are opening a transaction, get a lock as well.)'''
794 stripping). If you are opening a transaction, get a lock as well.)'''
795 l = self._lockref and self._lockref()
795 l = self._lockref and self._lockref()
796 if l is not None and l.held:
796 if l is not None and l.held:
797 l.lock()
797 l.lock()
798 return l
798 return l
799
799
800 l = self._lock(self.sjoin("lock"), wait, self.store.write,
800 l = self._lock(self.sjoin("lock"), wait, self.store.write,
801 self.invalidate, _('repository %s') % self.origroot)
801 self.invalidate, _('repository %s') % self.origroot)
802 self._lockref = weakref.ref(l)
802 self._lockref = weakref.ref(l)
803 return l
803 return l
804
804
805 def wlock(self, wait=True):
805 def wlock(self, wait=True):
806 '''Lock the non-store parts of the repository (everything under
806 '''Lock the non-store parts of the repository (everything under
807 .hg except .hg/store) and return a weak reference to the lock.
807 .hg except .hg/store) and return a weak reference to the lock.
808 Use this before modifying files in .hg.'''
808 Use this before modifying files in .hg.'''
809 l = self._wlockref and self._wlockref()
809 l = self._wlockref and self._wlockref()
810 if l is not None and l.held:
810 if l is not None and l.held:
811 l.lock()
811 l.lock()
812 return l
812 return l
813
813
814 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
814 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
815 self.dirstate.invalidate, _('working directory of %s') %
815 self.dirstate.invalidate, _('working directory of %s') %
816 self.origroot)
816 self.origroot)
817 self._wlockref = weakref.ref(l)
817 self._wlockref = weakref.ref(l)
818 return l
818 return l
819
819
820 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
820 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
821 """
821 """
822 commit an individual file as part of a larger transaction
822 commit an individual file as part of a larger transaction
823 """
823 """
824
824
825 fname = fctx.path()
825 fname = fctx.path()
826 text = fctx.data()
826 text = fctx.data()
827 flog = self.file(fname)
827 flog = self.file(fname)
828 fparent1 = manifest1.get(fname, nullid)
828 fparent1 = manifest1.get(fname, nullid)
829 fparent2 = fparent2o = manifest2.get(fname, nullid)
829 fparent2 = fparent2o = manifest2.get(fname, nullid)
830
830
831 meta = {}
831 meta = {}
832 copy = fctx.renamed()
832 copy = fctx.renamed()
833 if copy and copy[0] != fname:
833 if copy and copy[0] != fname:
834 # Mark the new revision of this file as a copy of another
834 # Mark the new revision of this file as a copy of another
835 # file. This copy data will effectively act as a parent
835 # file. This copy data will effectively act as a parent
836 # of this new revision. If this is a merge, the first
836 # of this new revision. If this is a merge, the first
837 # parent will be the nullid (meaning "look up the copy data")
837 # parent will be the nullid (meaning "look up the copy data")
838 # and the second one will be the other parent. For example:
838 # and the second one will be the other parent. For example:
839 #
839 #
840 # 0 --- 1 --- 3 rev1 changes file foo
840 # 0 --- 1 --- 3 rev1 changes file foo
841 # \ / rev2 renames foo to bar and changes it
841 # \ / rev2 renames foo to bar and changes it
842 # \- 2 -/ rev3 should have bar with all changes and
842 # \- 2 -/ rev3 should have bar with all changes and
843 # should record that bar descends from
843 # should record that bar descends from
844 # bar in rev2 and foo in rev1
844 # bar in rev2 and foo in rev1
845 #
845 #
846 # this allows this merge to succeed:
846 # this allows this merge to succeed:
847 #
847 #
848 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
848 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
849 # \ / merging rev3 and rev4 should use bar@rev2
849 # \ / merging rev3 and rev4 should use bar@rev2
850 # \- 2 --- 4 as the merge base
850 # \- 2 --- 4 as the merge base
851 #
851 #
852
852
853 cfname = copy[0]
853 cfname = copy[0]
854 crev = manifest1.get(cfname)
854 crev = manifest1.get(cfname)
855 newfparent = fparent2
855 newfparent = fparent2
856
856
857 if manifest2: # branch merge
857 if manifest2: # branch merge
858 if fparent2 == nullid or crev is None: # copied on remote side
858 if fparent2 == nullid or crev is None: # copied on remote side
859 if cfname in manifest2:
859 if cfname in manifest2:
860 crev = manifest2[cfname]
860 crev = manifest2[cfname]
861 newfparent = fparent1
861 newfparent = fparent1
862
862
863 # find source in nearest ancestor if we've lost track
863 # find source in nearest ancestor if we've lost track
864 if not crev:
864 if not crev:
865 self.ui.debug(" %s: searching for copy revision for %s\n" %
865 self.ui.debug(" %s: searching for copy revision for %s\n" %
866 (fname, cfname))
866 (fname, cfname))
867 for ancestor in self[None].ancestors():
867 for ancestor in self[None].ancestors():
868 if cfname in ancestor:
868 if cfname in ancestor:
869 crev = ancestor[cfname].filenode()
869 crev = ancestor[cfname].filenode()
870 break
870 break
871
871
872 if crev:
872 if crev:
873 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
873 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
874 meta["copy"] = cfname
874 meta["copy"] = cfname
875 meta["copyrev"] = hex(crev)
875 meta["copyrev"] = hex(crev)
876 fparent1, fparent2 = nullid, newfparent
876 fparent1, fparent2 = nullid, newfparent
877 else:
877 else:
878 self.ui.warn(_("warning: can't find ancestor for '%s' "
878 self.ui.warn(_("warning: can't find ancestor for '%s' "
879 "copied from '%s'!\n") % (fname, cfname))
879 "copied from '%s'!\n") % (fname, cfname))
880
880
881 elif fparent2 != nullid:
881 elif fparent2 != nullid:
882 # is one parent an ancestor of the other?
882 # is one parent an ancestor of the other?
883 fparentancestor = flog.ancestor(fparent1, fparent2)
883 fparentancestor = flog.ancestor(fparent1, fparent2)
884 if fparentancestor == fparent1:
884 if fparentancestor == fparent1:
885 fparent1, fparent2 = fparent2, nullid
885 fparent1, fparent2 = fparent2, nullid
886 elif fparentancestor == fparent2:
886 elif fparentancestor == fparent2:
887 fparent2 = nullid
887 fparent2 = nullid
888
888
889 # is the file changed?
889 # is the file changed?
890 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
890 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
891 changelist.append(fname)
891 changelist.append(fname)
892 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
892 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
893
893
894 # are just the flags changed during merge?
894 # are just the flags changed during merge?
895 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
895 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
896 changelist.append(fname)
896 changelist.append(fname)
897
897
898 return fparent1
898 return fparent1
899
899
900 def commit(self, text="", user=None, date=None, match=None, force=False,
900 def commit(self, text="", user=None, date=None, match=None, force=False,
901 editor=False, extra={}):
901 editor=False, extra={}):
902 """Add a new revision to current repository.
902 """Add a new revision to current repository.
903
903
904 Revision information is gathered from the working directory,
904 Revision information is gathered from the working directory,
905 match can be used to filter the committed files. If editor is
905 match can be used to filter the committed files. If editor is
906 supplied, it is called to get a commit message.
906 supplied, it is called to get a commit message.
907 """
907 """
908
908
909 def fail(f, msg):
909 def fail(f, msg):
910 raise util.Abort('%s: %s' % (f, msg))
910 raise util.Abort('%s: %s' % (f, msg))
911
911
912 if not match:
912 if not match:
913 match = matchmod.always(self.root, '')
913 match = matchmod.always(self.root, '')
914
914
915 if not force:
915 if not force:
916 vdirs = []
916 vdirs = []
917 match.dir = vdirs.append
917 match.dir = vdirs.append
918 match.bad = fail
918 match.bad = fail
919
919
920 wlock = self.wlock()
920 wlock = self.wlock()
921 try:
921 try:
922 wctx = self[None]
922 wctx = self[None]
923 merge = len(wctx.parents()) > 1
923 merge = len(wctx.parents()) > 1
924
924
925 if (not force and merge and match and
925 if (not force and merge and match and
926 (match.files() or match.anypats())):
926 (match.files() or match.anypats())):
927 raise util.Abort(_('cannot partially commit a merge '
927 raise util.Abort(_('cannot partially commit a merge '
928 '(do not specify files or patterns)'))
928 '(do not specify files or patterns)'))
929
929
930 changes = self.status(match=match, clean=force)
930 changes = self.status(match=match, clean=force)
931 if force:
931 if force:
932 changes[0].extend(changes[6]) # mq may commit unchanged files
932 changes[0].extend(changes[6]) # mq may commit unchanged files
933
933
934 # check subrepos
934 # check subrepos
935 subs = []
935 subs = []
936 removedsubs = set()
936 removedsubs = set()
937 for p in wctx.parents():
937 for p in wctx.parents():
938 removedsubs.update(s for s in p.substate if match(s))
938 removedsubs.update(s for s in p.substate if match(s))
939 for s in wctx.substate:
939 for s in wctx.substate:
940 removedsubs.discard(s)
940 removedsubs.discard(s)
941 if match(s) and wctx.sub(s).dirty():
941 if match(s) and wctx.sub(s).dirty():
942 subs.append(s)
942 subs.append(s)
943 if (subs or removedsubs):
943 if (subs or removedsubs):
944 if (not match('.hgsub') and
944 if (not match('.hgsub') and
945 '.hgsub' in (wctx.modified() + wctx.added())):
945 '.hgsub' in (wctx.modified() + wctx.added())):
946 raise util.Abort(_("can't commit subrepos without .hgsub"))
946 raise util.Abort(_("can't commit subrepos without .hgsub"))
947 if '.hgsubstate' not in changes[0]:
947 if '.hgsubstate' not in changes[0]:
948 changes[0].insert(0, '.hgsubstate')
948 changes[0].insert(0, '.hgsubstate')
949
949
950 if subs and not self.ui.configbool('ui', 'commitsubrepos', True):
950 if subs and not self.ui.configbool('ui', 'commitsubrepos', True):
951 changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
951 changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
952 if changedsubs:
952 if changedsubs:
953 raise util.Abort(_("uncommitted changes in subrepo %s")
953 raise util.Abort(_("uncommitted changes in subrepo %s")
954 % changedsubs[0])
954 % changedsubs[0])
955
955
956 # make sure all explicit patterns are matched
956 # make sure all explicit patterns are matched
957 if not force and match.files():
957 if not force and match.files():
958 matched = set(changes[0] + changes[1] + changes[2])
958 matched = set(changes[0] + changes[1] + changes[2])
959
959
960 for f in match.files():
960 for f in match.files():
961 if f == '.' or f in matched or f in wctx.substate:
961 if f == '.' or f in matched or f in wctx.substate:
962 continue
962 continue
963 if f in changes[3]: # missing
963 if f in changes[3]: # missing
964 fail(f, _('file not found!'))
964 fail(f, _('file not found!'))
965 if f in vdirs: # visited directory
965 if f in vdirs: # visited directory
966 d = f + '/'
966 d = f + '/'
967 for mf in matched:
967 for mf in matched:
968 if mf.startswith(d):
968 if mf.startswith(d):
969 break
969 break
970 else:
970 else:
971 fail(f, _("no match under directory!"))
971 fail(f, _("no match under directory!"))
972 elif f not in self.dirstate:
972 elif f not in self.dirstate:
973 fail(f, _("file not tracked!"))
973 fail(f, _("file not tracked!"))
974
974
975 if (not force and not extra.get("close") and not merge
975 if (not force and not extra.get("close") and not merge
976 and not (changes[0] or changes[1] or changes[2])
976 and not (changes[0] or changes[1] or changes[2])
977 and wctx.branch() == wctx.p1().branch()):
977 and wctx.branch() == wctx.p1().branch()):
978 return None
978 return None
979
979
980 ms = mergemod.mergestate(self)
980 ms = mergemod.mergestate(self)
981 for f in changes[0]:
981 for f in changes[0]:
982 if f in ms and ms[f] == 'u':
982 if f in ms and ms[f] == 'u':
983 raise util.Abort(_("unresolved merge conflicts "
983 raise util.Abort(_("unresolved merge conflicts "
984 "(see hg help resolve)"))
984 "(see hg help resolve)"))
985
985
986 cctx = context.workingctx(self, text, user, date, extra, changes)
986 cctx = context.workingctx(self, text, user, date, extra, changes)
987 if editor:
987 if editor:
988 cctx._text = editor(self, cctx, subs)
988 cctx._text = editor(self, cctx, subs)
989 edited = (text != cctx._text)
989 edited = (text != cctx._text)
990
990
991 # commit subs
991 # commit subs
992 if subs or removedsubs:
992 if subs or removedsubs:
993 state = wctx.substate.copy()
993 state = wctx.substate.copy()
994 for s in sorted(subs):
994 for s in sorted(subs):
995 sub = wctx.sub(s)
995 sub = wctx.sub(s)
996 self.ui.status(_('committing subrepository %s\n') %
996 self.ui.status(_('committing subrepository %s\n') %
997 subrepo.subrelpath(sub))
997 subrepo.subrelpath(sub))
998 sr = sub.commit(cctx._text, user, date)
998 sr = sub.commit(cctx._text, user, date)
999 state[s] = (state[s][0], sr)
999 state[s] = (state[s][0], sr)
1000 subrepo.writestate(self, state)
1000 subrepo.writestate(self, state)
1001
1001
1002 # Save commit message in case this transaction gets rolled back
1002 # Save commit message in case this transaction gets rolled back
1003 # (e.g. by a pretxncommit hook). Leave the content alone on
1003 # (e.g. by a pretxncommit hook). Leave the content alone on
1004 # the assumption that the user will use the same editor again.
1004 # the assumption that the user will use the same editor again.
1005 msgfile = self.opener('last-message.txt', 'wb')
1005 msgfile = self.opener('last-message.txt', 'wb')
1006 msgfile.write(cctx._text)
1006 msgfile.write(cctx._text)
1007 msgfile.close()
1007 msgfile.close()
1008
1008
1009 p1, p2 = self.dirstate.parents()
1009 p1, p2 = self.dirstate.parents()
1010 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1010 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1011 try:
1011 try:
1012 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
1012 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
1013 ret = self.commitctx(cctx, True)
1013 ret = self.commitctx(cctx, True)
1014 except:
1014 except:
1015 if edited:
1015 if edited:
1016 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
1016 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
1017 self.ui.write(
1017 self.ui.write(
1018 _('note: commit message saved in %s\n') % msgfn)
1018 _('note: commit message saved in %s\n') % msgfn)
1019 raise
1019 raise
1020
1020
1021 # update bookmarks, dirstate and mergestate
1021 # update bookmarks, dirstate and mergestate
1022 parents = (p1, p2)
1022 parents = (p1, p2)
1023 if p2 == nullid:
1023 if p2 == nullid:
1024 parents = (p1,)
1024 parents = (p1,)
1025 bookmarks.update(self, parents, ret)
1025 bookmarks.update(self, parents, ret)
1026 for f in changes[0] + changes[1]:
1026 for f in changes[0] + changes[1]:
1027 self.dirstate.normal(f)
1027 self.dirstate.normal(f)
1028 for f in changes[2]:
1028 for f in changes[2]:
1029 self.dirstate.forget(f)
1029 self.dirstate.forget(f)
1030 self.dirstate.setparents(ret)
1030 self.dirstate.setparents(ret)
1031 ms.reset()
1031 ms.reset()
1032 finally:
1032 finally:
1033 wlock.release()
1033 wlock.release()
1034
1034
1035 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1035 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1036 return ret
1036 return ret
1037
1037
1038 def commitctx(self, ctx, error=False):
1038 def commitctx(self, ctx, error=False):
1039 """Add a new revision to current repository.
1039 """Add a new revision to current repository.
1040 Revision information is passed via the context argument.
1040 Revision information is passed via the context argument.
1041 """
1041 """
1042
1042
1043 tr = lock = None
1043 tr = lock = None
1044 removed = list(ctx.removed())
1044 removed = list(ctx.removed())
1045 p1, p2 = ctx.p1(), ctx.p2()
1045 p1, p2 = ctx.p1(), ctx.p2()
1046 m1 = p1.manifest().copy()
1046 m1 = p1.manifest().copy()
1047 m2 = p2.manifest()
1047 m2 = p2.manifest()
1048 user = ctx.user()
1048 user = ctx.user()
1049
1049
1050 lock = self.lock()
1050 lock = self.lock()
1051 try:
1051 try:
1052 tr = self.transaction("commit")
1052 tr = self.transaction("commit")
1053 trp = weakref.proxy(tr)
1053 trp = weakref.proxy(tr)
1054
1054
1055 # check in files
1055 # check in files
1056 new = {}
1056 new = {}
1057 changed = []
1057 changed = []
1058 linkrev = len(self)
1058 linkrev = len(self)
1059 for f in sorted(ctx.modified() + ctx.added()):
1059 for f in sorted(ctx.modified() + ctx.added()):
1060 self.ui.note(f + "\n")
1060 self.ui.note(f + "\n")
1061 try:
1061 try:
1062 fctx = ctx[f]
1062 fctx = ctx[f]
1063 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1063 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1064 changed)
1064 changed)
1065 m1.set(f, fctx.flags())
1065 m1.set(f, fctx.flags())
1066 except OSError, inst:
1066 except OSError, inst:
1067 self.ui.warn(_("trouble committing %s!\n") % f)
1067 self.ui.warn(_("trouble committing %s!\n") % f)
1068 raise
1068 raise
1069 except IOError, inst:
1069 except IOError, inst:
1070 errcode = getattr(inst, 'errno', errno.ENOENT)
1070 errcode = getattr(inst, 'errno', errno.ENOENT)
1071 if error or errcode and errcode != errno.ENOENT:
1071 if error or errcode and errcode != errno.ENOENT:
1072 self.ui.warn(_("trouble committing %s!\n") % f)
1072 self.ui.warn(_("trouble committing %s!\n") % f)
1073 raise
1073 raise
1074 else:
1074 else:
1075 removed.append(f)
1075 removed.append(f)
1076
1076
1077 # update manifest
1077 # update manifest
1078 m1.update(new)
1078 m1.update(new)
1079 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1079 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1080 drop = [f for f in removed if f in m1]
1080 drop = [f for f in removed if f in m1]
1081 for f in drop:
1081 for f in drop:
1082 del m1[f]
1082 del m1[f]
1083 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1083 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1084 p2.manifestnode(), (new, drop))
1084 p2.manifestnode(), (new, drop))
1085
1085
1086 # update changelog
1086 # update changelog
1087 self.changelog.delayupdate()
1087 self.changelog.delayupdate()
1088 n = self.changelog.add(mn, changed + removed, ctx.description(),
1088 n = self.changelog.add(mn, changed + removed, ctx.description(),
1089 trp, p1.node(), p2.node(),
1089 trp, p1.node(), p2.node(),
1090 user, ctx.date(), ctx.extra().copy())
1090 user, ctx.date(), ctx.extra().copy())
1091 p = lambda: self.changelog.writepending() and self.root or ""
1091 p = lambda: self.changelog.writepending() and self.root or ""
1092 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1092 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1093 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1093 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1094 parent2=xp2, pending=p)
1094 parent2=xp2, pending=p)
1095 self.changelog.finalize(trp)
1095 self.changelog.finalize(trp)
1096 tr.close()
1096 tr.close()
1097
1097
1098 if self._branchcache:
1098 if self._branchcache:
1099 self.updatebranchcache()
1099 self.updatebranchcache()
1100 return n
1100 return n
1101 finally:
1101 finally:
1102 if tr:
1102 if tr:
1103 tr.release()
1103 tr.release()
1104 lock.release()
1104 lock.release()
1105
1105
1106 def destroyed(self):
1106 def destroyed(self):
1107 '''Inform the repository that nodes have been destroyed.
1107 '''Inform the repository that nodes have been destroyed.
1108 Intended for use by strip and rollback, so there's a common
1108 Intended for use by strip and rollback, so there's a common
1109 place for anything that has to be done after destroying history.'''
1109 place for anything that has to be done after destroying history.'''
1110 # XXX it might be nice if we could take the list of destroyed
1110 # XXX it might be nice if we could take the list of destroyed
1111 # nodes, but I don't see an easy way for rollback() to do that
1111 # nodes, but I don't see an easy way for rollback() to do that
1112
1112
1113 # Ensure the persistent tag cache is updated. Doing it now
1113 # Ensure the persistent tag cache is updated. Doing it now
1114 # means that the tag cache only has to worry about destroyed
1114 # means that the tag cache only has to worry about destroyed
1115 # heads immediately after a strip/rollback. That in turn
1115 # heads immediately after a strip/rollback. That in turn
1116 # guarantees that "cachetip == currenttip" (comparing both rev
1116 # guarantees that "cachetip == currenttip" (comparing both rev
1117 # and node) always means no nodes have been added or destroyed.
1117 # and node) always means no nodes have been added or destroyed.
1118
1118
1119 # XXX this is suboptimal when qrefresh'ing: we strip the current
1119 # XXX this is suboptimal when qrefresh'ing: we strip the current
1120 # head, refresh the tag cache, then immediately add a new head.
1120 # head, refresh the tag cache, then immediately add a new head.
1121 # But I think doing it this way is necessary for the "instant
1121 # But I think doing it this way is necessary for the "instant
1122 # tag cache retrieval" case to work.
1122 # tag cache retrieval" case to work.
1123 self.invalidatecaches()
1123 self.invalidatecaches()
1124
1124
1125 def walk(self, match, node=None):
1125 def walk(self, match, node=None):
1126 '''
1126 '''
1127 walk recursively through the directory tree or a given
1127 walk recursively through the directory tree or a given
1128 changeset, finding all files matched by the match
1128 changeset, finding all files matched by the match
1129 function
1129 function
1130 '''
1130 '''
1131 return self[node].walk(match)
1131 return self[node].walk(match)
1132
1132
1133 def status(self, node1='.', node2=None, match=None,
1133 def status(self, node1='.', node2=None, match=None,
1134 ignored=False, clean=False, unknown=False,
1134 ignored=False, clean=False, unknown=False,
1135 listsubrepos=False):
1135 listsubrepos=False):
1136 """return status of files between two nodes or node and working directory
1136 """return status of files between two nodes or node and working directory
1137
1137
1138 If node1 is None, use the first dirstate parent instead.
1138 If node1 is None, use the first dirstate parent instead.
1139 If node2 is None, compare node1 with working directory.
1139 If node2 is None, compare node1 with working directory.
1140 """
1140 """
1141
1141
1142 def mfmatches(ctx):
1142 def mfmatches(ctx):
1143 mf = ctx.manifest().copy()
1143 mf = ctx.manifest().copy()
1144 for fn in mf.keys():
1144 for fn in mf.keys():
1145 if not match(fn):
1145 if not match(fn):
1146 del mf[fn]
1146 del mf[fn]
1147 return mf
1147 return mf
1148
1148
1149 if isinstance(node1, context.changectx):
1149 if isinstance(node1, context.changectx):
1150 ctx1 = node1
1150 ctx1 = node1
1151 else:
1151 else:
1152 ctx1 = self[node1]
1152 ctx1 = self[node1]
1153 if isinstance(node2, context.changectx):
1153 if isinstance(node2, context.changectx):
1154 ctx2 = node2
1154 ctx2 = node2
1155 else:
1155 else:
1156 ctx2 = self[node2]
1156 ctx2 = self[node2]
1157
1157
1158 working = ctx2.rev() is None
1158 working = ctx2.rev() is None
1159 parentworking = working and ctx1 == self['.']
1159 parentworking = working and ctx1 == self['.']
1160 match = match or matchmod.always(self.root, self.getcwd())
1160 match = match or matchmod.always(self.root, self.getcwd())
1161 listignored, listclean, listunknown = ignored, clean, unknown
1161 listignored, listclean, listunknown = ignored, clean, unknown
1162
1162
1163 # load earliest manifest first for caching reasons
1163 # load earliest manifest first for caching reasons
1164 if not working and ctx2.rev() < ctx1.rev():
1164 if not working and ctx2.rev() < ctx1.rev():
1165 ctx2.manifest()
1165 ctx2.manifest()
1166
1166
1167 if not parentworking:
1167 if not parentworking:
1168 def bad(f, msg):
1168 def bad(f, msg):
1169 if f not in ctx1:
1169 if f not in ctx1:
1170 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1170 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1171 match.bad = bad
1171 match.bad = bad
1172
1172
1173 if working: # we need to scan the working dir
1173 if working: # we need to scan the working dir
1174 subrepos = []
1174 subrepos = []
1175 if '.hgsub' in self.dirstate:
1175 if '.hgsub' in self.dirstate:
1176 subrepos = ctx1.substate.keys()
1176 subrepos = ctx1.substate.keys()
1177 s = self.dirstate.status(match, subrepos, listignored,
1177 s = self.dirstate.status(match, subrepos, listignored,
1178 listclean, listunknown)
1178 listclean, listunknown)
1179 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1179 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1180
1180
1181 # check for any possibly clean files
1181 # check for any possibly clean files
1182 if parentworking and cmp:
1182 if parentworking and cmp:
1183 fixup = []
1183 fixup = []
1184 # do a full compare of any files that might have changed
1184 # do a full compare of any files that might have changed
1185 for f in sorted(cmp):
1185 for f in sorted(cmp):
1186 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1186 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1187 or ctx1[f].cmp(ctx2[f])):
1187 or ctx1[f].cmp(ctx2[f])):
1188 modified.append(f)
1188 modified.append(f)
1189 else:
1189 else:
1190 fixup.append(f)
1190 fixup.append(f)
1191
1191
1192 # update dirstate for files that are actually clean
1192 # update dirstate for files that are actually clean
1193 if fixup:
1193 if fixup:
1194 if listclean:
1194 if listclean:
1195 clean += fixup
1195 clean += fixup
1196
1196
1197 try:
1197 try:
1198 # updating the dirstate is optional
1198 # updating the dirstate is optional
1199 # so we don't wait on the lock
1199 # so we don't wait on the lock
1200 wlock = self.wlock(False)
1200 wlock = self.wlock(False)
1201 try:
1201 try:
1202 for f in fixup:
1202 for f in fixup:
1203 self.dirstate.normal(f)
1203 self.dirstate.normal(f)
1204 finally:
1204 finally:
1205 wlock.release()
1205 wlock.release()
1206 except error.LockError:
1206 except error.LockError:
1207 pass
1207 pass
1208
1208
1209 if not parentworking:
1209 if not parentworking:
1210 mf1 = mfmatches(ctx1)
1210 mf1 = mfmatches(ctx1)
1211 if working:
1211 if working:
1212 # we are comparing working dir against non-parent
1212 # we are comparing working dir against non-parent
1213 # generate a pseudo-manifest for the working dir
1213 # generate a pseudo-manifest for the working dir
1214 mf2 = mfmatches(self['.'])
1214 mf2 = mfmatches(self['.'])
1215 for f in cmp + modified + added:
1215 for f in cmp + modified + added:
1216 mf2[f] = None
1216 mf2[f] = None
1217 mf2.set(f, ctx2.flags(f))
1217 mf2.set(f, ctx2.flags(f))
1218 for f in removed:
1218 for f in removed:
1219 if f in mf2:
1219 if f in mf2:
1220 del mf2[f]
1220 del mf2[f]
1221 else:
1221 else:
1222 # we are comparing two revisions
1222 # we are comparing two revisions
1223 deleted, unknown, ignored = [], [], []
1223 deleted, unknown, ignored = [], [], []
1224 mf2 = mfmatches(ctx2)
1224 mf2 = mfmatches(ctx2)
1225
1225
1226 modified, added, clean = [], [], []
1226 modified, added, clean = [], [], []
1227 for fn in mf2:
1227 for fn in mf2:
1228 if fn in mf1:
1228 if fn in mf1:
1229 if (mf1.flags(fn) != mf2.flags(fn) or
1229 if (mf1.flags(fn) != mf2.flags(fn) or
1230 (mf1[fn] != mf2[fn] and
1230 (mf1[fn] != mf2[fn] and
1231 (mf2[fn] or ctx1[fn].cmp(ctx2[fn])))):
1231 (mf2[fn] or ctx1[fn].cmp(ctx2[fn])))):
1232 modified.append(fn)
1232 modified.append(fn)
1233 elif listclean:
1233 elif listclean:
1234 clean.append(fn)
1234 clean.append(fn)
1235 del mf1[fn]
1235 del mf1[fn]
1236 else:
1236 else:
1237 added.append(fn)
1237 added.append(fn)
1238 removed = mf1.keys()
1238 removed = mf1.keys()
1239
1239
1240 r = modified, added, removed, deleted, unknown, ignored, clean
1240 r = modified, added, removed, deleted, unknown, ignored, clean
1241
1241
1242 if listsubrepos:
1242 if listsubrepos:
1243 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1243 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1244 if working:
1244 if working:
1245 rev2 = None
1245 rev2 = None
1246 else:
1246 else:
1247 rev2 = ctx2.substate[subpath][1]
1247 rev2 = ctx2.substate[subpath][1]
1248 try:
1248 try:
1249 submatch = matchmod.narrowmatcher(subpath, match)
1249 submatch = matchmod.narrowmatcher(subpath, match)
1250 s = sub.status(rev2, match=submatch, ignored=listignored,
1250 s = sub.status(rev2, match=submatch, ignored=listignored,
1251 clean=listclean, unknown=listunknown,
1251 clean=listclean, unknown=listunknown,
1252 listsubrepos=True)
1252 listsubrepos=True)
1253 for rfiles, sfiles in zip(r, s):
1253 for rfiles, sfiles in zip(r, s):
1254 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1254 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1255 except error.LookupError:
1255 except error.LookupError:
1256 self.ui.status(_("skipping missing subrepository: %s\n")
1256 self.ui.status(_("skipping missing subrepository: %s\n")
1257 % subpath)
1257 % subpath)
1258
1258
1259 for l in r:
1259 for l in r:
1260 l.sort()
1260 l.sort()
1261 return r
1261 return r
1262
1262
1263 def heads(self, start=None):
1263 def heads(self, start=None):
1264 heads = self.changelog.heads(start)
1264 heads = self.changelog.heads(start)
1265 # sort the output in rev descending order
1265 # sort the output in rev descending order
1266 return sorted(heads, key=self.changelog.rev, reverse=True)
1266 return sorted(heads, key=self.changelog.rev, reverse=True)
1267
1267
1268 def branchheads(self, branch=None, start=None, closed=False):
1268 def branchheads(self, branch=None, start=None, closed=False):
1269 '''return a (possibly filtered) list of heads for the given branch
1269 '''return a (possibly filtered) list of heads for the given branch
1270
1270
1271 Heads are returned in topological order, from newest to oldest.
1271 Heads are returned in topological order, from newest to oldest.
1272 If branch is None, use the dirstate branch.
1272 If branch is None, use the dirstate branch.
1273 If start is not None, return only heads reachable from start.
1273 If start is not None, return only heads reachable from start.
1274 If closed is True, return heads that are marked as closed as well.
1274 If closed is True, return heads that are marked as closed as well.
1275 '''
1275 '''
1276 if branch is None:
1276 if branch is None:
1277 branch = self[None].branch()
1277 branch = self[None].branch()
1278 branches = self.branchmap()
1278 branches = self.branchmap()
1279 if branch not in branches:
1279 if branch not in branches:
1280 return []
1280 return []
1281 # the cache returns heads ordered lowest to highest
1281 # the cache returns heads ordered lowest to highest
1282 bheads = list(reversed(branches[branch]))
1282 bheads = list(reversed(branches[branch]))
1283 if start is not None:
1283 if start is not None:
1284 # filter out the heads that cannot be reached from startrev
1284 # filter out the heads that cannot be reached from startrev
1285 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1285 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1286 bheads = [h for h in bheads if h in fbheads]
1286 bheads = [h for h in bheads if h in fbheads]
1287 if not closed:
1287 if not closed:
1288 bheads = [h for h in bheads if
1288 bheads = [h for h in bheads if
1289 ('close' not in self.changelog.read(h)[5])]
1289 ('close' not in self.changelog.read(h)[5])]
1290 return bheads
1290 return bheads
1291
1291
1292 def branches(self, nodes):
1292 def branches(self, nodes):
1293 if not nodes:
1293 if not nodes:
1294 nodes = [self.changelog.tip()]
1294 nodes = [self.changelog.tip()]
1295 b = []
1295 b = []
1296 for n in nodes:
1296 for n in nodes:
1297 t = n
1297 t = n
1298 while 1:
1298 while 1:
1299 p = self.changelog.parents(n)
1299 p = self.changelog.parents(n)
1300 if p[1] != nullid or p[0] == nullid:
1300 if p[1] != nullid or p[0] == nullid:
1301 b.append((t, n, p[0], p[1]))
1301 b.append((t, n, p[0], p[1]))
1302 break
1302 break
1303 n = p[0]
1303 n = p[0]
1304 return b
1304 return b
1305
1305
1306 def between(self, pairs):
1306 def between(self, pairs):
1307 r = []
1307 r = []
1308
1308
1309 for top, bottom in pairs:
1309 for top, bottom in pairs:
1310 n, l, i = top, [], 0
1310 n, l, i = top, [], 0
1311 f = 1
1311 f = 1
1312
1312
1313 while n != bottom and n != nullid:
1313 while n != bottom and n != nullid:
1314 p = self.changelog.parents(n)[0]
1314 p = self.changelog.parents(n)[0]
1315 if i == f:
1315 if i == f:
1316 l.append(n)
1316 l.append(n)
1317 f = f * 2
1317 f = f * 2
1318 n = p
1318 n = p
1319 i += 1
1319 i += 1
1320
1320
1321 r.append(l)
1321 r.append(l)
1322
1322
1323 return r
1323 return r
1324
1324
1325 def pull(self, remote, heads=None, force=False):
1325 def pull(self, remote, heads=None, force=False):
1326 lock = self.lock()
1326 lock = self.lock()
1327 try:
1327 try:
1328 usecommon = remote.capable('getbundle')
1328 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1329 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1329 force=force)
1330 force=force, commononly=usecommon)
1330 common, fetch, rheads = tmp
1331 common, fetch, rheads = tmp
1331 if not fetch:
1332 if not fetch:
1332 self.ui.status(_("no changes found\n"))
1333 self.ui.status(_("no changes found\n"))
1333 result = 0
1334 result = 0
1334 else:
1335 else:
1335 if heads is None and fetch == [nullid]:
1336 if heads is None and list(common) == [nullid]:
1336 self.ui.status(_("requesting all changes\n"))
1337 self.ui.status(_("requesting all changes\n"))
1337 elif heads is None and remote.capable('changegroupsubset'):
1338 elif heads is None and remote.capable('changegroupsubset'):
1338 # issue1320, avoid a race if remote changed after discovery
1339 # issue1320, avoid a race if remote changed after discovery
1339 heads = rheads
1340 heads = rheads
1340
1341
1341 if heads is None:
1342 if usecommon:
1343 cg = remote.getbundle('pull', common=common,
1344 heads=heads or rheads)
1345 elif heads is None:
1342 cg = remote.changegroup(fetch, 'pull')
1346 cg = remote.changegroup(fetch, 'pull')
1343 elif not remote.capable('changegroupsubset'):
1347 elif not remote.capable('changegroupsubset'):
1344 raise util.Abort(_("partial pull cannot be done because "
1348 raise util.Abort(_("partial pull cannot be done because "
1345 "other repository doesn't support "
1349 "other repository doesn't support "
1346 "changegroupsubset."))
1350 "changegroupsubset."))
1347 else:
1351 else:
1348 cg = remote.changegroupsubset(fetch, heads, 'pull')
1352 cg = remote.changegroupsubset(fetch, heads, 'pull')
1349 result = self.addchangegroup(cg, 'pull', remote.url(),
1353 result = self.addchangegroup(cg, 'pull', remote.url(),
1350 lock=lock)
1354 lock=lock)
1351 finally:
1355 finally:
1352 lock.release()
1356 lock.release()
1353
1357
1354 return result
1358 return result
1355
1359
1356 def checkpush(self, force, revs):
1360 def checkpush(self, force, revs):
1357 """Extensions can override this function if additional checks have
1361 """Extensions can override this function if additional checks have
1358 to be performed before pushing, or call it if they override push
1362 to be performed before pushing, or call it if they override push
1359 command.
1363 command.
1360 """
1364 """
1361 pass
1365 pass
1362
1366
1363 def push(self, remote, force=False, revs=None, newbranch=False):
1367 def push(self, remote, force=False, revs=None, newbranch=False):
1364 '''Push outgoing changesets (limited by revs) from the current
1368 '''Push outgoing changesets (limited by revs) from the current
1365 repository to remote. Return an integer:
1369 repository to remote. Return an integer:
1366 - 0 means HTTP error *or* nothing to push
1370 - 0 means HTTP error *or* nothing to push
1367 - 1 means we pushed and remote head count is unchanged *or*
1371 - 1 means we pushed and remote head count is unchanged *or*
1368 we have outgoing changesets but refused to push
1372 we have outgoing changesets but refused to push
1369 - other values as described by addchangegroup()
1373 - other values as described by addchangegroup()
1370 '''
1374 '''
1371 # there are two ways to push to remote repo:
1375 # there are two ways to push to remote repo:
1372 #
1376 #
1373 # addchangegroup assumes local user can lock remote
1377 # addchangegroup assumes local user can lock remote
1374 # repo (local filesystem, old ssh servers).
1378 # repo (local filesystem, old ssh servers).
1375 #
1379 #
1376 # unbundle assumes local user cannot lock remote repo (new ssh
1380 # unbundle assumes local user cannot lock remote repo (new ssh
1377 # servers, http servers).
1381 # servers, http servers).
1378
1382
1379 self.checkpush(force, revs)
1383 self.checkpush(force, revs)
1380 lock = None
1384 lock = None
1381 unbundle = remote.capable('unbundle')
1385 unbundle = remote.capable('unbundle')
1382 if not unbundle:
1386 if not unbundle:
1383 lock = remote.lock()
1387 lock = remote.lock()
1384 try:
1388 try:
1385 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1389 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1386 newbranch)
1390 newbranch)
1387 ret = remote_heads
1391 ret = remote_heads
1388 if cg is not None:
1392 if cg is not None:
1389 if unbundle:
1393 if unbundle:
1390 # local repo finds heads on server, finds out what
1394 # local repo finds heads on server, finds out what
1391 # revs it must push. once revs transferred, if server
1395 # revs it must push. once revs transferred, if server
1392 # finds it has different heads (someone else won
1396 # finds it has different heads (someone else won
1393 # commit/push race), server aborts.
1397 # commit/push race), server aborts.
1394 if force:
1398 if force:
1395 remote_heads = ['force']
1399 remote_heads = ['force']
1396 # ssh: return remote's addchangegroup()
1400 # ssh: return remote's addchangegroup()
1397 # http: return remote's addchangegroup() or 0 for error
1401 # http: return remote's addchangegroup() or 0 for error
1398 ret = remote.unbundle(cg, remote_heads, 'push')
1402 ret = remote.unbundle(cg, remote_heads, 'push')
1399 else:
1403 else:
1400 # we return an integer indicating remote head count change
1404 # we return an integer indicating remote head count change
1401 ret = remote.addchangegroup(cg, 'push', self.url(),
1405 ret = remote.addchangegroup(cg, 'push', self.url(),
1402 lock=lock)
1406 lock=lock)
1403 finally:
1407 finally:
1404 if lock is not None:
1408 if lock is not None:
1405 lock.release()
1409 lock.release()
1406
1410
1407 self.ui.debug("checking for updated bookmarks\n")
1411 self.ui.debug("checking for updated bookmarks\n")
1408 rb = remote.listkeys('bookmarks')
1412 rb = remote.listkeys('bookmarks')
1409 for k in rb.keys():
1413 for k in rb.keys():
1410 if k in self._bookmarks:
1414 if k in self._bookmarks:
1411 nr, nl = rb[k], hex(self._bookmarks[k])
1415 nr, nl = rb[k], hex(self._bookmarks[k])
1412 if nr in self:
1416 if nr in self:
1413 cr = self[nr]
1417 cr = self[nr]
1414 cl = self[nl]
1418 cl = self[nl]
1415 if cl in cr.descendants():
1419 if cl in cr.descendants():
1416 r = remote.pushkey('bookmarks', k, nr, nl)
1420 r = remote.pushkey('bookmarks', k, nr, nl)
1417 if r:
1421 if r:
1418 self.ui.status(_("updating bookmark %s\n") % k)
1422 self.ui.status(_("updating bookmark %s\n") % k)
1419 else:
1423 else:
1420 self.ui.warn(_('updating bookmark %s'
1424 self.ui.warn(_('updating bookmark %s'
1421 ' failed!\n') % k)
1425 ' failed!\n') % k)
1422
1426
1423 return ret
1427 return ret
1424
1428
1425 def changegroupinfo(self, nodes, source):
1429 def changegroupinfo(self, nodes, source):
1426 if self.ui.verbose or source == 'bundle':
1430 if self.ui.verbose or source == 'bundle':
1427 self.ui.status(_("%d changesets found\n") % len(nodes))
1431 self.ui.status(_("%d changesets found\n") % len(nodes))
1428 if self.ui.debugflag:
1432 if self.ui.debugflag:
1429 self.ui.debug("list of changesets:\n")
1433 self.ui.debug("list of changesets:\n")
1430 for node in nodes:
1434 for node in nodes:
1431 self.ui.debug("%s\n" % hex(node))
1435 self.ui.debug("%s\n" % hex(node))
1432
1436
1433 def changegroupsubset(self, bases, heads, source):
1437 def changegroupsubset(self, bases, heads, source):
1434 """Compute a changegroup consisting of all the nodes that are
1438 """Compute a changegroup consisting of all the nodes that are
1435 descendents of any of the bases and ancestors of any of the heads.
1439 descendents of any of the bases and ancestors of any of the heads.
1436 Return a chunkbuffer object whose read() method will return
1440 Return a chunkbuffer object whose read() method will return
1437 successive changegroup chunks.
1441 successive changegroup chunks.
1438
1442
1439 It is fairly complex as determining which filenodes and which
1443 It is fairly complex as determining which filenodes and which
1440 manifest nodes need to be included for the changeset to be complete
1444 manifest nodes need to be included for the changeset to be complete
1441 is non-trivial.
1445 is non-trivial.
1442
1446
1443 Another wrinkle is doing the reverse, figuring out which changeset in
1447 Another wrinkle is doing the reverse, figuring out which changeset in
1444 the changegroup a particular filenode or manifestnode belongs to.
1448 the changegroup a particular filenode or manifestnode belongs to.
1445 """
1449 """
1446 cl = self.changelog
1450 cl = self.changelog
1447 if not bases:
1451 if not bases:
1448 bases = [nullid]
1452 bases = [nullid]
1449 csets, bases, heads = cl.nodesbetween(bases, heads)
1453 csets, bases, heads = cl.nodesbetween(bases, heads)
1450 # We assume that all ancestors of bases are known
1454 # We assume that all ancestors of bases are known
1451 common = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1455 common = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1452 return self._changegroupsubset(common, csets, heads, source)
1456 return self._changegroupsubset(common, csets, heads, source)
1453
1457
1454 def getbundle(self, source, heads=None, common=None):
1458 def getbundle(self, source, heads=None, common=None):
1455 """Like changegroupsubset, but returns the set difference between the
1459 """Like changegroupsubset, but returns the set difference between the
1456 ancestors of heads and the ancestors common.
1460 ancestors of heads and the ancestors common.
1457
1461
1458 If heads is None, use the local heads. If common is None, use [nullid].
1462 If heads is None, use the local heads. If common is None, use [nullid].
1459
1463
1460 The nodes in common might not all be known locally due to the way the
1464 The nodes in common might not all be known locally due to the way the
1461 current discovery protocol works.
1465 current discovery protocol works.
1462 """
1466 """
1463 cl = self.changelog
1467 cl = self.changelog
1464 if common:
1468 if common:
1465 nm = cl.nodemap
1469 nm = cl.nodemap
1466 common = [n for n in common if n in nm]
1470 common = [n for n in common if n in nm]
1467 else:
1471 else:
1468 common = [nullid]
1472 common = [nullid]
1469 if not heads:
1473 if not heads:
1470 heads = cl.heads()
1474 heads = cl.heads()
1471 common, missing = cl.findcommonmissing(common, heads)
1475 common, missing = cl.findcommonmissing(common, heads)
1472 return self._changegroupsubset(common, missing, heads, source)
1476 return self._changegroupsubset(common, missing, heads, source)
1473
1477
1474 def _changegroupsubset(self, commonrevs, csets, heads, source):
1478 def _changegroupsubset(self, commonrevs, csets, heads, source):
1475
1479
1476 cl = self.changelog
1480 cl = self.changelog
1477 mf = self.manifest
1481 mf = self.manifest
1478 mfs = {} # needed manifests
1482 mfs = {} # needed manifests
1479 fnodes = {} # needed file nodes
1483 fnodes = {} # needed file nodes
1480
1484
1481 # can we go through the fast path ?
1485 # can we go through the fast path ?
1482 heads.sort()
1486 heads.sort()
1483 if heads == sorted(self.heads()):
1487 if heads == sorted(self.heads()):
1484 return self._changegroup(csets, source)
1488 return self._changegroup(csets, source)
1485
1489
1486 # slow path
1490 # slow path
1487 self.hook('preoutgoing', throw=True, source=source)
1491 self.hook('preoutgoing', throw=True, source=source)
1488 self.changegroupinfo(csets, source)
1492 self.changegroupinfo(csets, source)
1489
1493
1490 # A function generating function that sets up the initial environment
1494 # A function generating function that sets up the initial environment
1491 # the inner function.
1495 # the inner function.
1492 def filenode_collector(changedfiles):
1496 def filenode_collector(changedfiles):
1493 # This gathers information from each manifestnode included in the
1497 # This gathers information from each manifestnode included in the
1494 # changegroup about which filenodes the manifest node references
1498 # changegroup about which filenodes the manifest node references
1495 # so we can include those in the changegroup too.
1499 # so we can include those in the changegroup too.
1496 #
1500 #
1497 # It also remembers which changenode each filenode belongs to. It
1501 # It also remembers which changenode each filenode belongs to. It
1498 # does this by assuming the a filenode belongs to the changenode
1502 # does this by assuming the a filenode belongs to the changenode
1499 # the first manifest that references it belongs to.
1503 # the first manifest that references it belongs to.
1500 def collect(mnode):
1504 def collect(mnode):
1501 r = mf.rev(mnode)
1505 r = mf.rev(mnode)
1502 clnode = mfs[mnode]
1506 clnode = mfs[mnode]
1503 mdata = mf.readfast(mnode)
1507 mdata = mf.readfast(mnode)
1504 for f in changedfiles:
1508 for f in changedfiles:
1505 if f in mdata:
1509 if f in mdata:
1506 fnodes.setdefault(f, {}).setdefault(mdata[f], clnode)
1510 fnodes.setdefault(f, {}).setdefault(mdata[f], clnode)
1507
1511
1508 return collect
1512 return collect
1509
1513
1510 # If we determine that a particular file or manifest node must be a
1514 # If we determine that a particular file or manifest node must be a
1511 # node that the recipient of the changegroup will already have, we can
1515 # node that the recipient of the changegroup will already have, we can
1512 # also assume the recipient will have all the parents. This function
1516 # also assume the recipient will have all the parents. This function
1513 # prunes them from the set of missing nodes.
1517 # prunes them from the set of missing nodes.
1514 def prune(revlog, missingnodes):
1518 def prune(revlog, missingnodes):
1515 # drop any nodes that claim to be part of a cset in commonrevs
1519 # drop any nodes that claim to be part of a cset in commonrevs
1516 drop = set()
1520 drop = set()
1517 for n in missingnodes:
1521 for n in missingnodes:
1518 if revlog.linkrev(revlog.rev(n)) in commonrevs:
1522 if revlog.linkrev(revlog.rev(n)) in commonrevs:
1519 drop.add(n)
1523 drop.add(n)
1520 for n in drop:
1524 for n in drop:
1521 missingnodes.pop(n, None)
1525 missingnodes.pop(n, None)
1522
1526
1523 # Now that we have all theses utility functions to help out and
1527 # Now that we have all theses utility functions to help out and
1524 # logically divide up the task, generate the group.
1528 # logically divide up the task, generate the group.
1525 def gengroup():
1529 def gengroup():
1526 # The set of changed files starts empty.
1530 # The set of changed files starts empty.
1527 changedfiles = set()
1531 changedfiles = set()
1528 collect = changegroup.collector(cl, mfs, changedfiles)
1532 collect = changegroup.collector(cl, mfs, changedfiles)
1529
1533
1530 # Create a changenode group generator that will call our functions
1534 # Create a changenode group generator that will call our functions
1531 # back to lookup the owning changenode and collect information.
1535 # back to lookup the owning changenode and collect information.
1532 group = cl.group(csets, lambda x: x, collect)
1536 group = cl.group(csets, lambda x: x, collect)
1533 for count, chunk in enumerate(group):
1537 for count, chunk in enumerate(group):
1534 yield chunk
1538 yield chunk
1535 # revlog.group yields three entries per node, so
1539 # revlog.group yields three entries per node, so
1536 # dividing by 3 gives an approximation of how many
1540 # dividing by 3 gives an approximation of how many
1537 # nodes have been processed.
1541 # nodes have been processed.
1538 self.ui.progress(_('bundling'), count / 3,
1542 self.ui.progress(_('bundling'), count / 3,
1539 unit=_('changesets'))
1543 unit=_('changesets'))
1540 changecount = count / 3
1544 changecount = count / 3
1541 efiles = len(changedfiles)
1545 efiles = len(changedfiles)
1542 self.ui.progress(_('bundling'), None)
1546 self.ui.progress(_('bundling'), None)
1543
1547
1544 prune(mf, mfs)
1548 prune(mf, mfs)
1545 # Create a generator for the manifestnodes that calls our lookup
1549 # Create a generator for the manifestnodes that calls our lookup
1546 # and data collection functions back.
1550 # and data collection functions back.
1547 group = mf.group(sorted(mfs, key=mf.rev),
1551 group = mf.group(sorted(mfs, key=mf.rev),
1548 lambda mnode: mfs[mnode],
1552 lambda mnode: mfs[mnode],
1549 filenode_collector(changedfiles))
1553 filenode_collector(changedfiles))
1550 for count, chunk in enumerate(group):
1554 for count, chunk in enumerate(group):
1551 yield chunk
1555 yield chunk
1552 # see above comment for why we divide by 3
1556 # see above comment for why we divide by 3
1553 self.ui.progress(_('bundling'), count / 3,
1557 self.ui.progress(_('bundling'), count / 3,
1554 unit=_('manifests'), total=changecount)
1558 unit=_('manifests'), total=changecount)
1555 self.ui.progress(_('bundling'), None)
1559 self.ui.progress(_('bundling'), None)
1556
1560
1557 mfs.clear()
1561 mfs.clear()
1558
1562
1559 # Go through all our files in order sorted by name.
1563 # Go through all our files in order sorted by name.
1560 for idx, fname in enumerate(sorted(changedfiles)):
1564 for idx, fname in enumerate(sorted(changedfiles)):
1561 filerevlog = self.file(fname)
1565 filerevlog = self.file(fname)
1562 if not len(filerevlog):
1566 if not len(filerevlog):
1563 raise util.Abort(_("empty or missing revlog for %s") % fname)
1567 raise util.Abort(_("empty or missing revlog for %s") % fname)
1564 # Toss out the filenodes that the recipient isn't really
1568 # Toss out the filenodes that the recipient isn't really
1565 # missing.
1569 # missing.
1566 missingfnodes = fnodes.pop(fname, {})
1570 missingfnodes = fnodes.pop(fname, {})
1567 prune(filerevlog, missingfnodes)
1571 prune(filerevlog, missingfnodes)
1568 # If any filenodes are left, generate the group for them,
1572 # If any filenodes are left, generate the group for them,
1569 # otherwise don't bother.
1573 # otherwise don't bother.
1570 if missingfnodes:
1574 if missingfnodes:
1571 yield changegroup.chunkheader(len(fname))
1575 yield changegroup.chunkheader(len(fname))
1572 yield fname
1576 yield fname
1573 # Create a group generator and only pass in a changenode
1577 # Create a group generator and only pass in a changenode
1574 # lookup function as we need to collect no information
1578 # lookup function as we need to collect no information
1575 # from filenodes.
1579 # from filenodes.
1576 group = filerevlog.group(
1580 group = filerevlog.group(
1577 sorted(missingfnodes, key=filerevlog.rev),
1581 sorted(missingfnodes, key=filerevlog.rev),
1578 lambda fnode: missingfnodes[fnode])
1582 lambda fnode: missingfnodes[fnode])
1579 for chunk in group:
1583 for chunk in group:
1580 # even though we print the same progress on
1584 # even though we print the same progress on
1581 # most loop iterations, put the progress call
1585 # most loop iterations, put the progress call
1582 # here so that time estimates (if any) can be updated
1586 # here so that time estimates (if any) can be updated
1583 self.ui.progress(
1587 self.ui.progress(
1584 _('bundling'), idx, item=fname,
1588 _('bundling'), idx, item=fname,
1585 unit=_('files'), total=efiles)
1589 unit=_('files'), total=efiles)
1586 yield chunk
1590 yield chunk
1587 # Signal that no more groups are left.
1591 # Signal that no more groups are left.
1588 yield changegroup.closechunk()
1592 yield changegroup.closechunk()
1589 self.ui.progress(_('bundling'), None)
1593 self.ui.progress(_('bundling'), None)
1590
1594
1591 if csets:
1595 if csets:
1592 self.hook('outgoing', node=hex(csets[0]), source=source)
1596 self.hook('outgoing', node=hex(csets[0]), source=source)
1593
1597
1594 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1598 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1595
1599
1596 def changegroup(self, basenodes, source):
1600 def changegroup(self, basenodes, source):
1597 # to avoid a race we use changegroupsubset() (issue1320)
1601 # to avoid a race we use changegroupsubset() (issue1320)
1598 return self.changegroupsubset(basenodes, self.heads(), source)
1602 return self.changegroupsubset(basenodes, self.heads(), source)
1599
1603
1600 def _changegroup(self, nodes, source):
1604 def _changegroup(self, nodes, source):
1601 """Compute the changegroup of all nodes that we have that a recipient
1605 """Compute the changegroup of all nodes that we have that a recipient
1602 doesn't. Return a chunkbuffer object whose read() method will return
1606 doesn't. Return a chunkbuffer object whose read() method will return
1603 successive changegroup chunks.
1607 successive changegroup chunks.
1604
1608
1605 This is much easier than the previous function as we can assume that
1609 This is much easier than the previous function as we can assume that
1606 the recipient has any changenode we aren't sending them.
1610 the recipient has any changenode we aren't sending them.
1607
1611
1608 nodes is the set of nodes to send"""
1612 nodes is the set of nodes to send"""
1609
1613
1610 self.hook('preoutgoing', throw=True, source=source)
1614 self.hook('preoutgoing', throw=True, source=source)
1611
1615
1612 cl = self.changelog
1616 cl = self.changelog
1613 revset = set([cl.rev(n) for n in nodes])
1617 revset = set([cl.rev(n) for n in nodes])
1614 self.changegroupinfo(nodes, source)
1618 self.changegroupinfo(nodes, source)
1615
1619
1616 def gennodelst(log):
1620 def gennodelst(log):
1617 for r in log:
1621 for r in log:
1618 if log.linkrev(r) in revset:
1622 if log.linkrev(r) in revset:
1619 yield log.node(r)
1623 yield log.node(r)
1620
1624
1621 def lookuplinkrev_func(revlog):
1625 def lookuplinkrev_func(revlog):
1622 def lookuplinkrev(n):
1626 def lookuplinkrev(n):
1623 return cl.node(revlog.linkrev(revlog.rev(n)))
1627 return cl.node(revlog.linkrev(revlog.rev(n)))
1624 return lookuplinkrev
1628 return lookuplinkrev
1625
1629
1626 def gengroup():
1630 def gengroup():
1627 '''yield a sequence of changegroup chunks (strings)'''
1631 '''yield a sequence of changegroup chunks (strings)'''
1628 # construct a list of all changed files
1632 # construct a list of all changed files
1629 changedfiles = set()
1633 changedfiles = set()
1630 mmfs = {}
1634 mmfs = {}
1631 collect = changegroup.collector(cl, mmfs, changedfiles)
1635 collect = changegroup.collector(cl, mmfs, changedfiles)
1632
1636
1633 for count, chunk in enumerate(cl.group(nodes, lambda x: x, collect)):
1637 for count, chunk in enumerate(cl.group(nodes, lambda x: x, collect)):
1634 # revlog.group yields three entries per node, so
1638 # revlog.group yields three entries per node, so
1635 # dividing by 3 gives an approximation of how many
1639 # dividing by 3 gives an approximation of how many
1636 # nodes have been processed.
1640 # nodes have been processed.
1637 self.ui.progress(_('bundling'), count / 3, unit=_('changesets'))
1641 self.ui.progress(_('bundling'), count / 3, unit=_('changesets'))
1638 yield chunk
1642 yield chunk
1639 efiles = len(changedfiles)
1643 efiles = len(changedfiles)
1640 changecount = count / 3
1644 changecount = count / 3
1641 self.ui.progress(_('bundling'), None)
1645 self.ui.progress(_('bundling'), None)
1642
1646
1643 mnfst = self.manifest
1647 mnfst = self.manifest
1644 nodeiter = gennodelst(mnfst)
1648 nodeiter = gennodelst(mnfst)
1645 for count, chunk in enumerate(mnfst.group(nodeiter,
1649 for count, chunk in enumerate(mnfst.group(nodeiter,
1646 lookuplinkrev_func(mnfst))):
1650 lookuplinkrev_func(mnfst))):
1647 # see above comment for why we divide by 3
1651 # see above comment for why we divide by 3
1648 self.ui.progress(_('bundling'), count / 3,
1652 self.ui.progress(_('bundling'), count / 3,
1649 unit=_('manifests'), total=changecount)
1653 unit=_('manifests'), total=changecount)
1650 yield chunk
1654 yield chunk
1651 self.ui.progress(_('bundling'), None)
1655 self.ui.progress(_('bundling'), None)
1652
1656
1653 for idx, fname in enumerate(sorted(changedfiles)):
1657 for idx, fname in enumerate(sorted(changedfiles)):
1654 filerevlog = self.file(fname)
1658 filerevlog = self.file(fname)
1655 if not len(filerevlog):
1659 if not len(filerevlog):
1656 raise util.Abort(_("empty or missing revlog for %s") % fname)
1660 raise util.Abort(_("empty or missing revlog for %s") % fname)
1657 nodeiter = gennodelst(filerevlog)
1661 nodeiter = gennodelst(filerevlog)
1658 nodeiter = list(nodeiter)
1662 nodeiter = list(nodeiter)
1659 if nodeiter:
1663 if nodeiter:
1660 yield changegroup.chunkheader(len(fname))
1664 yield changegroup.chunkheader(len(fname))
1661 yield fname
1665 yield fname
1662 lookup = lookuplinkrev_func(filerevlog)
1666 lookup = lookuplinkrev_func(filerevlog)
1663 for chunk in filerevlog.group(nodeiter, lookup):
1667 for chunk in filerevlog.group(nodeiter, lookup):
1664 self.ui.progress(
1668 self.ui.progress(
1665 _('bundling'), idx, item=fname,
1669 _('bundling'), idx, item=fname,
1666 total=efiles, unit=_('files'))
1670 total=efiles, unit=_('files'))
1667 yield chunk
1671 yield chunk
1668 self.ui.progress(_('bundling'), None)
1672 self.ui.progress(_('bundling'), None)
1669
1673
1670 yield changegroup.closechunk()
1674 yield changegroup.closechunk()
1671
1675
1672 if nodes:
1676 if nodes:
1673 self.hook('outgoing', node=hex(nodes[0]), source=source)
1677 self.hook('outgoing', node=hex(nodes[0]), source=source)
1674
1678
1675 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1679 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1676
1680
1677 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1681 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1678 """Add the changegroup returned by source.read() to this repo.
1682 """Add the changegroup returned by source.read() to this repo.
1679 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1683 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1680 the URL of the repo where this changegroup is coming from.
1684 the URL of the repo where this changegroup is coming from.
1681 If lock is not None, the function takes ownership of the lock
1685 If lock is not None, the function takes ownership of the lock
1682 and releases it after the changegroup is added.
1686 and releases it after the changegroup is added.
1683
1687
1684 Return an integer summarizing the change to this repo:
1688 Return an integer summarizing the change to this repo:
1685 - nothing changed or no source: 0
1689 - nothing changed or no source: 0
1686 - more heads than before: 1+added heads (2..n)
1690 - more heads than before: 1+added heads (2..n)
1687 - fewer heads than before: -1-removed heads (-2..-n)
1691 - fewer heads than before: -1-removed heads (-2..-n)
1688 - number of heads stays the same: 1
1692 - number of heads stays the same: 1
1689 """
1693 """
1690 def csmap(x):
1694 def csmap(x):
1691 self.ui.debug("add changeset %s\n" % short(x))
1695 self.ui.debug("add changeset %s\n" % short(x))
1692 return len(cl)
1696 return len(cl)
1693
1697
1694 def revmap(x):
1698 def revmap(x):
1695 return cl.rev(x)
1699 return cl.rev(x)
1696
1700
1697 if not source:
1701 if not source:
1698 return 0
1702 return 0
1699
1703
1700 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1704 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1701
1705
1702 changesets = files = revisions = 0
1706 changesets = files = revisions = 0
1703 efiles = set()
1707 efiles = set()
1704
1708
1705 # write changelog data to temp files so concurrent readers will not see
1709 # write changelog data to temp files so concurrent readers will not see
1706 # inconsistent view
1710 # inconsistent view
1707 cl = self.changelog
1711 cl = self.changelog
1708 cl.delayupdate()
1712 cl.delayupdate()
1709 oldheads = len(cl.heads())
1713 oldheads = len(cl.heads())
1710
1714
1711 tr = self.transaction("\n".join([srctype, urlmod.hidepassword(url)]))
1715 tr = self.transaction("\n".join([srctype, urlmod.hidepassword(url)]))
1712 try:
1716 try:
1713 trp = weakref.proxy(tr)
1717 trp = weakref.proxy(tr)
1714 # pull off the changeset group
1718 # pull off the changeset group
1715 self.ui.status(_("adding changesets\n"))
1719 self.ui.status(_("adding changesets\n"))
1716 clstart = len(cl)
1720 clstart = len(cl)
1717 class prog(object):
1721 class prog(object):
1718 step = _('changesets')
1722 step = _('changesets')
1719 count = 1
1723 count = 1
1720 ui = self.ui
1724 ui = self.ui
1721 total = None
1725 total = None
1722 def __call__(self):
1726 def __call__(self):
1723 self.ui.progress(self.step, self.count, unit=_('chunks'),
1727 self.ui.progress(self.step, self.count, unit=_('chunks'),
1724 total=self.total)
1728 total=self.total)
1725 self.count += 1
1729 self.count += 1
1726 pr = prog()
1730 pr = prog()
1727 source.callback = pr
1731 source.callback = pr
1728
1732
1729 if (cl.addgroup(source, csmap, trp) is None
1733 if (cl.addgroup(source, csmap, trp) is None
1730 and not emptyok):
1734 and not emptyok):
1731 raise util.Abort(_("received changelog group is empty"))
1735 raise util.Abort(_("received changelog group is empty"))
1732 clend = len(cl)
1736 clend = len(cl)
1733 changesets = clend - clstart
1737 changesets = clend - clstart
1734 for c in xrange(clstart, clend):
1738 for c in xrange(clstart, clend):
1735 efiles.update(self[c].files())
1739 efiles.update(self[c].files())
1736 efiles = len(efiles)
1740 efiles = len(efiles)
1737 self.ui.progress(_('changesets'), None)
1741 self.ui.progress(_('changesets'), None)
1738
1742
1739 # pull off the manifest group
1743 # pull off the manifest group
1740 self.ui.status(_("adding manifests\n"))
1744 self.ui.status(_("adding manifests\n"))
1741 pr.step = _('manifests')
1745 pr.step = _('manifests')
1742 pr.count = 1
1746 pr.count = 1
1743 pr.total = changesets # manifests <= changesets
1747 pr.total = changesets # manifests <= changesets
1744 # no need to check for empty manifest group here:
1748 # no need to check for empty manifest group here:
1745 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1749 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1746 # no new manifest will be created and the manifest group will
1750 # no new manifest will be created and the manifest group will
1747 # be empty during the pull
1751 # be empty during the pull
1748 self.manifest.addgroup(source, revmap, trp)
1752 self.manifest.addgroup(source, revmap, trp)
1749 self.ui.progress(_('manifests'), None)
1753 self.ui.progress(_('manifests'), None)
1750
1754
1751 needfiles = {}
1755 needfiles = {}
1752 if self.ui.configbool('server', 'validate', default=False):
1756 if self.ui.configbool('server', 'validate', default=False):
1753 # validate incoming csets have their manifests
1757 # validate incoming csets have their manifests
1754 for cset in xrange(clstart, clend):
1758 for cset in xrange(clstart, clend):
1755 mfest = self.changelog.read(self.changelog.node(cset))[0]
1759 mfest = self.changelog.read(self.changelog.node(cset))[0]
1756 mfest = self.manifest.readdelta(mfest)
1760 mfest = self.manifest.readdelta(mfest)
1757 # store file nodes we must see
1761 # store file nodes we must see
1758 for f, n in mfest.iteritems():
1762 for f, n in mfest.iteritems():
1759 needfiles.setdefault(f, set()).add(n)
1763 needfiles.setdefault(f, set()).add(n)
1760
1764
1761 # process the files
1765 # process the files
1762 self.ui.status(_("adding file changes\n"))
1766 self.ui.status(_("adding file changes\n"))
1763 pr.step = 'files'
1767 pr.step = 'files'
1764 pr.count = 1
1768 pr.count = 1
1765 pr.total = efiles
1769 pr.total = efiles
1766 source.callback = None
1770 source.callback = None
1767
1771
1768 while 1:
1772 while 1:
1769 f = source.chunk()
1773 f = source.chunk()
1770 if not f:
1774 if not f:
1771 break
1775 break
1772 self.ui.debug("adding %s revisions\n" % f)
1776 self.ui.debug("adding %s revisions\n" % f)
1773 pr()
1777 pr()
1774 fl = self.file(f)
1778 fl = self.file(f)
1775 o = len(fl)
1779 o = len(fl)
1776 if fl.addgroup(source, revmap, trp) is None:
1780 if fl.addgroup(source, revmap, trp) is None:
1777 raise util.Abort(_("received file revlog group is empty"))
1781 raise util.Abort(_("received file revlog group is empty"))
1778 revisions += len(fl) - o
1782 revisions += len(fl) - o
1779 files += 1
1783 files += 1
1780 if f in needfiles:
1784 if f in needfiles:
1781 needs = needfiles[f]
1785 needs = needfiles[f]
1782 for new in xrange(o, len(fl)):
1786 for new in xrange(o, len(fl)):
1783 n = fl.node(new)
1787 n = fl.node(new)
1784 if n in needs:
1788 if n in needs:
1785 needs.remove(n)
1789 needs.remove(n)
1786 if not needs:
1790 if not needs:
1787 del needfiles[f]
1791 del needfiles[f]
1788 self.ui.progress(_('files'), None)
1792 self.ui.progress(_('files'), None)
1789
1793
1790 for f, needs in needfiles.iteritems():
1794 for f, needs in needfiles.iteritems():
1791 fl = self.file(f)
1795 fl = self.file(f)
1792 for n in needs:
1796 for n in needs:
1793 try:
1797 try:
1794 fl.rev(n)
1798 fl.rev(n)
1795 except error.LookupError:
1799 except error.LookupError:
1796 raise util.Abort(
1800 raise util.Abort(
1797 _('missing file data for %s:%s - run hg verify') %
1801 _('missing file data for %s:%s - run hg verify') %
1798 (f, hex(n)))
1802 (f, hex(n)))
1799
1803
1800 newheads = len(cl.heads())
1804 newheads = len(cl.heads())
1801 heads = ""
1805 heads = ""
1802 if oldheads and newheads != oldheads:
1806 if oldheads and newheads != oldheads:
1803 heads = _(" (%+d heads)") % (newheads - oldheads)
1807 heads = _(" (%+d heads)") % (newheads - oldheads)
1804
1808
1805 self.ui.status(_("added %d changesets"
1809 self.ui.status(_("added %d changesets"
1806 " with %d changes to %d files%s\n")
1810 " with %d changes to %d files%s\n")
1807 % (changesets, revisions, files, heads))
1811 % (changesets, revisions, files, heads))
1808
1812
1809 if changesets > 0:
1813 if changesets > 0:
1810 p = lambda: cl.writepending() and self.root or ""
1814 p = lambda: cl.writepending() and self.root or ""
1811 self.hook('pretxnchangegroup', throw=True,
1815 self.hook('pretxnchangegroup', throw=True,
1812 node=hex(cl.node(clstart)), source=srctype,
1816 node=hex(cl.node(clstart)), source=srctype,
1813 url=url, pending=p)
1817 url=url, pending=p)
1814
1818
1815 # make changelog see real files again
1819 # make changelog see real files again
1816 cl.finalize(trp)
1820 cl.finalize(trp)
1817
1821
1818 tr.close()
1822 tr.close()
1819 finally:
1823 finally:
1820 tr.release()
1824 tr.release()
1821 if lock:
1825 if lock:
1822 lock.release()
1826 lock.release()
1823
1827
1824 if changesets > 0:
1828 if changesets > 0:
1825 # forcefully update the on-disk branch cache
1829 # forcefully update the on-disk branch cache
1826 self.ui.debug("updating the branch cache\n")
1830 self.ui.debug("updating the branch cache\n")
1827 self.updatebranchcache()
1831 self.updatebranchcache()
1828 self.hook("changegroup", node=hex(cl.node(clstart)),
1832 self.hook("changegroup", node=hex(cl.node(clstart)),
1829 source=srctype, url=url)
1833 source=srctype, url=url)
1830
1834
1831 for i in xrange(clstart, clend):
1835 for i in xrange(clstart, clend):
1832 self.hook("incoming", node=hex(cl.node(i)),
1836 self.hook("incoming", node=hex(cl.node(i)),
1833 source=srctype, url=url)
1837 source=srctype, url=url)
1834
1838
1835 # never return 0 here:
1839 # never return 0 here:
1836 if newheads < oldheads:
1840 if newheads < oldheads:
1837 return newheads - oldheads - 1
1841 return newheads - oldheads - 1
1838 else:
1842 else:
1839 return newheads - oldheads + 1
1843 return newheads - oldheads + 1
1840
1844
1841
1845
1842 def stream_in(self, remote, requirements):
1846 def stream_in(self, remote, requirements):
1843 lock = self.lock()
1847 lock = self.lock()
1844 try:
1848 try:
1845 fp = remote.stream_out()
1849 fp = remote.stream_out()
1846 l = fp.readline()
1850 l = fp.readline()
1847 try:
1851 try:
1848 resp = int(l)
1852 resp = int(l)
1849 except ValueError:
1853 except ValueError:
1850 raise error.ResponseError(
1854 raise error.ResponseError(
1851 _('Unexpected response from remote server:'), l)
1855 _('Unexpected response from remote server:'), l)
1852 if resp == 1:
1856 if resp == 1:
1853 raise util.Abort(_('operation forbidden by server'))
1857 raise util.Abort(_('operation forbidden by server'))
1854 elif resp == 2:
1858 elif resp == 2:
1855 raise util.Abort(_('locking the remote repository failed'))
1859 raise util.Abort(_('locking the remote repository failed'))
1856 elif resp != 0:
1860 elif resp != 0:
1857 raise util.Abort(_('the server sent an unknown error code'))
1861 raise util.Abort(_('the server sent an unknown error code'))
1858 self.ui.status(_('streaming all changes\n'))
1862 self.ui.status(_('streaming all changes\n'))
1859 l = fp.readline()
1863 l = fp.readline()
1860 try:
1864 try:
1861 total_files, total_bytes = map(int, l.split(' ', 1))
1865 total_files, total_bytes = map(int, l.split(' ', 1))
1862 except (ValueError, TypeError):
1866 except (ValueError, TypeError):
1863 raise error.ResponseError(
1867 raise error.ResponseError(
1864 _('Unexpected response from remote server:'), l)
1868 _('Unexpected response from remote server:'), l)
1865 self.ui.status(_('%d files to transfer, %s of data\n') %
1869 self.ui.status(_('%d files to transfer, %s of data\n') %
1866 (total_files, util.bytecount(total_bytes)))
1870 (total_files, util.bytecount(total_bytes)))
1867 start = time.time()
1871 start = time.time()
1868 for i in xrange(total_files):
1872 for i in xrange(total_files):
1869 # XXX doesn't support '\n' or '\r' in filenames
1873 # XXX doesn't support '\n' or '\r' in filenames
1870 l = fp.readline()
1874 l = fp.readline()
1871 try:
1875 try:
1872 name, size = l.split('\0', 1)
1876 name, size = l.split('\0', 1)
1873 size = int(size)
1877 size = int(size)
1874 except (ValueError, TypeError):
1878 except (ValueError, TypeError):
1875 raise error.ResponseError(
1879 raise error.ResponseError(
1876 _('Unexpected response from remote server:'), l)
1880 _('Unexpected response from remote server:'), l)
1877 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1881 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1878 # for backwards compat, name was partially encoded
1882 # for backwards compat, name was partially encoded
1879 ofp = self.sopener(store.decodedir(name), 'w')
1883 ofp = self.sopener(store.decodedir(name), 'w')
1880 for chunk in util.filechunkiter(fp, limit=size):
1884 for chunk in util.filechunkiter(fp, limit=size):
1881 ofp.write(chunk)
1885 ofp.write(chunk)
1882 ofp.close()
1886 ofp.close()
1883 elapsed = time.time() - start
1887 elapsed = time.time() - start
1884 if elapsed <= 0:
1888 if elapsed <= 0:
1885 elapsed = 0.001
1889 elapsed = 0.001
1886 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1890 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1887 (util.bytecount(total_bytes), elapsed,
1891 (util.bytecount(total_bytes), elapsed,
1888 util.bytecount(total_bytes / elapsed)))
1892 util.bytecount(total_bytes / elapsed)))
1889
1893
1890 # new requirements = old non-format requirements + new format-related
1894 # new requirements = old non-format requirements + new format-related
1891 # requirements from the streamed-in repository
1895 # requirements from the streamed-in repository
1892 requirements.update(set(self.requirements) - self.supportedformats)
1896 requirements.update(set(self.requirements) - self.supportedformats)
1893 self._applyrequirements(requirements)
1897 self._applyrequirements(requirements)
1894 self._writerequirements()
1898 self._writerequirements()
1895
1899
1896 self.invalidate()
1900 self.invalidate()
1897 return len(self.heads()) + 1
1901 return len(self.heads()) + 1
1898 finally:
1902 finally:
1899 lock.release()
1903 lock.release()
1900
1904
1901 def clone(self, remote, heads=[], stream=False):
1905 def clone(self, remote, heads=[], stream=False):
1902 '''clone remote repository.
1906 '''clone remote repository.
1903
1907
1904 keyword arguments:
1908 keyword arguments:
1905 heads: list of revs to clone (forces use of pull)
1909 heads: list of revs to clone (forces use of pull)
1906 stream: use streaming clone if possible'''
1910 stream: use streaming clone if possible'''
1907
1911
1908 # now, all clients that can request uncompressed clones can
1912 # now, all clients that can request uncompressed clones can
1909 # read repo formats supported by all servers that can serve
1913 # read repo formats supported by all servers that can serve
1910 # them.
1914 # them.
1911
1915
1912 # if revlog format changes, client will have to check version
1916 # if revlog format changes, client will have to check version
1913 # and format flags on "stream" capability, and use
1917 # and format flags on "stream" capability, and use
1914 # uncompressed only if compatible.
1918 # uncompressed only if compatible.
1915
1919
1916 if stream and not heads:
1920 if stream and not heads:
1917 # 'stream' means remote revlog format is revlogv1 only
1921 # 'stream' means remote revlog format is revlogv1 only
1918 if remote.capable('stream'):
1922 if remote.capable('stream'):
1919 return self.stream_in(remote, set(('revlogv1',)))
1923 return self.stream_in(remote, set(('revlogv1',)))
1920 # otherwise, 'streamreqs' contains the remote revlog format
1924 # otherwise, 'streamreqs' contains the remote revlog format
1921 streamreqs = remote.capable('streamreqs')
1925 streamreqs = remote.capable('streamreqs')
1922 if streamreqs:
1926 if streamreqs:
1923 streamreqs = set(streamreqs.split(','))
1927 streamreqs = set(streamreqs.split(','))
1924 # if we support it, stream in and adjust our requirements
1928 # if we support it, stream in and adjust our requirements
1925 if not streamreqs - self.supportedformats:
1929 if not streamreqs - self.supportedformats:
1926 return self.stream_in(remote, streamreqs)
1930 return self.stream_in(remote, streamreqs)
1927 return self.pull(remote, heads)
1931 return self.pull(remote, heads)
1928
1932
1929 def pushkey(self, namespace, key, old, new):
1933 def pushkey(self, namespace, key, old, new):
1930 return pushkey.push(self, namespace, key, old, new)
1934 return pushkey.push(self, namespace, key, old, new)
1931
1935
1932 def listkeys(self, namespace):
1936 def listkeys(self, namespace):
1933 return pushkey.list(self, namespace)
1937 return pushkey.list(self, namespace)
1934
1938
1935 def debugwireargs(self, one, two, three=None, four=None):
1939 def debugwireargs(self, one, two, three=None, four=None):
1936 '''used to test argument passing over the wire'''
1940 '''used to test argument passing over the wire'''
1937 return "%s %s %s %s" % (one, two, three, four)
1941 return "%s %s %s %s" % (one, two, three, four)
1938
1942
1939 # used to avoid circular references so destructors work
1943 # used to avoid circular references so destructors work
1940 def aftertrans(files):
1944 def aftertrans(files):
1941 renamefiles = [tuple(t) for t in files]
1945 renamefiles = [tuple(t) for t in files]
1942 def a():
1946 def a():
1943 for src, dest in renamefiles:
1947 for src, dest in renamefiles:
1944 util.rename(src, dest)
1948 util.rename(src, dest)
1945 return a
1949 return a
1946
1950
1947 def instance(ui, path, create):
1951 def instance(ui, path, create):
1948 return localrepository(ui, util.drop_scheme('file', path), create)
1952 return localrepository(ui, util.drop_scheme('file', path), create)
1949
1953
1950 def islocal(path):
1954 def islocal(path):
1951 return True
1955 return True
@@ -1,89 +1,91 b''
1 Issue586: removing remote files after merge appears to corrupt the
1 Issue586: removing remote files after merge appears to corrupt the
2 dirstate
2 dirstate
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ hg ci -Ama
7 $ hg ci -Ama
8 adding a
8 adding a
9
9
10 $ hg init ../b
10 $ hg init ../b
11 $ cd ../b
11 $ cd ../b
12 $ echo b > b
12 $ echo b > b
13 $ hg ci -Amb
13 $ hg ci -Amb
14 adding b
14 adding b
15
15
16 $ hg pull -f ../a
16 $ hg pull -f ../a
17 pulling from ../a
17 pulling from ../a
18 searching for changes
18 searching for changes
19 warning: repository is unrelated
19 warning: repository is unrelated
20 requesting all changes
20 adding changesets
21 adding changesets
21 adding manifests
22 adding manifests
22 adding file changes
23 adding file changes
23 added 1 changesets with 1 changes to 1 files (+1 heads)
24 added 1 changesets with 1 changes to 1 files (+1 heads)
24 (run 'hg heads' to see heads, 'hg merge' to merge)
25 (run 'hg heads' to see heads, 'hg merge' to merge)
25 $ hg merge
26 $ hg merge
26 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 (branch merge, don't forget to commit)
28 (branch merge, don't forget to commit)
28 $ hg rm -f a
29 $ hg rm -f a
29 $ hg ci -Amc
30 $ hg ci -Amc
30
31
31 $ hg st -A
32 $ hg st -A
32 C b
33 C b
33 $ cd ..
34 $ cd ..
34
35
35 Issue1433: Traceback after two unrelated pull, two move, a merge and
36 Issue1433: Traceback after two unrelated pull, two move, a merge and
36 a commit (related to issue586)
37 a commit (related to issue586)
37
38
38 create test repos
39 create test repos
39
40
40 $ hg init repoa
41 $ hg init repoa
41 $ touch repoa/a
42 $ touch repoa/a
42 $ hg -R repoa ci -Am adda
43 $ hg -R repoa ci -Am adda
43 adding a
44 adding a
44
45
45 $ hg init repob
46 $ hg init repob
46 $ touch repob/b
47 $ touch repob/b
47 $ hg -R repob ci -Am addb
48 $ hg -R repob ci -Am addb
48 adding b
49 adding b
49
50
50 $ hg init repoc
51 $ hg init repoc
51 $ cd repoc
52 $ cd repoc
52 $ hg pull ../repoa
53 $ hg pull ../repoa
53 pulling from ../repoa
54 pulling from ../repoa
54 requesting all changes
55 requesting all changes
55 adding changesets
56 adding changesets
56 adding manifests
57 adding manifests
57 adding file changes
58 adding file changes
58 added 1 changesets with 1 changes to 1 files
59 added 1 changesets with 1 changes to 1 files
59 (run 'hg update' to get a working copy)
60 (run 'hg update' to get a working copy)
60 $ hg update
61 $ hg update
61 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 $ mkdir tst
63 $ mkdir tst
63 $ hg mv * tst
64 $ hg mv * tst
64 $ hg ci -m "import a in tst"
65 $ hg ci -m "import a in tst"
65 $ hg pull -f ../repob
66 $ hg pull -f ../repob
66 pulling from ../repob
67 pulling from ../repob
67 searching for changes
68 searching for changes
68 warning: repository is unrelated
69 warning: repository is unrelated
70 requesting all changes
69 adding changesets
71 adding changesets
70 adding manifests
72 adding manifests
71 adding file changes
73 adding file changes
72 added 1 changesets with 1 changes to 1 files (+1 heads)
74 added 1 changesets with 1 changes to 1 files (+1 heads)
73 (run 'hg heads' to see heads, 'hg merge' to merge)
75 (run 'hg heads' to see heads, 'hg merge' to merge)
74
76
75 merge both repos
77 merge both repos
76
78
77 $ hg merge
79 $ hg merge
78 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 (branch merge, don't forget to commit)
81 (branch merge, don't forget to commit)
80 $ mkdir src
82 $ mkdir src
81
83
82 move b content
84 move b content
83
85
84 $ hg mv b src
86 $ hg mv b src
85 $ hg ci -m "import b in src"
87 $ hg ci -m "import b in src"
86 $ hg manifest
88 $ hg manifest
87 src/b
89 src/b
88 tst/a
90 tst/a
89
91
@@ -1,431 +1,432 b''
1 $ "$TESTDIR/hghave" no-outer-repo || exit 80
1 $ "$TESTDIR/hghave" no-outer-repo || exit 80
2
2
3 $ hg init a
3 $ hg init a
4 $ cd a
4 $ cd a
5 $ echo a > a
5 $ echo a > a
6 $ hg ci -A -d'1 0' -m a
6 $ hg ci -A -d'1 0' -m a
7 adding a
7 adding a
8
8
9 $ cd ..
9 $ cd ..
10
10
11 $ hg init b
11 $ hg init b
12 $ cd b
12 $ cd b
13 $ echo b > b
13 $ echo b > b
14 $ hg ci -A -d'1 0' -m b
14 $ hg ci -A -d'1 0' -m b
15 adding b
15 adding b
16
16
17 $ cd ..
17 $ cd ..
18
18
19 $ hg clone a c
19 $ hg clone a c
20 updating to branch default
20 updating to branch default
21 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
21 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 $ cd c
22 $ cd c
23 $ cat >> .hg/hgrc <<EOF
23 $ cat >> .hg/hgrc <<EOF
24 > [paths]
24 > [paths]
25 > relative = ../a
25 > relative = ../a
26 > EOF
26 > EOF
27 $ hg pull -f ../b
27 $ hg pull -f ../b
28 pulling from ../b
28 pulling from ../b
29 searching for changes
29 searching for changes
30 warning: repository is unrelated
30 warning: repository is unrelated
31 requesting all changes
31 adding changesets
32 adding changesets
32 adding manifests
33 adding manifests
33 adding file changes
34 adding file changes
34 added 1 changesets with 1 changes to 1 files (+1 heads)
35 added 1 changesets with 1 changes to 1 files (+1 heads)
35 (run 'hg heads' to see heads, 'hg merge' to merge)
36 (run 'hg heads' to see heads, 'hg merge' to merge)
36 $ hg merge
37 $ hg merge
37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 (branch merge, don't forget to commit)
39 (branch merge, don't forget to commit)
39
40
40 $ cd ..
41 $ cd ..
41
42
42 Testing -R/--repository:
43 Testing -R/--repository:
43
44
44 $ hg -R a tip
45 $ hg -R a tip
45 changeset: 0:8580ff50825a
46 changeset: 0:8580ff50825a
46 tag: tip
47 tag: tip
47 user: test
48 user: test
48 date: Thu Jan 01 00:00:01 1970 +0000
49 date: Thu Jan 01 00:00:01 1970 +0000
49 summary: a
50 summary: a
50
51
51 $ hg --repository b tip
52 $ hg --repository b tip
52 changeset: 0:b6c483daf290
53 changeset: 0:b6c483daf290
53 tag: tip
54 tag: tip
54 user: test
55 user: test
55 date: Thu Jan 01 00:00:01 1970 +0000
56 date: Thu Jan 01 00:00:01 1970 +0000
56 summary: b
57 summary: b
57
58
58
59
59 -R with a URL:
60 -R with a URL:
60
61
61 $ hg -R file:a identify
62 $ hg -R file:a identify
62 8580ff50825a tip
63 8580ff50825a tip
63 $ hg -R file://localhost/`pwd`/a/ identify
64 $ hg -R file://localhost/`pwd`/a/ identify
64 8580ff50825a tip
65 8580ff50825a tip
65
66
66 -R with path aliases:
67 -R with path aliases:
67
68
68 $ cd c
69 $ cd c
69 $ hg -R default identify
70 $ hg -R default identify
70 8580ff50825a tip
71 8580ff50825a tip
71 $ hg -R relative identify
72 $ hg -R relative identify
72 8580ff50825a tip
73 8580ff50825a tip
73 $ echo '[paths]' >> $HGRCPATH
74 $ echo '[paths]' >> $HGRCPATH
74 $ echo 'relativetohome = a' >> $HGRCPATH
75 $ echo 'relativetohome = a' >> $HGRCPATH
75 $ HOME=`pwd`/../ hg -R relativetohome identify
76 $ HOME=`pwd`/../ hg -R relativetohome identify
76 8580ff50825a tip
77 8580ff50825a tip
77 $ cd ..
78 $ cd ..
78
79
79 Implicit -R:
80 Implicit -R:
80
81
81 $ hg ann a/a
82 $ hg ann a/a
82 0: a
83 0: a
83 $ hg ann a/a a/a
84 $ hg ann a/a a/a
84 0: a
85 0: a
85 $ hg ann a/a b/b
86 $ hg ann a/a b/b
86 abort: There is no Mercurial repository here (.hg not found)!
87 abort: There is no Mercurial repository here (.hg not found)!
87 [255]
88 [255]
88 $ hg -R b ann a/a
89 $ hg -R b ann a/a
89 abort: a/a not under root
90 abort: a/a not under root
90 [255]
91 [255]
91 $ hg log
92 $ hg log
92 abort: There is no Mercurial repository here (.hg not found)!
93 abort: There is no Mercurial repository here (.hg not found)!
93 [255]
94 [255]
94
95
95 Abbreviation of long option:
96 Abbreviation of long option:
96
97
97 $ hg --repo c tip
98 $ hg --repo c tip
98 changeset: 1:b6c483daf290
99 changeset: 1:b6c483daf290
99 tag: tip
100 tag: tip
100 parent: -1:000000000000
101 parent: -1:000000000000
101 user: test
102 user: test
102 date: Thu Jan 01 00:00:01 1970 +0000
103 date: Thu Jan 01 00:00:01 1970 +0000
103 summary: b
104 summary: b
104
105
105
106
106 earlygetopt with duplicate options (36d23de02da1):
107 earlygetopt with duplicate options (36d23de02da1):
107
108
108 $ hg --cwd a --cwd b --cwd c tip
109 $ hg --cwd a --cwd b --cwd c tip
109 changeset: 1:b6c483daf290
110 changeset: 1:b6c483daf290
110 tag: tip
111 tag: tip
111 parent: -1:000000000000
112 parent: -1:000000000000
112 user: test
113 user: test
113 date: Thu Jan 01 00:00:01 1970 +0000
114 date: Thu Jan 01 00:00:01 1970 +0000
114 summary: b
115 summary: b
115
116
116 $ hg --repo c --repository b -R a tip
117 $ hg --repo c --repository b -R a tip
117 changeset: 0:8580ff50825a
118 changeset: 0:8580ff50825a
118 tag: tip
119 tag: tip
119 user: test
120 user: test
120 date: Thu Jan 01 00:00:01 1970 +0000
121 date: Thu Jan 01 00:00:01 1970 +0000
121 summary: a
122 summary: a
122
123
123
124
124 earlygetopt short option without following space:
125 earlygetopt short option without following space:
125
126
126 $ hg -q -Rb tip
127 $ hg -q -Rb tip
127 0:b6c483daf290
128 0:b6c483daf290
128
129
129 earlygetopt with illegal abbreviations:
130 earlygetopt with illegal abbreviations:
130
131
131 $ hg --confi "foo.bar=baz"
132 $ hg --confi "foo.bar=baz"
132 abort: option --config may not be abbreviated!
133 abort: option --config may not be abbreviated!
133 [255]
134 [255]
134 $ hg --cw a tip
135 $ hg --cw a tip
135 abort: option --cwd may not be abbreviated!
136 abort: option --cwd may not be abbreviated!
136 [255]
137 [255]
137 $ hg --rep a tip
138 $ hg --rep a tip
138 abort: Option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
139 abort: Option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
139 [255]
140 [255]
140 $ hg --repositor a tip
141 $ hg --repositor a tip
141 abort: Option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
142 abort: Option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
142 [255]
143 [255]
143 $ hg -qR a tip
144 $ hg -qR a tip
144 abort: Option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
145 abort: Option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
145 [255]
146 [255]
146 $ hg -qRa tip
147 $ hg -qRa tip
147 abort: Option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
148 abort: Option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
148 [255]
149 [255]
149
150
150 Testing --cwd:
151 Testing --cwd:
151
152
152 $ hg --cwd a parents
153 $ hg --cwd a parents
153 changeset: 0:8580ff50825a
154 changeset: 0:8580ff50825a
154 tag: tip
155 tag: tip
155 user: test
156 user: test
156 date: Thu Jan 01 00:00:01 1970 +0000
157 date: Thu Jan 01 00:00:01 1970 +0000
157 summary: a
158 summary: a
158
159
159
160
160 Testing -y/--noninteractive - just be sure it is parsed:
161 Testing -y/--noninteractive - just be sure it is parsed:
161
162
162 $ hg --cwd a tip -q --noninteractive
163 $ hg --cwd a tip -q --noninteractive
163 0:8580ff50825a
164 0:8580ff50825a
164 $ hg --cwd a tip -q -y
165 $ hg --cwd a tip -q -y
165 0:8580ff50825a
166 0:8580ff50825a
166
167
167 Testing -q/--quiet:
168 Testing -q/--quiet:
168
169
169 $ hg -R a -q tip
170 $ hg -R a -q tip
170 0:8580ff50825a
171 0:8580ff50825a
171 $ hg -R b -q tip
172 $ hg -R b -q tip
172 0:b6c483daf290
173 0:b6c483daf290
173 $ hg -R c --quiet parents
174 $ hg -R c --quiet parents
174 0:8580ff50825a
175 0:8580ff50825a
175 1:b6c483daf290
176 1:b6c483daf290
176
177
177 Testing -v/--verbose:
178 Testing -v/--verbose:
178
179
179 $ hg --cwd c head -v
180 $ hg --cwd c head -v
180 changeset: 1:b6c483daf290
181 changeset: 1:b6c483daf290
181 tag: tip
182 tag: tip
182 parent: -1:000000000000
183 parent: -1:000000000000
183 user: test
184 user: test
184 date: Thu Jan 01 00:00:01 1970 +0000
185 date: Thu Jan 01 00:00:01 1970 +0000
185 files: b
186 files: b
186 description:
187 description:
187 b
188 b
188
189
189
190
190 changeset: 0:8580ff50825a
191 changeset: 0:8580ff50825a
191 user: test
192 user: test
192 date: Thu Jan 01 00:00:01 1970 +0000
193 date: Thu Jan 01 00:00:01 1970 +0000
193 files: a
194 files: a
194 description:
195 description:
195 a
196 a
196
197
197
198
198 $ hg --cwd b tip --verbose
199 $ hg --cwd b tip --verbose
199 changeset: 0:b6c483daf290
200 changeset: 0:b6c483daf290
200 tag: tip
201 tag: tip
201 user: test
202 user: test
202 date: Thu Jan 01 00:00:01 1970 +0000
203 date: Thu Jan 01 00:00:01 1970 +0000
203 files: b
204 files: b
204 description:
205 description:
205 b
206 b
206
207
207
208
208
209
209 Testing --config:
210 Testing --config:
210
211
211 $ hg --cwd c --config paths.quuxfoo=bar paths | grep quuxfoo > /dev/null && echo quuxfoo
212 $ hg --cwd c --config paths.quuxfoo=bar paths | grep quuxfoo > /dev/null && echo quuxfoo
212 quuxfoo
213 quuxfoo
213 $ hg --cwd c --config '' tip -q
214 $ hg --cwd c --config '' tip -q
214 abort: malformed --config option: '' (use --config section.name=value)
215 abort: malformed --config option: '' (use --config section.name=value)
215 [255]
216 [255]
216 $ hg --cwd c --config a.b tip -q
217 $ hg --cwd c --config a.b tip -q
217 abort: malformed --config option: 'a.b' (use --config section.name=value)
218 abort: malformed --config option: 'a.b' (use --config section.name=value)
218 [255]
219 [255]
219 $ hg --cwd c --config a tip -q
220 $ hg --cwd c --config a tip -q
220 abort: malformed --config option: 'a' (use --config section.name=value)
221 abort: malformed --config option: 'a' (use --config section.name=value)
221 [255]
222 [255]
222 $ hg --cwd c --config a.= tip -q
223 $ hg --cwd c --config a.= tip -q
223 abort: malformed --config option: 'a.=' (use --config section.name=value)
224 abort: malformed --config option: 'a.=' (use --config section.name=value)
224 [255]
225 [255]
225 $ hg --cwd c --config .b= tip -q
226 $ hg --cwd c --config .b= tip -q
226 abort: malformed --config option: '.b=' (use --config section.name=value)
227 abort: malformed --config option: '.b=' (use --config section.name=value)
227 [255]
228 [255]
228
229
229 Testing --debug:
230 Testing --debug:
230
231
231 $ hg --cwd c log --debug
232 $ hg --cwd c log --debug
232 changeset: 1:b6c483daf2907ce5825c0bb50f5716226281cc1a
233 changeset: 1:b6c483daf2907ce5825c0bb50f5716226281cc1a
233 tag: tip
234 tag: tip
234 parent: -1:0000000000000000000000000000000000000000
235 parent: -1:0000000000000000000000000000000000000000
235 parent: -1:0000000000000000000000000000000000000000
236 parent: -1:0000000000000000000000000000000000000000
236 manifest: 1:23226e7a252cacdc2d99e4fbdc3653441056de49
237 manifest: 1:23226e7a252cacdc2d99e4fbdc3653441056de49
237 user: test
238 user: test
238 date: Thu Jan 01 00:00:01 1970 +0000
239 date: Thu Jan 01 00:00:01 1970 +0000
239 files+: b
240 files+: b
240 extra: branch=default
241 extra: branch=default
241 description:
242 description:
242 b
243 b
243
244
244
245
245 changeset: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab
246 changeset: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab
246 parent: -1:0000000000000000000000000000000000000000
247 parent: -1:0000000000000000000000000000000000000000
247 parent: -1:0000000000000000000000000000000000000000
248 parent: -1:0000000000000000000000000000000000000000
248 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
249 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
249 user: test
250 user: test
250 date: Thu Jan 01 00:00:01 1970 +0000
251 date: Thu Jan 01 00:00:01 1970 +0000
251 files+: a
252 files+: a
252 extra: branch=default
253 extra: branch=default
253 description:
254 description:
254 a
255 a
255
256
256
257
257
258
258 Testing --traceback:
259 Testing --traceback:
259
260
260 $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
261 $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
261 Traceback (most recent call last):
262 Traceback (most recent call last):
262
263
263 Testing --time:
264 Testing --time:
264
265
265 $ hg --cwd a --time id
266 $ hg --cwd a --time id
266 8580ff50825a tip
267 8580ff50825a tip
267 Time: real * (glob)
268 Time: real * (glob)
268
269
269 Testing --version:
270 Testing --version:
270
271
271 $ hg --version -q
272 $ hg --version -q
272 Mercurial Distributed SCM * (glob)
273 Mercurial Distributed SCM * (glob)
273
274
274 Testing -h/--help:
275 Testing -h/--help:
275
276
276 $ hg -h
277 $ hg -h
277 Mercurial Distributed SCM
278 Mercurial Distributed SCM
278
279
279 list of commands:
280 list of commands:
280
281
281 add add the specified files on the next commit
282 add add the specified files on the next commit
282 addremove add all new files, delete all missing files
283 addremove add all new files, delete all missing files
283 annotate show changeset information by line for each file
284 annotate show changeset information by line for each file
284 archive create an unversioned archive of a repository revision
285 archive create an unversioned archive of a repository revision
285 backout reverse effect of earlier changeset
286 backout reverse effect of earlier changeset
286 bisect subdivision search of changesets
287 bisect subdivision search of changesets
287 bookmarks track a line of development with movable markers
288 bookmarks track a line of development with movable markers
288 branch set or show the current branch name
289 branch set or show the current branch name
289 branches list repository named branches
290 branches list repository named branches
290 bundle create a changegroup file
291 bundle create a changegroup file
291 cat output the current or given revision of files
292 cat output the current or given revision of files
292 clone make a copy of an existing repository
293 clone make a copy of an existing repository
293 commit commit the specified files or all outstanding changes
294 commit commit the specified files or all outstanding changes
294 copy mark files as copied for the next commit
295 copy mark files as copied for the next commit
295 diff diff repository (or selected files)
296 diff diff repository (or selected files)
296 export dump the header and diffs for one or more changesets
297 export dump the header and diffs for one or more changesets
297 forget forget the specified files on the next commit
298 forget forget the specified files on the next commit
298 grep search for a pattern in specified files and revisions
299 grep search for a pattern in specified files and revisions
299 heads show current repository heads or show branch heads
300 heads show current repository heads or show branch heads
300 help show help for a given topic or a help overview
301 help show help for a given topic or a help overview
301 identify identify the working copy or specified revision
302 identify identify the working copy or specified revision
302 import import an ordered set of patches
303 import import an ordered set of patches
303 incoming show new changesets found in source
304 incoming show new changesets found in source
304 init create a new repository in the given directory
305 init create a new repository in the given directory
305 locate locate files matching specific patterns
306 locate locate files matching specific patterns
306 log show revision history of entire repository or files
307 log show revision history of entire repository or files
307 manifest output the current or given revision of the project manifest
308 manifest output the current or given revision of the project manifest
308 merge merge working directory with another revision
309 merge merge working directory with another revision
309 outgoing show changesets not found in the destination
310 outgoing show changesets not found in the destination
310 parents show the parents of the working directory or revision
311 parents show the parents of the working directory or revision
311 paths show aliases for remote repositories
312 paths show aliases for remote repositories
312 pull pull changes from the specified source
313 pull pull changes from the specified source
313 push push changes to the specified destination
314 push push changes to the specified destination
314 recover roll back an interrupted transaction
315 recover roll back an interrupted transaction
315 remove remove the specified files on the next commit
316 remove remove the specified files on the next commit
316 rename rename files; equivalent of copy + remove
317 rename rename files; equivalent of copy + remove
317 resolve redo merges or set/view the merge status of files
318 resolve redo merges or set/view the merge status of files
318 revert restore individual files or directories to an earlier state
319 revert restore individual files or directories to an earlier state
319 rollback roll back the last transaction (dangerous)
320 rollback roll back the last transaction (dangerous)
320 root print the root (top) of the current working directory
321 root print the root (top) of the current working directory
321 serve start stand-alone webserver
322 serve start stand-alone webserver
322 showconfig show combined config settings from all hgrc files
323 showconfig show combined config settings from all hgrc files
323 status show changed files in the working directory
324 status show changed files in the working directory
324 summary summarize working directory state
325 summary summarize working directory state
325 tag add one or more tags for the current or given revision
326 tag add one or more tags for the current or given revision
326 tags list repository tags
327 tags list repository tags
327 tip show the tip revision
328 tip show the tip revision
328 unbundle apply one or more changegroup files
329 unbundle apply one or more changegroup files
329 update update working directory (or switch revisions)
330 update update working directory (or switch revisions)
330 verify verify the integrity of the repository
331 verify verify the integrity of the repository
331 version output version and copyright information
332 version output version and copyright information
332
333
333 additional help topics:
334 additional help topics:
334
335
335 config Configuration Files
336 config Configuration Files
336 dates Date Formats
337 dates Date Formats
337 patterns File Name Patterns
338 patterns File Name Patterns
338 environment Environment Variables
339 environment Environment Variables
339 revisions Specifying Single Revisions
340 revisions Specifying Single Revisions
340 multirevs Specifying Multiple Revisions
341 multirevs Specifying Multiple Revisions
341 revsets Specifying Revision Sets
342 revsets Specifying Revision Sets
342 diffs Diff Formats
343 diffs Diff Formats
343 merge-tools Merge Tools
344 merge-tools Merge Tools
344 templating Template Usage
345 templating Template Usage
345 urls URL Paths
346 urls URL Paths
346 extensions Using additional features
347 extensions Using additional features
347 subrepos Subrepositories
348 subrepos Subrepositories
348 hgweb Configuring hgweb
349 hgweb Configuring hgweb
349 glossary Glossary
350 glossary Glossary
350
351
351 use "hg -v help" to show builtin aliases and global options
352 use "hg -v help" to show builtin aliases and global options
352
353
353 $ hg --help
354 $ hg --help
354 Mercurial Distributed SCM
355 Mercurial Distributed SCM
355
356
356 list of commands:
357 list of commands:
357
358
358 add add the specified files on the next commit
359 add add the specified files on the next commit
359 addremove add all new files, delete all missing files
360 addremove add all new files, delete all missing files
360 annotate show changeset information by line for each file
361 annotate show changeset information by line for each file
361 archive create an unversioned archive of a repository revision
362 archive create an unversioned archive of a repository revision
362 backout reverse effect of earlier changeset
363 backout reverse effect of earlier changeset
363 bisect subdivision search of changesets
364 bisect subdivision search of changesets
364 bookmarks track a line of development with movable markers
365 bookmarks track a line of development with movable markers
365 branch set or show the current branch name
366 branch set or show the current branch name
366 branches list repository named branches
367 branches list repository named branches
367 bundle create a changegroup file
368 bundle create a changegroup file
368 cat output the current or given revision of files
369 cat output the current or given revision of files
369 clone make a copy of an existing repository
370 clone make a copy of an existing repository
370 commit commit the specified files or all outstanding changes
371 commit commit the specified files or all outstanding changes
371 copy mark files as copied for the next commit
372 copy mark files as copied for the next commit
372 diff diff repository (or selected files)
373 diff diff repository (or selected files)
373 export dump the header and diffs for one or more changesets
374 export dump the header and diffs for one or more changesets
374 forget forget the specified files on the next commit
375 forget forget the specified files on the next commit
375 grep search for a pattern in specified files and revisions
376 grep search for a pattern in specified files and revisions
376 heads show current repository heads or show branch heads
377 heads show current repository heads or show branch heads
377 help show help for a given topic or a help overview
378 help show help for a given topic or a help overview
378 identify identify the working copy or specified revision
379 identify identify the working copy or specified revision
379 import import an ordered set of patches
380 import import an ordered set of patches
380 incoming show new changesets found in source
381 incoming show new changesets found in source
381 init create a new repository in the given directory
382 init create a new repository in the given directory
382 locate locate files matching specific patterns
383 locate locate files matching specific patterns
383 log show revision history of entire repository or files
384 log show revision history of entire repository or files
384 manifest output the current or given revision of the project manifest
385 manifest output the current or given revision of the project manifest
385 merge merge working directory with another revision
386 merge merge working directory with another revision
386 outgoing show changesets not found in the destination
387 outgoing show changesets not found in the destination
387 parents show the parents of the working directory or revision
388 parents show the parents of the working directory or revision
388 paths show aliases for remote repositories
389 paths show aliases for remote repositories
389 pull pull changes from the specified source
390 pull pull changes from the specified source
390 push push changes to the specified destination
391 push push changes to the specified destination
391 recover roll back an interrupted transaction
392 recover roll back an interrupted transaction
392 remove remove the specified files on the next commit
393 remove remove the specified files on the next commit
393 rename rename files; equivalent of copy + remove
394 rename rename files; equivalent of copy + remove
394 resolve redo merges or set/view the merge status of files
395 resolve redo merges or set/view the merge status of files
395 revert restore individual files or directories to an earlier state
396 revert restore individual files or directories to an earlier state
396 rollback roll back the last transaction (dangerous)
397 rollback roll back the last transaction (dangerous)
397 root print the root (top) of the current working directory
398 root print the root (top) of the current working directory
398 serve start stand-alone webserver
399 serve start stand-alone webserver
399 showconfig show combined config settings from all hgrc files
400 showconfig show combined config settings from all hgrc files
400 status show changed files in the working directory
401 status show changed files in the working directory
401 summary summarize working directory state
402 summary summarize working directory state
402 tag add one or more tags for the current or given revision
403 tag add one or more tags for the current or given revision
403 tags list repository tags
404 tags list repository tags
404 tip show the tip revision
405 tip show the tip revision
405 unbundle apply one or more changegroup files
406 unbundle apply one or more changegroup files
406 update update working directory (or switch revisions)
407 update update working directory (or switch revisions)
407 verify verify the integrity of the repository
408 verify verify the integrity of the repository
408 version output version and copyright information
409 version output version and copyright information
409
410
410 additional help topics:
411 additional help topics:
411
412
412 config Configuration Files
413 config Configuration Files
413 dates Date Formats
414 dates Date Formats
414 patterns File Name Patterns
415 patterns File Name Patterns
415 environment Environment Variables
416 environment Environment Variables
416 revisions Specifying Single Revisions
417 revisions Specifying Single Revisions
417 multirevs Specifying Multiple Revisions
418 multirevs Specifying Multiple Revisions
418 revsets Specifying Revision Sets
419 revsets Specifying Revision Sets
419 diffs Diff Formats
420 diffs Diff Formats
420 merge-tools Merge Tools
421 merge-tools Merge Tools
421 templating Template Usage
422 templating Template Usage
422 urls URL Paths
423 urls URL Paths
423 extensions Using additional features
424 extensions Using additional features
424 subrepos Subrepositories
425 subrepos Subrepositories
425 hgweb Configuring hgweb
426 hgweb Configuring hgweb
426 glossary Glossary
427 glossary Glossary
427
428
428 use "hg -v help" to show builtin aliases and global options
429 use "hg -v help" to show builtin aliases and global options
429
430
430 Not tested: --debugger
431 Not tested: --debugger
431
432
@@ -1,264 +1,264 b''
1
1
2 $ hg init remote
2 $ hg init remote
3 $ cd remote
3 $ cd remote
4
4
5 creating 'remote
5 creating 'remote
6
6
7 $ cat >>afile <<EOF
7 $ cat >>afile <<EOF
8 > 0
8 > 0
9 > EOF
9 > EOF
10 $ hg add afile
10 $ hg add afile
11 $ hg commit -m "0.0"
11 $ hg commit -m "0.0"
12 $ cat >>afile <<EOF
12 $ cat >>afile <<EOF
13 > 1
13 > 1
14 > EOF
14 > EOF
15 $ hg commit -m "0.1"
15 $ hg commit -m "0.1"
16 $ cat >>afile <<EOF
16 $ cat >>afile <<EOF
17 > 2
17 > 2
18 > EOF
18 > EOF
19 $ hg commit -m "0.2"
19 $ hg commit -m "0.2"
20 $ cat >>afile <<EOF
20 $ cat >>afile <<EOF
21 > 3
21 > 3
22 > EOF
22 > EOF
23 $ hg commit -m "0.3"
23 $ hg commit -m "0.3"
24 $ hg update -C 0
24 $ hg update -C 0
25 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
26 $ cat >>afile <<EOF
26 $ cat >>afile <<EOF
27 > 1
27 > 1
28 > EOF
28 > EOF
29 $ hg commit -m "1.1"
29 $ hg commit -m "1.1"
30 created new head
30 created new head
31 $ cat >>afile <<EOF
31 $ cat >>afile <<EOF
32 > 2
32 > 2
33 > EOF
33 > EOF
34 $ hg commit -m "1.2"
34 $ hg commit -m "1.2"
35 $ cat >fred <<EOF
35 $ cat >fred <<EOF
36 > a line
36 > a line
37 > EOF
37 > EOF
38 $ cat >>afile <<EOF
38 $ cat >>afile <<EOF
39 > 3
39 > 3
40 > EOF
40 > EOF
41 $ hg add fred
41 $ hg add fred
42 $ hg commit -m "1.3"
42 $ hg commit -m "1.3"
43 $ hg mv afile adifferentfile
43 $ hg mv afile adifferentfile
44 $ hg commit -m "1.3m"
44 $ hg commit -m "1.3m"
45 $ hg update -C 3
45 $ hg update -C 3
46 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
46 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
47 $ hg mv afile anotherfile
47 $ hg mv afile anotherfile
48 $ hg commit -m "0.3m"
48 $ hg commit -m "0.3m"
49 $ hg debugindex .hg/store/data/afile.i
49 $ hg debugindex .hg/store/data/afile.i
50 rev offset length base linkrev nodeid p1 p2
50 rev offset length base linkrev nodeid p1 p2
51 0 0 3 0 0 362fef284ce2 000000000000 000000000000
51 0 0 3 0 0 362fef284ce2 000000000000 000000000000
52 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000
52 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000
53 2 8 7 2 2 4c982badb186 125144f7e028 000000000000
53 2 8 7 2 2 4c982badb186 125144f7e028 000000000000
54 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000
54 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000
55 $ hg debugindex .hg/store/data/adifferentfile.i
55 $ hg debugindex .hg/store/data/adifferentfile.i
56 rev offset length base linkrev nodeid p1 p2
56 rev offset length base linkrev nodeid p1 p2
57 0 0 75 0 7 2565f3199a74 000000000000 000000000000
57 0 0 75 0 7 2565f3199a74 000000000000 000000000000
58 $ hg debugindex .hg/store/data/anotherfile.i
58 $ hg debugindex .hg/store/data/anotherfile.i
59 rev offset length base linkrev nodeid p1 p2
59 rev offset length base linkrev nodeid p1 p2
60 0 0 75 0 8 2565f3199a74 000000000000 000000000000
60 0 0 75 0 8 2565f3199a74 000000000000 000000000000
61 $ hg debugindex .hg/store/data/fred.i
61 $ hg debugindex .hg/store/data/fred.i
62 rev offset length base linkrev nodeid p1 p2
62 rev offset length base linkrev nodeid p1 p2
63 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000
63 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000
64 $ hg debugindex .hg/store/00manifest.i
64 $ hg debugindex .hg/store/00manifest.i
65 rev offset length base linkrev nodeid p1 p2
65 rev offset length base linkrev nodeid p1 p2
66 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000
66 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000
67 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000
67 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000
68 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000
68 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000
69 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000
69 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000
70 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000
70 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000
71 5 250 68 3 7 09bb521d218d de68e904d169 000000000000
71 5 250 68 3 7 09bb521d218d de68e904d169 000000000000
72 6 318 54 6 8 1fde233dfb0f f54c32f13478 000000000000
72 6 318 54 6 8 1fde233dfb0f f54c32f13478 000000000000
73 $ hg verify
73 $ hg verify
74 checking changesets
74 checking changesets
75 checking manifests
75 checking manifests
76 crosschecking files in changesets and manifests
76 crosschecking files in changesets and manifests
77 checking files
77 checking files
78 4 files, 9 changesets, 7 total revisions
78 4 files, 9 changesets, 7 total revisions
79
79
80 Starting server
80 Starting server
81
81
82 $ hg serve -p $HGPORT -E ../error.log -d --pid-file=../hg1.pid
82 $ hg serve -p $HGPORT -E ../error.log -d --pid-file=../hg1.pid
83 $ cd ..
83 $ cd ..
84 $ cat hg1.pid >> $DAEMON_PIDS
84 $ cat hg1.pid >> $DAEMON_PIDS
85
85
86 clone remote via stream
86 clone remote via stream
87
87
88 $ for i in 0 1 2 3 4 5 6 7 8; do
88 $ for i in 0 1 2 3 4 5 6 7 8; do
89 > hg clone -r "$i" http://localhost:$HGPORT/ test-"$i"
89 > hg clone -r "$i" http://localhost:$HGPORT/ test-"$i"
90 > if cd test-"$i"; then
90 > if cd test-"$i"; then
91 > hg verify
91 > hg verify
92 > cd ..
92 > cd ..
93 > fi
93 > fi
94 > done
94 > done
95 adding changesets
95 adding changesets
96 adding manifests
96 adding manifests
97 adding file changes
97 adding file changes
98 added 1 changesets with 1 changes to 1 files
98 added 1 changesets with 1 changes to 1 files
99 updating to branch default
99 updating to branch default
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 checking changesets
101 checking changesets
102 checking manifests
102 checking manifests
103 crosschecking files in changesets and manifests
103 crosschecking files in changesets and manifests
104 checking files
104 checking files
105 1 files, 1 changesets, 1 total revisions
105 1 files, 1 changesets, 1 total revisions
106 adding changesets
106 adding changesets
107 adding manifests
107 adding manifests
108 adding file changes
108 adding file changes
109 added 2 changesets with 2 changes to 1 files
109 added 2 changesets with 2 changes to 1 files
110 updating to branch default
110 updating to branch default
111 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
112 checking changesets
112 checking changesets
113 checking manifests
113 checking manifests
114 crosschecking files in changesets and manifests
114 crosschecking files in changesets and manifests
115 checking files
115 checking files
116 1 files, 2 changesets, 2 total revisions
116 1 files, 2 changesets, 2 total revisions
117 adding changesets
117 adding changesets
118 adding manifests
118 adding manifests
119 adding file changes
119 adding file changes
120 added 3 changesets with 3 changes to 1 files
120 added 3 changesets with 3 changes to 1 files
121 updating to branch default
121 updating to branch default
122 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
123 checking changesets
123 checking changesets
124 checking manifests
124 checking manifests
125 crosschecking files in changesets and manifests
125 crosschecking files in changesets and manifests
126 checking files
126 checking files
127 1 files, 3 changesets, 3 total revisions
127 1 files, 3 changesets, 3 total revisions
128 adding changesets
128 adding changesets
129 adding manifests
129 adding manifests
130 adding file changes
130 adding file changes
131 added 4 changesets with 4 changes to 1 files
131 added 4 changesets with 4 changes to 1 files
132 updating to branch default
132 updating to branch default
133 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
134 checking changesets
134 checking changesets
135 checking manifests
135 checking manifests
136 crosschecking files in changesets and manifests
136 crosschecking files in changesets and manifests
137 checking files
137 checking files
138 1 files, 4 changesets, 4 total revisions
138 1 files, 4 changesets, 4 total revisions
139 adding changesets
139 adding changesets
140 adding manifests
140 adding manifests
141 adding file changes
141 adding file changes
142 added 2 changesets with 2 changes to 1 files
142 added 2 changesets with 2 changes to 1 files
143 updating to branch default
143 updating to branch default
144 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
144 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
145 checking changesets
145 checking changesets
146 checking manifests
146 checking manifests
147 crosschecking files in changesets and manifests
147 crosschecking files in changesets and manifests
148 checking files
148 checking files
149 1 files, 2 changesets, 2 total revisions
149 1 files, 2 changesets, 2 total revisions
150 adding changesets
150 adding changesets
151 adding manifests
151 adding manifests
152 adding file changes
152 adding file changes
153 added 3 changesets with 3 changes to 1 files
153 added 3 changesets with 3 changes to 1 files
154 updating to branch default
154 updating to branch default
155 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 checking changesets
156 checking changesets
157 checking manifests
157 checking manifests
158 crosschecking files in changesets and manifests
158 crosschecking files in changesets and manifests
159 checking files
159 checking files
160 1 files, 3 changesets, 3 total revisions
160 1 files, 3 changesets, 3 total revisions
161 adding changesets
161 adding changesets
162 adding manifests
162 adding manifests
163 adding file changes
163 adding file changes
164 added 4 changesets with 5 changes to 2 files
164 added 4 changesets with 5 changes to 2 files
165 updating to branch default
165 updating to branch default
166 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
166 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
167 checking changesets
167 checking changesets
168 checking manifests
168 checking manifests
169 crosschecking files in changesets and manifests
169 crosschecking files in changesets and manifests
170 checking files
170 checking files
171 2 files, 4 changesets, 5 total revisions
171 2 files, 4 changesets, 5 total revisions
172 adding changesets
172 adding changesets
173 adding manifests
173 adding manifests
174 adding file changes
174 adding file changes
175 added 5 changesets with 6 changes to 3 files
175 added 5 changesets with 6 changes to 3 files
176 updating to branch default
176 updating to branch default
177 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
177 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
178 checking changesets
178 checking changesets
179 checking manifests
179 checking manifests
180 crosschecking files in changesets and manifests
180 crosschecking files in changesets and manifests
181 checking files
181 checking files
182 3 files, 5 changesets, 6 total revisions
182 3 files, 5 changesets, 6 total revisions
183 adding changesets
183 adding changesets
184 adding manifests
184 adding manifests
185 adding file changes
185 adding file changes
186 added 5 changesets with 5 changes to 2 files
186 added 5 changesets with 5 changes to 2 files
187 updating to branch default
187 updating to branch default
188 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
188 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 checking changesets
189 checking changesets
190 checking manifests
190 checking manifests
191 crosschecking files in changesets and manifests
191 crosschecking files in changesets and manifests
192 checking files
192 checking files
193 2 files, 5 changesets, 5 total revisions
193 2 files, 5 changesets, 5 total revisions
194 $ cd test-8
194 $ cd test-8
195 $ hg pull ../test-7
195 $ hg pull ../test-7
196 pulling from ../test-7
196 pulling from ../test-7
197 searching for changes
197 searching for changes
198 adding changesets
198 adding changesets
199 adding manifests
199 adding manifests
200 adding file changes
200 adding file changes
201 added 4 changesets with 2 changes to 3 files (+1 heads)
201 added 4 changesets with 2 changes to 3 files (+1 heads)
202 (run 'hg heads' to see heads, 'hg merge' to merge)
202 (run 'hg heads' to see heads, 'hg merge' to merge)
203 $ hg verify
203 $ hg verify
204 checking changesets
204 checking changesets
205 checking manifests
205 checking manifests
206 crosschecking files in changesets and manifests
206 crosschecking files in changesets and manifests
207 checking files
207 checking files
208 4 files, 9 changesets, 7 total revisions
208 4 files, 9 changesets, 7 total revisions
209 $ cd ..
209 $ cd ..
210 $ cd test-1
210 $ cd test-1
211 $ hg pull -r 4 http://localhost:$HGPORT/
211 $ hg pull -r 4 http://localhost:$HGPORT/
212 pulling from http://localhost:$HGPORT/
212 pulling from http://localhost:$HGPORT/
213 searching for changes
213 searching for changes
214 adding changesets
214 adding changesets
215 adding manifests
215 adding manifests
216 adding file changes
216 adding file changes
217 added 1 changesets with 0 changes to 1 files (+1 heads)
217 added 1 changesets with 0 changes to 0 files (+1 heads)
218 (run 'hg heads' to see heads, 'hg merge' to merge)
218 (run 'hg heads' to see heads, 'hg merge' to merge)
219 $ hg verify
219 $ hg verify
220 checking changesets
220 checking changesets
221 checking manifests
221 checking manifests
222 crosschecking files in changesets and manifests
222 crosschecking files in changesets and manifests
223 checking files
223 checking files
224 1 files, 3 changesets, 2 total revisions
224 1 files, 3 changesets, 2 total revisions
225 $ hg pull http://localhost:$HGPORT/
225 $ hg pull http://localhost:$HGPORT/
226 pulling from http://localhost:$HGPORT/
226 pulling from http://localhost:$HGPORT/
227 searching for changes
227 searching for changes
228 adding changesets
228 adding changesets
229 adding manifests
229 adding manifests
230 adding file changes
230 adding file changes
231 added 6 changesets with 5 changes to 4 files
231 added 6 changesets with 5 changes to 4 files
232 (run 'hg update' to get a working copy)
232 (run 'hg update' to get a working copy)
233 $ cd ..
233 $ cd ..
234 $ cd test-2
234 $ cd test-2
235 $ hg pull -r 5 http://localhost:$HGPORT/
235 $ hg pull -r 5 http://localhost:$HGPORT/
236 pulling from http://localhost:$HGPORT/
236 pulling from http://localhost:$HGPORT/
237 searching for changes
237 searching for changes
238 adding changesets
238 adding changesets
239 adding manifests
239 adding manifests
240 adding file changes
240 adding file changes
241 added 2 changesets with 0 changes to 1 files (+1 heads)
241 added 2 changesets with 0 changes to 0 files (+1 heads)
242 (run 'hg heads' to see heads, 'hg merge' to merge)
242 (run 'hg heads' to see heads, 'hg merge' to merge)
243 $ hg verify
243 $ hg verify
244 checking changesets
244 checking changesets
245 checking manifests
245 checking manifests
246 crosschecking files in changesets and manifests
246 crosschecking files in changesets and manifests
247 checking files
247 checking files
248 1 files, 5 changesets, 3 total revisions
248 1 files, 5 changesets, 3 total revisions
249 $ hg pull http://localhost:$HGPORT/
249 $ hg pull http://localhost:$HGPORT/
250 pulling from http://localhost:$HGPORT/
250 pulling from http://localhost:$HGPORT/
251 searching for changes
251 searching for changes
252 adding changesets
252 adding changesets
253 adding manifests
253 adding manifests
254 adding file changes
254 adding file changes
255 added 4 changesets with 4 changes to 4 files
255 added 4 changesets with 4 changes to 4 files
256 (run 'hg update' to get a working copy)
256 (run 'hg update' to get a working copy)
257 $ hg verify
257 $ hg verify
258 checking changesets
258 checking changesets
259 checking manifests
259 checking manifests
260 crosschecking files in changesets and manifests
260 crosschecking files in changesets and manifests
261 checking files
261 checking files
262 4 files, 9 changesets, 7 total revisions
262 4 files, 9 changesets, 7 total revisions
263 $ cd ..
263 $ cd ..
264 $ cat error.log
264 $ cat error.log
@@ -1,120 +1,120 b''
1
1
2 $ hg init a
2 $ hg init a
3 $ cd a
3 $ cd a
4 $ echo a > a
4 $ echo a > a
5 $ hg ci -Ama -d '1123456789 0'
5 $ hg ci -Ama -d '1123456789 0'
6 adding a
6 adding a
7 $ hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=hg.pid
7 $ hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=hg.pid
8 $ cat hg.pid >> $DAEMON_PIDS
8 $ cat hg.pid >> $DAEMON_PIDS
9 $ cd ..
9 $ cd ..
10 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
10 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
11 $ echo $! > proxy.pid)
11 $ echo $! > proxy.pid)
12 $ cat proxy.pid >> $DAEMON_PIDS
12 $ cat proxy.pid >> $DAEMON_PIDS
13 $ sleep 2
13 $ sleep 2
14
14
15 url for proxy, stream
15 url for proxy, stream
16
16
17 $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b
17 $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b
18 streaming all changes
18 streaming all changes
19 3 files to transfer, 303 bytes of data
19 3 files to transfer, 303 bytes of data
20 transferred * bytes in * seconds (*B/sec) (glob)
20 transferred * bytes in * seconds (*B/sec) (glob)
21 updating to branch default
21 updating to branch default
22 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 $ cd b
23 $ cd b
24 $ hg verify
24 $ hg verify
25 checking changesets
25 checking changesets
26 checking manifests
26 checking manifests
27 crosschecking files in changesets and manifests
27 crosschecking files in changesets and manifests
28 checking files
28 checking files
29 1 files, 1 changesets, 1 total revisions
29 1 files, 1 changesets, 1 total revisions
30 $ cd ..
30 $ cd ..
31
31
32 url for proxy, pull
32 url for proxy, pull
33
33
34 $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone http://localhost:$HGPORT/ b-pull
34 $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone http://localhost:$HGPORT/ b-pull
35 requesting all changes
35 requesting all changes
36 adding changesets
36 adding changesets
37 adding manifests
37 adding manifests
38 adding file changes
38 adding file changes
39 added 1 changesets with 1 changes to 1 files
39 added 1 changesets with 1 changes to 1 files
40 updating to branch default
40 updating to branch default
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 $ cd b-pull
42 $ cd b-pull
43 $ hg verify
43 $ hg verify
44 checking changesets
44 checking changesets
45 checking manifests
45 checking manifests
46 crosschecking files in changesets and manifests
46 crosschecking files in changesets and manifests
47 checking files
47 checking files
48 1 files, 1 changesets, 1 total revisions
48 1 files, 1 changesets, 1 total revisions
49 $ cd ..
49 $ cd ..
50
50
51 host:port for proxy
51 host:port for proxy
52
52
53 $ http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ c
53 $ http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ c
54 requesting all changes
54 requesting all changes
55 adding changesets
55 adding changesets
56 adding manifests
56 adding manifests
57 adding file changes
57 adding file changes
58 added 1 changesets with 1 changes to 1 files
58 added 1 changesets with 1 changes to 1 files
59 updating to branch default
59 updating to branch default
60 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
61
61
62 proxy url with user name and password
62 proxy url with user name and password
63
63
64 $ http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ d
64 $ http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ d
65 requesting all changes
65 requesting all changes
66 adding changesets
66 adding changesets
67 adding manifests
67 adding manifests
68 adding file changes
68 adding file changes
69 added 1 changesets with 1 changes to 1 files
69 added 1 changesets with 1 changes to 1 files
70 updating to branch default
70 updating to branch default
71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
72
72
73 url with user name and password
73 url with user name and password
74
74
75 $ http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://user:passwd@localhost:$HGPORT/ e
75 $ http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://user:passwd@localhost:$HGPORT/ e
76 requesting all changes
76 requesting all changes
77 adding changesets
77 adding changesets
78 adding manifests
78 adding manifests
79 adding file changes
79 adding file changes
80 added 1 changesets with 1 changes to 1 files
80 added 1 changesets with 1 changes to 1 files
81 updating to branch default
81 updating to branch default
82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83
83
84 bad host:port for proxy
84 bad host:port for proxy
85
85
86 $ http_proxy=localhost:$HGPORT2 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ f
86 $ http_proxy=localhost:$HGPORT2 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ f
87 abort: error: Connection refused
87 abort: error: Connection refused
88 [255]
88 [255]
89
89
90 do not use the proxy if it is in the no list
90 do not use the proxy if it is in the no list
91
91
92 $ http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.no=localhost http://localhost:$HGPORT/ g
92 $ http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.no=localhost http://localhost:$HGPORT/ g
93 requesting all changes
93 requesting all changes
94 adding changesets
94 adding changesets
95 adding manifests
95 adding manifests
96 adding file changes
96 adding file changes
97 added 1 changesets with 1 changes to 1 files
97 added 1 changesets with 1 changes to 1 files
98 updating to branch default
98 updating to branch default
99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 $ cat proxy.log
100 $ cat proxy.log
101 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
101 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
102 * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob)
102 * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob)
103 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
103 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
104 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
104 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
105 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
105 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
106 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
106 * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 HTTP/1.1" - - (glob)
107 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
107 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
108 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
108 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
109 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
109 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
110 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
110 * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 HTTP/1.1" - - (glob)
111 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
111 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
112 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
112 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
113 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
113 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
114 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
114 * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 HTTP/1.1" - - (glob)
115 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
115 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
116 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
116 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
117 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
117 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
118 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
118 * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 HTTP/1.1" - - (glob)
119 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
119 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
120
120
@@ -1,45 +1,46 b''
1
1
2 $ cat <<EOF >> $HGRCPATH
2 $ cat <<EOF >> $HGRCPATH
3 > [extensions]
3 > [extensions]
4 > schemes=
4 > schemes=
5 >
5 >
6 > [schemes]
6 > [schemes]
7 > l = http://localhost:$HGPORT/
7 > l = http://localhost:$HGPORT/
8 > parts = http://{1}:$HGPORT/
8 > parts = http://{1}:$HGPORT/
9 > z = file:\$PWD/
9 > z = file:\$PWD/
10 > EOF
10 > EOF
11 $ hg init test
11 $ hg init test
12 $ cd test
12 $ cd test
13 $ echo a > a
13 $ echo a > a
14 $ hg ci -Am initial
14 $ hg ci -Am initial
15 adding a
15 adding a
16 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
16 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
17 $ cat hg.pid >> $DAEMON_PIDS
17 $ cat hg.pid >> $DAEMON_PIDS
18 $ hg incoming l://
18 $ hg incoming l://
19 comparing with l://
19 comparing with l://
20 searching for changes
20 searching for changes
21 no changes found
21 no changes found
22 [1]
22 [1]
23
23
24 check that {1} syntax works
24 check that {1} syntax works
25
25
26 $ hg incoming --debug parts://localhost
26 $ hg incoming --debug parts://localhost
27 using http://localhost:$HGPORT/
27 using http://localhost:$HGPORT/
28 sending capabilities command
28 sending capabilities command
29 comparing with parts://localhost
29 comparing with parts://localhost
30 sending heads command
30 sending heads command
31 searching for changes
31 searching for changes
32 sending known command
32 no changes found
33 no changes found
33 [1]
34 [1]
34
35
35 check that paths are expanded
36 check that paths are expanded
36
37
37 $ PWD=`pwd` hg incoming z://
38 $ PWD=`pwd` hg incoming z://
38 comparing with z://
39 comparing with z://
39 searching for changes
40 searching for changes
40 no changes found
41 no changes found
41 [1]
42 [1]
42
43
43 errors
44 errors
44
45
45 $ cat errors.log
46 $ cat errors.log
@@ -1,280 +1,280 b''
1 This test tries to exercise the ssh functionality with a dummy script
1 This test tries to exercise the ssh functionality with a dummy script
2
2
3 $ cat <<EOF > dummyssh
3 $ cat <<EOF > dummyssh
4 > import sys
4 > import sys
5 > import os
5 > import os
6 >
6 >
7 > os.chdir(os.path.dirname(sys.argv[0]))
7 > os.chdir(os.path.dirname(sys.argv[0]))
8 > if sys.argv[1] != "user@dummy":
8 > if sys.argv[1] != "user@dummy":
9 > sys.exit(-1)
9 > sys.exit(-1)
10 >
10 >
11 > if not os.path.exists("dummyssh"):
11 > if not os.path.exists("dummyssh"):
12 > sys.exit(-1)
12 > sys.exit(-1)
13 >
13 >
14 > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
14 > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
15 >
15 >
16 > log = open("dummylog", "ab")
16 > log = open("dummylog", "ab")
17 > log.write("Got arguments")
17 > log.write("Got arguments")
18 > for i, arg in enumerate(sys.argv[1:]):
18 > for i, arg in enumerate(sys.argv[1:]):
19 > log.write(" %d:%s" % (i+1, arg))
19 > log.write(" %d:%s" % (i+1, arg))
20 > log.write("\n")
20 > log.write("\n")
21 > log.close()
21 > log.close()
22 > r = os.system(sys.argv[2])
22 > r = os.system(sys.argv[2])
23 > sys.exit(bool(r))
23 > sys.exit(bool(r))
24 > EOF
24 > EOF
25 $ hg init remote
25 $ hg init remote
26 $ cd remote
26 $ cd remote
27
27
28 creating 'remote
28 creating 'remote
29
29
30 $ cat >>afile <<EOF
30 $ cat >>afile <<EOF
31 > 0
31 > 0
32 > EOF
32 > EOF
33 $ hg add afile
33 $ hg add afile
34 $ hg commit -m "0.0"
34 $ hg commit -m "0.0"
35 $ cat >>afile <<EOF
35 $ cat >>afile <<EOF
36 > 1
36 > 1
37 > EOF
37 > EOF
38 $ hg commit -m "0.1"
38 $ hg commit -m "0.1"
39 $ cat >>afile <<EOF
39 $ cat >>afile <<EOF
40 > 2
40 > 2
41 > EOF
41 > EOF
42 $ hg commit -m "0.2"
42 $ hg commit -m "0.2"
43 $ cat >>afile <<EOF
43 $ cat >>afile <<EOF
44 > 3
44 > 3
45 > EOF
45 > EOF
46 $ hg commit -m "0.3"
46 $ hg commit -m "0.3"
47 $ hg update -C 0
47 $ hg update -C 0
48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 $ cat >>afile <<EOF
49 $ cat >>afile <<EOF
50 > 1
50 > 1
51 > EOF
51 > EOF
52 $ hg commit -m "1.1"
52 $ hg commit -m "1.1"
53 created new head
53 created new head
54 $ cat >>afile <<EOF
54 $ cat >>afile <<EOF
55 > 2
55 > 2
56 > EOF
56 > EOF
57 $ hg commit -m "1.2"
57 $ hg commit -m "1.2"
58 $ cat >fred <<EOF
58 $ cat >fred <<EOF
59 > a line
59 > a line
60 > EOF
60 > EOF
61 $ cat >>afile <<EOF
61 $ cat >>afile <<EOF
62 > 3
62 > 3
63 > EOF
63 > EOF
64 $ hg add fred
64 $ hg add fred
65 $ hg commit -m "1.3"
65 $ hg commit -m "1.3"
66 $ hg mv afile adifferentfile
66 $ hg mv afile adifferentfile
67 $ hg commit -m "1.3m"
67 $ hg commit -m "1.3m"
68 $ hg update -C 3
68 $ hg update -C 3
69 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
69 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
70 $ hg mv afile anotherfile
70 $ hg mv afile anotherfile
71 $ hg commit -m "0.3m"
71 $ hg commit -m "0.3m"
72 $ hg debugindex .hg/store/data/afile.i
72 $ hg debugindex .hg/store/data/afile.i
73 rev offset length base linkrev nodeid p1 p2
73 rev offset length base linkrev nodeid p1 p2
74 0 0 3 0 0 362fef284ce2 000000000000 000000000000
74 0 0 3 0 0 362fef284ce2 000000000000 000000000000
75 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000
75 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000
76 2 8 7 2 2 4c982badb186 125144f7e028 000000000000
76 2 8 7 2 2 4c982badb186 125144f7e028 000000000000
77 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000
77 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000
78 $ hg debugindex .hg/store/data/adifferentfile.i
78 $ hg debugindex .hg/store/data/adifferentfile.i
79 rev offset length base linkrev nodeid p1 p2
79 rev offset length base linkrev nodeid p1 p2
80 0 0 75 0 7 2565f3199a74 000000000000 000000000000
80 0 0 75 0 7 2565f3199a74 000000000000 000000000000
81 $ hg debugindex .hg/store/data/anotherfile.i
81 $ hg debugindex .hg/store/data/anotherfile.i
82 rev offset length base linkrev nodeid p1 p2
82 rev offset length base linkrev nodeid p1 p2
83 0 0 75 0 8 2565f3199a74 000000000000 000000000000
83 0 0 75 0 8 2565f3199a74 000000000000 000000000000
84 $ hg debugindex .hg/store/data/fred.i
84 $ hg debugindex .hg/store/data/fred.i
85 rev offset length base linkrev nodeid p1 p2
85 rev offset length base linkrev nodeid p1 p2
86 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000
86 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000
87 $ hg debugindex .hg/store/00manifest.i
87 $ hg debugindex .hg/store/00manifest.i
88 rev offset length base linkrev nodeid p1 p2
88 rev offset length base linkrev nodeid p1 p2
89 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000
89 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000
90 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000
90 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000
91 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000
91 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000
92 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000
92 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000
93 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000
93 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000
94 5 250 68 3 7 09bb521d218d de68e904d169 000000000000
94 5 250 68 3 7 09bb521d218d de68e904d169 000000000000
95 6 318 54 6 8 1fde233dfb0f f54c32f13478 000000000000
95 6 318 54 6 8 1fde233dfb0f f54c32f13478 000000000000
96 $ hg verify
96 $ hg verify
97 checking changesets
97 checking changesets
98 checking manifests
98 checking manifests
99 crosschecking files in changesets and manifests
99 crosschecking files in changesets and manifests
100 checking files
100 checking files
101 4 files, 9 changesets, 7 total revisions
101 4 files, 9 changesets, 7 total revisions
102 $ cd ..
102 $ cd ..
103
103
104 clone remote via stream
104 clone remote via stream
105
105
106 $ for i in 0 1 2 3 4 5 6 7 8; do
106 $ for i in 0 1 2 3 4 5 6 7 8; do
107 > hg clone -e "python ./dummyssh" --uncompressed -r "$i" ssh://user@dummy/remote test-"$i"
107 > hg clone -e "python ./dummyssh" --uncompressed -r "$i" ssh://user@dummy/remote test-"$i"
108 > if cd test-"$i"; then
108 > if cd test-"$i"; then
109 > hg verify
109 > hg verify
110 > cd ..
110 > cd ..
111 > fi
111 > fi
112 > done
112 > done
113 adding changesets
113 adding changesets
114 adding manifests
114 adding manifests
115 adding file changes
115 adding file changes
116 added 1 changesets with 1 changes to 1 files
116 added 1 changesets with 1 changes to 1 files
117 updating to branch default
117 updating to branch default
118 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
119 checking changesets
119 checking changesets
120 checking manifests
120 checking manifests
121 crosschecking files in changesets and manifests
121 crosschecking files in changesets and manifests
122 checking files
122 checking files
123 1 files, 1 changesets, 1 total revisions
123 1 files, 1 changesets, 1 total revisions
124 adding changesets
124 adding changesets
125 adding manifests
125 adding manifests
126 adding file changes
126 adding file changes
127 added 2 changesets with 2 changes to 1 files
127 added 2 changesets with 2 changes to 1 files
128 updating to branch default
128 updating to branch default
129 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
130 checking changesets
130 checking changesets
131 checking manifests
131 checking manifests
132 crosschecking files in changesets and manifests
132 crosschecking files in changesets and manifests
133 checking files
133 checking files
134 1 files, 2 changesets, 2 total revisions
134 1 files, 2 changesets, 2 total revisions
135 adding changesets
135 adding changesets
136 adding manifests
136 adding manifests
137 adding file changes
137 adding file changes
138 added 3 changesets with 3 changes to 1 files
138 added 3 changesets with 3 changes to 1 files
139 updating to branch default
139 updating to branch default
140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 checking changesets
141 checking changesets
142 checking manifests
142 checking manifests
143 crosschecking files in changesets and manifests
143 crosschecking files in changesets and manifests
144 checking files
144 checking files
145 1 files, 3 changesets, 3 total revisions
145 1 files, 3 changesets, 3 total revisions
146 adding changesets
146 adding changesets
147 adding manifests
147 adding manifests
148 adding file changes
148 adding file changes
149 added 4 changesets with 4 changes to 1 files
149 added 4 changesets with 4 changes to 1 files
150 updating to branch default
150 updating to branch default
151 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 checking changesets
152 checking changesets
153 checking manifests
153 checking manifests
154 crosschecking files in changesets and manifests
154 crosschecking files in changesets and manifests
155 checking files
155 checking files
156 1 files, 4 changesets, 4 total revisions
156 1 files, 4 changesets, 4 total revisions
157 adding changesets
157 adding changesets
158 adding manifests
158 adding manifests
159 adding file changes
159 adding file changes
160 added 2 changesets with 2 changes to 1 files
160 added 2 changesets with 2 changes to 1 files
161 updating to branch default
161 updating to branch default
162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 checking changesets
163 checking changesets
164 checking manifests
164 checking manifests
165 crosschecking files in changesets and manifests
165 crosschecking files in changesets and manifests
166 checking files
166 checking files
167 1 files, 2 changesets, 2 total revisions
167 1 files, 2 changesets, 2 total revisions
168 adding changesets
168 adding changesets
169 adding manifests
169 adding manifests
170 adding file changes
170 adding file changes
171 added 3 changesets with 3 changes to 1 files
171 added 3 changesets with 3 changes to 1 files
172 updating to branch default
172 updating to branch default
173 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 checking changesets
174 checking changesets
175 checking manifests
175 checking manifests
176 crosschecking files in changesets and manifests
176 crosschecking files in changesets and manifests
177 checking files
177 checking files
178 1 files, 3 changesets, 3 total revisions
178 1 files, 3 changesets, 3 total revisions
179 adding changesets
179 adding changesets
180 adding manifests
180 adding manifests
181 adding file changes
181 adding file changes
182 added 4 changesets with 5 changes to 2 files
182 added 4 changesets with 5 changes to 2 files
183 updating to branch default
183 updating to branch default
184 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
184 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
185 checking changesets
185 checking changesets
186 checking manifests
186 checking manifests
187 crosschecking files in changesets and manifests
187 crosschecking files in changesets and manifests
188 checking files
188 checking files
189 2 files, 4 changesets, 5 total revisions
189 2 files, 4 changesets, 5 total revisions
190 adding changesets
190 adding changesets
191 adding manifests
191 adding manifests
192 adding file changes
192 adding file changes
193 added 5 changesets with 6 changes to 3 files
193 added 5 changesets with 6 changes to 3 files
194 updating to branch default
194 updating to branch default
195 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
196 checking changesets
196 checking changesets
197 checking manifests
197 checking manifests
198 crosschecking files in changesets and manifests
198 crosschecking files in changesets and manifests
199 checking files
199 checking files
200 3 files, 5 changesets, 6 total revisions
200 3 files, 5 changesets, 6 total revisions
201 adding changesets
201 adding changesets
202 adding manifests
202 adding manifests
203 adding file changes
203 adding file changes
204 added 5 changesets with 5 changes to 2 files
204 added 5 changesets with 5 changes to 2 files
205 updating to branch default
205 updating to branch default
206 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 checking changesets
207 checking changesets
208 checking manifests
208 checking manifests
209 crosschecking files in changesets and manifests
209 crosschecking files in changesets and manifests
210 checking files
210 checking files
211 2 files, 5 changesets, 5 total revisions
211 2 files, 5 changesets, 5 total revisions
212 $ cd test-8
212 $ cd test-8
213 $ hg pull ../test-7
213 $ hg pull ../test-7
214 pulling from ../test-7
214 pulling from ../test-7
215 searching for changes
215 searching for changes
216 adding changesets
216 adding changesets
217 adding manifests
217 adding manifests
218 adding file changes
218 adding file changes
219 added 4 changesets with 2 changes to 3 files (+1 heads)
219 added 4 changesets with 2 changes to 3 files (+1 heads)
220 (run 'hg heads' to see heads, 'hg merge' to merge)
220 (run 'hg heads' to see heads, 'hg merge' to merge)
221 $ hg verify
221 $ hg verify
222 checking changesets
222 checking changesets
223 checking manifests
223 checking manifests
224 crosschecking files in changesets and manifests
224 crosschecking files in changesets and manifests
225 checking files
225 checking files
226 4 files, 9 changesets, 7 total revisions
226 4 files, 9 changesets, 7 total revisions
227 $ cd ..
227 $ cd ..
228 $ cd test-1
228 $ cd test-1
229 $ hg pull -e "python ../dummyssh" -r 4 ssh://user@dummy/remote
229 $ hg pull -e "python ../dummyssh" -r 4 ssh://user@dummy/remote
230 pulling from ssh://user@dummy/remote
230 pulling from ssh://user@dummy/remote
231 searching for changes
231 searching for changes
232 adding changesets
232 adding changesets
233 adding manifests
233 adding manifests
234 adding file changes
234 adding file changes
235 added 1 changesets with 0 changes to 1 files (+1 heads)
235 added 1 changesets with 0 changes to 0 files (+1 heads)
236 (run 'hg heads' to see heads, 'hg merge' to merge)
236 (run 'hg heads' to see heads, 'hg merge' to merge)
237 $ hg verify
237 $ hg verify
238 checking changesets
238 checking changesets
239 checking manifests
239 checking manifests
240 crosschecking files in changesets and manifests
240 crosschecking files in changesets and manifests
241 checking files
241 checking files
242 1 files, 3 changesets, 2 total revisions
242 1 files, 3 changesets, 2 total revisions
243 $ hg pull -e "python ../dummyssh" ssh://user@dummy/remote
243 $ hg pull -e "python ../dummyssh" ssh://user@dummy/remote
244 pulling from ssh://user@dummy/remote
244 pulling from ssh://user@dummy/remote
245 searching for changes
245 searching for changes
246 adding changesets
246 adding changesets
247 adding manifests
247 adding manifests
248 adding file changes
248 adding file changes
249 added 6 changesets with 5 changes to 4 files
249 added 6 changesets with 5 changes to 4 files
250 (run 'hg update' to get a working copy)
250 (run 'hg update' to get a working copy)
251 $ cd ..
251 $ cd ..
252 $ cd test-2
252 $ cd test-2
253 $ hg pull -e "python ../dummyssh" -r 5 ssh://user@dummy/remote
253 $ hg pull -e "python ../dummyssh" -r 5 ssh://user@dummy/remote
254 pulling from ssh://user@dummy/remote
254 pulling from ssh://user@dummy/remote
255 searching for changes
255 searching for changes
256 adding changesets
256 adding changesets
257 adding manifests
257 adding manifests
258 adding file changes
258 adding file changes
259 added 2 changesets with 0 changes to 1 files (+1 heads)
259 added 2 changesets with 0 changes to 0 files (+1 heads)
260 (run 'hg heads' to see heads, 'hg merge' to merge)
260 (run 'hg heads' to see heads, 'hg merge' to merge)
261 $ hg verify
261 $ hg verify
262 checking changesets
262 checking changesets
263 checking manifests
263 checking manifests
264 crosschecking files in changesets and manifests
264 crosschecking files in changesets and manifests
265 checking files
265 checking files
266 1 files, 5 changesets, 3 total revisions
266 1 files, 5 changesets, 3 total revisions
267 $ hg pull -e "python ../dummyssh" ssh://user@dummy/remote
267 $ hg pull -e "python ../dummyssh" ssh://user@dummy/remote
268 pulling from ssh://user@dummy/remote
268 pulling from ssh://user@dummy/remote
269 searching for changes
269 searching for changes
270 adding changesets
270 adding changesets
271 adding manifests
271 adding manifests
272 adding file changes
272 adding file changes
273 added 4 changesets with 4 changes to 4 files
273 added 4 changesets with 4 changes to 4 files
274 (run 'hg update' to get a working copy)
274 (run 'hg update' to get a working copy)
275 $ hg verify
275 $ hg verify
276 checking changesets
276 checking changesets
277 checking manifests
277 checking manifests
278 crosschecking files in changesets and manifests
278 crosschecking files in changesets and manifests
279 checking files
279 checking files
280 4 files, 9 changesets, 7 total revisions
280 4 files, 9 changesets, 7 total revisions
@@ -1,44 +1,45 b''
1 $ mkdir a
1 $ mkdir a
2 $ cd a
2 $ cd a
3 $ hg init
3 $ hg init
4 $ echo 123 > a
4 $ echo 123 > a
5 $ hg add a
5 $ hg add a
6 $ hg commit -m "a" -u a
6 $ hg commit -m "a" -u a
7
7
8 $ cd ..
8 $ cd ..
9 $ mkdir b
9 $ mkdir b
10 $ cd b
10 $ cd b
11 $ hg init
11 $ hg init
12 $ echo 321 > b
12 $ echo 321 > b
13 $ hg add b
13 $ hg add b
14 $ hg commit -m "b" -u b
14 $ hg commit -m "b" -u b
15
15
16 $ hg pull ../a
16 $ hg pull ../a
17 pulling from ../a
17 pulling from ../a
18 searching for changes
18 searching for changes
19 abort: repository is unrelated
19 abort: repository is unrelated
20 [255]
20 [255]
21
21
22 $ hg pull -f ../a
22 $ hg pull -f ../a
23 pulling from ../a
23 pulling from ../a
24 searching for changes
24 searching for changes
25 warning: repository is unrelated
25 warning: repository is unrelated
26 requesting all changes
26 adding changesets
27 adding changesets
27 adding manifests
28 adding manifests
28 adding file changes
29 adding file changes
29 added 1 changesets with 1 changes to 1 files (+1 heads)
30 added 1 changesets with 1 changes to 1 files (+1 heads)
30 (run 'hg heads' to see heads, 'hg merge' to merge)
31 (run 'hg heads' to see heads, 'hg merge' to merge)
31
32
32 $ hg heads
33 $ hg heads
33 changeset: 1:9a79c33a9db3
34 changeset: 1:9a79c33a9db3
34 tag: tip
35 tag: tip
35 parent: -1:000000000000
36 parent: -1:000000000000
36 user: a
37 user: a
37 date: Thu Jan 01 00:00:00 1970 +0000
38 date: Thu Jan 01 00:00:00 1970 +0000
38 summary: a
39 summary: a
39
40
40 changeset: 0:01f8062b2de5
41 changeset: 0:01f8062b2de5
41 user: b
42 user: b
42 date: Thu Jan 01 00:00:00 1970 +0000
43 date: Thu Jan 01 00:00:00 1970 +0000
43 summary: b
44 summary: b
44
45
General Comments 0
You need to be logged in to leave comments. Login now