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