3 # This tool is copyright (c) 2006, Sean Estabrooks.
4 # It is released under the Gnu Public License, version 2.
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
17 from signal
import signal
, \
18 SIGPIPE
, SIGINT
, SIG_DFL
, \
21 signal(SIGPIPE
, SIG_DFL
)
22 s
= signal(SIGINT
, SIG_DFL
)
23 if s
!= default_int_handler
:
29 msg
= "%s %s" % (msg
, a
)
30 print "git-p4import fatal error:", msg
34 print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]"
39 ignore_warnings
= False
42 def report(level
, msg
, *args
):
46 msg
= "%s %s" % (msg
, a
)
47 fd
= open(logfile
, "a")
50 if level
<= verbosity
:
54 def __init__(self
, _repopath
):
58 if _repopath
[-1] == '/':
59 self
.repopath
= _repopath
[:-1]
61 self
.repopath
= _repopath
62 if self
.repopath
[-4:] != "/...":
63 self
.repopath
= "%s/..." % self
.repopath
64 f
=os
.popen('p4 -V 2>>%s'%logfile
, 'rb')
69 die("Could not find the \"p4\" command")
71 def p4(self
, cmd
, *args
):
73 cmd
= "%s %s" % (cmd
, ' '.join(args
))
75 f
=os
.popen('p4 -G %s 2>>%s' % (cmd
,logfile
), 'rb')
79 list.append(marshal
.load(f
))
85 def sync(self
, id, force
=False, trick
=False, test
=False):
87 ret
= self
.p4("sync -f %s@%s"%(self
.repopath
, id))[0]
89 ret
= self
.p4("sync -k %s@%s"%(self
.repopath
, id))[0]
91 ret
= self
.p4("sync -n %s@%s"%(self
.repopath
, id))[0]
93 ret
= self
.p4("sync %s@%s"%(self
.repopath
, id))[0]
94 if ret
['code'] == "error":
95 data
= ret
['data'].upper()
96 if data
.find('VIEW') > 0:
97 die("Perforce reports %s is not in client view"% self
.repopath
)
98 elif data
.find('UP-TO-DATE') < 0:
99 die("Could not sync files from perforce", self
.repopath
)
101 def changes(self
, since
=0):
104 for rec
in self
.p4("changes %s@%s,#head" % (self
.repopath
, since
+1)):
105 list.append(rec
['change'])
111 def authors(self
, filename
):
113 for l
in f
.readlines():
114 self
.userlist
[l
[:l
.find('=')].rstrip()] = \
115 (l
[l
.find('=')+1:l
.find('<')].rstrip(),l
[l
.find('<')+1:l
.find('>')])
117 for f
,e
in self
.userlist
.items():
118 report(2, f
, ":", e
[0], " <", e
[1], ">")
120 def _get_user(self
, id):
121 if not self
.userlist
.has_key(id):
123 user
= self
.p4("users", id)[0]
124 self
.userlist
[id] = (user
['FullName'], user
['Email'])
126 self
.userlist
[id] = (id, "")
127 return self
.userlist
[id]
129 def _format_date(self
, ticks
):
131 name
= time
.tzname
[0]
132 offset
= time
.timezone
134 name
= time
.tzname
[1]
135 offset
= time
.altzone
139 localo
= "%s%02d%02d %s" % (symbol
, offset
/ 3600, offset
% 3600, name
)
140 tickso
= time
.strftime("%a %b %d %H:%M:%S %Y", ticks
)
141 return "%s %s" % (tickso
, localo
)
145 return self
.p4("where %s" % self
.repopath
)[-1]['path']
149 def describe(self
, num
):
150 desc
= self
.p4("describe -s", num
)[0]
151 self
.msg
= desc
['desc']
152 self
.author
, self
.email
= self
._get
_user
(desc
['user'])
153 self
.date
= self
._format
_date
(time
.localtime(long(desc
['time'])))
159 self
.version
= self
.git("--version")[0][12:].rstrip()
161 die("Could not find the \"git\" command")
163 self
.gitdir
= self
.get_single("rev-parse --git-dir")
164 report(2, "gdir:", self
.gitdir
)
166 die("Not a git repository... did you forget to \"git init-db\" ?")
168 self
.cdup
= self
.get_single("rev-parse --show-cdup")
171 self
.topdir
= os
.getcwd()
172 report(2, "topdir:", self
.topdir
)
174 die("Could not find top git directory")
178 report(2, "GIT:", cmd
)
179 f
=os
.popen('git %s 2>>%s' % (cmd
,logfile
), 'rb')
184 def get_single(self
, cmd
):
185 return self
.git(cmd
)[0].rstrip()
187 def current_branch(self
):
189 testit
= self
.git("rev-parse --verify HEAD")[0]
190 return self
.git("symbolic-ref HEAD")[0][11:].rstrip()
194 def get_config(self
, variable
):
196 return self
.git("repo-config --get %s" % variable
)[0].rstrip()
200 def set_config(self
, variable
, value
):
202 self
.git("repo-config %s %s"%(variable
, value
) )
204 die("Could not set %s to " % variable
, value
)
206 def make_tag(self
, name
, head
):
207 self
.git("tag -f %s %s"%(name
,head
))
209 def top_change(self
, branch
):
211 a
=self
.get_single("name-rev --tags refs/heads/%s" % branch
)
212 loc
= a
.find(' tags/') + 6
213 if a
[loc
:loc
+3] != "p4/":
215 return int(a
[loc
+3:][:-2])
219 def update_index(self
):
220 self
.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
222 def checkout(self
, branch
):
223 self
.git("checkout %s" % branch
)
225 def repoint_head(self
, branch
):
226 self
.git("symbolic-ref HEAD refs/heads/%s" % branch
)
228 def remove_files(self
):
229 self
.git("ls-files | xargs rm")
231 def clean_directories(self
):
234 def fresh_branch(self
, branch
):
235 report(1, "Creating new branch", branch
)
236 self
.git("ls-files | xargs rm")
237 os
.remove(".git/index")
238 self
.repoint_head(branch
)
244 def commit(self
, author
, email
, date
, msg
, id):
250 current
= self
.get_single("rev-parse --verify HEAD")
255 tree
= self
.get_single("write-tree")
256 for r
,l
in [('DATE',date
),('NAME',author
),('EMAIL',email
)]:
257 os
.environ
['GIT_AUTHOR_%s'%r] = l
258 os
.environ
['GIT_COMMITTER_%s'%r] = l
259 commit
= self
.get_single("commit-tree %s %s < .msg" % (tree
,head
))
261 self
.make_tag("p4/%s"%id, commit
)
262 self
.git("update-ref HEAD %s %s" % (commit
, current
) )
266 opts
, args
= getopt
.getopt(sys
.argv
[1:], "qhvt:",
267 ["authors=","help","stitch=","timezone=","log=","ignore"])
268 except getopt
.GetoptError
:
278 if o
in ("-h", "--help"):
280 if o
in ("--ignore"):
281 ignore_warnings
= True
284 branch
=git
.current_branch()
287 if o
in ("-t", "--timezone"):
288 git
.set_config("perforce.timezone", a
)
289 if o
in ("--stitch"):
290 git
.set_config("perforce.%s.path" % branch
, a
)
296 if branch
== git
.current_branch():
297 die("Branch %s already exists!" % branch
)
298 report(1, "Setting perforce to ", args
[0])
299 git
.set_config("perforce.%s.path" % branch
, args
[0])
301 die("You must specify the perforce //depot/path and git branch")
303 p4path
= git
.get_config("perforce.%s.path" % branch
)
305 die("Do not know Perforce //depot/path for git branch", branch
)
307 p4
= p4_command(p4path
)
310 if o
in ("-a", "--authors"):
313 localdir
= git
.basedir()
314 if p4
.where()[:len(localdir
)] != localdir
:
315 report(1, "**WARNING** Appears p4 client is misconfigured")
316 report(1, " for sync from %s to %s" % (p4
.repopath
, localdir
))
317 if ignore_warnings
!= True:
318 die("Reconfigure or use \"--ignore\" on command line")
321 top
= git
.top_change(branch
)
324 changes
= p4
.changes(top
)
327 report(1, "Already up to date...")
330 ptz
= git
.get_config("perforce.timezone")
332 report(1, "Setting timezone to", ptz
)
333 os
.environ
['TZ'] = ptz
338 git
.clean_directories()
339 p4
.sync(changes
[0], force
=True)
340 elif top
== 0 and branch
!= git
.current_branch():
341 p4
.sync(changes
[0], test
=True)
342 report(1, "Creating new initial commit");
343 git
.fresh_branch(branch
)
344 p4
.sync(changes
[0], force
=True)
346 p4
.sync(changes
[0], trick
=True)
348 report(1, "processing %s changes from p4 (%s) to git (%s)" % (count
, p4
.repopath
, branch
))
350 report(1, "Importing changeset", id)
351 change
= p4
.describe(id)
353 git
.commit(change
.author
, change
.email
, change
.date
, change
.msg
, id)
355 git
.clean_directories()