##// END OF EJS Templates
Transparent proxy support...
mpm@selenic.com -
r321:73b8a8a0 default
parent child Browse files
Show More
@@ -1,212 +1,228 b''
1 1 HG(1)
2 2 =====
3 3 Matt Mackall <mpm@selenic.com>
4 4 v0.5, 27 May 2005
5 5
6 6 NAME
7 7 ----
8 8 hg - command line interface to the Mercurial source code management system
9 9
10 10 SYNOPSIS
11 11 --------
12 12 'hg' [-v -d -q -y] <command> [command options] [files]
13 13
14 14 DESCRIPTION
15 15 -----------
16 16 The hg(1) command provides a command line interface to the Mercurial system.
17 17
18 18 NOTE
19 19 ----
20 20 Many of the hg commands are not yet subdirectory and/or working directory
21 21 aware. This means that some commands will only work in the top level
22 22 repository directory or will only accept paths and filenames relative to the
23 23 top level. Merges and commits, in particular, should be done in the
24 24 top-level directory.
25 25
26 26 OPTIONS
27 27 -------
28 28 --debug, -d::
29 29 enable debugging output
30 30
31 31 --quiet, -q::
32 32 suppress output
33 33
34 34 --verbose, -v::
35 35 enable additional output
36 36
37 37 --noninteractive, -y::
38 38 do not prompt, assume 'yes' for any required answers
39 39
40 40 COMMAND ELEMENTS
41 41 ----------------
42 42
43 43 files ...::
44 44 indicates one or more filename or relative path filenames
45 45
46 46 path::
47 47 indicates a path on the local machine
48 48
49 49 revision::
50 50 indicates a changeset which can be specified as a changeset id (int),
51 51 a tag, or a unique substring of the changeset hash value
52 52
53 53 repository path::
54 54 is either the pathname of a local repository of the URI of a remote
55 55 repository. There are two available URI protocols, http:// which is
56 56 fast and the old-http:// protocol which is much slower but does not
57 57 require python on the web host.
58 58
59 59 COMMANDS
60 60 --------
61 61 add [files ...]::
62 62 add the given files to the repository. Note that this just schedules the
63 63 files for addition at the next hg commit time.
64 64
65 65 addremove::
66 66 add all new files and remove all missing files from the repository. new
67 67 files are ignored if they match any of the patterns in .hgignore
68 68
69 69 annotate [-r revision -u -n -c] [files ...]::
70 70 list the files with each line showing the revision id responsible
71 71 for that line. -u will add the author to the revision id, -c will
72 72 print the changeset hash, and -n will ...
73 73
74 74 branch <path>::
75 75 create a new branch of the repository indicated by path in the current
76 76 directory. Note that there should not be a repository already initialized
77 77 in the current directory
78 78
79 79 checkout [revision]::
80 80 check out the indicated version of the repository into the working
81 81 directory. Note that currently no merge occurs with changed files
82 82 in the working dir.
83 83
84 84 commit::
85 85 commit all changed files in the working dir to the repository. This uses
86 86 the EDITOR environment variable to bring up an editor to add a commit
87 87 comment.
88 88
89 89 diff [-r revision] [-r revision] [files ...]::
90 90 generate a unified diff of the indicated files. If there are no
91 91 revisions specified, the working directory file is compared to
92 92 the tip, one revision specified indicates a comparison between the
93 93 working directory file and the specified revision, two revisions
94 94 compares the two versions specified.
95 95
96 96 dump <file> [revision]::
97 97 print the indicated revision of the file
98 98
99 99 dumpmanifest [revision]::
100 100 print the indicated revision of the manifest (list of version controlled
101 101 files)
102 102
103 103 export [revision]::
104 104 print the changeset header (author, changeset hash, parent, and commit
105 105 comment) and the diffs for a particular revision.
106 106
107 107 history::
108 108 print the revision history of the repository
109 109
110 110 init::
111 111 initialize a repository in the current directory
112 112
113 113 log <file>::
114 114 print the revision history of the specified file
115 115
116 116 merge <repository path>::
117 117 pull any changes from the specified repository to the repository in the
118 118 current directory. Use the value of the HGMERGE environment variable
119 119 as a program to resolve any merge conflicts between the two repositories.
120 120 An implicit commit is done at the end of this process if there were any
121 121 merge conflicts. Note that merge does not yet merge with changed files
122 122 in the working dir.
123 123
124 124 recover::
125 125 rollback an interrupted transaction
126 126
127 127 remove [files ...]::
128 128 schedule the indicated files for removal from the repository at the next
129 129 commit
130 130
131 131 serve [-a addr -n name -p port -t templatedir]::
132 132 this will start an http server, by default on port 8000, that will
133 133 allow browsing the repository using the hgweb interface and will allow
134 134 merging from the repository. -a sets the interface address, -p the
135 135 port to listen on, -n the name of the repository and -t sets the
136 136 location of the template directory.
137 137
138 138 status::
139 139 list new, changed, and missing files in the working directory
140 140
141 141 tags::
142 142 list the current tags
143 143
144 144 undo::
145 145 undo the last transaction
146 146
147 147 ENVIRONMENT VARIABLES
148 148 ---------------------
149 149 HGMERGE::
150 150 points to an executable to use for resolving merge conflicts, the
151 151 program will be executed with four arguments: local file, remote
152 152 file, ancestor file, and original filename.
153 153
154 154 HGUSER::
155 155 this is the string used for the author value of a commit
156 156
157 157 HG_OPTS::
158 158 this string is used for default arguments to hg
159 159
160 160 PYTHONPATH::
161 161 this is used by Python to find imported modules and needs to be set
162 162 appropriately based on where mercurial is installed
163 163
164 164 EMAIL::
165 165 if HGUSER is not set, this will be used next as the author value for
166 166 a commit
167 167
168 168 LOGNAME::
169 169 if neither HGUSER nor EMAIL is set, LOGNAME will be used (with
170 170 '@hostname' appended) as the author value for a commit
171 171
172 172 EDITOR::
173 173 this is the name of the editor to use when committing
174 174
175 175 FILES
176 176 -----
177 177 .hgignore::
178 178 this file contains regular expressions (one per line) that describe file
179 179 names that should be ignored by hg
180 180
181 181 .hgtags::
182 182 this file contains changeset hash values and text tag names (one of each
183 183 seperated by spaces) that correspond to tagged versions of the repository
184 184 contents.
185 185
186 186 $HOME/.hgpaths::
187 187 this file contains a mapping from a symbolic name to a repository path
188 188 (which could be a local path or a remote URI), the format is
189 189 <symbolic name> <repository path> with each mapping on a seperate line
190 190
191 NON_TRANSPARENT PROXY SUPPORT
192 -----
193
194 To access a mercurial repository through a proxy,
195 create a file $HOME/.hgrc in the following format:
196
197 [http_proxy]
198 host=myproxy:8080
199 user=<username>
200 passwd=<password>
201 no=<localhost1>,<localhost2>,<localhost3>,...
202
203 "user","passwd" fields are used for authenticating proxies,
204 "no" is a comma-separated list of local host names
205 for which proxy must be bypassed.
206
191 207 BUGS
192 208 ----
193 209 Probably lots, please post them to the mailing list (See Resources below)
194 210 when you find them.
195 211
196 212 AUTHOR
197 213 ------
198 214 Written by Matt Mackall <mpm@selenic.com>
199 215
200 216 RESOURCES
201 217 ---------
202 218 http://selenic.com/mercurial[Main Web Site]
203 219
204 220 http://selenic.com/hg[Source code repository]
205 221
206 222 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
207 223
208 224 COPYING
209 225 -------
210 226 Copyright (C) 2005 Matt Mackall.
211 227 Free use of this software is granted under the terms of the GNU General
212 228 Public License (GPL).
@@ -1,1306 +1,1336 b''
1 1 # hg.py - repository classes 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 sys, struct, os
9 9 from revlog import *
10 10 from demandload import *
11 11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 12 demandload(globals(), "tempfile byterange difflib")
13 13
14 14 def is_exec(f):
15 15 return (os.stat(f).st_mode & 0100 != 0)
16 16
17 17 def set_exec(f, mode):
18 18 s = os.stat(f).st_mode
19 19 if (s & 0100 != 0) == mode:
20 20 return
21 21 if mode:
22 22 # Turn on +x for every +r bit when making a file executable
23 23 # and obey umask.
24 24 umask = os.umask(0)
25 25 os.umask(umask)
26 26 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
27 27 else:
28 28 os.chmod(f, s & 0666)
29 29
30 30 class filelog(revlog):
31 31 def __init__(self, opener, path):
32 32 revlog.__init__(self, opener,
33 33 os.path.join("data", path + ".i"),
34 34 os.path.join("data", path + ".d"))
35 35
36 36 def read(self, node):
37 37 return self.revision(node)
38 38 def add(self, text, transaction, link, p1=None, p2=None):
39 39 return self.addrevision(text, transaction, link, p1, p2)
40 40
41 41 def annotate(self, node):
42 42
43 43 def decorate(text, rev):
44 44 return [(rev, l) for l in text.splitlines(1)]
45 45
46 46 def strip(annotation):
47 47 return [e[1] for e in annotation]
48 48
49 49 def pair(parent, child):
50 50 new = []
51 51 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
52 52 for o, m, n, s, t in sm.get_opcodes():
53 53 if o == 'equal':
54 54 new += parent[m:n]
55 55 else:
56 56 new += child[s:t]
57 57 return new
58 58
59 59 # find all ancestors
60 60 needed = {node:1}
61 61 visit = [node]
62 62 while visit:
63 63 n = visit.pop(0)
64 64 for p in self.parents(n):
65 65 if p not in needed:
66 66 needed[p] = 1
67 67 visit.append(p)
68 68 else:
69 69 # count how many times we'll use this
70 70 needed[p] += 1
71 71
72 72 # sort by revision which is a topological order
73 73 visit = needed.keys()
74 74 visit = [ (self.rev(n), n) for n in visit ]
75 75 visit.sort()
76 76 visit = [ p[1] for p in visit ]
77 77 hist = {}
78 78
79 79 for n in visit:
80 80 curr = decorate(self.read(n), self.linkrev(n))
81 81 for p in self.parents(n):
82 82 if p != nullid:
83 83 curr = pair(hist[p], curr)
84 84 # trim the history of unneeded revs
85 85 needed[p] -= 1
86 86 if not needed[p]:
87 87 del hist[p]
88 88 hist[n] = curr
89 89
90 90 return hist[n]
91 91
92 92 class manifest(revlog):
93 93 def __init__(self, opener):
94 94 self.mapcache = None
95 95 self.listcache = None
96 96 self.addlist = None
97 97 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
98 98
99 99 def read(self, node):
100 100 if node == nullid: return {} # don't upset local cache
101 101 if self.mapcache and self.mapcache[0] == node:
102 102 return self.mapcache[1].copy()
103 103 text = self.revision(node)
104 104 map = {}
105 105 flag = {}
106 106 self.listcache = (text, text.splitlines(1))
107 107 for l in self.listcache[1]:
108 108 (f, n) = l.split('\0')
109 109 map[f] = bin(n[:40])
110 110 flag[f] = (n[40:-1] == "x")
111 111 self.mapcache = (node, map, flag)
112 112 return map
113 113
114 114 def readflags(self, node):
115 115 if node == nullid: return {} # don't upset local cache
116 116 if self.mapcache or self.mapcache[0] != node:
117 117 self.read(node)
118 118 return self.mapcache[2]
119 119
120 120 def diff(self, a, b):
121 121 # this is sneaky, as we're not actually using a and b
122 122 if self.listcache and self.addlist and self.listcache[0] == a:
123 123 d = mdiff.diff(self.listcache[1], self.addlist, 1)
124 124 if mdiff.patch(a, d) != b:
125 125 sys.stderr.write("*** sortdiff failed, falling back ***\n")
126 126 return mdiff.textdiff(a, b)
127 127 return d
128 128 else:
129 129 return mdiff.textdiff(a, b)
130 130
131 131 def add(self, map, flags, transaction, link, p1=None, p2=None):
132 132 files = map.keys()
133 133 files.sort()
134 134
135 135 self.addlist = ["%s\000%s%s\n" %
136 136 (f, hex(map[f]), flags[f] and "x" or '')
137 137 for f in files]
138 138 text = "".join(self.addlist)
139 139
140 140 n = self.addrevision(text, transaction, link, p1, p2)
141 141 self.mapcache = (n, map, flags)
142 142 self.listcache = (text, self.addlist)
143 143 self.addlist = None
144 144
145 145 return n
146 146
147 147 class changelog(revlog):
148 148 def __init__(self, opener):
149 149 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
150 150
151 151 def extract(self, text):
152 152 if not text:
153 153 return (nullid, "", "0", [], "")
154 154 last = text.index("\n\n")
155 155 desc = text[last + 2:]
156 156 l = text[:last].splitlines()
157 157 manifest = bin(l[0])
158 158 user = l[1]
159 159 date = l[2]
160 160 files = l[3:]
161 161 return (manifest, user, date, files, desc)
162 162
163 163 def read(self, node):
164 164 return self.extract(self.revision(node))
165 165
166 166 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
167 167 user=None, date=None):
168 168 user = (user or
169 169 os.environ.get("HGUSER") or
170 170 os.environ.get("EMAIL") or
171 171 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
172 172 date = date or "%d %d" % (time.time(), time.timezone)
173 173 list.sort()
174 174 l = [hex(manifest), user, date] + list + ["", desc]
175 175 text = "\n".join(l)
176 176 return self.addrevision(text, transaction, self.count(), p1, p2)
177 177
178 178 class dirstate:
179 179 def __init__(self, opener, ui, root):
180 180 self.opener = opener
181 181 self.root = root
182 182 self.dirty = 0
183 183 self.ui = ui
184 184 self.map = None
185 185 self.pl = None
186 186
187 187 def __del__(self):
188 188 if self.dirty:
189 189 self.write()
190 190
191 191 def __getitem__(self, key):
192 192 try:
193 193 return self.map[key]
194 194 except TypeError:
195 195 self.read()
196 196 return self[key]
197 197
198 198 def __contains__(self, key):
199 199 if not self.map: self.read()
200 200 return key in self.map
201 201
202 202 def parents(self):
203 203 if not self.pl:
204 204 self.read()
205 205 return self.pl
206 206
207 207 def setparents(self, p1, p2 = nullid):
208 208 self.dirty = 1
209 209 self.pl = p1, p2
210 210
211 211 def state(self, key):
212 212 try:
213 213 return self[key][0]
214 214 except KeyError:
215 215 return "?"
216 216
217 217 def read(self):
218 218 if self.map is not None: return self.map
219 219
220 220 self.map = {}
221 221 self.pl = [nullid, nullid]
222 222 try:
223 223 st = self.opener("dirstate").read()
224 224 if not st: return
225 225 except: return
226 226
227 227 self.pl = [st[:20], st[20: 40]]
228 228
229 229 pos = 40
230 230 while pos < len(st):
231 231 e = struct.unpack(">cllll", st[pos:pos+17])
232 232 l = e[4]
233 233 pos += 17
234 234 f = st[pos:pos + l]
235 235 self.map[f] = e[:4]
236 236 pos += l
237 237
238 238 def update(self, files, state):
239 239 ''' current states:
240 240 n normal
241 241 m needs merging
242 242 r marked for removal
243 243 a marked for addition'''
244 244
245 245 if not files: return
246 246 self.read()
247 247 self.dirty = 1
248 248 for f in files:
249 249 if state == "r":
250 250 self.map[f] = ('r', 0, 0, 0)
251 251 else:
252 252 s = os.stat(os.path.join(self.root, f))
253 253 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
254 254
255 255 def forget(self, files):
256 256 if not files: return
257 257 self.read()
258 258 self.dirty = 1
259 259 for f in files:
260 260 try:
261 261 del self.map[f]
262 262 except KeyError:
263 263 self.ui.warn("not in dirstate: %s!\n" % f)
264 264 pass
265 265
266 266 def clear(self):
267 267 self.map = {}
268 268 self.dirty = 1
269 269
270 270 def write(self):
271 271 st = self.opener("dirstate", "w")
272 272 st.write("".join(self.pl))
273 273 for f, e in self.map.items():
274 274 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
275 275 st.write(e + f)
276 276 self.dirty = 0
277 277
278 278 def copy(self):
279 279 self.read()
280 280 return self.map.copy()
281 281
282 282 # used to avoid circular references so destructors work
283 283 def opener(base):
284 284 p = base
285 285 def o(path, mode="r"):
286 286 if p[:7] == "http://":
287 287 f = os.path.join(p, urllib.quote(path))
288 288 return httprangereader(f)
289 289
290 290 f = os.path.join(p, path)
291 291
292 292 mode += "b" # for that other OS
293 293
294 294 if mode[0] != "r":
295 295 try:
296 296 s = os.stat(f)
297 297 except OSError:
298 298 d = os.path.dirname(f)
299 299 if not os.path.isdir(d):
300 300 os.makedirs(d)
301 301 else:
302 302 if s.st_nlink > 1:
303 303 file(f + ".tmp", "w").write(file(f).read())
304 304 os.rename(f+".tmp", f)
305 305
306 306 return file(f, mode)
307 307
308 308 return o
309 309
310 310 class localrepository:
311 311 def __init__(self, ui, path=None, create=0):
312 312 self.remote = 0
313 313 if path and path[:7] == "http://":
314 314 self.remote = 1
315 315 self.path = path
316 316 else:
317 317 if not path:
318 318 p = os.getcwd()
319 319 while not os.path.isdir(os.path.join(p, ".hg")):
320 320 p = os.path.dirname(p)
321 321 if p == "/": raise "No repo found"
322 322 path = p
323 323 self.path = os.path.join(path, ".hg")
324 324
325 325 self.root = path
326 326 self.ui = ui
327 327
328 328 if create:
329 329 os.mkdir(self.path)
330 330 os.mkdir(self.join("data"))
331 331
332 332 self.opener = opener(self.path)
333 333 self.wopener = opener(self.root)
334 334 self.manifest = manifest(self.opener)
335 335 self.changelog = changelog(self.opener)
336 336 self.ignorelist = None
337 337 self.tags = None
338 338
339 339 if not self.remote:
340 340 self.dirstate = dirstate(self.opener, ui, self.root)
341 341
342 342 def ignore(self, f):
343 343 if self.ignorelist is None:
344 344 self.ignorelist = []
345 345 try:
346 346 l = self.wfile(".hgignore")
347 347 for pat in l:
348 348 if pat != "\n":
349 349 self.ignorelist.append(re.compile(pat[:-1]))
350 350 except IOError: pass
351 351 for pat in self.ignorelist:
352 352 if pat.search(f): return True
353 353 return False
354 354
355 355 def lookup(self, key):
356 356 if self.tags is None:
357 357 self.tags = {}
358 358 try:
359 359 # read each head of the tags file, ending with the tip
360 360 # and add each tag found to the map, with "newer" ones
361 361 # taking precedence
362 362 fl = self.file(".hgtags")
363 363 h = fl.heads()
364 364 h.reverse()
365 365 for r in h:
366 366 for l in fl.revision(r).splitlines():
367 367 if l:
368 368 n, k = l.split(" ")
369 369 self.tags[k] = bin(n)
370 370 except KeyError: pass
371 371 self.tags['tip'] = self.changelog.tip()
372 372 try:
373 373 return self.tags[key]
374 374 except KeyError:
375 375 return self.changelog.lookup(key)
376 376
377 377 def join(self, f):
378 378 return os.path.join(self.path, f)
379 379
380 380 def wjoin(self, f):
381 381 return os.path.join(self.root, f)
382 382
383 383 def file(self, f):
384 384 if f[0] == '/': f = f[1:]
385 385 return filelog(self.opener, f)
386 386
387 387 def wfile(self, f, mode='r'):
388 388 return self.wopener(f, mode)
389 389
390 390 def transaction(self):
391 391 # save dirstate for undo
392 392 try:
393 393 ds = self.opener("dirstate").read()
394 394 except IOError:
395 395 ds = ""
396 396 self.opener("undo.dirstate", "w").write(ds)
397 397
398 398 return transaction.transaction(self.opener, self.join("journal"),
399 399 self.join("undo"))
400 400
401 401 def recover(self):
402 402 lock = self.lock()
403 403 if os.path.exists(self.join("recover")):
404 404 self.ui.status("attempting to rollback interrupted transaction\n")
405 405 return transaction.rollback(self.opener, self.join("recover"))
406 406 else:
407 407 self.ui.warn("no interrupted transaction available\n")
408 408
409 409 def undo(self):
410 410 lock = self.lock()
411 411 if os.path.exists(self.join("undo")):
412 412 self.ui.status("attempting to rollback last transaction\n")
413 413 transaction.rollback(self.opener, self.join("undo"))
414 414 self.dirstate = None
415 415 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
416 416 self.dirstate = dirstate(self.opener, self.ui, self.root)
417 417 else:
418 418 self.ui.warn("no undo information available\n")
419 419
420 420 def lock(self, wait = 1):
421 421 try:
422 422 return lock.lock(self.join("lock"), 0)
423 423 except lock.LockHeld, inst:
424 424 if wait:
425 425 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
426 426 return lock.lock(self.join("lock"), wait)
427 427 raise inst
428 428
429 429 def rawcommit(self, files, text, user, date, p1=None, p2=None):
430 430 p1 = p1 or self.dirstate.parents()[0] or nullid
431 431 p2 = p2 or self.dirstate.parents()[1] or nullid
432 432 c1 = self.changelog.read(p1)
433 433 c2 = self.changelog.read(p2)
434 434 m1 = self.manifest.read(c1[0])
435 435 mf1 = self.manifest.readflags(c1[0])
436 436 m2 = self.manifest.read(c2[0])
437 437
438 438 tr = self.transaction()
439 439 mm = m1.copy()
440 440 mfm = mf1.copy()
441 441 linkrev = self.changelog.count()
442 442 self.dirstate.setparents(p1, p2)
443 443 for f in files:
444 444 try:
445 445 t = self.wfile(f).read()
446 446 tm = is_exec(self.wjoin(f))
447 447 r = self.file(f)
448 448 mfm[f] = tm
449 449 mm[f] = r.add(t, tr, linkrev,
450 450 m1.get(f, nullid), m2.get(f, nullid))
451 451 self.dirstate.update([f], "n")
452 452 except IOError:
453 453 try:
454 454 del mm[f]
455 455 del mfm[f]
456 456 self.dirstate.forget([f])
457 457 except:
458 458 # deleted from p2?
459 459 pass
460 460
461 461 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
462 462 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
463 463 tr.close()
464 464
465 465 def commit(self, files = None, text = "", user = None, date = None):
466 466 commit = []
467 467 remove = []
468 468 if files:
469 469 for f in files:
470 470 s = self.dirstate.state(f)
471 471 if s in 'nmai':
472 472 commit.append(f)
473 473 elif s == 'r':
474 474 remove.append(f)
475 475 else:
476 476 self.ui.warn("%s not tracked!\n" % f)
477 477 else:
478 478 (c, a, d, u) = self.diffdir(self.root)
479 479 commit = c + a
480 480 remove = d
481 481
482 482 if not commit and not remove:
483 483 self.ui.status("nothing changed\n")
484 484 return
485 485
486 486 p1, p2 = self.dirstate.parents()
487 487 c1 = self.changelog.read(p1)
488 488 c2 = self.changelog.read(p2)
489 489 m1 = self.manifest.read(c1[0])
490 490 mf1 = self.manifest.readflags(c1[0])
491 491 m2 = self.manifest.read(c2[0])
492 492 lock = self.lock()
493 493 tr = self.transaction()
494 494
495 495 # check in files
496 496 new = {}
497 497 linkrev = self.changelog.count()
498 498 commit.sort()
499 499 for f in commit:
500 500 self.ui.note(f + "\n")
501 501 try:
502 502 fp = self.wjoin(f)
503 503 mf1[f] = is_exec(fp)
504 504 t = file(fp).read()
505 505 except IOError:
506 506 self.warn("trouble committing %s!\n" % f)
507 507 raise
508 508
509 509 r = self.file(f)
510 510 fp1 = m1.get(f, nullid)
511 511 fp2 = m2.get(f, nullid)
512 512 new[f] = r.add(t, tr, linkrev, fp1, fp2)
513 513
514 514 # update manifest
515 515 m1.update(new)
516 516 for f in remove: del m1[f]
517 517 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
518 518
519 519 # add changeset
520 520 new = new.keys()
521 521 new.sort()
522 522
523 523 if not text:
524 524 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
525 525 edittext += "".join(["HG: changed %s\n" % f for f in new])
526 526 edittext += "".join(["HG: removed %s\n" % f for f in remove])
527 527 edittext = self.ui.edit(edittext)
528 528 if not edittext.rstrip():
529 529 return 1
530 530 text = edittext
531 531
532 532 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
533 533 tr.close()
534 534
535 535 self.dirstate.setparents(n)
536 536 self.dirstate.update(new, "n")
537 537 self.dirstate.forget(remove)
538 538
539 539 def diffdir(self, path, changeset = None):
540 540 changed = []
541 541 added = []
542 542 unknown = []
543 543 mf = {}
544 544
545 545 if changeset:
546 546 change = self.changelog.read(changeset)
547 547 mf = self.manifest.read(change[0])
548 548 dc = dict.fromkeys(mf)
549 549 else:
550 550 changeset = self.dirstate.parents()[0]
551 551 change = self.changelog.read(changeset)
552 552 mf = self.manifest.read(change[0])
553 553 dc = self.dirstate.copy()
554 554
555 555 def fcmp(fn):
556 556 t1 = self.wfile(fn).read()
557 557 t2 = self.file(fn).revision(mf[fn])
558 558 return cmp(t1, t2)
559 559
560 560 for dir, subdirs, files in os.walk(path):
561 561 d = dir[len(self.root)+1:]
562 562 if ".hg" in subdirs: subdirs.remove(".hg")
563 563
564 564 for f in files:
565 565 fn = os.path.join(d, f)
566 566 try: s = os.stat(os.path.join(self.root, fn))
567 567 except: continue
568 568 if fn in dc:
569 569 c = dc[fn]
570 570 del dc[fn]
571 571 if not c:
572 572 if fcmp(fn):
573 573 changed.append(fn)
574 574 elif c[0] == 'm':
575 575 changed.append(fn)
576 576 elif c[0] == 'a':
577 577 added.append(fn)
578 578 elif c[0] == 'r':
579 579 unknown.append(fn)
580 580 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
581 581 changed.append(fn)
582 582 elif c[1] != s.st_mode or c[3] != s.st_mtime:
583 583 if fcmp(fn):
584 584 changed.append(fn)
585 585 else:
586 586 if self.ignore(fn): continue
587 587 unknown.append(fn)
588 588
589 589 deleted = dc.keys()
590 590 deleted.sort()
591 591
592 592 return (changed, added, deleted, unknown)
593 593
594 594 def diffrevs(self, node1, node2):
595 595 changed, added = [], []
596 596
597 597 change = self.changelog.read(node1)
598 598 mf1 = self.manifest.read(change[0])
599 599 change = self.changelog.read(node2)
600 600 mf2 = self.manifest.read(change[0])
601 601
602 602 for fn in mf2:
603 603 if mf1.has_key(fn):
604 604 if mf1[fn] != mf2[fn]:
605 605 changed.append(fn)
606 606 del mf1[fn]
607 607 else:
608 608 added.append(fn)
609 609
610 610 deleted = mf1.keys()
611 611 deleted.sort()
612 612
613 613 return (changed, added, deleted)
614 614
615 615 def add(self, list):
616 616 for f in list:
617 617 p = self.wjoin(f)
618 618 if not os.path.isfile(p):
619 619 self.ui.warn("%s does not exist!\n" % f)
620 620 elif self.dirstate.state(f) == 'n':
621 621 self.ui.warn("%s already tracked!\n" % f)
622 622 else:
623 623 self.dirstate.update([f], "a")
624 624
625 625 def forget(self, list):
626 626 for f in list:
627 627 if self.dirstate.state(f) not in 'ai':
628 628 self.ui.warn("%s not added!\n" % f)
629 629 else:
630 630 self.dirstate.forget([f])
631 631
632 632 def remove(self, list):
633 633 for f in list:
634 634 p = self.wjoin(f)
635 635 if os.path.isfile(p):
636 636 self.ui.warn("%s still exists!\n" % f)
637 637 elif f not in self.dirstate:
638 638 self.ui.warn("%s not tracked!\n" % f)
639 639 else:
640 640 self.dirstate.update([f], "r")
641 641
642 642 def heads(self):
643 643 return self.changelog.heads()
644 644
645 645 def branches(self, nodes):
646 646 if not nodes: nodes = [self.changelog.tip()]
647 647 b = []
648 648 for n in nodes:
649 649 t = n
650 650 while n:
651 651 p = self.changelog.parents(n)
652 652 if p[1] != nullid or p[0] == nullid:
653 653 b.append((t, n, p[0], p[1]))
654 654 break
655 655 n = p[0]
656 656 return b
657 657
658 658 def between(self, pairs):
659 659 r = []
660 660
661 661 for top, bottom in pairs:
662 662 n, l, i = top, [], 0
663 663 f = 1
664 664
665 665 while n != bottom:
666 666 p = self.changelog.parents(n)[0]
667 667 if i == f:
668 668 l.append(n)
669 669 f = f * 2
670 670 n = p
671 671 i += 1
672 672
673 673 r.append(l)
674 674
675 675 return r
676 676
677 677 def newer(self, nodes):
678 678 m = {}
679 679 nl = []
680 680 pm = {}
681 681 cl = self.changelog
682 682 t = l = cl.count()
683 683
684 684 # find the lowest numbered node
685 685 for n in nodes:
686 686 l = min(l, cl.rev(n))
687 687 m[n] = 1
688 688
689 689 for i in xrange(l, t):
690 690 n = cl.node(i)
691 691 if n in m: # explicitly listed
692 692 pm[n] = 1
693 693 nl.append(n)
694 694 continue
695 695 for p in cl.parents(n):
696 696 if p in pm: # parent listed
697 697 pm[n] = 1
698 698 nl.append(n)
699 699 break
700 700
701 701 return nl
702 702
703 703 def getchangegroup(self, remote):
704 704 m = self.changelog.nodemap
705 705 search = []
706 706 fetch = []
707 707 seen = {}
708 708 seenbranch = {}
709 709
710 710 # if we have an empty repo, fetch everything
711 711 if self.changelog.tip() == nullid:
712 712 self.ui.status("requesting all changes\n")
713 713 return remote.changegroup([nullid])
714 714
715 715 # otherwise, assume we're closer to the tip than the root
716 716 self.ui.status("searching for changes\n")
717 717 heads = remote.heads()
718 718 unknown = []
719 719 for h in heads:
720 720 if h not in m:
721 721 unknown.append(h)
722 722
723 723 if not unknown:
724 724 self.ui.status("nothing to do!\n")
725 725 return None
726 726
727 727 unknown = remote.branches(unknown)
728 728 while unknown:
729 729 n = unknown.pop(0)
730 730 seen[n[0]] = 1
731 731
732 732 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
733 733 if n == nullid: break
734 734 if n in seenbranch:
735 735 self.ui.debug("branch already found\n")
736 736 continue
737 737 if n[1] and n[1] in m: # do we know the base?
738 738 self.ui.debug("found incomplete branch %s:%s\n"
739 739 % (short(n[0]), short(n[1])))
740 740 search.append(n) # schedule branch range for scanning
741 741 seenbranch[n] = 1
742 742 else:
743 743 if n[2] in m and n[3] in m:
744 744 if n[1] not in fetch:
745 745 self.ui.debug("found new changeset %s\n" %
746 746 short(n[1]))
747 747 fetch.append(n[1]) # earliest unknown
748 748 continue
749 749
750 750 r = []
751 751 for a in n[2:4]:
752 752 if a not in seen: r.append(a)
753 753
754 754 if r:
755 755 self.ui.debug("requesting %s\n" %
756 756 " ".join(map(short, r)))
757 757 for b in remote.branches(r):
758 758 self.ui.debug("received %s:%s\n" %
759 759 (short(b[0]), short(b[1])))
760 760 if b[0] not in m and b[0] not in seen:
761 761 unknown.append(b)
762 762
763 763 while search:
764 764 n = search.pop(0)
765 765 l = remote.between([(n[0], n[1])])[0]
766 766 p = n[0]
767 767 f = 1
768 768 for i in l + [n[1]]:
769 769 if i in m:
770 770 if f <= 2:
771 771 self.ui.debug("found new branch changeset %s\n" %
772 772 short(p))
773 773 fetch.append(p)
774 774 else:
775 775 self.ui.debug("narrowed branch search to %s:%s\n"
776 776 % (short(p), short(i)))
777 777 search.append((p, i))
778 778 break
779 779 p, f = i, f * 2
780 780
781 781 for f in fetch:
782 782 if f in m:
783 783 raise "already have", short(f[:4])
784 784
785 785 self.ui.note("adding new changesets starting at " +
786 786 " ".join([short(f) for f in fetch]) + "\n")
787 787
788 788 return remote.changegroup(fetch)
789 789
790 790 def changegroup(self, basenodes):
791 791 nodes = self.newer(basenodes)
792 792
793 793 # construct the link map
794 794 linkmap = {}
795 795 for n in nodes:
796 796 linkmap[self.changelog.rev(n)] = n
797 797
798 798 # construct a list of all changed files
799 799 changed = {}
800 800 for n in nodes:
801 801 c = self.changelog.read(n)
802 802 for f in c[3]:
803 803 changed[f] = 1
804 804 changed = changed.keys()
805 805 changed.sort()
806 806
807 807 # the changegroup is changesets + manifests + all file revs
808 808 revs = [ self.changelog.rev(n) for n in nodes ]
809 809
810 810 for y in self.changelog.group(linkmap): yield y
811 811 for y in self.manifest.group(linkmap): yield y
812 812 for f in changed:
813 813 yield struct.pack(">l", len(f) + 4) + f
814 814 g = self.file(f).group(linkmap)
815 815 for y in g:
816 816 yield y
817 817
818 818 def addchangegroup(self, generator):
819 819
820 820 class genread:
821 821 def __init__(self, generator):
822 822 self.g = generator
823 823 self.buf = ""
824 824 def read(self, l):
825 825 while l > len(self.buf):
826 826 try:
827 827 self.buf += self.g.next()
828 828 except StopIteration:
829 829 break
830 830 d, self.buf = self.buf[:l], self.buf[l:]
831 831 return d
832 832
833 833 def getchunk():
834 834 d = source.read(4)
835 835 if not d: return ""
836 836 l = struct.unpack(">l", d)[0]
837 837 if l <= 4: return ""
838 838 return source.read(l - 4)
839 839
840 840 def getgroup():
841 841 while 1:
842 842 c = getchunk()
843 843 if not c: break
844 844 yield c
845 845
846 846 def csmap(x):
847 847 self.ui.debug("add changeset %s\n" % short(x))
848 848 return self.changelog.count()
849 849
850 850 def revmap(x):
851 851 return self.changelog.rev(x)
852 852
853 853 if not generator: return
854 854 changesets = files = revisions = 0
855 855
856 856 source = genread(generator)
857 857 lock = self.lock()
858 858 tr = self.transaction()
859 859
860 860 # pull off the changeset group
861 861 self.ui.status("adding changesets\n")
862 862 co = self.changelog.tip()
863 863 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
864 864 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
865 865
866 866 # pull off the manifest group
867 867 self.ui.status("adding manifests\n")
868 868 mm = self.manifest.tip()
869 869 mo = self.manifest.addgroup(getgroup(), revmap, tr)
870 870
871 871 # process the files
872 872 self.ui.status("adding file revisions\n")
873 873 while 1:
874 874 f = getchunk()
875 875 if not f: break
876 876 self.ui.debug("adding %s revisions\n" % f)
877 877 fl = self.file(f)
878 878 o = fl.tip()
879 879 n = fl.addgroup(getgroup(), revmap, tr)
880 880 revisions += fl.rev(n) - fl.rev(o)
881 881 files += 1
882 882
883 883 self.ui.status(("modified %d files, added %d changesets" +
884 884 " and %d new revisions\n")
885 885 % (files, changesets, revisions))
886 886
887 887 tr.close()
888 888 return
889 889
890 890 def update(self, node, allow=False, force=False):
891 891 pl = self.dirstate.parents()
892 892 if not force and pl[1] != nullid:
893 893 self.ui.warn("aborting: outstanding uncommitted merges\n")
894 894 return
895 895
896 896 p1, p2 = pl[0], node
897 897 pa = self.changelog.ancestor(p1, p2)
898 898 m1n = self.changelog.read(p1)[0]
899 899 m2n = self.changelog.read(p2)[0]
900 900 man = self.manifest.ancestor(m1n, m2n)
901 901 m1 = self.manifest.read(m1n)
902 902 mf1 = self.manifest.readflags(m1n)
903 903 m2 = self.manifest.read(m2n)
904 904 mf2 = self.manifest.readflags(m2n)
905 905 ma = self.manifest.read(man)
906 906 mfa = self.manifest.readflags(m2n)
907 907
908 908 (c, a, d, u) = self.diffdir(self.root)
909 909
910 910 # resolve the manifest to determine which files
911 911 # we care about merging
912 912 self.ui.note("resolving manifests\n")
913 913 self.ui.debug(" ancestor %s local %s remote %s\n" %
914 914 (short(man), short(m1n), short(m2n)))
915 915
916 916 merge = {}
917 917 get = {}
918 918 remove = []
919 919 mark = {}
920 920
921 921 # construct a working dir manifest
922 922 mw = m1.copy()
923 923 mfw = mf1.copy()
924 924 for f in a + c + u:
925 925 mw[f] = ""
926 926 mfw[f] = is_exec(self.wjoin(f))
927 927 for f in d:
928 928 if f in mw: del mw[f]
929 929
930 930 for f, n in mw.iteritems():
931 931 if f in m2:
932 932 s = 0
933 933
934 934 # are files different?
935 935 if n != m2[f]:
936 936 a = ma.get(f, nullid)
937 937 # are both different from the ancestor?
938 938 if n != a and m2[f] != a:
939 939 self.ui.debug(" %s versions differ, resolve\n" % f)
940 940 merge[f] = (m1.get(f, nullid), m2[f])
941 941 # merge executable bits
942 942 # "if we changed or they changed, change in merge"
943 943 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
944 944 mode = ((a^b) | (a^c)) ^ a
945 945 merge[f] = (m1.get(f, nullid), m2[f], mode)
946 946 s = 1
947 947 # are we clobbering?
948 948 # is remote's version newer?
949 949 # or are we going back in time?
950 950 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
951 951 self.ui.debug(" remote %s is newer, get\n" % f)
952 952 get[f] = m2[f]
953 953 s = 1
954 954 else:
955 955 mark[f] = 1
956 956
957 957 if not s and mfw[f] != mf2[f]:
958 958 if force:
959 959 self.ui.debug(" updating permissions for %s\n" % f)
960 960 set_exec(self.wjoin(f), mf2[f])
961 961 else:
962 962 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
963 963 mode = ((a^b) | (a^c)) ^ a
964 964 if mode != b:
965 965 self.ui.debug(" updating permissions for %s\n" % f)
966 966 set_exec(self.wjoin(f), mode)
967 967 mark[f] = 1
968 968 del m2[f]
969 969 elif f in ma:
970 970 if not force and n != ma[f]:
971 971 r = self.ui.prompt(
972 972 (" local changed %s which remote deleted\n" % f) +
973 973 "(k)eep or (d)elete?", "[kd]", "k")
974 974 if r == "d":
975 975 remove.append(f)
976 976 else:
977 977 self.ui.debug("other deleted %s\n" % f)
978 978 remove.append(f) # other deleted it
979 979 else:
980 980 if n == m1.get(f, nullid): # same as parent
981 981 self.ui.debug("remote deleted %s\n" % f)
982 982 remove.append(f)
983 983 else:
984 984 self.ui.debug("working dir created %s, keeping\n" % f)
985 985
986 986 for f, n in m2.iteritems():
987 987 if f[0] == "/": continue
988 988 if not force and f in ma and n != ma[f]:
989 989 r = self.ui.prompt(
990 990 ("remote changed %s which local deleted\n" % f) +
991 991 "(k)eep or (d)elete?", "[kd]", "k")
992 992 if r == "d": remove.append(f)
993 993 else:
994 994 self.ui.debug("remote created %s\n" % f)
995 995 get[f] = n
996 996
997 997 del mw, m1, m2, ma
998 998
999 999 if force:
1000 1000 for f in merge:
1001 1001 get[f] = merge[f][1]
1002 1002 merge = {}
1003 1003
1004 1004 if pa == p1 or pa == p2:
1005 1005 # we don't need to do any magic, just jump to the new rev
1006 1006 mode = 'n'
1007 1007 p1, p2 = p2, nullid
1008 1008 else:
1009 1009 if not allow:
1010 1010 self.ui.status("this update spans a branch" +
1011 1011 " affecting the following files:\n")
1012 1012 fl = merge.keys() + get.keys()
1013 1013 fl.sort()
1014 1014 for f in fl:
1015 1015 cf = ""
1016 1016 if f in merge: cf = " (resolve)"
1017 1017 self.ui.status(" %s%s\n" % (f, cf))
1018 1018 self.ui.warn("aborting update spanning branches!\n")
1019 1019 self.ui.status("(use update -m to perform a branch merge)\n")
1020 1020 return 1
1021 1021 # we have to remember what files we needed to get/change
1022 1022 # because any file that's different from either one of its
1023 1023 # parents must be in the changeset
1024 1024 mode = 'm'
1025 1025 self.dirstate.update(mark.keys(), "m")
1026 1026
1027 1027 self.dirstate.setparents(p1, p2)
1028 1028
1029 1029 # get the files we don't need to change
1030 1030 files = get.keys()
1031 1031 files.sort()
1032 1032 for f in files:
1033 1033 if f[0] == "/": continue
1034 1034 self.ui.note("getting %s\n" % f)
1035 1035 t = self.file(f).read(get[f])
1036 1036 try:
1037 1037 self.wfile(f, "w").write(t)
1038 1038 except IOError:
1039 1039 os.makedirs(os.path.dirname(self.wjoin(f)))
1040 1040 self.wfile(f, "w").write(t)
1041 1041 set_exec(self.wjoin(f), mf2[f])
1042 1042 self.dirstate.update([f], mode)
1043 1043
1044 1044 # merge the tricky bits
1045 1045 files = merge.keys()
1046 1046 files.sort()
1047 1047 for f in files:
1048 1048 self.ui.status("merging %s\n" % f)
1049 1049 m, o, flag = merge[f]
1050 1050 self.merge3(f, m, o)
1051 1051 set_exec(self.wjoin(f), flag)
1052 1052 self.dirstate.update([f], 'm')
1053 1053
1054 1054 for f in remove:
1055 1055 self.ui.note("removing %s\n" % f)
1056 1056 os.unlink(f)
1057 1057 if mode == 'n':
1058 1058 self.dirstate.forget(remove)
1059 1059 else:
1060 1060 self.dirstate.update(remove, 'r')
1061 1061
1062 1062 def merge3(self, fn, my, other):
1063 1063 """perform a 3-way merge in the working directory"""
1064 1064
1065 1065 def temp(prefix, node):
1066 1066 pre = "%s~%s." % (os.path.basename(fn), prefix)
1067 1067 (fd, name) = tempfile.mkstemp("", pre)
1068 1068 f = os.fdopen(fd, "w")
1069 1069 f.write(fl.revision(node))
1070 1070 f.close()
1071 1071 return name
1072 1072
1073 1073 fl = self.file(fn)
1074 1074 base = fl.ancestor(my, other)
1075 1075 a = self.wjoin(fn)
1076 1076 b = temp("other", other)
1077 1077 c = temp("base", base)
1078 1078
1079 1079 self.ui.note("resolving %s\n" % fn)
1080 1080 self.ui.debug("file %s: other %s ancestor %s\n" %
1081 1081 (fn, short(other), short(base)))
1082 1082
1083 1083 cmd = os.environ.get("HGMERGE", "hgmerge")
1084 1084 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1085 1085 if r:
1086 1086 self.ui.warn("merging %s failed!\n" % fn)
1087 1087
1088 1088 os.unlink(b)
1089 1089 os.unlink(c)
1090 1090
1091 1091 def verify(self):
1092 1092 filelinkrevs = {}
1093 1093 filenodes = {}
1094 1094 changesets = revisions = files = 0
1095 1095 errors = 0
1096 1096
1097 1097 seen = {}
1098 1098 self.ui.status("checking changesets\n")
1099 1099 for i in range(self.changelog.count()):
1100 1100 changesets += 1
1101 1101 n = self.changelog.node(i)
1102 1102 if n in seen:
1103 1103 self.ui.warn("duplicate changeset at revision %d\n" % i)
1104 1104 errors += 1
1105 1105 seen[n] = 1
1106 1106
1107 1107 for p in self.changelog.parents(n):
1108 1108 if p not in self.changelog.nodemap:
1109 1109 self.ui.warn("changeset %s has unknown parent %s\n" %
1110 1110 (short(n), short(p)))
1111 1111 errors += 1
1112 1112 try:
1113 1113 changes = self.changelog.read(n)
1114 1114 except Exception, inst:
1115 1115 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1116 1116 errors += 1
1117 1117
1118 1118 for f in changes[3]:
1119 1119 filelinkrevs.setdefault(f, []).append(i)
1120 1120
1121 1121 seen = {}
1122 1122 self.ui.status("checking manifests\n")
1123 1123 for i in range(self.manifest.count()):
1124 1124 n = self.manifest.node(i)
1125 1125 if n in seen:
1126 1126 self.ui.warn("duplicate manifest at revision %d\n" % i)
1127 1127 errors += 1
1128 1128 seen[n] = 1
1129 1129
1130 1130 for p in self.manifest.parents(n):
1131 1131 if p not in self.manifest.nodemap:
1132 1132 self.ui.warn("manifest %s has unknown parent %s\n" %
1133 1133 (short(n), short(p)))
1134 1134 errors += 1
1135 1135
1136 1136 try:
1137 1137 delta = mdiff.patchtext(self.manifest.delta(n))
1138 1138 except KeyboardInterrupt:
1139 1139 print "aborted"
1140 1140 sys.exit(0)
1141 1141 except Exception, inst:
1142 1142 self.ui.warn("unpacking manifest %s: %s\n"
1143 1143 % (short(n), inst))
1144 1144 errors += 1
1145 1145
1146 1146 ff = [ l.split('\0') for l in delta.splitlines() ]
1147 1147 for f, fn in ff:
1148 1148 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1149 1149
1150 1150 self.ui.status("crosschecking files in changesets and manifests\n")
1151 1151 for f in filenodes:
1152 1152 if f not in filelinkrevs:
1153 1153 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1154 1154 errors += 1
1155 1155
1156 1156 for f in filelinkrevs:
1157 1157 if f not in filenodes:
1158 1158 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1159 1159 errors += 1
1160 1160
1161 1161 self.ui.status("checking files\n")
1162 1162 ff = filenodes.keys()
1163 1163 ff.sort()
1164 1164 for f in ff:
1165 1165 if f == "/dev/null": continue
1166 1166 files += 1
1167 1167 fl = self.file(f)
1168 1168 nodes = { nullid: 1 }
1169 1169 seen = {}
1170 1170 for i in range(fl.count()):
1171 1171 revisions += 1
1172 1172 n = fl.node(i)
1173 1173
1174 1174 if n in seen:
1175 1175 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1176 1176 errors += 1
1177 1177
1178 1178 if n not in filenodes[f]:
1179 1179 self.ui.warn("%s: %d:%s not in manifests\n"
1180 1180 % (f, i, short(n)))
1181 1181 print len(filenodes[f].keys()), fl.count(), f
1182 1182 errors += 1
1183 1183 else:
1184 1184 del filenodes[f][n]
1185 1185
1186 1186 flr = fl.linkrev(n)
1187 1187 if flr not in filelinkrevs[f]:
1188 1188 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1189 1189 % (f, short(n), fl.linkrev(n)))
1190 1190 errors += 1
1191 1191 else:
1192 1192 filelinkrevs[f].remove(flr)
1193 1193
1194 1194 # verify contents
1195 1195 try:
1196 1196 t = fl.read(n)
1197 1197 except Exception, inst:
1198 1198 self.ui.warn("unpacking file %s %s: %s\n"
1199 1199 % (f, short(n), inst))
1200 1200 errors += 1
1201 1201
1202 1202 # verify parents
1203 1203 (p1, p2) = fl.parents(n)
1204 1204 if p1 not in nodes:
1205 1205 self.ui.warn("file %s:%s unknown parent 1 %s" %
1206 1206 (f, short(n), short(p1)))
1207 1207 errors += 1
1208 1208 if p2 not in nodes:
1209 1209 self.ui.warn("file %s:%s unknown parent 2 %s" %
1210 1210 (f, short(n), short(p1)))
1211 1211 errors += 1
1212 1212 nodes[n] = 1
1213 1213
1214 1214 # cross-check
1215 1215 for node in filenodes[f]:
1216 1216 self.ui.warn("node %s in manifests not in %s\n"
1217 1217 % (hex(n), f))
1218 1218 errors += 1
1219 1219
1220 1220 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1221 1221 (files, changesets, revisions))
1222 1222
1223 1223 if errors:
1224 1224 self.ui.warn("%d integrity errors encountered!\n" % errors)
1225 1225 return 1
1226 1226
1227 1227 class remoterepository:
1228 1228 def __init__(self, ui, path):
1229 1229 self.url = path
1230 1230 self.ui = ui
1231 no_list = [ "localhost", "127.0.0.1" ]
1232 host = ui.config("http_proxy", "host")
1233 user = ui.config("http_proxy", "user")
1234 passwd = ui.config("http_proxy", "passwd")
1235 no = ui.config("http_proxy", "no")
1236 if no:
1237 no_list = no_list + no.split(",")
1238
1239 no_proxy = 0
1240 for h in no_list:
1241 if (path.startswith("http://" + h + "/") or
1242 path.startswith("http://" + h + ":") or
1243 path == "http://" + h):
1244 no_proxy = 1
1245
1246 # Note: urllib2 takes proxy values from the environment and those will
1247 # take precedence
1248
1249 proxy_handler = urllib2.BaseHandler()
1250 if host and not no_proxy:
1251 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1252
1253 authinfo = None
1254 if user and passwd:
1255 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1256 passmgr.add_password(None, host, user, passwd)
1257 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1258
1259 opener = urllib2.build_opener(proxy_handler, authinfo)
1260 urllib2.install_opener(opener)
1231 1261
1232 1262 def do_cmd(self, cmd, **args):
1233 1263 self.ui.debug("sending %s command\n" % cmd)
1234 1264 q = {"cmd": cmd}
1235 1265 q.update(args)
1236 1266 qs = urllib.urlencode(q)
1237 1267 cu = "%s?%s" % (self.url, qs)
1238 return urllib.urlopen(cu)
1268 return urllib2.urlopen(cu)
1239 1269
1240 1270 def heads(self):
1241 1271 d = self.do_cmd("heads").read()
1242 1272 try:
1243 1273 return map(bin, d[:-1].split(" "))
1244 1274 except:
1245 1275 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1246 1276 raise
1247 1277
1248 1278 def branches(self, nodes):
1249 1279 n = " ".join(map(hex, nodes))
1250 1280 d = self.do_cmd("branches", nodes=n).read()
1251 1281 try:
1252 1282 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1253 1283 return br
1254 1284 except:
1255 1285 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1256 1286 raise
1257 1287
1258 1288 def between(self, pairs):
1259 1289 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1260 1290 d = self.do_cmd("between", pairs=n).read()
1261 1291 try:
1262 1292 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1263 1293 return p
1264 1294 except:
1265 1295 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1266 1296 raise
1267 1297
1268 1298 def changegroup(self, nodes):
1269 1299 n = " ".join(map(hex, nodes))
1270 1300 zd = zlib.decompressobj()
1271 1301 f = self.do_cmd("changegroup", roots=n)
1272 1302 bytes = 0
1273 1303 while 1:
1274 1304 d = f.read(4096)
1275 1305 bytes += len(d)
1276 1306 if not d:
1277 1307 yield zd.flush()
1278 1308 break
1279 1309 yield zd.decompress(d)
1280 1310 self.ui.note("%d bytes of data transfered\n" % bytes)
1281 1311
1282 1312 def repository(ui, path=None, create=0):
1283 1313 if path and path[:7] == "http://":
1284 1314 return remoterepository(ui, path)
1285 1315 if path and path[:5] == "hg://":
1286 1316 return remoterepository(ui, path.replace("hg://", "http://"))
1287 1317 if path and path[:11] == "old-http://":
1288 1318 return localrepository(ui, path.replace("old-http://", "http://"))
1289 1319 else:
1290 1320 return localrepository(ui, path, create)
1291 1321
1292 1322 class httprangereader:
1293 1323 def __init__(self, url):
1294 1324 self.url = url
1295 1325 self.pos = 0
1296 1326 def seek(self, pos):
1297 1327 self.pos = pos
1298 1328 def read(self, bytes=None):
1299 1329 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1300 1330 urllib2.install_opener(opener)
1301 1331 req = urllib2.Request(self.url)
1302 1332 end = ''
1303 1333 if bytes: end = self.pos + bytes
1304 1334 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1305 1335 f = urllib2.urlopen(req)
1306 1336 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now