import hashlib
import errno
import time
+import itertools
import wizard
from wizard import deploy, util, shell, sset, command
errors = {}
i = 0
# [] needed to workaround subtle behavior of frozenset("")
- for d in deploy.parse_install_lines([app], options.versions_path):
+ deploys = deploy.parse_install_lines([app], options.versions_path)
+ requested_deploys = itertools.islice(deploys, options.limit)
+ for i, d in enumerate(requested_deploys, 1)
# check if we want to punt due to --limit
- i += 1
- if options.limit and i > options.limit:
- break
if d.location in seen:
continue
if is_root and not security_check_homedir(d):
has_git = os.path.isdir(".git")
has_scripts = os.path.isdir(".scripts")
-<<<<<<< HEAD
- # deal with old-style migration
- if os.path.isfile(".scripts/old-version") and not os.path.isfile(".scripts-version"):
- os.rename(".scripts/old-version", ".scripts-version")
-
- if not has_git and has_scripts:
- if not options.force:
- raise CorruptedAutoinstallError(dir)
- elif has_git and not has_scripts:
- # can't force this
- raise AlreadyVersionedError(dir)
- elif has_git and has_scripts:
- if not options.force:
- raise AlreadyMigratedError(dir)
-
- if options.force:
- def rm_with_backup(file):
- prefix = "%s.bak" % file
- name = None
- for i in itertools.count():
- name = "%s.%d" % (prefix, i)
- if not os.path.exists(name):
- break
- logging.warning("Force removing %s directory (backup at %s)" % (file, name))
- os.rename(file, name)
- if has_git:
- if not options.dry_run:
- rm_with_backup(".git")
- if has_scripts:
- if not options.dry_run:
- rm_with_backup(".scripts")
-
-def make_deployment():
- try:
- return deploy.Deployment(".")
- except IOError as e:
- if e.errno == errno.ENOENT:
- raise NotAutoinstallError(dir)
- else: raise e
-
-def check_if_tag_exists(sh, repo, tag, version):
- # check if the version we're trying to convert exists. We assume
- # a convention here, namely, app-1.2.3-scripts is what we want. If
- # you broke the convention... shame on you.
- try:
- sh.call("git", "--git-dir", repo, "rev-parse", tag)
- except shell.CallError:
- raise NoTagError(version)
-=======
if has_git:
logging.warning("Force removing .git directory")
if not options.dry_run: backup = safe_unlink(".git")
break
os.rename(file, name)
return name
->>>>>>> 255245146b500845fa13a75bfbeb6cece074417c
def make_repository(sh, options, repo, tag):
sh.call("git", "init") # create repository
import logging
import traceback
import os.path
+import itertools
+import random
from wizard import command, deploy, shell, util
def main(argv, baton):
options, show = parse_args(argv, baton)
sh = shell.Shell()
- for d in deploy.parse_install_lines(show, options.versions_path):
- try:
- d.verify()
- d.verifyTag(options.srv_path)
- d.verifyGit(options.srv_path)
- d.verifyConfigured()
- print d.location
- with util.ChangeDirectory(d.location):
- print sh.safeCall('git', 'diff', '--stat', d.app_version.scripts_tag, 'HEAD', strip=True)
- except deploy.Error as e:
- continue
+ deploys = deploy.parse_install_lines(show, options.versions_path)
+ stats = {}
+ iffy = 0
+ clean = 0
+ total = 0
+ deploys = itertools.islice(deploys, options.limit)
+ if options.sample:
+ deploys = random.sample(list(deploys), options.sample)
+ try:
+ for d in deploys:
+ logging.info("Processing " + d.location)
+ try:
+ d.verify()
+ d.verifyTag(options.srv_path)
+ d.verifyGit(options.srv_path)
+ d.verifyConfigured()
+ with util.ChangeDirectory(d.location):
+ results = []
+ out = sh.safeCall('git', 'diff', '--numstat', d.app_version.scripts_tag, strip=True)
+ total += 1
+ for line in out.split("\n"):
+ added, deleted, filename = line.split(None, 3)
+ if filename.endswith("php.ini"): continue
+ if added == '-': continue
+ if deleted == '-': continue
+ added = int(added)
+ deleted = int(deleted)
+ # hook
+ if filename == "LocalSettings.php":
+ if added == deleted == 10:
+ continue
+ elif added == deleted == 9:
+ continue
+ elif filename == "AdminSettings.php":
+ if added == 0 and deleted == 20:
+ continue
+ elif filename == "config/index.php" or filename == "config/index.php5":
+ if added == 0:
+ continue
+ if not added and not deleted:
+ continue
+ results.append((added,deleted,filename))
+ if len(results) > options.filter:
+ print "- - " + d.location
+ iffy += 1
+ continue
+ if not results:
+ clean += 1
+ for added,deleted,filename in results:
+ stats.setdefault(filename, 0)
+ stats[filename] += 1
+ # hook
+ if filename == "LocalSettings.php" and not options.verbose:
+ continue
+ print "%-7d %-7d %s/%s" % (added,deleted,d.location,filename)
+ except (deploy.NotConfiguredError, deploy.NotMigratedError):
+ # XXX: These should error, but for now don't
+ pass
+ except (deploy.Error, shell.CallError):
+ logging.error("%s in %s" % (traceback.format_exc(), d.location))
+ except KeyboardInterrupt:
+ raise
+ except:
+ logging.critical("%s in %s" % (traceback.format_exc(), d.location))
+ except KeyboardInterrupt:
+ print
+ print "Caught signal..."
+ pass
+ print '-' * 50
+ for filename in sorted(stats.keys()):
+ count = stats[filename]
+ if not count: continue
+ print "%-7d %s" % (count, filename)
+ print '-' * 50
+ print "%d out of %d (%.1f%%) had large diffstats" % (iffy, total, float(iffy)/total*100)
+ print "%d out of %d (%.1f%%) had clean diffstats" % (clean, total, float(clean)/total*100)
def parse_args(argv, baton):
usage = """usage: %prog research APP
Tells you how spectacularly an upgrade here will explode."""
parser = command.WizardOptionParser(usage)
+ parser.add_option("--limit", dest="limit", type="int",
+ default=None, help="Limit the number of autoinstalls to look at.")
+ parser.add_option("--sample", dest="sample", type="int", metavar="N",
+ default=None, help="Instead of researching all installs, research a random sample of N size.")
+ parser.add_option("--filter", dest="filter", type="int", metavar="N",
+ default=4, help="How many files are permitted in a diffstat before treating the install as having a 'large diffstat'")
baton.push(parser, "srv_path")
baton.push(parser, "versions_path")
options, args = parser.parse_all(argv)
"""
repo = self.application.repository(srv_path)
try:
- shell.Shell().eval("git", "--git-dir", repo, "rev-parse", self.app_version.scripts_tag)
+ shell.Shell().eval("git", "--git-dir", repo, "rev-parse", self.app_version.scripts_tag, '--')
except shell.CallError:
raise NoTagError(self.app_version.scripts_tag)
def repo_rev_parse(tag):
return sh.eval("git", "--git-dir", repo, "rev-parse", tag)
def self_rev_parse(tag):
- return sh.safeCall("git", "rev-parse", tag, strip=True)
+ try:
+ return sh.safeCall("git", "rev-parse", tag, strip=True)
+ except shell.CallError:
+ raise NoLocalTagError(tag)
def compare_tags(tag):
return repo_rev_parse(tag) == self_rev_parse(tag)
if not compare_tags(self.app_version.pristine_tag):
- raise InconsistentPristineTagError()
+ raise InconsistentPristineTagError(self.app_version.pristine_tag)
if not compare_tags(self.app_version.scripts_tag):
- raise InconsistentScriptsTagError()
+ raise InconsistentScriptsTagError(self.app_version.scripts_tag)
parent = repo_rev_parse(self.app_version.scripts_tag)
merge_base = sh.safeCall("git", "merge-base", parent, "HEAD", strip=True)
if merge_base != parent:
- raise HeadNotDescendantError()
+ raise HeadNotDescendantError(self.app_version.scripts_tag)
def verifyConfigured(self):
"""
ERROR: Could not find tag %s in repository.""" % self.tag
+class NoLocalTagError(Error):
+ def __init__(self, tag):
+ self.tag = tag
+ def __str__(self):
+ return """
+
+ERROR: Could not find tag %s in local repository.""" % self.tag
+
+class InconsistentPristineTagError(Error):
+ def __init__(self, tag):
+ self.tag = tag
+ def __str__(self):
+ return """
+
+ERROR: Local pristine tag %s did not match repository's. This
+probably means an upstream rebase occured.""" % self.tag
+
+class InconsistentScriptsTagError(Error):
+ def __init__(self, tag):
+ self.tag = tag
+ def __str__(self):
+ return """
+
+ERROR: Local scripts tag %s did not match repository's. This
+probably means an upstream rebase occurred.""" % self.tag
+
+class HeadNotDescendantError(Error):
+ def __init__(self, tag):
+ self.tag = tag
+ def __str__(self):
+ return """
+
+ERROR: HEAD is not a descendant of %s. This probably
+means that an upstream rebase occurred, and new tags were
+pulled, but local user commits were never rebased.""" % self.tag
+
_application_list = [
"mediawiki", "wordpress", "joomla", "e107", "gallery2",
"phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",