]> scripts.mit.edu Git - wizard.git/blob - wizard/command/migrate.py
Make AlreadyMigrated error less menacing.
[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         deployment.verifyVersion()
45         version = deployment.app_version
46     repo = version.application.repository(options.srv_path)
47     tag = version.scripts_tag
48
49     with util.LockDirectory(".scripts-migrate-lock"):
50         try:
51             if options.force:
52                 perform_force(options)
53             make_repository(sh, options, repo, tag)
54             check_variables(deployment, options)
55         except KeyboardInterrupt:
56             # revert it; barring zany race conditions this is safe
57             if os.path.exists(".scripts"):
58                 shutil.rmtree(".scripts")
59             if os.path.exists(".git"):
60                 shutil.rmtree(".git")
61
62 def parse_args(argv, baton):
63     usage = """usage: %prog migrate [ARGS] DIR
64
65 Migrates a directory to our Git-based autoinstall format.
66 Performs basic sanity checking and intelligently determines
67 what repository and tag to use.
68
69 This command is meant to be run as the owner of the install
70 it is upgrading (see the scripts AFS kernel patch).  Do
71 NOT run this command as root."""
72     parser = command.WizardOptionParser(usage)
73     baton.push(parser, "srv_path")
74     parser.add_option("--dry-run", dest="dry_run", action="store_true",
75             default=False, help="Prints would would be run without changing anything")
76     parser.add_option("--force", "-f", dest="force", action="store_true",
77             default=False, help="If .git or .scripts directory already exists,"
78             "delete them and migrate")
79     parser.add_option("--force-version", dest="force_version",
80             default=None, help="If .scripts-version tells lies, explicitly specify"
81             "a version to migrate to.")
82     options, args = parser.parse_all(argv)
83     if len(args) > 1:
84         parser.error("too many arguments")
85     return (options, args)
86
87 def perform_force(options):
88     has_git = os.path.isdir(".git")
89     has_scripts = os.path.isdir(".scripts")
90
91     if has_git:
92         logging.warning("Force removing .git directory")
93         if not options.dry_run: backup = util.safe_unlink(".git")
94         logging.info(".git backed up to %s" % backup)
95     if has_scripts:
96         logging.warning("Force removing .scripts directory")
97         if not options.dry_run: backup = util.safe_unlink(".scripts")
98         logging.info(".scripts backed up to %s" % backup)
99
100 def make_repository(sh, options, repo, tag):
101     sh.call("git", "init") # create repository
102     # configure our alternates (to save space and make this quick)
103     data = os.path.join(repo, "objects")
104     file = ".git/objects/info/alternates"
105     if not options.dry_run:
106         alternates = open(file, "w")
107         alternates.write(data)
108         alternates.close()
109         htaccess = open(".git/.htaccess", "w")
110         htaccess.write("Deny from all\n")
111         htaccess.close()
112     else:
113         logging.info("# create %s containing \"%s\"" % (file, data))
114         logging.info('# create .htaccess containing "Deny from all"')
115     # configure our remote (this is merely for convenience; wizard scripts
116     # will not rely on this)
117     sh.call("git", "remote", "add", "origin", repo)
118     # configure what would normally be set up on a 'git clone' for consistency
119     sh.call("git", "config", "branch.master.remote", "origin")
120     sh.call("git", "config", "branch.master.merge", "refs/heads/master")
121     # perform the initial fetch
122     sh.call("git", "fetch", "origin")
123     # soft reset to our tag
124     sh.call("git", "reset", tag, "--")
125     # checkout the .scripts directory
126     sh.call("git", "checkout", ".scripts")
127     logging.info("Diffstat:\n" + sh.eval("git", "diff", "--stat"))
128     # commit user local changes
129     message = "Autoinstall migration of %s locker.\n\n%s" % (util.get_dir_owner(), util.get_git_footer())
130     util.set_git_env()
131     try:
132         message += "\nMigrated-by: " + util.get_operator_git()
133     except util.NoOperatorInfo:
134         pass
135     sh.call("git", "commit", "--allow-empty", "-a", "-m", message)
136
137 def check_variables(d, options):
138     """Attempt to extract variables and complain if some are missing."""
139     variables = d.extract()
140     for k,v in variables.items():
141         if v is None and k not in d.application.deprecated_keys:
142             logging.warning("Variable %s not found" % k)
143         else:
144             logging.debug("Variable %s is %s" % (k,v))
145
146 class Error(command.Error):
147     """Base exception for all exceptions raised by migrate"""
148     pass
149
150 class AlreadyMigratedError(Error):
151     def __init__(self, dir):
152         self.dir = dir
153     def __str__(self):
154         return """
155
156 This autoinstall is already migrated; move along, nothing to
157 see here.  (If you really want to, you can force a re-migration
158 with --force, but this will blow away the existing .git and
159 .scripts directories (i.e. your history and Wizard configuration).)
160 """
161