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