merge from trunk -r 3004:3030
authorKito <kito@gentoo.org>
Wed, 29 Mar 2006 03:45:29 +0000 (03:45 -0000)
committerKito <kito@gentoo.org>
Wed, 29 Mar 2006 03:45:29 +0000 (03:45 -0000)
svn path=/main/branches/prefix/; revision=3031

AUTHORS
bin/ebuild.sh
bin/emerge
bin/repoman
cnf/make.conf
cnf/make.globals
pym/elog_modules/mod_mail.py
pym/elog_modules/mod_save.py
pym/output.py
pym/portage.py
pym/portage_manifest.py

diff --git a/AUTHORS b/AUTHORS
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..41d3e4a26d467087f3f5155d7ed89bcc4b50c31e 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -0,0 +1,8 @@
+In addition to the the long list of regular portage contributors, these folks have put their hard work into this.
+
+Michael Haubenwallner          <michael.haubenwallner at salomon.at>
+Brian Harring                  <ferringb at gmail.com>
+Kito Dietrich                  <kito at gentoo.org>
+Fabian Groffen                 <grobian at gentoo.org>
+Emanuele Giaquinta             <exg at gentoo.org>
+
index 68ec8655c74657c1a6d607d9407c5237d6c044ab..3073392642976bfcf12650ce1213fa165667aadd 100755 (executable)
@@ -320,7 +320,26 @@ with_localstatedir() {
        echo "--localstatedir=${mylocalstatedir}"
        return 0
 }
-                       
+
+with_tmpdir() {
+       local mytmpdir
+       if [ -z "${1}" ]; then
+               if [ ! -z "${EPREFIX}" ]; then
+                       mytmpdir="${EPREFIX}/tmp"
+               else
+                       mytmpdir="/tmp"
+               fi
+       else
+               if [ ! -z "${EPREFIX}" ]; then
+                       mytmpdir="${EPREFIX}${1}"
+               else
+                       mytmpdir="${1}"
+               fi
+       fi
+       echo "--with-tmpdir=${mytmpdir}"
+       return 0
+}
+       
 use_with() {
        if [ -z "$1" ]; then
                echo "!!! use_with() called without a parameter." >&2
@@ -540,6 +559,7 @@ unpack() {
                                ;;
                esac
        done
+       chmod -Rf a+rX,u+w,g-w,o-w .
 }
 
 strip_duplicate_slashes () {
index aa1110eff10e1f65a991ff4e9f40ed89002f3089..669c3df40b87399a0513e71482bbaddc5ce05436 100755 (executable)
@@ -612,16 +612,23 @@ if portage.wheelgid == portage.portage_gid:
        print "emerge: wheel group use is being deprecated. Please update group and passwd to"
        print "        include the portage user as noted above, and then use group portage."
 
+if "--debug" in myopts:
+       print "myaction", myaction
+       print "myopts", myopts
+
 # check if root user is the current user for the actions where emerge needs this
 if portage.secpass < 2:
        # We've already allowed "--version" and "--help" above.
        if "--pretend" not in myopts and \
        myaction not in ("search","info"):
-               if "--debug" in myopts:
-                       print "myaction",myaction
-                       print "myopts",myopts
-               print "emerge: root access required."
-               sys.exit(1)
+               if portage.secpass >= 1:
+                       if "--fetchonly" not in myopts and \
+                       "--fetch-all-uri" not in myopts:
+                               print "emerge: root access required."
+                               sys.exit(1)
+               else:
+                       print "emerge: root access required."
+                       sys.exit(1)
 
 if not "--pretend" in myopts:
        emergelog("Started emerge on: "+time.strftime("%b %d, %Y %H:%M:%S", time.localtime()))
@@ -2559,7 +2566,7 @@ if myaction in ["sync","metadata"] and (not "--help" in myopts):
        
                        if portage.settings["RSYNC_TIMEOUT"] != "":
                                portage.writemsg("WARNING: usage of RSYNC_TIMEOUT is deprecated, " + \
-                               "use PORTAGE_RSYNC_OPTS instead\n")
+                               "use PORTAGE_RSYNC_EXTRA_OPTS instead\n")
                                try:
                                        mytimeout = int(portage.settings["RSYNC_TIMEOUT"])
                                        rsync_opts.append("--timeout=%d" % mytimeout)
index b1babde77904aaa992949fa8dc36c624ba517141..c54ffbd42e5bdeb23e69a1202a17a24b2f5b4f22 100755 (executable)
@@ -1714,7 +1714,7 @@ else:
                                del myfiles[x]
                mydone=[]
                if repolevel==3:   # In a package dir
-                       repoman_settings["O"]="./"
+                       repoman_settings["O"]=os.getcwd()
                        portage.digestgen([],repoman_settings,manifestonly=1)
                elif repolevel==2: # In a category dir
                        for x in myfiles:
@@ -1724,7 +1724,7 @@ else:
                                if xs[0] in mydone:
                                        continue
                                mydone.append(xs[0])
-                               repoman_settings["O"]="./"+xs[0]
+                               repoman_settings["O"]=os.path.join(os.getcwd(), xs[0])
                                portage.digestgen([],repoman_settings,manifestonly=1)
                elif repolevel==1: # repo-cvsroot
                        print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
@@ -1735,7 +1735,7 @@ else:
                                if string.join(xs[:2],"/") in mydone:
                                        continue
                                mydone.append(string.join(xs[:2],"/"))
-                               repoman_settings["O"]="./"+string.join(xs[:2],"/")
+                               repoman_settings["O"]=os.path.join(os.getcwd(), x[0], x[1])
                                portage.digestgen([],repoman_settings,manifestonly=1)
                else:
                        print red("I'm confused... I don't know where I am!")
index 37e2603eb2b235ed2cdbe7afa1112aae4bfccf77..d7f22c4a36d193263dadc8679857783ea56853a2 100644 (file)
@@ -313,7 +313,8 @@ PORTAGE_ELOG_CLASSES="warn error log"
 
 # PORTAGE_ELOG_SYSTEM: selects the module(s) to process the log messages. Modules
 #                      included in portage are (empty means logging is disabled):
-#                          save (saves one log per package in $PORTAGE_TMPDIR/elogs)
+#                          save (saves one log per package in $PORT_LOGDIR/elog, 
+#                                /var/log/portage/elog if $PORT_LOGDIR is unset)
 #                          custom (passes all messages to $PORTAGE_LOG_COMMAND)
 #                          syslog (sends all messages to syslog)
 #                          mail (send all messages to the mailserver defined 
@@ -345,3 +346,14 @@ PORTAGE_ELOG_CLASSES="warn error log"
 #PORTAGE_ELOG_MAILURI="root@localhost localhost" (this is also the default setting)
 #PORTAGE_ELOG_MAILURI="user@some.domain mail.some.domain" (sends mails to user@some.domain using the mailserver mail.some.domain)
 #PORTAGE_ELOG_MAILURI="user@some.domain user:secret@mail.some.domain:100465" (this is left uncommented as a reader excercise ;)
+
+# PORTAGE_ELOG_MAILFROM: you can set the from-address of logmails with this variable,
+#                        if unset mails are sent by "portage" (this default may fail
+#                        in some environments).
+#PORTAGE_ELOG_MAILFROM="portage@some.domain"
+
+# PORTAGE_ELOG_MAILSUBJECT: template string to be used as subject for logmails. The following
+#                           variables are exanded:
+#                               ${PACKAGE} - see description of PORTAGE_ELOG_COMMAND
+#                               ${HOST} - FQDN of the host portage is running on
+#PORTAGE_ELOG_MAILSUBJECT="package ${PACKAGE} merged on ${HOST} with notice"
index bafa508c131438360dedb3e5c3c4fe99c5529540..91dfb121cf4a37bf9a8ca84547760d43040bac73 100644 (file)
@@ -77,6 +77,12 @@ DEFAULT_PATH="@DOMAIN_PREFIX@/sbin:@DOMAIN_PREFIX@/usr/sbin:@DOMAIN_PREFIX@/bin:
 # Mode bits for ${WORKDIR} (see ebuild.5).
 PORTAGE_WORKDIR_MODE="0700"
 
+# Some defaults for elog
+PORTAGE_ELOG_CLASSES="log warn error"
+
+PORTAGE_ELOG_MAILURI="@rootuser@"
+PORTAGE_ELOG_MAILSUBJECT="[portage] ebuild log for \${PACKAGE} on \${HOST}"
+PORTAGE_ELOG_MAILFROM="portage"
 
 #            *****************************
 #            **  DO NOT EDIT THIS FILE  **
index 59fd56a8edc5e79623ce898eed9a27f0a180c373..68771ba516b90068c6680dc741a9ec9a0e38816d 100644 (file)
@@ -14,29 +14,35 @@ def process(mysettings, cpv, logentries, fulltext):
        #       passwd:     password for smtp auth (defaults to none)
        #       mailserver: smtp server that should be used to deliver the mail (defaults to localhost)
        #       port:       port to use on the given smtp server (defaults to 25, values > 100000 indicate that starttls should be used on (port-100000))
-       if "PORTAGE_ELOG_MAILURI" in mysettings.keys():
-               if " " in mysettings["PORTAGE_ELOG_MAILURI"]:
-                       myrecipient, mymailuri = mysettings["PORTAGE_ELOG_MAILURI"].split()
-                       if "@" in mymailuri:
-                               myauthdata, myconndata = mymailuri.rsplit("@", 1)
-                               try:
-                                       mymailuser,mymailpasswd = myauthdata.split(":")
-                               except ValueError:
-                                       print "!!! invalid SMTP AUTH configuration, trying unauthenticated ..."
-                       else:
-                               myconndata = mymailuri
-                       if ":" in myconndata:
-                               mymailhost,mymailport = myconndata.split(":")
-                       else:
-                               mymailhost = myconndata
+       if " " in mysettings["PORTAGE_ELOG_MAILURI"]:
+               myrecipient, mymailuri = mysettings["PORTAGE_ELOG_MAILURI"].split()
+               if "@" in mymailuri:
+                       myauthdata, myconndata = mymailuri.rsplit("@", 1)
+                       try:
+                               mymailuser,mymailpasswd = myauthdata.split(":")
+                       except ValueError:
+                               print "!!! invalid SMTP AUTH configuration, trying unauthenticated ..."
                else:
-                       myrecipient = mysettings["PORTAGE_ELOG_MAILURI"]
+                       myconndata = mymailuri
+               if ":" in myconndata:
+                       mymailhost,mymailport = myconndata.split(":")
+               else:
+                       mymailhost = myconndata
+       else:
+               myrecipient = mysettings["PORTAGE_ELOG_MAILURI"]
        try:
+               myfrom = mysettings["PORTAGE_ELOG_MAILFROM"]
+               
                mymessage = email.Message.Message()
-               mymessage.set_unixfrom("portage")
+               mymessage.set_unixfrom(myfrom)
                mymessage.set_payload(fulltext)
                mymessage["To"] = myrecipient
-               mymessage["Subject"] = "[portage] Ebuild log for %s" % cpv
+               
+               mysubject = mysettings["PORTAGE_ELOG_MAILSUBJECT"]
+               mysubject = mysubject.replace("${PACKAGE}", cpv)
+               mysubject = mysubject.replace("${HOST}", socket.getfqdn())
+               
+               mymessage["Subject"] = mysubject
                                
                if int(mymailport) > 100000:
                        myconn = smtplib.SMTP(mymailhost, int(mymailport) - 100000)
@@ -45,10 +51,10 @@ def process(mysettings, cpv, logentries, fulltext):
                        myconn = smtplib.SMTP(mymailhost, mymailport)
                if mymailuser != "" and mymailpasswd != "":
                        myconn.login(mymailuser, mymailpasswd)
-               myconn.sendmail("portage", myrecipient, mymessage.as_string())
+               myconn.sendmail(myfrom, myrecipient, mymessage.as_string())
                myconn.quit()
        except smtplib.SMTPException, e:
                raise portage_exception.PortageException("!!! An error occured while trying to send logmail:\n"+str(e))
        except socket.error, e:
-               raise portage_exception.PortageException("!!! A network error occured while trying to send logmail:\n"+str(e)+"\nSure you configured PORTAGE_LOG_MAILURI correctly?")
+               raise portage_exception.PortageException("!!! A network error occured while trying to send logmail:\n"+str(e)+"\nSure you configured PORTAGE_ELOG_MAILURI correctly?")
        return
index 106e8800a7e107095bd832cdbd8cdcd167ceae78..6c16c20413df96d07333d417f314d5f2fb96a18d 100644 (file)
@@ -4,7 +4,10 @@ from portage_data import portage_uid, portage_gid
 def process(mysettings, cpv, logentries, fulltext):
        cpv_path = cpv.replace("/", ":")
 
-       elogdir = mysettings["PORTAGE_TMPDIR"]+"/elogs"
+       if mysettings["PORT_LOGDIR"] != "":
+               elogdir = os.path.join(mysettings["PORT_LOGDIR"], "elog")
+       else:
+               elogdir = os.path.join(os.sep, "var", "log", "portage", "elog")
        if not os.path.exists(elogdir):
                os.makedirs(elogdir)
        os.chown(elogdir, portage_uid, portage_gid)
index f23bac263222a327eb20421c16e4b4af0f3af83b..e735d4d4762d18203fd73462cb6eb7edcb934f25 100644 (file)
@@ -132,47 +132,24 @@ def nocolor():
 def resetColor():
        return codes["reset"]
 
-def ctext(color,text):
-       return codes[ctext]+text+codes["reset"]
-
-def bold(text):
-       return codes["bold"]+text+codes["reset"]
-def white(text):
-       return bold(text)
-
-def teal(text):
-       return codes["teal"]+text+codes["reset"]
-def turquoise(text):
-       return codes["turquoise"]+text+codes["reset"]
-def darkteal(text):
-       return turquoise(text)
-
-def fuscia(text): # Don't use this one. It's spelled wrong!
-       return codes["fuchsia"]+text+codes["reset"]
-def fuchsia(text):
-       return codes["fuchsia"]+text+codes["reset"]
-def purple(text):
-       return codes["purple"]+text+codes["reset"]
-
-def blue(text):
-       return codes["blue"]+text+codes["reset"]
-def darkblue(text):
-       return codes["darkblue"]+text+codes["reset"]
-
-def green(text):
-       return codes["green"]+text+codes["reset"]
-def darkgreen(text):
-       return codes["darkgreen"]+text+codes["reset"]
-
-def yellow(text):
-       return codes["yellow"]+text+codes["reset"]
-def brown(text):
-       return codes["brown"]+text+codes["reset"]
-def darkyellow(text):
-       return brown(text)
-
-def red(text):
-       return codes["red"]+text+codes["reset"]
-def darkred(text):
-       return codes["darkred"]+text+codes["reset"]
-
+def colorize(color_key, text):
+       return codes[color_key] + text + codes["reset"]
+
+codes["darkteal"]   = codes["turquoise"]
+codes["darkyellow"] = codes["brown"]
+codes["fuscia"]     = codes["fuchsia"]
+codes["white"]      = codes["bold"]
+
+compat_functions_colors = ["bold","white","teal","turquoise","darkteal",
+       "fuscia","fuchsia","purple","blue","darkblue","green","darkgreen","yellow",
+       "brown","darkyellow","red","darkred"]
+
+def create_color_func(color_key):
+       def derived_func(*args):
+               newargs = list(args)
+               newargs.insert(0, color_key)
+               return colorize(*newargs)
+       return derived_func
+
+for c in compat_functions_colors:
+       setattr(sys.modules[__name__], c, create_color_func(c))
index f7341ce86a2bfc2bcd4f2ed4569fe50d9635c4a6..b3518ebf1aefb115db4b5911d9aee2aabc4b8599 100644 (file)
@@ -215,7 +215,7 @@ def cacheddir(my_original_path, ignorecvs, ignorelist, EmptyOnError, followSymli
                if stat.S_ISDIR(pathstat[stat.ST_MODE]):
                        mtime = pathstat[stat.ST_MTIME]
                else:
-                       raise portage_exception.PortageException
+                       raise portage_exception.DirectoryNotFound(mypath)
        except (IOError,OSError,portage_exception.PortageException):
                if EmptyOnError:
                        return [], []
@@ -2505,70 +2505,72 @@ def prepare_build_dirs(myroot, mysettings, cleanup):
                        writemsg("File Not Found: '%s'\n" % str(e))
                        return 1
 
-       if "ccache" in features:
-               ccache_enabled = True
-               if "CCACHE_DIR" not in mysettings or "" == mysettings["CCACHE_DIR"]:
-                       mysettings["CCACHE_DIR"] = os.path.join(mysettings["PORTAGE_TMPDIR"], "ccache")
-
-               ccache_enabled = makedirs(mysettings["CCACHE_DIR"])
-
-               if ccache_enabled:
-                       ccache_enabled = apply_recursive_permissions(
-                               mysettings["CCACHE_DIR"], gid=portage_gid,
-                               dirmode=02070, dirmask=02,
-                               filemode=060, filemask=02)
-
-               if not ccache_enabled:
-                       writemsg("!!! Failed resetting perms on CCACHE_DIR='%s'\n" % mysettings["CCACHE_DIR"])
-                       features.remove("ccache")
-                       mysettings["FEATURES"] = " ".join(features)
-
-               del ccache_enabled
-
-       if "confcache" in features:
-               confcache_enabled = True
-               if "CONFCACHE_DIR" not in mysettings:
-                       mysettings["CONFCACHE_DIR"] = os.path.join(mysettings["PORTAGE_TMPDIR"], "confcache")
-
-               confcache_enabled = makedirs(mysettings["CONFCACHE_DIR"])
-
-               if confcache_enabled:
-                       confcache_enabled = apply_recursive_permissions(
-                               mysettings["CONFCACHE_DIR"], gid=portage_gid,
-                               dirmode=02070, dirmask=02,
-                               filemode=060, filemask=02)
-
-               if not confcache_enabled:
-                       writemsg("!!! Failed resetting perms on confcachedir %s\n" % mysettings["CONFCACHE_DIR"])
-                       features.remove("confcache")
-                       mysettings["FEATURES"] = " ".join(features)
-
-               del confcache_enabled
-
-       if "distcc" in features:
-               
-               distcc_enabled = True
-
-               if "DISTCC_DIR" not in mysettings or "" == mysettings["DISTCC_DIR"]:
-                       mysettings["DISTCC_DIR"] = os.path.join(mysettings["BUILD_PREFIX"], ".distcc")
-               for x in ("", "lock", "state"):
-                       mydir = os.path.join(mysettings["DISTCC_DIR"], x)
-                       if not makedirs(mydir):
-                               distcc_enabled = False
-                               break
-
-               if distcc_enabled:
-                       distcc_enabled = apply_recursive_permissions(
-                       mysettings["DISTCC_DIR"], gid=portage_gid,
-                       dirmode=02070, dirmask=02, filemode=060, filemask=02)
-
-               if not distcc_enabled:
-                       writemsg("\n!!! File system problem when setting DISTCC_DIR directory permissions.\n")
-                       writemsg(  "!!! DISTCC_DIR="+str(mysettings["DISTCC_DIR"]+"\n"))
-                       time.sleep(5)
-                       features.remove("distcc")
-                       mysettings["FEATURES"] = " ".join(features)
-                       mysettings["DISTCC_DIR"]=""
+       features_dirs = {
+               "ccache":{
+                       "basedir_var":"CCACHE_DIR",
+                       "default_dir":os.path.join(mysettings["PORTAGE_TMPDIR"], "ccache"),
+                       "always_recurse":False},
+               "confcache":{
+                       "basedir_var":"CONFCACHE_DIR",
+                       "default_dir":os.path.join(mysettings["PORTAGE_TMPDIR"], "confcache"),
+                       "always_recurse":True},
+               "distcc":{
+                       "basedir_var":"DISTCC_DIR",
+                       "default_dir":os.path.join(mysettings["BUILD_PREFIX"], ".distcc"),
+                       "subdirs":("lock", "state"),
+                       "always_recurse":True}
+       }
+       dirmode  = 02070
+       filemode =   060
+       modemask =    02
+       for myfeature, kwargs in features_dirs.iteritems():
+               if myfeature in features:
+                       basedir = mysettings[kwargs["basedir_var"]]
+                       if basedir == "":
+                               basedir = kwargs["default_dir"]
+                               mysettings[kwargs["basedir_var"]] = basedir
+                       try:
+                               mydirs = [mysettings[kwargs["basedir_var"]]]
+                               if "subdirs" in kwargs:
+                                       for subdir in kwargs["subdirs"]:
+                                               mydirs.append(os.path.join(basedir, subdir))
+                               for mydir in mydirs:
+                                       if not makedirs(mydir):
+                                               raise portage_exception.DirectoryNotFound(
+                                                       "Failed to create directory.")
+                                       try:
+                                               initial_stat = os.stat(mydir)
+                                               apply_secpass_permissions(mydir,
+                                                       gid=portage_gid, mode=dirmode, mask=modemask, stat_cached=initial_stat)
+                                               result_stat = os.stat(mydir)
+                                       except OSError, oe:
+                                               if errno.EPERM == oe.errno:
+                                                       writemsg("!!! %s\n" % oe)
+                                                       raise portage_exception.OperationNotPermitted("stat('%s')" % mydir)
+                                               raise
+                                       # To avoid excessive recursive stat calls, we trigger
+                                       # recursion when the top level directory does not initially
+                                       # match our permission requirements.
+                                       if kwargs["always_recurse"] or \
+                                       result_stat.st_gid != initial_stat.st_gid or \
+                                       result_stat.st_mode & 07777 != initial_stat.st_mode & 07777:
+                                               if not kwargs["always_recurse"]:
+                                                       writemsg("Adjusting permissions recursively: '%s'" % mydir)
+                                               def onerror(e):
+                                                       raise   # The feature is disabled if a single error
+                                                                       # occurs during permissions adjustment.
+                                               if not apply_recursive_permissions(mydir,
+                                               gid=portage_gid, dirmode=dirmode, dirmask=modemask,
+                                               filemode=filemode, filemask=modemask, onerror=onerror):
+                                                       raise portage_exception.OperationNotPermitted(
+                                                               "Failed to apply recursive permissions for the portage group.")
+                       except portage_exception.PortageException, e:
+                               features.remove(myfeature)
+                               mysettings["FEATURES"] = " ".join(features)
+                               writemsg("!!! %s\n" % str(e))
+                               writemsg("!!! Failed resetting perms on %s='%s'\n" % (kwargs["basedir_var"], basedir))
+                               writemsg("!!! Disabled FEATURES='%s'\n" % myfeature)
+                               time.sleep(5)
 
        workdir_mode = 0700
        try:
@@ -5875,9 +5877,11 @@ class dblink:
                        if self.pkg in otherversions:
                                otherversions.remove(self.pkg)  # we already checked this package
 
+                       myslot = self.settings["SLOT"]
                        for v in otherversions:
-                               # should we check for same SLOT here ?
-                               mypkglist.append(dblink(self.cat,v,destroot,self.settings))
+                               # only allow versions with same slot to overwrite files
+                               if myslot == db[self.myroot]["vartree"].dbapi.aux_get(self.mycpv, ["SLOT"])[0]:
+                                       mypkglist.append(dblink(self.cat,v,destroot,self.settings))
 
                        print green("*")+" checking "+str(len(myfilelist))+" files for package collisions"
                        for f in myfilelist:
index 31b77ed1aed770ed093d5a757d32e8dca47e2254..a9df1cc68d10bc494a369e6216eedbdc7c3aef9d 100644 (file)
@@ -1,15 +1,16 @@
-import os, sets
+import errno, os, sets
 
 import portage, portage_exception, portage_versions, portage_const
 from portage_checksum import *
 from portage_exception import *
+from portage_util import write_atomic
 
 class FileNotInManifestException(PortageException):
        pass
 
 def manifest2AuxfileFilter(filename):
        filename = filename.strip(os.sep)
-       return not (filename in ["CVS", ".svn"] or filename.startswith("digest-") or filename.startswith(".svn"))
+       return not (filename in [".svn", "CVS"] or filename.startswith("CVS"+os.sep) or filename.startswith(".svn"+os.sep) or filename.startswith("digest-"))
 
 def manifest2MiscfileFilter(filename):
        filename = filename.strip(os.sep)
@@ -83,6 +84,11 @@ class Manifest(object):
                mylines = fd.readlines()
                fd.close()
                mylines.extend(self._readDigests().split("\n"))
+               self._parseDigests(mylines, myhashdict=self.fhashdict)
+
+       def _parseDigests(self, mylines, myhashdict=None):
+               if myhashdict is None:
+                       myhashdict = {}
                for l in mylines:
                        myname = ""
                        mysplit = l.split()
@@ -102,32 +108,71 @@ class Manifest(object):
                                myhashes = dict(zip(mysplit[3::2], mysplit[4::2]))
                        if len(myname) == 0:
                                continue
-                       if not self.fhashdict[mytype].has_key(myname):
-                               self.fhashdict[mytype][myname] = {} 
-                       self.fhashdict[mytype][myname].update(myhashes)
-                       self.fhashdict[mytype][myname]["size"] = mysize
+                       myhashdict.setdefault(mytype, {})
+                       myhashdict[mytype].setdefault(myname, {})
+                       myhashdict[mytype][myname].update(myhashes)
+                       myhashdict[mytype][myname]["size"] = mysize
+               return myhashdict
        
-       def _writeDigests(self):
+       def _writeDigests(self, force=False):
                """ Create old style digest files for this Manifest instance """
                cpvlist = [os.path.join(self.pkgdir.rstrip(os.sep).split(os.sep)[-2], x[:-7]) for x in portage.listdir(self.pkgdir) if x.endswith(".ebuild")]
                rval = []
                for cpv in cpvlist:
                        dname = os.path.join(self.pkgdir, "files", "digest-"+portage.catsplit(cpv)[1])
-                       mylines = []
                        distlist = self._getCpvDistfiles(cpv)
-                       for f in self.fhashdict["DIST"].keys():
-                               if f in distlist:
-                                       for h in self.fhashdict["DIST"][f].keys():
-                                               if h not in portage_const.MANIFEST1_HASH_FUNCTIONS:
-                                                       continue
-                                               myline = " ".join([h, str(self.fhashdict["DIST"][f][h]), f, str(self.fhashdict["DIST"][f]["size"])])
-                                               mylines.append(myline)
-                       fd = open(dname, "w")
-                       fd.write("\n".join(mylines))
-                       fd.write("\n")
-                       fd.close()
+                       update_digest = True
+                       if not force:
+                               try:
+                                       f = open(dname, "r")
+                                       old_data = self._parseDigests(f.readlines())
+                                       f.close()
+                                       if len(old_data) == 1 and "DIST" in old_data:
+                                               new_data = self._getDigestData(distlist)
+                                               for myfile in new_data["DIST"]:
+                                                       for hashname in new_data["DIST"][myfile].keys():
+                                                               if hashname != "size" and \
+                                                               hashname not in portage_const.MANIFEST1_HASH_FUNCTIONS:
+                                                                       del new_data["DIST"][myfile][hashname]
+                                               if new_data["DIST"] == old_data["DIST"]:
+                                                       update_digest = False
+                               except (IOError, OSError), e:
+                                       if errno.ENOENT == e.errno:
+                                               pass
+                                       else:
+                                               raise
+                       if update_digest:
+                               write_atomic(dname,
+                               "\n".join(self._createDigestLines1(distlist, self.fhashdict))+"\n")
                        rval.append(dname)
                return rval
+
+       def _getDigestData(self, distlist):
+               """create a hash dict for a specific list of files"""
+               myhashdict = {}
+               for myname in distlist:
+                       for mytype in self.fhashdict:
+                               if myname in self.fhashdict[mytype]:
+                                       myhashdict.setdefault(mytype, {})
+                                       myhashdict[mytype].setdefault(myname, {})
+                                       myhashdict[mytype][myname] = self.fhashdict[mytype][myname]
+               return myhashdict
+
+       def _createDigestLines1(self, distlist, myhashdict):
+               """ Create an old style digest file."""
+               mylines = []
+               myfiles = myhashdict["DIST"].keys()
+               myfiles.sort()
+               for f in myfiles:
+                       if f in distlist:
+                               myhashkeys = myhashdict["DIST"][f].keys()
+                               myhashkeys.sort()
+                               for h in myhashkeys:
+                                       if h not in portage_const.MANIFEST1_HASH_FUNCTIONS:
+                                               continue
+                                       myline = " ".join([h, str(myhashdict["DIST"][f][h]), f, str(myhashdict["DIST"][f]["size"])])
+                                       mylines.append(myline)
+               return mylines
        
        def _addDigestsToManifest(self, digests, fd):
                """ Add entries for old style digest files to Manifest file """
@@ -142,8 +187,12 @@ class Manifest(object):
        def _write(self, fd):
                """ Actual Manifest file generator """
                mylines = []
-               for t in self.fhashdict.keys():
-                       for f in self.fhashdict[t].keys():
+               mytypes = self.fhashdict.keys()
+               mytypes.sort()
+               for t in mytypes:
+                       myfiles = self.fhashdict[t].keys()
+                       myfiles.sort()
+                       for f in myfiles:
                                # compat hack for v1 manifests
                                if t == "AUX":
                                        f2 = os.path.join("files", f)
@@ -151,13 +200,15 @@ class Manifest(object):
                                        f2 = f
                                myline = " ".join([t, f, str(self.fhashdict[t][f]["size"])])
                                myhashes = self.fhashdict[t][f]
-                               for h in myhashes.keys():
+                               myhashkeys = myhashes.keys()
+                               myhashkeys.sort()
+                               for h in myhashkeys:
                                        if h not in portage_const.MANIFEST2_HASH_FUNCTIONS:
                                                continue
                                        myline += " "+h+" "+str(myhashes[h])
                                mylines.append(myline)
                                if self.compat and t != "DIST":
-                                       for h in myhashes.keys():
+                                       for h in myhashkeys:
                                                if h not in portage_const.MANIFEST1_HASH_FUNCTIONS:
                                                        continue
                                                mylines.append((" ".join([h, str(myhashes[h]), f2, str(myhashes["size"])])))