]> scripts.mit.edu Git - wizard.git/blob - wizard/command/migrate.py
Rewrite merge functionality into its own module.
[wizard.git] / wizard / command / migrate.py
1 import os
2 import shutil
3 import logging
4
5 from wizard import command, deploy, shell, util
6
7 def main(argv, baton):
8     options, args = parse_args(argv, baton)
9     if args:
10         dir = args[0]
11     else:
12         dir = os.getcwd()
13
14     shell.drop_priviledges(dir, options.log_file)
15
16     util.chdir(dir)
17     sh = shell.Shell(options.dry_run)
18
19     logging.info("Migrating %s" % dir)
20     logging.debug("uid is %d" % os.getuid())
21
22     deployment = deploy.ProductionCopy(".")
23
24     # deal with old-style migration, remove this later
25     if os.path.isfile(".scripts/old-version") and not os.path.isfile(".scripts-version"):
26         os.rename(".scripts/old-version", ".scripts-version")
27
28     os.unsetenv("GIT_DIR") # prevent some perverse errors
29
30     try:
31         deployment.verify()
32         raise AlreadyMigratedError(deployment.location)
33     except deploy.NotMigratedError:
34         pass
35     except (deploy.CorruptedAutoinstallError, AlreadyMigratedError):
36         if not options.force:
37             raise
38
39     deployment.verifyTag(options.srv_path)
40
41     if options.force_version:
42         version = deployment.application.makeVersion(options.force_version)
43     else:
44         try:
45             deployment.verifyVersion()
46             version = deployment.app_version
47         except deploy.VersionMismatchError as e:
48             # well, we'll use that then
49             version = deployment.application.makeVersion(str(e.real_version))
50     repo = version.application.repository(options.srv_path)
51     tag = version.scripts_tag
52     try:
53         sh.call("git", "--git-dir=%s" % repo, "rev-parse", tag)
54     except shell.CallError:
55         raise UnsupportedVersion(version.version)
56
57     with util.LockDirectory(".scripts-migrate-lock"):
58         try:
59             if options.force:
60                 perform_force(options)
61             make_repository(sh, options, repo, tag)
62             check_variables(deployment, options)
63         except KeyboardInterrupt:
64             # revert it; barring zany race conditions this is safe
65             if os.path.exists(".scripts"):
66                 shutil.rmtree(".scripts")
67             if os.path.exists(".git"):
68                 shutil.rmtree(".git")
69
70 def parse_args(argv, baton):
71     usage = """usage: %prog migrate [ARGS] DIR
72
73 Migrates a directory to our Git-based autoinstall format.
74 Performs basic sanity checking and intelligently determines
75 what repository and tag to use.
76
77 This command is meant to be run as the owner of the install
78 it is upgrading (see the scripts AFS kernel patch).  Do
79 NOT run this command as root."""
80     parser = command.WizardOptionParser(usage)
81     baton.push(parser, "srv_path")
82     parser.add_option("--dry-run", dest="dry_run", action="store_true",
83             default=False, help="Prints would would be run without changing anything")
84     parser.add_option("--force", "-f", dest="force", action="store_true",
85             default=False, help="If .git or .scripts directory already exists,"
86             "delete them and migrate")
87     parser.add_option("--force-version", dest="force_version",
88             default=None, help="If .scripts-version tells lies, explicitly specify"
89             "a version to migrate to.")
90     options, args = parser.parse_all(argv)
91     if len(args) > 1:
92         parser.error("too many arguments")
93     return (options, args)
94
95 def perform_force(options):
96     has_git = os.path.isdir(".git")
97     has_scripts = os.path.isdir(".scripts")
98
99     if has_git:
100         logging.warning("Force removing .git directory")
101         if not options.dry_run: backup = util.safe_unlink(".git")
102         logging.info(".git backed up to %s" % backup)
103     if has_scripts:
104         logging.warning("Force removing .scripts directory")
105         if not options.dry_run: backup = util.safe_unlink(".scripts")
106         logging.info(".scripts backed up to %s" % backup)
107
108 def make_repository(sh, options, repo, tag):
109     sh.call("git", "init") # create repository
110     # configure our alternates (to save space and make this quick)
111     data = os.path.join(repo, "objects")
112     file = ".git/objects/info/alternates"
113     if not options.dry_run:
114         alternates = open(file, "w")
115         alternates.write(data)
116         alternates.close()
117         htaccess = open(".git/.htaccess", "w")
118         htaccess.write("Deny from all\n")
119         htaccess.close()
120     else:
121         logging.info("# create %s containing \"%s\"" % (file, data))
122         logging.info('# create .htaccess containing "Deny from all"')
123     # configure our remote (this is merely for convenience; wizard scripts
124     # will not rely on this)
125     sh.call("git", "remote", "add", "origin", repo)
126     # configure what would normally be set up on a 'git clone' for consistency
127     sh.call("git", "config", "branch.master.remote", "origin")
128     sh.call("git", "config", "branch.master.merge", "refs/heads/master")
129     # perform the initial fetch
130     sh.call("git", "fetch", "origin")
131     # soft reset to our tag
132     sh.call("git", "reset", tag, "--")
133     # checkout the .scripts directory
134     sh.call("git", "checkout", ".scripts")
135     logging.info("Diffstat:\n" + sh.eval("git", "diff", "--stat"))
136     # commit user local changes
137     message = "Autoinstall migration of %s locker.\n\n%s" % (util.get_dir_owner(), util.get_git_footer())
138     util.set_git_env()
139     try:
140         message += "\nMigrated-by: " + util.get_operator_git()
141     except util.NoOperatorInfo:
142         pass
143     sh.call("git", "commit", "--allow-empty", "-a", "-m", message)
144
145 def check_variables(d, options):
146     """Attempt to extract variables and complain if some are missing."""
147     variables = d.extract()
148     for k,v in variables.items():
149         if v is None and k not in d.application.deprecated_keys:
150             logging.warning("Variable %s not found" % k)
151         else:
152             logging.debug("Variable %s is %s" % (k,v))
153
154 class Error(command.Error):
155     """Base exception for all exceptions raised by migrate"""
156     pass
157
158 class AlreadyMigratedError(Error):
159     quiet = True
160     def __init__(self, dir):
161         self.dir = dir
162     def __str__(self):
163         return """
164
165 This autoinstall is already migrated; move along, nothing to
166 see here.  (If you really want to, you can force a re-migration
167 with --force, but this will blow away the existing .git and
168 .scripts directories (i.e. your history and Wizard configuration).)
169 """
170
171 class UnsupportedVersion(Error):
172     def __init__(self, version):
173         self.version = version
174     def __str__(self):
175         return """
176
177 ERROR: This autoinstall is presently on %s, which is unsupported by
178 Wizard.  Please manually upgrade it to one that is supported,
179 and then retry the migration; usually the latest version is supported.
180 """ % self.version