@matheusbsbs / matheus-bagliano / commits / 82e46d6b83
Fork 0

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>

Andrew Oakley committed May 10, 2020 at 11:16 UTC 82e46d6b837e96de5ee01f0298b600519aef1bba
2 files changed +111 -2
git-p4.py
+41 -2
@@ -3214,6 +3214,42 @@ class P4Sync(Command, P4UserMap):
3214 print('Ignoring file outside of prefix: {0}'.format(path))
3215 return hasPrefix
3216
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 +
3253 def commit(self, details, files, branch, parent = "", allow_empty=False):
3254 epoch = details["time"]
3255 author = details["user"]
@@ -3222,11 +3258,14 @@ class P4Sync(Command, P4UserMap):
3258 if self.verbose:
3259 print('commit into {0}'.format(branch))
3260
3261 + files = [f for f in files
3262 + if self.hasBranchPrefix(decode_path(f['path']))]
3263 + self.findShadowedFiles(files, details['change'])
3264 +
3265 if self.clientSpecDirs:
3266 self.clientSpecDirs.update_client_spec_path_cache(files)
3267
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']))]
3269
3270 if gitConfigBool('git-p4.keepEmptyCommits'):
3271 allow_empty = True
t/t9834-git-p4-file-dir-bug.sh new
+70
@@ -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