git-p4: Remove --log-substitutions feature.
[git/git.git] / contrib / fast-import / git-p4
CommitLineData
86949eef
SH
1#!/usr/bin/env python
2#
3# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
4#
c8cbbee9
SH
5# Author: Simon Hausmann <simon@lst.de>
6# Copyright: 2007 Simon Hausmann <simon@lst.de>
83dce55a 7# 2007 Trolltech ASA
86949eef
SH
8# License: MIT <http://www.opensource.org/licenses/mit-license.php>
9#
10
08483580 11import optparse, sys, os, marshal, popen2, subprocess, shelve
25df95cc 12import tempfile, getopt, sha, os.path, time, platform
ce6f33c8 13import re
8b41a97f 14
b984733c 15from sets import Set;
4f5cf76a 16
4addad22 17verbose = False
86949eef 18
86dff6b6
HWN
19def die(msg):
20 if verbose:
21 raise Exception(msg)
22 else:
23 sys.stderr.write(msg + "\n")
24 sys.exit(1)
25
bce4c5fc 26def write_pipe(c, str):
4addad22 27 if verbose:
86dff6b6 28 sys.stderr.write('Writing pipe: %s\n' % c)
b016d397 29
bce4c5fc 30 pipe = os.popen(c, 'w')
b016d397 31 val = pipe.write(str)
bce4c5fc 32 if pipe.close():
86dff6b6 33 die('Command failed: %s' % c)
b016d397
HWN
34
35 return val
36
4addad22
HWN
37def read_pipe(c, ignore_error=False):
38 if verbose:
86dff6b6 39 sys.stderr.write('Reading pipe: %s\n' % c)
8b41a97f 40
bce4c5fc 41 pipe = os.popen(c, 'rb')
b016d397 42 val = pipe.read()
4addad22 43 if pipe.close() and not ignore_error:
86dff6b6 44 die('Command failed: %s' % c)
b016d397
HWN
45
46 return val
47
48
bce4c5fc 49def read_pipe_lines(c):
4addad22 50 if verbose:
86dff6b6 51 sys.stderr.write('Reading pipe: %s\n' % c)
b016d397 52 ## todo: check return status
bce4c5fc 53 pipe = os.popen(c, 'rb')
b016d397 54 val = pipe.readlines()
bce4c5fc 55 if pipe.close():
86dff6b6 56 die('Command failed: %s' % c)
b016d397
HWN
57
58 return val
caace111 59
6754a299 60def system(cmd):
4addad22 61 if verbose:
bb6e09b2 62 sys.stderr.write("executing %s\n" % cmd)
6754a299
HWN
63 if os.system(cmd) != 0:
64 die("command failed: %s" % cmd)
65
b9fc6ea9
DB
66def isP4Exec(kind):
67 """Determine if a Perforce 'kind' should have execute permission
68
69 'p4 help filetypes' gives a list of the types. If it starts with 'x',
70 or x follows one of a few letters. Otherwise, if there is an 'x' after
71 a plus sign, it is also executable"""
72 return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
73
c65b670e
CP
74def setP4ExecBit(file, mode):
75 # Reopens an already open file and changes the execute bit to match
76 # the execute bit setting in the passed in mode.
77
78 p4Type = "+x"
79
80 if not isModeExec(mode):
81 p4Type = getP4OpenedType(file)
82 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
83 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
84 if p4Type[-1] == "+":
85 p4Type = p4Type[0:-1]
86
87 system("p4 reopen -t %s %s" % (p4Type, file))
88
89def getP4OpenedType(file):
90 # Returns the perforce file type for the given file.
91
92 result = read_pipe("p4 opened %s" % file)
93 match = re.match(".*\((.+)\)$", result)
94 if match:
95 return match.group(1)
96 else:
97 die("Could not determine file type for %s" % file)
98
b43b0a3c
CP
99def diffTreePattern():
100 # This is a simple generator for the diff tree regex pattern. This could be
101 # a class variable if this and parseDiffTreeEntry were a part of a class.
102 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
103 while True:
104 yield pattern
105
106def parseDiffTreeEntry(entry):
107 """Parses a single diff tree entry into its component elements.
108
109 See git-diff-tree(1) manpage for details about the format of the diff
110 output. This method returns a dictionary with the following elements:
111
112 src_mode - The mode of the source file
113 dst_mode - The mode of the destination file
114 src_sha1 - The sha1 for the source file
115 dst_sha1 - The sha1 fr the destination file
116 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
117 status_score - The score for the status (applicable for 'C' and 'R'
118 statuses). This is None if there is no score.
119 src - The path for the source file.
120 dst - The path for the destination file. This is only present for
121 copy or renames. If it is not present, this is None.
122
123 If the pattern is not matched, None is returned."""
124
125 match = diffTreePattern().next().match(entry)
126 if match:
127 return {
128 'src_mode': match.group(1),
129 'dst_mode': match.group(2),
130 'src_sha1': match.group(3),
131 'dst_sha1': match.group(4),
132 'status': match.group(5),
133 'status_score': match.group(6),
134 'src': match.group(7),
135 'dst': match.group(10)
136 }
137 return None
138
c65b670e
CP
139def isModeExec(mode):
140 # Returns True if the given git mode represents an executable file,
141 # otherwise False.
142 return mode[-3:] == "755"
143
144def isModeExecChanged(src_mode, dst_mode):
145 return isModeExec(src_mode) != isModeExec(dst_mode)
146
9f90c733 147def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
86949eef 148 cmd = "p4 -G %s" % cmd
6a49f8e2
HWN
149 if verbose:
150 sys.stderr.write("Opening pipe: %s\n" % cmd)
9f90c733
SL
151
152 # Use a temporary file to avoid deadlocks without
153 # subprocess.communicate(), which would put another copy
154 # of stdout into memory.
155 stdin_file = None
156 if stdin is not None:
157 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
158 stdin_file.write(stdin)
159 stdin_file.flush()
160 stdin_file.seek(0)
161
162 p4 = subprocess.Popen(cmd, shell=True,
163 stdin=stdin_file,
164 stdout=subprocess.PIPE)
86949eef
SH
165
166 result = []
167 try:
168 while True:
9f90c733 169 entry = marshal.load(p4.stdout)
86949eef
SH
170 result.append(entry)
171 except EOFError:
172 pass
9f90c733
SL
173 exitCode = p4.wait()
174 if exitCode != 0:
ac3e0d79
SH
175 entry = {}
176 entry["p4ExitCode"] = exitCode
177 result.append(entry)
86949eef
SH
178
179 return result
180
181def p4Cmd(cmd):
182 list = p4CmdList(cmd)
183 result = {}
184 for entry in list:
185 result.update(entry)
186 return result;
187
cb2c9db5
SH
188def p4Where(depotPath):
189 if not depotPath.endswith("/"):
190 depotPath += "/"
191 output = p4Cmd("where %s..." % depotPath)
dc524036
SH
192 if output["code"] == "error":
193 return ""
cb2c9db5
SH
194 clientPath = ""
195 if "path" in output:
196 clientPath = output.get("path")
197 elif "data" in output:
198 data = output.get("data")
199 lastSpace = data.rfind(" ")
200 clientPath = data[lastSpace + 1:]
201
202 if clientPath.endswith("..."):
203 clientPath = clientPath[:-3]
204 return clientPath
205
86949eef 206def currentGitBranch():
b25b2065 207 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
86949eef 208
4f5cf76a 209def isValidGitDir(path):
bb6e09b2
HWN
210 if (os.path.exists(path + "/HEAD")
211 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
4f5cf76a
SH
212 return True;
213 return False
214
463e8af6 215def parseRevision(ref):
b25b2065 216 return read_pipe("git rev-parse %s" % ref).strip()
463e8af6 217
6ae8de88
SH
218def extractLogMessageFromGitCommit(commit):
219 logMessage = ""
b016d397
HWN
220
221 ## fixme: title is first line of commit, not 1st paragraph.
6ae8de88 222 foundTitle = False
b016d397 223 for log in read_pipe_lines("git cat-file commit %s" % commit):
6ae8de88
SH
224 if not foundTitle:
225 if len(log) == 1:
1c094184 226 foundTitle = True
6ae8de88
SH
227 continue
228
229 logMessage += log
230 return logMessage
231
bb6e09b2 232def extractSettingsGitLog(log):
6ae8de88
SH
233 values = {}
234 for line in log.split("\n"):
235 line = line.strip()
6326aa58
HWN
236 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
237 if not m:
238 continue
239
240 assignments = m.group(1).split (':')
241 for a in assignments:
242 vals = a.split ('=')
243 key = vals[0].strip()
244 val = ('='.join (vals[1:])).strip()
245 if val.endswith ('\"') and val.startswith('"'):
246 val = val[1:-1]
247
248 values[key] = val
249
845b42cb
SH
250 paths = values.get("depot-paths")
251 if not paths:
252 paths = values.get("depot-path")
a3fdd579
SH
253 if paths:
254 values['depot-paths'] = paths.split(',')
bb6e09b2 255 return values
6ae8de88 256
8136a639 257def gitBranchExists(branch):
bb6e09b2
HWN
258 proc = subprocess.Popen(["git", "rev-parse", branch],
259 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
caace111 260 return proc.wait() == 0;
8136a639 261
01265103 262def gitConfig(key):
4addad22 263 return read_pipe("git config %s" % key, ignore_error=True).strip()
01265103 264
062410bb
SH
265def p4BranchesInGit(branchesAreInRemotes = True):
266 branches = {}
267
268 cmdline = "git rev-parse --symbolic "
269 if branchesAreInRemotes:
270 cmdline += " --remotes"
271 else:
272 cmdline += " --branches"
273
274 for line in read_pipe_lines(cmdline):
275 line = line.strip()
276
277 ## only import to p4/
278 if not line.startswith('p4/') or line == "p4/HEAD":
279 continue
280 branch = line
281
282 # strip off p4
283 branch = re.sub ("^p4/", "", line)
284
285 branches[branch] = parseRevision(line)
286 return branches
287
9ceab363 288def findUpstreamBranchPoint(head = "HEAD"):
86506fe5
SH
289 branches = p4BranchesInGit()
290 # map from depot-path to branch name
291 branchByDepotPath = {}
292 for branch in branches.keys():
293 tip = branches[branch]
294 log = extractLogMessageFromGitCommit(tip)
295 settings = extractSettingsGitLog(log)
296 if settings.has_key("depot-paths"):
297 paths = ",".join(settings["depot-paths"])
298 branchByDepotPath[paths] = "remotes/p4/" + branch
299
27d2d811 300 settings = None
27d2d811
SH
301 parent = 0
302 while parent < 65535:
9ceab363 303 commit = head + "~%s" % parent
27d2d811
SH
304 log = extractLogMessageFromGitCommit(commit)
305 settings = extractSettingsGitLog(log)
86506fe5
SH
306 if settings.has_key("depot-paths"):
307 paths = ",".join(settings["depot-paths"])
308 if branchByDepotPath.has_key(paths):
309 return [branchByDepotPath[paths], settings]
27d2d811 310
86506fe5 311 parent = parent + 1
27d2d811 312
86506fe5 313 return ["", settings]
27d2d811 314
5ca44617
SH
315def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
316 if not silent:
317 print ("Creating/updating branch(es) in %s based on origin branch(es)"
318 % localRefPrefix)
319
320 originPrefix = "origin/p4/"
321
322 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
323 line = line.strip()
324 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
325 continue
326
327 headName = line[len(originPrefix):]
328 remoteHead = localRefPrefix + headName
329 originHead = line
330
331 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
332 if (not original.has_key('depot-paths')
333 or not original.has_key('change')):
334 continue
335
336 update = False
337 if not gitBranchExists(remoteHead):
338 if verbose:
339 print "creating %s" % remoteHead
340 update = True
341 else:
342 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
343 if settings.has_key('change') > 0:
344 if settings['depot-paths'] == original['depot-paths']:
345 originP4Change = int(original['change'])
346 p4Change = int(settings['change'])
347 if originP4Change > p4Change:
348 print ("%s (%s) is newer than %s (%s). "
349 "Updating p4 branch from origin."
350 % (originHead, originP4Change,
351 remoteHead, p4Change))
352 update = True
353 else:
354 print ("Ignoring: %s was imported from %s while "
355 "%s was imported from %s"
356 % (originHead, ','.join(original['depot-paths']),
357 remoteHead, ','.join(settings['depot-paths'])))
358
359 if update:
360 system("git update-ref %s %s" % (remoteHead, originHead))
361
362def originP4BranchesExist():
363 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
364
4f6432d8
SH
365def p4ChangesForPaths(depotPaths, changeRange):
366 assert depotPaths
367 output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, changeRange)
368 for p in depotPaths]))
369
370 changes = []
371 for line in output:
372 changeNum = line.split(" ")[1]
373 changes.append(int(changeNum))
374
375 changes.sort()
376 return changes
377
b984733c
SH
378class Command:
379 def __init__(self):
380 self.usage = "usage: %prog [options]"
8910ac0e 381 self.needsGit = True
b984733c
SH
382
383class P4Debug(Command):
86949eef 384 def __init__(self):
6ae8de88 385 Command.__init__(self)
86949eef 386 self.options = [
b1ce9447
HWN
387 optparse.make_option("--verbose", dest="verbose", action="store_true",
388 default=False),
4addad22 389 ]
c8c39116 390 self.description = "A tool to debug the output of p4 -G."
8910ac0e 391 self.needsGit = False
b1ce9447 392 self.verbose = False
86949eef
SH
393
394 def run(self, args):
b1ce9447 395 j = 0
86949eef 396 for output in p4CmdList(" ".join(args)):
b1ce9447
HWN
397 print 'Element: %d' % j
398 j += 1
86949eef 399 print output
b984733c 400 return True
86949eef 401
5834684d
SH
402class P4RollBack(Command):
403 def __init__(self):
404 Command.__init__(self)
405 self.options = [
0c66a783
SH
406 optparse.make_option("--verbose", dest="verbose", action="store_true"),
407 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
5834684d
SH
408 ]
409 self.description = "A tool to debug the multi-branch import. Don't use :)"
52102d47 410 self.verbose = False
0c66a783 411 self.rollbackLocalBranches = False
5834684d
SH
412
413 def run(self, args):
414 if len(args) != 1:
415 return False
416 maxChange = int(args[0])
0c66a783 417
ad192f28 418 if "p4ExitCode" in p4Cmd("changes -m 1"):
66a2f523
SH
419 die("Problems executing p4");
420
0c66a783
SH
421 if self.rollbackLocalBranches:
422 refPrefix = "refs/heads/"
b016d397 423 lines = read_pipe_lines("git rev-parse --symbolic --branches")
0c66a783
SH
424 else:
425 refPrefix = "refs/remotes/"
b016d397 426 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
0c66a783
SH
427
428 for line in lines:
429 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
b25b2065
HWN
430 line = line.strip()
431 ref = refPrefix + line
5834684d 432 log = extractLogMessageFromGitCommit(ref)
bb6e09b2
HWN
433 settings = extractSettingsGitLog(log)
434
435 depotPaths = settings['depot-paths']
436 change = settings['change']
437
5834684d 438 changed = False
52102d47 439
6326aa58
HWN
440 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
441 for p in depotPaths]))) == 0:
52102d47
SH
442 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
443 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
444 continue
445
bb6e09b2 446 while change and int(change) > maxChange:
5834684d 447 changed = True
52102d47
SH
448 if self.verbose:
449 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
5834684d
SH
450 system("git update-ref %s \"%s^\"" % (ref, ref))
451 log = extractLogMessageFromGitCommit(ref)
bb6e09b2
HWN
452 settings = extractSettingsGitLog(log)
453
454
455 depotPaths = settings['depot-paths']
456 change = settings['change']
5834684d
SH
457
458 if changed:
52102d47 459 print "%s rewound to %s" % (ref, change)
5834684d
SH
460
461 return True
462
711544b0 463class P4Submit(Command):
4f5cf76a 464 def __init__(self):
b984733c 465 Command.__init__(self)
4f5cf76a
SH
466 self.options = [
467 optparse.make_option("--continue", action="store_false", dest="firstTime"),
4addad22 468 optparse.make_option("--verbose", dest="verbose", action="store_true"),
4f5cf76a
SH
469 optparse.make_option("--origin", dest="origin"),
470 optparse.make_option("--reset", action="store_true", dest="reset"),
c1b296b9 471 optparse.make_option("--direct", dest="directSubmit", action="store_true"),
d9a5f25b 472 optparse.make_option("-M", dest="detectRename", action="store_true"),
4f5cf76a
SH
473 ]
474 self.description = "Submit changes from git to the perforce depot."
c9b50e63 475 self.usage += " [name of git branch to submit into perforce depot]"
4f5cf76a
SH
476 self.firstTime = True
477 self.reset = False
478 self.interactive = True
4f5cf76a 479 self.firstTime = True
9512497b 480 self.origin = ""
c1b296b9 481 self.directSubmit = False
d9a5f25b 482 self.detectRename = False
b0d10df7 483 self.verbose = False
f7baba8b 484 self.isWindows = (platform.system() == "Windows")
4f5cf76a
SH
485
486 self.logSubstitutions = {}
487 self.logSubstitutions["<enter description here>"] = "%log%"
488 self.logSubstitutions["\tDetails:"] = "\tDetails: %log%"
489
490 def check(self):
491 if len(p4CmdList("opened ...")) > 0:
492 die("You have files opened with perforce! Close them before starting the sync.")
493
494 def start(self):
495 if len(self.config) > 0 and not self.reset:
cebdf5af
HWN
496 die("Cannot start sync. Previous sync config found at %s\n"
497 "If you want to start submitting again from scratch "
498 "maybe you want to call git-p4 submit --reset" % self.configFile)
4f5cf76a
SH
499
500 commits = []
c1b296b9
SH
501 if self.directSubmit:
502 commits.append("0")
503 else:
b016d397 504 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
b25b2065 505 commits.append(line.strip())
c1b296b9 506 commits.reverse()
4f5cf76a
SH
507
508 self.config["commits"] = commits
509
4f5cf76a
SH
510 def prepareLogMessage(self, template, message):
511 result = ""
512
513 for line in template.split("\n"):
514 if line.startswith("#"):
515 result += line + "\n"
516 continue
517
518 substituted = False
519 for key in self.logSubstitutions.keys():
520 if line.find(key) != -1:
521 value = self.logSubstitutions[key]
522 value = value.replace("%log%", message)
523 if value != "@remove@":
524 result += line.replace(key, value) + "\n"
525 substituted = True
526 break
527
528 if not substituted:
529 result += line + "\n"
530
531 return result
532
ea99c3ae
SH
533 def prepareSubmitTemplate(self):
534 # remove lines in the Files section that show changes to files outside the depot path we're committing into
535 template = ""
536 inFilesSection = False
537 for line in read_pipe_lines("p4 change -o"):
538 if inFilesSection:
539 if line.startswith("\t"):
540 # path starts and ends with a tab
541 path = line[1:]
542 lastTab = path.rfind("\t")
543 if lastTab != -1:
544 path = path[:lastTab]
545 if not path.startswith(self.depotPath):
546 continue
547 else:
548 inFilesSection = False
549 else:
550 if line.startswith("Files:"):
551 inFilesSection = True
552
553 template += line
554
555 return template
556
7cb5cbef 557 def applyCommit(self, id):
c1b296b9
SH
558 if self.directSubmit:
559 print "Applying local change in working directory/index"
560 diff = self.diffStatus
561 else:
b016d397 562 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
d9a5f25b 563 diffOpts = ("", "-M")[self.detectRename]
b43b0a3c 564 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
4f5cf76a
SH
565 filesToAdd = set()
566 filesToDelete = set()
d336c158 567 editedFiles = set()
c65b670e 568 filesToChangeExecBit = {}
4f5cf76a 569 for line in diff:
b43b0a3c
CP
570 diff = parseDiffTreeEntry(line)
571 modifier = diff['status']
572 path = diff['src']
4f5cf76a 573 if modifier == "M":
d336c158 574 system("p4 edit \"%s\"" % path)
c65b670e
CP
575 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
576 filesToChangeExecBit[path] = diff['dst_mode']
d336c158 577 editedFiles.add(path)
4f5cf76a
SH
578 elif modifier == "A":
579 filesToAdd.add(path)
c65b670e 580 filesToChangeExecBit[path] = diff['dst_mode']
4f5cf76a
SH
581 if path in filesToDelete:
582 filesToDelete.remove(path)
583 elif modifier == "D":
584 filesToDelete.add(path)
585 if path in filesToAdd:
586 filesToAdd.remove(path)
d9a5f25b 587 elif modifier == "R":
b43b0a3c 588 src, dest = diff['src'], diff['dst']
d9a5f25b
CP
589 system("p4 integrate -Dt \"%s\" \"%s\"" % (src, dest))
590 system("p4 edit \"%s\"" % (dest))
c65b670e
CP
591 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
592 filesToChangeExecBit[dest] = diff['dst_mode']
d9a5f25b
CP
593 os.unlink(dest)
594 editedFiles.add(dest)
595 filesToDelete.add(src)
4f5cf76a
SH
596 else:
597 die("unknown modifier %s for %s" % (modifier, path))
598
c1b296b9
SH
599 if self.directSubmit:
600 diffcmd = "cat \"%s\"" % self.diffFile
601 else:
602 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
47a130b7 603 patchcmd = diffcmd + " | git apply "
c1b296b9
SH
604 tryPatchCmd = patchcmd + "--check -"
605 applyPatchCmd = patchcmd + "--check --apply -"
51a2640a 606
47a130b7 607 if os.system(tryPatchCmd) != 0:
51a2640a
SH
608 print "Unfortunately applying the change failed!"
609 print "What do you want to do?"
610 response = "x"
611 while response != "s" and response != "a" and response != "w":
cebdf5af
HWN
612 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
613 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
51a2640a
SH
614 if response == "s":
615 print "Skipping! Good luck with the next patches..."
20947149
SH
616 for f in editedFiles:
617 system("p4 revert \"%s\"" % f);
618 for f in filesToAdd:
619 system("rm %s" %f)
51a2640a
SH
620 return
621 elif response == "a":
47a130b7 622 os.system(applyPatchCmd)
51a2640a
SH
623 if len(filesToAdd) > 0:
624 print "You may also want to call p4 add on the following files:"
625 print " ".join(filesToAdd)
626 if len(filesToDelete):
627 print "The following files should be scheduled for deletion with p4 delete:"
628 print " ".join(filesToDelete)
cebdf5af
HWN
629 die("Please resolve and submit the conflict manually and "
630 + "continue afterwards with git-p4 submit --continue")
51a2640a
SH
631 elif response == "w":
632 system(diffcmd + " > patch.txt")
633 print "Patch saved to patch.txt in %s !" % self.clientPath
cebdf5af
HWN
634 die("Please resolve and submit the conflict manually and "
635 "continue afterwards with git-p4 submit --continue")
51a2640a 636
47a130b7 637 system(applyPatchCmd)
4f5cf76a
SH
638
639 for f in filesToAdd:
e6b711f0 640 system("p4 add \"%s\"" % f)
4f5cf76a 641 for f in filesToDelete:
e6b711f0
SH
642 system("p4 revert \"%s\"" % f)
643 system("p4 delete \"%s\"" % f)
4f5cf76a 644
c65b670e
CP
645 # Set/clear executable bits
646 for f in filesToChangeExecBit.keys():
647 mode = filesToChangeExecBit[f]
648 setP4ExecBit(f, mode)
649
c1b296b9
SH
650 logMessage = ""
651 if not self.directSubmit:
652 logMessage = extractLogMessageFromGitCommit(id)
653 logMessage = logMessage.replace("\n", "\n\t")
f7baba8b
MSO
654 if self.isWindows:
655 logMessage = logMessage.replace("\n", "\r\n")
b25b2065 656 logMessage = logMessage.strip()
4f5cf76a 657
ea99c3ae 658 template = self.prepareSubmitTemplate()
4f5cf76a
SH
659
660 if self.interactive:
661 submitTemplate = self.prepareLogMessage(template, logMessage)
b016d397 662 diff = read_pipe("p4 diff -du ...")
4f5cf76a
SH
663
664 for newFile in filesToAdd:
665 diff += "==== new file ====\n"
666 diff += "--- /dev/null\n"
667 diff += "+++ %s\n" % newFile
668 f = open(newFile, "r")
669 for line in f.readlines():
670 diff += "+" + line
671 f.close()
672
25df95cc
SH
673 separatorLine = "######## everything below this line is just the diff #######"
674 if platform.system() == "Windows":
675 separatorLine += "\r"
676 separatorLine += "\n"
4f5cf76a 677
e96e400f
SH
678 [handle, fileName] = tempfile.mkstemp()
679 tmpFile = os.fdopen(handle, "w+")
680 tmpFile.write(submitTemplate + separatorLine + diff)
681 tmpFile.close()
682 defaultEditor = "vi"
683 if platform.system() == "Windows":
684 defaultEditor = "notepad"
685 editor = os.environ.get("EDITOR", defaultEditor);
686 system(editor + " " + fileName)
687 tmpFile = open(fileName, "rb")
688 message = tmpFile.read()
689 tmpFile.close()
690 os.remove(fileName)
691 submitTemplate = message[:message.index(separatorLine)]
692 if self.isWindows:
693 submitTemplate = submitTemplate.replace("\r\n", "\n")
694
695 if self.directSubmit:
696 print "Submitting to git first"
697 os.chdir(self.oldWorkingDirectory)
698 write_pipe("git commit -a -F -", submitTemplate)
699 os.chdir(self.clientPath)
700
701 write_pipe("p4 submit -i", submitTemplate)
4f5cf76a
SH
702 else:
703 fileName = "submit.txt"
704 file = open(fileName, "w+")
705 file.write(self.prepareLogMessage(template, logMessage))
706 file.close()
cebdf5af
HWN
707 print ("Perforce submit template written as %s. "
708 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
709 % (fileName, fileName))
4f5cf76a
SH
710
711 def run(self, args):
c9b50e63
SH
712 if len(args) == 0:
713 self.master = currentGitBranch()
4280e533 714 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
c9b50e63
SH
715 die("Detecting current git branch failed!")
716 elif len(args) == 1:
717 self.master = args[0]
718 else:
719 return False
720
27d2d811 721 [upstream, settings] = findUpstreamBranchPoint()
ea99c3ae 722 self.depotPath = settings['depot-paths'][0]
27d2d811
SH
723 if len(self.origin) == 0:
724 self.origin = upstream
a3fdd579
SH
725
726 if self.verbose:
727 print "Origin branch is " + self.origin
9512497b 728
ea99c3ae 729 if len(self.depotPath) == 0:
9512497b
SH
730 print "Internal error: cannot locate perforce depot path from existing branches"
731 sys.exit(128)
732
ea99c3ae 733 self.clientPath = p4Where(self.depotPath)
9512497b 734
51a2640a 735 if len(self.clientPath) == 0:
ea99c3ae 736 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
9512497b
SH
737 sys.exit(128)
738
ea99c3ae 739 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
7944f142 740 self.oldWorkingDirectory = os.getcwd()
c1b296b9
SH
741
742 if self.directSubmit:
b016d397 743 self.diffStatus = read_pipe_lines("git diff -r --name-status HEAD")
cbf5efa6
SH
744 if len(self.diffStatus) == 0:
745 print "No changes in working directory to submit."
746 return True
b016d397 747 patch = read_pipe("git diff -p --binary --diff-filter=ACMRTUXB HEAD")
b86f7378 748 self.diffFile = self.gitdir + "/p4-git-diff"
c1b296b9
SH
749 f = open(self.diffFile, "wb")
750 f.write(patch)
751 f.close();
752
51a2640a 753 os.chdir(self.clientPath)
31f9ec12
SH
754 print "Syncronizing p4 checkout..."
755 system("p4 sync ...")
9512497b 756
4f5cf76a
SH
757 if self.reset:
758 self.firstTime = True
759
4f5cf76a 760 self.check()
b86f7378 761 self.configFile = self.gitdir + "/p4-git-sync.cfg"
4f5cf76a
SH
762 self.config = shelve.open(self.configFile, writeback=True)
763
764 if self.firstTime:
765 self.start()
766
767 commits = self.config.get("commits", [])
768
769 while len(commits) > 0:
770 self.firstTime = False
771 commit = commits[0]
772 commits = commits[1:]
773 self.config["commits"] = commits
7cb5cbef 774 self.applyCommit(commit)
4f5cf76a
SH
775 if not self.interactive:
776 break
777
778 self.config.close()
779
c1b296b9
SH
780 if self.directSubmit:
781 os.remove(self.diffFile)
782
4f5cf76a
SH
783 if len(commits) == 0:
784 if self.firstTime:
785 print "No changes found to apply between %s and current HEAD" % self.origin
786 else:
787 print "All changes applied!"
7944f142 788 os.chdir(self.oldWorkingDirectory)
14594f4b
SH
789
790 sync = P4Sync()
791 sync.run([])
792
e96e400f
SH
793 rebase = P4Rebase()
794 rebase.rebase()
4f5cf76a
SH
795 os.remove(self.configFile)
796
b984733c
SH
797 return True
798
711544b0 799class P4Sync(Command):
b984733c
SH
800 def __init__(self):
801 Command.__init__(self)
802 self.options = [
803 optparse.make_option("--branch", dest="branch"),
804 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
805 optparse.make_option("--changesfile", dest="changesFile"),
806 optparse.make_option("--silent", dest="silent", action="store_true"),
ef48f909 807 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
a028a98e 808 optparse.make_option("--verbose", dest="verbose", action="store_true"),
d2c6dd30
HWN
809 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
810 help="Import into refs/heads/ , not refs/remotes"),
8b41a97f 811 optparse.make_option("--max-changes", dest="maxChanges"),
86dff6b6
HWN
812 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
813 help="Keep entire BRANCH/DIR/SUBDIR prefix during import")
b984733c
SH
814 ]
815 self.description = """Imports from Perforce into a git repository.\n
816 example:
817 //depot/my/project/ -- to import the current head
818 //depot/my/project/@all -- to import everything
819 //depot/my/project/@1,6 -- to import only from revision 1 to 6
820
821 (a ... is not needed in the path p4 specification, it's added implicitly)"""
822
823 self.usage += " //depot/path[@revRange]"
b984733c 824 self.silent = False
b984733c
SH
825 self.createdBranches = Set()
826 self.committedChanges = Set()
569d1bd4 827 self.branch = ""
b984733c 828 self.detectBranches = False
cb53e1f8 829 self.detectLabels = False
b984733c 830 self.changesFile = ""
01265103 831 self.syncWithOrigin = True
4b97ffb1 832 self.verbose = False
a028a98e 833 self.importIntoRemotes = True
01a9c9c5 834 self.maxChanges = ""
c1f9197f 835 self.isWindows = (platform.system() == "Windows")
8b41a97f 836 self.keepRepoPath = False
6326aa58 837 self.depotPaths = None
3c699645 838 self.p4BranchesInGit = []
354081d5 839 self.cloneExclude = []
b984733c 840
01265103
SH
841 if gitConfig("git-p4.syncFromOrigin") == "false":
842 self.syncWithOrigin = False
843
b984733c 844 def extractFilesFromCommit(self, commit):
354081d5
TT
845 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
846 for path in self.cloneExclude]
b984733c
SH
847 files = []
848 fnum = 0
849 while commit.has_key("depotFile%s" % fnum):
850 path = commit["depotFile%s" % fnum]
6326aa58 851
354081d5
TT
852 if [p for p in self.cloneExclude
853 if path.startswith (p)]:
854 found = False
855 else:
856 found = [p for p in self.depotPaths
857 if path.startswith (p)]
6326aa58 858 if not found:
b984733c
SH
859 fnum = fnum + 1
860 continue
861
862 file = {}
863 file["path"] = path
864 file["rev"] = commit["rev%s" % fnum]
865 file["action"] = commit["action%s" % fnum]
866 file["type"] = commit["type%s" % fnum]
867 files.append(file)
868 fnum = fnum + 1
869 return files
870
6326aa58 871 def stripRepoPath(self, path, prefixes):
8b41a97f 872 if self.keepRepoPath:
6326aa58
HWN
873 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
874
875 for p in prefixes:
876 if path.startswith(p):
877 path = path[len(p):]
8b41a97f 878
6326aa58 879 return path
6754a299 880
71b112d4 881 def splitFilesIntoBranches(self, commit):
d5904674 882 branches = {}
71b112d4
SH
883 fnum = 0
884 while commit.has_key("depotFile%s" % fnum):
885 path = commit["depotFile%s" % fnum]
6326aa58
HWN
886 found = [p for p in self.depotPaths
887 if path.startswith (p)]
888 if not found:
71b112d4
SH
889 fnum = fnum + 1
890 continue
891
892 file = {}
893 file["path"] = path
894 file["rev"] = commit["rev%s" % fnum]
895 file["action"] = commit["action%s" % fnum]
896 file["type"] = commit["type%s" % fnum]
897 fnum = fnum + 1
898
6326aa58 899 relPath = self.stripRepoPath(path, self.depotPaths)
b984733c 900
4b97ffb1 901 for branch in self.knownBranches.keys():
6754a299
HWN
902
903 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
904 if relPath.startswith(branch + "/"):
d5904674
SH
905 if branch not in branches:
906 branches[branch] = []
71b112d4 907 branches[branch].append(file)
6555b2cc 908 break
b984733c
SH
909
910 return branches
911
6a49f8e2
HWN
912 ## Should move this out, doesn't use SELF.
913 def readP4Files(self, files):
b1ce9447 914 files = [f for f in files
982bb8a3 915 if f['action'] != 'delete']
6a49f8e2 916
b1ce9447 917 if not files:
f2eda79f
HWN
918 return
919
78800190
SL
920 filedata = p4CmdList('-x - print',
921 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
922 for f in files]),
923 stdin_mode='w+')
924 if "p4ExitCode" in filedata[0]:
925 die("Problems executing p4. Error: [%d]."
926 % (filedata[0]['p4ExitCode']));
6a49f8e2 927
d2c6dd30
HWN
928 j = 0;
929 contents = {}
b1ce9447 930 while j < len(filedata):
d2c6dd30 931 stat = filedata[j]
b1ce9447
HWN
932 j += 1
933 text = ''
f3e9512b
JM
934 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
935 tmp = filedata[j]['data']
936 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
937 tmp = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', tmp)
938 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
939 tmp = re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', tmp)
940 text += tmp
b1ce9447 941 j += 1
6a49f8e2 942
1b9a4684
HWN
943
944 if not stat.has_key('depotFile'):
945 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
946 continue
947
b1ce9447 948 contents[stat['depotFile']] = text
6a49f8e2 949
d2c6dd30
HWN
950 for f in files:
951 assert not f.has_key('data')
952 f['data'] = contents[f['path']]
6a49f8e2 953
6326aa58 954 def commit(self, details, files, branch, branchPrefixes, parent = ""):
b984733c
SH
955 epoch = details["time"]
956 author = details["user"]
957
4b97ffb1
SH
958 if self.verbose:
959 print "commit into %s" % branch
960
96e07dd2
HWN
961 # start with reading files; if that fails, we should not
962 # create a commit.
963 new_files = []
964 for f in files:
965 if [p for p in branchPrefixes if f['path'].startswith(p)]:
966 new_files.append (f)
967 else:
968 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
969 files = new_files
970 self.readP4Files(files)
971
972
973
974
b984733c 975 self.gitStream.write("commit %s\n" % branch)
6a49f8e2 976# gitStream.write("mark :%s\n" % details["change"])
b984733c
SH
977 self.committedChanges.add(int(details["change"]))
978 committer = ""
b607e71e
SH
979 if author not in self.users:
980 self.getUserMapFromPerforceServer()
b984733c 981 if author in self.users:
0828ab14 982 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
b984733c 983 else:
0828ab14 984 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
b984733c
SH
985
986 self.gitStream.write("committer %s\n" % committer)
987
988 self.gitStream.write("data <<EOT\n")
989 self.gitStream.write(details["desc"])
6581de09
SH
990 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
991 % (','.join (branchPrefixes), details["change"]))
992 if len(details['options']) > 0:
993 self.gitStream.write(": options = %s" % details['options'])
994 self.gitStream.write("]\nEOT\n\n")
b984733c
SH
995
996 if len(parent) > 0:
4b97ffb1
SH
997 if self.verbose:
998 print "parent %s" % parent
b984733c
SH
999 self.gitStream.write("from %s\n" % parent)
1000
6a49f8e2 1001 for file in files:
b984733c 1002 if file["type"] == "apple":
6a49f8e2 1003 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
b984733c
SH
1004 continue
1005
6a49f8e2
HWN
1006 relPath = self.stripRepoPath(file['path'], branchPrefixes)
1007 if file["action"] == "delete":
b984733c
SH
1008 self.gitStream.write("D %s\n" % relPath)
1009 else:
6a49f8e2 1010 data = file['data']
b984733c 1011
74276ec6 1012 mode = "644"
b9fc6ea9 1013 if isP4Exec(file["type"]):
74276ec6
SH
1014 mode = "755"
1015 elif file["type"] == "symlink":
1016 mode = "120000"
1017 # p4 print on a symlink contains "target\n", so strip it off
1018 data = data[:-1]
1019
c1f9197f
MSO
1020 if self.isWindows and file["type"].endswith("text"):
1021 data = data.replace("\r\n", "\n")
1022
74276ec6 1023 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
b984733c
SH
1024 self.gitStream.write("data %s\n" % len(data))
1025 self.gitStream.write(data)
1026 self.gitStream.write("\n")
1027
1028 self.gitStream.write("\n")
1029
1f4ba1cb
SH
1030 change = int(details["change"])
1031
9bda3a85 1032 if self.labels.has_key(change):
1f4ba1cb
SH
1033 label = self.labels[change]
1034 labelDetails = label[0]
1035 labelRevisions = label[1]
71b112d4
SH
1036 if self.verbose:
1037 print "Change %s is labelled %s" % (change, labelDetails)
1f4ba1cb 1038
6326aa58
HWN
1039 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1040 for p in branchPrefixes]))
1f4ba1cb
SH
1041
1042 if len(files) == len(labelRevisions):
1043
1044 cleanedFiles = {}
1045 for info in files:
1046 if info["action"] == "delete":
1047 continue
1048 cleanedFiles[info["depotFile"]] = info["rev"]
1049
1050 if cleanedFiles == labelRevisions:
1051 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1052 self.gitStream.write("from %s\n" % branch)
1053
1054 owner = labelDetails["Owner"]
1055 tagger = ""
1056 if author in self.users:
1057 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1058 else:
1059 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1060 self.gitStream.write("tagger %s\n" % tagger)
1061 self.gitStream.write("data <<EOT\n")
1062 self.gitStream.write(labelDetails["Description"])
1063 self.gitStream.write("EOT\n\n")
1064
1065 else:
a46668fa 1066 if not self.silent:
cebdf5af
HWN
1067 print ("Tag %s does not match with change %s: files do not match."
1068 % (labelDetails["label"], change))
1f4ba1cb
SH
1069
1070 else:
a46668fa 1071 if not self.silent:
cebdf5af
HWN
1072 print ("Tag %s does not match with change %s: file count is different."
1073 % (labelDetails["label"], change))
b984733c 1074
183b8ef8 1075 def getUserCacheFilename(self):
b2d2d16a
SH
1076 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1077 return home + "/.gitp4-usercache.txt"
183b8ef8 1078
b607e71e 1079 def getUserMapFromPerforceServer(self):
ebd81168
SH
1080 if self.userMapFromPerforceServer:
1081 return
b984733c
SH
1082 self.users = {}
1083
1084 for output in p4CmdList("users"):
1085 if not output.has_key("User"):
1086 continue
1087 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1088
183b8ef8
HWN
1089
1090 s = ''
1091 for (key, val) in self.users.items():
1092 s += "%s\t%s\n" % (key, val)
1093
1094 open(self.getUserCacheFilename(), "wb").write(s)
ebd81168 1095 self.userMapFromPerforceServer = True
b607e71e
SH
1096
1097 def loadUserMapFromCache(self):
1098 self.users = {}
ebd81168 1099 self.userMapFromPerforceServer = False
b607e71e 1100 try:
183b8ef8 1101 cache = open(self.getUserCacheFilename(), "rb")
b607e71e
SH
1102 lines = cache.readlines()
1103 cache.close()
1104 for line in lines:
b25b2065 1105 entry = line.strip().split("\t")
b607e71e
SH
1106 self.users[entry[0]] = entry[1]
1107 except IOError:
1108 self.getUserMapFromPerforceServer()
1109
1f4ba1cb
SH
1110 def getLabels(self):
1111 self.labels = {}
1112
6326aa58 1113 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
10c3211b 1114 if len(l) > 0 and not self.silent:
183f8436 1115 print "Finding files belonging to labels in %s" % `self.depotPaths`
01ce1fe9
SH
1116
1117 for output in l:
1f4ba1cb
SH
1118 label = output["label"]
1119 revisions = {}
1120 newestChange = 0
71b112d4
SH
1121 if self.verbose:
1122 print "Querying files for label %s" % label
6326aa58
HWN
1123 for file in p4CmdList("files "
1124 + ' '.join (["%s...@%s" % (p, label)
1125 for p in self.depotPaths])):
1f4ba1cb
SH
1126 revisions[file["depotFile"]] = file["rev"]
1127 change = int(file["change"])
1128 if change > newestChange:
1129 newestChange = change
1130
9bda3a85
SH
1131 self.labels[newestChange] = [output, revisions]
1132
1133 if self.verbose:
1134 print "Label changes: %s" % self.labels.keys()
1f4ba1cb 1135
86dff6b6
HWN
1136 def guessProjectName(self):
1137 for p in self.depotPaths:
6e5295c4
SH
1138 if p.endswith("/"):
1139 p = p[:-1]
1140 p = p[p.strip().rfind("/") + 1:]
1141 if not p.endswith("/"):
1142 p += "/"
1143 return p
86dff6b6 1144
4b97ffb1 1145 def getBranchMapping(self):
6555b2cc
SH
1146 lostAndFoundBranches = set()
1147
4b97ffb1
SH
1148 for info in p4CmdList("branches"):
1149 details = p4Cmd("branch -o %s" % info["branch"])
1150 viewIdx = 0
1151 while details.has_key("View%s" % viewIdx):
1152 paths = details["View%s" % viewIdx].split(" ")
1153 viewIdx = viewIdx + 1
1154 # require standard //depot/foo/... //depot/bar/... mapping
1155 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1156 continue
1157 source = paths[0]
1158 destination = paths[1]
6509e19c
SH
1159 ## HACK
1160 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1161 source = source[len(self.depotPaths[0]):-4]
1162 destination = destination[len(self.depotPaths[0]):-4]
6555b2cc 1163
1a2edf4e
SH
1164 if destination in self.knownBranches:
1165 if not self.silent:
1166 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1167 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1168 continue
1169
6555b2cc
SH
1170 self.knownBranches[destination] = source
1171
1172 lostAndFoundBranches.discard(destination)
1173
29bdbac1 1174 if source not in self.knownBranches:
6555b2cc
SH
1175 lostAndFoundBranches.add(source)
1176
1177
1178 for branch in lostAndFoundBranches:
1179 self.knownBranches[branch] = branch
29bdbac1 1180
38f9f5ec
SH
1181 def getBranchMappingFromGitBranches(self):
1182 branches = p4BranchesInGit(self.importIntoRemotes)
1183 for branch in branches.keys():
1184 if branch == "master":
1185 branch = "main"
1186 else:
1187 branch = branch[len(self.projectName):]
1188 self.knownBranches[branch] = branch
1189
29bdbac1 1190 def listExistingP4GitBranches(self):
144ff46b
SH
1191 # branches holds mapping from name to commit
1192 branches = p4BranchesInGit(self.importIntoRemotes)
1193 self.p4BranchesInGit = branches.keys()
1194 for branch in branches.keys():
1195 self.initialParents[self.refPrefix + branch] = branches[branch]
4b97ffb1 1196
bb6e09b2
HWN
1197 def updateOptionDict(self, d):
1198 option_keys = {}
1199 if self.keepRepoPath:
1200 option_keys['keepRepoPath'] = 1
1201
1202 d["options"] = ' '.join(sorted(option_keys.keys()))
1203
1204 def readOptions(self, d):
1205 self.keepRepoPath = (d.has_key('options')
1206 and ('keepRepoPath' in d['options']))
6326aa58 1207
8134f69c
SH
1208 def gitRefForBranch(self, branch):
1209 if branch == "main":
1210 return self.refPrefix + "master"
1211
1212 if len(branch) <= 0:
1213 return branch
1214
1215 return self.refPrefix + self.projectName + branch
1216
1ca3d710
SH
1217 def gitCommitByP4Change(self, ref, change):
1218 if self.verbose:
1219 print "looking in ref " + ref + " for change %s using bisect..." % change
1220
1221 earliestCommit = ""
1222 latestCommit = parseRevision(ref)
1223
1224 while True:
1225 if self.verbose:
1226 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1227 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1228 if len(next) == 0:
1229 if self.verbose:
1230 print "argh"
1231 return ""
1232 log = extractLogMessageFromGitCommit(next)
1233 settings = extractSettingsGitLog(log)
1234 currentChange = int(settings['change'])
1235 if self.verbose:
1236 print "current change %s" % currentChange
1237
1238 if currentChange == change:
1239 if self.verbose:
1240 print "found %s" % next
1241 return next
1242
1243 if currentChange < change:
1244 earliestCommit = "^%s" % next
1245 else:
1246 latestCommit = "%s" % next
1247
1248 return ""
1249
1250 def importNewBranch(self, branch, maxChange):
1251 # make fast-import flush all changes to disk and update the refs using the checkpoint
1252 # command so that we can try to find the branch parent in the git history
1253 self.gitStream.write("checkpoint\n\n");
1254 self.gitStream.flush();
1255 branchPrefix = self.depotPaths[0] + branch + "/"
1256 range = "@1,%s" % maxChange
1257 #print "prefix" + branchPrefix
1258 changes = p4ChangesForPaths([branchPrefix], range)
1259 if len(changes) <= 0:
1260 return False
1261 firstChange = changes[0]
1262 #print "first change in branch: %s" % firstChange
1263 sourceBranch = self.knownBranches[branch]
1264 sourceDepotPath = self.depotPaths[0] + sourceBranch
1265 sourceRef = self.gitRefForBranch(sourceBranch)
1266 #print "source " + sourceBranch
1267
1268 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1269 #print "branch parent: %s" % branchParentChange
1270 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1271 if len(gitParent) > 0:
1272 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1273 #print "parent git commit: %s" % gitParent
1274
1275 self.importChanges(changes)
1276 return True
1277
e87f37ae
SH
1278 def importChanges(self, changes):
1279 cnt = 1
1280 for change in changes:
1281 description = p4Cmd("describe %s" % change)
1282 self.updateOptionDict(description)
1283
1284 if not self.silent:
1285 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1286 sys.stdout.flush()
1287 cnt = cnt + 1
1288
1289 try:
1290 if self.detectBranches:
1291 branches = self.splitFilesIntoBranches(description)
1292 for branch in branches.keys():
1293 ## HACK --hwn
1294 branchPrefix = self.depotPaths[0] + branch + "/"
1295
1296 parent = ""
1297
1298 filesForCommit = branches[branch]
1299
1300 if self.verbose:
1301 print "branch is %s" % branch
1302
1303 self.updatedBranches.add(branch)
1304
1305 if branch not in self.createdBranches:
1306 self.createdBranches.add(branch)
1307 parent = self.knownBranches[branch]
1308 if parent == branch:
1309 parent = ""
1ca3d710
SH
1310 else:
1311 fullBranch = self.projectName + branch
1312 if fullBranch not in self.p4BranchesInGit:
1313 if not self.silent:
1314 print("\n Importing new branch %s" % fullBranch);
1315 if self.importNewBranch(branch, change - 1):
1316 parent = ""
1317 self.p4BranchesInGit.append(fullBranch)
1318 if not self.silent:
1319 print("\n Resuming with change %s" % change);
1320
1321 if self.verbose:
1322 print "parent determined through known branches: %s" % parent
e87f37ae 1323
8134f69c
SH
1324 branch = self.gitRefForBranch(branch)
1325 parent = self.gitRefForBranch(parent)
e87f37ae
SH
1326
1327 if self.verbose:
1328 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1329
1330 if len(parent) == 0 and branch in self.initialParents:
1331 parent = self.initialParents[branch]
1332 del self.initialParents[branch]
1333
1334 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1335 else:
1336 files = self.extractFilesFromCommit(description)
1337 self.commit(description, files, self.branch, self.depotPaths,
1338 self.initialParent)
1339 self.initialParent = ""
1340 except IOError:
1341 print self.gitError.read()
1342 sys.exit(1)
1343
c208a243
SH
1344 def importHeadRevision(self, revision):
1345 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1346
1347 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1348 details["desc"] = ("Initial import of %s from the state at revision %s"
1349 % (' '.join(self.depotPaths), revision))
1350 details["change"] = revision
1351 newestRevision = 0
1352
1353 fileCnt = 0
1354 for info in p4CmdList("files "
1355 + ' '.join(["%s...%s"
1356 % (p, revision)
1357 for p in self.depotPaths])):
1358
1359 if info['code'] == 'error':
1360 sys.stderr.write("p4 returned an error: %s\n"
1361 % info['data'])
1362 sys.exit(1)
1363
1364
1365 change = int(info["change"])
1366 if change > newestRevision:
1367 newestRevision = change
1368
1369 if info["action"] == "delete":
1370 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1371 #fileCnt = fileCnt + 1
1372 continue
1373
1374 for prop in ["depotFile", "rev", "action", "type" ]:
1375 details["%s%s" % (prop, fileCnt)] = info[prop]
1376
1377 fileCnt = fileCnt + 1
1378
1379 details["change"] = newestRevision
1380 self.updateOptionDict(details)
1381 try:
1382 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1383 except IOError:
1384 print "IO error with git fast-import. Is your git version recent enough?"
1385 print self.gitError.read()
1386
1387
b984733c 1388 def run(self, args):
6326aa58 1389 self.depotPaths = []
179caebf
SH
1390 self.changeRange = ""
1391 self.initialParent = ""
6326aa58 1392 self.previousDepotPaths = []
ce6f33c8 1393
29bdbac1
SH
1394 # map from branch depot path to parent branch
1395 self.knownBranches = {}
1396 self.initialParents = {}
5ca44617 1397 self.hasOrigin = originP4BranchesExist()
a43ff00c
SH
1398 if not self.syncWithOrigin:
1399 self.hasOrigin = False
29bdbac1 1400
a028a98e
SH
1401 if self.importIntoRemotes:
1402 self.refPrefix = "refs/remotes/p4/"
1403 else:
db775559 1404 self.refPrefix = "refs/heads/p4/"
a028a98e 1405
cebdf5af
HWN
1406 if self.syncWithOrigin and self.hasOrigin:
1407 if not self.silent:
1408 print "Syncing with origin first by calling git fetch origin"
1409 system("git fetch origin")
10f880f8 1410
569d1bd4 1411 if len(self.branch) == 0:
db775559 1412 self.branch = self.refPrefix + "master"
a028a98e 1413 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
48df6fd8 1414 system("git update-ref %s refs/heads/p4" % self.branch)
48df6fd8 1415 system("git branch -D p4");
faf1bd20 1416 # create it /after/ importing, when master exists
0058a33a 1417 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
a3c55c09 1418 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
967f72e2 1419
6a49f8e2
HWN
1420 # TODO: should always look at previous commits,
1421 # merge with previous imports, if possible.
1422 if args == []:
d414c74a 1423 if self.hasOrigin:
5ca44617 1424 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
abcd790f
SH
1425 self.listExistingP4GitBranches()
1426
1427 if len(self.p4BranchesInGit) > 1:
1428 if not self.silent:
1429 print "Importing from/into multiple branches"
1430 self.detectBranches = True
967f72e2 1431
29bdbac1
SH
1432 if self.verbose:
1433 print "branches: %s" % self.p4BranchesInGit
1434
1435 p4Change = 0
1436 for branch in self.p4BranchesInGit:
cebdf5af 1437 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
bb6e09b2
HWN
1438
1439 settings = extractSettingsGitLog(logMsg)
29bdbac1 1440
bb6e09b2
HWN
1441 self.readOptions(settings)
1442 if (settings.has_key('depot-paths')
1443 and settings.has_key ('change')):
1444 change = int(settings['change']) + 1
29bdbac1
SH
1445 p4Change = max(p4Change, change)
1446
bb6e09b2
HWN
1447 depotPaths = sorted(settings['depot-paths'])
1448 if self.previousDepotPaths == []:
6326aa58 1449 self.previousDepotPaths = depotPaths
29bdbac1 1450 else:
6326aa58
HWN
1451 paths = []
1452 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
583e1707 1453 for i in range(0, min(len(cur), len(prev))):
6326aa58 1454 if cur[i] <> prev[i]:
583e1707 1455 i = i - 1
6326aa58
HWN
1456 break
1457
583e1707 1458 paths.append (cur[:i + 1])
6326aa58
HWN
1459
1460 self.previousDepotPaths = paths
29bdbac1
SH
1461
1462 if p4Change > 0:
bb6e09b2 1463 self.depotPaths = sorted(self.previousDepotPaths)
d5904674 1464 self.changeRange = "@%s,#head" % p4Change
330f53b8
SH
1465 if not self.detectBranches:
1466 self.initialParent = parseRevision(self.branch)
341dc1c1 1467 if not self.silent and not self.detectBranches:
967f72e2 1468 print "Performing incremental import into %s git branch" % self.branch
569d1bd4 1469
f9162f6a
SH
1470 if not self.branch.startswith("refs/"):
1471 self.branch = "refs/heads/" + self.branch
179caebf 1472
6326aa58 1473 if len(args) == 0 and self.depotPaths:
b984733c 1474 if not self.silent:
6326aa58 1475 print "Depot paths: %s" % ' '.join(self.depotPaths)
b984733c 1476 else:
6326aa58 1477 if self.depotPaths and self.depotPaths != args:
cebdf5af 1478 print ("previous import used depot path %s and now %s was specified. "
6326aa58
HWN
1479 "This doesn't work!" % (' '.join (self.depotPaths),
1480 ' '.join (args)))
b984733c 1481 sys.exit(1)
6326aa58 1482
bb6e09b2 1483 self.depotPaths = sorted(args)
b984733c 1484
1c49fc19 1485 revision = ""
b984733c 1486 self.users = {}
b984733c 1487
6326aa58
HWN
1488 newPaths = []
1489 for p in self.depotPaths:
1490 if p.find("@") != -1:
1491 atIdx = p.index("@")
1492 self.changeRange = p[atIdx:]
1493 if self.changeRange == "@all":
1494 self.changeRange = ""
6a49f8e2 1495 elif ',' not in self.changeRange:
1c49fc19 1496 revision = self.changeRange
6326aa58 1497 self.changeRange = ""
7fcff9de 1498 p = p[:atIdx]
6326aa58
HWN
1499 elif p.find("#") != -1:
1500 hashIdx = p.index("#")
1c49fc19 1501 revision = p[hashIdx:]
7fcff9de 1502 p = p[:hashIdx]
6326aa58 1503 elif self.previousDepotPaths == []:
1c49fc19 1504 revision = "#head"
6326aa58
HWN
1505
1506 p = re.sub ("\.\.\.$", "", p)
1507 if not p.endswith("/"):
1508 p += "/"
1509
1510 newPaths.append(p)
1511
1512 self.depotPaths = newPaths
1513
b984733c 1514
b607e71e 1515 self.loadUserMapFromCache()
cb53e1f8
SH
1516 self.labels = {}
1517 if self.detectLabels:
1518 self.getLabels();
b984733c 1519
4b97ffb1 1520 if self.detectBranches:
df450923
SH
1521 ## FIXME - what's a P4 projectName ?
1522 self.projectName = self.guessProjectName()
1523
38f9f5ec
SH
1524 if self.hasOrigin:
1525 self.getBranchMappingFromGitBranches()
1526 else:
1527 self.getBranchMapping()
29bdbac1
SH
1528 if self.verbose:
1529 print "p4-git branches: %s" % self.p4BranchesInGit
1530 print "initial parents: %s" % self.initialParents
1531 for b in self.p4BranchesInGit:
1532 if b != "master":
6326aa58
HWN
1533
1534 ## FIXME
29bdbac1
SH
1535 b = b[len(self.projectName):]
1536 self.createdBranches.add(b)
4b97ffb1 1537
f291b4e3 1538 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
b984733c 1539
cebdf5af 1540 importProcess = subprocess.Popen(["git", "fast-import"],
6326aa58
HWN
1541 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1542 stderr=subprocess.PIPE);
08483580
SH
1543 self.gitOutput = importProcess.stdout
1544 self.gitStream = importProcess.stdin
1545 self.gitError = importProcess.stderr
b984733c 1546
1c49fc19 1547 if revision:
c208a243 1548 self.importHeadRevision(revision)
b984733c
SH
1549 else:
1550 changes = []
1551
0828ab14 1552 if len(self.changesFile) > 0:
b984733c
SH
1553 output = open(self.changesFile).readlines()
1554 changeSet = Set()
1555 for line in output:
1556 changeSet.add(int(line))
1557
1558 for change in changeSet:
1559 changes.append(change)
1560
1561 changes.sort()
1562 else:
29bdbac1 1563 if self.verbose:
86dff6b6 1564 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
6326aa58 1565 self.changeRange)
4f6432d8 1566 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
b984733c 1567
01a9c9c5 1568 if len(self.maxChanges) > 0:
7fcff9de 1569 changes = changes[:min(int(self.maxChanges), len(changes))]
01a9c9c5 1570
b984733c 1571 if len(changes) == 0:
0828ab14 1572 if not self.silent:
341dc1c1 1573 print "No changes to import!"
1f52af6c 1574 return True
b984733c 1575
a9d1a27a
SH
1576 if not self.silent and not self.detectBranches:
1577 print "Import destination: %s" % self.branch
1578
341dc1c1
SH
1579 self.updatedBranches = set()
1580
e87f37ae 1581 self.importChanges(changes)
b984733c 1582
341dc1c1
SH
1583 if not self.silent:
1584 print ""
1585 if len(self.updatedBranches) > 0:
1586 sys.stdout.write("Updated branches: ")
1587 for b in self.updatedBranches:
1588 sys.stdout.write("%s " % b)
1589 sys.stdout.write("\n")
b984733c 1590
b984733c 1591 self.gitStream.close()
29bdbac1
SH
1592 if importProcess.wait() != 0:
1593 die("fast-import failed: %s" % self.gitError.read())
b984733c
SH
1594 self.gitOutput.close()
1595 self.gitError.close()
1596
b984733c
SH
1597 return True
1598
01ce1fe9
SH
1599class P4Rebase(Command):
1600 def __init__(self):
1601 Command.__init__(self)
01265103 1602 self.options = [ ]
cebdf5af
HWN
1603 self.description = ("Fetches the latest revision from perforce and "
1604 + "rebases the current work (branch) against it")
68c42153 1605 self.verbose = False
01ce1fe9
SH
1606
1607 def run(self, args):
1608 sync = P4Sync()
1609 sync.run([])
d7e3868c 1610
14594f4b
SH
1611 return self.rebase()
1612
1613 def rebase(self):
36ee4ee4
SH
1614 if os.system("git update-index --refresh") != 0:
1615 die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
1616 if len(read_pipe("git diff-index HEAD --")) > 0:
1617 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1618
d7e3868c
SH
1619 [upstream, settings] = findUpstreamBranchPoint()
1620 if len(upstream) == 0:
1621 die("Cannot find upstream branchpoint for rebase")
1622
1623 # the branchpoint may be p4/foo~3, so strip off the parent
1624 upstream = re.sub("~[0-9]+$", "", upstream)
1625
1626 print "Rebasing the current branch onto %s" % upstream
b25b2065 1627 oldHead = read_pipe("git rev-parse HEAD").strip()
d7e3868c 1628 system("git rebase %s" % upstream)
1f52af6c 1629 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
01ce1fe9
SH
1630 return True
1631
f9a3a4f7
SH
1632class P4Clone(P4Sync):
1633 def __init__(self):
1634 P4Sync.__init__(self)
1635 self.description = "Creates a new git repository and imports from Perforce into it"
bb6e09b2 1636 self.usage = "usage: %prog [options] //depot/path[@revRange]"
354081d5 1637 self.options += [
bb6e09b2
HWN
1638 optparse.make_option("--destination", dest="cloneDestination",
1639 action='store', default=None,
354081d5
TT
1640 help="where to leave result of the clone"),
1641 optparse.make_option("-/", dest="cloneExclude",
1642 action="append", type="string",
1643 help="exclude depot path")
1644 ]
bb6e09b2 1645 self.cloneDestination = None
f9a3a4f7 1646 self.needsGit = False
f9a3a4f7 1647
354081d5
TT
1648 # This is required for the "append" cloneExclude action
1649 def ensure_value(self, attr, value):
1650 if not hasattr(self, attr) or getattr(self, attr) is None:
1651 setattr(self, attr, value)
1652 return getattr(self, attr)
1653
6a49f8e2
HWN
1654 def defaultDestination(self, args):
1655 ## TODO: use common prefix of args?
1656 depotPath = args[0]
1657 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1658 depotDir = re.sub("(#[^#]*)$", "", depotDir)
053d9e43 1659 depotDir = re.sub(r"\.\.\.$", "", depotDir)
6a49f8e2
HWN
1660 depotDir = re.sub(r"/$", "", depotDir)
1661 return os.path.split(depotDir)[1]
1662
f9a3a4f7
SH
1663 def run(self, args):
1664 if len(args) < 1:
1665 return False
bb6e09b2
HWN
1666
1667 if self.keepRepoPath and not self.cloneDestination:
1668 sys.stderr.write("Must specify destination for --keep-path\n")
1669 sys.exit(1)
f9a3a4f7 1670
6326aa58 1671 depotPaths = args
5e100b5c
SH
1672
1673 if not self.cloneDestination and len(depotPaths) > 1:
1674 self.cloneDestination = depotPaths[-1]
1675 depotPaths = depotPaths[:-1]
1676
354081d5 1677 self.cloneExclude = ["/"+p for p in self.cloneExclude]
6326aa58
HWN
1678 for p in depotPaths:
1679 if not p.startswith("//"):
1680 return False
f9a3a4f7 1681
bb6e09b2 1682 if not self.cloneDestination:
98ad4faf 1683 self.cloneDestination = self.defaultDestination(args)
f9a3a4f7 1684
86dff6b6 1685 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
c3bf3f13
KG
1686 if not os.path.exists(self.cloneDestination):
1687 os.makedirs(self.cloneDestination)
bb6e09b2 1688 os.chdir(self.cloneDestination)
f9a3a4f7 1689 system("git init")
b86f7378 1690 self.gitdir = os.getcwd() + "/.git"
6326aa58 1691 if not P4Sync.run(self, depotPaths):
f9a3a4f7 1692 return False
f9a3a4f7 1693 if self.branch != "master":
8f9b2e08
SH
1694 if gitBranchExists("refs/remotes/p4/master"):
1695 system("git branch master refs/remotes/p4/master")
1696 system("git checkout -f")
1697 else:
1698 print "Could not detect main branch. No checkout/master branch created."
86dff6b6 1699
f9a3a4f7
SH
1700 return True
1701
09d89de2
SH
1702class P4Branches(Command):
1703 def __init__(self):
1704 Command.__init__(self)
1705 self.options = [ ]
1706 self.description = ("Shows the git branches that hold imports and their "
1707 + "corresponding perforce depot paths")
1708 self.verbose = False
1709
1710 def run(self, args):
5ca44617
SH
1711 if originP4BranchesExist():
1712 createOrUpdateBranchesFromOrigin()
1713
09d89de2
SH
1714 cmdline = "git rev-parse --symbolic "
1715 cmdline += " --remotes"
1716
1717 for line in read_pipe_lines(cmdline):
1718 line = line.strip()
1719
1720 if not line.startswith('p4/') or line == "p4/HEAD":
1721 continue
1722 branch = line
1723
1724 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1725 settings = extractSettingsGitLog(log)
1726
1727 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1728 return True
1729
b984733c
SH
1730class HelpFormatter(optparse.IndentedHelpFormatter):
1731 def __init__(self):
1732 optparse.IndentedHelpFormatter.__init__(self)
1733
1734 def format_description(self, description):
1735 if description:
1736 return description + "\n"
1737 else:
1738 return ""
4f5cf76a 1739
86949eef
SH
1740def printUsage(commands):
1741 print "usage: %s <command> [options]" % sys.argv[0]
1742 print ""
1743 print "valid commands: %s" % ", ".join(commands)
1744 print ""
1745 print "Try %s <command> --help for command specific help." % sys.argv[0]
1746 print ""
1747
1748commands = {
b86f7378
HWN
1749 "debug" : P4Debug,
1750 "submit" : P4Submit,
a9834f58 1751 "commit" : P4Submit,
b86f7378
HWN
1752 "sync" : P4Sync,
1753 "rebase" : P4Rebase,
1754 "clone" : P4Clone,
09d89de2
SH
1755 "rollback" : P4RollBack,
1756 "branches" : P4Branches
86949eef
SH
1757}
1758
86949eef 1759
bb6e09b2
HWN
1760def main():
1761 if len(sys.argv[1:]) == 0:
1762 printUsage(commands.keys())
1763 sys.exit(2)
4f5cf76a 1764
bb6e09b2
HWN
1765 cmd = ""
1766 cmdName = sys.argv[1]
1767 try:
b86f7378
HWN
1768 klass = commands[cmdName]
1769 cmd = klass()
bb6e09b2
HWN
1770 except KeyError:
1771 print "unknown command %s" % cmdName
1772 print ""
1773 printUsage(commands.keys())
1774 sys.exit(2)
1775
1776 options = cmd.options
b86f7378 1777 cmd.gitdir = os.environ.get("GIT_DIR", None)
bb6e09b2
HWN
1778
1779 args = sys.argv[2:]
1780
1781 if len(options) > 0:
1782 options.append(optparse.make_option("--git-dir", dest="gitdir"))
1783
1784 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1785 options,
1786 description = cmd.description,
1787 formatter = HelpFormatter())
1788
1789 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1790 global verbose
1791 verbose = cmd.verbose
1792 if cmd.needsGit:
b86f7378
HWN
1793 if cmd.gitdir == None:
1794 cmd.gitdir = os.path.abspath(".git")
1795 if not isValidGitDir(cmd.gitdir):
1796 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1797 if os.path.exists(cmd.gitdir):
bb6e09b2
HWN
1798 cdup = read_pipe("git rev-parse --show-cdup").strip()
1799 if len(cdup) > 0:
1800 os.chdir(cdup);
e20a9e53 1801
b86f7378
HWN
1802 if not isValidGitDir(cmd.gitdir):
1803 if isValidGitDir(cmd.gitdir + "/.git"):
1804 cmd.gitdir += "/.git"
bb6e09b2 1805 else:
b86f7378 1806 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
e20a9e53 1807
b86f7378 1808 os.environ["GIT_DIR"] = cmd.gitdir
86949eef 1809
bb6e09b2
HWN
1810 if not cmd.run(args):
1811 parser.print_help()
4f5cf76a 1812
4f5cf76a 1813
bb6e09b2
HWN
1814if __name__ == '__main__':
1815 main()