1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-04 01:56:15 +02:00

git-p4: recover from inconsistent perforce history

Perforce allows you commit files and directories with the same name,
so you could have files //depot/foo and //depot/foo/bar both checked
in.  A p4 sync of a repository in this state fails.  Deleting one of
the files recovers the repository.

When this happens we want git-p4 to recover in the same way as
perforce.

Note that Perforce has this change in their 2017.1 version:

     Bugs fixed in 2017.1
     #1489051 (Job #2170) **
        Submitting a file with the same name as an existing depot
        directory path (or vice versa) will now be rejected.

so people hopefully will not creating damaged Perforce repos
anymore, but "git p4" needs to be able to interact with already
corrupt ones.

Signed-off-by: Andrew Oakley <andrew@adoakley.name>
Reviewed-by: Luke Diamand <luke@diamand.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Andrew Oakley 2020-05-10 11:16:50 +01:00 committed by Junio C Hamano
parent b994622632
commit 82e46d6b83
2 changed files with 111 additions and 2 deletions

View File

@ -3214,6 +3214,42 @@ def hasBranchPrefix(self, path):
print('Ignoring file outside of prefix: {0}'.format(path))
return hasPrefix
def findShadowedFiles(self, files, change):
# Perforce allows you commit files and directories with the same name,
# so you could have files //depot/foo and //depot/foo/bar both checked
# in. A p4 sync of a repository in this state fails. Deleting one of
# the files recovers the repository.
#
# Git will not allow the broken state to exist and only the most recent
# of the conflicting names is left in the repository. When one of the
# conflicting files is deleted we need to re-add the other one to make
# sure the git repository recovers in the same way as perforce.
deleted = [f for f in files if f['action'] in self.delete_actions]
to_check = set()
for f in deleted:
path = decode_path(f['path'])
to_check.add(path + '/...')
while True:
path = path.rsplit("/", 1)[0]
if path == "/" or path in to_check:
break
to_check.add(path)
to_check = ['%s@%s' % (wildcard_encode(p), change) for p in to_check
if self.hasBranchPrefix(p)]
if to_check:
stat_result = p4CmdList(["-x", "-", "fstat", "-T",
"depotFile,headAction,headRev,headType"], stdin=to_check)
for record in stat_result:
if record['code'] != 'stat':
continue
if record['headAction'] in self.delete_actions:
continue
files.append({
'action': 'add',
'path': record['depotFile'],
'rev': record['headRev'],
'type': record['headType']})
def commit(self, details, files, branch, parent = "", allow_empty=False):
epoch = details["time"]
author = details["user"]
@ -3222,11 +3258,14 @@ def commit(self, details, files, branch, parent = "", allow_empty=False):
if self.verbose:
print('commit into {0}'.format(branch))
files = [f for f in files
if self.hasBranchPrefix(decode_path(f['path']))]
self.findShadowedFiles(files, details['change'])
if self.clientSpecDirs:
self.clientSpecDirs.update_client_spec_path_cache(files)
files = [f for (f, path) in ((f, decode_path(f['path'])) for f in files)
if self.inClientSpec(path) and self.hasBranchPrefix(path)]
files = [f for f in files if self.inClientSpec(decode_path(f['path']))]
if gitConfigBool('git-p4.keepEmptyCommits'):
allow_empty = True

70
t/t9834-git-p4-file-dir-bug.sh Executable file
View File

@ -0,0 +1,70 @@
#!/bin/sh
test_description='git p4 directory/file bug handling
This test creates files and directories with the same name in perforce and
checks that git-p4 recovers from the error at the same time as the perforce
repository.'
. ./lib-git-p4.sh
test_expect_success 'start p4d' '
start_p4d &&
test_might_fail p4 configure set submit.collision.check=0
'
test_expect_success 'init depot' '
(
cd "$cli" &&
touch add_file_add_dir_del_file add_file_add_dir_del_dir &&
p4 add add_file_add_dir_del_file add_file_add_dir_del_dir &&
mkdir add_dir_add_file_del_file add_dir_add_file_del_dir &&
touch add_dir_add_file_del_file/file add_dir_add_file_del_dir/file &&
p4 add add_dir_add_file_del_file/file add_dir_add_file_del_dir/file &&
p4 submit -d "add initial" &&
rm -f add_file_add_dir_del_file add_file_add_dir_del_dir &&
mkdir add_file_add_dir_del_file add_file_add_dir_del_dir &&
touch add_file_add_dir_del_file/file add_file_add_dir_del_dir/file &&
p4 add add_file_add_dir_del_file/file add_file_add_dir_del_dir/file &&
rm -rf add_dir_add_file_del_file add_dir_add_file_del_dir &&
touch add_dir_add_file_del_file add_dir_add_file_del_dir &&
p4 add add_dir_add_file_del_file add_dir_add_file_del_dir &&
p4 submit -d "add conflicting" &&
p4 delete -k add_file_add_dir_del_file &&
p4 delete -k add_file_add_dir_del_dir/file &&
p4 delete -k add_dir_add_file_del_file &&
p4 delete -k add_dir_add_file_del_dir/file &&
p4 submit -d "delete conflicting" &&
p4 delete -k "add_file_add_dir_del_file/file" &&
p4 delete -k "add_file_add_dir_del_dir" &&
p4 delete -k "add_dir_add_file_del_file/file" &&
p4 delete -k "add_dir_add_file_del_dir" &&
p4 submit -d "delete remaining"
)
'
test_expect_success 'clone with git-p4' '
git p4 clone --dest="$git" //depot/@1,3
'
test_expect_success 'check contents' '
test_path_is_dir "$git/add_file_add_dir_del_file" &&
test_path_is_file "$git/add_file_add_dir_del_dir" &&
test_path_is_dir "$git/add_dir_add_file_del_file" &&
test_path_is_file "$git/add_dir_add_file_del_dir"
'
test_expect_success 'rebase and check empty' '
git -C "$git" p4 rebase &&
test_path_is_missing "$git/add_file_add_dir_del_file" &&
test_path_is_missing "$git/add_file_add_dir_del_dir" &&
test_path_is_missing "$git/add_dir_add_file_del_file" &&
test_path_is_missing "$git/add_dir_add_file_del_dir"
'
test_done