Skip to content

Commit 60df071

Browse files
luked99gitster
authored andcommitted
git-p4: add initial support for RCS keywords
RCS keywords cause problems for git-p4 as perforce always expands them (if +k is set) and so when applying the patch, git reports that the files have been modified by both sides, when in fact they haven't. This change means that when git-p4 detects a problem applying a patch, it will check to see if keyword expansion could be the culprit. If it is, it strips the keywords in the p4 repository so that they match what git is expecting. It then has another go at applying the patch. This behaviour is enabled with a new git-p4 configuration option and is off by default. Acked-by: Pete Wyckoff <[email protected]> Signed-off-by: Luke Diamand <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1276686 commit 60df071

File tree

3 files changed

+501
-10
lines changed

3 files changed

+501
-10
lines changed

Documentation/git-p4.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,11 @@ git-p4.skipUserNameCheck::
483483
user map, 'git p4' exits. This option can be used to force
484484
submission regardless.
485485

486+
git-p4.attemptRCSCleanup:
487+
If enabled, 'git p4 submit' will attempt to cleanup RCS keywords
488+
($Header$, etc). These would otherwise cause merge conflicts and prevent
489+
the submit going ahead. This option should be considered experimental at
490+
present.
486491

487492
IMPLEMENTATION DETAILS
488493
----------------------

contrib/fast-import/git-p4

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import optparse, sys, os, marshal, subprocess, shelve
1212
import tempfile, getopt, os.path, time, platform
13-
import re
13+
import re, shutil
1414

1515
verbose = False
1616

@@ -186,6 +186,47 @@ def split_p4_type(p4type):
186186
mods = s[1]
187187
return (base, mods)
188188

189+
#
190+
# return the raw p4 type of a file (text, text+ko, etc)
191+
#
192+
def p4_type(file):
193+
results = p4CmdList(["fstat", "-T", "headType", file])
194+
return results[0]['headType']
195+
196+
#
197+
# Given a type base and modifier, return a regexp matching
198+
# the keywords that can be expanded in the file
199+
#
200+
def p4_keywords_regexp_for_type(base, type_mods):
201+
if base in ("text", "unicode", "binary"):
202+
kwords = None
203+
if "ko" in type_mods:
204+
kwords = 'Id|Header'
205+
elif "k" in type_mods:
206+
kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
207+
else:
208+
return None
209+
pattern = r"""
210+
\$ # Starts with a dollar, followed by...
211+
(%s) # one of the keywords, followed by...
212+
(:[^$]+)? # possibly an old expansion, followed by...
213+
\$ # another dollar
214+
""" % kwords
215+
return pattern
216+
else:
217+
return None
218+
219+
#
220+
# Given a file, return a regexp matching the possible
221+
# RCS keywords that will be expanded, or None for files
222+
# with kw expansion turned off.
223+
#
224+
def p4_keywords_regexp_for_file(file):
225+
if not os.path.exists(file):
226+
return None
227+
else:
228+
(type_base, type_mods) = split_p4_type(p4_type(file))
229+
return p4_keywords_regexp_for_type(type_base, type_mods)
189230

190231
def setP4ExecBit(file, mode):
191232
# Reopens an already open file and changes the execute bit to match
@@ -753,6 +794,29 @@ class P4Submit(Command, P4UserMap):
753794

754795
return result
755796

797+
def patchRCSKeywords(self, file, pattern):
798+
# Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
799+
(handle, outFileName) = tempfile.mkstemp(dir='.')
800+
try:
801+
outFile = os.fdopen(handle, "w+")
802+
inFile = open(file, "r")
803+
regexp = re.compile(pattern, re.VERBOSE)
804+
for line in inFile.readlines():
805+
line = regexp.sub(r'$\1$', line)
806+
outFile.write(line)
807+
inFile.close()
808+
outFile.close()
809+
# Forcibly overwrite the original file
810+
os.unlink(file)
811+
shutil.move(outFileName, file)
812+
except:
813+
# cleanup our temporary file
814+
os.unlink(outFileName)
815+
print "Failed to strip RCS keywords in %s" % file
816+
raise
817+
818+
print "Patched up RCS keywords in %s" % file
819+
756820
def p4UserForCommit(self,id):
757821
# Return the tuple (perforce user,git email) for a given git commit id
758822
self.getUserMapFromPerforceServer()
@@ -918,6 +982,7 @@ class P4Submit(Command, P4UserMap):
918982
filesToDelete = set()
919983
editedFiles = set()
920984
filesToChangeExecBit = {}
985+
921986
for line in diff:
922987
diff = parseDiffTreeEntry(line)
923988
modifier = diff['status']
@@ -964,9 +1029,45 @@ class P4Submit(Command, P4UserMap):
9641029
patchcmd = diffcmd + " | git apply "
9651030
tryPatchCmd = patchcmd + "--check -"
9661031
applyPatchCmd = patchcmd + "--check --apply -"
1032+
patch_succeeded = True
9671033

9681034
if os.system(tryPatchCmd) != 0:
1035+
fixed_rcs_keywords = False
1036+
patch_succeeded = False
9691037
print "Unfortunately applying the change failed!"
1038+
1039+
# Patch failed, maybe it's just RCS keyword woes. Look through
1040+
# the patch to see if that's possible.
1041+
if gitConfig("git-p4.attemptRCSCleanup","--bool") == "true":
1042+
file = None
1043+
pattern = None
1044+
kwfiles = {}
1045+
for file in editedFiles | filesToDelete:
1046+
# did this file's delta contain RCS keywords?
1047+
pattern = p4_keywords_regexp_for_file(file)
1048+
1049+
if pattern:
1050+
# this file is a possibility...look for RCS keywords.
1051+
regexp = re.compile(pattern, re.VERBOSE)
1052+
for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
1053+
if regexp.search(line):
1054+
if verbose:
1055+
print "got keyword match on %s in %s in %s" % (pattern, line, file)
1056+
kwfiles[file] = pattern
1057+
break
1058+
1059+
for file in kwfiles:
1060+
if verbose:
1061+
print "zapping %s with %s" % (line,pattern)
1062+
self.patchRCSKeywords(file, kwfiles[file])
1063+
fixed_rcs_keywords = True
1064+
1065+
if fixed_rcs_keywords:
1066+
print "Retrying the patch with RCS keywords cleaned up"
1067+
if os.system(tryPatchCmd) == 0:
1068+
patch_succeeded = True
1069+
1070+
if not patch_succeeded:
9701071
print "What do you want to do?"
9711072
response = "x"
9721073
while response != "s" and response != "a" and response != "w":
@@ -1585,15 +1686,12 @@ class P4Sync(Command, P4UserMap):
15851686

15861687
# Note that we do not try to de-mangle keywords on utf16 files,
15871688
# even though in theory somebody may want that.
1588-
if type_base in ("text", "unicode", "binary"):
1589-
if "ko" in type_mods:
1590-
text = ''.join(contents)
1591-
text = re.sub(r'\$(Id|Header):[^$]*\$', r'$\1$', text)
1592-
contents = [ text ]
1593-
elif "k" in type_mods:
1594-
text = ''.join(contents)
1595-
text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', text)
1596-
contents = [ text ]
1689+
pattern = p4_keywords_regexp_for_type(type_base, type_mods)
1690+
if pattern:
1691+
regexp = re.compile(pattern, re.VERBOSE)
1692+
text = ''.join(contents)
1693+
text = regexp.sub(r'$\1$', text)
1694+
contents = [ text ]
15971695

15981696
self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
15991697

0 commit comments

Comments
 (0)