from wizard import command, deploy, shell, util
-# XXX: WARNING EXPERIMENTAL DANGER DANGER WILL ROBINSON
-
-# need errors for checking DAG integrity (if the user is on a completely
-# different history tree, stuff is problems)
+# XXX: need errors for history sanity checking (if the user is on a completely
+# different history tree, we should abort, and manually figure out the
+# appropriate rebase)
def main(argv, baton):
options, args = parse_args(argv, baton)
- dir = args[0]
- command.chdir(dir)
- if not os.path.isdir(".git"):
- raise NotAutoinstallError()
- try:
- d = deploy.Deployment(".")
- except IOError as e:
- if e.errno == errno.ENOENT:
- raise NotAutoinstallError()
- else: raise e
+ command.chdir(args[0])
+ d = make_deployment_from_cwd()
repo = d.application.repository(options.srv_path)
- # begin the command line process
- sh = shell.Shell()
- # setup environment
util.set_git_env()
- # commit their changes
+ sh = shell.Shell()
if not options.dry_run:
- pre_upgrade_commit(sh)
- # perform fetch to update repository state
- sh.call("git", "fetch", repo)
- # clone their website to a temporary directory
- temp_dir = tempfile.mkdtemp(prefix="wizard")
- temp_wc_dir = os.path.join(temp_dir, "repo")
- logging.info("Using temporary directory: " + temp_wc_dir)
- sh.call("git", "clone", "--shared", ".", temp_wc_dir)
+ perform_pre_commit(sh)
+ temp_wc_dir = perform_tmp_clone(sh)
with util.ChangeDirectory(temp_wc_dir):
if options.dry_run:
- pre_upgrade_commit(sh)
- # reconfigure the repository path
+ # we delayed performing the pre upgrade commit
+ # until we're in the temporary directory
+ perform_pre_commit(sh)
sh.call("git", "remote", "add", "scripts", repo)
sh.call("git", "fetch", "scripts")
- # perform the merge
- version = sh.call("git", "--git-dir="+repo, "describe", "--tags", "master")[0].rstrip()
- user_commit = sh.call("git", "rev-parse", "HEAD")[0].rstrip()
- next_commit = sh.call("git", "rev-parse", version)[0].rstrip()
- message = "Upgraded autoinstall in %s to %s.\n\n%s" % (util.get_dir_owner(), version, util.get_git_footer())
- try:
- message += "\nUpgraded-by: " + util.get_operator_git()
- except util.NoOperatorInfo:
- pass
- try:
- # naive merge algorithm:
- # sh.call("git", "merge", "-m", message, "scripts/master")
- # crazy merge algorithm:
- def make_virtual_commit(tag, parents = []):
- """WARNING: Changes state of Git repository"""
- sh.call("git", "checkout", tag, "--")
- d.parametrize(temp_wc_dir)
- for file in d.application.parametrized_files:
- try:
- sh.call("git", "add", "--", file)
- except shell.CallError:
- pass
- virtual_tree = sh.call("git", "write-tree")[0].rstrip()
- parent_args = itertools.chain(*(["-p", p] for p in parents))
- virtual_commit = sh.call("git", "commit-tree", virtual_tree, *parent_args, input="")[0].rstrip()
- sh.call("git", "reset", "--hard")
- return virtual_commit
- user_tree = sh.call("git", "rev-parse", "HEAD^{tree}")[0].rstrip()
- base_virtual_commit = make_virtual_commit(d.app_version.pristine_tag)
- next_virtual_commit = make_virtual_commit(version, [base_virtual_commit])
- user_virtual_commit = sh.call("git", "commit-tree", user_tree, "-p", base_virtual_commit, input="")[0].rstrip()
- sh.call("git", "checkout", user_virtual_commit, "--")
- sh.call("git", "merge", next_virtual_commit) # XXX
- except shell.CallError:
- print temp_wc_dir
- raise MergeFailed
+ version = sh.eval("git", "--git-dir="+repo, "describe", "--tags", "master")
+ user_commit = sh.eval("git", "rev-parse", "HEAD")
+ next_commit = sh.eval("git", "rev-parse", version)
+ perform_merge(sh, repo, d, version)
# Make it possible to resume here
- new_tree = sh.call("git", "rev-parse", "HEAD^{tree}")[0].rstrip()
- final_commit = sh.call("git", "commit-tree", new_tree, "-p", user_commit, "-p", next_commit, input=message)[0].rstrip()
+ new_tree = sh.eval("git", "rev-parse", "HEAD^{tree}")
+ message = make_commit_message(version)
+ final_commit = sh.eval("git", "commit-tree", new_tree, "-p", user_commit, "-p", next_commit, input=message)
try:
sh.call("git", "checkout", "-b", "master", "--")
except shell.CallError:
return
# XXX: frob .htaccess to make site inaccessible
# git merge (which performs a fast forward)
- # - merge could fail (race)
+ # - merge could fail (race); that's /really/ dangerous.
sh.call("git", "pull", temp_wc_dir, "master")
# run update script
sh.call(".scripts/update")
# process might have frobbed it. Don't be
# particularly worried if the segment dissappeared
-def pre_upgrade_commit(sh):
+def make_deployment_from_cwd():
+ if not os.path.isdir(".git"):
+ raise NotAutoinstallError()
+ try:
+ d = deploy.Deployment(".")
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ raise NotAutoinstallError()
+ else: raise e
+ return d
+
+def make_commit_message(version):
+ message = "Upgraded autoinstall in %s to %s.\n\n%s" % (util.get_dir_owner(), version, util.get_git_footer())
+ try:
+ message += "\nUpgraded-by: " + util.get_operator_git()
+ except util.NoOperatorInfo:
+ pass
+
+def perform_pre_commit(sh):
try:
message = "Pre-commit of %s locker before autoinstall upgrade.\n\n%s" % (util.get_dir_owner(), util.get_git_footer())
try:
logging.info("No changes detected")
pass
+def perform_tmp_clone(sh):
+ temp_dir = tempfile.mkdtemp(prefix="wizard")
+ temp_wc_dir = os.path.join(temp_dir, "repo")
+ logging.info("Using temporary directory: " + temp_wc_dir)
+ sh.call("git", "clone", "--shared", ".", temp_wc_dir)
+ return temp_wc_dir
+
+def perform_merge(sh, repo, d, version):
+ try:
+ # naive merge algorithm:
+ # sh.call("git", "merge", "-m", message, "scripts/master")
+ # crazy merge algorithm:
+ def make_virtual_commit(tag, parents = []):
+ """WARNING: Changes state of Git repository"""
+ sh.call("git", "checkout", tag, "--")
+ d.parametrize(".")
+ for file in d.application.parametrized_files:
+ try:
+ sh.call("git", "add", "--", file)
+ except shell.CallError:
+ pass
+ virtual_tree = sh.eval("git", "write-tree")
+ parent_args = itertools.chain(*(["-p", p] for p in parents))
+ virtual_commit = sh.eval("git", "commit-tree", virtual_tree, *parent_args, input="")
+ sh.call("git", "reset", "--hard")
+ return virtual_commit
+ user_tree = sh.eval("git", "rev-parse", "HEAD^{tree}")
+ base_virtual_commit = make_virtual_commit(d.app_version.pristine_tag)
+ next_virtual_commit = make_virtual_commit(version, [base_virtual_commit])
+ user_virtual_commit = sh.eval("git", "commit-tree", user_tree, "-p", base_virtual_commit, input="")
+ sh.call("git", "checkout", user_virtual_commit, "--")
+ sh.call("git", "merge", next_virtual_commit)
+ except shell.CallError:
+ print os.getcwd()
+ raise MergeFailed
+
def parse_args(argv, baton):
usage = """usage: %prog upgrade [ARGS] DIR
:param input: input to feed the subprocess on standard input.
:param interactive: whether or not directly hook up all pipes
to the controlling terminal, to allow interaction with subprocess.
- :returns: a tuple of strings ``(stdout, stderr)``
+ :param strip: if ``True``, instead of returning a tuple,
+ return the string stdout output of the command with trailing newlines
+ removed. This emulates the behavior of backticks and ``$()`` in Bash.
+ Prefer to use :meth:`eval` instead (you should only need to explicitly
+ specify this if you are using another wrapper around this function).
+ :returns: a tuple of strings ``(stdout, stderr)``, or a string ``stdout``
+ if ``strip`` is specified.
>>> sh = Shell()
>>> sh.call("echo", "Foobar")
('Foobar\\n', '')
-
- .. note::
-
- This function does not munge trailing whitespace. A common
- idiom for dealing with this is::
-
- sh.call("echo", "Foobar")[0].rstrip()
+ >>> sh.call("cat", input='Foobar')
+ ('Foobar', '')
"""
kwargs.setdefault("python", None)
logging.info("Running `" + ' '.join(args) + "`")
args[0] = wizard_bin
kwargs.setdefault("input", None)
kwargs.setdefault("interactive", False)
+ kwargs.setdefault("strip", False)
if kwargs["interactive"]:
stdout=sys.stdout
stdin=sys.stdin
if kwargs["python"]: eclass = PythonCallError
else: eclass = CallError
raise eclass(proc.returncode, args, stdout, stderr)
+ if kwargs["strip"]:
+ return stdout.rstrip("\n")
return (stdout, stderr)
def _log(self, stdout, stderr):
"""Logs the standard output and standard input from a command."""
return self.callAsUser(*args, **kwargs)
else:
return self.call(*args, **kwargs)
+ def eval(self, *args, **kwargs):
+ """
+ Evaluates a command and returns its output, with trailing newlines
+ stripped (like backticks in Bash). This is a convenience method for
+ calling :meth:`call` with ``strip``.
+
+ >>> sh = Shell()
+ >>> sh.eval("echo", "Foobar")
+ 'Foobar'
+ """
+ kwargs["strip"] = True
+ return self.call(*args, **kwargs)
class ParallelShell(Shell):
"""