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