git.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. #!/usr/bin/env python
  2. """Git helper functions.
  3. Everything here should be project agnostic: it shouldn't rely on project's
  4. structure, or make assumptions about the passed arguments or calls' outcomes.
  5. """
  6. import os
  7. import subprocess
  8. def is_repo_root(path):
  9. path_exists = os.path.exists(path)
  10. if not path_exists:
  11. return False
  12. git_folder_path = os.path.join(path, '.git')
  13. git_folder_exists = os.path.exists(git_folder_path)
  14. return git_folder_exists
  15. def get_repo_root(path):
  16. """Finds a closest ancestor folder which is a repo root."""
  17. norm_path = os.path.normpath(path)
  18. norm_path_exists = os.path.exists(norm_path)
  19. if not norm_path_exists:
  20. return None
  21. if is_repo_root(norm_path):
  22. return norm_path
  23. parent_path = os.path.dirname(norm_path)
  24. # Check if we're in the root folder already.
  25. if parent_path == norm_path:
  26. return None
  27. return get_repo_root(parent_path)
  28. def am(repo, patch_data, threeway=False, directory=None, exclude=None,
  29. committer_name=None, committer_email=None):
  30. args = []
  31. if threeway:
  32. args += ['--3way']
  33. if directory is not None:
  34. args += ['--directory', directory]
  35. if exclude is not None:
  36. for path_pattern in exclude:
  37. args += ['--exclude', path_pattern]
  38. root_args = ['-C', repo]
  39. if committer_name is not None:
  40. root_args += ['-c', 'user.name=' + committer_name]
  41. if committer_email is not None:
  42. root_args += ['-c', 'user.email=' + committer_email]
  43. root_args += ['-c', 'commit.gpgsign=false']
  44. command = ['git'] + root_args + ['am'] + args
  45. proc = subprocess.Popen(
  46. command,
  47. stdin=subprocess.PIPE)
  48. proc.communicate(patch_data)
  49. if proc.returncode != 0:
  50. raise RuntimeError("Command {} returned {}".format(command,
  51. proc.returncode))
  52. def apply_patch(repo, patch_path, directory=None, index=False, reverse=False):
  53. args = ['git', '-C', repo, 'apply',
  54. '--ignore-space-change',
  55. '--ignore-whitespace',
  56. '--whitespace', 'fix'
  57. ]
  58. if directory:
  59. args += ['--directory', directory]
  60. if index:
  61. args += ['--index']
  62. if reverse:
  63. args += ['--reverse']
  64. args += ['--', patch_path]
  65. return_code = subprocess.call(args)
  66. applied_successfully = (return_code == 0)
  67. return applied_successfully
  68. def get_patch(repo, commit_hash):
  69. args = ['git', '-C', repo, 'diff-tree',
  70. '-p',
  71. commit_hash,
  72. '--' # Explicitly tell Git `commit_hash` is a revision, not a path.
  73. ]
  74. return subprocess.check_output(args)
  75. def get_head_commit(repo):
  76. args = ['git', '-C', repo, 'rev-parse', 'HEAD']
  77. return subprocess.check_output(args).strip()
  78. def update_ref(repo, ref, newvalue):
  79. args = ['git', '-C', repo, 'update-ref', ref, newvalue]
  80. return subprocess.check_call(args)
  81. def reset(repo):
  82. args = ['git', '-C', repo, 'reset']
  83. subprocess.check_call(args)
  84. def commit(repo, author, message):
  85. """ Commit whatever in the index is now."""
  86. # Let's setup committer info so git won't complain about it being missing.
  87. # TODO: Is there a better way to set committer's name and email?
  88. env = os.environ.copy()
  89. env['GIT_COMMITTER_NAME'] = 'Anonymous Committer'
  90. env['GIT_COMMITTER_EMAIL'] = '[email protected]'
  91. args = ['git', '-C', repo, 'commit',
  92. '--author', author,
  93. '--message', message
  94. ]
  95. return_code = subprocess.call(args, env=env)
  96. committed_successfully = (return_code == 0)
  97. return committed_successfully