2 # Script to create and restore a git fingerprint of the ghc repositories.
4 from datetime import datetime
5 from optparse import OptionParser
10 from subprocess import PIPE, Popen
14 opts, args = parseopts(sys.argv[1:])
17 def create_action(opts):
18 """Action called for the create commmand"""
20 fp = FingerPrint.read(opts.source)
22 fp = fingerprint(opts.source)
24 error("Got empty fingerprint from source: "+str(opts.source))
26 print "Writing fingerprint to: ", opts.output_file
29 def restore_action(opts):
30 """Action called for the restore commmand"""
31 def branch_name(filename):
32 return "fingerprint_" + os.path.basename(filename).replace(".", "_")
35 fp = FingerPrint.read(opts.source)
36 bn = branch_name(opts.fpfile)
37 except MalformedFingerPrintError:
38 error("Error parsing fingerprint file: "+opts.fpfile)
40 error("No fingerprint found in fingerprint file: "+opts.fpfile)
42 fp = fingerprint(opts.source)
43 bn = branch_name(opts.logfile)
45 error("No fingerprint found in build log file: "+opts.logfile)
47 error("Must restore from fingerprint or log file")
48 restore(fp, branch_name=bn if opts.branch else None)
50 def fingerprint(source=None):
51 """Create a new fingerprint of current repositories.
53 The source argument is parsed to look for the expected output
54 from a `sync-all` command. If the source is `None` then the
55 `sync-all` command will be run to get the current fingerprint.
58 sync_all = ["./sync-all", "log", "HEAD^..", "--pretty=oneline"]
59 source = Popen(sync_all, stdout=PIPE).stdout
63 for line in source.readlines():
64 if line.startswith("=="):
65 lib = line.split()[1].rstrip(":")
66 lib = "." if lib == "running" else lib # hack for top ghc repo
67 elif re.match("[abcdef0-9]{40}", line):
70 return FingerPrint(commits)
72 def restore(fp, branch_name=None):
73 """Restore the ghc repos to the commits in the fingerprint
75 This function performs a checkout of each commit specifed in
76 the fingerprint. If `branch_name` is not None then a new branch
77 will be created for the top ghc repository. We also add an entry
78 to the git config that sets the remote for the new branch as `origin`
79 so that the `sync-all` command can be used from the branch.
81 checkout = ["git", "checkout"]
83 # run checkout in all subdirs
84 for (subdir, commit) in fp:
86 cmd = checkout + [commit]
87 print "==", subdir, " ".join(cmd)
88 if os.path.exists(subdir):
89 rc = subprocess.call(cmd, cwd=subdir)
91 error("Too many errors, aborting")
93 sys.stderr.write("WARNING: "+
94 subdir+" is in fingerprint but missing in working directory\n")
96 # special handling for top ghc repo
97 # if we are creating a new branch then also add an entry to the
98 # git config so the sync-all command is happy
99 branch_args = ["-b", branch_name] if branch_name else []
100 rc = subprocess.call(checkout + branch_args + [fp["."]])
101 if (rc == 0) and branch_name:
102 branch_config = "branch."+branch_name+".remote"
103 subprocess.call(["git", "config", "--add", branch_config, "origin"])
105 actions = {"create" : create_action, "restore" : restore_action}
107 """Parse and check the validity of the command line arguments"""
108 usage = "fingerprint ("+"|".join(sorted(actions.keys()))+") [options]"
109 parser = OptionParser(usage=usage)
111 parser.add_option("-d", "--dir", dest="dir",
112 help="write output to directory DIR", metavar="DIR")
114 parser.add_option("-o", "--output", dest="output",
115 help="write output to file FILE", metavar="FILE")
117 parser.add_option("-l", "--from-log", dest="logfile",
118 help="reconstruct fingerprint from build log", metavar="FILE")
120 parser.add_option("-f", "--from-fp", dest="fpfile",
121 help="reconstruct fingerprint from fingerprint file", metavar="FILE")
123 parser.add_option("-n", "--no-branch",
124 action="store_false", dest="branch", default=True,
125 help="do not create a new branch when restoring fingerprint")
127 parser.add_option("-g", "--ghc-dir", dest="ghcdir",
128 help="perform actions in GHC dir", metavar="DIR")
130 opts,args = parser.parse_args(argv)
131 return (validate(opts, args, parser), args)
133 def validate(opts, args, parser):
134 """ Validate and prepare the command line options.
136 It performs the following actions:
137 * Check that we have a valid action to perform
138 * Check that we have a valid output destination
139 * Opens the output file if needed
140 * Opens the input file if needed
142 # Determine the action
144 opts.action = actions[args[0]]
145 except (IndexError, KeyError):
146 error("Must specify a valid action", parser)
149 if opts.logfile and opts.fpfile:
150 error("Must specify only one of -l and -f")
154 opts.source = file(opts.logfile, "r")
156 opts.source = file(opts.fpfile, "r")
162 fname = datetime.today().strftime("%Y-%m%-%d_%H-%M-%S") + ".fp"
163 path = os.path.join(opts.dir, fname)
164 opts.output_file = path
165 opts.output = file(path, "w")
167 opts.output_file = opts.output
168 opts.output = file(opts.output_file, "w")
170 opts.output_file = None
171 opts.output = sys.stdout
174 # As a last step change the directory to the GHC directory specified
176 os.chdir(opts.ghcdir)
180 def error(msg="fatal error", parser=None, exit=1):
181 """Function that prints error message and exits"""
187 class MalformedFingerPrintError(Exception):
188 """Exception raised when parsing a bad fingerprint file"""
192 """Class representing a fingerprint of all ghc git repos.
194 A finger print is represented by a dictionary that maps a
195 directory to a commit. The directory "." is used for the top
196 level ghc repository.
198 def __init__(self, subcommits = {}):
199 self.commits = subcommits
201 def __eq__(self, other):
202 if other.__class__ != self.__class__:
204 return self.commits == other.commits
206 def __neq__(self, other):
210 return hash(str(self))
213 return len(self.commits)
216 return "FingerPrint(" + repr(self.commits) + ")"
220 for lib in sorted(self.commits.keys()):
221 commit = self.commits[lib]
222 s += "{0}|{1}\n".format(lib, commit)
225 def __getitem__(self, item):
226 return self.commits[item]
229 return self.commits.iteritems()
231 def write(self, outh):
232 outh.write(str(self))
237 """Read a fingerprint from a fingerprint file"""
239 for line in inh.readlines():
240 splits = line.strip().split("|", 1)
242 raise MalformedFingerPrintError(line)
244 commits[lib] = commit
245 return FingerPrint(commits)
247 if __name__ == "__main__":