Speed up the import of individual files from Perforce into git by passing the output...
[fast-export.git] / contrib / fast-import / p4-fast-export.py
blob72a4fd70a5bc200c1758b4cb56e2dfa9a6523104
1 #!/usr/bin/python
3 # p4-fast-export.py
5 # Author: Simon Hausmann <hausmann@kde.org>
6 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
8 # TODO:
9 # - support integrations (at least p4i)
10 # - support incremental imports
11 # - create tags
12 # - instead of reading all files into a variable try to pipe from
13 # - support p4 submit (hah!)
14 # - don't hardcode the import to master
16 import os, string, sys, time
18 if len(sys.argv) != 2:
19 sys.stderr.write("usage: %s //depot/path[@revRange]\n" % sys.argv[0]);
20 sys.stderr.write("\n example:\n");
21 sys.stderr.write(" %s //depot/my/project/ -- to import everything\n");
22 sys.stderr.write(" %s //depot/my/project/@1,6 -- to import only from revision 1 to 6\n");
23 sys.stderr.write("\n");
24 sys.stderr.write(" (a ... is not needed in the path p4 specification, it's added implicitly)\n");
25 sys.stderr.write("\n");
26 sys.exit(1)
28 prefix = sys.argv[1]
29 changeRange = ""
30 try:
31 atIdx = prefix.index("@")
32 changeRange = prefix[atIdx:]
33 prefix = prefix[0:atIdx]
34 except ValueError:
35 changeRange = ""
37 if not prefix.endswith("/"):
38 prefix += "/"
40 def describe(change):
41 output = os.popen("p4 describe %s" % change).readlines()
43 firstLine = output[0]
45 splitted = firstLine.split(" ")
46 author = splitted[3]
47 author = author[:author.find("@")]
48 tm = time.strptime(splitted[5] + " " + splitted[6], "%Y/%m/%d %H:%M:%S ")
49 epoch = int(time.mktime(tm))
51 filesSection = 0
52 try:
53 filesSection = output.index("Affected files ...\n")
54 except ValueError:
55 sys.stderr.write("Change %s doesn't seem to affect any files. Weird.\n" % change)
56 return [], [], [], [], []
58 differencesSection = 0
59 try:
60 differencesSection = output.index("Differences ...\n")
61 except ValueError:
62 sys.stderr.write("Change %s doesn't seem to have a differences section. Weird.\n" % change)
63 return [], [], [], [], []
65 log = output[2:filesSection - 1]
67 lines = output[filesSection + 2:differencesSection - 1]
69 changed = []
70 removed = []
72 for line in lines:
73 # chop off "... " and trailing newline
74 line = line[4:len(line) - 1]
76 lastSpace = line.rfind(" ")
77 if lastSpace == -1:
78 sys.stderr.write("trouble parsing line %s, skipping!\n" % line)
79 continue
81 operation = line[lastSpace + 1:]
82 path = line[:lastSpace]
84 if operation == "delete":
85 removed.append(path)
86 else:
87 changed.append(path)
89 return author, log, epoch, changed, removed
91 def p4cat(path):
92 return os.popen("p4 print -q \"%s\"" % path).read()
94 def p4Stat(path):
95 output = os.popen("p4 fstat -Ol \"%s\"" % path).readlines()
96 fileSize = 0
97 mode = 644
98 for line in output:
99 if line.startswith("... headType x"):
100 mode = 755
101 elif line.startswith("... fileSize "):
102 fileSize = long(line[12:])
103 return mode, fileSize
105 def stripRevision(path):
106 hashPos = path.rindex("#")
107 return path[:hashPos]
109 def getUserMap():
110 users = {}
111 output = os.popen("p4 users")
112 for line in output:
113 firstSpace = line.index(" ")
114 secondSpace = line.index(" ", firstSpace + 1)
115 key = line[:firstSpace]
116 email = line[firstSpace + 1:secondSpace]
117 openParenPos = line.index("(", secondSpace)
118 closedParenPos = line.index(")", openParenPos)
119 name = line[openParenPos + 1:closedParenPos]
121 users[key] = name + " " + email
123 return users
125 users = getUserMap()
127 output = os.popen("p4 changes %s...%s" % (prefix, changeRange)).readlines()
129 changes = []
130 for line in output:
131 changeNum = line.split(" ")[1]
132 changes.append(changeNum)
134 changes.reverse()
136 sys.stderr.write("\n")
138 tz = - time.timezone / 36
140 cnt = 1
141 for change in changes:
142 [ author, log, epoch, changedFiles, removedFiles ] = describe(change)
143 sys.stderr.write("\rimporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
144 cnt = cnt + 1
146 print "commit refs/heads/master"
147 if author in users:
148 print "committer %s %s %s" % (users[author], epoch, tz)
149 else:
150 print "committer %s <a@b> %s %s" % (author, epoch, tz)
151 print "data <<EOT"
152 for l in log:
153 print l[:len(l) - 1]
154 print "EOT"
156 print ""
158 for f in changedFiles:
159 if not f.startswith(prefix):
160 sys.stderr.write("\nchanged files: ignoring path %s outside of %s in change %s\n" % (f, prefix, change))
161 continue
162 relpath = f[len(prefix):]
164 [mode, fileSize] = p4Stat(f)
166 print "M %s inline %s" % (mode, stripRevision(relpath))
167 print "data %s" % fileSize
168 sys.stdout.flush();
169 os.system("p4 print -q \"%s\"" % f)
170 print ""
172 for f in removedFiles:
173 if not f.startswith(prefix):
174 sys.stderr.write("\ndeleted files: ignoring path %s outside of %s in change %s\n" % (f, prefix, change))
175 continue
176 relpath = f[len(prefix):]
177 print "D %s" % stripRevision(relpath)
179 print ""
181 sys.stderr.write("\n")