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