8 from wizard import command, deploy, shell, util
10 def main(argv, baton):
11 options, args = parse_args(argv, baton)
14 shell.drop_priviledges(dir, options.log_file)
17 sh = shell.Shell(options.dry_run)
19 logging.info("Migrating %s" % dir)
20 logging.debug("uid is %d" % os.getuid())
22 deployment = deploy.Deployment(".")
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")
28 os.unsetenv("GIT_DIR") # prevent some perverse errors
32 raise AlreadyMigratedError(deployment.location)
33 except deploy.NotMigratedError:
35 except (deploy.CorruptedAutoinstallError, AlreadyMigratedError):
39 deployment.verifyTag(options.srv_path)
41 if options.force_version:
42 version = deployment.application.makeVersion(options.force_version)
44 deployment.verifyVersion()
45 version = deployment.app_version
46 repo = version.application.repository(options.srv_path)
47 tag = version.scripts_tag
49 # XXX: turn this into a context
50 with util.LockDirectory(".scripts-migrate-lock"):
52 if options.force: perform_force(options)
53 make_repository(sh, options, repo, tag)
54 check_variables(deployment, options)
55 except KeyboardInterrupt:
56 # revert it; barring a particularly zany race condition
58 if os.path.exists(".scripts"):
59 shutil.rmtree(".scripts")
60 if os.path.exists(".git"):
63 def parse_args(argv, baton):
64 usage = """usage: %prog migrate [ARGS] DIR
66 Migrates a directory to our Git-based autoinstall format.
67 Performs basic sanity checking and intelligently determines
68 what repository and tag to use.
70 This command is meant to be run as the owner of the install
71 it is upgrading (see the scripts AFS kernel patch). Do
72 NOT run this command as root."""
73 parser = command.WizardOptionParser(usage)
74 baton.push(parser, "srv_path")
75 parser.add_option("--dry-run", dest="dry_run", action="store_true",
76 default=False, help="Prints would would be run without changing anything")
77 parser.add_option("--force", "-f", dest="force", action="store_true",
78 default=False, help="If .git or .scripts directory already exists,"
79 "delete them and migrate")
80 parser.add_option("--force-version", dest="force_version",
81 default=None, help="If .scripts-version tells lies, explicitly specify"
82 "a version to migrate to.")
83 options, args = parser.parse_all(argv)
85 parser.error("too many arguments")
87 parser.error("must specify directory")
88 return (options, args)
90 def perform_force(options):
91 has_git = os.path.isdir(".git")
92 has_scripts = os.path.isdir(".scripts")
95 logging.warning("Force removing .git directory")
96 if not options.dry_run: backup = util.safe_unlink(".git")
97 logging.info(".git backed up to %s" % backup)
99 logging.warning("Force removing .scripts directory")
100 if not options.dry_run: backup = util.safe_unlink(".scripts")
101 logging.info(".scripts backed up to %s" % backup)
103 def make_repository(sh, options, repo, tag):
104 sh.call("git", "init") # create repository
105 # configure our alternates (to save space and make this quick)
106 data = os.path.join(repo, "objects")
107 file = ".git/objects/info/alternates"
108 if not options.dry_run:
109 alternates = open(file, "w")
110 alternates.write(data)
112 htaccess = open(".git/.htaccess", "w")
113 htaccess.write("Deny from all\n")
116 logging.info("# create %s containing \"%s\"" % (file, data))
117 logging.info('# create .htaccess containing "Deny from all"')
118 # configure our remote (this is merely for convenience; wizard scripts
119 # will not rely on this)
120 sh.call("git", "remote", "add", "origin", repo)
121 # configure what would normally be set up on a 'git clone' for consistency
122 sh.call("git", "config", "branch.master.remote", "origin")
123 sh.call("git", "config", "branch.master.merge", "refs/heads/master")
124 # perform the initial fetch
125 sh.call("git", "fetch", "origin")
126 # soft reset to our tag
127 sh.call("git", "reset", tag, "--")
128 # checkout the .scripts directory
129 sh.call("git", "checkout", ".scripts")
130 logging.info("Diffstat:\n" + sh.eval("git", "diff", "--stat"))
131 # commit user local changes
132 message = "Autoinstall migration of %s locker.\n\n%s" % (util.get_dir_owner(), util.get_git_footer())
135 message += "\nMigrated-by: " + util.get_operator_git()
136 except util.NoOperatorInfo:
138 sh.call("git", "commit", "--allow-empty", "-a", "-m", message)
140 def check_variables(d, options):
141 """Attempt to extract variables and complain if some are missing."""
142 variables = d.extract()
143 for k,v in variables.items():
144 if v is None and k not in d.application.deprecated_keys:
145 logging.warning("Variable %s not found" % k)
147 logging.debug("Variable %s is %s" % (k,v))
149 class Error(command.Error):
150 """Base exception for all exceptions raised by migrate"""
153 class AlreadyMigratedError(Error):
154 def __init__(self, dir):
159 ERROR: Directory already contains a .git and
160 .scripts directory. If you force this migration,
161 both of these directories will be removed.