##// END OF EJS Templates
bookmarks: add invalidate() to bookmark_repo...
Paul Molodowitch -
r10597:153d688c stable
parent child Browse files
Show More
@@ -1,327 +1,334 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. hg
15 It is possible to use bookmark names in every revision lookup (e.g. hg
16 merge, hg update).
16 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
33 from mercurial import util, commands, repair, extensions
34 import os
34 import os
35
35
36 def write(repo):
36 def write(repo):
37 '''Write bookmarks
37 '''Write bookmarks
38
38
39 Write the given bookmark => hash dictionary to the .hg/bookmarks file
39 Write the given bookmark => hash dictionary to the .hg/bookmarks file
40 in a format equal to those of localtags.
40 in a format equal to those of localtags.
41
41
42 We also store a backup of the previous state in undo.bookmarks that
42 We also store a backup of the previous state in undo.bookmarks that
43 can be copied back on rollback.
43 can be copied back on rollback.
44 '''
44 '''
45 refs = repo._bookmarks
45 refs = repo._bookmarks
46 if os.path.exists(repo.join('bookmarks')):
46 if os.path.exists(repo.join('bookmarks')):
47 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
47 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
48 if repo._bookmarkcurrent not in refs:
48 if repo._bookmarkcurrent not in refs:
49 setcurrent(repo, None)
49 setcurrent(repo, None)
50 wlock = repo.wlock()
50 wlock = repo.wlock()
51 try:
51 try:
52 file = repo.opener('bookmarks', 'w', atomictemp=True)
52 file = repo.opener('bookmarks', 'w', atomictemp=True)
53 for refspec, node in refs.iteritems():
53 for refspec, node in refs.iteritems():
54 file.write("%s %s\n" % (hex(node), refspec))
54 file.write("%s %s\n" % (hex(node), refspec))
55 file.rename()
55 file.rename()
56 finally:
56 finally:
57 wlock.release()
57 wlock.release()
58
58
59 def setcurrent(repo, mark):
59 def setcurrent(repo, mark):
60 '''Set the name of the bookmark that we are currently on
60 '''Set the name of the bookmark that we are currently on
61
61
62 Set the name of the bookmark that we are on (hg update <bookmark>).
62 Set the name of the bookmark that we are on (hg update <bookmark>).
63 The name is recorded in .hg/bookmarks.current
63 The name is recorded in .hg/bookmarks.current
64 '''
64 '''
65 current = repo._bookmarkcurrent
65 current = repo._bookmarkcurrent
66 if current == mark:
66 if current == mark:
67 return
67 return
68
68
69 refs = repo._bookmarks
69 refs = repo._bookmarks
70
70
71 # do not update if we do update to a rev equal to the current bookmark
71 # do not update if we do update to a rev equal to the current bookmark
72 if (mark and mark not in refs and
72 if (mark and mark not in refs and
73 current and refs[current] == repo.changectx('.').node()):
73 current and refs[current] == repo.changectx('.').node()):
74 return
74 return
75 if mark not in refs:
75 if mark not in refs:
76 mark = ''
76 mark = ''
77 wlock = repo.wlock()
77 wlock = repo.wlock()
78 try:
78 try:
79 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
79 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
80 file.write(mark)
80 file.write(mark)
81 file.rename()
81 file.rename()
82 finally:
82 finally:
83 wlock.release()
83 wlock.release()
84 repo._bookmarkcurrent = mark
84 repo._bookmarkcurrent = mark
85
85
86 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
86 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
87 '''track a line of development with movable markers
87 '''track a line of development with movable markers
88
88
89 Bookmarks are pointers to certain commits that move when
89 Bookmarks are pointers to certain commits that move when
90 committing. Bookmarks are local. They can be renamed, copied and
90 committing. Bookmarks are local. They can be renamed, copied and
91 deleted. It is possible to use bookmark names in 'hg merge' and
91 deleted. It is possible to use bookmark names in 'hg merge' and
92 'hg update' to merge and update respectively to a given bookmark.
92 'hg update' to merge and update respectively to a given bookmark.
93
93
94 You can use 'hg bookmark NAME' to set a bookmark on the working
94 You can use 'hg bookmark NAME' to set a bookmark on the working
95 directory's parent revision with the given name. If you specify
95 directory's parent revision with the given name. If you specify
96 a revision using -r REV (where REV may be an existing bookmark),
96 a revision using -r REV (where REV may be an existing bookmark),
97 the bookmark is assigned to that revision.
97 the bookmark is assigned to that revision.
98 '''
98 '''
99 hexfn = ui.debugflag and hex or short
99 hexfn = ui.debugflag and hex or short
100 marks = repo._bookmarks
100 marks = repo._bookmarks
101 cur = repo.changectx('.').node()
101 cur = repo.changectx('.').node()
102
102
103 if rename:
103 if rename:
104 if rename not in marks:
104 if rename not in marks:
105 raise util.Abort(_("a bookmark of this name does not exist"))
105 raise util.Abort(_("a bookmark of this name does not exist"))
106 if mark in marks and not force:
106 if mark in marks and not force:
107 raise util.Abort(_("a bookmark of the same name already exists"))
107 raise util.Abort(_("a bookmark of the same name already exists"))
108 if mark is None:
108 if mark is None:
109 raise util.Abort(_("new bookmark name required"))
109 raise util.Abort(_("new bookmark name required"))
110 marks[mark] = marks[rename]
110 marks[mark] = marks[rename]
111 del marks[rename]
111 del marks[rename]
112 if repo._bookmarkcurrent == rename:
112 if repo._bookmarkcurrent == rename:
113 setcurrent(repo, mark)
113 setcurrent(repo, mark)
114 write(repo)
114 write(repo)
115 return
115 return
116
116
117 if delete:
117 if delete:
118 if mark is None:
118 if mark is None:
119 raise util.Abort(_("bookmark name required"))
119 raise util.Abort(_("bookmark name required"))
120 if mark not in marks:
120 if mark not in marks:
121 raise util.Abort(_("a bookmark of this name does not exist"))
121 raise util.Abort(_("a bookmark of this name does not exist"))
122 if mark == repo._bookmarkcurrent:
122 if mark == repo._bookmarkcurrent:
123 setcurrent(repo, None)
123 setcurrent(repo, None)
124 del marks[mark]
124 del marks[mark]
125 write(repo)
125 write(repo)
126 return
126 return
127
127
128 if mark != None:
128 if mark != None:
129 if "\n" in mark:
129 if "\n" in mark:
130 raise util.Abort(_("bookmark name cannot contain newlines"))
130 raise util.Abort(_("bookmark name cannot contain newlines"))
131 mark = mark.strip()
131 mark = mark.strip()
132 if mark in marks and not force:
132 if mark in marks and not force:
133 raise util.Abort(_("a bookmark of the same name already exists"))
133 raise util.Abort(_("a bookmark of the same name already exists"))
134 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
134 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
135 and not force):
135 and not force):
136 raise util.Abort(
136 raise util.Abort(
137 _("a bookmark cannot have the name of an existing branch"))
137 _("a bookmark cannot have the name of an existing branch"))
138 if rev:
138 if rev:
139 marks[mark] = repo.lookup(rev)
139 marks[mark] = repo.lookup(rev)
140 else:
140 else:
141 marks[mark] = repo.changectx('.').node()
141 marks[mark] = repo.changectx('.').node()
142 setcurrent(repo, mark)
142 setcurrent(repo, mark)
143 write(repo)
143 write(repo)
144 return
144 return
145
145
146 if mark is None:
146 if mark is None:
147 if rev:
147 if rev:
148 raise util.Abort(_("bookmark name required"))
148 raise util.Abort(_("bookmark name required"))
149 if len(marks) == 0:
149 if len(marks) == 0:
150 ui.status(_("no bookmarks set\n"))
150 ui.status(_("no bookmarks set\n"))
151 else:
151 else:
152 for bmark, n in marks.iteritems():
152 for bmark, n in marks.iteritems():
153 if ui.configbool('bookmarks', 'track.current'):
153 if ui.configbool('bookmarks', 'track.current'):
154 current = repo._bookmarkcurrent
154 current = repo._bookmarkcurrent
155 prefix = (bmark == current and n == cur) and '*' or ' '
155 prefix = (bmark == current and n == cur) and '*' or ' '
156 else:
156 else:
157 prefix = (n == cur) and '*' or ' '
157 prefix = (n == cur) and '*' or ' '
158
158
159 if ui.quiet:
159 if ui.quiet:
160 ui.write("%s\n" % bmark)
160 ui.write("%s\n" % bmark)
161 else:
161 else:
162 ui.write(" %s %-25s %d:%s\n" % (
162 ui.write(" %s %-25s %d:%s\n" % (
163 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
163 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
164 return
164 return
165
165
166 def _revstostrip(changelog, node):
166 def _revstostrip(changelog, node):
167 srev = changelog.rev(node)
167 srev = changelog.rev(node)
168 tostrip = [srev]
168 tostrip = [srev]
169 saveheads = []
169 saveheads = []
170 for r in xrange(srev, len(changelog)):
170 for r in xrange(srev, len(changelog)):
171 parents = changelog.parentrevs(r)
171 parents = changelog.parentrevs(r)
172 if parents[0] in tostrip or parents[1] in tostrip:
172 if parents[0] in tostrip or parents[1] in tostrip:
173 tostrip.append(r)
173 tostrip.append(r)
174 if parents[1] != nullrev:
174 if parents[1] != nullrev:
175 for p in parents:
175 for p in parents:
176 if p not in tostrip and p > srev:
176 if p not in tostrip and p > srev:
177 saveheads.append(p)
177 saveheads.append(p)
178 return [r for r in tostrip if r not in saveheads]
178 return [r for r in tostrip if r not in saveheads]
179
179
180 def strip(oldstrip, ui, repo, node, backup="all"):
180 def strip(oldstrip, ui, repo, node, backup="all"):
181 """Strip bookmarks if revisions are stripped using
181 """Strip bookmarks if revisions are stripped using
182 the mercurial.strip method. This usually happens during
182 the mercurial.strip method. This usually happens during
183 qpush and qpop"""
183 qpush and qpop"""
184 revisions = _revstostrip(repo.changelog, node)
184 revisions = _revstostrip(repo.changelog, node)
185 marks = repo._bookmarks
185 marks = repo._bookmarks
186 update = []
186 update = []
187 for mark, n in marks.iteritems():
187 for mark, n in marks.iteritems():
188 if repo.changelog.rev(n) in revisions:
188 if repo.changelog.rev(n) in revisions:
189 update.append(mark)
189 update.append(mark)
190 oldstrip(ui, repo, node, backup)
190 oldstrip(ui, repo, node, backup)
191 if len(update) > 0:
191 if len(update) > 0:
192 for m in update:
192 for m in update:
193 marks[m] = repo.changectx('.').node()
193 marks[m] = repo.changectx('.').node()
194 write(repo)
194 write(repo)
195
195
196 def reposetup(ui, repo):
196 def reposetup(ui, repo):
197 if not repo.local():
197 if not repo.local():
198 return
198 return
199
199
200 class bookmark_repo(repo.__class__):
200 class bookmark_repo(repo.__class__):
201
201
202 @util.propertycache
202 @util.propertycache
203 def _bookmarks(self):
203 def _bookmarks(self):
204 '''Parse .hg/bookmarks file and return a dictionary
204 '''Parse .hg/bookmarks file and return a dictionary
205
205
206 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
206 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
207 in the .hg/bookmarks file. They are read returned as a dictionary
207 in the .hg/bookmarks file. They are read returned as a dictionary
208 with name => hash values.
208 with name => hash values.
209 '''
209 '''
210 try:
210 try:
211 bookmarks = {}
211 bookmarks = {}
212 for line in self.opener('bookmarks'):
212 for line in self.opener('bookmarks'):
213 sha, refspec = line.strip().split(' ', 1)
213 sha, refspec = line.strip().split(' ', 1)
214 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
214 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
215 except:
215 except:
216 pass
216 pass
217 return bookmarks
217 return bookmarks
218
218
219 @util.propertycache
219 @util.propertycache
220 def _bookmarkcurrent(self):
220 def _bookmarkcurrent(self):
221 '''Get the current bookmark
221 '''Get the current bookmark
222
222
223 If we use gittishsh branches we have a current bookmark that
223 If we use gittishsh branches we have a current bookmark that
224 we are on. This function returns the name of the bookmark. It
224 we are on. This function returns the name of the bookmark. It
225 is stored in .hg/bookmarks.current
225 is stored in .hg/bookmarks.current
226 '''
226 '''
227 mark = None
227 mark = None
228 if os.path.exists(self.join('bookmarks.current')):
228 if os.path.exists(self.join('bookmarks.current')):
229 file = self.opener('bookmarks.current')
229 file = self.opener('bookmarks.current')
230 # No readline() in posixfile_nt, reading everything is cheap
230 # No readline() in posixfile_nt, reading everything is cheap
231 mark = (file.readlines() or [''])[0]
231 mark = (file.readlines() or [''])[0]
232 if mark == '':
232 if mark == '':
233 mark = None
233 mark = None
234 file.close()
234 file.close()
235 return mark
235 return mark
236
236
237 def rollback(self):
237 def rollback(self):
238 if os.path.exists(self.join('undo.bookmarks')):
238 if os.path.exists(self.join('undo.bookmarks')):
239 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
239 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
240 return super(bookmark_repo, self).rollback()
240 return super(bookmark_repo, self).rollback()
241
241
242 def lookup(self, key):
242 def lookup(self, key):
243 if key in self._bookmarks:
243 if key in self._bookmarks:
244 key = self._bookmarks[key]
244 key = self._bookmarks[key]
245 return super(bookmark_repo, self).lookup(key)
245 return super(bookmark_repo, self).lookup(key)
246
246
247 def _bookmarksupdate(self, parents, node):
247 def _bookmarksupdate(self, parents, node):
248 marks = self._bookmarks
248 marks = self._bookmarks
249 update = False
249 update = False
250 if ui.configbool('bookmarks', 'track.current'):
250 if ui.configbool('bookmarks', 'track.current'):
251 mark = self._bookmarkcurrent
251 mark = self._bookmarkcurrent
252 if mark and marks[mark] in parents:
252 if mark and marks[mark] in parents:
253 marks[mark] = node
253 marks[mark] = node
254 update = True
254 update = True
255 else:
255 else:
256 for mark, n in marks.items():
256 for mark, n in marks.items():
257 if n in parents:
257 if n in parents:
258 marks[mark] = node
258 marks[mark] = node
259 update = True
259 update = True
260 if update:
260 if update:
261 write(self)
261 write(self)
262
262
263 def commitctx(self, ctx, error=False):
263 def commitctx(self, ctx, error=False):
264 """Add a revision to the repository and
264 """Add a revision to the repository and
265 move the bookmark"""
265 move the bookmark"""
266 wlock = self.wlock() # do both commit and bookmark with lock held
266 wlock = self.wlock() # do both commit and bookmark with lock held
267 try:
267 try:
268 node = super(bookmark_repo, self).commitctx(ctx, error)
268 node = super(bookmark_repo, self).commitctx(ctx, error)
269 if node is None:
269 if node is None:
270 return None
270 return None
271 parents = self.changelog.parents(node)
271 parents = self.changelog.parents(node)
272 if parents[1] == nullid:
272 if parents[1] == nullid:
273 parents = (parents[0],)
273 parents = (parents[0],)
274
274
275 self._bookmarksupdate(parents, node)
275 self._bookmarksupdate(parents, node)
276 return node
276 return node
277 finally:
277 finally:
278 wlock.release()
278 wlock.release()
279
279
280 def addchangegroup(self, source, srctype, url, emptyok=False):
280 def addchangegroup(self, source, srctype, url, emptyok=False):
281 parents = self.dirstate.parents()
281 parents = self.dirstate.parents()
282
282
283 result = super(bookmark_repo, self).addchangegroup(
283 result = super(bookmark_repo, self).addchangegroup(
284 source, srctype, url, emptyok)
284 source, srctype, url, emptyok)
285 if result > 1:
285 if result > 1:
286 # We have more heads than before
286 # We have more heads than before
287 return result
287 return result
288 node = self.changelog.tip()
288 node = self.changelog.tip()
289
289
290 self._bookmarksupdate(parents, node)
290 self._bookmarksupdate(parents, node)
291 return result
291 return result
292
292
293 def _findtags(self):
293 def _findtags(self):
294 """Merge bookmarks with normal tags"""
294 """Merge bookmarks with normal tags"""
295 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
295 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
296 tags.update(self._bookmarks)
296 tags.update(self._bookmarks)
297 return (tags, tagtypes)
297 return (tags, tagtypes)
298
298
299 if hasattr(repo, 'invalidate'):
300 def invalidate(self):
301 super(bookmark_repo, self).invalidate()
302 for attr in ('_bookmarks', '_bookmarkcurrent'):
303 if attr in self.__dict__:
304 delattr(repo, attr)
305
299 repo.__class__ = bookmark_repo
306 repo.__class__ = bookmark_repo
300
307
301 def uisetup(ui):
308 def uisetup(ui):
302 extensions.wrapfunction(repair, "strip", strip)
309 extensions.wrapfunction(repair, "strip", strip)
303 if ui.configbool('bookmarks', 'track.current'):
310 if ui.configbool('bookmarks', 'track.current'):
304 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
311 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
305
312
306 def updatecurbookmark(orig, ui, repo, *args, **opts):
313 def updatecurbookmark(orig, ui, repo, *args, **opts):
307 '''Set the current bookmark
314 '''Set the current bookmark
308
315
309 If the user updates to a bookmark we update the .hg/bookmarks.current
316 If the user updates to a bookmark we update the .hg/bookmarks.current
310 file.
317 file.
311 '''
318 '''
312 res = orig(ui, repo, *args, **opts)
319 res = orig(ui, repo, *args, **opts)
313 rev = opts['rev']
320 rev = opts['rev']
314 if not rev and len(args) > 0:
321 if not rev and len(args) > 0:
315 rev = args[0]
322 rev = args[0]
316 setcurrent(repo, rev)
323 setcurrent(repo, rev)
317 return res
324 return res
318
325
319 cmdtable = {
326 cmdtable = {
320 "bookmarks":
327 "bookmarks":
321 (bookmark,
328 (bookmark,
322 [('f', 'force', False, _('force')),
329 [('f', 'force', False, _('force')),
323 ('r', 'rev', '', _('revision')),
330 ('r', 'rev', '', _('revision')),
324 ('d', 'delete', False, _('delete a given bookmark')),
331 ('d', 'delete', False, _('delete a given bookmark')),
325 ('m', 'rename', '', _('rename a given bookmark'))],
332 ('m', 'rename', '', _('rename a given bookmark'))],
326 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
333 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
327 }
334 }
General Comments 0
You need to be logged in to leave comments. Login now