72a4fd70a5bc200c1758b4cb56e2dfa9a6523104
1#!/usr/bin/python
2#
3# p4-fast-export.py
4#
5# Author: Simon Hausmann <hausmann@kde.org>
6# License: MIT <http://www.opensource.org/licenses/mit-license.php>
7#
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
15#
16import os, string, sys, time
17
18if 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)
27
28prefix = sys.argv[1]
29changeRange = ""
30try:
31 atIdx = prefix.index("@")
32 changeRange = prefix[atIdx:]
33 prefix = prefix[0:atIdx]
34except ValueError:
35 changeRange = ""
36
37if not prefix.endswith("/"):
38 prefix += "/"
39
40def describe(change):
41 output = os.popen("p4 describe %s" % change).readlines()
42
43 firstLine = output[0]
44
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))
50
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 [], [], [], [], []
57
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 [], [], [], [], []
64
65 log = output[2:filesSection - 1]
66
67 lines = output[filesSection + 2:differencesSection - 1]
68
69 changed = []
70 removed = []
71
72 for line in lines:
73 # chop off "... " and trailing newline
74 line = line[4:len(line) - 1]
75
76 lastSpace = line.rfind(" ")
77 if lastSpace == -1:
78 sys.stderr.write("trouble parsing line %s, skipping!\n" % line)
79 continue
80
81 operation = line[lastSpace + 1:]
82 path = line[:lastSpace]
83
84 if operation == "delete":
85 removed.append(path)
86 else:
87 changed.append(path)
88
89 return author, log, epoch, changed, removed
90
91def p4cat(path):
92 return os.popen("p4 print -q \"%s\"" % path).read()
93
94def 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
104
105def stripRevision(path):
106 hashPos = path.rindex("#")
107 return path[:hashPos]
108
109def 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]
120
121 users[key] = name + " " + email
122
123 return users
124
125users = getUserMap()
126
127output = os.popen("p4 changes %s...%s" % (prefix, changeRange)).readlines()
128
129changes = []
130for line in output:
131 changeNum = line.split(" ")[1]
132 changes.append(changeNum)
133
134changes.reverse()
135
136sys.stderr.write("\n")
137
138tz = - time.timezone / 36
139
140cnt = 1
141for 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
145
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"
155
156 print ""
157
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):]
163
164 [mode, fileSize] = p4Stat(f)
165
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 ""
171
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)
178
179 print ""
180
181sys.stderr.write("\n")
182