зеркало из https://github.com/microsoft/git.git
Merge branch 'ld/git-p4-updates'
"git p4" updates. * ld/git-p4-updates: git-p4: auto-size the block git-p4: narrow the scope of exceptions caught when parsing an int git-p4: raise exceptions from p4CmdList based on error from p4 server git-p4: better error reporting when p4 fails git-p4: add option to disable syncing of p4/master with p4 git-p4: disable-rebase: allow setting this via configuration git-p4: add options --commit and --disable-rebase
This commit is contained in:
Коммит
e638899470
|
@ -149,6 +149,12 @@ To specify a branch other than the current one, use:
|
|||
$ git p4 submit topicbranch
|
||||
------------
|
||||
|
||||
To specify a single commit or a range of commits, use:
|
||||
------------
|
||||
$ git p4 submit --commit <sha1>
|
||||
$ git p4 submit --commit <sha1..sha1>
|
||||
------------
|
||||
|
||||
The upstream reference is generally 'refs/remotes/p4/master', but can
|
||||
be overridden using the `--origin=` command-line option.
|
||||
|
||||
|
@ -355,6 +361,19 @@ These options can be used to modify 'git p4 submit' behavior.
|
|||
p4/master. See the "Sync options" section above for more
|
||||
information.
|
||||
|
||||
--commit <sha1>|<sha1..sha1>::
|
||||
Submit only the specified commit or range of commits, instead of the full
|
||||
list of changes that are in the current Git branch.
|
||||
|
||||
--disable-rebase::
|
||||
Disable the automatic rebase after all commits have been successfully
|
||||
submitted. Can also be set with git-p4.disableRebase.
|
||||
|
||||
--disable-p4sync::
|
||||
Disable the automatic sync of p4/master from Perforce after commits have
|
||||
been submitted. Implies --disable-rebase. Can also be set with
|
||||
git-p4.disableP4Sync. Sync with origin/master still goes ahead if possible.
|
||||
|
||||
Rebase options
|
||||
~~~~~~~~~~~~~~
|
||||
These options can be used to modify 'git p4 rebase' behavior.
|
||||
|
@ -676,6 +695,12 @@ git-p4.conflict::
|
|||
Specify submit behavior when a conflict with p4 is found, as per
|
||||
--conflict. The default behavior is 'ask'.
|
||||
|
||||
git-p4.disableRebase::
|
||||
Do not rebase the tree against p4/master following a submit.
|
||||
|
||||
git-p4.disableP4Sync::
|
||||
Do not sync p4/master with Perforce following a submit. Implies git-p4.disableRebase.
|
||||
|
||||
IMPLEMENTATION DETAILS
|
||||
----------------------
|
||||
* Changesets from p4 are imported using Git fast-import.
|
||||
|
|
180
git-p4.py
180
git-p4.py
|
@ -47,8 +47,10 @@ verbose = False
|
|||
# Only labels/tags matching this will be imported/exported
|
||||
defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
|
||||
|
||||
# Grab changes in blocks of this many revisions, unless otherwise requested
|
||||
defaultBlockSize = 512
|
||||
# The block size is reduced automatically if required
|
||||
defaultBlockSize = 1<<20
|
||||
|
||||
p4_access_checked = False
|
||||
|
||||
def p4_build_cmd(cmd):
|
||||
"""Build a suitable p4 command line.
|
||||
|
@ -91,6 +93,13 @@ def p4_build_cmd(cmd):
|
|||
real_cmd = ' '.join(real_cmd) + ' ' + cmd
|
||||
else:
|
||||
real_cmd += cmd
|
||||
|
||||
# now check that we can actually talk to the server
|
||||
global p4_access_checked
|
||||
if not p4_access_checked:
|
||||
p4_access_checked = True # suppress access checks in p4_check_access itself
|
||||
p4_check_access()
|
||||
|
||||
return real_cmd
|
||||
|
||||
def git_dir(path):
|
||||
|
@ -264,6 +273,52 @@ def p4_system(cmd):
|
|||
if retcode:
|
||||
raise CalledProcessError(retcode, real_cmd)
|
||||
|
||||
def die_bad_access(s):
|
||||
die("failure accessing depot: {0}".format(s.rstrip()))
|
||||
|
||||
def p4_check_access(min_expiration=1):
|
||||
""" Check if we can access Perforce - account still logged in
|
||||
"""
|
||||
results = p4CmdList(["login", "-s"])
|
||||
|
||||
if len(results) == 0:
|
||||
# should never get here: always get either some results, or a p4ExitCode
|
||||
assert("could not parse response from perforce")
|
||||
|
||||
result = results[0]
|
||||
|
||||
if 'p4ExitCode' in result:
|
||||
# p4 returned non-zero status, e.g. P4PORT invalid, or p4 not in path
|
||||
die_bad_access("could not run p4")
|
||||
|
||||
code = result.get("code")
|
||||
if not code:
|
||||
# we get here if we couldn't connect and there was nothing to unmarshal
|
||||
die_bad_access("could not connect")
|
||||
|
||||
elif code == "stat":
|
||||
expiry = result.get("TicketExpiration")
|
||||
if expiry:
|
||||
expiry = int(expiry)
|
||||
if expiry > min_expiration:
|
||||
# ok to carry on
|
||||
return
|
||||
else:
|
||||
die_bad_access("perforce ticket expires in {0} seconds".format(expiry))
|
||||
|
||||
else:
|
||||
# account without a timeout - all ok
|
||||
return
|
||||
|
||||
elif code == "error":
|
||||
data = result.get("data")
|
||||
if data:
|
||||
die_bad_access("p4 error: {0}".format(data))
|
||||
else:
|
||||
die_bad_access("unknown error")
|
||||
else:
|
||||
die_bad_access("unknown error code {0}".format(code))
|
||||
|
||||
_p4_version_string = None
|
||||
def p4_version_string():
|
||||
"""Read the version string, showing just the last line, which
|
||||
|
@ -511,10 +566,30 @@ def isModeExec(mode):
|
|||
# otherwise False.
|
||||
return mode[-3:] == "755"
|
||||
|
||||
class P4Exception(Exception):
|
||||
""" Base class for exceptions from the p4 client """
|
||||
def __init__(self, exit_code):
|
||||
self.p4ExitCode = exit_code
|
||||
|
||||
class P4ServerException(P4Exception):
|
||||
""" Base class for exceptions where we get some kind of marshalled up result from the server """
|
||||
def __init__(self, exit_code, p4_result):
|
||||
super(P4ServerException, self).__init__(exit_code)
|
||||
self.p4_result = p4_result
|
||||
self.code = p4_result[0]['code']
|
||||
self.data = p4_result[0]['data']
|
||||
|
||||
class P4RequestSizeException(P4ServerException):
|
||||
""" One of the maxresults or maxscanrows errors """
|
||||
def __init__(self, exit_code, p4_result, limit):
|
||||
super(P4RequestSizeException, self).__init__(exit_code, p4_result)
|
||||
self.limit = limit
|
||||
|
||||
def isModeExecChanged(src_mode, dst_mode):
|
||||
return isModeExec(src_mode) != isModeExec(dst_mode)
|
||||
|
||||
def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False):
|
||||
def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
|
||||
errors_as_exceptions=False):
|
||||
|
||||
if isinstance(cmd,basestring):
|
||||
cmd = "-G " + cmd
|
||||
|
@ -561,9 +636,25 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False):
|
|||
pass
|
||||
exitCode = p4.wait()
|
||||
if exitCode != 0:
|
||||
entry = {}
|
||||
entry["p4ExitCode"] = exitCode
|
||||
result.append(entry)
|
||||
if errors_as_exceptions:
|
||||
if len(result) > 0:
|
||||
data = result[0].get('data')
|
||||
if data:
|
||||
m = re.search('Too many rows scanned \(over (\d+)\)', data)
|
||||
if not m:
|
||||
m = re.search('Request too large \(over (\d+)\)', data)
|
||||
|
||||
if m:
|
||||
limit = int(m.group(1))
|
||||
raise P4RequestSizeException(exitCode, result, limit)
|
||||
|
||||
raise P4ServerException(exitCode, result)
|
||||
else:
|
||||
raise P4Exception(exitCode)
|
||||
else:
|
||||
entry = {}
|
||||
entry["p4ExitCode"] = exitCode
|
||||
result.append(entry)
|
||||
|
||||
return result
|
||||
|
||||
|
@ -868,7 +959,7 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
|
|||
try:
|
||||
(changeStart, changeEnd) = p4ParseNumericChangeRange(parts)
|
||||
block_size = chooseBlockSize(requestedBlockSize)
|
||||
except:
|
||||
except ValueError:
|
||||
changeStart = parts[0][1:]
|
||||
changeEnd = parts[1]
|
||||
if requestedBlockSize:
|
||||
|
@ -878,7 +969,8 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
|
|||
changes = set()
|
||||
|
||||
# Retrieve changes a block at a time, to prevent running
|
||||
# into a MaxResults/MaxScanRows error from the server.
|
||||
# into a MaxResults/MaxScanRows error from the server. If
|
||||
# we _do_ hit one of those errors, turn down the block size
|
||||
|
||||
while True:
|
||||
cmd = ['changes']
|
||||
|
@ -892,10 +984,24 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
|
|||
for p in depotPaths:
|
||||
cmd += ["%s...@%s" % (p, revisionRange)]
|
||||
|
||||
# fetch the changes
|
||||
try:
|
||||
result = p4CmdList(cmd, errors_as_exceptions=True)
|
||||
except P4RequestSizeException as e:
|
||||
if not block_size:
|
||||
block_size = e.limit
|
||||
elif block_size > e.limit:
|
||||
block_size = e.limit
|
||||
else:
|
||||
block_size = max(2, block_size // 2)
|
||||
|
||||
if verbose: print("block size error, retrying with block size {0}".format(block_size))
|
||||
continue
|
||||
except P4Exception as e:
|
||||
die('Error retrieving changes description ({0})'.format(e.p4ExitCode))
|
||||
|
||||
# Insert changes in chronological order
|
||||
for entry in reversed(p4CmdList(cmd)):
|
||||
if entry.has_key('p4ExitCode'):
|
||||
die('Error retrieving changes descriptions ({})'.format(entry['p4ExitCode']))
|
||||
for entry in reversed(result):
|
||||
if not entry.has_key('change'):
|
||||
continue
|
||||
changes.add(int(entry['change']))
|
||||
|
@ -1363,7 +1469,14 @@ class P4Submit(Command, P4UserMap):
|
|||
optparse.make_option("--update-shelve", dest="update_shelve", action="append", type="int",
|
||||
metavar="CHANGELIST",
|
||||
help="update an existing shelved changelist, implies --shelve, "
|
||||
"repeat in-order for multiple shelved changelists")
|
||||
"repeat in-order for multiple shelved changelists"),
|
||||
optparse.make_option("--commit", dest="commit", metavar="COMMIT",
|
||||
help="submit only the specified commit(s), one commit or xxx..xxx"),
|
||||
optparse.make_option("--disable-rebase", dest="disable_rebase", action="store_true",
|
||||
help="Disable rebase after submit is completed. Can be useful if you "
|
||||
"work from a local git branch that is not master"),
|
||||
optparse.make_option("--disable-p4sync", dest="disable_p4sync", action="store_true",
|
||||
help="Skip Perforce sync of p4/master after submit or shelve"),
|
||||
]
|
||||
self.description = "Submit changes from git to the perforce depot."
|
||||
self.usage += " [name of git branch to submit into perforce depot]"
|
||||
|
@ -1373,6 +1486,9 @@ class P4Submit(Command, P4UserMap):
|
|||
self.dry_run = False
|
||||
self.shelve = False
|
||||
self.update_shelve = list()
|
||||
self.commit = ""
|
||||
self.disable_rebase = gitConfigBool("git-p4.disableRebase")
|
||||
self.disable_p4sync = gitConfigBool("git-p4.disableP4Sync")
|
||||
self.prepare_p4_only = False
|
||||
self.conflict_behavior = None
|
||||
self.isWindows = (platform.system() == "Windows")
|
||||
|
@ -2114,9 +2230,18 @@ class P4Submit(Command, P4UserMap):
|
|||
else:
|
||||
committish = 'HEAD'
|
||||
|
||||
for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, committish)]):
|
||||
commits.append(line.strip())
|
||||
commits.reverse()
|
||||
if self.commit != "":
|
||||
if self.commit.find("..") != -1:
|
||||
limits_ish = self.commit.split("..")
|
||||
for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (limits_ish[0], limits_ish[1])]):
|
||||
commits.append(line.strip())
|
||||
commits.reverse()
|
||||
else:
|
||||
commits.append(self.commit)
|
||||
else:
|
||||
for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, committish)]):
|
||||
commits.append(line.strip())
|
||||
commits.reverse()
|
||||
|
||||
if self.preserveUser or gitConfigBool("git-p4.skipUserNameCheck"):
|
||||
self.checkAuthorship = False
|
||||
|
@ -2224,10 +2349,14 @@ class P4Submit(Command, P4UserMap):
|
|||
sync = P4Sync()
|
||||
if self.branch:
|
||||
sync.branch = self.branch
|
||||
sync.run([])
|
||||
if self.disable_p4sync:
|
||||
sync.sync_origin_only()
|
||||
else:
|
||||
sync.run([])
|
||||
|
||||
rebase = P4Rebase()
|
||||
rebase.rebase()
|
||||
if not self.disable_rebase:
|
||||
rebase = P4Rebase()
|
||||
rebase.rebase()
|
||||
|
||||
else:
|
||||
if len(applied) == 0:
|
||||
|
@ -3307,6 +3436,14 @@ class P4Sync(Command, P4UserMap):
|
|||
print self.gitError.read()
|
||||
sys.exit(1)
|
||||
|
||||
def sync_origin_only(self):
|
||||
if self.syncWithOrigin:
|
||||
self.hasOrigin = originP4BranchesExist()
|
||||
if self.hasOrigin:
|
||||
if not self.silent:
|
||||
print 'Syncing with origin first, using "git fetch origin"'
|
||||
system("git fetch origin")
|
||||
|
||||
def importHeadRevision(self, revision):
|
||||
print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
|
||||
|
||||
|
@ -3385,12 +3522,7 @@ class P4Sync(Command, P4UserMap):
|
|||
else:
|
||||
self.refPrefix = "refs/heads/p4/"
|
||||
|
||||
if self.syncWithOrigin:
|
||||
self.hasOrigin = originP4BranchesExist()
|
||||
if self.hasOrigin:
|
||||
if not self.silent:
|
||||
print 'Syncing with origin first, using "git fetch origin"'
|
||||
system("git fetch origin")
|
||||
self.sync_origin_only()
|
||||
|
||||
branch_arg_given = bool(self.branch)
|
||||
if len(self.branch) == 0:
|
||||
|
|
|
@ -155,6 +155,46 @@ test_expect_success 'allow submit from branch with same revision but different n
|
|||
)
|
||||
'
|
||||
|
||||
# make two commits, but tell it to apply only one
|
||||
|
||||
test_expect_success 'submit --commit one' '
|
||||
test_when_finished cleanup_git &&
|
||||
git p4 clone --dest="$git" //depot &&
|
||||
(
|
||||
cd "$git" &&
|
||||
test_commit "file9" &&
|
||||
test_commit "file10" &&
|
||||
git config git-p4.skipSubmitEdit true &&
|
||||
git p4 submit --commit HEAD
|
||||
) &&
|
||||
(
|
||||
cd "$cli" &&
|
||||
test_path_is_missing "file9.t" &&
|
||||
test_path_is_file "file10.t"
|
||||
)
|
||||
'
|
||||
|
||||
# make three commits, but tell it to apply only range
|
||||
|
||||
test_expect_success 'submit --commit range' '
|
||||
test_when_finished cleanup_git &&
|
||||
git p4 clone --dest="$git" //depot &&
|
||||
(
|
||||
cd "$git" &&
|
||||
test_commit "file11" &&
|
||||
test_commit "file12" &&
|
||||
test_commit "file13" &&
|
||||
git config git-p4.skipSubmitEdit true &&
|
||||
git p4 submit --commit HEAD~2..HEAD
|
||||
) &&
|
||||
(
|
||||
cd "$cli" &&
|
||||
test_path_is_missing "file11.t" &&
|
||||
test_path_is_file "file12.t" &&
|
||||
test_path_is_file "file13.t"
|
||||
)
|
||||
'
|
||||
|
||||
#
|
||||
# Basic submit tests, the five handled cases
|
||||
#
|
||||
|
|
|
@ -129,6 +129,7 @@ test_expect_success 'Create a repo with multiple depot paths' '
|
|||
'
|
||||
|
||||
test_expect_success 'Clone repo with multiple depot paths' '
|
||||
test_when_finished cleanup_git &&
|
||||
(
|
||||
cd "$git" &&
|
||||
git p4 clone --changes-block-size=4 //depot/pathA@all //depot/pathB@all \
|
||||
|
@ -138,6 +139,13 @@ test_expect_success 'Clone repo with multiple depot paths' '
|
|||
)
|
||||
'
|
||||
|
||||
test_expect_success 'Clone repo with self-sizing block size' '
|
||||
test_when_finished cleanup_git &&
|
||||
git p4 clone --changes-block-size=1000000 //depot@all --destination="$git" &&
|
||||
git -C "$git" log --oneline >log &&
|
||||
test_line_count \> 10 log
|
||||
'
|
||||
|
||||
test_expect_success 'kill p4d' '
|
||||
kill_p4d
|
||||
'
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='git p4 errors'
|
||||
|
||||
. ./lib-git-p4.sh
|
||||
|
||||
test_expect_success 'start p4d' '
|
||||
start_p4d
|
||||
'
|
||||
|
||||
test_expect_success 'add p4 files' '
|
||||
(
|
||||
cd "$cli" &&
|
||||
echo file1 >file1 &&
|
||||
p4 add file1 &&
|
||||
p4 submit -d "file1"
|
||||
)
|
||||
'
|
||||
|
||||
# after this test, the default user requires a password
|
||||
test_expect_success 'error handling' '
|
||||
git p4 clone --dest="$git" //depot@all &&
|
||||
(
|
||||
cd "$git" &&
|
||||
P4PORT=: test_must_fail git p4 submit 2>errmsg
|
||||
) &&
|
||||
p4 passwd -P newpassword &&
|
||||
(
|
||||
P4PASSWD=badpassword test_must_fail git p4 clone //depot/foo 2>errmsg &&
|
||||
grep -q "failure accessing depot.*P4PASSWD" errmsg
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'ticket logged out' '
|
||||
P4TICKETS="$cli/tickets" &&
|
||||
echo "newpassword" | p4 login &&
|
||||
(
|
||||
cd "$git" &&
|
||||
test_commit "ticket-auth-check" &&
|
||||
p4 logout &&
|
||||
test_must_fail git p4 submit 2>errmsg &&
|
||||
grep -q "failure accessing depot" errmsg
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'create group with short ticket expiry' '
|
||||
P4TICKETS="$cli/tickets" &&
|
||||
echo "newpassword" | p4 login &&
|
||||
p4_add_user short_expiry_user &&
|
||||
p4 -u short_expiry_user passwd -P password &&
|
||||
p4 group -i <<-EOF &&
|
||||
Group: testgroup
|
||||
Timeout: 3
|
||||
Users: short_expiry_user
|
||||
EOF
|
||||
|
||||
p4 users | grep short_expiry_user
|
||||
'
|
||||
|
||||
test_expect_success 'git operation with expired ticket' '
|
||||
P4TICKETS="$cli/tickets" &&
|
||||
P4USER=short_expiry_user &&
|
||||
echo "password" | p4 login &&
|
||||
(
|
||||
cd "$git" &&
|
||||
git p4 sync &&
|
||||
sleep 5 &&
|
||||
test_must_fail git p4 sync 2>errmsg &&
|
||||
grep "failure accessing depot" errmsg
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'kill p4d' '
|
||||
kill_p4d
|
||||
'
|
||||
|
||||
|
||||
test_done
|
Загрузка…
Ссылка в новой задаче