1 # Copyright (C) 2006, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 from __future__
import generators
8 from zeroinstall
import SafeException
10 """A manifest is a string representing a directory tree, with the property
11 that two trees will generate identical manifest strings if and only if:
13 - They have extactly the same set of files, directories and symlinks.
14 - For each pair of corresponding directories in the two sets:
15 - The mtimes are the same.
16 - For each pair of corresponding files in the two sets:
17 - The size, executable flag and mtime are the same.
18 - The contents have matching SHA1 sums.
19 - For each pair of corresponding symlinks in the two sets:
20 - The mtime and size are the same.
21 - The targets have matching SHA1 sums.
23 The manifest is typically processed with SHA1 itself. So, the idea is that
24 any significant change to the contents of the tree will change the SHA1 sum
27 A top-level ".manifest" file is ignored.
30 def generate_manifest(root
):
32 # To ensure that a line-by-line comparison of the manifests
33 # is possible, we require that filenames don't contain newlines.
34 # Otherwise, you can name a file so that the part after the \n
35 # would be interpreted as another line in the manifest.
36 assert '\n' not in sub
37 assert sub
.startswith('/')
39 if sub
== '/.manifest': return
41 full
= os
.path
.join(root
, sub
[1:])
47 yield "D %s %s" % (info
.st_mtime
, sub
)
48 items
= os
.listdir(full
)
51 for y
in recurse(os
.path
.join(sub
, x
)):
56 leaf
= os
.path
.basename(sub
[1:])
58 d
= sha
.new(file(full
).read()).hexdigest()
60 yield "X %s %s %s %s" % (d
, info
.st_mtime
,info
.st_size
, leaf
)
62 yield "F %s %s %s %s" % (d
, info
.st_mtime
,info
.st_size
, leaf
)
64 d
= sha
.new(os
.readlink(full
)).hexdigest()
65 # Note: Can't use utime on symlinks, so skip mtime
66 yield "S %s %s %s" % (d
, info
.st_size
, leaf
)
68 raise SafeException("Unknown object '%s' (not a file, directory or symlink)" %
70 for x
in recurse('/'): yield x
72 def add_manifest_file(dir, digest
):
73 """Writes a .manifest file into 'dir', and updates digest."""
74 mfile
= os
.path
.join(dir, '.manifest')
75 if os
.path
.islink(mfile
) or os
.path
.exists(mfile
):
76 raise Exception('Archive contains a .manifest file!')
78 for line
in generate_manifest(dir):
79 manifest
+= line
+ '\n'
80 digest
.update(manifest
)
81 stream
= file(mfile
, 'w')
82 stream
.write(manifest
)