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 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")
22 from signal
import signal
, \
23 SIGPIPE
, SIGINT
, SIG_DFL
, \
26 signal(SIGPIPE
, SIG_DFL
)
27 s
= signal(SIGINT
, SIG_DFL
)
28 if s
!= default_int_handler
:
33 msg
= "%s %s" % (msg
, a
)
34 print "git-p4import fatal error:", msg
38 print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]"
43 ignore_warnings
= False
47 def report(level
, msg
, *args
):
51 msg
= "%s %s" % (msg
, a
)
52 fd
= open(logfile
, "a")
55 if level
<= verbosity
:
59 def __init__(self
, _repopath
):
63 if _repopath
[-1] == '/':
64 self
.repopath
= _repopath
[:-1]
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')
74 die("Could not find the \"p4\" command")
76 def p4(self
, cmd
, *args
):
78 cmd
= "%s %s" % (cmd
, ' '.join(args
))
80 f
=os
.popen('p4 -G %s 2>>%s' % (cmd
,logfile
), 'rb')
84 list.append(marshal
.load(f
))
90 def sync(self
, id, force
=False, trick
=False, test
=False):
92 ret
= self
.p4("sync -f %s@%s"%(self
.repopath
, id))[0]
94 ret
= self
.p4("sync -k %s@%s"%(self
.repopath
, id))[0]
96 ret
= self
.p4("sync -n %s@%s"%(self
.repopath
, id))[0]
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
)
106 def changes(self
, since
=0):
109 for rec
in self
.p4("changes %s@%s,#head" % (self
.repopath
, since
+1)):
110 list.append(rec
['change'])
116 def authors(self
, 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('>')])
122 for f
,e
in self
.userlist
.items():
123 report(2, f
, ":", e
[0], " <", e
[1], ">")
125 def _get_user(self
, id):
126 if not self
.userlist
.has_key(id):
128 user
= self
.p4("users", id)[0]
129 self
.userlist
[id] = (user
['FullName'], user
['Email'])
131 self
.userlist
[id] = (id, "")
132 return self
.userlist
[id]
134 def _format_date(self
, ticks
):
136 name
= time
.tzname
[0]
137 offset
= time
.timezone
139 name
= time
.tzname
[1]
140 offset
= time
.altzone
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
)
150 return self
.p4("where %s" % self
.repopath
)[-1]['path']
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'])))
164 self
.version
= self
.git("--version")[0][12:].rstrip()
166 die("Could not find the \"git\" command")
168 self
.gitdir
= self
.get_single("rev-parse --git-dir")
169 report(2, "gdir:", self
.gitdir
)
171 die("Not a git repository... did you forget to \"git init\" ?")
173 self
.cdup
= self
.get_single("rev-parse --show-cdup")
176 self
.topdir
= os
.getcwd()
177 report(2, "topdir:", self
.topdir
)
179 die("Could not find top git directory")
183 report(2, "GIT:", cmd
)
184 f
=os
.popen('git %s 2>>%s' % (cmd
,logfile
), 'rb')
189 def get_single(self
, cmd
):
190 return self
.git(cmd
)[0].rstrip()
192 def current_branch(self
):
194 testit
= self
.git("rev-parse --verify HEAD")[0]
195 return self
.git("symbolic-ref HEAD")[0][11:].rstrip()
199 def get_config(self
, variable
):
201 return self
.git("config --get %s" % variable
)[0].rstrip()
205 def set_config(self
, variable
, value
):
207 self
.git("config %s %s"%(variable
, value
) )
209 die("Could not set %s to " % variable
, value
)
211 def make_tag(self
, name
, head
):
212 self
.git("tag -f %s %s"%(name
,head
))
214 def top_change(self
, branch
):
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/":
220 return int(a
[loc
+3:][:-2])
224 def update_index(self
):
225 self
.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
227 def checkout(self
, branch
):
228 self
.git("checkout %s" % branch
)
230 def repoint_head(self
, branch
):
231 self
.git("symbolic-ref HEAD refs/heads/%s" % branch
)
233 def remove_files(self
):
234 self
.git("ls-files | xargs rm")
236 def clean_directories(self
):
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
)
249 def commit(self
, author
, email
, date
, msg
, id):
255 current
= self
.get_single("rev-parse --verify 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
))
266 self
.make_tag("p4/%s"%id, commit
)
267 self
.git("update-ref HEAD %s %s" % (commit
, current
) )
270 opts
, args
= getopt
.getopt(sys
.argv
[1:], "qhvt:",
271 ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
272 except getopt
.GetoptError
:
282 if o
in ("--notags"):
284 if o
in ("-h", "--help"):
286 if o
in ("--ignore"):
287 ignore_warnings
= True
290 branch
=git
.current_branch()
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
)
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])
307 die("You must specify the perforce //depot/path and git branch")
309 p4path
= git
.get_config("perforce.%s.path" % branch
)
311 die("Do not know Perforce //depot/path for git branch", branch
)
313 p4
= p4_command(p4path
)
316 if o
in ("-a", "--authors"):
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")
327 top
= git
.top_change(branch
)
330 changes
= p4
.changes(top
)
333 report(1, "Already up to date...")
336 ptz
= git
.get_config("perforce.timezone")
338 report(1, "Setting timezone to", ptz
)
339 os
.environ
['TZ'] = ptz
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)
352 p4
.sync(changes
[0], trick
=True)
354 report(1, "processing %s changes from p4 (%s) to git (%s)" % (count
, p4
.repopath
, branch
))
356 report(1, "Importing changeset", id)
357 change
= p4
.describe(id)
360 git
.commit(change
.author
, change
.email
, change
.date
, change
.msg
, id)
362 git
.commit(change
.author
, change
.email
, change
.date
, change
.msg
, "import")
364 git
.clean_directories()