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