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