verbose = False
+
+def p4_build_cmd(cmd):
+ """Build a suitable p4 command line.
+
+ This consolidates building and returning a p4 command line into one
+ location. It means that hooking into the environment, or other configuration
+ can be done more easily.
+ """
+ real_cmd = "%s " % "p4"
+
+ user = gitConfig("git-p4.user")
+ if len(user) > 0:
+ real_cmd += "-u %s " % user
+
+ password = gitConfig("git-p4.password")
+ if len(password) > 0:
+ real_cmd += "-P %s " % password
+
+ port = gitConfig("git-p4.port")
+ if len(port) > 0:
+ real_cmd += "-p %s " % port
+
+ host = gitConfig("git-p4.host")
+ if len(host) > 0:
+ real_cmd += "-h %s " % host
+
+ client = gitConfig("git-p4.client")
+ if len(client) > 0:
+ real_cmd += "-c %s " % client
+
+ real_cmd += "%s" % (cmd)
+ if verbose:
+ print real_cmd
+ return real_cmd
+
+def chdir(dir):
+ if os.name == 'nt':
+ os.environ['PWD']=dir
+ os.chdir(dir)
+
def die(msg):
if verbose:
raise Exception(msg)
return val
+def p4_write_pipe(c, str):
+ real_cmd = p4_build_cmd(c)
+ return write_pipe(real_cmd, str)
+
def read_pipe(c, ignore_error=False):
if verbose:
sys.stderr.write('Reading pipe: %s\n' % c)
return val
+def p4_read_pipe(c, ignore_error=False):
+ real_cmd = p4_build_cmd(c)
+ return read_pipe(real_cmd, ignore_error)
def read_pipe_lines(c):
if verbose:
return val
+def p4_read_pipe_lines(c):
+ """Specifically invoke p4 on the command supplied. """
+ real_cmd = p4_build_cmd(c)
+ return read_pipe_lines(real_cmd)
+
def system(cmd):
if verbose:
sys.stderr.write("executing %s\n" % cmd)
if os.system(cmd) != 0:
die("command failed: %s" % cmd)
+def p4_system(cmd):
+ """Specifically invoke p4 as the system command. """
+ real_cmd = p4_build_cmd(cmd)
+ return system(real_cmd)
+
def isP4Exec(kind):
"""Determine if a Perforce 'kind' should have execute permission
if p4Type[-1] == "+":
p4Type = p4Type[0:-1]
- system("p4 reopen -t %s %s" % (p4Type, file))
+ p4_system("reopen -t %s %s" % (p4Type, file))
def getP4OpenedType(file):
# Returns the perforce file type for the given file.
- result = read_pipe("p4 opened %s" % file)
- match = re.match(".*\((.+)\)$", result)
+ result = p4_read_pipe("opened %s" % file)
+ match = re.match(".*\((.+)\)\r?$", result)
if match:
return match.group(1)
else:
- die("Could not determine file type for %s" % file)
+ die("Could not determine file type for %s (result: '%s')" % (file, result))
def diffTreePattern():
# This is a simple generator for the diff tree regex pattern. This could be
return isModeExec(src_mode) != isModeExec(dst_mode)
def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
- cmd = "p4 -G %s" % cmd
+ cmd = p4_build_cmd("-G %s" % (cmd))
if verbose:
sys.stderr.write("Opening pipe: %s\n" % cmd)
def p4ChangesForPaths(depotPaths, changeRange):
assert depotPaths
- output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, changeRange)
+ output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
for p in depotPaths]))
changes = []
def __init__(self):
Command.__init__(self)
self.options = [
- optparse.make_option("--continue", action="store_false", dest="firstTime"),
optparse.make_option("--verbose", dest="verbose", action="store_true"),
optparse.make_option("--origin", dest="origin"),
- optparse.make_option("--reset", action="store_true", dest="reset"),
- optparse.make_option("--log-substitutions", dest="substFile"),
- optparse.make_option("--dry-run", action="store_true"),
- optparse.make_option("--direct", dest="directSubmit", action="store_true"),
- optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"),
optparse.make_option("-M", dest="detectRename", action="store_true"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
- self.firstTime = True
- self.reset = False
self.interactive = True
- self.dryRun = False
- self.substFile = ""
- self.firstTime = True
self.origin = ""
- self.directSubmit = False
- self.trustMeLikeAFool = False
self.detectRename = False
self.verbose = False
self.isWindows = (platform.system() == "Windows")
- self.logSubstitutions = {}
- self.logSubstitutions["<enter description here>"] = "%log%"
- self.logSubstitutions["\tDetails:"] = "\tDetails: %log%"
-
def check(self):
if len(p4CmdList("opened ...")) > 0:
die("You have files opened with perforce! Close them before starting the sync.")
- def start(self):
- if len(self.config) > 0 and not self.reset:
- die("Cannot start sync. Previous sync config found at %s\n"
- "If you want to start submitting again from scratch "
- "maybe you want to call git-p4 submit --reset" % self.configFile)
-
- commits = []
- if self.directSubmit:
- commits.append("0")
- else:
- for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
- commits.append(line.strip())
- commits.reverse()
-
- self.config["commits"] = commits
-
+ # replaces everything between 'Description:' and the next P4 submit template field with the
+ # commit message
def prepareLogMessage(self, template, message):
result = ""
+ inDescriptionSection = False
+
for line in template.split("\n"):
if line.startswith("#"):
result += line + "\n"
continue
- substituted = False
- for key in self.logSubstitutions.keys():
- if line.find(key) != -1:
- value = self.logSubstitutions[key]
- value = value.replace("%log%", message)
- if value != "@remove@":
- result += line.replace(key, value) + "\n"
- substituted = True
- break
+ if inDescriptionSection:
+ if line.startswith("Files:"):
+ inDescriptionSection = False
+ else:
+ continue
+ else:
+ if line.startswith("Description:"):
+ inDescriptionSection = True
+ line += "\n"
+ for messageLine in message.split("\n"):
+ line += "\t" + messageLine + "\n"
- if not substituted:
- result += line + "\n"
+ result += line + "\n"
return result
# remove lines in the Files section that show changes to files outside the depot path we're committing into
template = ""
inFilesSection = False
- for line in read_pipe_lines("p4 change -o"):
+ for line in p4_read_pipe_lines("change -o"):
+ if line.endswith("\r\n"):
+ line = line[:-2] + "\n"
if inFilesSection:
if line.startswith("\t"):
# path starts and ends with a tab
return template
def applyCommit(self, id):
- if self.directSubmit:
- print "Applying local change in working directory/index"
- diff = self.diffStatus
- else:
- print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
- diffOpts = ("", "-M")[self.detectRename]
- diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
+ print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
+ diffOpts = ("", "-M")[self.detectRename]
+ diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
filesToAdd = set()
filesToDelete = set()
editedFiles = set()
modifier = diff['status']
path = diff['src']
if modifier == "M":
- system("p4 edit \"%s\"" % path)
+ p4_system("edit \"%s\"" % path)
if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
filesToChangeExecBit[path] = diff['dst_mode']
editedFiles.add(path)
filesToAdd.remove(path)
elif modifier == "R":
src, dest = diff['src'], diff['dst']
- system("p4 integrate -Dt \"%s\" \"%s\"" % (src, dest))
- system("p4 edit \"%s\"" % (dest))
+ p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+ p4_system("edit \"%s\"" % (dest))
if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
filesToChangeExecBit[dest] = diff['dst_mode']
os.unlink(dest)
else:
die("unknown modifier %s for %s" % (modifier, path))
- if self.directSubmit:
- diffcmd = "cat \"%s\"" % self.diffFile
- else:
- diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
+ diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
patchcmd = diffcmd + " | git apply "
tryPatchCmd = patchcmd + "--check -"
applyPatchCmd = patchcmd + "--check --apply -"
if response == "s":
print "Skipping! Good luck with the next patches..."
for f in editedFiles:
- system("p4 revert \"%s\"" % f);
+ p4_system("revert \"%s\"" % f);
for f in filesToAdd:
system("rm %s" %f)
return
system(applyPatchCmd)
for f in filesToAdd:
- system("p4 add \"%s\"" % f)
+ p4_system("add \"%s\"" % f)
for f in filesToDelete:
- system("p4 revert \"%s\"" % f)
- system("p4 delete \"%s\"" % f)
+ p4_system("revert \"%s\"" % f)
+ p4_system("delete \"%s\"" % f)
# Set/clear executable bits
for f in filesToChangeExecBit.keys():
mode = filesToChangeExecBit[f]
setP4ExecBit(f, mode)
- logMessage = ""
- if not self.directSubmit:
- logMessage = extractLogMessageFromGitCommit(id)
- logMessage = logMessage.replace("\n", "\n\t")
- if self.isWindows:
- logMessage = logMessage.replace("\n", "\r\n")
- logMessage = logMessage.strip()
+ logMessage = extractLogMessageFromGitCommit(id)
+ logMessage = logMessage.strip()
template = self.prepareSubmitTemplate()
if self.interactive:
submitTemplate = self.prepareLogMessage(template, logMessage)
- diff = read_pipe("p4 diff -du ...")
+ if os.environ.has_key("P4DIFF"):
+ del(os.environ["P4DIFF"])
+ diff = p4_read_pipe("diff -du ...")
+ newdiff = ""
for newFile in filesToAdd:
- diff += "==== new file ====\n"
- diff += "--- /dev/null\n"
- diff += "+++ %s\n" % newFile
+ newdiff += "==== new file ====\n"
+ newdiff += "--- /dev/null\n"
+ newdiff += "+++ %s\n" % newFile
f = open(newFile, "r")
for line in f.readlines():
- diff += "+" + line
+ newdiff += "+" + line
f.close()
- separatorLine = "######## everything below this line is just the diff #######"
+ separatorLine = "######## everything below this line is just the diff #######\n"
+
+ [handle, fileName] = tempfile.mkstemp()
+ tmpFile = os.fdopen(handle, "w+")
+ if self.isWindows:
+ submitTemplate = submitTemplate.replace("\n", "\r\n")
+ separatorLine = separatorLine.replace("\n", "\r\n")
+ newdiff = newdiff.replace("\n", "\r\n")
+ tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
+ tmpFile.close()
+ mtime = os.stat(fileName).st_mtime
+ defaultEditor = "vi"
if platform.system() == "Windows":
- separatorLine += "\r"
- separatorLine += "\n"
-
- response = "e"
- if self.trustMeLikeAFool:
- response = "y"
-
- firstIteration = True
- while response == "e":
- if not firstIteration:
- response = raw_input("Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip ")
- firstIteration = False
- if response == "e":
- [handle, fileName] = tempfile.mkstemp()
- tmpFile = os.fdopen(handle, "w+")
- tmpFile.write(submitTemplate + separatorLine + diff)
- tmpFile.close()
- defaultEditor = "vi"
- if platform.system() == "Windows":
- defaultEditor = "notepad"
- editor = os.environ.get("EDITOR", defaultEditor);
- system(editor + " " + fileName)
- tmpFile = open(fileName, "rb")
- message = tmpFile.read()
- tmpFile.close()
- os.remove(fileName)
- submitTemplate = message[:message.index(separatorLine)]
- if self.isWindows:
- submitTemplate = submitTemplate.replace("\r\n", "\n")
-
- if response == "y" or response == "yes":
- if self.dryRun:
- print submitTemplate
- raw_input("Press return to continue...")
- else:
- if self.directSubmit:
- print "Submitting to git first"
- os.chdir(self.oldWorkingDirectory)
- write_pipe("git commit -a -F -", submitTemplate)
- os.chdir(self.clientPath)
-
- write_pipe("p4 submit -i", submitTemplate)
- elif response == "s":
+ defaultEditor = "notepad"
+ if os.environ.has_key("P4EDITOR"):
+ editor = os.environ.get("P4EDITOR")
+ else:
+ editor = os.environ.get("EDITOR", defaultEditor);
+ system(editor + " " + fileName)
+
+ response = "y"
+ if os.stat(fileName).st_mtime <= mtime:
+ response = "x"
+ while response != "y" and response != "n":
+ response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
+
+ if response == "y":
+ tmpFile = open(fileName, "rb")
+ message = tmpFile.read()
+ tmpFile.close()
+ submitTemplate = message[:message.index(separatorLine)]
+ if self.isWindows:
+ submitTemplate = submitTemplate.replace("\r\n", "\n")
+ p4_write_pipe("submit -i", submitTemplate)
+ else:
for f in editedFiles:
- system("p4 revert \"%s\"" % f);
+ p4_system("revert \"%s\"" % f);
for f in filesToAdd:
- system("p4 revert \"%s\"" % f);
+ p4_system("revert \"%s\"" % f);
system("rm %s" %f)
- for f in filesToDelete:
- system("p4 delete \"%s\"" % f);
- return
- else:
- print "Not submitting!"
- self.interactive = False
+
+ os.remove(fileName)
else:
fileName = "submit.txt"
file = open(fileName, "w+")
else:
return False
+ allowSubmit = gitConfig("git-p4.allowSubmit")
+ if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
+ die("%s is not in git-p4.allowSubmit" % self.master)
+
[upstream, settings] = findUpstreamBranchPoint()
self.depotPath = settings['depot-paths'][0]
if len(self.origin) == 0:
print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
self.oldWorkingDirectory = os.getcwd()
- if self.directSubmit:
- self.diffStatus = read_pipe_lines("git diff -r --name-status HEAD")
- if len(self.diffStatus) == 0:
- print "No changes in working directory to submit."
- return True
- patch = read_pipe("git diff -p --binary --diff-filter=ACMRTUXB HEAD")
- self.diffFile = self.gitdir + "/p4-git-diff"
- f = open(self.diffFile, "wb")
- f.write(patch)
- f.close();
-
- os.chdir(self.clientPath)
+ chdir(self.clientPath)
print "Syncronizing p4 checkout..."
- system("p4 sync ...")
-
- if self.reset:
- self.firstTime = True
-
- if len(self.substFile) > 0:
- for line in open(self.substFile, "r").readlines():
- tokens = line.strip().split("=")
- self.logSubstitutions[tokens[0]] = tokens[1]
+ p4_system("sync ...")
self.check()
- self.configFile = self.gitdir + "/p4-git-sync.cfg"
- self.config = shelve.open(self.configFile, writeback=True)
- if self.firstTime:
- self.start()
-
- commits = self.config.get("commits", [])
+ commits = []
+ for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
+ commits.append(line.strip())
+ commits.reverse()
while len(commits) > 0:
- self.firstTime = False
commit = commits[0]
commits = commits[1:]
- self.config["commits"] = commits
self.applyCommit(commit)
if not self.interactive:
break
- self.config.close()
-
- if self.directSubmit:
- os.remove(self.diffFile)
-
if len(commits) == 0:
- if self.firstTime:
- print "No changes found to apply between %s and current HEAD" % self.origin
- else:
- print "All changes applied!"
- os.chdir(self.oldWorkingDirectory)
+ print "All changes applied!"
+ chdir(self.oldWorkingDirectory)
- sync = P4Sync()
- sync.run([])
+ sync = P4Sync()
+ sync.run([])
- response = raw_input("Do you want to rebase current HEAD from Perforce now using git-p4 rebase? [y]es/[n]o ")
- if response == "y" or response == "yes":
- rebase = P4Rebase()
- rebase.rebase()
- os.remove(self.configFile)
+ rebase = P4Rebase()
+ rebase.rebase()
return True
help="Import into refs/heads/ , not refs/remotes"),
optparse.make_option("--max-changes", dest="maxChanges"),
optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
- help="Keep entire BRANCH/DIR/SUBDIR prefix during import")
+ help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
+ optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
+ help="Only sync files that are included in the Perforce Client Spec")
]
self.description = """Imports from Perforce into a git repository.\n
example:
self.keepRepoPath = False
self.depotPaths = None
self.p4BranchesInGit = []
+ self.cloneExclude = []
+ self.useClientSpec = False
+ self.clientSpecDirs = []
if gitConfig("git-p4.syncFromOrigin") == "false":
self.syncWithOrigin = False
def extractFilesFromCommit(self, commit):
+ self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
+ for path in self.cloneExclude]
files = []
fnum = 0
while commit.has_key("depotFile%s" % fnum):
path = commit["depotFile%s" % fnum]
- found = [p for p in self.depotPaths
- if path.startswith (p)]
+ if [p for p in self.cloneExclude
+ if path.startswith (p)]:
+ found = False
+ else:
+ found = [p for p in self.depotPaths
+ if path.startswith (p)]
if not found:
fnum = fnum + 1
continue
## Should move this out, doesn't use SELF.
def readP4Files(self, files):
- files = [f for f in files
- if f['action'] != 'delete']
+ filesForCommit = []
+ filesToRead = []
- if not files:
- return
+ for f in files:
+ includeFile = True
+ for val in self.clientSpecDirs:
+ if f['path'].startswith(val[0]):
+ if val[1] <= 0:
+ includeFile = False
+ break
+
+ if includeFile:
+ filesForCommit.append(f)
+ if f['action'] != 'delete':
+ filesToRead.append(f)
+
+ filedata = []
+ if len(filesToRead) > 0:
+ filedata = p4CmdList('-x - print',
+ stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
+ for f in filesToRead]),
+ stdin_mode='w+')
- filedata = p4CmdList('-x - print',
- stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
- for f in files]),
- stdin_mode='w+')
- if "p4ExitCode" in filedata[0]:
- die("Problems executing p4. Error: [%d]."
- % (filedata[0]['p4ExitCode']));
+ if "p4ExitCode" in filedata[0]:
+ die("Problems executing p4. Error: [%d]."
+ % (filedata[0]['p4ExitCode']));
j = 0;
contents = {}
while j < len(filedata):
stat = filedata[j]
j += 1
- text = ''
- while j < len(filedata) and filedata[j]['code'] in ('text',
- 'binary'):
- text += filedata[j]['data']
+ text = [];
+ while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
+ text.append(filedata[j]['data'])
j += 1
-
+ text = ''.join(text)
if not stat.has_key('depotFile'):
sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
continue
+ if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
+ text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
+ elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
+ text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', text)
+
contents[stat['depotFile']] = text
- for f in files:
- assert not f.has_key('data')
- f['data'] = contents[f['path']]
+ for f in filesForCommit:
+ path = f['path']
+ if contents.has_key(path):
+ f['data'] = contents[path]
+
+ return filesForCommit
def commit(self, details, files, branch, branchPrefixes, parent = ""):
epoch = details["time"]
new_files.append (f)
else:
sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
- files = new_files
- self.readP4Files(files)
-
-
-
+ files = self.readP4Files(new_files)
self.gitStream.write("commit %s\n" % branch)
# gitStream.write("mark :%s\n" % details["change"])
print self.gitError.read()
+ def getClientSpec(self):
+ specList = p4CmdList( "client -o" )
+ temp = {}
+ for entry in specList:
+ for k,v in entry.iteritems():
+ if k.startswith("View"):
+ if v.startswith('"'):
+ start = 1
+ else:
+ start = 0
+ index = v.find("...")
+ v = v[start:index]
+ if v.startswith("-"):
+ v = v[1:]
+ temp[v] = -len(v)
+ else:
+ temp[v] = len(v)
+ self.clientSpecDirs = temp.items()
+ self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
+
def run(self, args):
self.depotPaths = []
self.changeRange = ""
if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
+ if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
+ self.getClientSpec()
+
# TODO: should always look at previous commits,
# merge with previous imports, if possible.
if args == []:
return self.rebase()
def rebase(self):
+ if os.system("git update-index --refresh") != 0:
+ 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.");
+ if len(read_pipe("git diff-index HEAD --")) > 0:
+ die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
+
[upstream, settings] = findUpstreamBranchPoint()
if len(upstream) == 0:
die("Cannot find upstream branchpoint for rebase")
P4Sync.__init__(self)
self.description = "Creates a new git repository and imports from Perforce into it"
self.usage = "usage: %prog [options] //depot/path[@revRange]"
- self.options.append(
+ self.options += [
optparse.make_option("--destination", dest="cloneDestination",
action='store', default=None,
- help="where to leave result of the clone"))
+ help="where to leave result of the clone"),
+ optparse.make_option("-/", dest="cloneExclude",
+ action="append", type="string",
+ help="exclude depot path")
+ ]
self.cloneDestination = None
self.needsGit = False
+ # This is required for the "append" cloneExclude action
+ def ensure_value(self, attr, value):
+ if not hasattr(self, attr) or getattr(self, attr) is None:
+ setattr(self, attr, value)
+ return getattr(self, attr)
+
def defaultDestination(self, args):
## TODO: use common prefix of args?
depotPath = args[0]
depotDir = re.sub("(@[^@]*)$", "", depotPath)
depotDir = re.sub("(#[^#]*)$", "", depotDir)
- depotDir = re.sub(r"\.\.\.$,", "", depotDir)
+ depotDir = re.sub(r"\.\.\.$", "", depotDir)
depotDir = re.sub(r"/$", "", depotDir)
return os.path.split(depotDir)[1]
self.cloneDestination = depotPaths[-1]
depotPaths = depotPaths[:-1]
+ self.cloneExclude = ["/"+p for p in self.cloneExclude]
for p in depotPaths:
if not p.startswith("//"):
return False
print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
if not os.path.exists(self.cloneDestination):
os.makedirs(self.cloneDestination)
- os.chdir(self.cloneDestination)
+ chdir(self.cloneDestination)
system("git init")
self.gitdir = os.getcwd() + "/.git"
if not P4Sync.run(self, depotPaths):
if os.path.exists(cmd.gitdir):
cdup = read_pipe("git rev-parse --show-cdup").strip()
if len(cdup) > 0:
- os.chdir(cdup);
+ chdir(cdup);
if not isValidGitDir(cmd.gitdir):
if isValidGitDir(cmd.gitdir + "/.git"):