import optparse
import os
import re
-import shutil
import signal
import stat
import sys
import time
from commands import getstatusoutput
-from fileinput import input
-from grp import getgrnam
from itertools import izip
from stat import S_ISDIR, ST_CTIME
portage.dep._dep_check_strict = True
import portage.exception
from portage import cvstree, normalize_path
+from portage import util
from portage.exception import ParseError
from portage.manifest import Manifest
from portage.process import find_binary, spawn
-from portage.util import initialize_logger
from portage.output import bold, create_color_func, darkgreen, \
green, nocolor, red, turquoise, yellow
from portage.output import ConsoleStyleFile, StyleWriter
-initialize_logger()
+util.initialize_logger()
# 14 is the length of DESCRIPTION=""
max_desc_len = 100
'fix' : 'Fix simple QA issues (stray digests, missing digests)',
'full' : 'Scan directory tree and print all issues (not a summary)',
'help' : 'Show this screen',
- 'last' : 'Remember report from last ru',
+ 'last' : 'Remember report from last run',
'lfull' : 'Remember report from last run (full listing)',
'manifest' : 'Generate a Manifest (fetches files if necessary)',
'scan' : 'Scan directory tree for QA issues'
qawarnings.append(x)
valid_restrict = frozenset(["binchecks", "bindist",
- "fetch", "installsources", "mirror",
+ "fetch", "installsources", "mirror",
"primaryuri", "strip", "test", "userpriv"])
# file.executable
no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
-def editor_is_executable(editor):
- """
- Given an EDITOR string, validate that it refers to
- an executable. This uses shlex.split() to split the
- first component and do a PATH lookup if necessary.
-
- @param editor: An EDITOR value from the environment.
- @type: string
- @rtype: bool
- @returns: True if an executable is found, False otherwise.
- """
- import shlex
- editor_split = shlex.split(editor)
- if not editor_split:
- return False
- filename = editor_split[0]
- if not os.path.isabs(filename):
- return find_binary(filename) is not None
- return os.access(filename, os.X_OK) and os.path.isfile(filename)
-
-def get_commit_message_with_editor(editor, message=None):
- """
- Execute editor with a temporary file as it's argument
- and return the file content afterwards.
-
- @param editor: An EDITOR value from the environment
- @type: string
- @param message: An iterable of lines to show in the editor.
- @type: iterable
- @rtype: string or None
- @returns: A string on success or None if an error occurs.
- """
- from tempfile import mkstemp
- fd, filename = mkstemp()
- try:
- os.write(fd, "\n# Please enter the commit message " + \
- "for your changes.\n# (Comment lines starting " + \
- "with '#' will not be included)\n")
- if message:
- os.write(fd, "#\n")
- for line in message:
- os.write(fd, "#" + line)
- os.close(fd)
- retval = os.system(editor + " '%s'" % filename)
- if not (os.WIFEXITED(retval) and os.WEXITSTATUS(retval) == os.EX_OK):
- return None
- try:
- mylines = open(filename).readlines()
- except OSError, e:
- if e.errno != errno.ENOENT:
- raise
- del e
- return None
- return "".join(line for line in mylines if not line.startswith("#"))
- finally:
- try:
- os.unlink(filename)
- except OSError:
- pass
-
-def get_commit_message_with_stdin():
- """
- Read a commit message from the user and return it.
-
- @rtype: string or None
- @returns: A string on success or None if an error occurs.
- """
- print "Please enter a commit message. Use Ctrl-d to finish or Ctrl-c to abort."
- commitmessage = []
- while True:
- commitmessage.append(sys.stdin.readline())
- if not commitmessage[-1]:
- break
- commitmessage = "".join(commitmessage)
- return commitmessage
-
-def format_qa_output(f, stats, fails, dofull, dofail):
- full = options.mode in ("full", "lfull")
- for x in qacats:
- if not stats[x]:
- continue
- f.add_literal_data(" " + x.ljust(30))
- if x in qawarnings:
- f.push_style("WARN")
- else:
- f.push_style("BAD")
- f.add_literal_data(str(stats[x]))
- f.pop_style()
- f.add_line_break()
- if not dofull:
- if not full and dofail and x in qawarnings:
- # warnings are considered noise when there are failures
- continue
- fails_list = fails[x]
- if not full and len(fails_list) > 12:
- fails_list = fails_list[:12]
- for y in fails_list:
- f.add_literal_data(" "+y)
- f.add_line_break()
def last(full=False):
"""Print the results of the last repoman run
console_writer = StyleWriter(file=style_file, maxcol=9999)
console_writer.style_listener = style_file.new_styles
f = formatter.AbstractFormatter(console_writer)
- format_qa_output(f, stats, fails, dofull, dofail)
+ utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
print
if dofull:
print bold("Note: type \"repoman lfull\" for a complete listing of repomans last run.")
can_force = True
-isCvs=False
+isVcs = False
+vcs = ''
if os.path.isdir("CVS"):
- isCvs = True
-
-isSvn=False
+ isVcs = True
+ vcs = "cvs"
if os.path.isdir(".svn"):
- isSvn=True
+ isVcs = True
+ vcs = "svn"
-if isCvs or isSvn and \
+if isVcs and vcs == "cvs" and \
"commit" == options.mode and \
"RMD160" not in portage.checksum.hashorigin_map:
from portage.util import grablines
repo_lines = grablines("./CVS/Repository")
- if isSvn or (repo_lines and \
- "gentoo-x86" == repo_lines[0].strip().split(os.path.sep)[0]):
+ if repo_lines and \
+ "gentoo-x86" == repo_lines[0].strip().split(os.path.sep)[0]:
msg = "Please install " \
"pycrypto or enable python's ssl USE flag in order " \
"to enable RMD160 hash support. See bug #198398 for " \
sys.exit(1)
del repo_lines
-if options.mode == 'commit' and not options.pretend and not isCvs and not isSvn:
- logging.info("Not in a CVS/SVN repository; enabling pretend mode.")
+if options.mode == 'commit' and not options.pretend and not isVcs:
+ logging.info("Not in a version controlled repository; enabling pretend mode.")
options.pretend = True
try:
# Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
# this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
if options.mode == 'commit' and repolevel not in [1,2,3]:
- print red("***")+" Commit attempts *must* be from within a cvs or svn co, category, or package directory."
+ print red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory."
print red("***")+" Attempting to commit from a packages files directory will be blocked for instance."
print red("***")+" This is intended behaviour, to ensure the manifest is recommited for a package."
print red("***")
#this can be problematic if xmllint changes their output
xmllint_capable=True
-if options.mode == 'commit':
- retval = ("","")
- if isCvs:
- print
- print "Performing a " + green("cvs -n up") + \
- " with a little magic grep to check for updates."
- retval = getstatusoutput("cvs -n up 2>&1 | " + \
- "egrep '^[^\?] .*' | " + \
- "egrep -v '^. .*/digest-[^/]+|^cvs server: .* -- ignored$'")
-
- if isSvn:
- print
- print "Performing a "+green("svn status -u") + \
- " with a little magic grep to check for updates."
- retval=getstatusoutput("svn status -u 2>&1 | " + \
- "egrep -v '^. +.*/digest-[^/]+' | " + \
- "head -n-1")
-
- mylines = retval[1].splitlines()
- myupdates = []
- for x in mylines:
- if not x:
- continue
- if x[0] not in "UPMARD": # Updates,Patches,Modified,Added,Removed/Replaced(svn),Deleted(svn)
- print red("!!! Please fix the following issues reported " + \
- "from vcs: ")+green("(U,P,M,A,R,D are ok)")
- print red("!!! Note: This is a pretend/no-modify pass...")
- print retval[1]
- print
- sys.exit(1)
- elif isCvs and x[0] in ["U","P"]:
- myupdates.append(x[2:])
- elif isSvn and x[8] == '*':
- myupdates.append(x[9:].lstrip(" 1234567890"))
-
- if myupdates:
- print green("Fetching trivial updates...")
- if options.pretend:
- print "(cvs/svn up "+" ".join(myupdates)+")"
- retval = os.EX_OK
- else:
- if isCvs:
- retval=os.system("cvs up "+" ".join(myupdates))
- elif isSvn:
- retval=os.system("svn update "+" ".join(myupdates))
- if retval != os.EX_OK:
- print "!!! cvs/svn exited with an error. Terminating."
- sys.exit(retval)
+if options.mode == 'commit' and isVcs:
+ utilties.detect_vcs_conflicts(options, vcs=vcs)
if options.mode == "manifest":
pass
stats["filedir.missing"] += 1
fails["filedir.missing"].append(checkdir)
- if isCvs or isSvn:
+ if isVcs:
try:
- if isCvs:
+ if vcs == "cvs":
myf=open(checkdir+"/CVS/Entries","r")
- if isSvn:
+ if vcs == "svn":
myf=os.popen("svn update > /dev/null; svn list")
myl=myf.readlines()
myf.close()
for l in myl:
- if isCvs:
+ if vcs == "cvs":
if l[0]!="/":
continue
splitl=l[1:].split("/")
continue
if splitl[0][-7:]==".ebuild":
eadded.append(splitl[0][:-7])
- if isSvn:
+ if vcs == "svn":
l = l.rstrip();
if l[-1:] == "/":
continue
if l[-7:] == ".ebuild":
eadded.append(l[:-7])
- if isSvn:
+ if vcs == "svn":
myf=os.popen("svn status")
myl=myf.readlines()
myf.close()
if l[-7:] == ".ebuild":
eadded.append(l[:-7])
except IOError:
- if options.mode == 'commit' and isCvs:
+ if options.mode == 'commit' and vcs == "cvs":
stats["CVS/Entries.IO_error"] += 1
fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
- if options.mode == 'commit' and isSvn:
+ if options.mode == 'commit' and vcs == "svn":
stats["svn.IO_error"] += 1
fails["svn.IO_error"].append(checkdir+"svn info")
continue
- if isCvs and has_filesdir:
+ if isVcs and has_filesdir:
try:
- if isCvs:
+ if vcs == "cvs":
myf=open(checkdir+"/files/CVS/Entries","r")
- if isSvn:
+ if vcs == "svn":
myf=os.popen("svn list "+os.path.normpath(checkdir+"/files"))
myl=myf.readlines()
myf.close()
for l in myl:
- if isCvs:
+ if vcs == "cvs":
if l[0]!="/":
continue
splitl=l[1:].split("/")
continue
if splitl[0][:7]=="digest-":
dadded.append(splitl[0][7:])
- if isSvn:
+ if vcs == "svn":
l = l.rstrip();
if l[-1:] == "/":
continue
if l[:7] == "digest-":
dadded.append(l[7:])
- if isSvn:
+ if vcs == "svn":
myf=os.popen("svn status "+os.path.normpath(checkdir+"/files"))
myl=myf.readlines()
myf.close()
if l[:7] == "digest-":
dadded.append(l[7:])
except IOError:
- if options.mode == 'commit' and isCvs:
+ if options.mode == 'commit' and vcs == "cvs":
stats["CVS/Entries.IO_error"] += 1
fails["CVS/Entries.IO_error"].append(checkdir+"/files/CVS/Entries")
- if options.mode == 'commit' and isSvn:
+ if options.mode == 'commit' and vcs == "svn":
stats["svn.IO_error"] += 1
fails["svn.IO_error"].append(checkdir+"/files svn info")
continue
if y[7:] not in ebuildlist:
#stray digest
if options.mode == "fix":
- if isCvs:
+ if vcs == "cvs":
vcsrm="cvs rm -f"
- if isSvn:
+ if vcs == "svn":
vcsrm="svn rm --force"
if options.pretend:
- print "(cd "+repodir+"/"+x+"/files; "+vcsrm+" "+y+")"
+ print "(cd "+repodir+"/"+x+"/files; "+vcsrm+" "+y+")"
else:
os.system("(cd "+repodir+"/"+x+"/files; "+vcsrm+" "+y+")")
else:
else:
raise oe
if S_ISDIR(mystat.st_mode):
+# !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
if y == "CVS" or y == ".svn":
continue
for z in os.listdir(checkdir+"/files/"+y):
if stat.S_IMODE(os.stat(full_path).st_mode) & 0111:
stats["file.executable"] += 1
fails["file.executable"].append(x+"/"+y+".ebuild")
- if (isCvs or isSvn) and y not in eadded:
- #ebuild not added to cvs
+ if isVcs and y not in eadded:
+ #ebuild not added to vcs
stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
if y in dadded:
f = formatter.AbstractFormatter(console_writer)
-format_qa_output(f, stats, fails, dofull, dofail)
+utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
style_file.flush()
del console_writer, f, style_file
print "!!! Exiting on ebuild digest (shell) error code:",retval
sys.exit(retval)
- if isCvs:
+ if isVcs and vcs == "cvs":
try:
myvcstree=portage.cvstree.getentries("./",recursive=1)
myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./")
except:
err("Error retrieving CVS tree; exiting.")
- if isSvn:
+ if isVcs and vcs == "svn":
try:
svnstatus=os.popen("svn status").readlines()
myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") ]
if myautoadd:
print ">>> Auto-Adding missing digests..."
if options.pretend:
- if isCvs:
+ if vcs == "cvs":
print "(cvs add "+" ".join(myautoadd)+")"
- if isSvn:
+ if vcs == "svn":
print "(svn add "+" ".join(myautoadd)+")"
retval=0
else:
- if isCvs:
+ if vcs == "cvs":
retval=os.system("cvs add "+" ".join(myautoadd))
- if isSvn:
+ if vcs == "svn":
retval=os.system("svn add "+" ".join(myautoadd))
if retval:
print "!!! Exiting on vcs (shell) error code:",retval
print
sys.exit(1)
- if isCvs:
+ if vcs == "cvs":
mycvstree=portage.cvstree.getentries("./",recursive=1)
mychanged=portage.cvstree.findchanged(mycvstree,recursive=1,basedir="./")
mynew=portage.cvstree.findnew(mycvstree,recursive=1,basedir="./")
recursive=1, basedir="./"))
- if isSvn:
+ if vcs == "svn":
svnstatus = os.popen("svn status").readlines()
mychanged = [ elem.rstrip()[7:] for elem in svnstatus if elem.startswith("M") ]
for manifest in [ file for file in mychanged if '/Manifest' in file ]:
# no idea how to detect binaries in SVN
bin_blobs = []
- if isCvs or isSvn:
+ if isVcs:
if not (mychanged or mynew or myremoved):
print green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\""
print
# However, in SVN mode, the manifest won't change, hence, if we
# don't sign, we can commit it right away, avoiding an extra commit,
# of course this assumes SVN doesn't do Header: expansion.
- if not isSvn or "sign" in repoman_settings.features:
+ if vcs != "svn" or "sign" in repoman_settings.features:
mymanifests = [f for f in mychanged if "Manifest" == os.path.basename(f)]
mychanged = [f for f in mychanged if "Manifest" != os.path.basename(f)]
else:
if not commitmessage or not commitmessage.strip():
try:
editor = os.environ.get("EDITOR")
- if editor and editor_is_executable(editor):
- commitmessage = get_commit_message_with_editor(
+ if editor and utilities.editor_is_executable(editor):
+ commitmessage = utilties.get_commit_message_with_editor(
editor, message=qa_output)
else:
- commitmessage = get_commit_message_with_stdin()
+ commitmessage = utilties.get_commit_message_with_stdin()
except KeyboardInterrupt:
exithandler()
if not commitmessage or not commitmessage.strip():
sys.stderr.flush()
portage_version = "Unknown"
unameout=getstatusoutput("uname -srp")[1]
- commitmessage+="\n(Portage version: "+str(portage_version)+"/"
- if isCvs:
- commitmessage+="CVS"
- elif isSvn:
- commitmessage+="SVN"
- commitmessage+="/"+unameout
+ commitmessage+="\n(Portage version: "+str(portage_version)+"/"+vcs+"/"+unameout
if options.force:
commitmessage += ", RepoMan options: --force"
commitmessage += ")"
if digest_files:
digest_files.sort()
if options.pretend:
- if isCvs:
+ if vcs == "cvs":
print "(rm %s)" % " ".join(digest_files)
print "(cvs remove %s)" % " ".join(digest_files)
- elif isSvn:
+ elif vcs == "svn":
print "(svn remove %s)" % " ".join(digest_files)
else:
- if isCvs:
+ if vcs == "cvs":
for x in digest_files:
os.unlink(os.path.join(startdir, x))
retval = spawn(["cvs", "remove"] + digest_files,
env=os.environ)
- elif isSvn:
+ elif vcs == "svn":
retval = spawn(["svn", "remove"] + digest_files,
env=os.environ)
if retval != os.EX_OK:
retval = None
if options.pretend:
- if isCvs:
+ if vcs == "cvs":
print "(cvs -q commit -F %s %s)" % \
(commitmessagefile, " ".join(myfiles))
- if isSvn:
+ if vcs == "svn":
print "(svn commit -F %s %s)" % \
(commitmessagefile, " ".join(myfiles))
else:
- if isCvs:
+ if vcs == "cvs":
retval = spawn(["cvs", "-q", "commit",
"-F", commitmessagefile] + myfiles,
env=os.environ)
- if isSvn:
+ if vcs == "svn":
retval = spawn(["svn", "commit",
"-F", commitmessagefile] + myfiles,
env=os.environ)
# Force an unsigned commit when more than one Manifest needs to be signed.
if repolevel < 3 and "sign" in repoman_settings.features:
if options.pretend:
- print "(cvs -q commit -F commitmessagefile)"
+ if vcs == "cvs":
+ print "(cvs -q commit -F commitmessagefile)"
+ if vcs == "svn":
+ print "(svn -q commit -F commitmessagefile)"
else:
fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
mymsg = os.fdopen(fd, "w")
mymsg.write(commitmessage)
mymsg.write("\n (Unsigned Manifest commit)")
mymsg.close()
- retval=os.system("cvs -q commit -F "+commitmessagefile)
+ if vcs == "cvs":
+ retval=os.system("cvs -q commit -F "+commitmessagefile)
+ if vcs == "svn":
+ retval=os.system("svn -q commit -F "+commitmessagefile)
try:
os.unlink(commitmessagefile)
except OSError:
if manifest_commit_required or signed:
if options.pretend:
- if isCvs:
+ if vcs == "cvs":
print "(cvs -q commit -F commitmessagefile)"
- if isSvn:
+ if vcs == "svn":
print "(svn -q commit -F commitmessagefile)"
else:
fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
else:
mymsg.write("\n (Unsigned Manifest commit)")
mymsg.close()
- if isCvs:
+ if vcs == "cvs":
retval=os.system("cvs -q commit -F "+commitmessagefile)
- if isSvn:
+ if vcs == "svn":
retval=os.system("svn -q commit -F "+commitmessagefile)
try:
os.unlink(commitmessagefile)
sys.exit(retval)
print
- if isCvs or isSvn:
+ if isVcs:
print "Commit complete."
else:
print "repoman was too scared by not seeing any familiar version control file that he forgot to commit anything"
# Distributed under the terms of the GNU General Public License v2
# $Id$
+"""This module contains utility functions to help repoman find ebuilds to
+scan"""
+
+import commands
+import itertools
import logging
import os
+import sys
-from portage import util
+from portage import output
from portage import exception
-
+from portage import util
normalize_path = util.normalize_path
util.initialize_logger()
+
+def detect_vcs_conflicts(vcs, options):
+ """Determine if the checkout has problems like cvs conflicts.
+
+ If you want more vcs support here just keep adding if blocks...
+ This could be better.
+
+ TODO(antarus): Also this should probably not call sys.exit() as
+ repoman is run on >1 packages and one failure should not cause
+ subsequent packages to fail.
+
+ Args:
+ vcs - A string identifying the version control system in use
+ Returns:
+ None (calls sys.exit on fatal problems)
+ """
+ retval = ("","")
+ if vcs == 'cvs':
+ logging.info("Performing a " + output.green("cvs -n up") + \
+ " with a little magic grep to check for updates.")
+ retval = commands.getstatusoutput("cvs -n up 2>&1 | " + \
+ "egrep '^[^\?] .*' | " + \
+ "egrep -v '^. .*/digest-[^/]+|^cvs server: .* -- ignored$'")
+ if vcs == 'svn':
+ logging.info("Performing a " + output.green("svn status -u") + \
+ " with a little magic grep to check for updates.")
+ retval = getstatusoutput("svn status -u 2>&1 | " + \
+ "egrep -v '^. +.*/digest-[^/]+' | " + \
+ "head -n-1")
+
+ if vcs in ['cvs', 'svn']:
+ mylines = retval[1].splitlines()
+ myupdates = []
+ for line in mylines:
+ if not line:
+ continue
+ if line[0] not in "UPMARD": # Updates,Patches,Modified,Added,Removed/Replaced(svn),Deleted(svn)
+ logging.error(red("!!! Please fix the following issues reported " + \
+ "from cvs: ")+green("(U,P,M,A,R,D are ok)"))
+ logging.error(red("!!! Note: This is a pretend/no-modify pass..."))
+ logging.error(retval[1])
+ sys.exit(1)
+ elif vcs == 'cvs' and line[0] in "UP":
+ myupdates.append(line[2:])
+ elif vcs == 'svn' and line[8] == '*':
+ myupdates.append(line[9:].lstrip(" 1234567890"))
+
+ if myupdates:
+ logging.info(green("Fetching trivial updates..."))
+ if options.pretend:
+ logging.info("(" + vcs + " update " + " ".join(myupdates) + ")")
+ retval = os.EX_OK
+ else:
+ retval = os.system(vcs + " update " + " ".join(myupdates))
+ if retval != os.EX_OK:
+ logging.fatal("!!! " + cvs + " exited with an error. Terminating.")
+ sys.exit(retval)
+
+
def have_profile_dir(path, maxdepth=3):
""" Try to figure out if 'path' has a /profiles dir in it by checking for a package.mask file
"""
path = normalize_path(path + "/..")
maxdepth -= 1
+
def parse_use_local_desc(mylines, usedict=None):
"""
Records are of the form PACKAGE:FLAG - DESC
usedict[pkg].add(flag)
return usedict
+
def FindPackagesToScan(settings, startdir, reposplit):
""" Try to find packages that need to be scanned
A list of directories to scan
"""
+
def AddPackagesInDir(path):
""" Given a list of dirs, add any packages in it """
ret = []
scanlist.append(os.path.join(catdir, pkgdir))
return scanlist
+
+def format_qa_output(formatter, stats, fails, dofull, dofail, options, qawarnings):
+ """Helper function that formats output properly
+
+ Args:
+ formatter - a subclass of Formatter
+ stats - a dict of qa status items
+ fails - a dict of qa status failures
+ dofull - boolean to print full results or a summary
+ dofail - boolean to decide if failure was hard or soft
+
+ Returns:
+ None (modifies formatter)
+ """
+ full = options.mode in ("full", "lfull")
+ # we only want key value pairs where value > 0
+ for category, number in \
+ itertools.ifilter(lambda myitem: myitem[1] > 0, stats.iteritems()):
+ formatter.add_literal_data(" " + category.ljust(30))
+ if category in qawarnings:
+ formatter.push_style("WARN")
+ else:
+ formatter.push_style("BAD")
+ formatter.add_literal_data(str(number))
+ formatter.pop_style()
+ formatter.add_line_break()
+ if not dofull:
+ if not full and dofail and category in qawarnings:
+ # warnings are considered noise when there are failures
+ continue
+ fails_list = fails[category]
+ if not full and len(fails_list) > 12:
+ fails_list = fails_list[:12]
+ for failure in fails_list:
+ formatter.add_literal_data(" " + failure)
+ formatter.add_line_break()
+
+
+def editor_is_executable(editor):
+ """
+ Given an EDITOR string, validate that it refers to
+ an executable. This uses shlex.split() to split the
+ first component and do a PATH lookup if necessary.
+
+ @param editor: An EDITOR value from the environment.
+ @type: string
+ @rtype: bool
+ @returns: True if an executable is found, False otherwise.
+ """
+ import shlex
+ editor_split = shlex.split(editor)
+ if not editor_split:
+ return False
+ filename = editor_split[0]
+ if not os.path.isabs(filename):
+ return find_binary(filename) is not None
+ return os.access(filename, os.X_OK) and os.path.isfile(filename)
+
+
+def get_commit_message_with_editor(editor, message=None):
+ """
+ Execute editor with a temporary file as it's argument
+ and return the file content afterwards.
+
+ @param editor: An EDITOR value from the environment
+ @type: string
+ @param message: An iterable of lines to show in the editor.
+ @type: iterable
+ @rtype: string or None
+ @returns: A string on success or None if an error occurs.
+ """
+ from tempfile import mkstemp
+ fd, filename = mkstemp()
+ try:
+ os.write(fd, "\n# Please enter the commit message " + \
+ "for your changes.\n# (Comment lines starting " + \
+ "with '#' will not be included)\n")
+ if message:
+ os.write(fd, "#\n")
+ for line in message:
+ os.write(fd, "#" + line)
+ os.close(fd)
+ retval = os.system(editor + " '%s'" % filename)
+ if not (os.WIFEXITED(retval) and os.WEXITSTATUS(retval) == os.EX_OK):
+ return None
+ try:
+ mylines = open(filename).readlines()
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ del e
+ return None
+ return "".join(line for line in mylines if not line.startswith("#"))
+ finally:
+ try:
+ os.unlink(filename)
+ except OSError:
+ pass
+
+
+def get_commit_message_with_stdin():
+ """
+ Read a commit message from the user and return it.
+
+ @rtype: string or None
+ @returns: A string on success or None if an error occurs.
+ """
+ print "Please enter a commit message. Use Ctrl-d to finish or Ctrl-c to abort."
+ commitmessage = []
+ while True:
+ commitmessage.append(sys.stdin.readline())
+ if not commitmessage[-1]:
+ break
+ commitmessage = "".join(commitmessage)
+ return commitmessage
+
+
def FindPortdir(settings):
""" Try to figure out what repo we are in and whether we are in a regular
tree or an overlay.