+ pass
+ # Working copy is not anchored anywhere useful for git describe,
+ # so we need to give it a hint.
+ self.wc.setAppVersion(self.prod.app_version)
+
+ def preflight(self):
+ """
+ Make sure that a number of pre-upgrade invariants are met before
+ attempting anything.
+ """
+ options = self.options
+ for i in range(0,2):
+ self.prod = deploy.ProductionCopy(".")
+ self.prod.verify()
+ self.repo = self.prod.application.repository(options.srv_path)
+ # XXX: put this in Application
+ self.version = shell.eval("git", "--git-dir="+self.repo, "describe", "--tags", "master")
+ self.preflightBlacklist()
+ self.prod.verify()
+ self.prod.verifyDatabase()
+ self.prod.verifyTag(options.srv_path)
+ self.prod.verifyGit(options.srv_path)
+ if not options.skip_verification:
+ self.prod.verifyConfigured()
+ try:
+ shell.call("git", "fetch", "--tags") # XXX: hack since some installs have stale tags
+ except shell.CallError as e:
+ if "Disk quota exceeded" in e.stderr:
+ raise QuotaTooLow
+ raise
+ try:
+ self.prod.verifyVersion()
+ except deploy.VersionMismatchError as e:
+ # XXX: kind of hacky, mainly it does change the Git working copy
+ # state (although /very/ non-destructively)
+ try:
+ shell.call("git", "merge", "--strategy=ours", self.prod.application.makeVersion(str(e.real_version)).wizard_tag)
+ except shell.CallError as e2:
+ if "does not point to a commit" in e2.stderr:
+ raise UnknownVersionError(e.real_version)
+ else:
+ raise
+ continue
+ break
+ else:
+ raise VersionRematchFailed
+ if not options.skip_verification:
+ self.prod.verifyWeb()
+ self.preflightAlreadyUpgraded()
+ self.preflightQuota()
+ def preflightBlacklist(self):
+ # XXX: should use deploy info
+ if os.path.exists(".wizard/blacklisted"):
+ reason = open(".wizard/blacklisted").read()
+ # ignore blank blacklisted files
+ if reason:
+ print reason
+ raise BlacklistedError(reason)
+ else:
+ logging.warning("Application was blacklisted, but no reason was found");
+ def preflightAlreadyUpgraded(self):
+ if self.version == self.prod.app_version.wizard_tag and not self.options.force:
+ # don't log this error; we need to have the traceback line
+ # so that the parsing code can catch it
+ # XXX: maybe we should build this in as a flag to add
+ # to exceptions w/ our exception handler
+ sys.stderr.write("Traceback:\n (n/a)\nAlreadyUpgraded\n")
+ sys.exit(2)
+ def preflightQuota(self):
+ r = user.quota()
+ if r is not None:
+ usage, limit = r
+ if limit is not None and (limit - usage) < buffer:
+ logging.info("preflightQuota: limit = %d, usage = %d, buffer = %d", limit, usage, buffer)
+ raise QuotaTooLow
+
+ def merge(self):
+ if not self.options.dry_run:
+ self.mergePreCommit()
+ self.mergeClone()
+ logging.debug("Temporary WC dir is %s", self.temp_wc_dir)
+ with util.ChangeDirectory(self.temp_wc_dir):
+ self.wc = deploy.WorkingCopy(".")
+ shell.call("git", "remote", "add", "wizard", self.repo)
+ shell.call("git", "fetch", "-q", "wizard")
+ self.user_commit = shell.eval("git", "rev-parse", "HEAD")
+ self.next_commit = shell.eval("git", "rev-parse", self.version)
+ self.mergeSaveState()
+ self.mergePerform()
+ def mergePreCommit(self):
+ def get_file_set(rev):
+ return set(shell.eval("git", "ls-tree", "-r", "--name-only", rev).split("\n"))
+ # add all files that are unversioned but would be replaced by the pull,
+ # and generate a new commit
+ old_files = get_file_set("HEAD")
+ new_files = get_file_set(self.version)
+ added_files = new_files - old_files
+ for f in added_files:
+ if os.path.lexists(f): # broken symbolic links count too!
+ shell.call("git", "add", f)
+ message = "Pre-commit before autoinstall upgrade.\n\n%s" % util.get_git_footer()
+ try:
+ message += "\nPre-commit-by: " + util.get_operator_git()
+ except util.NoOperatorInfo:
+ pass
+ try:
+ shell.call("git", "commit", "-a", "-m", message)
+ except shell.CallError as e:
+ if "Permission denied" in e.stderr:
+ raise util.PermissionsError
+ elif e.stderr:
+ raise
+ logging.info("No changes detected")
+ def mergeClone(self):
+ # If /dev/shm exists, it's a tmpfs and we can use it
+ # to do a fast git merge. Don't forget to move it to
+ # /tmp if it fails.
+ if not self.options.dry_run and not self.options.debug:
+ self.use_shm = os.path.exists("/dev/shm")
+ if self.use_shm:
+ dir = "/dev/shm/wizard"
+ if not os.path.exists(dir):
+ os.mkdir(dir)
+ # XXX: race
+ os.chmod(dir, 0o777)
+ else:
+ dir = None
+ self.temp_dir = tempfile.mkdtemp(prefix="wizard", dir=dir)
+ self.temp_wc_dir = os.path.join(self.temp_dir, "repo")
+ logging.info("Using temporary directory: " + self.temp_wc_dir)
+ shell.call("git", "clone", "-q", "--shared", ".", self.temp_wc_dir)
+ def mergeSaveState(self):
+ """Save variables so that ``--continue`` will work."""
+ # yeah yeah no trailing newline whatever
+ open(".git/WIZARD_UPGRADE_VERSION", "w").write(self.version)
+ open(".git/WIZARD_PARENTS", "w").write("%s\n%s" % (self.user_commit, self.next_commit))
+ open(".git/WIZARD_SIZE", "w").write(str(util.disk_usage()))
+ if self.options.log_file:
+ open(".git/WIZARD_LOG_FILE", "w").write(self.options.log_file)
+ def mergePerform(self):
+ def prepare_config():
+ self.wc.prepareConfig()
+ shell.call("git", "add", ".")
+ def resolve_conflicts():
+ return self.wc.resolveConflicts()
+ shell.call("git", "config", "merge.conflictstyle", "diff3")
+ # setup rerere
+ if self.options.rr_cache is None:
+ self.options.rr_cache = os.path.join(self.prod.location, ".git", "rr-cache")
+ if not os.path.exists(self.options.rr_cache):
+ os.mkdir(self.options.rr_cache)
+ os.symlink(self.options.rr_cache, os.path.join(self.wc.location, ".git", "rr-cache"))
+ shell.call("git", "config", "rerere.enabled", "true")
+ try:
+ merge.merge(self.wc.app_version.wizard_tag, self.version,
+ prepare_config, resolve_conflicts)
+ except merge.MergeError:
+ self.mergeFail()
+ def mergeFail(self):
+ files = set()
+ for line in shell.eval("git", "ls-files", "--unmerged").splitlines():
+ files.add(line.split(None, 3)[-1])
+ conflicts = len(files)
+ # XXX: this is kind of fiddly; note that temp_dir still points at the OLD
+ # location after this code.
+ self.temp_wc_dir = mv_shm_to_tmp(os.getcwd(), self.use_shm)
+ self.wc.location = self.temp_wc_dir
+ os.chdir(self.temp_wc_dir)
+ open(self.prod.pending_file, "w").write(self.temp_wc_dir)
+ if self.options.non_interactive:
+ print "%d %s" % (conflicts, self.temp_wc_dir)