Merge branch 'ld/git-p4-expanded-keywords'
authorJunio C Hamano <gitster@pobox.com>
Thu, 23 Feb 2012 21:30:31 +0000 (13:30 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 23 Feb 2012 21:30:31 +0000 (13:30 -0800)
* 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

1  2 
contrib/fast-import/git-p4
index d2fd265b1c351b942e8ed3e1aa3f95866b6d965d,c8b6c8abe47ec1c1b8a71ebc78db2d76aa43c444..053955349a92207b9f67aec73aa54143c2204129
@@@ -10,7 -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
  
@@@ -38,7 -38,7 +38,7 @@@ def p4_build_cmd(cmd)
  
      host = gitConfig("git-p4.host")
      if len(host) > 0:
 -        real_cmd += ["-h", host]
 +        real_cmd += ["-H", host]
  
      client = gitConfig("git-p4.client")
      if len(client) > 0:
@@@ -186,6 -186,47 +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
@@@ -753,6 -794,29 +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()
          filesToDelete = set()
          editedFiles = set()
          filesToChangeExecBit = {}
          for line in diff:
              diff = parseDiffTreeEntry(line)
              modifier = diff['status']
          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":
@@@ -1585,15 -1686,12 +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))