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