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