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