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