##// END OF EJS Templates
bookmarks: use UTF-8 storage...
Matt Mackall -
r13048:e298cca2 default
parent child Browse files
Show More
@@ -1,571 +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
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), 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 bookmarks[refspec] = self.changelog.lookup(sha)
237 bookmarks[refspec] = self.changelog.lookup(sha)
237 except:
238 except:
238 pass
239 pass
239 return bookmarks
240 return bookmarks
240
241
241 @util.propertycache
242 @util.propertycache
242 def _bookmarkcurrent(self):
243 def _bookmarkcurrent(self):
243 '''Get the current bookmark
244 '''Get the current bookmark
244
245
245 If we use gittishsh branches we have a current bookmark that
246 If we use gittishsh branches we have a current bookmark that
246 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
247 is stored in .hg/bookmarks.current
248 is stored in .hg/bookmarks.current
248 '''
249 '''
249 mark = None
250 mark = None
250 if os.path.exists(self.join('bookmarks.current')):
251 if os.path.exists(self.join('bookmarks.current')):
251 file = self.opener('bookmarks.current')
252 file = self.opener('bookmarks.current')
252 # No readline() in posixfile_nt, reading everything is cheap
253 # No readline() in posixfile_nt, reading everything is cheap
253 mark = (file.readlines() or [''])[0]
254 mark = (file.readlines() or [''])[0]
254 if mark == '':
255 if mark == '':
255 mark = None
256 mark = None
256 file.close()
257 file.close()
257 return mark
258 return mark
258
259
259 def rollback(self, *args):
260 def rollback(self, *args):
260 if os.path.exists(self.join('undo.bookmarks')):
261 if os.path.exists(self.join('undo.bookmarks')):
261 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
262 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
262 return super(bookmark_repo, self).rollback(*args)
263 return super(bookmark_repo, self).rollback(*args)
263
264
264 def lookup(self, key):
265 def lookup(self, key):
265 if key in self._bookmarks:
266 if key in self._bookmarks:
266 key = self._bookmarks[key]
267 key = self._bookmarks[key]
267 return super(bookmark_repo, self).lookup(key)
268 return super(bookmark_repo, self).lookup(key)
268
269
269 def _bookmarksupdate(self, parents, node):
270 def _bookmarksupdate(self, parents, node):
270 marks = self._bookmarks
271 marks = self._bookmarks
271 update = False
272 update = False
272 if ui.configbool('bookmarks', 'track.current'):
273 if ui.configbool('bookmarks', 'track.current'):
273 mark = self._bookmarkcurrent
274 mark = self._bookmarkcurrent
274 if mark and marks[mark] in parents:
275 if mark and marks[mark] in parents:
275 marks[mark] = node
276 marks[mark] = node
276 update = True
277 update = True
277 else:
278 else:
278 for mark, n in marks.items():
279 for mark, n in marks.items():
279 if n in parents:
280 if n in parents:
280 marks[mark] = node
281 marks[mark] = node
281 update = True
282 update = True
282 if update:
283 if update:
283 write(self)
284 write(self)
284
285
285 def commitctx(self, ctx, error=False):
286 def commitctx(self, ctx, error=False):
286 """Add a revision to the repository and
287 """Add a revision to the repository and
287 move the bookmark"""
288 move the bookmark"""
288 wlock = self.wlock() # do both commit and bookmark with lock held
289 wlock = self.wlock() # do both commit and bookmark with lock held
289 try:
290 try:
290 node = super(bookmark_repo, self).commitctx(ctx, error)
291 node = super(bookmark_repo, self).commitctx(ctx, error)
291 if node is None:
292 if node is None:
292 return None
293 return None
293 parents = self.changelog.parents(node)
294 parents = self.changelog.parents(node)
294 if parents[1] == nullid:
295 if parents[1] == nullid:
295 parents = (parents[0],)
296 parents = (parents[0],)
296
297
297 self._bookmarksupdate(parents, node)
298 self._bookmarksupdate(parents, node)
298 return node
299 return node
299 finally:
300 finally:
300 wlock.release()
301 wlock.release()
301
302
302 def pull(self, remote, heads=None, force=False):
303 def pull(self, remote, heads=None, force=False):
303 result = super(bookmark_repo, self).pull(remote, heads, force)
304 result = super(bookmark_repo, self).pull(remote, heads, force)
304
305
305 self.ui.debug("checking for updated bookmarks\n")
306 self.ui.debug("checking for updated bookmarks\n")
306 rb = remote.listkeys('bookmarks')
307 rb = remote.listkeys('bookmarks')
307 changed = False
308 changed = False
308 for k in rb.keys():
309 for k in rb.keys():
309 if k in self._bookmarks:
310 if k in self._bookmarks:
310 nr, nl = rb[k], self._bookmarks[k]
311 nr, nl = rb[k], self._bookmarks[k]
311 if nr in self:
312 if nr in self:
312 cr = self[nr]
313 cr = self[nr]
313 cl = self[nl]
314 cl = self[nl]
314 if cl.rev() >= cr.rev():
315 if cl.rev() >= cr.rev():
315 continue
316 continue
316 if cr in cl.descendants():
317 if cr in cl.descendants():
317 self._bookmarks[k] = cr.node()
318 self._bookmarks[k] = cr.node()
318 changed = True
319 changed = True
319 self.ui.status(_("updating bookmark %s\n") % k)
320 self.ui.status(_("updating bookmark %s\n") % k)
320 else:
321 else:
321 self.ui.warn(_("not updating divergent"
322 self.ui.warn(_("not updating divergent"
322 " bookmark %s\n") % k)
323 " bookmark %s\n") % k)
323 if changed:
324 if changed:
324 write(repo)
325 write(repo)
325
326
326 return result
327 return result
327
328
328 def push(self, remote, force=False, revs=None, newbranch=False):
329 def push(self, remote, force=False, revs=None, newbranch=False):
329 result = super(bookmark_repo, self).push(remote, force, revs,
330 result = super(bookmark_repo, self).push(remote, force, revs,
330 newbranch)
331 newbranch)
331
332
332 self.ui.debug("checking for updated bookmarks\n")
333 self.ui.debug("checking for updated bookmarks\n")
333 rb = remote.listkeys('bookmarks')
334 rb = remote.listkeys('bookmarks')
334 for k in rb.keys():
335 for k in rb.keys():
335 if k in self._bookmarks:
336 if k in self._bookmarks:
336 nr, nl = rb[k], self._bookmarks[k]
337 nr, nl = rb[k], self._bookmarks[k]
337 if nr in self:
338 if nr in self:
338 cr = self[nr]
339 cr = self[nr]
339 cl = self[nl]
340 cl = self[nl]
340 if cl in cr.descendants():
341 if cl in cr.descendants():
341 r = remote.pushkey('bookmarks', k, nr, nl)
342 r = remote.pushkey('bookmarks', k, nr, nl)
342 if r:
343 if r:
343 self.ui.status(_("updating bookmark %s\n") % k)
344 self.ui.status(_("updating bookmark %s\n") % k)
344 else:
345 else:
345 self.ui.warn(_('updating bookmark %s'
346 self.ui.warn(_('updating bookmark %s'
346 ' failed!\n') % k)
347 ' failed!\n') % k)
347
348
348 return result
349 return result
349
350
350 def addchangegroup(self, *args, **kwargs):
351 def addchangegroup(self, *args, **kwargs):
351 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
352 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
352 if result > 1:
353 if result > 1:
353 # We have more heads than before
354 # We have more heads than before
354 return result
355 return result
355 node = self.changelog.tip()
356 node = self.changelog.tip()
356 parents = self.dirstate.parents()
357 parents = self.dirstate.parents()
357 self._bookmarksupdate(parents, node)
358 self._bookmarksupdate(parents, node)
358 return result
359 return result
359
360
360 def _findtags(self):
361 def _findtags(self):
361 """Merge bookmarks with normal tags"""
362 """Merge bookmarks with normal tags"""
362 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
363 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
363 tags.update(self._bookmarks)
364 tags.update(self._bookmarks)
364 return (tags, tagtypes)
365 return (tags, tagtypes)
365
366
366 if hasattr(repo, 'invalidate'):
367 if hasattr(repo, 'invalidate'):
367 def invalidate(self):
368 def invalidate(self):
368 super(bookmark_repo, self).invalidate()
369 super(bookmark_repo, self).invalidate()
369 for attr in ('_bookmarks', '_bookmarkcurrent'):
370 for attr in ('_bookmarks', '_bookmarkcurrent'):
370 if attr in self.__dict__:
371 if attr in self.__dict__:
371 delattr(self, attr)
372 delattr(self, attr)
372
373
373 repo.__class__ = bookmark_repo
374 repo.__class__ = bookmark_repo
374
375
375 def listbookmarks(repo):
376 def listbookmarks(repo):
376 # 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
377 # support it (e.g., statichttprepository).
378 # support it (e.g., statichttprepository).
378 if not hasattr(repo, '_bookmarks'):
379 if not hasattr(repo, '_bookmarks'):
379 return {}
380 return {}
380
381
381 d = {}
382 d = {}
382 for k, v in repo._bookmarks.iteritems():
383 for k, v in repo._bookmarks.iteritems():
383 d[k] = hex(v)
384 d[k] = hex(v)
384 return d
385 return d
385
386
386 def pushbookmark(repo, key, old, new):
387 def pushbookmark(repo, key, old, new):
387 w = repo.wlock()
388 w = repo.wlock()
388 try:
389 try:
389 marks = repo._bookmarks
390 marks = repo._bookmarks
390 if hex(marks.get(key, '')) != old:
391 if hex(marks.get(key, '')) != old:
391 return False
392 return False
392 if new == '':
393 if new == '':
393 del marks[key]
394 del marks[key]
394 else:
395 else:
395 if new not in repo:
396 if new not in repo:
396 return False
397 return False
397 marks[key] = repo[new].node()
398 marks[key] = repo[new].node()
398 write(repo)
399 write(repo)
399 return True
400 return True
400 finally:
401 finally:
401 w.release()
402 w.release()
402
403
403 def pull(oldpull, ui, repo, source="default", **opts):
404 def pull(oldpull, ui, repo, source="default", **opts):
404 # translate bookmark args to rev args for actual pull
405 # translate bookmark args to rev args for actual pull
405 if opts.get('bookmark'):
406 if opts.get('bookmark'):
406 # this is an unpleasant hack as pull will do this internally
407 # this is an unpleasant hack as pull will do this internally
407 source, branches = hg.parseurl(ui.expandpath(source),
408 source, branches = hg.parseurl(ui.expandpath(source),
408 opts.get('branch'))
409 opts.get('branch'))
409 other = hg.repository(hg.remoteui(repo, opts), source)
410 other = hg.repository(hg.remoteui(repo, opts), source)
410 rb = other.listkeys('bookmarks')
411 rb = other.listkeys('bookmarks')
411
412
412 for b in opts['bookmark']:
413 for b in opts['bookmark']:
413 if b not in rb:
414 if b not in rb:
414 raise util.Abort(_('remote bookmark %s not found!') % b)
415 raise util.Abort(_('remote bookmark %s not found!') % b)
415 opts.setdefault('rev', []).append(b)
416 opts.setdefault('rev', []).append(b)
416
417
417 result = oldpull(ui, repo, source, **opts)
418 result = oldpull(ui, repo, source, **opts)
418
419
419 # update specified bookmarks
420 # update specified bookmarks
420 if opts.get('bookmark'):
421 if opts.get('bookmark'):
421 for b in opts['bookmark']:
422 for b in opts['bookmark']:
422 # explicit pull overrides local bookmark if any
423 # explicit pull overrides local bookmark if any
423 ui.status(_("importing bookmark %s\n") % b)
424 ui.status(_("importing bookmark %s\n") % b)
424 repo._bookmarks[b] = repo[rb[b]].node()
425 repo._bookmarks[b] = repo[rb[b]].node()
425 write(repo)
426 write(repo)
426
427
427 return result
428 return result
428
429
429 def push(oldpush, ui, repo, dest=None, **opts):
430 def push(oldpush, ui, repo, dest=None, **opts):
430 dopush = True
431 dopush = True
431 if opts.get('bookmark'):
432 if opts.get('bookmark'):
432 dopush = False
433 dopush = False
433 for b in opts['bookmark']:
434 for b in opts['bookmark']:
434 if b in repo._bookmarks:
435 if b in repo._bookmarks:
435 dopush = True
436 dopush = True
436 opts.setdefault('rev', []).append(b)
437 opts.setdefault('rev', []).append(b)
437
438
438 result = 0
439 result = 0
439 if dopush:
440 if dopush:
440 result = oldpush(ui, repo, dest, **opts)
441 result = oldpush(ui, repo, dest, **opts)
441
442
442 if opts.get('bookmark'):
443 if opts.get('bookmark'):
443 # this is an unpleasant hack as push will do this internally
444 # this is an unpleasant hack as push will do this internally
444 dest = ui.expandpath(dest or 'default-push', dest or 'default')
445 dest = ui.expandpath(dest or 'default-push', dest or 'default')
445 dest, branches = hg.parseurl(dest, opts.get('branch'))
446 dest, branches = hg.parseurl(dest, opts.get('branch'))
446 other = hg.repository(hg.remoteui(repo, opts), dest)
447 other = hg.repository(hg.remoteui(repo, opts), dest)
447 rb = other.listkeys('bookmarks')
448 rb = other.listkeys('bookmarks')
448 for b in opts['bookmark']:
449 for b in opts['bookmark']:
449 # explicit push overrides remote bookmark if any
450 # explicit push overrides remote bookmark if any
450 if b in repo._bookmarks:
451 if b in repo._bookmarks:
451 ui.status(_("exporting bookmark %s\n") % b)
452 ui.status(_("exporting bookmark %s\n") % b)
452 new = repo[b].hex()
453 new = repo[b].hex()
453 elif b in rb:
454 elif b in rb:
454 ui.status(_("deleting remote bookmark %s\n") % b)
455 ui.status(_("deleting remote bookmark %s\n") % b)
455 new = '' # delete
456 new = '' # delete
456 else:
457 else:
457 ui.warn(_('bookmark %s does not exist on the local '
458 ui.warn(_('bookmark %s does not exist on the local '
458 'or remote repository!\n') % b)
459 'or remote repository!\n') % b)
459 return 2
460 return 2
460 old = rb.get(b, '')
461 old = rb.get(b, '')
461 r = other.pushkey('bookmarks', b, old, new)
462 r = other.pushkey('bookmarks', b, old, new)
462 if not r:
463 if not r:
463 ui.warn(_('updating bookmark %s failed!\n') % b)
464 ui.warn(_('updating bookmark %s failed!\n') % b)
464 if not result:
465 if not result:
465 result = 2
466 result = 2
466
467
467 return result
468 return result
468
469
469 def diffbookmarks(ui, repo, remote):
470 def diffbookmarks(ui, repo, remote):
470 ui.status(_("searching for changed bookmarks\n"))
471 ui.status(_("searching for changed bookmarks\n"))
471
472
472 lmarks = repo.listkeys('bookmarks')
473 lmarks = repo.listkeys('bookmarks')
473 rmarks = remote.listkeys('bookmarks')
474 rmarks = remote.listkeys('bookmarks')
474
475
475 diff = sorted(set(rmarks) - set(lmarks))
476 diff = sorted(set(rmarks) - set(lmarks))
476 for k in diff:
477 for k in diff:
477 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
478 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
478
479
479 if len(diff) <= 0:
480 if len(diff) <= 0:
480 ui.status(_("no changed bookmarks found\n"))
481 ui.status(_("no changed bookmarks found\n"))
481 return 1
482 return 1
482 return 0
483 return 0
483
484
484 def incoming(oldincoming, ui, repo, source="default", **opts):
485 def incoming(oldincoming, ui, repo, source="default", **opts):
485 if opts.get('bookmarks'):
486 if opts.get('bookmarks'):
486 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
487 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
487 other = hg.repository(hg.remoteui(repo, opts), source)
488 other = hg.repository(hg.remoteui(repo, opts), source)
488 ui.status(_('comparing with %s\n') % url.hidepassword(source))
489 ui.status(_('comparing with %s\n') % url.hidepassword(source))
489 return diffbookmarks(ui, repo, other)
490 return diffbookmarks(ui, repo, other)
490 else:
491 else:
491 return oldincoming(ui, repo, source, **opts)
492 return oldincoming(ui, repo, source, **opts)
492
493
493 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
494 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
494 if opts.get('bookmarks'):
495 if opts.get('bookmarks'):
495 dest = ui.expandpath(dest or 'default-push', dest or 'default')
496 dest = ui.expandpath(dest or 'default-push', dest or 'default')
496 dest, branches = hg.parseurl(dest, opts.get('branch'))
497 dest, branches = hg.parseurl(dest, opts.get('branch'))
497 other = hg.repository(hg.remoteui(repo, opts), dest)
498 other = hg.repository(hg.remoteui(repo, opts), dest)
498 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
499 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
499 return diffbookmarks(ui, other, repo)
500 return diffbookmarks(ui, other, repo)
500 else:
501 else:
501 return oldoutgoing(ui, repo, dest, **opts)
502 return oldoutgoing(ui, repo, dest, **opts)
502
503
503 def uisetup(ui):
504 def uisetup(ui):
504 extensions.wrapfunction(repair, "strip", strip)
505 extensions.wrapfunction(repair, "strip", strip)
505 if ui.configbool('bookmarks', 'track.current'):
506 if ui.configbool('bookmarks', 'track.current'):
506 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
507 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
507
508
508 entry = extensions.wrapcommand(commands.table, 'pull', pull)
509 entry = extensions.wrapcommand(commands.table, 'pull', pull)
509 entry[1].append(('B', 'bookmark', [],
510 entry[1].append(('B', 'bookmark', [],
510 _("bookmark to import"),
511 _("bookmark to import"),
511 _('BOOKMARK')))
512 _('BOOKMARK')))
512 entry = extensions.wrapcommand(commands.table, 'push', push)
513 entry = extensions.wrapcommand(commands.table, 'push', push)
513 entry[1].append(('B', 'bookmark', [],
514 entry[1].append(('B', 'bookmark', [],
514 _("bookmark to export"),
515 _("bookmark to export"),
515 _('BOOKMARK')))
516 _('BOOKMARK')))
516 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
517 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
517 entry[1].append(('B', 'bookmarks', False,
518 entry[1].append(('B', 'bookmarks', False,
518 _("compare bookmark")))
519 _("compare bookmark")))
519 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
520 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
520 entry[1].append(('B', 'bookmarks', False,
521 entry[1].append(('B', 'bookmarks', False,
521 _("compare bookmark")))
522 _("compare bookmark")))
522
523
523 pushkey.register('bookmarks', pushbookmark, listbookmarks)
524 pushkey.register('bookmarks', pushbookmark, listbookmarks)
524
525
525 def updatecurbookmark(orig, ui, repo, *args, **opts):
526 def updatecurbookmark(orig, ui, repo, *args, **opts):
526 '''Set the current bookmark
527 '''Set the current bookmark
527
528
528 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
529 file.
530 file.
530 '''
531 '''
531 res = orig(ui, repo, *args, **opts)
532 res = orig(ui, repo, *args, **opts)
532 rev = opts['rev']
533 rev = opts['rev']
533 if not rev and len(args) > 0:
534 if not rev and len(args) > 0:
534 rev = args[0]
535 rev = args[0]
535 setcurrent(repo, rev)
536 setcurrent(repo, rev)
536 return res
537 return res
537
538
538 def bmrevset(repo, subset, x):
539 def bmrevset(repo, subset, x):
539 """``bookmark([name])``
540 """``bookmark([name])``
540 The named bookmark or all bookmarks.
541 The named bookmark or all bookmarks.
541 """
542 """
542 # i18n: "bookmark" is a keyword
543 # i18n: "bookmark" is a keyword
543 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'))
544 if args:
545 if args:
545 bm = revset.getstring(args[0],
546 bm = revset.getstring(args[0],
546 # i18n: "bookmark" is a keyword
547 # i18n: "bookmark" is a keyword
547 _('the argument to bookmark must be a string'))
548 _('the argument to bookmark must be a string'))
548 bmrev = listbookmarks(repo).get(bm, None)
549 bmrev = listbookmarks(repo).get(bm, None)
549 if bmrev:
550 if bmrev:
550 bmrev = repo.changelog.rev(bin(bmrev))
551 bmrev = repo.changelog.rev(bin(bmrev))
551 return [r for r in subset if r == bmrev]
552 return [r for r in subset if r == bmrev]
552 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()])
553 return [r for r in subset if r in bms]
554 return [r for r in subset if r in bms]
554
555
555 def extsetup(ui):
556 def extsetup(ui):
556 revset.symbols['bookmark'] = bmrevset
557 revset.symbols['bookmark'] = bmrevset
557
558
558 cmdtable = {
559 cmdtable = {
559 "bookmarks":
560 "bookmarks":
560 (bookmark,
561 (bookmark,
561 [('f', 'force', False, _('force')),
562 [('f', 'force', False, _('force')),
562 ('r', 'rev', '', _('revision'), _('REV')),
563 ('r', 'rev', '', _('revision'), _('REV')),
563 ('d', 'delete', False, _('delete a given bookmark')),
564 ('d', 'delete', False, _('delete a given bookmark')),
564 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
565 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
565 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
566 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
566 }
567 }
567
568
568 colortable = {'bookmarks.current': 'green'}
569 colortable = {'bookmarks.current': 'green'}
569
570
570 # tell hggettext to extract docstrings from these functions:
571 # tell hggettext to extract docstrings from these functions:
571 i18nfunctions = [bmrevset]
572 i18nfunctions = [bmrevset]
General Comments 0
You need to be logged in to leave comments. Login now