]> scripts.mit.edu Git - wizard.git/blob - wizard/command/migrate.py
Rename to use Wizard-revision, which is more accurate.
[wizard.git] / wizard / command / migrate.py
1 import os
2 import shutil
3 import logging
4 import errno
5 import sys
6
7 from wizard import deploy
8 from wizard import shell
9 from wizard import util
10 from wizard.command import _base
11
12 def main(argv, global_options):
13     options, args = parse_args(argv)
14     dir = args[0]
15
16     logging.debug("uid is %d" % os.getuid())
17
18     _base.chdir(dir)
19     check_if_already_migrated(options)
20
21     version = calculate_version()
22     repo    = version.application.getRepository()
23     tag     = version.getScriptsTag()
24
25     os.unsetenv("GIT_DIR") # prevent some perverse errors
26
27     sh = shell.Shell(options.dry_run)
28     check_if_tag_exists(sh, repo, tag)
29     make_repository(sh, options, repo, tag)
30
31     os.rename(".scripts-version", ".scripts/old-version") # archive
32
33 def parse_args(argv):
34     usage = """usage: %prog migrate [ARGS] DIR
35
36 Migrates a directory to our Git-based autoinstall format.
37 Performs basic sanity checking and intelligently determines
38 what repository and tag to use.
39
40 This command is meant to be run as the owner of the install
41 it is upgrading (see the scripts AFS kernel patch).  Do
42 NOT run this command as root."""
43     parser = _base.WizardOptionParser(usage)
44     parser.add_option("--dry-run", dest="dry_run", action="store_true",
45             default=False, help="Prints would would be run without changing anything")
46     parser.add_option("--force", "-f", dest="force", action="store_true",
47             default=False, help="If .git or .scripts directory already exists, delete them and migrate")
48     options, args = parser.parse_all(argv)
49     if len(args) > 1:
50         parser.error("too many arguments")
51     elif not args:
52         parser.error("must specify directory")
53     return (options, args)
54
55 def check_if_already_migrated(options):
56     if os.path.isdir(".git") or os.path.isdir(".scripts"):
57         if not options.force:
58             raise AlreadyMigratedError(dir)
59         else:
60             if os.path.isdir(".git"):
61                 logging.warning("Force removing .git directory")
62                 if not options.dry_run: shutil.rmtree(".git")
63             if os.path.isdir(".scripts"):
64                 logging.warning("Force removing .scripts directory")
65                 if not options.dry_run: shutil.rmtree(".scripts")
66
67 def calculate_version():
68     try:
69         d = deploy.Deployment.fromDir(".")
70         return d.getAppVersion()
71     except IOError as e:
72         if e.errno == errno.ENOENT:
73             raise NotAutoinstallError(dir)
74         else: raise e
75
76 def check_if_tag_exists(sh, repo, tag):
77     # check if the version we're trying to convert exists. We assume
78     # a convention here, namely, v1.2.3-scripts is what we want. If
79     # you broke the convention... shame on you.
80     try:
81         sh.call("git", "--git-dir", repo, "rev-parse", tag)
82     except shell.CallError:
83         raise NoTagError(version)
84
85 def make_repository(sh, options, repo, tag):
86     sh.call("git", "init") # create repository
87     # configure our alternates (to save space and make this quick)
88     data = os.path.join(repo, "objects")
89     file = ".git/objects/info/alternates"
90     if not options.dry_run:
91         alternates = open(file, "w")
92         alternates.write(data)
93         alternates.close()
94     else:
95         logging.info("# create %s containing \"%s\"" % (file, data))
96     # configure our remote (this is merely for convenience; wizard scripts
97     # will not rely on this)
98     sh.call("git", "remote", "add", "origin", repo)
99     # configure what would normally be set up on a 'git clone' for consistency
100     sh.call("git", "config", "branch.master.remote", "origin")
101     sh.call("git", "config", "branch.master.merge", "refs/heads/master")
102     # perform the initial fetch
103     sh.call("git", "fetch", "origin")
104     # soft reset to our tag
105     sh.call("git", "reset", tag, "--")
106     # checkout the .scripts directory
107     sh.call("git", "checkout", ".scripts")
108     # commit user local changes
109     lines = ["Initial commit after migration."
110             ,""
111             ,"Wizard-revision: %s" % util.get_revision()
112             ,"Wizard-args: %s" % " ".join(sys.argv)
113             ]
114     try:
115         lines.append("Migrated-by: " + util.get_operator_git())
116         # maybe this should go in massmigrate
117         op_realname, op_email = util.get_operator_info()
118         os.putenv("GIT_COMMITTER_NAME", op_realname)
119         os.putenv("GIT_COMMITTER_EMAIL", op_email)
120     except util.NoOperatorInfo:
121         pass
122     try:
123         lockername = util.get_dir_owner(".")
124         os.putenv("GIT_AUTHOR_NAME", "%s locker" % lockername)
125         os.putenv("GIT_AUTHOR_EMAIL", "%s@scripts.mit.edu" % lockername)
126     except KeyError:
127         pass
128     sh.call("git", "commit", "--allow-empty", "-a", "-m", "\n".join(lines))
129     # for verbose purposes, give us a git status and git diff
130     if options.verbose:
131         try:
132             sh.call("git", "status")
133         except shell.CallError:
134             pass
135         try:
136             sh.call("git", "diff")
137         except shell.CallError:
138             pass
139
140 class Error(_base.Error):
141     """Base exception for all exceptions raised by migrate"""
142     pass
143
144 class AlreadyMigratedError(Error):
145     def __init__(self, dir):
146         self.dir = dir
147     def __str__(self):
148         return """
149
150 ERROR: Directory already contains a .git and/or
151 .scripts directory.  Did you already migrate it?
152 """
153
154 class NotAutoinstallError(Error):
155     def __init__(self, dir):
156         self.dir = dir
157     def __str__(self):
158         return """
159
160 ERROR: Could not find .scripts-version file. Are you sure
161 this is an autoinstalled application?
162 """
163
164 class NoRepositoryError(Error):
165     def __init__(self, app):
166         self.app = app
167     def __str__(self):
168         return """
169
170 ERROR: Could not find repository for this application. Have
171 you converted the repository over? Is the name %s
172 the same as the name of the .git folder?
173 """ % self.app
174
175 class NoTagError(Error):
176     def __init__(self, version):
177         self.version = version
178     def __str__(self):
179         return """
180
181 ERROR: Could not find tag v%s-scripts in repository
182 for %s.  Double check and make sure
183 the repository was prepared with all necessary tags!
184 """ % (self.version.version, self.version.application.name)