##// END OF EJS Templates
Hook fixups...
mpm@selenic.com -
r1316:b650bfdf default
parent child Browse files
Show More
@@ -1,229 +1,231
1 1 HGRC(5)
2 2 =======
3 3 Bryan O'Sullivan <bos@serpentine.com>
4 4
5 5 NAME
6 6 ----
7 7 hgrc - configuration files for Mercurial
8 8
9 9 SYNOPSIS
10 10 --------
11 11
12 12 The Mercurial system uses a set of configuration files to control
13 13 aspects of its behaviour.
14 14
15 15 FILES
16 16 -----
17 17
18 18 Mercurial reads configuration data from up to three files, if they
19 19 exist. The names of these files depend on the system on which
20 20 Mercurial is installed.
21 21
22 22 (Unix) /etc/mercurial/hgrc::
23 23 (Windows) C:\Mercurial\Mercurial.ini::
24 24 Options in this global configuration file apply to all Mercurial
25 25 commands executed by any user in any directory.
26 26
27 27 (Unix) $HOME/.hgrc::
28 28 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini
29 29 Per-user configuration options that apply to all Mercurial commands,
30 30 no matter from which directory they are run. Values in this file
31 31 override global settings.
32 32
33 33 (Unix, Windows) <repo>/.hg/hgrc::
34 34 Per-repository configuration options that only apply in a
35 35 particular repository. This file is not version-controlled, and
36 36 will not get transferred during a "clone" operation. Values in
37 37 this file override global and per-user settings.
38 38
39 39 SYNTAX
40 40 ------
41 41
42 42 A configuration file consists of sections, led by a "[section]" header
43 43 and followed by "name: value" entries; "name=value" is also accepted.
44 44
45 45 [spam]
46 46 eggs=ham
47 47 green=
48 48 eggs
49 49
50 50 Each line contains one entry. If the lines that follow are indented,
51 51 they are treated as continuations of that entry.
52 52
53 53 Leading whitespace is removed from values. Empty lines are skipped.
54 54
55 55 The optional values can contain format strings which refer to other
56 56 values in the same section, or values in a special DEFAULT section.
57 57
58 58 Lines beginning with "#" or ";" are ignored and may be used to provide
59 59 comments.
60 60
61 61 SECTIONS
62 62 --------
63 63
64 64 This section describes the different sections that may appear in a
65 65 Mercurial "hgrc" file, the purpose of each section, its possible
66 66 keys, and their possible values.
67 67
68 68 decode/encode::
69 69 Filters for transforming files on checkout/checkin. This would
70 70 typically be used for newline processing or other
71 71 localization/canonicalization of files.
72 72
73 73 Filters consist of a filter pattern followed by a filter command.
74 74 Filter patterns are globs by default, rooted at the repository
75 75 root. For example, to match any file ending in ".txt" in the root
76 76 directory only, use the pattern "*.txt". To match any file ending
77 77 in ".c" anywhere in the repository, use the pattern "**.c".
78 78
79 79 The filter command can start with a specifier, either "pipe:" or
80 80 "tempfile:". If no specifier is given, "pipe:" is used by default.
81 81
82 82 A "pipe:" command must accept data on stdin and return the
83 83 transformed data on stdout.
84 84
85 85 Pipe example:
86 86
87 87 [encode]
88 88 # uncompress gzip files on checkin to improve delta compression
89 89 # note: not necessarily a good idea, just an example
90 90 *.gz = pipe: gunzip
91 91
92 92 [decode]
93 93 # recompress gzip files when writing them to the working dir (we
94 94 # can safely omit "pipe:", because it's the default)
95 95 *.gz = gzip
96 96
97 97 A "tempfile:" command is a template. The string INFILE is replaced
98 98 with the name of a temporary file that contains the data to be
99 99 filtered by the command. The string OUTFILE is replaced with the
100 100 name of an empty temporary file, where the filtered data must be
101 101 written by the command.
102 102
103 103 NOTE: the tempfile mechanism is recommended for Windows systems,
104 104 where the standard shell I/O redirection operators often have
105 105 strange effects. In particular, if you are doing line ending
106 106 conversion on Windows using the popular dos2unix and unix2dos
107 107 programs, you *must* use the tempfile mechanism, as using pipes will
108 108 corrupt the contents of your files.
109 109
110 110 Tempfile example:
111 111
112 112 [encode]
113 113 # convert files to unix line ending conventions on checkin
114 114 **.txt = tempfile: dos2unix -n INFILE OUTFILE
115 115
116 116 [decode]
117 117 # convert files to windows line ending conventions when writing
118 118 # them to the working dir
119 119 **.txt = tempfile: unix2dos -n INFILE OUTFILE
120 120
121 121 hooks::
122 122 Commands that get automatically executed by various actions such as
123 123 starting or finishing a commit.
124 124 changegroup;;
125 Run after a changegroup has been added via push or pull.
125 Run after a changegroup has been added via push or pull. Passed
126 the ID of the first new changeset in $NODE.
126 127 commit;;
127 Run after a changeset has been created. Passed the ID of the newly
128 created changeset.
128 Run after a changeset has been created or for each changeset
129 pulled. Passed the ID of the newly created changeset in
130 environment variable $NODE.
129 131 precommit;;
130 132 Run before starting a commit. Exit status 0 allows the commit to
131 133 proceed. Non-zero status will cause the commit to fail.
132 134
133 135 http_proxy::
134 136 Used to access web-based Mercurial repositories through a HTTP
135 137 proxy.
136 138 host;;
137 139 Host name and (optional) port of the proxy server, for example
138 140 "myproxy:8000".
139 141 no;;
140 142 Optional. Comma-separated list of host names that should bypass
141 143 the proxy.
142 144 passwd;;
143 145 Optional. Password to authenticate with at the proxy server.
144 146 user;;
145 147 Optional. User name to authenticate with at the proxy server.
146 148
147 149 paths::
148 150 Assigns symbolic names to repositories. The left side is the
149 151 symbolic name, and the right gives the directory or URL that is the
150 152 location of the repository.
151 153
152 154 ui::
153 155 User interface controls.
154 156 debug;;
155 157 Print debugging information. True or False. Default is False.
156 158 editor;;
157 159 The editor to use during a commit. Default is $EDITOR or "vi".
158 160 interactive;;
159 161 Allow to prompt the user. True or False. Default is True.
160 162 merge;;
161 163 The conflict resolution program to use during a manual merge.
162 164 Default is "hgmerge".
163 165 quiet;;
164 166 Reduce the amount of output printed. True or False. Default is False.
165 167 remotecmd;;
166 168 remote command to use for clone/push/pull operations. Default is 'hg'.
167 169 ssh;;
168 170 command to use for SSH connections. Default is 'ssh'.
169 171 username;;
170 172 The committer of a changeset created when running "commit".
171 173 Typically a person's name and email address, e.g. "Fred Widget
172 174 <fred@example.com>". Default is $EMAIL or username@hostname.
173 175 verbose;;
174 176 Increase the amount of output printed. True or False. Default is False.
175 177
176 178
177 179 web::
178 180 Web interface configuration.
179 181 accesslog;;
180 182 Where to output the access log. Default is stdout.
181 183 address;;
182 184 Interface address to bind to. Default is all.
183 185 allowbz2;;
184 186 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
185 187 allowgz;;
186 188 Whether to allow .tar.gz downloading of repo revisions. Default is false.
187 189 allowpull;;
188 190 Whether to allow pulling from the repository. Default is true.
189 191 allowzip;;
190 192 Whether to allow .zip downloading of repo revisions. Default is false.
191 193 This feature creates temporary files.
192 194 description;;
193 195 Textual description of the repository's purpose or contents.
194 196 Default is "unknown".
195 197 errorlog;;
196 198 Where to output the error log. Default is stderr.
197 199 ipv6;;
198 200 Whether to use IPv6. Default is false.
199 201 name;;
200 202 Repository name to use in the web interface. Default is current
201 203 working directory.
202 204 maxchanges;;
203 205 Maximum number of changes to list on the changelog. Default is 10.
204 206 maxfiles;;
205 207 Maximum number of files to list per changeset. Default is 10.
206 208 port;;
207 209 Port to listen on. Default is 8000.
208 210 style;;
209 211 Which template map style to use.
210 212 templates;;
211 213 Where to find the HTML templates. Default is install path.
212 214
213 215
214 216 AUTHOR
215 217 ------
216 218 Bryan O'Sullivan <bos@serpentine.com>.
217 219
218 220 Mercurial was written by Matt Mackall <mpm@selenic.com>.
219 221
220 222 SEE ALSO
221 223 --------
222 224 hg(1)
223 225
224 226 COPYING
225 227 -------
226 228 This manual page is copyright 2005 Bryan O'Sullivan.
227 229 Mercurial is copyright 2005 Matt Mackall.
228 230 Free use of this software is granted under the terms of the GNU General
229 231 Public License (GPL).
@@ -1,1426 +1,1431
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import struct, os, util
9 9 import filelog, manifest, changelog, dirstate, repo
10 10 from node import *
11 11 from demandload import *
12 12 demandload(globals(), "re lock transaction tempfile stat mdiff")
13 13
14 14 class localrepository:
15 15 def __init__(self, ui, path=None, create=0):
16 16 if not path:
17 17 p = os.getcwd()
18 18 while not os.path.isdir(os.path.join(p, ".hg")):
19 19 oldp = p
20 20 p = os.path.dirname(p)
21 21 if p == oldp: raise repo.RepoError("no repo found")
22 22 path = p
23 23 self.path = os.path.join(path, ".hg")
24 24
25 25 if not create and not os.path.isdir(self.path):
26 26 raise repo.RepoError("repository %s not found" % self.path)
27 27
28 28 self.root = os.path.abspath(path)
29 29 self.ui = ui
30 30 self.opener = util.opener(self.path)
31 31 self.wopener = util.opener(self.root)
32 32 self.manifest = manifest.manifest(self.opener)
33 33 self.changelog = changelog.changelog(self.opener)
34 34 self.tagscache = None
35 35 self.nodetagscache = None
36 36 self.encodepats = None
37 37 self.decodepats = None
38 38
39 39 if create:
40 40 os.mkdir(self.path)
41 41 os.mkdir(self.join("data"))
42 42
43 43 self.dirstate = dirstate.dirstate(self.opener, ui, self.root)
44 44 try:
45 45 self.ui.readconfig(self.opener("hgrc"))
46 46 except IOError: pass
47 47
48 48 def hook(self, name, **args):
49 49 s = self.ui.config("hooks", name)
50 50 if s:
51 51 self.ui.note("running hook %s: %s\n" % (name, s))
52 52 old = {}
53 53 for k, v in args.items():
54 54 k = k.upper()
55 55 old[k] = os.environ.get(k, None)
56 56 os.environ[k] = v
57 57
58 58 r = os.system(s)
59 59
60 60 for k, v in old.items():
61 61 if v != None:
62 62 os.environ[k] = v
63 63 else:
64 64 del os.environ[k]
65 65
66 66 if r:
67 67 self.ui.warn("abort: %s hook failed with status %d!\n" %
68 68 (name, r))
69 69 return False
70 70 return True
71 71
72 72 def tags(self):
73 73 '''return a mapping of tag to node'''
74 74 if not self.tagscache:
75 75 self.tagscache = {}
76 76 def addtag(self, k, n):
77 77 try:
78 78 bin_n = bin(n)
79 79 except TypeError:
80 80 bin_n = ''
81 81 self.tagscache[k.strip()] = bin_n
82 82
83 83 try:
84 84 # read each head of the tags file, ending with the tip
85 85 # and add each tag found to the map, with "newer" ones
86 86 # taking precedence
87 87 fl = self.file(".hgtags")
88 88 h = fl.heads()
89 89 h.reverse()
90 90 for r in h:
91 91 for l in fl.read(r).splitlines():
92 92 if l:
93 93 n, k = l.split(" ", 1)
94 94 addtag(self, k, n)
95 95 except KeyError:
96 96 pass
97 97
98 98 try:
99 99 f = self.opener("localtags")
100 100 for l in f:
101 101 n, k = l.split(" ", 1)
102 102 addtag(self, k, n)
103 103 except IOError:
104 104 pass
105 105
106 106 self.tagscache['tip'] = self.changelog.tip()
107 107
108 108 return self.tagscache
109 109
110 110 def tagslist(self):
111 111 '''return a list of tags ordered by revision'''
112 112 l = []
113 113 for t, n in self.tags().items():
114 114 try:
115 115 r = self.changelog.rev(n)
116 116 except:
117 117 r = -2 # sort to the beginning of the list if unknown
118 118 l.append((r,t,n))
119 119 l.sort()
120 120 return [(t,n) for r,t,n in l]
121 121
122 122 def nodetags(self, node):
123 123 '''return the tags associated with a node'''
124 124 if not self.nodetagscache:
125 125 self.nodetagscache = {}
126 126 for t,n in self.tags().items():
127 127 self.nodetagscache.setdefault(n,[]).append(t)
128 128 return self.nodetagscache.get(node, [])
129 129
130 130 def lookup(self, key):
131 131 try:
132 132 return self.tags()[key]
133 133 except KeyError:
134 134 try:
135 135 return self.changelog.lookup(key)
136 136 except:
137 137 raise repo.RepoError("unknown revision '%s'" % key)
138 138
139 139 def dev(self):
140 140 return os.stat(self.path).st_dev
141 141
142 142 def local(self):
143 143 return True
144 144
145 145 def join(self, f):
146 146 return os.path.join(self.path, f)
147 147
148 148 def wjoin(self, f):
149 149 return os.path.join(self.root, f)
150 150
151 151 def file(self, f):
152 152 if f[0] == '/': f = f[1:]
153 153 return filelog.filelog(self.opener, f)
154 154
155 155 def getcwd(self):
156 156 return self.dirstate.getcwd()
157 157
158 158 def wfile(self, f, mode='r'):
159 159 return self.wopener(f, mode)
160 160
161 161 def wread(self, filename):
162 162 if self.encodepats == None:
163 163 l = []
164 164 for pat, cmd in self.ui.configitems("encode"):
165 165 mf = util.matcher("", "/", [pat], [], [])[1]
166 166 l.append((mf, cmd))
167 167 self.encodepats = l
168 168
169 169 data = self.wopener(filename, 'r').read()
170 170
171 171 for mf, cmd in self.encodepats:
172 172 if mf(filename):
173 173 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
174 174 data = util.filter(data, cmd)
175 175 break
176 176
177 177 return data
178 178
179 179 def wwrite(self, filename, data, fd=None):
180 180 if self.decodepats == None:
181 181 l = []
182 182 for pat, cmd in self.ui.configitems("decode"):
183 183 mf = util.matcher("", "/", [pat], [], [])[1]
184 184 l.append((mf, cmd))
185 185 self.decodepats = l
186 186
187 187 for mf, cmd in self.decodepats:
188 188 if mf(filename):
189 189 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
190 190 data = util.filter(data, cmd)
191 191 break
192 192
193 193 if fd:
194 194 return fd.write(data)
195 195 return self.wopener(filename, 'w').write(data)
196 196
197 197 def transaction(self):
198 198 # save dirstate for undo
199 199 try:
200 200 ds = self.opener("dirstate").read()
201 201 except IOError:
202 202 ds = ""
203 203 self.opener("journal.dirstate", "w").write(ds)
204 204
205 205 def after():
206 206 util.rename(self.join("journal"), self.join("undo"))
207 207 util.rename(self.join("journal.dirstate"),
208 208 self.join("undo.dirstate"))
209 209
210 210 return transaction.transaction(self.ui.warn, self.opener,
211 211 self.join("journal"), after)
212 212
213 213 def recover(self):
214 214 lock = self.lock()
215 215 if os.path.exists(self.join("journal")):
216 216 self.ui.status("rolling back interrupted transaction\n")
217 217 return transaction.rollback(self.opener, self.join("journal"))
218 218 else:
219 219 self.ui.warn("no interrupted transaction available\n")
220 220
221 221 def undo(self):
222 222 lock = self.lock()
223 223 if os.path.exists(self.join("undo")):
224 224 self.ui.status("rolling back last transaction\n")
225 225 transaction.rollback(self.opener, self.join("undo"))
226 226 self.dirstate = None
227 227 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
228 228 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
229 229 else:
230 230 self.ui.warn("no undo information available\n")
231 231
232 232 def lock(self, wait=1):
233 233 try:
234 234 return lock.lock(self.join("lock"), 0)
235 235 except lock.LockHeld, inst:
236 236 if wait:
237 237 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
238 238 return lock.lock(self.join("lock"), wait)
239 239 raise inst
240 240
241 241 def rawcommit(self, files, text, user, date, p1=None, p2=None):
242 242 orig_parent = self.dirstate.parents()[0] or nullid
243 243 p1 = p1 or self.dirstate.parents()[0] or nullid
244 244 p2 = p2 or self.dirstate.parents()[1] or nullid
245 245 c1 = self.changelog.read(p1)
246 246 c2 = self.changelog.read(p2)
247 247 m1 = self.manifest.read(c1[0])
248 248 mf1 = self.manifest.readflags(c1[0])
249 249 m2 = self.manifest.read(c2[0])
250 250 changed = []
251 251
252 252 if orig_parent == p1:
253 253 update_dirstate = 1
254 254 else:
255 255 update_dirstate = 0
256 256
257 257 tr = self.transaction()
258 258 mm = m1.copy()
259 259 mfm = mf1.copy()
260 260 linkrev = self.changelog.count()
261 261 for f in files:
262 262 try:
263 263 t = self.wread(f)
264 264 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
265 265 r = self.file(f)
266 266 mfm[f] = tm
267 267
268 268 fp1 = m1.get(f, nullid)
269 269 fp2 = m2.get(f, nullid)
270 270
271 271 # is the same revision on two branches of a merge?
272 272 if fp2 == fp1:
273 273 fp2 = nullid
274 274
275 275 if fp2 != nullid:
276 276 # is one parent an ancestor of the other?
277 277 fpa = r.ancestor(fp1, fp2)
278 278 if fpa == fp1:
279 279 fp1, fp2 = fp2, nullid
280 280 elif fpa == fp2:
281 281 fp2 = nullid
282 282
283 283 # is the file unmodified from the parent?
284 284 if t == r.read(fp1):
285 285 # record the proper existing parent in manifest
286 286 # no need to add a revision
287 287 mm[f] = fp1
288 288 continue
289 289
290 290 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
291 291 changed.append(f)
292 292 if update_dirstate:
293 293 self.dirstate.update([f], "n")
294 294 except IOError:
295 295 try:
296 296 del mm[f]
297 297 del mfm[f]
298 298 if update_dirstate:
299 299 self.dirstate.forget([f])
300 300 except:
301 301 # deleted from p2?
302 302 pass
303 303
304 304 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
305 305 user = user or self.ui.username()
306 306 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
307 307 tr.close()
308 308 if update_dirstate:
309 309 self.dirstate.setparents(n, nullid)
310 310
311 311 def commit(self, files = None, text = "", user = None, date = None,
312 312 match = util.always, force=False):
313 313 commit = []
314 314 remove = []
315 315 changed = []
316 316
317 317 if files:
318 318 for f in files:
319 319 s = self.dirstate.state(f)
320 320 if s in 'nmai':
321 321 commit.append(f)
322 322 elif s == 'r':
323 323 remove.append(f)
324 324 else:
325 325 self.ui.warn("%s not tracked!\n" % f)
326 326 else:
327 327 (c, a, d, u) = self.changes(match=match)
328 328 commit = c + a
329 329 remove = d
330 330
331 331 p1, p2 = self.dirstate.parents()
332 332 c1 = self.changelog.read(p1)
333 333 c2 = self.changelog.read(p2)
334 334 m1 = self.manifest.read(c1[0])
335 335 mf1 = self.manifest.readflags(c1[0])
336 336 m2 = self.manifest.read(c2[0])
337 337
338 338 if not commit and not remove and not force and p2 == nullid:
339 339 self.ui.status("nothing changed\n")
340 340 return None
341 341
342 342 if not self.hook("precommit"):
343 343 return None
344 344
345 345 lock = self.lock()
346 346 tr = self.transaction()
347 347
348 348 # check in files
349 349 new = {}
350 350 linkrev = self.changelog.count()
351 351 commit.sort()
352 352 for f in commit:
353 353 self.ui.note(f + "\n")
354 354 try:
355 355 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
356 356 t = self.wread(f)
357 357 except IOError:
358 358 self.ui.warn("trouble committing %s!\n" % f)
359 359 raise
360 360
361 361 r = self.file(f)
362 362
363 363 meta = {}
364 364 cp = self.dirstate.copied(f)
365 365 if cp:
366 366 meta["copy"] = cp
367 367 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
368 368 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
369 369 fp1, fp2 = nullid, nullid
370 370 else:
371 371 fp1 = m1.get(f, nullid)
372 372 fp2 = m2.get(f, nullid)
373 373
374 374 # is the same revision on two branches of a merge?
375 375 if fp2 == fp1:
376 376 fp2 = nullid
377 377
378 378 if fp2 != nullid:
379 379 # is one parent an ancestor of the other?
380 380 fpa = r.ancestor(fp1, fp2)
381 381 if fpa == fp1:
382 382 fp1, fp2 = fp2, nullid
383 383 elif fpa == fp2:
384 384 fp2 = nullid
385 385
386 386 # is the file unmodified from the parent?
387 387 if not meta and t == r.read(fp1):
388 388 # record the proper existing parent in manifest
389 389 # no need to add a revision
390 390 new[f] = fp1
391 391 continue
392 392
393 393 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
394 394 # remember what we've added so that we can later calculate
395 395 # the files to pull from a set of changesets
396 396 changed.append(f)
397 397
398 398 # update manifest
399 399 m1.update(new)
400 400 for f in remove:
401 401 if f in m1:
402 402 del m1[f]
403 403 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
404 404 (new, remove))
405 405
406 406 # add changeset
407 407 new = new.keys()
408 408 new.sort()
409 409
410 410 if not text:
411 411 edittext = ""
412 412 if p2 != nullid:
413 413 edittext += "HG: branch merge\n"
414 414 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
415 415 edittext += "".join(["HG: changed %s\n" % f for f in changed])
416 416 edittext += "".join(["HG: removed %s\n" % f for f in remove])
417 417 if not changed and not remove:
418 418 edittext += "HG: no files changed\n"
419 419 edittext = self.ui.edit(edittext)
420 420 if not edittext.rstrip():
421 421 return None
422 422 text = edittext
423 423
424 424 user = user or self.ui.username()
425 425 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
426 426 tr.close()
427 427
428 428 self.dirstate.setparents(n)
429 429 self.dirstate.update(new, "n")
430 430 self.dirstate.forget(remove)
431 431
432 432 if not self.hook("commit", node=hex(n)):
433 433 return None
434 434 return n
435 435
436 436 def walk(self, node=None, files=[], match=util.always):
437 437 if node:
438 438 for fn in self.manifest.read(self.changelog.read(node)[0]):
439 439 if match(fn): yield 'm', fn
440 440 else:
441 441 for src, fn in self.dirstate.walk(files, match):
442 442 yield src, fn
443 443
444 444 def changes(self, node1 = None, node2 = None, files = [],
445 445 match = util.always):
446 446 mf2, u = None, []
447 447
448 448 def fcmp(fn, mf):
449 449 t1 = self.wread(fn)
450 450 t2 = self.file(fn).read(mf.get(fn, nullid))
451 451 return cmp(t1, t2)
452 452
453 453 def mfmatches(node):
454 454 mf = dict(self.manifest.read(node))
455 455 for fn in mf.keys():
456 456 if not match(fn):
457 457 del mf[fn]
458 458 return mf
459 459
460 460 # are we comparing the working directory?
461 461 if not node2:
462 462 l, c, a, d, u = self.dirstate.changes(files, match)
463 463
464 464 # are we comparing working dir against its parent?
465 465 if not node1:
466 466 if l:
467 467 # do a full compare of any files that might have changed
468 468 change = self.changelog.read(self.dirstate.parents()[0])
469 469 mf2 = mfmatches(change[0])
470 470 for f in l:
471 471 if fcmp(f, mf2):
472 472 c.append(f)
473 473
474 474 for l in c, a, d, u:
475 475 l.sort()
476 476
477 477 return (c, a, d, u)
478 478
479 479 # are we comparing working dir against non-tip?
480 480 # generate a pseudo-manifest for the working dir
481 481 if not node2:
482 482 if not mf2:
483 483 change = self.changelog.read(self.dirstate.parents()[0])
484 484 mf2 = mfmatches(change[0])
485 485 for f in a + c + l:
486 486 mf2[f] = ""
487 487 for f in d:
488 488 if f in mf2: del mf2[f]
489 489 else:
490 490 change = self.changelog.read(node2)
491 491 mf2 = mfmatches(change[0])
492 492
493 493 # flush lists from dirstate before comparing manifests
494 494 c, a = [], []
495 495
496 496 change = self.changelog.read(node1)
497 497 mf1 = mfmatches(change[0])
498 498
499 499 for fn in mf2:
500 500 if mf1.has_key(fn):
501 501 if mf1[fn] != mf2[fn]:
502 502 if mf2[fn] != "" or fcmp(fn, mf1):
503 503 c.append(fn)
504 504 del mf1[fn]
505 505 else:
506 506 a.append(fn)
507 507
508 508 d = mf1.keys()
509 509
510 510 for l in c, a, d, u:
511 511 l.sort()
512 512
513 513 return (c, a, d, u)
514 514
515 515 def add(self, list):
516 516 for f in list:
517 517 p = self.wjoin(f)
518 518 if not os.path.exists(p):
519 519 self.ui.warn("%s does not exist!\n" % f)
520 520 elif not os.path.isfile(p):
521 521 self.ui.warn("%s not added: only files supported currently\n" % f)
522 522 elif self.dirstate.state(f) in 'an':
523 523 self.ui.warn("%s already tracked!\n" % f)
524 524 else:
525 525 self.dirstate.update([f], "a")
526 526
527 527 def forget(self, list):
528 528 for f in list:
529 529 if self.dirstate.state(f) not in 'ai':
530 530 self.ui.warn("%s not added!\n" % f)
531 531 else:
532 532 self.dirstate.forget([f])
533 533
534 534 def remove(self, list):
535 535 for f in list:
536 536 p = self.wjoin(f)
537 537 if os.path.exists(p):
538 538 self.ui.warn("%s still exists!\n" % f)
539 539 elif self.dirstate.state(f) == 'a':
540 540 self.ui.warn("%s never committed!\n" % f)
541 541 self.dirstate.forget([f])
542 542 elif f not in self.dirstate:
543 543 self.ui.warn("%s not tracked!\n" % f)
544 544 else:
545 545 self.dirstate.update([f], "r")
546 546
547 547 def copy(self, source, dest):
548 548 p = self.wjoin(dest)
549 549 if not os.path.exists(p):
550 550 self.ui.warn("%s does not exist!\n" % dest)
551 551 elif not os.path.isfile(p):
552 552 self.ui.warn("copy failed: %s is not a file\n" % dest)
553 553 else:
554 554 if self.dirstate.state(dest) == '?':
555 555 self.dirstate.update([dest], "a")
556 556 self.dirstate.copy(source, dest)
557 557
558 558 def heads(self):
559 559 return self.changelog.heads()
560 560
561 561 # branchlookup returns a dict giving a list of branches for
562 562 # each head. A branch is defined as the tag of a node or
563 563 # the branch of the node's parents. If a node has multiple
564 564 # branch tags, tags are eliminated if they are visible from other
565 565 # branch tags.
566 566 #
567 567 # So, for this graph: a->b->c->d->e
568 568 # \ /
569 569 # aa -----/
570 570 # a has tag 2.6.12
571 571 # d has tag 2.6.13
572 572 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
573 573 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
574 574 # from the list.
575 575 #
576 576 # It is possible that more than one head will have the same branch tag.
577 577 # callers need to check the result for multiple heads under the same
578 578 # branch tag if that is a problem for them (ie checkout of a specific
579 579 # branch).
580 580 #
581 581 # passing in a specific branch will limit the depth of the search
582 582 # through the parents. It won't limit the branches returned in the
583 583 # result though.
584 584 def branchlookup(self, heads=None, branch=None):
585 585 if not heads:
586 586 heads = self.heads()
587 587 headt = [ h for h in heads ]
588 588 chlog = self.changelog
589 589 branches = {}
590 590 merges = []
591 591 seenmerge = {}
592 592
593 593 # traverse the tree once for each head, recording in the branches
594 594 # dict which tags are visible from this head. The branches
595 595 # dict also records which tags are visible from each tag
596 596 # while we traverse.
597 597 while headt or merges:
598 598 if merges:
599 599 n, found = merges.pop()
600 600 visit = [n]
601 601 else:
602 602 h = headt.pop()
603 603 visit = [h]
604 604 found = [h]
605 605 seen = {}
606 606 while visit:
607 607 n = visit.pop()
608 608 if n in seen:
609 609 continue
610 610 pp = chlog.parents(n)
611 611 tags = self.nodetags(n)
612 612 if tags:
613 613 for x in tags:
614 614 if x == 'tip':
615 615 continue
616 616 for f in found:
617 617 branches.setdefault(f, {})[n] = 1
618 618 branches.setdefault(n, {})[n] = 1
619 619 break
620 620 if n not in found:
621 621 found.append(n)
622 622 if branch in tags:
623 623 continue
624 624 seen[n] = 1
625 625 if pp[1] != nullid and n not in seenmerge:
626 626 merges.append((pp[1], [x for x in found]))
627 627 seenmerge[n] = 1
628 628 if pp[0] != nullid:
629 629 visit.append(pp[0])
630 630 # traverse the branches dict, eliminating branch tags from each
631 631 # head that are visible from another branch tag for that head.
632 632 out = {}
633 633 viscache = {}
634 634 for h in heads:
635 635 def visible(node):
636 636 if node in viscache:
637 637 return viscache[node]
638 638 ret = {}
639 639 visit = [node]
640 640 while visit:
641 641 x = visit.pop()
642 642 if x in viscache:
643 643 ret.update(viscache[x])
644 644 elif x not in ret:
645 645 ret[x] = 1
646 646 if x in branches:
647 647 visit[len(visit):] = branches[x].keys()
648 648 viscache[node] = ret
649 649 return ret
650 650 if h not in branches:
651 651 continue
652 652 # O(n^2), but somewhat limited. This only searches the
653 653 # tags visible from a specific head, not all the tags in the
654 654 # whole repo.
655 655 for b in branches[h]:
656 656 vis = False
657 657 for bb in branches[h].keys():
658 658 if b != bb:
659 659 if b in visible(bb):
660 660 vis = True
661 661 break
662 662 if not vis:
663 663 l = out.setdefault(h, [])
664 664 l[len(l):] = self.nodetags(b)
665 665 return out
666 666
667 667 def branches(self, nodes):
668 668 if not nodes: nodes = [self.changelog.tip()]
669 669 b = []
670 670 for n in nodes:
671 671 t = n
672 672 while n:
673 673 p = self.changelog.parents(n)
674 674 if p[1] != nullid or p[0] == nullid:
675 675 b.append((t, n, p[0], p[1]))
676 676 break
677 677 n = p[0]
678 678 return b
679 679
680 680 def between(self, pairs):
681 681 r = []
682 682
683 683 for top, bottom in pairs:
684 684 n, l, i = top, [], 0
685 685 f = 1
686 686
687 687 while n != bottom:
688 688 p = self.changelog.parents(n)[0]
689 689 if i == f:
690 690 l.append(n)
691 691 f = f * 2
692 692 n = p
693 693 i += 1
694 694
695 695 r.append(l)
696 696
697 697 return r
698 698
699 699 def newer(self, nodes):
700 700 m = {}
701 701 nl = []
702 702 pm = {}
703 703 cl = self.changelog
704 704 t = l = cl.count()
705 705
706 706 # find the lowest numbered node
707 707 for n in nodes:
708 708 l = min(l, cl.rev(n))
709 709 m[n] = 1
710 710
711 711 for i in xrange(l, t):
712 712 n = cl.node(i)
713 713 if n in m: # explicitly listed
714 714 pm[n] = 1
715 715 nl.append(n)
716 716 continue
717 717 for p in cl.parents(n):
718 718 if p in pm: # parent listed
719 719 pm[n] = 1
720 720 nl.append(n)
721 721 break
722 722
723 723 return nl
724 724
725 725 def findincoming(self, remote, base=None, heads=None):
726 726 m = self.changelog.nodemap
727 727 search = []
728 728 fetch = {}
729 729 seen = {}
730 730 seenbranch = {}
731 731 if base == None:
732 732 base = {}
733 733
734 734 # assume we're closer to the tip than the root
735 735 # and start by examining the heads
736 736 self.ui.status("searching for changes\n")
737 737
738 738 if not heads:
739 739 heads = remote.heads()
740 740
741 741 unknown = []
742 742 for h in heads:
743 743 if h not in m:
744 744 unknown.append(h)
745 745 else:
746 746 base[h] = 1
747 747
748 748 if not unknown:
749 749 return None
750 750
751 751 rep = {}
752 752 reqcnt = 0
753 753
754 754 # search through remote branches
755 755 # a 'branch' here is a linear segment of history, with four parts:
756 756 # head, root, first parent, second parent
757 757 # (a branch always has two parents (or none) by definition)
758 758 unknown = remote.branches(unknown)
759 759 while unknown:
760 760 r = []
761 761 while unknown:
762 762 n = unknown.pop(0)
763 763 if n[0] in seen:
764 764 continue
765 765
766 766 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
767 767 if n[0] == nullid:
768 768 break
769 769 if n in seenbranch:
770 770 self.ui.debug("branch already found\n")
771 771 continue
772 772 if n[1] and n[1] in m: # do we know the base?
773 773 self.ui.debug("found incomplete branch %s:%s\n"
774 774 % (short(n[0]), short(n[1])))
775 775 search.append(n) # schedule branch range for scanning
776 776 seenbranch[n] = 1
777 777 else:
778 778 if n[1] not in seen and n[1] not in fetch:
779 779 if n[2] in m and n[3] in m:
780 780 self.ui.debug("found new changeset %s\n" %
781 781 short(n[1]))
782 782 fetch[n[1]] = 1 # earliest unknown
783 783 base[n[2]] = 1 # latest known
784 784 continue
785 785
786 786 for a in n[2:4]:
787 787 if a not in rep:
788 788 r.append(a)
789 789 rep[a] = 1
790 790
791 791 seen[n[0]] = 1
792 792
793 793 if r:
794 794 reqcnt += 1
795 795 self.ui.debug("request %d: %s\n" %
796 796 (reqcnt, " ".join(map(short, r))))
797 797 for p in range(0, len(r), 10):
798 798 for b in remote.branches(r[p:p+10]):
799 799 self.ui.debug("received %s:%s\n" %
800 800 (short(b[0]), short(b[1])))
801 801 if b[0] in m:
802 802 self.ui.debug("found base node %s\n" % short(b[0]))
803 803 base[b[0]] = 1
804 804 elif b[0] not in seen:
805 805 unknown.append(b)
806 806
807 807 # do binary search on the branches we found
808 808 while search:
809 809 n = search.pop(0)
810 810 reqcnt += 1
811 811 l = remote.between([(n[0], n[1])])[0]
812 812 l.append(n[1])
813 813 p = n[0]
814 814 f = 1
815 815 for i in l:
816 816 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
817 817 if i in m:
818 818 if f <= 2:
819 819 self.ui.debug("found new branch changeset %s\n" %
820 820 short(p))
821 821 fetch[p] = 1
822 822 base[i] = 1
823 823 else:
824 824 self.ui.debug("narrowed branch search to %s:%s\n"
825 825 % (short(p), short(i)))
826 826 search.append((p, i))
827 827 break
828 828 p, f = i, f * 2
829 829
830 830 # sanity check our fetch list
831 831 for f in fetch.keys():
832 832 if f in m:
833 833 raise repo.RepoError("already have changeset " + short(f[:4]))
834 834
835 835 if base.keys() == [nullid]:
836 836 self.ui.warn("warning: pulling from an unrelated repository!\n")
837 837
838 838 self.ui.note("found new changesets starting at " +
839 839 " ".join([short(f) for f in fetch]) + "\n")
840 840
841 841 self.ui.debug("%d total queries\n" % reqcnt)
842 842
843 843 return fetch.keys()
844 844
845 845 def findoutgoing(self, remote, base=None, heads=None):
846 846 if base == None:
847 847 base = {}
848 848 self.findincoming(remote, base, heads)
849 849
850 850 self.ui.debug("common changesets up to "
851 851 + " ".join(map(short, base.keys())) + "\n")
852 852
853 853 remain = dict.fromkeys(self.changelog.nodemap)
854 854
855 855 # prune everything remote has from the tree
856 856 del remain[nullid]
857 857 remove = base.keys()
858 858 while remove:
859 859 n = remove.pop(0)
860 860 if n in remain:
861 861 del remain[n]
862 862 for p in self.changelog.parents(n):
863 863 remove.append(p)
864 864
865 865 # find every node whose parents have been pruned
866 866 subset = []
867 867 for n in remain:
868 868 p1, p2 = self.changelog.parents(n)
869 869 if p1 not in remain and p2 not in remain:
870 870 subset.append(n)
871 871
872 872 # this is the set of all roots we have to push
873 873 return subset
874 874
875 875 def pull(self, remote):
876 876 lock = self.lock()
877 877
878 878 # if we have an empty repo, fetch everything
879 879 if self.changelog.tip() == nullid:
880 880 self.ui.status("requesting all changes\n")
881 881 fetch = [nullid]
882 882 else:
883 883 fetch = self.findincoming(remote)
884 884
885 885 if not fetch:
886 886 self.ui.status("no changes found\n")
887 887 return 1
888 888
889 889 cg = remote.changegroup(fetch)
890 890 return self.addchangegroup(cg)
891 891
892 892 def push(self, remote, force=False):
893 893 lock = remote.lock()
894 894
895 895 base = {}
896 896 heads = remote.heads()
897 897 inc = self.findincoming(remote, base, heads)
898 898 if not force and inc:
899 899 self.ui.warn("abort: unsynced remote changes!\n")
900 900 self.ui.status("(did you forget to sync? use push -f to force)\n")
901 901 return 1
902 902
903 903 update = self.findoutgoing(remote, base)
904 904 if not update:
905 905 self.ui.status("no changes found\n")
906 906 return 1
907 907 elif not force:
908 908 if len(heads) < len(self.changelog.heads()):
909 909 self.ui.warn("abort: push creates new remote branches!\n")
910 910 self.ui.status("(did you forget to merge?" +
911 911 " use push -f to force)\n")
912 912 return 1
913 913
914 914 cg = self.changegroup(update)
915 915 return remote.addchangegroup(cg)
916 916
917 917 def changegroup(self, basenodes):
918 918 genread = util.chunkbuffer
919 919
920 920 def gengroup():
921 921 nodes = self.newer(basenodes)
922 922
923 923 # construct the link map
924 924 linkmap = {}
925 925 for n in nodes:
926 926 linkmap[self.changelog.rev(n)] = n
927 927
928 928 # construct a list of all changed files
929 929 changed = {}
930 930 for n in nodes:
931 931 c = self.changelog.read(n)
932 932 for f in c[3]:
933 933 changed[f] = 1
934 934 changed = changed.keys()
935 935 changed.sort()
936 936
937 937 # the changegroup is changesets + manifests + all file revs
938 938 revs = [ self.changelog.rev(n) for n in nodes ]
939 939
940 940 for y in self.changelog.group(linkmap): yield y
941 941 for y in self.manifest.group(linkmap): yield y
942 942 for f in changed:
943 943 yield struct.pack(">l", len(f) + 4) + f
944 944 g = self.file(f).group(linkmap)
945 945 for y in g:
946 946 yield y
947 947
948 948 yield struct.pack(">l", 0)
949 949
950 950 return genread(gengroup())
951 951
952 952 def addchangegroup(self, source):
953 953
954 954 def getchunk():
955 955 d = source.read(4)
956 956 if not d: return ""
957 957 l = struct.unpack(">l", d)[0]
958 958 if l <= 4: return ""
959 959 d = source.read(l - 4)
960 960 if len(d) < l - 4:
961 961 raise repo.RepoError("premature EOF reading chunk" +
962 962 " (got %d bytes, expected %d)"
963 963 % (len(d), l - 4))
964 964 return d
965 965
966 966 def getgroup():
967 967 while 1:
968 968 c = getchunk()
969 969 if not c: break
970 970 yield c
971 971
972 972 def csmap(x):
973 973 self.ui.debug("add changeset %s\n" % short(x))
974 974 return self.changelog.count()
975 975
976 976 def revmap(x):
977 977 return self.changelog.rev(x)
978 978
979 979 if not source: return
980 980 changesets = files = revisions = 0
981 981
982 982 tr = self.transaction()
983 983
984 984 oldheads = len(self.changelog.heads())
985 985
986 986 # pull off the changeset group
987 987 self.ui.status("adding changesets\n")
988 988 co = self.changelog.tip()
989 989 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
990 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
990 cnr, cor = map(self.changelog.rev, (cn, co))
991 changesets = cnr - cor
991 992
992 993 # pull off the manifest group
993 994 self.ui.status("adding manifests\n")
994 995 mm = self.manifest.tip()
995 996 mo = self.manifest.addgroup(getgroup(), revmap, tr)
996 997
997 998 # process the files
998 999 self.ui.status("adding file changes\n")
999 1000 while 1:
1000 1001 f = getchunk()
1001 1002 if not f: break
1002 1003 self.ui.debug("adding %s revisions\n" % f)
1003 1004 fl = self.file(f)
1004 1005 o = fl.count()
1005 1006 n = fl.addgroup(getgroup(), revmap, tr)
1006 1007 revisions += fl.count() - o
1007 1008 files += 1
1008 1009
1009 1010 newheads = len(self.changelog.heads())
1010 1011 heads = ""
1011 1012 if oldheads and newheads > oldheads:
1012 1013 heads = " (+%d heads)" % (newheads - oldheads)
1013 1014
1014 1015 self.ui.status(("added %d changesets" +
1015 1016 " with %d changes to %d files%s\n")
1016 1017 % (changesets, revisions, files, heads))
1017 1018
1018 1019 tr.close()
1019 1020
1020 if not self.hook("changegroup"):
1021 if not self.hook("changegroup", node=hex(self.changelog.node(cor+1))):
1022 self.ui.warn("abort: changegroup hook returned failure!\n")
1021 1023 return 1
1022 1024
1025 for i in range(cor + 1, cnr + 1):
1026 self.hook("commit", node=hex(self.changelog.node(i)))
1027
1023 1028 return
1024 1029
1025 1030 def update(self, node, allow=False, force=False, choose=None,
1026 1031 moddirstate=True):
1027 1032 pl = self.dirstate.parents()
1028 1033 if not force and pl[1] != nullid:
1029 1034 self.ui.warn("aborting: outstanding uncommitted merges\n")
1030 1035 return 1
1031 1036
1032 1037 p1, p2 = pl[0], node
1033 1038 pa = self.changelog.ancestor(p1, p2)
1034 1039 m1n = self.changelog.read(p1)[0]
1035 1040 m2n = self.changelog.read(p2)[0]
1036 1041 man = self.manifest.ancestor(m1n, m2n)
1037 1042 m1 = self.manifest.read(m1n)
1038 1043 mf1 = self.manifest.readflags(m1n)
1039 1044 m2 = self.manifest.read(m2n)
1040 1045 mf2 = self.manifest.readflags(m2n)
1041 1046 ma = self.manifest.read(man)
1042 1047 mfa = self.manifest.readflags(man)
1043 1048
1044 1049 (c, a, d, u) = self.changes()
1045 1050
1046 1051 # is this a jump, or a merge? i.e. is there a linear path
1047 1052 # from p1 to p2?
1048 1053 linear_path = (pa == p1 or pa == p2)
1049 1054
1050 1055 # resolve the manifest to determine which files
1051 1056 # we care about merging
1052 1057 self.ui.note("resolving manifests\n")
1053 1058 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1054 1059 (force, allow, moddirstate, linear_path))
1055 1060 self.ui.debug(" ancestor %s local %s remote %s\n" %
1056 1061 (short(man), short(m1n), short(m2n)))
1057 1062
1058 1063 merge = {}
1059 1064 get = {}
1060 1065 remove = []
1061 1066
1062 1067 # construct a working dir manifest
1063 1068 mw = m1.copy()
1064 1069 mfw = mf1.copy()
1065 1070 umap = dict.fromkeys(u)
1066 1071
1067 1072 for f in a + c + u:
1068 1073 mw[f] = ""
1069 1074 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1070 1075
1071 1076 for f in d:
1072 1077 if f in mw: del mw[f]
1073 1078
1074 1079 # If we're jumping between revisions (as opposed to merging),
1075 1080 # and if neither the working directory nor the target rev has
1076 1081 # the file, then we need to remove it from the dirstate, to
1077 1082 # prevent the dirstate from listing the file when it is no
1078 1083 # longer in the manifest.
1079 1084 if moddirstate and linear_path and f not in m2:
1080 1085 self.dirstate.forget((f,))
1081 1086
1082 1087 # Compare manifests
1083 1088 for f, n in mw.iteritems():
1084 1089 if choose and not choose(f): continue
1085 1090 if f in m2:
1086 1091 s = 0
1087 1092
1088 1093 # is the wfile new since m1, and match m2?
1089 1094 if f not in m1:
1090 1095 t1 = self.wread(f)
1091 1096 t2 = self.file(f).read(m2[f])
1092 1097 if cmp(t1, t2) == 0:
1093 1098 n = m2[f]
1094 1099 del t1, t2
1095 1100
1096 1101 # are files different?
1097 1102 if n != m2[f]:
1098 1103 a = ma.get(f, nullid)
1099 1104 # are both different from the ancestor?
1100 1105 if n != a and m2[f] != a:
1101 1106 self.ui.debug(" %s versions differ, resolve\n" % f)
1102 1107 # merge executable bits
1103 1108 # "if we changed or they changed, change in merge"
1104 1109 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1105 1110 mode = ((a^b) | (a^c)) ^ a
1106 1111 merge[f] = (m1.get(f, nullid), m2[f], mode)
1107 1112 s = 1
1108 1113 # are we clobbering?
1109 1114 # is remote's version newer?
1110 1115 # or are we going back in time?
1111 1116 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1112 1117 self.ui.debug(" remote %s is newer, get\n" % f)
1113 1118 get[f] = m2[f]
1114 1119 s = 1
1115 1120 elif f in umap:
1116 1121 # this unknown file is the same as the checkout
1117 1122 get[f] = m2[f]
1118 1123
1119 1124 if not s and mfw[f] != mf2[f]:
1120 1125 if force:
1121 1126 self.ui.debug(" updating permissions for %s\n" % f)
1122 1127 util.set_exec(self.wjoin(f), mf2[f])
1123 1128 else:
1124 1129 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1125 1130 mode = ((a^b) | (a^c)) ^ a
1126 1131 if mode != b:
1127 1132 self.ui.debug(" updating permissions for %s\n" % f)
1128 1133 util.set_exec(self.wjoin(f), mode)
1129 1134 del m2[f]
1130 1135 elif f in ma:
1131 1136 if n != ma[f]:
1132 1137 r = "d"
1133 1138 if not force and (linear_path or allow):
1134 1139 r = self.ui.prompt(
1135 1140 (" local changed %s which remote deleted\n" % f) +
1136 1141 "(k)eep or (d)elete?", "[kd]", "k")
1137 1142 if r == "d":
1138 1143 remove.append(f)
1139 1144 else:
1140 1145 self.ui.debug("other deleted %s\n" % f)
1141 1146 remove.append(f) # other deleted it
1142 1147 else:
1143 1148 # file is created on branch or in working directory
1144 1149 if force and f not in umap:
1145 1150 self.ui.debug("remote deleted %s, clobbering\n" % f)
1146 1151 remove.append(f)
1147 1152 elif n == m1.get(f, nullid): # same as parent
1148 1153 if p2 == pa: # going backwards?
1149 1154 self.ui.debug("remote deleted %s\n" % f)
1150 1155 remove.append(f)
1151 1156 else:
1152 1157 self.ui.debug("local modified %s, keeping\n" % f)
1153 1158 else:
1154 1159 self.ui.debug("working dir created %s, keeping\n" % f)
1155 1160
1156 1161 for f, n in m2.iteritems():
1157 1162 if choose and not choose(f): continue
1158 1163 if f[0] == "/": continue
1159 1164 if f in ma and n != ma[f]:
1160 1165 r = "k"
1161 1166 if not force and (linear_path or allow):
1162 1167 r = self.ui.prompt(
1163 1168 ("remote changed %s which local deleted\n" % f) +
1164 1169 "(k)eep or (d)elete?", "[kd]", "k")
1165 1170 if r == "k": get[f] = n
1166 1171 elif f not in ma:
1167 1172 self.ui.debug("remote created %s\n" % f)
1168 1173 get[f] = n
1169 1174 else:
1170 1175 if force or p2 == pa: # going backwards?
1171 1176 self.ui.debug("local deleted %s, recreating\n" % f)
1172 1177 get[f] = n
1173 1178 else:
1174 1179 self.ui.debug("local deleted %s\n" % f)
1175 1180
1176 1181 del mw, m1, m2, ma
1177 1182
1178 1183 if force:
1179 1184 for f in merge:
1180 1185 get[f] = merge[f][1]
1181 1186 merge = {}
1182 1187
1183 1188 if linear_path or force:
1184 1189 # we don't need to do any magic, just jump to the new rev
1185 1190 branch_merge = False
1186 1191 p1, p2 = p2, nullid
1187 1192 else:
1188 1193 if not allow:
1189 1194 self.ui.status("this update spans a branch" +
1190 1195 " affecting the following files:\n")
1191 1196 fl = merge.keys() + get.keys()
1192 1197 fl.sort()
1193 1198 for f in fl:
1194 1199 cf = ""
1195 1200 if f in merge: cf = " (resolve)"
1196 1201 self.ui.status(" %s%s\n" % (f, cf))
1197 1202 self.ui.warn("aborting update spanning branches!\n")
1198 1203 self.ui.status("(use update -m to merge across branches" +
1199 1204 " or -C to lose changes)\n")
1200 1205 return 1
1201 1206 branch_merge = True
1202 1207
1203 1208 if moddirstate:
1204 1209 self.dirstate.setparents(p1, p2)
1205 1210
1206 1211 # get the files we don't need to change
1207 1212 files = get.keys()
1208 1213 files.sort()
1209 1214 for f in files:
1210 1215 if f[0] == "/": continue
1211 1216 self.ui.note("getting %s\n" % f)
1212 1217 t = self.file(f).read(get[f])
1213 1218 try:
1214 1219 self.wwrite(f, t)
1215 1220 except IOError:
1216 1221 os.makedirs(os.path.dirname(self.wjoin(f)))
1217 1222 self.wwrite(f, t)
1218 1223 util.set_exec(self.wjoin(f), mf2[f])
1219 1224 if moddirstate:
1220 1225 if branch_merge:
1221 1226 self.dirstate.update([f], 'n', st_mtime=-1)
1222 1227 else:
1223 1228 self.dirstate.update([f], 'n')
1224 1229
1225 1230 # merge the tricky bits
1226 1231 files = merge.keys()
1227 1232 files.sort()
1228 1233 for f in files:
1229 1234 self.ui.status("merging %s\n" % f)
1230 1235 my, other, flag = merge[f]
1231 1236 self.merge3(f, my, other)
1232 1237 util.set_exec(self.wjoin(f), flag)
1233 1238 if moddirstate:
1234 1239 if branch_merge:
1235 1240 # We've done a branch merge, mark this file as merged
1236 1241 # so that we properly record the merger later
1237 1242 self.dirstate.update([f], 'm')
1238 1243 else:
1239 1244 # We've update-merged a locally modified file, so
1240 1245 # we set the dirstate to emulate a normal checkout
1241 1246 # of that file some time in the past. Thus our
1242 1247 # merge will appear as a normal local file
1243 1248 # modification.
1244 1249 f_len = len(self.file(f).read(other))
1245 1250 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1246 1251
1247 1252 remove.sort()
1248 1253 for f in remove:
1249 1254 self.ui.note("removing %s\n" % f)
1250 1255 try:
1251 1256 os.unlink(self.wjoin(f))
1252 1257 except OSError, inst:
1253 1258 self.ui.warn("update failed to remove %s: %s!\n" % (f, inst))
1254 1259 # try removing directories that might now be empty
1255 1260 try: os.removedirs(os.path.dirname(self.wjoin(f)))
1256 1261 except: pass
1257 1262 if moddirstate:
1258 1263 if branch_merge:
1259 1264 self.dirstate.update(remove, 'r')
1260 1265 else:
1261 1266 self.dirstate.forget(remove)
1262 1267
1263 1268 def merge3(self, fn, my, other):
1264 1269 """perform a 3-way merge in the working directory"""
1265 1270
1266 1271 def temp(prefix, node):
1267 1272 pre = "%s~%s." % (os.path.basename(fn), prefix)
1268 1273 (fd, name) = tempfile.mkstemp("", pre)
1269 1274 f = os.fdopen(fd, "wb")
1270 1275 self.wwrite(fn, fl.read(node), f)
1271 1276 f.close()
1272 1277 return name
1273 1278
1274 1279 fl = self.file(fn)
1275 1280 base = fl.ancestor(my, other)
1276 1281 a = self.wjoin(fn)
1277 1282 b = temp("base", base)
1278 1283 c = temp("other", other)
1279 1284
1280 1285 self.ui.note("resolving %s\n" % fn)
1281 1286 self.ui.debug("file %s: other %s ancestor %s\n" %
1282 1287 (fn, short(other), short(base)))
1283 1288
1284 1289 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1285 1290 or "hgmerge")
1286 1291 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1287 1292 if r:
1288 1293 self.ui.warn("merging %s failed!\n" % fn)
1289 1294
1290 1295 os.unlink(b)
1291 1296 os.unlink(c)
1292 1297
1293 1298 def verify(self):
1294 1299 filelinkrevs = {}
1295 1300 filenodes = {}
1296 1301 changesets = revisions = files = 0
1297 1302 errors = 0
1298 1303
1299 1304 seen = {}
1300 1305 self.ui.status("checking changesets\n")
1301 1306 for i in range(self.changelog.count()):
1302 1307 changesets += 1
1303 1308 n = self.changelog.node(i)
1304 1309 if n in seen:
1305 1310 self.ui.warn("duplicate changeset at revision %d\n" % i)
1306 1311 errors += 1
1307 1312 seen[n] = 1
1308 1313
1309 1314 for p in self.changelog.parents(n):
1310 1315 if p not in self.changelog.nodemap:
1311 1316 self.ui.warn("changeset %s has unknown parent %s\n" %
1312 1317 (short(n), short(p)))
1313 1318 errors += 1
1314 1319 try:
1315 1320 changes = self.changelog.read(n)
1316 1321 except Exception, inst:
1317 1322 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1318 1323 errors += 1
1319 1324
1320 1325 for f in changes[3]:
1321 1326 filelinkrevs.setdefault(f, []).append(i)
1322 1327
1323 1328 seen = {}
1324 1329 self.ui.status("checking manifests\n")
1325 1330 for i in range(self.manifest.count()):
1326 1331 n = self.manifest.node(i)
1327 1332 if n in seen:
1328 1333 self.ui.warn("duplicate manifest at revision %d\n" % i)
1329 1334 errors += 1
1330 1335 seen[n] = 1
1331 1336
1332 1337 for p in self.manifest.parents(n):
1333 1338 if p not in self.manifest.nodemap:
1334 1339 self.ui.warn("manifest %s has unknown parent %s\n" %
1335 1340 (short(n), short(p)))
1336 1341 errors += 1
1337 1342
1338 1343 try:
1339 1344 delta = mdiff.patchtext(self.manifest.delta(n))
1340 1345 except KeyboardInterrupt:
1341 1346 self.ui.warn("interrupted")
1342 1347 raise
1343 1348 except Exception, inst:
1344 1349 self.ui.warn("unpacking manifest %s: %s\n"
1345 1350 % (short(n), inst))
1346 1351 errors += 1
1347 1352
1348 1353 ff = [ l.split('\0') for l in delta.splitlines() ]
1349 1354 for f, fn in ff:
1350 1355 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1351 1356
1352 1357 self.ui.status("crosschecking files in changesets and manifests\n")
1353 1358 for f in filenodes:
1354 1359 if f not in filelinkrevs:
1355 1360 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1356 1361 errors += 1
1357 1362
1358 1363 for f in filelinkrevs:
1359 1364 if f not in filenodes:
1360 1365 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1361 1366 errors += 1
1362 1367
1363 1368 self.ui.status("checking files\n")
1364 1369 ff = filenodes.keys()
1365 1370 ff.sort()
1366 1371 for f in ff:
1367 1372 if f == "/dev/null": continue
1368 1373 files += 1
1369 1374 fl = self.file(f)
1370 1375 nodes = { nullid: 1 }
1371 1376 seen = {}
1372 1377 for i in range(fl.count()):
1373 1378 revisions += 1
1374 1379 n = fl.node(i)
1375 1380
1376 1381 if n in seen:
1377 1382 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1378 1383 errors += 1
1379 1384
1380 1385 if n not in filenodes[f]:
1381 1386 self.ui.warn("%s: %d:%s not in manifests\n"
1382 1387 % (f, i, short(n)))
1383 1388 errors += 1
1384 1389 else:
1385 1390 del filenodes[f][n]
1386 1391
1387 1392 flr = fl.linkrev(n)
1388 1393 if flr not in filelinkrevs[f]:
1389 1394 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1390 1395 % (f, short(n), fl.linkrev(n)))
1391 1396 errors += 1
1392 1397 else:
1393 1398 filelinkrevs[f].remove(flr)
1394 1399
1395 1400 # verify contents
1396 1401 try:
1397 1402 t = fl.read(n)
1398 1403 except Exception, inst:
1399 1404 self.ui.warn("unpacking file %s %s: %s\n"
1400 1405 % (f, short(n), inst))
1401 1406 errors += 1
1402 1407
1403 1408 # verify parents
1404 1409 (p1, p2) = fl.parents(n)
1405 1410 if p1 not in nodes:
1406 1411 self.ui.warn("file %s:%s unknown parent 1 %s" %
1407 1412 (f, short(n), short(p1)))
1408 1413 errors += 1
1409 1414 if p2 not in nodes:
1410 1415 self.ui.warn("file %s:%s unknown parent 2 %s" %
1411 1416 (f, short(n), short(p1)))
1412 1417 errors += 1
1413 1418 nodes[n] = 1
1414 1419
1415 1420 # cross-check
1416 1421 for node in filenodes[f]:
1417 1422 self.ui.warn("node %s in manifests not in %s\n"
1418 1423 % (hex(node), f))
1419 1424 errors += 1
1420 1425
1421 1426 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1422 1427 (files, changesets, revisions))
1423 1428
1424 1429 if errors:
1425 1430 self.ui.warn("%d integrity errors encountered!\n" % errors)
1426 1431 return 1
General Comments 0
You need to be logged in to leave comments. Login now