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