commit:     3c2cce57700e8a2be4774d653cd632d9e59aab78
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Tue Dec 15 07:29:36 2015 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Tue Dec 15 16:10:53 2015 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=3c2cce57

Manifest._apply_max_mtime: account for removals and renames (bug 567920)

Include directory mtimes in the max mtime calculation, in order
to account for removals and renames.

Fixes: 6dacd0ed9f6d ("Manifest.write: stable/predictable Manifest mtime for 
rsync (bug 557962)")
X-Gentoo-Bug: 567920
X-Gentoo-Bug-url: https://bugs.gentoo.org/show_bug.cgi?id=567920
Acked-by: Alexander Berntsen <bernalex <AT> gentoo.org>

 pym/portage/manifest.py | 40 +++++++++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 11 deletions(-)

diff --git a/pym/portage/manifest.py b/pym/portage/manifest.py
index f5cf0f5..818515f 100644
--- a/pym/portage/manifest.py
+++ b/pym/portage/manifest.py
@@ -282,7 +282,8 @@ class Manifest(object):
                try:
                        myentries = list(self._createManifestEntries())
                        update_manifest = True
-                       existing_st = None
+                       preserved_stats = {}
+                       preserved_stats[self.pkgdir.rstrip(os.sep)] = 
os.stat(self.pkgdir)
                        if myentries and not force:
                                try:
                                        f = 
io.open(_unicode_encode(self.getFullname(),
@@ -290,7 +291,7 @@ class Manifest(object):
                                                mode='r', 
encoding=_encodings['repo.content'],
                                                errors='replace')
                                        oldentries = 
list(self._parseManifestLines(f))
-                                       existing_st = os.fstat(f.fileno())
+                                       preserved_stats[self.getFullname()] = 
os.fstat(f.fileno())
                                        f.close()
                                        if len(oldentries) == len(myentries):
                                                update_manifest = False
@@ -312,7 +313,7 @@ class Manifest(object):
                                        # non-empty for all currently known use 
cases.
                                        write_atomic(self.getFullname(), 
"".join("%s\n" %
                                                _unicode(myentry) for myentry 
in myentries))
-                                       self._apply_max_mtime(existing_st, 
myentries)
+                                       self._apply_max_mtime(preserved_stats, 
myentries)
                                        rval = True
                                else:
                                        # With thin manifest, there's no need 
to have
@@ -332,17 +333,21 @@ class Manifest(object):
                        raise
                return rval
 
-       def _apply_max_mtime(self, existing_st, entries):
+       def _apply_max_mtime(self, preserved_stats, entries):
                """
                Set the Manifest mtime to the max mtime of all relevant files
-               (the existing Manifest mtime is included in order to account for
-               eclass modifications that change DIST entries). This results in 
a
+               and directories. Directory mtimes account for file renames and
+               removals. The existing Manifest mtime accounts for eclass
+               modifications that change DIST entries. This results in a
                stable/predictable mtime, which is useful when converting thin
                manifests to thick manifests for distribution via rsync. For
                portability, the mtime is set with 1 second resolution.
 
                @param existing_st: stat result for existing Manifest
                @type existing_st: posix.stat_result
+               @param preserved_stats: maps paths to preserved stat results
+                       that should be used instead of os.stat() calls
+               @type preserved_stats: dict
                @param entries: list of current Manifest2Entry instances
                @type entries: list
                """
@@ -350,18 +355,31 @@ class Manifest(object):
                # it always rounds down. Note that stat_result.st_mtime will 
round
                # up from 0.999999999 to 1.0 when precision is lost during 
conversion
                # from nanosecond resolution to float.
-               max_mtime = None if existing_st is None else 
existing_st[stat.ST_MTIME]
+               max_mtime = None
+               _update_max = (lambda st: max_mtime if max_mtime is not None
+                       and max_mtime > st[stat.ST_MTIME] else 
st[stat.ST_MTIME])
+               _stat = (lambda path: preserved_stats[path] if path in 
preserved_stats
+                       else os.stat(path))
+
+               for stat_result in preserved_stats.values():
+                       max_mtime = _update_max(stat_result)
+
+               dirs = set()
                for entry in entries:
                        if entry.type == 'DIST':
                                continue
                        abs_path = (os.path.join(self.pkgdir, 'files', 
entry.name) if
                                entry.type == 'AUX' else 
os.path.join(self.pkgdir, entry.name))
-                       mtime = os.stat(abs_path)[stat.ST_MTIME]
-                       if max_mtime is None or mtime > max_mtime:
-                               max_mtime = mtime
+                       max_mtime = _update_max(_stat(abs_path))
+
+                       parent_dir = os.path.dirname(abs_path)
+                       if parent_dir not in dirs:
+                               dirs.add(parent_dir)
+                               max_mtime = _update_max(_stat(parent_dir))
 
                if max_mtime is not None:
-                       os.utime(self.getFullname(), (max_mtime, max_mtime))
+                       for path in preserved_stats:
+                               os.utime(path, (max_mtime, max_mtime))
 
        def sign(self):
                """ Sign the Manifest """

Reply via email to