##// END OF EJS Templates
pushkey: use UTF-8
Matt Mackall -
r13050:3790452d default
parent child Browse files
Show More
@@ -1,572 +1,572 b''
1 # Mercurial extension to provide the 'hg bookmark' command
1 # Mercurial extension to provide the 'hg bookmark' command
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
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 '''track a line of development with movable markers
8 '''track a line of development with movable markers
9
9
10 Bookmarks are local movable markers to changesets. Every bookmark
10 Bookmarks are local movable markers to changesets. Every bookmark
11 points to a changeset identified by its hash. If you commit a
11 points to a changeset identified by its hash. If you commit a
12 changeset that is based on a changeset that has a bookmark on it, the
12 changeset that is based on a changeset that has a bookmark on it, the
13 bookmark shifts to the new changeset.
13 bookmark shifts to the new changeset.
14
14
15 It is possible to use bookmark names in every revision lookup (e.g.
15 It is possible to use bookmark names in every revision lookup (e.g.
16 :hg:`merge`, :hg:`update`).
16 :hg:`merge`, :hg:`update`).
17
17
18 By default, when several bookmarks point to the same changeset, they
18 By default, when several bookmarks point to the same changeset, they
19 will all move forward together. It is possible to obtain a more
19 will all move forward together. It is possible to obtain a more
20 git-like experience by adding the following configuration option to
20 git-like experience by adding the following configuration option to
21 your configuration file::
21 your configuration file::
22
22
23 [bookmarks]
23 [bookmarks]
24 track.current = True
24 track.current = True
25
25
26 This will cause Mercurial to track the bookmark that you are currently
26 This will cause Mercurial to track the bookmark that you are currently
27 using, and only update it. This is similar to git's approach to
27 using, and only update it. This is similar to git's approach to
28 branching.
28 branching.
29 '''
29 '''
30
30
31 from mercurial.i18n import _
31 from mercurial.i18n import _
32 from mercurial.node import nullid, nullrev, bin, hex, short
32 from mercurial.node import nullid, nullrev, bin, hex, short
33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
34 from mercurial import revset, encoding
34 from mercurial import revset, encoding
35 import os
35 import os
36
36
37 def write(repo):
37 def write(repo):
38 '''Write bookmarks
38 '''Write bookmarks
39
39
40 Write the given bookmark => hash dictionary to the .hg/bookmarks file
40 Write the given bookmark => hash dictionary to the .hg/bookmarks file
41 in a format equal to those of localtags.
41 in a format equal to those of localtags.
42
42
43 We also store a backup of the previous state in undo.bookmarks that
43 We also store a backup of the previous state in undo.bookmarks that
44 can be copied back on rollback.
44 can be copied back on rollback.
45 '''
45 '''
46 refs = repo._bookmarks
46 refs = repo._bookmarks
47 if os.path.exists(repo.join('bookmarks')):
47 if os.path.exists(repo.join('bookmarks')):
48 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
48 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
49 if repo._bookmarkcurrent not in refs:
49 if repo._bookmarkcurrent not in refs:
50 setcurrent(repo, None)
50 setcurrent(repo, None)
51 wlock = repo.wlock()
51 wlock = repo.wlock()
52 try:
52 try:
53 file = repo.opener('bookmarks', 'w', atomictemp=True)
53 file = repo.opener('bookmarks', 'w', atomictemp=True)
54 for refspec, node in refs.iteritems():
54 for refspec, node in refs.iteritems():
55 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
55 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
56 file.rename()
56 file.rename()
57
57
58 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
58 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
59 try:
59 try:
60 os.utime(repo.sjoin('00changelog.i'), None)
60 os.utime(repo.sjoin('00changelog.i'), None)
61 except OSError:
61 except OSError:
62 pass
62 pass
63
63
64 finally:
64 finally:
65 wlock.release()
65 wlock.release()
66
66
67 def setcurrent(repo, mark):
67 def setcurrent(repo, mark):
68 '''Set the name of the bookmark that we are currently on
68 '''Set the name of the bookmark that we are currently on
69
69
70 Set the name of the bookmark that we are on (hg update <bookmark>).
70 Set the name of the bookmark that we are on (hg update <bookmark>).
71 The name is recorded in .hg/bookmarks.current
71 The name is recorded in .hg/bookmarks.current
72 '''
72 '''
73 current = repo._bookmarkcurrent
73 current = repo._bookmarkcurrent
74 if current == mark:
74 if current == mark:
75 return
75 return
76
76
77 refs = repo._bookmarks
77 refs = repo._bookmarks
78
78
79 # do not update if we do update to a rev equal to the current bookmark
79 # do not update if we do update to a rev equal to the current bookmark
80 if (mark and mark not in refs and
80 if (mark and mark not in refs and
81 current and refs[current] == repo.changectx('.').node()):
81 current and refs[current] == repo.changectx('.').node()):
82 return
82 return
83 if mark not in refs:
83 if mark not in refs:
84 mark = ''
84 mark = ''
85 wlock = repo.wlock()
85 wlock = repo.wlock()
86 try:
86 try:
87 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
87 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
88 file.write(mark)
88 file.write(mark)
89 file.rename()
89 file.rename()
90 finally:
90 finally:
91 wlock.release()
91 wlock.release()
92 repo._bookmarkcurrent = mark
92 repo._bookmarkcurrent = mark
93
93
94 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
94 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
95 '''track a line of development with movable markers
95 '''track a line of development with movable markers
96
96
97 Bookmarks are pointers to certain commits that move when
97 Bookmarks are pointers to certain commits that move when
98 committing. Bookmarks are local. They can be renamed, copied and
98 committing. Bookmarks are local. They can be renamed, copied and
99 deleted. It is possible to use bookmark names in :hg:`merge` and
99 deleted. It is possible to use bookmark names in :hg:`merge` and
100 :hg:`update` to merge and update respectively to a given bookmark.
100 :hg:`update` to merge and update respectively to a given bookmark.
101
101
102 You can use :hg:`bookmark NAME` to set a bookmark on the working
102 You can use :hg:`bookmark NAME` to set a bookmark on the working
103 directory's parent revision with the given name. If you specify
103 directory's parent revision with the given name. If you specify
104 a revision using -r REV (where REV may be an existing bookmark),
104 a revision using -r REV (where REV may be an existing bookmark),
105 the bookmark is assigned to that revision.
105 the bookmark is assigned to that revision.
106
106
107 Bookmarks can be pushed and pulled between repositories (see :hg:`help
107 Bookmarks can be pushed and pulled between repositories (see :hg:`help
108 push` and :hg:`help pull`). This requires the bookmark extension to be
108 push` and :hg:`help pull`). This requires the bookmark extension to be
109 enabled for both the local and remote repositories.
109 enabled for both the local and remote repositories.
110 '''
110 '''
111 hexfn = ui.debugflag and hex or short
111 hexfn = ui.debugflag and hex or short
112 marks = repo._bookmarks
112 marks = repo._bookmarks
113 cur = repo.changectx('.').node()
113 cur = repo.changectx('.').node()
114
114
115 if rename:
115 if rename:
116 if rename not in marks:
116 if rename not in marks:
117 raise util.Abort(_("a bookmark of this name does not exist"))
117 raise util.Abort(_("a bookmark of this name does not exist"))
118 if mark in marks and not force:
118 if mark in marks and not force:
119 raise util.Abort(_("a bookmark of the same name already exists"))
119 raise util.Abort(_("a bookmark of the same name already exists"))
120 if mark is None:
120 if mark is None:
121 raise util.Abort(_("new bookmark name required"))
121 raise util.Abort(_("new bookmark name required"))
122 marks[mark] = marks[rename]
122 marks[mark] = marks[rename]
123 del marks[rename]
123 del marks[rename]
124 if repo._bookmarkcurrent == rename:
124 if repo._bookmarkcurrent == rename:
125 setcurrent(repo, mark)
125 setcurrent(repo, mark)
126 write(repo)
126 write(repo)
127 return
127 return
128
128
129 if delete:
129 if delete:
130 if mark is None:
130 if mark is None:
131 raise util.Abort(_("bookmark name required"))
131 raise util.Abort(_("bookmark name required"))
132 if mark not in marks:
132 if mark not in marks:
133 raise util.Abort(_("a bookmark of this name does not exist"))
133 raise util.Abort(_("a bookmark of this name does not exist"))
134 if mark == repo._bookmarkcurrent:
134 if mark == repo._bookmarkcurrent:
135 setcurrent(repo, None)
135 setcurrent(repo, None)
136 del marks[mark]
136 del marks[mark]
137 write(repo)
137 write(repo)
138 return
138 return
139
139
140 if mark is not None:
140 if mark is not None:
141 if "\n" in mark:
141 if "\n" in mark:
142 raise util.Abort(_("bookmark name cannot contain newlines"))
142 raise util.Abort(_("bookmark name cannot contain newlines"))
143 mark = mark.strip()
143 mark = mark.strip()
144 if not mark:
144 if not mark:
145 raise util.Abort(_("bookmark names cannot consist entirely of "
145 raise util.Abort(_("bookmark names cannot consist entirely of "
146 "whitespace"))
146 "whitespace"))
147 if mark in marks and not force:
147 if mark in marks and not force:
148 raise util.Abort(_("a bookmark of the same name already exists"))
148 raise util.Abort(_("a bookmark of the same name already exists"))
149 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
149 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
150 and not force):
150 and not force):
151 raise util.Abort(
151 raise util.Abort(
152 _("a bookmark cannot have the name of an existing branch"))
152 _("a bookmark cannot have the name of an existing branch"))
153 if rev:
153 if rev:
154 marks[mark] = repo.lookup(rev)
154 marks[mark] = repo.lookup(rev)
155 else:
155 else:
156 marks[mark] = repo.changectx('.').node()
156 marks[mark] = repo.changectx('.').node()
157 setcurrent(repo, mark)
157 setcurrent(repo, mark)
158 write(repo)
158 write(repo)
159 return
159 return
160
160
161 if mark is None:
161 if mark is None:
162 if rev:
162 if rev:
163 raise util.Abort(_("bookmark name required"))
163 raise util.Abort(_("bookmark name required"))
164 if len(marks) == 0:
164 if len(marks) == 0:
165 ui.status(_("no bookmarks set\n"))
165 ui.status(_("no bookmarks set\n"))
166 else:
166 else:
167 for bmark, n in marks.iteritems():
167 for bmark, n in marks.iteritems():
168 if ui.configbool('bookmarks', 'track.current'):
168 if ui.configbool('bookmarks', 'track.current'):
169 current = repo._bookmarkcurrent
169 current = repo._bookmarkcurrent
170 if bmark == current and n == cur:
170 if bmark == current and n == cur:
171 prefix, label = '*', 'bookmarks.current'
171 prefix, label = '*', 'bookmarks.current'
172 else:
172 else:
173 prefix, label = ' ', ''
173 prefix, label = ' ', ''
174 else:
174 else:
175 if n == cur:
175 if n == cur:
176 prefix, label = '*', 'bookmarks.current'
176 prefix, label = '*', 'bookmarks.current'
177 else:
177 else:
178 prefix, label = ' ', ''
178 prefix, label = ' ', ''
179
179
180 if ui.quiet:
180 if ui.quiet:
181 ui.write("%s\n" % bmark, label=label)
181 ui.write("%s\n" % bmark, label=label)
182 else:
182 else:
183 ui.write(" %s %-25s %d:%s\n" % (
183 ui.write(" %s %-25s %d:%s\n" % (
184 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
184 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
185 label=label)
185 label=label)
186 return
186 return
187
187
188 def _revstostrip(changelog, node):
188 def _revstostrip(changelog, node):
189 srev = changelog.rev(node)
189 srev = changelog.rev(node)
190 tostrip = [srev]
190 tostrip = [srev]
191 saveheads = []
191 saveheads = []
192 for r in xrange(srev, len(changelog)):
192 for r in xrange(srev, len(changelog)):
193 parents = changelog.parentrevs(r)
193 parents = changelog.parentrevs(r)
194 if parents[0] in tostrip or parents[1] in tostrip:
194 if parents[0] in tostrip or parents[1] in tostrip:
195 tostrip.append(r)
195 tostrip.append(r)
196 if parents[1] != nullrev:
196 if parents[1] != nullrev:
197 for p in parents:
197 for p in parents:
198 if p not in tostrip and p > srev:
198 if p not in tostrip and p > srev:
199 saveheads.append(p)
199 saveheads.append(p)
200 return [r for r in tostrip if r not in saveheads]
200 return [r for r in tostrip if r not in saveheads]
201
201
202 def strip(oldstrip, ui, repo, node, backup="all"):
202 def strip(oldstrip, ui, repo, node, backup="all"):
203 """Strip bookmarks if revisions are stripped using
203 """Strip bookmarks if revisions are stripped using
204 the mercurial.strip method. This usually happens during
204 the mercurial.strip method. This usually happens during
205 qpush and qpop"""
205 qpush and qpop"""
206 revisions = _revstostrip(repo.changelog, node)
206 revisions = _revstostrip(repo.changelog, node)
207 marks = repo._bookmarks
207 marks = repo._bookmarks
208 update = []
208 update = []
209 for mark, n in marks.iteritems():
209 for mark, n in marks.iteritems():
210 if repo.changelog.rev(n) in revisions:
210 if repo.changelog.rev(n) in revisions:
211 update.append(mark)
211 update.append(mark)
212 oldstrip(ui, repo, node, backup)
212 oldstrip(ui, repo, node, backup)
213 if len(update) > 0:
213 if len(update) > 0:
214 for m in update:
214 for m in update:
215 marks[m] = repo.changectx('.').node()
215 marks[m] = repo.changectx('.').node()
216 write(repo)
216 write(repo)
217
217
218 def reposetup(ui, repo):
218 def reposetup(ui, repo):
219 if not repo.local():
219 if not repo.local():
220 return
220 return
221
221
222 class bookmark_repo(repo.__class__):
222 class bookmark_repo(repo.__class__):
223
223
224 @util.propertycache
224 @util.propertycache
225 def _bookmarks(self):
225 def _bookmarks(self):
226 '''Parse .hg/bookmarks file and return a dictionary
226 '''Parse .hg/bookmarks file and return a dictionary
227
227
228 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
228 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
229 in the .hg/bookmarks file.
229 in the .hg/bookmarks file.
230 Read the file and return a (name=>nodeid) dictionary
230 Read the file and return a (name=>nodeid) dictionary
231 '''
231 '''
232 try:
232 try:
233 bookmarks = {}
233 bookmarks = {}
234 for line in self.opener('bookmarks'):
234 for line in self.opener('bookmarks'):
235 sha, refspec = line.strip().split(' ', 1)
235 sha, refspec = line.strip().split(' ', 1)
236 refspec = encoding.tolocal(refspec)
236 refspec = encoding.tolocal(refspec)
237 bookmarks[refspec] = self.changelog.lookup(sha)
237 bookmarks[refspec] = self.changelog.lookup(sha)
238 except:
238 except:
239 pass
239 pass
240 return bookmarks
240 return bookmarks
241
241
242 @util.propertycache
242 @util.propertycache
243 def _bookmarkcurrent(self):
243 def _bookmarkcurrent(self):
244 '''Get the current bookmark
244 '''Get the current bookmark
245
245
246 If we use gittishsh branches we have a current bookmark that
246 If we use gittishsh branches we have a current bookmark that
247 we are on. This function returns the name of the bookmark. It
247 we are on. This function returns the name of the bookmark. It
248 is stored in .hg/bookmarks.current
248 is stored in .hg/bookmarks.current
249 '''
249 '''
250 mark = None
250 mark = None
251 if os.path.exists(self.join('bookmarks.current')):
251 if os.path.exists(self.join('bookmarks.current')):
252 file = self.opener('bookmarks.current')
252 file = self.opener('bookmarks.current')
253 # No readline() in posixfile_nt, reading everything is cheap
253 # No readline() in posixfile_nt, reading everything is cheap
254 mark = (file.readlines() or [''])[0]
254 mark = (file.readlines() or [''])[0]
255 if mark == '':
255 if mark == '':
256 mark = None
256 mark = None
257 file.close()
257 file.close()
258 return mark
258 return mark
259
259
260 def rollback(self, *args):
260 def rollback(self, *args):
261 if os.path.exists(self.join('undo.bookmarks')):
261 if os.path.exists(self.join('undo.bookmarks')):
262 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
262 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
263 return super(bookmark_repo, self).rollback(*args)
263 return super(bookmark_repo, self).rollback(*args)
264
264
265 def lookup(self, key):
265 def lookup(self, key):
266 if key in self._bookmarks:
266 if key in self._bookmarks:
267 key = self._bookmarks[key]
267 key = self._bookmarks[key]
268 return super(bookmark_repo, self).lookup(key)
268 return super(bookmark_repo, self).lookup(key)
269
269
270 def _bookmarksupdate(self, parents, node):
270 def _bookmarksupdate(self, parents, node):
271 marks = self._bookmarks
271 marks = self._bookmarks
272 update = False
272 update = False
273 if ui.configbool('bookmarks', 'track.current'):
273 if ui.configbool('bookmarks', 'track.current'):
274 mark = self._bookmarkcurrent
274 mark = self._bookmarkcurrent
275 if mark and marks[mark] in parents:
275 if mark and marks[mark] in parents:
276 marks[mark] = node
276 marks[mark] = node
277 update = True
277 update = True
278 else:
278 else:
279 for mark, n in marks.items():
279 for mark, n in marks.items():
280 if n in parents:
280 if n in parents:
281 marks[mark] = node
281 marks[mark] = node
282 update = True
282 update = True
283 if update:
283 if update:
284 write(self)
284 write(self)
285
285
286 def commitctx(self, ctx, error=False):
286 def commitctx(self, ctx, error=False):
287 """Add a revision to the repository and
287 """Add a revision to the repository and
288 move the bookmark"""
288 move the bookmark"""
289 wlock = self.wlock() # do both commit and bookmark with lock held
289 wlock = self.wlock() # do both commit and bookmark with lock held
290 try:
290 try:
291 node = super(bookmark_repo, self).commitctx(ctx, error)
291 node = super(bookmark_repo, self).commitctx(ctx, error)
292 if node is None:
292 if node is None:
293 return None
293 return None
294 parents = self.changelog.parents(node)
294 parents = self.changelog.parents(node)
295 if parents[1] == nullid:
295 if parents[1] == nullid:
296 parents = (parents[0],)
296 parents = (parents[0],)
297
297
298 self._bookmarksupdate(parents, node)
298 self._bookmarksupdate(parents, node)
299 return node
299 return node
300 finally:
300 finally:
301 wlock.release()
301 wlock.release()
302
302
303 def pull(self, remote, heads=None, force=False):
303 def pull(self, remote, heads=None, force=False):
304 result = super(bookmark_repo, self).pull(remote, heads, force)
304 result = super(bookmark_repo, self).pull(remote, heads, force)
305
305
306 self.ui.debug("checking for updated bookmarks\n")
306 self.ui.debug("checking for updated bookmarks\n")
307 rb = remote.listkeys('bookmarks')
307 rb = remote.listkeys('bookmarks')
308 changed = False
308 changed = False
309 for k in rb.keys():
309 for k in rb.keys():
310 if k in self._bookmarks:
310 if k in self._bookmarks:
311 nr, nl = rb[k], self._bookmarks[k]
311 nr, nl = rb[k], self._bookmarks[k]
312 if nr in self:
312 if nr in self:
313 cr = self[nr]
313 cr = self[nr]
314 cl = self[nl]
314 cl = self[nl]
315 if cl.rev() >= cr.rev():
315 if cl.rev() >= cr.rev():
316 continue
316 continue
317 if cr in cl.descendants():
317 if cr in cl.descendants():
318 self._bookmarks[k] = cr.node()
318 self._bookmarks[k] = cr.node()
319 changed = True
319 changed = True
320 self.ui.status(_("updating bookmark %s\n") % k)
320 self.ui.status(_("updating bookmark %s\n") % k)
321 else:
321 else:
322 self.ui.warn(_("not updating divergent"
322 self.ui.warn(_("not updating divergent"
323 " bookmark %s\n") % k)
323 " bookmark %s\n") % k)
324 if changed:
324 if changed:
325 write(repo)
325 write(repo)
326
326
327 return result
327 return result
328
328
329 def push(self, remote, force=False, revs=None, newbranch=False):
329 def push(self, remote, force=False, revs=None, newbranch=False):
330 result = super(bookmark_repo, self).push(remote, force, revs,
330 result = super(bookmark_repo, self).push(remote, force, revs,
331 newbranch)
331 newbranch)
332
332
333 self.ui.debug("checking for updated bookmarks\n")
333 self.ui.debug("checking for updated bookmarks\n")
334 rb = remote.listkeys('bookmarks')
334 rb = remote.listkeys('bookmarks')
335 for k in rb.keys():
335 for k in rb.keys():
336 if k in self._bookmarks:
336 if k in self._bookmarks:
337 nr, nl = rb[k], self._bookmarks[k]
337 nr, nl = rb[k], hex(self._bookmarks[k])
338 if nr in self:
338 if nr in self:
339 cr = self[nr]
339 cr = self[nr]
340 cl = self[nl]
340 cl = self[nl]
341 if cl in cr.descendants():
341 if cl in cr.descendants():
342 r = remote.pushkey('bookmarks', k, nr, nl)
342 r = remote.pushkey('bookmarks', k, nr, nl)
343 if r:
343 if r:
344 self.ui.status(_("updating bookmark %s\n") % k)
344 self.ui.status(_("updating bookmark %s\n") % k)
345 else:
345 else:
346 self.ui.warn(_('updating bookmark %s'
346 self.ui.warn(_('updating bookmark %s'
347 ' failed!\n') % k)
347 ' failed!\n') % k)
348
348
349 return result
349 return result
350
350
351 def addchangegroup(self, *args, **kwargs):
351 def addchangegroup(self, *args, **kwargs):
352 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
352 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
353 if result > 1:
353 if result > 1:
354 # We have more heads than before
354 # We have more heads than before
355 return result
355 return result
356 node = self.changelog.tip()
356 node = self.changelog.tip()
357 parents = self.dirstate.parents()
357 parents = self.dirstate.parents()
358 self._bookmarksupdate(parents, node)
358 self._bookmarksupdate(parents, node)
359 return result
359 return result
360
360
361 def _findtags(self):
361 def _findtags(self):
362 """Merge bookmarks with normal tags"""
362 """Merge bookmarks with normal tags"""
363 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
363 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
364 tags.update(self._bookmarks)
364 tags.update(self._bookmarks)
365 return (tags, tagtypes)
365 return (tags, tagtypes)
366
366
367 if hasattr(repo, 'invalidate'):
367 if hasattr(repo, 'invalidate'):
368 def invalidate(self):
368 def invalidate(self):
369 super(bookmark_repo, self).invalidate()
369 super(bookmark_repo, self).invalidate()
370 for attr in ('_bookmarks', '_bookmarkcurrent'):
370 for attr in ('_bookmarks', '_bookmarkcurrent'):
371 if attr in self.__dict__:
371 if attr in self.__dict__:
372 delattr(self, attr)
372 delattr(self, attr)
373
373
374 repo.__class__ = bookmark_repo
374 repo.__class__ = bookmark_repo
375
375
376 def listbookmarks(repo):
376 def listbookmarks(repo):
377 # We may try to list bookmarks on a repo type that does not
377 # We may try to list bookmarks on a repo type that does not
378 # support it (e.g., statichttprepository).
378 # support it (e.g., statichttprepository).
379 if not hasattr(repo, '_bookmarks'):
379 if not hasattr(repo, '_bookmarks'):
380 return {}
380 return {}
381
381
382 d = {}
382 d = {}
383 for k, v in repo._bookmarks.iteritems():
383 for k, v in repo._bookmarks.iteritems():
384 d[k] = hex(v)
384 d[k] = hex(v)
385 return d
385 return d
386
386
387 def pushbookmark(repo, key, old, new):
387 def pushbookmark(repo, key, old, new):
388 w = repo.wlock()
388 w = repo.wlock()
389 try:
389 try:
390 marks = repo._bookmarks
390 marks = repo._bookmarks
391 if hex(marks.get(key, '')) != old:
391 if hex(marks.get(key, '')) != old:
392 return False
392 return False
393 if new == '':
393 if new == '':
394 del marks[key]
394 del marks[key]
395 else:
395 else:
396 if new not in repo:
396 if new not in repo:
397 return False
397 return False
398 marks[key] = repo[new].node()
398 marks[key] = repo[new].node()
399 write(repo)
399 write(repo)
400 return True
400 return True
401 finally:
401 finally:
402 w.release()
402 w.release()
403
403
404 def pull(oldpull, ui, repo, source="default", **opts):
404 def pull(oldpull, ui, repo, source="default", **opts):
405 # translate bookmark args to rev args for actual pull
405 # translate bookmark args to rev args for actual pull
406 if opts.get('bookmark'):
406 if opts.get('bookmark'):
407 # this is an unpleasant hack as pull will do this internally
407 # this is an unpleasant hack as pull will do this internally
408 source, branches = hg.parseurl(ui.expandpath(source),
408 source, branches = hg.parseurl(ui.expandpath(source),
409 opts.get('branch'))
409 opts.get('branch'))
410 other = hg.repository(hg.remoteui(repo, opts), source)
410 other = hg.repository(hg.remoteui(repo, opts), source)
411 rb = other.listkeys('bookmarks')
411 rb = other.listkeys('bookmarks')
412
412
413 for b in opts['bookmark']:
413 for b in opts['bookmark']:
414 if b not in rb:
414 if b not in rb:
415 raise util.Abort(_('remote bookmark %s not found!') % b)
415 raise util.Abort(_('remote bookmark %s not found!') % b)
416 opts.setdefault('rev', []).append(b)
416 opts.setdefault('rev', []).append(b)
417
417
418 result = oldpull(ui, repo, source, **opts)
418 result = oldpull(ui, repo, source, **opts)
419
419
420 # update specified bookmarks
420 # update specified bookmarks
421 if opts.get('bookmark'):
421 if opts.get('bookmark'):
422 for b in opts['bookmark']:
422 for b in opts['bookmark']:
423 # explicit pull overrides local bookmark if any
423 # explicit pull overrides local bookmark if any
424 ui.status(_("importing bookmark %s\n") % b)
424 ui.status(_("importing bookmark %s\n") % b)
425 repo._bookmarks[b] = repo[rb[b]].node()
425 repo._bookmarks[b] = repo[rb[b]].node()
426 write(repo)
426 write(repo)
427
427
428 return result
428 return result
429
429
430 def push(oldpush, ui, repo, dest=None, **opts):
430 def push(oldpush, ui, repo, dest=None, **opts):
431 dopush = True
431 dopush = True
432 if opts.get('bookmark'):
432 if opts.get('bookmark'):
433 dopush = False
433 dopush = False
434 for b in opts['bookmark']:
434 for b in opts['bookmark']:
435 if b in repo._bookmarks:
435 if b in repo._bookmarks:
436 dopush = True
436 dopush = True
437 opts.setdefault('rev', []).append(b)
437 opts.setdefault('rev', []).append(b)
438
438
439 result = 0
439 result = 0
440 if dopush:
440 if dopush:
441 result = oldpush(ui, repo, dest, **opts)
441 result = oldpush(ui, repo, dest, **opts)
442
442
443 if opts.get('bookmark'):
443 if opts.get('bookmark'):
444 # this is an unpleasant hack as push will do this internally
444 # this is an unpleasant hack as push will do this internally
445 dest = ui.expandpath(dest or 'default-push', dest or 'default')
445 dest = ui.expandpath(dest or 'default-push', dest or 'default')
446 dest, branches = hg.parseurl(dest, opts.get('branch'))
446 dest, branches = hg.parseurl(dest, opts.get('branch'))
447 other = hg.repository(hg.remoteui(repo, opts), dest)
447 other = hg.repository(hg.remoteui(repo, opts), dest)
448 rb = other.listkeys('bookmarks')
448 rb = other.listkeys('bookmarks')
449 for b in opts['bookmark']:
449 for b in opts['bookmark']:
450 # explicit push overrides remote bookmark if any
450 # explicit push overrides remote bookmark if any
451 if b in repo._bookmarks:
451 if b in repo._bookmarks:
452 ui.status(_("exporting bookmark %s\n") % b)
452 ui.status(_("exporting bookmark %s\n") % b)
453 new = repo[b].hex()
453 new = repo[b].hex()
454 elif b in rb:
454 elif b in rb:
455 ui.status(_("deleting remote bookmark %s\n") % b)
455 ui.status(_("deleting remote bookmark %s\n") % b)
456 new = '' # delete
456 new = '' # delete
457 else:
457 else:
458 ui.warn(_('bookmark %s does not exist on the local '
458 ui.warn(_('bookmark %s does not exist on the local '
459 'or remote repository!\n') % b)
459 'or remote repository!\n') % b)
460 return 2
460 return 2
461 old = rb.get(b, '')
461 old = rb.get(b, '')
462 r = other.pushkey('bookmarks', b, old, new)
462 r = other.pushkey('bookmarks', b, old, new)
463 if not r:
463 if not r:
464 ui.warn(_('updating bookmark %s failed!\n') % b)
464 ui.warn(_('updating bookmark %s failed!\n') % b)
465 if not result:
465 if not result:
466 result = 2
466 result = 2
467
467
468 return result
468 return result
469
469
470 def diffbookmarks(ui, repo, remote):
470 def diffbookmarks(ui, repo, remote):
471 ui.status(_("searching for changed bookmarks\n"))
471 ui.status(_("searching for changed bookmarks\n"))
472
472
473 lmarks = repo.listkeys('bookmarks')
473 lmarks = repo.listkeys('bookmarks')
474 rmarks = remote.listkeys('bookmarks')
474 rmarks = remote.listkeys('bookmarks')
475
475
476 diff = sorted(set(rmarks) - set(lmarks))
476 diff = sorted(set(rmarks) - set(lmarks))
477 for k in diff:
477 for k in diff:
478 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
478 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
479
479
480 if len(diff) <= 0:
480 if len(diff) <= 0:
481 ui.status(_("no changed bookmarks found\n"))
481 ui.status(_("no changed bookmarks found\n"))
482 return 1
482 return 1
483 return 0
483 return 0
484
484
485 def incoming(oldincoming, ui, repo, source="default", **opts):
485 def incoming(oldincoming, ui, repo, source="default", **opts):
486 if opts.get('bookmarks'):
486 if opts.get('bookmarks'):
487 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
487 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
488 other = hg.repository(hg.remoteui(repo, opts), source)
488 other = hg.repository(hg.remoteui(repo, opts), source)
489 ui.status(_('comparing with %s\n') % url.hidepassword(source))
489 ui.status(_('comparing with %s\n') % url.hidepassword(source))
490 return diffbookmarks(ui, repo, other)
490 return diffbookmarks(ui, repo, other)
491 else:
491 else:
492 return oldincoming(ui, repo, source, **opts)
492 return oldincoming(ui, repo, source, **opts)
493
493
494 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
494 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
495 if opts.get('bookmarks'):
495 if opts.get('bookmarks'):
496 dest = ui.expandpath(dest or 'default-push', dest or 'default')
496 dest = ui.expandpath(dest or 'default-push', dest or 'default')
497 dest, branches = hg.parseurl(dest, opts.get('branch'))
497 dest, branches = hg.parseurl(dest, opts.get('branch'))
498 other = hg.repository(hg.remoteui(repo, opts), dest)
498 other = hg.repository(hg.remoteui(repo, opts), dest)
499 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
499 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
500 return diffbookmarks(ui, other, repo)
500 return diffbookmarks(ui, other, repo)
501 else:
501 else:
502 return oldoutgoing(ui, repo, dest, **opts)
502 return oldoutgoing(ui, repo, dest, **opts)
503
503
504 def uisetup(ui):
504 def uisetup(ui):
505 extensions.wrapfunction(repair, "strip", strip)
505 extensions.wrapfunction(repair, "strip", strip)
506 if ui.configbool('bookmarks', 'track.current'):
506 if ui.configbool('bookmarks', 'track.current'):
507 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
507 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
508
508
509 entry = extensions.wrapcommand(commands.table, 'pull', pull)
509 entry = extensions.wrapcommand(commands.table, 'pull', pull)
510 entry[1].append(('B', 'bookmark', [],
510 entry[1].append(('B', 'bookmark', [],
511 _("bookmark to import"),
511 _("bookmark to import"),
512 _('BOOKMARK')))
512 _('BOOKMARK')))
513 entry = extensions.wrapcommand(commands.table, 'push', push)
513 entry = extensions.wrapcommand(commands.table, 'push', push)
514 entry[1].append(('B', 'bookmark', [],
514 entry[1].append(('B', 'bookmark', [],
515 _("bookmark to export"),
515 _("bookmark to export"),
516 _('BOOKMARK')))
516 _('BOOKMARK')))
517 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
517 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
518 entry[1].append(('B', 'bookmarks', False,
518 entry[1].append(('B', 'bookmarks', False,
519 _("compare bookmark")))
519 _("compare bookmark")))
520 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
520 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
521 entry[1].append(('B', 'bookmarks', False,
521 entry[1].append(('B', 'bookmarks', False,
522 _("compare bookmark")))
522 _("compare bookmark")))
523
523
524 pushkey.register('bookmarks', pushbookmark, listbookmarks)
524 pushkey.register('bookmarks', pushbookmark, listbookmarks)
525
525
526 def updatecurbookmark(orig, ui, repo, *args, **opts):
526 def updatecurbookmark(orig, ui, repo, *args, **opts):
527 '''Set the current bookmark
527 '''Set the current bookmark
528
528
529 If the user updates to a bookmark we update the .hg/bookmarks.current
529 If the user updates to a bookmark we update the .hg/bookmarks.current
530 file.
530 file.
531 '''
531 '''
532 res = orig(ui, repo, *args, **opts)
532 res = orig(ui, repo, *args, **opts)
533 rev = opts['rev']
533 rev = opts['rev']
534 if not rev and len(args) > 0:
534 if not rev and len(args) > 0:
535 rev = args[0]
535 rev = args[0]
536 setcurrent(repo, rev)
536 setcurrent(repo, rev)
537 return res
537 return res
538
538
539 def bmrevset(repo, subset, x):
539 def bmrevset(repo, subset, x):
540 """``bookmark([name])``
540 """``bookmark([name])``
541 The named bookmark or all bookmarks.
541 The named bookmark or all bookmarks.
542 """
542 """
543 # i18n: "bookmark" is a keyword
543 # i18n: "bookmark" is a keyword
544 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
544 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
545 if args:
545 if args:
546 bm = revset.getstring(args[0],
546 bm = revset.getstring(args[0],
547 # i18n: "bookmark" is a keyword
547 # i18n: "bookmark" is a keyword
548 _('the argument to bookmark must be a string'))
548 _('the argument to bookmark must be a string'))
549 bmrev = listbookmarks(repo).get(bm, None)
549 bmrev = listbookmarks(repo).get(bm, None)
550 if bmrev:
550 if bmrev:
551 bmrev = repo.changelog.rev(bin(bmrev))
551 bmrev = repo.changelog.rev(bin(bmrev))
552 return [r for r in subset if r == bmrev]
552 return [r for r in subset if r == bmrev]
553 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
553 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
554 return [r for r in subset if r in bms]
554 return [r for r in subset if r in bms]
555
555
556 def extsetup(ui):
556 def extsetup(ui):
557 revset.symbols['bookmark'] = bmrevset
557 revset.symbols['bookmark'] = bmrevset
558
558
559 cmdtable = {
559 cmdtable = {
560 "bookmarks":
560 "bookmarks":
561 (bookmark,
561 (bookmark,
562 [('f', 'force', False, _('force')),
562 [('f', 'force', False, _('force')),
563 ('r', 'rev', '', _('revision'), _('REV')),
563 ('r', 'rev', '', _('revision'), _('REV')),
564 ('d', 'delete', False, _('delete a given bookmark')),
564 ('d', 'delete', False, _('delete a given bookmark')),
565 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
565 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
566 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
566 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
567 }
567 }
568
568
569 colortable = {'bookmarks.current': 'green'}
569 colortable = {'bookmarks.current': 'green'}
570
570
571 # tell hggettext to extract docstrings from these functions:
571 # tell hggettext to extract docstrings from these functions:
572 i18nfunctions = [bmrevset]
572 i18nfunctions = [bmrevset]
@@ -1,331 +1,348 b''
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-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 import urllib, tempfile, os, sys
8 import urllib, tempfile, os, sys
9 from i18n import _
9 from i18n import _
10 from node import bin, hex
10 from node import bin, hex
11 import changegroup as changegroupmod
11 import changegroup as changegroupmod
12 import repo, error, encoding, util, store
12 import repo, error, encoding, util, store
13 import pushkey as pushkeymod
13 import pushkey as pushkeymod
14
14
15 # list of nodes encoding / decoding
15 # list of nodes encoding / decoding
16
16
17 def decodelist(l, sep=' '):
17 def decodelist(l, sep=' '):
18 return map(bin, l.split(sep))
18 return map(bin, l.split(sep))
19
19
20 def encodelist(l, sep=' '):
20 def encodelist(l, sep=' '):
21 return sep.join(map(hex, l))
21 return sep.join(map(hex, l))
22
22
23 # client side
23 # client side
24
24
25 class wirerepository(repo.repository):
25 class wirerepository(repo.repository):
26 def lookup(self, key):
26 def lookup(self, key):
27 self.requirecap('lookup', _('look up remote revision'))
27 self.requirecap('lookup', _('look up remote revision'))
28 d = self._call("lookup", key=encoding.fromlocal(key))
28 d = self._call("lookup", key=encoding.fromlocal(key))
29 success, data = d[:-1].split(" ", 1)
29 success, data = d[:-1].split(" ", 1)
30 if int(success):
30 if int(success):
31 return bin(data)
31 return bin(data)
32 self._abort(error.RepoError(data))
32 self._abort(error.RepoError(data))
33
33
34 def heads(self):
34 def heads(self):
35 d = self._call("heads")
35 d = self._call("heads")
36 try:
36 try:
37 return decodelist(d[:-1])
37 return decodelist(d[:-1])
38 except:
38 except:
39 self._abort(error.ResponseError(_("unexpected response:"), d))
39 self._abort(error.ResponseError(_("unexpected response:"), d))
40
40
41 def branchmap(self):
41 def branchmap(self):
42 d = self._call("branchmap")
42 d = self._call("branchmap")
43 try:
43 try:
44 branchmap = {}
44 branchmap = {}
45 for branchpart in d.splitlines():
45 for branchpart in d.splitlines():
46 branchname, branchheads = branchpart.split(' ', 1)
46 branchname, branchheads = branchpart.split(' ', 1)
47 branchname = encoding.tolocal(urllib.unquote(branchname))
47 branchname = encoding.tolocal(urllib.unquote(branchname))
48 branchheads = decodelist(branchheads)
48 branchheads = decodelist(branchheads)
49 branchmap[branchname] = branchheads
49 branchmap[branchname] = branchheads
50 return branchmap
50 return branchmap
51 except TypeError:
51 except TypeError:
52 self._abort(error.ResponseError(_("unexpected response:"), d))
52 self._abort(error.ResponseError(_("unexpected response:"), d))
53
53
54 def branches(self, nodes):
54 def branches(self, nodes):
55 n = encodelist(nodes)
55 n = encodelist(nodes)
56 d = self._call("branches", nodes=n)
56 d = self._call("branches", nodes=n)
57 try:
57 try:
58 br = [tuple(decodelist(b)) for b in d.splitlines()]
58 br = [tuple(decodelist(b)) for b in d.splitlines()]
59 return br
59 return br
60 except:
60 except:
61 self._abort(error.ResponseError(_("unexpected response:"), d))
61 self._abort(error.ResponseError(_("unexpected response:"), d))
62
62
63 def between(self, pairs):
63 def between(self, pairs):
64 batch = 8 # avoid giant requests
64 batch = 8 # avoid giant requests
65 r = []
65 r = []
66 for i in xrange(0, len(pairs), batch):
66 for i in xrange(0, len(pairs), batch):
67 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
67 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
68 d = self._call("between", pairs=n)
68 d = self._call("between", pairs=n)
69 try:
69 try:
70 r.extend(l and decodelist(l) or [] for l in d.splitlines())
70 r.extend(l and decodelist(l) or [] for l in d.splitlines())
71 except:
71 except:
72 self._abort(error.ResponseError(_("unexpected response:"), d))
72 self._abort(error.ResponseError(_("unexpected response:"), d))
73 return r
73 return r
74
74
75 def pushkey(self, namespace, key, old, new):
75 def pushkey(self, namespace, key, old, new):
76 if not self.capable('pushkey'):
76 if not self.capable('pushkey'):
77 return False
77 return False
78 d = self._call("pushkey",
78 d = self._call("pushkey",
79 namespace=namespace, key=key, old=old, new=new)
79 namespace=encoding.fromlocal(namespace),
80 key=encoding.fromlocal(key),
81 old=encoding.fromlocal(old),
82 new=encoding.fromlocal(new))
80 return bool(int(d))
83 return bool(int(d))
81
84
82 def listkeys(self, namespace):
85 def listkeys(self, namespace):
83 if not self.capable('pushkey'):
86 if not self.capable('pushkey'):
84 return {}
87 return {}
85 d = self._call("listkeys", namespace=namespace)
88 d = self._call("listkeys", namespace=encoding.fromlocal(namespace))
86 r = {}
89 r = {}
87 for l in d.splitlines():
90 for l in d.splitlines():
88 k, v = l.split('\t')
91 k, v = l.split('\t')
89 r[k.decode('string-escape')] = v.decode('string-escape')
92 r[encoding.tolocal(k)] = encoding.tolocal(v)
90 return r
93 return r
91
94
92 def stream_out(self):
95 def stream_out(self):
93 return self._callstream('stream_out')
96 return self._callstream('stream_out')
94
97
95 def changegroup(self, nodes, kind):
98 def changegroup(self, nodes, kind):
96 n = encodelist(nodes)
99 n = encodelist(nodes)
97 f = self._callstream("changegroup", roots=n)
100 f = self._callstream("changegroup", roots=n)
98 return changegroupmod.unbundle10(self._decompress(f), 'UN')
101 return changegroupmod.unbundle10(self._decompress(f), 'UN')
99
102
100 def changegroupsubset(self, bases, heads, kind):
103 def changegroupsubset(self, bases, heads, kind):
101 self.requirecap('changegroupsubset', _('look up remote changes'))
104 self.requirecap('changegroupsubset', _('look up remote changes'))
102 bases = encodelist(bases)
105 bases = encodelist(bases)
103 heads = encodelist(heads)
106 heads = encodelist(heads)
104 f = self._callstream("changegroupsubset",
107 f = self._callstream("changegroupsubset",
105 bases=bases, heads=heads)
108 bases=bases, heads=heads)
106 return changegroupmod.unbundle10(self._decompress(f), 'UN')
109 return changegroupmod.unbundle10(self._decompress(f), 'UN')
107
110
108 def unbundle(self, cg, heads, source):
111 def unbundle(self, cg, heads, source):
109 '''Send cg (a readable file-like object representing the
112 '''Send cg (a readable file-like object representing the
110 changegroup to push, typically a chunkbuffer object) to the
113 changegroup to push, typically a chunkbuffer object) to the
111 remote server as a bundle. Return an integer indicating the
114 remote server as a bundle. Return an integer indicating the
112 result of the push (see localrepository.addchangegroup()).'''
115 result of the push (see localrepository.addchangegroup()).'''
113
116
114 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
117 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
115 if ret == "":
118 if ret == "":
116 raise error.ResponseError(
119 raise error.ResponseError(
117 _('push failed:'), output)
120 _('push failed:'), output)
118 try:
121 try:
119 ret = int(ret)
122 ret = int(ret)
120 except ValueError:
123 except ValueError:
121 raise error.ResponseError(
124 raise error.ResponseError(
122 _('push failed (unexpected response):'), ret)
125 _('push failed (unexpected response):'), ret)
123
126
124 for l in output.splitlines(True):
127 for l in output.splitlines(True):
125 self.ui.status(_('remote: '), l)
128 self.ui.status(_('remote: '), l)
126 return ret
129 return ret
127
130
128 # server side
131 # server side
129
132
130 class streamres(object):
133 class streamres(object):
131 def __init__(self, gen):
134 def __init__(self, gen):
132 self.gen = gen
135 self.gen = gen
133
136
134 class pushres(object):
137 class pushres(object):
135 def __init__(self, res):
138 def __init__(self, res):
136 self.res = res
139 self.res = res
137
140
138 class pusherr(object):
141 class pusherr(object):
139 def __init__(self, res):
142 def __init__(self, res):
140 self.res = res
143 self.res = res
141
144
142 def dispatch(repo, proto, command):
145 def dispatch(repo, proto, command):
143 func, spec = commands[command]
146 func, spec = commands[command]
144 args = proto.getargs(spec)
147 args = proto.getargs(spec)
145 return func(repo, proto, *args)
148 return func(repo, proto, *args)
146
149
147 def between(repo, proto, pairs):
150 def between(repo, proto, pairs):
148 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
151 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
149 r = []
152 r = []
150 for b in repo.between(pairs):
153 for b in repo.between(pairs):
151 r.append(encodelist(b) + "\n")
154 r.append(encodelist(b) + "\n")
152 return "".join(r)
155 return "".join(r)
153
156
154 def branchmap(repo, proto):
157 def branchmap(repo, proto):
155 branchmap = repo.branchmap()
158 branchmap = repo.branchmap()
156 heads = []
159 heads = []
157 for branch, nodes in branchmap.iteritems():
160 for branch, nodes in branchmap.iteritems():
158 branchname = urllib.quote(encoding.fromlocal(branch))
161 branchname = urllib.quote(encoding.fromlocal(branch))
159 branchnodes = encodelist(nodes)
162 branchnodes = encodelist(nodes)
160 heads.append('%s %s' % (branchname, branchnodes))
163 heads.append('%s %s' % (branchname, branchnodes))
161 return '\n'.join(heads)
164 return '\n'.join(heads)
162
165
163 def branches(repo, proto, nodes):
166 def branches(repo, proto, nodes):
164 nodes = decodelist(nodes)
167 nodes = decodelist(nodes)
165 r = []
168 r = []
166 for b in repo.branches(nodes):
169 for b in repo.branches(nodes):
167 r.append(encodelist(b) + "\n")
170 r.append(encodelist(b) + "\n")
168 return "".join(r)
171 return "".join(r)
169
172
170 def capabilities(repo, proto):
173 def capabilities(repo, proto):
171 caps = 'lookup changegroupsubset branchmap pushkey'.split()
174 caps = 'lookup changegroupsubset branchmap pushkey'.split()
172 if _allowstream(repo.ui):
175 if _allowstream(repo.ui):
173 requiredformats = repo.requirements & repo.supportedformats
176 requiredformats = repo.requirements & repo.supportedformats
174 # if our local revlogs are just revlogv1, add 'stream' cap
177 # if our local revlogs are just revlogv1, add 'stream' cap
175 if not requiredformats - set(('revlogv1',)):
178 if not requiredformats - set(('revlogv1',)):
176 caps.append('stream')
179 caps.append('stream')
177 # otherwise, add 'streamreqs' detailing our local revlog format
180 # otherwise, add 'streamreqs' detailing our local revlog format
178 else:
181 else:
179 caps.append('streamreqs=%s' % ','.join(requiredformats))
182 caps.append('streamreqs=%s' % ','.join(requiredformats))
180 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
183 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
181 return ' '.join(caps)
184 return ' '.join(caps)
182
185
183 def changegroup(repo, proto, roots):
186 def changegroup(repo, proto, roots):
184 nodes = decodelist(roots)
187 nodes = decodelist(roots)
185 cg = repo.changegroup(nodes, 'serve')
188 cg = repo.changegroup(nodes, 'serve')
186 return streamres(proto.groupchunks(cg))
189 return streamres(proto.groupchunks(cg))
187
190
188 def changegroupsubset(repo, proto, bases, heads):
191 def changegroupsubset(repo, proto, bases, heads):
189 bases = decodelist(bases)
192 bases = decodelist(bases)
190 heads = decodelist(heads)
193 heads = decodelist(heads)
191 cg = repo.changegroupsubset(bases, heads, 'serve')
194 cg = repo.changegroupsubset(bases, heads, 'serve')
192 return streamres(proto.groupchunks(cg))
195 return streamres(proto.groupchunks(cg))
193
196
194 def heads(repo, proto):
197 def heads(repo, proto):
195 h = repo.heads()
198 h = repo.heads()
196 return encodelist(h) + "\n"
199 return encodelist(h) + "\n"
197
200
198 def hello(repo, proto):
201 def hello(repo, proto):
199 '''the hello command returns a set of lines describing various
202 '''the hello command returns a set of lines describing various
200 interesting things about the server, in an RFC822-like format.
203 interesting things about the server, in an RFC822-like format.
201 Currently the only one defined is "capabilities", which
204 Currently the only one defined is "capabilities", which
202 consists of a line in the form:
205 consists of a line in the form:
203
206
204 capabilities: space separated list of tokens
207 capabilities: space separated list of tokens
205 '''
208 '''
206 return "capabilities: %s\n" % (capabilities(repo, proto))
209 return "capabilities: %s\n" % (capabilities(repo, proto))
207
210
208 def listkeys(repo, proto, namespace):
211 def listkeys(repo, proto, namespace):
209 d = pushkeymod.list(repo, namespace).items()
212 d = pushkeymod.list(repo, encoding.tolocal(namespace)).items()
210 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
213 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
211 v.encode('string-escape')) for k, v in d])
214 for k, v in d])
212 return t
215 return t
213
216
214 def lookup(repo, proto, key):
217 def lookup(repo, proto, key):
215 try:
218 try:
216 r = hex(repo.lookup(encoding.tolocal(key)))
219 r = hex(repo.lookup(encoding.tolocal(key)))
217 success = 1
220 success = 1
218 except Exception, inst:
221 except Exception, inst:
219 r = str(inst)
222 r = str(inst)
220 success = 0
223 success = 0
221 return "%s %s\n" % (success, r)
224 return "%s %s\n" % (success, r)
222
225
223 def pushkey(repo, proto, namespace, key, old, new):
226 def pushkey(repo, proto, namespace, key, old, new):
224 r = pushkeymod.push(repo, namespace, key, old, new)
227 # compatibility with pre-1.8 clients which were accidentally
228 # sending raw binary nodes rather than utf-8-encoded hex
229 if len(new) == 20 and new.encode('string-escape') != new:
230 # looks like it could be a binary node
231 try:
232 u = new.decode('utf-8')
233 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
234 except UnicodeDecodeError:
235 pass # binary, leave unmodified
236 else:
237 new = encoding.tolocal(new) # normal path
238
239 r = pushkeymod.push(repo,
240 encoding.tolocal(namespace), encoding.tolocal(key),
241 encoding.tolocal(old), new)
225 return '%s\n' % int(r)
242 return '%s\n' % int(r)
226
243
227 def _allowstream(ui):
244 def _allowstream(ui):
228 return ui.configbool('server', 'uncompressed', True, untrusted=True)
245 return ui.configbool('server', 'uncompressed', True, untrusted=True)
229
246
230 def stream(repo, proto):
247 def stream(repo, proto):
231 '''If the server supports streaming clone, it advertises the "stream"
248 '''If the server supports streaming clone, it advertises the "stream"
232 capability with a value representing the version and flags of the repo
249 capability with a value representing the version and flags of the repo
233 it is serving. Client checks to see if it understands the format.
250 it is serving. Client checks to see if it understands the format.
234
251
235 The format is simple: the server writes out a line with the amount
252 The format is simple: the server writes out a line with the amount
236 of files, then the total amount of bytes to be transfered (separated
253 of files, then the total amount of bytes to be transfered (separated
237 by a space). Then, for each file, the server first writes the filename
254 by a space). Then, for each file, the server first writes the filename
238 and filesize (separated by the null character), then the file contents.
255 and filesize (separated by the null character), then the file contents.
239 '''
256 '''
240
257
241 if not _allowstream(repo.ui):
258 if not _allowstream(repo.ui):
242 return '1\n'
259 return '1\n'
243
260
244 entries = []
261 entries = []
245 total_bytes = 0
262 total_bytes = 0
246 try:
263 try:
247 # get consistent snapshot of repo, lock during scan
264 # get consistent snapshot of repo, lock during scan
248 lock = repo.lock()
265 lock = repo.lock()
249 try:
266 try:
250 repo.ui.debug('scanning\n')
267 repo.ui.debug('scanning\n')
251 for name, ename, size in repo.store.walk():
268 for name, ename, size in repo.store.walk():
252 entries.append((name, size))
269 entries.append((name, size))
253 total_bytes += size
270 total_bytes += size
254 finally:
271 finally:
255 lock.release()
272 lock.release()
256 except error.LockError:
273 except error.LockError:
257 return '2\n' # error: 2
274 return '2\n' # error: 2
258
275
259 def streamer(repo, entries, total):
276 def streamer(repo, entries, total):
260 '''stream out all metadata files in repository.'''
277 '''stream out all metadata files in repository.'''
261 yield '0\n' # success
278 yield '0\n' # success
262 repo.ui.debug('%d files, %d bytes to transfer\n' %
279 repo.ui.debug('%d files, %d bytes to transfer\n' %
263 (len(entries), total_bytes))
280 (len(entries), total_bytes))
264 yield '%d %d\n' % (len(entries), total_bytes)
281 yield '%d %d\n' % (len(entries), total_bytes)
265 for name, size in entries:
282 for name, size in entries:
266 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
283 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
267 # partially encode name over the wire for backwards compat
284 # partially encode name over the wire for backwards compat
268 yield '%s\0%d\n' % (store.encodedir(name), size)
285 yield '%s\0%d\n' % (store.encodedir(name), size)
269 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
286 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
270 yield chunk
287 yield chunk
271
288
272 return streamres(streamer(repo, entries, total_bytes))
289 return streamres(streamer(repo, entries, total_bytes))
273
290
274 def unbundle(repo, proto, heads):
291 def unbundle(repo, proto, heads):
275 their_heads = decodelist(heads)
292 their_heads = decodelist(heads)
276
293
277 def check_heads():
294 def check_heads():
278 heads = repo.heads()
295 heads = repo.heads()
279 return their_heads == ['force'] or their_heads == heads
296 return their_heads == ['force'] or their_heads == heads
280
297
281 proto.redirect()
298 proto.redirect()
282
299
283 # fail early if possible
300 # fail early if possible
284 if not check_heads():
301 if not check_heads():
285 return pusherr('unsynced changes')
302 return pusherr('unsynced changes')
286
303
287 # write bundle data to temporary file because it can be big
304 # write bundle data to temporary file because it can be big
288 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
305 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
289 fp = os.fdopen(fd, 'wb+')
306 fp = os.fdopen(fd, 'wb+')
290 r = 0
307 r = 0
291 try:
308 try:
292 proto.getfile(fp)
309 proto.getfile(fp)
293 lock = repo.lock()
310 lock = repo.lock()
294 try:
311 try:
295 if not check_heads():
312 if not check_heads():
296 # someone else committed/pushed/unbundled while we
313 # someone else committed/pushed/unbundled while we
297 # were transferring data
314 # were transferring data
298 return pusherr('unsynced changes')
315 return pusherr('unsynced changes')
299
316
300 # push can proceed
317 # push can proceed
301 fp.seek(0)
318 fp.seek(0)
302 gen = changegroupmod.readbundle(fp, None)
319 gen = changegroupmod.readbundle(fp, None)
303
320
304 try:
321 try:
305 r = repo.addchangegroup(gen, 'serve', proto._client(),
322 r = repo.addchangegroup(gen, 'serve', proto._client(),
306 lock=lock)
323 lock=lock)
307 except util.Abort, inst:
324 except util.Abort, inst:
308 sys.stderr.write("abort: %s\n" % inst)
325 sys.stderr.write("abort: %s\n" % inst)
309 finally:
326 finally:
310 lock.release()
327 lock.release()
311 return pushres(r)
328 return pushres(r)
312
329
313 finally:
330 finally:
314 fp.close()
331 fp.close()
315 os.unlink(tempname)
332 os.unlink(tempname)
316
333
317 commands = {
334 commands = {
318 'between': (between, 'pairs'),
335 'between': (between, 'pairs'),
319 'branchmap': (branchmap, ''),
336 'branchmap': (branchmap, ''),
320 'branches': (branches, 'nodes'),
337 'branches': (branches, 'nodes'),
321 'capabilities': (capabilities, ''),
338 'capabilities': (capabilities, ''),
322 'changegroup': (changegroup, 'roots'),
339 'changegroup': (changegroup, 'roots'),
323 'changegroupsubset': (changegroupsubset, 'bases heads'),
340 'changegroupsubset': (changegroupsubset, 'bases heads'),
324 'heads': (heads, ''),
341 'heads': (heads, ''),
325 'hello': (hello, ''),
342 'hello': (hello, ''),
326 'listkeys': (listkeys, 'namespace'),
343 'listkeys': (listkeys, 'namespace'),
327 'lookup': (lookup, 'key'),
344 'lookup': (lookup, 'key'),
328 'pushkey': (pushkey, 'namespace key old new'),
345 'pushkey': (pushkey, 'namespace key old new'),
329 'stream_out': (stream, ''),
346 'stream_out': (stream, ''),
330 'unbundle': (unbundle, 'heads'),
347 'unbundle': (unbundle, 'heads'),
331 }
348 }
@@ -1,291 +1,291 b''
1
1
2 $ cp "$TESTDIR"/printenv.py .
2 $ cp "$TESTDIR"/printenv.py .
3
3
4 This test tries to exercise the ssh functionality with a dummy script
4 This test tries to exercise the ssh functionality with a dummy script
5
5
6 $ cat <<EOF > dummyssh
6 $ cat <<EOF > dummyssh
7 > import sys
7 > import sys
8 > import os
8 > import os
9 >
9 >
10 > os.chdir(os.path.dirname(sys.argv[0]))
10 > os.chdir(os.path.dirname(sys.argv[0]))
11 > if sys.argv[1] != "user@dummy":
11 > if sys.argv[1] != "user@dummy":
12 > sys.exit(-1)
12 > sys.exit(-1)
13 >
13 >
14 > if not os.path.exists("dummyssh"):
14 > if not os.path.exists("dummyssh"):
15 > sys.exit(-1)
15 > sys.exit(-1)
16 >
16 >
17 > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
17 > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
18 >
18 >
19 > log = open("dummylog", "ab")
19 > log = open("dummylog", "ab")
20 > log.write("Got arguments")
20 > log.write("Got arguments")
21 > for i, arg in enumerate(sys.argv[1:]):
21 > for i, arg in enumerate(sys.argv[1:]):
22 > log.write(" %d:%s" % (i+1, arg))
22 > log.write(" %d:%s" % (i+1, arg))
23 > log.write("\n")
23 > log.write("\n")
24 > log.close()
24 > log.close()
25 > r = os.system(sys.argv[2])
25 > r = os.system(sys.argv[2])
26 > sys.exit(bool(r))
26 > sys.exit(bool(r))
27 > EOF
27 > EOF
28 $ cat <<EOF > badhook
28 $ cat <<EOF > badhook
29 > import sys
29 > import sys
30 > sys.stdout.write("KABOOM\n")
30 > sys.stdout.write("KABOOM\n")
31 > EOF
31 > EOF
32
32
33 creating 'remote
33 creating 'remote
34
34
35 $ hg init remote
35 $ hg init remote
36 $ cd remote
36 $ cd remote
37 $ echo this > foo
37 $ echo this > foo
38 $ echo this > fooO
38 $ echo this > fooO
39 $ hg ci -A -m "init" foo fooO
39 $ hg ci -A -m "init" foo fooO
40 $ echo <<EOF > .hg/hgrc
40 $ echo <<EOF > .hg/hgrc
41 > [server]
41 > [server]
42 > uncompressed = True
42 > uncompressed = True
43 >
43 >
44 > [extensions]
44 > [extensions]
45 > bookmarks =
45 > bookmarks =
46 >
46 >
47 > [hooks]
47 > [hooks]
48 > changegroup = python ../printenv.py changegroup-in-remote 0 ../dummylog
48 > changegroup = python ../printenv.py changegroup-in-remote 0 ../dummylog
49 > EOF
49 > EOF
50 $ cd ..
50 $ cd ..
51
51
52 repo not found error
52 repo not found error
53
53
54 $ hg clone -e "python ./dummyssh" ssh://user@dummy/nonexistent local
54 $ hg clone -e "python ./dummyssh" ssh://user@dummy/nonexistent local
55 remote: abort: There is no Mercurial repository here (.hg not found)!
55 remote: abort: There is no Mercurial repository here (.hg not found)!
56 abort: no suitable response from remote hg!
56 abort: no suitable response from remote hg!
57 [255]
57 [255]
58
58
59 non-existent absolute path
59 non-existent absolute path
60
60
61 $ hg clone -e "python ./dummyssh" ssh://user@dummy//`pwd`/nonexistent local
61 $ hg clone -e "python ./dummyssh" ssh://user@dummy//`pwd`/nonexistent local
62 remote: abort: There is no Mercurial repository here (.hg not found)!
62 remote: abort: There is no Mercurial repository here (.hg not found)!
63 abort: no suitable response from remote hg!
63 abort: no suitable response from remote hg!
64 [255]
64 [255]
65
65
66 clone remote via stream
66 clone remote via stream
67
67
68 $ hg clone -e "python ./dummyssh" --uncompressed ssh://user@dummy/remote local-stream
68 $ hg clone -e "python ./dummyssh" --uncompressed ssh://user@dummy/remote local-stream
69 streaming all changes
69 streaming all changes
70 4 files to transfer, 392 bytes of data
70 4 files to transfer, 392 bytes of data
71 transferred 392 bytes in * seconds (*/sec) (glob)
71 transferred 392 bytes in * seconds (*/sec) (glob)
72 updating to branch default
72 updating to branch default
73 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
73 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 $ cd local-stream
74 $ cd local-stream
75 $ hg verify
75 $ hg verify
76 checking changesets
76 checking changesets
77 checking manifests
77 checking manifests
78 crosschecking files in changesets and manifests
78 crosschecking files in changesets and manifests
79 checking files
79 checking files
80 2 files, 1 changesets, 2 total revisions
80 2 files, 1 changesets, 2 total revisions
81 $ cd ..
81 $ cd ..
82
82
83 clone remote via pull
83 clone remote via pull
84
84
85 $ hg clone -e "python ./dummyssh" ssh://user@dummy/remote local
85 $ hg clone -e "python ./dummyssh" ssh://user@dummy/remote local
86 requesting all changes
86 requesting all changes
87 adding changesets
87 adding changesets
88 adding manifests
88 adding manifests
89 adding file changes
89 adding file changes
90 added 1 changesets with 2 changes to 2 files
90 added 1 changesets with 2 changes to 2 files
91 updating to branch default
91 updating to branch default
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93
93
94 verify
94 verify
95
95
96 $ cd local
96 $ cd local
97 $ hg verify
97 $ hg verify
98 checking changesets
98 checking changesets
99 checking manifests
99 checking manifests
100 crosschecking files in changesets and manifests
100 crosschecking files in changesets and manifests
101 checking files
101 checking files
102 2 files, 1 changesets, 2 total revisions
102 2 files, 1 changesets, 2 total revisions
103 $ echo '[hooks]' >> .hg/hgrc
103 $ echo '[hooks]' >> .hg/hgrc
104 $ echo 'changegroup = python ../printenv.py changegroup-in-local 0 ../dummylog' >> .hg/hgrc
104 $ echo 'changegroup = python ../printenv.py changegroup-in-local 0 ../dummylog' >> .hg/hgrc
105
105
106 empty default pull
106 empty default pull
107
107
108 $ hg paths
108 $ hg paths
109 default = ssh://user@dummy/remote
109 default = ssh://user@dummy/remote
110 $ hg pull -e "python ../dummyssh"
110 $ hg pull -e "python ../dummyssh"
111 pulling from ssh://user@dummy/remote
111 pulling from ssh://user@dummy/remote
112 searching for changes
112 searching for changes
113 no changes found
113 no changes found
114
114
115 local change
115 local change
116
116
117 $ echo bleah > foo
117 $ echo bleah > foo
118 $ hg ci -m "add"
118 $ hg ci -m "add"
119
119
120 updating rc
120 updating rc
121
121
122 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
122 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
123 $ echo "[ui]" >> .hg/hgrc
123 $ echo "[ui]" >> .hg/hgrc
124 $ echo "ssh = python ../dummyssh" >> .hg/hgrc
124 $ echo "ssh = python ../dummyssh" >> .hg/hgrc
125 $ echo '[extensions]' >> .hg/hgrc
125 $ echo '[extensions]' >> .hg/hgrc
126 $ echo 'bookmarks =' >> .hg/hgrc
126 $ echo 'bookmarks =' >> .hg/hgrc
127
127
128 find outgoing
128 find outgoing
129
129
130 $ hg out ssh://user@dummy/remote
130 $ hg out ssh://user@dummy/remote
131 comparing with ssh://user@dummy/remote
131 comparing with ssh://user@dummy/remote
132 searching for changes
132 searching for changes
133 changeset: 1:a28a9d1a809c
133 changeset: 1:a28a9d1a809c
134 tag: tip
134 tag: tip
135 user: test
135 user: test
136 date: Thu Jan 01 00:00:00 1970 +0000
136 date: Thu Jan 01 00:00:00 1970 +0000
137 summary: add
137 summary: add
138
138
139
139
140 find incoming on the remote side
140 find incoming on the remote side
141
141
142 $ hg incoming -R ../remote -e "python ../dummyssh" ssh://user@dummy/local
142 $ hg incoming -R ../remote -e "python ../dummyssh" ssh://user@dummy/local
143 comparing with ssh://user@dummy/local
143 comparing with ssh://user@dummy/local
144 searching for changes
144 searching for changes
145 changeset: 1:a28a9d1a809c
145 changeset: 1:a28a9d1a809c
146 tag: tip
146 tag: tip
147 user: test
147 user: test
148 date: Thu Jan 01 00:00:00 1970 +0000
148 date: Thu Jan 01 00:00:00 1970 +0000
149 summary: add
149 summary: add
150
150
151
151
152 find incoming on the remote side (using absolute path)
152 find incoming on the remote side (using absolute path)
153
153
154 $ hg incoming -R ../remote -e "python ../dummyssh" "ssh://user@dummy/`pwd`"
154 $ hg incoming -R ../remote -e "python ../dummyssh" "ssh://user@dummy/`pwd`"
155 comparing with ssh://user@dummy/$TESTTMP/local
155 comparing with ssh://user@dummy/$TESTTMP/local
156 searching for changes
156 searching for changes
157 changeset: 1:a28a9d1a809c
157 changeset: 1:a28a9d1a809c
158 tag: tip
158 tag: tip
159 user: test
159 user: test
160 date: Thu Jan 01 00:00:00 1970 +0000
160 date: Thu Jan 01 00:00:00 1970 +0000
161 summary: add
161 summary: add
162
162
163
163
164 push
164 push
165
165
166 $ hg push
166 $ hg push
167 pushing to ssh://user@dummy/remote
167 pushing to ssh://user@dummy/remote
168 searching for changes
168 searching for changes
169 remote: adding changesets
169 remote: adding changesets
170 remote: adding manifests
170 remote: adding manifests
171 remote: adding file changes
171 remote: adding file changes
172 remote: added 1 changesets with 1 changes to 1 files
172 remote: added 1 changesets with 1 changes to 1 files
173 $ cd ../remote
173 $ cd ../remote
174
174
175 check remote tip
175 check remote tip
176
176
177 $ hg tip
177 $ hg tip
178 changeset: 1:a28a9d1a809c
178 changeset: 1:a28a9d1a809c
179 tag: tip
179 tag: tip
180 user: test
180 user: test
181 date: Thu Jan 01 00:00:00 1970 +0000
181 date: Thu Jan 01 00:00:00 1970 +0000
182 summary: add
182 summary: add
183
183
184 $ hg verify
184 $ hg verify
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, 2 changesets, 3 total revisions
189 2 files, 2 changesets, 3 total revisions
190 $ hg cat -r tip foo
190 $ hg cat -r tip foo
191 bleah
191 bleah
192 $ echo z > z
192 $ echo z > z
193 $ hg ci -A -m z z
193 $ hg ci -A -m z z
194 created new head
194 created new head
195
195
196 test pushkeys and bookmarks
196 test pushkeys and bookmarks
197
197
198 $ cd ../local
198 $ cd ../local
199 $ echo '[extensions]' >> ../remote/.hg/hgrc
199 $ echo '[extensions]' >> ../remote/.hg/hgrc
200 $ echo 'bookmarks =' >> ../remote/.hg/hgrc
200 $ echo 'bookmarks =' >> ../remote/.hg/hgrc
201 $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote namespaces
201 $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote namespaces
202 bookmarks
202 bookmarks
203 namespaces
203 namespaces
204 $ hg book foo -r 0
204 $ hg book foo -r 0
205 $ hg out -B
205 $ hg out -B
206 comparing with ssh://user@dummy/remote
206 comparing with ssh://user@dummy/remote
207 searching for changed bookmarks
207 searching for changed bookmarks
208 foo 1160648e36ce
208 foo 1160648e36ce
209 $ hg push -B foo
209 $ hg push -B foo
210 pushing to ssh://user@dummy/remote
210 pushing to ssh://user@dummy/remote
211 searching for changes
211 searching for changes
212 no changes found
212 no changes found
213 exporting bookmark foo
213 exporting bookmark foo
214 $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote bookmarks
214 $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote bookmarks
215 foo 1160648e36cec0054048a7edc4110c6f84fde594
215 foo 1160648e36cec0054048a7edc4110c6f84fde594
216 $ hg book -f foo
216 $ hg book -f foo
217 $ hg push
217 $ hg push --traceback
218 pushing to ssh://user@dummy/remote
218 pushing to ssh://user@dummy/remote
219 searching for changes
219 searching for changes
220 no changes found
220 no changes found
221 updating bookmark foo
221 updating bookmark foo
222 $ hg book -d foo
222 $ hg book -d foo
223 $ hg in -B
223 $ hg in -B
224 comparing with ssh://user@dummy/remote
224 comparing with ssh://user@dummy/remote
225 searching for changed bookmarks
225 searching for changed bookmarks
226 foo a28a9d1a809c
226 foo a28a9d1a809c
227 $ hg book -f -r 0 foo
227 $ hg book -f -r 0 foo
228 $ hg pull -B foo
228 $ hg pull -B foo
229 pulling from ssh://user@dummy/remote
229 pulling from ssh://user@dummy/remote
230 searching for changes
230 searching for changes
231 no changes found
231 no changes found
232 updating bookmark foo
232 updating bookmark foo
233 importing bookmark foo
233 importing bookmark foo
234 $ hg book -d foo
234 $ hg book -d foo
235 $ hg push -B foo
235 $ hg push -B foo
236 deleting remote bookmark foo
236 deleting remote bookmark foo
237
237
238 a bad, evil hook that prints to stdout
238 a bad, evil hook that prints to stdout
239
239
240 $ echo '[hooks]' >> ../remote/.hg/hgrc
240 $ echo '[hooks]' >> ../remote/.hg/hgrc
241 $ echo 'changegroup.stdout = python ../badhook' >> ../remote/.hg/hgrc
241 $ echo 'changegroup.stdout = python ../badhook' >> ../remote/.hg/hgrc
242 $ echo r > r
242 $ echo r > r
243 $ hg ci -A -m z r
243 $ hg ci -A -m z r
244
244
245 push should succeed even though it has an unexpected response
245 push should succeed even though it has an unexpected response
246
246
247 $ hg push
247 $ hg push
248 pushing to ssh://user@dummy/remote
248 pushing to ssh://user@dummy/remote
249 searching for changes
249 searching for changes
250 note: unsynced remote changes!
250 note: unsynced remote changes!
251 remote: adding changesets
251 remote: adding changesets
252 remote: adding manifests
252 remote: adding manifests
253 remote: adding file changes
253 remote: adding file changes
254 remote: added 1 changesets with 1 changes to 1 files
254 remote: added 1 changesets with 1 changes to 1 files
255 remote: KABOOM
255 remote: KABOOM
256 $ hg -R ../remote heads
256 $ hg -R ../remote heads
257 changeset: 3:1383141674ec
257 changeset: 3:1383141674ec
258 tag: tip
258 tag: tip
259 parent: 1:a28a9d1a809c
259 parent: 1:a28a9d1a809c
260 user: test
260 user: test
261 date: Thu Jan 01 00:00:00 1970 +0000
261 date: Thu Jan 01 00:00:00 1970 +0000
262 summary: z
262 summary: z
263
263
264 changeset: 2:6c0482d977a3
264 changeset: 2:6c0482d977a3
265 parent: 0:1160648e36ce
265 parent: 0:1160648e36ce
266 user: test
266 user: test
267 date: Thu Jan 01 00:00:00 1970 +0000
267 date: Thu Jan 01 00:00:00 1970 +0000
268 summary: z
268 summary: z
269
269
270 $ cd ..
270 $ cd ..
271 $ cat dummylog
271 $ cat dummylog
272 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
272 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
273 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
273 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
274 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
274 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
275 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
275 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
276 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
276 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
277 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
277 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
278 Got arguments 1:user@dummy 2:hg -R local serve --stdio
278 Got arguments 1:user@dummy 2:hg -R local serve --stdio
279 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
279 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
280 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
280 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
281 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
281 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
282 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
282 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
283 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
283 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
284 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
284 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
285 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
285 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
286 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
286 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
287 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
287 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
288 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
288 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
289 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
289 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
290 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
290 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
291 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
291 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
General Comments 0
You need to be logged in to leave comments. Login now