##// END OF EJS Templates
bookmarks: always write undo file...
David Soria Parra -
r13306:146bad85 stable
parent child Browse files
Show More
@@ -1,579 +1,578 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
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
47
48 try:
48 try:
49 bms = repo.opener('bookmarks').read()
49 bms = repo.opener('bookmarks').read()
50 except IOError:
50 except IOError:
51 bms = None
51 bms = ''
52 if bms is not None:
52 repo.opener('undo.bookmarks', 'w').write(bms)
53 repo.opener('undo.bookmarks', 'w').write(bms)
54
53
55 if repo._bookmarkcurrent not in refs:
54 if repo._bookmarkcurrent not in refs:
56 setcurrent(repo, None)
55 setcurrent(repo, None)
57 wlock = repo.wlock()
56 wlock = repo.wlock()
58 try:
57 try:
59 file = repo.opener('bookmarks', 'w', atomictemp=True)
58 file = repo.opener('bookmarks', 'w', atomictemp=True)
60 for refspec, node in refs.iteritems():
59 for refspec, node in refs.iteritems():
61 file.write("%s %s\n" % (hex(node), refspec))
60 file.write("%s %s\n" % (hex(node), refspec))
62 file.rename()
61 file.rename()
63
62
64 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
63 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
65 try:
64 try:
66 os.utime(repo.sjoin('00changelog.i'), None)
65 os.utime(repo.sjoin('00changelog.i'), None)
67 except OSError:
66 except OSError:
68 pass
67 pass
69
68
70 finally:
69 finally:
71 wlock.release()
70 wlock.release()
72
71
73 def setcurrent(repo, mark):
72 def setcurrent(repo, mark):
74 '''Set the name of the bookmark that we are currently on
73 '''Set the name of the bookmark that we are currently on
75
74
76 Set the name of the bookmark that we are on (hg update <bookmark>).
75 Set the name of the bookmark that we are on (hg update <bookmark>).
77 The name is recorded in .hg/bookmarks.current
76 The name is recorded in .hg/bookmarks.current
78 '''
77 '''
79 current = repo._bookmarkcurrent
78 current = repo._bookmarkcurrent
80 if current == mark:
79 if current == mark:
81 return
80 return
82
81
83 refs = repo._bookmarks
82 refs = repo._bookmarks
84
83
85 # do not update if we do update to a rev equal to the current bookmark
84 # do not update if we do update to a rev equal to the current bookmark
86 if (mark and mark not in refs and
85 if (mark and mark not in refs and
87 current and refs[current] == repo.changectx('.').node()):
86 current and refs[current] == repo.changectx('.').node()):
88 return
87 return
89 if mark not in refs:
88 if mark not in refs:
90 mark = ''
89 mark = ''
91 wlock = repo.wlock()
90 wlock = repo.wlock()
92 try:
91 try:
93 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
92 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
94 file.write(mark)
93 file.write(mark)
95 file.rename()
94 file.rename()
96 finally:
95 finally:
97 wlock.release()
96 wlock.release()
98 repo._bookmarkcurrent = mark
97 repo._bookmarkcurrent = mark
99
98
100 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
99 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
101 '''track a line of development with movable markers
100 '''track a line of development with movable markers
102
101
103 Bookmarks are pointers to certain commits that move when
102 Bookmarks are pointers to certain commits that move when
104 committing. Bookmarks are local. They can be renamed, copied and
103 committing. Bookmarks are local. They can be renamed, copied and
105 deleted. It is possible to use bookmark names in :hg:`merge` and
104 deleted. It is possible to use bookmark names in :hg:`merge` and
106 :hg:`update` to merge and update respectively to a given bookmark.
105 :hg:`update` to merge and update respectively to a given bookmark.
107
106
108 You can use :hg:`bookmark NAME` to set a bookmark on the working
107 You can use :hg:`bookmark NAME` to set a bookmark on the working
109 directory's parent revision with the given name. If you specify
108 directory's parent revision with the given name. If you specify
110 a revision using -r REV (where REV may be an existing bookmark),
109 a revision using -r REV (where REV may be an existing bookmark),
111 the bookmark is assigned to that revision.
110 the bookmark is assigned to that revision.
112
111
113 Bookmarks can be pushed and pulled between repositories (see :hg:`help
112 Bookmarks can be pushed and pulled between repositories (see :hg:`help
114 push` and :hg:`help pull`). This requires the bookmark extension to be
113 push` and :hg:`help pull`). This requires the bookmark extension to be
115 enabled for both the local and remote repositories.
114 enabled for both the local and remote repositories.
116 '''
115 '''
117 hexfn = ui.debugflag and hex or short
116 hexfn = ui.debugflag and hex or short
118 marks = repo._bookmarks
117 marks = repo._bookmarks
119 cur = repo.changectx('.').node()
118 cur = repo.changectx('.').node()
120
119
121 if rename:
120 if rename:
122 if rename not in marks:
121 if rename not in marks:
123 raise util.Abort(_("a bookmark of this name does not exist"))
122 raise util.Abort(_("a bookmark of this name does not exist"))
124 if mark in marks and not force:
123 if mark in marks and not force:
125 raise util.Abort(_("a bookmark of the same name already exists"))
124 raise util.Abort(_("a bookmark of the same name already exists"))
126 if mark is None:
125 if mark is None:
127 raise util.Abort(_("new bookmark name required"))
126 raise util.Abort(_("new bookmark name required"))
128 marks[mark] = marks[rename]
127 marks[mark] = marks[rename]
129 del marks[rename]
128 del marks[rename]
130 if repo._bookmarkcurrent == rename:
129 if repo._bookmarkcurrent == rename:
131 setcurrent(repo, mark)
130 setcurrent(repo, mark)
132 write(repo)
131 write(repo)
133 return
132 return
134
133
135 if delete:
134 if delete:
136 if mark is None:
135 if mark is None:
137 raise util.Abort(_("bookmark name required"))
136 raise util.Abort(_("bookmark name required"))
138 if mark not in marks:
137 if mark not in marks:
139 raise util.Abort(_("a bookmark of this name does not exist"))
138 raise util.Abort(_("a bookmark of this name does not exist"))
140 if mark == repo._bookmarkcurrent:
139 if mark == repo._bookmarkcurrent:
141 setcurrent(repo, None)
140 setcurrent(repo, None)
142 del marks[mark]
141 del marks[mark]
143 write(repo)
142 write(repo)
144 return
143 return
145
144
146 if mark != None:
145 if mark != None:
147 if "\n" in mark:
146 if "\n" in mark:
148 raise util.Abort(_("bookmark name cannot contain newlines"))
147 raise util.Abort(_("bookmark name cannot contain newlines"))
149 mark = mark.strip()
148 mark = mark.strip()
150 if not mark:
149 if not mark:
151 raise util.Abort(_("bookmark names cannot consist entirely of "
150 raise util.Abort(_("bookmark names cannot consist entirely of "
152 "whitespace"))
151 "whitespace"))
153 if mark in marks and not force:
152 if mark in marks and not force:
154 raise util.Abort(_("a bookmark of the same name already exists"))
153 raise util.Abort(_("a bookmark of the same name already exists"))
155 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
154 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
156 and not force):
155 and not force):
157 raise util.Abort(
156 raise util.Abort(
158 _("a bookmark cannot have the name of an existing branch"))
157 _("a bookmark cannot have the name of an existing branch"))
159 if rev:
158 if rev:
160 marks[mark] = repo.lookup(rev)
159 marks[mark] = repo.lookup(rev)
161 else:
160 else:
162 marks[mark] = repo.changectx('.').node()
161 marks[mark] = repo.changectx('.').node()
163 setcurrent(repo, mark)
162 setcurrent(repo, mark)
164 write(repo)
163 write(repo)
165 return
164 return
166
165
167 if mark is None:
166 if mark is None:
168 if rev:
167 if rev:
169 raise util.Abort(_("bookmark name required"))
168 raise util.Abort(_("bookmark name required"))
170 if len(marks) == 0:
169 if len(marks) == 0:
171 ui.status(_("no bookmarks set\n"))
170 ui.status(_("no bookmarks set\n"))
172 else:
171 else:
173 for bmark, n in marks.iteritems():
172 for bmark, n in marks.iteritems():
174 if ui.configbool('bookmarks', 'track.current'):
173 if ui.configbool('bookmarks', 'track.current'):
175 current = repo._bookmarkcurrent
174 current = repo._bookmarkcurrent
176 if bmark == current and n == cur:
175 if bmark == current and n == cur:
177 prefix, label = '*', 'bookmarks.current'
176 prefix, label = '*', 'bookmarks.current'
178 else:
177 else:
179 prefix, label = ' ', ''
178 prefix, label = ' ', ''
180 else:
179 else:
181 if n == cur:
180 if n == cur:
182 prefix, label = '*', 'bookmarks.current'
181 prefix, label = '*', 'bookmarks.current'
183 else:
182 else:
184 prefix, label = ' ', ''
183 prefix, label = ' ', ''
185
184
186 if ui.quiet:
185 if ui.quiet:
187 ui.write("%s\n" % bmark, label=label)
186 ui.write("%s\n" % bmark, label=label)
188 else:
187 else:
189 ui.write(" %s %-25s %d:%s\n" % (
188 ui.write(" %s %-25s %d:%s\n" % (
190 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
189 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
191 label=label)
190 label=label)
192 return
191 return
193
192
194 def _revstostrip(changelog, node):
193 def _revstostrip(changelog, node):
195 srev = changelog.rev(node)
194 srev = changelog.rev(node)
196 tostrip = [srev]
195 tostrip = [srev]
197 saveheads = []
196 saveheads = []
198 for r in xrange(srev, len(changelog)):
197 for r in xrange(srev, len(changelog)):
199 parents = changelog.parentrevs(r)
198 parents = changelog.parentrevs(r)
200 if parents[0] in tostrip or parents[1] in tostrip:
199 if parents[0] in tostrip or parents[1] in tostrip:
201 tostrip.append(r)
200 tostrip.append(r)
202 if parents[1] != nullrev:
201 if parents[1] != nullrev:
203 for p in parents:
202 for p in parents:
204 if p not in tostrip and p > srev:
203 if p not in tostrip and p > srev:
205 saveheads.append(p)
204 saveheads.append(p)
206 return [r for r in tostrip if r not in saveheads]
205 return [r for r in tostrip if r not in saveheads]
207
206
208 def strip(oldstrip, ui, repo, node, backup="all"):
207 def strip(oldstrip, ui, repo, node, backup="all"):
209 """Strip bookmarks if revisions are stripped using
208 """Strip bookmarks if revisions are stripped using
210 the mercurial.strip method. This usually happens during
209 the mercurial.strip method. This usually happens during
211 qpush and qpop"""
210 qpush and qpop"""
212 revisions = _revstostrip(repo.changelog, node)
211 revisions = _revstostrip(repo.changelog, node)
213 marks = repo._bookmarks
212 marks = repo._bookmarks
214 update = []
213 update = []
215 for mark, n in marks.iteritems():
214 for mark, n in marks.iteritems():
216 if repo.changelog.rev(n) in revisions:
215 if repo.changelog.rev(n) in revisions:
217 update.append(mark)
216 update.append(mark)
218 oldstrip(ui, repo, node, backup)
217 oldstrip(ui, repo, node, backup)
219 if len(update) > 0:
218 if len(update) > 0:
220 for m in update:
219 for m in update:
221 marks[m] = repo.changectx('.').node()
220 marks[m] = repo.changectx('.').node()
222 write(repo)
221 write(repo)
223
222
224 def reposetup(ui, repo):
223 def reposetup(ui, repo):
225 if not repo.local():
224 if not repo.local():
226 return
225 return
227
226
228 class bookmark_repo(repo.__class__):
227 class bookmark_repo(repo.__class__):
229
228
230 @util.propertycache
229 @util.propertycache
231 def _bookmarks(self):
230 def _bookmarks(self):
232 '''Parse .hg/bookmarks file and return a dictionary
231 '''Parse .hg/bookmarks file and return a dictionary
233
232
234 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
233 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
235 in the .hg/bookmarks file.
234 in the .hg/bookmarks file.
236 Read the file and return a (name=>nodeid) dictionary
235 Read the file and return a (name=>nodeid) dictionary
237 '''
236 '''
238 try:
237 try:
239 bookmarks = {}
238 bookmarks = {}
240 for line in self.opener('bookmarks'):
239 for line in self.opener('bookmarks'):
241 sha, refspec = line.strip().split(' ', 1)
240 sha, refspec = line.strip().split(' ', 1)
242 bookmarks[refspec] = self.changelog.lookup(sha)
241 bookmarks[refspec] = self.changelog.lookup(sha)
243 except:
242 except:
244 pass
243 pass
245 return bookmarks
244 return bookmarks
246
245
247 @util.propertycache
246 @util.propertycache
248 def _bookmarkcurrent(self):
247 def _bookmarkcurrent(self):
249 '''Get the current bookmark
248 '''Get the current bookmark
250
249
251 If we use gittishsh branches we have a current bookmark that
250 If we use gittishsh branches we have a current bookmark that
252 we are on. This function returns the name of the bookmark. It
251 we are on. This function returns the name of the bookmark. It
253 is stored in .hg/bookmarks.current
252 is stored in .hg/bookmarks.current
254 '''
253 '''
255 mark = None
254 mark = None
256 if os.path.exists(self.join('bookmarks.current')):
255 if os.path.exists(self.join('bookmarks.current')):
257 file = self.opener('bookmarks.current')
256 file = self.opener('bookmarks.current')
258 # No readline() in posixfile_nt, reading everything is cheap
257 # No readline() in posixfile_nt, reading everything is cheap
259 mark = (file.readlines() or [''])[0]
258 mark = (file.readlines() or [''])[0]
260 if mark == '':
259 if mark == '':
261 mark = None
260 mark = None
262 file.close()
261 file.close()
263 return mark
262 return mark
264
263
265 def rollback(self, *args):
264 def rollback(self, *args):
266 if os.path.exists(self.join('undo.bookmarks')):
265 if os.path.exists(self.join('undo.bookmarks')):
267 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
266 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
268 return super(bookmark_repo, self).rollback(*args)
267 return super(bookmark_repo, self).rollback(*args)
269
268
270 def lookup(self, key):
269 def lookup(self, key):
271 if key in self._bookmarks:
270 if key in self._bookmarks:
272 key = self._bookmarks[key]
271 key = self._bookmarks[key]
273 return super(bookmark_repo, self).lookup(key)
272 return super(bookmark_repo, self).lookup(key)
274
273
275 def _bookmarksupdate(self, parents, node):
274 def _bookmarksupdate(self, parents, node):
276 marks = self._bookmarks
275 marks = self._bookmarks
277 update = False
276 update = False
278 if ui.configbool('bookmarks', 'track.current'):
277 if ui.configbool('bookmarks', 'track.current'):
279 mark = self._bookmarkcurrent
278 mark = self._bookmarkcurrent
280 if mark and marks[mark] in parents:
279 if mark and marks[mark] in parents:
281 marks[mark] = node
280 marks[mark] = node
282 update = True
281 update = True
283 else:
282 else:
284 for mark, n in marks.items():
283 for mark, n in marks.items():
285 if n in parents:
284 if n in parents:
286 marks[mark] = node
285 marks[mark] = node
287 update = True
286 update = True
288 if update:
287 if update:
289 write(self)
288 write(self)
290
289
291 def commitctx(self, ctx, error=False):
290 def commitctx(self, ctx, error=False):
292 """Add a revision to the repository and
291 """Add a revision to the repository and
293 move the bookmark"""
292 move the bookmark"""
294 wlock = self.wlock() # do both commit and bookmark with lock held
293 wlock = self.wlock() # do both commit and bookmark with lock held
295 try:
294 try:
296 node = super(bookmark_repo, self).commitctx(ctx, error)
295 node = super(bookmark_repo, self).commitctx(ctx, error)
297 if node is None:
296 if node is None:
298 return None
297 return None
299 parents = self.changelog.parents(node)
298 parents = self.changelog.parents(node)
300 if parents[1] == nullid:
299 if parents[1] == nullid:
301 parents = (parents[0],)
300 parents = (parents[0],)
302
301
303 self._bookmarksupdate(parents, node)
302 self._bookmarksupdate(parents, node)
304 return node
303 return node
305 finally:
304 finally:
306 wlock.release()
305 wlock.release()
307
306
308 def pull(self, remote, heads=None, force=False):
307 def pull(self, remote, heads=None, force=False):
309 result = super(bookmark_repo, self).pull(remote, heads, force)
308 result = super(bookmark_repo, self).pull(remote, heads, force)
310
309
311 self.ui.debug("checking for updated bookmarks\n")
310 self.ui.debug("checking for updated bookmarks\n")
312 rb = remote.listkeys('bookmarks')
311 rb = remote.listkeys('bookmarks')
313 changed = False
312 changed = False
314 for k in rb.keys():
313 for k in rb.keys():
315 if k in self._bookmarks:
314 if k in self._bookmarks:
316 nr, nl = rb[k], self._bookmarks[k]
315 nr, nl = rb[k], self._bookmarks[k]
317 if nr in self:
316 if nr in self:
318 cr = self[nr]
317 cr = self[nr]
319 cl = self[nl]
318 cl = self[nl]
320 if cl.rev() >= cr.rev():
319 if cl.rev() >= cr.rev():
321 continue
320 continue
322 if cr in cl.descendants():
321 if cr in cl.descendants():
323 self._bookmarks[k] = cr.node()
322 self._bookmarks[k] = cr.node()
324 changed = True
323 changed = True
325 self.ui.status(_("updating bookmark %s\n") % k)
324 self.ui.status(_("updating bookmark %s\n") % k)
326 else:
325 else:
327 self.ui.warn(_("not updating divergent"
326 self.ui.warn(_("not updating divergent"
328 " bookmark %s\n") % k)
327 " bookmark %s\n") % k)
329 if changed:
328 if changed:
330 write(repo)
329 write(repo)
331
330
332 return result
331 return result
333
332
334 def push(self, remote, force=False, revs=None, newbranch=False):
333 def push(self, remote, force=False, revs=None, newbranch=False):
335 result = super(bookmark_repo, self).push(remote, force, revs,
334 result = super(bookmark_repo, self).push(remote, force, revs,
336 newbranch)
335 newbranch)
337
336
338 self.ui.debug("checking for updated bookmarks\n")
337 self.ui.debug("checking for updated bookmarks\n")
339 rb = remote.listkeys('bookmarks')
338 rb = remote.listkeys('bookmarks')
340 for k in rb.keys():
339 for k in rb.keys():
341 if k in self._bookmarks:
340 if k in self._bookmarks:
342 nr, nl = rb[k], self._bookmarks[k]
341 nr, nl = rb[k], self._bookmarks[k]
343 if nr in self:
342 if nr in self:
344 cr = self[nr]
343 cr = self[nr]
345 cl = self[nl]
344 cl = self[nl]
346 if cl in cr.descendants():
345 if cl in cr.descendants():
347 r = remote.pushkey('bookmarks', k, nr, nl)
346 r = remote.pushkey('bookmarks', k, nr, nl)
348 if r:
347 if r:
349 self.ui.status(_("updating bookmark %s\n") % k)
348 self.ui.status(_("updating bookmark %s\n") % k)
350 else:
349 else:
351 self.ui.warn(_('updating bookmark %s'
350 self.ui.warn(_('updating bookmark %s'
352 ' failed!\n') % k)
351 ' failed!\n') % k)
353
352
354 return result
353 return result
355
354
356 def addchangegroup(self, *args, **kwargs):
355 def addchangegroup(self, *args, **kwargs):
357 parents = self.dirstate.parents()
356 parents = self.dirstate.parents()
358
357
359 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
358 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
360 if result > 1:
359 if result > 1:
361 # We have more heads than before
360 # We have more heads than before
362 return result
361 return result
363 node = self.changelog.tip()
362 node = self.changelog.tip()
364
363
365 self._bookmarksupdate(parents, node)
364 self._bookmarksupdate(parents, node)
366 return result
365 return result
367
366
368 def _findtags(self):
367 def _findtags(self):
369 """Merge bookmarks with normal tags"""
368 """Merge bookmarks with normal tags"""
370 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
369 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
371 tags.update(self._bookmarks)
370 tags.update(self._bookmarks)
372 return (tags, tagtypes)
371 return (tags, tagtypes)
373
372
374 if hasattr(repo, 'invalidate'):
373 if hasattr(repo, 'invalidate'):
375 def invalidate(self):
374 def invalidate(self):
376 super(bookmark_repo, self).invalidate()
375 super(bookmark_repo, self).invalidate()
377 for attr in ('_bookmarks', '_bookmarkcurrent'):
376 for attr in ('_bookmarks', '_bookmarkcurrent'):
378 if attr in self.__dict__:
377 if attr in self.__dict__:
379 delattr(self, attr)
378 delattr(self, attr)
380
379
381 repo.__class__ = bookmark_repo
380 repo.__class__ = bookmark_repo
382
381
383 def listbookmarks(repo):
382 def listbookmarks(repo):
384 # We may try to list bookmarks on a repo type that does not
383 # We may try to list bookmarks on a repo type that does not
385 # support it (e.g., statichttprepository).
384 # support it (e.g., statichttprepository).
386 if not hasattr(repo, '_bookmarks'):
385 if not hasattr(repo, '_bookmarks'):
387 return {}
386 return {}
388
387
389 d = {}
388 d = {}
390 for k, v in repo._bookmarks.iteritems():
389 for k, v in repo._bookmarks.iteritems():
391 d[k] = hex(v)
390 d[k] = hex(v)
392 return d
391 return d
393
392
394 def pushbookmark(repo, key, old, new):
393 def pushbookmark(repo, key, old, new):
395 w = repo.wlock()
394 w = repo.wlock()
396 try:
395 try:
397 marks = repo._bookmarks
396 marks = repo._bookmarks
398 if hex(marks.get(key, '')) != old:
397 if hex(marks.get(key, '')) != old:
399 return False
398 return False
400 if new == '':
399 if new == '':
401 del marks[key]
400 del marks[key]
402 else:
401 else:
403 if new not in repo:
402 if new not in repo:
404 return False
403 return False
405 marks[key] = repo[new].node()
404 marks[key] = repo[new].node()
406 write(repo)
405 write(repo)
407 return True
406 return True
408 finally:
407 finally:
409 w.release()
408 w.release()
410
409
411 def pull(oldpull, ui, repo, source="default", **opts):
410 def pull(oldpull, ui, repo, source="default", **opts):
412 # translate bookmark args to rev args for actual pull
411 # translate bookmark args to rev args for actual pull
413 if opts.get('bookmark'):
412 if opts.get('bookmark'):
414 # this is an unpleasant hack as pull will do this internally
413 # this is an unpleasant hack as pull will do this internally
415 source, branches = hg.parseurl(ui.expandpath(source),
414 source, branches = hg.parseurl(ui.expandpath(source),
416 opts.get('branch'))
415 opts.get('branch'))
417 other = hg.repository(hg.remoteui(repo, opts), source)
416 other = hg.repository(hg.remoteui(repo, opts), source)
418 rb = other.listkeys('bookmarks')
417 rb = other.listkeys('bookmarks')
419
418
420 for b in opts['bookmark']:
419 for b in opts['bookmark']:
421 if b not in rb:
420 if b not in rb:
422 raise util.Abort(_('remote bookmark %s not found!') % b)
421 raise util.Abort(_('remote bookmark %s not found!') % b)
423 opts.setdefault('rev', []).append(b)
422 opts.setdefault('rev', []).append(b)
424
423
425 result = oldpull(ui, repo, source, **opts)
424 result = oldpull(ui, repo, source, **opts)
426
425
427 # update specified bookmarks
426 # update specified bookmarks
428 if opts.get('bookmark'):
427 if opts.get('bookmark'):
429 for b in opts['bookmark']:
428 for b in opts['bookmark']:
430 # explicit pull overrides local bookmark if any
429 # explicit pull overrides local bookmark if any
431 ui.status(_("importing bookmark %s\n") % b)
430 ui.status(_("importing bookmark %s\n") % b)
432 repo._bookmarks[b] = repo[rb[b]].node()
431 repo._bookmarks[b] = repo[rb[b]].node()
433 write(repo)
432 write(repo)
434
433
435 return result
434 return result
436
435
437 def push(oldpush, ui, repo, dest=None, **opts):
436 def push(oldpush, ui, repo, dest=None, **opts):
438 dopush = True
437 dopush = True
439 if opts.get('bookmark'):
438 if opts.get('bookmark'):
440 dopush = False
439 dopush = False
441 for b in opts['bookmark']:
440 for b in opts['bookmark']:
442 if b in repo._bookmarks:
441 if b in repo._bookmarks:
443 dopush = True
442 dopush = True
444 opts.setdefault('rev', []).append(b)
443 opts.setdefault('rev', []).append(b)
445
444
446 result = 0
445 result = 0
447 if dopush:
446 if dopush:
448 result = oldpush(ui, repo, dest, **opts)
447 result = oldpush(ui, repo, dest, **opts)
449
448
450 if opts.get('bookmark'):
449 if opts.get('bookmark'):
451 # this is an unpleasant hack as push will do this internally
450 # this is an unpleasant hack as push will do this internally
452 dest = ui.expandpath(dest or 'default-push', dest or 'default')
451 dest = ui.expandpath(dest or 'default-push', dest or 'default')
453 dest, branches = hg.parseurl(dest, opts.get('branch'))
452 dest, branches = hg.parseurl(dest, opts.get('branch'))
454 other = hg.repository(hg.remoteui(repo, opts), dest)
453 other = hg.repository(hg.remoteui(repo, opts), dest)
455 rb = other.listkeys('bookmarks')
454 rb = other.listkeys('bookmarks')
456 for b in opts['bookmark']:
455 for b in opts['bookmark']:
457 # explicit push overrides remote bookmark if any
456 # explicit push overrides remote bookmark if any
458 if b in repo._bookmarks:
457 if b in repo._bookmarks:
459 ui.status(_("exporting bookmark %s\n") % b)
458 ui.status(_("exporting bookmark %s\n") % b)
460 new = repo[b].hex()
459 new = repo[b].hex()
461 elif b in rb:
460 elif b in rb:
462 ui.status(_("deleting remote bookmark %s\n") % b)
461 ui.status(_("deleting remote bookmark %s\n") % b)
463 new = '' # delete
462 new = '' # delete
464 else:
463 else:
465 ui.warn(_('bookmark %s does not exist on the local '
464 ui.warn(_('bookmark %s does not exist on the local '
466 'or remote repository!\n') % b)
465 'or remote repository!\n') % b)
467 return 2
466 return 2
468 old = rb.get(b, '')
467 old = rb.get(b, '')
469 r = other.pushkey('bookmarks', b, old, new)
468 r = other.pushkey('bookmarks', b, old, new)
470 if not r:
469 if not r:
471 ui.warn(_('updating bookmark %s failed!\n') % b)
470 ui.warn(_('updating bookmark %s failed!\n') % b)
472 if not result:
471 if not result:
473 result = 2
472 result = 2
474
473
475 return result
474 return result
476
475
477 def diffbookmarks(ui, repo, remote):
476 def diffbookmarks(ui, repo, remote):
478 ui.status(_("searching for changed bookmarks\n"))
477 ui.status(_("searching for changed bookmarks\n"))
479
478
480 lmarks = repo.listkeys('bookmarks')
479 lmarks = repo.listkeys('bookmarks')
481 rmarks = remote.listkeys('bookmarks')
480 rmarks = remote.listkeys('bookmarks')
482
481
483 diff = sorted(set(rmarks) - set(lmarks))
482 diff = sorted(set(rmarks) - set(lmarks))
484 for k in diff:
483 for k in diff:
485 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
484 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
486
485
487 if len(diff) <= 0:
486 if len(diff) <= 0:
488 ui.status(_("no changed bookmarks found\n"))
487 ui.status(_("no changed bookmarks found\n"))
489 return 1
488 return 1
490 return 0
489 return 0
491
490
492 def incoming(oldincoming, ui, repo, source="default", **opts):
491 def incoming(oldincoming, ui, repo, source="default", **opts):
493 if opts.get('bookmarks'):
492 if opts.get('bookmarks'):
494 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
493 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
495 other = hg.repository(hg.remoteui(repo, opts), source)
494 other = hg.repository(hg.remoteui(repo, opts), source)
496 ui.status(_('comparing with %s\n') % url.hidepassword(source))
495 ui.status(_('comparing with %s\n') % url.hidepassword(source))
497 return diffbookmarks(ui, repo, other)
496 return diffbookmarks(ui, repo, other)
498 else:
497 else:
499 return oldincoming(ui, repo, source, **opts)
498 return oldincoming(ui, repo, source, **opts)
500
499
501 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
500 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
502 if opts.get('bookmarks'):
501 if opts.get('bookmarks'):
503 dest = ui.expandpath(dest or 'default-push', dest or 'default')
502 dest = ui.expandpath(dest or 'default-push', dest or 'default')
504 dest, branches = hg.parseurl(dest, opts.get('branch'))
503 dest, branches = hg.parseurl(dest, opts.get('branch'))
505 other = hg.repository(hg.remoteui(repo, opts), dest)
504 other = hg.repository(hg.remoteui(repo, opts), dest)
506 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
505 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
507 return diffbookmarks(ui, other, repo)
506 return diffbookmarks(ui, other, repo)
508 else:
507 else:
509 return oldoutgoing(ui, repo, dest, **opts)
508 return oldoutgoing(ui, repo, dest, **opts)
510
509
511 def uisetup(ui):
510 def uisetup(ui):
512 extensions.wrapfunction(repair, "strip", strip)
511 extensions.wrapfunction(repair, "strip", strip)
513 if ui.configbool('bookmarks', 'track.current'):
512 if ui.configbool('bookmarks', 'track.current'):
514 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
513 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
515
514
516 entry = extensions.wrapcommand(commands.table, 'pull', pull)
515 entry = extensions.wrapcommand(commands.table, 'pull', pull)
517 entry[1].append(('B', 'bookmark', [],
516 entry[1].append(('B', 'bookmark', [],
518 _("bookmark to import"),
517 _("bookmark to import"),
519 _('BOOKMARK')))
518 _('BOOKMARK')))
520 entry = extensions.wrapcommand(commands.table, 'push', push)
519 entry = extensions.wrapcommand(commands.table, 'push', push)
521 entry[1].append(('B', 'bookmark', [],
520 entry[1].append(('B', 'bookmark', [],
522 _("bookmark to export"),
521 _("bookmark to export"),
523 _('BOOKMARK')))
522 _('BOOKMARK')))
524 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
523 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
525 entry[1].append(('B', 'bookmarks', False,
524 entry[1].append(('B', 'bookmarks', False,
526 _("compare bookmark")))
525 _("compare bookmark")))
527 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
526 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
528 entry[1].append(('B', 'bookmarks', False,
527 entry[1].append(('B', 'bookmarks', False,
529 _("compare bookmark")))
528 _("compare bookmark")))
530
529
531 pushkey.register('bookmarks', pushbookmark, listbookmarks)
530 pushkey.register('bookmarks', pushbookmark, listbookmarks)
532
531
533 def updatecurbookmark(orig, ui, repo, *args, **opts):
532 def updatecurbookmark(orig, ui, repo, *args, **opts):
534 '''Set the current bookmark
533 '''Set the current bookmark
535
534
536 If the user updates to a bookmark we update the .hg/bookmarks.current
535 If the user updates to a bookmark we update the .hg/bookmarks.current
537 file.
536 file.
538 '''
537 '''
539 res = orig(ui, repo, *args, **opts)
538 res = orig(ui, repo, *args, **opts)
540 rev = opts['rev']
539 rev = opts['rev']
541 if not rev and len(args) > 0:
540 if not rev and len(args) > 0:
542 rev = args[0]
541 rev = args[0]
543 setcurrent(repo, rev)
542 setcurrent(repo, rev)
544 return res
543 return res
545
544
546 def bmrevset(repo, subset, x):
545 def bmrevset(repo, subset, x):
547 """``bookmark([name])``
546 """``bookmark([name])``
548 The named bookmark or all bookmarks.
547 The named bookmark or all bookmarks.
549 """
548 """
550 # i18n: "bookmark" is a keyword
549 # i18n: "bookmark" is a keyword
551 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
550 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
552 if args:
551 if args:
553 bm = revset.getstring(args[0],
552 bm = revset.getstring(args[0],
554 # i18n: "bookmark" is a keyword
553 # i18n: "bookmark" is a keyword
555 _('the argument to bookmark must be a string'))
554 _('the argument to bookmark must be a string'))
556 bmrev = listbookmarks(repo).get(bm, None)
555 bmrev = listbookmarks(repo).get(bm, None)
557 if bmrev:
556 if bmrev:
558 bmrev = repo.changelog.rev(bin(bmrev))
557 bmrev = repo.changelog.rev(bin(bmrev))
559 return [r for r in subset if r == bmrev]
558 return [r for r in subset if r == bmrev]
560 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
559 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
561 return [r for r in subset if r in bms]
560 return [r for r in subset if r in bms]
562
561
563 def extsetup(ui):
562 def extsetup(ui):
564 revset.symbols['bookmark'] = bmrevset
563 revset.symbols['bookmark'] = bmrevset
565
564
566 cmdtable = {
565 cmdtable = {
567 "bookmarks":
566 "bookmarks":
568 (bookmark,
567 (bookmark,
569 [('f', 'force', False, _('force')),
568 [('f', 'force', False, _('force')),
570 ('r', 'rev', '', _('revision'), _('REV')),
569 ('r', 'rev', '', _('revision'), _('REV')),
571 ('d', 'delete', False, _('delete a given bookmark')),
570 ('d', 'delete', False, _('delete a given bookmark')),
572 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
571 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
573 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
572 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
574 }
573 }
575
574
576 colortable = {'bookmarks.current': 'green'}
575 colortable = {'bookmarks.current': 'green'}
577
576
578 # tell hggettext to extract docstrings from these functions:
577 # tell hggettext to extract docstrings from these functions:
579 i18nfunctions = [bmrevset]
578 i18nfunctions = [bmrevset]
General Comments 0
You need to be logged in to leave comments. Login now