Skip to content

Commit

Permalink
git-p4: add initial support for RCS keywords
Browse files Browse the repository at this point in the history
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 <pw@padd.com>
Signed-off-by: Luke Diamand <luke@diamand.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Luke Diamand authored and Junio C Hamano committed Feb 23, 2012
1 parent 1276686 commit 60df071
Show file tree
Hide file tree
Showing 3 changed files with 501 additions and 10 deletions.
5 changes: 5 additions & 0 deletions Documentation/git-p4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,11 @@ git-p4.skipUserNameCheck::
user map, 'git p4' exits. This option can be used to force
submission regardless.

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

IMPLEMENTATION DETAILS
----------------------
Expand Down
118 changes: 108 additions & 10 deletions contrib/fast-import/git-p4
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import optparse, sys, os, marshal, subprocess, shelve
import tempfile, getopt, os.path, time, platform
import re
import re, shutil

verbose = False

Expand Down Expand Up @@ -186,6 +186,47 @@ def split_p4_type(p4type):
mods = s[1]
return (base, mods)

#
# return the raw p4 type of a file (text, text+ko, etc)
#
def p4_type(file):
results = p4CmdList(["fstat", "-T", "headType", file])
return results[0]['headType']

#
# Given a type base and modifier, return a regexp matching
# the keywords that can be expanded in the file
#
def p4_keywords_regexp_for_type(base, type_mods):
if base in ("text", "unicode", "binary"):
kwords = None
if "ko" in type_mods:
kwords = 'Id|Header'
elif "k" in type_mods:
kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
else:
return None
pattern = r"""
\$ # Starts with a dollar, followed by...
(%s) # one of the keywords, followed by...
(:[^$]+)? # possibly an old expansion, followed by...
\$ # another dollar
""" % kwords
return pattern
else:
return None

#
# Given a file, return a regexp matching the possible
# RCS keywords that will be expanded, or None for files
# with kw expansion turned off.
#
def p4_keywords_regexp_for_file(file):
if not os.path.exists(file):
return None
else:
(type_base, type_mods) = split_p4_type(p4_type(file))
return p4_keywords_regexp_for_type(type_base, type_mods)

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

return result

def patchRCSKeywords(self, file, pattern):
# Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
(handle, outFileName) = tempfile.mkstemp(dir='.')
try:
outFile = os.fdopen(handle, "w+")
inFile = open(file, "r")
regexp = re.compile(pattern, re.VERBOSE)
for line in inFile.readlines():
line = regexp.sub(r'$\1$', line)
outFile.write(line)
inFile.close()
outFile.close()
# Forcibly overwrite the original file
os.unlink(file)
shutil.move(outFileName, file)
except:
# cleanup our temporary file
os.unlink(outFileName)
print "Failed to strip RCS keywords in %s" % file
raise

print "Patched up RCS keywords in %s" % file

def p4UserForCommit(self,id):
# Return the tuple (perforce user,git email) for a given git commit id
self.getUserMapFromPerforceServer()
Expand Down Expand Up @@ -918,6 +982,7 @@ class P4Submit(Command, P4UserMap):
filesToDelete = set()
editedFiles = set()
filesToChangeExecBit = {}

for line in diff:
diff = parseDiffTreeEntry(line)
modifier = diff['status']
Expand Down Expand Up @@ -964,9 +1029,45 @@ class P4Submit(Command, P4UserMap):
patchcmd = diffcmd + " | git apply "
tryPatchCmd = patchcmd + "--check -"
applyPatchCmd = patchcmd + "--check --apply -"
patch_succeeded = True

if os.system(tryPatchCmd) != 0:
fixed_rcs_keywords = False
patch_succeeded = False
print "Unfortunately applying the change failed!"

# Patch failed, maybe it's just RCS keyword woes. Look through
# the patch to see if that's possible.
if gitConfig("git-p4.attemptRCSCleanup","--bool") == "true":
file = None
pattern = None
kwfiles = {}
for file in editedFiles | filesToDelete:
# did this file's delta contain RCS keywords?
pattern = p4_keywords_regexp_for_file(file)

if pattern:
# this file is a possibility...look for RCS keywords.
regexp = re.compile(pattern, re.VERBOSE)
for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
if regexp.search(line):
if verbose:
print "got keyword match on %s in %s in %s" % (pattern, line, file)
kwfiles[file] = pattern
break

for file in kwfiles:
if verbose:
print "zapping %s with %s" % (line,pattern)
self.patchRCSKeywords(file, kwfiles[file])
fixed_rcs_keywords = True

if fixed_rcs_keywords:
print "Retrying the patch with RCS keywords cleaned up"
if os.system(tryPatchCmd) == 0:
patch_succeeded = True

if not patch_succeeded:
print "What do you want to do?"
response = "x"
while response != "s" and response != "a" and response != "w":
Expand Down Expand Up @@ -1585,15 +1686,12 @@ class P4Sync(Command, P4UserMap):

# Note that we do not try to de-mangle keywords on utf16 files,
# even though in theory somebody may want that.
if type_base in ("text", "unicode", "binary"):
if "ko" in type_mods:
text = ''.join(contents)
text = re.sub(r'\$(Id|Header):[^$]*\$', r'$\1$', text)
contents = [ text ]
elif "k" in type_mods:
text = ''.join(contents)
text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', text)
contents = [ text ]
pattern = p4_keywords_regexp_for_type(type_base, type_mods)
if pattern:
regexp = re.compile(pattern, re.VERBOSE)
text = ''.join(contents)
text = regexp.sub(r'$\1$', text)
contents = [ text ]

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

Expand Down
Loading

0 comments on commit 60df071

Please sign in to comment.