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