##// END OF EJS Templates
bookmarks: use reST syntax for literal block
Martin Geisler -
r9202:82de0bb0 default
parent child Browse files
Show More
@@ -1,330 +1,330 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 points to a
11 11 changeset identified by its hash. If you commit a changeset that is based on a
12 12 changeset that has a bookmark on it, the bookmark shifts to the new changeset.
13 13
14 14 It is possible to use bookmark names in every revision lookup (e.g. hg merge,
15 15 hg update).
16 16
17 17 By default, when several bookmarks point to the same changeset, they will all
18 18 move forward together. It is possible to obtain a more git-like experience by
19 adding the following configuration option to your .hgrc:
19 adding the following configuration option to your .hgrc::
20 20
21 21 [bookmarks]
22 22 track.current = True
23 23
24 24 This will cause Mercurial to track the bookmark that you are currently using,
25 25 and only update it. This is similar to git's approach to branching.
26 26 '''
27 27
28 28 from mercurial.i18n import _
29 29 from mercurial.node import nullid, nullrev, hex, short
30 30 from mercurial import util, commands, localrepo, repair, extensions
31 31 import os
32 32
33 33 def parse(repo):
34 34 '''Parse .hg/bookmarks file and return a dictionary
35 35
36 36 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
37 37 in the .hg/bookmarks file. They are read by the parse() method and
38 38 returned as a dictionary with name => hash values.
39 39
40 40 The parsed dictionary is cached until a write() operation is done.
41 41 '''
42 42 try:
43 43 if repo._bookmarks:
44 44 return repo._bookmarks
45 45 repo._bookmarks = {}
46 46 for line in repo.opener('bookmarks'):
47 47 sha, refspec = line.strip().split(' ', 1)
48 48 repo._bookmarks[refspec] = repo.lookup(sha)
49 49 except:
50 50 pass
51 51 return repo._bookmarks
52 52
53 53 def write(repo, refs):
54 54 '''Write bookmarks
55 55
56 56 Write the given bookmark => hash dictionary to the .hg/bookmarks file
57 57 in a format equal to those of localtags.
58 58
59 59 We also store a backup of the previous state in undo.bookmarks that
60 60 can be copied back on rollback.
61 61 '''
62 62 if os.path.exists(repo.join('bookmarks')):
63 63 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
64 64 if current(repo) not in refs:
65 65 setcurrent(repo, None)
66 66 wlock = repo.wlock()
67 67 try:
68 68 file = repo.opener('bookmarks', 'w', atomictemp=True)
69 69 for refspec, node in refs.iteritems():
70 70 file.write("%s %s\n" % (hex(node), refspec))
71 71 file.rename()
72 72 finally:
73 73 wlock.release()
74 74
75 75 def current(repo):
76 76 '''Get the current bookmark
77 77
78 78 If we use gittishsh branches we have a current bookmark that
79 79 we are on. This function returns the name of the bookmark. It
80 80 is stored in .hg/bookmarks.current
81 81 '''
82 82 if repo._bookmarkcurrent:
83 83 return repo._bookmarkcurrent
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 repo._bookmarkcurrent = mark
93 93 return mark
94 94
95 95 def setcurrent(repo, mark):
96 96 '''Set the name of the bookmark that we are currently on
97 97
98 98 Set the name of the bookmark that we are on (hg update <bookmark>).
99 99 The name is recorded in .hg/bookmarks.current
100 100 '''
101 101 if current(repo) == mark:
102 102 return
103 103
104 104 refs = parse(repo)
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(repo) and refs[current(repo)] == 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 committing.
125 125 Bookmarks are local. They can be renamed, copied and deleted. It is
126 126 possible to use bookmark names in 'hg merge' and 'hg update' to merge and
127 127 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 a revision
131 131 using -r REV (where REV may be an existing bookmark), the bookmark is
132 132 assigned to that revision.
133 133 '''
134 134 hexfn = ui.debugflag and hex or short
135 135 marks = parse(repo)
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 current(repo) == rename:
148 148 setcurrent(repo, mark)
149 149 write(repo, marks)
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 == current(repo):
158 158 setcurrent(repo, None)
159 159 del marks[mark]
160 160 write(repo, marks)
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, marks)
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 prefix = (bmark == current(repo) and n == cur) and '*' or ' '
190 190 else:
191 191 prefix = (n == cur) and '*' or ' '
192 192
193 193 ui.write(" %s %-25s %d:%s\n" % (
194 194 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
195 195 return
196 196
197 197 def _revstostrip(changelog, node):
198 198 srev = changelog.rev(node)
199 199 tostrip = [srev]
200 200 saveheads = []
201 201 for r in xrange(srev, len(changelog)):
202 202 parents = changelog.parentrevs(r)
203 203 if parents[0] in tostrip or parents[1] in tostrip:
204 204 tostrip.append(r)
205 205 if parents[1] != nullrev:
206 206 for p in parents:
207 207 if p not in tostrip and p > srev:
208 208 saveheads.append(p)
209 209 return [r for r in tostrip if r not in saveheads]
210 210
211 211 def strip(oldstrip, ui, repo, node, backup="all"):
212 212 """Strip bookmarks if revisions are stripped using
213 213 the mercurial.strip method. This usually happens during
214 214 qpush and qpop"""
215 215 revisions = _revstostrip(repo.changelog, node)
216 216 marks = parse(repo)
217 217 update = []
218 218 for mark, n in marks.iteritems():
219 219 if repo.changelog.rev(n) in revisions:
220 220 update.append(mark)
221 221 oldstrip(ui, repo, node, backup)
222 222 if len(update) > 0:
223 223 for m in update:
224 224 marks[m] = repo.changectx('.').node()
225 225 write(repo, marks)
226 226
227 227 def reposetup(ui, repo):
228 228 if not isinstance(repo, localrepo.localrepository):
229 229 return
230 230
231 231 # init a bookmark cache as otherwise we would get a infinite reading
232 232 # in lookup()
233 233 repo._bookmarks = None
234 234 repo._bookmarkcurrent = None
235 235
236 236 class bookmark_repo(repo.__class__):
237 237 def rollback(self):
238 238 if os.path.exists(self.join('undo.bookmarks')):
239 239 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
240 240 return super(bookmark_repo, self).rollback()
241 241
242 242 def lookup(self, key):
243 243 if self._bookmarks is None:
244 244 self._bookmarks = parse(self)
245 245 if key in self._bookmarks:
246 246 key = self._bookmarks[key]
247 247 return super(bookmark_repo, self).lookup(key)
248 248
249 249 def commit(self, *k, **kw):
250 250 """Add a revision to the repository and
251 251 move the bookmark"""
252 252 wlock = self.wlock() # do both commit and bookmark with lock held
253 253 try:
254 254 node = super(bookmark_repo, self).commit(*k, **kw)
255 255 if node is None:
256 256 return None
257 257 parents = self.changelog.parents(node)
258 258 if parents[1] == nullid:
259 259 parents = (parents[0],)
260 260 marks = parse(self)
261 261 update = False
262 262 for mark, n in marks.items():
263 263 if ui.configbool('bookmarks', 'track.current'):
264 264 if mark == current(self) and n in parents:
265 265 marks[mark] = node
266 266 update = True
267 267 else:
268 268 if n in parents:
269 269 marks[mark] = node
270 270 update = True
271 271 if update:
272 272 write(self, marks)
273 273 return node
274 274 finally:
275 275 wlock.release()
276 276
277 277 def addchangegroup(self, source, srctype, url, emptyok=False):
278 278 parents = self.dirstate.parents()
279 279
280 280 result = super(bookmark_repo, self).addchangegroup(
281 281 source, srctype, url, emptyok)
282 282 if result > 1:
283 283 # We have more heads than before
284 284 return result
285 285 node = self.changelog.tip()
286 286 marks = parse(self)
287 287 update = False
288 288 for mark, n in marks.items():
289 289 if n in parents:
290 290 marks[mark] = node
291 291 update = True
292 292 if update:
293 293 write(self, marks)
294 294 return result
295 295
296 296 def _findtags(self):
297 297 """Merge bookmarks with normal tags"""
298 298 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
299 299 tags.update(parse(self))
300 300 return (tags, tagtypes)
301 301
302 302 repo.__class__ = bookmark_repo
303 303
304 304 def uisetup(ui):
305 305 extensions.wrapfunction(repair, "strip", strip)
306 306 if ui.configbool('bookmarks', 'track.current'):
307 307 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
308 308
309 309 def updatecurbookmark(orig, ui, repo, *args, **opts):
310 310 '''Set the current bookmark
311 311
312 312 If the user updates to a bookmark we update the .hg/bookmarks.current
313 313 file.
314 314 '''
315 315 res = orig(ui, repo, *args, **opts)
316 316 rev = opts['rev']
317 317 if not rev and len(args) > 0:
318 318 rev = args[0]
319 319 setcurrent(repo, rev)
320 320 return res
321 321
322 322 cmdtable = {
323 323 "bookmarks":
324 324 (bookmark,
325 325 [('f', 'force', False, _('force')),
326 326 ('r', 'rev', '', _('revision')),
327 327 ('d', 'delete', False, _('delete a given bookmark')),
328 328 ('m', 'rename', '', _('rename a given bookmark'))],
329 329 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
330 330 }
General Comments 0
You need to be logged in to leave comments. Login now