Skip to content

Commit d065f68

Browse files
committed
Merge branch 'ld/git-p4-expanded-keywords'
* ld/git-p4-expanded-keywords: : Teach git-p4 to unexpand $RCS$-like keywords that are embedded in : tracked contents in order to reduce unnecessary merge conflicts. git-p4: add initial support for RCS keywords
2 parents fd1727f + 60df071 commit d065f68

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)