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