]>
Commit | Line | Data |
---|---|---|
44211e8c | 1 | #!/usr/bin/env python |
1d84a604 SE |
2 | # |
3 | # This tool is copyright (c) 2006, Sean Estabrooks. | |
4 | # It is released under the Gnu Public License, version 2. | |
5 | # | |
6 | # Import Perforce branches into Git repositories. | |
7 | # Checking out the files is done by calling the standard p4 | |
8 | # client which you must have properly configured yourself | |
9 | # | |
10 | ||
11 | import marshal | |
12 | import os | |
13 | import sys | |
14 | import time | |
15 | import getopt | |
16 | ||
a33faf28 ER |
17 | if sys.hexversion < 0x02020000: |
18 | # The behavior of the marshal module changed significantly in 2.2 | |
19 | sys.stderr.write("git-p4import.py: requires Python 2.2 or later.\n") | |
20 | sys.exit(1) | |
21 | ||
1d84a604 SE |
22 | from signal import signal, \ |
23 | SIGPIPE, SIGINT, SIG_DFL, \ | |
24 | default_int_handler | |
25 | ||
26 | signal(SIGPIPE, SIG_DFL) | |
27 | s = signal(SIGINT, SIG_DFL) | |
28 | if s != default_int_handler: | |
29 | signal(SIGINT, s) | |
30 | ||
1d84a604 SE |
31 | def die(msg, *args): |
32 | for a in args: | |
33 | msg = "%s %s" % (msg, a) | |
34 | print "git-p4import fatal error:", msg | |
35 | sys.exit(1) | |
36 | ||
37 | def usage(): | |
38 | print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]" | |
39 | sys.exit(1) | |
40 | ||
41 | verbosity = 1 | |
42 | logfile = "/dev/null" | |
43 | ignore_warnings = False | |
44 | stitch = 0 | |
ada7781d | 45 | tagall = True |
1d84a604 SE |
46 | |
47 | def report(level, msg, *args): | |
48 | global verbosity | |
49 | global logfile | |
50 | for a in args: | |
51 | msg = "%s %s" % (msg, a) | |
52 | fd = open(logfile, "a") | |
53 | fd.writelines(msg) | |
54 | fd.close() | |
55 | if level <= verbosity: | |
56 | print msg | |
57 | ||
58 | class p4_command: | |
59 | def __init__(self, _repopath): | |
60 | try: | |
61 | global logfile | |
62 | self.userlist = {} | |
63 | if _repopath[-1] == '/': | |
64 | self.repopath = _repopath[:-1] | |
65 | else: | |
66 | self.repopath = _repopath | |
67 | if self.repopath[-4:] != "/...": | |
68 | self.repopath= "%s/..." % self.repopath | |
69 | f=os.popen('p4 -V 2>>%s'%logfile, 'rb') | |
70 | a = f.readlines() | |
71 | if f.close(): | |
72 | raise | |
73 | except: | |
74 | die("Could not find the \"p4\" command") | |
75 | ||
76 | def p4(self, cmd, *args): | |
77 | global logfile | |
78 | cmd = "%s %s" % (cmd, ' '.join(args)) | |
79 | report(2, "P4:", cmd) | |
80 | f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb') | |
81 | list = [] | |
82 | while 1: | |
83 | try: | |
84 | list.append(marshal.load(f)) | |
85 | except EOFError: | |
86 | break | |
87 | self.ret = f.close() | |
88 | return list | |
89 | ||
90 | def sync(self, id, force=False, trick=False, test=False): | |
91 | if force: | |
92 | ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0] | |
93 | elif trick: | |
94 | ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0] | |
95 | elif test: | |
96 | ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0] | |
97 | else: | |
98 | ret = self.p4("sync %s@%s"%(self.repopath, id))[0] | |
99 | if ret['code'] == "error": | |
100 | data = ret['data'].upper() | |
101 | if data.find('VIEW') > 0: | |
102 | die("Perforce reports %s is not in client view"% self.repopath) | |
103 | elif data.find('UP-TO-DATE') < 0: | |
104 | die("Could not sync files from perforce", self.repopath) | |
105 | ||
106 | def changes(self, since=0): | |
107 | try: | |
108 | list = [] | |
109 | for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)): | |
110 | list.append(rec['change']) | |
111 | list.reverse() | |
112 | return list | |
113 | except: | |
114 | return [] | |
115 | ||
116 | def authors(self, filename): | |
117 | f=open(filename) | |
118 | for l in f.readlines(): | |
119 | self.userlist[l[:l.find('=')].rstrip()] = \ | |
120 | (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')]) | |
121 | f.close() | |
122 | for f,e in self.userlist.items(): | |
123 | report(2, f, ":", e[0], " <", e[1], ">") | |
124 | ||
125 | def _get_user(self, id): | |
126 | if not self.userlist.has_key(id): | |
127 | try: | |
128 | user = self.p4("users", id)[0] | |
129 | self.userlist[id] = (user['FullName'], user['Email']) | |
130 | except: | |
131 | self.userlist[id] = (id, "") | |
132 | return self.userlist[id] | |
133 | ||
134 | def _format_date(self, ticks): | |
135 | symbol='+' | |
136 | name = time.tzname[0] | |
137 | offset = time.timezone | |
138 | if ticks[8]: | |
139 | name = time.tzname[1] | |
140 | offset = time.altzone | |
141 | if offset < 0: | |
142 | offset *= -1 | |
143 | symbol = '-' | |
144 | localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name) | |
145 | tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks) | |
146 | return "%s %s" % (tickso, localo) | |
147 | ||
148 | def where(self): | |
149 | try: | |
150 | return self.p4("where %s" % self.repopath)[-1]['path'] | |
151 | except: | |
152 | return "" | |
153 | ||
154 | def describe(self, num): | |
155 | desc = self.p4("describe -s", num)[0] | |
156 | self.msg = desc['desc'] | |
157 | self.author, self.email = self._get_user(desc['user']) | |
158 | self.date = self._format_date(time.localtime(long(desc['time']))) | |
159 | return self | |
160 | ||
161 | class git_command: | |
162 | def __init__(self): | |
163 | try: | |
164 | self.version = self.git("--version")[0][12:].rstrip() | |
165 | except: | |
166 | die("Could not find the \"git\" command") | |
167 | try: | |
168 | self.gitdir = self.get_single("rev-parse --git-dir") | |
169 | report(2, "gdir:", self.gitdir) | |
170 | except: | |
5c94f87e | 171 | die("Not a git repository... did you forget to \"git init\" ?") |
1d84a604 SE |
172 | try: |
173 | self.cdup = self.get_single("rev-parse --show-cdup") | |
174 | if self.cdup != "": | |
175 | os.chdir(self.cdup) | |
176 | self.topdir = os.getcwd() | |
177 | report(2, "topdir:", self.topdir) | |
178 | except: | |
179 | die("Could not find top git directory") | |
180 | ||
181 | def git(self, cmd): | |
182 | global logfile | |
183 | report(2, "GIT:", cmd) | |
184 | f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb') | |
185 | r=f.readlines() | |
186 | self.ret = f.close() | |
187 | return r | |
188 | ||
189 | def get_single(self, cmd): | |
190 | return self.git(cmd)[0].rstrip() | |
191 | ||
192 | def current_branch(self): | |
193 | try: | |
194 | testit = self.git("rev-parse --verify HEAD")[0] | |
195 | return self.git("symbolic-ref HEAD")[0][11:].rstrip() | |
196 | except: | |
197 | return None | |
198 | ||
199 | def get_config(self, variable): | |
200 | try: | |
e0d10e1c | 201 | return self.git("config --get %s" % variable)[0].rstrip() |
1d84a604 SE |
202 | except: |
203 | return None | |
204 | ||
205 | def set_config(self, variable, value): | |
206 | try: | |
e0d10e1c | 207 | self.git("config %s %s"%(variable, value) ) |
1d84a604 SE |
208 | except: |
209 | die("Could not set %s to " % variable, value) | |
210 | ||
211 | def make_tag(self, name, head): | |
212 | self.git("tag -f %s %s"%(name,head)) | |
213 | ||
214 | def top_change(self, branch): | |
215 | try: | |
216 | a=self.get_single("name-rev --tags refs/heads/%s" % branch) | |
217 | loc = a.find(' tags/') + 6 | |
218 | if a[loc:loc+3] != "p4/": | |
219 | raise | |
220 | return int(a[loc+3:][:-2]) | |
221 | except: | |
222 | return 0 | |
223 | ||
224 | def update_index(self): | |
225 | self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin") | |
226 | ||
227 | def checkout(self, branch): | |
228 | self.git("checkout %s" % branch) | |
229 | ||
230 | def repoint_head(self, branch): | |
231 | self.git("symbolic-ref HEAD refs/heads/%s" % branch) | |
232 | ||
233 | def remove_files(self): | |
234 | self.git("ls-files | xargs rm") | |
235 | ||
236 | def clean_directories(self): | |
237 | self.git("clean -d") | |
238 | ||
239 | def fresh_branch(self, branch): | |
240 | report(1, "Creating new branch", branch) | |
241 | self.git("ls-files | xargs rm") | |
242 | os.remove(".git/index") | |
243 | self.repoint_head(branch) | |
244 | self.git("clean -d") | |
245 | ||
246 | def basedir(self): | |
247 | return self.topdir | |
248 | ||
249 | def commit(self, author, email, date, msg, id): | |
250 | self.update_index() | |
251 | fd=open(".msg", "w") | |
252 | fd.writelines(msg) | |
253 | fd.close() | |
254 | try: | |
255 | current = self.get_single("rev-parse --verify HEAD") | |
256 | head = "-p HEAD" | |
257 | except: | |
258 | current = "" | |
259 | head = "" | |
260 | tree = self.get_single("write-tree") | |
261 | for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]: | |
262 | os.environ['GIT_AUTHOR_%s'%r] = l | |
263 | os.environ['GIT_COMMITTER_%s'%r] = l | |
264 | commit = self.get_single("commit-tree %s %s < .msg" % (tree,head)) | |
265 | os.remove(".msg") | |
266 | self.make_tag("p4/%s"%id, commit) | |
267 | self.git("update-ref HEAD %s %s" % (commit, current) ) | |
268 | ||
1d84a604 SE |
269 | try: |
270 | opts, args = getopt.getopt(sys.argv[1:], "qhvt:", | |
ada7781d | 271 | ["authors=","help","stitch=","timezone=","log=","ignore","notags"]) |
1d84a604 SE |
272 | except getopt.GetoptError: |
273 | usage() | |
274 | ||
275 | for o, a in opts: | |
276 | if o == "-q": | |
277 | verbosity = 0 | |
278 | if o == "-v": | |
279 | verbosity += 1 | |
280 | if o in ("--log"): | |
281 | logfile = a | |
ada7781d SE |
282 | if o in ("--notags"): |
283 | tagall = False | |
1d84a604 SE |
284 | if o in ("-h", "--help"): |
285 | usage() | |
286 | if o in ("--ignore"): | |
287 | ignore_warnings = True | |
288 | ||
289 | git = git_command() | |
290 | branch=git.current_branch() | |
291 | ||
292 | for o, a in opts: | |
293 | if o in ("-t", "--timezone"): | |
294 | git.set_config("perforce.timezone", a) | |
295 | if o in ("--stitch"): | |
296 | git.set_config("perforce.%s.path" % branch, a) | |
297 | stitch = 1 | |
298 | ||
299 | if len(args) == 2: | |
300 | branch = args[1] | |
301 | git.checkout(branch) | |
302 | if branch == git.current_branch(): | |
303 | die("Branch %s already exists!" % branch) | |
304 | report(1, "Setting perforce to ", args[0]) | |
305 | git.set_config("perforce.%s.path" % branch, args[0]) | |
306 | elif len(args) != 0: | |
307 | die("You must specify the perforce //depot/path and git branch") | |
308 | ||
309 | p4path = git.get_config("perforce.%s.path" % branch) | |
310 | if p4path == None: | |
311 | die("Do not know Perforce //depot/path for git branch", branch) | |
312 | ||
313 | p4 = p4_command(p4path) | |
314 | ||
315 | for o, a in opts: | |
316 | if o in ("-a", "--authors"): | |
317 | p4.authors(a) | |
318 | ||
319 | localdir = git.basedir() | |
320 | if p4.where()[:len(localdir)] != localdir: | |
321 | report(1, "**WARNING** Appears p4 client is misconfigured") | |
322 | report(1, " for sync from %s to %s" % (p4.repopath, localdir)) | |
323 | if ignore_warnings != True: | |
324 | die("Reconfigure or use \"--ignore\" on command line") | |
325 | ||
326 | if stitch == 0: | |
327 | top = git.top_change(branch) | |
328 | else: | |
329 | top = 0 | |
330 | changes = p4.changes(top) | |
331 | count = len(changes) | |
332 | if count == 0: | |
333 | report(1, "Already up to date...") | |
334 | sys.exit(0) | |
335 | ||
336 | ptz = git.get_config("perforce.timezone") | |
337 | if ptz: | |
338 | report(1, "Setting timezone to", ptz) | |
339 | os.environ['TZ'] = ptz | |
340 | time.tzset() | |
341 | ||
342 | if stitch == 1: | |
343 | git.remove_files() | |
344 | git.clean_directories() | |
345 | p4.sync(changes[0], force=True) | |
346 | elif top == 0 and branch != git.current_branch(): | |
347 | p4.sync(changes[0], test=True) | |
348 | report(1, "Creating new initial commit"); | |
349 | git.fresh_branch(branch) | |
350 | p4.sync(changes[0], force=True) | |
351 | else: | |
352 | p4.sync(changes[0], trick=True) | |
353 | ||
354 | report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch)) | |
355 | for id in changes: | |
356 | report(1, "Importing changeset", id) | |
357 | change = p4.describe(id) | |
358 | p4.sync(id) | |
ada7781d SE |
359 | if tagall : |
360 | git.commit(change.author, change.email, change.date, change.msg, id) | |
361 | else: | |
362 | git.commit(change.author, change.email, change.date, change.msg, "import") | |
1d84a604 SE |
363 | if stitch == 1: |
364 | git.clean_directories() | |
365 | stitch = 0 |