##// END OF EJS Templates
bookmarks: add force option to command syntax description
David Soria Parra -
r7795:5043def5 default
parent child Browse files
Show More
@@ -1,318 +1,318 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''mercurial bookmarks
8 '''mercurial bookmarks
9
9
10 Mercurial bookmarks are local moveable pointers to changesets. Every
10 Mercurial bookmarks are local moveable pointers to changesets. Every
11 bookmark points to a changeset identified by its hash. If you commit a
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
12 changeset that is based on a changeset that has a bookmark on it, the
13 bookmark is forwarded to the new changeset.
13 bookmark is forwarded to the new changeset.
14
14
15 It is possible to use bookmark names in every revision lookup (e.g. hg
15 It is possible to use bookmark names in every revision lookup (e.g. hg
16 merge, hg update).
16 merge, hg update).
17
17
18 The bookmark extension offers the possiblity to have a more git-like experience
18 The bookmark extension offers the possiblity to have a more git-like experience
19 by adding the following configuration option to your .hgrc:
19 by 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 bookmarks to track the bookmark that you are currently on, and
24 This will cause bookmarks to track the bookmark that you are currently on, and
25 just updates it. This is similar to git's approach of branching.
25 just updates it. This is similar to git's approach of 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 file = repo.opener('bookmarks', 'w+')
66 file = repo.opener('bookmarks', 'w+')
67 for refspec, node in refs.iteritems():
67 for refspec, node in refs.iteritems():
68 file.write("%s %s\n" % (hex(node), refspec))
68 file.write("%s %s\n" % (hex(node), refspec))
69 file.close()
69 file.close()
70
70
71 def current(repo):
71 def current(repo):
72 '''Get the current bookmark
72 '''Get the current bookmark
73
73
74 If we use gittishsh branches we have a current bookmark that
74 If we use gittishsh branches we have a current bookmark that
75 we are on. This function returns the name of the bookmark. It
75 we are on. This function returns the name of the bookmark. It
76 is stored in .hg/bookmarks.current
76 is stored in .hg/bookmarks.current
77 '''
77 '''
78 if repo._bookmarkcurrent:
78 if repo._bookmarkcurrent:
79 return repo._bookmarkcurrent
79 return repo._bookmarkcurrent
80 mark = None
80 mark = None
81 if os.path.exists(repo.join('bookmarks.current')):
81 if os.path.exists(repo.join('bookmarks.current')):
82 file = repo.opener('bookmarks.current')
82 file = repo.opener('bookmarks.current')
83 # No readline() in posixfile_nt, reading everything is cheap
83 # No readline() in posixfile_nt, reading everything is cheap
84 mark = (file.readlines() or [''])[0]
84 mark = (file.readlines() or [''])[0]
85 if mark == '':
85 if mark == '':
86 mark = None
86 mark = None
87 file.close()
87 file.close()
88 repo._bookmarkcurrent = mark
88 repo._bookmarkcurrent = mark
89 return mark
89 return mark
90
90
91 def setcurrent(repo, mark):
91 def setcurrent(repo, mark):
92 '''Set the name of the bookmark that we are currently on
92 '''Set the name of the bookmark that we are currently on
93
93
94 Set the name of the bookmark that we are on (hg update <bookmark>).
94 Set the name of the bookmark that we are on (hg update <bookmark>).
95 The name is recoreded in .hg/bookmarks.current
95 The name is recoreded in .hg/bookmarks.current
96 '''
96 '''
97 if current(repo) == mark:
97 if current(repo) == mark:
98 return
98 return
99
99
100 refs = parse(repo)
100 refs = parse(repo)
101
101
102 # do not update if we do update to a rev equal to the current bookmark
102 # do not update if we do update to a rev equal to the current bookmark
103 if (mark not in refs and
103 if (mark not in refs and
104 current(repo) and refs[current(repo)] == repo.changectx('.').node()):
104 current(repo) and refs[current(repo)] == repo.changectx('.').node()):
105 return
105 return
106 if mark not in refs:
106 if mark not in refs:
107 mark = ''
107 mark = ''
108 file = repo.opener('bookmarks.current', 'w+')
108 file = repo.opener('bookmarks.current', 'w+')
109 file.write(mark)
109 file.write(mark)
110 file.close()
110 file.close()
111 repo._bookmarkcurrent = mark
111 repo._bookmarkcurrent = mark
112
112
113 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
113 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
114 '''mercurial bookmarks
114 '''mercurial bookmarks
115
115
116 Bookmarks are pointers to certain commits that move when
116 Bookmarks are pointers to certain commits that move when
117 commiting. Bookmarks are local. They can be renamed, copied and
117 commiting. Bookmarks are local. They can be renamed, copied and
118 deleted. It is possible to use bookmark names in 'hg merge' and 'hg
118 deleted. It is possible to use bookmark names in 'hg merge' and 'hg
119 update' to update to a given bookmark.
119 update' to update to a given bookmark.
120
120
121 You can use 'hg bookmark NAME' to set a bookmark on the current
121 You can use 'hg bookmark NAME' to set a bookmark on the current
122 tip with the given name. If you specify a revision using -r REV
122 tip with the given name. If you specify a revision using -r REV
123 (where REV may be an existing bookmark), the bookmark is set to
123 (where REV may be an existing bookmark), the bookmark is set to
124 that revision.
124 that revision.
125 '''
125 '''
126 hexfn = ui.debugflag and hex or short
126 hexfn = ui.debugflag and hex or short
127 marks = parse(repo)
127 marks = parse(repo)
128 cur = repo.changectx('.').node()
128 cur = repo.changectx('.').node()
129
129
130 if rename:
130 if rename:
131 if rename not in marks:
131 if rename not in marks:
132 raise util.Abort(_("a bookmark of this name does not exist"))
132 raise util.Abort(_("a bookmark of this name does not exist"))
133 if mark in marks and not force:
133 if mark in marks and not force:
134 raise util.Abort(_("a bookmark of the same name already exists"))
134 raise util.Abort(_("a bookmark of the same name already exists"))
135 if mark is None:
135 if mark is None:
136 raise util.Abort(_("new bookmark name required"))
136 raise util.Abort(_("new bookmark name required"))
137 marks[mark] = marks[rename]
137 marks[mark] = marks[rename]
138 del marks[rename]
138 del marks[rename]
139 if current(repo) == rename:
139 if current(repo) == rename:
140 setcurrent(repo, mark)
140 setcurrent(repo, mark)
141 write(repo, marks)
141 write(repo, marks)
142 return
142 return
143
143
144 if delete:
144 if delete:
145 if mark == None:
145 if mark == None:
146 raise util.Abort(_("bookmark name required"))
146 raise util.Abort(_("bookmark name required"))
147 if mark not in marks:
147 if mark not in marks:
148 raise util.Abort(_("a bookmark of this name does not exist"))
148 raise util.Abort(_("a bookmark of this name does not exist"))
149 del marks[mark]
149 del marks[mark]
150 write(repo, marks)
150 write(repo, marks)
151 return
151 return
152
152
153 if mark != None:
153 if mark != None:
154 if "\n" in mark:
154 if "\n" in mark:
155 raise util.Abort(_("bookmark name cannot contain newlines"))
155 raise util.Abort(_("bookmark name cannot contain newlines"))
156 mark = mark.strip()
156 mark = mark.strip()
157 if mark in marks and not force:
157 if mark in marks and not force:
158 raise util.Abort(_("a bookmark of the same name already exists"))
158 raise util.Abort(_("a bookmark of the same name already exists"))
159 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
159 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
160 and not force):
160 and not force):
161 raise util.Abort(
161 raise util.Abort(
162 _("a bookmark cannot have the name of an existing branch"))
162 _("a bookmark cannot have the name of an existing branch"))
163 if rev:
163 if rev:
164 marks[mark] = repo.lookup(rev)
164 marks[mark] = repo.lookup(rev)
165 else:
165 else:
166 marks[mark] = repo.changectx('.').node()
166 marks[mark] = repo.changectx('.').node()
167 write(repo, marks)
167 write(repo, marks)
168 return
168 return
169
169
170 if mark == None:
170 if mark == None:
171 if rev:
171 if rev:
172 raise util.Abort(_("bookmark name required"))
172 raise util.Abort(_("bookmark name required"))
173 if len(marks) == 0:
173 if len(marks) == 0:
174 ui.status("no bookmarks set\n")
174 ui.status("no bookmarks set\n")
175 else:
175 else:
176 for bmark, n in marks.iteritems():
176 for bmark, n in marks.iteritems():
177 if ui.configbool('bookmarks', 'track.current'):
177 if ui.configbool('bookmarks', 'track.current'):
178 prefix = (bmark == current(repo) and n == cur) and '*' or ' '
178 prefix = (bmark == current(repo) and n == cur) and '*' or ' '
179 else:
179 else:
180 prefix = (n == cur) and '*' or ' '
180 prefix = (n == cur) and '*' or ' '
181
181
182 ui.write(" %s %-25s %d:%s\n" % (
182 ui.write(" %s %-25s %d:%s\n" % (
183 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
183 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
184 return
184 return
185
185
186 def _revstostrip(changelog, node):
186 def _revstostrip(changelog, node):
187 srev = changelog.rev(node)
187 srev = changelog.rev(node)
188 tostrip = [srev]
188 tostrip = [srev]
189 saveheads = []
189 saveheads = []
190 for r in xrange(srev, len(changelog)):
190 for r in xrange(srev, len(changelog)):
191 parents = changelog.parentrevs(r)
191 parents = changelog.parentrevs(r)
192 if parents[0] in tostrip or parents[1] in tostrip:
192 if parents[0] in tostrip or parents[1] in tostrip:
193 tostrip.append(r)
193 tostrip.append(r)
194 if parents[1] != nullrev:
194 if parents[1] != nullrev:
195 for p in parents:
195 for p in parents:
196 if p not in tostrip and p > srev:
196 if p not in tostrip and p > srev:
197 saveheads.append(p)
197 saveheads.append(p)
198 return [r for r in tostrip if r not in saveheads]
198 return [r for r in tostrip if r not in saveheads]
199
199
200 def strip(oldstrip, ui, repo, node, backup="all"):
200 def strip(oldstrip, ui, repo, node, backup="all"):
201 """Strip bookmarks if revisions are stripped using
201 """Strip bookmarks if revisions are stripped using
202 the mercurial.strip method. This usually happens during
202 the mercurial.strip method. This usually happens during
203 qpush and qpop"""
203 qpush and qpop"""
204 revisions = _revstostrip(repo.changelog, node)
204 revisions = _revstostrip(repo.changelog, node)
205 marks = parse(repo)
205 marks = parse(repo)
206 update = []
206 update = []
207 for mark, n in marks.iteritems():
207 for mark, n in marks.iteritems():
208 if repo.changelog.rev(n) in revisions:
208 if repo.changelog.rev(n) in revisions:
209 update.append(mark)
209 update.append(mark)
210 oldstrip(ui, repo, node, backup)
210 oldstrip(ui, repo, node, backup)
211 if len(update) > 0:
211 if len(update) > 0:
212 for m in update:
212 for m in update:
213 marks[m] = repo.changectx('.').node()
213 marks[m] = repo.changectx('.').node()
214 write(repo, marks)
214 write(repo, marks)
215
215
216 def reposetup(ui, repo):
216 def reposetup(ui, repo):
217 if not isinstance(repo, localrepo.localrepository):
217 if not isinstance(repo, localrepo.localrepository):
218 return
218 return
219
219
220 # init a bookmark cache as otherwise we would get a infinite reading
220 # init a bookmark cache as otherwise we would get a infinite reading
221 # in lookup()
221 # in lookup()
222 repo._bookmarks = None
222 repo._bookmarks = None
223 repo._bookmarkcurrent = None
223 repo._bookmarkcurrent = None
224
224
225 class bookmark_repo(repo.__class__):
225 class bookmark_repo(repo.__class__):
226 def rollback(self):
226 def rollback(self):
227 if os.path.exists(self.join('undo.bookmarks')):
227 if os.path.exists(self.join('undo.bookmarks')):
228 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
228 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
229 return super(bookmark_repo, self).rollback()
229 return super(bookmark_repo, self).rollback()
230
230
231 def lookup(self, key):
231 def lookup(self, key):
232 if self._bookmarks is None:
232 if self._bookmarks is None:
233 self._bookmarks = parse(self)
233 self._bookmarks = parse(self)
234 if key in self._bookmarks:
234 if key in self._bookmarks:
235 key = self._bookmarks[key]
235 key = self._bookmarks[key]
236 return super(bookmark_repo, self).lookup(key)
236 return super(bookmark_repo, self).lookup(key)
237
237
238 def commit(self, *k, **kw):
238 def commit(self, *k, **kw):
239 """Add a revision to the repository and
239 """Add a revision to the repository and
240 move the bookmark"""
240 move the bookmark"""
241 node = super(bookmark_repo, self).commit(*k, **kw)
241 node = super(bookmark_repo, self).commit(*k, **kw)
242 if node == None:
242 if node == None:
243 return None
243 return None
244 parents = repo.changelog.parents(node)
244 parents = repo.changelog.parents(node)
245 if parents[1] == nullid:
245 if parents[1] == nullid:
246 parents = (parents[0],)
246 parents = (parents[0],)
247 marks = parse(repo)
247 marks = parse(repo)
248 update = False
248 update = False
249 for mark, n in marks.items():
249 for mark, n in marks.items():
250 if ui.configbool('bookmarks', 'track.current'):
250 if ui.configbool('bookmarks', 'track.current'):
251 if mark == current(repo) and n in parents:
251 if mark == current(repo) and n in parents:
252 marks[mark] = node
252 marks[mark] = node
253 update = True
253 update = True
254 else:
254 else:
255 if n in parents:
255 if n in parents:
256 marks[mark] = node
256 marks[mark] = node
257 update = True
257 update = True
258 if update:
258 if update:
259 write(repo, marks)
259 write(repo, marks)
260 return node
260 return node
261
261
262 def addchangegroup(self, source, srctype, url, emptyok=False):
262 def addchangegroup(self, source, srctype, url, emptyok=False):
263 parents = repo.dirstate.parents()
263 parents = repo.dirstate.parents()
264
264
265 result = super(bookmark_repo, self).addchangegroup(
265 result = super(bookmark_repo, self).addchangegroup(
266 source, srctype, url, emptyok)
266 source, srctype, url, emptyok)
267 if result > 1:
267 if result > 1:
268 # We have more heads than before
268 # We have more heads than before
269 return result
269 return result
270 node = repo.changelog.tip()
270 node = repo.changelog.tip()
271 marks = parse(repo)
271 marks = parse(repo)
272 update = False
272 update = False
273 for mark, n in marks.items():
273 for mark, n in marks.items():
274 if n in parents:
274 if n in parents:
275 marks[mark] = node
275 marks[mark] = node
276 update = True
276 update = True
277 if update:
277 if update:
278 write(repo, marks)
278 write(repo, marks)
279 return result
279 return result
280
280
281 def tags(self):
281 def tags(self):
282 """Merge bookmarks with normal tags"""
282 """Merge bookmarks with normal tags"""
283 if self.tagscache:
283 if self.tagscache:
284 return self.tagscache
284 return self.tagscache
285
285
286 tagscache = super(bookmark_repo, self).tags()
286 tagscache = super(bookmark_repo, self).tags()
287 tagscache.update(parse(repo))
287 tagscache.update(parse(repo))
288 return tagscache
288 return tagscache
289
289
290 repo.__class__ = bookmark_repo
290 repo.__class__ = bookmark_repo
291
291
292 def uisetup(ui):
292 def uisetup(ui):
293 extensions.wrapfunction(repair, "strip", strip)
293 extensions.wrapfunction(repair, "strip", strip)
294 if ui.configbool('bookmarks', 'track.current'):
294 if ui.configbool('bookmarks', 'track.current'):
295 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
295 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
296
296
297 def updatecurbookmark(orig, ui, repo, *args, **opts):
297 def updatecurbookmark(orig, ui, repo, *args, **opts):
298 '''Set the current bookmark
298 '''Set the current bookmark
299
299
300 If the user updates to a bookmark we update the .hg/bookmarks.current
300 If the user updates to a bookmark we update the .hg/bookmarks.current
301 file.
301 file.
302 '''
302 '''
303 res = orig(ui, repo, *args, **opts)
303 res = orig(ui, repo, *args, **opts)
304 rev = opts['rev']
304 rev = opts['rev']
305 if not rev and len(args) > 0:
305 if not rev and len(args) > 0:
306 rev = args[0]
306 rev = args[0]
307 setcurrent(repo, rev)
307 setcurrent(repo, rev)
308 return res
308 return res
309
309
310 cmdtable = {
310 cmdtable = {
311 "bookmarks":
311 "bookmarks":
312 (bookmark,
312 (bookmark,
313 [('f', 'force', False, _('force')),
313 [('f', 'force', False, _('force')),
314 ('r', 'rev', '', _('revision')),
314 ('r', 'rev', '', _('revision')),
315 ('d', 'delete', False, _('delete a given bookmark')),
315 ('d', 'delete', False, _('delete a given bookmark')),
316 ('m', 'rename', '', _('rename a given bookmark'))],
316 ('m', 'rename', '', _('rename a given bookmark'))],
317 _('hg bookmarks [-d] [-m NAME] [-r NAME] [NAME]')),
317 _('hg bookmarks [-f] [-d] [-m NAME] [-r NAME] [NAME]')),
318 }
318 }
General Comments 0
You need to be logged in to leave comments. Login now