5 from wizard import command, deploy, shell, util
8 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.ProductionCopy(".")
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 with util.LockDirectory(".scripts-migrate-lock"):
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"):
62 def parse_args(argv, baton):
63 usage = """usage: %prog migrate [ARGS] DIR
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.
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)
84 parser.error("too many arguments")
85 return (options, args)
87 def perform_force(options):
88 has_git = os.path.isdir(".git")
89 has_scripts = os.path.isdir(".scripts")
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)
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)
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)
109 htaccess = open(".git/.htaccess", "w")
110 htaccess.write("Deny from all\n")
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())
132 message += "\nMigrated-by: " + util.get_operator_git()
133 except util.NoOperatorInfo:
135 sh.call("git", "commit", "--allow-empty", "-a", "-m", message)
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)
144 logging.debug("Variable %s is %s" % (k,v))
146 class Error(command.Error):
147 """Base exception for all exceptions raised by migrate"""
150 class AlreadyMigratedError(Error):
151 def __init__(self, dir):
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).)