##// END OF EJS Templates
bookmarks: add support for pull --bookmark to import remote bookmarks
Matt Mackall -
r11378:cb21fb1b default
parent child Browse files
Show More
@@ -1,415 +1,446 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 .hgrc::
21 your .hgrc::
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, hex, short
32 from mercurial.node import nullid, nullrev, hex, short
33 from mercurial import util, commands, repair, extensions, pushkey
33 from mercurial import util, commands, repair, extensions, pushkey, hg
34 import os
34 import os
35
35
36 def write(repo):
36 def write(repo):
37 '''Write bookmarks
37 '''Write bookmarks
38
38
39 Write the given bookmark => hash dictionary to the .hg/bookmarks file
39 Write the given bookmark => hash dictionary to the .hg/bookmarks file
40 in a format equal to those of localtags.
40 in a format equal to those of localtags.
41
41
42 We also store a backup of the previous state in undo.bookmarks that
42 We also store a backup of the previous state in undo.bookmarks that
43 can be copied back on rollback.
43 can be copied back on rollback.
44 '''
44 '''
45 refs = repo._bookmarks
45 refs = repo._bookmarks
46 if os.path.exists(repo.join('bookmarks')):
46 if os.path.exists(repo.join('bookmarks')):
47 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
47 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
48 if repo._bookmarkcurrent not in refs:
48 if repo._bookmarkcurrent not in refs:
49 setcurrent(repo, None)
49 setcurrent(repo, None)
50 wlock = repo.wlock()
50 wlock = repo.wlock()
51 try:
51 try:
52 file = repo.opener('bookmarks', 'w', atomictemp=True)
52 file = repo.opener('bookmarks', 'w', atomictemp=True)
53 for refspec, node in refs.iteritems():
53 for refspec, node in refs.iteritems():
54 file.write("%s %s\n" % (hex(node), refspec))
54 file.write("%s %s\n" % (hex(node), refspec))
55 file.rename()
55 file.rename()
56 finally:
56 finally:
57 wlock.release()
57 wlock.release()
58
58
59 def setcurrent(repo, mark):
59 def setcurrent(repo, mark):
60 '''Set the name of the bookmark that we are currently on
60 '''Set the name of the bookmark that we are currently on
61
61
62 Set the name of the bookmark that we are on (hg update <bookmark>).
62 Set the name of the bookmark that we are on (hg update <bookmark>).
63 The name is recorded in .hg/bookmarks.current
63 The name is recorded in .hg/bookmarks.current
64 '''
64 '''
65 current = repo._bookmarkcurrent
65 current = repo._bookmarkcurrent
66 if current == mark:
66 if current == mark:
67 return
67 return
68
68
69 refs = repo._bookmarks
69 refs = repo._bookmarks
70
70
71 # do not update if we do update to a rev equal to the current bookmark
71 # do not update if we do update to a rev equal to the current bookmark
72 if (mark and mark not in refs and
72 if (mark and mark not in refs and
73 current and refs[current] == repo.changectx('.').node()):
73 current and refs[current] == repo.changectx('.').node()):
74 return
74 return
75 if mark not in refs:
75 if mark not in refs:
76 mark = ''
76 mark = ''
77 wlock = repo.wlock()
77 wlock = repo.wlock()
78 try:
78 try:
79 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
79 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
80 file.write(mark)
80 file.write(mark)
81 file.rename()
81 file.rename()
82 finally:
82 finally:
83 wlock.release()
83 wlock.release()
84 repo._bookmarkcurrent = mark
84 repo._bookmarkcurrent = mark
85
85
86 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
86 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
87 '''track a line of development with movable markers
87 '''track a line of development with movable markers
88
88
89 Bookmarks are pointers to certain commits that move when
89 Bookmarks are pointers to certain commits that move when
90 committing. Bookmarks are local. They can be renamed, copied and
90 committing. Bookmarks are local. They can be renamed, copied and
91 deleted. It is possible to use bookmark names in :hg:`merge` and
91 deleted. It is possible to use bookmark names in :hg:`merge` and
92 :hg:`update` to merge and update respectively to a given bookmark.
92 :hg:`update` to merge and update respectively to a given bookmark.
93
93
94 You can use :hg:`bookmark NAME` to set a bookmark on the working
94 You can use :hg:`bookmark NAME` to set a bookmark on the working
95 directory's parent revision with the given name. If you specify
95 directory's parent revision with the given name. If you specify
96 a revision using -r REV (where REV may be an existing bookmark),
96 a revision using -r REV (where REV may be an existing bookmark),
97 the bookmark is assigned to that revision.
97 the bookmark is assigned to that revision.
98 '''
98 '''
99 hexfn = ui.debugflag and hex or short
99 hexfn = ui.debugflag and hex or short
100 marks = repo._bookmarks
100 marks = repo._bookmarks
101 cur = repo.changectx('.').node()
101 cur = repo.changectx('.').node()
102
102
103 if rename:
103 if rename:
104 if rename not in marks:
104 if rename not in marks:
105 raise util.Abort(_("a bookmark of this name does not exist"))
105 raise util.Abort(_("a bookmark of this name does not exist"))
106 if mark in marks and not force:
106 if mark in marks and not force:
107 raise util.Abort(_("a bookmark of the same name already exists"))
107 raise util.Abort(_("a bookmark of the same name already exists"))
108 if mark is None:
108 if mark is None:
109 raise util.Abort(_("new bookmark name required"))
109 raise util.Abort(_("new bookmark name required"))
110 marks[mark] = marks[rename]
110 marks[mark] = marks[rename]
111 del marks[rename]
111 del marks[rename]
112 if repo._bookmarkcurrent == rename:
112 if repo._bookmarkcurrent == rename:
113 setcurrent(repo, mark)
113 setcurrent(repo, mark)
114 write(repo)
114 write(repo)
115 return
115 return
116
116
117 if delete:
117 if delete:
118 if mark is None:
118 if mark is None:
119 raise util.Abort(_("bookmark name required"))
119 raise util.Abort(_("bookmark name required"))
120 if mark not in marks:
120 if mark not in marks:
121 raise util.Abort(_("a bookmark of this name does not exist"))
121 raise util.Abort(_("a bookmark of this name does not exist"))
122 if mark == repo._bookmarkcurrent:
122 if mark == repo._bookmarkcurrent:
123 setcurrent(repo, None)
123 setcurrent(repo, None)
124 del marks[mark]
124 del marks[mark]
125 write(repo)
125 write(repo)
126 return
126 return
127
127
128 if mark != None:
128 if mark != None:
129 if "\n" in mark:
129 if "\n" in mark:
130 raise util.Abort(_("bookmark name cannot contain newlines"))
130 raise util.Abort(_("bookmark name cannot contain newlines"))
131 mark = mark.strip()
131 mark = mark.strip()
132 if mark in marks and not force:
132 if mark in marks and not force:
133 raise util.Abort(_("a bookmark of the same name already exists"))
133 raise util.Abort(_("a bookmark of the same name already exists"))
134 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
134 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
135 and not force):
135 and not force):
136 raise util.Abort(
136 raise util.Abort(
137 _("a bookmark cannot have the name of an existing branch"))
137 _("a bookmark cannot have the name of an existing branch"))
138 if rev:
138 if rev:
139 marks[mark] = repo.lookup(rev)
139 marks[mark] = repo.lookup(rev)
140 else:
140 else:
141 marks[mark] = repo.changectx('.').node()
141 marks[mark] = repo.changectx('.').node()
142 setcurrent(repo, mark)
142 setcurrent(repo, mark)
143 write(repo)
143 write(repo)
144 return
144 return
145
145
146 if mark is None:
146 if mark is None:
147 if rev:
147 if rev:
148 raise util.Abort(_("bookmark name required"))
148 raise util.Abort(_("bookmark name required"))
149 if len(marks) == 0:
149 if len(marks) == 0:
150 ui.status(_("no bookmarks set\n"))
150 ui.status(_("no bookmarks set\n"))
151 else:
151 else:
152 for bmark, n in marks.iteritems():
152 for bmark, n in marks.iteritems():
153 if ui.configbool('bookmarks', 'track.current'):
153 if ui.configbool('bookmarks', 'track.current'):
154 current = repo._bookmarkcurrent
154 current = repo._bookmarkcurrent
155 if bmark == current and n == cur:
155 if bmark == current and n == cur:
156 prefix, label = '*', 'bookmarks.current'
156 prefix, label = '*', 'bookmarks.current'
157 else:
157 else:
158 prefix, label = ' ', ''
158 prefix, label = ' ', ''
159 else:
159 else:
160 if n == cur:
160 if n == cur:
161 prefix, label = '*', 'bookmarks.current'
161 prefix, label = '*', 'bookmarks.current'
162 else:
162 else:
163 prefix, label = ' ', ''
163 prefix, label = ' ', ''
164
164
165 if ui.quiet:
165 if ui.quiet:
166 ui.write("%s\n" % bmark, label=label)
166 ui.write("%s\n" % bmark, label=label)
167 else:
167 else:
168 ui.write(" %s %-25s %d:%s\n" % (
168 ui.write(" %s %-25s %d:%s\n" % (
169 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
169 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
170 label=label)
170 label=label)
171 return
171 return
172
172
173 def _revstostrip(changelog, node):
173 def _revstostrip(changelog, node):
174 srev = changelog.rev(node)
174 srev = changelog.rev(node)
175 tostrip = [srev]
175 tostrip = [srev]
176 saveheads = []
176 saveheads = []
177 for r in xrange(srev, len(changelog)):
177 for r in xrange(srev, len(changelog)):
178 parents = changelog.parentrevs(r)
178 parents = changelog.parentrevs(r)
179 if parents[0] in tostrip or parents[1] in tostrip:
179 if parents[0] in tostrip or parents[1] in tostrip:
180 tostrip.append(r)
180 tostrip.append(r)
181 if parents[1] != nullrev:
181 if parents[1] != nullrev:
182 for p in parents:
182 for p in parents:
183 if p not in tostrip and p > srev:
183 if p not in tostrip and p > srev:
184 saveheads.append(p)
184 saveheads.append(p)
185 return [r for r in tostrip if r not in saveheads]
185 return [r for r in tostrip if r not in saveheads]
186
186
187 def strip(oldstrip, ui, repo, node, backup="all"):
187 def strip(oldstrip, ui, repo, node, backup="all"):
188 """Strip bookmarks if revisions are stripped using
188 """Strip bookmarks if revisions are stripped using
189 the mercurial.strip method. This usually happens during
189 the mercurial.strip method. This usually happens during
190 qpush and qpop"""
190 qpush and qpop"""
191 revisions = _revstostrip(repo.changelog, node)
191 revisions = _revstostrip(repo.changelog, node)
192 marks = repo._bookmarks
192 marks = repo._bookmarks
193 update = []
193 update = []
194 for mark, n in marks.iteritems():
194 for mark, n in marks.iteritems():
195 if repo.changelog.rev(n) in revisions:
195 if repo.changelog.rev(n) in revisions:
196 update.append(mark)
196 update.append(mark)
197 oldstrip(ui, repo, node, backup)
197 oldstrip(ui, repo, node, backup)
198 if len(update) > 0:
198 if len(update) > 0:
199 for m in update:
199 for m in update:
200 marks[m] = repo.changectx('.').node()
200 marks[m] = repo.changectx('.').node()
201 write(repo)
201 write(repo)
202
202
203 def reposetup(ui, repo):
203 def reposetup(ui, repo):
204 if not repo.local():
204 if not repo.local():
205 return
205 return
206
206
207 class bookmark_repo(repo.__class__):
207 class bookmark_repo(repo.__class__):
208
208
209 @util.propertycache
209 @util.propertycache
210 def _bookmarks(self):
210 def _bookmarks(self):
211 '''Parse .hg/bookmarks file and return a dictionary
211 '''Parse .hg/bookmarks file and return a dictionary
212
212
213 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
213 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
214 in the .hg/bookmarks file. They are read returned as a dictionary
214 in the .hg/bookmarks file. They are read returned as a dictionary
215 with name => hash values.
215 with name => hash values.
216 '''
216 '''
217 try:
217 try:
218 bookmarks = {}
218 bookmarks = {}
219 for line in self.opener('bookmarks'):
219 for line in self.opener('bookmarks'):
220 sha, refspec = line.strip().split(' ', 1)
220 sha, refspec = line.strip().split(' ', 1)
221 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
221 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
222 except:
222 except:
223 pass
223 pass
224 return bookmarks
224 return bookmarks
225
225
226 @util.propertycache
226 @util.propertycache
227 def _bookmarkcurrent(self):
227 def _bookmarkcurrent(self):
228 '''Get the current bookmark
228 '''Get the current bookmark
229
229
230 If we use gittishsh branches we have a current bookmark that
230 If we use gittishsh branches we have a current bookmark that
231 we are on. This function returns the name of the bookmark. It
231 we are on. This function returns the name of the bookmark. It
232 is stored in .hg/bookmarks.current
232 is stored in .hg/bookmarks.current
233 '''
233 '''
234 mark = None
234 mark = None
235 if os.path.exists(self.join('bookmarks.current')):
235 if os.path.exists(self.join('bookmarks.current')):
236 file = self.opener('bookmarks.current')
236 file = self.opener('bookmarks.current')
237 # No readline() in posixfile_nt, reading everything is cheap
237 # No readline() in posixfile_nt, reading everything is cheap
238 mark = (file.readlines() or [''])[0]
238 mark = (file.readlines() or [''])[0]
239 if mark == '':
239 if mark == '':
240 mark = None
240 mark = None
241 file.close()
241 file.close()
242 return mark
242 return mark
243
243
244 def rollback(self, *args):
244 def rollback(self, *args):
245 if os.path.exists(self.join('undo.bookmarks')):
245 if os.path.exists(self.join('undo.bookmarks')):
246 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
246 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
247 return super(bookmark_repo, self).rollback(*args)
247 return super(bookmark_repo, self).rollback(*args)
248
248
249 def lookup(self, key):
249 def lookup(self, key):
250 if key in self._bookmarks:
250 if key in self._bookmarks:
251 key = self._bookmarks[key]
251 key = self._bookmarks[key]
252 return super(bookmark_repo, self).lookup(key)
252 return super(bookmark_repo, self).lookup(key)
253
253
254 def _bookmarksupdate(self, parents, node):
254 def _bookmarksupdate(self, parents, node):
255 marks = self._bookmarks
255 marks = self._bookmarks
256 update = False
256 update = False
257 if ui.configbool('bookmarks', 'track.current'):
257 if ui.configbool('bookmarks', 'track.current'):
258 mark = self._bookmarkcurrent
258 mark = self._bookmarkcurrent
259 if mark and marks[mark] in parents:
259 if mark and marks[mark] in parents:
260 marks[mark] = node
260 marks[mark] = node
261 update = True
261 update = True
262 else:
262 else:
263 for mark, n in marks.items():
263 for mark, n in marks.items():
264 if n in parents:
264 if n in parents:
265 marks[mark] = node
265 marks[mark] = node
266 update = True
266 update = True
267 if update:
267 if update:
268 write(self)
268 write(self)
269
269
270 def commitctx(self, ctx, error=False):
270 def commitctx(self, ctx, error=False):
271 """Add a revision to the repository and
271 """Add a revision to the repository and
272 move the bookmark"""
272 move the bookmark"""
273 wlock = self.wlock() # do both commit and bookmark with lock held
273 wlock = self.wlock() # do both commit and bookmark with lock held
274 try:
274 try:
275 node = super(bookmark_repo, self).commitctx(ctx, error)
275 node = super(bookmark_repo, self).commitctx(ctx, error)
276 if node is None:
276 if node is None:
277 return None
277 return None
278 parents = self.changelog.parents(node)
278 parents = self.changelog.parents(node)
279 if parents[1] == nullid:
279 if parents[1] == nullid:
280 parents = (parents[0],)
280 parents = (parents[0],)
281
281
282 self._bookmarksupdate(parents, node)
282 self._bookmarksupdate(parents, node)
283 return node
283 return node
284 finally:
284 finally:
285 wlock.release()
285 wlock.release()
286
286
287 def pull(self, remote, heads=None, force=False):
287 def pull(self, remote, heads=None, force=False):
288 result = super(bookmark_repo, self).pull(remote, heads, force)
288 result = super(bookmark_repo, self).pull(remote, heads, force)
289
289
290 self.ui.debug("checking for updated bookmarks\n")
290 self.ui.debug("checking for updated bookmarks\n")
291 rb = remote.listkeys('bookmarks')
291 rb = remote.listkeys('bookmarks')
292 changes = 0
292 changes = 0
293 for k in rb.keys():
293 for k in rb.keys():
294 if k in self._bookmarks:
294 if k in self._bookmarks:
295 nr, nl = rb[k], self._bookmarks[k]
295 nr, nl = rb[k], self._bookmarks[k]
296 if nr in self:
296 if nr in self:
297 cr = self[nr]
297 cr = self[nr]
298 cl = self[nl]
298 cl = self[nl]
299 if cl.rev() >= cr.rev():
299 if cl.rev() >= cr.rev():
300 continue
300 continue
301 if cr in cl.descendants():
301 if cr in cl.descendants():
302 self._bookmarks[k] = cr.node()
302 self._bookmarks[k] = cr.node()
303 changes += 1
303 changes += 1
304 self.ui.status(_("updating bookmark %s\n") % k)
304 self.ui.status(_("updating bookmark %s\n") % k)
305 else:
305 else:
306 self.ui.warn(_("not updating divergent"
306 self.ui.warn(_("not updating divergent"
307 " bookmark %s\n") % k)
307 " bookmark %s\n") % k)
308 if changes:
308 if changes:
309 write(repo)
309 write(repo)
310
310
311 return result
311 return result
312
312
313 def push(self, remote, force=False, revs=None, newbranch=False):
313 def push(self, remote, force=False, revs=None, newbranch=False):
314 result = super(bookmark_repo, self).push(remote, force, revs,
314 result = super(bookmark_repo, self).push(remote, force, revs,
315 newbranch)
315 newbranch)
316
316
317 self.ui.debug("checking for updated bookmarks\n")
317 self.ui.debug("checking for updated bookmarks\n")
318 rb = remote.listkeys('bookmarks')
318 rb = remote.listkeys('bookmarks')
319 for k in rb.keys():
319 for k in rb.keys():
320 if k in self._bookmarks:
320 if k in self._bookmarks:
321 nr, nl = rb[k], self._bookmarks[k]
321 nr, nl = rb[k], self._bookmarks[k]
322 if nr in self:
322 if nr in self:
323 cr = self[nr]
323 cr = self[nr]
324 cl = self[nl]
324 cl = self[nl]
325 if cl in cr.descendants():
325 if cl in cr.descendants():
326 r = remote.pushkey('bookmarks', k, nr, nl)
326 r = remote.pushkey('bookmarks', k, nr, nl)
327 if r:
327 if r:
328 self.ui.status(_("updating bookmark %s\n") % k)
328 self.ui.status(_("updating bookmark %s\n") % k)
329 else:
329 else:
330 self.ui.warn(_("failed to update bookmark"
330 self.ui.warn(_("failed to update bookmark"
331 " %s!\n") % k)
331 " %s!\n") % k)
332
332
333 return result
333 return result
334
334
335 def addchangegroup(self, source, srctype, url, emptyok=False):
335 def addchangegroup(self, source, srctype, url, emptyok=False):
336 parents = self.dirstate.parents()
336 parents = self.dirstate.parents()
337
337
338 result = super(bookmark_repo, self).addchangegroup(
338 result = super(bookmark_repo, self).addchangegroup(
339 source, srctype, url, emptyok)
339 source, srctype, url, emptyok)
340 if result > 1:
340 if result > 1:
341 # We have more heads than before
341 # We have more heads than before
342 return result
342 return result
343 node = self.changelog.tip()
343 node = self.changelog.tip()
344
344
345 self._bookmarksupdate(parents, node)
345 self._bookmarksupdate(parents, node)
346 return result
346 return result
347
347
348 def _findtags(self):
348 def _findtags(self):
349 """Merge bookmarks with normal tags"""
349 """Merge bookmarks with normal tags"""
350 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
350 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
351 tags.update(self._bookmarks)
351 tags.update(self._bookmarks)
352 return (tags, tagtypes)
352 return (tags, tagtypes)
353
353
354 if hasattr(repo, 'invalidate'):
354 if hasattr(repo, 'invalidate'):
355 def invalidate(self):
355 def invalidate(self):
356 super(bookmark_repo, self).invalidate()
356 super(bookmark_repo, self).invalidate()
357 for attr in ('_bookmarks', '_bookmarkcurrent'):
357 for attr in ('_bookmarks', '_bookmarkcurrent'):
358 if attr in self.__dict__:
358 if attr in self.__dict__:
359 delattr(self, attr)
359 delattr(self, attr)
360
360
361 repo.__class__ = bookmark_repo
361 repo.__class__ = bookmark_repo
362
362
363 def listbookmarks(repo):
363 def listbookmarks(repo):
364 d = {}
364 d = {}
365 for k, v in repo._bookmarks.iteritems():
365 for k, v in repo._bookmarks.iteritems():
366 d[k] = hex(v)
366 d[k] = hex(v)
367 return d
367 return d
368
368
369 def pushbookmark(repo, key, old, new):
369 def pushbookmark(repo, key, old, new):
370 w = repo.wlock()
370 w = repo.wlock()
371 try:
371 try:
372 marks = repo._bookmarks
372 marks = repo._bookmarks
373 if hex(marks.get(key, '')) != old:
373 if hex(marks.get(key, '')) != old:
374 return False
374 return False
375 if new == '':
375 if new == '':
376 del marks[key]
376 del marks[key]
377 else:
377 else:
378 if new not in repo:
378 if new not in repo:
379 return False
379 return False
380 marks[key] = repo[new].node()
380 marks[key] = repo[new].node()
381 write(repo)
381 write(repo)
382 return True
382 return True
383 finally:
383 finally:
384 w.release()
384 w.release()
385
385
386 def pull(oldpull, ui, repo, source="default", **opts):
387 # translate bookmark args to rev args for actual pull
388 if opts.get('bookmark'):
389 # this is an unpleasant hack as pull will do this internally
390 source, branches = hg.parseurl(ui.expandpath(source),
391 opts.get('branch'))
392 other = hg.repository(hg.remoteui(repo, opts), source)
393 rb = other.listkeys('bookmarks')
394
395 for b in opts['bookmark']:
396 if b not in rb:
397 raise util.Abort(_('remote bookmark %s not found!') % b)
398 opts.setdefault('rev', []).append(b)
399
400 result = oldpull(ui, repo, source, **opts)
401
402 # update specified bookmarks
403 if opts.get('bookmark'):
404 for b in opts['bookmark']:
405 # explicit pull overrides local bookmark if any
406 ui.status(_("importing bookmark %s\n") % b)
407 repo._bookmarks[b] = repo[rb[b]].node()
408 write(repo)
409
410 return result
411
386 def uisetup(ui):
412 def uisetup(ui):
387 extensions.wrapfunction(repair, "strip", strip)
413 extensions.wrapfunction(repair, "strip", strip)
388 if ui.configbool('bookmarks', 'track.current'):
414 if ui.configbool('bookmarks', 'track.current'):
389 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
415 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
416
417 entry = extensions.wrapcommand(commands.table, 'pull', pull)
418 entry[1].append(('B', 'bookmark', [],
419 _("bookmark to import")))
420
390 pushkey.register('bookmarks', pushbookmark, listbookmarks)
421 pushkey.register('bookmarks', pushbookmark, listbookmarks)
391
422
392 def updatecurbookmark(orig, ui, repo, *args, **opts):
423 def updatecurbookmark(orig, ui, repo, *args, **opts):
393 '''Set the current bookmark
424 '''Set the current bookmark
394
425
395 If the user updates to a bookmark we update the .hg/bookmarks.current
426 If the user updates to a bookmark we update the .hg/bookmarks.current
396 file.
427 file.
397 '''
428 '''
398 res = orig(ui, repo, *args, **opts)
429 res = orig(ui, repo, *args, **opts)
399 rev = opts['rev']
430 rev = opts['rev']
400 if not rev and len(args) > 0:
431 if not rev and len(args) > 0:
401 rev = args[0]
432 rev = args[0]
402 setcurrent(repo, rev)
433 setcurrent(repo, rev)
403 return res
434 return res
404
435
405 cmdtable = {
436 cmdtable = {
406 "bookmarks":
437 "bookmarks":
407 (bookmark,
438 (bookmark,
408 [('f', 'force', False, _('force')),
439 [('f', 'force', False, _('force')),
409 ('r', 'rev', '', _('revision'), _('REV')),
440 ('r', 'rev', '', _('revision'), _('REV')),
410 ('d', 'delete', False, _('delete a given bookmark')),
441 ('d', 'delete', False, _('delete a given bookmark')),
411 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
442 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
412 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
443 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
413 }
444 }
414
445
415 colortable = {'bookmarks.current': 'green'}
446 colortable = {'bookmarks.current': 'green'}
General Comments 0
You need to be logged in to leave comments. Login now