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