Skip to content

Commit f4507ce

Browse files
committed
Merge branch 'ao/p4-d-f-conflict-recover'
"git p4" learned to recover from a (broken) state where a directory and a file are recorded at the same path in the Perforce repository the same way as their clients do. * ao/p4-d-f-conflict-recover: git-p4: recover from inconsistent perforce history
2 parents a2a0942 + 82e46d6 commit f4507ce

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

git-p4.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3214,6 +3214,42 @@ def hasBranchPrefix(self, path):
32143214
print('Ignoring file outside of prefix: {0}'.format(path))
32153215
return hasPrefix
32163216

3217+
def findShadowedFiles(self, files, change):
3218+
# Perforce allows you commit files and directories with the same name,
3219+
# so you could have files //depot/foo and //depot/foo/bar both checked
3220+
# in. A p4 sync of a repository in this state fails. Deleting one of
3221+
# the files recovers the repository.
3222+
#
3223+
# Git will not allow the broken state to exist and only the most recent
3224+
# of the conflicting names is left in the repository. When one of the
3225+
# conflicting files is deleted we need to re-add the other one to make
3226+
# sure the git repository recovers in the same way as perforce.
3227+
deleted = [f for f in files if f['action'] in self.delete_actions]
3228+
to_check = set()
3229+
for f in deleted:
3230+
path = decode_path(f['path'])
3231+
to_check.add(path + '/...')
3232+
while True:
3233+
path = path.rsplit("/", 1)[0]
3234+
if path == "/" or path in to_check:
3235+
break
3236+
to_check.add(path)
3237+
to_check = ['%s@%s' % (wildcard_encode(p), change) for p in to_check
3238+
if self.hasBranchPrefix(p)]
3239+
if to_check:
3240+
stat_result = p4CmdList(["-x", "-", "fstat", "-T",
3241+
"depotFile,headAction,headRev,headType"], stdin=to_check)
3242+
for record in stat_result:
3243+
if record['code'] != 'stat':
3244+
continue
3245+
if record['headAction'] in self.delete_actions:
3246+
continue
3247+
files.append({
3248+
'action': 'add',
3249+
'path': record['depotFile'],
3250+
'rev': record['headRev'],
3251+
'type': record['headType']})
3252+
32173253
def commit(self, details, files, branch, parent = "", allow_empty=False):
32183254
epoch = details["time"]
32193255
author = details["user"]
@@ -3222,11 +3258,14 @@ def commit(self, details, files, branch, parent = "", allow_empty=False):
32223258
if self.verbose:
32233259
print('commit into {0}'.format(branch))
32243260

3261+
files = [f for f in files
3262+
if self.hasBranchPrefix(decode_path(f['path']))]
3263+
self.findShadowedFiles(files, details['change'])
3264+
32253265
if self.clientSpecDirs:
32263266
self.clientSpecDirs.update_client_spec_path_cache(files)
32273267

3228-
files = [f for (f, path) in ((f, decode_path(f['path'])) for f in files)
3229-
if self.inClientSpec(path) and self.hasBranchPrefix(path)]
3268+
files = [f for f in files if self.inClientSpec(decode_path(f['path']))]
32303269

32313270
if gitConfigBool('git-p4.keepEmptyCommits'):
32323271
allow_empty = True

t/t9834-git-p4-file-dir-bug.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/sh
2+
3+
test_description='git p4 directory/file bug handling
4+
5+
This test creates files and directories with the same name in perforce and
6+
checks that git-p4 recovers from the error at the same time as the perforce
7+
repository.'
8+
9+
. ./lib-git-p4.sh
10+
11+
test_expect_success 'start p4d' '
12+
start_p4d &&
13+
test_might_fail p4 configure set submit.collision.check=0
14+
'
15+
16+
test_expect_success 'init depot' '
17+
(
18+
cd "$cli" &&
19+
20+
touch add_file_add_dir_del_file add_file_add_dir_del_dir &&
21+
p4 add add_file_add_dir_del_file add_file_add_dir_del_dir &&
22+
mkdir add_dir_add_file_del_file add_dir_add_file_del_dir &&
23+
touch add_dir_add_file_del_file/file add_dir_add_file_del_dir/file &&
24+
p4 add add_dir_add_file_del_file/file add_dir_add_file_del_dir/file &&
25+
p4 submit -d "add initial" &&
26+
27+
rm -f add_file_add_dir_del_file add_file_add_dir_del_dir &&
28+
mkdir add_file_add_dir_del_file add_file_add_dir_del_dir &&
29+
touch add_file_add_dir_del_file/file add_file_add_dir_del_dir/file &&
30+
p4 add add_file_add_dir_del_file/file add_file_add_dir_del_dir/file &&
31+
rm -rf add_dir_add_file_del_file add_dir_add_file_del_dir &&
32+
touch add_dir_add_file_del_file add_dir_add_file_del_dir &&
33+
p4 add add_dir_add_file_del_file add_dir_add_file_del_dir &&
34+
p4 submit -d "add conflicting" &&
35+
36+
p4 delete -k add_file_add_dir_del_file &&
37+
p4 delete -k add_file_add_dir_del_dir/file &&
38+
p4 delete -k add_dir_add_file_del_file &&
39+
p4 delete -k add_dir_add_file_del_dir/file &&
40+
p4 submit -d "delete conflicting" &&
41+
42+
p4 delete -k "add_file_add_dir_del_file/file" &&
43+
p4 delete -k "add_file_add_dir_del_dir" &&
44+
p4 delete -k "add_dir_add_file_del_file/file" &&
45+
p4 delete -k "add_dir_add_file_del_dir" &&
46+
p4 submit -d "delete remaining"
47+
)
48+
'
49+
50+
test_expect_success 'clone with git-p4' '
51+
git p4 clone --dest="$git" //depot/@1,3
52+
'
53+
54+
test_expect_success 'check contents' '
55+
test_path_is_dir "$git/add_file_add_dir_del_file" &&
56+
test_path_is_file "$git/add_file_add_dir_del_dir" &&
57+
test_path_is_dir "$git/add_dir_add_file_del_file" &&
58+
test_path_is_file "$git/add_dir_add_file_del_dir"
59+
'
60+
61+
test_expect_success 'rebase and check empty' '
62+
git -C "$git" p4 rebase &&
63+
64+
test_path_is_missing "$git/add_file_add_dir_del_file" &&
65+
test_path_is_missing "$git/add_file_add_dir_del_dir" &&
66+
test_path_is_missing "$git/add_dir_add_file_del_file" &&
67+
test_path_is_missing "$git/add_dir_add_file_del_dir"
68+
'
69+
70+
test_done

0 commit comments

Comments
 (0)