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