git-ident-filter.py 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. #!/usr/bin/env python
  2. #ident "@(#)$Format:LocalFoodAI:app.py:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$"
  3. import sys
  4. import os
  5. import subprocess
  6. import re
  7. from datetime import datetime
  8. # Force LF-only line endings on Windows for stdin and stdout to prevent automatic CRLF translation
  9. if hasattr(sys.stdin, 'reconfigure'):
  10. sys.stdin.reconfigure(newline='\n')
  11. if hasattr(sys.stdout, 'reconfigure'):
  12. sys.stdout.reconfigure(newline='\n')
  13. # Detect execution mode (clean or smudge) passed by Git
  14. mode = sys.argv[1] if len(sys.argv) > 1 else "smudge"
  15. def get_git_info(file_path):
  16. """Retrieves commit metadata for the specific file using git log, or falls back to system context."""
  17. try:
  18. # 1. Query git log for the last commit details of the specific file
  19. cmd = [
  20. "git", "log", "-1",
  21. "--date=format:%Y/%m/%d %H:%M:%S",
  22. "--format=%an|%ae|%ad|%cn|%ce|%cd|%H|%D|%N",
  23. "--", file_path
  24. ]
  25. out = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode('utf-8', errors='ignore').strip()
  26. if out:
  27. parts = out.split('|')
  28. if len(parts) == 9:
  29. return parts
  30. except Exception:
  31. pass
  32. # 2. Fallback: Query local Git configuration if file is not committed yet
  33. try:
  34. author_name = subprocess.check_output(["git", "config", "user.name"], stderr=subprocess.DEVNULL).decode('utf-8', errors='ignore').strip() or "system"
  35. author_email = subprocess.check_output(["git", "config", "user.email"], stderr=subprocess.DEVNULL).decode('utf-8', errors='ignore').strip() or "system@mail.com"
  36. except Exception:
  37. author_name = "system"
  38. author_email = "system@mail.com"
  39. now_str = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
  40. return [author_name, author_email, now_str, author_name, author_email, now_str, "Not Committed Yet", "local", "none"]
  41. if mode == "clean":
  42. # CLEAN Mode: Replaces any smudged $Format:...$ tag back to the standard neutral git representation
  43. content = sys.stdin.read()
  44. # Non-greedy substitution to restore standard placeholder format for Git storage.
  45. # We construct the search pattern and replacement dynamically to avoid matching our own code.
  46. pattern = r'\$Format' + r':([^%:\r\n]+):([^%:\r\n]+):[^\r\n$]*?\$'
  47. cleaned = re.sub(pattern, r'$Format:\1:\2:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$', content)
  48. sys.stdout.write(cleaned)
  49. else:
  50. # SMUDGE Mode: Dynamically injects actual project, file path, and commit metadata into the file
  51. try:
  52. # Get absolute path of repository to find project directory name
  53. toplevel = subprocess.check_output(["git", "rev-parse", "--show-toplevel"], stderr=subprocess.DEVNULL).decode().strip()
  54. project_name = os.path.basename(toplevel)
  55. # Get the relative path of the file being smudged
  56. file_name = sys.argv[2] if len(sys.argv) > 2 else "unknown_file"
  57. file_name = file_name.replace('\\', '/')
  58. # Read the file content sent by Git on stdin
  59. content = sys.stdin.read()
  60. # Query git log metadata or local fallbacks
  61. info = get_git_info(file_name)
  62. # Format replacement string using dynamic project and file name
  63. replacement = f"$Format" + f":{project_name}:{file_name}:{info[0]}:{info[1]}:{info[2]}:{info[3]}:{info[4]}:{info[5]}:{info[6]}:{info[7]}:{info[8]}$"
  64. # Regex replacement targeting the dynamic format placeholders
  65. pattern = r'\$Format' + r':([^%:\r\n]+):([^%:\r\n]+):%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N\$'
  66. smudged = re.sub(pattern, replacement, content)
  67. sys.stdout.write(smudged)
  68. except Exception:
  69. # Security fallback: If executed outside Git repo, write stream unchanged
  70. sys.stdout.write(sys.stdin.read())