##// END OF EJS Templates
bookmarks: don't allow name to contain whitespaces only
Idan Kamara -
r11704:18c47562 stable
parent child Browse files
Show More
@@ -1,531 +1,534 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, 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:
140 raise util.Abort(_("bookmark names cannot consist entirely of "
141 "whitespace"))
139 if mark in marks and not force:
142 if mark in marks and not force:
140 raise util.Abort(_("a bookmark of the same name already exists"))
143 raise util.Abort(_("a bookmark of the same name already exists"))
141 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
144 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
142 and not force):
145 and not force):
143 raise util.Abort(
146 raise util.Abort(
144 _("a bookmark cannot have the name of an existing branch"))
147 _("a bookmark cannot have the name of an existing branch"))
145 if rev:
148 if rev:
146 marks[mark] = repo.lookup(rev)
149 marks[mark] = repo.lookup(rev)
147 else:
150 else:
148 marks[mark] = repo.changectx('.').node()
151 marks[mark] = repo.changectx('.').node()
149 setcurrent(repo, mark)
152 setcurrent(repo, mark)
150 write(repo)
153 write(repo)
151 return
154 return
152
155
153 if mark is None:
156 if mark is None:
154 if rev:
157 if rev:
155 raise util.Abort(_("bookmark name required"))
158 raise util.Abort(_("bookmark name required"))
156 if len(marks) == 0:
159 if len(marks) == 0:
157 ui.status(_("no bookmarks set\n"))
160 ui.status(_("no bookmarks set\n"))
158 else:
161 else:
159 for bmark, n in marks.iteritems():
162 for bmark, n in marks.iteritems():
160 if ui.configbool('bookmarks', 'track.current'):
163 if ui.configbool('bookmarks', 'track.current'):
161 current = repo._bookmarkcurrent
164 current = repo._bookmarkcurrent
162 if bmark == current and n == cur:
165 if bmark == current and n == cur:
163 prefix, label = '*', 'bookmarks.current'
166 prefix, label = '*', 'bookmarks.current'
164 else:
167 else:
165 prefix, label = ' ', ''
168 prefix, label = ' ', ''
166 else:
169 else:
167 if n == cur:
170 if n == cur:
168 prefix, label = '*', 'bookmarks.current'
171 prefix, label = '*', 'bookmarks.current'
169 else:
172 else:
170 prefix, label = ' ', ''
173 prefix, label = ' ', ''
171
174
172 if ui.quiet:
175 if ui.quiet:
173 ui.write("%s\n" % bmark, label=label)
176 ui.write("%s\n" % bmark, label=label)
174 else:
177 else:
175 ui.write(" %s %-25s %d:%s\n" % (
178 ui.write(" %s %-25s %d:%s\n" % (
176 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
179 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
177 label=label)
180 label=label)
178 return
181 return
179
182
180 def _revstostrip(changelog, node):
183 def _revstostrip(changelog, node):
181 srev = changelog.rev(node)
184 srev = changelog.rev(node)
182 tostrip = [srev]
185 tostrip = [srev]
183 saveheads = []
186 saveheads = []
184 for r in xrange(srev, len(changelog)):
187 for r in xrange(srev, len(changelog)):
185 parents = changelog.parentrevs(r)
188 parents = changelog.parentrevs(r)
186 if parents[0] in tostrip or parents[1] in tostrip:
189 if parents[0] in tostrip or parents[1] in tostrip:
187 tostrip.append(r)
190 tostrip.append(r)
188 if parents[1] != nullrev:
191 if parents[1] != nullrev:
189 for p in parents:
192 for p in parents:
190 if p not in tostrip and p > srev:
193 if p not in tostrip and p > srev:
191 saveheads.append(p)
194 saveheads.append(p)
192 return [r for r in tostrip if r not in saveheads]
195 return [r for r in tostrip if r not in saveheads]
193
196
194 def strip(oldstrip, ui, repo, node, backup="all"):
197 def strip(oldstrip, ui, repo, node, backup="all"):
195 """Strip bookmarks if revisions are stripped using
198 """Strip bookmarks if revisions are stripped using
196 the mercurial.strip method. This usually happens during
199 the mercurial.strip method. This usually happens during
197 qpush and qpop"""
200 qpush and qpop"""
198 revisions = _revstostrip(repo.changelog, node)
201 revisions = _revstostrip(repo.changelog, node)
199 marks = repo._bookmarks
202 marks = repo._bookmarks
200 update = []
203 update = []
201 for mark, n in marks.iteritems():
204 for mark, n in marks.iteritems():
202 if repo.changelog.rev(n) in revisions:
205 if repo.changelog.rev(n) in revisions:
203 update.append(mark)
206 update.append(mark)
204 oldstrip(ui, repo, node, backup)
207 oldstrip(ui, repo, node, backup)
205 if len(update) > 0:
208 if len(update) > 0:
206 for m in update:
209 for m in update:
207 marks[m] = repo.changectx('.').node()
210 marks[m] = repo.changectx('.').node()
208 write(repo)
211 write(repo)
209
212
210 def reposetup(ui, repo):
213 def reposetup(ui, repo):
211 if not repo.local():
214 if not repo.local():
212 return
215 return
213
216
214 class bookmark_repo(repo.__class__):
217 class bookmark_repo(repo.__class__):
215
218
216 @util.propertycache
219 @util.propertycache
217 def _bookmarks(self):
220 def _bookmarks(self):
218 '''Parse .hg/bookmarks file and return a dictionary
221 '''Parse .hg/bookmarks file and return a dictionary
219
222
220 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
223 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
221 in the .hg/bookmarks file.
224 in the .hg/bookmarks file.
222 Read the file and return a (name=>nodeid) dictionary
225 Read the file and return a (name=>nodeid) dictionary
223 '''
226 '''
224 try:
227 try:
225 bookmarks = {}
228 bookmarks = {}
226 for line in self.opener('bookmarks'):
229 for line in self.opener('bookmarks'):
227 sha, refspec = line.strip().split(' ', 1)
230 sha, refspec = line.strip().split(' ', 1)
228 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
231 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
229 except:
232 except:
230 pass
233 pass
231 return bookmarks
234 return bookmarks
232
235
233 @util.propertycache
236 @util.propertycache
234 def _bookmarkcurrent(self):
237 def _bookmarkcurrent(self):
235 '''Get the current bookmark
238 '''Get the current bookmark
236
239
237 If we use gittishsh branches we have a current bookmark that
240 If we use gittishsh branches we have a current bookmark that
238 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
239 is stored in .hg/bookmarks.current
242 is stored in .hg/bookmarks.current
240 '''
243 '''
241 mark = None
244 mark = None
242 if os.path.exists(self.join('bookmarks.current')):
245 if os.path.exists(self.join('bookmarks.current')):
243 file = self.opener('bookmarks.current')
246 file = self.opener('bookmarks.current')
244 # No readline() in posixfile_nt, reading everything is cheap
247 # No readline() in posixfile_nt, reading everything is cheap
245 mark = (file.readlines() or [''])[0]
248 mark = (file.readlines() or [''])[0]
246 if mark == '':
249 if mark == '':
247 mark = None
250 mark = None
248 file.close()
251 file.close()
249 return mark
252 return mark
250
253
251 def rollback(self, *args):
254 def rollback(self, *args):
252 if os.path.exists(self.join('undo.bookmarks')):
255 if os.path.exists(self.join('undo.bookmarks')):
253 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
256 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
254 return super(bookmark_repo, self).rollback(*args)
257 return super(bookmark_repo, self).rollback(*args)
255
258
256 def lookup(self, key):
259 def lookup(self, key):
257 if key in self._bookmarks:
260 if key in self._bookmarks:
258 key = self._bookmarks[key]
261 key = self._bookmarks[key]
259 return super(bookmark_repo, self).lookup(key)
262 return super(bookmark_repo, self).lookup(key)
260
263
261 def _bookmarksupdate(self, parents, node):
264 def _bookmarksupdate(self, parents, node):
262 marks = self._bookmarks
265 marks = self._bookmarks
263 update = False
266 update = False
264 if ui.configbool('bookmarks', 'track.current'):
267 if ui.configbool('bookmarks', 'track.current'):
265 mark = self._bookmarkcurrent
268 mark = self._bookmarkcurrent
266 if mark and marks[mark] in parents:
269 if mark and marks[mark] in parents:
267 marks[mark] = node
270 marks[mark] = node
268 update = True
271 update = True
269 else:
272 else:
270 for mark, n in marks.items():
273 for mark, n in marks.items():
271 if n in parents:
274 if n in parents:
272 marks[mark] = node
275 marks[mark] = node
273 update = True
276 update = True
274 if update:
277 if update:
275 write(self)
278 write(self)
276
279
277 def commitctx(self, ctx, error=False):
280 def commitctx(self, ctx, error=False):
278 """Add a revision to the repository and
281 """Add a revision to the repository and
279 move the bookmark"""
282 move the bookmark"""
280 wlock = self.wlock() # do both commit and bookmark with lock held
283 wlock = self.wlock() # do both commit and bookmark with lock held
281 try:
284 try:
282 node = super(bookmark_repo, self).commitctx(ctx, error)
285 node = super(bookmark_repo, self).commitctx(ctx, error)
283 if node is None:
286 if node is None:
284 return None
287 return None
285 parents = self.changelog.parents(node)
288 parents = self.changelog.parents(node)
286 if parents[1] == nullid:
289 if parents[1] == nullid:
287 parents = (parents[0],)
290 parents = (parents[0],)
288
291
289 self._bookmarksupdate(parents, node)
292 self._bookmarksupdate(parents, node)
290 return node
293 return node
291 finally:
294 finally:
292 wlock.release()
295 wlock.release()
293
296
294 def pull(self, remote, heads=None, force=False):
297 def pull(self, remote, heads=None, force=False):
295 result = super(bookmark_repo, self).pull(remote, heads, force)
298 result = super(bookmark_repo, self).pull(remote, heads, force)
296
299
297 self.ui.debug("checking for updated bookmarks\n")
300 self.ui.debug("checking for updated bookmarks\n")
298 rb = remote.listkeys('bookmarks')
301 rb = remote.listkeys('bookmarks')
299 changes = 0
302 changes = 0
300 for k in rb.keys():
303 for k in rb.keys():
301 if k in self._bookmarks:
304 if k in self._bookmarks:
302 nr, nl = rb[k], self._bookmarks[k]
305 nr, nl = rb[k], self._bookmarks[k]
303 if nr in self:
306 if nr in self:
304 cr = self[nr]
307 cr = self[nr]
305 cl = self[nl]
308 cl = self[nl]
306 if cl.rev() >= cr.rev():
309 if cl.rev() >= cr.rev():
307 continue
310 continue
308 if cr in cl.descendants():
311 if cr in cl.descendants():
309 self._bookmarks[k] = cr.node()
312 self._bookmarks[k] = cr.node()
310 changes += 1
313 changes += 1
311 self.ui.status(_("updating bookmark %s\n") % k)
314 self.ui.status(_("updating bookmark %s\n") % k)
312 else:
315 else:
313 self.ui.warn(_("not updating divergent"
316 self.ui.warn(_("not updating divergent"
314 " bookmark %s\n") % k)
317 " bookmark %s\n") % k)
315 if changes:
318 if changes:
316 write(repo)
319 write(repo)
317
320
318 return result
321 return result
319
322
320 def push(self, remote, force=False, revs=None, newbranch=False):
323 def push(self, remote, force=False, revs=None, newbranch=False):
321 result = super(bookmark_repo, self).push(remote, force, revs,
324 result = super(bookmark_repo, self).push(remote, force, revs,
322 newbranch)
325 newbranch)
323
326
324 self.ui.debug("checking for updated bookmarks\n")
327 self.ui.debug("checking for updated bookmarks\n")
325 rb = remote.listkeys('bookmarks')
328 rb = remote.listkeys('bookmarks')
326 for k in rb.keys():
329 for k in rb.keys():
327 if k in self._bookmarks:
330 if k in self._bookmarks:
328 nr, nl = rb[k], self._bookmarks[k]
331 nr, nl = rb[k], self._bookmarks[k]
329 if nr in self:
332 if nr in self:
330 cr = self[nr]
333 cr = self[nr]
331 cl = self[nl]
334 cl = self[nl]
332 if cl in cr.descendants():
335 if cl in cr.descendants():
333 r = remote.pushkey('bookmarks', k, nr, nl)
336 r = remote.pushkey('bookmarks', k, nr, nl)
334 if r:
337 if r:
335 self.ui.status(_("updating bookmark %s\n") % k)
338 self.ui.status(_("updating bookmark %s\n") % k)
336 else:
339 else:
337 self.ui.warn(_('updating bookmark %s'
340 self.ui.warn(_('updating bookmark %s'
338 ' failed!\n') % k)
341 ' failed!\n') % k)
339
342
340 return result
343 return result
341
344
342 def addchangegroup(self, *args, **kwargs):
345 def addchangegroup(self, *args, **kwargs):
343 parents = self.dirstate.parents()
346 parents = self.dirstate.parents()
344
347
345 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
348 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
346 if result > 1:
349 if result > 1:
347 # We have more heads than before
350 # We have more heads than before
348 return result
351 return result
349 node = self.changelog.tip()
352 node = self.changelog.tip()
350
353
351 self._bookmarksupdate(parents, node)
354 self._bookmarksupdate(parents, node)
352 return result
355 return result
353
356
354 def _findtags(self):
357 def _findtags(self):
355 """Merge bookmarks with normal tags"""
358 """Merge bookmarks with normal tags"""
356 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
359 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
357 tags.update(self._bookmarks)
360 tags.update(self._bookmarks)
358 return (tags, tagtypes)
361 return (tags, tagtypes)
359
362
360 if hasattr(repo, 'invalidate'):
363 if hasattr(repo, 'invalidate'):
361 def invalidate(self):
364 def invalidate(self):
362 super(bookmark_repo, self).invalidate()
365 super(bookmark_repo, self).invalidate()
363 for attr in ('_bookmarks', '_bookmarkcurrent'):
366 for attr in ('_bookmarks', '_bookmarkcurrent'):
364 if attr in self.__dict__:
367 if attr in self.__dict__:
365 delattr(self, attr)
368 delattr(self, attr)
366
369
367 repo.__class__ = bookmark_repo
370 repo.__class__ = bookmark_repo
368
371
369 def listbookmarks(repo):
372 def listbookmarks(repo):
370 d = {}
373 d = {}
371 for k, v in repo._bookmarks.iteritems():
374 for k, v in repo._bookmarks.iteritems():
372 d[k] = hex(v)
375 d[k] = hex(v)
373 return d
376 return d
374
377
375 def pushbookmark(repo, key, old, new):
378 def pushbookmark(repo, key, old, new):
376 w = repo.wlock()
379 w = repo.wlock()
377 try:
380 try:
378 marks = repo._bookmarks
381 marks = repo._bookmarks
379 if hex(marks.get(key, '')) != old:
382 if hex(marks.get(key, '')) != old:
380 return False
383 return False
381 if new == '':
384 if new == '':
382 del marks[key]
385 del marks[key]
383 else:
386 else:
384 if new not in repo:
387 if new not in repo:
385 return False
388 return False
386 marks[key] = repo[new].node()
389 marks[key] = repo[new].node()
387 write(repo)
390 write(repo)
388 return True
391 return True
389 finally:
392 finally:
390 w.release()
393 w.release()
391
394
392 def pull(oldpull, ui, repo, source="default", **opts):
395 def pull(oldpull, ui, repo, source="default", **opts):
393 # translate bookmark args to rev args for actual pull
396 # translate bookmark args to rev args for actual pull
394 if opts.get('bookmark'):
397 if opts.get('bookmark'):
395 # this is an unpleasant hack as pull will do this internally
398 # this is an unpleasant hack as pull will do this internally
396 source, branches = hg.parseurl(ui.expandpath(source),
399 source, branches = hg.parseurl(ui.expandpath(source),
397 opts.get('branch'))
400 opts.get('branch'))
398 other = hg.repository(hg.remoteui(repo, opts), source)
401 other = hg.repository(hg.remoteui(repo, opts), source)
399 rb = other.listkeys('bookmarks')
402 rb = other.listkeys('bookmarks')
400
403
401 for b in opts['bookmark']:
404 for b in opts['bookmark']:
402 if b not in rb:
405 if b not in rb:
403 raise util.Abort(_('remote bookmark %s not found!') % b)
406 raise util.Abort(_('remote bookmark %s not found!') % b)
404 opts.setdefault('rev', []).append(b)
407 opts.setdefault('rev', []).append(b)
405
408
406 result = oldpull(ui, repo, source, **opts)
409 result = oldpull(ui, repo, source, **opts)
407
410
408 # update specified bookmarks
411 # update specified bookmarks
409 if opts.get('bookmark'):
412 if opts.get('bookmark'):
410 for b in opts['bookmark']:
413 for b in opts['bookmark']:
411 # explicit pull overrides local bookmark if any
414 # explicit pull overrides local bookmark if any
412 ui.status(_("importing bookmark %s\n") % b)
415 ui.status(_("importing bookmark %s\n") % b)
413 repo._bookmarks[b] = repo[rb[b]].node()
416 repo._bookmarks[b] = repo[rb[b]].node()
414 write(repo)
417 write(repo)
415
418
416 return result
419 return result
417
420
418 def push(oldpush, ui, repo, dest=None, **opts):
421 def push(oldpush, ui, repo, dest=None, **opts):
419 dopush = True
422 dopush = True
420 if opts.get('bookmark'):
423 if opts.get('bookmark'):
421 dopush = False
424 dopush = False
422 for b in opts['bookmark']:
425 for b in opts['bookmark']:
423 if b in repo._bookmarks:
426 if b in repo._bookmarks:
424 dopush = True
427 dopush = True
425 opts.setdefault('rev', []).append(b)
428 opts.setdefault('rev', []).append(b)
426
429
427 result = 0
430 result = 0
428 if dopush:
431 if dopush:
429 result = oldpush(ui, repo, dest, **opts)
432 result = oldpush(ui, repo, dest, **opts)
430
433
431 if opts.get('bookmark'):
434 if opts.get('bookmark'):
432 # this is an unpleasant hack as push will do this internally
435 # this is an unpleasant hack as push will do this internally
433 dest = ui.expandpath(dest or 'default-push', dest or 'default')
436 dest = ui.expandpath(dest or 'default-push', dest or 'default')
434 dest, branches = hg.parseurl(dest, opts.get('branch'))
437 dest, branches = hg.parseurl(dest, opts.get('branch'))
435 other = hg.repository(hg.remoteui(repo, opts), dest)
438 other = hg.repository(hg.remoteui(repo, opts), dest)
436 rb = other.listkeys('bookmarks')
439 rb = other.listkeys('bookmarks')
437 for b in opts['bookmark']:
440 for b in opts['bookmark']:
438 # explicit push overrides remote bookmark if any
441 # explicit push overrides remote bookmark if any
439 if b in repo._bookmarks:
442 if b in repo._bookmarks:
440 ui.status(_("exporting bookmark %s\n") % b)
443 ui.status(_("exporting bookmark %s\n") % b)
441 new = repo[b].hex()
444 new = repo[b].hex()
442 else:
445 else:
443 ui.status(_("deleting remote bookmark %s\n") % b)
446 ui.status(_("deleting remote bookmark %s\n") % b)
444 new = '' # delete
447 new = '' # delete
445 old = rb.get(b, '')
448 old = rb.get(b, '')
446 r = other.pushkey('bookmarks', b, old, new)
449 r = other.pushkey('bookmarks', b, old, new)
447 if not r:
450 if not r:
448 ui.warn(_('updating bookmark %s failed!\n') % b)
451 ui.warn(_('updating bookmark %s failed!\n') % b)
449 if not result:
452 if not result:
450 result = 2
453 result = 2
451
454
452 return result
455 return result
453
456
454 def diffbookmarks(ui, repo, remote):
457 def diffbookmarks(ui, repo, remote):
455 ui.status(_("searching for changes\n"))
458 ui.status(_("searching for changes\n"))
456
459
457 lmarks = repo.listkeys('bookmarks')
460 lmarks = repo.listkeys('bookmarks')
458 rmarks = remote.listkeys('bookmarks')
461 rmarks = remote.listkeys('bookmarks')
459
462
460 diff = set(rmarks) - set(lmarks)
463 diff = set(rmarks) - set(lmarks)
461 for k in diff:
464 for k in diff:
462 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
465 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
463
466
464 if len(diff) <= 0:
467 if len(diff) <= 0:
465 ui.status(_("no changes found\n"))
468 ui.status(_("no changes found\n"))
466 return 1
469 return 1
467 return 0
470 return 0
468
471
469 def incoming(oldincoming, ui, repo, source="default", **opts):
472 def incoming(oldincoming, ui, repo, source="default", **opts):
470 if opts.get('bookmarks'):
473 if opts.get('bookmarks'):
471 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
474 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
472 other = hg.repository(hg.remoteui(repo, opts), source)
475 other = hg.repository(hg.remoteui(repo, opts), source)
473 ui.status(_('comparing with %s\n') % url.hidepassword(source))
476 ui.status(_('comparing with %s\n') % url.hidepassword(source))
474 return diffbookmarks(ui, repo, other)
477 return diffbookmarks(ui, repo, other)
475 else:
478 else:
476 return oldincoming(ui, repo, source, **opts)
479 return oldincoming(ui, repo, source, **opts)
477
480
478 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
481 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
479 if opts.get('bookmarks'):
482 if opts.get('bookmarks'):
480 dest = ui.expandpath(dest or 'default-push', dest or 'default')
483 dest = ui.expandpath(dest or 'default-push', dest or 'default')
481 dest, branches = hg.parseurl(dest, opts.get('branch'))
484 dest, branches = hg.parseurl(dest, opts.get('branch'))
482 other = hg.repository(hg.remoteui(repo, opts), dest)
485 other = hg.repository(hg.remoteui(repo, opts), dest)
483 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
486 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
484 return diffbookmarks(ui, other, repo)
487 return diffbookmarks(ui, other, repo)
485 else:
488 else:
486 return oldoutgoing(ui, repo, dest, **opts)
489 return oldoutgoing(ui, repo, dest, **opts)
487
490
488 def uisetup(ui):
491 def uisetup(ui):
489 extensions.wrapfunction(repair, "strip", strip)
492 extensions.wrapfunction(repair, "strip", strip)
490 if ui.configbool('bookmarks', 'track.current'):
493 if ui.configbool('bookmarks', 'track.current'):
491 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
494 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
492
495
493 entry = extensions.wrapcommand(commands.table, 'pull', pull)
496 entry = extensions.wrapcommand(commands.table, 'pull', pull)
494 entry[1].append(('B', 'bookmark', [],
497 entry[1].append(('B', 'bookmark', [],
495 _("bookmark to import")))
498 _("bookmark to import")))
496 entry = extensions.wrapcommand(commands.table, 'push', push)
499 entry = extensions.wrapcommand(commands.table, 'push', push)
497 entry[1].append(('B', 'bookmark', [],
500 entry[1].append(('B', 'bookmark', [],
498 _("bookmark to export")))
501 _("bookmark to export")))
499 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
502 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
500 entry[1].append(('B', 'bookmarks', False,
503 entry[1].append(('B', 'bookmarks', False,
501 _("compare bookmark")))
504 _("compare bookmark")))
502 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
505 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
503 entry[1].append(('B', 'bookmarks', False,
506 entry[1].append(('B', 'bookmarks', False,
504 _("compare bookmark")))
507 _("compare bookmark")))
505
508
506 pushkey.register('bookmarks', pushbookmark, listbookmarks)
509 pushkey.register('bookmarks', pushbookmark, listbookmarks)
507
510
508 def updatecurbookmark(orig, ui, repo, *args, **opts):
511 def updatecurbookmark(orig, ui, repo, *args, **opts):
509 '''Set the current bookmark
512 '''Set the current bookmark
510
513
511 If the user updates to a bookmark we update the .hg/bookmarks.current
514 If the user updates to a bookmark we update the .hg/bookmarks.current
512 file.
515 file.
513 '''
516 '''
514 res = orig(ui, repo, *args, **opts)
517 res = orig(ui, repo, *args, **opts)
515 rev = opts['rev']
518 rev = opts['rev']
516 if not rev and len(args) > 0:
519 if not rev and len(args) > 0:
517 rev = args[0]
520 rev = args[0]
518 setcurrent(repo, rev)
521 setcurrent(repo, rev)
519 return res
522 return res
520
523
521 cmdtable = {
524 cmdtable = {
522 "bookmarks":
525 "bookmarks":
523 (bookmark,
526 (bookmark,
524 [('f', 'force', False, _('force')),
527 [('f', 'force', False, _('force')),
525 ('r', 'rev', '', _('revision'), _('REV')),
528 ('r', 'rev', '', _('revision'), _('REV')),
526 ('d', 'delete', False, _('delete a given bookmark')),
529 ('d', 'delete', False, _('delete a given bookmark')),
527 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
530 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
528 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
531 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
529 }
532 }
530
533
531 colortable = {'bookmarks.current': 'green'}
534 colortable = {'bookmarks.current': 'green'}
@@ -1,103 +1,106 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 echo "[extensions]" >> $HGRCPATH
4 echo "bookmarks=" >> $HGRCPATH
4 echo "bookmarks=" >> $HGRCPATH
5
5
6 hg init
6 hg init
7
7
8 echo % no bookmarks
8 echo % no bookmarks
9 hg bookmarks
9 hg bookmarks
10
10
11 echo % bookmark rev -1
11 echo % bookmark rev -1
12 hg bookmark X
12 hg bookmark X
13
13
14 echo % list bookmarks
14 echo % list bookmarks
15 hg bookmarks
15 hg bookmarks
16
16
17 echo % list bookmarks with color
17 echo % list bookmarks with color
18 hg --config extensions.color= --config color.mode=ansi \
18 hg --config extensions.color= --config color.mode=ansi \
19 bookmarks --color=always
19 bookmarks --color=always
20
20
21 echo a > a
21 echo a > a
22 hg add a
22 hg add a
23 hg commit -m 0
23 hg commit -m 0
24
24
25 echo % bookmark X moved to rev 0
25 echo % bookmark X moved to rev 0
26 hg bookmarks
26 hg bookmarks
27
27
28 echo % look up bookmark
28 echo % look up bookmark
29 hg log -r X
29 hg log -r X
30
30
31 echo % second bookmark for rev 0
31 echo % second bookmark for rev 0
32 hg bookmark X2
32 hg bookmark X2
33
33
34 echo % bookmark rev -1 again
34 echo % bookmark rev -1 again
35 hg bookmark -r null Y
35 hg bookmark -r null Y
36
36
37 echo % list bookmarks
37 echo % list bookmarks
38 hg bookmarks
38 hg bookmarks
39
39
40 echo b > b
40 echo b > b
41 hg add b
41 hg add b
42 hg commit -m 1
42 hg commit -m 1
43
43
44 echo % bookmarks X and X2 moved to rev 1, Y at rev -1
44 echo % bookmarks X and X2 moved to rev 1, Y at rev -1
45 hg bookmarks
45 hg bookmarks
46
46
47 echo % bookmark rev 0 again
47 echo % bookmark rev 0 again
48 hg bookmark -r 0 Z
48 hg bookmark -r 0 Z
49
49
50 echo c > c
50 echo c > c
51 hg add c
51 hg add c
52 hg commit -m 2
52 hg commit -m 2
53
53
54 echo % bookmarks X and X2 moved to rev 2, Y at rev -1, Z at rev 0
54 echo % bookmarks X and X2 moved to rev 2, Y at rev -1, Z at rev 0
55 hg bookmarks
55 hg bookmarks
56
56
57 echo % rename nonexistent bookmark
57 echo % rename nonexistent bookmark
58 hg bookmark -m A B
58 hg bookmark -m A B
59
59
60 echo % rename to existent bookmark
60 echo % rename to existent bookmark
61 hg bookmark -m X Y
61 hg bookmark -m X Y
62
62
63 echo % force rename to existent bookmark
63 echo % force rename to existent bookmark
64 hg bookmark -f -m X Y
64 hg bookmark -f -m X Y
65
65
66 echo % list bookmarks
66 echo % list bookmarks
67 hg bookmark
67 hg bookmark
68
68
69 echo % rename without new name
69 echo % rename without new name
70 hg bookmark -m Y
70 hg bookmark -m Y
71
71
72 echo % delete without name
72 echo % delete without name
73 hg bookmark -d
73 hg bookmark -d
74
74
75 echo % delete nonexistent bookmark
75 echo % delete nonexistent bookmark
76 hg bookmark -d A
76 hg bookmark -d A
77
77
78 echo % bookmark name with spaces should be stripped
78 echo % bookmark name with spaces should be stripped
79 hg bookmark ' x y '
79 hg bookmark ' x y '
80
80
81 echo % list bookmarks
81 echo % list bookmarks
82 hg bookmarks
82 hg bookmarks
83
83
84 echo % look up stripped bookmark name
84 echo % look up stripped bookmark name
85 hg log -r '"x y"'
85 hg log -r '"x y"'
86
86
87 echo % reject bookmark name with newline
87 echo % reject bookmark name with newline
88 hg bookmark '
88 hg bookmark '
89 '
89 '
90
90
91 echo % bookmark with existing name
91 echo % bookmark with existing name
92 hg bookmark Z
92 hg bookmark Z
93
93
94 echo % force bookmark with existing name
94 echo % force bookmark with existing name
95 hg bookmark -f Z
95 hg bookmark -f Z
96
96
97 echo % list bookmarks
97 echo % list bookmarks
98 hg bookmark
98 hg bookmark
99
99
100 echo % revision but no bookmark name
100 echo % revision but no bookmark name
101 hg bookmark -r .
101 hg bookmark -r .
102
102
103 echo % bookmark name with whitespace only
104 hg bookmark ' '
105
103 true
106 true
@@ -1,76 +1,78 b''
1 % no bookmarks
1 % no bookmarks
2 no bookmarks set
2 no bookmarks set
3 % bookmark rev -1
3 % bookmark rev -1
4 % list bookmarks
4 % list bookmarks
5 * X -1:000000000000
5 * X -1:000000000000
6 % list bookmarks with color
6 % list bookmarks with color
7  * X -1:000000000000
7  * X -1:000000000000
8 % bookmark X moved to rev 0
8 % bookmark X moved to rev 0
9 * X 0:f7b1eb17ad24
9 * X 0:f7b1eb17ad24
10 % look up bookmark
10 % look up bookmark
11 changeset: 0:f7b1eb17ad24
11 changeset: 0:f7b1eb17ad24
12 tag: X
12 tag: X
13 tag: tip
13 tag: tip
14 user: test
14 user: test
15 date: Thu Jan 01 00:00:00 1970 +0000
15 date: Thu Jan 01 00:00:00 1970 +0000
16 summary: 0
16 summary: 0
17
17
18 % second bookmark for rev 0
18 % second bookmark for rev 0
19 % bookmark rev -1 again
19 % bookmark rev -1 again
20 % list bookmarks
20 % list bookmarks
21 * X2 0:f7b1eb17ad24
21 * X2 0:f7b1eb17ad24
22 * X 0:f7b1eb17ad24
22 * X 0:f7b1eb17ad24
23 Y -1:000000000000
23 Y -1:000000000000
24 % bookmarks X and X2 moved to rev 1, Y at rev -1
24 % bookmarks X and X2 moved to rev 1, Y at rev -1
25 * X2 1:925d80f479bb
25 * X2 1:925d80f479bb
26 * X 1:925d80f479bb
26 * X 1:925d80f479bb
27 Y -1:000000000000
27 Y -1:000000000000
28 % bookmark rev 0 again
28 % bookmark rev 0 again
29 % bookmarks X and X2 moved to rev 2, Y at rev -1, Z at rev 0
29 % bookmarks X and X2 moved to rev 2, Y at rev -1, Z at rev 0
30 * X2 2:0316ce92851d
30 * X2 2:0316ce92851d
31 * X 2:0316ce92851d
31 * X 2:0316ce92851d
32 Z 0:f7b1eb17ad24
32 Z 0:f7b1eb17ad24
33 Y -1:000000000000
33 Y -1:000000000000
34 % rename nonexistent bookmark
34 % rename nonexistent bookmark
35 abort: a bookmark of this name does not exist
35 abort: a bookmark of this name does not exist
36 % rename to existent bookmark
36 % rename to existent bookmark
37 abort: a bookmark of the same name already exists
37 abort: a bookmark of the same name already exists
38 % force rename to existent bookmark
38 % force rename to existent bookmark
39 % list bookmarks
39 % list bookmarks
40 * X2 2:0316ce92851d
40 * X2 2:0316ce92851d
41 * Y 2:0316ce92851d
41 * Y 2:0316ce92851d
42 Z 0:f7b1eb17ad24
42 Z 0:f7b1eb17ad24
43 % rename without new name
43 % rename without new name
44 abort: new bookmark name required
44 abort: new bookmark name required
45 % delete without name
45 % delete without name
46 abort: bookmark name required
46 abort: bookmark name required
47 % delete nonexistent bookmark
47 % delete nonexistent bookmark
48 abort: a bookmark of this name does not exist
48 abort: a bookmark of this name does not exist
49 % bookmark name with spaces should be stripped
49 % bookmark name with spaces should be stripped
50 % list bookmarks
50 % list bookmarks
51 * X2 2:0316ce92851d
51 * X2 2:0316ce92851d
52 * Y 2:0316ce92851d
52 * Y 2:0316ce92851d
53 Z 0:f7b1eb17ad24
53 Z 0:f7b1eb17ad24
54 * x y 2:0316ce92851d
54 * x y 2:0316ce92851d
55 % look up stripped bookmark name
55 % look up stripped bookmark name
56 changeset: 2:0316ce92851d
56 changeset: 2:0316ce92851d
57 tag: X2
57 tag: X2
58 tag: Y
58 tag: Y
59 tag: tip
59 tag: tip
60 tag: x y
60 tag: x y
61 user: test
61 user: test
62 date: Thu Jan 01 00:00:00 1970 +0000
62 date: Thu Jan 01 00:00:00 1970 +0000
63 summary: 2
63 summary: 2
64
64
65 % reject bookmark name with newline
65 % reject bookmark name with newline
66 abort: bookmark name cannot contain newlines
66 abort: bookmark name cannot contain newlines
67 % bookmark with existing name
67 % bookmark with existing name
68 abort: a bookmark of the same name already exists
68 abort: a bookmark of the same name already exists
69 % force bookmark with existing name
69 % force bookmark with existing name
70 % list bookmarks
70 % list bookmarks
71 * X2 2:0316ce92851d
71 * X2 2:0316ce92851d
72 * Y 2:0316ce92851d
72 * Y 2:0316ce92851d
73 * Z 2:0316ce92851d
73 * Z 2:0316ce92851d
74 * x y 2:0316ce92851d
74 * x y 2:0316ce92851d
75 % revision but no bookmark name
75 % revision but no bookmark name
76 abort: bookmark name required
76 abort: bookmark name required
77 % bookmark name with whitespace only
78 abort: bookmark names cannot consist entirely of whitespace
General Comments 0
You need to be logged in to leave comments. Login now