#!/usr/bin/python # # p4-fast-export.py # # Author: Simon Hausmann # License: MIT # # TODO: # - support integrations (at least p4i) # - support incremental imports # - create tags # - instead of reading all files into a variable try to pipe from # - support p4 submit (hah!) # - don't hardcode the import to master # import os, string, sys, time if len(sys.argv) != 2: sys.stderr.write("usage: %s //depot/path[@revRange]\n" % sys.argv[0]); sys.stderr.write("\n example:\n"); sys.stderr.write(" %s //depot/my/project/ -- to import everything\n"); sys.stderr.write(" %s //depot/my/project/@1,6 -- to import only from revision 1 to 6\n"); sys.stderr.write("\n"); sys.stderr.write(" (a ... is not needed in the path p4 specification, it's added implicitly)\n"); sys.stderr.write("\n"); sys.exit(1) prefix = sys.argv[1] changeRange = "" try: atIdx = prefix.index("@") changeRange = prefix[atIdx:] prefix = prefix[0:atIdx] except ValueError: changeRange = "" if not prefix.endswith("/"): prefix += "/" def describe(change): output = os.popen("p4 describe %s" % change).readlines() firstLine = output[0] splitted = firstLine.split(" ") author = splitted[3] author = author[:author.find("@")] tm = time.strptime(splitted[5] + " " + splitted[6], "%Y/%m/%d %H:%M:%S ") epoch = int(time.mktime(tm)) filesSection = 0 try: filesSection = output.index("Affected files ...\n") except ValueError: sys.stderr.write("Change %s doesn't seem to affect any files. Weird.\n" % change) return [], [], [], [], [] differencesSection = 0 try: differencesSection = output.index("Differences ...\n") except ValueError: sys.stderr.write("Change %s doesn't seem to have a differences section. Weird.\n" % change) return [], [], [], [], [] log = output[2:filesSection - 1] lines = output[filesSection + 2:differencesSection - 1] changed = [] removed = [] for line in lines: # chop off "... " and trailing newline line = line[4:len(line) - 1] lastSpace = line.rfind(" ") if lastSpace == -1: sys.stderr.write("trouble parsing line %s, skipping!\n" % line) continue operation = line[lastSpace + 1:] path = line[:lastSpace] if operation == "delete": removed.append(path) else: changed.append(path) return author, log, epoch, changed, removed def p4cat(path): return os.popen("p4 print -q \"%s\"" % path).read() def p4Stat(path): output = os.popen("p4 fstat -Ol \"%s\"" % path).readlines() fileSize = 0 mode = 644 for line in output: if line.startswith("... headType x"): mode = 755 elif line.startswith("... fileSize "): fileSize = long(line[12:]) return mode, fileSize def stripRevision(path): hashPos = path.rindex("#") return path[:hashPos] def getUserMap(): users = {} output = os.popen("p4 users") for line in output: firstSpace = line.index(" ") secondSpace = line.index(" ", firstSpace + 1) key = line[:firstSpace] email = line[firstSpace + 1:secondSpace] openParenPos = line.index("(", secondSpace) closedParenPos = line.index(")", openParenPos) name = line[openParenPos + 1:closedParenPos] users[key] = name + " " + email return users users = getUserMap() output = os.popen("p4 changes %s...%s" % (prefix, changeRange)).readlines() changes = [] for line in output: changeNum = line.split(" ")[1] changes.append(changeNum) changes.reverse() sys.stderr.write("\n") tz = - time.timezone / 36 cnt = 1 for change in changes: [ author, log, epoch, changedFiles, removedFiles ] = describe(change) sys.stderr.write("\rimporting revision %s (%s%%)" % (change, cnt * 100 / len(changes))) cnt = cnt + 1 print "commit refs/heads/master" if author in users: print "committer %s %s %s" % (users[author], epoch, tz) else: print "committer %s %s %s" % (author, epoch, tz) print "data <