Skip to content

Commit

Permalink
Merge branch 'ld/git-p4-expanded-keywords'
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Junio C Hamano committed Feb 23, 2012
2 parents fd1727f + 60df071 commit d065f68
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 d065f68

Please sign in to comment.