summaryrefslogtreecommitdiff
path: root/contrib/fast-import/p4-fast-export.py
blob: d1faa7c29040407fd80a8724f5c1e565ca00d24b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#!/usr/bin/python
#
# p4-fast-export.py
#
# Author: Simon Hausmann <hausmann@kde.org>
# License: MIT <http://www.opensource.org/licenses/mit-license.php>
#
# TODO:
#       - support integrations (at least p4i)
#       - support p4 submit (hah!)
#
import os, string, sys, time
import marshal, popen2, getopt

branch = "refs/heads/p4"
prefix = previousDepotPath = os.popen("git-repo-config --get p4.depotpath").read()
if len(prefix) != 0:
    prefix = prefix[:-1]

try:
    opts, args = getopt.getopt(sys.argv[1:], "", [ "branch=" ])
except getopt.GetoptError:
    print "fixme, syntax error"
    sys.exit(1)

for o, a in opts:
    if o == "--branch":
        branch = "refs/heads/" + a

if len(args) == 0 and len(prefix) != 0:
    print "[using previously specified depot path %s]" % prefix
elif len(args) != 1:
    print "usage: %s //depot/path[@revRange]" % sys.argv[0]
    print "\n    example:"
    print "    %s //depot/my/project/ -- to import the current head"
    print "    %s //depot/my/project/@all -- to import everything"
    print "    %s //depot/my/project/@1,6 -- to import only from revision 1 to 6"
    print ""
    print "    (a ... is not needed in the path p4 specification, it's added implicitly)"
    print ""
    sys.exit(1)
else:
    if len(prefix) != 0 and prefix != args[0]:
        print "previous import used depot path %s and now %s was specified. this doesn't work!" % (prefix, args[0])
        sys.exit(1)
    prefix = args[0]

changeRange = ""
revision = ""
users = {}
initialParent = ""

if prefix.find("@") != -1:
    atIdx = prefix.index("@")
    changeRange = prefix[atIdx:]
    if changeRange == "@all":
        changeRange = ""
    elif changeRange.find(",") == -1:
        revision = changeRange
        changeRange = ""
    prefix = prefix[0:atIdx]
elif prefix.find("#") != -1:
    hashIdx = prefix.index("#")
    revision = prefix[hashIdx:]
    prefix = prefix[0:hashIdx]
elif len(previousDepotPath) == 0:
    revision = "#head"

if prefix.endswith("..."):
    prefix = prefix[:-3]

if not prefix.endswith("/"):
    prefix += "/"

def p4CmdList(cmd):
    pipe = os.popen("p4 -G %s" % cmd, "rb")
    result = []
    try:
        while True:
            entry = marshal.load(pipe)
            result.append(entry)
    except EOFError:
        pass
    pipe.close()
    return result

def p4Cmd(cmd):
    list = p4CmdList(cmd)
    result = {}
    for entry in list:
        result.update(entry)
    return result;

def commit(details):
    global initialParent
    global users

    epoch = details["time"]
    author = details["user"]

    gitStream.write("commit %s\n" % branch)
    committer = ""
    if author in users:
        committer = "%s %s %s" % (users[author], epoch, tz)
    else:
        committer = "%s <a@b> %s %s" % (author, epoch, tz)

    gitStream.write("committer %s\n" % committer)

    gitStream.write("data <<EOT\n")
    gitStream.write(details["desc"])
    gitStream.write("\n[ imported from %s; change %s ]\n" % (prefix, details["change"]))
    gitStream.write("EOT\n\n")

    if len(initialParent) > 0:
        gitStream.write("from %s\n" % initialParent)
        initialParent = ""

    fnum = 0
    while details.has_key("depotFile%s" % fnum):
        path = details["depotFile%s" % fnum]
        if not path.startswith(prefix):
            print "\nchanged files: ignoring path %s outside of %s in change %s" % (path, prefix, change)
            fnum = fnum + 1
            continue

        rev = details["rev%s" % fnum]
        depotPath = path + "#" + rev
        relPath = path[len(prefix):]
        action = details["action%s" % fnum]

        if action == "delete":
            gitStream.write("D %s\n" % relPath)
        else:
            mode = 644
            if details["type%s" % fnum].startswith("x"):
                mode = 755

            data = os.popen("p4 print -q \"%s\"" % depotPath, "rb").read()

            gitStream.write("M %s inline %s\n" % (mode, relPath))
            gitStream.write("data %s\n" % len(data))
            gitStream.write(data)
            gitStream.write("\n")

        fnum = fnum + 1

    gitStream.write("\n")

    gitStream.write("tag p4/%s\n" % details["change"])
    gitStream.write("from %s\n" % branch);
    gitStream.write("tagger %s\n" % committer);
    gitStream.write("data 0\n\n")


def getUserMap():
    users = {}

    for output in p4CmdList("users"):
        if not output.has_key("User"):
            continue
        users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
    return users

users = getUserMap()

if len(changeRange) == 0:
    try:
        sout, sin, serr = popen2.popen3("git-name-rev --tags `git-rev-parse %s`" % branch)
        output = sout.read()
        tagIdx = output.index(" tags/p4/")
        caretIdx = output.index("^")
        rev = int(output[tagIdx + 9 : caretIdx]) + 1
        changeRange = "@%s,#head" % rev
        initialParent = os.popen("git-rev-parse %s" % branch).read()[:-1]
    except:
        pass

sys.stderr.write("\n")

tz = - time.timezone / 36
tzsign = ("%s" % tz)[0]
if tzsign != '+' and tzsign != '-':
    tz = "+" + ("%s" % tz)

gitOutput, gitStream, gitError = popen2.popen3("git-fast-import")

if len(revision) > 0:
    print "Doing initial import of %s from revision %s" % (prefix, revision)

    details = { "user" : "git perforce import user", "time" : int(time.time()) }
    details["desc"] = "Initial import of %s from the state at revision %s" % (prefix, revision)
    details["change"] = revision
    newestRevision = 0

    fileCnt = 0
    for info in p4CmdList("files %s...%s" % (prefix, revision)):
        change = int(info["change"])
        if change > newestRevision:
            newestRevision = change

        if info["action"] == "delete":
            continue

        for prop in [ "depotFile", "rev", "action", "type" ]:
            details["%s%s" % (prop, fileCnt)] = info[prop]

        fileCnt = fileCnt + 1

    details["change"] = newestRevision

    try:
        commit(details)
    except:
        print gitError.read()

else:
    output = os.popen("p4 changes %s...%s" % (prefix, changeRange)).readlines()

    changes = []
    for line in output:
        changeNum = line.split(" ")[1]
        changes.append(changeNum)

    changes.reverse()

    if len(changes) == 0:
        print "no changes to import!"
        sys.exit(1)

    cnt = 1
    for change in changes:
        description = p4Cmd("describe %s" % change)

        sys.stdout.write("\rimporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
        sys.stdout.flush()
        cnt = cnt + 1

        try:
            commit(description)
        except:
            print gitError.read()
            sys.exit(1)

print ""

gitStream.close()
gitOutput.close()
gitError.close()

os.popen("git-repo-config p4.depotpath %s" % prefix).read()

sys.exit(0)