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