##// END OF EJS Templates
bookmarks: abort directly on invalid name...
Kevin Bullock -
r17815:be146734 default
parent child Browse files
Show More
@@ -1,288 +1,284 b''
1 # Mercurial bookmark support code
1 # Mercurial bookmark support code
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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial.node import hex
9 from mercurial.node import hex
10 from mercurial import encoding, error, util, obsolete, phases
10 from mercurial import encoding, error, util, obsolete, phases
11 import errno, os
11 import errno, os
12
12
13 def valid(mark):
13 def checkvalid(mark):
14 for c in (':', '\0', '\n', '\r'):
14 for c in (':', '\0', '\n', '\r'):
15 if c in mark:
15 if c in mark:
16 return False
16 raise util.Abort(_("bookmark '%s' contains illegal "
17 return True
17 "character" % mark))
18
18
19 def read(repo):
19 def read(repo):
20 '''Parse .hg/bookmarks file and return a dictionary
20 '''Parse .hg/bookmarks file and return a dictionary
21
21
22 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
22 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
23 in the .hg/bookmarks file.
23 in the .hg/bookmarks file.
24 Read the file and return a (name=>nodeid) dictionary
24 Read the file and return a (name=>nodeid) dictionary
25 '''
25 '''
26 bookmarks = {}
26 bookmarks = {}
27 try:
27 try:
28 for line in repo.opener('bookmarks'):
28 for line in repo.opener('bookmarks'):
29 line = line.strip()
29 line = line.strip()
30 if not line:
30 if not line:
31 continue
31 continue
32 if ' ' not in line:
32 if ' ' not in line:
33 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') % line)
33 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') % line)
34 continue
34 continue
35 sha, refspec = line.split(' ', 1)
35 sha, refspec = line.split(' ', 1)
36 refspec = encoding.tolocal(refspec)
36 refspec = encoding.tolocal(refspec)
37 try:
37 try:
38 bookmarks[refspec] = repo.changelog.lookup(sha)
38 bookmarks[refspec] = repo.changelog.lookup(sha)
39 except LookupError:
39 except LookupError:
40 pass
40 pass
41 except IOError, inst:
41 except IOError, inst:
42 if inst.errno != errno.ENOENT:
42 if inst.errno != errno.ENOENT:
43 raise
43 raise
44 return bookmarks
44 return bookmarks
45
45
46 def readcurrent(repo):
46 def readcurrent(repo):
47 '''Get the current bookmark
47 '''Get the current bookmark
48
48
49 If we use gittishsh branches we have a current bookmark that
49 If we use gittishsh branches we have a current bookmark that
50 we are on. This function returns the name of the bookmark. It
50 we are on. This function returns the name of the bookmark. It
51 is stored in .hg/bookmarks.current
51 is stored in .hg/bookmarks.current
52 '''
52 '''
53 mark = None
53 mark = None
54 try:
54 try:
55 file = repo.opener('bookmarks.current')
55 file = repo.opener('bookmarks.current')
56 except IOError, inst:
56 except IOError, inst:
57 if inst.errno != errno.ENOENT:
57 if inst.errno != errno.ENOENT:
58 raise
58 raise
59 return None
59 return None
60 try:
60 try:
61 # No readline() in osutil.posixfile, reading everything is cheap
61 # No readline() in osutil.posixfile, reading everything is cheap
62 mark = encoding.tolocal((file.readlines() or [''])[0])
62 mark = encoding.tolocal((file.readlines() or [''])[0])
63 if mark == '' or mark not in repo._bookmarks:
63 if mark == '' or mark not in repo._bookmarks:
64 mark = None
64 mark = None
65 finally:
65 finally:
66 file.close()
66 file.close()
67 return mark
67 return mark
68
68
69 def write(repo):
69 def write(repo):
70 '''Write bookmarks
70 '''Write bookmarks
71
71
72 Write the given bookmark => hash dictionary to the .hg/bookmarks file
72 Write the given bookmark => hash dictionary to the .hg/bookmarks file
73 in a format equal to those of localtags.
73 in a format equal to those of localtags.
74
74
75 We also store a backup of the previous state in undo.bookmarks that
75 We also store a backup of the previous state in undo.bookmarks that
76 can be copied back on rollback.
76 can be copied back on rollback.
77 '''
77 '''
78 refs = repo._bookmarks
78 refs = repo._bookmarks
79
79
80 if repo._bookmarkcurrent not in refs:
80 if repo._bookmarkcurrent not in refs:
81 setcurrent(repo, None)
81 setcurrent(repo, None)
82 for mark in refs.keys():
82 for mark in refs.keys():
83 if not valid(mark):
83 checkvalid(mark)
84 raise util.Abort(_("bookmark '%s' contains illegal "
85 "character" % mark))
86
84
87 wlock = repo.wlock()
85 wlock = repo.wlock()
88 try:
86 try:
89
87
90 file = repo.opener('bookmarks', 'w', atomictemp=True)
88 file = repo.opener('bookmarks', 'w', atomictemp=True)
91 for refspec, node in refs.iteritems():
89 for refspec, node in refs.iteritems():
92 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
90 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
93 file.close()
91 file.close()
94
92
95 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
93 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
96 try:
94 try:
97 os.utime(repo.sjoin('00changelog.i'), None)
95 os.utime(repo.sjoin('00changelog.i'), None)
98 except OSError:
96 except OSError:
99 pass
97 pass
100
98
101 finally:
99 finally:
102 wlock.release()
100 wlock.release()
103
101
104 def setcurrent(repo, mark):
102 def setcurrent(repo, mark):
105 '''Set the name of the bookmark that we are currently on
103 '''Set the name of the bookmark that we are currently on
106
104
107 Set the name of the bookmark that we are on (hg update <bookmark>).
105 Set the name of the bookmark that we are on (hg update <bookmark>).
108 The name is recorded in .hg/bookmarks.current
106 The name is recorded in .hg/bookmarks.current
109 '''
107 '''
110 current = repo._bookmarkcurrent
108 current = repo._bookmarkcurrent
111 if current == mark:
109 if current == mark:
112 return
110 return
113
111
114 if mark not in repo._bookmarks:
112 if mark not in repo._bookmarks:
115 mark = ''
113 mark = ''
116 if not valid(mark):
114 checkvalid(mark)
117 raise util.Abort(_("bookmark '%s' contains illegal "
118 "character" % mark))
119
115
120 wlock = repo.wlock()
116 wlock = repo.wlock()
121 try:
117 try:
122 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
118 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
123 file.write(encoding.fromlocal(mark))
119 file.write(encoding.fromlocal(mark))
124 file.close()
120 file.close()
125 finally:
121 finally:
126 wlock.release()
122 wlock.release()
127 repo._bookmarkcurrent = mark
123 repo._bookmarkcurrent = mark
128
124
129 def unsetcurrent(repo):
125 def unsetcurrent(repo):
130 wlock = repo.wlock()
126 wlock = repo.wlock()
131 try:
127 try:
132 try:
128 try:
133 util.unlink(repo.join('bookmarks.current'))
129 util.unlink(repo.join('bookmarks.current'))
134 repo._bookmarkcurrent = None
130 repo._bookmarkcurrent = None
135 except OSError, inst:
131 except OSError, inst:
136 if inst.errno != errno.ENOENT:
132 if inst.errno != errno.ENOENT:
137 raise
133 raise
138 finally:
134 finally:
139 wlock.release()
135 wlock.release()
140
136
141 def updatecurrentbookmark(repo, oldnode, curbranch):
137 def updatecurrentbookmark(repo, oldnode, curbranch):
142 try:
138 try:
143 return update(repo, oldnode, repo.branchtip(curbranch))
139 return update(repo, oldnode, repo.branchtip(curbranch))
144 except error.RepoLookupError:
140 except error.RepoLookupError:
145 if curbranch == "default": # no default branch!
141 if curbranch == "default": # no default branch!
146 return update(repo, oldnode, repo.lookup("tip"))
142 return update(repo, oldnode, repo.lookup("tip"))
147 else:
143 else:
148 raise util.Abort(_("branch %s not found") % curbranch)
144 raise util.Abort(_("branch %s not found") % curbranch)
149
145
150 def update(repo, parents, node):
146 def update(repo, parents, node):
151 marks = repo._bookmarks
147 marks = repo._bookmarks
152 update = False
148 update = False
153 cur = repo._bookmarkcurrent
149 cur = repo._bookmarkcurrent
154 if not cur:
150 if not cur:
155 return False
151 return False
156
152
157 toupdate = [b for b in marks if b.split('@', 1)[0] == cur.split('@', 1)[0]]
153 toupdate = [b for b in marks if b.split('@', 1)[0] == cur.split('@', 1)[0]]
158 for mark in toupdate:
154 for mark in toupdate:
159 if mark and marks[mark] in parents:
155 if mark and marks[mark] in parents:
160 old = repo[marks[mark]]
156 old = repo[marks[mark]]
161 new = repo[node]
157 new = repo[node]
162 if old.descendant(new) and mark == cur:
158 if old.descendant(new) and mark == cur:
163 marks[cur] = new.node()
159 marks[cur] = new.node()
164 update = True
160 update = True
165 if mark != cur:
161 if mark != cur:
166 del marks[mark]
162 del marks[mark]
167 if update:
163 if update:
168 repo._writebookmarks(marks)
164 repo._writebookmarks(marks)
169 return update
165 return update
170
166
171 def listbookmarks(repo):
167 def listbookmarks(repo):
172 # We may try to list bookmarks on a repo type that does not
168 # We may try to list bookmarks on a repo type that does not
173 # support it (e.g., statichttprepository).
169 # support it (e.g., statichttprepository).
174 marks = getattr(repo, '_bookmarks', {})
170 marks = getattr(repo, '_bookmarks', {})
175
171
176 d = {}
172 d = {}
177 for k, v in marks.iteritems():
173 for k, v in marks.iteritems():
178 # don't expose local divergent bookmarks
174 # don't expose local divergent bookmarks
179 if '@' not in k or k.endswith('@'):
175 if '@' not in k or k.endswith('@'):
180 d[k] = hex(v)
176 d[k] = hex(v)
181 return d
177 return d
182
178
183 def pushbookmark(repo, key, old, new):
179 def pushbookmark(repo, key, old, new):
184 w = repo.wlock()
180 w = repo.wlock()
185 try:
181 try:
186 marks = repo._bookmarks
182 marks = repo._bookmarks
187 if hex(marks.get(key, '')) != old:
183 if hex(marks.get(key, '')) != old:
188 return False
184 return False
189 if new == '':
185 if new == '':
190 del marks[key]
186 del marks[key]
191 else:
187 else:
192 if new not in repo:
188 if new not in repo:
193 return False
189 return False
194 marks[key] = repo[new].node()
190 marks[key] = repo[new].node()
195 write(repo)
191 write(repo)
196 return True
192 return True
197 finally:
193 finally:
198 w.release()
194 w.release()
199
195
200 def updatefromremote(ui, repo, remote, path):
196 def updatefromremote(ui, repo, remote, path):
201 ui.debug("checking for updated bookmarks\n")
197 ui.debug("checking for updated bookmarks\n")
202 rb = remote.listkeys('bookmarks')
198 rb = remote.listkeys('bookmarks')
203 changed = False
199 changed = False
204 for k in rb.keys():
200 for k in rb.keys():
205 if k in repo._bookmarks:
201 if k in repo._bookmarks:
206 nr, nl = rb[k], repo._bookmarks[k]
202 nr, nl = rb[k], repo._bookmarks[k]
207 if nr in repo:
203 if nr in repo:
208 cr = repo[nr]
204 cr = repo[nr]
209 cl = repo[nl]
205 cl = repo[nl]
210 if cl.rev() >= cr.rev():
206 if cl.rev() >= cr.rev():
211 continue
207 continue
212 if validdest(repo, cl, cr):
208 if validdest(repo, cl, cr):
213 repo._bookmarks[k] = cr.node()
209 repo._bookmarks[k] = cr.node()
214 changed = True
210 changed = True
215 ui.status(_("updating bookmark %s\n") % k)
211 ui.status(_("updating bookmark %s\n") % k)
216 else:
212 else:
217 if k == '@':
213 if k == '@':
218 kd = ''
214 kd = ''
219 else:
215 else:
220 kd = k
216 kd = k
221 # find a unique @ suffix
217 # find a unique @ suffix
222 for x in range(1, 100):
218 for x in range(1, 100):
223 n = '%s@%d' % (kd, x)
219 n = '%s@%d' % (kd, x)
224 if n not in repo._bookmarks:
220 if n not in repo._bookmarks:
225 break
221 break
226 # try to use an @pathalias suffix
222 # try to use an @pathalias suffix
227 # if an @pathalias already exists, we overwrite (update) it
223 # if an @pathalias already exists, we overwrite (update) it
228 for p, u in ui.configitems("paths"):
224 for p, u in ui.configitems("paths"):
229 if path == u:
225 if path == u:
230 n = '%s@%s' % (kd, p)
226 n = '%s@%s' % (kd, p)
231
227
232 repo._bookmarks[n] = cr.node()
228 repo._bookmarks[n] = cr.node()
233 changed = True
229 changed = True
234 ui.warn(_("divergent bookmark %s stored as %s\n") % (k, n))
230 ui.warn(_("divergent bookmark %s stored as %s\n") % (k, n))
235 elif rb[k] in repo:
231 elif rb[k] in repo:
236 # add remote bookmarks for changes we already have
232 # add remote bookmarks for changes we already have
237 repo._bookmarks[k] = repo[rb[k]].node()
233 repo._bookmarks[k] = repo[rb[k]].node()
238 changed = True
234 changed = True
239 ui.status(_("adding remote bookmark %s\n") % k)
235 ui.status(_("adding remote bookmark %s\n") % k)
240
236
241 if changed:
237 if changed:
242 write(repo)
238 write(repo)
243
239
244 def diff(ui, dst, src):
240 def diff(ui, dst, src):
245 ui.status(_("searching for changed bookmarks\n"))
241 ui.status(_("searching for changed bookmarks\n"))
246
242
247 smarks = src.listkeys('bookmarks')
243 smarks = src.listkeys('bookmarks')
248 dmarks = dst.listkeys('bookmarks')
244 dmarks = dst.listkeys('bookmarks')
249
245
250 diff = sorted(set(smarks) - set(dmarks))
246 diff = sorted(set(smarks) - set(dmarks))
251 for k in diff:
247 for k in diff:
252 mark = ui.debugflag and smarks[k] or smarks[k][:12]
248 mark = ui.debugflag and smarks[k] or smarks[k][:12]
253 ui.write(" %-25s %s\n" % (k, mark))
249 ui.write(" %-25s %s\n" % (k, mark))
254
250
255 if len(diff) <= 0:
251 if len(diff) <= 0:
256 ui.status(_("no changed bookmarks found\n"))
252 ui.status(_("no changed bookmarks found\n"))
257 return 1
253 return 1
258 return 0
254 return 0
259
255
260 def validdest(repo, old, new):
256 def validdest(repo, old, new):
261 """Is the new bookmark destination a valid update from the old one"""
257 """Is the new bookmark destination a valid update from the old one"""
262 if old == new:
258 if old == new:
263 # Old == new -> nothing to update.
259 # Old == new -> nothing to update.
264 return False
260 return False
265 elif not old:
261 elif not old:
266 # old is nullrev, anything is valid.
262 # old is nullrev, anything is valid.
267 # (new != nullrev has been excluded by the previous check)
263 # (new != nullrev has been excluded by the previous check)
268 return True
264 return True
269 elif repo.obsstore:
265 elif repo.obsstore:
270 # We only need this complicated logic if there is obsolescence
266 # We only need this complicated logic if there is obsolescence
271 # XXX will probably deserve an optimised revset.
267 # XXX will probably deserve an optimised revset.
272
268
273 validdests = set([old])
269 validdests = set([old])
274 plen = -1
270 plen = -1
275 # compute the whole set of successors or descendants
271 # compute the whole set of successors or descendants
276 while len(validdests) != plen:
272 while len(validdests) != plen:
277 plen = len(validdests)
273 plen = len(validdests)
278 succs = set(c.node() for c in validdests)
274 succs = set(c.node() for c in validdests)
279 for c in validdests:
275 for c in validdests:
280 if c.phase() > phases.public:
276 if c.phase() > phases.public:
281 # obsolescence marker does not apply to public changeset
277 # obsolescence marker does not apply to public changeset
282 succs.update(obsolete.anysuccessors(repo.obsstore,
278 succs.update(obsolete.anysuccessors(repo.obsstore,
283 c.node()))
279 c.node()))
284 validdests = set(repo.set('%ln::', succs))
280 validdests = set(repo.set('%ln::', succs))
285 validdests.remove(old)
281 validdests.remove(old)
286 return new in validdests
282 return new in validdests
287 else:
283 else:
288 return old.descendant(new)
284 return old.descendant(new)
General Comments 0
You need to be logged in to leave comments. Login now