6 from wizard import app, command, deploy, shell, util
9 options, args = parse_args(argv, baton)
10 dir = os.path.abspath(args[0]) if args else os.getcwd()
11 shell.drop_priviledges(dir, options.log_file)
14 sh = shell.Shell(options.dry_run)
16 logging.info("Migrating %s" % dir)
17 logging.debug("uid is %d" % os.getuid())
19 deployment = deploy.ProductionCopy(".")
21 os.unsetenv("GIT_DIR") # prevent some perverse errors
25 raise AlreadyMigratedError(deployment.location)
26 except deploy.NotAutoinstallError:
27 # Previously, this was a fatal error, but now let's try
29 # XXX: The user still has to tell us what application ; a more
30 # user friendly thing to do is figure it out automatically
31 if not options.force_app:
33 # actual version number will get overwritten shortly
34 deployment.setAppVersion(app.ApplicationVersion.make(options.force_app, "unknown"))
35 except deploy.NotMigratedError:
38 except (deploy.CorruptedAutoinstallError, AlreadyMigratedError):
42 if options.force_version:
43 deployment.setAppVersion(deployment.application.makeVersion(options.force_version))
46 deployment.verifyVersion()
47 except deploy.VersionMismatchError as e:
48 # well, we'll use that then
49 deployment.setAppVersion(deployment.application.makeVersion(str(e.real_version)))
51 deployment.verifyTag(options.srv_path)
53 repo = deployment.application.repository(options.srv_path)
54 tag = deployment.app_version.wizard_tag
56 sh.call("git", "--git-dir=%s" % repo, "rev-parse", tag)
57 except shell.CallError:
58 raise UnsupportedVersion(deployment.version)
60 with util.LockDirectory(".wizard-migrate-lock"):
63 perform_force(options)
64 make_repository(sh, options, repo, tag)
65 check_variables(deployment, options)
66 except KeyboardInterrupt:
67 # revert it; barring zany race conditions this is safe
68 if os.path.exists(".wizard"):
69 shutil.rmtree(".wizard")
70 if os.path.exists(".git"):
73 def parse_args(argv, baton):
74 usage = """usage: %prog migrate [ARGS] DIR
76 Migrates a directory to our Git-based autoinstall format.
77 Performs basic sanity checking and intelligently determines
78 what repository and tag to use.
80 This command is meant to be run as the owner of the install it is
81 upgrading . Do NOT run this command as root."""
82 parser = command.WizardOptionParser(usage)
83 baton.push(parser, "srv_path")
84 parser.add_option("--dry-run", dest="dry_run", action="store_true",
85 default=False, help="Prints would would be run without changing anything")
86 parser.add_option("--force", "-f", dest="force", action="store_true",
87 default=False, help="If .git or .wizard directory already exists, "
88 "delete them and migrate")
89 parser.add_option("--force-version", dest="force_version",
90 default=None, help="If .scripts-version is corrupted or non-existent, explicitly specify "
91 "a version to migrate to.")
92 parser.add_option("--force-app", dest="force_app",
93 default=None, help="If .scripts-version is corrupted or non-existent, explicitly specify "
94 "an application to migrate to.")
95 options, args = parser.parse_all(argv)
97 parser.error("too many arguments")
98 return (options, args)
100 def perform_force(options):
101 has_git = os.path.isdir(".git")
102 has_wizard = os.path.isdir(".wizard")
105 logging.warning("Force removing .git directory")
106 if not options.dry_run: backup = util.safe_unlink(".git")
107 logging.info(".git backed up to %s" % backup)
109 logging.warning("Force removing .wizard directory")
110 if not options.dry_run: backup = util.safe_unlink(".wizard")
111 logging.info(".wizard backed up to %s" % backup)
113 def make_repository(sh, options, repo, tag):
114 sh.call("git", "init") # create repository
115 # configure our alternates (to save space and make this quick)
116 data = os.path.join(repo, "objects")
117 file = ".git/objects/info/alternates"
118 if not options.dry_run:
119 alternates = open(file, "w")
120 alternates.write(data)
122 htaccess = open(".git/.htaccess", "w")
123 htaccess.write("Deny from all\n")
126 logging.info("# create %s containing \"%s\"" % (file, data))
127 logging.info('# create .htaccess containing "Deny from all"')
128 # configure our remote (this is merely for convenience; wizard
129 # will not rely on this)
130 sh.call("git", "remote", "add", "origin", repo)
131 # configure what would normally be set up on a 'git clone' for consistency
132 sh.call("git", "config", "branch.master.remote", "origin")
133 sh.call("git", "config", "branch.master.merge", "refs/heads/master")
134 # perform the initial fetch
135 sh.call("git", "fetch", "origin")
136 # soft reset to our tag
137 sh.call("git", "reset", tag, "--")
138 # initialize the .wizard directory
139 util.init_wizard_dir()
140 logging.info("Diffstat:\n" + sh.eval("git", "diff", "--stat"))
141 # commit user local changes
142 message = "Autoinstall migration.\n\n%s" % util.get_git_footer()
145 message += "\nMigrated-by: " + util.get_operator_git()
146 except util.NoOperatorInfo:
148 sh.call("git", "commit", "--allow-empty", "-a", "-m", message)
150 def check_variables(d, options):
151 """Attempt to extract variables and complain if some are missing."""
152 variables = d.extract()
153 for k,v in variables.items():
154 if v is None and k not in d.application.deprecated_keys:
155 logging.warning("Variable %s not found" % k)
157 logging.debug("Variable %s is %s" % (k,v))
159 class Error(command.Error):
160 """Base exception for all exceptions raised by migrate"""
163 class AlreadyMigratedError(Error):
165 def __init__(self, dir):
170 This autoinstall is already migrated; move along, nothing to
171 see here. (If you really want to, you can force a re-migration
172 with --force, but this will blow away the existing .git and
173 .scripts directories (i.e. your history and Wizard configuration).)
176 class UnsupportedVersion(Error):
177 def __init__(self, version):
178 self.version = version
182 ERROR: This autoinstall is presently on %s, which is unsupported by
183 Wizard. Please manually upgrade it to one that is supported,
184 and then retry the migration; usually the latest version is supported.