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