##// END OF EJS Templates
bookmarks: Correctly reject newlines in bookmark names...
Joel Rosdahl -
r7259:18c23375 default
parent child Browse files
Show More
@@ -1,231 +1,231 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
18
19 from mercurial.commands import templateopts, hex, short
19 from mercurial.commands import templateopts, hex, short
20 from mercurial.i18n import _
20 from mercurial.i18n import _
21 from mercurial import cmdutil, util, commands, changelog
21 from mercurial import cmdutil, util, commands, changelog
22 from mercurial.node import nullid, nullrev
22 from mercurial.node import nullid, nullrev
23 from mercurial.repo import RepoError
23 from mercurial.repo import RepoError
24 import mercurial, mercurial.localrepo, mercurial.repair, os
24 import mercurial, mercurial.localrepo, mercurial.repair, os
25
25
26 def parse(repo):
26 def parse(repo):
27 '''Parse .hg/bookmarks file and return a dictionary
27 '''Parse .hg/bookmarks file and return a dictionary
28
28
29 Bookmarks are stored as {HASH}\s{NAME}\n (localtags format) values
29 Bookmarks are stored as {HASH}\s{NAME}\n (localtags format) values
30 in the .hg/bookmarks file. They are read by the parse() method and
30 in the .hg/bookmarks file. They are read by the parse() method and
31 returned as a dictionary with name => hash values.
31 returned as a dictionary with name => hash values.
32
32
33 The parsed dictionary is cached until a write() operation is done.
33 The parsed dictionary is cached until a write() operation is done.
34 '''
34 '''
35 try:
35 try:
36 if repo._bookmarks:
36 if repo._bookmarks:
37 return repo._bookmarks
37 return repo._bookmarks
38 repo._bookmarks = {}
38 repo._bookmarks = {}
39 for line in repo.opener('bookmarks'):
39 for line in repo.opener('bookmarks'):
40 sha, refspec = line.strip().split(' ', 1)
40 sha, refspec = line.strip().split(' ', 1)
41 repo._bookmarks[refspec] = repo.lookup(sha)
41 repo._bookmarks[refspec] = repo.lookup(sha)
42 except:
42 except:
43 pass
43 pass
44 return repo._bookmarks
44 return repo._bookmarks
45
45
46 def write(repo, refs):
46 def write(repo, refs):
47 '''Write bookmarks
47 '''Write bookmarks
48
48
49 Write the given bookmark => hash dictionary to the .hg/bookmarks file
49 Write the given bookmark => hash dictionary to the .hg/bookmarks file
50 in a format equal to those of localtags.
50 in a format equal to those of localtags.
51
51
52 We also store a backup of the previous state in undo.bookmarks that
52 We also store a backup of the previous state in undo.bookmarks that
53 can be copied back on rollback.
53 can be copied back on rollback.
54 '''
54 '''
55 if os.path.exists(repo.join('bookmarks')):
55 if os.path.exists(repo.join('bookmarks')):
56 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
56 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
57 file = repo.opener('bookmarks', 'w+')
57 file = repo.opener('bookmarks', 'w+')
58 for refspec, node in refs.items():
58 for refspec, node in refs.items():
59 file.write("%s %s\n" % (hex(node), refspec))
59 file.write("%s %s\n" % (hex(node), refspec))
60 file.close()
60 file.close()
61
61
62 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
62 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
63 '''mercurial bookmarks
63 '''mercurial bookmarks
64
64
65 Bookmarks are pointers to certain commits that move when
65 Bookmarks are pointers to certain commits that move when
66 commiting. Bookmarks are local. They can be renamed, copied and
66 commiting. Bookmarks are local. They can be renamed, copied and
67 deleted. It is possible to use bookmark names in 'hg merge' and 'hg
67 deleted. It is possible to use bookmark names in 'hg merge' and 'hg
68 update' to update to a given bookmark.
68 update' to update to a given bookmark.
69
69
70 You can use 'hg bookmark NAME' to set a bookmark on the current
70 You can use 'hg bookmark NAME' to set a bookmark on the current
71 tip with the given name. If you specify a revision using -r REV
71 tip with the given name. If you specify a revision using -r REV
72 (where REV may be an existing bookmark), the bookmark is set to
72 (where REV may be an existing bookmark), the bookmark is set to
73 that revision.
73 that revision.
74 '''
74 '''
75 hexfn = ui.debugflag and hex or short
75 hexfn = ui.debugflag and hex or short
76 marks = parse(repo)
76 marks = parse(repo)
77 cur = repo.changectx('.').node()
77 cur = repo.changectx('.').node()
78
78
79 if rename:
79 if rename:
80 if rename not in marks:
80 if rename not in marks:
81 raise util.Abort(_("a bookmark of this name does not exist"))
81 raise util.Abort(_("a bookmark of this name does not exist"))
82 if mark in marks and not force:
82 if mark in marks and not force:
83 raise util.Abort(_("a bookmark of the same name already exists"))
83 raise util.Abort(_("a bookmark of the same name already exists"))
84 if mark is None:
84 if mark is None:
85 raise util.Abort(_("new bookmark name required"))
85 raise util.Abort(_("new bookmark name required"))
86 marks[mark] = marks[rename]
86 marks[mark] = marks[rename]
87 del marks[rename]
87 del marks[rename]
88 write(repo, marks)
88 write(repo, marks)
89 return
89 return
90
90
91 if delete:
91 if delete:
92 if mark == None:
92 if mark == None:
93 raise util.Abort(_("bookmark name required"))
93 raise util.Abort(_("bookmark name required"))
94 if mark not in marks:
94 if mark not in marks:
95 raise util.Abort(_("a bookmark of this name does not exist"))
95 raise util.Abort(_("a bookmark of this name does not exist"))
96 del marks[mark]
96 del marks[mark]
97 write(repo, marks)
97 write(repo, marks)
98 return
98 return
99
99
100 if mark != None:
100 if mark != None:
101 if mark.strip().count("\n") > 0:
101 if "\n" in mark:
102 raise Exception("bookmark cannot contain newlines")
102 raise util.Abort(_("bookmark name cannot contain newlines"))
103 if mark in marks and not force:
103 if mark in marks and not force:
104 raise util.Abort(_("a bookmark of the same name already exists"))
104 raise util.Abort(_("a bookmark of the same name already exists"))
105 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
105 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
106 and not force):
106 and not force):
107 raise util.Abort(
107 raise util.Abort(
108 _("a bookmark cannot have the name of an existing branch"))
108 _("a bookmark cannot have the name of an existing branch"))
109 if rev:
109 if rev:
110 marks[mark] = repo.lookup(rev)
110 marks[mark] = repo.lookup(rev)
111 else:
111 else:
112 marks[mark] = repo.changectx('.').node()
112 marks[mark] = repo.changectx('.').node()
113 write(repo, marks)
113 write(repo, marks)
114 return
114 return
115
115
116 if mark == None:
116 if mark == None:
117 if rev:
117 if rev:
118 raise util.Abort(_("bookmark name required"))
118 raise util.Abort(_("bookmark name required"))
119 if len(marks) == 0:
119 if len(marks) == 0:
120 ui.status("no bookmarks set\n")
120 ui.status("no bookmarks set\n")
121 else:
121 else:
122 for bmark, n in marks.iteritems():
122 for bmark, n in marks.iteritems():
123 prefix = (n == cur) and '*' or ' '
123 prefix = (n == cur) and '*' or ' '
124 ui.write(" %s %-25s %d:%s\n" % (
124 ui.write(" %s %-25s %d:%s\n" % (
125 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
125 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
126 return
126 return
127
127
128 def _revstostrip(changelog, node):
128 def _revstostrip(changelog, node):
129 srev = changelog.rev(node)
129 srev = changelog.rev(node)
130 tostrip = [srev]
130 tostrip = [srev]
131 saveheads = []
131 saveheads = []
132 for r in xrange(srev, changelog.rev(changelog.tip()) + 1):
132 for r in xrange(srev, changelog.rev(changelog.tip()) + 1):
133 parents = changelog.parentrevs(r)
133 parents = changelog.parentrevs(r)
134 if parents[0] in tostrip or parents[1] in tostrip:
134 if parents[0] in tostrip or parents[1] in tostrip:
135 tostrip.append(r)
135 tostrip.append(r)
136 if parents[1] != nullrev:
136 if parents[1] != nullrev:
137 for p in parents:
137 for p in parents:
138 if p not in tostrip and p > striprev:
138 if p not in tostrip and p > striprev:
139 saveheads.append(p)
139 saveheads.append(p)
140 return [r for r in tostrip if r not in saveheads]
140 return [r for r in tostrip if r not in saveheads]
141
141
142 def strip(ui, repo, node, backup="all"):
142 def strip(ui, repo, node, backup="all"):
143 """Strip bookmarks if revisions are stripped using
143 """Strip bookmarks if revisions are stripped using
144 the mercurial.strip method. This usually happens during
144 the mercurial.strip method. This usually happens during
145 qpush and qpop"""
145 qpush and qpop"""
146 revisions = _revstostrip(repo.changelog, node)
146 revisions = _revstostrip(repo.changelog, node)
147 marks = parse(repo)
147 marks = parse(repo)
148 update = []
148 update = []
149 for mark, n in marks.items():
149 for mark, n in marks.items():
150 if repo.changelog.rev(n) in revisions:
150 if repo.changelog.rev(n) in revisions:
151 update.append(mark)
151 update.append(mark)
152 result = oldstrip(ui, repo, node, backup)
152 result = oldstrip(ui, repo, node, backup)
153 if len(update) > 0:
153 if len(update) > 0:
154 for m in update:
154 for m in update:
155 marks[m] = repo.changectx('.').node()
155 marks[m] = repo.changectx('.').node()
156 write(repo, marks)
156 write(repo, marks)
157
157
158 oldstrip = mercurial.repair.strip
158 oldstrip = mercurial.repair.strip
159 mercurial.repair.strip = strip
159 mercurial.repair.strip = strip
160
160
161 def reposetup(ui, repo):
161 def reposetup(ui, repo):
162 if not isinstance(repo, mercurial.localrepo.localrepository):
162 if not isinstance(repo, mercurial.localrepo.localrepository):
163 return
163 return
164
164
165 # init a bookmark cache as otherwise we would get a infinite reading
165 # init a bookmark cache as otherwise we would get a infinite reading
166 # in lookup()
166 # in lookup()
167 repo._bookmarks = None
167 repo._bookmarks = None
168
168
169 class bookmark_repo(repo.__class__):
169 class bookmark_repo(repo.__class__):
170 def rollback(self):
170 def rollback(self):
171 if os.path.exists(self.join('undo.bookmarks')):
171 if os.path.exists(self.join('undo.bookmarks')):
172 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
172 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
173 return super(bookmark_repo, self).rollback()
173 return super(bookmark_repo, self).rollback()
174
174
175 def lookup(self, key):
175 def lookup(self, key):
176 if self._bookmarks is None:
176 if self._bookmarks is None:
177 self._bookmarks = parse(self)
177 self._bookmarks = parse(self)
178 if key in self._bookmarks:
178 if key in self._bookmarks:
179 key = self._bookmarks[key]
179 key = self._bookmarks[key]
180 return super(bookmark_repo, self).lookup(key)
180 return super(bookmark_repo, self).lookup(key)
181
181
182 def commit(self, *k, **kw):
182 def commit(self, *k, **kw):
183 """Add a revision to the repository and
183 """Add a revision to the repository and
184 move the bookmark"""
184 move the bookmark"""
185 node = super(bookmark_repo, self).commit(*k, **kw)
185 node = super(bookmark_repo, self).commit(*k, **kw)
186 parents = repo.changelog.parents(node)
186 parents = repo.changelog.parents(node)
187 if parents[1] == nullid:
187 if parents[1] == nullid:
188 parents = (parents[0],)
188 parents = (parents[0],)
189 marks = parse(repo)
189 marks = parse(repo)
190 update = False
190 update = False
191 for mark, n in marks.items():
191 for mark, n in marks.items():
192 if n in parents:
192 if n in parents:
193 marks[mark] = node
193 marks[mark] = node
194 update = True
194 update = True
195 if update:
195 if update:
196 write(repo, marks)
196 write(repo, marks)
197 return node
197 return node
198
198
199 def addchangegroup(self, source, srctype, url, emptyok=False):
199 def addchangegroup(self, source, srctype, url, emptyok=False):
200 try:
200 try:
201 onode = repo.changectx('.').node()
201 onode = repo.changectx('.').node()
202 except RepoError, inst:
202 except RepoError, inst:
203 pass
203 pass
204
204
205 result = super(bookmark_repo, self).addchangegroup(
205 result = super(bookmark_repo, self).addchangegroup(
206 source, srctype, url, emptyok)
206 source, srctype, url, emptyok)
207 if result > 1:
207 if result > 1:
208 # We have more heads than before
208 # We have more heads than before
209 return result
209 return result
210 node = repo.changelog.tip()
210 node = repo.changelog.tip()
211 marks = parse(repo)
211 marks = parse(repo)
212 update = False
212 update = False
213 for mark, n in marks.items():
213 for mark, n in marks.items():
214 if n == onode:
214 if n == onode:
215 marks[mark] = node
215 marks[mark] = node
216 update = True
216 update = True
217 if update:
217 if update:
218 write(repo, marks)
218 write(repo, marks)
219 return result
219 return result
220
220
221 repo.__class__ = bookmark_repo
221 repo.__class__ = bookmark_repo
222
222
223 cmdtable = {
223 cmdtable = {
224 "bookmarks":
224 "bookmarks":
225 (bookmark,
225 (bookmark,
226 [('f', 'force', False, _('force')),
226 [('f', 'force', False, _('force')),
227 ('r', 'rev', '', _('revision')),
227 ('r', 'rev', '', _('revision')),
228 ('d', 'delete', False, _('delete a given bookmark')),
228 ('d', 'delete', False, _('delete a given bookmark')),
229 ('m', 'rename', '', _('rename a given bookmark'))],
229 ('m', 'rename', '', _('rename a given bookmark'))],
230 _('hg bookmarks [-d] [-m NAME] [-r NAME] [NAME]')),
230 _('hg bookmarks [-d] [-m NAME] [-r NAME] [NAME]')),
231 }
231 }
General Comments 0
You need to be logged in to leave comments. Login now