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