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