1 |
Include directory mtimes in the max mtime calculation, in order |
2 |
to account for removals and renames. |
3 |
|
4 |
Fixes: 6dacd0ed9f6d ("Manifest.write: stable/predictable Manifest mtime for rsync (bug 557962)") |
5 |
X-Gentoo-Bug: 567920 |
6 |
X-Gentoo-Bug-url: https://bugs.gentoo.org/show_bug.cgi?id=567920 |
7 |
--- |
8 |
pym/portage/manifest.py | 40 +++++++++++++++++++++++++++++----------- |
9 |
1 file changed, 29 insertions(+), 11 deletions(-) |
10 |
|
11 |
diff --git a/pym/portage/manifest.py b/pym/portage/manifest.py |
12 |
index f5cf0f5..451f869 100644 |
13 |
--- a/pym/portage/manifest.py |
14 |
+++ b/pym/portage/manifest.py |
15 |
@@ -282,7 +282,8 @@ class Manifest(object): |
16 |
try: |
17 |
myentries = list(self._createManifestEntries()) |
18 |
update_manifest = True |
19 |
- existing_st = None |
20 |
+ preserved_stats = {} |
21 |
+ preserved_stats[self.pkgdir.rstrip(os.sep)] = os.stat(self.pkgdir) |
22 |
if myentries and not force: |
23 |
try: |
24 |
f = io.open(_unicode_encode(self.getFullname(), |
25 |
@@ -290,7 +291,7 @@ class Manifest(object): |
26 |
mode='r', encoding=_encodings['repo.content'], |
27 |
errors='replace') |
28 |
oldentries = list(self._parseManifestLines(f)) |
29 |
- existing_st = os.fstat(f.fileno()) |
30 |
+ preserved_stats[self.getFullname()] = os.fstat(f.fileno()) |
31 |
f.close() |
32 |
if len(oldentries) == len(myentries): |
33 |
update_manifest = False |
34 |
@@ -312,7 +313,7 @@ class Manifest(object): |
35 |
# non-empty for all currently known use cases. |
36 |
write_atomic(self.getFullname(), "".join("%s\n" % |
37 |
_unicode(myentry) for myentry in myentries)) |
38 |
- self._apply_max_mtime(existing_st, myentries) |
39 |
+ self._apply_max_mtime(preserved_stats, myentries) |
40 |
rval = True |
41 |
else: |
42 |
# With thin manifest, there's no need to have |
43 |
@@ -332,17 +333,21 @@ class Manifest(object): |
44 |
raise |
45 |
return rval |
46 |
|
47 |
- def _apply_max_mtime(self, existing_st, entries): |
48 |
+ def _apply_max_mtime(self, preserved_stats, entries): |
49 |
""" |
50 |
Set the Manifest mtime to the max mtime of all relevant files |
51 |
- (the existing Manifest mtime is included in order to account for |
52 |
- eclass modifications that change DIST entries). This results in a |
53 |
+ and directories. Directory mtimes account for file renames and |
54 |
+ removals. The existing Manifest mtime accounts for eclass |
55 |
+ modifications that change DIST entries. This results in a |
56 |
stable/predictable mtime, which is useful when converting thin |
57 |
manifests to thick manifests for distribution via rsync. For |
58 |
portability, the mtime is set with 1 second resolution. |
59 |
|
60 |
@param existing_st: stat result for existing Manifest |
61 |
@type existing_st: posix.stat_result |
62 |
+ @param preserved_stats: maps paths to preserved stat results |
63 |
+ that should be used instead place of os.stat() calls |
64 |
+ @type preserved_stats: dict |
65 |
@param entries: list of current Manifest2Entry instances |
66 |
@type entries: list |
67 |
""" |
68 |
@@ -350,18 +355,31 @@ class Manifest(object): |
69 |
# it always rounds down. Note that stat_result.st_mtime will round |
70 |
# up from 0.999999999 to 1.0 when precision is lost during conversion |
71 |
# from nanosecond resolution to float. |
72 |
- max_mtime = None if existing_st is None else existing_st[stat.ST_MTIME] |
73 |
+ max_mtime = None |
74 |
+ _update_max = (lambda st: max_mtime if max_mtime is not None |
75 |
+ and max_mtime > st[stat.ST_MTIME] else st[stat.ST_MTIME]) |
76 |
+ _stat = (lambda path: preserved_stats[path] if path in preserved_stats |
77 |
+ else os.stat(path)) |
78 |
+ |
79 |
+ for stat_result in preserved_stats.values(): |
80 |
+ max_mtime = _update_max(stat_result) |
81 |
+ |
82 |
+ dirs = set() |
83 |
for entry in entries: |
84 |
if entry.type == 'DIST': |
85 |
continue |
86 |
abs_path = (os.path.join(self.pkgdir, 'files', entry.name) if |
87 |
entry.type == 'AUX' else os.path.join(self.pkgdir, entry.name)) |
88 |
- mtime = os.stat(abs_path)[stat.ST_MTIME] |
89 |
- if max_mtime is None or mtime > max_mtime: |
90 |
- max_mtime = mtime |
91 |
+ max_mtime = _update_max(_stat(abs_path)) |
92 |
+ |
93 |
+ parent_dir = os.path.dirname(abs_path) |
94 |
+ if parent_dir not in dirs: |
95 |
+ dirs.add(parent_dir) |
96 |
+ max_mtime = _update_max(_stat(parent_dir)) |
97 |
|
98 |
if max_mtime is not None: |
99 |
- os.utime(self.getFullname(), (max_mtime, max_mtime)) |
100 |
+ for path in preserved_stats: |
101 |
+ os.utime(path, (max_mtime, max_mtime)) |
102 |
|
103 |
def sign(self): |
104 |
""" Sign the Manifest """ |
105 |
-- |
106 |
2.4.10 |