##// END OF EJS Templates
bookmarks: more robust parsing of bookmarks file
Pierre-Yves David -
r14845:67733952 default
parent child Browse files
Show More
@@ -1,208 +1,211 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
11 11 import errno, os
12 12
13 13 def valid(mark):
14 14 for c in (':', '\0', '\n', '\r'):
15 15 if c in mark:
16 16 return False
17 17 return True
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 line = line.strip()
30 if ' ' not in line:
31 continue
29 32 sha, refspec = line.strip().split(' ', 1)
30 33 refspec = encoding.tolocal(refspec)
31 34 try:
32 35 bookmarks[refspec] = repo.changelog.lookup(sha)
33 36 except error.RepoLookupError:
34 37 pass
35 38 except IOError, inst:
36 39 if inst.errno != errno.ENOENT:
37 40 raise
38 41 return bookmarks
39 42
40 43 def readcurrent(repo):
41 44 '''Get the current bookmark
42 45
43 46 If we use gittishsh branches we have a current bookmark that
44 47 we are on. This function returns the name of the bookmark. It
45 48 is stored in .hg/bookmarks.current
46 49 '''
47 50 mark = None
48 51 try:
49 52 file = repo.opener('bookmarks.current')
50 53 except IOError, inst:
51 54 if inst.errno != errno.ENOENT:
52 55 raise
53 56 return None
54 57 try:
55 58 # No readline() in posixfile_nt, reading everything is cheap
56 59 mark = encoding.tolocal((file.readlines() or [''])[0])
57 60 if mark == '' or mark not in repo._bookmarks:
58 61 mark = None
59 62 finally:
60 63 file.close()
61 64 return mark
62 65
63 66 def write(repo):
64 67 '''Write bookmarks
65 68
66 69 Write the given bookmark => hash dictionary to the .hg/bookmarks file
67 70 in a format equal to those of localtags.
68 71
69 72 We also store a backup of the previous state in undo.bookmarks that
70 73 can be copied back on rollback.
71 74 '''
72 75 refs = repo._bookmarks
73 76
74 77 if repo._bookmarkcurrent not in refs:
75 78 setcurrent(repo, None)
76 79 for mark in refs.keys():
77 80 if not valid(mark):
78 81 raise util.Abort(_("bookmark '%s' contains illegal "
79 82 "character" % mark))
80 83
81 84 wlock = repo.wlock()
82 85 try:
83 86
84 87 file = repo.opener('bookmarks', 'w', atomictemp=True)
85 88 for refspec, node in refs.iteritems():
86 89 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
87 90 file.rename()
88 91
89 92 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
90 93 try:
91 94 os.utime(repo.sjoin('00changelog.i'), None)
92 95 except OSError:
93 96 pass
94 97
95 98 finally:
96 99 wlock.release()
97 100
98 101 def setcurrent(repo, mark):
99 102 '''Set the name of the bookmark that we are currently on
100 103
101 104 Set the name of the bookmark that we are on (hg update <bookmark>).
102 105 The name is recorded in .hg/bookmarks.current
103 106 '''
104 107 current = repo._bookmarkcurrent
105 108 if current == mark:
106 109 return
107 110
108 111 if mark not in repo._bookmarks:
109 112 mark = ''
110 113 if not valid(mark):
111 114 raise util.Abort(_("bookmark '%s' contains illegal "
112 115 "character" % mark))
113 116
114 117 wlock = repo.wlock()
115 118 try:
116 119 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
117 120 file.write(encoding.fromlocal(mark))
118 121 file.rename()
119 122 finally:
120 123 wlock.release()
121 124 repo._bookmarkcurrent = mark
122 125
123 126 def updatecurrentbookmark(repo, oldnode, curbranch):
124 127 try:
125 128 update(repo, oldnode, repo.branchtags()[curbranch])
126 129 except KeyError:
127 130 if curbranch == "default": # no default branch!
128 131 update(repo, oldnode, repo.lookup("tip"))
129 132 else:
130 133 raise util.Abort(_("branch %s not found") % curbranch)
131 134
132 135 def update(repo, parents, node):
133 136 marks = repo._bookmarks
134 137 update = False
135 138 mark = repo._bookmarkcurrent
136 139 if mark and marks[mark] in parents:
137 140 old = repo[marks[mark]]
138 141 new = repo[node]
139 142 if new in old.descendants():
140 143 marks[mark] = new.node()
141 144 update = True
142 145 if update:
143 146 write(repo)
144 147
145 148 def listbookmarks(repo):
146 149 # We may try to list bookmarks on a repo type that does not
147 150 # support it (e.g., statichttprepository).
148 151 if not hasattr(repo, '_bookmarks'):
149 152 return {}
150 153
151 154 d = {}
152 155 for k, v in repo._bookmarks.iteritems():
153 156 d[k] = hex(v)
154 157 return d
155 158
156 159 def pushbookmark(repo, key, old, new):
157 160 w = repo.wlock()
158 161 try:
159 162 marks = repo._bookmarks
160 163 if hex(marks.get(key, '')) != old:
161 164 return False
162 165 if new == '':
163 166 del marks[key]
164 167 else:
165 168 if new not in repo:
166 169 return False
167 170 marks[key] = repo[new].node()
168 171 write(repo)
169 172 return True
170 173 finally:
171 174 w.release()
172 175
173 176 def updatefromremote(ui, repo, remote):
174 177 ui.debug("checking for updated bookmarks\n")
175 178 rb = remote.listkeys('bookmarks')
176 179 changed = False
177 180 for k in rb.keys():
178 181 if k in repo._bookmarks:
179 182 nr, nl = rb[k], repo._bookmarks[k]
180 183 if nr in repo:
181 184 cr = repo[nr]
182 185 cl = repo[nl]
183 186 if cl.rev() >= cr.rev():
184 187 continue
185 188 if cr in cl.descendants():
186 189 repo._bookmarks[k] = cr.node()
187 190 changed = True
188 191 ui.status(_("updating bookmark %s\n") % k)
189 192 else:
190 193 ui.warn(_("not updating divergent"
191 194 " bookmark %s\n") % k)
192 195 if changed:
193 196 write(repo)
194 197
195 198 def diff(ui, repo, remote):
196 199 ui.status(_("searching for changed bookmarks\n"))
197 200
198 201 lmarks = repo.listkeys('bookmarks')
199 202 rmarks = remote.listkeys('bookmarks')
200 203
201 204 diff = sorted(set(rmarks) - set(lmarks))
202 205 for k in diff:
203 206 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
204 207
205 208 if len(diff) <= 0:
206 209 ui.status(_("no changed bookmarks found\n"))
207 210 return 1
208 211 return 0
@@ -1,344 +1,352 b''
1 1 $ hg init
2 2
3 3 no bookmarks
4 4
5 5 $ hg bookmarks
6 6 no bookmarks set
7 7
8 8 bookmark rev -1
9 9
10 10 $ hg bookmark X
11 11
12 12 list bookmarks
13 13
14 14 $ hg bookmarks
15 15 * X -1:000000000000
16 16
17 17 list bookmarks with color
18 18
19 19 $ hg --config extensions.color= --config color.mode=ansi \
20 20 > bookmarks --color=always
21 21 \x1b[0;32m * X -1:000000000000\x1b[0m (esc)
22 22
23 23 $ echo a > a
24 24 $ hg add a
25 25 $ hg commit -m 0
26 26
27 27 bookmark X moved to rev 0
28 28
29 29 $ hg bookmarks
30 30 * X 0:f7b1eb17ad24
31 31
32 32 look up bookmark
33 33
34 34 $ hg log -r X
35 35 changeset: 0:f7b1eb17ad24
36 36 bookmark: X
37 37 tag: tip
38 38 user: test
39 39 date: Thu Jan 01 00:00:00 1970 +0000
40 40 summary: 0
41 41
42 42
43 43 second bookmark for rev 0
44 44
45 45 $ hg bookmark X2
46 46
47 47 bookmark rev -1 again
48 48
49 49 $ hg bookmark -r null Y
50 50
51 51 list bookmarks
52 52
53 53 $ hg bookmarks
54 54 X 0:f7b1eb17ad24
55 55 * X2 0:f7b1eb17ad24
56 56 Y -1:000000000000
57 57
58 58 $ echo b > b
59 59 $ hg add b
60 60 $ hg commit -m 1
61 61
62 62 bookmarks revset
63 63
64 64 $ hg log -r 'bookmark()'
65 65 changeset: 0:f7b1eb17ad24
66 66 bookmark: X
67 67 user: test
68 68 date: Thu Jan 01 00:00:00 1970 +0000
69 69 summary: 0
70 70
71 71 changeset: 1:925d80f479bb
72 72 bookmark: X2
73 73 tag: tip
74 74 user: test
75 75 date: Thu Jan 01 00:00:00 1970 +0000
76 76 summary: 1
77 77
78 78 $ hg log -r 'bookmark(Y)'
79 79 $ hg log -r 'bookmark(X2)'
80 80 changeset: 1:925d80f479bb
81 81 bookmark: X2
82 82 tag: tip
83 83 user: test
84 84 date: Thu Jan 01 00:00:00 1970 +0000
85 85 summary: 1
86 86
87 87 $ hg log -r 'bookmark(unknown)'
88 88 abort: bookmark 'unknown' does not exist
89 89 [255]
90 90
91 91 $ hg help revsets | grep 'bookmark('
92 92 "bookmark([name])"
93 93
94 94 bookmarks X and X2 moved to rev 1, Y at rev -1
95 95
96 96 $ hg bookmarks
97 97 X 0:f7b1eb17ad24
98 98 * X2 1:925d80f479bb
99 99 Y -1:000000000000
100 100
101 101 bookmark rev 0 again
102 102
103 103 $ hg bookmark -r 0 Z
104 104
105 105 $ hg update X
106 106 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
107 107 $ echo c > c
108 108 $ hg add c
109 109 $ hg commit -m 2
110 110 created new head
111 111
112 112 bookmarks X moved to rev 2, Y at rev -1, Z at rev 0
113 113
114 114 $ hg bookmarks
115 115 * X 2:db815d6d32e6
116 116 X2 1:925d80f479bb
117 117 Y -1:000000000000
118 118 Z 0:f7b1eb17ad24
119 119
120 120 rename nonexistent bookmark
121 121
122 122 $ hg bookmark -m A B
123 123 abort: bookmark 'A' does not exist
124 124 [255]
125 125
126 126 rename to existent bookmark
127 127
128 128 $ hg bookmark -m X Y
129 129 abort: bookmark 'Y' already exists (use -f to force)
130 130 [255]
131 131
132 132 force rename to existent bookmark
133 133
134 134 $ hg bookmark -f -m X Y
135 135
136 136 list bookmarks
137 137
138 138 $ hg bookmark
139 139 X2 1:925d80f479bb
140 140 * Y 2:db815d6d32e6
141 141 Z 0:f7b1eb17ad24
142 142
143 143 rename without new name
144 144
145 145 $ hg bookmark -m Y
146 146 abort: new bookmark name required
147 147 [255]
148 148
149 149 delete without name
150 150
151 151 $ hg bookmark -d
152 152 abort: bookmark name required
153 153 [255]
154 154
155 155 delete nonexistent bookmark
156 156
157 157 $ hg bookmark -d A
158 158 abort: bookmark 'A' does not exist
159 159 [255]
160 160
161 161 bookmark name with spaces should be stripped
162 162
163 163 $ hg bookmark ' x y '
164 164
165 165 list bookmarks
166 166
167 167 $ hg bookmarks
168 168 X2 1:925d80f479bb
169 169 Y 2:db815d6d32e6
170 170 Z 0:f7b1eb17ad24
171 171 * x y 2:db815d6d32e6
172 172
173 173 look up stripped bookmark name
174 174
175 175 $ hg log -r '"x y"'
176 176 changeset: 2:db815d6d32e6
177 177 bookmark: Y
178 178 bookmark: x y
179 179 tag: tip
180 180 parent: 0:f7b1eb17ad24
181 181 user: test
182 182 date: Thu Jan 01 00:00:00 1970 +0000
183 183 summary: 2
184 184
185 185
186 186 reject bookmark name with newline
187 187
188 188 $ hg bookmark '
189 189 > '
190 190 abort: bookmark name cannot contain newlines
191 191 [255]
192 192
193 193 bookmark with existing name
194 194
195 195 $ hg bookmark Z
196 196 abort: bookmark 'Z' already exists (use -f to force)
197 197 [255]
198 198
199 199 force bookmark with existing name
200 200
201 201 $ hg bookmark -f Z
202 202
203 203 list bookmarks
204 204
205 205 $ hg bookmark
206 206 X2 1:925d80f479bb
207 207 Y 2:db815d6d32e6
208 208 * Z 2:db815d6d32e6
209 209 x y 2:db815d6d32e6
210 210
211 211 revision but no bookmark name
212 212
213 213 $ hg bookmark -r .
214 214 abort: bookmark name required
215 215 [255]
216 216
217 217 bookmark name with whitespace only
218 218
219 219 $ hg bookmark ' '
220 220 abort: bookmark names cannot consist entirely of whitespace
221 221 [255]
222 222
223 223 invalid bookmark
224 224
225 225 $ hg bookmark 'foo:bar'
226 226 abort: bookmark 'foo:bar' contains illegal character
227 227 [255]
228 228
229 229 the bookmark extension should be ignored now that it is part of core
230 230
231 231 $ echo "[extensions]" >> $HGRCPATH
232 232 $ echo "bookmarks=" >> $HGRCPATH
233 233 $ hg bookmarks
234 234 X2 1:925d80f479bb
235 235 Y 2:db815d6d32e6
236 236 * Z 2:db815d6d32e6
237 237 x y 2:db815d6d32e6
238 238
239 239 test summary
240 240
241 241 $ hg summary
242 242 parent: 2:db815d6d32e6 tip Y Z x y
243 243 2
244 244 branch: default
245 245 commit: (clean)
246 246 update: 1 new changesets, 2 branch heads (merge)
247 247
248 248 test id
249 249
250 250 $ hg id
251 251 db815d6d32e6 tip Y/Z/x y
252 252
253 253 test rollback
254 254
255 255 $ echo foo > f1
256 256 $ hg ci -Amr
257 257 adding f1
258 258 $ hg bookmark -f Y -r 1
259 259 $ hg bookmark -f Z -r 1
260 260 $ hg rollback
261 261 repository tip rolled back to revision 2 (undo commit)
262 262 working directory now based on revision 2
263 263 $ hg bookmarks
264 264 X2 1:925d80f479bb
265 265 Y 2:db815d6d32e6
266 266 * Z 2:db815d6d32e6
267 267 x y 2:db815d6d32e6
268 268
269 269 test clone
270 270
271 271 $ hg bookmarks
272 272 X2 1:925d80f479bb
273 273 Y 2:db815d6d32e6
274 274 * Z 2:db815d6d32e6
275 275 x y 2:db815d6d32e6
276 276 $ hg clone . cloned-bookmarks
277 277 updating to branch default
278 278 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
279 279 $ hg -R cloned-bookmarks bookmarks
280 280 X2 1:925d80f479bb
281 281 Y 2:db815d6d32e6
282 282 Z 2:db815d6d32e6
283 283 x y 2:db815d6d32e6
284 284
285 285 test clone with pull protocol
286 286
287 287 $ hg clone --pull . cloned-bookmarks-pull
288 288 requesting all changes
289 289 adding changesets
290 290 adding manifests
291 291 adding file changes
292 292 added 3 changesets with 3 changes to 3 files (+1 heads)
293 293 updating to branch default
294 294 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 295 $ hg -R cloned-bookmarks-pull bookmarks
296 296 X2 1:925d80f479bb
297 297 Y 2:db815d6d32e6
298 298 Z 2:db815d6d32e6
299 299 x y 2:db815d6d32e6
300 300
301 301 test clone with a specific revision
302 302
303 303 $ hg clone -r 925d80 . cloned-bookmarks-rev
304 304 adding changesets
305 305 adding manifests
306 306 adding file changes
307 307 added 2 changesets with 2 changes to 2 files
308 308 updating to branch default
309 309 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
310 310 $ hg -R cloned-bookmarks-rev bookmarks
311 311 X2 1:925d80f479bb
312 312
313 313 create bundle with two heads
314 314
315 315 $ hg clone . tobundle
316 316 updating to branch default
317 317 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
318 318 $ echo x > tobundle/x
319 319 $ hg -R tobundle add tobundle/x
320 320 $ hg -R tobundle commit -m'x'
321 321 $ hg -R tobundle update -r -2
322 322 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
323 323 $ echo y > tobundle/y
324 324 $ hg -R tobundle branch test
325 325 marked working directory as branch test
326 326 $ hg -R tobundle add tobundle/y
327 327 $ hg -R tobundle commit -m'y'
328 328 $ hg -R tobundle bundle tobundle.hg
329 329 searching for changes
330 330 2 changesets found
331 331 $ hg unbundle tobundle.hg
332 332 adding changesets
333 333 adding manifests
334 334 adding file changes
335 335 added 2 changesets with 2 changes to 2 files (+1 heads)
336 336 (run 'hg heads' to see heads, 'hg merge' to merge)
337 337 $ hg update
338 338 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
339 339 $ hg bookmarks
340 340 X2 1:925d80f479bb
341 341 Y 2:db815d6d32e6
342 342 * Z 3:125c9a1d6df6
343 343 x y 2:db815d6d32e6
344 344
345 test wrongly formated bookmark
346
347 $ echo '' >> .hg/bookmarks
348 $ hg bookmarks
349 X2 1:925d80f479bb
350 Y 2:db815d6d32e6
351 * Z 3:125c9a1d6df6
352 x y 2:db815d6d32e6
General Comments 0
You need to be logged in to leave comments. Login now