repoman: fix popen unicode handling, bug #310789
authorZac Medico <zmedico@gentoo.org>
Mon, 10 Sep 2012 00:53:22 +0000 (17:53 -0700)
committerZac Medico <zmedico@gentoo.org>
Mon, 10 Sep 2012 00:53:22 +0000 (17:53 -0700)
bin/repoman

index d7c69b32faa41d4e295a2b9cd777261f03b26ef7..1f4daede9bdc7d66a9df47f4ae1c8ac1f1f67a38 100755 (executable)
@@ -9,6 +9,7 @@
 from __future__ import print_function
 
 import calendar
+import codecs
 import copy
 import errno
 import formatter
@@ -741,6 +742,36 @@ else:
 def caterror(mycat):
        err(mycat+" is not an official category.  Skipping QA checks in this directory.\nPlease ensure that you add "+catdir+" to "+repodir+"/profiles/categories\nif it is a new category.")
 
+class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
+       """
+       Implements an interface similar to os.popen(), but with customized
+       unicode handling (see bug #310789) and without the shell.
+       """
+
+       __slots__ = ('_proc', '_stdout')
+
+       def __init__(self, cmd):
+               args = portage.util.shlex_split(cmd)
+               encoding = _encodings['fs']
+               if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
+                       # Python 3.1 does not support bytes in Popen args.
+                       args = [_unicode_encode(x,
+                               encoding=encoding, errors='strict') for x in args]
+               proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+               object.__setattr__(self, '_proc', proc)
+               object.__setattr__(self, '_stdout',
+                       codecs.getreader(encoding)(proc.stdout, 'strict'))
+
+       def _get_target(self):
+               return object.__getattribute__(self, '_stdout')
+
+       __enter__ = _get_target
+
+       def __exit__(self, exc_type, exc_value, traceback):
+               proc = object.__getattribute__(self, '_proc')
+               proc.wait()
+               proc.stdout.close()
+
 class ProfileDesc(object):
        __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
        def __init__(self, arch, status, sub_path, tree_path):
@@ -1150,7 +1181,7 @@ if vcs == "cvs":
                myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
 
 elif vcs == "svn":
-       with os.popen("svn status") as f:
+       with repoman_popen("svn status") as f:
                svnstatus = f.readlines()
        mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
        mynew     = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
@@ -1158,23 +1189,23 @@ elif vcs == "svn":
                myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
 
 elif vcs == "git":
-       with os.popen("git diff-index --name-only "
+       with repoman_popen("git diff-index --name-only "
                "--relative --diff-filter=M HEAD") as f:
                mychanged = f.readlines()
        mychanged = ["./" + elem[:-1] for elem in mychanged]
 
-       with os.popen("git diff-index --name-only "
+       with repoman_popen("git diff-index --name-only "
                "--relative --diff-filter=A HEAD") as f:
                mynew = f.readlines()
        mynew = ["./" + elem[:-1] for elem in mynew]
        if options.if_modified == "y":
-               with os.popen("git diff-index --name-only "
+               with repoman_popen("git diff-index --name-only "
                        "--relative --diff-filter=D HEAD") as f:
                        myremoved = f.readlines()
                myremoved = ["./" + elem[:-1] for elem in myremoved]
 
 elif vcs == "bzr":
-       with os.popen("bzr status -S .") as f:
+       with repoman_popen("bzr status -S .") as f:
                bzrstatus = f.readlines()
        mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
        mynew     = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "NK" or elem[0:1] == "R" ) ]
@@ -1182,13 +1213,13 @@ elif vcs == "bzr":
                myremoved = [ "./" + elem.split()[-3:-2][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "K" or elem[0:1] == "R" ) ]
 
 elif vcs == "hg":
-       with os.popen("hg status --no-status --modified .") as f:
+       with repoman_popen("hg status --no-status --modified .") as f:
                mychanged = f.readlines()
        mychanged = ["./" + elem.rstrip() for elem in mychanged]
-       mynew = os.popen("hg status --no-status --added .").readlines()
+       mynew = repoman_popen("hg status --no-status --added .").readlines()
        mynew = ["./" + elem.rstrip() for elem in mynew]
        if options.if_modified == "y":
-               with os.popen("hg status --no-status --removed .") as f:
+               with repoman_popen("hg status --no-status --removed .") as f:
                        myremoved = f.readlines()
                myremoved = ["./" + elem.rstrip() for elem in myremoved]
 
@@ -1426,10 +1457,10 @@ for x in effective_scanlist:
 
        if vcs in ("git", "hg") and check_ebuild_notadded:
                if vcs == "git":
-                       myf = os.popen("git ls-files --others %s" % \
+                       myf = repoman_popen("git ls-files --others %s" % \
                                (portage._shell_quote(checkdir_relative),))
                if vcs == "hg":
-                       myf = os.popen("hg status --no-status --unknown %s" % \
+                       myf = repoman_popen("hg status --no-status --unknown %s" % \
                                (portage._shell_quote(checkdir_relative),))
                for l in myf:
                        if l[:-1][-7:] == ".ebuild":
@@ -1443,9 +1474,11 @@ for x in effective_scanlist:
                        if vcs == "cvs":
                                myf=open(checkdir+"/CVS/Entries","r")
                        if vcs == "svn":
-                               myf = os.popen("svn status --depth=files --verbose " + checkdir)
+                               myf = repoman_popen("svn status --depth=files --verbose " +
+                                       portage._shell_quote(checkdir))
                        if vcs == "bzr":
-                               myf = os.popen("bzr ls -v --kind=file " + checkdir)
+                               myf = repoman_popen("bzr ls -v --kind=file " +
+                                       portage._shell_quote(checkdir))
                        myl = myf.readlines()
                        myf.close()
                        for l in myl:
@@ -1473,7 +1506,8 @@ for x in effective_scanlist:
                                        if l[-7:] == ".ebuild":
                                                eadded.append(os.path.basename(l[:-7]))
                        if vcs == "svn":
-                               myf = os.popen("svn status " + checkdir)
+                               myf = repoman_popen("svn status " +
+                                       portage._shell_quote(checkdir))
                                myl=myf.readlines()
                                myf.close()
                                for l in myl:
@@ -2318,7 +2352,7 @@ else:
                        err("Error retrieving CVS tree; exiting.")
        if vcs == "svn":
                try:
-                       with os.popen("svn status --no-ignore") as f:
+                       with repoman_popen("svn status --no-ignore") as f:
                                svnstatus = f.readlines()
                        myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ]
                except SystemExit as e:
@@ -2327,12 +2361,12 @@ else:
                        err("Error retrieving SVN info; exiting.")
        if vcs == "git":
                # get list of files not under version control or missing
-               myf = os.popen("git ls-files --others")
+               myf = repoman_popen("git ls-files --others")
                myunadded = [ "./" + elem[:-1] for elem in myf ]
                myf.close()
        if vcs == "bzr":
                try:
-                       with os.popen("bzr status -S .") as f:
+                       with repoman_popen("bzr status -S .") as f:
                                bzrstatus = f.readlines()
                        myunadded = [ "./"+elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D" ]
                except SystemExit as e:
@@ -2340,14 +2374,14 @@ else:
                except:
                        err("Error retrieving bzr info; exiting.")
        if vcs == "hg":
-               with os.popen("hg status --no-status --unknown .") as f:
+               with repoman_popen("hg status --no-status --unknown .") as f:
                        myunadded = f.readlines()
                myunadded = ["./" + elem.rstrip() for elem in myunadded]
                
                # Mercurial doesn't handle manually deleted files as removed from
                # the repository, so the user need to remove them before commit,
                # using "hg remove [FILES]"
-               with os.popen("hg status --no-status --deleted .") as f:
+               with repoman_popen("hg status --no-status --deleted .") as f:
                        mydeleted = f.readlines()
                mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
 
@@ -2393,36 +2427,36 @@ else:
 
 
        if vcs == "svn":
-               with os.popen("svn status") as f:
+               with repoman_popen("svn status") as f:
                        svnstatus = f.readlines()
                mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
                mynew     = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
                myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
 
                # Subversion expands keywords specified in svn:keywords properties.
-               with os.popen("svn propget -R svn:keywords") as f:
+               with repoman_popen("svn propget -R svn:keywords") as f:
                        props = f.readlines()
                expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
                        for prop in props if " - " in prop)
 
        elif vcs == "git":
-               with os.popen("git diff-index --name-only "
+               with repoman_popen("git diff-index --name-only "
                        "--relative --diff-filter=M HEAD") as f:
                        mychanged = f.readlines()
                mychanged = ["./" + elem[:-1] for elem in mychanged]
 
-               with os.popen("git diff-index --name-only "
+               with repoman_popen("git diff-index --name-only "
                        "--relative --diff-filter=A HEAD") as f:
                        mynew = f.readlines()
                mynew = ["./" + elem[:-1] for elem in mynew]
 
-               with os.popen("git diff-index --name-only "
+               with repoman_popen("git diff-index --name-only "
                        "--relative --diff-filter=D HEAD") as f:
                        myremoved = f.readlines()
                myremoved = ["./" + elem[:-1] for elem in myremoved]
 
        if vcs == "bzr":
-               with os.popen("bzr status -S .") as f:
+               with repoman_popen("bzr status -S .") as f:
                        bzrstatus = f.readlines()
                mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
                mynew     = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] in "NK" or elem[0:1] == "R" ) ]
@@ -2431,15 +2465,15 @@ else:
                # Bazaar expands nothing.
 
        if vcs == "hg":
-               with os.popen("hg status --no-status --modified .") as f:
+               with repoman_popen("hg status --no-status --modified .") as f:
                        mychanged = f.readlines()
                mychanged = ["./" + elem.rstrip() for elem in mychanged]
 
-               with os.popen("hg status --no-status --added .") as f:
+               with repoman_popen("hg status --no-status --added .") as f:
                        mynew = f.readlines()
                mynew = ["./" + elem.rstrip() for elem in mynew]
 
-               with os.popen("hg status --no-status --removed .") as f:
+               with repoman_popen("hg status --no-status --removed .") as f:
                        myremoved = f.readlines()
                myremoved = ["./" + elem.rstrip() for elem in myremoved]