##// END OF EJS Templates
bookmarks: when @ bookmark diverges, don't double the @ sign (BC)...
David M. Carr -
r17770:6c81b8eb default
parent child Browse files
Show More
@@ -1,284 +1,288
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 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 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 83 if not valid(mark):
84 84 raise util.Abort(_("bookmark '%s' contains illegal "
85 85 "character" % mark))
86 86
87 87 wlock = repo.wlock()
88 88 try:
89 89
90 90 file = repo.opener('bookmarks', 'w', atomictemp=True)
91 91 for refspec, node in refs.iteritems():
92 92 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
93 93 file.close()
94 94
95 95 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
96 96 try:
97 97 os.utime(repo.sjoin('00changelog.i'), None)
98 98 except OSError:
99 99 pass
100 100
101 101 finally:
102 102 wlock.release()
103 103
104 104 def setcurrent(repo, mark):
105 105 '''Set the name of the bookmark that we are currently on
106 106
107 107 Set the name of the bookmark that we are on (hg update <bookmark>).
108 108 The name is recorded in .hg/bookmarks.current
109 109 '''
110 110 current = repo._bookmarkcurrent
111 111 if current == mark:
112 112 return
113 113
114 114 if mark not in repo._bookmarks:
115 115 mark = ''
116 116 if not valid(mark):
117 117 raise util.Abort(_("bookmark '%s' contains illegal "
118 118 "character" % mark))
119 119
120 120 wlock = repo.wlock()
121 121 try:
122 122 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
123 123 file.write(encoding.fromlocal(mark))
124 124 file.close()
125 125 finally:
126 126 wlock.release()
127 127 repo._bookmarkcurrent = mark
128 128
129 129 def unsetcurrent(repo):
130 130 wlock = repo.wlock()
131 131 try:
132 132 try:
133 133 util.unlink(repo.join('bookmarks.current'))
134 134 repo._bookmarkcurrent = None
135 135 except OSError, inst:
136 136 if inst.errno != errno.ENOENT:
137 137 raise
138 138 finally:
139 139 wlock.release()
140 140
141 141 def updatecurrentbookmark(repo, oldnode, curbranch):
142 142 try:
143 143 return update(repo, oldnode, repo.branchtip(curbranch))
144 144 except error.RepoLookupError:
145 145 if curbranch == "default": # no default branch!
146 146 return update(repo, oldnode, repo.lookup("tip"))
147 147 else:
148 148 raise util.Abort(_("branch %s not found") % curbranch)
149 149
150 150 def update(repo, parents, node):
151 151 marks = repo._bookmarks
152 152 update = False
153 153 cur = repo._bookmarkcurrent
154 154 if not cur:
155 155 return False
156 156
157 157 toupdate = [b for b in marks if b.split('@', 1)[0] == cur.split('@', 1)[0]]
158 158 for mark in toupdate:
159 159 if mark and marks[mark] in parents:
160 160 old = repo[marks[mark]]
161 161 new = repo[node]
162 162 if old.descendant(new) and mark == cur:
163 163 marks[cur] = new.node()
164 164 update = True
165 165 if mark != cur:
166 166 del marks[mark]
167 167 if update:
168 168 repo._writebookmarks(marks)
169 169 return update
170 170
171 171 def listbookmarks(repo):
172 172 # We may try to list bookmarks on a repo type that does not
173 173 # support it (e.g., statichttprepository).
174 174 marks = getattr(repo, '_bookmarks', {})
175 175
176 176 d = {}
177 177 for k, v in marks.iteritems():
178 178 # don't expose local divergent bookmarks
179 179 if '@' not in k or k.endswith('@'):
180 180 d[k] = hex(v)
181 181 return d
182 182
183 183 def pushbookmark(repo, key, old, new):
184 184 w = repo.wlock()
185 185 try:
186 186 marks = repo._bookmarks
187 187 if hex(marks.get(key, '')) != old:
188 188 return False
189 189 if new == '':
190 190 del marks[key]
191 191 else:
192 192 if new not in repo:
193 193 return False
194 194 marks[key] = repo[new].node()
195 195 write(repo)
196 196 return True
197 197 finally:
198 198 w.release()
199 199
200 200 def updatefromremote(ui, repo, remote, path):
201 201 ui.debug("checking for updated bookmarks\n")
202 202 rb = remote.listkeys('bookmarks')
203 203 changed = False
204 204 for k in rb.keys():
205 205 if k in repo._bookmarks:
206 206 nr, nl = rb[k], repo._bookmarks[k]
207 207 if nr in repo:
208 208 cr = repo[nr]
209 209 cl = repo[nl]
210 210 if cl.rev() >= cr.rev():
211 211 continue
212 212 if validdest(repo, cl, cr):
213 213 repo._bookmarks[k] = cr.node()
214 214 changed = True
215 215 ui.status(_("updating bookmark %s\n") % k)
216 216 else:
217 if k == '@':
218 kd = ''
219 else:
220 kd = k
217 221 # find a unique @ suffix
218 222 for x in range(1, 100):
219 n = '%s@%d' % (k, x)
223 n = '%s@%d' % (kd, x)
220 224 if n not in repo._bookmarks:
221 225 break
222 226 # try to use an @pathalias suffix
223 227 # if an @pathalias already exists, we overwrite (update) it
224 228 for p, u in ui.configitems("paths"):
225 229 if path == u:
226 n = '%s@%s' % (k, p)
230 n = '%s@%s' % (kd, p)
227 231
228 232 repo._bookmarks[n] = cr.node()
229 233 changed = True
230 234 ui.warn(_("divergent bookmark %s stored as %s\n") % (k, n))
231 235 elif rb[k] in repo:
232 236 # add remote bookmarks for changes we already have
233 237 repo._bookmarks[k] = repo[rb[k]].node()
234 238 changed = True
235 239 ui.status(_("adding remote bookmark %s\n") % k)
236 240
237 241 if changed:
238 242 write(repo)
239 243
240 244 def diff(ui, dst, src):
241 245 ui.status(_("searching for changed bookmarks\n"))
242 246
243 247 smarks = src.listkeys('bookmarks')
244 248 dmarks = dst.listkeys('bookmarks')
245 249
246 250 diff = sorted(set(smarks) - set(dmarks))
247 251 for k in diff:
248 252 mark = ui.debugflag and smarks[k] or smarks[k][:12]
249 253 ui.write(" %-25s %s\n" % (k, mark))
250 254
251 255 if len(diff) <= 0:
252 256 ui.status(_("no changed bookmarks found\n"))
253 257 return 1
254 258 return 0
255 259
256 260 def validdest(repo, old, new):
257 261 """Is the new bookmark destination a valid update from the old one"""
258 262 if old == new:
259 263 # Old == new -> nothing to update.
260 264 return False
261 265 elif not old:
262 266 # old is nullrev, anything is valid.
263 267 # (new != nullrev has been excluded by the previous check)
264 268 return True
265 269 elif repo.obsstore:
266 270 # We only need this complicated logic if there is obsolescence
267 271 # XXX will probably deserve an optimised revset.
268 272
269 273 validdests = set([old])
270 274 plen = -1
271 275 # compute the whole set of successors or descendants
272 276 while len(validdests) != plen:
273 277 plen = len(validdests)
274 278 succs = set(c.node() for c in validdests)
275 279 for c in validdests:
276 280 if c.phase() > phases.public:
277 281 # obsolescence marker does not apply to public changeset
278 282 succs.update(obsolete.anysuccessors(repo.obsstore,
279 283 c.node()))
280 284 validdests = set(repo.set('%ln::', succs))
281 285 validdests.remove(old)
282 286 return new in validdests
283 287 else:
284 288 return old.descendant(new)
@@ -1,346 +1,361
1 1 $ "$TESTDIR/hghave" serve || exit 80
2 2
3 3 $ cat << EOF >> $HGRCPATH
4 4 > [phases]
5 5 > publish=False
6 6 > [extensions]
7 7 > EOF
8 8 $ cat > obs.py << EOF
9 9 > import mercurial.obsolete
10 10 > mercurial.obsolete._enabled = True
11 11 > EOF
12 12 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
13 13
14 14 initialize
15 15
16 16 $ hg init a
17 17 $ cd a
18 18 $ echo 'test' > test
19 19 $ hg commit -Am'test'
20 20 adding test
21 21
22 22 set bookmarks
23 23
24 24 $ hg bookmark X
25 25 $ hg bookmark Y
26 26 $ hg bookmark Z
27 27
28 28 import bookmark by name
29 29
30 30 $ hg init ../b
31 31 $ cd ../b
32 32 $ hg book Y
33 33 $ hg book
34 34 * Y -1:000000000000
35 35 $ hg pull ../a
36 36 pulling from ../a
37 37 requesting all changes
38 38 adding changesets
39 39 adding manifests
40 40 adding file changes
41 41 added 1 changesets with 1 changes to 1 files
42 42 updating bookmark Y
43 43 adding remote bookmark X
44 44 adding remote bookmark Z
45 45 (run 'hg update' to get a working copy)
46 46 $ hg bookmarks
47 47 X 0:4e3505fd9583
48 48 Y 0:4e3505fd9583
49 49 Z 0:4e3505fd9583
50 50 $ hg debugpushkey ../a namespaces
51 51 bookmarks
52 52 phases
53 53 namespaces
54 54 obsolete
55 55 $ hg debugpushkey ../a bookmarks
56 56 Y 4e3505fd95835d721066b76e75dbb8cc554d7f77
57 57 X 4e3505fd95835d721066b76e75dbb8cc554d7f77
58 58 Z 4e3505fd95835d721066b76e75dbb8cc554d7f77
59 59 $ hg pull -B X ../a
60 60 pulling from ../a
61 61 no changes found
62 62 importing bookmark X
63 63 $ hg bookmark
64 64 X 0:4e3505fd9583
65 65 Y 0:4e3505fd9583
66 66 Z 0:4e3505fd9583
67 67
68 68 export bookmark by name
69 69
70 70 $ hg bookmark W
71 71 $ hg bookmark foo
72 72 $ hg bookmark foobar
73 73 $ hg push -B W ../a
74 74 pushing to ../a
75 75 searching for changes
76 76 no changes found
77 77 exporting bookmark W
78 78 [1]
79 79 $ hg -R ../a bookmarks
80 80 W -1:000000000000
81 81 X 0:4e3505fd9583
82 82 Y 0:4e3505fd9583
83 83 * Z 0:4e3505fd9583
84 84
85 85 delete a remote bookmark
86 86
87 87 $ hg book -d W
88 88 $ hg push -B W ../a
89 89 pushing to ../a
90 90 searching for changes
91 91 no changes found
92 92 deleting remote bookmark W
93 93 [1]
94 94
95 95 push/pull name that doesn't exist
96 96
97 97 $ hg push -B badname ../a
98 98 pushing to ../a
99 99 searching for changes
100 100 no changes found
101 101 bookmark badname does not exist on the local or remote repository!
102 102 [2]
103 103 $ hg pull -B anotherbadname ../a
104 104 pulling from ../a
105 105 abort: remote bookmark anotherbadname not found!
106 106 [255]
107 107
108 108 divergent bookmarks
109 109
110 110 $ cd ../a
111 111 $ echo c1 > f1
112 112 $ hg ci -Am1
113 113 adding f1
114 $ hg book -f @
114 115 $ hg book -f X
115 116 $ hg book
117 @ 1:0d2164f0ce0d
116 118 * X 1:0d2164f0ce0d
117 119 Y 0:4e3505fd9583
118 120 Z 1:0d2164f0ce0d
119 121
120 122 $ cd ../b
121 123 $ hg up
122 124 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
123 125 updating bookmark foobar
124 126 $ echo c2 > f2
125 127 $ hg ci -Am2
126 128 adding f2
129 $ hg book -f @
127 130 $ hg book -f X
128 131 $ hg book
132 @ 1:9b140be10808
129 133 * X 1:9b140be10808
130 134 Y 0:4e3505fd9583
131 135 Z 0:4e3505fd9583
132 136 foo -1:000000000000
133 137 foobar 1:9b140be10808
134 138
135 139 $ hg pull --config paths.foo=../a foo
136 140 pulling from $TESTTMP/a (glob)
137 141 searching for changes
138 142 adding changesets
139 143 adding manifests
140 144 adding file changes
141 145 added 1 changesets with 1 changes to 1 files (+1 heads)
142 146 divergent bookmark X stored as X@foo
143 147 updating bookmark Z
148 divergent bookmark @ stored as @foo
144 149 (run 'hg heads' to see heads, 'hg merge' to merge)
145 150 $ hg book
151 @ 1:9b140be10808
152 @foo 2:0d2164f0ce0d
146 153 * X 1:9b140be10808
147 154 X@foo 2:0d2164f0ce0d
148 155 Y 0:4e3505fd9583
149 156 Z 2:0d2164f0ce0d
150 157 foo -1:000000000000
151 158 foobar 1:9b140be10808
152 159 $ hg push -f ../a
153 160 pushing to ../a
154 161 searching for changes
155 162 adding changesets
156 163 adding manifests
157 164 adding file changes
158 165 added 1 changesets with 1 changes to 1 files (+1 heads)
159 166 $ hg -R ../a book
167 @ 1:0d2164f0ce0d
160 168 * X 1:0d2164f0ce0d
161 169 Y 0:4e3505fd9583
162 170 Z 1:0d2164f0ce0d
163 171
164 172 update a remote bookmark from a non-head to a head
165 173
166 174 $ hg up -q Y
167 175 $ echo c3 > f2
168 176 $ hg ci -Am3
169 177 adding f2
170 178 created new head
171 179 $ hg push ../a
172 180 pushing to ../a
173 181 searching for changes
174 182 adding changesets
175 183 adding manifests
176 184 adding file changes
177 185 added 1 changesets with 1 changes to 1 files (+1 heads)
178 186 updating bookmark Y
179 187 $ hg -R ../a book
188 @ 1:0d2164f0ce0d
180 189 * X 1:0d2164f0ce0d
181 190 Y 3:f6fc62dde3c0
182 191 Z 1:0d2164f0ce0d
183 192
184 193 diverging a remote bookmark fails
185 194
186 195 $ hg up -q 4e3505fd9583
187 196 $ echo c4 > f2
188 197 $ hg ci -Am4
189 198 adding f2
190 199 created new head
191 200 $ hg book -f Y
192 201
193 202 $ cat <<EOF > ../a/.hg/hgrc
194 203 > [web]
195 204 > push_ssl = false
196 205 > allow_push = *
197 206 > EOF
198 207
199 208 $ hg -R ../a serve -p $HGPORT2 -d --pid-file=../hg2.pid
200 209 $ cat ../hg2.pid >> $DAEMON_PIDS
201 210
202 211 $ hg push http://localhost:$HGPORT2/
203 212 pushing to http://localhost:$HGPORT2/
204 213 searching for changes
205 214 abort: push creates new remote head 4efff6d98829!
206 215 (did you forget to merge? use push -f to force)
207 216 [255]
208 217 $ hg -R ../a book
218 @ 1:0d2164f0ce0d
209 219 * X 1:0d2164f0ce0d
210 220 Y 3:f6fc62dde3c0
211 221 Z 1:0d2164f0ce0d
212 222
213 223
214 224 Unrelated marker does not alter the decision
215 225
216 226 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
217 227 $ hg push http://localhost:$HGPORT2/
218 228 pushing to http://localhost:$HGPORT2/
219 229 searching for changes
220 230 abort: push creates new remote head 4efff6d98829!
221 231 (did you forget to merge? use push -f to force)
222 232 [255]
223 233 $ hg -R ../a book
234 @ 1:0d2164f0ce0d
224 235 * X 1:0d2164f0ce0d
225 236 Y 3:f6fc62dde3c0
226 237 Z 1:0d2164f0ce0d
227 238
228 239 Update to a successor works
229 240
230 241 $ hg id --debug -r 3
231 242 f6fc62dde3c0771e29704af56ba4d8af77abcc2f
232 243 $ hg id --debug -r 4
233 244 4efff6d98829d9c824c621afd6e3f01865f5439f tip Y
234 245 $ hg debugobsolete f6fc62dde3c0771e29704af56ba4d8af77abcc2f 4efff6d98829d9c824c621afd6e3f01865f5439f
235 246 $ hg push http://localhost:$HGPORT2/
236 247 pushing to http://localhost:$HGPORT2/
237 248 searching for changes
238 249 remote: adding changesets
239 250 remote: adding manifests
240 251 remote: adding file changes
241 252 remote: added 1 changesets with 1 changes to 1 files (+1 heads)
242 253 updating bookmark Y
243 254 $ hg -R ../a book
255 @ 1:0d2164f0ce0d
244 256 * X 1:0d2164f0ce0d
245 257 Y 4:4efff6d98829
246 258 Z 1:0d2164f0ce0d
247 259
248 260 hgweb
249 261
250 262 $ cat <<EOF > .hg/hgrc
251 263 > [web]
252 264 > push_ssl = false
253 265 > allow_push = *
254 266 > EOF
255 267
256 268 $ hg serve -p $HGPORT -d --pid-file=../hg.pid -E errors.log
257 269 $ cat ../hg.pid >> $DAEMON_PIDS
258 270 $ cd ../a
259 271
260 272 $ hg debugpushkey http://localhost:$HGPORT/ namespaces
261 273 bookmarks
262 274 phases
263 275 namespaces
264 276 obsolete
265 277 $ hg debugpushkey http://localhost:$HGPORT/ bookmarks
278 @ 9b140be1080824d768c5a4691a564088eede71f9
279 foo 0000000000000000000000000000000000000000
280 foobar 9b140be1080824d768c5a4691a564088eede71f9
266 281 Y 4efff6d98829d9c824c621afd6e3f01865f5439f
267 foobar 9b140be1080824d768c5a4691a564088eede71f9
282 X 9b140be1080824d768c5a4691a564088eede71f9
268 283 Z 0d2164f0ce0d8f1d6f94351eba04b794909be66c
269 foo 0000000000000000000000000000000000000000
270 X 9b140be1080824d768c5a4691a564088eede71f9
271 284 $ hg out -B http://localhost:$HGPORT/
272 285 comparing with http://localhost:$HGPORT/
273 286 searching for changed bookmarks
274 287 no changed bookmarks found
275 288 [1]
276 289 $ hg push -B Z http://localhost:$HGPORT/
277 290 pushing to http://localhost:$HGPORT/
278 291 searching for changes
279 292 no changes found
280 293 exporting bookmark Z
281 294 [1]
282 295 $ hg book -d Z
283 296 $ hg in -B http://localhost:$HGPORT/
284 297 comparing with http://localhost:$HGPORT/
285 298 searching for changed bookmarks
286 299 Z 0d2164f0ce0d
287 300 foo 000000000000
288 301 foobar 9b140be10808
289 302 $ hg pull -B Z http://localhost:$HGPORT/
290 303 pulling from http://localhost:$HGPORT/
291 304 no changes found
305 divergent bookmark @ stored as @1
306 adding remote bookmark foo
292 307 adding remote bookmark foobar
308 divergent bookmark X stored as X@1
293 309 adding remote bookmark Z
294 adding remote bookmark foo
295 divergent bookmark X stored as X@1
296 310 importing bookmark Z
297 311 $ hg clone http://localhost:$HGPORT/ cloned-bookmarks
298 312 requesting all changes
299 313 adding changesets
300 314 adding manifests
301 315 adding file changes
302 316 added 4 changesets with 4 changes to 3 files (+2 heads)
303 317 updating to branch default
304 318 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
305 319 $ hg -R cloned-bookmarks bookmarks
320 @ 1:9b140be10808
306 321 X 1:9b140be10808
307 322 Y 3:4efff6d98829
308 323 Z 2:0d2164f0ce0d
309 324 foo -1:000000000000
310 325 foobar 1:9b140be10808
311 326
312 327 $ cd ..
313 328
314 329 Pushing a bookmark should only push the changes required by that
315 330 bookmark, not all outgoing changes:
316 331 $ hg clone http://localhost:$HGPORT/ addmarks
317 332 requesting all changes
318 333 adding changesets
319 334 adding manifests
320 335 adding file changes
321 336 added 4 changesets with 4 changes to 3 files (+2 heads)
322 337 updating to branch default
323 338 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
324 339 $ cd addmarks
325 340 $ echo foo > foo
326 341 $ hg add foo
327 342 $ hg commit -m 'add foo'
328 343 $ echo bar > bar
329 344 $ hg add bar
330 345 $ hg commit -m 'add bar'
331 346 $ hg co "tip^"
332 347 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
333 348 $ hg book add-foo
334 349 $ hg book -r tip add-bar
335 350 Note: this push *must* push only a single changeset, as that's the point
336 351 of this test.
337 352 $ hg push -B add-foo --traceback
338 353 pushing to http://localhost:$HGPORT/
339 354 searching for changes
340 355 remote: adding changesets
341 356 remote: adding manifests
342 357 remote: adding file changes
343 358 remote: added 1 changesets with 1 changes to 1 files
344 359 exporting bookmark add-foo
345 360
346 361 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now